☰
基于Spring Boot的交友系统开发实战:从数据库设计到WebSocket部署
2026/10/18 10:27:10 网站建设 项目流程

在毕业设计的选题池里,“交友系统”几乎是最容易让老师眼前一亮、又最容易让新手翻车的题目。说得直白一点,这个题目听起来比“图书管理系统”高级,但做起来又不像“电商平台”那么庞大,非常适合展示Spring Boot、数据库设计、实时通讯这几块核心技能。我帮不少人梳理过这类项目的骨架,自己也完整跟过一套“基于Java Spring Boot的交友系统”从零到落地的全过程。今天这篇就把整个脉络拆开讲清楚,从需求分析、功能模块、数据库设计到技术选型和部署避坑,一条线捋到底,希望能让正在搞毕业设计或者想练手的朋友少走弯路。

1. 交友系统的整体设计与思路拆解

1.1 这个题目到底在考什么

很多同学看到“交友系统”第一反应是“不就是个聊天软件吗”,这个理解不算错,但放在毕业设计里,它考的东西比想象中多得多。导师真正想看的是你对业务场景的理解、对数据关系的建模能力、对实时通信和权限控制这些技术难点的把握。换句话说,这不仅仅是一个CRUD项目,而是一个带业务逻辑、带安全要求、带实时交互的综合系统。

从实际完成度来看,一个达标的交友系统至少需要覆盖几个层面:用户注册登录、个人资料管理、用户匹配或推荐、好友关系管理、即时聊天、系统通知。如果再往上加难度,还可以考虑动态广场、点赞评论、兴趣标签匹配、地理位置附近的人这类社交属性更强的功能。但核心始终是“用户”和“关系”这两个字。

1.2 技术选型背后的取舍逻辑

技术栈这一块,题目已经限定得比较明确:Spring Boot + Java。这本身就是当前Java后端开发的主流组合,选它不会错。但具体到每个环节,还是有不少可以斟酌的地方。

  • 持久层框架:MyBatis Plus 和 Spring Data JPA 都有人用。我的建议是,如果你对SQL更熟悉,想精细控制查询逻辑,选 MyBatis Plus,它在分页、条件构造器、代码生成这些方面对新手特别友好。如果对实体映射和仓储模式更熟悉,JPA写起来更快,但碰到复杂查询时反而容易懵。多数毕业设计场景下,MyBatis Plus 是更稳妥的选择。
  • 数据库:MySQL 是标配,不用犹豫。关于要不要用 Redis,如果只是做一个简单的交友系统Demo,Redis 不加也没太大问题。但如果功能里有验证码、在线状态、热点数据缓存,加一个 Redis 会让方案的完整性高很多。导师答辩时问到“缓存策略”,你答得上来,这就是加分项。
  • 实时聊天:这是交友系统区别于普通管理系统的关键。技术上有轮询、长轮询、WebSocket、Netty + WebSocket 几条路。毕设层面最合适的是 Spring Boot 自带的 WebSocket 模块,它和 Spring 集成度好,代码量适中,能轻松实现一对一聊天和在线状态推送。Netty 性能是更强,但对新手来说实现成本偏高,性价比不大。
  • 安全认证:Shiro 和 Spring Security 都行。Spring Security + JWT 是现在前后端分离场景下的主流方案,虽然学习曲线稍微陡一点,但一劳永逸。JWT 无状态认证对于交友系统这种需要接口鉴权的场景很合适。

1.3 怎么控制项目规模才不会被拖垮

交友系统很好做,但如果不懂克制,也极其容易失控。我见过有同学把功能表列得满满当当,从语音聊天到视频通话到礼物打赏全都要做,结果做了三个月还停留在登录注册页面。毕设项目的核心是完整,不是庞大。

合理规划应该是:基础功能(注册、登录、资料管理)+ 核心业务(用户推荐或搜索、好友申请与处理、私聊)+ 锦上添花(动态广场、消息通知)。先把骨架跑通,再根据时间决定要不要加皮肤。这个顺序非常重要,因为交友系统的难点往往不是单个功能的实现,而是多个模块之间的数据串联,比如“好友申请通过之后要建立会话关系”“发消息之前要验证好友状态”,这些联动才是真正花时间的地方。

2. 核心功能模块与数据库设计

2.1 用户与资料模块:一切社交关系的起点

用户模块是交友系统的最底层依赖,这个模块设计得好不好,直接决定了后续所有功能的开发效率。注册环节要有手机号或邮箱 + 密码,密码必须加密存储,推荐使用 BCrypt,不要用什么 MD5 之类,那玩意儿在答辩现场被问到安全问题会很尴尬。注册流程最好加上一个验证码机制,哪怕是简单的图形验证码都行,这既能体现你对接口安全的思考,也能防止脚本批量注册。

用户资料这块,除了基本的昵称、头像、性别、生日,最关键的是兴趣标签和个人简介。这两个字段是后续做推荐和匹配的数据基础。很多人做交友系统喜欢随机推荐用户,看起来功能是通了,但完全没有业务灵魂,答辩时老师一句“你的推荐逻辑是什么”就能让人语塞。合理的做法是给用户预置一组兴趣标签,比如电影、运动、读书、旅行、游戏,然后通过标签重合度去推荐“可能感兴趣的人”。

2.2 好友关系与匹配逻辑:数据表的“关系”怎么设计

好友关系设计会区分很多种方案。最经典的是两张表:好友关系表(记录双方 ID 和关系状态)+ 好友申请表(记录申请方、接收方、申请时间和状态)。关系状态可以定义为 0 待处理、1 已通过、2 已拒绝。这里有一个特别容易踩的坑:好友关系必须存储为两条记录,或者必须用字段约束好“小 ID 在前、大 ID 在后”的唯一规则。否则,A 加 B 和 B 加 A 会被当成两条不同的记录,好友列表里就会出现“明明已经是好友了,列表却显示两条单向关系”的诡异 bug。

如果做了“附近的人”功能,那用户表里还需要存储经度和纬度。查询附近的人的 SQL 一般用 Haversine 公式计算距离,或者为了简化,直接用经纬度范围粗筛再精确计算。毕设阶段用后一种方法就足够了,不用上什么地理位置搜索引擎,那是给自己找麻烦。

2.3 即时通讯模块:聊天记录与未读数处理

聊天模块是交友系统的技术高点,做的时候优先想清楚两件事:消息的实时性和消息的存储。实时性靠 WebSocket 解决,服务端在用户连接时维护一个会话池,收到消息后根据目标用户 ID 找到对应的 WebSocket Session 并推送。存储方面,既可以在发送消息时直接写库,也可以先推后异步落库。毕设阶段直接同步写库是最简单的,只要在同一事务里先落库再推送,或者先推送再落库,稍微注意一下消息丢失的边界情况就行。

未读数看似简单,实际上有很多细节。每张聊天记录表里都有 status 或 is_read 字段,接收方进入会话时把会话内消息置为已读。未读数统计其实就是 count of unread messages per session。但如果你做了“总未读消息数”,就要维护会话维度去重,这个逻辑绕一圈很容易绕晕。建议毕设阶段只做总未读数累加即可,等系统复杂度上去了再优化。

2.4 数据库表结构设计参考

下面给出一套我在实操中使用过且验证没有明显缺陷的表结构。这套设计覆盖了交友系统的核心链路,字段命名和类型都是真实跑过的,可直接作为底稿使用。

  • user(用户表):id、username、password(BCrypt 密文)、nickname、avatar、gender(0 男、1 女)、birthday、city、bio(个人简介)、interest_tags(JSON 或逗号分隔字符串)、create_time、update_time
  • user_friend(好友关系表):id、user_id、friend_id、status(0 正常、1 已删除)、create_time
  • friend_request(好友申请表):id、from_user_id、to_user_id、apply_msg、status(0 待处理、1 通过、2 拒绝)、handle_time
  • chat_message(聊天记录表):id、from_user_id、to_user_id、content、msg_type(0 文本、1 图片)、is_read(0 未读、1 已读)、create_time
  • moment(动态表):id、user_id、content、image_url、like_count、comment_count、create_time
  • moment_like(点赞表):id、moment_id、user_id、create_time
  • moment_comment(评论表):id、moment_id、user_id、content、create_time

这套表结构没有做拆库分表,也没有冗余点赞数,但足够支撑一个 5~10 万用户规模的毕设项目。导师问到性能问题时,你只需要说“通过索引和 Redis 热点缓存应对,生产环境可通过水平拆分扩展”,这句话的含金量远高于你硬造一个复杂结构。

3. 实操过程与核心环节实现

3.1 项目初始化和基础环境准备

我建议使用 Spring Initializr 创建项目,依赖勾选 Spring Web、Spring Security、MyBatis Plus、MySQL Driver、Validation、Lombok、WebSocket。Java 版本选 JDK 1.8 或 JDK 11 都可以,Spring Boot 2.x 和这两个版本配合最省心。如果选 Spring Boot 3.x,需要注意 JDK 17 的最低要求,部分旧教程的配置会有出入,查起来比较费劲。

项目结构上,规范的包划分是 com.example.social 下的 controller、service、mapper、entity、config、common、dto、vo。页面和静态资源放到 src/main/resources/static 或 templates。如果是前后端分离方案,那么前端独立部署到 Nginx 或直接通过静态资源访问,后端只提供 RESTful API。毕设场景里,绝大多数人会选择简单的前后端分离:前端用 Thymeleaf 渲染或者独立 Vue 项目。如果时间吃紧,直接用 Thymeleaf 加 Bootstrap 是最低的实现成本,但如果你想展示前后端分离的能力,Vue + Element UI 是稳妥选项。

3.2 注册登录与 JWT 认证的实现细节

注册接口的流程是接收 username 和 password,校验用户名是否重复,密码用 BCryptPasswordEncoder 加密后落库。这里有一个隐藏细节:Spring Security 的 UserDetailsService 需要加载用户并返回 UserDetails 对象。如果你用 JWT,可以在登录成功后生成 token,返回给前端;前端后续请求在 Authorization 头带 token,后端通过 OncePerRequestFilter 解析 token 并设置 SecurityContext。

我实际写过这一套,有一个必须提醒的点:token 过期时间要设置合理。太长不安全,太短用户体验差。建议设 24 小时,并且配合前端 401 拦截后跳转登录页的逻辑。还有一点是,JWT 是无状态的,如果你要做“退出登录”并且希望 token 立即失效,那就必须在 Redis 里维护一个 token 黑名单,或者用“双 token 方案”。毕设阶段做前者就可以了,讲解时重点说清楚“为什么要做失效处理”和“目前通过 Redis 实现”。

3.3 用户推荐算法的简单落地

推荐逻辑不要做得太玄乎,核心是“标签相似度 + 排除已有关系”。SQL 或内存过滤都可以。如果用户量不大,直接查出当前用户标签集合,再查所有用户,用 Java 流式统计标签交集个数,按交集个数降序排序,排除自己、已是好友、已经申请过的人。

这个逻辑看起来有点“笨”,但效果非常直观,面试官或答辩老师一听就懂。附上关键代码示意:

public List<UserVO> recommendUsers(Long userId) { User currentUser = userService.getById(userId); List<String> myTags = Arrays.asList(currentUser.getInterestTags().split(",")); List<User> allUsers = userService.list(); return allUsers.stream() .filter(u -> !u.getId().equals(userId)) .filter(u -> !friendService.isFriend(userId, u.getId())) .filter(u -> !friendRequestService.existsPendingRequest(userId, u.getId())) .sorted((u1, u2) -> score(u2, myTags) - score(u1, myTags)) .limit(20) .map(UserVO::from) .collect(Collectors.toList()); } private int score(User user, List<String> myTags) { List<String> targetTags = Arrays.asList(user.getInterestTags().split(",")); return (int) myTags.stream().filter(targetTags::contains).count(); }

你不用把这段代码原封不动放进论文,但把它理解透,答辩时能大大方方讲清楚逻辑,这比什么都强。

3.4 WebSocket 私聊与消息推送

用 Spring Boot 原生 WebSocket,先注册一个 WebSocketHandler,把每个连接的用户 ID 和 Session 绑定到一个 ConcurrentHashMap 中。客户端连接时带上 token 作为查询参数,后端校验通过后再建立连接。

消息流程大概是这样:用户 A 发送消息给用户 B → 服务端落库 → 从在线用户表里找到 B 的 Session → 构造消息体推送。B 不在线的话,消息落库就好,等 B 上线后拉取未读消息,或者通过系统通知提醒。

下面是一个简化版 WebSocket 处理器的结构:

@Component public class ChatWebSocketHandler extends TextWebSocketHandler { private final Map<Long, WebSocketSession> sessionMap = new ConcurrentHashMap<>(); @Override public void afterConnectionEstablished(WebSocketSession session) { Long userId = (Long) session.getAttributes().get("userId"); sessionMap.put(userId, session); } @Override protected void handleTextMessage(WebSocketSession session, TextMessage message) { JSONObject payload = JSONObject.parseObject(message.getPayload()); Long toUserId = payload.getLong("toUserId"); String content = payload.getString("content"); // 1. 落库 // 2. 获取目标 session,推送消息 WebSocketSession targetSession = sessionMap.get(toUserId); if (targetSession != null && targetSession.isOpen()) { targetSession.sendMessage(new TextMessage(...)); } } }

实际项目中肯定还要加心跳检测、断线重连、未读推送,但这些属于细节优化,先跑通主流程最重要。

3.5 从本地到服务器部署的完整路径

开发完成后,部署是一个不可回避的环节。我的建议是买一台云服务器,配置不用高,2核4G 足够跑 PostgreSQL 或 MySQL 加一个 Spring Boot 应用。步骤如下:打包成 jar → 上传到服务器 → 安装并启动 MySQL/Tomcat → 运行 jar → 配置 Nginx 反向代理,前端请求 /api 走后端,WebSocket 走 ws 协议。最后别忘了修改后端配置中的数据库链接、Redis 地址为服务器内网地址,不要用 localhost,否则远程连不上。

还有一个很常见的坑是 WebSocket 在反向代理里默认不支持长久连接。Nginx 配置需要加上 Upgrade 和 Connection 头。如果不加,前端会不断触发 101 切换协议失败,表现就是“能连上,一秒就断”。这个原因很隐蔽,出现过好多次了,提前留意。

4. 常见问题与排查技巧实录

4.1 登录认证 401 或 403 的处理

如果你的接口一直被拦截器挡在门外,先不要怀疑配置。JWT 过滤器里如果没放行登录和注册接口,就会出问题。常用处理方式是在 Security 配置中设置 permitAll 路径:/api/auth/login、/api/auth/register、/ws/**。排查时可以先用浏览器控制台查看请求头,看是否带上了 Authorization。很多时候是前端没有统一处理登录后的 token 存储,导致后续请求根本没带 token。

4.2 聊天消息不能实时到达

出现这个问题时先分清两种情况:对方在线但收不到;对方不在线,上线后也没有未读。第一种情况基本是 WebSocket 连接没有建立成功,打开浏览器 F12 看 Network 面板里有没有 WebSocket 连接记录,连接失败的原因通常是 token 校验失败或者握手地址写错。第二种情况要检查未读消息的拉取逻辑:进入会话时是否调用了已读接口,消息查询是否按会话维度过滤。

4.3 数据库连接错误和表字段类型不一致

错误信息里有 Communications link failure 或 Public Key Retrieval is not allowed,基本就是数据库连接串和驱动配置的问题。比较常见的是 MySQL 8.x 的 driver 类名和连接地址后缀,如果少了 useSSL=false 和 serverTimezone=Asia/Shanghai,就会出现连接超时或时间错乱的情况。表字段方面,Boolean 字段在 MySQL 里映射 tinyint(1),MyBatis Plus 默认能处理,但如果用 Integer 接收 tinyint,要注意 0 和 1 的逻辑对应,别搞反。

4.4 并发下重复好友申请怎么防

用户狂点“申请好友”按钮,会产生多条重复申请记录。应对措施有两个层面:数据库层面,在 friend_request 表上加联合唯一索引 (from_user_id, to_user_id, status);业务层面,在申请前先查询是否已有待处理记录。推荐两处都做。毕设答辩时讲出“唯一索引兜底 + 业务层预判”,评委一般就不会为难你了。

4.5 图片上传后无法访问

这是最幼稚也最耽误事的坑。图片上传成功,数据库也保存了路径,但页面就是加载不出来。多半原因就是上传到了本地临时目录,或者没有配置静态资源映射。WebMvcConfigurer 里 addResourceHandlers 要映射 /upload/** 到磁盘路径,或者你直接把图片存到项目运行目录的 /static/upload 下。多人开发时,本地磁盘路径还不一样,更建议上传用的是相对路径加资源映射。

4.6 性能优化的小建议

查询层面,好友列表、聊天记录、动态列表这三块是高频查询,一定要给外键和常用条件字段加索引。例如 chat_message 里 (from_user_id, to_user_id, create_time) 联查会走得好很多。热点用户信息可以缓存进 Redis,比如前缀 user:基本资料:ID。WebSocket 消息队列里可以加一个阻塞队列做异步推送,防止大量消息时阻塞主线程。这些都是小改动大收益的点,写在论文里也能体现工程素养。

5. 实操心得和进阶建议

整个项目做下来,我最大的感受是,交友系统最难的部分不是某个技术点,而是如何把用户、关系、消息、状态这么多条线串起来。这也是为什么很多同学单看每个模块觉得“都会”,一联调就这里漏登录态、那里漏好友限制。建议在动手写代码之前,先花两三天时间画清楚数据流转图,把“用户 A 添加用户 B 为好友”的完整路径从页面到数据库走一遍,“A 给 B 发消息”的全链路再画一遍。纸上理顺了,代码基本上不太会翻车。

如果做完基础版本还想继续扩展,我建议优先级这样排:动态广场和评论 > 消息通知系统 > 兴趣匹配算法优化 > 在线状态监控。动态广场能让项目看上去更完整,消息通知能把 WebSocket 的用途放大,匹配算法优化则是最能体现个人思考的部分。这些做好任何一项,放进论文都能变成亮点,而不是流水账。

最后分享一个小经验:在答辩前一定要准备一段完整的“用户操作演示流程”。从注册、登录、完善资料、搜索用户、发送好友申请、通过申请、进入聊天、发送消息,一气呵成。整套流程如果跑得顺,导师基本不会在细节上挑刺。要是演示过程出现“发消息对方收不到”这种问题,就算论文写得再好,印象分也会掉一截。所以至少要留出三天时间专门做联调和模拟测试,把网络、路径、token 等等全部按真实环境过一遍。这个流程,值得你比其他任何环节都多花时间。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询