最坑的是后端拦截器、Handler注入问题,以及前端使用的时机控制
1.后端
1.1配置类
package com.glodon.webSocket;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.socket.config.annotation.EnableWebSocket;
import org.springframework.web.socket.config.annotation.WebSocketConfigurer;
import org.springframework.web.socket.config.annotation.WebSocketHandlerRegistry;
/**
* @author lihongxiang
* @ 2020/5/25 20:41
*/
@Configuration
@EnableWebSocket
public class WsConfigure implements WebSocketConfigurer {
@Override
public void registerWebSocketHandlers(WebSocketHandlerRegistry registry)
{
registry.addHandler(myHandler(), "/websocket").setAllowedOrigins("*").addInterceptors(new WebSocketInterceptor());
}
/**
* 向spring容器注册javabean由spring容器来管理
* @return
*/
@Bean
public WsHandler myHandler()
{
return new WsHandler();
}
}
1.2拦截器
package com.glodon.webSocket;
import java.util.Map;
import javax.servlet.http.HttpServletRequest;
import com.glodon.common.service.EBQEditAuthAPIService;
import org.springframework.http.server.ServerHttpRequest;
import org.springframework.http.server.ServerHttpResponse;
import org.springframework.http.server.ServletServerHttpRequest;
import org.springframework.stereotype.Component;
import org.springframework.web.socket.WebSocketHandler;
import org.springframework.web.socket.server.support.HttpSessionHandshakeInterceptor;
/**
* @author lihongxiang
* @ 2020/5/26 8:33
*/
@Component
public class WebSocketInterceptor extends HttpSessionHandshakeInterceptor {
private EBQEditAuthAPIService verificationService = SpringUtil.getBean(EBQEditAuthAPIService.class);
/**
* 当客户端与服务器端握手之前之前执行的方法
* 取出当前存在session的用户信息,封装到WebSocket对象中的map中;
* 由Handler处理器中获取id
* @return
*/
@Override
public boolean beforeHandshake(ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler wsHandler,
Map<String, Object> attributes) throws Exception {
//将增强的request转换httpservletRequest
if (request instanceof ServletServerHttpRequest) {
ServletServerHttpRequest serverHttpRequest = (ServletServerHttpRequest) request;
HttpServletRequest servletRequest = serverHttpRequest.getServletRequest();
String account = servletRequest.getParameter("account");
String token = servletRequest.getParameter("token");
attributes.put("account",account);
attributes.put("token",token);
boolean isValid = verificationService.checkUserByToken(account, token);
if (isValid) {
System.out.println("dddddd");
}
return super.beforeHandshake(request, response, wsHandler, attributes);
}
//放行
return true;
}
/**
* 与服务器websoket建立握手之后执行的方法
*/
@Override
public void afterHandshake(ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler handler, Exception exception) {
}
}
1.3Handler
package com.glodon.webSocket;
import com.glodon.common.service.EBQEditAuthAPIService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;
import org.springframework.web.socket.CloseStatus;
import org.springframework.web.socket.TextMessage;
import org.springframework.web.socket.WebSocketMessage;
import org.springframework.web.socket.WebSocketSession;
import org.springframework.web.socket.handler.TextWebSocketHandler;
import java.util.concurrent.ConcurrentHashMap;
/**
* @author lihongxiang
* @ 2020/5/25 20:40
*/
@Component
public class WsHandler extends TextWebSocketHandler {
private Logger logger = LoggerFactory.getLogger(WsHandler.class);
@Autowired
private EBQEditAuthAPIService verificationService;
@Autowired
private RedisTemplate redisTemplate;
// concurrent包的线程安全Map,用来存放每个客户端对应的WebSocketSession对象。
public static ConcurrentHashMap<String,WebSocketSession> webSocketMap = new ConcurrentHashMap<>();
/**
* 连接建立成功调用的方法
* @param session
* @throws Exception
*/
@Override
public void afterConnectionEstablished(WebSocketSession session) throws Exception
{
String account = (String) session.getAttributes().get("account");
String token = (String) session.getAttributes().get("token");
if (!redisTemplate.hasKey(token))
{
logger.error("redis中未找到Token: \n token:[{}] \n account:[{}]", token, account);
session.close();
return;
}
if (verificationService.checkUserByToken(account, token)) {
String sGldUserID = redisTemplate.opsForHash().get(token, "gldUserId").toString();
webSocketMap.put(account, session);
logger.info("webSocket校验成功: \n token:[{}] \n account:[{}]", token, account);
} else {
logger.info("webSocket校验失败,: \n token:[{}] \n account:[{}]", token, account);
session.close();
}
super.afterConnectionEstablished(session);
}
/**
* 连接关闭调用的方法
* @param session
* @param status
* @throws Exception
*/
@Override
public void afterConnectionClosed(WebSocketSession session, CloseStatus status) throws Exception
{
String account = getAttributeValue(session, "account");
if(webSocketMap.containsKey(account)){
webSocketMap.remove(account);
}
super.afterConnectionClosed(session, status);
}
/**
* 实现服务器主动推送
* @param session
* @param message
* @throws Exception
*/
@Override
protected void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception
{
TextMessage msg=new TextMessage(message.getPayload());
session.sendMessage(msg);
}
/**
* 收到客户端消息后调用的方法
* @param session
* @param message
* @throws Exception
*/
@Override
public void handleMessage(WebSocketSession session, WebSocketMessage<?> message) throws Exception
{
super.handleMessage(session, message);
}
/**
* 连接发生异常后触发的方法,关闭出错会话的连接,和删除在Map集合中的记录
* @param session
* @param exception
* @throws Exception
*/
@Override
public void handleTransportError(WebSocketSession session, Throwable exception) throws Exception
{
if (session.isOpen()) {
session.close();
}
logger.error("连接出错");
webSocketMap.remove(getAttributeValue(session, "account"));
super.handleTransportError(session, exception);
}
/**
* 获取session中的属性值
* @param session
* @param attribute
* @return
*/
private String getAttributeValue(WebSocketSession session, String attribute) {
try {
// 获取存入websocket的attributeValue
String attributeValue = (String) session.getAttributes().get(attribute);
return attributeValue;
} catch (Exception e) {
return null;
}
}
}
1.4实现ApplicationContextAware
package com.glodon.webSocket;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.stereotype.Component;
/**
* @author lihongxiang
* @ 2020/5/25 16:27
*/
@Component
public class SpringUtil implements ApplicationContextAware {
//ApplicationContext对象是Spring开源框架的上下文对象实例,在项目运行时自动装载Handler内的所有信息到内存。
private static ApplicationContext applicationContext;
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
if (SpringUtil.applicationContext == null) {
SpringUtil.applicationContext = applicationContext;
}
}
//获取applicationContext
public static ApplicationContext getApplicationContext() {
return applicationContext;
}
//通过name获取 Bean.
public static Object getBean(String name) {
return getApplicationContext().getBean(name);
}
//通过class获取Bean.
public static <T> T getBean(Class<T> clazz) {
return getApplicationContext().getBean(clazz);
}
//通过name,以及Clazz返回指定的Bean
public static <T> T getBean(String name, Class<T> clazz) {
return getApplicationContext().getBean(name, clazz);
}
}
1.5调用接口类
package com.glodon.webSocket;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.socket.TextMessage;
/**
* @author lihongxiang
* @ 2020/5/8 17:36
*/
@Controller
@RequestMapping("/v2/edit/websocket")
public class CheckCenterController {
@Autowired
private WsHandler wsHandler;
//推送数据接口
@ResponseBody
@GetMapping("/push/{account}")
public String pushToWsHandler(@PathVariable String account) {
try {
wsHandler.handleMessage(WsHandler.webSocketMap.get(account), new TextMessage("招标方更新了招标清单"));
} catch (Exception e) {
e.printStackTrace();
}
return account;
}
}
2.前端
2.1前端配置
/* * @Author: lihx * @Date: 2020-05-12 09:15:36 * @LastEditTime: 2020-05-26 19:31:41 * @LastEditors: lihx * @Description: webSocket请求相关 * @FilePath: \front\webbqFront\src\api\common\common.webSocket.js * @可以输入预定的版权声明、个性签名、空行等 */import storage from '../../sessionstorage/edit.storage.js';
var websock = null;function initWebSocket() { // 初始化websocket if (typeof (WebSocket) == 'undefined') { console.log('您的浏览器不支持WebSocket'); } else { console.log('您的浏览器支持WebSocket'); let loginInfo = storage().getLoginInfo('account'); let account = loginInfo.account; let token = loginInfo.token; let wsuri = 'ws://127.0.0.1:9010/websocket?account=' + account + '&&token=' + token; // 加入token校验 websock = new WebSocket(wsuri); console.log(websock); websock.onopen = websocketopen; websock.onmessage = websocketonmessage; websock.onclose = websocketclose; websock.onerror = websocketerror; } return websock;}
function websocketopen() { // 打开 console.log('WebSocket连接成功'); alert('WebSocket连接成功');}
function websocketonmessage(e) { // 数据接收 console.log(e.data); alert(e.data);}
function websocketclose() { // 关闭 console.log('WebSocket关闭'); alert('WebSocket关闭');}
function websocketerror() { // 失败 console.log('WebSocket连接失败'); alert('WebSocket连接失败');}
// 发送数据function send(websock, data) { // 此处先判断socket连接状态 switch (websock.readyState) { // CONNECTING:值为0,表示正在连接。 case websock.CONNECTING: console.log('WebSocket连接失败'); alert('WebSocket连接失败'); break; // OPEN:值为1,表示连接成功,可以通信了。 case websock.OPEN: console.log('WebSocket可以通信'); alert('WebSocket已发送消息'); websock.send(data); break; // CLOSING:值为2,表示连接正在关闭。 case websock.CLOSING: console.log('WebSocket正在关闭'); alert('WebSocket正在关闭,无法发送消息'); break; // CLOSED:值为3,表示连接已经关闭,或者打开连接失败。 case websock.CLOSED: // do something break; default: // this never happens break; }}
// 关闭websocketfunction close(websock, onSuccess, onError) { websock.close();}
export default function webSocket() { return { initWebSocket, send, close };};
2.2前端使用
在根组件 App.vue 中create方法中接收消息
import workerUtils from './service/edit/common/edit.workerUtils.js';
export default { components: { 'v-header': Header, 'v-new-header': qbHeader, 'v-checkAnalysisNav': checkAnalysisNav // 'v-footer': Footer, }, data: function() { return { totalAmount: 0, projectName: '广联达企业清单编制文件', openFromCef: window.open_source_fromcef, curPro: {}, webSocket: null }; }, created: function () { let vueObj = this; bus.$on('updateFooterProjectTotal', function (newTotal) { vueObj.totalAmount = newTotal; }); bus.$on('updateFooterProjectName', function (newName) { vueObj.projectName = newName; }); bus.$on('webSocket-open', function (project) { vueObj.webSocket = webSocket().initWebSocket(project.tenderInfo.tenderID); }); bus.$on('webSocket-close', function () { webSocket().close(vueObj.webSocket); }); bus.$on('webSocket-send', function (message) { webSocket().send(vueObj.webSocket, message); }); projectManager().read(function(data) { vueObj.curPro = projectManager().getCurProject(); if (vueObj.curPro.type == 'tb') { projectManager().getCurrentProjectLocker(vueObj.curPro._id, vueObj.curPro.type, function (opener) { if (opener.data !== 'noLocker') { vueObj.webSocket = webSocket().initWebSocket(vueObj.curPro.tenderInfo.tenderID); } }); } }); vueObj.registerWindowFunc(); },
}