Spring MVC 实现web Socket向前端实时推送数据

时间:2023-03-09 07:03:28
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方法就行了,推送消息服务完成。