webscoket这个一般是运用于聊天场景,我之前做一个项目内容需求是这样的,后端对接一个设备,这个设备新增一个事件的时候会调用后端的接口,然后后端需要发送一个webscoket通知给小程序端用户让他们知悉,话不多说直接上代码。
1、websocket配置类
@Configuration public class WebSocketConfig { /** * 注入一个ServerEndpointExporter,该Bean会自动注册使用@ServerEndpoint注解申明的websocket endpoint */ @Bean public ServerEndpointExporter serverEndpointExporter() { return new ServerEndpointExporter(); } }
2、webscoket工具类
这边前端小程序登录之后需要每隔10s通过webscoket发送一次心跳包给我以证实其在线,我这里规定超过120s没接收到心跳消息即主动断开webscoket连接。使用的是onMessage这个方法
/** * <p> * websocket服务 * </p> @ServerEndpoint(value = "/websocket/{userId}") @Component public class WebSocketService { private static EventService eventService; static { //从 Spring 容器中 获取 startFlowService 对象 eventService = SpringContextUtil.getBean(EventService.class); } private static final Logger log = LoggerFactory.getLogger(WebSocketService.class); /** * 静态变量,用来记录当前在线连接数。应该把它设计成线程安全的。 */ private static AtomicInteger onlineCount = new AtomicInteger(0); /** * concurrent包的线程安全Set,用来存放每个客户端对应的WebSocketServer对象。 */ private static ConcurrentHashMap<String, WebSocketClient> webSocketMap = new ConcurrentHashMap<>(); /** * 与某个客户端的连接会话,需要通过它来给客户端发送数据 */ private Session session; /** * 接收userId */ private String userId = null; /** * 连接建立成功调用的方法 */ @OnOpen public void onOpen(Session session, @PathParam("userId") String userId) { if (!webSocketMap.containsKey(userId)) { onlineCount.incrementAndGet(); // 在线数 +1 } this.session = session; this.userId = userId; WebSocketClient client = WebSocketClient.builder() .session(session) .uri(session.getRequestURI().toString()) .userId(userId) .lastTime(new Date()) .build(); webSocketMap.put(userId, client); log.info("用户连接:" + userId + ",当前连接用户数为:" + onlineCount.get()); System.out.println("连接建立---------------------------------------------------------------------------"); } /** * 连接关闭调用的方法 */ @OnClose public void onClose() { if (webSocketMap.containsKey(userId)) { webSocketMap.remove(userId); // 在线数减1 onlineCount.decrementAndGet(); } log.info(userId + "用户退出,当前在线人数为:" + onlineCount.get()); } /** * 收到客户端消息后调用的方法 * * @param message 客户端发送过来的消息 */ @OnMessage public void onMessage(String message) throws Exception { int deviationValue = 120; Date now = new Date(); if (CollectionUtils.isEmpty(webSocketMap)) { //没有连接时处理 log.error("小程序心跳-连接池为空,非正常连接用户:" + userId); } if (!webSocketMap.containsKey(userId)) { //没有连接时处理 log.error("小程序心跳-此用户不存在连接" + userId); } WebSocketClient webSocketClient = webSocketMap.get(userId); //判断连接是否已超时 if (DateUtil.adjustDateBySecond(webSocketClient.getLastTime(), deviationValue).compareTo(now) < 0) { //断开连接 onClose(); } else { //更新时间 webSocketClient.setLastTime(now); log.info("收到用户:" + userId + ",消息:" + message); } } /** * 连接错误处理 * * @param session * @param error */ @OnError public void onError(Session session, Throwable error) { log.error("用户错误:" + this.userId + ",原因:" + error.getMessage()); error.printStackTrace(); } /** * 关闭指定用户的连接 * * @param userId 用户标识 */ public void closeConnect(String userId) { if (webSocketMap.containsKey(userId)) { webSocketMap.remove(userId); // 在线数减1 onlineCount.decrementAndGet(); } log.info(userId + "用户退出,当前在线人数为:" + onlineCount.get()); } /** * 连接服务器成功后主动推送 */ public void sendMessage(String message) throws IOException { synchronized (session) { this.session.getBasicRemote().sendText(message + "------------------" + session); } } /** * 向指定客户端发送消息 * * @param * @param */ public static void sendInfo(String userId, String message) { log.info("推送给" + userId + ",内容是:" + message); try { WebSocketClient webSocketClient = webSocketMap.get(userId); if (webSocketClient != null) { webSocketClient.getSession().getBasicRemote().sendText(message); } } catch (IOException e) { e.printStackTrace(); throw new RuntimeException(e.getMessage()); } } public static ConcurrentHashMap<String, WebSocketClient> getWebSocketMap() { return webSocketMap; } public static void setWebSocketMap(ConcurrentHashMap<String, WebSocketClient> webSocketMap) { WebSocketService.webSocketMap = webSocketMap; } public Session getSession() { return session; } public void setSession(Session session) { this.session = session; } public String getuserId() { return userId; } public void setuserId(String userId) { this.userId = userId; } }
3、实体类
/** * * * @date 2021-11-22 14:28:15 */ @Data @TableName("ar_event") public class EventEntity implements Serializable { private static final long serialVersionUID = 1L; /** *事件id */ @TableId private Integer eventId; /** *设备id */ @NotNull( message = "设备id不为空") private Integer id; /** * 事件类型 */ @NotNull( message = "事件类型不为空") @Max(value = 15) private Integer eventtype; /** * 二级事件类型,只有eventTypeId=0或14使用 */ private String eventtype2; /** * 事件图片base64格式。 * 人脸抓拍和体温测试必填 */ private String image; /** * 目前只有eventTypeId=13使用 */ private String image2; /** * 事件发生时间 */ @NotNull(message = "事件发生时间不能为空") private Date eventtime; /** * 姓名 */ private String name; /** * 相似度 */ private Float score; /** * 体温 */ private String celsius; /** * 体温状态 * 0:异常 1:正常 */ private Integer status; /** * 是否戴口罩 * 0:否 1:是,2未检测 */ private Integer mask; /** *预留字段json格式 */ private String extra; /** * 事件阅读状态,0:未读 1:已读 */ private Integer isRead; }
4、推送相关消息
@Override public int insertEvent(EventEntity eventEntity) { Date now = new Date(); int deviationValue = 120; int id = eventEntity.getId(); int app_id = deviceDao.selectBydId(id); //TODO 获取ar用户和小程序用户的关联 从而获取小程序用户id String app_user_id = String.valueOf(app_id); //websocket连接池 ConcurrentHashMap<String, WebSocketClient> webSocketMap = WebSocketService.getWebSocketMap(); //连接池为空 或不存在连接 不推送事件 if (CollectionUtils.isEmpty(webSocketMap) || !webSocketMap.containsKey(app_user_id)) { return insert(eventEntity); } //判断连接是否已过期 过期不推送事件 WebSocketClient webSocketClient = webSocketMap.get(app_user_id); if (DateUtil.adjustDateBySecond(webSocketClient.getLastTime(), deviationValue).compareTo(now) < 0) { return insert(eventEntity); } int res = insert(eventEntity); if(res == 1){ //推送事件到前端 Map<String,Object> map = new HashMap<>(3); map.put("user_id",app_user_id); map.put("event_id",eventEntity.getEventId()); WebSocketService.sendInfo(app_user_id, JSON.toJSONString(map)); }else{ log.error("新增失败"); return 0; } return res; }
大体流程就是这样,前端相关我就不发布了,如果有困惑的码友可以私信我。