SpringBoot整合 - WebSocket

本文介绍了如何在SpringBoot项目中使用WebSocket,遇到`@ServerEndpoint`无法自动注入依赖的问题,提供了两种解决方案:使用静态对象和动态从Spring容器中获取实例。


今日内容

  • ⑴ ⑵ ⑶ ⑷ ⑸ ⑹ ⑺ ⑻ ⑼

    ① ② ③ ④ ⑤ ⑥ ⑦ ⑧ ⑨


学习目标

  • ⑴ ⑵ ⑶ ⑷ ⑸ ⑹ ⑺ ⑻ ⑼ ⑽

    ⑾ ⑿ ⒀ ⒁ ⒂ ⒃ ⒄ ⒅ ⒆ ⒇

    ① ② ③ ④ ⑤ ⑥ ⑦ ⑧ ⑨


一、引入依赖

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-websocket</artifactId>
    <version>2.7.0</version>
</dependency>

二、核心配置

@Configuration
public class WebSocketConfiguration {

    /**
     * 开启@ServerEndpoint注解声明, WebSocketEndpoint
     *
     * @return
     */
    @Bean
    public ServerEndpointExporter serverEndpointExporter() {
        return new ServerEndpointExporter();
    }

}

三、创建WebSocket工具类

注意: 在@ServerEndpoint修饰的类中, 无法直接通过@Autowired注入service或者bean

原因及解决方法: 查看SpringBoot使用@ServerEndpoint无法@Autowired依赖注入问题解决

@Slf4j
@Component
@ServerEndpoint("/inquire/modelSession/{qccUserId}")
public class ModelSessionWebSocket {

    /**
     * 存储Session集合
     */
    private static ConcurrentHashMap<String, Session> sessionMap = new ConcurrentHashMap<>();

    /**
     * 存储对象集合
     */
    private static ConcurrentHashMap<String, SysUser> userMap = new ConcurrentHashMap<>();

    /**
     * 连接建立时的操作
     *
     * @param session
     * @param qccUserId
     */
    @OnOpen
    public void onOpen(Session session, @PathParam("qccUserId") String qccUserId) throws Exception {
        // 判断该用户是否已经连接
        if (sessionMap.get(qccUserId) != null) {
            log.error("用户已经登录! {}", qccUserId);
            return;
        }
        // 判断是否有该用户
        ISysUserService sysUserService = SpringUtils.getBean(ISysUserService.class);
        SysUser sysUser = sysUserService.lambdaQuery().eq(SysUser::getQccUserId, qccUserId).one();
        if (sysUser == null) {
            log.error("[ModelSessionWebSocket] 提醒您: 未获取到用户信息! {}", qccUserId);
            session.close();
            return;
        }
        // 设置Map
        setMap(session, sysUser);
        // 获取在线人数
        log.info("用户连接: {}, 名称为: {}, 当前在线人数: {}", qccUserId, sysUser.getNickName(), sessionMap.size());
    }

    /**
     * 设置Map集合
     *
     * @param session 会话
     * @param sysUser 用户
     */
    private void setMap(Session session, SysUser sysUser) {
        // 获取用户Id
        String qccUserId = sysUser.getQccUserId();
        // 存储会话到会话集合
        sessionMap.put(qccUserId, session);
        // 存储用户到用户集合
        userMap.put(qccUserId, sysUser);
    }

    /**
     * 连接关闭时操作
     *
     * @param session 会话
     */
    @OnClose
    public void onClose(Session session) {
        String qccUserId = getQccUserIdBySession(session);
        removeMap(session);
        log.info("用户下线: {}, 当前在线人数: {}", qccUserId, sessionMap.size());
    }

    /**
     * 当用户关闭会话时, 移除用户会话记录
     *
     * @param session 会话
     */
    private void removeMap(Session session) {
         String qccUserId = getQccUserIdBySession(session);
         if (StrUtil.isEmpty(qccUserId)){
             // 如果用户不存在, 则直接结束
             return;
         }
         sessionMap.remove(qccUserId);
         userMap.remove(qccUserId);
    }

    /**
     * 根据会话找到用户id
     *
     * @param session 会话
     * @return
     */
    private String getQccUserIdBySession(Session session) {
        for (String qccUserId : sessionMap.keySet()) {
            if (sessionMap.get(qccUserId).getId().equals(session.getId())){
                return qccUserId;
            }
        }
        return null;
    }

    /**
     * 连接异常时操作
     *
     * @param throwable
     */
    @OnError
    public void onError(Throwable throwable) {
        log.error("连接异常: {}", throwable.getMessage());
        throwable.printStackTrace();
    }

    /**
     * 接收到消息时的操作
     *
     * @param session
     * @param message
     */
    @OnMessage
    public void onMessage(Session session, String message) {
        // 根据会话获取当前用户
        SysUser sysUser = getUserBySession(session);
        if (ObjectUtil.isNull(sysUser)) {
            log.info("未获取到用户信息!");
            return;
        }
        // 解析消息
        log.info("用户: {}, 接收到了消息: {}", sysUser.getQccUserId(), message);
        if (!JSONUtil.isTypeJSON(message)) {
            log.info("消息体异常: {}", message);
            return;
        }
        ModelSessionDto modelSessionDto = JSONUtil.toBean(message, ModelSessionDto.class);
        Session toSession = sessionMap.get(sysUser.getQccUserId());
        if (toSession == null){
            log.info("用户: {}不在线!", sysUser.getQccUserId());
            return;
        }
        // 发送消息
        // ModelSessionService modelSessionService = SpringUtils.getBean(ModelSessionService.class);
        // modelSessionService.conversation(modelSessionDto, session, sysUser);
        toSession.getBasicRemote().sendText("收到! 收到! 收到!");
    }

    /**
     * 根据会话找到用户
     *
     * @param session 会话
     * @return
     */
    private SysUser getUserBySession(Session session) {
        String qccUserId = getQccUserIdBySession(session);
        if (StrUtil.isEmpty(qccUserId)) {
            return null;
        }
        return userMap.get(qccUserId);
    }

}

四、以上即可使用, 具体的业务需要自行修改




@ServerEndpoint无法@Autowired依赖注入问题解决

1.问题

websocket里面使用@Autowired注入service或bean时,报空指针异常。

@Component
@ServerEndpoint("/groupChat/{groupNo}/{uuid}")
public class GroupChatWebsocket {

    @Autowired
    private RedisTemplateUtil redisTemplateUtil;
}

2.解决方案

2.1 使用static静态对象

private static RedisTemplateUtil redisUtil;
 
@Autowired
public static void setRedisTemplateUtil(RedisTemplateUtil redisUtil) {
	WebSocketServer.redisUtil = redisUtil;
}

2.2 动态的从spring容器中取出RedisTemplateUtil 推荐方案

package cn.pconline.framework.util;

import org.springframework.beans.BeansException;
import org.springframework.beans.factory.NoUniqueBeanDefinitionException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;

/**
 * 获取spring容器
 * 当一个类实现了这个接口ApplicationContextAware之后,这个类就可以方便获得ApplicationContext中的所有bean。
 * 换句话说,这个类可以直接获取spring配置文件中所有有引用到的bean对象
 * 前提条件需作为一个普通的bean在spring的配置文件中进行注册 
 */
public class SpringCtxUtils implements ApplicationContextAware {

    private static ApplicationContext applicationContext;

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
    	SpringCtxUtils.applicationContext = applicationContext;
    }


    public static <T> T getBean(Class<T> type) {
        try {
            return applicationContext.getBean(type);
        } catch (NoUniqueBeanDefinitionException e) {   // 出现多个,选第一个
            String beanName = applicationContext.getBeanNamesForType(type)[0];
            return applicationContext.getBean(beanName, type);
        }
    }

    public static <T> T getBean(String beanName, Class<T> type) {
        return applicationContext.getBean(beanName, type);
    }
}

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值