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

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

springboot整合websocket(一)简单聊天室
springboot整合websocket(二)聊天室补充篇

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


说明

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

  1. 前端webSocket.onmessage()方法里面,我们需要判断一下,文件是否发送完成,没有发送完成就发送下一份,发送完成了就结束。

  2. 前端发送文件的时候,我们需要保存文件的上传进度,以便发下一份文件的时候,我们可以发送正确的部分

  • 文件切割用的是slice()方法
  1. 后端再保存文件的时候,就需要追加文件,而不是覆盖文件

直接上代码

1、服务器端

说一下重点
  1. saveFile(File file, byte[] message) 中文件需要追加
  2. onMessage(Session session, byte[] message) 中需要返回本次保存的大小(message.size)
@Log4j2
@Controller
@ServerEndpoint("/websocket")
public class BaseWebsocketController
{

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

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

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

    //onopen 在连接创建(用户进入聊天室)时触发
    @OnOpen
    public void openSession(Session session, EndpointConfig config)
    {

    }

    //响应字符串
    @OnMessage
    public void onMessage(Session session, String message) throws IOException
    {
        //使用 fastjson 解析 json 字符串
        final Message data = JSONObject.parseObject(message, Message.class);
        //响应的信息
        final Message response = Message.builder()
                .operation(data.getOperation())         //将请求的 operation 放入
                .build();
        //根据不同的 operation 执行不同的操作
        switch (data.getOperation()) {
            //进入聊天室后保存用户名
            case "tip":
                session.getUserProperties().put("username", data.getMsg());
                sessions.put(session.getId(), session);
                response.setMsg("[" + data.getMsg() + "]进入房间");
                sendAll(JSONObject.toJSONString(response));
                break;
            //发送消息
            case "msg":
                final String username = (String) session.getUserProperties().get("username");
                response.setMsg("[" + username + "]" + data.getMsg());
                sendAll(JSONObject.toJSONString(response));
                break;
            case "filename":
                //删除原有文件
                File file = new File(SpringbootPathUtil.getResourcePath() + "/web-socket/file/" + data.getMsg());
                file.delete();
                log.info(file.getCanonicalPath());

                //保存文件信息
                session.getUserProperties().put("file", file);

                response.setMsg("文件【" + data.getMsg() + "】开始上传");
                sendTo(session, JSONObject.toJSONString(response));
                break;
        }
    }

    //响应字节流
    @OnMessage
    public void onMessage(Session session, byte[] message)
    {
        final Message response = new Message();

        final File file = (File) session.getUserProperties().get("file");

        if (saveFile(file, message)) {
            response.setOperation("file-upload-success");
            response.setMsg(message.length + "");
            sendTo(session, JSONObject.toJSONString(response));
        }
        else {
            response.setOperation("file-upload-fail");
            response.setMsg("文件【" + file.getName() + "】上传失败");
            file.delete();
            sendTo(session, JSONObject.toJSONString(response));
        }
    }

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

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

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

    private void sendAll(String message)
    {
        for (Session s : sessions.values()) {
            sendTo(s, message);
        }
    }

    private void sendTo(Session session, String message)
    {
        final RemoteEndpoint.Basic remote = session.getBasicRemote();
        try {
            //发送消息
            remote.sendText(message);
        }
        catch (IOException e) {
            e.printStackTrace();
        }
    }

    private boolean saveFile(File file, byte[] message)
    {
        try (OutputStream os = new FileOutputStream(file, true)) {
            os.write(message, 0, message.length);
            return true;
        }
        catch (IOException e) {
            e.printStackTrace();
            return false;
        }
    }
}

2、页面

html部分不动,再上一篇中可以找到一样的,我就放一下script的部分

说一下重点
  1. 文件切分使用 file.slice(start,end)
  2. 需要一个字段记录上传的文件大小 let fileUploadSize = 0;
  3. filename 中,记得将 fileUploadSize 重置为0
  4. file-upload-success 中,我们需要判断文件是否发送完成。分别对应不同的操作——继续上传 or 结束
<script>
    let webSocket;
    //ip和端口号用自己项目的
    //{websocket}: 其实是刚刚那个@ServerEndpoint("/websocket")中定义的
    let url = 'ws://127.0.0.1:8080/websocket';
    
    let file;
    
    $('#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连接创建。。。');
        sendMessage('tip', username);
      }
      webSocket.onclose = function () {
        console.log('webSocket已断开。。。');
        $('#messageArea').append('websocket已断开\n');
      }
      webSocket.onmessage = function (event) {
        //这个 data 和刚刚的 Message 一样
        let data = {
          operation: '',
          msg: ''
        };
        
        data = JSON.parse(event.data);
        switch (data.operation) {
          case "tip":
            $('#messageArea').append(data.msg + '\n');
            break;
          case "msg":     //显示消息
            $('#messageArea').append(data.msg + '\n');
            break;
          case "filename":
            $('#messageArea').append(data.msg + '\n');
            fileUploadSize = 0;
            sendFile(file);
            break;
          case "file-upload-success":
            fileUploadSize += parseInt(data.msg);
            //文件没有上传完成
            if (fileUploadSize < file.size) {
              sendFile(file);
            } else {
              sendMessage('msg', '上传了一个文件【' + file.name + '】');
            }
            break;
          case "file-upload-fail":
            $('#messageArea').append(data.msg + '\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;
      }
      sendMessage('msg', $('#sendMessage').val());
      
      $('#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;
      }
      
      //发送文件名
      file = files[0];
      sendMessage('filename', file.name);
    });
    
    //发送消息
    function sendMessage(operation, msg) {
      //这个 data 和刚刚的 Message 一样
      let data = {
        operation: operation,
        msg: msg,
      };
      
      //将 data 转成 json 字符串
      webSocket.send(JSON.stringify(data));
    }
    
    let fileUploadSize = 0;
    
    //发送文件
    function sendFile(file) {
      let dist = file.size < fileUploadSize + 1024 ? file.size : fileUploadSize + 1024;
      //切分文件
      let blob = file.slice(fileUploadSize, dist);
      //文件读取对象
      let reader = new FileReader();
      
      //文件加载后的回调函数
      reader.onload = ev => {
        let buffer = ev.target.result;
        webSocket.send(buffer);
      }
      //二进制加载文件
      reader.readAsArrayBuffer(blob);
    }
  
  script>

那么,大文件也可以通过切分的方式上传了!!!

最后,还是提醒一下,文件上传还是用接口比较方便,websocket就不是做这个用滴。

End


Question 1

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

Question 2

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


springboot整合websocket(一)简单聊天室
springboot整合websocket(二)聊天室补充篇

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

你可能感兴趣的:(spring,boot,websocket,java)