因为websoket协议问题,采用了see推送,架构使用是SSM+jdk7,spingboot已经集成了,在百度上有,不做代码解释,受限于jdk,spring最少要4.2以上才支持。
前端封装一个SseEmitterServer
package com.work.controller.app; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.web.servlet.mvc.method.annotation.SseEmitter; import java.io.IOException; import java.util.ArrayList; import java.util.List; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.atomic.AtomicInteger; public class SseEmitterServer { private static final Logger logger = LoggerFactory.getLogger(SseEmitterServer.class); /** * 当前连接数 */ private static AtomicInteger count=new AtomicInteger(0); /** * 使用map对象,便于根据userId来获取对应的SseEmitter,或者放redis里面 */ private static Map<String, SseEmitter> sseEmitterMap = new ConcurrentHashMap<>(); /** * 创建用户连接并返回 SseEmitter * * @param userId 用户ID * @return SseEmitter */ public static SseEmitter connect(final String userId) { //在连接的时候,如果存在就移除原来连接,生产新的连接 if (sseEmitterMap.containsKey(userId)){ removeUser(userId); } // 设置超时时间,0表示不过期。默认30秒,超过时间未完成会抛出异常:AsyncRequestTimeoutException SseEmitter sseEmitter = new SseEmitter(0L); //超时回调触发 sseEmitter.onTimeout(new Runnable() { @Override public void run() { sseEmitterMap.remove(userId); } }); // 注册回调 sseEmitter.onCompletion(new Runnable() { @Override public void run() { } }); sseEmitterMap.put(userId, sseEmitter); // 数量+1 count.getAndIncrement(); return sseEmitter; } /** * 给指定用户发送信息 */ public static void sendMessage(String userId,String message) { if (sseEmitterMap.containsKey(userId)) { try { sseEmitterMap.get(userId).send(message); } catch (IOException e) { logger.error("用户[{}]推送异常:{}", userId, e.getMessage()); removeUser(userId); } } } /** * 群发所有人 */ /* public static void batchSendMessage(String message){ for (String key : sseEmitterMap.keySet()) { String value = sseEmitterMap.get(key).toString(); try { sseEmitterMap.get(key).send(message); } catch (IOException e) { logger.error("用户[{}]推送异常:{}", key, e.getMessage()); removeUser(key); } } }*/ /** * 移除用户连接 * @param userId */ public static void removeUser(String userId) { sseEmitterMap.remove(userId); // 数量-1 count.getAndDecrement(); } /** * 获取当前连接信息 */ public static List<String> getIds(){ return new ArrayList<>(sseEmitterMap.keySet()); } /** * 获取当前连接数量 */ public static int getUserCount() { return count.intValue(); } }
在controller层创建长链接
@RequestMapping(value = "/events/{id}", produces = "text/event-stream;charset=UTF-8") @ResponseBody public SseEmitter testSseEmitter(@PathVariable final String id) throws Exception{ // 默认30秒超时,设置为0L则永不超时 SseEmitter sseEmitter = SseEmitterServer.connect(id); return sseEmitter; }
js
<script> if (!!window.EventSource) { var source = new EventSource("http://" + window.location.host + "${zspath}/exterInt/appr/events/123456"); //为http://localhost:8080/testSpringMVC/push s = ''; source.addEventListener('message', function (e) { console.log("see连接打开."+e.data); }); source.addEventListener('open', function (e) { console.log("see连接打开."); }, false); source.addEventListener('error', function (e) { if (e.readyState == EventSource.CLOSED) { console.log("see连接关闭"); } else { console.log(e.readyState); } }, false); } else { console.log("没有sse"); } </script>
需要特别注意的是,部分架构,搭建的时候,并没有设置异构请求返回,需要在web.xml设置<filter>和<servlet>都加上这两个
dispatcher>ASYNC</dispatcher>和async-supported>true</async-supported>配置,才能返回正确的对象
<filter> <filter-name>encodingFilter</filter-name> <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class> <async-supported>true</async-supported> <init-param> <param-name>encoding</param-name> <param-value>UTF-8</param-value> </init-param> <init-param> <param-name>forceEncoding</param-name> <param-value>true</param-value> </init-param> </filter> <filter-mapping> <filter-name>encodingFilter</filter-name> <url-pattern>/*</url-pattern> <dispatcher>REQUEST</dispatcher> <dispatcher>FORWARD</dispatcher> <dispatcher>ASYNC</dispatcher> </filter-mapping> <filter> <filter-name>DruidWebStatFilter</filter-name> <filter-class>com.alibaba.druid.support.http.WebStatFilter</filter-class> <async-supported>true</async-supported> <init-param> <param-name>exclusions</param-name> <param-value>*.js,*.gif,*.jpg,*.png,*.css,*.ico,/druid/*</param-value> </init-param> </filter> <filter-mapping> <filter-name>DruidWebStatFilter</filter-name> <url-pattern>/*</url-pattern> <dispatcher>REQUEST</dispatcher> <dispatcher>FORWARD</dispatcher> <dispatcher>ASYNC</dispatcher> </filter-mapping> <servlet> <servlet-name>DruidStatView</servlet-name> <servlet-class>com.alibaba.druid.support.http.StatViewServlet</servlet-class> <async-supported>true</async-supported> </servlet> <servlet-mapping> <servlet-name>DruidStatView</servlet-name> <url-pattern>/druid/*</url-pattern> </servlet-mapping>