资讯动态

JavaWeb实战:基于Servlet与WebSocket的一对一聊天系统设计

发布时间:2026/9/8 6:46:50 来源:尧图企业网站定制
简介面向javaweb初学者及课程设计者的一对一网页聊天系统源码基于jspjsajaxservlettomcatmysql技术栈覆盖用户登录注册、好友列表、添加好友、实时收发消息等基础聊天功能。压缩包共54个文件包含12个java源文件及对应class编译文件、8个jsp页面、7个依赖jar包、4个xml配置、sql数据库脚本及eclipse工程配置等资源总大小2.88MB目录保留完整项目结构方便导入运行。目前已有346人学习下载。项目聊天页面由jsp负责消息展示与参数传递jsajax每秒钟向servlet发送轮询请求以获取最新消息其中TalkServlet处理自己发送消息的请求TalkFromServlet负责更新页面消息清晰展示ajax前后端交互与短轮询消息推送逻辑读者可从源码与sql脚本中快速理解数据库表设计和连接池配置项目实现了核心功能但UI尚未设计适合希望快速理解jspservletajax聊天轮询机制、用于学习或二次开发的开发者。1. 项目整体设计与技术选型1.1 为什么选传统 JavaWeb 技术栈而不是 Spring Boot接到这个一对一网页聊天系统需求时第一反应其实是顺手用 Spring Boot 加 WebSocket 几分钟就搭出来了。但冷静下来想了一下作为涉及课程设计、毕业设计或者想快速熟悉底层 Servlet 原理的人而言用传统 JavaWeb 方案反而更有价值原因有三点。第一传统 JavaWeb 能让你完整经历从配置 Servlet、编写 web.xml或者用 Servlet 3.0 注解、打 war 包再到部署 Tomcat 的全过程。这些东西在 Spring Boot 里被隐藏得太深了很多同学做完整个项目都不知道请求是怎么到 Controller 的。第二聊天系统天然涉及长连接、并发写消息这些场景用传统 Servlet 加 WebSocket 接口手动管理线程和会话对理解并发模型很有帮助。第三很多学校的课程大纲和实验环境仍然停留在 Eclipse 加 Tomcat 加 MySQL 的组合用这套技术栈做出来的项目更容易在答辩和验收时讲清楚。这次项目我采用的是 Servlet 3.0 注解版不需要手写 web.xml 映射开发效率接近 Spring MVC同时保留了原生 Servlet 的透明性。Tomcat 版本选的是 8.5 以上因为从 8.5 开始 WebSocket 的支持已经比较稳定了。这里补充一句JavaEE 的 WebSocket 规范从 JSR 356 开始就是标准了Tomcat 7.0.47 之后都内置了实现不需要额外引依赖但处理起来确实需要小心版本差异。1.2 核心业务逻辑梳理在线状态、一对一消息路由、离线消息聊天系统的核心业务其实没有想象中复杂剥开界面和花哨功能后骨架就三件事。在线状态管理是最基础的一层。每个用户登录之后系统要知道这个人当前在哪台服务器、哪个 WebSocket 会话上挂着。一对一聊天项目里在线表其实就是一个 Mapkey 是用户 IDvalue 是当前存活的会话对象。但这里有个很容易出错的设计点同一个用户如果在两个浏览器标签页里同时登录就会产生两个 WebSocket 连接如果不加处理在线表里就会互相覆盖。我在这次项目中做了一个简单的处理同一个用户 ID 如果发起新的连接就把旧的会话主动关掉保证一个用户最多只有一个活跃会话这样消息路由就简单了。一对一消息投递是第二个核心环节。A 给 B 发消息时服务器要先判断 B 是否在线。如果在线直接从在线表里取出 B 的会话调用 session.getBasicRemote().sendText() 把消息推过去。如果不在线消息格式化成离线消息插入 MySQL 消息表等 B 下次登录时再拉取。这里推荐一个比较通用的消息体格式把所有消息类型统一封装成一个 JSON 对象里面带上消息类型、发送者、接收者、内容和时间戳前端拿到后先解析类型再做对应处理。离线消息这块是很多新手容易忽略的但恰恰是评价一个聊天系统是否完整的重要指标。我的做法是消息表用标志位区分已读和未读。用户登录成功、WebSocket 握手完成之后前端一次性拉取所有未读消息展示在会话列表里随后把未读标志更新为已读。这种方式实现成本低效果也够用。1.3 为什么用 WebSocket 而不是 HTTP 轮询网页聊天系统里HTTP 轮询也不是不能用每个请求都透传出完整的报文头和 Cookie服务端压力大实时性还受轮询间隔限制。用 WebSocket 的根本原因在于它解决了 HTTP 协议服务器无法主动推送数据的先天缺陷。打个比方HTTP 协议就像是你去快递站取件每次都要自己跑一趟问有我的快递吗哪怕没有也得去哪怕一天去一百趟也是这个逻辑。WebSocket 则像是快递员把你加进了配送名单有新件直接送上门不需要你再主动去问。聊天场景里消息随时可能进来靠客户端频繁请求来模拟实时性不仅浪费带宽还会造成明显的延迟和资源浪费。而且 JavaWeb 里接入 WebSocket 的成本真的很低一个 ServerEndpoint 注解加几个回调方法就能用对比一下用轮询要写一堆 Ajax 定时器加消息队列乱序处理逻辑WebSocket 明显是更优解。当然WebSocket 也不是银弹它要求服务器维持长连接会占用文件描述符和内存但对一个课程设计级别的聊天系统来说这完全不是问题。项目后续如果想扩容到集群可以把会话状态放到 Redis 里统一管理这就留给有兴趣的同学做了。2. 核心模块设计与实现细节2.1 系统总体功能结构与页面规划功能规划上我按用户体系 消息体系 会话体系三个维度来切分。用户体系负责注册、登录、个人信息维护这是所有功能的地基。消息体系负责聊天的核心数据流转包括单聊消息、离线消息、已读未读状态。会话体系负责在线状态和连接管理也就是 WebSocket 部分。页面方面一共做了三个主要页面。登录页和注册页用一套简洁的模板主要校验用户名密码和重复注册问题。聊天主页面是整个系统的门面左侧是联系人列表显示全部注册用户和各自的在线状态右侧是聊天窗口包含消息展示区域和输入框。顶部展示当前登录用户的信息和退出登录按钮。这套布局和微信网页版很像用户基本不需要学习成本。为了简化开发没有强行做前后端分离。JSP 负责页面的主体结构Ajax 负责异步数据交互WebSocket 负责实时消息推送。后端接口返回统一格式的 JSON 对象结构包括状态码、提示信息和数据体。这样即便以后想把前端改造成 Vue 单页应用后端接口也不用大动。表结构设计上比较简单users 表存用户信息字段有 id、username、password、nickname、avatar、create_time。messages 表存聊天记录字段有 id、from_user_id、to_user_id、content、send_time、is_read。最关键的一个索引要给 to_user_id 加上因为查询离线未读消息的时候主要的查询条件就是这个字段不加索引数据量上来后会非常慢。2.2 WebSocket 会话管理在线表与心跳机制WebSocket 服务端核心类我用 ChatEndpoint 来命名加上 ServerEndpoint 注解value 设为 /chat/{userId}这样路径上直接携带用户 ID省去握手阶段再查一遍用户信息的麻烦。这里有个细节userId 是字符串类型前端连接的时候从 localStorage 里取出来拼到 URL 上就好。在线表的实现是核心中的核心。我用了一个静态的 ConcurrentHashMap 来存在线用户key 是 userIdvalue 是 Session 对象。为什么用 ConcurrentHashMap 而不是 HashMap因为在 WebSocket 的 onMessage 回调方法里多个用户可能同时给不同的人发消息这意味着在线表会被多个线程同时读和写。HashMap 在并发修改时可能造成死循环或数据丢失ConcurrentHashMap 通过分段锁机制保证了线程安全但读操作几乎不受影响。如果没有心跳机制在线表会出现幽灵连接。用户只是关掉了浏览器标签页但没触发 WebSocket 的 onClose比如断网服务端这边 Session 还活着在线表里也还有记录但实际上用户已经收不到消息了。解决思路是服务端定时做心跳检测我设置了每 30 秒遍历一次在线表向所有会话发送一个 ping 类型的消息如果在 60 秒内没有收到任何 pong 回复就主动关闭这个 Session并清理在线表记录。这个处理逻辑不长但能让在线状态准确率提升很多。2.3 消息模型与报文格式设计前后端通信必须有一套统一的协议格式否则时间一长就会乱。我定义了 Message 类先序列化成 JSON 字符串再用 WebSocket 发送核心字段如下。public class Message { private String type; // 消息类型chat / online / offline / ping / pong / read private String fromUser; // 发送者用户 ID private String toUser; // 接收者用户 ID private String content; // 消息内容 private String sendTime; // 发送时间格式 yyyy-MM-dd HH:mm:ss }type 字段用来区分多种业务场景。chat 是普通聊天消息online 和 offline 用于通知联系人列表刷新状态ping 和 pong 是心跳报文read 用于标记已读回执。这么设计的好处是 WebSocket 只作为一条通道具体业务含义全部通过报文内容来区分服务端和前端写起来都很清晰。比如服务端收到前端发来的 chat 类型消息时先解析出 fromUser 和 toUser再查在线表投递消息同时把消息异步写入 MySQL 保存。注意这里要提醒一个实战中的坑MySQL 里保存消息最好直接用 VARCHAR 或 TEXT 类型存原始内容不要在前端展示的时候再做太多转义操作。消息内容里如果包含引号或者特殊字符插入数据时用 PreparedStatement 的 setString 方法不用字符串拼接 SQL否则容易出 SQL 注入问题。2.4 前端页面功能拆解与浏览器如何衔接 WebSocket前端这块我尽量保持原生的方式没有引入框架一个 HTML 文件加一个 JS 文件搞定。JS 里最重要的一个对象就是 WebSocket 实例连接地址形如 ws://localhost:8080/chat/123其中 123 替换成当前登录用户的 ID。页面加载完成后自动建立连接同时注册 onmessage 回调处理服务端推过来的消息。收到消息后第一步是用 JSON.parse 解析报文第二步判断 type 字段。如果是 chat 类型且 toUser 是当前用户就追加一条聊天气泡到消息展示区并播放提示音。如果是 online 或 offline 类型的通知就刷新左侧联系人列表的在线状态。如果收到的是 ping就直接回发一条 pong 保持心跳。这套消息分发机制把整个前端的逻辑组织得井井有条后续新增功能只需要加一个 case 分支。聊天记录展示区域需要实现自动滚动到底部否则用户聊几句之后还要手动拉滚动条体验很差。实现也简单追加完聊天气泡后调用一下 scrollTop scrollHeight 即可。另外输入框要支持回车发送消息但注意在输入中文时用拼音输入法里按回车会确认选词这时候不能触发发送需要判断 event.keyCode 或者 event.isComposing。这是很多新手特别容易忽略的细节用户体验差就差在这种地方。3. 实操过程与核心功能实现3.1 开发环境搭建与项目骨架创建开发环境我推荐用 IDEA 2023 版本原因不多说确实是目前 JavaWeb 开发最顺手的工具。第一步创建一个普通的 Maven 项目不用勾选任何模板直接把 pom.xml 里的 packaging 改为 war。然后是添加依赖Servlet API 和 JSP API 需要以 provided 方式引入因为 Tomcat 运行时自带了这些类如果打成编译依赖部署时容易和容器自带的类冲突。dependencies dependency groupIdjavax.servlet/groupId artifactIdjavax.servlet-api/artifactId version4.0.1/version scopeprovided/scope /dependency dependency groupIdjavax.servlet.jsp/groupId artifactIdjavax.servlet.jsp-api/artifactId version2.3.3/version scopeprovided/scope /dependency dependency groupIdmysql/groupId artifactIdmysql-connector-java/artifactId version8.0.33/version /dependency dependency groupIdcom.alibaba/groupId artifactIddruid/artifactId version1.2.20/version /dependency /dependenciesMySQL 连接池我选了 Druid在 pom 里加一个依赖就行。配置方面写一个 druid.properties 文件放在 resources 目录下后续编写 JDBC 工具类时直接读取配置用 DruidDataSourceFactory 创建数据源。相比每次手动 DriverManager.getConnection()连接池能提升很多数据库访问性能。连接池的初始连接数、最大连接数这些参数按默认值就行对一个聊天系统的数据量来说绰绰有余。项目骨架方面分包按 controller、service、dao、entity、websocket 和 util 来组织。controller 放登录注册相关的 Servletservice 做业务处理dao 封装所有 JDBC 操作entity 放 Users 和 Message 两个实体类websocket 放 ChatEndpointutil 放数据库工具类和 JSON 处理工具。包名我习惯用 com.chat 这种简洁的前缀方便写代码时导入。3.2 登录注册功能与密码加密处理登录注册的 Servlet 里注册接口从请求参数里取出 username 和 password先调用 DAO 查询 username 是否已存在然后做密码加密存储。密码不能明文存数据库这是底线。我用 JDK 内置的 MessageDigest 做 SHA-256 加盐哈希盐值直接用当前时间戳拼在密码后面。String salt String.valueOf(System.currentTimeMillis()); String hashed DigestUtils.sha256Hex(password salt);DigestUtils 是 Spring 的一个工具类但传统 JavaWeb 项目里我更喜欢用 Apache Commons Codec 的 DigestUtils把它加到依赖里就行。登录的时候根据 username 查出 salt 和 hashed把前端传入的密码拼上 salt 再哈希一次对比数据库里存的哈希值一致则登录成功。登录成功后把用户 ID 和昵称放入 Session同时重定向到 chat.jsp 页面。注意实际项目中盐值应该每个用户独立生成并单独存储而不是用时间戳拼出来的。这里为了简化课程设计使用了时间戳作为盐生产环境务必改成随机生成的强盐。3.3 WebSocket 服务端完整实现与消息转发逻辑ChatEndpoint 是整个系统最关键的一个类先贴出核心代码框架。ServerEndpoint(/chat/{userId}) public class ChatEndpoint { private static ConcurrentHashMapString, Session onlineSessions new ConcurrentHashMap(); OnOpen public void onOpen(Session session, PathParam(userId) String userId) { Session old onlineSessions.put(userId, session); if (old ! null) { try { old.close(); } catch (IOException e) { e.printStackTrace(); } } // 广播上线通知 broadcastOnlineStatus(userId, true); } OnMessage public void onMessage(String messageJson, Session session) { Message msg JSON.parseObject(messageJson, Message.class); if (ping.equals(msg.getType())) { // 收到心跳直接返回 pong不需要做持久化 sendMessage(session, buildPongMessage()); return; } if (chat.equals(msg.getType())) { handleChatMessage(msg); } } OnClose public void onClose(Session session, PathParam(userId) String userId) { // 这里有个坑直接根据 userId 删除可能误删新会话 // 需要先比较 session 是否还是当前会话 Session current onlineSessions.get(userId); if (current ! null current.getId().equals(session.getId())) { onlineSessions.remove(userId); broadcastOnlineStatus(userId, false); } } OnError public void onError(Session session, Throwable error) { error.printStackTrace(); } }handleChatMessage 方法是消息转发的核心逻辑是从 Message 对象里取 toUser从在线表里找目标会话如果在线就直接 sendText 过去不在线就保存到 MySQL 消息表。对方在线时消息要写数据库做聊天记录备份不然刷新页面后聊天记录就没了。这里我用的方式是先保存数据库再尝试 WebSocket 推送顺序不能反过来因为推送失败了还是要留记录的。关于广播在线状态实现方式是遍历在线表给所有在线会话发一条类型为 online 或 offline 的 JSON 报文这样每个用户的联系人列表都能实时更新。数据量大了以后广播会有性能问题但这个聊天系统规模顶多十几个用户完全不用过度设计。3.4 前端 JS 核心逻辑解析连接建立与消息渲染前端连接 WebSocket 的代码写得比较直白核心流程就是建立连接、处理收发消息、心跳保活、断开重连。let ws; function connectWebSocket() { const userId localStorage.getItem(userId); ws new WebSocket(ws:// window.location.host /chat/ userId); ws.onopen function() { console.log(WebSocket connected); startHeartbeat(); }; ws.onmessage function(event) { handleMessage(JSON.parse(event.data)); }; ws.onclose function() { // 断线后 3 秒自动重连 setTimeout(connectWebSocket, 3000); }; }页面加载时除了建立 WebSocket 连接还要用 Ajax 拉取联系人列表和未读消息。联系人列表的数据结构是包含用户 ID、昵称、头像和在线状态的数组渲染成左侧列表项。点击某个联系人时聊天区域切换对应的聊天记录这块我用了一个简单的对象记录当前正在跟谁聊发送消息时从这里面取 toUser。消息渲染要考虑消息方向自己发的显示在右侧别人发的显示在左侧这个用消息里的 fromUser 和当前登录用户 ID 比对来判断。聊天气泡的基础样式写在 CSS 里左侧气泡背景用浅灰色右侧气泡背景用蓝色底部加上时间戳。渲染方式直接拼接 HTML 字符串插入到消息容器里课程设计级别的项目不需要考虑 XSS 攻击防护但如果你愿意加一层转义处理代码会更严谨一些。发送消息的流程是先从输入框取出内容组织成 JSON 对象调用 ws.send() 发送同时本地立刻渲染一条自己发出的消息。这里不要等服务器回执再渲染否则会有明显的延迟感。如果发送失败或者 WebSocket 连接断了就把消息标记为发送失败给用户一个红色感叹号的提示。3.5 离线消息拉取与已读回执实现用户登录成功后我让前端调用一个 loadUnreadMessages 接口从数据库查出当前用户所有未读消息按发送者分组后展示。展示完立即调用 markRead 接口把对应的 is_read 字段从 0 更新为 1。这里有个细节如果 A 和 B 同时在聊天A 发消息给 BB 在线能实时收到那么那条消息就不需要出现在未读列表里。我在服务端判断了实时投递成功后就同步把 is_read 置为 1避免 B 下次刷新页面又看到一遍旧消息。已读回执这块做得更细一点的话可以给发送者发一个 read 类型的 WebSocket 报文前端收到后把消息的已读状态从未读变为已读图标。这个功能做出来后整个聊天系统和微信的体验就非常接近了作为加分项写在答辩里很亮眼。4. 常见问题与排错技巧实录4.1 WebSocket 连接 404 或连接被拒绝的排查方案这个问题的出现频率在我做测试时非常高80% 的情况都是项目名或上下文路径没配对。WebSocket 的请求地址必须包含项目的 contextPath也就是部署后访问路径。假设项目名是 chat-web那么 WebSocket 地址应该是 ws://localhost:8080/chat-web/chat/123而不是 ws://localhost:8080/chat/123。另一个常见原因是用 IDEA 内置 Tomcat 调试时热部署会导致 WebSocket 类被重复加载连接被拒绝。解决方式是把 Tomcat 配置里的Deploy applications configured in Tomcat instance选项取消使用外部 Tomcat 发布。这个坑最容易在新手身上出现因为代码改动了IDEA 自动把整个应用重启了但旧会话还没释放浏览器重连时自然连接失败。遇到这个问题最简单直接的办法是重启浏览器页面再试不要一直点刷新按钮。如果还是 404检查一下 JDK 版本和 Tomcat 版本是否兼容。JDK 17 搭配 Tomcat 10 是完全没问题的但 Tomcat 10 默认是 Jakarta EE 规范包名变成了 jakarta.servlet很多旧教程的 javax.servlet 导入直接报编译错误。如果不想折腾就用 Tomcat 9 搭配 javax.servlet 包名比较省心。4.2 消息积压、发送失败和消息丢失的应对消息发送失败首先要判断是 WebSocket 通道断了还是服务器内部报错。我习惯在前端发送消息前做一次 ws.readyState 检查只有等于 WebSocket.OPEN 时才允许发送。如果状态不对提示用户重新连接。后端方面在 try-catch 块里捕获发送异常一旦 sendText 失败就把目标用户从在线表移除并记录日志。离线消息积压的问题一般不严重因为设计时已经保证了发送时对方不在线就写库在线则实时投递加写库所有消息最终都会持久化到 MySQL。真正要担心的是同一对用户之间消息量特别大时未读消息列表可能会一次拉取很多数据导致页面卡顿。优化方式是在查询未读消息的 SQL 外面套一层分页每次只查最近 20 条剩下的按需加载。消息丢失的情况多数出在 WebSocket 连接刚好断开的那一瞬间。比如服务器转发消息时发现对方在线然后拿到 Session 准备发送但此刻对方掉线了sendText 抛出 IOException消息没发送成功但数据库已经插入进去了。这种情况下用户重登后还是能通过离线消息拉取看到内容所以严格说并不算丢失。我在代码里是把 WebSocket 推送成功与否和数据库写入解耦的即使推送失败也不回滚数据库保证消息永远有记录可查。4.3 在线列表不同步、重复登录冲突的处理思路在线列表不同步的表现是用户明明已经下线了联系人头像还亮着。造成这个问题的原因有两个一是用户断网没有触发 onClose系统还认为在线二是用户直接关闭了浏览器进程没有走正常关闭握手流程。我的心跳机制已经覆盖了这两种情况但要注意一点如果用户使用的是手机热点这种网络状况客户端和服务端的长连接很容易被运营商劫持断掉心跳能起到及时发现的作用却不能恢复断掉的连接所以前端才写了 3 秒自动重连的逻辑。重复登录冲突的处理策略我上面已经提到过同一个 userId 的新连接会主动关闭旧连接。这个策略在真实业务中可能会造成用户体验问题比如用户在公司电脑登录了网页回家又用手机登录公司电脑会被顶下线。在课程设计里我宁可选择这样的策略因为它容错率极高不会出现两个会话互相抢消息的情况。如果你想做得更精细可以给用户加一个登录设备列表允许同一账号多处登录消息通过广播推送给所有端。但这样就复杂了因为要管理每个用户的多个会话一对一聊天的一对一特性就无法保证了。这里给出一个排查在线列表状态的技巧在服务端写一个定时任务或者提供一个调试接口打印当前 onlineSessions 的键值对把 userId 和 session.getId() 输出到控制台。排查时先确认在线表里有没有这个用户再看 Session 是否有效基本几分钟就能定位问题。4.4 性能优化与并发安全注意事项并发方面最大的隐患是并发发送消息时操作 HashMap 或者字符串拼接 SQL。我在代码里所有涉及在线表的读写都走 ConcurrentHashMap数据库存储统一用 PreparedStatement单例的 WebSocket 端点类被所有用户共享并发操作时线程安全由 ConcurrentHashMap 保证。如果你是 JDK 8 以上其实用 ConcurrentHashMap 的 compute 方法可以做到原子性的读写删除有兴趣可以自己研究下。为了确保数据库连接不泄漏我在 DAO 层用了 try-with-resources 语法所有 Connection、PreparedStatement、ResultSet 都实现了 AutoCloseable不需要手动在 finally 块里关连接代码简洁也更安全。如果你不信这套写 finally 块手动关也完全可以但千万注意关闭顺序要逆序先关 ResultSet再关 Statement最后关 Connection。Tomcat 默认对 WebSocket 的消息大小限制是 8KB如果用默认配置传图片或者更大的文本会直接抛出异常。我这次项目只传文本所以没改这个限制如果你后续要传图片可以设置 ServerEndpoint 的 maxMessageSize 属性或者用 base64 编码后切片传输。5. 部署调试与扩展思路5.1 部署到 Tomcat 的完整流程与验证清单部署流程我这里捋一遍照做基本不会出问题。先用 IDEA 的 Maven 面板执行 package 命令成功后在 target 目录下会生成 war 包。把这个 war 包复制到 Tomcat 的 webapps 目录下启动 Tomcat它会自动解压部署。然后访问题目地址 http://localhost:8080/项目名/login.jsp就能看到登录页了。验证功能时我建议按这个顺序测试注册两个账号登录 A在另一个浏览器开无痕窗口登录 B。然后 A 给 B 发消息B 应该实时收到。B 不回复A 继续发几条B 退出登录再登录应该能看到离线消息。再测试一下心跳断线场景B 登录后直接拔网线或者杀掉浏览器进程等两三分钟A 的联系人列表里 B 的头像应该变成灰色这个过程自动完成不需要人工干预。5.2 从课程设计到生产系统的演进建议做完了这个版本文档我再补充一个扩展思路。当前系统的在线表是 JVM 内存里的 ConcurrentHashMap一旦要多实例部署到多台服务器在线状态就共享不了了。演进方向是把在线 Session 对应的 Server 地址记录到 Redis每个实例只处理本机上的 Session消息先通过 Redis 发布订阅或者其他 MQ 路由到目标实例再推送给用户。这个改造虽然工作量不小但思维路径很清晰对分布式、消息队列这些概念都有很大的锻炼。如果还想加更多功能消息已读回执、正在输入提示、文件图片发送、聊天记录搜索、历史消息分页加载这些都是很不错的方向。我最推荐加的是历史消息分页加载因为当前版本把所有聊天记录一次性加载消息量大了之后性能会很差。改动量不大前端监听滚动条触底事件带上页签参数请求历史消息后端用 LIMIT 分页查询半天就能完成。最后说一下我对传统 JavaWeb 的评价。这个技术在 Spring Boot 成为主流之后确实显得有些笨重但它的价值恰恰在于笨重让你有机会看清底层的工作方式。我用它做完这个聊天系统之后再回头看 Spring Boot 里的 WebSocket、拦截器、过滤器理解层面完全不一样了。如果你在这个项目的过程中遇到了奇怪的问题建议用断点调试一步步跟进去看不要急着搜答案。自己排查一遍学到的东西比看好几十篇教程都管用。本文还有配套的精品资源点击获取

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价