ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

宠物健康顾问系统实战:SpringBoot2+Vue3+MyBatis-Plus全栈解析

宠物健康顾问系统实战:SpringBoot2+Vue3+MyBatis-Plus全栈解析 说实话我最初看到“宠物健康顾问系统”这个标题第一反应是这不就是一个换了层业务皮的管理系统么。但真正把源码和配套文档过了一遍之后我发现这个项目的价值远不止“又一个SpringBoot2Vue3的CRUD”。它把SpringBoot2 Vue3 MyBatis-Plus MySQL8.0这套当下非常主流的技术栈扎扎实实地落到了一套有完整业务闭环的场景里宠物档案、健康评估、疫苗提醒、在线咨询、预约管理。对正在学Java Web、准备毕业设计、或者想找一套“能跑通前后端又能讲清楚业务”的参考项目的朋友来说这是一个可以直接抄作业的完整样本。整篇文章我会从业务拆解、技术选型、核心模块落地、环境搭建避坑这几个维度展开。重点是把我自己在实际运行这类项目时踩过的坑、排查思路和一些常规文档里不会写的细节都放进来。1. 项目整体拆解宠物健康顾问系统到底在解决什么问题1.1 业务模块怎么拆才不虚很多初学者拿到一个项目源码第一件事是急着跑起来第二件事是急着看代码但很少有人先站在“这个系统到底卖给谁、解决什么痛点”的角度去审视结构。宠物健康顾问系统这套业务面向的核心用户是宠物主和健康顾问可以理解为兽医、营养师或客服运营。宠物主需要记录宠物的基础信息和体检数据需要知道宠物最近状态怎么样、疫苗是不是快到期了、要不要约一次顾问咨询。顾问则需要维护宠物的健康档案、回答用户的咨询、处理预约订单。这套业务拆下来之后模块其实非常清晰用户侧注册登录、宠物档案管理、健康记录查询、健康评估结果查看、咨询提问、预约顾问。顾问侧处理咨询、维护健康建议、查看预约排期。管理侧用户管理、宠物信息审核、基础数据配置如疫苗种类、服务机构。我最欣赏这个项目的一点是它的模块粒度没有做“过度设计”。市面上很多管理系统恨不得把每个模块都拆成微服务、每张表都加十个字段结果真正写代码时一半时间在绕弯子。这个项目整体是单体应用前后端分离后端模块按业务功能分包前端按页面和组件拆分复杂度控制得刚刚好。对于想学习一个完整业务链路怎么落地的朋友来说这种“刚好能看清全貌”的复杂度是最合适的。1.2 角色权限与数据流设计这个项目在设计上有三条核心数据流把握住这三条线整个系统的代码脉络就清楚了。第一条是“宠物档案流”用户注册登录后创建宠物档案档案里包含宠物种属、品种、性别、生日、体重等基础信息。第二条是“健康评估流”围绕宠物档案产生体检记录和疫苗接种记录后端根据这些数据计算健康评分生成评估结果。第三条是“服务流”用户基于健康评估结果发起在线咨询或预约顾问承接并回复形成完整的服务闭环。权限控制这块项目采用的是JWT 拦截器的方式没有引入Spring Security那套重武器。三种角色通过token中的角色标识区分后端拦截器统一校验。这个设计我比较认同——对这样一个规模的项目来说自己实现一套轻量鉴权比引入一整套安全框架更可控也更适合拿来学习。如果你希望扩充成更复杂的权限模型比如RBAC加菜单权限表在这个基础上加一张角色菜单关联表就可以平滑升级。1.3 这套源码适合谁拿来用我分别接触过三类用户他们的诉求完全不一样但这个项目都能覆盖。正在准备Java Web相关内容的人把它当成“一份完整的、能讲清楚的案例”。从Controller到Service再到Mapper每一层的职责都很清晰适合对着代码理解分层架构。准备做毕业设计的人会看中它的完整度有清晰的业务故事、有前后端交互、有可视化图表答辩时能讲的东西非常多“宠物健康顾问”这个方向也足够新颖不容易和同学撞车。已经在工作中写到一定阶段的开发者拿到这套代码可以当脚手架把宠物档案和咨询模块改成自己业务领域的客户档案和工单系统省掉最前期搭工程的时间。不管哪类读者我建议先按“跑起来 - 走一遍业务流程 - 改一个模块”这个顺序去使用它只看不跑等于白看。2. 技术选型解析为什么是SpringBoot2 Vue3 MyBatis-Plus MySQL8.02.1 SpringBoot2 MyBatis-Plus后端骨架的现实考量先聊后端。项目用的是SpringBoot2.x而不是SpringBoot3这个选择非常务实。SpringBoot2搭配JDK8是目前生产环境里最普及的组合网上随便搜一个报错信息都有大量的解决方案。SpringBoot3虽然更“新”但强制JDK17起步某些企业内部的中间件兼容性还不稳定。对大多数人来说拿SpringBoot2做这套系统踩坑成本是最低的。持久层选了MyBatis-Plus而不是原生MyBatis或者Spring Data JPA这是效率考量。MyBatis-Plus继承了MyBatis灵活控制SQL的优点所有的复杂查询都可以自己写XML同时把单表CRUD全部内置BaseMapper提供了insert、deleteById、selectPage、updateById等现成方法写代码时90%的单表操作不需要碰SQL。它还支持逻辑删除、字段自动填充、乐观锁插件、分页插件这些都极大减少了样板代码。我见到过有人用MyBatis-Plus把业务写成一串QueryWrapper的链式调用甚至多表关联也试图用Wrapper硬拼这其实不是一种好实践。在这个项目中简单的单表查询用它自带的Wrapper多表聚合则老老实实写在XML里这种“怎么简单怎么来、不要为了用ORM而绕远路”的分寸感才是这个项目背后真正值得学习的地方。2.2 Vue3前端工程化Composition API、Pinia与请求封装前端选择Vue3也符合现在团队的普遍状态。Vue3的Composition API让逻辑复用变得很舒服同一个健康评估模块里的数据请求、状态计算、图表渲染用setup函数组织之后嵌套层级比Vue2时代舒服得多。状态管理这块项目用的是Pinia现在Vue官方推荐的首选方案。相比VuexPinia的API更简单不需要写那么多mutation样板代码天然支持TypeScript组件里用起来非常顺手。它完全可以替代Vuex在中小型项目中的地位。前端工程化上有个很关键的点请求封装。项目里对axios做了统一封装拦截器里统一注入JWT token统一处理HTTP错误码后端返回的非业务状态码比如401、500在前端统一弹出提示。这套封装的思路建议直接整套迁移到自己的项目里不要每次开发都重新写一遍请求层。2.3 MySQL8.0与核心表结构设计MySQL8.0现在的普及度已经非常高了和5.7相比它的字符集默认就是utf8mb4对emoji和特殊字符的支持更加友好窗口函数、CTE表达式这些能力也给业务研发提供了更多可能性。在这套系统里MySQL8.0还扮演了一个重要的角色——JSON字段的应用。比如宠物疫苗接种记录考虑到不同疫苗的批次信息差异很大用常规的关系表去建模会显得很笨重项目在部分扩展信息上使用了JSON字段存储读取的时候用后端解析灵活性和查询性能之间取得了平衡。表结构设计上我拿到代码之后专门翻了一遍挑几个核心表说说。user表和pet表是一对多的关系pet表设计了species、breed、weight、birthday等字段基本上覆盖了宠物档案需要的信息health_record表记录每次体检的体温、体重、心率等关键指标vaccine_record表记录疫苗名称、接种日期和下一针到期日appointment表和consultation表则是把线上预约和在线咨询的交互流程支撑起来。3. 核心业务模块实现细节从表结构到业务代码3.1 宠物档案与健康记录数据模型与落地宠物档案是整个系统的基石没有宠物档案后面的评估、咨询、提醒全都无从谈起。这个模块的表设计看似简单但有两个细节很值得学习。第一个细节是冗余字段的取舍。pet表里存了species和breed两个字段看起来有点重复但实际查询时按物种dog/cat/rabbit做统计报表的场景很多把这两个字段分开存储避免了每次都要去字典表关联查询品种归属。第二个细节是软删除。所有业务表都带了一个deleted字段配合MyBatis-Plus的逻辑删除功能删数据时执行的是update语句而不是delete。这样做的好处是数据可追溯出问题可以快速恢复。逻辑删除的配置也很简单mybatis-plus: global-config: db-config: logic-delete-field: deleted logic-delete-value: 1 logic-not-delete-value: 0建表语句可以参考这个思路CREATE TABLE pet ( id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY, user_id BIGINT NOT NULL COMMENT 所属用户ID, name VARCHAR(50) NOT NULL COMMENT 宠物昵称, species VARCHAR(20) NOT NULL COMMENT 物种dog/cat/rabbit等, breed VARCHAR(50) COMMENT 品种, gender TINYINT COMMENT 0未知 1公 2母, birthday DATE COMMENT 出生日期, weight DECIMAL(5,2) COMMENT 体重(kg), deleted TINYINT DEFAULT 0 COMMENT 逻辑删除, create_time DATETIME DEFAULT CURRENT_TIMESTAMP, update_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, KEY idx_user_id (user_id), KEY idx_species (species) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT宠物档案表;有几个索引细节我要单独提醒一下user_id上建立普通索引是因为所有“我的宠物”列表查询都会命中这个条件species上建立索引是因为按物种筛选统计是后台管理的常见操作。重量字段用DECIMAL而不是DOUBLE避免浮点数误差。3.2 健康评估功能规则要简单逻辑要清楚宠物健康顾问系统里最有业务特色的是健康评估功能。这个模块的核心逻辑是根据宠物的基础信息年龄、体重和最近体检记录体温、心率、体重指数等结合疫苗接种完成度输出一个综合健康评分。评分规则本身就是业务知识的一部分代码实现并不复杂public HealthScore evaluateHealth(Pet pet, HealthRecord latestRecord) { int score 100; // 1. 体重偏差评估体重偏离标准体重超过30%扣20分超过15%扣10分 double idealWeight getIdealWeight(pet); double diff Math.abs(pet.getWeight() - idealWeight) / idealWeight; if (diff 0.30) { score - 20; } else if (diff 0.15) { score - 10; } // 2. 疫苗接种完成度评估有未接种的必打疫苗扣15分 if (!vaccineComplete(pet.getId())) { score - 15; } // 3. 最近体检指标异常扣分 if (latestRecord ! null latestRecord.isAbnormal()) { score - 10; } score Math.max(score, 0); return new HealthScore(score, buildAdvice(score)); }这段逻辑的两个特点值得展开。第一规则是显式的每个扣分项都有注释说明业务原因而不是把规则藏在某个让人看不懂的公式里。第二输出结果除了分数还有一个buildAdvice方法根据分数区间生成对应的健康建议文案比如80分以上是“状态良好继续保持”60到80分是“建议改善饮食结构并增加运动”60分以下是“建议尽快预约顾问做全面检查”。这种设计让一个简单的评分功能变成了“顾问系统”的体现用户看到的不只是一个数字还有可执行的建议。前端展示健康评分时项目用了Element Plus的Progress组件和ECharts图表评分趋势用折线图展示宠物体重变化用柱状图展示。数据的可视化部分对非专业用户来说很重要毕竟让宠物主看懂一张折线图比看懂一堆体检数值容易得多。3.3 在线咨询、预约与提醒怎么把业务串起来在线咨询和预约模块本质上是把宠物主和顾问连接起来的桥梁。用户的诉求是“我家猫最近食欲不振但是又没到需要去医院的严重程度我想先问问顾问”顾问的诉求是“我需要在统一的工作台回复所有咨询并且能管理我的可预约时段”。咨询模块的实现思路是用户创建一条咨询记录内容包括标题、详细描述关联的宠物ID顾问端拉取待处理的咨询列表逐条回复。回复内容存在一个独立的表里或者用JSON字段咨询状态在“待处理 - 已回复 - 已关闭”之间流转。用户端查看回复时能看到完整的对话历史。预约模块则涉及一个关键的时间冲突控制问题同一时段不能让多个用户预约同一位顾问。代码里处理这个问题的标准做法是在插入预约记录时加上顾问ID 开始时间的唯一约束这样即便两个用户同时点击预约数据库层也能拦住冲突数据。如果只是在前端做时段判断后端不做唯一约束并发场景下必然会有数据问题。这个思路在任何涉及“抢购、占座、预约”的系统里都通用。提醒功能相对简单但非常重要系统启动时或者用户登录后查询疫苗记录筛选出下一针到期时间在30天内的记录生成提醒列表。这个功能也可以做成定时任务每天跑一次把即将到期的提醒推送给用户。项目里是放在列表页直接展示这种轻量方案用于学习阶段足够了。如果想升级成推送通知把查询结果接入WebSocket或消息推送服务即可。4. 环境搭建与部署避坑实录4.1 从零搭一套开发环境含MySQL8.0本地/Docker方式拿到源码之后第一步是把环境跑起来。我以Windows / macOS开发机为例按顺序说明。第一步装JDK8配置好JAVA_HOME和PATH。第二步装Maven 3.6以上配置阿里云镜像加速依赖下载。第三步准备MySQL8.0。数据库这步通常有两种方式本地安装的话官网下载MySQL Installer一路下一步。这里有个高概率踩坑点MySQL8.0默认认证插件是caching_sha2_password如果你的JDBC驱动版本太老5.x连接时会报Unable to load authentication plugin错误。解决方法是确保使用mysql-connector-java 8.0.x及以上的驱动。不想污染本地环境的直接Docker起一个实例更快docker run -d \ --name mysql8 \ -p 3306:3306 \ -e MYSQL_ROOT_PASSWORDroot123456 \ -e MYSQL_DATABASEpet_health \ -v /my/own/datadir:/var/lib/mysql \ mysql:8.0数据卷映射一定要加否则容器一删数据全没。启动容器后用项目里提供的SQL脚本初始化库表再记得核对一下application.yml里的数据库连接配置spring: datasource: url: jdbc:mysql://localhost:3306/pet_health?useUnicodetruecharacterEncodingutf8serverTimezoneAsia/Shanghai username: root password: root123456 driver-class-name: com.mysql.cj.jdbc.Driver第四步启动后端。用IDEA打开后端工程等待Maven依赖下载完成直接运行启动类。正常情况控制台会出现Tomcat started on port(s): 8080。这里推荐加一个Postman或ApiPost先把后端的登录接口测试通过确认接口层没问题再启动前端不然联调时错误来源容易混淆。第五步启动前端。进入前端目录执行npm install网络慢就配置npmmirror淘宝镜像依赖装完执行npm run dev。如果后端端口不是8080记得同时修改后端CORS配置和前端Vite的代理目标。4.2 MyBatis-Plus与MySQL8.0的经典坑我在跑这套项目时碰到的最经典的问题是分页失效。MyBatis-Plus的分页功能必须显式配置分页插件才生效没有配置的话selectPage返回的数据里total永远是0records也只是部分数据前端翻页直接异常Configuration public class MybatisPlusConfig { Bean public MybatisPlusInterceptor mybatisPlusInterceptor() { MybatisPlusInterceptor interceptor new MybatisPlusInterceptor(); interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL)); return interceptor; } }第二个经典坑是LocalDateTime序列化。Java8的LocalDateTime默认序列化成数组格式比如[2026, 5, 20, 14, 30, 0]前端根本没法直接用。解决方式是在application.yml中全局配置Jacksonspring: jackson: date-format: yyyy-MM-dd HH:mm:ss time-zone: GMT8或者在实体类的日期字段上加JsonFormat注解。两种都行我更推荐全局配置避免每个字段手动加注解。第三个坑是逻辑删除字段与唯一索引的冲突。很多人在业务表上给业务编号字段建立了唯一索引配合逻辑删除时如果记录被软删除了再次插入相同编号的记录会触发唯一索引冲突。标准解法有两种一是唯一索引中包含deleted字段删除时deleted字段写入主键值逻辑删除字段也可以配成update为当前时间戳让每条数据的唯一组合都不相同二是彻底不依赖数据库唯一约束改用应用层先查后插。实际项目中我一般选第二种代码可控性更好。4.3 前后端联调跨域、鉴权、时间格式前端和后端分开跑跨域问题几乎一定会遇到。项目里后端的跨域配置我建议用CorsFilter统一解决Configuration public class CorsConfig { Bean public CorsFilter corsFilter() { CorsConfiguration config new CorsConfiguration(); config.addAllowedOriginPattern(*); config.addAllowedHeader(*); config.addAllowedMethod(*); config.setAllowCredentials(true); UrlBasedCorsConfigurationSource source new UrlBasedCorsConfigurationSource(); source.registerCorsConfiguration(/**, config); return new CorsFilter(source); } }这里有个细节要注意allowCredentials(true)表示允许前端请求携带Cookie或Authorization头。如果前端用了自定义请求头比如在axios拦截器里加token头后端的allowedHeaders里一定要带上Authorization否则浏览器预检请求会直接失败。鉴权联调方面记得在axios请求拦截器里把token加上service.interceptors.request.use(config { const token localStorage.getItem(token) if (token) { config.headers[Authorization] Bearer token } return config })如果后端拦截器校验的是请求头中固定名称的token前端代码这里的字段名就要保持一致。这个项目用的是标准Bearer格式后端解析时从Authorization头取逻辑清晰干净。时间格式的联调也不容忽视。后端返回的时间如果格式不对前端列表就会出现一串数字或T字母分隔的乱糟糟格式。前后端联调第一步先约定好时间字段的序列化格式前端展示层再用Dayjs或者Moment统一格式化。5. 常见问题与排查技巧速查表5.1 编译 / 启动阶段的高频错误我把这个项目实际运行中最高频的几类错误整理成了一张表方便你对着排查。错误现象根本原因解决办法启动报Unable to load authentication pluginJDBC驱动版本过低MySQL8使用新版认证插件升级驱动为mysql-connector-java 8.0.x或更高启动报Failed to configure a DataSourceapplication.yml的数据库URL配置错误或没有导入SQL脚本检查库名、账号密码确认数据库已初始化npm install卡住或失败npm源网络问题切换npmmirror镜像源npm config set registry https://registry.npmmirror.com端口8080被占用本地其他服务占用改端口或在IDEA里用-Dserver.port8081覆盖编译报无效的源发行版IDE默认JDK版本与项目要求不一致把Project Structure里的SDK和Language Level都调整到JDK85.2 数据与接口联调问题排查运行起来之后问题大多集中在数据和接口层面。以下几类问题频率最高。第一类前端的列表请求返回200但数据为空。先查网络面板里的实际响应结构确认是后端返回的data为空还是前端解析字段对不上。比如后端返回Result对象包了一层data字段前端如果直接拿数组去遍历就会白屏。第二类登录接口401或403。优先检查token是否写入localStorage有没有在请求拦截器中正确携带。注意JWT有效期如果token过期时间设置太短调问卷里的接口就会间歇性失败。排错时把后端拦截器的日志打开观察请求头里是否真的带上了content-type。第三类是分页数据出现重复或缺失。这类问题优先怀疑排序字段不唯一。MySQL分页offset越大如果排序字段有很多相同的值比如都用create_time页与页之间可能出现数据错乱。优化方案是排序条件加主键兜底ORDER BY create_time DESC, id DESC。5.3 文档对不上代码时的兜底方法开源项目一个很常见的痛点是文档写的和代码实际实现的不一样。遇到这种情况不要慌我分享一套自己惯用的兜底方法。第一步把文档当作“预期行为”而不是“事实”来看待真正能反映事实的是代码和数据库。第二步遇到接口行为不符先查数据库表结构和后端的Mapper XML确认字段名和查询逻辑如果发现代码里引入了文档里没有的新字段以数据库为准。第三步遇到前端页面报“无法获取数据”用浏览器的DevTools看接口实际返回的JSON对比TypeScript或JavaScript里的类型定义这种问题基本都是字段名不一致或嵌套层级不同导致的。另外一个有效技巧是在IDEA中全局搜索文档里出现的关键词比如“健康评估”“预约”顺着Controller方法一路跟下去就能快速定位文档对应的代码位置。比从头通读源码目标明确得多。这套项目既然带文档也建议按这个思路做一次“代码与文档核对”把发现的偏差记录下来本身就是一次很好的源码分析练习。我在审阅代码时还发现项目的Troubleshooting内容如果只写“遇到问题就重启”这类无效建议说明这份文档的质量一般真正有用的流程是“现象 - 排查路径 - 根因 - 解决方案”而这套项目文档的排查思路基本是朝这个方向走的。结尾这套宠物健康顾问系统带给我的最大收获其实不在于它用了多先进的技术而在于它把一套完整业务用主流技术栈踏踏实实落地了。对我自己来说跑通这个项目后我把它内部那套“宠物档案 健康评估 咨询预约”的骨架搬到了一个社区养老服务的原型项目里表结构、权限控制和整体分层几乎没改只换了业务字段和评估规则前后端联调过程非常顺。我个人在实际操作中的体会是这种“先吃透一套完整骨架、再换业务壳子”的学习方式比从零堆代码效率要高得多。如果你也想拿这套项目练手建议不要满足于“跑起来”而是挑一个你感兴趣的模块比如健康评估规则自己改一版评分逻辑这才是真正把代码变成自己的过程。
RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进