这两年做过的项目里,让我印象最深的一个,就是这套基于 Spring Boot + 微信小程序的小动物救助领养系统。为什么印象深?因为它的业务逻辑并不复杂,但涉及的角色、状态流转和前后端协同问题非常典型:用户端要轻、管理端要全、接口要稳、图片要能传能显。今天这篇就把这个项目的完整思路、核心实现、调试实录和一些踩坑经验全部拆开来讲,给正在做类似前后端分离小程序项目的朋友一个可以直接参考的版本。
这个项目本质上是给救助站、个人志愿者和领养人搭一座桥。C 端微信小程序负责浏览待领养小动物、提交领养申请、收藏和查看公告;后台管理端用 Vue 实现,负责动物信息录入、审核申请、管理用户、发布公告;Spring Boot 作为统一 API 服务层,把两端串起来。无论你是学生做毕业设计、接外包项目,还是想给本地救助组织做一套公益工具,这套架构和实现思路都值得认真过一遍。
1. 项目定位与需求拆解:这个系统到底要解决什么问题
1.1 救助站/志愿者的真实痛点
我在调研需求的时候,跟好几个做流浪动物救助的朋友聊过。他们的日常状态基本是:微信群里发照片、朋友圈发领养信息、Excel 表登记意向、聊天记录翻到天荒地老。信息分散、状态不同步、领养人资质没有统一审核标准。
这个项目要解决的,恰恰就是这三件事:
- 信息集中化:所有待领养动物统一录入后台,小程序端按分类、品种、城市展示。
- 流程标准化:从“看到信息”到“提交申请”到“后台审核”到“领养成功”,每一步都有明确状态。
- 管理后台化:救助站管理员不用再对着手机截图,直接在 Vue 管理后台批量处理。
这里有一个很关键的认知:救助领养类小程序的核心不是“炫技”,而是把信任链条做完整。所以数据模型设计上,用户、动物、申请、公告四个主体之间的关联关系,比界面好不好看重要得多。
1.2 目标用户、角色划分与核心价值
这个系统涉及三类角色,权限边界必须清晰:
| 角色 | 使用端 | 核心操作 |
|---|---|---|
| 游客/普通用户 | 微信小程序 | 浏览动物、查看详情、提交领养申请、收藏 |
| 救助站管理员 | Vue 管理后台 | 动物信息录入、上下架、审核领养申请、公告管理 |
| 超级管理员 | Vue 管理后台 | 管理员账号管理、数据统计、系统配置 |
还有一个容易被忽略的点:用户在小程序端第一次进入时,是通过微信授权登录的,后台需要存储 openid 作为唯一标识。这个 openid 是后续申请领养、收藏、查看进度所有操作的凭证。所以登录逻辑不能只做一个“登录成功”的假象,token 的签发和校验必须在一开始就规划好。
实际做下来我的体会是,把角色和状态机先画清楚,后面写代码会顺畅得多。动物有“待审核/已上架/已下架/已被领养/已下架”这些状态,申请有“待审核/已通过/已拒绝/已完成”,不同角色在不同状态下能做的操作完全不一样,提前理清能少改很多 bug。
2. 整体架构与技术选型:为什么是 Spring Boot + 微信小程序 + Vue
2.1 前后端分离,到底分的是什么
项目标题里明确写了“前后端分离”,这不仅是技术架构的选择,更是团队协作模式的转变。在这个项目里,分离体现在三个层面:
- 工程分离:小程序端是一个独立工程,Vue 管理后台是一个独立工程,Spring Boot 后端是一个独立工程,三个目录互不干扰。
- 部署分离:后端 API 部署在云服务器,小程序端通过微信开发者工具上传发布,Vue 后台打包成静态文件用 Nginx 托管。
- 职责分离:前端只负责视图渲染和用户交互,后端只负责数据处理和业务逻辑,通信全靠 RESTful API。
如果你之前习惯写 JSP 那种前后端不分的架构,刚切到这种模式时最明显的感觉是:联调成本变高了,但代码维护成本降得很明显。改前端不用重启后端,改接口不用动页面,各改各的,只要把接口约定做好就行。
2.2 Spring Boot 在这个项目里为什么够用且合适
很多人在技术选型时会纠结:要不要用 Spring Cloud?要不要上微服务?我的建议是:一个面向救助站的小系统,单体应用 + Spring Boot 是最务实的选择。
Spring Boot 的优势在这个项目里体现得非常具体:
- 自动配置极大减少了 XML 和配置文件的维护。一个 Spring Initializr 生成的工程直接就能跑起来。
- 内置 Tomcat,打 jar 包即可部署。配合 Maven 打包,一条命令就能出产物。
- Spring Data JPA 或 MyBatis 的选择上,我用的是 MyBatis-Plus,因为 CRUD 操作多、条件查询复杂,MyBatis-Plus 的 LambdaQueryWrapper 写起来非常顺手。
- 生态成熟,集成微信登录、文件上传、参数校验都有现成方案。
有一个细节必须提醒:Spring Boot 版本不要盲目追求最新。2.7.x 或者 3.x 具体怎么选,要看你的 JDK 版本和依赖兼容性。我遇到过 Spring Boot 3 和某些第三方依赖不兼容导致启动报错的情况,后来回到 2.7.x 就一切正常。新手建议先用 2.7.x 起步,稳。
2.3 微信小程序为什么比 App 更适合这个场景
救助领养这东西有很强的地域性和即时性。领养人往往是刷朋友圈时看到某只流浪狗的照片才动了恻隐之心,让他专门下载一个 App 再注册一遍,转化率会低很多。微信小程序“扫码即用、用完即走、转发方便”的特点,和这个场景天然匹配。
小程序端使用的原生框架,配合微信开发者工具进行调试。页面结构上分为首页、动物列表、动物详情、我的、申请记录等几个模块。微信小程序的 WXML 语法本质上和 HTML 有相似之处,但又有自己的组件体系和事件绑定方式,上手的时候需要注意区分。
视频播放也是一个常见需求,比如给待领养动物拍一段动态视频。小程序端的 video 组件天然支持播放,但要注意是 mp4 格式。如果后端给的是流媒体格式(比如 m3u8),微信小程序原生不支持直接播,需要做特殊处理或转码,这点后面调试部分会细说。
2.4 Vue 管理后台:轻量但必须完整
管理后台我选了 Vue 2 + Element UI 这个组合。虽然现在 Vue 3 + Element Plus 已经很成熟,但考虑到项目稳定性和大量现成示例,Vue 2 生态在这个体量的项目中非常够用。
管理后台的核心页面包括:
- 登录页(管理员账号密码,JWT 鉴权)
- 动物管理列表(分页查询、筛选、上架/下架、编辑、删除)
- 动物录入表单(图片上传、多图展示、品种/年龄/性别/疫苗状态等字段)
- 领养申请审核列表(查看申请人信息、审核通过/拒绝)
- 用户管理列表(查看用户基础信息、openid、注册时间)
- 公告管理(发布公告、展示在小程序端首页)
- 数据统计(简单展示每天新增动物数、申请数、领养成功数)
Vue 后台不是给普通用户用的,是给救助站工作人员用的,所以功能宁可多而全,不要花哨但缺按钮。我做的版本里,批量操作、状态筛选这类“易用性”功能,比图表可视化更实用。
3. 数据模型设计与功能模块拆解
3.1 数据库表设计:四张核心表与两张辅助表
整个系统的数据表,我整理成下面这个结构:
| 表名 | 用途 | 关键字段 |
|---|---|---|
| user | 微信用户 | id, openid, nickname, avatar, phone, city, create_time |
| admin_user | 后台管理员 | id, username, password(BCrypt加密), role, status |
| animal | 待领养动物信息 | id, name, type(猫/狗/其他), breed, gender, age, health_status, vaccine_status, description, cover_image, images, status, create_time |
| adopt_application | 领养申请 | id, animal_id, user_id, applicant_name, applicant_phone, applicant_address, reason, status, create_time, update_time |
| favorite | 收藏记录 | id, user_id, animal_id, create_time |
| notice | 公告 | id, title, content, status, create_time |
设计这几张表的时候,有几个细节值得展开讲。
第一,animal 表的 images 字段我用了 JSON 字符串存储,存的是图片 URL 数组,前台展示时方便直接解析;但这会牺牲一定的数据库规范化,如果你对性能有极致要求或者后期要做图片维度统计,建议拆成独立的 animal_image 表。
第二,adopt_application 表必须冗余申请人姓名、电话、地址。为什么?因为如果只存 user_id,后续用户改了头像昵称,或者你想统计某个城市的领养数据,就要多表关联查询。冗余这些字段换来的是列表查询少 join 一次,值。
第三,BCrypt 加密密码是必须的。管理后台的 admin_user 表密码绝不能明文存储,Spring Security 里的 BCryptPasswordEncoder 一行代码就能搞定。
3.2 功能模块全景:从用户端到管理端
小程序端功能模块:
- 首页:轮播公告 + 推荐动物列表 + 快速筛选入口(猫/狗)
- 动物列表页:分页加载、按品种性别筛选、搜索
- 动物详情页:轮播图、基本信息、救助站描述、收藏按钮、领养申请入口
- 领养申请页:表单填写(姓名、电话、住址、养宠经验、申请理由)
- 我的页面:我的收藏、我的申请记录、个人资料
- 申请进度查询:查看申请处于待审核/通过/拒绝/完成状态
管理后台功能模块:
- 仪表盘:核心数据概览(动物总数、待审核申请数、用户数)
- 动物管理:列表、新增、编辑、删除、上下架
- 申请审核:列表、详情、审核操作、联系用户
- 用户管理:列表、详情、禁用
- 公告管理:新增编辑删除、上下线
- 管理员管理:新增管理员、重置密码、停用
整个功能清单看下来,其实没有特别复杂的业务。但每一条链路都涉及到前端调用接口、后端操作数据库、再返回结果渲染的过程,把这些链路做顺,就是项目的核心工作。
4. 系统实现过程:从空工程到可运行的全流程记录
4.1 Spring Boot 后端:接口设计与核心代码逻辑
后端这块我按照 controller-service-mapper 三层来组织。controller 只做参数接收和结果封装,service 写业务逻辑,mapper 负责数据库交互。
以动物列表接口为例:
@GetMapping("/animal/page") public Result<PageResult<AnimalVO>> page(@RequestParam(defaultValue = "1") Integer page, @RequestParam(defaultValue = "10") Integer size, @RequestParam(required = false) String type, @RequestParam(required = false) String keyword) { LambdaQueryWrapper<Animal> wrapper = new LambdaQueryWrapper<>(); wrapper.eq(Animal::getStatus, 1) // 只展示已上架 .eq(StringUtils.hasText(type), Animal::getType, type) .like(StringUtils.hasText(keyword), Animal::getName(), keyword) .orderByDesc(Animal::getCreateTime); Page<Animal> p = animalMapper.selectPage(new Page<>(page, size), wrapper); // 转换为VO,隐藏敏感字段 return Result.success(new PageResult<>(p.getRecords(), p.getTotal())); }这里有两个习惯值得推荐:
- 返回统一结构 Result。无论成功失败,code/message/data 三件套,前端判断 code 即可,不用每次写 try-catch。
- 用 VO 对象返回前端需要的字段,而不是直接返回实体。实体类里可能有多余的字段(比如 status 表示内部状态),前端不一定需要,通过 VO 做一次字段收敛,接口更干净。
领养申请提交的接口稍微复杂一点,因为要校验用户登录状态、校验动物是否可领养、校验是否重复申请:
@PostMapping("/adopt/apply") public Result<?> apply(@RequestBody ApplyDTO dto, @RequestHeader("token") String token) { Integer userId = userService.getUserIdByToken(token); if (userId == null) return Result.error(401, "登录已过期"); Animal animal = animalMapper.selectById(dto.getAnimalId()); if (animal == null || animal.getStatus() != 1) { return Result.error("该动物不存在或暂不可领养"); } // 防止重复申请 Long count = applyMapper.selectCount(new LambdaQueryWrapper<AdoptApplication>() .eq(AdoptApplication::getAnimalId, dto.getAnimalId()) .eq(AdoptApplication::getUserId, userId) .in(AdoptApplication::getStatus, 0, 1)); // 待审核或已通过 if (count > 0) return Result.error("您已申请过该动物,请勿重复提交"); // 业务合法性校验通过后插入申请记录 // ... return Result.success(); }这段代码里体现了一个重要原则:后端不能信任前端传的任何状态,所有校验必须在后端重做一遍。前端可能隐藏了按钮,但人为构造请求也可以绕过,后端接口是最后一道防线。
4.2 微信小程序端:登录、页面结构与关键交互
微信小程序的登录逻辑是:wx.login 获取 code,传给后端,后端拿着 code + appid + secret 去微信接口换 openid 和 session_key,再生成自己的 token 返回前端。后续所有请求都在 header 里带 token。
具体代码如下:
// 小程序端 app.js 里的登录逻辑 login() { wx.login({ success: (res) => { wx.request({ url: 'https://api.example.com/user/login', method: 'POST', data: { code: res.code }, success: (response) => { const { token } = response.data.data; wx.setStorageSync('token', token); // 再拉取用户信息 this.getUserInfo(); } }); } }); }后端的处理逻辑核心就是调微信接口换 openid,然后查询或创建用户,再返回 token:
String url = "https://api.weixin.qq.com/sns/jscode2session?appid=" + appid + "&secret=" + secret + "&js_code=" + code + "&grant_type=authorization_code"; // 用 RestTemplate 发起 GET 请求 // 解析返回的 openid // 查 user 表,有则更新昵称头像,无则新建 // 生成 UUID 作为 token,存 Redis 并设置过期时间这里有一个小坑:微信接口返回的 session_key 是敏感信息,绝对不能让前端拿到,否则可能导致用户数据安全问题。后端只在内部使用,token 用自生成的 UUID 就好,不需要把 session_key 暴露出去。
小程序端的核心页面交互,我需要重点强调“状态同步”的体验。比如用户点击收藏后,按钮立即变灰,列表页的收藏状态也要同步。我的做法是:收藏操作成功后,更新本地缓存的收藏列表,同时 store 里保存收藏的 animalId 集合,详情页根据这个集合判断按钮状态。
4.3 Vue 管理后台:Element UI 表格、表单与图片上传
管理后台最核心的页面是动物录入表单。这个表单字段多、类型杂,用 Element UI 的 el-form 加上动态校验规则可以很好解决。
图片上传这块必须多说一句。Vue 端用 el-upload 组件,配置 action 指向后端的文件上传接口。后端接收 MultipartFile,保存到服务器的指定目录,然后返回一个可访问的 URL。这个 URL 写成绝对路径还是相对路径很有讲究。我在本地开发时用 http://localhost:8080 的绝对路径,部署到服务器后就换了服务器公网 IP 或域名。如果前后端域名不同,还要在 Nginx 或后端配置跨域策略,保证图片能正常加载出来。
图片存储方案的选择上,本地存是最简单的。但如果图片量大、服务器带宽有限,建议用对象存储加 CDN。这个项目因为量级不大,本地存储完全够用,关键是要把图片目录和上传接口设计好,后期换对象存储只需要改上传接口的实现,前端无感知。
状态管理用 Vuex 或 Pinia 都行。管理后台我主要用 Vuex 存用户登录信息、菜单权限、路由状态。有个很实用的小技巧:在 axios 拦截器里统一处理 token,请求前在 header 里带 token,响应时遇到 401 就跳回登录页。这样每个页面都不用单独处理登录过期。
5. 调试过程实录:联调、报错、排查的完整复盘
5.1 前后端联调时的跨域问题(CORS)
这几乎是前后端分离项目必经的一个坑。小程序端不存在跨域问题,因为微信小程序的请求不遵循浏览器同源策略;但 Vue 管理后台跑在浏览器里面,跨域是必须处理的。
我的处理方案是后端加一个全局 CORS 配置类:
@Configuration public class CorsConfig implements WebMvcConfigurer { @Override public void addCorsMappings(CorsRegistry registry) { registry.addMapping("/**") .allowedOriginPatterns("*") .allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS") .allowedHeaders("*") .allowCredentials(true) .maxAge(3600); } }注意 allowCredentials(true) 的时候,allowedOrigins 不能直接用 “*”,要使用 allowedOriginPatterns,这是很多新手的经典报错点。
还有一种更常见的做法是走 Nginx 反向代理,把 /api/ 路径代理到后端服务,前端请求同源的 /api/xxx,由 Nginx 转发并解决跨域。生产环境我强烈建议这种方式,统一入口后还可以顺手做 HTTPS 和请求日志。
5.2 小程序真机调试时图片加载不出来
联调时遇到最诡异的问题是:开发者工具里图片显示正常,手机上一加载就空白。排查了半天才发现,是因为后端开发服务器的 IP 是局域网地址,手机和电脑不在同一网段,访问不到。
这个问题的标准解法是:
- 开发阶段,让手机和电脑连同一个 Wi-Fi,后端启动时设置
--server.address=0.0.0.0允许局域网访问。 - 或者用内网穿透工具把本地端口映射到公网地址,微信小程序后台配置合法域名,手机就能访问了。
- 上线阶段,所有接口和图片 URL 必须换成 HTTPS 域名,微信小程序要求所有 request 请求的域名必须在小程序后台配置为合法域名,且必须是 HTTPS。
还有一个小细节:微信小程序里图片域名和 request 合法域名是分开配置的,如果图片一直加载不出来,先检查是否把图片域名配到了 downloadFile 合法域名里。
5.3 微信登录与 token 失效问题排查
出现过一次非常典型的 bug:用户每天第一次打开小程序都要重新登录,但明明 token 已经存储了。排查后发现是 token 过期时间设置得太短(只有 2 小时),用户隔天打开必然过期。
解决思路:
- 把 token 过期时间设置为 7 天,同时提供刷新接口。
- 在用户每次操作时刷新 token 的过期时间,保持活跃用户的登录态。
- 前端在请求拦截器里遇到 401 不立即跳登录,而是先去调刷新接口,刷新失败再跳。
另外一个容易踩的坑是:wx.login 的 code 只能使用一次,不能重复使用。如果前端在短时间内多次调用 wx.login,后端处理时要每个 code 只换一次,否则微信接口会报错。
5.4 文件上传大小与类型限制
上传图片一直失败,排查半天发现是 Spring Boot 默认请求体大小限制在 1MB。用户用手机拍的照片动辄 3-5MB,直接 413 报错。
在 application.yml 里配置:
spring: servlet: multipart: max-file-size: 10MB max-request-size: 20MB同时在后端做文件类型校验,只允许 jpg、png、gif、webp 格式,避免有人传其他危险文件。这里和线上安全相关,千万别省略。
后端对上传的文件名最好做一次重命名,用 UUID + 原始后缀,避免中文文件名乱码,同时防止路径穿越攻击。这些细节不处理,系统虽然能跑,但隐患很多。
6. 部署上线与后续扩展建议
6.1 从本地到服务器:部署实操记录
这个项目的部署流程相对标准,我在生产环境跑通并记录了步骤:
- 后端:Maven 打包得到 jar,上传到服务器,用 nohup 启动或用 systemd 管理服务,建议写一个小脚本处理启停和日志备份。
- 前端 Vue 后台:
npm run build生成 dist 目录,把静态文件放到 Nginx 的 html 目录,配置反向代理/api到后端端口。 - 小程序端:在微信开发者工具中上传代码,填好版本号和备注,提交审核。审核通过后发布。
- 域名与证书:小程序要求所有 request 域名必须是备案过的 HTTPS 域名,所以提前准备域名和 SSL 证书,并配置到小程序后台的合法域名里。
这里有一个经常被忽略的点:管理后台的接口如果直接暴露到公网,任何人拿到接口地址就可以绕过登录直接调用。我的处理方式是:后端接口用自定义 token 校验拦截器统一处理,管理后台接口要求必须携带合法的管理员 token,并且对管理端接口做独立的权限校验,保证普通用户 token 无法访问管理接口。
6.2 这个系统还能怎么扩展
如果这个项目不是毕业设计交付完就不再管,而是想真正落地使用,我建议按下面几个方向迭代:
第一,增加志愿者模块。救助站往往缺的不是信息展示,是志愿者管理。可以增加志愿者报名、排班、任务认领功能。
第二,增加回访机制。领养不是终点,回访才能发现动物是否真的被善待。可以在系统中增加“领养回访记录”功能,管理员定期回访后在后台登记,形成完整的领养闭环。
第三,增加捐赠和物资众筹。救助站的资金压力是持续的,小程序端增加“帮助它”按钮,跳转捐赠页面,让更多人参与进来。
第四,增加地图定位。用微信小程序的 wx.getLocation 获取用户所在城市,根据经纬度推荐附近的待领养动物,提高领养转化率。集成腾讯地图或高德地图的逆地址解析,就能实现“附近的小动物”这个功能。
第五,消息通知。小程序的订阅消息非常适合申请审核结果通知。用户提交领养申请后,管理员审核通过或拒绝,后端调用微信订阅消息接口,用户就能收到模板消息通知。这一块能明显提升用户体验。
写在最后的经验
我个人在实际开发中最大的体会是:一套前后端分离的小程序项目,真正难的从来不是某个技术点,而是把登录态、状态流转、文件传输这三条链路接口设计得足够顺畅。
如果你正在做类似项目,我强烈建议先花一天时间把接口文档写清楚,把每个接口的入参、出参、错误码定义好,再开始写代码。前后端分离模式下,接口是唯一的契约,契约乱了,联调阶段会浪费大量时间。
最后再分享一个小技巧:微信开发者工具里的“真机调试”功能,建议从一开始就养成随手点开的习惯。很多样式和交互在模拟器里是正常的,一到真机上就出现导航栏高度、底部安全区、图片加载速度这些差异。越早发现,改起来越轻松。做公益类项目本身就是一件有温度的事,代码写扎实一点,也是对那些等待被领养的小生命负责。