想把这套系统讲明白,先得说清楚一个问题:很多人拿到“java springboot基于微信小程序的宠物领养平台系统”这套源码,第一反应是赶紧部署跑起来,但跑起来之后呢?面试官一问“领养状态是怎么流转的”“为什么用Redis不用本地缓存”“code2session被你用在哪一步了”,十个人里有八个答不上来。源码是别人的,项目是别人的,唯独答辩和面试是你自己上,这中间的差距,就是这篇文章想帮你补齐的。
如果你正在做毕业设计、课程设计,或者准备拿一个全栈项目去投简历,那这个选题确实很典型:它前后端分离、有移动端、有后台管理、有核心业务状态机,完整覆盖了一个真实商业项目的开发链路。我会从技术选型的真实理由、数据库设计、接口逻辑、小程序端对接、部署上线的完整链路,到调试中那些视频不会告诉你的坑,一层层拆开讲。
1. 为什么“宠物领养平台”这个选题值得做
1.1 业务痛点:线下领养的“三无困境”
在技术展开之前,先想想这个系统到底在解决什么问题。国内很多城市的宠物领养,依然是靠朋友圈转发、线下宠物店门口贴告示、救助站微信群接龙。信息高度分散,领养人看不到宠物的真实背景,救助站也没有统一的回访记录,宠物送出去之后基本就失联了。这个平台的业务价值就在这里:给“流浪宠物—救助人—领养人”三方一个统一的线上流转渠道。
对毕设和求职而言,有真实业务背景的项目,远好过“增删改查”的图书管理系统。因为面试官听到“宠物领养”会自然地产生场景感,追问也会更具体:宠物信息怎么审核?领养申请怎么防止一个人重复提交?宠物下架之后申请怎么处理?这些追问恰好都是项目里已经实现的核心逻辑。
1.2 一套能讲清楚完整闭环的业务模型
这套系统有两个端,用户端是微信小程序,管理端是Spring Boot后台。核心角色有三个:普通用户(可以浏览、收藏、申请领养)、管理员(审核宠物信息、审核领养申请、上下架管理)、以及被领养的宠物本身(它有自己的状态流转)。
完整业务闭环是这样的:救助人(或管理员)录入宠物资料,管理员审核通过后宠物上架;用户在小程序端刷到宠物,查看详情后提交领养申请;管理员在后台看到申请,审核通过后用户进入“待领取”状态,线下完成宠物交接;之后系统会记录领养状态,部分项目还会加入回访提醒。每个环节都有状态字段控制,这不是一个单纯的CRUD系统,而是一个有业务状态机的系统,这是它“值钱”的地方。
1.3 谁适合拿这个项目当毕设或简历项目
如果你是Java后端方向的学生,对这个项目里Spring Boot、MyBatis-Plus、Redis、微信小程序登录这些技术点已经能看懂七成,那这套源码就很适合作为你的主项目。如果你目前还只停留在跟着教程写Controller的阶段,那这个项目稍微有点挑战,但也可以反过来用——先跑起来,再逐个接口去读,把每个表、每个状态字段的流转画出来,这个过程本身比看十遍教程都管用。
别被“源码+文档+运行视频”这些附加内容迷惑,那些东西只是辅助。真正能写进简历、说给面试官听的,是你对这套系统设计逻辑的理解程度。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 技术栈选型:每一步都要能说出理由
2.1 Spring Boot版本:别贪新,稳字当头
开发任何一个Java Web项目,第一步就是选Spring Boot版本。现在很多教程默认Spring Boot 2.7.x或2.6.x,因为国内大多数企业、教学资源还有面试题都基于2.x展开。你看到的很多源码项目,也基本都是Spring Boot 2.x为主。
为什么建议大家别一上来就上Spring Boot 3.x?因为3.x基于Jakarta EE命名空间,很多老项目的包名从javax改成jakarta,部分第三方组件对3.x的支持还比较滞后,遇到问题时你搜到的解决方案可能还是2.x时代的。对毕设项目来说,稳定大于新潮。除非你的项目文档明确说用了Spring Boot 3,否则我建议保持2.7.x版本,这样和MyBatis-Plus、Sa-Token、微信支付SDK等组件的兼容性都已经被大量人验证过。
2.2 MyBatis-Plus比JPA更适合这类项目的理由
ORM框架通常是MyBatis-Plus和Spring Data JPA二选一。这个项目用的是MyBatis-Plus,这也是国内Java项目里非常常见的组合。MyBatis-Plus的优势在于“增强而不改变”:它没有取代MyBatis,而是在MyBatis的基础上提供了通用Mapper、条件构造器、分页插件,单表CRUD几乎不用手写SQL。
比如查询宠物列表时按状态和类型筛选,条件构造器一行就能搞定:
java复制LambdaQueryWrapper<Pet> wrapper = new LambdaQueryWrapper<>();
wrapper.eq(Pet::getStatus, 1)
.like(StringUtils.hasText(keyword), Pet::getName, keyword)
.orderByDesc(Pet::getCreateTime);
Page<Pet> page = petMapper.selectPage(new Page<>(pageNum, pageSize), wrapper);
这种做法的好处对毕设项目特别实际:单表查询不用写XML,代码量小,很容易讲清楚。同时因为底层还是MyBatis,遇到复杂多表查询时你依然可以写自定义SQL,不会被框架限制住。JPA虽然也不错,但在国内面试场景下,MyBatis系列的使用率明显更高,拿来当简历项目,简历和面试之间几乎没有认知鸿沟。
2.3 Redis在项目里的真实作用,以及哪些场景可以省
Redis在宠物领养平台里不是花架子。最核心的用途是存微信小程序端的登录凭证。小程序每次打开都会调用wx.login获取code,后端拿着code去微信接口换openid和session_key,然后签发一个自定义token给前端,这个token需要有过期时间,而且是无状态的——Redis天然适合干这个。此外,宠物详情的浏览量计数、首页推荐列表的缓存,也可以放Redis。
如果你觉得Redis部署麻烦,能不能换成JWT?能,但那样的话“登录态”就完全交给前端保存了,服务端没法主动控制失效。一旦管理员要把某个用户封禁、或者用户要退出登录,JWT的被动失效问题会很麻烦。所以这个项目里用Redis做会话管理,在技术答辩时是明显加分的设计,因为你能说出“让服务端持有控制权”这种话。
2.4 小程序端用原生还是uni-app
这套系统的小程序端一般是微信原生语法,WXML+WXSS+JS。为什么不用uni-app?核心原因是:原生小程序上的登录、授权、支付、订阅消息等能力,文档最全、踩坑成本最低。uni-app虽然跨端,但很多组件和API在微信端还是走了一层封装,遇到问题排查起来更麻烦。
从学习角度讲,原生小程序语法是微信官方的东西,学会了之后再做其他微信小程序项目是通吃的。从项目答辩角度讲,原生小程序的代码结构(pages目录、app.json、app.js)你一打开就能给答辩老师讲清楚页面间跳转和数据流,而uni-app的vue单文件组件风格会让部分评委觉得你在“绕路”。
3. 数据库设计与领养状态机的核心思路
3.1 核心表结构设计及关键字段解读
一个让人一看就舒服的数据库设计,是该项目能跑通、能讲清楚的基础。宠物领养平台的表结构通常包含这些核心表:用户表(user)、宠物表(pet)、领养申请表(adoption_apply)、收藏表(favorite)、反馈表(feedback)、轮播图表(banner)、通知消息表(notice)。我见过有些版本还会加一个文章资讯表(article)用来做内容运营。
用户表最关键的字段是openid,这是微信用户的唯一标识。注意openid不是自己生成的,是小程序端调用wx.login()拿到code,后端再通过code2session接口从微信那边换来的。表里还要有nickname、avatar、phone、role等字段。role字段用来区分普通用户和管理员,通常0表示普通用户,1表示管理员。
宠物表是整个系统的信息核心,字段包括:
| 字段 | 类型 | 说明 |
|---|---|---|
| name | varchar | 宠物名称 |
| category | varchar | 猫/狗/其他 |
| breed | varchar | 品种 |
| age | int | 月龄/年龄 |
| gender | tinyint | 0未知 1公 2母 |
| is_vaccinated | tinyint | 是否已打疫苗 |
| is_neutered | tinyint | 是否已绝育 |
| description | text | 宠物介绍 |
| images | varchar | 图片,可存JSON数组 |
| status | tinyint | 0草稿/审核中 1上架 2下架 3已领养 |
| audit_status | tinyint | 审核状态,0待审核 1通过 2拒绝 |
| create_time | datetime | 创建时间 |
其中status和audit_status是两个不同维度的状态,不少新手会把它们混在一起用一个字段,这是后面出问题最多的地方。简单理解:audit_status表示这道关卡过没过,status表示这个宠物现在能不能被用户看到、能不能被领养。一个提交过来的宠物,先走audit_status,审核通过后status才变为1(上架);一旦有人领养成功,status变为3(已领养),这时候能不能再被申请,后端的逻辑判断里要优先看status。
领养申请表是业务状态机的核心载体,字段一般有apply_id、pet_id、user_id、applicant_name、phone、address、reason(申请理由)、status(0待审核 1通过 2拒绝 3已取消)、audit_time、audit_remark、create_time。
3.2 领养状态机:从申请到回访的状态流转
我给你的项目里,千万不要把状态处理成乱糟糟的if else。把整个领养流程抽出来,画一条状态流转线:
text复制提交申请(0) -> 管理员审核 -> 通过(1) -> 线下交接 -> 已完成(3)
-> 拒绝(2) -> 流程结束
这个流程里还有一个很多人忽略的环节:管理员通过申请后,宠物本身的状态也要同步变。比如一个宠物只有一条领养申请被通过,那么宠物应该立刻变为“已领养/待交接”状态,不能再被其他人申请。如果在代码里这两个表的状态是分开更新的,那必须放到同一个事务里,否则就会出现“申请通过了但宠物还在列表里”的数据不一致问题。
另外,用户主动取消申请也是一种状态。一个人可能同时申请了好几只宠物,后面发现自己家养不下,那他会想取消其中某些申请。这就需要申请表里有一个3(已取消)状态,取消后宠物可以重新回到可申请列表,同时要把原来的占用名额释放。这些逻辑如果没想清楚,跑通容易,答辩被追问时容易露怯。
3.3 设计时容易忽略的冗余字段与外键策略
很多从《数据库原理》课本里走出来的学生,会下意识地给每张表加上外键约束。但实际项目里,尤其是互联网项目,外键用得很少,宁可在代码里做逻辑控制。原因很简单:外键会影响写入性能,也会让分库分表变得非常困难。这个宠物领养平台属于中小型项目,不用分库分表,但保持“逻辑外键”的习惯依然是对的——表和表之间通过业务主键关联,由代码保证一致性。
冗余字段方面,一个典型场景:领养申请表里存一份pet_id和一份pet_name的冗余快照。为什么?因为如果管理员后来修改了宠物信息,甚至删除了宠物,用户“我的申请记录”里依然要能看到当年申请的是什么宠物。如果只存pet_id,关联查询一旦宠物被删,申请记录就断链了。这个设计细节,在答辩时可以单独拿出来讲,非常体现工程经验。
另外建议所有表都统一加上create_time和update_time,用MyBatis-Plus的自动填充功能,在插入和更新时自动写入,不用在代码里手动set。这样统一、规范,也方便做时间维度的统计查询。
4. 后端核心链路:登录、鉴权、领养申请的完整实现逻辑
4.1 小程序登录链路:code2session与自定义登录态
微信小程序的登录,几乎所有新手第一次都会搞混。正确的链路是这样的:
- 小程序端调用
wx.login(),拿一个临时code(注意这个code有效期只有5分钟,而且只能用一次)。 - 小程序把code发给自己的后端服务器。
- 后端拿着code + appid + secret,调用微信的
code2session接口,换回openid和session_key。 - 后端用openid查数据库,判断是注册过的老用户还是新用户,如果是新用户就自动注册一条user记录。
- 后端生成一个自定义的登录态token(通常用UUID或者Sa-Token的token),把token和openid的映射关系存到Redis,设置过期时间。
- 后端把token返回给小程序端,小程序后续所有请求的请求头里都带上
Authorization: token。
为什么要绕这么一圈,而不是直接把openid返回给前端?因为openid相当于用户的身份ID,如果泄露给前端,别人拿到这个ID就能伪装你的用户身份。而token是你自己签发、自己校验、自己设定过期时间的,主动权在服务端手里。这个链路如果你能在答辩时画出来,那绝对是大加分项。
接口层面,典型的Controller是这样:
java复制@PostMapping("/login")
public Result login(@RequestBody LoginDTO dto) {
// 1. 调用微信接口用code换取openid
// 2. 查库/注册用户
// 3. 生成token存入Redis
// 4. 返回token和用户信息
}
注意,这里一定不能用@GetMapping传code,因为code在URL里容易被日志和第三方截获。虽然实际项目中通信走的是HTTPS,但习惯上敏感参数放body里更稳妥。
4.2 领养申请接口:防重复与幂等设计
领养申请接口是最容易出逻辑漏洞的地方,面试官也最喜欢在这里追问。用户点“申请领养”按钮,前端把pet_id和申请表单POST到后端,后端要做几件事:
第一,校验宠物状态。如果宠物status不是1(上架),直接拒绝申请,返回“该宠物已不可领养”。第二,校验该用户是否已经申请过这只宠物。如果申请表里已经存在“同一个user_id + pet_id + status=0或1”的记录,就不能让用户重复提交,否则会出现一个人申请三遍,管理员审核三遍的荒唐情况。
防重复的实现,可以在代码里先查一遍再插入,但更可靠的方式是在数据库层面加唯一索引。假定业务规则是“一个用户对一只宠物只能有一条有效申请”,那么可以给adoption_apply表加一个uk_user_pet(user_id, pet_id)的唯一索引。不过这里要小心,“有效申请”的定义如果包含“状态为已取消的可以再申请”,那你最好在状态流转里把“取消”视为该条记录的终态,并允许重新插入新记录。这时候加唯一索引就需要考虑是联合所有字段区分,还是通过逻辑代码处理。我的经验是:字段不多、并发量不高的场景,用代码先查再插入就够了,数据库唯一索引作为兜底保护可以上,但别让唯一索引和业务状态冲突。
第三,申请成功后要不要给宠物表加“已被申请”的标记?我的建议是只在申请表里存状态,不额外加标记,因为一个宠物可能同时被很多人申请,管理员从申请列表里挑一个审核通过,其余人的申请在宠物状态变为“已领养”时同步被系统关闭。这个“批量关闭其他申请”的逻辑,在管理员审核通过的接口里通过一条SQLUPDATE adoption_apply SET status = 4 WHERE pet_id = ? AND status = 0就能完成,非常优雅。
4.3 管理员审核接口:事务与状态一致性
管理员审核流程有两条核心逻辑:审核领养申请、审核宠物上架。审核领养申请时,管理员看申请人的资料、申请理由,然后选择通过或拒绝。如果通过了,接下来要连续做几件事:
java复制@Transactional(rollbackFor = Exception.class)
public void approveApply(Long applyId) {
// 1. 检查申请表状态,必须是待审核
// 2. 将申请表状态更新为通过
// 3. 将宠物表状态更新为已领养
// 4. 关闭该宠物的其他待审核申请
// 5. 给申请用户发送一条站内通知/微信订阅消息
}
这里最关键的是@Transactional。假设第3步成功、第4步失败,没有事务的话,宠物已经变成已领养,但其他申请还处于待审核,用户刷新后还能看到申请中的状态,然后去点“取消申请”或者管理员再次操作时就乱了。加上事务后,任何一步抛异常,前面的更新全部回滚,数据库始终停留在“一致的快照”。
审核宠物上架的逻辑相对简单,但有一个点要注意:管理员拒绝宠物上架时,通常应该填写拒绝原因,比如“疫苗证明不齐全”“图片不够清晰”。这个拒绝原因要能展示给录入人看,也就是宠物详情或列表页要有对应的文案提示。很多实现直接在pet表里加一个audit_remark字段,审核拒绝时写入,前端展示时优先读取该字段。
4.4 权限控制:Sa-Token在前后端分离项目中的用法
如果你手头的源码用的是Shiro或者Spring Security,也没问题。不过现在很多新的毕设项目会直接用Sa-Token,因为它的API极其简洁,专为前后端分离设计。
Sa-Token用起来非常直白:
java复制// 登录成功
StpUtil.login(userId);
// 鉴权:必须登录后才能访问
@SaCheckLogin
@GetMapping("/my/applyList")
public Result myApplyList() {
// ...
}
// 鉴权:必须管理员才能访问
@SaCheckRole("admin")
@PostMapping("/admin/auditApply")
public Result auditApply(@RequestBody AuditDTO dto) {
// ...
}
Sa-Token本身支持把会话存储在Redis中(集成Sa-Token的Redis集成包后),这样应用重启后登录态不会丢,这也和前面“用Redis存token”的设计一致。它的核心思路就是“登录就给你一个token,之后每次请求都在Header里带token,框架自动校验”。你不用像Spring Security那样配置一长串SecurityFilterChain,这对毕设项目来说能省下大量调试时间。
5. 小程序端页面结构与业务对接
5.1 首页与列表页:数据加载与分页策略
小程序端的首页通常包含顶部搜索框、轮播图、分类标签、推荐宠物列表。这里最核心的交互是列表的加载方式:上拉加载更多,也就是分页。
分页最常规的实现是页码pageNum+每页大小pageSize,后端返回PageResult对象,包含records列表和total总数。小程序端在onReachBottom生命周期里触发下一页加载,并且加一个isLoading标志防止重复加载:
javascript复制onReachBottom() {
if (this.data.pageNum * this.data.pageSize >= this.data.total) {
wx.showToast({ title: '没有更多了', icon: 'none' });
return;
}
this.setData({ pageNum: this.data.pageNum + 1 });
this.loadPetList();
}
前端和后端的字段命名要注意对齐。比如后端返回create_time还是createTime,这取决于你后端的JSON序列化配置。建议后端统一使用驼峰命名,配合Jackson默认配置,前端取用res.data.createTime。如果遇到字段对不上,多半是后端某个实体类加了@JsonProperty注解或者全局配置了下划线转驼峰,调试时先看返回的JSON长什么样,再决定前端怎么取值。
5.2 详情页与领养申请:表单验证要对齐后端规则
宠物详情页是你“讲故事”的地方,除了要展示宠物照片、基本信息、性格描述外,还要展示当前状态:可领养、已领养、审核中。这个状态直接从后端返回的status字段来,前后端通过状态码映射文案,不要在前端写死。
申请领养的表单通常包含领养人姓名、联系电话、微信号、所在城市、家庭情况、养宠经验、申请理由。这里有几个前端校验很关键:
- 手机号格式:11位数字,以1开头;
- 微信号不能为空,因为管理员想联系你的时候,如果手机打不通,微信是第二通道;
- 家庭情况不能选“无”,至少要选一项,否则后端接口即使不校验,管理员审核时也会觉得这人不靠谱。
后端在接收申请时也建议再次校验手机号格式和字段长度,防止绕过前端直接调接口。这是很多人容易忽略的:前端校验只能保证正常用户的操作体验,真正的数据安全边界在后端。
5.3 个人中心与状态展示:让用户看得懂进度
个人中心页面主要展示用户自己的信息、我的申请列表、我的收藏、联系客服、关于我们等入口。其中“我的申请列表”是体现系统完整度的重要模块。用户提交领养申请后,他要在列表里看到这条申请的实时状态:待审核、审核通过、已拒绝、已取消、已完成。
这里有一个前后端联调的常见问题:状态字段是数字,前端拿到数字后要映射成中文文案。我建议在前端写一个常量映射表,不要把映射散落到各个页面:
javascript复制const APPLY_STATUS_MAP = {
0: '待审核',
1: '已通过',
2: '已拒绝',
3: '已取消',
4: '已失效' // 宠物被其他人领养后自动关闭
};
如果你的项目里没有4这个状态,那也建议加上。因为前面提到,一只宠物被某个人申请通过后,其他所有人的申请都被系统关闭,这时候不能直接显示“已拒绝”,那会让用户觉得管理员故意针对他。更好的表述是“该宠物已被其他爱心人士领养,申请自动关闭”。这种细节,答辩的时候讲出来,评委会觉得你考虑得很周全。
5.4 与后端字段对齐:命名、类型、时间格式化
前后端联调里,90%的Bug都是字段对齐问题。最常见的几个:
时间字段:后端返回的是2024-05-20 12:00:00还是2024-05-20T12:00:00?如果后端LocalDateTime默认序列化带T,前端需要做格式化。统一在后端配置文件里加:
yaml复制spring:
jackson:
date-format: yyyy-MM-dd HH:mm:ss
time-zone: GMT+8
图片字段:宠物图片在数据库里存的是JSON数组字符串还是逗号分隔的字符串?前端拿到后要用JSON.parse或者split(',')处理成数组,才能用wx:for循环渲染成swiper。如果这里没处理,最典型的表象是图片区域白屏或不显示第二张图。
数字类型:比如age字段后端是Integer,前端在做条件筛选和展示时,不要直接用===去比较字符串和数字。为了保证稳健,前端在拿到数值后最好Number()一下再做判断。
6. 部署上线的完整链路——从本地到微信真机可访问
6.1 后端打包与环境准备
一个Spring Boot项目要部署到服务器,最常规的做法是打包成可执行jar包。
bash复制mvn clean package -DskipTests
打出来的jar在target目录下,上传到服务器后运行:
bash复制java -jar pet-adoption-server.jar --spring.profiles.active=prod
这里的prod对应application-prod.yml里的数据库地址、Redis地址等线上配置。很多源码项目会给你一份application-dev.yml和application-prod.yml,注意启动时指定profile,别把本地数据库配置带到线上。
服务器的环境准备,参考配置:
text复制JDK 1.8 (或项目要求的版本)
MySQL 5.7 (或8.0,需要确认驱动依赖版本)
Redis (如果项目用到了)
Nginx (做反向代理和HTTPS)
6.2 Nginx反向代理与HTTPS证书
小程序正式线上环境有个硬性要求:所有请求域名必须是HTTPS,并且域名要在小程序后台配置为request合法域名。所以在服务器上配置Nginx反向代理是标准操作。
一个典型的最小配置:
nginx复制server {
listen 443 ssl;
server_name api.example.com;
ssl_certificate /etc/nginx/cert/api.example.com.pem;
ssl_certificate_key /etc/nginx/cert/api.example.com.key;
location / {
proxy_pass http://127.0.0.1:8080;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
}
server {
listen 80;
server_name api.example.com;
return 301 https://$host$request_uri;
}
证书怎么来?如果你有云服务器,直接在云厂商的控制台申请免费的SSL证书,一般一年期,申请后下载Nginx版本,把pem和key放到服务器上。注意证书文件路径别写错,Nginx重载后可以用nginx -t先检查语法。
6.3 小程序后台配置:request合法域名与业务域名
小程序代码里请求后端的所有URL,必须是配置过的合法域名。在微信公众平台的小程序后台,“开发管理”—“开发设置”—“服务器域名”里添加:
text复制request合法域名: https://api.example.com
这里有两个非常常见的坑:一是不能加端口,https://api.example.com:8080是不允许的,必须用Nginx把443端口反向代理到8080;二是开发调试阶段,在微信开发者工具里可以勾选“不校验合法域名”,但真机预览时这个选项无效,必须配好域名才能跑通。
如果你没有独立域名,暂时想先看看效果,也可以在开发者工具里关掉域名校验来调试,但要知道这只是临时方案,最终上线必须走正规域名流程。
6.4 数据库初始化与首次启动调试
部署上线后第一次启动,最常见的两个问题:数据库没初始化、Redis连不上。数据库方面,源码包里一般会带SQL脚本,用Navicat或命令行执行即可。注意MySQL 8.0以上的默认认证方式是caching_sha2_password,而某些JDBC驱动版本可能不兼容,解决方法是在建用户时指定认证插件。
首次启动后,先用简单的命令验证服务是否正常:
bash复制curl http://127.0.0.1:8080/api/pet/list
如果返回JSON,说明后端起来了。再用https://api.example.com/api/pet/list测一遍,如果返回正常,说明Nginx和HTTPS也通了。最后在小程序开发者工具里把请求地址改成正式域名,真机扫码测试一次,这一步通过,就算正式上线了。
7. 调试过程中那些视频不会讲的坑
7.1 真机白屏与401:域名白名单和code失效案例
我第一次跑这类小程序项目时,卡得最久的就是真机预览一直白屏,或者请求直接报401。后来排查发现两个原因:一是request合法域名没配,导致微信给拦截了;二是我在本地调试时把wx.login()写的code缓存在全局变量里,用户第二次进入页面时直接拿旧code去请求,但code是一次性的,第一次用完就失效了。
正确做法是:每次进入小程序首页,或者token过期时,重新调用wx.login()获取新code,再去换token。如果你发现请求token的接口偶尔成功偶尔失败,优先怀疑是不是code复用。
7.2 图片上传失败:Tomcat限制与文件存储路径
宠物图片上传,本地开发时最常遇到上传成功但保存失败,或者上传大图直接被拒绝。原因是Spring Boot内置Tomcat默认的最大请求大小是1MB(不同版本略有差异),一张手机拍的照片很容易超过这个值。
解决办法是在配置里调大限制:
yaml复制spring:
servlet:
multipart:
max-file-size: 10MB
max-request-size: 20MB
另外,图片保存到服务器本地磁盘时要考虑路径问题。不要把图片存到jar包同级目录下的/upload,因为每次重新部署jar包时,依赖的当前工作目录可能变化。更稳妥的方式是在配置里指定一个绝对路径,比如/data/pet-adoption/images/,同时用Nginx把这个路径映射成https://api.example.com/images/的静态访问。否则小程序端显示图片就会因为跨域或者路径不对而失败。
7.3 时间字段时区偏差:MySQL与JSON序列化的双重问题
项目在家里跑着没问题,部署到云服务器上后,发现创建时间比北京时间慢了8小时。这个问题几乎100%是时区配置问题,因为云服务器默认时区通常是UTC,而开发机是东八区。
处理办法有三处需要统一:
- MySQL连接串上加
serverTimezone=Asia/Shanghai - JVM启动参数加
-Duser.timezone=GMT+8 - 配置文件设置Jackson时区为
GMT+8
三条都做了,基本不会有时区类的幺蛾子。这里建议不要依赖服务器的系统时区,直接在应用层面强制指定,这样换服务器也不怕。
7.4 富文本内容在小程序中不显示
宠物详情页如果有“宠物故事”这种富文本内容,后台录入时用的是富文本编辑器,生成的是一段HTML。小程序端用<rich-text nodes="...">组件渲染,但有一个坑:HTML里的很多CSS样式(比如<section>、<style>标签里的样式)在rich-text里会被过滤掉,导致排版错乱或者图片显示异常。
解决办法是后端在返回富文本内容时,做一次简单的HTML清洗/兼容,比如把所有<section>替换为<div>,把图片的style中的max-width:100%手动加上。如果不做兼容,至少要在后端存储时限制图片大小,否则大图在小程序里会把页面撑得很宽。
7.5 “并发申请”导致的数据不一致
前面提到过领养申请防重复,但还有一种更隐蔽的并发问题:两个用户同时提交同一只宠物的领养申请,后端都查了“当前没有申请记录”,然后都插入成功。管理员看到两条申请,审核通过第一条,第二条变成失效状态,这个流程本身能兜住。但如果后端的“关闭其他申请”逻辑是在审核时执行,而没有在插入时判断宠物是否已经被领养,就可能出现宠物已经status=1(未领养)但有人提交申请时系统没有拦截。
在insert apply之前,后端必须再查一次pet.status,并且把这两步放到一个事务里,才能保证数据一致性。这种情况在并发量低到几乎没有的毕设项目里不太会发生,但如果你在简历上写了“处理过并发幂等”,答辩时却答不上来,就容易暴露。所以提前想明白这个问题,面试时反倒成了加分点。
8. 简历、答辩与后续扩展:怎么把这套系统讲成“自己的项目”
8.1 简历上怎么写技术亮点
如果你要把这个项目写进简历,不要写“实现登录注册、增删改查”这种谁都有的废话。可以按这个思路提炼:
- 基于Spring Boot + MyBatis-Plus + Redis构建宠物领养平台,包含用户端小程序和管理员端,覆盖宠物信息审核、领养申请、状态流转、个人中心全流程业务。
- 通过微信code2session完成用户身份认证,服务端发放在Redis中维护的自定义token,支持登录态主动失效。
- 设计领养申请表的状态机,包含待审核、通过、拒绝、取消、失效五种状态,通过事务保证宠物状态与申请状态的一致性。
- 使用Sa-Token实现基于角色的接口鉴权,管理员接口与普通用户接口隔离。
- 完成Nginx反向代理和HTTPS证书配置,小程序正式环境稳定运行。
每个点都可以在面试时展开讲10分钟,这样就“有东西可挖”。
8.2 答辩时最可能被追问的问题
答辩和面试官很容易针对业务细节追问,提前准备好下面这几个问题的答案:
- 用户取消了申请,宠物是否自动恢复可领养状态?答案:要恢复,因为取消申请的record状态变为3,宠物库存的“占用”逻辑要相应释放。
- 如何防止同一用户反复提交同一宠物的申请?答案:先查后插,数据库唯一索引兜底,接口幂等处理。
- 管理员审核通过一个申请后,其他申请怎么处理?答案:事务内批量更新为失效状态,同时更新宠物为已领养。
- 微信登录时,如果用户第一次进小程序但头像昵称没授权,user表怎么建?答案:可以用空昵称和默认头像先注册,等用户主动授权后再更新资料,而不是强制授权才能用。
- 为什么用Redis而不是存数据库session?答案:Redis读写快、可设置过期时间、支持分布式扩展,服务端能主动控制会话。
8.3 可以继续扩展的方向
项目做完了,如果想更进一步,可以考虑这些扩展方向:
- 接入微信订阅消息:审核结果通过订阅消息推送给用户。这个功能很实用,因为用户特别关心“我的申请到底过了没有”。注意订阅消息需要用户在小程序里主动点击授权,且一次性订阅有效期只有一次。
- 把管理员后台从纯HTML换成Vue3 + Element Plus:如果你时间充裕,给项目加一个现代前端后台,简历上会更好看。
- 增加宠物回访记录:领养不是终点,平台可以定期给领养人推送回访问卷,记录宠物在新家的健康状况。
- 引入消息通知的数据统计:统计每日新增宠物数、领养成功率、平均审核时长,做一个简单数据看板。这个能体现你对“数据分析”也有意识。
我在实际带学生看这类源码项目时发现,真正把项目吃透的人,不一定写过每一行代码,但一定自己亲手把数据库的ER图画过一遍,把每个业务场景的时序图手绘过一遍,把每一个“为什么”都解释清楚。拿到源码只是起点,跑通demo只能说明你会“复制”,能在原有代码上调整功能、修复问题、说出设计理由,那才算这张简历上的项目真的属于你了。
