基于SpringBoot与WebSocket实现json消息模块功能(一)

1,581 阅读4分钟

效果展示:

屏幕录制 2021-08-23 时间 19.58.55.gif

实现步骤

  1. 通过idea新建一个springboot的项目

image.png

image.png

image.png 创建项目等待完成

  1. maven依赖整合,以及配置文件编写

image.png 除了选中的创建项目是选中4个依赖以外,附加了这三个,mybatis-plus作为跟数据库之间的orm,druid用于做数据库连接池,之后做扩展准备,fastjson用于将字符串跟Object对象之间做转换。

依赖文件我是用:resource/application.yml 没有的自行创建或者使用properties也可以

image.png 3. 在src目录下创建基础的项目结构

image.png 4. 数据库表的创建(这是基础结构,之后会考虑扩展)

CREATE TABLE `t_message` (
  `id` int NOT NULL AUTO_INCREMENT, // 消息id
  `from_id` int DEFAULT NULL, // 发起消息的用户ID
  `to_id` int DEFAULT NULL,  // 接收消息的用户ID
  `content` text CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci, // 消息主体内容
  `is_read` int DEFAULT '0', // 是否已读
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=5 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
  1. WebSocketConfig配置
@Configuration
public class WebSocketConfig {

    @Bean
    public ServerEndpointExporter serverEndpointExporter(){
        return new ServerEndpointExporter();
    }

}
  1. CorsConfig配置,解决前端js跨域的问题
@Configuration
public class CorsConfig implements WebMvcConfigurer {

    @Override
    public void addCorsMappings(CorsRegistry registry) {
        registry.addMapping("/**")
                .allowedOriginPatterns("*")
                .allowedMethods("*")
                .allowedHeaders("*")
                .allowCredentials(true)
                .maxAge(3600L);
    }
}
  1. 创建消息实体类
Message.java 

// 这里用到了lombok,如果不懂的可以留言或者百度,问的多的话我抽空做一期

@Data
@AllArgsConstructor
@NoArgsConstructor
@ToString
@Builder
public class Message {
    @TableId(type = IdType.AUTO)
    private Integer id;
    private Integer fromId;
    private Integer toId;
    private String content;
    private int isRead=0;
}
  1. 创建message的dao层
// 这里用到了mybatis-plus,如果不懂的可以留言或者百度,问的多的话我抽空做一期

@Mapper
public interface IMessageMapper extends BaseMapper<Message> {
}
  1. 创建message的service以及他的实现
public interface MessageService {

    void insertMessage(Message message);

}

-----------------------------------------------------------------

@Service
public class MessageServiceImpl implements MessageService {

    @Autowired
    IMessageMapper messageMapper;

    @Override
    public void insertMessage(Message message) {
        messageMapper.insert(message);
    }
}
  1. 下面是核心的部分创建WebSocketServer以及工具类

public class WebSocketUtils {

    private final static MessageService messageService = MessageApplication.getBean(MessageServiceImpl.class);  // 获取到messageService

    public static final AtomicInteger ONLINE_COUNT = new AtomicInteger(0); // 存放到当前在线的连接数 

    public static final Map<Integer, Session> ONLINE_USER_SESSIONS = new ConcurrentHashMap<>(); // 存放当前在线用户的session



    /**
     * 推送消息接口
     * @param session 目标session
     * @param message 消息内容
     */
    public static void sendMessage(Session session, String message) {
        if (session == null) { return; }
        RemoteEndpoint.Async async = session.getAsyncRemote(); 发起消息
        if (async == null) { return; }
        async.sendText(message);
    }

    /**
     * 指定推送给某人
     * @param message 消息对象
     */
    public static void sendMessageSingle(Message message) {
        messageService.insertMessage(message); // 将message插入数据库中
        sendMessage(ONLINE_USER_SESSIONS.get(message.getToId()), JSON.toJSONString(message)); // 获取到制定目标的session,发出消息,消息内容是json对象
    }

    /**
     * 全用户推送
     * @param message 消息主体
     */
    public static void sendMessageAll(String message) {
        ONLINE_USER_SESSIONS.forEach((sessionId, session) -> { // 循环对所有的session进行发送消息
            sendMessage(session, message); 
        });
    }
}


@Component
@ServerEndpoint("/v1/websocket/{id}") // 此处是核心注入,创建一个websocket的控制器传入对象是用户id
public class WebSocketServer{

    @OnOpen // 标注的打开websocket链接的方法
    public void onOpen(@PathParam("id") int id,Session session){
        WebSocketUtils.ONLINE_COUNT.incrementAndGet(); // 链接数加一
        WebSocketUtils.ONLINE_USER_SESSIONS.put(id,session); // 将session添加到session池中
    }

    @OnMessage // 标注的监听websocket消息的方法
    public void onMessage(@PathParam("id") int id,String msg){
        Message message = JSON.parseObject(msg,Message.class); // 解析发送的消息,将json字符串转换为message对象
        message.setFromId(id); // 设置fromId=id
        System.out.println(message); // 打印一下消息对象
        WebSocketUtils.sendMessageSingle(message); // 发起消息
    }

    @OnError  // 标注的websocket异常的方法
    public void onError(@PathParam("id") int id,Session session,Throwable error) throws IOException {
        error.printStackTrace(); // 打印异常堆栈
        WebSocketUtils.ONLINE_COUNT.decrementAndGet(); // 链接数减一
        WebSocketUtils.ONLINE_USER_SESSIONS.remove(id); // 移除异常的session
        session.close(); // 关闭session
    }

    @OnClose // 标注的websocket关闭的方法
    public void onClose(Session session,@PathParam("id") int id) throws IOException {
        WebSocketUtils.ONLINE_COUNT.decrementAndGet(); // 链接数减一
        WebSocketUtils.ONLINE_USER_SESSIONS.remove(id); // 移除session
        session.close(); // 关闭session
    }
}

这里会存在的问题: 问题:在写到插入数据库的操作的时候,本来是用的是@Autowired的方式注入的,但是出现了messageService为空的情况

原因:是因为Spring默认的是单例模式,而WebSocket是多实例,每个session对应一个实例

解决方案:目前为了简单,我通过入口类实现了一个接口完成的

  1. 通过实现ApplicationContextAware接口将所有的bean都能获取出来
@SpringBootApplication
public class MessageApplication implements ApplicationContextAware {

    private static ApplicationContext applicationContext;

    public static void main(String[] args) {
        SpringApplication.run(MessageApplication.class, args);
    }

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        MessageApplication.applicationContext = applicationContext;
    }
    public ApplicationContext getApplicationContext(){
        return 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);
    }
}

并且在
WebSocketUtils.java中通过下面的方式获取messageService
private final static MessageService messageService = MessageApplication.getBean(MessageServiceImpl.class);  // 获取到messageService

到这里就完全实现了一个可以传递json消息传输的websocket服务器

测试可以通过网上任意的测试工具进行测试,效果在顶部

总结

万里长征第一步,websocket搭建实际上教程很多,这也算是我个人对于实践的一些总结,本来的目的也是为了实现一个支持查看未读消息的一个服务。之后我应该会对这个服务继续扩展,争取实现成一个可独立应用的消息模块。

如果有什么疑问或者想要探讨的,可以随时跟我联系。