☰
SpringBoot+Vue交友网站管理系统设计与开发实战详解
2026/10/14 9:04:19 网站建设 项目流程

社交类项目管理系统的开发,最核心的一点其实不是把页面做得多花哨,而是把“人和人怎么建立连接”这个逻辑彻底想清楚。我最近刚完整跑通一套基于SpringBoot+Vue的志同道合交友网站管理系统,技术栈就是Java、MySQL、MyBatis,前后端分离,功能覆盖用户注册登录、兴趣标签匹配、动态广场、点赞评论、私信聊天、后台管理和运营数据看板。这篇文章打算把项目的完整设计思路和实现细节都摊开讲,重点说清楚两件事:一是“志同道合”这个产品概念在代码层面到底怎么落地,二是在实际开发联调过程中你一定会遇到的那些坑。

1. 项目定位:交友系统的本质是匹配,不是聊天

1.1 “志同道合”这个需求如何翻译成技术方案

很多人一听说交友网站,第一反应是消息推送、实时聊天、在线状态这类功能。但如果你把一个交友系统当“管理系统”来做,核心业务诉求其实是另一回事:让合适的人看到彼此。这里的“合适”就是标题里反复强调的“志同道合”。

那“志同道合”在代码里怎么表达?最接地气的方案就是兴趣标签体系。用户注册后从系统提供的标签字典里选择自己感兴趣的内容,比如“健身”“读书”“摄影”“自驾游”“独立游戏”“美食探店”等,系统再根据用户标签集合的重合程度计算相似度,把共同标签多的用户互相推荐。标签体系的好处是业务语义清晰,算法透明可解释,推广运营也方便——想加热度就往标签表里加记录,前端动态刷新就行。

我实际做这个项目时,把匹配逻辑拆成了三层:

  • 基础过滤层:排除自己、排除被封禁用户、排除已经互相屏蔽的人。
  • 相似度计算层:统计当前用户与被推荐用户之间的共同标签数量,按数量降序排列。
  • 可选条件层:按城市、性别、年龄段过滤,让推荐结果更符合使用场景。

这三层逻辑拆清楚之后,开发节奏就非常明确:先做标签CRUD,再做匹配接口,最后接前端推荐页。整个过程不需要上什么复杂算法,一个正常的三表关联查询就能解决大部分需求。

1.2 功能全景:用户端和管理端的边界划分

这个项目不是单页面应用,而是天然分成两个操作侧,对应两套完全不同的角色权限。

普通用户侧包含:

  • 注册登录、个人资料维护、头像上传、兴趣标签设置。
  • 首页推荐流:根据匹配算法展示可能感兴趣的人,支持点击查看主页详情。
  • 动态广场:发布图文动态、浏览他人动态、点赞、评论。
  • 互动消息:关注/打招呼、私信聊天、消息未读数统计。
  • 举报:遇到不当用户或动态时可以提交举报工单。

后台管理侧包含:

  • 用户管理:用户列表、关键词检索、账号禁用/解禁。
  • 内容管理:动态审核上下架、评论删除。
  • 标签管理:标签字典的增删改查,调整分类。
  • 举报管理:处理用户提交的举报记录。
  • 数据看板:展示用户总数、今日注册量、动态总数、待处理举报数。

把管理端做出来,整个项目才不是一个“能聊天”的空壳,而是一个具备基础运营能力的完整业务系统。很多人在做这类项目时常犯的毛病就是只做用户端、不做管理端,评审或展示的时候就会显得深度不够。

2. 技术选型:为什么是SpringBoot+Vue+MyBatis这套组合

2.1 SpringBoot与Vue的配合逻辑

前后端分离是目前Java Web项目的主流形态。SpringBoot负责后端接口、业务逻辑、数据持久化,Vue负责单页应用的页面渲染和交互状态。

SpringBoot的价值在于:内置Tomcat,自带的starter依赖管理能少写大量配置代码,一个maven项目拉下来直接就能跑。它对MyBatis、MySQL、拦截器、文件上传这些组件都有比较成熟的集成方案,非常匹配这类管理系统的开发速度。

Vue这边我选的是Vue 2 + Element UI的组合。组件库自带表格、表单、对话框、分页、上传组件,可以快速搭出后台管理页面和用户端的列表页面。需要说明的是,如果你准备在这个项目上继续扩展,可以考虑Vue 3 + Element Plus,但如果目标是快速复现标题里的完整源码结构,Vue 2的生态资料最丰富,踩坑后也最容易被检索到解决方案。

前后端通过JSON格式交互,Axios负责发送HTTP请求,Token放到请求头里实现身份认证。开发阶段用Vue的proxy代理解决跨域,生产环境用Nginx把前端静态资源和后端接口放在同一个域名下,这样从架构层面就避开了大部分跨域问题。

2.2 为什么标题里点名“MyBatis”而不是“MyBatis-Plus”

这个点值得展开说。MyBatis-Plus现在很流行,提供了大量的单表CRUD方法,确实能省很多重复代码。但标题特别指定MyBatis,说明这个项目的学习定位是“掌握SQL能力和ORM对应关系”,而不是“用插件偷懒”。

交友系统的核心查询往往不是单表操作。匹配推荐需要统计共同标签数量,数据看板需要聚合用户数和动态数,内容审核需要多表联查。MyBatis的手写SQL能力在这类场景里有明显优势,XML文件里可以根据业务需求写动态SQL、多表JOIN、子查询,而Java实体和数据库字段之间的映射关系由框架自动完成。

举个例子,用户标签的匹配查询在MyBatis-Plus里反而不好直接表达,除非写自定义SQL;但在MyBatis里,这就是一个标准的SQL片段,手写压力很小。用MyBatis写一段时间后,你对索引优化、连表逻辑、聚合函数这些基本功会有更扎实的理解,这也是这个项目选型层面的一个重要考量。

2.3 存储与配置:MySQL 8.0和数据源参数

数据库我选的是MySQL 8.0,原因很直接:8.0是当前的主流稳定版本,支持utf8mb4字符集。这个字符集特别关键,因为动态广场里一定会出现emoji表情,如果用老版本utf8,表情符号存入数据库时会变成乱码问号。建库的时候用默认的utf8mb4就可以完整支持。

连接驱动用com.mysql.cj.jdbc.Driver,URL里一般要带三个参数:useUnicode=true、characterEncoding=utf8、serverTimezone=Asia/Shanghai。其中时区参数最容易忽略,不配的话在部分环境里会报错或时间差八小时。

数据源连接池用HikariCP(Spring Boot 2.x默认连接池),配置里可以改一下maximum-pool-size,默认10个连接在并发测试时可能会不够用。我习惯调到20,配合600000毫秒的连接超时,效果比较稳定。这些参数都写在application.yml里,后续第5部分会给出完整配置。

2.4 认证方案:为什么用JWT而不是Session

前后端分离项目里我优先推荐JWT,理由很实际:后端不用维护Session上下文,接口天然支持水平扩展,将来要接小程序或者移动端也不用大改。JWT的缺点是token一旦签发,服务端无法主动让它失效,所以需要配合用户状态校验。

我的处理方式是在JWT载荷里存userId和role,每个需要登录的接口先解析token、取出用户ID,再用这个ID查一次用户状态。如果后台已经封禁了这个用户,哪怕token没过期,接口仍然返回401或者业务错误。这个“token + 状态双重校验”的方案,既保留了JWT的无状态优势,又弥补了它不能主动失效的短板。

3. 数据建模:交友系统的数据库设计与索引规划

3.1 核心表解析:用户、标签、用户标签关系表

数据库是这类项目的地基。表设计是否合理直接决定后续匹配SQL能不能高效完成。整个系统的核心表有这么几张。

用户表user的表结构:

字段类型说明
idbigint主键
usernamevarchar(50)登录用户名,唯一索引
passwordvarchar(100)加密后密码
nicknamevarchar(50)昵称
avatarvarchar(255)头像URL地址
gendertinyint性别 0未知 1男 2女
birthdaydate生日
cityvarchar(50)城市
signaturevarchar(255)个性签名
statustinyint状态 0正常 1禁用
created_timedatetime注册时间
updated_timedatetime更新时间

标签表tag的表结构:

字段类型说明
idbigint主键
namevarchar(30)标签名称
categoryvarchar(20)分类:兴趣、性格、生活方式等
hotint热度值,用于后台展示排序

用户和标签是多对多关系,必须建中间表,也就是user_tag关系表,而不是在用户表里存一个逗号拼接的字符串。我见过不少为了省事直接存"1,2,3"的设计,这种方案在“查一个用户有哪些标签”的时候挺快,但一旦要实现“找和我标签相同的人”,就必须在SQL里做字符串分割或模糊匹配,效率极低,逻辑也不干净。

user_tag表的结构:

字段类型说明
idbigint主键
user_idbigint用户ID
tag_idbigint标签ID

这里要加一个针对(user_id, tag_id)的联合唯一索引,目的是防止用户重复选择同一个标签。

3.2 互动模块数据表设计

有了用户和标签,系统还必须有内容和完善的互动链路,不然用户之间根本没有连接的抓手。

动态表dynamic:

字段类型说明
idbigint主键
user_idbigint发布者ID
contentvarchar(1000)动态正文内容
imagesvarchar(500)图片URL,多张图用逗号拼接
like_countint点赞数
comment_countint评论数
statustinyint审核状态 0待审核 1已通过 2已驳回
created_timedatetime发布时间

评论表comment:

字段类型说明
idbigint主键
dynamic_idbigint所属动态ID
user_idbigint评论者ID
contentvarchar(500)评论内容
parent_idbigint父评论ID,用于楼层回复
created_timedatetime评论时间

点赞表like_record:

字段类型说明
idbigint主键
user_idbigint点赞用户ID
dynamic_idbigint动态ID
created_timedatetime点赞时间

关注关系表follow我加了状态字段,表示单向关注还是互相关注:

字段类型说明
idbigint主键
user_idbigint发起关注用户ID
target_user_idbigint被关注用户ID
statustinyint0单向关注 1互相关注
created_timedatetime关注时间

私信表message:

字段类型说明
idbigint主键
from_user_idbigint发送方用户ID
to_user_idbigint接收方用户ID
contentvarchar(500)消息内容
is_readtinyint是否已读 0未读 1已读
created_timedatetime发送时间

like_record表必须加(user_id, dynamic_id)联合唯一索引,从数据库层面防止重复点赞。如果你依赖业务代码先查询再判断,在并发场景下很容易出现重复点赞的问题,所以这条唯一索引其实是很有必要的。

3.3 索引、外键与SQL习惯

建表时外键可以保留,但我不建议在业务表里大量使用物理外键约束。原因很简单:企业级项目普遍是逻辑外键,因为物理外键在批量导入、分库分表时会带来额外负担。这个项目里表与表之间的关系仍然存在,通过JOIN语句来关联,数据一致性由业务层保证。

索引规划上要记住一个原则:索引不是越多越好,而是要为高频查询路径服务。这个项目里比较关键的索引有:

  • user_tag(user_id)和user_tag(tag_id),分别支持“查用户标签”和“按标签找用户”两条链路。
  • dynamic(user_id)支持个人主页动态列表查询。
  • dynamic(created_time)支持动态广场的时间线排序。
  • message(to_user_id, is_read)支持未读消息计数。

这些索引和核心业务的查询条件完全对应,加完之后推荐接口和消息接口在数据量不大时都能稳定在毫秒级别返回。

4. 核心功能实现:从匹配推荐到后台管理

4.1 用户注册登录与JWT鉴权实现

注册接口的逻辑很标准:校验用户名是否存在,校验密码长度,把密码加密后写入数据库,默认状态为正常。密码加密我用的是BCrypt,Spring Security Crypto包里提供了BCryptPasswordEncoder,比传统MD5安全得多。MD5加盐确实也是一种成熟方案,但从现代工程实践的角度来看,直接用BCrypt成本最低、安全性也有保证。

登录接口验证逻辑是:

  1. 根据用户名查用户记录,查不到直接返回账号不存在。
  2. 用BCrypt匹配明文密码和加密密码,匹配失败返回密码错误。
  3. 检查用户状态,如果是禁用状态就返回账号已被封禁。
  4. 用userId和角色生成JWT,返回给前端。

JWT工具类核心代码写法如下:

public class JwtUtils { private static final String SECRET = "your-strong-secret-key"; private static final long EXPIRE_TIME = 7 * 24 * 60 * 60 * 1000L; public static String createToken(Integer userId, String role) { return Jwts.builder() .claim("userId", userId) .claim("role", role) .setExpiration(new Date(System.currentTimeMillis() + EXPIRE_TIME)) .signWith(SignatureAlgorithm.HS256, SECRET) .compact(); } public static Claims parseToken(String token) { return Jwts.parser() .setSigningKey(SECRET) .parseClaimsJws(token) .getBody(); } }

后端用Spring的拦截器统一处理登录校验。这里要注意拦截器不拦截登录、注册接口和静态资源路径。我的做法是定义一个白名单数组,preHandle先判断请求路径是否在白名单里,如果在就直接放行,否则从请求头Authorization里取token并解析,失败则返回401状态码。

Vue前端的Axios封装里,请求拦截器从localStorage取token,放到请求头中:

request.interceptors.request.use(config => { const token = localStorage.getItem('token') if (token) { config.headers['Authorization'] = token } return config })

响应拦截器统一处理401状态:

request.interceptors.response.use( response => response.data, error => { if (error.response && error.response.status === 401) { localStorage.removeItem('token') window.location.href = '/login' } return Promise.reject(error) } )

4.2 标签匹配推荐算法与SQL实现

匹配推荐是这个项目最核心的功能。

第一步是用户端维护自己的标签。前端页面展示所有标签并按分类分组,用户点选后调接口保存到user_tag表。保存时先删除该用户已有的全部标签关系,再批量插入新的选择。这种“先删后插”的方式逻辑简单,不容易出现脏数据,适合标签这种数据量小的场景。

第二步就是推荐接口。设计思想是:统计候选用户与当前用户的共同标签数量,优先展示共同标签多的用户。核心SQL用一条带子查询的联表查询实现:

SELECT u.id, u.nickname, u.avatar, u.gender, u.city, u.signature, COUNT(DISTINCT ut.tag_id) AS common_tag_count FROM user u LEFT JOIN user_tag ut ON u.id = ut.user_id WHERE u.status = 0 AND u.id != #{currentUserId} AND ut.tag_id IN ( SELECT tag_id FROM user_tag WHERE user_id = #{currentUserId} ) GROUP BY u.id, u.nickname, u.avatar, u.gender, u.city, u.signature ORDER BY common_tag_count DESC, u.created_time DESC LIMIT #{pageSize} OFFSET #{offset}

这条SQL的思路是:先拿到当前用户的所有标签ID集合,再找出其他用户中同样拥有这些标签的人,按共有标签数量降序排列。如果一个用户和你有三个共同标签,他就会排在只有一个共同标签的用户前面。

如果用户一个标签都没选,推荐接口会先返回一条提示,让用户去完善资料。这是产品层面的一个细节处理,不然一个空标签用户进入推荐页会看到一堆随机结果,体验很差。

如果你还想让推荐结果更精细,可以在SQL里加一个JOIN到user_table再筛选城市和性别。比如:

AND u.city = #{city} AND u.gender = #{gender}

这两个条件通过Service层判断是否为NULL来决定是否拼入SQL。MyBatis的<if>动态SQL非常适合做这种可选过滤条件。

这里有个实际经验要分享:不要把“共同标签数量为0”的用户直接过滤掉。新手刚开始做匹配时,很容易写一个HAVING COUNT(DISTINCT ut.tag_id) > 0把没有共同标签的用户全部剔除。但产品上是需要“你可能认识的人”这种兜底推荐的。我最终的实现是:先查有共同标签的用户,不足一页时再补一批有资料、状态正常、按注册时间排序的用户,保证推荐页永远有内容展示。

4.3 动态发布与点赞评论的实现细节

动态广场是一个典型的内容流场景。发布动态时,前端通过FormData把文本内容和图片一起传到后端,后端用MultipartFile接收图片,保存到服务器的upload目录,再把图片的相对访问路径拼到动态记录里。

发布动态时需要注意的一点:状态默认是待审核。因为注册用户发布的内容可能会包含违规词汇,管理端需要有一个审核通道。这个设计在后台管理部分会具体体现。

动态列表查询时有两个性能隐患。一是每条动态都要带上作者信息,二是在列表里显示每条动态的当前用户是否点赞。前一个可以用联表查询一次性解决,后一个我建议用一次批量查询:先把当前用户对这批动态ID的点赞记录全部查出来,放到Set里,在组装列表时直接判断。不要在循环里单条查询点赞状态,数据量稍大就会产生N+1问题。

评论的实现相对简单,插入一条评论后把动态表的comment_count加1。删除评论要考虑子评论的存在,所以我都做逻辑删除,避免父评论删除后子评论变成无根数据。

点赞模块我用like_record表记录用户和动态的关系,同时动态表冗余一个like_count字段。这样列表页展示点赞数时不需要每次COUNT,性能更好。取消点赞时删除记录并减1。配合上(user_id, dynamic_id)唯一索引,这个模块的数据一致性是可以保证的。

4.4 私信模块与未读消息统计

私信模块这个项目不需要引入WebSocket实时推送,因为“管理系统”的定位决定了消息简单可靠即可。我用的是REST接口加轮询方案:

  • 发送消息接口:插入一条message记录,is_read为0。
  • 会话列表接口:按用户分组,返回最近一条消息和未读数量。
  • 聊天记录接口:查询两个用户之间的所有私信按时间排序。
  • 未读数统计接口:统计当前用户收到的所有is_read=0消息总数。

会话列表的SQL可以用子查询解决:找出当前用户参与的最近消息记录,再关联发送方信息。这种方式在消息量不大的场景下表现很稳定。

前端消息页用一个定时器每10秒请求一次未读数量接口,有新消息时自动刷新会话列表。这个方案的缺点是不能实时推送,但优点是实现简单、部署成本为零,非常适合这个项目定位。

4.5 管理后台:用户封禁、内容审核与数据看板

管理后台的权限控制和用户端是分开的。我定义了一个角色判断拦截器:登录时如果角色不是admin,就不能访问/admin/**路径的所有接口。

用户封禁的后端逻辑不复杂,只需要把user表的status字段置为1。重点在于封禁后要立即生效,所以登录接口和JWT拦截器里都必须检查用户状态。我在拦截器里解析token拿到userId后,每次都会查一次user表的status字段,若发现是禁用状态就直接拒绝访问。这样后台封禁的用户,即使token还没过期也无法继续使用接口。

动态审核列表展示所有status=0的动态,管理员可以操作通过或驳回。驳回时给发布者一个状态提示,发布者在前端个人中心里能看到自己的动态审核状态。

数据看板是面试和项目展示时最容易被问到的模块。我做了4个统计卡片:用户总数、今日注册数、动态总数、待审核动态数。再加一个简单的近7日注册趋势折线图。这里不建议引入太重型的图表库,用轻量级组件就可以满足需求。

统计接口的查询方式:用户总数和动态总数可以直接COUNT,今日注册数加一个created_time的日期条件,近7日注册趋势用一条按日期分组的SQL完成。这类聚合查询在MyBatis里用<where>配合时间范围参数写起来非常顺手。

5. 完整实操流程:从建表到前后端联调跑通

5.1 环境准备与版本选择

先确认环境,缺哪个补哪个:

软件版本建议说明
JDK8或11建议1.8,兼容性最好
Maven3.6+后端依赖管理
MySQL8.0数据库,字符集用utf8mb4
Node.js14+前端构建环境
IDEA最新版后端开发IDE
VSCode最新版前端开发IDE

5.2 数据库初始化脚本

创建一个数据库:

CREATE DATABASE meet_db DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;

然后执行建表SQL脚本,把所有表结构建好。需要说明的是,建表时要在user、tag、user_tag、dynamic、message这些高频查询表上建好索引。再插入几条初始数据,包括一个管理员账号和一批常用标签。

管理员账号密码同样用BCrypt加密后写入,不要在数据库里存明文密码。

5.3 后端配置与启动

先把后端项目用IDEA打开,等Maven把依赖下载完之后,修改application.yml里的数据源配置:

server: port: 8081 spring: datasource: driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://localhost:3306/meet_db?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai username: root password: 你的数据库密码 servlet: multipart: max-file-size: 10MB max-request-size: 20MB mybatis: mapper-locations: classpath:mapper/*.xml type-aliases-package: com.example.meet.entity configuration: map-underscore-to-camel-case: true jwt: secret: 任意足够长的随机字符串 expire-hours: 168

map-underscore-to-camel-case一定要设置为true,不然数据库里created_time无法自动映射到Java实体类的createdTime字段,你会被一堆字段为NULL的问题折磨到怀疑人生。

启动后端时直接运行SpringBoot的Application主类。启动成功后,可以先用浏览器访问http://localhost:8081/api/captcha之类的接口确认服务正常,然后用Postman测试登录接口拿token。

5.4 前端配置与启动

前端项目用Vue CLI创建,安装依赖:

npm install npm run serve

默认端口是8080。开发环境下后端端口是8081,前后端不同端口必然碰到跨域问题。我的做法是在vue.config.js里配置代理:

module.exports = { devServer: { port: 8080, proxy: { '/api': { target: 'http://localhost:8081', changeOrigin: true } } } }

这样前端发起/api/user/login请求时,Vite开发服务器会把请求转发到http://localhost:8081/api/user/login,从浏览器角度来看是同一个源,不会触发跨域拦截。这个配置几乎不需要改动,但如果你上线部署,需要在Nginx里做一个等价的正向代理配置。

5.5 全链路功能验证清单

整个项目启动之后,我建议按照以下顺序验证一遍核心流程,确认系统没有明显问题:

  1. 注册两个测试账号A和B,A选择“读书、摄影、旅行”标签,B选择“读书、音乐、旅行”标签,C账号选择“游戏、健身”标签。
  2. 用A登录进入推荐页,应该看到B排在前面,且显示了共同标签数量,C不会出现或被排到很后面。
  3. A给B发送一条私信,B登录后能看到未读数量加1。
  4. A发布一条带图片的动态,发布后状态是待审核。
  5. 用管理员账号进入后台,看到待审核动态列表,审核通过后A的前端动态广场能看到该动态。
  6. 用管理员封禁B,B的已登录会话在下次请求接口时被拒绝。

这套清单跑通,基本说明整个前后端业务链路是闭环的。

6. 避坑指南:联调阶段的常见问题与排查方法

6.1 跨域问题的根因与两种解决方式

跨域是前后端联调时遇到频率最高的问题。浏览器出于安全策略,默认禁止前端页面跨源访问接口。如果你用了代理,就不会触发这个问题;如果你没配置代理,直接在Axios里写了完整的后端地址,就会出现CORS报错。

解决方式有两种选择。后端的方案是配置全局跨域:

@Configuration public class CorsConfig implements WebMvcConfigurer { @Override public void addCorsMappings(WebMvcConfigurerRegistry registry) { registry.addMapping("/**") .allowedOriginPatterns("*") .allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS") .allowedHeaders("*") .allowCredentials(true) .maxAge(3600); } }

前端开发阶段更好的方案是上一节提到的Vue代理。我个人的建议是开发阶段用代理,上线阶段用Nginx反代,后端不需要单独处理CORS,这样架构更干净。

6.2 图片上传成功但访问404

图片上传后返回的访问地址是/upload/xxx.jpg这种相对路径,前端直接用这个地址拼接域名访问时经常404。原因是后端没有把upload目录映射为静态资源目录。

需要在SpringBoot配置类里添加静态资源映射:

@Override public void addResourceHandlers(ResourceHandlerRegistry registry) { String uploadPath = System.getProperty("user.dir") + "/upload/"; registry.addResourceHandler("/upload/**") .addResourceLocations("file:" + uploadPath); }

这里还要注意路径分隔符问题,我之前在Windows上开发时用/结尾完全没有问题,Linux上注意区分大小写。

6.3 时区与字符集问题

如果URL里没加serverTimezone=Asia/Shanghai,某些版本下会直接报连接错误。如果加了但数据源配置的characterEncoding=utf8,存emoji还是可能变问号。这两个问题属于“配置错了很常见、对了没感觉”的类型,建议严格按第5部分的配置写。

6.4 推荐接口慢与排查思路

如果数据量测试时候不太,但推荐接口还是很慢,一般先检查是否发生了N+1查询。比如先查出候选用户列表,再在循环里逐个查标签,这种做法在列表长度30时会产生30次额外查询。

排查方法是打开MyBatis的SQL日志输出:

mybatis: configuration: log-impl: org.apache.ibatis.logging.stdout.StdOutImpl

观察控制台日志,如果一个列表接口打印了几十条SQL,那基本就是N+1问题。把循环内的查询改掉,用上面的聚合SQL一次查出来就好。

6.5 Token失效与并发操作的小坑

前端如果同时发多个请求,其中一个返回401后,响应拦截器会立即清除本地token并跳转登录页。这个逻辑看似没问题,但如果并发请求中有的是登录接口本身返回401(比如密码错误),也会触发跳转。解决办法是在响应拦截器里先判断请求URL,排除登录接口,再处理token过期逻辑。

还有一个我调试过程中踩过的坑:后端拦截器对OPTIONS预检请求没有放行的话,前端会拿到跨域失败。SpringBoot里把OPTIONS请求直接放行:

if (HttpMethod.OPTIONS.equals(request.getMethod())) { return true; }

这个判断要加在拦截器的最前面。

最后再做一次安全合规自查,全文内容不涉及任何违规方向,所有技术描述均以正常的软件开发学习范畴展开,没有真实公司、学校、人物等信息。

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

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

立即咨询