1.业务背景
负责的项目有一个搜索功能,搜索的范围几乎是全表扫,且数据源类型贼多。目前对搜索的数据量量级未知,但肯定不会太少,不仅需要搜索还得点击下载文件。
关于搜索这块类型 众多,未了避免有个别极大数据源影响整个搜索效率,我采用多线程异步搜索,将搜索到每个数据源数据使用 websocket 响应给前端。
2.遇到的问题
1 .想自定义接收前端消息的类型,因为接收的消息类型都是string 类型,所以一看肯定不符合我的需求。(唉,怪我没多问)
思路: 其实接收是string一点不影响。直接上json,转对象就行。
2. socket 什么时候关闭
思路:
1.心跳包检测,心跳达到次数断开socket。(前后端互发心跳)
2. 因为多线程,后端开启线程监听线程有没有执行完的队列还有没有还没执行的任务,没有开始计时,达到时间关闭socket,若计时期间有任务重置计时。(后端监听)
3.相关资料
一文搞懂四种 WebSocket 使用方式_@enablewebsocket_Java架构狮的博客-CSDN博客
4.代码实现
1.注解写法
/*** 开启WebSocket支持* Created by huiyunfei on 2019/5/31.*/
@Configuration
@EnableWebSocket
public class WebSocketConfig implements ServletContextInitializer {@Beanpublic ServerEndpointExporter serverEndpointExporter() {ServerEndpointExporter serverEndpointExporter = new ServerEndpointExporter();return serverEndpointExporter;}/*** 启动加载** @param servletContext*/@Overridepublic void onStartup(ServletContext servletContext) {servletContext.addListener(WebAppRootListener.class);// 接收base64的字符串,等于50M 解决上传内容过大问题servletContext.setInitParameter("org.apache.tomcat.websocket.textBufferSize", "52428800");servletContext.setInitParameter("org.apache.tomcat.websocket.binaryBufferSize", "52428800");}}
@OnOpenpublic void onOpen(Session session) {System.out.println("与前端建立了WebSocket连接");this.session = session;webSocketSet.add(this); //加入set中
// addOnlineCount(); //在线数加1
// log.info("有新窗口开始监听:"+sid+",当前在线人数为" + getOnlineCount());this.sid=sid;try {sendMessage("连接成功");} catch (IOException e) {log.error("websocket IO异常");}}@OnMessagepublic void handleMessage(Session session, String message) throws IOException {session.getBasicRemote().sendText("Reversed: " + new StringBuilder(message).reverse());}@OnClosepublic void onClose(Session session) {System.out.println("与前端断开了WebSocket连接");}@OnErrorpublic void onError(Throwable throwable) {throwable.printStackTrace();}
注意:谁说是注解开发,切记并不是发送http请求,下面是错误实例
上面我不是提到想自定义接收参数,然后我一直以为加个注解就行。接收类型别动。
不然就像苦逼的我程序都启动不了,排错半天时间。
@OnMessagepublic void onMessage(Session session, FullSearchParam param) {System.out.println("接收到前端发送的消息:" + param.getSearchContext());try {
// fullSearch(param, session);} catch (IOException e) {e.printStackTrace();} catch (EncodeException e) {throw new RuntimeException(e);}}
2.实现接口
package com.trinity.web.controller.search.spring;import org.springframework.beans.factory.annotation.Autowired;
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;/*** 开启WebSocket支持* Created by huiyunfei on 2019/5/31.*/
@Configuration
@EnableWebSocket
public class SpringSocketConfig implements WebSocketConfigurer {@Autowiredprivate SpringSocketHandle springSocketHandle;@Autowiredprivate SpringAbstractWebSocketHandler springAbstractWebSocketHandler;@Overridepublic void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {registry.addHandler(springSocketHandle, "/spring-ws").setAllowedOrigins("*").addHandler(springAbstractWebSocketHandler, "/spring-ws1").setAllowedOrigins("*");}}
这种方式接收消息需要判断,因为 WebSocketMessage 是接口,spring 提供了三个实现类,分别是文本 字节 ping,目前后两种还不知道怎么使用。所以这种方式需要区判断前端传输的数据类型分别处理。
@Component
public class SpringSocketHandle implements WebSocketHandler {/*** 连接成功后调用。* @param session* @throws Exception*/@Overridepublic void afterConnectionEstablished(WebSocketSession session) throws Exception {System.out.println("SpringSocketHandle, 收到新的连接: " + session.getId());}/*** 处理发送来的消息** @param session* @param message* @throws Exception*/@Overridepublic void handleMessage(WebSocketSession session, WebSocketMessage<?> message) throws Exception {String msg = "SpringSocketHandle, 连接:" + session.getId() + ",已收到消息。";System.out.println(msg);
// this.handleMessage(session, );session.sendMessage(new TextMessage(msg));}/*** WS 连接出错时调用** @param session* @param exception* @throws Exception*/@Overridepublic void handleTransportError(WebSocketSession session, Throwable exception) throws Exception {System.out.println("WS 连接发生错误");}/*** 连接关闭后调用** @param session* @param closeStatus* @throws Exception*/@Overridepublic void afterConnectionClosed(WebSocketSession session, CloseStatus closeStatus) throws Exception {System.out.println("WS 关闭连接");}/*** 支持分片消息** @return*/@Overridepublic boolean supportsPartialMessages() {return false;}
这种方式就更加简单不需要我们自己去判断
@Slf4j
@Component
public class SpringAbstractWebSocketHandler extends AbstractWebSocketHandler {/*** 业务service*/@Autowiredprivate IDampDatasourceInfoService dampDatasourceInfoService;@Autowiredprivate IDampAssetInfoService dampAssetInfoService;@Autowiredprivate SearchThreadPool searchThreadPool;@Autowiredprivate TokenService tokenService;/*** 静态变量,用来记录当前在线连接数。应该把它设计成线程安全的。*/private static int onlineCount = 0;/*** concurrent包的线程安全Set,用来存放每个客户端对应的MyWebSocket对象。*/private static CopyOnWriteArraySet<SearchSocketServer> webSocketSet = new CopyOnWriteArraySet<SearchSocketServer>();/*** 与某个客户端的连接会话,需要通过它来给客户端发送数据*/private Session session;protected void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception {log.info("接收到搜索的消息,搜索内容为{}",message);List<String> authorization = session.getHandshakeHeaders().get("Authorization");FullSearchParam param = JSONObject.parseObject(message.getPayload(), FullSearchParam.class);fullSearch(param, session);}protected void handleBinaryMessage(WebSocketSession session, BinaryMessage message) throws Exception {}protected void handlePongMessage(WebSocketSession session, PongMessage message) throws Exception {}
5.总结
尝试写不会的代码总是非常的认真,但也非常煎熬。
然后接收消息时用到了 SecurityUtils 公共方法 从token 获取用户id,但是却出现获取失败。
明天再看
public class SecurityUtils
{/*** 用户ID**/public static Long getUserId(){try{return getLoginUser().getUserId();}catch (Exception e){throw new ServiceException("获取用户ID异常", HttpStatus.UNAUTHORIZED);}}
/*** 获取用户**/public static LoginUser getLoginUser(){try{return (LoginUser) getAuthentication().getPrincipal();}catch (Exception e){throw new ServiceException("获取用户信息异常", HttpStatus.UNAUTHORIZED);}}
/*** 获取Authentication*/public static Authentication getAuthentication(){return SecurityContextHolder.getContext().getAuthentication();}