遇到一个上传文件的问题,老大说使用http太慢了,因为http包含大量的请求头,刚好项目本身又集成了websocket,想着就用websocket来做文件上传。

相关技术

  • springboot
  • websocket
  • jdk1.8

创建springboot项目并集成websocket

先是创建一个spring boot项目

我们勾选了三个依赖

分别是 Lombok,web,websocket

这是当前完整的依赖

  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  3. xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
  4. <modelVersion>4.0.0</modelVersion>
  5. <parent>
  6. <groupId>org.springframework.boot</groupId>
  7. <artifactId>spring-boot-starter-parent</artifactId>
  8. <version>2.1.2.RELEASE</version>
  9. <relativePath/> <!-- lookup parent from repository -->
  10. </parent>
  11. <groupId>com.ccsert</groupId>
  12. <artifactId>websocketupload</artifactId>
  13. <version>0.0.1-SNAPSHOT</version>
  14. <name>websocketupload</name>
  15. <description>websocket Upload project</description>
  16. <properties>
  17. <java.version>1.8</java.version>
  18. </properties>
  19. <dependencies>
  20. <dependency>
  21. <groupId>org.springframework.boot</groupId>
  22. <artifactId>spring-boot-starter-web</artifactId>
  23. </dependency>
  24. <dependency>
  25. <groupId>org.springframework.boot</groupId>
  26. <artifactId>spring-boot-starter-websocket</artifactId>
  27. </dependency>
  28. <dependency>
  29. <groupId>org.projectlombok</groupId>
  30. <artifactId>lombok</artifactId>
  31. <optional>true</optional>
  32. </dependency>
  33. <dependency>
  34. <groupId>org.springframework.boot</groupId>
  35. <artifactId>spring-boot-starter-test</artifactId>
  36. <scope>test</scope>
  37. </dependency>
  38. <dependency>
  39. <groupId>com.alibaba</groupId>
  40. <artifactId>fastjson</artifactId>
  41. <version>1.2.58</version>
  42. </dependency>
  43. </dependencies>
  44. <build>
  45. <plugins>
  46. <plugin>
  47. <groupId>org.springframework.boot</groupId>
  48. <artifactId>spring-boot-maven-plugin</artifactId>
  49. </plugin>
  50. </plugins>
  51. </build>
  52. </project>

websocketupload包下创建config包,然后建一个配置类WebsocketConfig

然后实现ServletContextInitializer接口

代码如下

  1. package com.ccsert.websocketupload.config;
  2. import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
  3. import org.springframework.boot.web.servlet.ServletContextInitializer;
  4. import org.springframework.context.annotation.Bean;
  5. import org.springframework.context.annotation.ComponentScan;
  6. import org.springframework.context.annotation.Configuration;
  7. import org.springframework.web.socket.server.standard.ServerEndpointExporter;
  8. import org.springframework.web.util.WebAppRootListener;
  9. import javax.servlet.ServletContext;
  10. import javax.servlet.ServletException;
  11. /**
  12. * ClassName: WebsocketConfig <br/>
  13. * Description: 开启websocket支持 <br/>
  14. * date: 2020/2/4 10:58<br/>
  15. *
  16. * @author ccsert<br />
  17. * @since JDK 1.8
  18. */
  19. @Configuration
  20. @ComponentScan
  21. @EnableAutoConfiguration
  22. public class WebsocketConfig implements ServletContextInitializer {
  23. @Bean
  24. public ServerEndpointExporter serverEndpointExporter() {
  25. return new ServerEndpointExporter();
  26. }
  27. /**
  28. * 配置websocket文件接受的文件最大容量
  29. * @param servletContext context域对象
  30. * @throws ServletException 抛出异常
  31. */
  32. @Override
  33. public void onStartup(ServletContext servletContext) throws ServletException {
  34. servletContext.addListener(WebAppRootListener.class);
  35. servletContext.setInitParameter("org.apache.tomcat.websocket.textBufferSize","51200000");
  36. servletContext.setInitParameter("org.apache.tomcat.websocket.binaryBufferSize","51200000");
  37. }
  38. }

websocketupload包下创建一个web包然后建立一个WebSocketServer

我们需要实现它的三个方法:OnOpen,OnClose,OnMessage

他们都会自动调用,类似于事件触发,含义分别是,连接建立成功时调用的方法,连接关闭时调用的方法,最后一个是接收客户端发来的消息

其中OnMessage我们后面会使用多种实现

我们先来玩一个例子,讲websocket自然是喜闻乐见聊天室,贴一下WebSocketServer的代码

  1. package com.ccsert.websocketupload.web;
  2. import org.slf4j.Logger;
  3. import org.slf4j.LoggerFactory;
  4. import org.springframework.stereotype.Component;
  5. import javax.websocket.OnClose;
  6. import javax.websocket.OnMessage;
  7. import javax.websocket.OnOpen;
  8. import javax.websocket.Session;
  9. import javax.websocket.server.PathParam;
  10. import javax.websocket.server.ServerEndpoint;
  11. import java.io.IOException;
  12. import java.util.concurrent.CopyOnWriteArraySet;
  13. /**
  14. * ClassName: webSocketServer <br/>
  15. * Description: websocket服务处理类 <br/>
  16. * date: 2020/2/4 11:05<br/>
  17. *
  18. * @author ccsert<br />
  19. * @since JDK 1.8
  20. */
  21. @ServerEndpoint("/websocket/{sid}")
  22. @Component
  23. public class WebSocketServer {
  24. private static final Logger LOG = LoggerFactory.getLogger(WebSocketServer.class);
  25. /**
  26. * 静态变量,用来记录当前在线连接数。应该把它设计成线程安全的。
  27. */
  28. private static int onlineCount = 0;
  29. /**
  30. * concurrent包的线程安全Set,用来存放每个客户端对应的MyWebSocket对象。
  31. */
  32. private static CopyOnWriteArraySet<WebSocketServer> webSocketSet = new CopyOnWriteArraySet<>();
  33. /**
  34. * 与某个客户端的连接会话,需要通过它来给客户端发送数据
  35. */
  36. private Session session;
  37. /**
  38. * 连接建立成功时调用的方法
  39. */
  40. @OnOpen
  41. public void onOpen(Session session, @PathParam("sid") String sid) {
  42. this.session = session;
  43. //加入set中
  44. webSocketSet.add(this);
  45. //在线人数加1
  46. addOnlineCount();
  47. LOG.info(sid + "连接成功" + "----当前在线人数为:" + onlineCount);
  48. }
  49. /**
  50. * 连接关闭时调用的方法
  51. */
  52. @OnClose
  53. public void onClose(@PathParam("sid") String sid) {
  54. //在线人数减1
  55. subOnlineCount();
  56. //从set中删除
  57. webSocketSet.remove(this);
  58. LOG.info(sid + "已关闭连接" + "----剩余在线人数为:" + onlineCount);
  59. }
  60. /**
  61. * 接收客户端发送的消息时调用的方法
  62. *
  63. * @param message 接收的字符串消息
  64. */
  65. @OnMessage
  66. public void onMessage(String message, @PathParam("sid") String sid) {
  67. LOG.info(sid + "发送消息为:" + message);
  68. sendInfo(message, sid);
  69. }
  70. /**
  71. * 服务器主动提推送消息
  72. *
  73. * @param message 消息内容
  74. * @throws IOException io异常抛出
  75. */
  76. public void sendMessage(String message) throws IOException {
  77. this.session.getBasicRemote().sendText(message);
  78. }
  79. /**
  80. * 群发消息功能
  81. *
  82. * @param message 消息内容
  83. * @param sid 房间号
  84. */
  85. public static void sendInfo(String message, @PathParam("sid") String sid) {
  86. LOG.info("推送消息到窗口" + sid + ",推送内容:" + message);
  87. for (WebSocketServer item : webSocketSet) {
  88. try {
  89. //这里可以设定只推送给这个sid的,为null则全部推送
  90. item.sendMessage(message);
  91. } catch (IOException e) {
  92. LOG.error("消息发送失败" + e.getMessage(), e);
  93. return;
  94. }
  95. }
  96. }
  97. /**
  98. * 原子性的++操作
  99. */
  100. public static synchronized void addOnlineCount() {
  101. WebSocketServer.onlineCount++;
  102. }
  103. /**
  104. * 原子性的--操作
  105. */
  106. public static synchronized void subOnlineCount() {
  107. WebSocketServer.onlineCount--;
  108. }
  109. }

这里的代码挺简单没什么好讲的

然后在建立一个html

在static目录下建立一个websocketDemo.html

代码如下

  1. <!DOCTYPE html>
  2. <html lang="en">
  3. <head>
  4. <meta charset="UTF-8">
  5. <title>chat room websocket</title>
  6. <link rel="stylesheet" href="bootstrap.css">
  7. <script src="https://code.jquery.com/jquery-3.4.1.min.js"></script>
  8. </head>
  9. <body class="container" style="width: 60%">
  10. <div class="form-group"></br>
  11. <h5>聊天室</h5>
  12. <textarea id="message_content" class="form-control" readonly="readonly" cols="50"
  13. rows="10"></textarea>
  14. </div>
  15. <div class="form-group">
  16. <label for="in_user_name">⽤户姓名 &nbsp;</label>
  17. <input id="in_user_name" value="" class="form-control"/></br>
  18. <label for="in_user_name">房间名 &nbsp;</label>
  19. <input id="in_room_id" value="" class="form-control">
  20. <button id="user_join" onclick="verificationValue()" class="btn btn-success">加入聊天室</button>
  21. <button id="user_exit" onclick="outRoom()" class="btn btn-warning">离开聊天室</button>
  22. </div>
  23. <div class="form-group">
  24. <label for="in_room_msg">群发消息 &nbsp;</label>
  25. <input id="in_room_msg" value="" class="form-control"/></br>
  26. <button id="user_send_all" onclick="sendInfo()" class="btn btn-info">发送消息</button>
  27. </div>
  28. <script>
  29. var socket;//websocket连接
  30. var webSocketUrl = 'ws://127.0.0.1:8033/websocket/';//websocketi连接地址
  31. var roomId = "";//房间号
  32. var userName = "";//用户名
  33. //创建websocket连接
  34. function createWebSocketConnect(roomId) {
  35. if (!socket) {//避免重复连接
  36. console.log(roomId);
  37. socket = new WebSocket(webSocketUrl + roomId);
  38. socket.onopen = function () {
  39. console.log("websocket已连接");
  40. socket.send(userName + "已经成功加入房间");
  41. };
  42. socket.onmessage = function (e) {
  43. //服务端发送的消息
  44. $("#message_content").append(e.data + '\n');
  45. };
  46. socket.onclose = function () {
  47. socket.send(userName + "已经退出房间");
  48. }
  49. }
  50. }
  51. //验证用户名和房间号是否填写
  52. function verificationValue() {
  53. roomId = $("#in_room_id").val();
  54. userName = $("#in_user_name").val();
  55. if (roomId === "" || userName === "") {
  56. alert("请填写用户名并填写要加入的房间号");
  57. return;
  58. }
  59. createWebSocketConnect(roomId, userName);
  60. }
  61. //群发消息
  62. function sendInfo() {
  63. let msg = $('#in_room_msg').val();
  64. if (socket) {
  65. socket.send(userName + ":" + msg)
  66. }
  67. }
  68. //离开房间
  69. function outRoom() {
  70. if (socket) {
  71. socket.send(userName + "已退出");
  72. socket.close();
  73. $("#message_content").append(userName + "已退出");
  74. }
  75. }
  76. </script>
  77. </body>
  78. </html>

这个也挺简单,这里引用了bootstrap.css样式和jq

然后我们就可以通过这样的一个小demo测试下聊天室的功能了,文末附上代码地址

使用websocket实现文件上传功能

我们仿造刚才的WebSocketServer在写一个websocket类

还是在web包下建立一个类,类名为WebSocketUploadServer

可以将原来WebSocketServer的代码复制过来,然后稍微改造一下,其实我们实现文件上传也可以直接在原来WebSocketServer的代码里直接实现,但是现在为了让代码更清晰一些,我们先将就一下

原本聊天室的情况下,一个房间里是可以有多个客户端连接的,但是文件上传我们是不允许的,假如有多个人在同一个房间,那么消息就会传到每个客户端,因为我们要做分块上传,所以这里控制每个房间只能有一个人

我们把websocketDemo.html也复制一份,改名就叫uploadFileDemo.html

我们原先的房间号是手动输入的,现在,我们保证每次都是不同的房间号所以,这次房间号就用随机数

当然方法有很多种,我只是提供一种简单的实现方式,不同的业务场景当然也需要不同的实现方式。不用太过死板

后台的核心代码主要是接收字节流和json消息,我们将字符串消息格式化成了json消息

下面是接收字符串的OnMessage

  1. /**
  2. * 接收客户端发送的消息时调用的方法
  3. *
  4. * @param message 接收的字符串消息。该消息应当为json字符串
  5. */
  6. @OnMessage
  7. public void onMessage(String message, @PathParam("sid") String sid) {
  8. //前端传过来的消息都是一个json
  9. JSONObject jsonObject = JSON.parseObject(message);
  10. //消息类型
  11. String type = jsonObject.getString("type");
  12. //消息内容
  13. String data = jsonObject.getString("data");
  14. //判断类型是否为文件名
  15. if ("fileName".equals(type)) {
  16. LOG.info("传输文件为:" + data);
  17. //此处的 “.”需要进行转义
  18. /*String[] split = data.split("\\.");*/
  19. try {
  20. Map<String, Object> map = saveFileI.docPath(data);
  21. docUrl = (HashMap) map;
  22. this.sendMessage("ok");
  23. } catch (IOException e) {
  24. e.printStackTrace();
  25. }
  26. }
  27. else if ("fileCount".equals(type)){
  28. LOG.info("传输第"+data+"份");
  29. }
  30. //判断是否结束
  31. else if (endupload.equals(type)) {
  32. LOG.info("===============>传输成功");
  33. //返回一个文件下载地址
  34. String path = (String) docUrl.get("nginxPath");
  35. //返回客户端文件地址
  36. try {
  37. this.sendMessage(path);
  38. } catch (IOException e) {
  39. e.printStackTrace();
  40. }
  41. }
  42. }

然后是接收字节流的

  1. /**
  2. * 该方法用于接收字节流数组
  3. *
  4. * @param message 文件字节流数组
  5. * @param session 会话
  6. */
  7. @OnMessage
  8. public void onMessage(byte[] message, Session session) {
  9. //群发消息
  10. try {
  11. //将流写入文件
  12. saveFileI.saveFileFromBytes(message,docUrl);
  13. //文件写入成功,返回一个ok
  14. this.sendMessage("ok");
  15. } catch (IOException e) {
  16. e.printStackTrace();
  17. }
  18. }

单独看肯定是看不懂的

业务逻辑我们放在了service层进行处理

在websocketupload包下建立service包

然后建立SaveFileI接口,接口里有两个方法,一个是创建文件路径的,一个是将流数据写入文件的

  1. package com.ccsert.websocketupload.service;
  2. import java.util.Map;
  3. /**
  4. * ClassName: saveFileI <br/>
  5. * Description: 保存文件接口 <br/>
  6. * date: 2020/2/4 17:39<br/>
  7. *
  8. * @author ccsert<br />
  9. * @since JDK 1.8
  10. */
  11. public interface SaveFileI {
  12. /**
  13. * 生成文件路径
  14. * @param fileName 接收文件名
  15. * @return 返回文件路径
  16. */
  17. Map<String,Object> docPath(String fileName);
  18. /**
  19. * 将字节流写入文件
  20. * @param b 字节流数组
  21. * @param map 文件路径
  22. * @return 返回是否成功
  23. */
  24. boolean saveFileFromBytes(byte[] b, Map<String, Object> map);
  25. }

具体的实现我直接贴出来

  1. package com.ccsert.websocketupload.service.impl;
  2. import com.ccsert.websocketupload.service.SaveFileI;
  3. import org.springframework.stereotype.Service;
  4. import java.io.File;
  5. import java.io.FileOutputStream;
  6. import java.io.IOException;
  7. import java.text.SimpleDateFormat;
  8. import java.util.Date;
  9. import java.util.HashMap;
  10. import java.util.Map;
  11. /**
  12. * ClassName: SaveFileImpl <br/>
  13. * Description: <br/>
  14. * date: 2020/2/4 19:01<br/>
  15. *
  16. * @author ccsert<br />
  17. * @since JDK 1.8
  18. */
  19. @Service
  20. public class SaveFileImpl implements SaveFileI {
  21. @Override
  22. public Map<String, Object> docPath(String fileName) {
  23. HashMap<String, Object> map = new HashMap<>();
  24. //根据时间生成文件夹路径
  25. Date date = new Date();
  26. SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy/MM/dd");
  27. String docUrl = simpleDateFormat.format(date);
  28. //文件保存地址
  29. String path = "/data/images/" + docUrl;
  30. //创建文件
  31. File dest = new File(path+"/" + fileName);
  32. //如果文件已经存在就先删除掉
  33. if (dest.getParentFile().exists()) {
  34. dest.delete();
  35. }
  36. map.put("dest", dest);
  37. map.put("path", path+"/" + fileName);
  38. map.put("nginxPath","/"+docUrl+"/"+fileName);
  39. return map;
  40. }
  41. @Override
  42. public boolean saveFileFromBytes(byte[] b, Map<String, Object> map) {
  43. //创建文件流对象
  44. FileOutputStream fstream = null;
  45. //从map中获取file对象
  46. File file = (File) map.get("dest");
  47. //判断路径是否存在,不存在就创建
  48. if (!file.getParentFile().exists()) {
  49. file.getParentFile().mkdirs();
  50. }
  51. try {
  52. fstream = new FileOutputStream(file, true);
  53. fstream.write(b);
  54. } catch (Exception e) {
  55. e.printStackTrace();
  56. return false;
  57. } finally {
  58. if (fstream != null) {
  59. try {
  60. fstream.close();
  61. } catch (IOException e1) {
  62. e1.printStackTrace();
  63. }
  64. }
  65. }
  66. return true;
  67. }
  68. }

我们在websocket服务里注入接口的时候要注意一点,因为spring是单例的,websocket在初始化的时候就实例化了spring的bean,但是当websocket创建一个新的连接的时候spring的bean会出现null的问题,也就是它只注入了一次。这里我们这样注入可以解决这个问题。

  1. /**
  2. * 注入文件保存的接口
  3. */
  4. private static SaveFileI saveFileI;
  5. @Autowired
  6. public void setSaveFileI(SaveFileI saveFileI) {
  7. WebSocketUploadServer.saveFileI = saveFileI;
  8. }

我们贴一下WebSocketUploadServer完整的代码

  1. package com.ccsert.websocketupload.web;
  2. import com.alibaba.fastjson.JSON;
  3. import com.alibaba.fastjson.JSONObject;
  4. import com.ccsert.websocketupload.service.SaveFileI;
  5. import org.slf4j.Logger;
  6. import org.slf4j.LoggerFactory;
  7. import org.springframework.beans.factory.annotation.Autowired;
  8. import org.springframework.stereotype.Component;
  9. import javax.websocket.OnClose;
  10. import javax.websocket.OnMessage;
  11. import javax.websocket.OnOpen;
  12. import javax.websocket.Session;
  13. import javax.websocket.server.PathParam;
  14. import javax.websocket.server.ServerEndpoint;
  15. import java.io.IOException;
  16. import java.util.HashMap;
  17. import java.util.Map;
  18. import java.util.concurrent.CopyOnWriteArraySet;
  19. /**
  20. * ClassName: WebSocketUploadServer <br/>
  21. * Description: <br/>
  22. * date: 2020/2/4 14:51<br/>
  23. *
  24. * @author ccsert<br />
  25. * @since JDK 1.8
  26. */
  27. @ServerEndpoint("/upload/{sid}")
  28. @Component
  29. public class WebSocketUploadServer {
  30. private static final Logger LOG = LoggerFactory.getLogger(WebSocketUploadServer.class);
  31. /**
  32. * 静态变量,用来记录当前在线连接数。应该把它设计成线程安全的。
  33. */
  34. private static int onlineCount = 0;
  35. /**
  36. * concurrent包的线程安全Set,用来存放每个客户端对应的MyWebSocket对象。
  37. */
  38. private static CopyOnWriteArraySet<WebSocketUploadServer> webSocketSet = new CopyOnWriteArraySet<>();
  39. /**
  40. * 与某个客户端的连接会话,需要通过它来给客户端发送数据
  41. */
  42. private Session session;
  43. /**
  44. * 注入文件保存的接口
  45. */
  46. private static SaveFileI saveFileI;
  47. @Autowired
  48. public void setSaveFileI(SaveFileI saveFileI) {
  49. WebSocketUploadServer.saveFileI = saveFileI;
  50. }
  51. /**
  52. * 保证文件对象和文件路径的唯一性
  53. */
  54. private HashMap docUrl;
  55. /**
  56. * 结束标识判断
  57. */
  58. private String endupload = "over";
  59. /**
  60. * 连接建立成功时调用的方法
  61. */
  62. @OnOpen
  63. public void onOpen(Session session, @PathParam("sid") String sid) {
  64. this.session = session;
  65. //加入set中
  66. webSocketSet.add(this);
  67. //在线人数加1
  68. addOnlineCount();
  69. LOG.info(sid + "连接成功" + "----当前在线人数为:" + onlineCount);
  70. }
  71. /**
  72. * 连接关闭时调用的方法
  73. */
  74. @OnClose
  75. public void onClose(@PathParam("sid") String sid) {
  76. //在线人数减1
  77. subOnlineCount();
  78. //从set中删除
  79. webSocketSet.remove(this);
  80. LOG.info(sid + "已关闭连接" + "----剩余在线人数为:" + onlineCount);
  81. }
  82. /**
  83. * 接收客户端发送的消息时调用的方法
  84. *
  85. * @param message 接收的字符串消息。该消息应当为json字符串
  86. */
  87. @OnMessage
  88. public void onMessage(String message, @PathParam("sid") String sid) {
  89. //前端传过来的消息都是一个json
  90. JSONObject jsonObject = JSON.parseObject(message);
  91. //消息类型
  92. String type = jsonObject.getString("type");
  93. //消息内容
  94. String data = jsonObject.getString("data");
  95. //判断类型是否为文件名
  96. if ("fileName".equals(type)) {
  97. LOG.info("传输文件为:" + data);
  98. //此处的 “.”需要进行转义
  99. /*String[] split = data.split("\\.");*/
  100. try {
  101. Map<String, Object> map = saveFileI.docPath(data);
  102. docUrl = (HashMap) map;
  103. this.sendMessage("ok");
  104. } catch (IOException e) {
  105. e.printStackTrace();
  106. }
  107. }
  108. else if ("fileCount".equals(type)){
  109. LOG.info("传输第"+data+"份");
  110. }
  111. //判断是否结束
  112. else if (endupload.equals(type)) {
  113. LOG.info("===============>传输成功");
  114. //返回一个文件下载地址
  115. String path = (String) docUrl.get("nginxPath");
  116. //返回客户端文件地址
  117. try {
  118. this.sendMessage(path);
  119. } catch (IOException e) {
  120. e.printStackTrace();
  121. }
  122. }
  123. }
  124. /**
  125. * 该方法用于接收字节流数组
  126. *
  127. * @param message 文件字节流数组
  128. * @param session 会话
  129. */
  130. @OnMessage
  131. public void onMessage(byte[] message, Session session) {
  132. //群发消息
  133. try {
  134. //将流写入文件
  135. saveFileI.saveFileFromBytes(message,docUrl);
  136. //文件写入成功,返回一个ok
  137. this.sendMessage("ok");
  138. } catch (IOException e) {
  139. e.printStackTrace();
  140. }
  141. }
  142. /**
  143. * 服务器主动提推送消息
  144. *
  145. * @param message 消息内容
  146. * @throws IOException io异常抛出
  147. */
  148. public void sendMessage(String message) throws IOException {
  149. this.session.getBasicRemote().sendText(message);
  150. }
  151. /**
  152. * 群发消息功能
  153. *
  154. * @param message 消息内容
  155. * @param sid 房间号
  156. */
  157. public static void sendInfo(String message, @PathParam("sid") String sid) {
  158. LOG.info("推送消息到窗口" + sid + ",推送内容:" + message);
  159. for (WebSocketUploadServer item : webSocketSet) {
  160. try {
  161. //这里可以设定只推送给这个sid的,为null则全部推送
  162. item.sendMessage(message);
  163. } catch (IOException e) {
  164. LOG.error("消息发送失败" + e.getMessage(), e);
  165. return;
  166. }
  167. }
  168. }
  169. /**
  170. * 原子性的++操作
  171. */
  172. public static synchronized void addOnlineCount() {
  173. WebSocketUploadServer.onlineCount++;
  174. }
  175. /**
  176. * 原子性的--操作
  177. */
  178. public static synchronized void subOnlineCount() {
  179. WebSocketUploadServer.onlineCount--;
  180. }
  181. }

接着就是前端的一些处理了

前端我使用了很多打标记的思想

我就直接贴代码了

  1. <!DOCTYPE html>
  2. <html lang="en">
  3. <head>
  4. <meta charset="UTF-8">
  5. <title>chat room websocket</title>
  6. <link rel="stylesheet" href="bootstrap.css">
  7. <script src="https://code.jquery.com/jquery-3.4.1.min.js"></script>
  8. </head>
  9. <body class="container" style="width: 60%">
  10. <div class="form-group"></br>
  11. <a href="javascript:;" class="file">选择文件
  12. <input type="file" name="" onchange="fileOnchange()" id="fileId"> <span id="filename" style="color: red"></span>
  13. </a>
  14. <a href="javascript:;" onclick="uploadFileFun()" class="file">
  15. 上传
  16. </a>
  17. </div>
  18. <div class="progress">
  19. <div id="speedP" class="progress-bar" role="progressbar" aria-valuenow="60"
  20. aria-valuemin="0" aria-valuemax="100" style="width: 0%;">
  21. </div>
  22. </div>
  23. <div class="form-group"></br>
  24. <h5>传输信息</h5>
  25. <textarea id="message_content" class="form-control" readonly="readonly" cols="50"
  26. rows="10"></textarea>
  27. </div>
  28. <script>
  29. var socket;//websocket连接
  30. var webSocketUrl = 'ws://127.0.0.1:8033/upload/';//websocketi连接地址
  31. var roomId = Number(Math.random().toString().substr(3, 3) + Date.now()).toString(36);//房间号,生成唯一的id
  32. var SpeedOfProgress = "";//进度
  33. var fileObject;//文件对象
  34. var uploadFlag = true;//文件上传的标识
  35. var paragraph = 10485760;//文件分块上传大小
  36. var startSize, endSize = 0;//文件的起始大小和文件的结束大小
  37. var i = 0;//第几部分文件
  38. createWebSocketConnect(roomId);//自动调用
  39. //创建websocket连接
  40. function createWebSocketConnect(roomId) {
  41. if (!socket) {//避免重复连接
  42. console.log(roomId);
  43. socket = new WebSocket(webSocketUrl + roomId);
  44. socket.onopen = function () {
  45. console.log("websocket已连接");
  46. };
  47. socket.onmessage = function (e) {
  48. if (uploadFlag) {
  49. //服务端发送的消息
  50. $("#message_content").append(e.data + '\n');
  51. }
  52. };
  53. socket.onclose = function () {
  54. console.log("websocket已断开");
  55. }
  56. }
  57. }
  58. //文件上传核心方法
  59. function uploadFileFun() {
  60. //文件对象赋值
  61. let filedata = fileObject;
  62. //切换保存标识的状态
  63. uploadFlag = false;
  64. //先向后台传输文件名
  65. let fileName = fileObject.name;
  66. //后台只接收字符串类型,我们定义一个字符串的json对象给后台解析
  67. let fileJson = {
  68. type: "fileName",
  69. data: fileName
  70. };
  71. //后台接收到文件名以后会正式开始传输文件
  72. socket.send(JSON.stringify(fileJson));
  73. //此处为文件上传的核心中的核心,涉及分块上传
  74. socket.onmessage = function (msg) {
  75. if (uploadFlag === false) {
  76. //开始上传文件
  77. if (msg.data === 'ok') {
  78. //判断结束大小是否大于文件大小
  79. if (endSize < filedata.size) {
  80. $("#message_content").append("file.size:" + filedata.size+ '\n');
  81. startSize = endSize;
  82. endSize += paragraph;
  83. $("#message_content").append("file.size:" + filedata.size+ '\n');
  84. $("#message_content").append("startSize:" + startSize+'\n');
  85. $("#message_content").append("endSize:" + endSize+'\n');
  86. SpeedOfProgress = Math.round(startSize / filedata.size * 10000) / 100.00 + "%";
  87. $("#speedP").css("width",SpeedOfProgress);
  88. $("#message_content").append("Slice--->"+'\n');
  89. var blob = filedata.slice(startSize, endSize);
  90. var reader = new FileReader();
  91. reader.readAsArrayBuffer(blob);
  92. reader.onload = function loaded(evt) {
  93. var ArrayBuffer = evt.target.result;
  94. $("#message_content").append("发送文件第" + (i++) + "部分"+'\n');
  95. let fileObjJson={
  96. type: "fileCount",
  97. data:i
  98. };
  99. socket.send(JSON.stringify(fileObjJson));
  100. socket.send(ArrayBuffer);
  101. }
  102. } else {
  103. $("#speedP").css("width","100%");
  104. $("#message_content").append("endSize >= file.size-->" + msg.data + "<---"+'\n');
  105. $("#message_content").append("endSize >= file.size-->endSize:" + endSize+'\n');
  106. $("#message_content").append("endSize >= file.size-->file.size:" + filedata.size+'\n');
  107. startSize = endSize = 0;
  108. i = 0;
  109. $("#message_content").append("发送" + filedata.name + "完毕"+'\n');
  110. $("#message_content").append("发送文件完毕"+'\n');
  111. socketmess={
  112. type:"over",
  113. message:filedata.name
  114. };
  115. socket.send(JSON.stringify(socketmess));//告诉socket文件传输完毕,清空计数器
  116. }
  117. } else {
  118. //此处获取
  119. $("#message_content").append("文件路径为:"+msg.data+'\n');
  120. }
  121. }
  122. }
  123. }
  124. //监听file域对象的变化,然后用于回显文件名
  125. function fileOnchange() {
  126. //从file域对象获取文件对象
  127. let files = $("#fileId")[0].files;
  128. //存储文件对象
  129. fileObject = files[0];
  130. //回显文件名
  131. $("#filename").html(fileObject.name);
  132. }
  133. </script>
  134. <style>
  135. .file {
  136. position: relative;
  137. display: inline-block;
  138. background: #D0EEFF;
  139. border: 1px solid #99D3F5;
  140. border-radius: 4px;
  141. padding: 4px 12px;
  142. overflow: hidden;
  143. color: #1E88C7;
  144. text-decoration: none;
  145. text-indent: 0;
  146. line-height: 20px;
  147. }
  148. .file input {
  149. position: absolute;
  150. font-size: 100px;
  151. right: 0;
  152. top: 0;
  153. opacity: 0;
  154. }
  155. .file:hover {
  156. background: #AADFFD;
  157. border-color: #78C3F3;
  158. color: #004974;
  159. text-decoration: none;
  160. }
  161. </style>
  162. </body>
  163. </html>

加了点样式加了个进度条,大部分代码都有注释所以也不多做解释了

最后返回的这个文件地址,你可以在后台存放到文件服务器后返回给前端展示,这里我就不做过多的操作了,我没加网络地址那么文件就存在项目所在盘符的根路径下的日期格式目录下。

最后附上源码

GitHub地址:

git@github.com:ccsert/websocketUpload.git

gitee地址:

https://gitee.com/ccsert/websocketUpload.git

springboot集成websocket实现大文件分块上传的更多相关文章

  1. js大文件分块上传断点续传demo

    文件夹上传:从前端到后端 文件上传是 Web 开发肯定会碰到的问题,而文件夹上传则更加难缠.网上关于文件夹上传的资料多集中在前端,缺少对于后端的关注,然后讲某个后端框架文件上传的文章又不会涉及文件夹. ...

  2. 使用HTML5 FormData对象实现大文件分块上传(断点上传)功能

    FormData是HTML5新增的一个对象,通过FormData对象可以组装一组用 XMLHttpRequest发送请求的键/值对.它可以更灵活方便的发送表单数据,因为可以独立于表单使用.如果你把表单 ...

  3. asp.net大文件分块上传断点续传demo

    IE的自带下载功能中没有断点续传功能,要实现断点续传功能,需要用到HTTP协议中鲜为人知的几个响应头和请求头. 一. 两个必要响应头Accept-Ranges.ETag 客户端每次提交下载请求时,服务 ...

  4. .net大文件分块上传断点续传demo

    IE的自带下载功能中没有断点续传功能,要实现断点续传功能,需要用到HTTP协议中鲜为人知的几个响应头和请求头. 一. 两个必要响应头Accept-Ranges.ETag 客户端每次提交下载请求时,服务 ...

  5. java大文件分块上传断点续传demo

    第一点:Java代码实现文件上传 FormFile file = manform.getFile(); String newfileName = null; String newpathname =  ...

  6. php大文件分块上传断点续传demo

    前段时间做视频上传业务,通过网页上传视频到服务器. 视频大小 小则几十M,大则 1G+,以一般的HTTP请求发送数据的方式的话,会遇到的问题:1,文件过大,超出服务端的请求大小限制:2,请求时间过长, ...

  7. web大文件分块上传断点续传demo

    一.概述 所谓断点续传,其实只是指下载,也就是要从文件已经下载的地方开始继续下载.在以前版本的HTTP协议是不支持断点的,HTTP/1.1开始就支持了.一般断点下载时才用到Range和Content- ...

  8. java springboot 大文件分片上传处理

    参考自:https://blog.csdn.net/u014150463/article/details/74044467 这里只写后端的代码,基本的思想就是,前端将文件分片,然后每次访问上传接口的时 ...

  9. iOS大文件分片上传和断点续传

    总结一下大文件分片上传和断点续传的问题.因为文件过大(比如1G以上),必须要考虑上传过程网络中断的情况.http的网络请求中本身就已经具备了分片上传功能,当传输的文件比较大时,http协议自动会将文件 ...

随机推荐

  1. HZNU-ACM寒假集训Day1小结 STL 并查集

    常用STL 1.优先队列 priority_queue 内部是用堆(heap)实现的 priority_queue<int> pq; 默认为一个“越小的整数优先级越低的优先队列” 对于一些 ...

  2. python print %s 号格式化输出

    python %号格式化输出: 一种字符串格式化的语法, 基本用法是将值插入到%s占位符的字符串中. %s,表示格式化一个对象为字符 "%±(正负号表示)3(数字表示字符串的长度)s&quo ...

  3. POJ 1731:Orders

    Orders Time Limit: 1000MS   Memory Limit: 10000K Total Submissions: 9940   Accepted: 6048 Descriptio ...

  4. map/vector遍历删除

    map遍历删除 map<int, vector<int>>::iterator it = g_map.begin(); for (; it != g_map.end(); /* ...

  5. Q2:Add Two Numbers

    2. Add Two Numbers 官方的链接:2. Add Two Numbers Description : You are given two non-empty linked lists r ...

  6. 阿里云ECSlinux下php+mysql+apache

    https://yq.aliyun.com/articles/284131 安装apache https://yq.aliyun.com/articles/106387?spm=a2c4e.11153 ...

  7. spark与Scala版本对应问题

    在阅读一些博客和资料中,发现安装spark与Scala是要严格遵守两者的版本对应关系,如果版本不对应会在之后的使用中出现许多问题. 在安装时,我们可以在spark的官网中查到对应的Scala版本号,如 ...

  8. 循环(while,break,continue),转义字符

    01. 程序的三大流程 在程序开发中,一共有三种流程方式: 顺序 -- 从上向下,顺序执行代码 分支 -- 根据条件判断,决定执行代码的 分支 循环 -- 让 特定代码 重复 执行 02. while ...

  9. (转)jpbc的基本函数介绍

    双线性群简介 质数阶双线性群(Prime-Order Bilinear Groups) 质数双线性群可以由五元组(p,G1,G2,GT,e)来描述.五元组中p是一个与给定安全常数λ相关的大质数,G1, ...

  10. JavaScript 之 数据在内存中的存储和引用

    栈和堆 大家都知道,JS中的数据类型包括两种:简单数据类型(String.Number.Boolean.undefined.null)和复杂数据类型(object). 在内存中分为栈区(stack)和 ...