若依添加websock会话

2022-04-07  本文已影响0人  阿杰_96c5

websock会话

pom.xml

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

WebSocketSession缓存工具类

用来存储已经连接服务器的WebSocketSession信息

package com.community.framework.websocket;

import org.springframework.web.socket.WebSocketSession;

import java.io.IOException;
import java.util.concurrent.ConcurrentHashMap;

/**
 * @program: endocrine_admin
 * @description: websocket  session缓存工具类
 * @author: wangj
 * @create: 2022/04/02 15:30
 */

public class WsSessionManager {
    /**
     * 保存连接 session 的地方
     */
    private static ConcurrentHashMap<Long, WebSocketSession> SESSION_POOL = new ConcurrentHashMap<>();

    /**
     * 添加 session
     *
     * @param key
     */
    public static void add(Long key, WebSocketSession session) {
        // 添加 session
        SESSION_POOL.put(key, session);
    }

    /**
     * 删除 session,会返回删除的 session
     *
     * @param key
     * @return
     */
    public static WebSocketSession remove(Long key) {
        // 删除 session
        return SESSION_POOL.remove(key);
    }

    /**
     * 删除并同步关闭连接
     *
     * @param key
     */
    public static void removeAndClose(Long key) {
        WebSocketSession session = remove(key);
        if (session != null) {
            try {
                // 关闭连接
                session.close();
            } catch (IOException e) {
                // todo: 关闭出现异常处理
                e.printStackTrace();
            }
        }
    }

    /**
     * 获得 session
     *
     * @param key
     * @return
     */
    public static WebSocketSession get(Long key) {
        // 获得 session
        return SESSION_POOL.get(key);
    }


    /**
     * 获得在线人数
     *
     * @param
     * @return
     */
    public static int getOnlineNum() {
        // 获得 session
        return SESSION_POOL.size();
    }
}

websocket 配置类

配置过滤器和请求处理器

package com.community.framework.websocket;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.socket.config.annotation.EnableWebSocket;
import org.springframework.web.socket.config.annotation.WebSocketConfigurer;
import org.springframework.web.socket.config.annotation.WebSocketHandlerRegistry;

/**
 * @program: endocrine_admin
 * @description: websocket 配置类
 * @author: wangj
 * @create: 2022/04/02 15:51
 */
@Configuration
@EnableWebSocket
public class WebSocketConfig implements WebSocketConfigurer {

    @Autowired
    private HttpAuthHandler httpAuthHandler;
    @Autowired
    private WsInterceptor wsInterceptor;

    @Override
    public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
        registry
            // SecurityConfig添加 antMatchers("/websocket/**").permitAll() 不验证
            // 在拦截器wsInterceptor中验证token
                .addHandler(httpAuthHandler, "/websocket")  // websocket 请求处理器和请求url
                .addInterceptors(wsInterceptor)     // 拦截器(请求合法性校验)
                .setAllowedOrigins("*");    // 关闭跨域校验
    }
}

websocket 拦截器

这里是建立握手时的事件,分为握手前与握手后
握手前获取请求地址中的参数放入websocket 处理器的WebSocketSession中

可用于请求用户合法性认证

前端通过WebSocket构造函数传递token

拦截器得到token完成校验token合法性

package com.community.framework.websocket;

import com.community.common.constant.Constants;
import com.community.common.constant.UserConstants;
import com.community.common.core.domain.entity.LoginUser;
import com.community.system.service.TokenService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.server.ServerHttpRequest;
import org.springframework.http.server.ServerHttpResponse;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;
import org.springframework.web.socket.WebSocketHandler;
import org.springframework.web.socket.server.HandshakeInterceptor;

import java.util.List;
import java.util.Map;
import java.util.Objects;

/**
 * @program: endocrine_admin
 * @description: websocket 拦截器
 * @author: wangj
 * @create: 2022/04/02 15:36
 */
@Component
public class WsInterceptor implements HandshakeInterceptor {

    @Autowired
    TokenService tokenService;

    @Autowired
    IMemberService memberService;

    @Autowired
    IDoctorService doctorService;

    /**
     * 握手前
     *
     * @param request
     * @param response
     * @param wsHandler
     * @param attributes
     * @return
     * @throws Exception
     */
    @Override
    public boolean beforeHandshake(ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler wsHandler, Map<String, Object> attributes){
        System.out.println("握手开始");
        
        //   var ws = new WebSocket("ws://192.168.1.103:8080/websocket",["token"]);
        // 获取前端ws请求的 protocols 数据,即token
        List<String> tokens = request.getHeaders().get("Sec-WebSocket-Protocol");
        if(CollectionUtils.isEmpty(tokens)){
            return false;
        }
        // token校验
        String token = tokens.get(0).replace(Constants.TOKEN_PREFIX, "");
        LoginUser loginUser = tokenService.getLoginUser(token);
        // 给WebSocketSession添加参数
        attributes.put("uid",loginUser.getUserId());
        response.getHeaders().put("Sec-WebSocket-Protocol",tokens);
        return true;
    }

    /**
     * 握手后
     *
     * @param request
     * @param response
     * @param wsHandler
     * @param exception
     */
    @Override
    public void afterHandshake(ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler wsHandler, Exception exception) {
        System.out.println("握手完成");
    }
}

websocket 事件处理

  1. afterConnectionEstablished 方法是在 socket 连接成功后被触发,同原生注解里的 @OnOpen 功能
  2. **afterConnectionClosed **方法是在 socket 连接关闭后被触发,同原生注解里的 @OnClose 功能
  3. **handleTextMessage **方法是在客户端发送信息时触发,同原生注解里的 @OnMessage 功能
package com.community.framework.websocket;

import com.community.article_message.domain.Message;
import com.community.article_message.service.IMessageService;
import com.community.common.utils.DateUtils;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.web.socket.CloseStatus;
import org.springframework.web.socket.TextMessage;
import org.springframework.web.socket.WebSocketSession;
import org.springframework.web.socket.handler.TextWebSocketHandler;

import java.util.List;

/**
 * @program: endocrine_admin
 * @description: websocket 事件处理
 * @author: wangj
 * @create: 2022/04/02 15:26
 */
@Component
public class HttpAuthHandler extends TextWebSocketHandler {

    @Autowired
    IMessageService messageService;

    private static final ObjectMapper MAPPER = new ObjectMapper();

    /**
     * socket 建立成功事件
     *
     * @param session
     * @throws Exception
     */
    @Override
    public void afterConnectionEstablished(WebSocketSession session) throws Exception {
        Long uid = (Long)session.getAttributes().get("uid");
        WsSessionManager.add(uid, session);
        System.out.println("===========================================");
        System.out.println(uid + " 连接成功");
        System.out.println("在线人数" +  WsSessionManager.getOnlineNum());

        // 返回uid和连接成功信息
        Message msg = new Message();
        msg.setToId(String.valueOf(uid));
        msg.setFromId(String.valueOf(uid));
        msg.setStatus(Message.SYS_SENT);
        msg.setMsg(uid + "连接成功");
        session.sendMessage(new TextMessage(MAPPER.writeValueAsString(msg)));

        // 发送未读的信息
        msg.setToId(String.valueOf(uid));
        msg.setStatus(Message.SENT);
        msg.setFromId(null);
        msg.setMsg(null);

        List<Message> messageList = Optional.ofNullable(messageService.selectMessageList(msg)).orElse(Collections.emptyList());
        messageList.forEach( val ->{
                    try {
                        session.sendMessage(new TextMessage(MAPPER.writeValueAsString(msg)));
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
        );
        // 修改信息为已读
        messageService.updateMessageReceived(messageList.stream().map(Message::getId).collect(Collectors.toList()));
    }

    /**
     * 接收消息事件
     *
     * @param session
     * @param message
     * @throws Exception
     */
    @Override
    protected void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception {
        // 获得客户端传来的消息
        Long fromID = (Long)session.getAttributes().get("uid");
        System.out.println(message.getPayload());

        // 构造要发送的信息对象
        Message msg = MAPPER.readValue(message.getPayload(), Message.class);
        msg.setFromId(String.valueOf(fromID));
        msg.setStatus(Message.SENT);// 消息状态,1-已发送,2-已接收
        msg.setSendDate(DateUtils.getNowDate());

        // 判断to用户是否在线
        WebSocketSession toSession = WsSessionManager.get(Long.parseLong(msg.getToId()));
        if(toSession != null && toSession.isOpen()){
        // 发送消息
        toSession.sendMessage(new TextMessage(MAPPER.writeValueAsString(msg)));
        // 更新消息状态为已读
            msg.setStatus(Message.RECEIVED);
            msg.setReceiveDate(DateUtils.getNowDate());
        }
        messageService.insertMessage(msg);
    }

    /**
     * socket 断开连接时
     *
     * @param session
     * @param status
     * @throws Exception
     */
    @Override
    public void afterConnectionClosed(WebSocketSession session, CloseStatus status){
        Long uid = (Long)session.getAttributes().get("uid");
        WsSessionManager.remove(uid);
        System.out.println("===========================================");
        System.out.println(uid + " 断开连接");
        System.out.println("在线人数" +  WsSessionManager.getOnlineNum());
    }
}

信息实体类

/**
 * 聊天信息记录对象 t_message
 * 
 * @author LCJ
 * @date 2022-03-24
 */
public class Message
{
    private static final long serialVersionUID = 1L;

    public static final int  SENT = 1;  // 已发送
    public static final int  RECEIVED  = 2; // 已接收
    public static final int SYS_SENT = 3; // 系统发送

    /** 主键id */
    private Long id;

    /** 发送消息 */
    @Excel(name = "发送的消息")
    private String msg;

    /** 消息状态,1-已发送,2-已接收 */
    @Excel(name = "消息状态,1-已发送,2-已接收,3-系统发送信息")
    private Integer status;

    /** 发送时间 */
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    @Excel(name = "发送时间", width = 30, dateFormat = "yyyy-MM-dd HH:mm:ss")
    private Date sendDate;

    /** 接收时间 */
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    @Excel(name = "接收时间", width = 30, dateFormat = "yyyy-MM-dd HH:mm:ss")
    private Date receiveDate;

    /** 发送人id */
    @Excel(name = "发送人id")
    private String fromId;

    /** 接收人id */
    @Excel(name = "接收人id")
    private String toId;
}

前端测试页

<!DOCTYPE HTML>
<html>
<head>
  <meta charset="utf-8">
  <title>WebSocket测试页</title>
  <script src="https://cdn.jsdelivr.net/npm/vue@2.6.14/dist/vue.js"></script>
  <!-- 新 Bootstrap5 核心 CSS 文件 -->
  <link rel="stylesheet" href="https://cdn.staticfile.org/twitter-bootstrap/5.1.1/css/bootstrap.min.css">
  <!--  popper.min.js 用于弹窗、提示、下拉菜单 -->
  <script src="https://cdn.staticfile.org/popper.js/2.9.3/umd/popper.min.js"></script>
  <!-- 最新的 Bootstrap5 核心 JavaScript 文件 -->
  <script src="https://cdn.staticfile.org/twitter-bootstrap/5.1.1/js/bootstrap.min.js"></script>

</head>
<body>

<div id= "app" class="container">
<!--  websocket 连接地址输入  -->
  <div>
    <h3 v-text="'WebSocket连接状态: '+ this.message.fromId +  headText"></h3>
    <div class="mb-3 row" >
      <label  class="col-sm-2 col-form-label">WSURL</label>
      <div class="col-sm-10">
        <input type="text"  class="form-control-plaintext" id="url" v-model="url">
      </div>
    </div>
    <div class="mb-3 row">
      <label  class="col-sm-2 col-form-label">protocols</label>
      <div class="col-sm-10">
        <input type="text" class="form-control" id="protocols" v-model="protocols">
      </div>
    </div>
    <button type="submit" class="btn btn-primary" @click="openConection">连接</button>
    <button type="submit" class="btn btn-primary" @click="closeConection">关闭连接</button>
    </div>

  <!-- 对话显示框-->
  <div style="padding:2px;background-color:lightgray;min-height:500px;margin-top: 5px" >
    <div class="d-flex  mb-3" :class=" message.fromId == value.toId ? 'justify-content-end' : 'justify-content-start'" v-for="(value, key) in contentList">
      <div class="p-2 " :class=" message.fromId == value.toId ? 'bg-success' : 'bg-white'"  v-text="value.msg"></div>
    </div>

  </div>


<!-- 对话输入框-->
  <div>
    <div class="mb-3" style="border: 1px lightgray solid;margin-top: 10px">
      <h3>消息输入框</h3>
      <div class="input-group input-group-sm mb-1">
        <span class="input-group-text"  >fromId</span>
        <input type="text" class="form-control" aria-label="Sizing example input" aria-describedby="inputGroup-sizing-sm" v-model="message.fromId" readonly>
      </div>
      <div class="input-group input-group-sm mb-1">
        <span class="input-group-text"  >toId</span>
        <input type="text" class="form-control" aria-label="Sizing example input" aria-describedby="inputGroup-sizing-sm" v-model="message.toId">
      </div>
      <div class="input-group input-group-sm mb-1">
        <span class="input-group-text" >msg</span>
        <input type="text" class="form-control" aria-label="Sizing example input" aria-describedby="inputGroup-sizing-sm" v-model="message.msg">
      </div>
    </div>
    <button type="submit" class="btn btn-primary" @click="sendMsg">发送</button>
  </div>


<div>



  </div>

</div>
<script>
  new Vue({
    el: '#app',
    data: {
      url:'ws://192.168.1.103:8080/endocrine/websocket',
      protocols:'',
      message: {
        "fromId":0,
        "toId":1,
        "msg":"hi 医生",
        "status":1
      },
      contentList: [],
      headText:"未连接",
      websock: null,
      uid:0,
    },
    methods: {
      openConection () {
        // protocols 中传递的是 token
        if(this.protocols==""||this.protocols==null||this.protocols==undefined){
          this.websock = new WebSocket(this.url);
        }else{
          this.websock = new WebSocket(this.url,['Bearer ' + this.protocols]);
        }
        this.websock.onmessage = this.websocketonmessage
        this.websock.onerror = this.websocketonerror
        this.websock.onopen = this.websocketonopen
        this.websock.onclose = this.websocketclose
      },
      websocketonopen () {
        this.headText = "已连接!";
      },
      websocketonerror () { //连接错误
        console.log( 'WebSocket连接失败')
      },
      websocketonmessage (e) { // 数据接收

        let data = JSON.parse(e.data);
        // 建立连接时返回用户id
        if(data.status ==3){
          this.message.fromId = data.toId
        }
        this.contentList.push(data);
      },
      websocketclose (e) {  // 关闭连接
        console.log('已关闭连接', e)
        this.headText = "已关闭!";
      },
      closeConection(){
        this.websock.close()
      },
      sendMsg(){
        this.websock.send(JSON.stringify(this.message))
        this.contentList.push(JSON.parse(JSON.stringify(this.message)));
      }
    },
    destroyed () {
      this.websock.close() // 页面销毁后断开websocket连接
    }

  })
</script>
</body>
</html>
<style>
  .bg-white{
    background-color: white;
  }

</style>

1. 创建WebSocket连接

let url = 'ws://192.168.1.103:8080/endocrine/websocket'  // 服务器地址
let token = 'token'                                     // token字符串
let ws = new WebSocket(url ,['comveeToken_' + token])

2. 连接创建成功后的监听事件

ws.onopen = function(){  //当WebSocket创建成功时,触发onopen事件
   console.log("连接成功");  ws.send("hello"); //将消息发送到服务端
}
ws.onmessage = function(e){
  //当客户端收到服务端发来的消息时,触发onmessage事件,参数e.data包含server传递过来的数据
  console.log(e.data);
}
ws.onclose = function(e){
  //当客户端收到服务端发送的关闭连接请求时,触发onclose事件
  console.log("连接已关闭");
}
ws.onerror = function(e){
  //如果出现连接、处理、接收、发送数据失败的时候触发onerror事件
  console.log('连接发生错误');
}

3. 接收和发送信息

3.1消息对象

let message = {
        "fromId":103,  // 发送方用户id
        "toId":104,    // 接收方用户id
        "msg":"hi",     // 信息
        "status":1      // 1-已发送,2-已接收, 3- 系统发送信息
      }

3.2 接收信息

// 接收信息
ws.onmessage = function(e){  // 监听服务器向客户端发送的信息
  //参数e.data包含server传递过来的数据
   let message = JSON.parse(e.data);
  console.log(e.data);
}

3.3 发送信息

this.wSocket.send(JSON.stringify(this.message)); // 向后端发送数据

4. 关闭连接

this.websock.close() // 页面销毁后断开websocket连接
上一篇 下一篇

猜你喜欢

热点阅读