一个很典型的全栈项目:后端用 Spring Boot 3,前端用 Vue 3,做成一个交友平台系统。这类项目在各类毕业设计、个人练手作品里出现频率相当高,但大多数写出来都停留在“能跑通”的层面,离“能拿得出手”还有不小距离。我聊的不是复刻一个demo,而是从一个稍微有点要求的从业者视角,把整个系统设计、技术选型、核心模块落地和埋坑经验完整过一遍。
这个系统可以解决什么问题,适合谁来看?如果你是正在做课程设计、毕业设计的学生,或者想找一套完整项目练手的前端/后端开发者,再或者准备把这套东西改造成商业项目的创业者,这篇内容都能给你一个完整的落地方案。文章不会只贴代码,而是把每一步“为什么这么干”“还有哪些坑没踩”讲清楚,确保你看完不只是会敲键盘,而是真的理解这个系统怎么设计、怎么实现、怎么优化。
1. 项目定位与技术栈选型的思路
1.1 交友平台的核心本质是什么
先说一句容易得罪人的大实话:交友平台和电商平台在底层架构思路上的相似度远高于差异。都会员体系、有内容流、有推荐逻辑、有站内信、有风控需求。区别只是交易对象从商品换成了人和关系。
把这个定位想清楚,后面的设计就不会跑偏。比如用户表怎么设计、资料字段怎么扩展,都可以参考通用的用户中心方案;推荐流不是简单按时间排,而是要把“匹配度”这个业务概念落地成可计算的指标;聊天不是简单地用 WebSocket 发消息,还要考虑离线消息、未读数、敏感词过滤这些真实场景里必须面对的问题。
这套系统的功能边界,我建议按照 MVP 思路来切:用户注册登录、资料完善与编辑、用户推荐与筛选、心动匹配(喜欢/不喜欢)、匹配成功后聊天。管理端预留用户管理和内容审核。这个范围既是完整闭环,又不会在前期拖着不交付。
1.2 为什么是 Spring Boot 3 + Vue 3,为什么不用其他组合
Spring Boot 3 是 2022 年底发布的大版本,最核心的变化是 Java 17 基线、Jakarta EE 命名空间迁移、Spring Security 6 的引入。很多人在迁移时踩坑,恰恰也说明这套技术栈在公司级项目里已经开始普及。换成低版本的 Spring Boot 2,安全框架和依赖管理都差一代,你写的东西可能一开始就落后于企业的实际招聘要求。
前端选 Vue 3 的原因更直观:Composition API 和<script setup>语法让组件逻辑复用变得非常自然,再加上 Vite 构建的冷启动速度和热更新体验,开发效率确实比 Vue 2 + Webpack 时代高出一截。配合 TypeScript,项目在规模膨胀时能兜住不少低级错误。
也有不少人纠结要不要直接用若依这种脚手架。我的建议是:如果是快速交付后台管理系统,若依 + Vue 3 版本确实省事;但这套交友平台的核心业务逻辑——匹配推荐、心动互动、聊天消息——若依并不帮你实现,反倒容易被它的代码风格带着走,最后写出来的东西不像你自己的。自己从零搭建一个精简版的项目骨架,成本并不高,还能把所有代码控制在可解释的范围内。
2. 系统整体设计:从模块拆解到数据库模型
2.1 功能模块怎么切才合理
整个系统我建议分成四个端:用户端前台、用户端聊天服务、管理后台、公共服务模块。
用户端前台包含注册登录、个人资料管理、推荐列表(滑动卡片)、用户详情、心动操作(喜欢/不喜欢)、匹配列表。聊天服务单独拆出来,是因为 WebSocket 连接的生命周期管理、消息推送、断线重连这些逻辑不宜和普通 HTTP 接口混在一个 Controller 里处理,否则上线后出问题定位起来非常头疼。管理后台负责用户帐号管理、举报处理、敏感词管理、数据统计。公共服务模块包含文件上传、短信验证码(开发期可 Mock)、字典维护、系统配置。
这种切法最大的好处是:开发时可以分模块并行推进,测试时可以逐个功能闭环验证,部署时也可以按服务拆。如果你做的是毕设,单机部署把所有模块放一个应用里也没问题,但代码目录结构必须保持这种模块化的形态,这是项目能不能持续扩展的关键。
2.2 数据库设计:核心表结构和字段说明
这一块我直接给一套经过实践验证的表设计,覆盖主要业务场景。
用户主表是地基,字段不能太死板。基础字段包含自增主键、手机号、邮箱、密码(BCrypt 加密存储)、昵称、头像 URL、性别、生日、城市、职业、个人简介、状态字段(正常/禁用/封禁)。注意,身高、收入、教育背景这类交友场景的高频筛选字段,不要全都塞进主表,单独挂一个用户资料扩展表更合理。扩展表用 user_id 做外键,字段包括身高、学历、婚姻状态、兴趣爱好(以 JSON 或逗号分隔存储)、活跃状态、最后登录时间。
标签体系是推荐匹配的重要依据。设计一张 tag 表(id、标签名、标签分类、使用次数),再用一张 user_tag_rel 关系表(id、user_id、tag_id)做关联。这样用户可以选择多个标签,推荐逻辑统计标签交集时只需要简单 join,效率也扛得住。
心动记录表,字段为主键、用户 ID、被操作用户 ID、操作类型(喜欢/不喜欢)、操作状态(待匹配/已互相喜欢)、创建时间。每天操作上限可以限制为 100 次,防止刷接口。互相喜欢后,在去重约束下生成一条匹配记录,匹配表(id、用户A、用户B、匹配时间、最近聊天时间、状态)负责承载后续聊天会话。
消息表是聊天功能的地基,包含消息 ID、会话 ID、发送者 ID、接收者 ID、消息类型(文本/图片/系统)、消息内容、发送时间、已读状态。这个表会持续膨胀,上线规模大一点就得按会话 ID 做分表,或者引入 MongoDB。但在项目阶段,MySQL + 按月归档已经足够。
举报和敏感词表也建议一开始就建好。举报表结构是主键、举报人 ID、被举报人 ID、举报类型、举报说明、处理状态、处理结果。敏感词表就是一张词库表,接口层做文本匹配过滤。
2.3 前后端交互协议与统一响应设计
前后端分离开发,最忌讳接口定义随心情变。建议一开始就约定统一响应格式:
{ "code": 200, "message": "success", "data": {} }状态码 200 表示成功,400 表示参数错误,401 表示未认证,403 表示无权限,500 表示服务器异常。业务状态的错误码,比如“对方已将你拉黑”“不能对自己操作”这类,放在 code 字段里用自定义错误码表示。前端的 axios 拦截器统一判断 code,不用每次请求都手写一套异常处理逻辑。
接口路径的建议:认证相关/api/auth/**,用户相关/api/user/**,匹配相关/api/match/**,聊天相关/api/chat/**。管理后台单独放/api/admin/**,每个路径前缀对应不同的访问权限级别,配合 Spring Security 配置,权限一目了然。
3. 核心模块实现:认证、资料、推荐、聊天的具体落地
3.1 注册登录与 JWT 鉴权链路详解
Spring Boot 3 引入了 Spring Security 6,这个版本我建议不要只停留在“会配置”的阶段,要深入理解认证流程。核心链路是这样的:用户注册时密码用BCryptPasswordEncoder加密入库,登录时从数据库查出用户,比对密码,通过后生成 JWT 令牌返回前端。后续请求前端在 Authorization 头携带Bearer token,后端通过 JWT 过滤器解析用户信息,塞进 SecurityContext。
关键实现点有三个。第一个是 JWT 的有效期策略,Access Token 建议设置为 2 小时,过期后前端用 Refresh Token 调/auth/refresh换取新的 Access Token。如果不做刷新逻辑,用户体验就是聊得正起劲突然要重新登录,非常掉价。Refresh Token 的有效期可以设为 7 天,存在 Redis 里,后端收到刷新请求时先校验 Redis 里的 token 是否有效。
第二个是登录接口加验证码。图形验证码用 Hutool 的 CaptchaUtil 生成,存 Redis 的时候要绑定一个设备指纹(前端生成的 UUID),有效时间 5 分钟。刷接口的问题用阿里云短信服务做验证码下发,开发环境用 9999 这类固定验证码 Mock,但代码链路必须完整。不能因为开发期偷懒就不写这层逻辑,不然上线前测试会漏掉一大堆问题。
第三个是 Spring Security 6 的配置姿势和旧版本完全不同。用新版配置类示例:
httpSecurity .csrf(AbstractHttpConfigurer::disable) .sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS)) .authorizeHttpRequests(auth -> auth .requestMatchers("/api/auth/**", "/ws/**").permitAll() .requestMatchers("/api/admin/**").hasRole("ADMIN") .anyRequest().authenticated() ) .addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class);注意请求匹配器写法从antMatchers变成了requestMatchers,参数也从字符串变成了RequestMatcher对象,这是升级后最容易踩的第一个坑。
3.2 个人资料与标签体系:为匹配算法打地基
推荐匹配的质量,直接取决于用户资料的结构化程度。很多交友平台把自己的推荐做砸了,不是因为算法不够高级,而是因为压根没有足够好的特征数据。所以在做资料模块的时候,我的建议是让用户在注册时允许跳过信息填写,吸引进入后,再让用户在浏览推荐卡片的时候顺手补全。前端设计时,编辑资料的入口要明显,交互要轻量,每完成一项资料完善,可以弹一个小提示“资料完善度达到 80%,曝光度提升 X%”。
标签选择的交互要注意,不能让用户漫无目的地选 10 个标签,那样数据质量很差。正确做法是引导用户选择 3 到 5 个、最多不超过 8 个,并且在标签库中优先展示高频热门标签。标签分类可以设计成:性格特征(温柔、幽默、开朗)、兴趣爱好(健身、旅行、音乐)、生活方式(养猫、夜猫子、素食)、职业标签(程序员、设计师、教师)四类。后端存储时,标签插到关系表之前要先去重,保证同一个标签只能赋值一次。
3.3 推荐匹配:先用规则打底,再考虑算法进阶
对于大多数项目阶段来说,我强烈不建议一上来就搞协同过滤或者向量召回。数据量不够时,算法模型的效果还不如一套设计良好的规则策略。推荐列表的排序可以从这几个维度计算综合评分:
- 标签匹配度:双方共同标签数,每相同一个加 30 分
- 城市匹配:同城优先加分 100 分
- 年龄偏好:用户在筛选条件里设置的期望年龄区间,命中加 50 分
- 资料完善度:对方资料完善度高于 80% 的加 20 分
- 活跃度:对方 24 小时内有登录行为的加 30 分
最终推荐列表按综合分数倒序排列,同时把用户已经喜欢或已经划过的人排除掉。实现上,先查当前用户的筛选条件,再查用户表中符合条件的候选池,在内存里算分数排序。候选池数量超过 1 万之后再考虑用 Redis ZSET 做实时排序,或者引入 Elasticsearch 做分词和地理位置检索。项目初始阶段用SQL 条件过滤 + Java 内存排序完全够用。
这里配套一个滑动卡片交互机制。前端每次加载 10 位用户,呼出下一批的时机是剩余卡片数小于 3 张时自动预请求。每次滑动操作立即调用匹配接口,后端判断对方是否已经喜欢了当前用户,如果是,返回“匹配成功”标识,前端弹窗展示匹配动画,生成会话并引导进入聊天;如果不是,只记录操作结果。为防止匹配延迟,这个接口可以异步处理,主链路直接返回当前操作结果,匹配关系在异步线程里写入,前端匹配成功提示以 WebSocket 推送为准。
3.4 聊天模块:WebSocket + 离线消息 + 敏感词过滤
聊天是交友平台留存的核心功能,这一块的体验好坏直接决定用户会不会回来。我建议后端用 WebSocket 建立长连接,前端用封装后的 Socket 管理器管理连接状态。
建立连接时,客户端需要把 JWT Token 拼到 URL 的查询参数里,服务端通过拦截器完成鉴权并拿到 userId。这样设计的好处是,Spring WebSocket 的规范接口里不方便塞 Header,Token 参数化是最常用的解法。拿到 userId 后,把 WebSocket 会话和 userId 的映射关系存到 ConcurrentHashMap 中,同时把用户的在线状态同步更新到 Redis,并广播一条“好友上线”的事件给匹配过的用户。
消息推送链路要处理好三个状态:在线直接推送、离线存消息表、对方重新上线后拉取未读数。推送成功后要更新会话的最近一条消息内容,同时给前端推送一个“消息已送达”的回执。
敏感词过滤这一块,建议用 DFA 算法实现,而不是简单的批量String.contains判断。DFA 构建词库为树形结构之后,匹配效率是 O(n) 级别的,十万量级的敏感词库毫秒级完成。过滤的场景覆盖用户昵称、个人简介、聊天文本三处。命中敏感词的消息,存储时用*替换敏感词,库里的原文不留,避免事后吃举报亏。
4. 管理后台:内容安全与用户运营
4.1 审核机制:用户举报处理和封禁闭环
交友平台最怕的就是出现安全事件之后没有处理链路。管理后台必须包含用户举报列表、举报详情(被举报内容、聊天记录截取、资料信息)、处理动作(忽略、警告、封禁 7 天、永久封禁)。封禁操作执行后,需要同步清理该用户的在线状态、强制 WebSocket 断连,避免已经封禁的用户还能继续发消息。
推荐在用户封禁表之外再记录一份封禁操作日志,包含操作人员、封禁时长、原因、操作时间。这是为了事后审计,也是平台免责的关键依据。
4.2 数据统计:运营手里得有一份看得懂的报表
管理后台统计面板建议显示这些数据:注册用户数(按天趋势)、活跃用户数(DAU/MAU)、匹配成功数、成功配对率、聊天消息量趋势、日均举报量。这些指标可以通过定时任务在凌晨汇总前一天的数据,写入统计表,后台查询统计表展示趋势图。前端用 ECharts + Vue 3 封装图表组件,一眼看清平台状态。统计数字别搞得太复杂,先满足核心运营需求,后续要精细化再引入 BI 系统。
5. 开发环境搭建与部署方案
5.1 后端工程结构配置详解
创建一个 Spring Boot 3 项目,最稳的方式是去 Spring Initializr 生成基础骨架,选择 Java 17、Spring Web、Spring Security、Spring Data JPA(或 MyBatis)、MySQL Driver、Lombok。Sping Boot 3 默认不支持javax命名空间,如果你要引入第三方库,注意选择兼容jakarta的版本。用 MyBatis 的话,请直接引入mybatis-plus-spring-boot3-starter,老版 starter 的自动配置在 Spring Boot 3 下会失灵。
工程结构按模块分包:
com.platform ├── config # 跨域、WebSocket、Security 等配置类 ├── controller # 收到 HTTP 请求的入口层 ├── service # 业务逻辑层 ├── mapper # MyBatis 或 JPA 的数据访问层 ├── model # 实体类、DTO、VO ├── common # 统一响应类、异常处理、枚举 └── utils # JWT、敏感词过滤等工具类配置文件中,数据源连接用druid连接池,Redis 配置序列化器时最好用 Jackson 而不是默认的 JdkSerializationRedisSerializer,否则缓存数据在前端对接容易出编码问题。文件上传接口单独配了最大文件大小限制:
spring: servlet: multipart: max-file-size: 10MB max-request-size: 50MB5.2 前端工程搭建:Vite + Vue 3 + Pinia + Element Plus
前端骨架用npm create vite@latest初始化,模板选vue-ts,配合自动导入插件unplugin-auto-import和unplugin-vue-components,路由则用vue-router,状态管理用Pinia。Vue 3 官方推荐的 Element Plus 组件库在表单、弹窗、表格等场景能极大节省开发时间。
安装 scss 依赖只需要两步:npm install -D sass,然后<style lang="scss" scoped>直接用就能编译。如果遇到Vite编译sass时报错版本兼容问题,大多数情况下是sass版本过新导致legacy-js-api警告,可以在 vite 配置里加上css.preprocessorOptions.scss.additionalData或把sass版本锁到1.69.x。
跨域问题不要依赖前端代理去解决生产问题。开发环境中 Vite 配置server.proxy把/api代理到后端端口即可;但生产环境部署时前后端通常分域部署,最稳妥的是在后端 Security 配置中统一加跨域过滤器:
@Override public void addCorsMappings(CorsRegistry registry) { registry.addMapping("/**") .allowedOriginPatterns("*") .allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS") .allowedHeaders("*") .allowCredentials(true) .maxAge(3600); }注意allowedOriginPatterns("*")和allowedOrigins("*")在携带 Cookie 时有区别,前者可以与allowCredentials(true)共存,后者不能。
5.3 部署上线:用 Docker Compose 一把梭
单机部署场景下,推荐用 Docker Compose 把前端 Nginx、后端服务、MySQL、Redis 四个容器一次性拉起来。核心配置文件贴一下:
version: "3.8" services: mysql: image: mysql:8.0 environment: MYSQL_ROOT_PASSWORD: root123 MYSQL_DATABASE: dating_platform ports: - "3306:3306" volumes: - ./mysql-data:/var/lib/mysql redis: image: redis:7.0 ports: - "6379:6379" backend: build: ./backend depends_on: - mysql - redis environment: SPRING_PROFILES_ACTIVE: prod ports: - "8080:8080" frontend: build: ./frontend ports: - "80:80"前端 Dockerfile 里用多阶段构建,先 node 构建静态资源,再拷贝到 nginx 镜像中,这样最终镜像的体积会小很多。Nginx 里的反向代理只用配一条:
location /api/ { proxy_pass http://backend:8080/api/; } location /ws/ { proxy_pass http://backend:8080/ws/; proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection "upgrade"; }6. 开发中踩过的坑与调试技巧
6.1 WebSocket 连不上与断线重连问题
遇到最多的坑是 Nginx 默认的 60 秒超时导致 WebSocket 连接被切断。配置里必须显式设置proxy_read_timeout和proxy_send_timeout为 3600s,同时设置Connection: upgrade的转发头。
前端断线重连逻辑要写干净:监听onclose事件,指数退避重连(间隔 1s、2s、4s、8s,最大 30 秒),重连成功后补偿拉取断线期间的消息。后端也要在会话关闭事件里及时清理内存映射,防止会话泄漏导致内存持续增长。
6.2 Spring Boot 3 + MyBatis 兼容性问题
自研项目用 MyBatis 时,千万别用mybatis-spring-boot-starter的普通版本,否则你会看到Invalid value type for attribute 'factoryBeanObjectType'之类的报错。直接引入:
<dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus-spring-boot3-starter</artifactId> <version>3.5.5</version> </dependency>如果项目里需要分页查询,需要额外引入mybatis-plus-jsqlparser依赖(3.5.6 版本以上默认分割了分页插件依赖),不然PaginationInnerInterceptor会报找不到类的错误。这一点官方文档写得不明显,我当时排查了好几个小时。
6.3 图片上传后访问 404 的问题
Spring Boot 静态资源映射默认不把你上传目录暴露出来。上传文件保存到本地的绝对路径下,需要通过一个映射配置,把一个 URL 前缀指向本地目录,否则前端<img src="/upload/xxx.jpg">会直接 404。这也解释了为什么把上传目录放到资源目录里很多人改不成。最快的解法:
@Configuration public class WebMvcConfig implements WebMvcConfigurer { @Override public void addResourceHandlers(ResourceHandlerRegistry registry) { registry.addResourceHandler("/upload/**") .addResourceLocations("file:" + uploadPath); } }6.4 前后端联调时的日期格式坑
接口响应中的日期类型默认是时间戳格式,前端处理后能在控制台看到一串数字,而不是2024-01-15 14:30:00。统一在 application.yml 里配置:
spring: jackson: date-format: yyyy-MM-dd HH:mm:ss time-zone: GMT+8前端获取到之后直接展示,省去每个字段手动转换的过程。如果前端也需要传日期字符串给后端,配合@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")双管齐下,确保同一格式反向解析成功。
7. 拓展与优化方向:这套系统还能怎么升级
项目跑通只是起点,真正让一个系统有含金量的是后续的扩展思路。
推荐策略升级是第一个方向。目前是规则排序,积累了足够多的用户行为数据之后,可以引入协同过滤:用户 A 和用户 B 对一批用户的“喜欢/不喜欢”操作序列相似,则 A 喜欢过的用户更有可能被 B 喜欢。这一步用 Python 脚本离线计算,给用户打上候选集合后同步到 Redis 即可,不用全部推倒重写。
即时通讯升级方向是基于 MQ 实现消息异步可靠投递。现在的直接推送方案在用户量几百到几千都没问题,但消息量上来后建议引入 RocketMQ 解耦:消息先发到 MQ,消费端落库并推送,削峰填谷,消息不丢。这个改造逻辑清晰,不会动到上层业务接口。
最后想说,做这类系统最忌讳的就是停留在“接口能调通”的自我满足上。找一个朋友同时用你的平台互相匹配聊天,把所有异常链路走一遍:封禁后能不能登录、互相喜欢后消息是否即时到达、长时间在线内存会不会泄漏。这些问题不是写代码时能想到的,必须靠真实使用才暴露得出来。项目交付物里,能讲清每个模块为什么这么设计、上线前排查过哪些问题,比单纯多几个功能点加分得多。