ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

基于Spring Boot和微信小程序的宠物领养系统开发全解析

基于Spring Boot和微信小程序的宠物领养系统开发全解析 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) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;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) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;这里有个实操经验images 字段用逗号分隔存多张图片的 URL并不是最规范的设计——规范做法是建一张宠物图片表。但实际项目里宠物图片一般就 3 到 5 张查询时一次拿全部链接比再发一次查询效率更高而且后期如果要做图片管理改造成本也不高。项目初期我建议就用这种“适度反规范化”的设计等用户量和数据量真正上来了再考虑拆表。3. 后端 Spring Boot 接口实现3.1 会话管理与登录态维护微信小程序的登录机制和传统网页登录完全不一样这个坑我见过太多人踩。小程序端调用 wx.login 获取一个临时 code这个 code 一次性有效且只有 5 分钟有效期后端拿着这个 code 调用微信的 code2Session 接口换取 openid。拿到 openid 后你就拿到了用户的唯一身份标识。我在后端写了一个AuthController处理登录请求核心逻辑就是接收前端传来的 code调用微信接口换取 openid查数据库判断该用户是否已存在不存在就自动注册一个新用户然后生成一个自定义 token 返回给前端。这个 token 我采用的是 JWTJSON 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 的动态 SQLwhere标签加if条件判断有参数就拼条件没参数就全量查询。这样前端传参时可以随意组合搜索场景非常灵活。4.2 宠物详情与申请流程交互宠物详情页展示的信息非常丰富轮播图放多张宠物照片基本信息区放名称、品种、性别、年龄、健康状态描述区是发布者写的详细故事底部操作区固定两个按钮——收藏和申请领养。这里要特别注意操作按钮的权限控制如果当前宠物是用户自己发布的底部操作区就不再显示“申请领养”而是显示“管理申请”入口。这个判断的逻辑我放在后端接口返回的 pet 对象里字段名叫isOwner前端根据这个字段的布尔值来控制按钮显示非常省心。申请领养的流程我设计成弹窗表单而不是跳转新页面这样用户体验更连贯用户点按钮底部弹出半屏面板填写申请理由和个人情况说明然后提交。提交成功后立即刷新宠物详情数据此时宠物的状态已经变成 2前端按钮变为“该宠物已被申请”。这里我加了一个体验细节申请成功后给宠物发布者发送一条模板消息通知但小程序模板消息的发送条件限制较大需要用户主动触发且 7 天内有过互动。后来我改成了站内信方式在系统内部建了一张通知表发布者登录小程序时在“消息”入口看到申请通知。这个方案更可控不依赖微信的模板消息规则变动。收藏功能相对简单前端点击收藏按钮时发送请求后端判断数据库里是否已有记录有则删除取消收藏没有则插入新记录。这个接口我设计为重复点击自动切换状态前端无需关心当前是什么状态一个 toggle 接口搞定。但我建议这类写操作全部要求登录否则用户辛辛苦苦收藏了一堆宠物换台手机就全丢了体验很差。4.3 我的页面与发布流程“我的”页面集成了个人信息、我发布的宠物、我的收藏、我的领养申请、管理入口这些模块。其中“我发布的宠物”列表里的每条记录后面要跟着显示当前状态待审核/展示中/已被申请/已领养/已下架并且不同状态要用不同颜色标识——这是发布者最关心的信息。发布宠物流程是一个多字段的表单页面。需要填写的信息有宠物名称、种类、品种、性别、年龄、城市、健康状况描述、绝育状态、疫苗状态然后上传宠物照片。这里有个表单校验的经验所有必填字段在前端先做一次校验后端接口再根据规则二次校验前端的校验是为了减少无效请求后端的校验才是真正保证数据完整性的防线。比如年龄字段前端可以用正则校验但后端一定要对前端传来的所有参数做空的判断和非法值的过滤千万不要信任前端传过来的数据。图片上传部分我使用的是wx.chooseMedia接口新版 APIwx.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: 180000030 分钟让连接池定期检查连接有效性并设置连接最大存活时间低于 MySQL 的超时阈值。这个配置看起来不起眼但对线上稳定性影响非常大。事务管理方面我的建议是凡涉及到多表数据变更的操作都必须加Transactional注解。比如领养申请通过时要做两个操作——更新宠物状态和批量拒绝其他申请这两个操作必须在一个事务里要么都成功要么都失败。不加事务的情况下如果中途报错会导致宠物状态已更新但其他申请没有拒绝数据就错了。Spring Boot 使用Transactional的默认机制足够了注意自调用的情况——类内部方法互相调用时事务会失效因为 Spring 的 AOP 代理只在外部调用时生效。解决办法是把事务操作放到独立的 Service 组件里或者用AopContext.currentProxy()获取代理对象再调用。5.3 微信审核被拒的常见原因小程序提交微信官方审核时最容易被拒的就是类目和内容合规性问题。宠物领养类小程序建议选择“生活服务 宠物”类目如果涉及领养信息发布还需要持有相应资质。我提交审核时第一次被拒就是因为页面里出现了“免费领养”的字样微信审核人员认为涉及领养收费的敏感内容。后来把所有涉及费用相关的文案全部调整强调“公益领养”的平台定位第二次就通过了。这里建议准备上线的小伙伴提前熟悉微信平台的运营规范拿不准的时候多搜索同品类的小程序是怎么做的。另外一个小程序特有的问题代码包大小限制。微信小程序主包大小限制 2MB超过就无法上传。如果项目引入了较大的 UI 库或者图片资源很容易超限。解决方式使用分包加载机制把非核心页面放在分包里主包只保留核心入口和公共资源。我在这个项目中把“我的申请记录”“修改个人资料”等低频页面放到了分包里主包体积降到了 1.2MB 左右非常稳妥。5.4 状态机混乱的数据修复开发过程中我经历过一次宠物状态数据混乱的问题好在发生在测试阶段。当时是多个用户同时测试同一个宠物的领养申请功能由于代码里并发控制没做好出现了一个宠物被两个人同时申请成功的状况。发现后我先写了一个 SQL 脚本把所有被申请的宠物状态重置为 1展示中再手动删除所有领养申请记录最后重新测试通过了之后才开始对外开放。这次问题让我总结出一个教训凡是状态机有流转的业务一定要在代码注释和接口文档里把状态的流转路径写清楚。我在项目的 README 里专门画了一个表格列出所有可能的合法流转路径1 可以到 2、3、40 只能到 1 或者 42 只能到 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 天自动生成回访任务推送给送养人记录宠物的适应情况这个功能是宠物领养和电商交易的重大区别——领养不是交易完成就结束而是责任的开始。我个人的体会是做一个宠物领养系统技术上并没有多难真正的难点在于把“责任”和“信任”这两个词融进产品设计里。每一只被送养的宠物背后都是一个真实的故事系统能不能帮助它们找到靠谱的家取决于信息是否透明、流程是否扎实、审核是否严格。这也是这个项目最让我感慨的地方——代码写的是一行行的逻辑撑起来的却是生命和希望。希望这篇文章能给想要做类似项目的朋友一些启发少走几步弯路。
返回列表