1. 项目概览:这个“宠物健康顾问”到底是什么
先说结论:这套SpringBoot+Vue的宠物健康顾问系统,核心是做“宠物医院的轻量级数字化管理”。它不是一个花架子demo,而是把真实宠物门诊日常要干的几件事——宠物档案建档、在线问诊、疫苗提醒、健康资讯发布——全串起来的完整业务闭环。我拿到源码的第一感觉是:该有的模块一个不少,而且代码分层很规整,确实适合当毕设或课设的基底,也适合想搞懂前后端分离项目到底怎么协作的学习型选手。
这套系统的技术栈非常主流:后端是SpringBoot + MyBatis-Plus + MySQL,前端是Vue 2 + Element-UI + Axios,鉴权走JWT。没有引入一堆花里胡哨的中间件,也没有人为制造复杂度,就是一套“企业里最常见的组合”。对于学生来说,这意味着参考价值极高——你去任何一家中小型公司实习,看到的新项目大概率就是这类结构。
它能解决的问题也很聚焦:宠物主人可以注册登录、给自家宠物建档案、在线预约兽医问诊、查看问诊记录和健康建议;兽医端可以管理预约、填写诊断和用药建议;管理员则负责账号管理、资讯发布、基础数据维护。整个权限体系是“三种角色 + 一套路由”,前端控制页面显示,后端控制数据权限,典型的RBAC思路。
适合谁来参考?我大概分成三类:第一类是毕设选题和宠物医疗沾边的,这套源码能帮你省掉大量从零搭建的时间;第二类是课设要求“完整Web系统”的,它的功能复杂度刚好卡在“工作量足够又不至于失控”;第三类是单纯想学SpringBoot和Vue怎么配合的——把这份源码拆开看一遍,比你看十篇零散教程都管用。
2. 技术选型思路与整体架构拆解
2.1 为什么偏偏是SpringBoot + Vue
我经常被学生问:现在新框架那么多,微服务、云原生都喊得震天响,为什么毕设还得用SpringBoot + Vue?答案很现实:这套组合是“性价比之王”。SpringBoot让Java后端开发变得极其轻量,内嵌Tomcat,不用打WAR包,一个main方法就能起服务;Vue则是目前国内中小型项目前端占有率最高的框架之一,组件化开发让页面复用变得简单,而且Element-UI组件库能直接拖出后台管理界面需要的表格、表单、弹窗。
对比一下其它选项你就明白了:如果你用JSP + Servlet做传统单体应用,虽然也能实现功能,但前后端完全耦合,代码维护成本高,答辩时也容易被问“为什么不用更现代的技术”;如果你强行上Spring Cloud微服务,那根本是给自己挖坑——服务发现、配置中心、网关、分布式事务,每一层都需要时间学习,而一个宠物健康顾问系统根本撑不起微服务的体量。所以SpringBoot + Vue是恰到好处:既体现了“先进工程实践”,又不会因为过度设计卡住你的进度。
这套系统的架构是标准的前后端分离:前端跑在Node环境中(开发模式下默认8080端口,通过代理转发请求),后端跑在SpringBoot内嵌Tomcat中(默认8081端口),两者通过RESTful API通信。前端负责页面渲染、表单校验、路由跳转;后端负责业务逻辑、数据校验、持久化、权限校验。中间用JSON格式交换数据,JWT做身份凭证。这种架构的好处是:前端和后端可以并行开发,我改接口文档,你调页面,互不阻塞。
2.2 后端分层与前端工程结构解读
后端代码是经典的“Controller → Service → Mapper”三层结构。Controller层只做参数接收和结果封装,不写业务代码;Service层处理核心业务逻辑,比如预约状态流转、用户注册时的密码加密;Mapper层用MyBatis-Plus的BaseMapper,单表CRUD几乎不用写SQL,复杂查询用QueryWrapper构造条件。实体类直接用@TableName和数据库表对应,字段命名用驼峰和数据库下划线自动映射,省掉一大半xml配置。
我特意看了一眼项目的包结构,凡是按controller、service、mapper、entity、common、config分包的项目,层次都非常清晰。common包里放的通常是统一返回结果类Result、异常处理器、工具类;config包里放的是跨域配置、拦截器配置、MyBatis-Plus分页插件配置。如果你拿到源码后要改东西,先按包名找文件就行了。
前端工程用的是Vue CLI标准脚手架,src下分api、router、store、views、components、utils几个目录。api目录集中封装了所有请求方法,比如login(loginForm)内部调用request({url:'/user/login', method:'post', data:loginForm});router里定义了路由表,而且用了动态路由——根据登录用户的角色动态添加菜单和路由;store用Vuex存用户信息和token,刷新页面后从localStorage恢复。这套结构很典型,面试官要是问你“Vue项目怎么组织”,照着这套说就行。
2.3 登录鉴权和权限控制的实现思路
登录鉴权是这类系统最容易被问到的点。这套系统用了JWT(JSON Web Token),流程很简单:用户提交账号密码,后端校验通过后生成一个包含用户ID、用户名、角色的签名token返回给前端;前端把它存到localStorage,之后每次请求都在请求头里带上Authorization: Bearer <token>;后端拦截器校验token的合法性,解析出用户信息存入ThreadLocal,后续业务代码需要当前用户ID时直接取。
JWT的好处是服务端无状态,不用存session,天然适合前后端分离。但有两个坑我提醒一下:一是token过期时间要设置合理,太短用户频繁重新登录,太长又不利于安全,一般设置2到4小时;二是JWT一旦签发,在过期前是无法主动作废的,所以如果系统要求“管理员封禁用户后用户立刻失效”,就得额外引入Redis黑名单机制。就毕设场景而言,简单点用拦截器 + token过期时间足够。
权限控制方面,后端是在拦截器里校验角色,比如@RequireRole("ADMIN")之类的自定义注解,凡是没有对应角色的请求直接返回403。前端则是通过Vue Router的beforeEach路由守卫和动态菜单控制,不同角色看到不同侧边栏。对于课设答辩,你把这套“前端控制显示、后端控制访问”的权限设计讲清楚,已经是加分项了。
3. 数据库设计与核心功能模块解析
3.1 数据表设计:理清宠物健康数据的主线
数据库是这套系统的地基,设计得好不好直接决定后面的编码量。我打开SQL脚本看了一遍,核心表大概是这么几张:user用户表(账号、密码、真实姓名、手机号、角色类型)、pet宠物档案表(宠物名、品种、性别、生日、体重、是否绝育)、appointment预约表(宠物ID、医生ID、预约时间、状态)、consultation问诊记录表(预约ID、症状描述、诊断结果、用药建议)、reminder提醒表(宠物ID、提醒类型、提醒日期、是否已处理)。
这几张表之间的关系不难看出来:一个用户可以养多只宠物,所以pet表用user_id外键关联;一个宠物可以多次预约、多次问诊,预约和问诊是一对一关系(一次预约对应一次问诊),但一个宠物可以有多条预约和多条问诊记录;提醒表依附于宠物档案,用来做疫苗和驱虫提醒。对整个系统来说,这就是“用户-宠物-问诊”三条主线,所有功能都围绕它们展开。
具体建表时的几个细节值得学一下:一是时间字段统一用datetime,并且默认值设为CURRENT_TIMESTAMP,方便后续排序;二是状态字段用tinyint存储,比如预约状态0待确认、1已确认、2已完成、3已取消,而不是用字符串,这样存储更省,查询更快;三是所有表都加了deleted逻辑删除字段和create_time、update_time,这是MyBatis-Plus的标配,翻数据时不会出现“把记录删了就再也找不回来”的尴尬。
3.2 核心业务流程一:宠物建档与疫苗提醒
宠物建档是最基础的功能,但业务上有个隐藏需求:一个宠物主可能有三只猫两条狗,建档时得区分清楚。系统里通过pet表关联user_id实现“我的宠物列表”,前端根据当前登录用户加载对应宠物,后端查询时用LambdaQueryWrapper.in或eq限定用户维度。
疫苗提醒的实现思路是定时任务扫描。系统启动时开启一个Spring的@Scheduled定时任务,每天跑一次,扫描reminder表里“未处理且提醒日期距今天小于等于N天”的记录,把需要提醒的用户ID查出来,生成待提醒消息。这个场景在毕设中属于亮点了——你要能在答辩时说出“我用Spring内置的Scheduled定时任务扫描数据库,查到过期未处理的疫苗记录,自动推送给宠物主”,老师会觉得你确实理解了业务,而不只是跟着视频抄代码。
3.3 核心业务流程二:在线问诊与预约状态机
预约模块是系统里业务逻辑最重的部分。我常说预约状态不要用if-else写散,而应该脑子里有一张“状态机”图:用户发起预约时状态为待确认;医生接诊后变已确认;医生填写诊断报告并提交后变已完成;用户或医生有权取消。每一个状态变更都要在Service层校验当前状态是否合法,比如“已完成”状态不能被直接改成“待确认”。
在线问诊的业务流程大概是:用户选择宠物和医生,填写症状描述和预约时间,生成预约记录;兽医在后台看到待确认列表,点击确认后问诊记录生成;用户可以在“我的问诊”里查看医生的诊断建议。这里面有个容易遗漏的细节:医生填写诊断建议时,除了文字内容,往往还伴随用药建议字段,所以问诊记录表要预留medicine_advice字段,免得后期加需求时又要改表。
我实际跑通这套流程时,发现它的Controller参数用了@RequestBody接收JSON,前端提交的数据要保证字段名跟后端实体字段一致——比如petId如果前端传成pet_id就会直接接收不到,这是新手最容易踩的坑之一。遇到这种问题,最快的排查方式是在浏览器Network面板看请求体里的字段名,再回到代码里对齐,别上来就改后端。
3.4 数据字典与健康资讯模块的加分设计
健康资讯模块虽然业务简单,但做得好不好看就体现在这些细节上。资讯表通常有标题、封面图URL、摘要、正文内容、发布时间、作者ID。后台管理端提供富文本编辑框,前端门户页展示文章列表和详情。做成独立模块后,也方便毕设答辩时演示“管理员发布资讯、用户端实时看到”的功能。
数据字典是容易被忽略的加分点。宠物品种、疫苗类型、提醒类型这些字段如果硬编码在页面里,后期改起来非常痛苦;做成数据字典表,前端通过接口拉取枚举数据渲染下拉框,后端校验时也从字典里取值,代码会干净很多。这套系统里虽然字典没有做得特别复杂,但设计思路上已经预留了类似结构。你想在答辩时展示工程素养,把“数据字典如何避免魔法值散落各处”讲出来,是很加分的一环。
数据库表与业务模块的对应关系,我整理成了一张速查表:
| 业务模块 | 核心表 | 关键字段 | 说明 |
|---|---|---|---|
| 登录/注册 | user | username, password, role | role区分用户/医生/管理员 |
| 宠物档案 | pet | user_id, name, breed, weight | 一只宠物归属一个用户 |
| 预约问诊 | appointment | pet_id, doctor_id, status | status用状态机控制 |
| 问诊记录 | consultation | appointment_id, symptom, diagnosis | 一对一连结预约 |
| 疫苗提醒 | reminder | pet_id, remind_type, remind_date | 定时任务扫描日期 |
| 健康资讯 | article | title, cover, content, author | 管理员发布 |
| 数据字典 | dict | type, key, value | 下拉框动态数据 |
4. 从零跑通:环境准备与项目启动实战
4.1 环境版本清单:千万别用最新的,用能跑的
老规矩,先放一套我实际验证过“一定能跑起来”的环境配置。这套系统的依赖版本不算激进,后端SpringBoot用的是2.7.x,Java用8或者11都行,MySQL用5.7或者8.0都兼容,前端是Vue 2.6 + Vue CLI 4/5。千万别图新鲜上来就装SpringBoot 3.x,那玩意默认Java 17起步,很多老依赖(比如javax.servlet换成jakarta.servlet)会直接把项目搞崩。我见过太多人“明明按教程做的却一直报错”,最后发现是版本兼容性问题。
| 环境组件 | 推荐版本 | 备注 |
|---|---|---|
| JDK | 1.8 或 11 | 8就够,没必要升 |
| Maven | 3.6+ | 阿里云镜像配置好 |
| Node.js | 14 或 16 | Vue CLI 5要求Node 12+ |
| MySQL | 5.7 或 8.0 | 8.0记得关SSL或加参数 |
| IDEA | 2021+ | 社区版也可用 |
| VSCode | 任意 | 前端开发用 |
第一次接触JavaWeb项目的同学,我建议先把这三个装好:JDK、Maven、MySQL。Node.js如果只跑前端,装个16版本就够了。装环境这块不要跳步,但凡有一个环节的版本对不上,后面启动报错能把人折磨到怀疑人生。
4.2 后端启动步骤:从导入Maven工程到接口可用
后端启动分四步。第一步,在IDEA里选择“导入Maven工程”,选中后端根目录pom.xml,等待依赖下载完成。如果下载慢得像蜗牛,就去~/.m2/settings.xml里把中央仓库换成阿里云镜像,这一步能节省你80%的等待时间。第二步,修改application.yml里的数据源配置——数据库地址、账号、密码,以及mybatis-plus的配置。这里要注意:MySQL 8.0的驱动类名是com.mysql.cj.jdbc.Driver,而且URL得加上serverTimezone=Asia/Shanghai,否则时间会差8个小时;如果用旧驱动连8.0,会报SSL连接错误,直接在URL后加useSSL=false即可。
第三步,在Navicat或命令行里执行项目的SQL脚本,把表和测试数据都导进去。第四步,启动SpringBoot的main方法,看到Tomcat started on port(s): 8081说明后端起来了。测一个最简单的接口——登录接口,用Postman发一条POST请求,如果返回JSON里带token,说明环境没问题。我提醒一下:第一次启动时如果端口被占用,就在application.yml里换个觉得陌生的端口,比如8089,避免你本机某个程序占着8081。
4.3 前端启动步骤:依赖安装、代理配置与联调
前端的启动比后端稍微麻烦一点。在项目前端目录下打开终端,执行npm install装依赖。这一步有两个常见坑:一是Node版本太高,有些老依赖编译失败,解决办法是降Node版本或者换npm镜像源(npm config set registry https://registry.npmmirror.com);二是node_modules下载到一半就报错,多半是网络不稳或权限不足,删除node_modules和package-lock.json后重装即可。
依赖装完后,最关键的是vue.config.js里的代理配置。开发环境中前端默认跑在8080端口,后端跑在8081,前端请求/api开头的接口时,通过devServer的proxy项转给http://localhost:8081。比如前端请求/api/user/login,代理会转发到后端的/user/login。这样做的好处是避免了跨域问题,而且上线部署时也只需改代理目标地址。
联调的第一步是先把登录页跑通。打开浏览器访问http://localhost:8080,输入管理员账号密码,如果能跳转后台首页,整条链路就走通了。走不通的时候,先看浏览器Console的报错,分清是404还是跨域还是500。404大概率是路径少了前缀;跨域报错是代理没生效;500就去看后端日志,多半是SQL写错或者空指针。
4.4 核心代码快照:登录接口、JWT拦截器、分页查询
这系统里有几个代码片段非常典型,值得拿出来单独看。第一个是登录Controller,它接收用户名和密码,调用userService.login()返回自定义的Result对象,里面封装了token和用户信息。第二个是JWT拦截器,核心逻辑在preHandle方法里:从请求头取token,用Jwts.parser()解析,解析成功就把用户ID放到request的attribute里,失败就返回401。第三个是分页查询,用MyBatis-Plus的Page对象加LambdaQueryWrapper,一句.page()就能拿到分页数据,免手写SQL。
// 登录接口核心代码 @PostMapping("/login") public Result login(@RequestBody LoginDTO loginDTO) { String username = loginDTO.getUsername(); String password = loginDTO.getPassword(); // 校验账号密码 User user = userService.getOne(new LambdaQueryWrapper<User>() .eq(User::getUsername, username) .eq(User::getPassword, MD5Util.encode(password))); if (user == null) { return Result.error("用户名或密码错误"); } String token = JwtUtil.createToken(user.getId(), user.getRole()); return Result.success(new HashMap<String, Object>() {{ put("token", token); put("user", user); }}); }// 拦截器里解析token @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) { String token = request.getHeader("Authorization"); if (token != null && token.startsWith("Bearer ")) { token = token.substring(7); try { Claims claims = JwtUtil.parseToken(token); request.setAttribute("userId", claims.get("userId")); request.setAttribute("role", claims.get("role")); return true; } catch (Exception e) { response.setStatus(401); return false; } } response.setStatus(401); return false; }// MyBatis-Plus分页查询 Page<Appointment> page = new Page<>(current, size); LambdaQueryWrapper<Appointment> wrapper = new LambdaQueryWrapper<>(); wrapper.eq(Appointment::getDoctorId, doctorId) .orderByDesc(Appointment::getCreateTime); IPage<Appointment> result = appointmentMapper.selectPage(page, wrapper);Vue侧对应有三个经典片段:axios请求拦截器在请求头加token;路由守卫判断登录状态;动态渲染表格数据。这三个片段配合后端两个核心类,就是整个前后端交互的精髓。把这几段代码背下来,你就能在答辩现场从容回答“登录流程怎么实现的”“分页怎么做的”这种高频问题。
5. 常见问题与排查技巧实录
5.1 MySQL连接报错:SSL、驱动、时区三连坑
我统计过,后端启动失败的原因里,MySQL相关问题占了一半还多。最典型的是这个报错:Establishing SSL connection without server's identity verification is not recommended——这不是代码逻辑错误,而是MySQL 8.0默认要求SSL连接,旧驱动却不会自动判断。解决办法是在连接串后面加?useSSL=false。
第二种是ClassNotFoundException: com.mysql.jdbc.Driver——驱动类名用错了。MySQL 5.7用com.mysql.jdbc.Driver没问题,但8.0必须用com.mysql.cj.jdbc.Driver,并且pom里依赖版本也得同步更新成mysql-connector-java8.x。第三种是时区问题,报错里有The server time zone value字样,在连接串后加serverTimezone=Asia/Shanghai就解决了。建议你拿到源码后,直接把连接串一次性写成jdbc:mysql://localhost:3306/pet_health?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=Asia/Shanghai,后面省心。
5.2 Maven依赖下载失败与镜像源配置
Maven依赖下不下来,也是新手必踩的坑。默认中央仓库在国外,访问慢到经常超时,恢复的办法是配置国内镜像源。打开Maven的settings.xml,在mirrors节点新增阿里云镜像:
<mirror> <id>aliyunmaven</id> <mirrorOf>central</mirrorOf> <name>阿里云公共仓库</name> <url>https://maven.aliyun.com/repository/public</url> </mirror>改完之后,到IDEA里点一下Maven面板的刷新按钮重新拉取。如果某个依赖还是死活拉不下来,可能是本地仓库有损坏的半成品文件,把对应依赖的整个目录从~/.m2/repository里删掉,再重新reimport。这里再提醒一句:依赖首次下载特别耗时间,尽量一次性等完,别中途取消,否则容易出现各种奇怪的缓存错乱。
5.3 前后端联调:跨域、404、字段不一致
联调阶段遇到最多的问题是跨域。如果你不用代理,直接从8080的页面请求8081的接口,浏览器会拦截响应。解决方案有两种:第一种是我前面说的vue.config.js代理;第二种是在后端加一个全局CORS配置类。两者选其一就行,不要同时配,否则可能出现“预检请求被拦截”的反效果。
404问题,先确认请求路径是否跟后端Controller的@RequestMapping完全一致,注意大小写和后缀。举个例子:如果后端是/user/login,前端请求恰好是/api/user/login,代理里又没做pathRewrite把/api去掉,那永远404。字段不一致问题,多出在时间类型上——后端返回LocalDateTime时默认格式是"2024-06-01T10:30:00",前端如果不认得这个格式,就得在后端配置统一的时间格式化Jackson序列化器。这些小细节,调试的时候一旦踩中,没有两小时很难发现。
5.4 部署阶段的另外两个高频卡点
很多同学的毕设要求线上演示,这就需要把前端打包后塞进后端。Vue项目里执行npm run build会生成dist目录,把里面的内容复制到后端项目的src/main/resources/static下,再重启后端,访问http://localhost:8081就能看到前端页面。这里有个关键:打包后的静态资源默认路径是/,如果后端接口都在/api下,两者就不会冲突。但如果前端路由用了history模式,刷新页面会404,解决办法是把history模式改成hash模式,或者在后端加一个索引页转发控制器。
另外一个高频卡点是内存不足。SpringBoot项目默认启动参数堆内存不够大的时候,导入大量数据后可能直接OOM。解决办法是在IDEA的运行配置里增加VM参数-Xms256m -Xmx512m,前端Node服务如果提示内存不足,用NODE_OPTIONS=--max-old-space-size=1024给足空间。
5.5 常见问题速查表
| 报错现象 | 常见原因 | 快速解法 |
|---|---|---|
| MySQL SSL连接错误 | MySQL 8.0 + 旧驱动 | URL加useSSL=false |
| ClassNotFound驱动类 | 版本驱动类名变更 | 换成com.mysql.cj.jdbc.Driver |
| Maven下载卡死 | 默认源太慢 | 配阿里云镜像并删除损坏缓存 |
| 前端请求404 | 代理未转对或路径缺/多前缀 | 核对proxy和pathRewrite |
| 前端请求500 | SQL报错或空指针 | 看后端控制台日志定位行号 |
| 跨域预检失败 | 前端代理与后端CORS重复配置 | 二选一保留 |
| 页面刷新404 | Vue history路由 | 改hash模式或添加转发Controller |
| 端口被占用 | 本机其他进程占用端口 | 用netstat -ano查PID并关闭进程 |
6. 毕业答辩前必须吃透的几个“为什么”
源码能跑通只是第一步,答辩时老师更关注你“理解不理解”。我建议把下面这几个问题提前准备好:为什么用JWT而不是Session?关键术语是“无状态、分布式友好、扩展性好”。为什么MyBatis-Plus能少写SQL?因为它基于MyBatis做了通用Mapper的二次封装,复杂查询照样能构造。为什么前端能根据角色动态显示不同菜单?因为登录返回的user对象里有role字段,路由守卫每次跳转时动态添加对应权限路由。
有几个点还可以作为加分项来发散。如果你的系统有定时任务,就讲清楚@Scheduled的cron表达式;如果有文件上传,就讲一下OSS存储和本地存储的区别;如果有图表统计,就说是用ECharts做的数据可视化。怕就怕代码写完了,面对“这个功能怎么实现的”只能回答“就跟视频里一样”——这不叫理解,叫背诵。
给一个练习小技巧:把源码从头到尾读三遍。第一遍只读Controller和路由,搞明白“有哪些接口、每个接口干什么”;第二遍读Service实现,搞懂“每个接口背后的业务逻辑是怎么组织的”;第三遍读Mapper和数据库表,弄清楚“数据在哪里、怎么查出来”。三遍读完之后,你甚至能不看源码,自己在纸上画出整张系统的架构图。能做到这一步,答辩基本稳了。
我再唠叨一句关于二次开发的方向。这套源码的底子很干净,你可以在它上面加很多有意思的东西:比如给预约加个简单的排班功能,让医生能设置可预约时间段;比如加一个导入导出功能,用EasyExcel把宠物信息导出成Excel;比如引入Redis缓存热门资讯。每加一个功能,都要记得在论文里描述需求背景、表结构设计、接口设计和页面效果四个部分。这些扩展不追求技术的炫酷,但追求工程上的完整和逻辑自洽,这才是毕设图纸该有的深度。
从我实际翻源码、跑环境、调接口的经验来看,这套系统最大的优点是没有刻意堆坑,每一行代码都有迹可循。只要你能把环境跑起来,再顺着业务走一遍,项目逻辑基本就刻在脑子里了。后面无论是换数据库、改前端UI,还是加新模块,你都清楚该动哪一层。这,才是这份“源码”最值钱的地方。