简介:这是一套面向计算机专业本科生的Java全栈毕业设计实战资源,基于SpringBoot后端与Vue前端构建校园社交平台,解决大学生在校园场景下的动态分享、兴趣交友、互助求助及二手交易等实际需求。压缩包为完整可运行项目,含全部源码、MySQL数据库脚本及配置说明,共94.49MB,文件类型涵盖Java核心业务代码、Vue组件与路由配置、SQL建表与初始化脚本、application.yml等关键配置文件,结构清晰、注释充分,零基础开发者亦可快速部署调试。已有1039人学习下载,资源由实战经验丰富的开发者持续维护,提供开箱即用的管理员后台(支持用户/动态的增删改查)、学生端完整交互流程(登录、发帖、点赞、私信、商品发布)及响应式前端界面,特别适合作为课程设计、毕设选题或Java+Vue技术栈入门实践范例。 做毕业设计选“校园交友网站”这个题目,这几年一直很常见。原因很简单:它不像电商、外卖那种系统业务链路长、代码量大,写起来容易失控,又比图书管理、宿舍管理这类纯CRUD项目多了社交属性和实时互动的亮点,在功能展示和论文写作上都有话题可聊。更重要的是,用SpringBoot+Vue这套组合来实现,技术栈非常主流,不管是找工作写简历还是答辩展示,都比较拿得出手。
我这次拿到手的是一个完整的项目包,SpringBoot+Vue的校园交友网站源码,附数据库脚本,压缩包解压之后直接就能导入IDE启动。整套东西走的是前后端分离的标准路子,后端用Java做接口服务,前端用Vue写页面交互,数据落在MySQL里。整个项目实施下来,我最大的感受是:这个题目的核心不只是“写代码”,而是你怎么把“交友”这个业务抽象成合适的数据模型和交互逻辑,让这个系统看起来是真的能用,而不只是一个演示玩具。
如果你正准备拿这个题目做毕业设计,或者刚接触SpringBoot+Vue想找一个练手项目,这篇博客会帮你把项目的结构、代码逻辑、数据库设计、启动流程、常见坑全部捋干净,尤其是答辩环节老师最喜欢问的那些点,我也会结合经验讲透。
1. 项目整体设计与技术选型思路
1.1 为什么是SpringBoot+Vue这种组合
先说技术选型。校园交友网站这种项目,选SpringBoot+Vue不是偶然,而是综合了开发效率、学习成本、毕设展示效果之后的合理选择。
后端用SpringBoot,最大的优势是“开箱即用”。以前用SSH(Struts+Spring+Hibernate)或者SSM(Spring+SpringMVC+MyBatis)搭项目,配置文件一大堆,光是XML就能把人写崩溃。SpringBoot通过自动配置和起步依赖把这些繁琐的东西全部封装掉了,你只需要在Maven的pom.xml里引入需要的starter,框架就能帮你把大部分配置自动搞定。对于做毕设的学生来说,这意味着可以把精力放在业务逻辑上,而不是折腾环境。
前端用Vue,是因为Vue的上手曲线是三大框架(React、Vue、Angular)里最平缓的。它的核心概念就几个:数据绑定、组件化、路由、状态管理。对于写后端为主的Java学生来说,Vue的模板语法非常直观,看着像HTML,但实际上里面可以写JavaScript逻辑,理解成本低。加上Element UI这套组件库,表单、表格、弹窗这些界面元素拖出来就能用,做出的页面不会太丑。
前后端分离还有一个实际好处,就是联调的时候可以互相不阻塞。后端还没写完的接口,前端可以先mock数据;前端还没写完的页面,后端可以用Postman自己测接口。对于一个人单打独斗做毕设的学生来说,这种解耦方式能在赶进度的时候省下很多时间。
1.2 功能模块怎么划分才像一个完整的毕设
很多学生拿到这个题目,第一反应就是“注册登录、加好友、聊天”三件套。这没错,但要拿高分,功能设计得能体现出你对业务的理解。
我当时梳理功能时,把系统分成前台和后台两部分,前台面向普通学生用户,后台面向管理员。
用户端核心功能包括:
- 注册登录:邮箱注册、用户名密码登录、退出登录
- 个人资料管理:修改头像、昵称、院系、年级、兴趣爱好标签、个人简介
- 用户浏览与筛选:按院系、年级、性别、兴趣爱好标签筛选用户列表
- 好友申请与好友列表:发送好友申请、通过/拒绝请求、好友列表展示
- 动态发布:类似朋友圈的图文动态,支持文字内容和图片上传
- 互动功能:动态评论、点赞、取消点赞
- 私信聊天:好友之间一对一实时聊天
- 个人中心:查看自己发布的动态、收到的评论点赞通知
管理员端功能包括:
- 登录认证:管理员独立账号登录
- 用户管理:查看所有用户、封禁/解封账号
- 动态管理:查看动态列表、删除违规动态
- 评论管理:删除不当评论
- 数据概览:平台用户总数、动态总数、最近活跃用户等统计信息
这么设计的好处是:既有CURD基础操作,又有WebSocket实时通信这种进阶点,还有筛选匹配这种带一点算法色彩的逻辑。答辩的时候,每一块都能展开讲,不愁没话说。
1.3 高并发?不需要!但你必须让答辩老师相信你有考虑
校园交友网站这种脱胎于课程设计的项目,坦白讲不会有高并发的真实场景。但答辩老师很可能会问:“你这个网站如果有一万个学生同时访问,会不会挂?用什么方案扛?”这个问题本质是想考察你有没有考虑过系统性能问题。
你不需要真的去搞一套分布式架构,但可以在项目里预留一些合理的优化点,并能在答辩时讲清楚思路:
- 热门用户的个人主页和动态列表,可以用Redis做缓存,减少数据库压力
- 图片资源走单独的静态文件路径或对象存储,避免图片IO拖垮应用服务器
- 私信聊天这类高频操作,走WebSocket长连接而不是HTTP轮询
- 数据库层面对高频查询字段(如用户ID、好友关系)加索引
- 分页查询控制返回数据量,防止一次加载太多数据导致页面卡顿
把这些点体现在代码和文档里,答辩时主动说出来,老师会认为你是认真思考过项目落地的。
2. 核心功能实现与关键代码解析
2.1 用户注册登录:JWT认证是怎么跑通的
校园交友网站的用户系统不复杂,但认证方式是重点。传统思路用Session,但前后端分离项目更推荐用JWT(JSON Web Token)。
登录流程:用户提交用户名和密码,后端校验通过后生成一个JWT令牌返回给前端。前端拿到令牌后存在本地存储里,之后的每次请求都在请求头里带上Authorization: Bearer <token>。后端通过拦截器校验这个token,校验通过就把当前用户信息写入上下文。
注册的时候需要注意两点:一是密码不能明文存储,要用BCrypt加密再入库,防止数据库泄露后波及用户账号;二是校验邮箱格式和用户名唯一性,避免重复注册。
关键的后端代码结构如下:
// 用户登录接口 @PostMapping("/api/auth/login") public Result login(@RequestBody LoginDTO loginDTO) { // 1. 根据用户名查询用户 User user = userService.findByUsername(loginDTO.getUsername()); // 2. 校验密码(BCrypt匹配) if (user == null || !BCrypt.checkpw(loginDTO.getPassword(), user.getPassword())) { return Result.error("用户名或密码错误"); } // 3. 检查账号状态 if (user.getStatus() == 0) { return Result.error("账号已被封禁,请联系管理员"); } // 4. 生成JWT String token = JwtUtil.generateToken(user.getId(), user.getUsername()); return Result.success(new LoginResponse(token, user)); }前端有了token之后,用Axios做请求拦截,每次发请求前自动带上token,同时处理401状态码(token过期或无效)跳回登录页。
// Axios请求拦截器 axios.interceptors.request.use(config => { const token = localStorage.getItem('token') if (token) { config.headers.Authorization = `Bearer ${token}` } return config }) axios.interceptors.response.use( response => response, error => { if (error.response && error.response.status === 401) { localStorage.removeItem('token') router.push('/login') } return Promise.reject(error) } )这里踩过一个坑:JWT的密钥不要写死在代码里,更不要用默认值,否则任何人只要知道你的密钥就能伪造任意用户的token。我一般把它配置在application.yml里,并设置一个足够长的随机字符串。token过期时间也建议设置,我习惯设成24小时,太短影响体验,太长不安全。
2.2 用户筛选与匹配逻辑:怎么把“交友”做成可用的功能
“交友”这个功能,如果只做一个用户列表让管理员看,那就没意思了。校园交友网站的核心是让用户能找到想认识的人,所以筛选条件是关键。
我的实现方案是这样的:用户注册时选择自己的院系、年级、性别,并给自己打上兴趣爱好标签(比如“篮球”、“摄影”、“游戏”、“读书”)。在“发现用户”页面,用户可以按以下条件组合筛选:
- 性别(男/女/不限)
- 院系(下拉框选择)
- 年级(大一到大四/研究生)
- 兴趣爱好标签(多选)
后端通过MyBatis-Plus的条件构造器来实现动态SQL拼接。这里有一个设计细节值得展开:当用户传入多个筛选条件时,怎么优雅地处理“可选条件”。
public PageResult<UserVO> queryUsers(UserQueryDTO query, Long currentUserId) { LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>(); // 排除当前用户自己 wrapper.ne(User::getId, currentUserId); // 性别条件(为空时不参与过滤) if (StringUtils.hasText(query.getGender())) { wrapper.eq(User::getGender, query.getGender()); } // 院系条件 if (StringUtils.hasText(query.getCollege())) { wrapper.eq(User::getCollege, query.getCollege()); } // 年级条件 if (StringUtils.hasText(query.getGrade())) { wrapper.eq(User::getGrade, query.getGrade()); } // 分页 Page<User> page = userMapper.selectPage( new Page<>(query.getPageNum(), query.getPageSize()), wrapper); // 处理兴趣爱好标签匹配 ... }标签筛选实现起来比较复杂,因为用户和标签是多对多关系,不是简单的一个字段。我单独建了一个user_tag关联表,筛选时先根据标签ID查出一批用户ID,再用IN条件去过滤用户列表。
“匹配”这块我加了一个小花样:在用户详情页根据当前用户的标签,显示“你们有多少共同标签”,共同标签超过两个时,给出“高度匹配”的提示。这个逻辑在答辩时可以作为一个亮点讲,因为涉及多对多关联查询和简单的匹配算法。
2.3 动态发布与评论点赞:前后端怎么配合
动态发布本质上就是一个标准的CRUD,但因为涉及图片上传和用户关联,代码量会稍微多一些。
后端核心接口包括:发布动态、分页查询动态列表、查询某人的动态列表、删除自己的动态。
图片上传这一块,我建议用本地存储加静态资源映射的方案,而不要用Base64直接存数据库。Base64会让数据库字段特别大,而且传输效率低。本地存储就是配置一个上传路径,把图片文件写到服务器磁盘上,然后把访问URL存到数据库里。SpringBoot里配置静态资源映射的代码很简单:
# application.yml 中的配置 spring: web: resources: static-locations: file:D:/upload/这样上传到D:/upload/目录下的图片,可以通过http://localhost:8080/uploads/xxx.jpg直接访问。
动态列表的查询需要做连表操作,因为要显示发布者的头像和昵称。我用的是JOIN查询,返回一个VO对象给前端,前端拿到后直接渲染。前端用el-card组件做动态卡片,配上图片轮播和评论输入框,体验还算过得去。
点赞和评论是高频操作,这里有一个值得注意的点:点赞是“用户-动态”多对多关系,需要建关联表,并且要加唯一约束防止重复点赞。取消点赞就是删除这条关联记录。动态表里维护一个like_count字段,每次点赞/取消点赞都同步更新这个字段,这样列表展示时不用再去COUNT查询,性能更好。
2.4 私信聊天:WebSocket实时通信怎么落地
私信聊天是整个项目里最有“含金量”的功能,也是答辩时最好演示的功能。我用的是SpringBoot集成WebSocket的方案。
核心思路:每个用户登录后,前端通过WebSocket连接后端,并把自己的用户ID作为标识(比如在连接地址里带上?userId=xxx)。当A给B发消息时,A的前端把消息通过WebSocket发送到后端,后端解析出B的用户ID,找到B对应的WebSocket会话,将消息推送过去。如果B不在线,就把消息存入数据库的message表,等B下次登录时拉取未读消息。
WebSocket的后端配置核心类大致如下:
@Configuration @EnableWebSocket public class WebSocketConfig implements WebSocketConfigurer { @Override public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) { registry.addHandler(chatHandler(), "/ws/chat") .setAllowedOriginPatterns("*") .addInterceptors(new ChatHandshakeInterceptor()); } }ChatHandshakeInterceptor拦截握手请求,从请求参数里解析出userId,然后和WebSocketSession绑定。这里有一个非常容易踩的坑:SpringBoot的WebSocket在握手时如果发现Origin不合法会拒绝连接。本地开发时前端是localhost:8080,后端是localhost:9090,端口不一样就属于跨域,所以setAllowedOriginPatterns("*")这行必须写,否则前端连不上WebSocket。
消息格式我统一用JSON字符串,用一个type字段区分是聊天消息、系统通知还是心跳:
{ "type": "CHAT", "fromUserId": 1, "toUserId": 2, "content": "你好,认识一下?", "timestamp": "2025-05-01 12:00:00" }在Vue前端,我在用户详情页和好友聊天页都建立了WebSocket连接。值得一提的是,WebSocket连接不是HTTP那样的“请求-响应”模式,它是长连接,浏览器和服务器一直维持着一个TCP连接,所以能实现消息的实时推送。我在开发时用Network面板调试WebSocket消息,可以看到消息是主动从服务器推下来的,这就是聊天的“实时感”来源。
3. 数据库设计:交友网站的表到底怎么建
3.1 核心表结构与字段说明
数据库是整个项目的地基。我一个一个梳理核心表的结构和设计意图。
用户表(user)
用户表是系统的核心,存储所有注册用户的信息。
| 字段名 | 类型 | 说明 |
|---|---|---|
| id | bigint | 主键,自增 |
| username | varchar(50) | 用户名,唯一 |
| password | varchar(100) | BCrypt加密后的密码 |
| nickname | varchar(50) | 昵称 |
| avatar | varchar(255) | 头像URL |
| gender | varchar(10) | 性别 |
| college | varchar(100) | 院系 |
| grade | varchar(20) | 年级 |
| phone | varchar(20) | 手机号(可选) |
| varchar(100) | 邮箱 | |
| bio | varchar(500) | 个人简介 |
| status | tinyint | 账号状态(0封禁,1正常) |
| create_time | datetime | 注册时间 |
| update_time | datetime | 更新时间 |
这个表的设计要点是:把用户的基础信息全部放在一张表里,但不放兴趣爱好标签。因为标签是多选的,放一张表里会导致字段冗余或者需要逗号分隔,查询筛选很麻烦。标签用单独的关联表来维护。
标签表(tag)和用户标签关联表(user_tag)
tag表很简单:id、tag_name。预置一些常见标签:篮球、足球、摄影、编程、音乐、舞蹈、游戏、读书、旅行、美食等。
user_tag表保存用户和标签的关联:
| 字段名 | 类型 | 说明 |
|---|---|---|
| id | bigint | 主键 |
| user_id | bigint | 用户ID,外键 |
| tag_id | bigint | 标签ID,外键 |
建表时给user_id和tag_id加联合唯一索引,防止同一用户重复勾选同一个标签。
好友申请表(friend_request)
谁向谁发送了好友申请,状态是待处理还是已通过/已拒绝,都要记录。这个表就是典型的社交关系数据。
| 字段名 | 类型 | 说明 |
|---|---|---|
| id | bigint | 主键 |
| from_user_id | bigint | 发起方用户ID |
| to_user_id | bigint | 接收方用户ID |
| status | tinyint | 0待处理,1已通过,2已拒绝 |
| message | varchar(255) | 附言 |
| create_time | datetime | 申请时间 |
好友关系表(friend)
好友确认之后,往friend表里插一条记录。这个表比较简单:user_id、friend_id、create_time。
动态表(post)
负责发布动态的内容存储。
| 字段名 | 类型 | 说明 |
|---|---|---|
| id | bigint | 主键 |
| user_id | bigint | 发布人ID |
| content | text | 文字内容 |
| images | varchar(2000) | 图片URL列表,多个用逗号分隔 |
| like_count | int | 点赞数量 |
| comment_count | int | 评论数量 |
| status | tinyint | 0已删除,1正常 |
| create_time | datetime | 发布时间 |
这里多说一句:图片URL用逗号分隔存入一个字段是典型的偷懒做法,但在毕设级别是够用的。如果要做得更规范,可以再建一张post_image表来存图片URL,每个URL一条记录,和post表是一对多关系。答辩如果被问到这个点,你能说出来两种方案的优劣,老师反而会加分。
评论表(comment)
存储动态的评论内容:id、post_id、user_id、content、create_time。
私信消息表(message)
存储用户之间的聊天记录:id、from_user_id、to_user_id、content、is_read(0未读/1已读)、create_time。
3.2 好友关系设计为什么要两张表
这里值得停下来多说几句。好友关系看似简单,实际上有两种常见设计思路:
第一种是用一张“单向关注”表,含义是“A关注了B”。如果A和B要成为好友,就插两条记录:一条(A, B),一条(B, A)。优点是灵活,支持互相关注和单向关注两种模式;缺点是查“我有哪些好友”要查两次,虽然代码上也不复杂。
第二种是用一张“好友申请表”加一张“好友关系表”。申请表记录的是“谁申请了谁、处理了没有”,关系表记录的是“谁和谁已经是好友”。这个方案的好处是逻辑清晰,申请和关系分离,统计“我发出的申请”“我收到的申请”“我的好友”都非常直观。缺点是数据状态比较多,处理“重复申请”、“已经是好友还重复申请”这类边界情况时要注意。
我在这个项目里用的是第二种方案,也是大多数社交系统的主流做法。实际编码时需要注意:A向B发送好友申请前,先检查是否已经是好友,是否已经有一个待处理的申请,避免刷屏和重复数据。
3.3 索引与查询优化
毕设项目的数据量通常不大,但索引仍然是必要的。原因有二:一是养成好习惯,二是答辩时能讲出东西来。
我建索引的几个原则:
- 登录时的username查询,加唯一索引,这是高频且必须唯一的字段
- 好友关系表的user_id和friend_id,加复合索引
- 动态表的user_id加普通索引,因为要查“某个人的全部动态”
- 点赞表的user_id和post_id加联合唯一索引,防止重复点赞
- 消息表的from_user_id和to_user_id加普通索引
MySQL的索引原理不细说,但你要记住:索引不是越多越好,因为每次写入数据时索引也要更新,索引太多会拖慢写入速度。对于毕设系统,每个表建议不超过三到四个索引。
4. 本地运行配置实测
4.1 环境准备清单
运行这个项目前,先把环境装好。我用的是下面这套配置,实测没有任何问题:
| 软件 | 版本 | 说明 |
|---|---|---|
| JDK | 1.8(8u202+) | SpringBoot 2.7.x要求JDK8 |
| Maven | 3.6.3 | 依赖管理工具 |
| Node.js | 14.x或16.x | Vue开发环境 |
| npm | 6.x或8.x | Node包管理工具 |
| MySQL | 5.7或8.0 | 数据库 |
| Redis | 5.x+ | 缓存(非必须,如果没集成可以跳过) |
| IDE | IntelliJ IDEA / VS Code | 后端用IDEA,前端用VS Code |
注意JDK版本坑:不要装JDK 17以上。SpringBoot 2.7系列的某些版本在JDK 17下会出现不兼容,如果你非要用高版本JDK,就得升级SpringBoot 3.x,但SpringBoot 3.x里很多旧版依赖又对不上,纯给自己找麻烦。老老实实用JDK 8,兼容性最好。
4.2 后端启动详细步骤
后端项目导入IDEA之后,按这个顺序执行:
第一步,修改数据库配置。找到src/main/resources/application.yml,把数据库连接改成你的本地环境:
spring: datasource: url: jdbc:mysql://localhost:3306/campus_chat?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai username: root password: 你的密码 driver-class-name: com.mysql.cj.jdbc.Driver redis: host: localhost port: 6379第二步,导入数据库脚本。项目里自带的campus_chat.sql文件包含建库建表语句和初始数据,用Navicat、DataGrip或者命令行执行都行:
mysql -u root -p < campus_chat.sql执行完检查一下,数据库里应该有user、tag、user_tag、friend_request、friend、post、comment、message这些表,管理员账号和测试用户也应该有了。
第三步,启动SpringBoot应用。在IDEA里找到主启动类,右键运行。看到类似下面的日志,说明后端启动成功:
Tomcat started on port(s): 8080 (http) with context path ''第四步,验证接口。浏览器访问http://localhost:8080/api/ping,如果返回pong之类的字符串,说明后端跑起来了。
这里的坑在于:如果你本机的8080端口被占用了,后端起不来报“Port 8080 was already in use”,最简单的办法是改配置文件里的server.port,我改成9090:
server: port: 90904.3 前端启动与联调
前端项目的目录通常是vue-frontend或者frontend。用VS Code打开,打开终端,执行:
npm install这一步是安装依赖包。网络不好的话可能会很慢,甚至报错,我一般用淘宝镜像源:
npm config set registry https://registry.npmmirror.com依赖装完后启动开发服务器:
npm run serve启动成功后,终端会显示本地访问地址,通常是http://localhost:8080(注意,这里的前端占用的也是8080,如果后端也是8080就冲突了,所以刚才建议把后端改成9090)。
前端启动后,需要确认API请求地址是否正确。一般在src/utils/request.js或者.env.development文件里配置了后端访问地址:
// .env.development VUE_APP_BASE_API = 'http://localhost:9090/api'这个BASE_API必须和你后端的server.port对应上,否则请求全部404。如果有问题,打开浏览器控制台看Network面板,请求都红字基本就是地址错了。
4.4 把项目跑起来后的验收清单
项目启动成功不一定代表所有功能都正常。我建议按照下面的清单走一遍:
- 用管理员账号登录后台,能看到用户统计信息
- 注册一个新用户,给注册邮箱发验证码(如果你做了邮件功能)
- 新用户登录后,能修改个人资料和头像
- 在“发现用户”页面按条件筛选,能得到正确结果
- 申请加好友,对方登录后能看到申请并同意
- 已经成为好友的两人之间能正常发私信,并且实时收到
- 发布一条带图片的动态,刷新后图片还在
- 自己删除动态后,列表里不再显示
- 封禁一个用户后,该用户登录时提示账号异常
走完这套流程,系统基本就稳了。我每次收到这种毕设源码,第一件事就是用这套流程做冒烟测试,能过就说明项目是完整的、可以独立运行的。
5. 常见问题与排查记录
5.1 数据库连接失败
这是初学者遇到最多的问题,报错信息一般是:
Access denied for user 'root'@'localhost' (using password: YES)或者是:
Communications link failure. The last packet sent successfully to the server was 0 milliseconds ago.第一种情况是用户名或密码不对,第二种是MySQL服务没启动,或者端口不对。排查思路很简单:先确认MySQL服务已经启动,再确认application.yml里的端口(默认3306)、用户名、密码和你的实际环境一致。还有一个常见问题是数据库根本没建,SQL脚本没导入成功。用Navicat客户端连一下,如果表都看不到,说明导入有问题,重新执行脚本。
5.2 前端跨域请求被拦截
前端页面上能打开,但请求接口时浏览器控制台报错:
Access to XMLHttpRequest at 'http://localhost:9090/api/user/list' from origin 'http://localhost:8080' has been blocked by CORS policy这是因为前端8080端口向后端9090端口发请求,跨域了。解决办法在后端加一个CORS配置类,允许指定来源跨域:
@Configuration public class CorsConfig implements WebMvcConfigurer { @Override public void addCorsMappings(CorsRegistry registry) { registry.addMapping("/api/**") .allowedOrigins("http://localhost:8080") .allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS") .allowedHeaders("*") .allowCredentials(true); } }注意,如果后端配置了JWT拦截器,要确保OPTIONS预检请求不会被拦截,否则前端还是报跨域错误。通常需要在拦截器中判断,如果请求方法是OPTIONS,直接放行。
5.3 WebSocket连不上或消息收不到
WebSocket连不上,最常见的原因是跨域没有放行。前面提到过,在WebSocketConfig里要写setAllowedOriginPatterns("*"),这一行漏了就完蛋。
消息收不到还有一种隐蔽的情况:用户登录了,但前端建立的WebSocket连接没有正确带上当前用户标识。我建议在握手拦截器里把userId解析出来,存到attributes里,然后在WebSocketHandler的afterConnectionEstablished方法中取出userId,和session绑定。
这里再分享一个排查技巧:在浏览器控制台的Network面板里,找到WS类型请求,点进去能看到WebSocket的帧数据。如果连接已经建立,但帧数据一直是空的,说明后端没把消息推送出来。这时候在后端Handler的handleTextMessage方法里加一段日志,看消息有没有到达后端。
5.4 文件上传后访问404
图片上传成功了,数据库里也有URL,但浏览器访问图片地址返回404。这个问题几乎每次都会遇到。
原因通常是SpringBoot的静态资源映射没有配置对。SpringBoot默认只映射classpath下的/static、/public等目录,你上传到磁盘的目录(比如D:/upload/)不在默认映射范围内,所以访问不到。需要手动配置静态资源映射,把/uploads/**映射到磁盘路径:
@Configuration public class WebMvcConfig implements WebMvcConfigurer { @Override public void addResourceHandlers(ResourceHandlerRegistry registry) { registry.addResourceHandler("/uploads/**") .addResourceLocations("file:D:/upload/"); } }另外还有一个坑:Windows下路径要写成file:D:/upload/,结尾的斜杠不能丢。如果路径漏了file:前缀,SpringBoot会以为你要定向到classpath下的相对路径,结果还是404。
6. 论文写作与毕业答辩准备心得
6.1 论文各章节写作思路
项目做完,论文也是一道坎。很多学生代码写得不错,但不知道论文每章该写什么。我的经验是,对于这种系统开发类题目,论文结构基本是固定的:
第一章绪论,写项目背景和意义、国内外研究现状、论文组织结构。背景写“随着移动互联网发展和高校学生社交需求增加”之类的,研究现状写国内外校园社交产品的发展情况,到这里一般就是几百字。
第二章相关技术介绍,把SpringBoot、Vue、MySQL、Redis这些技术各写一小节,讲清楚它们是什么、为什么选它。不要写得太浅,至少要能体现你理解这些技术的基本原理,比如SpringBoot的自动配置原理、Vue的响应式数据绑定机制、MySQL的事务与索引等。
第三章系统需求分析,这一章要画出系统功能结构图、用例图,写出功能性需求和非功能性需求。从需求分析文档的角度,把用户在系统里的角色和动作都描述清楚。
第四章系统设计,包括总体架构设计、功能模块设计、数据库设计。数据库设计这部分重点写表结构和表关系,配ER图最好。可以放几张关键的建表语句。
第五章系统实现,按功能模块逐个介绍,每一小节说明这个模块“实现了什么功能”、“核心代码怎么写的”、“运行效果怎么样”。这里代码不宜贴太长,贴关键片段,加文字说明,重点是讲清逻辑和思路。
第六章系统测试,写测试目的和环境、功能测试用例表(比如“输入正确用户名密码,预期登录成功,实际登录成功”)、性能测试(可以用JMeter压一下登录接口),最后写给系统测试结论。
第七章总结与展望,总结你做了什么、遇到什么难点、怎么解决的,然后提一下系统还存在的不足和后续改进方向(比如引入推荐算法、增加移动端适配)。
每次答辩前我都会提醒:论文格式很重要。目录自动生成、图表要编号、代码缩进统一、参考文献要有真正的链接。老师翻论文第一眼就是看格式,格式乱会显得极不专业。
6.2 答辩时老师最爱问的几个问题
根据我自己的经历和辅导过的学生反馈,校园交友网站的答辩问得最多的问题有这么几个:
“你的项目几个人完成的?” —— 如实说一个人完成,老师一般会追问工作量,这时候你要把功能模块数、数据库表数、核心功能点说清楚。
“数据库表为什么要这么设计?” —— 例如好友关系为什么要两张表,标签为什么单独建关联表,这些问题都能用第3节的内容回答。
“用户密码安全怎么保证?” —— 答:BCrypt加密存储、JWT认证、token设置过期时间。这就是亮点。
“聊天功能怎么实现实时收到消息?” —— 答:WebSocket长连接,服务器主动推送,并解释为什么比HTTP轮询好。
“如果恶意用户刷接口怎么办?” —— 可以答:后端限流(比如用拦截器对IP做访问频率控制),这是加分项。
“动态里发了敏感词怎么办?” —— 可以答:做一层敏感词过滤,后端校验后再入库。
“系统并发能力怎么样?” —— 不要乱吹。如实说:“毕设项目主要在功能完整性上做文章,目前针对单机场景做了优化,Redis缓存和数据库索引都用上了。如果后续要支持高并发,可以从引入Nginx反向代理、扩展Redis集群、读写分离等方向演进。”这样答既诚实,又显得有思考。
答辩时最重要的原则是:你自己的代码要能讲清楚,每张表、每个接口、每个按钮背后的逻辑都要过一遍。我就见过有学生答辩时连自己写的index.html里有什么组件都说不出来,这肯定不行。写代码的时候多问自己一句“我为什么这么写”,答辩的时候你就不会慌。
这个项目做完之后,又经过了多轮功能和兼容性验证。就目前来看,整体链路是稳定的,源码和数据库都能直接使用。如果你在部署运行中还有搞不定的报错,建议先按第5节的排查思路自己走一遍,大多数问题都是环境配置层面的,耐心一点都能解决。希望这篇拆解记录能帮你少走几步弯路,把更多时间花在真正理解这个系统上。
本文还有配套的精品资源,点击获取