SSM + WebSocket 实现即时通讯

Table of Contents

 

1. pom.xml - 添加所需jar包

2. 创建 websocket 包

2.1 Message 消息类

2.2 User 用户类

2.3 服务器添加 WebSocket 服务 - NzWebSocketConfig

2.4 WebSocket 握手拦截器 -  NzHandShakeInterceptor

2.5 WebSocket 处理器 - NzWebSocketHandler

3. webSocketJsp - 创建一个页面

4. 结果图


1. pom.xml - 添加所需jar包

<!-- WebSocket配置开始-->
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-websocket</artifactId>
    <version>4.1.7.RELEASE</version>
</dependency>
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-messaging</artifactId>
    <version>4.1.7.RELEASE</version>
</dependency>
<dependency>
    <groupId>com.google.code.gson</groupId>
    <artifactId>gson</artifactId>
    <version>2.3.1</version>
</dependency>
<!-- WebSocket配置结束-->

2. 创建 websocket 包

2.1 Message 消息类

import java.sql.Timestamp;

public class Message {

    /**
     * 消息ID
     */
    private String messageId;

    /**
     * 发送者ID
     */
    private String fromId;

    /**
     * 发送者Name
     */
    private String fromName;

    /**
     * 接收者 userSocketSessionMap - ID , 存放在集合中链接 - ID
     */
    private String toId;

    /**
     * 发送的信息
     */
    private String messageText;

    /**
     * 发送的时间
     */
    private Timestamp messageDate;

    public String getMessageId() {
        return messageId;
    }

    public void setMessageId(String messageId) {
        this.messageId = messageId;
    }

    public String getFromId() {
        return fromId;
    }

    public void setFromId(String fromId) {
        this.fromId = fromId;
    }

    public String getFromName() {
        return fromName;
    }

    public void setFromName(String fromName) {
        this.fromName = fromName;
    }

    public String getToId() {
        return toId;
    }

    public void setToId(String toId) {
        this.toId = toId;
    }

    public String getMessageText() {
        return messageText;
    }

    public void setMessageText(String messageText) {
        this.messageText = messageText;
    }

    public Timestamp getMessageDate() {
        return messageDate;
    }

    public void setMessageDate(Timestamp messageDate) {
        this.messageDate = messageDate;
    }

    @Override
    public String toString() {
        return "Message{" +
                "messageId='" + messageId + '\'' +
                ", fromId='" + fromId + '\'' +
                ", fromName='" + fromName + '\'' +
                ", toId='" + toId + '\'' +
                ", messageText='" + messageText + '\'' +
                ", messageDate=" + messageDate +
                '}';
    }
}

2.2 User 用户类

public class User {

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

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

    /**
     * 用户聊天记录
     */
    private String userText;

    /**
     * 用户IP地址
     */
    private String userIp;

    /**
     * 用户最后发送时间
     */
    private String userSendTime;

    public User(String userId) {
        this.userId = userId;
    }

    public String getUserId() {
        return userId;
    }

    public void setUserId(String userId) {
        this.userId = userId;
    }

    public String getUserName() {
        return userName;
    }

    public void setUserName(String userName) {
        this.userName = userName;
    }

    public String getUserText() {
        return userText;
    }

    public void setUserText(String userText) {
        this.userText = userText;
    }

    public String getUserIp() {
        return userIp;
    }

    public void setUserIp(String userIp) {
        this.userIp = userIp;
    }

    public String getUserSendTime() {
        return userSendTime;
    }

    public void setUserSendTime(String userSendTime) {
        this.userSendTime = userSendTime;
    }
}

2.3 服务器添加 WebSocket 服务 - NzWebSocketConfig

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;
import org.springframework.web.socket.config.annotation.EnableWebSocket;
import org.springframework.web.socket.config.annotation.WebSocketConfigurer;
import org.springframework.web.socket.config.annotation.WebSocketHandlerRegistry;

import javax.servlet.http.HttpSession;

@Component
@EnableWebSocket
@Controller
public class NzWebSocketConfig extends WebMvcConfigurerAdapter implements WebSocketConfigurer {

    /**
     * 当WebSocketHandler类被加载时就会创建该Integer,随类而生
     * 测试用的,用于分辨
     */
    public static Integer count = 0;

    /**
     * 跳转到聊天View
     * @param session
     * @return
     */
    @RequestMapping("conSocket")
    public String conSocket(HttpSession session){

        count++;

        session.setAttribute("user", new User("NZ" + count));

        return "nz/webSocketJsp";
    }

    @Autowired
    NzWebSocketHandler handler;

    @Override
    public void registerWebSocketHandlers(WebSocketHandlerRegistry webSocketHandlerRegistry) {

        //添加websocket处理器,添加握手拦截器
        webSocketHandlerRegistry.addHandler(handler, "/websocket").addInterceptors(new NzHandShakeInterceptor());

    }

}

2.4 WebSocket 握手拦截器 -  NzHandShakeInterceptor

import org.springframework.http.server.ServerHttpRequest;
import org.springframework.http.server.ServerHttpResponse;
import org.springframework.http.server.ServletServerHttpRequest;
import org.springframework.web.socket.WebSocketHandler;
import org.springframework.web.socket.server.HandshakeInterceptor;

import javax.servlet.http.HttpSession;
import java.util.Map;

public class NzHandShakeInterceptor implements HandshakeInterceptor {

    /**
     * 握手前的处理
     * @param serverHttpRequest, serverHttpResponse, webSocketHandler, map
     * @return boolean
     */
    @Override
    public boolean beforeHandshake(ServerHttpRequest serverHttpRequest, ServerHttpResponse serverHttpResponse, WebSocketHandler webSocketHandler, Map<String, Object> map) throws Exception {
        System.out.println("Websocket:用户[ID:" + ((ServletServerHttpRequest) serverHttpRequest).getServletRequest().getSession(false).getAttribute("user") + "]已经建立连接");
        if (serverHttpRequest instanceof ServletServerHttpRequest) {
            ServletServerHttpRequest servletRequest = (ServletServerHttpRequest) serverHttpRequest;
            HttpSession session = servletRequest.getServletRequest().getSession(false);
            // 标记用户
            User user = (User) session.getAttribute("user");
            if (user != null) {
                // 为服务器创建WebSocketSession做准备
                map.put("userId", user.getUserId());
                System.out.println("用户id:" + user.getUserId() + " 被加入");
            } else {
                System.out.println("user为空");
                return false;
            }
        }
        return true;
    }

    /**
     * 握手后的处理
     * @param serverHttpRequest, serverHttpResponse, webSocketHandler, excption
     */
    @Override
    public void afterHandshake(ServerHttpRequest serverHttpRequest, ServerHttpResponse serverHttpResponse, WebSocketHandler webSocketHandler, Exception excption) {

    }

}

2.5 WebSocket 处理器 - NzWebSocketHandler

import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.web.socket.*;

import java.io.IOException;
import java.sql.Timestamp;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;

@Component
public class NzWebSocketHandler implements WebSocketHandler {

//    /**
//     * 数据访问层
//     */
//    @Autowired
//    private SendMsgService sendMsgService;

    /**
     * 当WebSocketHandler类被加载时就会创建该Map,随类而生
     */
    public static final Map<String, WebSocketSession> userSocketSessionMap;

    static {
        userSocketSessionMap = new HashMap<>();
    }

    /**
     * 握手实现连接后的处理
     * @param webSocketSession
     */
    @Override
    public void afterConnectionEstablished(WebSocketSession webSocketSession) {
        String userId = (String) webSocketSession.getAttributes().get("userId");
        if (userSocketSessionMap.get(userId) == null) {
            userSocketSessionMap.put(userId, webSocketSession);
        }
    }

    /**
     * 发送信息前的处理
     * @param webSocketSession
     * @param webSocketMessage
     * @throws Exception
     */
    @Override
    public void handleMessage(WebSocketSession webSocketSession, WebSocketMessage<?> webSocketMessage) throws Exception {

        if (webSocketMessage.getPayloadLength() == 0) {
            return;
        }

        //得到Socket通道中的数据并转化为Message对象
        Message msg = new Gson().fromJson(webSocketMessage.getPayload().toString(), Message.class);

        Timestamp now = new Timestamp(System.currentTimeMillis());
        msg.setMessageDate(now);

//        System.err.println("size = " + userSocketSessionMap.size());
//        for (Map.Entry map : userSocketSessionMap.entrySet()) {
//            System.err.println(map.getKey() + "  val= " + map.getValue());
//        }
//        System.err.println("---------------------------------------------------");

        //将信息保存至数据库
        //sendMsgService.addMessage(msg.getFromId(), msg.getFromName(), msg.getToId(), msg.getMessageText(), msg.getMessageDate());

        //发送Socket信息
        sendMessageToUser(msg.getToId() + "", new TextMessage(new GsonBuilder().setDateFormat("yyyy-MM-dd HH:mm:ss").create().toJson(msg)));

    }

    /**
     * 握手传输错误后的处理
     * @param webSocketSession
     * @param throwable
     * @throws Exception
     */
    @Override
    public void handleTransportError(WebSocketSession webSocketSession, Throwable throwable) throws Exception {

    }

    /**
     * 连接关闭前的处理
     * 在此刷新页面就相当于断开WebSocket连接,原本在静态变量userSocketSessionMap中的
     * WebSocketSession会变成关闭状态(close),但是刷新后的第二次连接服务器创建的
     * 新WebSocketSession(open状态)又不会加入到userSocketSessionMap中,所以这样就无法发送消息
     * 因此应当在关闭连接这个切面增加去除userSocketSessionMap中当前处于close状态的WebSocketSession,
     * 让新创建的WebSocketSession(open状态)可以加入到userSocketSessionMap中
     *
     * @param webSocketSession
     * @param closeStatus
     * @throws Exception
     */
    @Override
    public void afterConnectionClosed(WebSocketSession webSocketSession, CloseStatus closeStatus) throws Exception {
        System.out.println("WebSocket:" + webSocketSession.getAttributes().get("userId") + " close connection");
        Iterator<Map.Entry<String, WebSocketSession>> iterator = userSocketSessionMap.entrySet().iterator();
        while (iterator.hasNext()) {
            Map.Entry<String, WebSocketSession> entry = iterator.next();
            if (entry.getValue().getAttributes().get("userId") == webSocketSession.getAttributes().get("userId")) {
                userSocketSessionMap.remove(webSocketSession.getAttributes().get("userId"));
                System.out.println("WebSocket in staticMap:" + webSocketSession.getAttributes().get("userId") + " removed");
            }
        }
    }

    /**
     * 是否支持局部的消息
     * @return
     */
    @Override
    public boolean supportsPartialMessages() {
        return false;
    }

    /**
     * 发送信息的实现
     * @param userId, message
     * @throws IOException
     */
    public void sendMessageToUser(String userId, TextMessage message) throws IOException {
        WebSocketSession session = userSocketSessionMap.get(userId);
        if (session != null && session.isOpen()) {
            session.sendMessage(message);
        }
    }

}

3. webSocketJsp - 创建一个页面

<%@ page language="java" pageEncoding="UTF-8" %>

<!DOCTYPE html>
<html>
<head>
    <title>WebSocket</title>
</head>
<body>

<div>
    <div id="connect-container">
        <div>
            <button id="connect" onclick="connect('/ListMap/websocket');">Connect</button>
        </div>
        <div>
            <textarea id="message" style="width: 350px">Here is a message!</textarea>
        </div>
        <div>
            <button id="send" onclick="send();">Send message</button>
        </div>
    </div>
    <div id="console"></div>
</div>

</body>

<script type="text/javascript" src="${pageContext.request.contextPath }/statics/layui/jquery-2.1.1.js"></script>

<script type="text/javascript">

    var webSocket = null;
    var url = null;

    /**
     * 创建连接
     */
    function connect(urlPath) {

        /**
         * 判断协议 - http or https
         */
        url = isUrl(urlPath);

        /**
         * 显示信息
         */
        console.log("链接url为:" + url);

        /**
         * 建立连接
         */
        webSocket = new WebSocket(url);

        /**
         * 连接成功
         */
        webSocket.onopen = function () {
            console.log('Info: 连接成功.');
        };

        /**
         * 连接失败
         */
        webSocket.onerror = function () {
            console.log('Info: 连接失败.');
        };

        /**
         * 连接断开
         */
        webSocket.onclose = function (event) {
            console.log('Info: 连接断开.');
            console.log(event.data);
        };

        /**
         * 来自服务器的消息
         */
        webSocket.onmessage = function (event) {
            var data = JSON.parse(event.data);
            showText(data['messageDate'] + '<br/>Info: 来自服务器的消息. ' + data['messageText']);
        };

    }

    /**
     * 发送消息
     */
    function send() {

        // 判断是否已连接
        if (webSocket != null) {
            // 新建data对象,并规定属性名与相应的值
            var data = {};
            // 发送者ID
            data['fromId'] = 'NZ-ID-' + new Date().getTime();
            // 发送者Name
            data['fromName'] = 'NZ-NAME-' + new Date().getTime();
            // 接收者ID - userSocketSessionMap - key
            // 注意 - NZ1 代表是第一个连接人的key - 测试
            // 消息发送的也是给第一个连接者
            data['toId'] = 'NZ1';
            // 发送的消息
            data['messageText'] = document.getElementById('message').value;
            // 将对象封装成JSON后发送至服务器
            webSocket.send(JSON.stringify(data));
            // 显示消息
            console.log('Sent: ' + data['messageText']);
        } else {
            console.log('Send: 连接未建立,请连接。');
        }

    }

    /**
     * 显示文本
     * @param message
     */
    function showText(message) {

        var console = $('#console');

        $(console).html($(console).html() + "<br/>" + message);

    }

    /**
     * 判断协议 - http or https
     */
    function isUrl(urlPath) {

        if (window.location.protocol == 'http:') {
            return 'ws://' + window.location.host + urlPath;
        } else {
            return 'wss://' + window.location.host + urlPath;
        }

    }

</script>

</html>

4. 结果图

 参考链接: https://blog.csdn.net/qq_33290787/article/details/52245041

评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值