简介:针对大学生校园兼职微信小程序的后端系统,基于Java与Spring Boot框架构建,结合uniapp前端技术,覆盖后台管理、商家、用户三类角色,为小程序提供数据与业务逻辑支持。资源包一共包含一千五百八十九个文件,其中既有用于页面展示的Vue文件、负责交互逻辑的JavaScript文件、处理数据的Java源码文件,也有大量的JSON配置信息以及图片、样式等静态资源,压缩之后体积约为二十八点六八兆字节,整体目录结构划分清晰,便于快速检索定位。目前已有七十八人学习浏览,适合钻研后端接口开发、微信小程序前后端联调或校园兼职类系统设计的开发者。源码通过多个控制器实现兼职、收藏、留言、申请、商家等数据的增删改查,并具备文件上传下载、数据库备份恢复、图表生成、字典配置等功能,附带数据库脚本,是课程设计或毕业设计的良好起点。
1. 大学生校园兼职小程序:一份能跑完课程设计的全栈源码
校园兼职小程序这个源码包,拆开之后你会发现它并不是一套空壳页面,而是一条完整的业务链路:学生端发布兼职、浏览兼职、投递简历、查看录取结果,管理端发布公告、审核兼职信息、管理用户数据,两头都接上了微信生态。后端是 Spring Boot + MyBatis + MySQL,前端是 uniapp 工程,一次编写可以编译到微信小程序运行,也能顺手编译成 H5 或安卓 App。它最典型的使用场景是计算机专业的课程设计和毕业设计,题目就叫“校园兼职微信小程序的设计与实现”,其次是学校社团或二级学院想做内部兼职信息平台,直接拿这套源码做二次开发,比自己从空白工程搭要快得多。适合的人群也很明确:有一定 Java 基础、想把 Spring Boot 和小程序打通的学生,以及需要快速交付一个前后端分离项目的开发者。
后端结构不是一个简单的 CRUD 堆积,而是按照 Controller-Service-Mapper 分层写的,接口路径也带了 api/user、api/job 这样的前缀,看代码比看博客教程更接近真实项目的组织方式。小程序端对 uni.request 做了统一封装,所有请求自动携带 token,页面组件按兼职模块、我的模块、管理模块拆分,复用的思路很清晰。下文从后端骨架开始拆,然后讲 uniapp 端的工程组织,再讲前后端怎么对接登录态,最后把最容易翻车的位置和改造技巧一并列出来。
2. 后端骨架先立住:三层结构、JWT 鉴权和 MyBatis 参数细节
后端是整份源码的核心,它决定了小程序端能跑多顺。拿到代码后,我建议先从数据库表结构和项目分层入手,再去看具体的接口实现,这样可以快速定位“这条数据是从哪张表来、经过哪个 Service、在哪个 Mapper 里被写进去”的调用链。
2.1 数据模型与表设计
大学生兼职场景的核心数据,通常围绕用户、兼职信息、投递记录、收藏关系、公告轮播这几类展开。源码里的数据库脚本一般在 sql 目录下,导入 MySQL 后能看到类似下面的表结构:
| 表名 | 核心字段 | 说明 |
|---|---|---|
| user | id, openid, nickname, avatar, role, phone | 学生和后台管理员共用一张表,role 字段区分身份 |
| boss | id, user_id, company_name, credit_code | 发布兼职的企业或商户信息,与 user 表一对一 |
| job | id, title, type, salary, address, status, publisher_id | 兼职岗位主表,status 控制上下架和审核状态 |
| apply | id, job_id, user_id, status, create_time | 投递记录表,学生投递兼职后写入一条记录 |
| collection | id, user_id, job_id | 学生收藏的兼职记录 |
| banner | id, image_url, link_url, sort | 首页轮播图,管理端可维护 |
这里有一个容易被忽略的设计点:user 表和 boss 表是分开的,而不是把企业名称、营业执照直接塞进 user 表。这样学生、管理员、企业三种身份都在 user 表里用 role 区分,而企业独有的资质字段放在 boss 表里,扩展时不会污染基础用户表。如果你后面想加“企业认证审核”功能,只需要在 boss 表加一个 audit_status 字段,不需要动 user 表结构。
另一个关键设计是 apply 表里没有冗余兼职标题和薪资,只存 job_id。查询投递记录时通过关联查询拿到兼职信息,这样如果兼职内容被修改,投递记录里展示的始终是最新数据,不会出现历史残留。缺点是联表查询会多一些,但在这个数据量场景下性能完全不是问题。
2.2 三层结构与统一返回封装
源码里的后端包结构通常长这样,controller 暴露接口,service 处理业务逻辑,mapper 负责数据库读写。我拿到任何一个 Spring Boot 老项目,都会先确认它是不是严格分了这三层,因为很多课程设计喜欢把 SQL 写在 Controller 里,短期跑得通,后面改一个需求就要动好几处地方。
一个典型的 Controller 写法如下:
@RestController @RequestMapping("/api/job") public class JobController { @Resource private JobService jobService; @GetMapping("/list") public Result list(@RequestParam(defaultValue = "1") Integer page, @RequestParam(defaultValue = "10") Integer size, @RequestParam(required = false) String keyword) { return Result.success(jobService.pageQuery(page, size, keyword)); } @GetMapping("/{id}") public Result detail(@PathVariable Long id) { return Result.success(jobService.getDetail(id)); } }Controller 只做参数接收和结果返回,真正的分页组装、关键词过滤在 Service 里完成。这里@RequestParam(defaultValue = "1")意味着前端不传 page 时默认为第 1 页,不会因为参数缺失报 400;required = false的 keyword 表示搜索关键字可传可不传,Service 内部再判断是否需要拼 where 条件。这套写法是 Spring Boot 接口最常见的规范,新手照着写至少不会在参数绑定上翻车。
所有接口统一返回 Result 对象,结构是{ code, message, data },前端只需要判断 code 是否为约定值,就可以决定是走成功逻辑还是弹提示。这种封装的好处是异常信息不会直接暴露给用户,比如数据库查询出错时,后端可以捕获异常返回“系统繁忙”,而不是把堆栈信息打到小程序里。源码里对应的返回类大致是:
public class Result { private Integer code; private String message; private Object data; public static Result success(Object data) { Result r = new Result(); r.code = 0; r.message = "success"; r.data = data; return r; } public static Result error(String message) { Result r = new Result(); r.code = 500; r.message = message; return r; } }这里的 code 建议和后端 HTTP 状态码区分开。HTTP 200 表示请求到达服务器,业务 code 表示业务有没有成功,二者不是一回事。前端拦截器里可以先判断 HTTP statusCode 是否为 200,再判断 body.code 是否为 0,两层判断缺一不可。
2.3 登录鉴权:拦截器加 JWT 的完整链路
大多数校园兼职系统的接口,除了登录和轮播图,都不希望被未登录用户随意调用。源码里通常会写一个拦截器,对需要保护的路径做 token 校验。JWT 的生成和校验大致是这样:
@Component public class AuthInterceptor implements HandlerInterceptor { @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { String token = request.getHeader("Authorization"); if (token == null || !JwtUtil.verify(token)) { response.setStatus(401); response.setContentType("application/json;charset=UTF-8"); response.getWriter().write("{\"code\":401,\"message\":\"未登录或登录已过期\"}"); return false; } Long userId = JwtUtil.getUserId(token); request.setAttribute("userId", userId); return true; } }注意两个细节:第一,token 是从请求头Authorization取的,不是从 URL 参数或请求体里取;第二,校验通过后把 userId 放到 request attribute 里,后续 Controller 直接从 request 中拿当前登录用户,而不是让前端再传一次 userId。这样做可以防止越权——用户只能操作自己的数据,因为后端根本不信任前端传的 userId。
拦截器注册时要排除登录接口:
@Configuration public class WebConfig implements WebMvcConfigurer { @Resource private AuthInterceptor authInterceptor; @Override public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(authInterceptor) .addPathPatterns("/api/**") .excludePathPatterns("/api/user/login", "/api/banner/list"); } }/api/user/login是登录入口,不能拦;/api/banner/list是首页轮播,游客也要能看。如果你后面加了“公开的兼职列表页”,记得把对应路径加进 excludePathPatterns,否则就会出现“打开首页能看轮播,往下滑兼职列表却提示未登录”的诡异情况。
MyBatis mapper 里的 SQL 是另一处容易出错的地方。比如更新投递状态,初学者喜欢直接写:
<update id="updateApplyStatus"> update apply set status = #{status} where id = #{id} </update>这行 SQL 本身没错,但它没有限制“只能是当前用户更新自己的投递记录”,存在越权风险。更稳妥的做法是 Service 层先从 token 解析出 userId,再把它一并传给 Mapper:
<update id="updateApplyStatus"> update apply set status = #{status} where id = #{id} and user_id = #{userId} </update>新增了user_id条件后,即使有人猜到别人的投递记录 id,也无法修改不属于自己的记录。这一段代码看起来只是多加了一个条件,但对课程设计答辩来说,是能讲出“安全设计”亮点的关键细节。
3. uniapp 端工程组织:页面、请求封装和微信小程序配置
后端接口就绪后,小程序端才能有数据可用。uniapp 工程的目录结构、页面路由、请求封装方式,决定了你后续加页面和排查问题时是轻松还是痛苦。源码里通常已经把这些规范排好了,这一章讲清楚它为什么这样组织,以及改哪些地方能适配你自己的需求。
3.1 页面结构与分包配置
打开工程目录,pages 文件夹下一般会按业务模块分子目录,比如:
| 页面路径 | 功能 | 所需权限 |
|---|---|---|
| pages/index/index | 首页,兼职列表、轮播图、分类入口 | 游客可看 |
| pages/job/detail | 兼职详情、收藏、投递按钮 | 学生登录 |
| pages/publish/edit | 发布兼职表单 | 企业或管理员 |
| pages/apply/record | 我的投递记录 | 学生登录 |
| pages/mine/mine | 个人中心、身份切换 | 学生登录 |
| pages/manage/audit | 兼职审核列表 | 管理员 |
页面按模块分包的好处是,微信小程序有 2MB 主包大小限制,把管理端页面和用户端页面拆开,能显著降低主包体积。源码工程如果已经做了分包,pages.json 里会有一个 subPackages 配置,类似:
{ "pages": [ "pages/index/index", "pages/job/detail", "pages/mine/mine" ], "subPackages": [ { "root": "pages/manage", "pages": [ "pages/manage/audit/audit", "pages/manage/job/list" ] } ] }分包里的页面路径要写完整 root 加页面路径,跳转时使用/pages/manage/audit/audit这样的绝对路径。如果你不加分包,把所有页面都放主包,页面多了之后编译时会卡在“代码包大小超过限制”,到时候再拆包就要改一堆 page 路径,比一开始就按模块拆分要麻烦得多。
3.2 请求封装:把 uni.request 包成 Promise
源码里如果只有一个文件需要重点阅读,那就是 utils/request.js。它把 uni.request 包装成了 Promise,让页面里的代码可以用 async/await 写,而不是层层嵌套 success 回调。这是整个小程序前端最核心的基础设施。
const BASE_URL = 'http://192.168.1.100:8080'; export function request(options) { return new Promise((resolve, reject) => { uni.request({ url: BASE_URL + options.url, method: options.method || 'GET', data: options.data || {}, header: { 'Authorization': uni.getStorageSync('token') || '', 'Content-Type': 'application/json' }, success: (res) => { if (res.statusCode === 401) { uni.removeStorageSync('token'); uni.navigateTo({ url: '/pages/login/login' }); return; } if (res.data.code !== 0) { uni.showToast({ title: res.data.message || '请求失败', icon: 'none' }); reject(res.data); return; } resolve(res.data.data); }, fail: (err) => { uni.showToast({ title: '网络请求失败,请检查后端服务', icon: 'none' }); reject(err); } }); }); }这里的 BASE_URL 是后端服务的地址,本地联调时用局域网 IP。设置 header 时从 storage 里取 token,保证每个请求都自动带上登录凭证。success 回调里先判断 HTTP 状态码、再判断业务 code,401 时清空 token 并跳到登录页,这种统一处理让页面代码不需要每处都写登录过期判断。
每新建一个页面,请求数据时只需引入这个封装:
import { request } from '@/utils/request.js'; export function getJobList(params) { return request({ url: '/api/job/list', method: 'GET', data: params }); }命名导出比默认导出更直观,页面里按需引入,不会把整个工具类挂在全局。源码如果在 api 目录下按模块维护接口函数,比如 api/job.js、api/user.js,那改造起来就很舒服,只需要改 BASE_URL 或者替换接口路径,页面代码几乎不用动。
3.3 manifest 与 project.config:本地调试和上传准备
uniapp 工程要跑成微信小程序,需要改两个配置文件。manifest.json 里是 uni-app 的全局配置,微信小程序的 appid 在这里设置:
{ "mp-weixin": { "appid": "wx你的小程序appid", "setting": { "urlCheck": false }, "usingComponents": true } }project.config.json 是微信开发者工具读取的工程配置,它决定开发者工具以什么身份打开这个项目。新导入源码时,首先要检查 appid 是不是你自己的,如果不是,直接改成自己的小程序 appid,否则上传体验版时会报“appid 不匹配”。
这里要提醒一个常见的本地调试疑问:在开发者工具里,如果后端没有配置 HTTPS 域名,工具栏会提示“不在以下 request 合法域名列表中”。本地联调阶段,可以在详情-本地设置里勾选“不校验合法域名”,这是一种官方提供的调试方式,能让 http://192.168.1.100:8080 这样的局域网地址直接访问。但上线前必须把后端域名配置成 HTTPS 并加入小程序后台的 request 合法域名列表,否则真机预览会全部请求失败。
4. 前后端对接:登录态、边界和接口数据联动
后端接口写得再规范,小程序端如果对接不好,项目依然跑不起来。这一章专门讲前后端协同的几根关键链条:微信登录怎么打通、登录态失效怎么处理、本地联调的网络边界问题。
4.1 微信登录:code 换 openid,再由后端签发 token
小程序登录不走用户名密码,而是走微信的 code 换 openid 流程。前端调用uni.login拿到一个临时 code,把这个 code 发给后端,后端再用 code 调用微信接口换 openid。最终存储的身份凭证是后端签发的 JWT,而不是微信的 code。
@PostMapping("/api/user/login") public Result login(@RequestBody LoginDTO dto) { // 1. 调用微信接口,用 code 换取 openid 和 session_key String url = "https://api.weixin.qq.com/sns/jscode2session?appid=" + appId + "&secret=" + appSecret + "&js_code=" + dto.getCode() + "&grant_type=authorization_code"; String resp = restTemplate.getForObject(url, String.class); JSONObject json = JSONObject.parseObject(resp); String openid = json.getString("openid"); // 2. 查询用户是否存在,不存在则注册 User user = userMapper.findByOpenId(openid); if (user == null) { user = new User(); user.setOpenId(openid); user.setNickname("微信用户" + openid.substring(openid.length() - 4)); user.setRole(1); // 1 学生,2 企业,9 管理员 userMapper.insert(user); } // 3. 签发 JWT,返回给小程序端 String token = JwtUtil.createToken(user.getId(), user.getRole()); return Result.success(token); }这段逻辑里有三个关键点。第一,appId 和 appSecret 必须放在后端配置里,不能写在小程序前端代码中,否则任何人反编译小程序都能拿到你的密钥。第二,openid 是每个用户在每个小程序下的唯一标识,同一用户在别的公众号或小程序下 openid 不同,所以用它做用户主键是安全的。第三,首次登录时自动注册,小程序端不需要用户填写手机号或姓名,后续可以在个人中心补充资料。
前端登录调用:
uni.login({ provider: 'weixin', success: (loginRes) => { request({ url: '/api/user/login', method: 'POST', data: { code: loginRes.code } }).then((token) => { uni.setStorageSync('token', token); uni.switchTab({ url: '/pages/index/index' }); }); } });登录成功后 token 存入本地 storage,后续所有请求自动携带,这个链路就闭环了。
4.2 401 处理与登录态过期策略
JWT 的过期时间如果设置太长,用户注销后 token 依然有效;设置太短,用户操作过程中经常被踢下线。源码里一般把过期时间设在 7 天左右,这样一个学期内的使用基本不用重新登录,但又不至于永久有效。
前端对 401 的统一处理,在请求封装里已经写了,这里补充后端需要注意的边界。JWT 是无状态的,后端无法主动让某个 token 失效,所以当用户被管理员封禁或角色改变时,只校验 token 合法性是不够的。常见做法是在拦截器里除了检验签名,再查一下用户当前状态:
User user = userMapper.findById(userId); if (user == null || user.getStatus() != 1) { response.setStatus(401); return; }这样即使老 token 还没过期,用户被禁用后下一次请求也会被拦截,比单纯校验 JWT 更安全。如果你在源码里只看到了签名校验,建议自行补上这一步,答辩时能说出“我考虑了账号状态变更需要即时生效”这种话,比只说“我用 JWT 做鉴权”要有说服力得多。
4.3 联调环境:局域网、端口、域名白名单
前后端联调是新手翻车最多的地方,而且经常是同一个问题反复出现。先说本地联调的网络拓扑:小程序开发者工具运行在电脑上,后端 Spring Boot 也跑在同一台电脑,小程序请求http://localhost:8080其实是可以通的。但换成真机预览,手机和电脑必须在同一局域网,而且 BASE_URL 不能写 localhost,要写成电脑的局域网 IP,比如http://192.168.1.100:8080。
如果你发现手机真机预览时请求报“网络异常”或“fail: timeout”,按这个顺序排查:
- 先用电脑浏览器访问
http://192.168.1.100:8080/api/banner/list,能通说明后端启动正常且端口没被防火墙拦。 - 手机和电脑连同一个 WiFi,ping 一下能通说明网络层没问题。
- 在开发者工具里把不校验合法域名打开,再试真机预览。
- 注意后端不要只绑定 127.0.0.1,Spring Boot 默认绑定 0.0.0.0,一般没问题,但如果你动了 server.address 配置,就可能导致外部设备无法访问。
数据库连接也是联调阶段的高频错误源。如果启动后端时报Access denied for user 'root'@'localhost',检查 application.yml 里的用户名密码是否和本地 MySQL 一致。如果报Communications link failure,优先检查端口是不是 3306、MySQL 服务有没有启动。如果 MySQL 是 8.x 版本,连接驱动版本低了会报Public Key Retrieval is not allowed,此时改一下 URL 参数allowPublicKeyRetrieval=true,或者把 mysql-connector-java 版本升到 8.0 以上。
5. 避坑排查:六个最容易问翻车的位置
这套源码在运行时,有不少位置是新手几乎必踩的。把常见问题按“现象→原因→解决”列在这里,排查时直接对照。
5.1 小程序端报“不在以下 request 合法域名列表中”
- 现象:开发者工具中所有请求都失败,控制台报错提示域名不在合法域名列表。
- 原因:微信小程序要求所有网络请求的域名必须是配置过的 HTTPS 域名。本地开发时后端是
http://192.168.1.x:8080,既不是 HTTPS 也不在后台白名单里。 - 解决:本地调试阶段在开发者工具“详情-本地设置”中勾选“不校验合法域名”。上线前把后端部署到有 HTTPS 证书的域名,在小程序后台“开发管理-服务器域名”添加 request 合法域名。注意域名不能带端口号,且不能使用 IP。
5.2 查询列表接口报“Parameter ‘keyword’ not found”
- 现象:后端控制台抛异常
org.apache.ibatis.binding.BindingException: Parameter 'keyword' not found. Available parameters are [arg0, arg1, param1, param2]。 - 原因:MyBatis 在方法有多个参数时,如果没有使用
@Param注解,XML 里无法直接用#{keyword}引用。 - 解决:在 Mapper 接口方法参数前加
@Param:
List<Job> pageQuery(@Param("page") Integer page, @Param("size") Integer size, @Param("keyword") String keyword);单个参数时不会触发这个报错,多个参数时一定要留意。
5.3 发布兼职后列表里看不到这条记录
- 现象:提交成功后返回成功,但首页兼职列表刷不出来新数据。
- 原因:大概率是 status 字段问题。发布接口写入的 job 默认 status 为 0(待审核),而查询列表的 SQL 过滤了 status = 1(已发布)。后台审核通过后才会展示。
- 解决:确认发布接口和列表查询对 status 的约定是否一致。如果你不希望走审核流程,可以直接把发布时的 status 设为 1,但会失去“管理员审核”这个课程设计功能点,建议保留审核逻辑。
5.4 真机预览时登录成功,但列表页白屏
- 现象:手机上能打开首页,轮播图也能显示,但兼职列表为空。电脑开发者工具里一切正常。
- 原因:很可能是 BASE_URL 写的是
localhost或127.0.0.1,手机访问不到电脑上的服务。轮播图能显示可能是因为图片用的是网络图,不依赖后端接口。 - 解决:把 BASE_URL 改成电脑的局域网 IP。要拿 IP 可以在命令行执行
ipconfig(Windows)或ifconfig(macOS/Linux),找到局域网网段的那一个地址填入。
5.5 修改用户头像或昵称后,旧 token 还能操作
- 现象:用户在个人中心修改了头像,但后端记录的还是旧数据,甚至用户被管理员删除后,旧 token 依然能调接口。
- 原因:拦截器只校验了 token 签名和过期时间,没有实时查数据库确认用户状态。
- 解决:按前文 4.2 的做法,拦截器内查一次用户表,校验状态是否正常。这个改动很小,但能把“用户被禁用后 token 依然有效”的安全漏洞补上。
5.6 后端能启动,但接口请求时区错误或连接超时
- 现象:控制台报错
The server time zone value 'Öйú±ê׼ʱ¼ä' is unrecognized,或者Communications link failure。 - 原因:MySQL 8.x 对时区要求更严格,连接 URL 里没有指定时区;或者数据库服务本身没启动。
- 解决:在 JDBC URL 末尾拼接
?serverTimezone=Asia/Shanghai&useSSL=false,再检查 MySQL 端口是否被占用。完整示例:
spring: datasource: url: jdbc:mysql://localhost:3306/campus_job?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai&useSSL=false username: root password: 你的密码加上characterEncoding=utf8是为了防止中文乱码,很多同学全英文数据能查询,中文数据查出来是问号,就是少了这个参数。
6. 把默认系统改成自己的课题:半小时改造技巧
拿到的源码默认叫“校园兼职系统”,直接作为课程设计提交容易被看出是模板。花半小时做一轮“换皮整容”,让系统变成“你的”系统,最核心的是下面五个动作。
第一步,改数据库名和配置文件。把 sql 脚本里的建库语句从campus_job改成你的课题名,比如school_parttime,同时修改 application.yml 里的数据库连接。记住,数据库表结构不一定需要改表名,只要库名不同,从项目层面看就已经是“独立数据库”了。但如果你希望表名也有辨识度,可以全局替换 job 表名为parttime_job,注意 Mapper XML 里的表名要同步改。
第二步,处理后端项目名和包名。pom.xml 里的 artifactId 是项目标识,改成你的课题名。包名com.example.campusjob如果嫌不够个性,先用 IDE 的重构功能改 root package,再全局搜索替换。这里最容易漏的是 application.yml 里的mapper-locations路径,以及日志配置文件中的包路径,改完跑一遍所有接口确认没报路径错。
第三步,改小程序端的工程信息。manifest.json 里的 appid 换成你自己的,project.config.json 里的 projectname 改成课程设计名。首页轮播图换成学校相关的图,没有素材就用两张纯色带文字的占位图。“校园兼职”四个字如果和你课题不符,全局搜索替换成你的主题词,比如“校园二手交易”或“校园跑腿代取”。
第四步,加一个属于你自己的功能模块。这是让系统“看起来不一样”最有效的方式。比如在兼职详情页加一个“收藏”按钮,需要新增收藏接口、收藏表、收藏列表页,源码里如果没有实现,照着现有模块仿写一个专题列表:
@RestController @RequestMapping("/api/collection") public class CollectionController { @Resource private CollectionService collectionService; @PostMapping("/add") public Result add(@RequestAttribute Long userId, @RequestParam Long jobId) { collectionService.add(userId, jobId); return Result.success(); } @GetMapping("/list") public Result list(@RequestAttribute Long userId) { return Result.success(collectionService.listByUserId(userId)); } }@RequestAttribute Long userId这个写法,依赖拦截器里 put 进去的 userId,不需要前端传用户标识,天然防越权。页面端就两个动作:详情页点击时调用 add 接口,个人中心新增一个入口展示收藏列表。这种增量的二次开发,答辩时既能展示你对业务的理解,又不会改动过深导致源码跑不起来。
第五步,把“我的”页面里写死的用户信息,改成从后端查询。很多模板代码喜欢在前端 storage 里存昵称头像,这会导致换设备后信息丢失。改成调用/api/user/info拉取最新用户数据,代码改动不大,但数据一致性体验提升非常明显。
从那以后,我每次拿到别人的 Spring Boot 老项目,都强制自己先做一遍数据库表梳理、接口路径梳理和鉴权链路梳理,再动任何业务代码。先看清再下手,改造时间至少省一半。希望这篇拆解能帮你少走几趟弯路,把这套源码真正改成自己的东西。
本文还有配套的精品资源,点击获取