websocket如何区分用户_SpringBoot入门建站全系列(二十七)WebSocket做简单的聊天室...

7789262291f0591e41c3fcc3557138af.png

SpringBoot入门建站全系列(二十七)WebSocket做简单的聊天室

一、概述

WebSocket 是一种网络通信协议。RFC6455 定义了它的通信标准。 WebSocket 是 HTML5 开始提供的一种在单个 TCP 连接上进行全双工通讯的协议。

HTTP 协议是一种无状态的、无连接的、单向的应用层协议。它采用了请求/响应模型。通信请求只能由客户端发起,服务端对请求做出应答处理。 这种通信模型有一个弊端:HTTP 协议无法实现服务器主动向客户端发起消息。 这种单向请求的特点,注定了如果服务器有连续的状态变化,客户端要获知就非常麻烦。大多数 Web 应用程序将通过频繁的异步JavaScript和XML(AJAX)请求实现长轮询。轮询的效率低,非常浪费资源(因为必须不停连接,或者 HTTP 连接始终打开)。

WebSocket复用了HTTP的握手通道,使用ws或wss的统一资源标志符,类似于HTTPS。其中wss表示使用了TLS的Websocket。如:ws://example.com/wsapi或者wss://secure.example.com/wsapi

代码可以在SpringBoot组件化构建https://www.pomit.cn/java/spring/springboot.html中的WebSocket组件中查看,并下载。

首发地址: 品茗IT-首发

如果大家正在寻找一个java的学习环境,或者在开发中遇到困难,可以加入我们的java学习圈,点击即可加入,共同学习,节约学习时间,减少很多在学习中遇到的难题。

二、配置

本文假设你已经引入spring-boot-starter-web。已经是个SpringBoot项目了,如果不会搭建,可以打开这篇文章看一看《SpringBoot入门建站全系列(一)项目建立》。

2.1 Maven依赖

使用WebSocket需要引入spring-boot-starter-websocket。本文使用fastjson做json数据的传输。

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

2.2 配置文件

无需额外配置。

三、WebSocket配置

3.1 WebSocket处理相关配置

Springboot整合WebSocket需要配置websocket的监听url、配置WebSocketInterceptor(连接握手配置)、webSocketHandler(连接成功配置)。

SpringWebSocketConfig:

package com.cff.springbootwork.websocket.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.config.annotation.WebSocketConfigurer;
import org.springframework.web.socket.config.annotation.WebSocketHandlerRegistry;

import com.cff.springbootwork.websocket.handler.WebSocketHandler;
import com.cff.springbootwork.websocket.handler.WebSocketInterceptor;

@Configuration
@EnableWebSocket
public class SpringWebSocketConfig implements WebSocketConfigurer {
    public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
        registry.addHandler(webSocketHandler(), "/websocket").addInterceptors(new WebSocketInterceptor());
        registry.addHandler(webSocketHandler(), "/sockjs").addInterceptors(new WebSocketInterceptor()).withSockJS();
    }

    @Bean
    public WebSocketHandler webSocketHandler() {
        return new WebSocketHandler();
    }

}

3.2 WebSocket的session拦截处理

WebSocketInterceptor对WebSocket的连接进行过滤,可以对连接前和连接后自定义处理。

WebSocketInterceptor:

package com.cff.springbootwork.websocket.handler;

import java.util.Map;

import javax.servlet.http.HttpSession;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.server.ServerHttpRequest;
import org.springframework.http.server.ServerHttpResponse;
import org.springframework.http.server.ServletServerHttpRequest;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import org.springframework.web.socket.WebSocketHandler;
import org.springframework.web.socket.server.support.HttpSessionHandshakeInterceptor;

/**
 * websocket 握手处理器
 * @author fufei
 *
 */
@Component
public class WebSocketInterceptor extends HttpSessionHandshakeInterceptor {
    private Logger log = LoggerFactory.getLogger(this.getClass());

    @Override
    public boolean beforeHandshake(ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler wsHandler,
            Map<String, Object> attributes) throws Exception {
        log.info("收到握手请求。");
        if (request instanceof ServletServerHttpRequest) {
            ServletServerHttpRequest servletRequest = (ServletServerHttpRequest) request;
            HttpSession session = servletRequest.getServletRequest().getSession(false);
            if (session == null) {
                return false;
            }
            // 使用userName区分WebSocketHandler,以便定向发送消息
            String userName = (String) session.getAttribute("userName");
            if (StringUtils.isEmpty(userName)) {
                return false;
            }
            log.info("获取到用户信息:{}", userName);
            attributes.put("userId", userName);
        }
        return super.beforeHandshake(request, response, wsHandler, attributes);
    }

    @Override
    public void afterHandshake(ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler wsHandler,
            Exception ex) {
        log.info("成功握手。");
        super.afterHandshake(request, response, wsHandler, ex);
    }

}

3.3 WebSocket消息处理

WebsocketHandler负责处理消息发送接收的逻辑:

package com.cff.springbootwork.websocket.handler;

import java.io.IOException;
import java.util.List;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.util.StringUtils;
import org.springframework.web.socket.AbstractWebSocketMessage;
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 com.alibaba.fastjson.JSONObject;
import com.cff.springbootwork.websocket.dto.MessageDTO;
import com.cff.springbootwork.websocket.memory.WebSocketUser;

/**
 * websocket消息处理器
 * 
 * @author fufei
 *
 */
public class WebSocketHandler extends TextWebSocketHandler {
    protected Logger logger = LoggerFactory.getLogger(getClass());
    public static WebSocketUser users = new WebSocketUser();

    @Override
    protected void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception {
        logger.info("收到websocket消息:{}", message.toString());
        super.handleTextMessage(session, message);
        String msg = message.getPayload();
        logger.info("收到websocket消息的消息体:{}", msg);
        if (!StringUtils.isEmpty(msg)) {
            MessageDTO messageDTO = JSONObject.parseObject(msg, MessageDTO.class);
            sendMessageToUser(messageDTO.getTargetUserName(), message);
        }
    }

    @Override
    public void afterConnectionEstablished(WebSocketSession session) throws Exception {
        String userName = (String) session.getAttributes().get("userName");
        if (StringUtils.isEmpty(userName)) {
            logger.error("用户不能为空!");
        }

        WebSocketUser.add(userName, session);
        MessageDTO messageDTO = new MessageDTO();
        messageDTO.setFromUserName("");
        messageDTO.setTargetUserName(userName);
        messageDTO.setMessageType(MessageDTO.Type.TYPE_NEW.getMessageType());
        messageDTO.setMessage("欢迎您!");
        session.sendMessage(new TextMessage(JSONObject.toJSONString(messageDTO)));
    }

    @Override
    public void afterConnectionClosed(WebSocketSession session, CloseStatus status) throws Exception {
        String userName = (String) session.getAttributes().get("userName");
        if (StringUtils.isEmpty(userName)) {
            logger.error("用户不能为空!");
        }
        WebSocketUser.removeWebSocketSession(userName, session);
        super.afterConnectionClosed(session, status);
    }

    /**
     * 给某个用户发送消息
     *
     * @param userName
     * @param message
     */
    public void sendMessageToUser(String userName, AbstractWebSocketMessage<?> message) {
        List<WebSocketSession> webUsers = WebSocketUser.getSessionByUserName(userName);
        if (webUsers == null || webUsers.size() == 0) {
            logger.error("发送给{},当前无session", userName);
            return;
        }
        logger.info("发送给{},当前session个数为:{}", userName, webUsers.size());

        for (int i = 0; i < webUsers.size(); i++) {
            WebSocketSession session = webUsers.get(i);
            try {
                if (!session.isOpen()) {
                    WebSocketUser.removeWebSocketSession(userName, session);
                }
                session.sendMessage(message);
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}

3.4 用户与WebSocketSession对应

WebSocketUser是使用内存来存储用户:

package com.cff.springbootwork.websocket.memory;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;

import org.springframework.util.CollectionUtils;
import org.springframework.web.socket.WebSocketSession;

public class WebSocketUser {
    private static Map<String, List<WebSocketSession>> userNameWebsession = new ConcurrentHashMap<>();

    public static void add(String userName, WebSocketSession webSocketSession) {
        userNameWebsession.computeIfAbsent(userName, v -> new ArrayList<WebSocketSession>()).add(webSocketSession);
    }

    /**
     * 根据昵称拿WebSocketSession
     * 
     * @param nickName
     * @return
     */
    public static List<WebSocketSession> getSessionByUserName(String userName) {
        return userNameWebsession.get(userName);
    }

    /**
     * 移除失效的WebSocketSession
     * 
     * @param webSocketSession
     */
    public static void removeWebSocketSession(String userName, WebSocketSession webSocketSession) {
        if (webSocketSession == null)
            return;
        List<WebSocketSession> webSessoin = userNameWebsession.get(userName);
        if (webSessoin == null || CollectionUtils.isEmpty(webSessoin))
            return;
        webSessoin.remove(webSocketSession);
    }

    public static Set<String> getUserList(){
        return userNameWebsession.keySet();
    }
}

四、用户生成、查询及文件发送

如果我们想发送文件,需要上传文件后转为二进制数据并使用websocket发送。另外,这里使用setUser来产生用户,并提供获取当前用户,用户列表的接口。

package com.cff.springbootwork.websocket.web;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;

import javax.servlet.http.HttpServletRequest;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.socket.BinaryMessage;

import com.cff.springbootwork.websocket.dto.ResultModel;
import com.cff.springbootwork.websocket.handler.WebSocketHandler;
import com.cff.springbootwork.websocket.memory.WebSocketUser;

@RestController
@RequestMapping("/im")
public class WebSocketController {
    protected Logger logger = LoggerFactory.getLogger(getClass());

    @Autowired
    private WebSocketHandler websocketHandler;

    @RequestMapping(value = "/fileUpload")
    public ResultModel fileUpload(@RequestParam("userName") String userName, @RequestParam MultipartFile[] myfiles,
            HttpServletRequest request) {
        logger.info("收到发往用户[{}]的文件上传请求;文件数量:{}", userName, myfiles.length);

        int count = 0;
        for (MultipartFile myfile : myfiles) {
            if (myfile.isEmpty()) {
                count++;
            }
            logger.info("文件原名:{};文件类型:", myfile.getOriginalFilename(), myfile.getContentType());
            try (ByteArrayOutputStream swapStream = new ByteArrayOutputStream();
                    InputStream is = myfile.getInputStream();) {
                byte[] buff = new byte[100]; // buff用于存放循环读取的临时数据
                int rc = 0;
                while ((rc = is.read(buff, 0, 100)) > 0) {
                    swapStream.write(buff, 0, rc);
                }
                byte[] in_b = swapStream.toByteArray(); // in_b为转换之后的结果
                logger.info("正在发送文件: ");
                websocketHandler.sendMessageToUser(userName, new BinaryMessage(in_b));
            } catch (IOException e) {
                logger.error("文件原名:{}", myfile.getOriginalFilename(), e);
                e.printStackTrace();
                count++;
                continue;
            }
        }
        return ResultModel.ok(count);
    }

    @RequestMapping(value = "/setUser")
    public ResultModel setUser(@RequestParam("userName") String userName, HttpServletRequest request) {
        logger.info("设置用户[{}]", userName);

        request.getSession().setAttribute("userName", userName);
        return ResultModel.ok();
    }

    @RequestMapping(value = "/user")
    public ResultModel user(HttpServletRequest request) {
        Object userName = request.getSession().getAttribute("userName");
        if(userName == null)return ResultModel.error("无用户");
        return ResultModel.ok(userName);
    }

    @RequestMapping(value = "/userList")
    public ResultModel userList() {
        return ResultModel.ok(WebSocketUser.getUserList());
    }
}

五、聊天室页面

为了实现我们的简单聊天功能,我们需要前端进行配合。

chat.html实现了简单的聊天室,支持文字、表情、文件等:

该html需要很多js配合,下面贴出html和websocket.js,其他js都是很普遍的js,如果需要我发送,加入群聊向群主索要。

<!DOCTYPE html>
<html>

<head>
   <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
    <meta name="viewport" content="width=device-width, initial-scale=1"/>
    <title>品茗IT-WebSocket测试</title>

    <!-- CSS  -->
    <link href="https://lib.baomitu.com/material-design-icons/3.0.1/iconfont/material-icons.min.css" rel="stylesheet">
    <link href="https://lib.baomitu.com/materialize/0.100.2/css/materialize.min.css" type="text/css" rel="stylesheet" media="screen,projection"/>

  <style>
body { text-align:left; margin:0; font:normal 12px Verdana, Arial;
background:#FFEEFF } form { margin:0; font:normal 12px Verdana,
Arial; } table,input { font:normal 12px Verdana, Arial; }
a:link,a:visited{ text-decoration:none; color:#333333; } a:hover{
text-decoration:none; color:#FF6600 } #main { width:400px;
position:absolute; left:600px; top:100px; background:#EFEFFF;
text-align:left; filter:Alpha(opacity=90) } #ChatHead {
text-align:right; padding:3px; border:1px solid #003399;
background:#DCDCFF; font-size:20px; color:#3366FF; cursor:move; }
#ChatHead a:link,#ChatHead a:visited, { font-size:14px;
font-weight:bold; padding:0 3px } #ChatBody { border:1px solid
#003399; border-top:none; padding:2px; } #ChatContent {
height:200px; padding:6px; overflow-y:scroll; word-break: break-all
}#ChatBtn { border-top:1px solid #003399; padding:2px }

  </style>
</head>
<script type="text/javascript">
var ws = null;
var curUser=null;
var chatUser = null;
var imgName = null;
var fileImgSize = 0;
window.onbeforeunload = function()
{
    disconnect(ws);
}

function gs(d) {
    var t = document.getElementById(d);
    if (t) {
        return t.style;
    } else {
        return null;
    }
}
function gs2(d, a) {
    if (d.currentStyle) {
        var curVal = d.currentStyle[a]
    } else {
        var curVal = document.defaultView
                .getComputedStyle(d, null)[a]
    }
    return curVal;
}
function ChatHidden() {
    gs("ChatBody").display = "none";
}
function ChatShow() {
    gs("ChatBody").display = "";
}
function ChatClose() {
    gs("main").display = "none";
}
function ChatNew(userId) {
    gs("main").display = "";
    chatUser = userId;
    $("#ChatUsers").html(chatUser);
    $('.emotion').qqFace({

        id : 'facebox', 

        assign:'saytext', 

        path: './img/arclist/'  //表情存放的路径

    });
}
function ChatClear(obj) {
    $("#ChatContent").html("");
}

function ChatRead() {
    if(document.getElementById(chatUser)){
        document.getElementById(chatUser).setAttribute('src', './img/users.png');
    }
}

function ChatSend(obj) {
    var o = obj.ChatValue;
    var msg = replace_em(o.value);
    if (o.value.length > 0) {
        $("#ChatContent").append(
                "<p align="right"><strong>" + curUser + "(我) :</strong>" + msg
                        + "</p>");
        var number = $("#ChatContent").scrollTop();
        number += 16;
        $("#ChatContent").scrollTop(number);
        if(ws!=null){
            var json={"fromUserName":curUser,"targetUserName":chatUser,"message":o.value,"messageType":"1000"};
            // encodeURI(o.value)
            console.log(json);
            ws.send(JSON.stringify(json));
        }
        o.value = '';
    }

    var img = obj.ChatFile;
    if (img.value.length > 0){
        $("#ChatContent").append(
                "<p align="right"><strong>" + nickName + "(我) :</strong>" + img.value
                        + "</p><br/>");

        imgName = nickName+'(我)';
        fileImgSize = img.files.length;
        //alert(fileImgSize);
        $.ajaxFileUpload({
            //处理文件上传操作的服务器端地址(可以传参数,已亲测可用)
            url:'im/fileUpload?userId='+muserId,
            secureuri:true,                       //是否启用安全提交,默认为false 
            fileElementId:'ChatFile',           //文件选择框的id属性
            dataType:'text',                       //服务器返回的格式,可以是json或xml等
            success:function(data, status){        //服务器响应成功时的处理函数
                //$("#ChatContent").append("<p align="right">" + data + "</p><br/>");
            },
            error:function(data, status, e){ //服务器响应失败时的处理函数
                $("#ChatContent").append('<p align="right">图片上传失败,请重试!!</p><br/>');
                imgName = msgUser;
            }
        });
    }
}
    if (document.getElementById) {
        (function() {
            if (window.opera) {
                document.write("<input type='hidden' id='Q' value=' '>");
            }

            var n = 500;
            var dragok = false;
            var y, x, d, dy, dx;

            function move(e) {
                if (!e)
                    e = window.event;
                if (dragok) {
                    d.style.left = dx + e.clientX - x + "px";
                    d.style.top = dy + e.clientY - y + "px";
                    return false;
                }
            }

            function down(e) {
                if (!e)
                    e = window.event;
                var temp = (typeof e.target != "undefined") ? e.target
                        : e.srcElement;
                if (temp.tagName != "HTML" | "BODY"
                        && temp.className != "dragclass") {
                    temp = (typeof temp.parentNode != "undefined") ? temp.parentNode
                            : temp.parentElement;
                }
                if ('TR' == temp.tagName) {
                    temp = (typeof temp.parentNode != "undefined") ? temp.parentNode
                            : temp.parentElement;
                    temp = (typeof temp.parentNode != "undefined") ? temp.parentNode
                            : temp.parentElement;
                    temp = (typeof temp.parentNode != "undefined") ? temp.parentNode
                            : temp.parentElement;
                }

                if (temp.className == "dragclass") {
                    if (window.opera) {
                        document.getElementById("Q").focus();
                    }
                    dragok = true;
                    temp.style.zIndex = n++;
                    d = temp;
                    dx = parseInt(gs2(temp, "left")) | 0;
                    dy = parseInt(gs2(temp, "top")) | 0;
                    x = e.clientX;
                    y = e.clientY;
                    document.onmousemove = move;
                    return false;
                }
            }

            function up() {
                dragok = false;
                document.onmousemove = null;
            }

            document.onmousedown = down;
            document.onmouseup = up;

        })();
    }

    function toIndex(){
        window.location.href= contextPath + "/index.jsp";
    }

</script>
<body>
<div id="main" class="dragclass" onclick="ChatRead()" style="left: 400px; top: 200px;">
        <div id="ChatUsers" style="width:100px; padding:3px; font-size:15px;float:left; display:inline"></div>
        <div id="ChatHead">
            <a href="#" onclick="ChatHidden();">-</a> <a href="#"
                onclick="ChatShow();">+</a> <a href="#" onclick="ChatClose();">x</a>
        </div>
        <div id="ChatBody">
            <div id="ChatContent"></div>
            <div id="ChatBtn">
                <form action="" name="chat" method="post">
                    <textarea name="ChatValue" id="saytext" rows="3" style="width: 350px"></textarea>
                    <input name="Submit" type="button" value="发送"
                        onclick="ChatSend(this.form);" />
                    <input name="ClearMsg" type="button" value="清空记录"
                        onclick="ChatClear(this.form);" />
                    <input type="button" class="emotion" value="表情">
                    <input id="ChatFile" type="file" name="myfiles"  multiple>   
                </form>
            </div>
        </div>
</div>
<div id="modalAddUser" class="modal modal-fixed-footer" style="max-width:400px;max-height:400px">
    <div class="modal-content">
      <h4>生成用户名</h4>
        <div class="row center">
            <input class="browser-default searchInput" placeholder="请输入用户名" style="margin-top:50px;margin-left:20px;max-width:300px" id="catoryAddText" type="text" >

        </div>
        <div class="row center">                                
            <a class="waves-effect waves-light btn" id="userAddBtn" style="color:white;"><i class="material-icons" style="font-size:1.1rem">添用户</i></a>
        </div>
    </div>
    <div class="modal-footer">
      <a href="#!" class=" modal-action modal-close waves-effect waves-green btn-flat">关闭</a>
    </div>
</div>
<div align="left" style="margin-top: 50px;margin-left: 20px;">
    <p>欢迎您,
        <span id="userName">匿名用户</span>
    </p>
    <a id="addUser" class="btn waves-effect waves-light white cyan-text" style="border-radius: 40px;">添加用户</a>
    <p id="content"></p>
</div>
<script src="https://lib.baomitu.com/jquery/3.3.0/jquery.min.js"></script>
<script src="https://lib.baomitu.com/materialize/0.100.2/js/materialize.min.js"></script>
<script src="./js/websocket.js"></script>
<script src="./js/ajaxfileupload.js"></script>
<script src="./js/jquery-browser.js"></script>
<script src="./js/jquery.qqFace.js"></script>
<script>
function getUser(){
    $.ajax({
        type : "get",
        url : "im/user",
        dataType : "json",
        data : {} ,
        success : function(data) {
            if(data.errorCode == "0000"){
                $("#userName").html(data.data);
                curUser = data.data;
            }
        },
        error : function(XMLHttpRequest, textStatus, errorThrown) {
            alert(errorThrown);
        }
    });
}   
function addUser(userName){
    $.ajax({
        type : "post",
        url : "im/setUser",
        dataType : "json",
        data : {"userName":userName} ,
        success : function(data) {
            if(data.errorCode == "0000"){
                $("#userName").html(userName);
                curUser = data.data;
            }
        },
        error : function(XMLHttpRequest, textStatus, errorThrown) {
            alert(errorThrown);
        }
    });
}
function userList(){
    $.ajax({
        type : "get",
        url : "im/userList",
        dataType : "json",
        data : {} ,
        success : function(data) {
            if(data.errorCode == "0000"){
                var content = "";
                for(var i =0;i<data.data.length;i++){
                    var userId = data.data[i];
                    content += "<img src="./img/msgget.gif" id=""
                        + userId
                        + "" alt="" style="cursor: pointer" width='40px' "
                        + "onclick="ChatNew('"+userId+"')" />"
                        + userId
                        + "<br><br>";
                }
                $("#content").append(content);
            }
        },
        error : function(XMLHttpRequest, textStatus, errorThrown) {
            alert(errorThrown);
        }
    });
}

window.onbeforeunload = function(){
    disconnect(ws);
}
$(function () {
    $('.modal').modal({
        dismissible: true, // 点击模态外面模态消失关闭
        opacity: 0.1, // 相对于背景的不透明度
        in_duration: 300, // 显示特效的时间
        out_duration: 200, // 消失特效时间
        starting_top: '80%', // 启动时的样式属性
        ending_top: '20%', // 结束时的样式属性
        ready: function(modal, trigger) { // 模态加载完成触发事件

        },
        complete: function() { 

        } // 关闭时触发的事件
    });
    getUser();
    $("#addUser").click(function() {
        $('#modalAddUser').modal('open');   
    });

    $("#userAddBtn").click(function() {
        var catory = $('#catoryAddText').val();
        addUser(catory);
    });
    userList();
    if (ws == null) {
        var url = getUrl();
        //alert("url:"+url);  
        if (!url) {  
            return;  
        }  
        console.log(url);
        ws = new WebSocket(url);  
        connect(ws);
        ChatClose();
    }
});
</script>
</body>
<script>
var _hmt = _hmt || [];
(function() {
  var hm = document.createElement("script");
  hm.src = "https://hm.baidu.com/hm.js?e553ae2bb23494dee9b6f43415a1ce5a";
  var s = document.getElementsByTagName("script")[0]; 
  s.parentNode.insertBefore(hm, s);
})();
</script>

</html>

这个html需要websocket.js配合:

聊天室界面如下:

595d66994d2905a52a6c1daf1e0f3251.png

六、过程中用到的其他实体

MessageDTO:

ResultModel:

详细完整的代码,可以访问品茗IT-博客《SpringBoot入门建站全系列(二十七)WebSocket做简单的聊天室》进行查看

品茗IT-博客专题:https://www.pomit.cn/lecture.html汇总了Spring专题、Springboot专题、SpringCloud专题、web基础配置专题。

快速构建项目

Spring项目快速开发工具:

一键快速构建Spring项目工具

一键快速构建SpringBoot项目工具

一键快速构建SpringCloud项目工具

一站式Springboot项目生成

Mysql一键生成Mybatis注解Mapper

Spring组件化构建

SpringBoot组件化构建

SpringCloud服务化构建

喜欢这篇文章么,喜欢就加入我们一起讨论SpringBoot使用吧!

726bd49f2b9008c42de6fd8c09f378fe.png
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
提供的源码资源涵盖了Java应用等多个领域,每个领域都包含了丰富的实例和项目。这些源码都是基于各自平台的最新技术和标准编写,确保了在对应环境下能够无缝运行。同时,源码中配备了详细的注释和文档,帮助用户快速理解代码结构和实现逻辑。 适用人群: 适合毕业设计、课程设计作业。这些源码资源特别适合大学生群体。无论你是计算机相关专业的学生,还是对其他领域编程感兴趣的学生,这些资源都能为你提供宝贵的学习和实践机会。通过学习和运行这些源码,你可以掌握各平台开发的基础知识,提升编程能力和项目实战经验。 使用场景及目标: 在学习阶段,你可以利用这些源码资源进行课程实践、课外项目或毕业设计。通过分析和运行源码,你将深入了解各平台开发的技术细节和最佳实践,逐步培养起自己的项目开发和问题解决能力。此外,在求职或创业过程中,具备跨平台开发能力的大学生将更具竞争力。 其他说明: 为了确保源码资源的可运行性和易用性,特别注意了以下几点:首先,每份源码都提供了详细的运行环境和依赖说明,确保用户能够轻松搭建起开发环境;其次,源码中的注释和文档都非常完善,方便用户快速上手和理解代码;最后,我会定期更新这些源码资源,以适应各平台技术的最新发展和市场需求。 所有源码均经过严格测试,可以直接运行,可以放心下载使用。有任何使用问题欢迎随时与博主沟通,第一时间进行解答!

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值