背景
上传文件功能,一次可以上传大量数据。后端需要把上传的进度实时发给前端
痛点
1.如果用户上传文件的数据量过大,超出http请求的时间限制,前端会断开连接
2.用户需要等待较长时间,体验较差
技术方案
1.前端生成uuid,使用uuid作为websocket的userId,开启websocket
2.后端使用ConcurrentHashMap保存userId与对应的session,key为userId,value为session
3.在导出文件的http请求的参数中加入uuid,后端根据uuid找到session,并把文件导出进度通过websocket实时发送到前端
1.首先对websocket处理器进行改进
package com.dzqc.dz.common.controller;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import org.springframework.web.socket.CloseStatus;
import org.springframework.web.socket.TextMessage;
import org.springframework.web.socket.WebSocketSession;
import org.springframework.web.socket.handler.TextWebSocketHandler;
/**
* websocket处理器
* @author LENOVO
*/
public class WebSocketHandler extends TextWebSocketHandler {
public static Map<String,WebSocketSession> map=new HashMap<String,WebSocketSession>();
private String id;
//连接建立后处理
@Override
public void afterConnectionEstablished(WebSocketSession session) throws Exception {
String id=(String) session.getAttributes().get("socketId");
this.id=id;
System.out.println(id+":建立链接");
if(!map.containsKey(id)) {
map.put(id, session);
}else {
session.close();
}
super.afterConnectionEstablished(session);
}
//抛出异常时处理
@Override
public void handleTransportError(WebSocketSession session, Throwable exception) throws Exception {
if(session.isOpen()){
session.close();
if(map.containsKey(this.id)) {
map.remove(this.id);
}
}
}
//连接关闭后处理
@Override
public void afterConnectionClosed(WebSocketSession session, CloseStatus status) {
try {
if(map.containsKey(this.id)) {
map.remove(this.id);
}
session.close();
} catch (IOException e) {
e.printStackTrace();
}
}
//接收文本消息,并发送出去
@Override
protected void handleTextMessage(WebSocketSession session,TextMessage message) throws Exception {
return ;
}
public static boolean sendMessage(String id,String message) {
if(map.containsKey(id)) {
WebSocketSession session=map.get(id);
try {
session.sendMessage(new TextMessage(message));
} catch (IOException e) {
e.printStackTrace();
return false;
}
return true;
}else {
return false;
}
}
/**
* 关闭指定连接
* @param socketId
* @return
*/
public static boolean closeSocket(String socketId) {
if(map.containsKey(socketId)) {
WebSocketSession w=map.get(socketId);
if(w.isOpen()) {
try {
w.close();
return true;
} catch (IOException e) {
e.printStackTrace();
return false;
}
}else {
return false;
}
}else {
return false;
}
}
}
2.模拟获取socketId,以及发送数据的controller
package com.dzqc.dz.common.controller;
import java.util.UUID;
import javax.servlet.http.HttpSession;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import com.dzqc.dz.common.util.MyAjaxResult;
@Controller
@RequestMapping("/test")
public class TestControllers {
/**
* 跳转页面
* @param session
* @param id
* @return
*/
@RequestMapping("/test")
public String test(HttpSession session,String id) {
return "index2";
}
/**
* 获取标识socketId
* @param session
* @return
*/
@RequestMapping("/socketId")
@ResponseBody
public MyAjaxResult yanz(HttpSession session) {
String uid=UUID.randomUUID().toString();
session.setAttribute("socketId",uid);
return MyAjaxResult.success(uid);
}
/**
* 模拟发送信息
* @param session
* @return
*/
@RequestMapping("/send")
@ResponseBody
public String test(HttpSession session) {
String socketId=(String) session.getAttribute("socketId");
try {
new Thread(new Runnable() {
@Override
public void run() {
// TODO Auto-generated method stub
for(int i=0;i<100;i++){
boolean b=WebSocketHandler.sendMessage(socketId,i+"%");
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
WebSocketHandler.closeSocket(socketId);
}
}).start();
} catch (Exception e) {
e.printStackTrace();
return "send error";
}
return "send success";
}
}
3.模拟进度条页面
<%@ page language="java" import="java.util.*" contentType="text/html; charset=utf-8"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>测试</title>
</head>
<body>
<div>
测试 websocket<br>
<button id="up">开始上传</button><br>
进度<span id="jindu">0%</span>
</div>
<script src="https://code.jquery.com/jquery-3.3.1.min.js" type="text/javascript"></script>
<script type="text/javascript">
$(function(){
var uid;
$("#up").click(function(){
//请求socketId
$.ajax({
url:'${path}/test/socketId',
dataType:'json',
type:'get',
success:function(res){
uid=res.msg;
//请求成功以后 与服务器建立 ws协议 连接
var ws;
if ('WebSocket' in window) {
ws = new WebSocket("ws://"+window.location.host+"${pageContext.request.contextPath}/socket.do?type="+uid);
} else {
ws = new SockJS("http://"+window.location.host+"${pageContext.request.contextPath}/socket/info?type="+uid);
}
ws.onopen = function (evnt) {
console.log("websocket连接成功");
send();
};
ws.onmessage = function (evnt) {
console.log("进度:"+evnt.data);
$("#jindu").html(evnt.data);
};
ws.onerror = function (evnt) {
send();
console.log("连接失败");
$("#jindu").html("100%");
};
ws.onclose = function (evnt) {
console.log("关闭连接");
$("#jindu").html("100%");
}
}
})
//发送数据
function send(){
$.ajax({
url:'${path}/test/send',
dataType:'json',
type:'get',
success:function(res){
console.log(res);
}
})
}
})
})
</script>
</body>
</html>
4.测试