JavaWeb一对一网页聊天系统实战:从登录到消息落地的完整闭环

发布时间:2026/11/2 19:10:18
JavaWeb一对一网页聊天系统实战:从登录到消息落地的完整闭环 简介这份资源是一套基于JSPJavaScriptAjaxServletTomcatMySQL实现的一对一网页聊天系统源码面向正在学习JavaWeb、需要课程设计或入门项目实战的学生与开发者重点解决聊天消息实时收发与页面局部刷新的实现问题。压缩包共54个文件、约2.88MB包含12个Java类与12个class文件承载Servlet与业务逻辑8个JSP页面负责登录、注册、好友列表与聊天界面另有7个jar依赖、4个xml配置及js脚本等结构完整可直接导入Eclipse运行。目前已有348人学习下载。项目核心在于Ajax每秒轮询Servlet获取新消息TalkServlet处理发送请求、TalkFromServlet负责消息更新配合c3p0连接池与sql.txt建表脚本便于读者理解前后端交互流程、消息刷新机制与数据库设计是掌握JavaWeb聊天功能实现细节的实用参考UI部分尚未设计适合在此基础上自行扩展界面与功能。1. 一对一网页聊天系统从登录到消息落地的完整闭环两个人同时打开浏览器一个发消息另一个页面不刷新就能收到——这件事听起来简单真动手做的时候麻烦全在细节里会话怎么建、消息怎么推、断线怎么补、历史怎么存。这个 JavaWeb 一对一网页聊天系统就是围绕这条主线搭起来的完整工程适合正在学 JavaWeb、想做课程设计或想拿一个能跑通的即时通讯 Demo 二次开发的人。它不追求大而全而是把「用户登录 → 建立会话 → 双向收发 → 消息持久化」这条链路做扎实代码量可控结构清晰新手照着能跑起来熟手能直接在上面加功能。下面按「能干什么 → 怎么跑 → 怎么改」的顺序拆。2. 技术栈与目录结构一个请求从登录到收消息走了哪些层2.1 技术选型与各层职责这个项目的定位是「JavaWeb 入门级即时通讯」所以技术栈不会堆得太重。常见做法是后端用 Servlet JSP 打底数据库用 MySQL 存用户和消息前端用原生 JavaScript 加 AJAX 做页面交互消息推送部分用轮询或 WebSocket 二选一。我一般会建议如果只是课程设计轮询足够如果想体验真正的服务端推送就上 WebSocket。层次技术职责表现层JSP HTML/CSS/JS登录页、聊天页渲染消息展示控制层Servlet接收请求、参数校验、调用业务业务层Java 普通类Service用户校验、消息存取、会话管理持久层JDBC / DAO用户表、消息表读写数据库MySQL存账号、密码、聊天记录通信AJAX 轮询 / WebSocket消息实时到达选 Servlet 而不是 Spring 的原因很直接依赖少、启动快、不用配一堆 XML新手能看清一个请求到底经过了哪些类。代价是要自己写不少重复代码但对理解原理反而是好事。2.2 目录结构长什么样一个典型的 Maven Web 工程目录大致如下不同人写法会有差异但核心分层不会变chat-system/ ├── src/main/java/ │ ├── controller/ # ServletLoginServlet、ChatServlet、MessageServlet │ ├── service/ # 业务逻辑UserService、MessageService │ ├── dao/ # 数据访问UserDao、MessageDao │ ├── model/ # 实体类User、Message │ └── util/ # 工具类DBUtil、JsonUtil ├── src/main/webapp/ │ ├── login.jsp # 登录页 │ ├── chat.jsp # 聊天主页面 │ ├── js/chat.js # 前端收发逻辑 │ └── WEB-INF/web.xml # Servlet 映射 └── pom.xml # 依赖mysql-connector、servlet-api、gsoncontroller只做参数接收和转发service放业务判断dao只管 SQLmodel是纯数据载体。这种分层看起来啰嗦但改起来省心——比如以后要把轮询换成 WebSocket只需要动controller和前端service、dao基本不用碰。2.3 一次登录请求的完整链路以登录为例走一遍链路能看清各层怎么配合。前端表单提交到LoginServletServlet 取参数、调UserService.login()Service 调UserDao.findByUsername()查库比对密码结果回传后决定跳转chat.jsp还是回登录页报错。// LoginServlet.java 核心片段 protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { String username req.getParameter(username); String password req.getParameter(password); UserService userService new UserService(); User user userService.login(username, password); // 返回 null 表示失败 if (user ! null) { req.getSession().setAttribute(currentUser, user); // 登录态存 Session resp.sendRedirect(chat.jsp); } else { req.setAttribute(error, 用户名或密码错误); req.getRequestDispatcher(login.jsp).forward(req, resp); } }这里的关键点是登录态放在HttpSession里后续所有需要身份的接口都从 Session 取currentUser而不是每次请求都带用户名参数——否则别人改个参数就能冒充。userService.login()内部先按用户名查记录再用查出的盐值对密码做一次哈希比对明文密码不落库。参数说明username、password来自表单字段名必须和 JSP 里input name...保持一致这是新手最容易对不上的地方。3. 核心模块拆解消息怎么发出去、又怎么到达对方3.1 消息表设计与 DAO 写入消息能不能查得回来全看表设计。最小可用的消息表需要四个关键字段发送者、接收者、内容、时间。常见做法是加一个自增主键和已读标记方便以后做未读提醒。CREATE TABLE message ( id BIGINT PRIMARY KEY AUTO_INCREMENT, from_user VARCHAR(50) NOT NULL, -- 发送者用户名 to_user VARCHAR(50) NOT NULL, -- 接收者用户名 content TEXT NOT NULL, -- 消息正文 send_time DATETIME DEFAULT CURRENT_TIMESTAMP, is_read TINYINT DEFAULT 0, -- 0 未读 1 已读 INDEX idx_to_user (to_user, id) -- 按接收者拉取时走索引 );idx_to_user这个联合索引是血泪经验轮询模式下前端每隔一两秒就来拉一次「发给我的新消息」没有索引时数据一多查询就慢页面会明显卡顿。DAO 层写入就是一条标准INSERT读取则按to_user加id lastId增量拉取避免每次全量返回。// MessageDao.java 增量拉取新消息 public ListMessage findNewMessages(String toUser, long lastId) { String sql SELECT * FROM message WHERE to_user ? AND id ? ORDER BY id ASC; ListMessage list new ArrayList(); try (Connection conn DBUtil.getConnection(); PreparedStatement ps conn.prepareStatement(sql)) { ps.setString(1, toUser); ps.setLong(2, lastId); // 前端记住上次最大 id只取更新的 ResultSet rs ps.executeQuery(); while (rs.next()) { list.add(mapRow(rs)); // 逐行映射成 Message 对象 } } catch (SQLException e) { e.printStackTrace(); } return list; }lastId由前端保存并在每次请求时带上服务端只返回比它大的记录。这样即使轮询频率高单次返回的数据量也很小。参数toUser必须从 Session 里取当前登录用户不能信任前端传的值否则就能拉到别人的消息。3.2 前端轮询与消息渲染前端部分负责定时拉取和把消息贴到页面上。轮询的间隔要权衡太短请求多太长延迟明显。一般 12 秒比较合适。// chat.js 轮询核心逻辑 let lastId 0; const currentUser document.getElementById(currentUser).value; function pollMessages() { fetch(/message?actionpulllastId${lastId}) .then(res res.json()) .then(data { data.forEach(msg { appendMessage(msg); // 渲染到聊天框 lastId Math.max(lastId, msg.id); // 更新游标 }); }) .catch(err console.error(拉取失败, err)); } setInterval(pollMessages, 1500); // 每 1.5 秒拉一次lastId是游标只增不减保证不重复渲染也不漏消息。appendMessage里做两件事拼 DOM 节点、滚动到底部。注意currentUser从页面隐藏域取服务端仍要以 Session 为准做二次校验前端传的只用于展示区分左右气泡。3.3 换成 WebSocket 的改造点如果不想忍受轮询延迟可以把拉取换成 WebSocket。服务端用ServerEndpoint标注一个端点连接建立时把用户和 Session 绑定收到消息后直接推给目标用户的连接。ServerEndpoint(/ws/{username}) public class ChatEndpoint { private static final MapString, Session ONLINE new ConcurrentHashMap(); OnOpen public void onOpen(Session session, PathParam(username) String username) { ONLINE.put(username, session); // 上线登记 } OnMessage public void onMessage(String text, Session session) { // text 约定格式toUser|content实际项目建议用 JSON String[] parts text.split(\\|, 2); Session target ONLINE.get(parts[0]); if (target ! null target.isOpen()) { target.getAsyncRemote().sendText(parts[1]); // 推给对方 } } OnClose public void onClose(PathParam(username) String username) { ONLINE.remove(username); // 下线清理 } }ONLINE用ConcurrentHashMap是因为多个连接会并发读写普通HashMap在高并发下可能出问题。PathParam把 URL 里的用户名带进来做绑定。改造时前端把setInterval换成new WebSocket(...)消息到达走onmessage回调其余渲染逻辑可以复用。注意 WebSocket 端点也要做身份校验不能只靠 URL 里的用户名。4. 本地跑起来环境准备到双窗口互发消息4.1 环境与依赖准备跑这个项目需要 JDK 8 及以上、Maven、MySQL 5.7 或 8.0、一个 Servlet 容器Tomcat 8/9 都行。数据库先建库建表用户表至少要有username、password、salt三个字段。依赖在pom.xml里声明dependencies dependency groupIdjavax.servlet/groupId artifactIdjavax.servlet-api/artifactId version4.0.1/version scopeprovided/scope !-- 容器已提供不打进 war -- /dependency dependency groupIdmysql/groupId artifactIdmysql-connector-java/artifactId version8.0.33/version /dependency dependency groupIdcom.google.code.gson/groupId artifactIdgson/artifactId version2.10.1/version !-- 前后端 JSON 序列化 -- /dependency /dependenciesservlet-api的scope必须是provided否则和容器自带的包冲突启动时报类加载异常。数据库连接信息写在DBUtil或配置文件里常见做法是放db.properties避免硬编码。4.2 启动与验证步骤第一步执行建表 SQL插入两个测试账号。第二步mvn clean package打出 war 包丢进 Tomcat 的webapps目录启动容器。第三步浏览器开两个窗口或一个正常窗口加一个隐身窗口分别登录两个账号在聊天页选中对方发消息观察另一个窗口是否在 12 秒内出现新消息。验证时重点看三处登录后 Session 里有没有currentUser、消息表里有没有新记录、前端控制台的轮询请求返回是否正常。任何一处断了消息都不会到达。数据库连不上时先看DBUtil里的 URL、账号、密码和驱动类名MySQL 8 的驱动类是com.mysql.cj.jdbc.Driver写成旧版com.mysql.jdbc.Driver会报驱动找不到。提示两个窗口登录同一账号会互相覆盖 Session 里的用户信息测试时必须用两个不同账号这是最常见的「消息发出去对方收不到」的假象来源。5. 避坑与排查那些让消息「发出去却收不到」的坑5.1 消息发出但对方页面无反应现象发送方提示成功接收方页面一直不刷新。原因通常是轮询请求没带对身份服务端从 Session 取不到当前用户查询条件落空。解决检查前端请求是否携带 Cookie同源请求默认带确认pull接口里用的是session.getAttribute(currentUser)而不是前端传的用户名。另一个可能是lastId初始化错误前端一开始就传了一个很大的值导致新消息全被过滤掉。5.2 中文消息变乱码现象数据库里存的中文正常页面显示成问号或方块。原因多在请求编码没统一。解决在 Servlet 取参数前设置req.setCharacterEncoding(UTF-8)响应设置resp.setContentType(text/html;charsetUTF-8)数据库连接 URL 加上useUnicodetruecharacterEncodingutf8。三处缺一处都可能翻车我一般三处一起配。5.3 轮询越跑越卡现象聊一会儿页面明显变慢请求响应时间越来越长。原因是没有增量拉取每次全量查历史消息数据量涨上去自然慢。解决确认lastId游标生效SQL 带id ?条件并给to_user建索引。如果已经上了 WebSocket 还卡检查是不是在onMessage里做了同步数据库写入改成异步或批量落库。5.4 部署后 404 找不到 Servlet现象本地 IDE 里能跑打成 war 部署到 Tomcat 后访问 404。原因通常是web.xml里的url-pattern和前端请求路径对不上或者用了注解WebServlet但web.xml的metadata-completetrue把注解扫描关了。解决统一用注解方式并确认metadata-complete为false或者两边都写清楚映射路径大小写也要一致。5.5 连接池耗尽现象用一段时间后报「too many connections」。原因是每次请求都新建连接却没关闭。解决try-with-resources确保Connection、PreparedStatement、ResultSet都关闭或者引入一个简单连接池统一管理。这个项目规模小try-with-resources基本够用。6. 二次开发在现有骨架上加已读回执和离线消息拿到这个项目后最值得先加的两个功能是已读回执和离线消息因为它们直接决定聊天体验是否「像个真东西」。已读回执的思路很简单接收方拉取到消息后回一个确认接口把对应id的is_read置 1发送方轮询时顺带查自己发出的消息里哪些已读在气泡上打勾。改造点集中在MessageDao加一个markRead(ListLong ids)方法MessageServlet加一个actionread分支前端在appendMessage后调一次确认。离线消息则依赖「消息本来就落库」这个前提——用户不在线时消息照样写进表里下次登录拉历史时自然带出来。要注意的是拉历史的接口和拉新消息的接口要分开历史按时间倒序分页新消息按id增量混在一起容易重复渲染。我一般会约定actionhistorypage1和actionpulllastIdx两个入口前端首次进入加载历史之后只走增量。验证改造是否成功别只看页面直接查库最可靠发一条消息确认is_read从 0 变 1退出一个账号再发消息重新登录后确认历史里能查到。还有一个容易忽略的点是并发两个用户同时给对方发消息时ONLINE映射和数据库写入都要保证线程安全ConcurrentHashMap和数据库事务各管一段别在 Service 里用成员变量缓存状态。从那以后我每次改这类聊天项目都会先把「发一条消息 → 查库 → 对方拉取 → 标记已读」这条链路在数据库层面手动走一遍确认每一步都有痕迹再去动前端。页面上的玄学问题十有八九能在表里找到答案。希望帮到你。本文还有配套的精品资源点击获取

关于本文作者

来自尧图内容编辑团队

尧图内容编辑团队 内容团队

尧图内容编辑团队

本文由尧图网络内容编辑团队执笔。团队由资深项目经理、前端工程师与设计师组成,所有内容均来自亲手交付的真实项目,先讲清问题、再给出可落地的解法。尧图深耕北京网站建设十年,服务过京华建材集团、智造科技等各行业客户,把一线经验沉淀为可复用的行业观察。

  • 十年建站经验,覆盖建材、制造、服务、文创等
  • 项目经理把关选题与事实准确性
  • 工程师与设计师联合撰写专业细节
  • 统一编辑规范,保证文风与排版一致
  • 每月复盘转化数据,迭代选题方向

延伸阅读

相关资讯与近期热门内容

深度阅读推荐

建站决策前值得细读的三篇

网站改版的5个关键决策
2024-08-12

网站改版的5个关键决策

什么时候该改版、改到什么程度、如何避免流量掉光,京华建材集团改版复盘给出答案。

获取专属建站方案

看完文章,把您的行业与预算告诉我们,免费获取一份量身定制的官网建设方案与报价。

立即免费咨询