springboot整合websocket
一、引入maven依赖
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-websocket</artifactId> </dependency>
二、websocket配置类
@Configuration public class WebSocketConfig { /** * ServerEndpointExporter 作用 * * 这个Bean会自动注册使用@ServerEndpoint注解声明的websocket endpoint * * @return */ @Bean public ServerEndpointExporter serverEndpointExporter() { return new ServerEndpointExporter(); } }
三、websocket业务代码
1 @Slf4j 2 @Component 3 @ServerEndpoint("/websocket/{userId}") 4 public class WebSocket { 5 6 /** 7 * 与某个客户端的连接对话,需要通过它来给客户端发送消息 8 */ 9 private Session session; 10 11 /** 12 * 标识当前连接客户端的用户id 13 */ 14 private String userId; 15 16 /** 17 * 用于存所有的连接服务的客户端,这个对象存储是安全的 18 */ 19 private static ConcurrentHashMap<String,WebSocket> webSocketSet = new ConcurrentHashMap<>(); 20 21 22 /** 23 * 开启连接 24 * @param session 25 * @param userId 26 */ 27 @OnOpen 28 public void onOpen(Session session, @PathParam(value = "userId") String userId){ 29 this.session = session; 30 this.userId = userId; 31 // userId是用来表示唯一客户端,如果需要指定发送,需要指定发送通过userId来区分 32 webSocketSet.put(userId,this); 33 log.info("[WebSocket] 连接成功,当前连接人数为:={}",webSocketSet.size()); 34 } 35 36 /** 37 * 退出连接 38 */ 39 @OnClose 40 public void onClose(){ 41 if (webSocketSet.containsKey(this.userId)){ 42 webSocketSet.remove(this.userId); 43 log.info("[WebSocket] 退出成功,当前连接人数为:={}",webSocketSet.size()); 44 } 45 } 46 47 /** 48 * 收到客户端消息后调用的方法 49 * @param message 50 */ 51 @OnMessage 52 public void onMessage(String message, Session session) throws IOException { 53 log.info("收到消息:{}-{}",session,message); 54 session.getBasicRemote().sendText("收到了消息:"+message); 55 } 56 57 58 /** 59 * 发送自定义消息 60 * */ 61 public static void sendInfo(String message,@PathParam("userId") String userId) { 62 if(StringUtils.isNotBlank(userId) && webSocketSet.containsKey(userId)){ 63 try { 64 webSocketSet.get(userId).session.getBasicRemote().sendText(message); 65 } catch (IOException e) { 66 log.error("消息发送失败:{}-{}-{}",userId,message,e.getMessage()); 67 } 68 }else{ 69 log.error("用户{},不在线!",userId); 70 } 71 }
注:springboot+springsecurity整合websocket时,获取登陆的用户信息
@OnOpen public void onOpen(Session session) { this.session = session; //获取用户信息,保存到websocket中 this.userId = SecurityUtils.getUser((Authentication) session.getUserPrincipal()).getId(); if (webSocketMap.containsKey(userId)) { webSocketMap.remove(userId); webSocketMap.put(userId, this); //加入set中 } else { webSocketMap.put(userId, this); //加入set中 addOnlineCount(); //在线数加1 } log.info("用户连接:" + userId + ",当前在线人数为:" + getOnlineCount()); try { sendMessage("连接成功"); } catch (IOException e) { log.error("用户:" + userId + ",网络异常!!!!!!"); } }
四、使用redis实现websocket的session共享
由于websocket的session不能序列化,所以不能直接存储到redis中(而且就算可以存储到redi中,A系统产生的session可能也不能在B系统使用),因此可以采用rredis发布/订阅配置方式实现共享。
4.1、redis发布/订阅配置
@Configuration @EnableCaching public class RedisConfig { public static final String TOPIC_PATTERN = "channel:websocket"; @Autowired private RedisConnectionFactory connectionFactory; @Bean RedisMessageListenerContainer container(MessageListenerAdapter listenerAdapter) { RedisMessageListenerContainer container = new RedisMessageListenerContainer(); container.setConnectionFactory(connectionFactory); // 可以添加多个 messageListener,配置不同的交换机 container.addMessageListener(listenerAdapter, new PatternTopic(TOPIC_PATTERN)); return container; } @Bean MessageListenerAdapter listenerAdapter(RedisReceiver receiver) { //监听消息的对象和方法 return new MessageListenerAdapter(receiver, "onMessage"); } }
4.2、监听类
@Slf4j @Component public class RedisReceiver implements MessageListener { // 字符串的分隔符 public static final String DELIMITER ="✈"; @Override public void onMessage(Message message, byte[] bytes) { byte[] body = message.getBody(); if (body.length>0){ String content = new String(body); if (content.contains(DELIMITER)){ String[] split = StringUtils.split(content, DELIMITER); if (split.length == 2){ WebSocket.sendInfo(split[1],split[0]); } } } } }
4.3、编写测试接口
@ApiOperation("测试webSocket") @PostMapping("testWebSocket") public R testWebSocket(@ApiParam("发送的消息") @RequestParam String message, @ApiParam("接收用户id")@RequestParam String userId){ redisTemplate.convertAndSend(RedisConfig.TOPIC_PATTERN,userId+ RedisReceiver.DELIMITER+message); return R.ok(); }
备注:websocket的在线测试地址:http://www.websocket-test.com/