☰
Spring Boot+微信小程序宠物领养平台全栈开发实战解析
2026/10/19 16:15:49 网站建设 项目流程

宠物领养平台这个题目,在 Java 全栈项目里算是出现频率很高的类型了。我前后带过好几个版本,从最早的 PC 端后台,到后面配合微信小程序做前后端分离,踩过的坑不算少。今天这篇文章,就按最近一版“Java Spring Boot + 微信小程序”的宠物领养平台为主线,把从需求拆分、数据库设计、后端接口,到小程序端联调、管理后台落地的完整思路讲透。如果你是正在做毕业设计,或者想找个完整项目练手的开发者,这篇建议花十分钟读完。

先说说这个系统到底解决了什么问题。线下宠物领养的信息,基本靠朋友圈转发、救助站门口贴告示,信息分散,领养人找靠谱渠道很费劲。宠物领养平台就是把宠物信息、领养申请、审核记录、留言沟通这几件事统一放到线上:管理员发布待领养宠物,用户刷宠物列表、看详情、提交领养申请,管理员审核申请并更新宠物状态。流程不复杂,但涉及多端、多角色、权限控制和状态流转,作为全栈入门项目非常合适。

1. 项目整体设计与方案选型

1.1 先拆需求:这个系统到底要做什么

动手写代码之前,一定要先想清楚系统里有谁、每个角色能干什么。宠物领养平台的核心角色就两个:普通用户和管理员。

用户端需要支持:

  • 浏览宠物列表,按分类、关键词筛选。
  • 查看宠物详情,包括图片、年龄、性别、健康情况、救助故事。
  • 填写领养申请,提交后可以在“我的申请”里看审核进度。
  • 收藏喜欢的宠物,方便后面再回来看。
  • 在宠物详情页留言咨询。

管理员端需要支持:

  • 发布宠物信息,包括上传图片、填写基础资料、上下架宠物。
  • 审核用户提交的领养申请,通过或拒绝。
  • 维护用户列表,必要时禁用异常账号。
  • 发布平台公告,比如领养须知、回访通知。
  • 简单统计:待领养数量、本月新增申请数等。

这些功能拆完之后,其实就是一个标准的资源管理型应用。宠物是核心资源,用户围绕宠物做操作,管理员对操作结果做审核。

1.2 为什么选 Spring Boot + 微信小程序

这个组合是目前这类系统最主流的技术选型,不是没有道理的。先说说为什么前端选微信小程序而不是 H5 或 App。

领养这个场景非常依赖信任。小程序的入口在微信里,用户扫码就能用,不需要额外装 App,对救助站来说传播成本最低。朋友圈、群聊、公众号文章里放一个小程序码,用户点开就能看到待领养宠物,这比让用户去应用商店下载一个 App 现实得多。另外微信小程序有官方的登录体系,用户授权后就能拿到 openid,不需要单独做手机号注册流程,开发成本也低不少。

后端选 Spring Boot,主要看中它生态成熟、上手快。内置 Tomcat,配合 MyBatis-Plus 之后,基础 CRUD 几乎不用写 SQL,而且单体部署非常省事,本地一个 jar 包就能跑,上服务器也只是把端口放出来。对于宠物领养平台这个体量的项目,完全不需要引入 Spring Cloud 那套微服务架构,单体应用加合理的模块划分就足够了。

1.3 方案选型的几个注意点

我见过不少人在技术选型上纠结,这里直接给结论。

小程序端用原生写还是用 uni-app?如果项目只是跑在微信上,原生小程序就够了,逻辑清晰、文档多、出问题好排查。uni-app 的优势是一次编写多端发布,但领养平台的用户群就在微信里,没有多端需求,引入 uni-app 反而是负担。

后端要不要引入 Spring Security?不要。微信小程序登录后,后端只需要生成一个 token 返回给前端,前端后续请求带上这个 token,后端用一个拦截器校验就足够了。Spring Security 在这种场景下配置成本大于收益,反而会把项目复杂度拉高。做这类项目的关键是逻辑清晰,不是技术堆叠。

数据库选择 MySQL 5.7 或 8.0 都行,注意 8.0 的驱动名和 5.7 不同,项目文档里会写清楚。

2. 数据库设计与核心表结构

2.1 实体关系先理清楚

数据库是整个系统最底层的部分,结构设计不好,后面写代码会非常痛苦。宠物领养平台的实体关系可以这样梳理:

  • 用户和领养申请是 1 对 N:一个用户可以提交多条领养申请。
  • 宠物和领养申请是 1 对 N:一只宠物可以被多条申请记录关联,但只有一条申请会最终通过。
  • 用户和宠物通过收藏表构成多对多关系:一个用户能收藏多只宠物,一只宠物也能被多个用户收藏。
  • 用户和宠物通过留言表也构成多对多关系:留言本质上是用户对宠物产生的行为记录。

核心表就是用户表、宠物表、领养申请表、收藏表、留言表,外加一个公告表。

2.2 核心表结构设计

建表语句按实际项目里的写法来,我直接贴常用的版本。

CREATE TABLE `user` ( `id` bigint(20) NOT NULL AUTO_INCREMENT, `openid` varchar(64) NOT NULL COMMENT '微信openid', `nickname` varchar(50) DEFAULT NULL COMMENT '昵称', `avatar` varchar(255) DEFAULT NULL COMMENT '头像', `phone` varchar(20) DEFAULT NULL COMMENT '手机号', `status` tinyint(4) DEFAULT 0 COMMENT '0正常 1禁用', `create_time` datetime DEFAULT NULL, PRIMARY KEY (`id`), UNIQUE KEY `uk_openid` (`openid`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

用户表最关键的是 openid 字段,它由微信官方生成,一个微信号对应一个 openid,是天然的唯一标识。手机号字段不是必填的,可以在用户提交领养申请时再补充,避免一进来就弹授权框被用户反感。

CREATE TABLE `pet` ( `id` bigint(20) NOT NULL AUTO_INCREMENT, `name` varchar(50) DEFAULT NULL COMMENT '宠物昵称', `category_id` bigint(20) DEFAULT NULL COMMENT '分类id', `breed` varchar(50) DEFAULT NULL COMMENT '品种', `age` varchar(20) DEFAULT NULL COMMENT '年龄描述', `gender` tinyint(4) DEFAULT 0 COMMENT '0未知 1公 2母', `health` varchar(255) DEFAULT NULL COMMENT '健康状况', `description` text COMMENT '宠物描述', `images` varchar(1000) DEFAULT NULL COMMENT '图片地址,逗号分隔', `status` tinyint(4) DEFAULT 0 COMMENT '0待领养 1申请中 2已领养 3下架', `create_time` datetime DEFAULT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

宠物表里有个 status 字段,这个设计要重点说。很多第一次做的人会把宠物状态直接交给领养申请来判断,比如查一下有没有通过的申请,有就说明已领养。但这样每次展示宠物列表都要关联子查询,性能差不说,逻辑也绕。直接在宠物表维护一个状态值,展示的时候取这个字段就够了,状态流转放在事务里保证一致性。

CREATE TABLE `adoption` ( `id` bigint(20) NOT NULL AUTO_INCREMENT, `pet_id` bigint(20) NOT NULL, `user_id` bigint(20) NOT NULL, `apply_reason` varchar(500) DEFAULT NULL COMMENT '申请理由', `status` tinyint(4) DEFAULT 0 COMMENT '0审核中 1已通过 2已拒绝 3已取消', `remark` varchar(255) DEFAULT NULL COMMENT '审核备注', `create_time` datetime DEFAULT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

申请表的 status 是审核流的核心。管理员驳回时可以在 remark 里写原因,用户端能看到。

收藏表和留言表结构就简单了。

CREATE TABLE `favorite` ( `id` bigint(20) NOT NULL AUTO_INCREMENT, `user_id` bigint(20) NOT NULL, `pet_id` bigint(20) NOT NULL, `create_time` datetime DEFAULT NULL, PRIMARY KEY (`id`), UNIQUE KEY `uk_user_pet` (`user_id`, `pet_id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

收藏表要加唯一索引,这样数据库层面就保证了一个用户只能收藏一只宠物一次,代码里不需要额外查重。

2.3 表设计的实操心得

宠物图片用逗号分隔存字符串,这是个偷懒但有效的做法。图片数量有限,一般三五张,不用为此单独建一张宠物图片表。取出来之后按逗号 split 成年数组,小程序端轮播图直接绑定。

所有表统一用 utf8mb4 编码,不要用 utf8。utf8mb4 能存 emoji,用户昵称和宠物描述里经常会出现,用 utf8 会报 Incorrect string value 错误,这个坑我踩过不止一次。

公告表字段就标题、内容、创建时间,不需要额外设计。

3. 后端核心接口设计与实现

3.1 项目分层与统一返回结构

后端代码结构按 controller、service、mapper、entity、config、common 这几层分。controller 只做参数接收和结果返回,业务逻辑全放 service,mapper 层用 MyBatis-Plus 的 BaseMapper 提交流程后基础 CRUD 不用写 SQL。

统一返回结果这一步一定要做。不要每个接口自己拼 Map 返回,不然前端对接的时候每个人拿到的结构都不一样,联调效率极低。我习惯用这种方式:

public class Result<T> { private Integer code; private String message; private T data; public static <T> Result<T> success(T data) { Result<T> result = new Result<>(); result.setCode(200); result.setMessage("success"); result.setData(data); return result; } public static <T> Result<T> error(String message) { Result<T> result = new Result<>(); result.setCode(500); result.setMessage(message); return result; } }

code 表示业务状态,200 成功,500 失败。前端统一先判断 code 再取 data,逻辑一目了然。如果涉及未登录,要加一个 401 的 code,拦截器抛出后全局异常处理器转成 Result 返回。

3.2 登录接口与 token 鉴权

微信小程序登录流程是这样的:小程序端调用 wx.login 拿到临时 code,把 code 发给后端,后端拿 code 加上 appid、secret 去微信接口换 openid。拿到 openid 之后查用户表,没有就自动注册,有就正常登录。最后后端生成一个 token 返回小程序,小程序保存到本地 storage,后续所有请求带上这个 token。

后端生成 token 我用的是 UUID 加用户 id 拼接,然后存到数据库的 token 表或 Redis 里。如果项目没有引入 Redis,直接存一张 token 表也能用。不过我更建议用 Redis,简单设置过期时间。生成逻辑大概这样:

String token = UUID.randomUUID().toString().replace("-", ""); redisTemplate.opsForValue().set("token:" + token, user.getId().toString(), 7, TimeUnit.DAYS);

小程序请求头里带 token,后端写一个拦截器统一校验。校验方式就是从请求头拿到 token,去 Redis 查用户 id,查不到就返回 401,查到了把用户信息放到 ThreadLocal 里,方便 service 层直接获取当前登录用户。

这里有一个细节:拦截器要放行登录接口和宠物列表、宠物详情这类公开接口,只拦截领养申请、收藏、留言等需要登录的接口。放行列表在拦截器里写死或者配在配置文件里都行。

3.3 宠物列表分页查询接口

宠物列表是所有接口里最核心的一个。前端需要支持分类筛选、关键词搜索、分页加载。接口设计成这样的参数:

GET /api/pet/list?page=1&size=10&categoryId=1&keyword=猫

Service 层用 LambdaQueryWrapper 拼条件:

LambdaQueryWrapper<Pet> wrapper = new LambdaQueryWrapper<>(); if (categoryId != null) { wrapper.eq(Pet::getCategoryId, categoryId); } if (StringUtils.hasText(keyword)) { wrapper.like(Pet::getName, keyword).or().like(Pet::getBreed, keyword); } wrapper.eq(Pet::getStatus, 0).orderByDesc(Pet::getCreateTime); Page<Pet> page = petMapper.selectPage(new Page<>(pageNum, pageSize), wrapper);

注意 keyword 的匹配不要加在 status 条件后面用 and 连着,因为 keyword 可能匹配 name 也可能匹配 breed,需要用小括号把两个 like 括起来。LambdaQueryWrapper 里用 and 嵌套或者直接链式写多条件要注意这一点。

分页返回要包含总条数和当前页数据列表,前端做触底加载时自己判断还有没有下一页。MyBatis-Plus 的分页插件记得在 config 里配置,不然 selectPage 不会真正分页,这个坑很多新手会踩。

3.4 领养申请与审核状态流转

用户提交领养申请时,后端要做两件事:插入一条申请记录,同时把宠物状态改成“申请中”。这两个操作必须在同一个事务里,否则会出现申请记录有了但宠物状态没变,用户重复提交的情况。

@Transactional public void applyAdoption(Long petId, String reason) { Pet pet = petMapper.selectById(petId); if (pet == null || pet.getStatus() != 0) { throw new BusinessException("该宠物暂不可申请领养"); } Adoption adoption = new Adoption(); adoption.setPetId(petId); adoption.setUserId(currentUserId()); adoption.setApplyReason(reason); adoption.setStatus(0); adoptionMapper.insert(adoption); Pet update = new Pet(); update.setId(petId); update.setStatus(1); petMapper.updateById(update); }

管理员审核时也是两个操作:更新申请状态、更新宠物状态。通过时申请变 1,宠物变 2;拒绝时申请变 2,宠物变 0。同样要加事务。

这块代码的核心是状态机的控制。不要在 controller 里随手改状态,所有状态变更都走 service 层,统一校验前置状态,否则多人同时操作时数据会乱掉。

4. 小程序端实现要点

4.1 页面架构与导航设计

小程序端的页面结构按照功能和 tabBar 来规划。底部导航一般是三个 tab:首页、分类、我的。首页用来展示宠物列表和公告轮播,分类页按宠物种类筛选,我的页面放用户信息和功能入口。宠物详情页、领养申请页、我的申请列表、申请进度详情是二级页面,从对应入口跳过去。

app.json 里配置 tabBar 时,图标可以用项目自带的图片,也可以不配、只配文字,都不会影响功能。页面文件的路径要注册准确,这是小程序新手最常见的报错来源。

4.2 微信登录与 token 管理

小程序端登录逻辑我一般放在首页 onLoad 时触发。wx.login 拿 code,然后 wx.request 把 code 发给后端登录接口。后端返回 token 之后,wx.setStorageSync('token', token) 存起来。后续请求从 storage 里取 token 放到 header 的 Authorization 字段。

wx.login({ success: (res) => { wx.request({ url: 'http://localhost:8080/api/user/login', method: 'POST', data: { code: res.code }, success: (response) => { if (response.data.code === 200) { wx.setStorageSync('token', response.data.data.token); } } }); } });

写这几个接口时,我建议把 wx.request 封装成一个公共方法。统一处理请求头、token 过期跳转、错误提示。不然每个页面都写一遍 wx.request,代码会非常冗余。封装之后页面里的调用就简单了。

4.3 列表加载与下拉刷新

宠物列表要考虑用户刷新的场景。下拉刷新用页面自带的 enablePullDownRefresh,触底加载用 onReachBottom 生命周期。加载的逻辑就是维护一个 pageNum,第一次加载是 1,触底时 pageNum 加 1,请求下一页。服务端返回的数据里如果没有 more 字段就停止加载。

首页列表请求要带分类参数和关键词参数。用户点击分类 tab 或者搜索时,重置 pageNum 为 1,清空数组再加载。交互上要给一个空的提示,不然用户看到白屏会怀疑出 bug 了。

4.4 领养申请表单与状态回显

领养申请页就是一个表单,字段包括联系人、手机号、住址、申请理由、是否养过宠物。表单提交前要做非空校验,手机号要验证 11 位。提交成功后跳转到“我的申请”页面。

“我的申请”页面展示当前用户提交的所有申请记录,每条记录显示宠物名称、申请时间、审核状态。审核状态用文字显示比用数字好,后端返回的是 0、1、2,前端映射成“审核中”“已通过”“已拒绝”。

这里有个体验细节:用户在提交申请后,会想反复确认“到底通过没有”。所以在详情页可以加一个状态刷新按钮,或者简单点,下拉刷新页面时重新请求接口就行。

5. 管理后台的功能落地

5.1 后台技术选型

管理后台我用的方案是服务端模板渲染,HTML 加 CSS 框架加 jQuery。为什么不用 Vue 再做一套前后端分离?因为领养平台后台只有管理员一个人用,功能也就是增删改查,用模板渲染开发效率最高,不需要额外搭建前端工程。常见的 admin 模板选一个就行,重点是把功能做完整,不是把架构做得花哨。

后台和用户端共用同一个后端服务。后台接口直接用传统的方式,管理员登录后把用户信息存在 session 里,访问后台页面时用拦截器判断是否有 session。这个方法虽然老,但在这种小规模后台里是最好用的。

5.2 宠物管理与图片上传

后台的宠物管理页面是核心。表格展示宠物列表,提供新增、编辑、删除、上下架操作。新增和编辑共用同一个表单页面,按 id 判断是新增还是修改。

图片上传这块有几个容易出问题的地方。后台表单里用了文件选择框,提交时用 multipart 格式上传。后端接收后把文件保存到服务器的一个目录里,同时做静态资源映射,让图片可以通过 URL 直接访问。文件名一定要重命名,用 UUID 加后缀,不然两个用户上传相同名字的图片时后一个会覆盖前一个。

String originalFilename = file.getOriginalFilename(); String ext = originalFilename.substring(originalFilename.lastIndexOf(".")); String filename = UUID.randomUUID().toString().replace("-", "") + ext; file.transferTo(new File(uploadDir + filename));

上传后返回的文件访问地址要拼接完整路径。这里有一个常见坑:本地开发时图片访问地址是 localhost,小程序端调试时可以访问,但上线后小程序必须用 HTTPS 域名,图片地址也得跟着变成线上域名。

5.3 领养审核操作

后台的领养审核页面展示所有申请记录,列表里要关联查询出宠物名称和申请人昵称,方便管理员快速判断。每条记录后面放“通过”和“拒绝”两个按钮。点击通过时请求审核接口,传入申请 id 和审核结果。拒绝时弹一个输入框让管理员填拒绝理由,这个理由用户端会看到。

审核列表的查询要配置分页,申请记录多了之后不分页页面会卡。状态可以筛选,默认显示待审核的记录,管理员处理完一条就换下一条,操作效率是最高的。

6. 运行部署与常见问题排查

6.1 本地运行完整流程

拿到源码后,本地跑起来的步骤如下:

  1. 创建数据库,导入项目里的 sql 文件。
  2. 修改 application.yml 里的数据库用户名密码。
  3. 启动后端 Spring Boot 的 main 方法。
  4. 用微信开发者工具导入小程序项目,修改 request 请求域名配置。
  5. 后端端口和管理后台页面的访问地址按文档配置。

这里第一天跑项目的同学最容易卡在第 4 步。本地调试时,小程序必须在开发者工具的“详情-本地设置”里勾选“不校验合法域名、web-view(业务域名)、TLS 版本以及 HTTPS 证书”,否则请求 http://localhost 会被拦截,报 request:fail。

6.2 常见报错排查速查表

我整理了一份高频问题排查表,都是实际项目中遇到过的:

现象可能原因解决办法
小程序请求报 request:fail没有勾选不校验合法域名本地设置里勾选,或用 HTTPS 域名
后端接口返回 404请求路径不对或服务未启动检查控制台日志,确认路径和端口
图片显示不出来静态资源映射没配置检查资源映射目录和文件是否存在
登录后 token 失效Redis 里 key 过期或 token 没存检查 Redis 配置,检查 storage key
数据库连接失败密码错误或 MySQL 未启动检查配置文件,在命令行测试连接
中文乱码数据库连接串缺编码参数在 JDBC URL 加 characterEncoding=utf8

其中图片不显示这个问题值得多说一句。后端做了静态资源映射后,访问路径一般是 /upload/xxx.jpg。如果通过浏览器直接访问这个地址能显示但小程序里不显示,大概率是用了 HTTP 协议,而微信开发者工具的正常校验模式下限制非 HTTPS 请求。本地调试时可以勾选不校验解决,上线时用 HTTPS 域名解决。

6.3 上线部署的几个注意事项

项目部署到云服务器时,有几个事情要提前想好。首先是 HTTPS,微信小程序的正式环境要求所有请求域名必须是 HTTPS,并且要在小程序后台配置合法域名。没有 HTTPS 的小程序接口是调不通的。

其次是图片存储的位置。本地磁盘存储对小项目够用,但要注意磁盘空间和备份。更推荐的做法是使用云存储服务,后端把图片上传后拿到 URL 存储。如果你用的是本地磁盘,记得把上传目录挂在服务器的数据盘上,避免系统盘被写满导致服务异常。

最后一个容易忽略的是 Nginx 反向代理。后端服务建议只监听内网端口,对外统一走 Nginx 的 HTTPS 端口,然后用 location 规则把 /api 开头的请求转发给 Spring Boot,/upload 开头的请求映射到图片目录。这样安全很多,也不会把后端端口直接暴露到公网上。

我个人在项目实操里还有一个体会:宠物领养平台的业务逻辑不复杂,但状态一致性很容易写崩。申请领养和审核这两块一定要记得加事务,最好再加一个乐观锁或者数据库层面的状态校验,防止两个管理员同时操作同一只宠物导致数据错乱。多做几次并发请求测试,比后期上线后发现问题再修要省心得多。

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

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

立即咨询