SpringBoot(十三)SpringBoot配置webSocket

在PHP版本的博客中,我使用PHP+swoole实现了webscoket即时聊天的功能。

在java版本的博客中,我也想使用webscoket来实现即时聊天的功能,下边是我实现过程的一个记录。

一:在pom.xml中添加记录

<!-- spring-websocket start -->
 <dependency>
     <groupId>org.springframework.boot</groupId>
     <artifactId>spring-boot-starter-websocket</artifactId>
 </dependency>
 <!-- spring-websocket end -->

二:在config目录添加WebSocketConfig.java文件

package com.springbootblog.config;
 
 import org.springframework.context.annotation.Bean;
 import org.springframework.context.annotation.Configuration;
 import org.springframework.web.socket.config.annotation.EnableWebSocket;
 import org.springframework.web.socket.server.standard.ServerEndpointExporter;
 /**
  * @ Description: 开启WebSocket支持
  */
 @Configuration
 @EnableWebSocket
 public class WebSocketConfig
 {
     @Bean
     public ServerEndpointExporter serverEndpointExporter() {
         return new ServerEndpointExporter();
     }
 }

三:解决@ServerEndpoint注解中的@Autowired注解失效的问题

原理是WebSocket是线程安全的,有用户连接时就会创建一个新的端点实例,一个端WebSocket是多对象的,使用的spring却是单例模式。这两者刚好冲突。

所以在@ServerEndpoint注解下不能使用Autowired注解注入对象,那我在webscoket的控制器中也是需要操作redis以及操作数据库的,那我该如何去处理这个问题呢?

我百思不得其解。最后在百度上一位大神的指点下解决了这个问题。

使用从容器中取对象的工具类

在utils目录中添加SpringUtil.java文件

package com.springbootblog.utils;
 
 import org.springframework.beans.BeansException;
 import org.springframework.context.ApplicationContext;
 import org.springframework.context.ApplicationContextAware;
 import org.springframework.stereotype.Component;
 
 @Component
 public class SpringUtil implements ApplicationContextAware
 {
     private static ApplicationContext applicationContext;
 
     @Override
     public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
         SpringUtil.applicationContext = applicationContext;
     }
 
     public ApplicationContext getApplicationContext(){
         return applicationContext;
     }
 
     public static Object getBean(String beanName){
         return applicationContext.getBean(beanName);
     }
 
     public static <T> T getBean(Class<T> clazz){
         try
         {
             return (T)applicationContext.getBean(clazz);
         }
         catch(Exception e)
         {
             return null;
         }
     }
 }

四:在SpringBoot中使用webscoket

package com.springbootblog.controller.fontend;
 
 import com.alibaba.fastjson.JSONObject;
 import com.springbootblog.dao.ChatRecordDao;
 import com.springbootblog.dao.UserDao;
 import com.springbootblog.pojo.ChatRecord;
 import com.springbootblog.pojo.User;
 import com.springbootblog.utils.Function;
 import com.springbootblog.utils.RedisUtil;
 import com.springbootblog.utils.SpringUtil;
 import lombok.extern.slf4j.Slf4j;
 import org.json.JSONException;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Component;
 import org.springframework.stereotype.Service;
 
 import javax.websocket.*;
 import javax.websocket.server.PathParam;
 import javax.websocket.server.ServerEndpoint;
 import java.io.IOException;
 import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
 import java.util.concurrent.CopyOnWriteArraySet;
 
 @Component                              // 组件
 @Slf4j                                  // 日志依赖
 @Service
 @ServerEndpoint("/api/websocket/{sid}") // 配置webscoket访问链接
 public class WebSocketServer
 {
     /**
      * 注入redis工具类
      */
     // @Autowired
     // private RedisUtil redisUtil;
     private RedisUtil redisUtil = SpringUtil.getBean(RedisUtil.class);
 
     /**
      * 自动装载(dao接口注入)
      */
     // @Autowired
     // private UserDao userDao;
     private UserDao userDao = SpringUtil.getBean(UserDao.class);
 
     /**
      * 自动装载(dao接口注入)
      */
     // @Autowired
     // private ChatRecordDao chatDao;
     private ChatRecordDao chatDao = SpringUtil.getBean(ChatRecordDao.class);
 
     //静态变量,用来记录当前在线连接数。应该把它设计成线程安全的。
     public static int onlineCount = 0;
     //concurrent包的线程安全Set,用来存放每个客户端对应的MyWebSocket对象。
     private static CopyOnWriteArraySet<WebSocketServer> webSocketSet = new CopyOnWriteArraySet<>();
 
     //与某个客户端的连接会话,需要通过它来给客户端发送数据
     private Session session;
 
     //接收sid
     public String sid = "";
 
     /**
      * 连接建立成功调用的方法
      */
     @OnOpen
     public void onOpen(Session session, @PathParam("sid") String sid)
     {
         this.session = session;
         //加入set中
         webSocketSet.add(this);
         this.sid = sid;
         //在线数加1
         addOnlineCount();
         try
         {
             sendMessage("conn_success");
             System.out.println("有新窗口开始监听:" + sid + ",当前在线人数为:" + getOnlineCount());
         }
         catch (IOException | EncodeException e)
         {
             System.out.println("websocket IO Exception");
         }
     }
 
     /**
      * 连接关闭调用的方法
      */
     @OnClose
     public void onClose()
     {
         webSocketSet.remove(this);  //从set中删除
         subOnlineCount();           //在线数减1
         //断开连接情况下,更新主板占用情况为释放
         System.out.println("释放的sid为:"+sid);
         //这里写你 释放的时候,要处理的业务
         System.out.println("有一连接关闭!当前在线人数为" + getOnlineCount());
 
     }
 
     /**
      * 收到客户端消息后调用的方法
      * @ Param message 客户端发送过来的消息
      */
     @OnMessage
     public void onMessage(String message, Session session) throws JSONException, IOException, EncodeException {
         this.session = session;
         System.out.println("收到来自窗口" + sid + "的信息:" + message);
         // 获取key对应的值
         String type = Function.stringToJson(message,"type");
         String from_user_id = Function.stringToJson(message,"from_user_id");
         String to_user_id = Function.stringToJson(message,"to_user_id");
         String msg = Function.stringToJson(message,"msg");
         String fd = Function.stringToJson(message,"fd");
 
         // 用户聊天
         if ((from_user_id.equals("") || to_user_id.equals("")) && type.equals("userchat"))
         {
             Map<String,Object> result = new HashMap<>(2);
             result.put("code", -1);
             result.put("msg", "参数错误!请重新登录~");
             sendMessage(message);
         }
 
         if(type.equals("ping"))
         {// 心跳
             // 在线用户列表
             Map<String,Object> array = getWebscoketUserList(from_user_id, session.getId());
             ArrayList<Map<String,Object>> userList = (ArrayList<Map<String, Object>>) array.get("userList");
             Integer totalUnReadNumber = (Integer) array.get("totalUnReadNumber");
 
             // System.out.println("totalUnReadNumber:"+totalUnReadNumber);
             // System.out.println("userList:"+userList);
 
             Map<String,Object> result = new HashMap<>(4);
             result.put("code", 1);
             result.put("type", type);
             result.put("list", userList);
             result.put("totalUnReadNumber", totalUnReadNumber);
             // System.out.println(JSONObject.toJSONString(result));
             sendMessage(JSONObject.toJSONString(result));
         }
         else if(type.equals("userchat"))
         {// 用户一对一聊天
             // System.out.println("type:"+type);
             // System.out.println("from_user_id:"+from_user_id);
             // System.out.println("to_user_id:"+to_user_id);
             // System.out.println("msg:"+msg);
             // System.out.println("fd:"+fd);
             // 保存聊天记录
             saveChatRecord(from_user_id,to_user_id,msg);
             // Map<String,Object> param = new HashMap<>(1);
             // param.put("answer", msg);
             Map<String,Object> result = new HashMap<>(2);
             result.put("response", "");
             result.put("type", type);
             sendMessage(JSONObject.toJSONString(result));
         }
         session.getId();
         //群发消息
         /*for (WebSocketServer item : webSocketSet)
         {
             try
             {
                 item.sendMessage(message);
             }
             catch (IOException e)
             {
                 e.printStackTrace();
             }
         }//*/
     }
 
     public Map<String,Object> saveChatRecord(String from_user_id, String to_user_id,String msg)
     {
         ChatRecord chatRecord = new ChatRecord();
         chatRecord.setFrom_user_id(Integer.valueOf(from_user_id));
         chatRecord.setTo_user_id(Integer.valueOf(to_user_id));
         chatRecord.setMsg(msg);
         Integer res = chatDao.addChatRecord(chatRecord);
         Map<String,Object> result = new HashMap<>(2);
         result.put("code", 1);
         result.put("id", res);
         return result;
     }
 
     /**
      * 获取webscoket链接的用户列表
      * @param user_id
      * @param fd
      * @return
      */
     public Map<String,Object> getWebscoketUserList(String user_id,String fd)
     {
         // 存redis ,10分钟过期,防止用户退出之后redis中存储的数据一直存在。
         // 存储格式:webscoket-用户id : webscoket连接主键
         // 组装redis-key
         String redisKey = "webscoket-" + user_id;
         String webscoketFD = "";
         // 首先获取当前key值(webscoket 连接主键)
         // System.out.println(redisKey);
         // System.out.println("redisUtil:"+redisUtil.get(redisKey));
         // System.out.println("fd:"+fd);
         if(redisUtil.get(redisKey) != null )
         {
             webscoketFD = redisUtil.get(redisKey).toString();
         }
         // System.out.println("webscoketFD:"+webscoketFD);
         // 判断当前主键是否redis中记录的主键一致。(就是判断这个玩意redis中有没有)
         if(!webscoketFD.equals(fd))
         {
             // 设置一个有过期时间的key
             redisUtil.set(redisKey, fd,60*60*2);
             // System.out.println("存储之后的redis:"+redisUtil.get(redisKey));
         }
         // 获取指定前缀的Key(返回一个数组)
         List<String> keyList = redisUtil.findKeysByPattern("webscoket-*");
         // System.out.println(keyList);
         Integer totalUnReadNumber = 0;
         ArrayList<Map<String,Object>> array = new ArrayList<>();
         for (int i = 0; i < keyList.size(); i++)
         {
             Object key = keyList.get(i);
             // System.out.println(key);
             String[] arr = key.toString().split("-");
             if(arr.length < 2)
             {
                 break;
             }
             String to_user_id = arr[1];
             String value = redisUtil.get(key.toString()).toString();
             // System.out.println(to_user_id);
             // System.out.println(value);
             Map<String,Object> temp = new HashMap<>(5);
             temp.put("fd", value);
             // 查未读消息数量
             Integer unReadNumber = chatDao.getUnReadNumber(user_id,to_user_id);
             totalUnReadNumber += unReadNumber;
             // 查用户信息
             User userinfo = userDao.getUserInfoById(to_user_id);
             if(userinfo == null)
             {
                 redisUtil.del(key.toString());
                 continue;
             }
             if(!userinfo.getFigureurl_wx().equals(""))
             {
                 temp.put("userlogo", userinfo.getFigureurl_wx());
             }
             else
             {
                 temp.put("userlogo", userinfo.getFigureurl_qq());
             }
             temp.put("username", userinfo.getNickname());
             temp.put("id", userinfo.getId());
             array.add(temp);
         }
         Map<String,Object> result = new HashMap<>(2);
         result.put("userList", array);
         result.put("totalUnReadNumber", totalUnReadNumber);
         return result;
     }
 
     /**
      * @ Param session
      * @ Param error
      */
     @OnError
     public void onError(Session session, Throwable error)
     {
         // log.error("发生错误");
         error.printStackTrace();
     }
 
     /**
      * 实现服务器主动推送
      */
     public void sendMessage(String message) throws IOException, EncodeException
     {
         this.session.getBasicRemote().sendText(message);
         // this.session.getBasicRemote().sendObject(message);
     }
 
     /**
      * 群发自定义消息
      */
     public static void sendInfo(String message, @PathParam("sid") String sid) throws IOException
     {
         // System.out.println("推送消息到窗口" + sid + ",推送内容:" + message);
 
         for (WebSocketServer item : webSocketSet)
         {
             try
             {
                 //这里可以设定只推送给这个sid的,为null则全部推送
                 if (sid == null)
                 {
                     // item.sendMessage(message);
                 }
                 else if (item.sid.equals(sid))
                 {
                     item.sendMessage(message);
                 }
             }
             catch (IOException e)
             {
                 continue;
             } catch (EncodeException e) {
                 throw new RuntimeException(e);
             }
         }
     }
 
     public static synchronized int getOnlineCount()
     {
         return onlineCount;
     }
 
     public static synchronized void addOnlineCount()
     {
         WebSocketServer.onlineCount++;
     }
 
     public static synchronized void subOnlineCount()
     {
         WebSocketServer.onlineCount--;
     }
 }

以上大概就是我在SpringBoot中对webscoket的基本使用。

有好的建议,请在下方输入你的评论。

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值