1. 项目概述与核心场景分析
做毕业设计这几年我看了大量 Spring Boot 项目,宠物成长记录平台算是生命力比较旺盛的一类选题。它不像电商系统那么烂大街,又比纯粹的图书管理有温度,数据模型上还保留了一定的复杂度,答辩时既有技术点可讲,又有业务故事可以聊。你拿到这个题目,千万别只把它当成“增删改查”来应付,得从真实的养宠需求出发去理解它,否则做出来的东西就是一套空壳。
宠物成长记录平台本质上解决的是两个问题。第一,宠物的健康数据太分散:疫苗本在抽屉里、驱虫日期记在手机备忘录里、体重变化散落在聊天记录里,没有一个统一的地方去沉淀这些信息。第二,养宠人之间有展示与交流的需求:今天我家猫学会了新技能,体重涨了 0.3 公斤,想晒一下又觉得发朋友圈太频繁。把这些需求落到系统里,就形成了三大核心模块:宠物档案管理、成长记录管理、社区分享互动。再往上加,就是围绕健康维度的体重曲线图、疫苗接种提醒、驱虫周期提醒,这些功能技术上不复杂,但展示出来很好看,答辩效果也加分。
从技术角度看,这个项目选 Spring Boot 作为基底是非常合理的。Spring Boot 天然适合这类中小型管理系统,起步快、生态成熟、资料多,你遇到的 90% 的报错都能在社区找到答案。整套系统的技术栈覆盖了 Java 后端开发、关系型数据库设计、接口开发、前端交互这几个核心环节,作为本科毕业设计来说,广度是够的。如果预算允许,小程序端用原生或者 uni-app 写一套,那方案就更加完整了,能同时覆盖移动端和 PC 端两个场景,答辩时的演示效果会更好。
这篇内容我不打算只给你看功能列表,而是把从零到一的设计思路、表结构细节、核心代码逻辑、部署方式,以及最容易翻车的坑都串起来讲。代码方面我会给出关键片段的实现思路,重点放在“为什么这样做”和“这一步的坑在哪”上,而不是长篇大论地贴代码。
2. 技术选型与实际方案取舍
2.1 后端框架与核心依赖选型
后端骨架用 Spring Boot 2.7.x,不用最新的 3.x。原因很简单:3.x 基于 Jakarta EE,很多网上教程和毕业设计模板还在用 javax 命名空间,你一旦遇到问题去查资料,会发现大量代码根本跑不起来。毕业设计求的是稳,不是追新,Spring Boot 2.7 已经非常成熟,等毕业工作后再去追 3.x 也不迟。
ORM 框架我推荐 MyBatis Plus,不用 Spring Data JPA,也不用手写 MyBatis 的 XML。MyBatis Plus 的优势在于:单表 CRUD 根本不用写 SQL,代码生成器直接生成实体类、Mapper、Service、Controller 四层代码,一周之内把后端骨架拉起来完全可行。而且它的 LambdaQueryWrapper 写条件查询非常顺手,比如“查一下这只宠物所有体重大于 10kg 的记录”,一行代码就搞定。
数据库选 MySQL 8.0,这没什么好说的,市面上主流配的就是它。Redis 可视情况加,如果只是为了做登录 Token 缓存,直接用 JWT + 拦截器就行,不引入 Redis 反而少一套部署依赖。等答辩时如果老师问“为什么没用 Redis”,你完全可以答:“本系统的用户量级处于中小规模,JWT 无状态认证方案更轻量,避免了额外中间件的运维复杂度,后续若需要横向扩展,可以平滑地引入 Redis 做 Token 黑名单与热点数据缓存”,这个回答既诚实又体现思考。
前端的 PC 端方案用 Vue 2 + Element UI。Vue 2 虽然官方已停止维护,但毕业设计里它依然是主流选择,因为资料最多、组件最全、踩坑记录最丰富。如果你是前端基础薄弱的那种“后端选手”,直接用 Thymeleaf 做服务端渲染也不是不行,但那会比较难做出好看的界面。我的建议是:既然做毕业设计,前后端分离的经验一定要有,以后找工作这也是必问的。
追加一个小程序端的话,原生小程序或者 uni-app 都可以。uni-app 的好处是一套代码可以同时编译到微信小程序和 H5,而且它的语法接近 Vue,技术栈上是顺滑过渡的。唯一的坑是社区组件质量参差不齐,别乱下依赖,核心功能尽量自己写。
2.2 系统角色与权限设计
这个平台的用户角色不用搞得很复杂,两个角色足够了:普通用户和管理员。注意,宠物是挂在某个用户名下的,不是独立的用户实体。一个人可以有多只宠物,每只宠物有独立的成长档案。
管理端的权限用拦截器 + 注解实现就行,不需要引入 Spring Security 全家桶。自研一套简单的 JWT 认证流程,代码量不过百行左右,但对 JWT 生成、解析、鉴权的理解会非常深入。用户端和管理端用不同的接口前缀区分,比如/api/user/**与/api/admin/**,在拦截器里对路径做匹配判断,方法上再用自定义注解@RequireAdmin做细粒度控制,这小手段简单实用,还能在答辩时讲出设计意图。
我给项目起名的时候,不用太纠结,就叫pet-record-platform之类的就行。包名结构建议这样组织:
com.pet.platform ├── common │ ├── config │ ├── exception │ ├── result │ └── utils ├── controller ├── service ├── mapper ├── entity └── dtocommon 里放统一返回结果、全局异常处理器、JWT 工具类,entity 放数据库实体,dto 放前端交互的参数对象。注意 entity 和 dto 一定要分开,不要把数据库字段直接暴露给前端,比如创建时间这类字段不应该让前端提交。
3. 数据库设计与核心功能模块拆解
3.1 核心数据表设计与字段规划
数据库设计是整个项目的灵魂,表设计得好,后面写代码如行云流水;表设计得烂,后期每加一个功能都是噩梦。我按我的实际项目经验给你列一套可直接用的表结构。
第一张表是用户表user。字段规划为:主键 id、用户名 username、密码 password(BCrypt 加密存储)、昵称 nickname、头像 avatar、手机号 phone、创建时间 create_time。用户名要建唯一索引,登录时就是用它查用户。密码千万别存明文,用 Spring Security 自带的 BCryptPasswordEncoder,或者引入 hutool 工具类里的 BCrypt 实现,一行代码完成加密和校验,这个点是答辩时容易被老师问到的。
第二张表是宠物表pet。字段包括:主键 id、用户 id(user_id)、宠物名称 pet_name、宠物种类 pet_type(猫、狗、兔子、仓鼠等)、品种 breed、性别 gender、出生日期 birth_date、体重 weight、是否绝育 is_neutered、头像 pet_avatar、创建时间 create_time。user_id 建普通索引,因为绝大多数查询都是“根据用户查这个人的所有宠物”。疫苗信息和驱虫信息我建议不要单独建表,而是挂在宠物表或者成长记录表里,用一个类型字段做区分,具体方案下面第 3.2 小节会细讲。
第三张表是成长记录表growth_record,这是系统的核心表。字段规划为:主键 id、宠物 id(pet_id)、记录类型 record_type(1=日常动态,2=疫苗接种,3=驱虫记录,4=体重记录,5=医疗记录)、事件名称 event_name、记录内容 record_content、发生时间 event_date、体重值 weight(仅体重记录时才有值)、相关照片 photo_urls、创建时间 create_time。每条记录通过 record_type 来区分是“今天洗了澡”还是“打了狂犬疫苗”。
这种设计的好处是:你只需要一张表就能覆盖所有类型的成长轨迹,查询时按 record_type 过滤即可。缺点是不同类型的记录字段冗余,比如体重记录时照片可能没有,医疗记录时体重可能为空,但这在中小系统里完全可以接受。如果你用多张表分开存储,代码量和管理成本都会翻倍,毕业设计阶段不划算。
3.2 疫苗接种与提醒功能的设计技巧
疫苗提醒功能是宠物平台的特色功能,也是答辩时的加分点。我的设计思路是:在成长记录表中插入一条记录,record_type=2(疫苗),event_name 存疫苗名称比如“猫三联第一针”,event_date 存接种日期。数据库里需要记录这种疫苗下一次该什么时候接种。疫苗间隔天数不是一个固定的值,狂犬疫苗一年一次,猫三联的第一针和第二针间隔是 21 到 28 天。
我的处理方法是加一个字段next_event_date(下次接种日期)。用户在录入疫苗记录时,前端会根据用户选择的疫苗种类填充默认的间隔天数,比如选“狂犬疫苗”默认填充 365 天,选“猫三联第一针”默认填充 21 天,后端计算好下一次日期存入next_event_date。然后跑一个定时任务,每天扫描一次:如果next_event_date在未来 7 天内,就生成一条待办提醒,推送给用户。
定时任务用 Spring Boot 自带的@Scheduled注解就能搞定,不需要引入 Quartz。推送提醒这里有个方案问题:如果做短信或微信公众号通知,都需要企业资质,个人开发者拿不到。最务实的方式是系统内提醒 + 小程序订阅消息提醒。小程序订阅消息的坑在于:用户必须主动点击授权一次,你才能给他发一条,属于一次性订阅,用户不点就没法推送。这个限制可以在页面里做一个“开启疫苗到期提醒”的按钮,引导用户触发订阅,这样体验上也能说得过去。
3.3 体重曲线与数据可视化实现思路
体重变化曲线是宠物成长记录里最直观、最有展示效果的功能。每当用户录一条体重记录,growth_record表里就新增一条 record_type=4 的记录,weight 字段存当时的体重。查询时直接把某只宠物的所有体重记录按 event_date 升序查出来,传给前端。
前端的图表我推荐 ECharts,这是国产的可视化库,文档齐全,对中文支持好。用折线图展示体重变化,横轴的刻度要处理一下:如果宠物的年龄跨度大,比如一只 3 岁的猫从出生到现在的体重数据,时间跨度 1000 多天,你直接用日期做 X 轴,点会非常密。更好的方案是 X 轴显示“月龄”或者“周龄”,也就是把 event_date 和 birth_date 做差,换算成“第 3 个月”“第 7 个月”这样的刻度,看起来就更专业,也更符合养宠人看成长曲线的习惯。
图表的类型不限于折线图,还可以加一个宠物年龄半径图、疫苗接种进度的环形图、驱虫记录的时间轴图。展示时拼一个“数据看板”页面,四个图表排布在一起,观感比表格强太多了,答辩时老师打开这个页面的第一反应就是“这项目做得完整”。
4. 实战实现:从后端到前端的完整落地过程
4.1 后端分层架构与统一响应封装
后端严格按 Controller、Service、Mapper 三层来做,不让任何一层越权。Controller 只做参数接收和结果返回,Service 层处理业务逻辑,Mapper 层只用 MyBatis Plus 的 BaseMapper 做单表操作。
统一返回结果类是必须的,不然前后端对接时每个接口的返回格式都不一样,前端处理起来会崩溃。我习惯这样定义:
{ "code": 200, "message": "操作成功", "data": { } }后端的Result类用泛型实现,提供Result.ok(data)和Result.error(code, message)两个静态工厂方法。业务异常抛出去之后,由全局异常处理器统一捕获并转换成 Result 格式返回,Controller 里就不需要反复写 try-catch 了。全局异常处理器继承@RestControllerAdvice,里面处理三个层次的异常:业务异常、参数校验异常、兜底异常。
参数校验这块,我的建议是在 DTO 上直接加@NotBlank、@NotNull等 JSR-303 注解,再在 Controller 方法的参数前加@Validated。比如宠物新增接口的 DTO,宠物名称不能为空、宠物类型不能为空,这些用注解声明式搞定,比在 Service 里手写 if 判断干净得多。
4.2 基于 JWT 的认证与权限控制实践
JWT 的实现思路如下:用户登录成功后,后端用用户的 id 和用户名生成一个 Token,设置过期时间(我通常设为 7 天),返回给前端。前端存储 Token(小程序里存 storage,Web 端存 localStorage),每次请求在 Header 里带上Authorization: Bearer <token>。
后端用一个拦截器(HandlerInterceptor)拦下所有/api/**的请求,从请求头里取出 Token,解析成功后把用户信息放进 ThreadLocal。ThreadLocal 在请求结束时要记得 remove,否则会有内存泄漏风险,这一点很多同学会忽略。拦截器里同时做管理员路径的校验:如果请求路径以/api/admin开头,再从 ThreadLocal 中拿出的用户判断角色是否为管理员,不是就直接返回 403。用两个if就能完成这个控制,你没必要引入 Shiro 或者 Spring Security,它们配置起来既重又繁琐。
登录接口的密码比对,我前端存的是 BCrypt 加密后的密文,登录时用加密器校验原文与密文。密码加密这个点十有八九会被答辩老师追问,你把 BCrypt 的加盐逻辑说清楚就能过关:BCrypt 每次生成的盐都不同,所以同一个密码两次加密出来的密文不一样,这正是它能抵御彩虹表攻击的原因。
4.3 前端核心页面:用户端记录与个人看板
用户端的核心页面一共四个:首页、宠物档案、记录列表、个人中心。
首页放的是当前用户所有宠物的快捷入口,每只宠物显示头像、名字、生日、最近一次体重。这个页面的难点在于怎么组织“最近一次体重”的查询:如果你每查一只宠物就写一次 SQL,宠物多的时候会非常慢。我直接用一句 SQL 关联子查询,查出宠物列表的同时,把growth_record表里最新一条体重记录带出来。这个 SQL 在 MyBatis Plus 里不好用 LambdaQueryWrapper 表达,得手写 XML,但就这一个查询,手写完全没问题。
记录列表页是典型的 CRUD 页面,重点要设计好筛选器。顶部放一个下拉框,按记录类型筛选(全部、日常、疫苗、驱虫、体重、医疗),再加一个时间范围选择器。按宠物切换时,记录列表要联动刷新。
个人看板页是整套系统的展示担当。上面放了 ECharts 的四个图表,下面放最近的 5 条待办提醒。这个页面的数据无需实时推送那么复杂,进入页面时请求一次即可,数据量不大,后端也不需要考虑缓存。
4.4 小程序端的差异化设计与实现要点
如果你要写小程序端,页面结构比 Web 端更简洁:首页换成“宠物首页”大卡片,里面直接嵌入体重曲线和最近三条动态;记录录入页做一个大按钮悬浮在底部,点击后弹出操作菜单,选择记录类型,进入不同的表单页。这个交互和养宠 App 一致,比堆功能列表更合用户习惯。
小程序的登录用微信的wx.login拿 code,发给后端,后端再向微信服务器换取 openid。这里的关键设计是:首次登录自动注册,第二次登录就是老用户直接放行。你需要在user表里存一个 openid 字段,并建唯一索引。会话保持依然用 JWT,不用微信的 session_key,因为小程序端的 session_key 有效期短而且管理麻烦。
图片上传在小程序端注意一个问题:wx.chooseMedia选择的临时文件路径,不能直接拿去展示,必须通过wx.uploadFile上传到服务器后保存服务器返回的 URL。后端存储用本地磁盘还是 OSS,这个建议本地磁盘就够了。在配置文件里指明upload.path,启动时会自动创建目录。文件名重命名规则我用的是yyyyMMddHHmmss加随机数,避免中文文件名和重名导致的乱码问题。
5. 部署上线与常见问题排查实录
5.1 本地运行与打包部署的全流程
本地运行阶段,注意几个基础配置:MySQL 的serverTimezone参数要设置为Asia/Shanghai,否则日期写入数据库会差 8 个小时。Spring Boot 的application.yml里配置数据源时,加一行spring.jackson.date-format来统一 JSON 的日期格式,否则前端拿到的可能是时间戳而不是可读的字符串。
打包部署我用的是阿里云轻量应用服务器 2 核 4G 配置,这个配置跑 Spring Boot + MySQL 完全够用。部署流程是:本地mvn clean package打成 jar 包,通过scp上传到服务器,然后用nohup java -jar pet-platform.jar > log.log 2>&1 &启动。Java 版本一定要与本地一致,我踩过本地 JDK 17、服务器 JDK 8 导致 Jackson 报错的坑,这个低级失误特别浪费时间。
前端打包后,在服务器上用 Nginx 托管静态文件,配置一个反向代理,把/api开头的请求转发到本机 8080 端口。这样访问 80 端口就是前端页面,访问 8080 的是后端接口,前后端域名一致,还规避了跨域问题。
这里有一个经验之谈:跨域问题,开发环境下用后端的 CORS 配置放行本地端口,生产环境下一定要用 Nginx 反向代理来规避跨域,而不是在后端继续开放跨域。生产环境开放跨域相当于门户大开,并不安全。
5.2 开发期最常见的六个坑与排查方法
第一个坑是 JWT Token 在过滤器里解析失败但是请求就断了,前端拿不到具体的错误提示。原因是拦截器里的异常被 Spring 的@RestControllerAdvice拦不到,因为拦截器在进入 Controller 之前就抛出异常了。解决办法是在拦截器里自己 catch 异常,然后直接把错误信息写入 Response。
第二个坑是时间类型在前端显示不对。明明数据库存的是 2025-04-01 18:00:00,接口返回给前端变成了 2025-04-01 10:00:00,差 8 小时。排查最终确认是 Jackson 序列化时用了 UTC 时区,解决办法是在配置里设置spring.jackson.time-zone=GMT+8。
第三个坑是 MyBatis Plus 的自动填充不生效。create_time字段我设置了@TableField(fill = FieldFill.INSERT),但每次插入都是空值。原因是实体类上少了@TableName注解的autoResultMap?不,真正的原因是实体类的字段名和数据库列名不一致,MyBatis Plus 默认开启驼峰转下划线,createTime会映射到create_time,但如果实体类里写的是createTime而数据库列是create_time,在 UPDATE 的时候会有问题,排查方法就是打开 SQL 日志看输出。
第四个坑是图片上传后无法访问。本地开发好好的,部署到服务器就404。原因在于没有配置静态资源映射。Spring Boot 默认不会把本地磁盘上的upload目录映射为静态资源,你需要写一个配置类来做资源映射,或者走/api/file/{filename}的接口来下发图片。
第五个坑是小程序手机号获取。很多同学想着买短信服务获取用户手机号,后来发现小程序有按钮授权获取手机号的机制,原理是前端button设置open-type="getPhoneNumber",然后拿动态 code 给后端,后端调用微信接口换取手机号。不需要企业资质,个人开发者也能用。
第六个坑是定时任务在集群环境重复执行。你本地只有一份实例没问题,但将来部署多实例时,确实会导致重复发送提醒。我在项目里用一个乐观锁的策略来规避:任务执行时先取一条待处理记录,更新它的状态字段(CAS 操作),成功了才继续处理,失败了就跳过。
5.3 答辩环节的高频提问与回答思路
答辩时老师大概率会问这几个方向的问题,提前准备一下就没压力。
第一个问题:为什么用 Spring Boot,而不用 SSM?回答思路分三点:Spring Boot 简化了配置,内置 Tomcat,解决了 SSM 大量 XML 配置的问题;它的自动配置机制能让项目快速启动;该框架生态成熟、性能表现稳定,满足当前业务需求。
第二个问题:项目中的难点是什么?这一问最好回答下面两个点之一,不要笼统说“增加删改”。一个点是可以讲体重曲线的数据查询优化,即如何从 R 树或者时间序列的角度;另一个点是可以讲疫苗提醒的定时任务与小程序订阅消息的结合,如何确保用户能收到通知。具体讲述时重点突出你遇到的真实困难、排查思路和最终方案。
第三个问题:项目有哪些可以改进的地方?这个别傻傻地只说“没有”,要说至少两个具体的改进方向。比如引入 Redis 做热点数据缓存,降低数据库压力;再比如引入消息队列 MQ 做异步推送,彻底解耦通知发送。这种回答既体现出你有全局意识,又不容易被老师顺着追问到不懂的细节。
第四个问题:你这个系统的表和表之间是什么关系?你就画出宠物表、成长记录表、用户表,把外键关系和查询路径讲清楚即可。讲的时候顺带说一句“我在设计时没有使用数据库物理外键,而是通过逻辑外键来维护关系,因为物理外键会带来级联操作和迁移的复杂度”,这一句是一箭双雕,既展示你有思考,又解释了为什么没建外键。
6. 项目扩展与长期迭代思路
做这个项目如果仅限于交付,那确实有点可惜。它完全可以继续发展为一个小型 SaaS 服务平台,把视角从单用户工具拉高到多用户协同的家庭场景。
第一个可行性很高的扩展方向是多宠家庭与家庭成员共享。目前的设计里,一只宠物只归属于一个用户,改进方案是引入“家庭组”概念,一个家庭组中有多个成员,宠物挂在家庭组下,组内成员均有权限查看和录入记录。权限控制需要引入一个成员角色字段(管理员、普通成员),对数据隔离是一个不小的挑战,但也是真实软件设计里非常有价值的场景。
第二个扩展方向是物联网硬件对接。目前数据靠手动录入,如果对接智能称重设备或智能项圈等硬件,每次称重后自动同步到平台,这就从工具升级为数据中台了。接口层面无非是给设备一个专用 Token 并限制其只能写入固定类型的数据,但对于整个平台的想象空间是完全不同的。
第三个扩展方向是社区内容的深化。现在只有宠物动态分享,后面可以加话题聚合、同城宠物活动组织、丢失宠物协助扩散等功能。这些功能会引入内容审核机制以保证安全,社交关系链也会让技术方案变得复杂,比如关注关系、信息流推荐等。
单独一个毕业设计,能做到第一点和第二点的雏形,就已经非常出色了。我在实际做项目的过程中感受到,真正拉开差距的不是代码写得多炫酷,而是对业务需求的理解深度。宠物成长记录平台这个题目,表面上是增删改查,但你把疫苗提醒的周期逻辑理清了,把体重曲线的月龄换算写对了,把多类型记录的状态管理用好了,这个项目的含金量就会超出绝大多数同类毕设。
最后再分享一个关于交付和展示的小技巧:演示的时候,提前准备几组完整的假数据,覆盖一只猫从 2 个月到 1 岁的完整成长过程,至少要有 6 条体重记录、5 次疫苗记录、3 次驱虫记录,加上若干日常动态。图片素材可以去免费图库下载几张可爱的宠物照片。这样演示的时候,图表曲线是连续漂亮的,时间轴是饱满的,老师第一感觉就是这个项目是真的被认真使用过的。实际体验非常重要——毕竟,一个空空荡荡的系统,和一套数据丰满的系统,给人的信任感是完全不同的。