Spring MVC 实现web Socket向前端实时推送数据
最近项目中用到了webSocket服务,由后台实时向所有的前端推送消息,前端暂时是不可以发消息给后端的,数据的来源是由具体的设备数据收集器收集起来,然后通过socket推送给后端,后端收到数据后,再将这些数据推送给前端。
听起来业务逻辑有点复杂。其实单独的实现socket或websocket都比较简单,但是二者之间的数据传输问题,困扰了我很久。也想过用redis做一个消息队列,将socket接收到的数据处理后丢进去,然后再用websocket从redis里取出数据,再推送给前端。
但是。问题来了,这么配置的话,一个简单的功能,要另外再加一个服务出来,配置起来好麻烦的感觉。再说了,目前的业务逻辑是,数据不做任何处理就直接扔出去了,干嘛要一层一层的来设计这些东西呢?虽然它有很好的模式和很高的扩展性,可我就是懒的去写多余的代码来配置这些东西。so,本着能懒就懒的原则,我整出来一套自己适合的方案来做这个事情。
思路:socket推送给后端的数据是实时的,有则推送,没有就一边呆着,等消息发过来。所以呢,我干嘛不弄个http接口来接收呢,本来数据就不多,老半天才会推一条出来,有时候一天都不会有几条数据,所以,搞一个socket还不如直接提供一个HTTP接口来接收数据来的划算,关键是代码写起来简单啊。所以就有了这个:
@RequestMapping(value = "/socket", method = {RequestMethod.POST, RequestMethod.GET}) public void webSocket(HttpServletRequest request) { Map map = request.getParameterMap(); ... }
这个东西没啥可说的,不用测试都知道没问题。
好了,数据是接收到了,怎么发送给前端呢?我的想法是,把推送前端的代码直接写到上面的代码体里面,这样就能接到一个推送就直接广播给前端,接不到数据就不推送,多好啊。
想象中的代码应该是这样的:
@RequestMapping(value = "/socket", method = {RequestMethod.POST, RequestMethod.GET}) public void webSocket(HttpServletRequest request) { Map map = request.getParameterMap(); ... // sendMessageToFront(message); }
如果想这样写,要么使用标签注解,要么自定义一个方法,继承websocket来实现功能。but how?
<坑里的生活就不播了,直接写出坑后的成果吧>
标签注解的方式或许可以实现,但是,这样以来,就有三个URI提供给前端了,一个用来握手,一个用来发送消息,一个用来接收消息。好吧,前端也以懒为天,能少写一个字母绝不多加半个符号。so,我的这种方案直接被否决了,所以得另寻出路。
然后就是写个方法继承websocket来实现这个功能了。代码是这样的:
@Configuration @EnableWebSocket public class WebSocketConfig implements WebSocketConfigurer { @Resource private AliceWebSocketHandler webSocketHandler; @Override public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) { registry.addHandler(webSocketHandler, "/webSocket").setAllowedOrigins("*") .addInterceptors(new AliceHandShakeInterceptor()); registry.addHandler(webSocketHandler, "/webSockJs").setAllowedOrigins("*") .addInterceptors(new AliceHandShakeInterceptor()).withSockJS(); } }
@Component public class AliceHandShakeInterceptor extends HttpSessionHandshakeInterceptor { @Override public boolean beforeHandshake(ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler wsHandler, Map<String, Object> attributes) throws Exception { return super.beforeHandshake(request, response, wsHandler, attributes); } @Override public void afterHandshake(ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler wsHandler, Exception exception) { super.afterHandshake(request, response, wsHandler, exception); } }
@Component public class AliceWebSocketHandler extends TextWebSocketHandler { private static final Logger LOGGER = LoggerFactory.getLogger(AliceWebSocketHandler.class); private static Map<String, WebSocketSession> SESSION_MAP = Maps.newConcurrentMap(); @Override public void afterConnectionEstablished(WebSocketSession session) throws Exception { LOGGER.debug("[{} : {}] has be connected...", session.getUri(), session.getId()); SESSION_MAP.put(session.getId(), session); } @Override public void handleMessage(WebSocketSession session, WebSocketMessage<?> message) throws Exception { } @Override public void afterConnectionClosed(WebSocketSession session, CloseStatus closeStatus) throws Exception { LOGGER.debug("[{} : {}]", session.getUri(), session.getId()); SESSION_MAP.remove(session.getId()); } @Override public boolean supportsPartialMessages() { return false; } /** * 群发消息 */ public void broadcast(final TextMessage message) throws IOException { for (Map.Entry<String, WebSocketSession> entry : SESSION_MAP.entrySet()) { if (entry.getValue().isOpen()) { new Thread(() -> { try { if (entry.getValue().isOpen()) { entry.getValue().sendMessage(message); } } catch (IOException e) { e.printStackTrace(); } }).start(); } } } }
完事,方法体中,就直接调用broadcast方法就行了,推送消息服务完成。