简介这是一份面向Java Web课程大作业的聊天系统完整项目适合需要完成类似课题的本专科学生与初级开发者。项目采用前后端分离的分层架构后端按config、controller、dao、dto、entity、processor、service、utils、vo等包划分覆盖配置管理、接口开发、JPA数据操作、实体映射、过滤器拦截器监听器、业务逻辑与前后端交互等环节并配套具体文档说明便于理解整体设计思路。资源压缩包共138个文件以66个Java源文件、12个Vue组件、11个SCSS样式、9个Markdown说明及9张图片为主体另含少量JS/CSS/HTML配置等整体大小仅2.08MB轻量易部署。已有1152人学习浏览可作为课程设计参考或二次开发基础。从中可清晰看到聊天系统从数据模型到接口、再到前端界面的完整落地过程尤其适合希望快速掌握Spring Data JPA与Vue协作开发的学习者。1. 大作业答辩不是背代码这份 Java Web 聊天系统到底装了什么答辩季最常见的一种状态代码能跑但老师一句「你这个请求从页面到数据库都经过哪些类」就把人问住了。这份 Java Web 聊天系统大作业好在不是只有一堆散落的页面和 Servlet它有完整的模块划分和项目文档后端按 controller、service、dao 分层前端用一个 chat2.html 串起登录、好友列表和聊天窗口。适合两类人看一是拿它当底子改自己大作业的二是想弄明白一个 Java Web 项目各包之间到底怎么协作的。我先说结论这资源能不能用取决于你有没有把它从「能打开」拆到「能讲清」——尤其当你决定在答辩里用 Spring Boot 技术栈的时候分包逻辑就是你的护城河。2. 从 config 到 vo 的八层分包Spring Boot JPA 的工程化写法这份资源的目录里没有把代码全堆在 Servlet 里而是按职责拆成了八个包。大作业做到这个颗粒度不是炫技是为了让老师在代码评审时一眼看清你「知道某个类该放哪」。我拆过不少类似的项目说实话绝大部分学生项目死在两件事上包结构混乱以及业务逻辑写在 Controller 里。这份资源在这两点上都是合格样本。2.1 包结构对照摘要里的模块在代码里长什么样拿到资源后先别急着跑把包结构拉出来和文档里的模块说明逐一对一遍。下面是这套系统里最核心的八个包和它们各自承担的职责包名存什么典型代码内容config配置类CORS 跨域配置、拦截器注册、WebSocket 配置controller后端 API 入口登录、注册、消息发送的 RestControllerdaoJPA 操作层继承 JpaRepository 的接口如 UserDao、MessageDaodto部分字段传输对象登录时只传 username password不传整个实体entity数据库表映射User、Message 等带 Entity 注解的类processor过滤器、拦截器、监听器登录拦截器、Session 监听器service业务逻辑层接口 实现类供 controller 调用vo前端交互返回类型ResultVO、UserVO 这类统一封装这里最容易被忽略的是 dto 和 vo 的区别dto 是「前端传给后端」的时候用的vo 是「后端返回给前端」的时候用的。很多大作业只建一个 entity 从头传到尾遇到字段不想暴露给前端比如密码就会很尴尬。这份资源把两者拆开答辩时如果老师问「为什么不用 entity 直接返给前端」你就可以直接答避免把密码哈希和数据库内部字段暴露出去。2.2 请求是怎么穿过 controller、service、dao 的以登录为例一次完整请求的路径是这样的chat2.html 里的 fetch 把 JSON 发到/api/user/logincontroller 接住后转给 service 接口的实现类实现类调用 dao 里的 JPA 方法最终由 Hibernate 生成 SQL 打到数据库。全程没有一行 SQL 写在 controller 里。看一段典型的 controller 写法RestController RequestMapping(/api/user) public class UserController { private final UserService userService; public UserController(UserService userService) { this.userService userService; } PostMapping(/login) public ResultVO login(RequestBody LoginDTO dto, HttpSession session) { User user userService.login(dto.getUsername(), dto.getPassword()); if (user null) { return ResultVO.error(用户名或密码错误); } session.setAttribute(loginUser, user); return ResultVO.success(user); } }逻辑说明这里用的是构造器注入比 Autowired 字段注入更利于单元测试也更容易让答辩老师认可。RequestBody LoginDTO dto表示前端传过来的 JSON 会被 Spring 自动反序列化成 LoginDTO所以前端字段名必须和 DTO 里的属性名一致否则拿到的是 null。参数说明HttpSession session是 Spring MVC 自动注入的不需要你自己 new。登录成功后把 user 对象塞进 session后续拦截器就是靠这个判断用户有没有登录。2.3 为什么用 JPA 而不是 MyBatis大作业答辩的选型理由这份资源用的是 Spring Data JPA对应的 dao 层写起来非常短。一个典型的 UserDao 长这样public interface UserDao extends JpaRepositoryUser, Long { User findByUsername(String username); }逻辑说明JpaRepositoryUser, Long里第一个泛型是实体类第二个是主键类型。findByUsername是 Spring Data JPA 的命名规则方法你不需要写实现框架会根据方法名自动生成查询。同理如果你需要「按用户名和密码查」可以直接声明findByUsernameAndPassword。参数说明这方法适合单表简单查询。如果大作业里涉及多表联查JPA 写起来会难受那时候 MyBatis 的 Select 注解或 XML 更直观。但聊天系统这种场景用户表、好友表、消息表都是单表操作居多JPA 的代码量最少答辩时也最好讲——你就说「框架根据方法名自动生成 SQL」就够了。我见过不少同学在答辩时被问「你用的 ORM 是什么为什么选它」答不上来。其实选型理由很简单JPA 适合表结构清晰、CRUD 居多的小项目MyBatis 适合 SQL 需要精细控制的报表类场景。聊天系统显然是前者这个逻辑闭环能直接堵住追问。3. 登录注册到消息收发请求链路与两种推送选型这一章把系统最核心的业务链路拆开用户怎么进来、登录态怎么维持、消息怎么在两个人之间传递。这三块是聊天系统的命门也是答辩时老师最容易追问的区域。代码能跑只是第一步你能把链路讲成一条线才算真正吃透这份资源。3.1 从 chat2.html 到 controller请求参数怎么对齐后端接口写好了前端怎么调才是真正的坑。chat2.html 里的登录逻辑一般走 fetch注意看请求头的 Content-Type 和后端期待的格式是否一致fetch(/api/user/login, { method: POST, headers: { Content-Type: application/json }, body: JSON.stringify({ username: document.getElementById(username).value, password: document.getElementById(password).value }) }) .then(resp resp.json()) .then(data { if (data.code 0) { location.href chat2.html?userId data.data.id; } else { alert(data.msg); } });逻辑说明JSON.stringify把对象转成 JSON 字符串后端用RequestBody LoginDTO dto来接这个字符串并反序列化。注意这里必须显式设置Content-Type: application/json如果你不设浏览器默认会发application/x-www-form-urlencoded后端用 RequestBody 去接就接不到直接报 400。参数说明data.code 0是这套系统的前后端约定后端 ResultVO 里 code 为 0 表示成功非 0 表示失败。如果你想改自己的项目建议把 code、msg、data 三件套统一好前端所有接口都走同一个判断逻辑能省掉大量 if 分支。3.2 登录不是查一次表就行Session 与登录态的拦截这个系统的登录态靠 Session 维持后端有一个拦截器在 processor 包里它的职责是拦截除登录和注册之外的所有/api/**请求检查 Session 里有没有loginUser。拦截器注册一般写在 config 包里Configuration public class WebConfig implements WebMvcConfigurer { private final LoginInterceptor loginInterceptor; public WebConfig(LoginInterceptor loginInterceptor) { this.loginInterceptor loginInterceptor; } Override public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(loginInterceptor) .addPathPatterns(/api/**) .excludePathPatterns(/api/user/login, /api/user/register); } }逻辑说明addPathPatterns(/api/**)表示拦截所有以 /api/ 开头的请求excludePathPatterns把登录和注册放行。如果你在改这个资源的时候发现登录接口一直 401第一件事不是查数据库而是看这里的放行列表有没有写对。参数说明这套配置对路径顺序敏感/api/user/login要比/api/**更具体Spring 会优先匹配更具体的规则。还有一点要注意如果你用了CrossOrigin注解做跨域它跟拦截器是两回事跨域是浏览器层面的拦截器是服务端层面的两个都得配好。3.3 消息发送的两种实现轮询与 WebSocket 选哪种聊天系统的消息推送大作业里最实际的方案是轮询。页面每隔两三秒调一次「获取新消息」的接口有新消息就追加到聊天窗口。轮询的好处是简单不需要引入 WebSocket 依赖也不会踩跨域和 Session 共享的深坑。这段代码表示按时间增量拉取未读消息GetMapping(/message/new) public ResultVO getNewMessages(RequestParam Long fromUserId, RequestParam Long toUserId, RequestParam Long lastId) { ListMessage messages messageService.findNewMessages(fromUserId, toUserId, lastId); return ResultVO.success(messages); }逻辑说明前端记住自己已展示的最后一条消息的 idlastId下次轮询时把 lastId 传过来后端只返回 id 大于 lastId 的新记录。这个做法的查询效率比按时间戳比对更高因为主键索引天然有序同时也天然避免了消息重复渲染。参数说明RequestParam对应 URL 里的问号参数比如/api/message/new?fromUserId1toUserId2lastId100。如果你改成 WebSocket 方案这段代码就不需要了服务端主动推给前端即可。我的建议是如果你的大作业只做展示和演示轮询足够如果老师明确要求「实时」再加 WebSocket 作为加分项。两份方案都保留代码里用开关切换答辩时很加分。4. 前端 chat2.html 的对接实录接口联调与静态资源映射资源里那堆 css 和 iconfont 文件单独看没什么合在一起才是前端能正常渲染的关键。聊天系统最典型的问题不是后端逻辑而是前端页面打开了接口也调通了但样式全乱、图标全没。这章把前端文件的分工和静态资源的映射规则说清楚。4.1 页面骨架登录、好友列表、聊天窗口的 HTML 分工chat2.html 里通常同时承载了登录页和聊天页两套结构通过 display 属性的切换来控制显示哪一块。资源里的 css 文件名值得注意main.css管整体布局index.css管登录页专属样式demo.css多半是聊天窗口的演示样式。iconfont.css 和字体文件负责聊天界面里那些发送、表情、附件图标。拿到资源后你应该做一次「静态资源完整性检查」打开浏览器控制台看 Network 面板里有没有 404。常见的翻车场景是页面打开了但 iconfont.eot 和 iconfont.ttf 加载不出来导致图标变成方框。这类问题 90% 是资源路径没带上下文路径部署到你自己的 Tomcat 时项目名不一样路径就断了。4.2 接口联调的参数对齐JSON 字段名别踩坑后端返回的 vo 字段名如果跟前端 JS 里写的不一致页面就会渲染出一堆 undefined。这属于纯手工联调的坑没有捷径。我一般会先在浏览器控制台打印data.data看一眼实际字段结构再决定渲染代码怎么写。常见做法是后端把返回结构统一成 ResultVO里面包含 code、msg、data 三个字段data 里再放真正的业务数据。前端渲染函数长这样function renderMessage(msg) { const item document.createElement(div); item.classList.add(message-item); item.innerHTML span classsender msg.senderName /span p classcontent msg.content /p span classtime msg.createTime /span; document.getElementById(chatWindow).appendChild(item); }逻辑说明这里直接拼接 HTML 字符串胜在简单但需要注意 msg 里的内容如果包含 HTML 标签会被浏览器解析执行正式项目里要做转义。大作业里只要保证发送的时候把script过滤掉或转义成lt;scriptgt;就行。参数说明字段名全部来自后端 MessageVO——senderName 是发送者昵称而不是用户名content 是消息文本createTime 是后端格式化好的时间字符串。如果你拿到资源发现字段对不上优先改前端 JS 的字段名不要动后端实体类的字段名因为实体类一改JPA 的表结构就跟着变了。4.3 静态资源放哪儿才不会 404Spring Boot 的默认映射规则Spring Boot 对静态资源有默认映射规则。放在 classpath 下这些目录里的文件可以直接通过根路径访问目录位置访问路径示例src/main/resources/static/http://localhost:8080/chat2.htmlsrc/main/resources/public/http://localhost:8080/chat2.htmlsrc/main/resources/resources/http://localhost:8080/resources/chat2.htmlclasspath:/META-INF/resources/http://localhost:8080/chat2.html最常见的坑是有人把 css 文件放到了src/main/resources/根目录下结果访问/css/main.css一直 404。原因是 Spring Boot 只扫描上面那几个特定子目录直接放在 resources 根目录下的文件不会对外暴露。另一个高发坑是你自己在 config 里实现了WebMvcConfigurer的addResourceHandlers方法自定义了映射结果把 Spring Boot 的默认映射覆盖掉了。如果你确实需要自定义正确姿势是同时保留默认映射Override public void addResourceHandlers(ResourceHandlerRegistry registry) { registry.addResourceHandler(/static/**) .addResourceLocations(classpath:/static/); }逻辑说明/static/**是浏览器访问的 URL 路径classpath:/static/是文件系统里的真实位置。注意结尾的斜杠少了它 Spring 会拼出错误的路径。配置完之后http://localhost:8080/static/css/main.css才能正常访问。5. Java Web 大作业排查答辩前最容易翻车的四个位置这份资源整体完成度不错但正因为它是「大作业」而非常规企业项目天然带着一些粗糙的角落。以下四个位置是我拆这类项目时反复遇到过的每一条都对应真实的翻车现场。如果你决定用这份资源做底子建议在答辩前逐条确认自己的代码里没有这些问题。5.1 接口 401 或 404先把拦截器和路径映射检查一遍现象前端页面能打开但调用/api/user/login接口时返回 401或者直接 404。原因401 是拦截器拦截了你的登录请求——注意看拦截器配置里excludePathPatterns是不是写成了/api/user/*这在 Spring 的路径匹配规则里只能匹配一层/api/user/login虽然是一层但写法上容易写错导致不生效。404 则是接口路径不一致比如 controller 里写的是RequestMapping(/user)前端调的是/api/user。解决先把浏览器 Network 面板里的请求 URL 复制出来和后端RequestMapping的值慢慢比对。然后是拦截器的放行规则用/api/user/login这种精确路径最保险。如果逻辑上放行策略复杂宁可把放行路径写全列表也不要贪图简洁写斜杠通配。还有个血泪经验改了拦截器配置必须重启应用Spring 的拦截器注册只在启动时读取一次热部署经常不生效。5.2 Session 里拿不到登录用户连接发起时机错了现象登录成功后跳转到聊天页但发消息时后端报「用户未登录」查看 Session 发现里面确实是空的。原因这和大作业里常见的前后端分离部署方式有关。前端页面在一个端口比如 5500后端在另一个端口比如 8080浏览器跨域请求时默认不带 CookieSession 自然就丢了。前端必须显式开启携带凭证后端也要配置允许凭证的跨域规则。解决前后端两部分都要改。前端 fetch 加一行fetch(/api/user/login, { method: POST, credentials: include })逻辑说明credentials: include告诉浏览器这个跨域请求要带上目标域名的 Cookie。不加这个即使后端登录成功后续请求 Session 也是新的。后端方面跨域配置必须允许凭证并指定具体来源不能写*registry.addMapping(/api/**) .allowedOrigins(http://localhost:5500) .allowedMethods(GET, POST, PUT, DELETE) .allowCredentials(true);注意allowedOrigins配了*的时候 Spring 会忽略allowCredentials(true)因为浏览器不允许带凭证的请求来源是通配符。这俩必须同时存在、同时正确缺一个就是静默失败——不报错但登录态始终建立不起来。5.3 前端弹窗报 JSON 解析错误接口返回结构不一致现象控制台报Unexpected token in JSON或者页面渲染出来全是 undefined。原因这个报错几乎可以断定请求返回的不是 JSON而是一段 HTML。最常见的情况是你请求了一个不存在的接口Spring 默认把错误页以 HTML 形式返回。另一个可能原因是 controller 里有方法返回了 String 类型但没用 ResponseBody导致 Spring 去找同名视图模板最后给你返回了一个 500 的 HTML 错误页。解决先看 Network 面板里这条请求的响应内容如果看到的是html开头基本就是上面两个原因之一。检查两个地方一是接口路径是否真正存在二是所有 Controller 类上有没有加RestController或方法上有没有加ResponseBody。如果你的项目里混用了Controller和RestController逐类检查混用最容易漏。5.4 mvnw.cmd 运行报错Windows 环境的两个隐藏坑现象在项目根目录运行mvnw.cmd spring-boot:run报JAVA_HOME is not defined correctly或者直接闪退。原因mvnw.cmd 脚本在 Windows 上有两个性格弱点第一它要求JAVA_HOME环境变量指向 JDK 的根目录而不是 bin 目录第二如果 JAVA_HOME 路径里有空格比如C:\Program Files\Java\jdk17脚本处理不当会导致找不到 java 命令。解决检查环境变量JAVA_HOME的值是否精确到 JDK 根目录标准长这样JAVA_HOMEC:\Program Files\Java\jdk-17然后在命令行里验证一下拼接结果能不能跑%JAVA_HOME%\bin\java -version能正常输出版本号再执行mvnw.cmd。如果还是不行不要跟脚本死磕直接用 IDE 内置的 Maven 配置。IntelliJ IDEA 里设置 Maven 的 JDK 为项目 SDK通常能避开 mvnw 在 Windows 上的各种玄学问题。6. 把别人的骨架改成自己的三个增量改造方向拿到这份资源最忌讳的是原封不动交上去。同一个学校同一个专业往年可能有人交过一模一样的老师扫一眼就知道是下载的。改造不需要伤筋动骨三个方向每改一个都是答辩加分项。第一个方向从单聊改成群聊。当前消息表的字段大概只有 fromUserId、toUserId、content、createTime。改成群聊需要加一个 group_id 字段消息查询改成按 group_id 拉取。改动清单如下改动位置具体操作实体类 Message新增 groupId 字段并加关联索引数据库表加 group_id 字段建一个 group 表消息查询方法按 groupId 分页查询替代原来的 from/to 双条件前端渲染聊天窗口标题改为群名称消息发送对象改为 groupId第二个方向把消息记录做分页。现在很多此类系统是一次性把全部历史消息返回数据一多页面就卡。加一个RequestParam(defaultValue 20) int size用 JPA 的Pageable做分页是性价比极高的优化代码量不到十行答辩时能清晰讲出「性能优化」四个字。第三个方向增加心跳检测。如果做的是轮询聊天加一个lastId增量拉取3.3 节已经讲过。这一个小小的点能让你的聊天窗口渲染效率大幅提升老师问起来你也能顺着说「不做全量刷新只拉增量数据」。这些年我拆过不少大作业资源一个很深的感受是下载下来的代码能不能真正成为「你的」取决于你改过哪一行、踩过哪个坑、又在答辩时怎么把那个坑讲成你排查过的经验。从那以后我每次拿到一份 Java Web 大作业资源都会强制自己先跑通、再改一条数据链路、最后写一页排查记录这三步走完才敢往答辩 PPT 里放。希望帮到你。本文还有配套的精品资源点击获取