IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 网络协议 -> springboot整合websocket(三)上传文件(终篇) -> 正文阅读

[网络协议]springboot整合websocket(三)上传文件(终篇)

springboot整合websocket(三)上传文件(终篇)

springboot整合websocket(一)简单聊天室
springboot整合websocket(二)上传文件(引导篇)
springboot整合websocket(三)上传文件(终篇)


说明

这里就涉及到一个问题,文件保存在服务器,前端页面要等后端保存完了之后,再发送下一份。

而前端想要接收服务器的消息,只能在OnMessage,所以我们需要改3个地方

  1. 前端webSocket.onmessage()方法里面,我们需要判断一下,哪些消息是显示的,哪些消息是发送下一份文件
  • 这个采用的就是Message类将消息封装,转成json后发给前端,前端拿到json后在转成对象
  1. 前端发送文件的时候,我们需要保存文件的上传进度,以便发下一份文件的时候,我们可以发送正确的部分
  • 文件切割用的是slice()方法,具体在js的sendFile(addSize)
  1. 服务器onMessage(byte[]),需要给前端发送一个消息,告诉前端发送下一部分文件

直接上代码

这里我使用了Gson,放一下依赖

<!--gson-->
<dependency>
  <groupId>com.google.code.gson</groupId>
  <artifactId>gson</artifactId>
</dependency>

服务器端

@Log4j2
@Controller
@ServerEndpoint("/websocket")
public class BaseWebsocketController
{

    //使用 ConcurrentHashMap, 保证线程安全, static全局共享 session

    //这里之所以static,是因为这个类不是单例的!!
    //他虽然有@Controller注解,但是不适用Ioc容器中拿对象,每一次请求过来都是一个新的对象

    //存放 session
    private final static Map<String, Session> sessions = new ConcurrentHashMap<>();

    private final static Gson gson = new Gson();

    //onopen 在连接创建(用户进入聊天室)时触发
    @OnOpen
    public void openSession(Session session, EndpointConfig config)
    {
        //将session存起来, 用于服务器向浏览器发送消息
        sessions.put(session.getId(), session);
        sendAll("msg", "[" + session.getId() + "]进入房间");
    }

    //响应字符串
    @OnMessage
    public void onMessage(Session session, String message) throws Exception
    {
        // session.getUserProperties() 是一个 Map<String,Object>, 用于存放数据
        session.getUserProperties().put("filename", message);
        //这里处理一下, 如果有该文件, 就先删除
        File file = new File(getProjectRootPath(), message);
        if (file.exists()) {
            file.delete();
        }
        //输出一下文件路径
        System.out.println(file.getCanonicalPath());
    }

    //响应字节流
    @OnMessage
    public void onMessage(Session session, byte[] message)
    {
        // 1、先拿到文件名
        final String filename = (String) session.getUserProperties().get("filename");
        // 2、追加到该文件
        File file = new File(getProjectRootPath(), filename);

        try (OutputStream os = new FileOutputStream(file, true)) {
            os.write(message, 0, message.length);
        }
        catch (IOException e) {
            e.printStackTrace();
        }

        //返回已经上传的字节数
        send(session, "file", message.length + "");
    }

    //onclose 在连接断开(用户离开聊天室)时触发
    @OnClose
    public void closeSession(Session session, CloseReason closeReason)
    {
        //记得移除相对应的session
        sessions.remove(session.getId());

        //打印一下原因
        System.out.println(closeReason.getReasonPhrase());

        sendAll("msg", "[" + session.getId() + "]离开了房间");
    }

    @OnError
    public void sessionError(Session session, Throwable throwable)
    {
        throwable.printStackTrace();
        //通常有异常会关闭session
        try {
            session.close();
        }
        catch (IOException e) {
            e.printStackTrace();
        }
    }

    private void sendAll(String tip, String message)
    {
        //使用Message封装
        Message msg = new Message(tip, message);
        //用gson转成json字符串
        final String msgStr = gson.toJson(msg);

        for (Session s : sessions.values()) {
            //获得session发送消息的对象
            //Basic是同步, 会阻塞
            //Async是异步, 这个会有多线程并发导致异常, 发送消息太快也会有并发异常, 需要有 消息队列 来辅助使用
            final RemoteEndpoint.Basic remote = s.getBasicRemote();
            try {
                //发送消息
                remote.sendText(msgStr);
            }
            catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    private void send(Session session, String tip, String message)
    {
        //使用Message封装
        Message msg = new Message(tip, message);
        //用gson转成json字符串
        final String msgStr = gson.toJson(msg);

        final RemoteEndpoint.Basic remote = session.getBasicRemote();
        try {
            //发送消息
            remote.sendText(msgStr);
        }
        catch (IOException e) {
            e.printStackTrace();
        }
    }

    /**
     * 获得项目resource目录下static的路径
     *
     * @return
     */
    private String getProjectRootPath()
    {
        try {
            String path = ResourceUtils.getURL(ResourceUtils.CLASSPATH_URL_PREFIX).getPath();
            path = URLDecoder.decode(path, "UTF-8");
            final File file = new File(path, "static");
            return file.getCanonicalPath();
        }
        catch (Exception e) {
            e.printStackTrace();
            return "";
        }
    }

    @Data
    @Builder
    @NoArgsConstructor
    @AllArgsConstructor
    private static class Message
    {
        private String tip;
        private String msg;
    }
}



页面

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org" lang="en">
<head>
  <meta charset="UTF-8">
  <title>websocket-demo</title>
  
  <link rel="stylesheet" href="https://cdn.bootcdn.net/ajax/libs/twitter-bootstrap/4.2.1/css/bootstrap.min.css">
</head>
<body>
  <div class="container py-3">
    <div class="row">
      
      <div class="col-6">
        <div>
          <label for="messageArea">聊天信息:</label>
        </div>
        <div>
          <textarea id="messageArea" readonly class="w-100" style="height: 75vh;"></textarea>
        </div>
      </div>
      
      <div class="col">
        
        <div class="my-1">
          <label for="messageArea">用 户 名:</label>
        </div>
        
        <div class="my-1">
          <input type="text" id="username" autocomplete="off">
        </div>
        
        <div class="my-1">
          <button class="btn-info" id="joinRoomBtn">进入聊天室</button>
          <button class="btn-warning" id="leaveRoomBtn">离开聊天室</button>
        </div>
        
        <hr/>
        
        <div class="my-1">
          <label for="sendMessage">输入消息:</label>
        </div>
        <div>
          <textarea id="sendMessage" rows="5" class="w-100"></textarea>
        </div>
        
        <div class="my-1">
          <button class="btn-primary" id="sendBtn">发送消息</button>
        </div>
        
        <hr/>
        
        <div class="my-1">
          <input id="file" type="file" value="选择文件"/>
          <button id="fileBtn" class="btn-primary">上传</button>
        </div>
      
      </div>
    
    </div>
  
  </div>
  
  <script src="https://s3.pstatp.com/cdn/expire-1-M/jquery/3.3.1/jquery.min.js"></script>
  
  <script>
    let webSocket;
    let url = 'ws://127.0.0.1:8080/websocket';
    //文件上传进度
    let fileData = {
      file: File,
      filename: '',
      size: 0,            //文件总大小
      uploadSize: 0,      //已上传大小
    };
    //缓存大小
    let cacheSize = 4096;
    
    $('#username').keyup(function (e) {
      let keycode = e.which;
      if (keycode == 13) {
        $('#joinRoomBtn').click();
      }
    });
    
    //进入聊天室
    $('#joinRoomBtn').click(function () {
      let username = $('#username').val();
      webSocket = new WebSocket(url);
      webSocket.onopen = function () {
        console.log('webSocket连接创建。。。');
      }
      webSocket.onclose = function () {
        $('#messageArea').append('websocket已断开\n');
      }
      webSocket.onmessage = function (event) {
        let data = {
          tip: '',
          msg: ''
        };
        
        data = JSON.parse(event.data);
        console.log(data)
        switch (data.tip) {
          case 'msg':     //显示消息
            $('#messageArea').append(data.msg + '\n');
            break;
          case "file":      //发送下一份文件
            let addSize = parseInt(data.msg);
            if (addSize > 0) {
              if (!sendFile(addSize)) {
                $('#messageArea').append('文件[' + fileData.filename + ']上传成功\n');
              }
            }
            break;
        }
        
      }
      webSocket.onerror = function (event) {
        console.log(event)
        console.log('webSocket连接异常。。。');
      }
    });
    
    //退出聊天室
    $('#leaveRoomBtn').click(function () {
      if (webSocket) {
        //关闭连接
        webSocket.close();
      }
    });
    
    //发送消息
    $('#sendBtn').click(function () {
      // var msg = $('#sendMessage').val();
      // if (msg.trim().length === 0) {
      //   alert('请输入内容');
      //   return;
      // }
      // webSocket.send(msg);
      //
      // $('#sendMessage').val('');
    });
    
    //上传文件
    // https://www.cnblogs.com/myfjd/archive/2012/03/22/2411374.html
    $('#fileBtn').click(function () {
      let files = [];
      files = $('#file')[0].files;
      if (files.length === 0) {
        alert('请选择文件');
        return;
      }
      
      //发送文件名
      webSocket.send(files[0].name);
      
      //初始化上传进度
      fileData.file = files[0];
      fileData.filename = files[0].name;
      fileData.size = files[0].size;
      fileData.uploadSize = 0;
      sendFile(0);
    });
    
    //发送文件
    function sendFile(addSize) {
      fileData.uploadSize += addSize;
      
      if (fileData.uploadSize < fileData.size) {
        //文件切分, 从 fileData.uploadSize 到 fileData.uploadSize+cacheSize
        let dist = fileData.uploadSize + cacheSize < fileData.size ? fileData.uploadSize + cacheSize : fileData.size;
        //切分
        let blob = fileData.file.slice(fileData.uploadSize, dist);
        let reader = new FileReader();
        reader.onload = function (evt) {
          let buffer = evt.target.result;
          webSocket.send(buffer);
        }
        reader.readAsArrayBuffer(blob);
        return true;
      } else {
        return false;
      }
    }
  
  
  </script>

</body>
</html>

End


Question 1

Q:我又想发消息又想上传文件怎么办
A:
1、在js里面也封装一个对象,将json传给后
2、后端收到json后,转成对象,判断是发消息,还是发送的文件名就ok啦

Question 2

Q:我想选择很多个文件咋办
A:将选择的文件存进数组里面,上传完一个再上传第二个

Question 3

Q:我想同时上传多个文件欸
A:
1、websocket一次只能传递一个文件,要想多个文件,只能建立多个websocket连接。

  网络协议 最新文章
使用Easyswoole 搭建简单的Websoket服务
常见的数据通信方式有哪些?
Openssl 1024bit RSA算法---公私钥获取和处
HTTPS协议的密钥交换流程
《小白WEB安全入门》03. 漏洞篇
HttpRunner4.x 安装与使用
2021-07-04
手写RPC学习笔记
K8S高可用版本部署
mySQL计算IP地址范围
上一篇文章      下一篇文章      查看所有文章
加:2021-11-10 12:45:58  更:2021-11-10 12:48:00 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年7日历 -2024/7/1 21:39:22-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码