Springboot集成WebSocket

应用案例(审批消息主动通知-Mes服务集成)

1、依赖引入
<!-- websocket 依赖 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-websocket</artifactId>
            <version>2.2.5.RELEASE</version>
        </dependency>
2、websocket配置类

注册自定义处理器、拦截器

@Configuration
@EnableWebSocket
@Slf4j
public class WebSocketConfig implements WebSocketConfigurer {

    @Resource
    private WebsocketHandler websocketHandler;
    @Resource
    private WebsocketInterceptor websocketInterceptor;

    /**
     * 注册自定义处理器
     *
     * @param webSocketHandlerRegistry
     */
    @Override
    public void registerWebSocketHandlers(WebSocketHandlerRegistry webSocketHandlerRegistry) {
        // 注册处理器
        webSocketHandlerRegistry.addHandler(websocketHandler,"/websocket")
                .setAllowedOrigins("*")
                // 注册拦截器
                .addInterceptors(websocketInterceptor);
    }
}
3、websocket自定义拦截器
@Slf4j
@Component
public class WebsocketInterceptor extends HttpSessionHandshakeInterceptor {

    /**
     * 管理用户信息
     *
     * @param request
     * @param response
     * @param handler
     * @param map
     * @return
     * @throws Exception
     */
    @Override
    public boolean beforeHandshake(ServerHttpRequest request,
                                   ServerHttpResponse response,
                                   WebSocketHandler handler,
                                   Map<String, Object> map) throws Exception {
        String headerKey = "Sec-WebSocket-Protocol";
        HttpHeaders headers = request.getHeaders();
        String info = headers.getFirst("sec-websocket-protocol");
        // 从头信息中获取的数据进行分割
        String[] split = info.split(",");
        log.info(info);
        HttpHeaders responseHeaders = response.getHeaders();
        // 获取到用户信息并set进实体类中
        UserAuthDTO userAuthDTO = new UserAuthDTO();
        userAuthDTO.setToken(split[0]);
        userAuthDTO.setUserId(split[2]);
        userAuthDTO.setCustId(split[1]);
        if(ObjectUtil.isEmpty(userAuthDTO)){
            System.out.println("socket连接失败 ---> token过期 --->"+info);
            response.setStatusCode(HttpStatus.NETWORK_AUTHENTICATION_REQUIRED);
            return false;
        }
        map.put("userAuthDTO",userAuthDTO);
        responseHeaders.add(headerKey,split[0]);
        return super.beforeHandshake(request, response, handler, map);
    }
}
4、websocket自定义处理器
@Slf4j
@Component
public class WebsocketHandler extends AbstractWebSocketHandler {

    /**
     * webSocket连接创建后调用
     *
     * @param session
     */
    @Override
    public void afterConnectionEstablished(WebSocketSession session){
        Map<String, Object> attrMap = session.getAttributes();
        // 获取用户信息
        UserAuthDTO userAuthDTO = (UserAuthDTO) attrMap.get("userAuthDTO");
        long time = System.currentTimeMillis();
        // 为了避免key重复 需要添加时间戳保证key的唯一性
        String key = userAuthDTO.getToken() + "-" + time;
        // 存入集合中
        WebSocketUser.addUserInfoS(userAuthDTO);
        // 用户信息存入list集合
        WebSocketUser.putUserInfo(key,session);
        log.info("连接建立成功");
    }

    /**
     * 接收到消息会调用
     *
     * @param session
     * @param message
     */
    @Override
    protected void handleTextMessage(WebSocketSession session, TextMessage message){
        log.info("收到客户端消息[{}]", message);
    }

    /**
     * 连接关闭会调用
     *
     * @param session
     * @param status
     * @throws Exception
     */
    @Override
    public void afterConnectionClosed(WebSocketSession session, CloseStatus status) throws Exception {
        log.info("客户端关闭连接....");
        //Map<String, Object> attrMap = session.getAttributes();
        // 删除离线session信息
        WebSocketUser.remove(session);
        // 关闭连接
        session.close();
        log.info(status.toString());
        log.info("已关闭socket连接");
    }

    /**
     * 连接出错会调用
     *
     * @param session
     * @param exception
     * @throws Exception
     */
    @Override
    public void handleTransportError(WebSocketSession session, Throwable exception) throws Exception {
        log.error("socket连接出错...");
        exception.printStackTrace();
        // 关闭连接
        session.close();
        log.error("已关闭socket连接");
    }
}
5、websocket消息处理
@Slf4j
@Component
public class WebSocketUser {

    /**
     * WebSocketSession集合
     */
    static Map<String, WebSocketSession> USER_SOCKETS = new ConcurrentHashMap<>();

    /**
     * 用户数据信息
     */
    static List<UserAuthDTO> USER_INFO = new ArrayList<>();

    @Resource
    private ExternalUserTokenClient externalUserTokenClient;

    /**
     * 存储session信息
     *
     * @param key 唯一键
     * @param webSocketSession 用户信息
     */
    public static void putUserInfo(String key, WebSocketSession webSocketSession){
        USER_SOCKETS.put(key, webSocketSession);
    }

    /**
     * 存储用户信息
     *
     * @param userAuthDTO 用户信息
     */
    public static void addUserInfoS(UserAuthDTO userAuthDTO){
        USER_INFO.add(userAuthDTO);
    }

    /**
     * 获取在线用户session列表
     *
     * @return 返回用户集合
     */
    public static Map<String, WebSocketSession> getUsers(){
        return USER_SOCKETS;
    }

    /**
     * 获取在线用户信息集合列表
     *
     * @return 返回用户集合
     */
    public static List<UserAuthDTO> getUserInfo(){
        return USER_INFO;
    }

    /**
     * 移除用户
     *
     * @param webSocketSession 用户信息
     *
     * @return 移除结果
     */
    public static boolean remove(WebSocketSession webSocketSession){
        String key = null;
        // 是否包含websocketsession
        boolean flag = USER_SOCKETS.containsValue(webSocketSession);
        // 如果还包含的话
        if (flag){
            Set<Map.Entry<String, WebSocketSession>> entries = USER_SOCKETS.entrySet();
            for (Map.Entry<String, WebSocketSession> entry : entries)
            {
                WebSocketSession value = entry.getValue();
                if (value.equals(webSocketSession))
                {
                    key = entry.getKey();
                    break;
                }
            }
        }else{
            return true;
        }
        //移除用户信息
        return remove(key);
    }

    /**
     * 移出用户
     *
     * @param key 键
     */
    public static boolean remove(String key){
        log.info("\n 正在移出用户 - {}", key);
        //
        WebSocketSession remove = USER_SOCKETS.remove(key);
        USER_INFO.remove(key);
        if (remove != null){
            boolean containsValue = USER_SOCKETS.containsValue(remove);
            log.info("\n 移出结果 - {}", containsValue ? "失败" : "成功");
            return containsValue;
        }else{
            return true;
        }
    }

    /**
     * 群发消息文本消息
     *
     * @param message 消息内容
     */
    public static void sendMessageToUsersByText(TextMessage message)
    {
        Collection<WebSocketSession> values = USER_SOCKETS.values();
        if(values.isEmpty()){
//            throw new CommonRuntimeException(MesStatusEnum.MES_APPROVAL_57021);
            log.info("客户端在线人数为0");
        }else{
            for (WebSocketSession value : values){
                sendMessageToUserByText(value, message);
            }
        }
    }

    /**
     * 发送文本消息
     *
     * @param session 自己的用户名
     * @param message 消息内容
     */
    public static void sendMessageToUserByText(WebSocketSession session, TextMessage message){
        if (session != null){
            try{
                session.sendMessage(message);
            }catch (IOException e){
                log.error("\n[发送消息异常]", e);
            }
        }else{
            log.info("\n[你已离线]");
        }
    }

    /**
     * 获取用户信息(废弃)
     *
     * @param webSocketSession
     * @return
     */
    public static UserAuthDTO getUserAuthDto(WebSocketSession webSocketSession){
        Map<String, Object> attrMap = webSocketSession.getAttributes();
        // 获取用户信息
        UserAuthDTO userAuthDTO = (UserAuthDTO) attrMap.get("userAuthDTO");
        return userAuthDTO;
    }

    /**
     * 消息发送指定人
     *
     * @param message
     * @param custId
     * @param userList
     */
    @Async
    public void sendMessageTo(TextMessage message, String custId, List<MesMessageRelation> userList){
        // WebSocketUser.getUsers()获取所有在线的用户信息
        Set<Map.Entry<String, WebSocketSession>> entries = WebSocketUser.getUsers().entrySet();
        List<UserAuthDTO> entriesUserInfo = WebSocketUser.getUserInfo();
        for (MesMessageRelation mesMessageRelation : userList) {
            if("02".equals(mesMessageRelation.getObjIdType())){
                for (Map.Entry<String, WebSocketSession> entry : entries){
                    String key = entry.getKey();
                    String[] splitRes = key.split("-");
                    String token = splitRes[0];
                    // 判断token是否过期,如果过期就从map集合中删除,如果没有过期就说明该用户在线 需要比对发送消息
                    // 调用/external/uaa/v1/userToken/{token}接口
                    ResultDTO<ExternalUserTokenResponseDTO> resultDTO = externalUserTokenClient.selectByToken(token);
                    if (!ObjectUtil.equal(resultDTO.getStatus(), HttpStatusEnum.SUCCESS.getCode())) {
                        throw new CommonRuntimeException(resultDTO.getStatus(), resultDTO.getMessage());
                    }
                    // 如果token过期了则需要在集合中将其删除
                    ExternalUserTokenResponseDTO data = resultDTO.getData();
                    if(ObjectUtil.isEmpty(data)){
                        WebSocketUser.remove(token);
                        // 如果没有过期则需要发送消息
                    }else{
                        WebSocketSession session = entry.getValue();
                        if(CollUtil.isNotEmpty(entriesUserInfo)){
                            for (UserAuthDTO userAuthEntry : entriesUserInfo) {
                                if(userAuthEntry.getCustId().equals(custId) && userAuthEntry.getUserId().equals(mesMessageRelation.getObjId())){
                                    //消息发送指定人
                                    try {
                                        session.sendMessage(message);
                                    }catch (Exception e){
                                        e.printStackTrace();
                                    }
                                    log.info("【发送消息】:向{}发送消息:{}",mesMessageRelation.getObjName(),message);
                                    break;
                                }
                            }
                        }
                    }
                }
            }
        }
    }
}
6、用户信息实体类
@Data
public class UserAuthDTO {

    /**
     * 用户token
     */
    private String token;

    /**
     * 用户id
     */
    private String userId;

    /**
     * 客户id
     */
    private String custId;

    /**
     * 用户姓名
     */
    private String userName;
}

配置网关时遇到的问题及解决方案

问题1:路由转发问题

出现的原因:

       Gateway未能配置路由导致经过网关的时候找不到转发路径。

解决的方法:

        因为前端需要发送ws://请求,因此经过网关的时候需要配置转发路由,由于项目中网关路由是通过数据库读取到redis缓存中,因此需要在数据库中添加转发路由数据,之后重启网关服务使得路由加载到redis中,就可以解决此问题。

问题2:请求过滤问题

出现的原因:

       网关过滤器中过滤掉ws和wss请求。

解决的方法:

        修改代码中过滤器部分,对ws和wss请求添加判断解决此问题。

在线websockt测试工具

通过WebSocket King client及postman接口测试的形式

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值