Spring Boot 整合 WebSocket 模拟聊天

1.后端搭建

  a.创建SpringBoot工程,选择引入Web、Thymeleaf、Websocket依赖,并手动引入其他依赖

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

        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>1.2.62</version>
        </dependency>

        ......

    </dependencies>

  b.配置application.yml配置文件

# 服务端口
server:
  port: 8080

spring:
  # 服务名称
  application:
    name: springboot-websocket
  # thymeleaf热更新
  thymeleaf:
    cache: false

  c.创建WebSocket配置类

@Configuration
public class WebSocketConfig {

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

  d.创建WebSocket消息监听类

@ServerEndpoint("/ws/{userId}")
@Component
public class WebSocket {

    private Logger logger = LoggerFactory.getLogger(this.getClass());

    private Session session;
    private String userId;
    private WebSocketHandler webSocketHandler = (WebSocketHandler) BeanFactoryUtil.getBean("webSocketHandler");

    @OnOpen
    public void onOpen(Session session, @PathParam("userId") String userId) throws Exception {
        this.session = session;
        this.userId = userId;
        webSocketHandler.putSocket(userId, this);
    }

    @OnClose
    public void onClose() {
        webSocketHandler.removeSocket(userId);
    }

    @OnMessage
    public void onMessage(String message, Session session) {
        webSocketHandler.handleMsg(message);
    }

    @OnError
    public void onError(Session session, Throwable error) {
        logger.error("WebSocket发生错误", error);
    }

    //发送消息
    public void sendMessage(String message) throws Exception {
        if (this.session.isOpen()) {
            this.session.getAsyncRemote().sendText(message);
        }
    }

}

  e.创建消息处理Handler

@Component
public class WebSocketHandler {

    private Logger logger = LoggerFactory.getLogger(this.getClass());
    //用户ID与Socket的对应关系
    public Map<String, WebSocket> userSocketMap = new ConcurrentHashMap<>();

    //添加socket
    public void putSocket(String userId, WebSocket socket){
        this.userSocketMap.put(userId, socket);
    }

    //删除socket
    public void removeSocket(String userId){
        this.userSocketMap.remove(userId);
    }

    //发送消息
    public void sendMsg(String userId, String msg){
        WebSocket socket = this.userSocketMap.get(userId);
        if(socket != null){
            try {
                socket.sendMessage(msg);
            }catch (Exception e){
                logger.error("WebSocket发送消息异常", e);
            }
        }
    }

    //群发消息
    public void sendMsg2All(String msg){
        for(WebSocket socket : this.userSocketMap.values()){
            try {
                socket.sendMessage(msg);
            }catch (Exception e){
                logger.error("WebSocket发送消息异常", e);
            }
        }
    }

    //处理消息
    public void handleMsg(String msgJson){
        MsgVo msgVo = JSON.parseObject(msgJson, MsgVo.class);
        if(msgVo.getType() == MsgTypeEnum.ONE2ONE.getCode()){
            this.sendMsg(msgVo.getToId(), msgJson);
            this.sendMsg(msgVo.getFromId(), msgJson);
        }else if(msgVo.getType() == MsgTypeEnum.ONE2ALL.getCode()){
            this.sendMsg2All(msgJson);
        }
    }

}

  f.创建BeanFactory工具类

@Component
public class BeanFactoryUtil implements ApplicationContextAware {

    private static ApplicationContext beanFactory;

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

    public static ApplicationContext getBeanFactory() {
        return beanFactory;
    }

    public static Object getBean(String beanName){
        return beanFactory.getBean(beanName);
    }

}

  g.创建消息VO类

public class MsgVo implements Serializable {

    private int type;
    private String fromId;
    private String toId;
    private String body;

    public int getType() {
        return type;
    }

    public void setType(int type) {
        this.type = type;
    }

    public String getFromId() {
        return fromId;
    }

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

    public String getToId() {
        return toId;
    }

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

    public String getBody() {
        return body;
    }

    public void setBody(String body) {
        this.body = body;
    }
}

  h.创建消息类型枚举

public enum MsgTypeEnum {

    ONE2ONE(1,"私聊"),
    ONE2ALL(2,"公屏");

    private int code;
    private String name;

    public int getCode() {
        return code;
    }
    public void setCode(int code) {
        this.code = code;
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }

    private MsgTypeEnum(int code, String name) {
        this.code = code;
        this.name = name;
    }

    public static MsgTypeEnum getMsgTypeEnum(int code) {
        for(MsgTypeEnum item : MsgTypeEnum.values()) {
            if (item.getCode() == code) {
                return item;
            }
        }
        return null;
    }

}

  i.创建公用Controller

@Controller
public class CommonController {

    @RequestMapping("/")
    public String index(){
        return "home";
    }

}

  j.注意:由于 @ServerEndpoint 是每创建一个连接,则new一个对象,所以无法通过 @Resource 和 @Autowired 注入其他依赖,需要通过调用 ApplicationContext 的 getBean 方法手动获取

2,前端测试

  a.在 resources/templates 下创建 home.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Home</title>
    <style type="text/css">
        h1{
            text-align:center;
        }
        .row{
            margin:10px 0px;
        }
        .col{
            display: inline-block;
            margin:0px 5px;
        }
        .msg-container{
            width: 40%;
            height: 500px;
        }
        .type-select-wrapper{
            margin: 0 0 0 10%;
        }
        .msg-div{
            width: 80%;
            margin:0 0 0 10%;
            height: 300px;
            border:solid 1px;
            overflow: scroll;
        }
    </style>
</head>
<body>
<div>
    <div class="row">
        <div class="col msg-container">
            <div class="row">
                <h1>消息窗口1</h1>
            </div>
            <div class="row">
                <div class="col msg-div"></div>
            </div>
            <div class="row">
                <div class="col type-select-wrapper">
                    <select class="type-select">
                        <option data-value="1">私聊</option>
                        <option selected data-value="2">公屏</option>
                    </select>
                </div>
                <div class="col msg-input-wrapper">
                    <input class="msg-input" type="text"/>
                </div>
                <div class="col">
                    <button id="send-btn-1">发送</button>
                </div>
            </div>
        </div>

        <div class="col msg-container">
            <div class="row">
                <h1>消息窗口2</h1>
            </div>
            <div class="row">
                <div class="col msg-div"></div>
            </div>
            <div class="row">
                <div class="col type-select-wrapper">
                    <select class="type-select">
                        <option data-value="1">私聊</option>
                        <option selected data-value="2">公屏</option>
                    </select>
                </div>
                <div class="col msg-input-wrapper">
                    <input class="msg-input" type="text"/>
                </div>
                <div class="col">
                    <button id="send-btn-2">发送</button>
                </div>
            </div>
        </div>
    </div>

    <div class="row">
        <div class="col msg-container">
            <div class="row">
                <h1>消息窗口3</h1>
            </div>
            <div class="row">
                <div class="col msg-div"></div>
            </div>
            <div class="row">
                <div class="col type-select-wrapper">
                    <select class="type-select">
                        <option data-value="1">私聊</option>
                        <option selected data-value="2">公屏</option>
                    </select>
                </div>
                <div class="col msg-input-wrapper">
                    <input class="msg-input" type="text"/>
                </div>
                <div class="col">
                    <button id="send-btn-3">发送</button>
                </div>
            </div>
        </div>
    </div>
</div>
</body>
<script src="https://code.jquery.com/jquery-3.1.1.min.js"></script>
<script>
    let userIdArray = ["张三", "李四", "王五"];
    let wsArray = new Array(3);

    $(function(){
        initWebSocketFunc(userIdArray[0], 0);
        initWebSocketFunc(userIdArray[1], 1);
        initWebSocketFunc(userIdArray[2], 2);
        $("#send-btn-1").on("click", {num: 0, fromId: userIdArray[0], toId: userIdArray[1]}, sendMsgFunc);
        $("#send-btn-2").on("click",{num: 1, fromId: userIdArray[1], toId: userIdArray[2]}, sendMsgFunc);
        $("#send-btn-3").on("click",{num: 2, fromId: userIdArray[2], toId: userIdArray[0]}, sendMsgFunc);
    });


    let initWebSocketFunc = function(userId, num){
        // 初始化一个 WebSocket 对象
        let ws = new WebSocket("ws://localhost:8080/ws/" + userId);

        // 建立 web socket 连接成功触发事件
        ws.onopen = function () {
            console.log("正在建立连接...");
        };

        // 接收服务端数据时触发事件
        ws.onmessage = function (evt) {
            let msg = JSON.parse(evt.data);
            let out;
            if(msg.type == 1){
                out = "[私聊] " + (msg.fromId==userId?"":msg.fromId) + "" + (msg.toId==userId?"":msg.toId) + " 说:" + msg.body;
            }else if(msg.type == 2){
                out = "[公屏] " + (msg.fromId==userId?"":msg.fromId) + " 说:" + msg.body;
            }
            $($(".msg-div")[num]).append(out + "<br/>");
        };

        // 断开 web socket 连接成功触发事件
        ws.onclose = function () {
            console.log("连接已关闭...");
        };

        wsArray[num] = ws;
    };


    let sendMsgFunc = function(e){
        let num = e.data.num;
        let fromId = e.data.fromId;
        let toId = e.data.toId;

        let type = $($(".type-select")[num]).find("option:selected").attr("data-value");
        let msg = $($(".msg-input")[num]).val();
        let msgData = {
            type: type,
            fromId: fromId,
            body: msg
        };
        if(type == 1){
            msgData.toId = toId;
        }
        let msgStr = JSON.stringify(msgData);
        wsArray[num].send(msgStr);
    }
</script>
</html>

  b.访问 localhost:8080 测试

猜你喜欢

转载自www.cnblogs.com/vettel0329/p/12053664.html