☰
基于Spring Boot和微信小程序的宠物领养系统开发全解析
2026/10/6 5:29:12 网站建设 项目流程

1. 项目概述与需求拆解

先交代一下这个项目的背景。宠物领养这件事,在线下一直存在信息不对称的问题:救助站和流浪动物收容所里猫狗爆满,想领养的人却找不到靠谱渠道;民间自发救助的宠物,扩散范围基本靠朋友圈转发。我做的这套基于 Spring Boot 和微信小程序的宠物领养系统,就是想把这个信息鸿沟填上。系统分两端:小程序端给普通用户浏览宠物、申请领养、发布送养信息;后端用 Spring Boot 提供接口能力,管理用户身份、宠物信息、领养审核、数据统计这些核心逻辑。

这个项目适合谁参考?两类人。第一类是正在做毕业设计或课程项目的计算机专业学生,这套系统的业务链路完整——有注册登录、权限区分、文件上传、状态流转,从数据库设计到前后端联调能覆盖一整套开发流程;第二类是确实想跑通一个真实小程序项目的开发者,想知道 Spring Boot 后端和微信小程序前端到底怎么配合,信息怎么加密传输、文件怎么传、会话怎么维系,看完能少踩很多坑。

技术栈选型的逻辑也说说。前端用微信小程序原生语法,因为微信的生态最成熟,用户搜“宠物领养”这类关键词可以直接在小程序里找到入口,不用额外下载 App;后端用 Spring Boot,因为生态成熟、资料多、快捷开发效率高,而且跟小程序端的接口对接非常直接,没有复杂的跨域限制要处理。整个系统的数据流是:小程序端通过 wx.request 发 HTTP 请求到 Spring Boot 的 RESTful 接口,接口层做参数校验和身份校验,Service 层处理业务逻辑,Mapper 层操作 MySQL 数据库。后面我会把每个环节的具体实现拆开讲。

2. 数据库设计与核心业务模块

2.1 数据模型设计思路

先讲数据库。这个系统的核心实体有五个:用户、宠物、领养申请、收藏记录、审核记录。设计表结构的时候我遵循一个原则——每个核心业务节点都要有独立的表,但关联字段一定用外键逻辑关联,不搞冗余字段散落。

拿用户表来说,除了常规的 openid、昵称、头像、手机号,我还加了 role 字段来区分普通用户和管理员。为什么在用户表里直接加角色而不是单独建一张角色表?因为这个系统的角色只有两种,且角色固定不频繁变动,一张表加字段的方式远比角色表 + 用户角色关联表轻量。小程序端的用户基本是从微信授权登录进来的,首次登录时系统会把用户信息拉取写入库,后续再访问就靠 openid 直接查询。

宠物表是最核心的一张表。字段包括宠物名称、种类(猫/狗/其他)、品种、性别、年龄、毛色、健康状况、绝育状态、疫苗状态、所在城市、详细描述、图片链接、发布者 ID、状态字段。这个 status 字段我强烈建议用数字状态机而不是字符串。我用的是:0 表示待审核、1 表示展示中、2 表示已被申请、3 表示已领养、4 表示已下架。为什么?因为领养流程是个状态流转过程,用数字状态配合枚举类,在 Java 代码里判断和流转都非常清晰,而且数据库索引对数字的检索效率更高。

领养申请表需要重点说。字段有申请宠物 ID、申请用户 ID、申请理由、个人情况说明(住房情况、养宠经验等)、期望领养时间、状态字段。状态同样用数字:0 待审核、1 已通过、2 已拒绝。这张表是系统里业务逻辑最复杂的部分,因为它要支撑的是“一个宠物同时被多个人申请,但最终只能被一个人领养”的并发场景。我个人在后端接口里做了两层校验,后面详细讲。

2.2 关键表结构示例

我把核心表的建表语句直接贴出来,方便你对照着理解,自己建库的时候也可以直接改改用。

CREATE TABLE `user` ( `id` int(11) NOT NULL AUTO_INCREMENT, `openid` varchar(64) NOT NULL COMMENT '微信openid', `nickname` varchar(64) DEFAULT NULL COMMENT '昵称', `avatar` varchar(255) DEFAULT NULL COMMENT '头像地址', `phone` varchar(20) DEFAULT NULL COMMENT '手机号', `role` tinyint(1) DEFAULT '0' COMMENT '0普通用户 1管理员', `city` varchar(64) DEFAULT NULL COMMENT '所在城市', `create_time` datetime DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (`id`), UNIQUE KEY `uk_openid` (`openid`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
CREATE TABLE `pet` ( `id` int(11) NOT NULL AUTO_INCREMENT, `name` varchar(32) DEFAULT NULL COMMENT '宠物昵称', `category` varchar(16) DEFAULT NULL COMMENT '猫/狗/其他', `breed` varchar(32) DEFAULT NULL COMMENT '品种', `gender` tinyint(1) DEFAULT '1' COMMENT '1公 2母', `age` int(11) DEFAULT NULL COMMENT '年龄(月)', `color` varchar(32) DEFAULT NULL COMMENT '毛色', `health_status` varchar(255) DEFAULT NULL COMMENT '健康状况描述', `sterilization` tinyint(1) DEFAULT '0' COMMENT '是否绝育', `vaccine` tinyint(1) DEFAULT '0' COMMENT '是否疫苗', `city` varchar(64) DEFAULT NULL COMMENT '城市', `description` text COMMENT '详细描述', `images` varchar(1024) DEFAULT NULL COMMENT '图片链接,逗号分隔', `publisher_id` int(11) NOT NULL COMMENT '发布者用户ID', `status` tinyint(1) DEFAULT '0' COMMENT '0待审核 1展示中 2已被申请 3已领养 4已下架', `create_time` datetime DEFAULT CURRENT_TIMESTAMP, `update_time` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (`id`), KEY `idx_status` (`status`), KEY `idx_city` (`city`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

这里有个实操经验:images 字段用逗号分隔存多张图片的 URL,并不是最规范的设计——规范做法是建一张宠物图片表。但实际项目里宠物图片一般就 3 到 5 张,查询时一次拿全部链接比再发一次查询效率更高,而且后期如果要做图片管理,改造成本也不高。项目初期我建议就用这种“适度反规范化”的设计,等用户量和数据量真正上来了再考虑拆表。

3. 后端 Spring Boot 接口实现

3.1 会话管理与登录态维护

微信小程序的登录机制和传统网页登录完全不一样,这个坑我见过太多人踩。小程序端调用 wx.login 获取一个临时 code,这个 code 一次性有效且只有 5 分钟有效期,后端拿着这个 code 调用微信的 code2Session 接口换取 openid。拿到 openid 后,你就拿到了用户的唯一身份标识。

我在后端写了一个AuthController处理登录请求,核心逻辑就是:接收前端传来的 code,调用微信接口换取 openid,查数据库判断该用户是否已存在,不存在就自动注册一个新用户,然后生成一个自定义 token 返回给前端。这个 token 我采用的是 JWT(JSON Web Token)方案,而不是把 openid 直接返回给前端。为什么用 JWT?因为小程序端的每个请求都带着 openid 走,安全性太差,JWT 可以设置过期时间而且无状态,后端不用专门存 session。

JWT 的生成密钥我建议是放在 application.yml 里,用环境变量引用的方式配置,不要直接硬编码在代码里。另外 JWT 的过期时间我设置为 7 天,这个周期对小程序的用户习惯来说比较合适——用户打开小程序频率高,7 天内必然再次使用,token 会自动续期,而微信官方登录态有效期也是 7 天,两边能对齐。

小程序端拿到 token 后,需要在后续每个 wx.request 请求的 header 里带上Authorization: Bearer xxx。后端写了一个拦截器去解析这个 token,解析失败或者 token 过期就返回 401,前端收到 401 后引导用户重新授权。这套流程是标准做法,但我在实际开发中还加了一个细节:拦截器放行白名单。登录接口、宠物列表接口、宠物详情接口这些无需登录就能访问的接口放行,其余需要登录的操作(发布、申请、收藏)必须校验登录态。如果全部接口都要求登录,用户体验会很差——游客在列表页看到了喜欢的宠物,点进详情想看看,这个时候弹登录框是很劝退的。

3.2 宠物模块接口设计

宠物模块的接口设计是整个系统的基础,我按照 RESTful 风格整理出了以下核心接口。每个接口都尽量遵循“资源名 + HTTP 方法”的规范,前端调用时也非常直观:

接口方法说明
/api/pet/listGET分页查询宠物列表,支持按城市/种类/状态筛选
/api/pet/detailGET查询宠物详情,参数是宠物 ID
/api/pet/publishPOST发布送养信息,需登录
/api/pet/updateStatusPOST更新宠物状态,发布者或管理员可调用
/api/pet/myGET查询我发布的宠物列表,需登录
/api/pet/favoritePOST收藏/取消收藏宠物,需登录
/api/pet/myFavoriteGET查询我的收藏列表,需登录

列表接口看起来简单,但我在实现时做了两个关键的优化:一是分页参数用 pageNum 和 pageSize 标准分页,配合 PageHelper 插件,一行代码搞定分页查询;二是列表接口默认只返回状态为 1(展示中)的宠物,因为待审核、已领养、已下架的信息都不应该出现在公开展示列表里。这是我踩过的一个坑:初期没加状态过滤,管理员审核不通过的宠物也出现在列表里,体验很糟。后来改成默认只展示状态 1,同时管理员端单独有接口查看全部状态的宠物。

发布宠物接口要注意图片上传的问题。小程序端 wx.uploadFile 上传图片到后端,后端接收 MultipartFile 后再转发到对象存储。我初期图省事直接存到服务器本地磁盘,开发阶段问题不大,但部署上线就有风险——服务器磁盘有限、图片访问性能不佳、重启服务可能丢数据。后来我把图片上传做成了抽象策略:接口层接收图片后调用一个存储策略接口,这个接口有本地存储和云存储两个实现类,通过配置切换。这样开发时用本地路径,部署时切到云存储,代码基本不用改。

3.3 领养申请的状态机和并发控制

领养申请是整个系统里业务规则最复杂的部分,这里我单独拿出来讲。一个宠物被用户 A 申请后,宠物状态从 1(展示中)变为 2(已被申请)。这个状态变化有两个目的:一是告诉其他用户这宠物已经有主了,不要再申请;二是给发布者一个管理入口——他可以在申请列表里选择通过谁或拒绝谁。

这里有个并发问题需要处理:假如用户 A 和用户 B 同时对同一个宠物发起申请,而后端没有做任何限制的话,两个人可能都申请成功,最后发布者只能手动挑一个,另一个白白等了很多天。我的处理方案是在 Service 层使用数据库悲观锁或乐观锁。用 SQL 层面来限制也可以:先执行UPDATE pet SET status = 2 WHERE id = ? AND status = 1,这个更新语句本身是原子性的,如果返回影响行数为 0,说明宠物状态已经不是 1 了(可能已被申请或下架),直接返回提示信息;如果影响行数为 1,说明当前用户成功锁定了这个宠物的申请资格。这个方案简单有效,连事务都不用开,一次原子操作就完成了并发控制。

领养申请通过后,宠物状态变更为 3(已领养),同时系统会自动拒绝该宠物下的其他所有待审核申请。这个逻辑一开始我漏掉了,导致出现一只宠物已经被领养了,其他人的申请还挂在“待审核”的尴尬情况。后来我在通过申请的后端代码里加了一步:批量更新该宠物下所有状态为 0 的申请为 2(已拒绝)。一个 UPDATE 语句搞定,效率也不差。

4. 小程序端核心页面与交互实现

4.1 首页与列表页的数据加载

小程序首页要承担两个任务:品牌展示和内容分发。顶部放搜索框和城市选择器,用户可以选择自己所在城市,列表会对应切换为该城市的宠物信息。中间部分是分类筛选栏(全部/猫/狗/其他),点击切换时重新请求接口。下方就是宠物卡片列表,左图右文的形式展示。

这里我重点讲讲列表的加载更多功能,因为小程序列表加载更多是高频需求,但很多人做得不丝滑。我在数据层用了一个pageNum变量记录当前加载到第几页,每页固定 10 条,触底时调用加载函数。列表触底事件是onReachBottom(),在小程序页面配置里把这个监听开启就行。

数据返回的格式我定义为{ list: [...], total: xx, hasMore: true },前端根据 hasMore 判断是否还有更多数据。没有更多数据了,就显示“没有更多了”的提示;有更多数据就 loading 提示后请求下一页。这里有个细节:请求新一页数据时,要用数组拼接而不是覆盖,即this.data.petList = this.data.petList.concat(res.data.list),用 setData 更新时注意小程序对数据长度的性能限制,列表几百条数据以内没问题,超过一千条就要考虑虚拟列表,但宠物领养这种场景基本到不了这个量。

搜索功能出现了,我在后端写的是模糊查询的 SQL,用 LIKE 关键词匹配宠物名称和描述字段。城市筛选用等值查询,种类筛选也用等值查询。这两个条件是可选的,所以我实现的时候用了 MyBatis 的动态 SQL,<where>标签加<if>条件判断,有参数就拼条件,没参数就全量查询。这样前端传参时可以随意组合,搜索场景非常灵活。

4.2 宠物详情与申请流程交互

宠物详情页展示的信息非常丰富:轮播图放多张宠物照片,基本信息区放名称、品种、性别、年龄、健康状态;描述区是发布者写的详细故事;底部操作区固定两个按钮——收藏和申请领养。这里要特别注意操作按钮的权限控制:如果当前宠物是用户自己发布的,底部操作区就不再显示“申请领养”,而是显示“管理申请”入口。这个判断的逻辑我放在后端接口返回的 pet 对象里,字段名叫isOwner,前端根据这个字段的布尔值来控制按钮显示,非常省心。

申请领养的流程我设计成弹窗表单而不是跳转新页面,这样用户体验更连贯:用户点按钮,底部弹出半屏面板,填写申请理由和个人情况说明,然后提交。提交成功后立即刷新宠物详情数据,此时宠物的状态已经变成 2,前端按钮变为“该宠物已被申请”。这里我加了一个体验细节:申请成功后给宠物发布者发送一条模板消息通知,但小程序模板消息的发送条件限制较大,需要用户主动触发且 7 天内有过互动。后来我改成了站内信方式,在系统内部建了一张通知表,发布者登录小程序时在“消息”入口看到申请通知。这个方案更可控,不依赖微信的模板消息规则变动。

收藏功能相对简单,前端点击收藏按钮时发送请求,后端判断数据库里是否已有记录:有则删除取消收藏,没有则插入新记录。这个接口我设计为重复点击自动切换状态,前端无需关心当前是什么状态,一个 toggle 接口搞定。但我建议这类写操作全部要求登录,否则用户辛辛苦苦收藏了一堆宠物,换台手机就全丢了,体验很差。

4.3 我的页面与发布流程

“我的”页面集成了个人信息、我发布的宠物、我的收藏、我的领养申请、管理入口这些模块。其中“我发布的宠物”列表里的每条记录后面要跟着显示当前状态(待审核/展示中/已被申请/已领养/已下架),并且不同状态要用不同颜色标识——这是发布者最关心的信息。

发布宠物流程是一个多字段的表单页面。需要填写的信息有宠物名称、种类、品种、性别、年龄、城市、健康状况描述、绝育状态、疫苗状态,然后上传宠物照片。这里有个表单校验的经验:所有必填字段在前端先做一次校验,后端接口再根据规则二次校验,前端的校验是为了减少无效请求,后端的校验才是真正保证数据完整性的防线。比如年龄字段前端可以用正则校验,但后端一定要对前端传来的所有参数做空的判断和非法值的过滤,千万不要信任前端传过来的数据。

图片上传部分,我使用的是wx.chooseMedia接口(新版 API,wx.chooseImage已被官方废弃)选择图片,然后循环调用wx.uploadFile逐张上传。这里有个性能优化点需要提前考虑:wx.uploadFile每次只能传一张图,如果用户选了 5 张图就发 5 次请求,虽然也能跑通,但体验不够好。我实际采用的是先选择图片,预览确认后一次性全部上传,上传期间显示全屏 loading 遮罩,防止用户重复点击。上传完成后后端返回图片 URL 数组,前端把它们拼接到宠物信息表单里,一起提交发布接口。

发成功后的提示也值得注意。发布成功后不能直接跳回首页——用户可能想继续发布下一只猫或者是查看刚发布的宠物详情,所以发布成功弹窗的按钮文案我设计为“查看详情”和“继续发布”。这个交互细节虽然小,但对用户体验的提升很直接。

5. 常见问题与排查技巧实录

5.1 真机预览时的接口调试问题

做过小程序开发的朋友都懂,开发工具里写接口一点问题没有,鼠标一点就能正常请求,但一上真机预览就各种问题。最常见的就是域名白名单限制:微信小程序正式环境要求所有请求域名必须是 HTTPS 且通过 ICP 备案,开发阶段虽然可以在开发者工具里关闭“校验合法域名”,但手机预览时这个开关不生效。我的解决方法是在开发阶段域名设置里勾选“不校验合法域名、TLS 版本以及 HTTPS 证书”,同时用内网穿透工具把本地 Spring Boot 服务的地址映射成一个公网 HTTPS 地址,手机预览时就可以正常调试了。

到了正式上线阶段,必须把后端服务的域名做好 ICP 备案和 HTTPS 证书配置,然后在微信公众平台配置 request 合法域名、uploadFile 合法域名和 downloadFile 合法域名。这个配置经常被忽略——只配了 request 域名忘配 uploadFile 域名,结果列表正常加载但图片上传报错,提示信息有时候还特别不明确。我第一次上线时就踩过这个坑,排查了整整半天,最后发现是 uploadFile 域名没加。

接口响应慢的问题也分享一下排查思路。用户在小程序里打开首页,如果等待超过 3 秒基本就会流失。我第一次部署上线时首页加载速度非常差,用微信开发者工具的 Network 面板一看,原来是首页同时请求了宠物列表、轮播图配置、城市热门标签三个接口,而且每个接口都要查数据库。后来我把三个接口合并成一个首页聚合接口,一次请求返回所有数据,加载时间从 2.8 秒降到了 800 毫秒。小程序首页这种重流量页面,接口聚合是提升用户体验非常直接的手段。

5.2 数据库连接与事务配置

Spring Boot 连接 MySQL 时,我早期踩过一个非常隐蔽的坑:连接超时导致接口偶发报错。原因是 MySQL 默认的连接空闲超时时间是 8 小时,而连接池里的连接如果不做有效检查,超过 8 小时后 MySQL 服务端会主动断开这些连接,客户端这边还在傻傻地用旧连接发请求,自然报错。解决方式是配置 HikariCP 连接池的两个参数:connection-test-query: SELECT 1和max-lifetime: 1800000(30 分钟),让连接池定期检查连接有效性,并设置连接最大存活时间低于 MySQL 的超时阈值。这个配置看起来不起眼,但对线上稳定性影响非常大。

事务管理方面,我的建议是:凡涉及到多表数据变更的操作都必须加@Transactional注解。比如领养申请通过时,要做两个操作——更新宠物状态和批量拒绝其他申请,这两个操作必须在一个事务里,要么都成功要么都失败。不加事务的情况下如果中途报错,会导致宠物状态已更新但其他申请没有拒绝,数据就错了。Spring Boot 使用@Transactional的默认机制足够了,注意自调用的情况——类内部方法互相调用时事务会失效,因为 Spring 的 AOP 代理只在外部调用时生效。解决办法是把事务操作放到独立的 Service 组件里,或者用AopContext.currentProxy()获取代理对象再调用。

5.3 微信审核被拒的常见原因

小程序提交微信官方审核时,最容易被拒的就是类目和内容合规性问题。宠物领养类小程序建议选择“生活服务 > 宠物”类目,如果涉及领养信息发布,还需要持有相应资质。我提交审核时第一次被拒就是因为页面里出现了“免费领养”的字样,微信审核人员认为涉及领养收费的敏感内容。后来把所有涉及费用相关的文案全部调整,强调“公益领养”的平台定位,第二次就通过了。这里建议准备上线的小伙伴提前熟悉微信平台的运营规范,拿不准的时候多搜索同品类的小程序是怎么做的。

另外一个小程序特有的问题:代码包大小限制。微信小程序主包大小限制 2MB,超过就无法上传。如果项目引入了较大的 UI 库或者图片资源,很容易超限。解决方式:使用分包加载机制,把非核心页面放在分包里,主包只保留核心入口和公共资源。我在这个项目中把“我的申请记录”“修改个人资料”等低频页面放到了分包里,主包体积降到了 1.2MB 左右,非常稳妥。

5.4 状态机混乱的数据修复

开发过程中我经历过一次宠物状态数据混乱的问题,好在发生在测试阶段。当时是多个用户同时测试同一个宠物的领养申请功能,由于代码里并发控制没做好,出现了一个宠物被两个人同时申请成功的状况。发现后我先写了一个 SQL 脚本把所有被申请的宠物状态重置为 1(展示中),再手动删除所有领养申请记录,最后重新测试通过了之后才开始对外开放。

这次问题让我总结出一个教训:凡是状态机有流转的业务,一定要在代码注释和接口文档里把状态的流转路径写清楚。我在项目的 README 里专门画了一个表格,列出所有可能的合法流转路径:1 可以到 2、3、4,0 只能到 1 或者 4,2 只能到 3,等等。非法流转就直接抛业务异常。后面再多人测试也没有出过同样的数据错乱问题。

6. 部署上线与持续迭代建议

6.1 服务器部署与环境配置

项目顺利开发完成后,部署上线就是把 Spring Boot 打好 jar 包,扔到服务器上跑起来。这里我建议用 Docker 容器化部署,因为环境一致性好,迁移时不用重复配 Java 环境。我的部署方案是:服务器上用 Docker 跑 MySQL 容器和 Spring Boot 应用容器,用 Nginx 做反向代理,把 HTTPS 请求转发到 Spring Boot 端口。这套方案的好处是每个组件独立管理,升级单独组件不影响其他服务。

配置文件管理有一个建议:Spring Boot 的 application.yml 里包含数据库密码、JWT 密钥、对象存储密钥这些敏感信息,我建议用环境变量注入的方式而非写死在配置文件里。做法是在 application.yml 里写${DB_PASSWORD}这种占位符,然后在 Docker 启动时通过-e参数传入。这样即使代码仓库泄露,密钥也不会跟着泄露。

6.2 后续迭代方向

宠物领养系统的核心功能做完了,但离一个好用的产品还有距离。我规划了三个迭代方向。第一个是社区模块,让领养成功的用户在社区发帖晒宠物,不仅是对送养人的一种反馈,也是潜在领养者了解宠物性格的渠道——很多救助人领养前都希望看到这只宠物在新家的生活状态,这是一个很强的信任需求。第二个是地图找宠功能,在地图上展示用户所在城市各个救助站和宠物所在的坐标,用户打开地图就能看到附近的猫狗,这对同城领养场景非常友好。第三个是回访机制,系统在领养成功后的第 7 天、第 30 天自动生成回访任务,推送给送养人记录宠物的适应情况,这个功能是宠物领养和电商交易的重大区别——领养不是交易完成就结束,而是责任的开始。

我个人的体会是,做一个宠物领养系统,技术上并没有多难,真正的难点在于把“责任”和“信任”这两个词融进产品设计里。每一只被送养的宠物背后都是一个真实的故事,系统能不能帮助它们找到靠谱的家,取决于信息是否透明、流程是否扎实、审核是否严格。这也是这个项目最让我感慨的地方——代码写的是一行行的逻辑,撑起来的却是生命和希望。希望这篇文章能给想要做类似项目的朋友一些启发,少走几步弯路。

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

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

立即咨询