1. 项目整体设计与技术选型解析
做毕设或者接手类似“宠物医院管理系统”这种题目时,大部分人第一反应是“不就是个增删改查吗”。但真把球踢到脚边,你才会发现:滑块认证、客户预约、电子病历、疫苗朔源、库存药品联动提醒、权限分级,每一块单拎出来都能写上一两千行代码。这个题目说到底,核心不是“花哨的算法”,而是业务流的完整度和工程结构的规范度。
1.1 这个项目到底在解决什么问题
“基于SpringBoot的萌宠医疗健康档案管理平台”看起来是给宠物做档案的,实际上它服务的是三类人:诊所前台、坐诊兽医、宠物主人。
- 对诊所前台来说,他们要管理挂号、预约、档案登记、疫苗提醒。
- 对坐诊兽医来说,他们要看到完整的就诊历史、过往处方、疫苗记录、过敏史,能快速开单、写病历。
- 对宠物主人来说,他们要能预约、查看基础档案和接种记录。
一个管理系统如果只是给管理员做CRUD,那它只是个后台;但加上云端诊疗这个关键词,性质就变了——意味着部分业务要打通线上入口,宠物主人能登录小程序或Web端预约、查询,相当于把一个诊所内网系统扩展成轻量级的云端服务平台。这个定位直接决定了技术选型和数据库设计方向。
1.2 为什么选SpringBoot这一整套
SpringBoot在毕业设计里“出镜率”极高,不是因为它最先进,而是因为它用最少的样板代码把工程跑起来。做这个项目时,随手一搜就能看到SSM框架的老教程,但我还是建议你直接用SpringBoot 2.7.x(或者3.x,看学校盲审要求),原因有四条:
- SpirngBoot的自动配置能把数据源、JSON序列化、Web容器这些琐碎事全部接管,几分钟内项目就能启动完成,不用像SSM那样堆一堆XML。
- 围绕SpringBoot的生态太全了,集成MyBatis-Plus、Spring Security、Redis、Swagger这些几乎都有现成方案,遇到问题搜答案一搜一个准。
- SpringBoot内嵌的Tomcat,打成一个jar包就直接部署,后续交项目时演示成本低。
- 大多数导师和评审对这个技术栈熟悉,答辩时沟通成本低。
配套选型我的建议是:数据库用MySQL 8.0,ORM用MyBatis-Plus,鉴权用Spring Security + JWT,接口文档用knife4j,前端管理端用Vue 2或Vue 3 + Element UI,用户端(宠物主人)做一个H5或微信小程序页面。如果你个人精力有限,用户端先用Thymeleaf写几个核心页面也行,但项目里体现前后端分离会更加分。
给新手的提醒:项目题目里带着“云端诊疗”,如果你一点都不做线上入口,答辩时容易被问“云在哪里”。至少留一个“用户登录 + 预约/查询”的入口模块,哪怕简单一点也有交代。
2. 核心功能模块设计与数据库拆解
功能设计不要一上来就铺开写代码。先画一遍业务闭环,再落数据库表。一个宠物诊所的业务闭环大概是:宠物主人建档 -> 预约挂号 -> 接诊写病历 -> 开处方/用药 -> 疫苗接种记录 -> 定期回访提醒。系统要做的就是把这个流程的信息都串起来。
2.1 宠物档案与主人信息的一对多模型
宠物主人不可能只养一只猫或一条狗,所以在数据库设计上,宠物主人表和宠物档案表要分开,用外键关联。这是很多新手第一次就栽的地方——把主人信息直接塞到宠物表里,后面要做“一个主人名下多只宠物”的功能就全乱了。
宠物档案表(pet)核心字段:
CREATE TABLE `pet` ( `id` BIGINT PRIMARY KEY AUTO_INCREMENT COMMENT '宠物ID', `owner_id` BIGINT NOT NULL COMMENT '主人用户ID', `pet_name` VARCHAR(50) NOT NULL COMMENT '宠物昵称', `pet_type` TINYINT NOT NULL COMMENT '宠物类型:1犬 2猫 3鸟 4仓鼠 9其他', `breed` VARCHAR(50) COMMENT '品种,如金毛、英短', `gender` TINYINT COMMENT '性别:0母 1公', `birth_date` DATE COMMENT '出生日期', `weight` DECIMAL(5,2) COMMENT '体重(kg)', `sterilized` TINYINT DEFAULT 0 COMMENT '是否绝育:0否 1是', `avatar_url` VARCHAR(255) COMMENT '宠物头像', `status` TINYINT DEFAULT 1 COMMENT '状态:1正常 0禁用', `create_time` DATETIME DEFAULT CURRENT_TIMESTAMP, `update_time` DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP ) ENGINE=InnoDB COMMENT='宠物档案表';宠物主人(如果医生端也要登录,建议设计成用户表带角色)一般是user表,通过role字段区分管理员、医生、用户。
设计关联关系时一定要想一想实际查询场景:医生在诊台上最常看到的是宠物名字、品种、体重、年龄、过敏史,所以pet表里该冗余的冗余,不该冗余的用关联表查。体重不要只存一个值,如果有“体重变化趋势”需求,建议单独设计体检记录表或宠物成长记录表,这样后期做数据展示时更灵活。
2.2 疫苗接种模块:不是“记一条”那么简单
疫苗接种管理是这类项目里比较能拉开档次的功能。新手容易做成“给宠物表加一列疫苗状态”,那是完全错误的。疫苗是一个高频、周期性、有隐患追溯需求的行为,必须单独建表。
CREATE TABLE `vaccine_record` ( `id` BIGINT PRIMARY KEY AUTO_INCREMENT COMMENT '疫苗记录ID', `pet_id` BIGINT NOT NULL COMMENT '宠物ID', `vaccine_id` BIGINT NOT NULL COMMENT '疫苗种类ID', `batch_no` VARCHAR(64) COMMENT '疫苗批号', `hospital_id` BIGINT COMMENT '接种医院/诊所ID', `doctor_id` BIGINT COMMENT '接种医生ID', `inject_date` DATETIME NOT NULL COMMENT '接种时间', `next_due_date` DATE COMMENT '下次接种日期', `reaction_desc` VARCHAR(255) COMMENT '接种后反应描述', `certificate_url` VARCHAR(255) COMMENT '疫苗证/免疫证附件', `create_time` DATETIME DEFAULT CURRENT_TIMESTAMP ) ENGINE=InnoDB COMMENT='疫苗接种记录表';好好的疫苗模块不只是记录已打过的针,还要能到期提醒。狂犬疫苗基本是一年一针,猫三联和犬四联各有时间窗口,如果系统里维护了一个“疫苗批次”概念,那就可以根据注射日期算出下次提醒日期,配合定时任务或单纯的列表筛选,就能做到“当天该打疫苗的宠物清单”。
更进一步完善的做法是:加一张疫苗品种表(vaccine),里面维护疫苗名称、适用宠物类型、建议接种间隔(天数)、单价等信息。这样医生开“狂犬疫苗”时,系统自动带出建议间隔和下次接种日,操作少,数据也规整。
真实诊所业务里,疫苗批次追踪很重要——某批次的疫苗出现问题,要能查出所有打过这批号的宠物。所以刚才表里加了batch_no(疫苗批号),这一列在做系统演示时,评委如果问到“如果疫苗出问题怎么召回”,你的表里有这个字段,事情就说得通。
2.3 云端诊疗与预约模块:别把“预约”做成“登记”
很多项目的预约模块,做着做着就退化成“管理员给用户登记一条预约记录”,完事。这不太行。预约的核心价值是时间插槽机制,也就是说,你要有排班或时间槽的数据设计。
至少有三张表:
- doctor_schedule(医生排班表):记录某医生某天坐诊时段,例如上午09:00-12:00、下午14:00-18:00。
- appointment(预约表):记录哪个宠物主人约了哪个时段,状态是待签到/已签到/已完成/已取消。
CREATE TABLE `appointment` ( `id` BIGINT PRIMARY KEY AUTO_INCREMENT, `pet_id` BIGINT NOT NULL COMMENT '预约宠物ID', `schedule_id` BIGINT NOT NULL COMMENT '排班ID', `appoint_date` DATE NOT NULL COMMENT '预约日期', `time_slot` VARCHAR(20) NOT NULL COMMENT '时段:09:00-10:00等', `reason` VARCHAR(255) COMMENT '预约原因:接种疫苗/体检/看病', `status` TINYINT DEFAULT 0 COMMENT '0待就诊 1已完成 2已取消 3爽约', `create_time` DATETIME DEFAULT CURRENT_TIMESTAMP ) ENGINE=InnoDB COMMENT='预约挂号表';设计预约时要注意一个细节:同一个时段同一只宠物不能重复预约,同一个医生同一时段不能被重复预约满额。这就是并发控制问题。数据量不大时,可以先把schedule表加一个appointed_count字段,每次预约前先查一下;如果项目想体现水平,可以用Redis的原子增减来做一个简单的锁机制。
云端的部分我建议分成两块:
- 预约+查询类:宠物主人登录后可以预约、取消、看历史记录,这类同步接口用普通REST API就够。
- 慢病随访/图文咨询类:如果题目要求“线上问诊”,可以做成宠物主人提交病情描述,医生端做待诊列表,回复文字结论,核心逻辑绕不开“会话”和“消息”,至少设计一个consultation表(问诊表)和一个reply表(回复表)。
2.4 权限模型:用户角色怎么设计
宠物医院系统涉及三种角色,而且**同一个人可能同时是“宠物主人”和“医生”**吗?现实中不太会,但系统里要注意权限边界。最简单的方式是在用户表加role字段:ROLE_ADMIN、ROLE_DOCTOR、ROLE_USER。控制逻辑放在Spring Security的拦截器里。
但这里容易踩一个坑:如果前后端分离,后端用JWT做无状态鉴权,你要考虑token里带哪些信息。我建议token里只放userId和role,每次请求从token解析身份,再到数据库或者Redis取一次用户完整信息;不要为了图快把大量用户信息全塞进token里,否则后续要改用户信息(比如医生要临时开通管理员权限)时,旧token不失效会很难受。
具体到接口权限:
- 管理员:所有接口,包括系统设置、用户管理、数据统计。
- 医生:宠物档案只读,诊疗记录增改,处方管理,疫苗接种记录增改,预约列表处理。
- 宠物主人:只能查自己的宠物、自己的预约接口,不能看其他人。
为了省时间,可以把接口权限注解化:自定义一个@RequireRole注解,然后用AOP拦截,比Spring Security的注解原生写法更直观,也更适合答辩时讲。
3. 关键技术实现与实操细节
这一部分我结合个人踩坑经历,说几个真正写代码时会卡住的地方。
3.1 数据库设计时最容易出错的三件事
第一件:日期时间类型的使用不一致。有的人在Java里用LocalDateTime,数据库里存datetime,排序没问题;但如果哪天想计算“这个月接种了多少针”,你直接用BETWEEN去比较字符串看起来没问题,实际上因为时区、格式的坑会查出脏数据。统一:Java对应字段用LocalDateTime,数据库用datetime或timestamp,不要手动转字符串日期做查询条件,用MyBatis-Plus的LambdaQueryWrapper传入时间对象,避免时区字符串带来的坑。
第二件:没有做逻辑删除。医疗数据有审计需求。宠物档案如果做成物理删除,后续查历史就诊记录会出现“宠物不存在”的情况。所以建议:所有核心业务表都加deleted字段(0正常,1删除),用MyBatis-Plus的@TableLogic注解,查询自动带条件,删除变成更新。答辩时也能说是“遵循医疗数据合规要求”。
第三件:金额用错类型。诊疗费用、疫苗费用如果数据库用double或float,可能带上几十位小数。金额必须用DECIMAL(10,2),Java用BigDecimal。例如宠物疫苗套餐核销,会员折扣算下来是99.99元,float在累加时精度丢失会导致“订单对不上账”,这在项目演示时是一个隐藏炸弹。
3.2 后端工程结构与核心代码片段
推荐的工程结构:
pet-hospital-server ├── common 通用枚举、异常处理、统一返回体 ├── config Spring配置、Security配置、MyBatis-Plus配置 ├── controller REST接口层 ├── service 业务逻辑层 ├── mapper MyBatis-Plus的Mapper接口 ├── entity 数据库实体类 ├── dto 前端交互的对象 └── utils JWT工具、日期工具等新手写接口最常见的毛病是:把一堆SQL操作直接堆在Controller里。比如生成一份“宠物健康档案”接口,直接在Controller里查宠物、查疫苗、查病历,最后手动组装对象。这样能跑,但代码特别乱。更体面的做法是定义MedicalRecordVO,在Service里组装好再返回。
核心示例——疫苗接种记录的Service代码(局部):
@Override @Transactional(rollbackFor = Exception.class) public boolean addVaccineRecord(VaccineRecordDTO dto) { // 1. 校验宠物是否存在 Pet pet = petMapper.selectById(dto.getPetId()); if (pet == null) { throw new ServiceException("宠物档案不存在"); } // 2. 校验疫苗是否存在 Vaccine vaccine = vaccineMapper.selectById(dto.getVaccineId()); if (vaccine == null) { throw new ServiceException("疫苗种类不存在"); } // 3. 保存接种记录 VaccineRecord record = new VaccineRecord(); BeanUtils.copyProperties(dto, record); // 4. 根据疫苗建议间隔自动计算下次接种时间(补充逻辑) if (dto.getNextDueDate() == null) { record.setNextDueDate(dto.getInjectDate() .toLocalDate().plusDays(vaccine.getIntervalDays())); } return vaccineRecordMapper.insert(record) > 0; }这里就有一个很实用的业务细节:下次接种时间不靠手填,靠计算。在录入疫苗时已经维护了该疫苗的推荐间隔周期,保存接种记录时系统自动算出下次应种日期。展示端筛选“近一周需要接种疫苗的宠物”时,一条LambdaQueryWrapper就能查出来:
LocalDate endDate = LocalDate.now().plusDays(7); LambdaQueryWrapper<VaccineRecord> wrapper = new LambdaQueryWrapper<>(); wrapper.le(VaccineRecord::getNextDueDate, endDate); wrapper.eq(VaccineRecord::getDeleted, 0);3.3 鉴权模块落地步骤
Spring Security + JWT在毕设里是标配。但配置流程经常让人想骂人,因为过滤器链一旦顺序不对,接口全被拦死。我建议按这几步走:
- 先写一个JwtUtil工具类:包括生成token、解析token、校验过期时间。
- 写一个JwtAuthenticationFilter,继承OncePerRequestFilter,从请求头取token,判断有效后写入SecurityContext。
- SecurityConfig里放行登录注册接口、Swagger文档路径、用户端公共查询接口,其余全部
authenticated()。 - 自己封装一个
@RequireRole注解配合AOP拦截,控制接口级权限。 - 统一返回体Result类要包含code、msg、data三个字段,鉴权失败时返回401和code 40100。
注意:登录接口要给登录失败的场景返回统一格式,而不是Spring Security默认的403页面,否则前后端对接时客户端解析不了。建议在SecurityConfig里注册一个自定义AuthenticationEntryPoint,返回JSON。
实际写的时候还有一个细节:如果小程序端或H5端调用接口,要允许跨域。在CorsFilter里配置允许的跨域来源,前端开发时(Vite或CLI)代理端口不一致是常态,这块不配好,会出现“接口通了但浏览器报跨域”的经典问题。
3.4 前端页面与后端交互的处理心得
管理端以Vue + Element UI为例,核心页面有:登录页、工作台(今日预约/待办提醒)、宠物档案列表、疫苗接种页、病历记录页、排班管理页。
有几个页面的交互细节值得用心做:
- 宠物档案页:列表卡片式展示,点击宠物可以进入详情页,详情页要有Tab页签:基本信息、就诊记录、疫苗记录、处方记录。这种聚合展示最能体现“档案平台”的气质。
- 疫苗接种页:建议把“已接种”和“待接种”分开两个标签页,上面加一个“近7天到期提醒”的黄色提示条。虽然只是前端组件组合,但演示效果非常好。
- 预约页:医生排班日历或时间槽点选,医生端看到的是以天为单位的预约队列,点某个宠物可以跳到病历填写。
我在做类似项目时,前端页面通常不追求酷炫,反而把重点放在**“路径通畅”**上。比如这样一条操作链路:宠物主人注册 -> 添加宠物 -> 选择疫苗预约 -> 医生端看到预约 -> 签到 -> 录入接种记录 -> 宠物主人端看到“已接种”和下次应种时间。整条链路如果完全通畅且数据层层联动,答辩时讲完基本能压住场。
4. 联调、部署与演示环境搭建
很多项目“写完了”但“跑不通”,问题多半出在环境配置和联调阶段,而不是代码逻辑上。
4.1 环境准备与初始化数据脚本
建议本地开发环境统一如下:
| 组件 | 版本建议 | 说明 |
|---|---|---|
| JDK | 1.8 或 11 | SpringBoot 2.7 用 8/11 都行,3.x 必须 17+ |
| MySQL | 8.0+ | 如果是学校机房老版本 5.7 也能兼容,但8.0更稳 |
| Redis | 5.0+ | 缓存token、预约并发控制 |
| Maven | 3.6+ | 依赖管理 |
| Node.js | 16+ | 前端工程用 |
初始数据要准备:管理员账号、测试医生账号、测试宠物主人账号(含3只不同的宠物:猫、狗、仓鼠)、10种常见疫苗、5个排班记录。敏感数据一律别用明文,加个BCrypt加密。
4.2 接口自测与Swagger文档
用knife4j(Swagger增强版)生成接口文档,管理端接口和用户端接口分成两个group,方便演示的时候筛选。写接口时Controller层不要偷懒,该写的@ApiOperation注解写全,尤其像是“修改宠物档案”这种接口注释写清楚:登录用户只能修改自己名下的宠物,医生可以修改所有宠物。
接口自测阶段,先确认每个模块的CRUD没问题,再做“业务串联”自测:
- 注册一个用户A(宠物主人),登录拿到token。
- 用A的token添加宠物档案。
- A创建一个预约单。
- 切换到医生账号,看到A的预约,标记“已签到”。
- 医生为A的宠物录入一条病历,并开具处方(药品库存-1)。
- 提示A创建一条疫苗记录并上传疫苗证图片。
- 切换到管理员账号,在统计页面看到今日接诊数据。
一整套流程走下来,代码里隐藏的逻辑问题会暴露得很明显。比如“医生接口怎么拿到当前登录用户是哪个医生”——这里建议提前在token里保存doctorId,或用userId去查doctor表,不要在业务代码里写死。
4.3 打包部署的两种方式
如果演示环境是本地,一种方式是后端jar包 + 前端打包后放到SpringBoot的static目录一起部署,这种方式演示最省心,双击启动就完事。
另一种方式是前后端分开部署。前端构建后扔到Nginx,后端jar包用systemd托管。这种部署方式万一机器人不对,很容易出问题:后端启动成功但前端无法访问接口——多半是Nginx代理配置问题。
一个比较稳的Nginx配置片段(只做参考):
location /api/ { proxy_pass http://127.0.0.1:8080/api/; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; }如果后端为前后端分离,代码里不要用相对路径访问接口,统一使用/api/前缀。比如登录接口写成/api/auth/login,这样代理前缀就好配。
5. 常见问题与避坑指南
这部分大概是我最想分享的,因为项目踩过的坑都会以莫名其妙的形式出现,而网上教程基本不会说。
5.1 常见问题速查表
| 问题现象 | 可能原因 | 排查/解决方案 |
|---|---|---|
| 前端请求接口返回403 | Spring Security拦截了请求,未放行的路径或Token失效 | 检查SecurityConfig的permitAll配置;检查前端请求头Authorization |
| 分页查询时数据重复 | 排序字段没加唯一字段,同时间数据排序不稳定 | 排序条件加主键,如order by create_time desc, id desc |
| 本地启动时数据库连接失败 | MySQL时区或SSL认证问题 | 连接串加useSSL=false&serverTimezone=Asia/Shanghai |
| 上传头像/疫苗证图片后前端打不开 | 静态资源映射缺失或路径不对 | 配置spring.mvc.static-path-pattern,或单独做一个图片读取接口 |
| 用户注册时密码存入数据库是明文 | 没有用加密工具 | 使用Spring Security的BCryptPasswordEncoder |
| JWT过期时间太短导致频繁重登 | 默认设置2小时以内 | 根据角色调整,管理员token可长一些,用户端建议2小时+refresh机制 |
| 实体类字段名与数据库下划线字段对不上 | MyBatis-Plus映射类型驼峰转换问题 | 检查配置map-underscore-to-camel-case: true,或字段加@TableField |
5.2 实际开发中容易忽略的坑
接口体量控制。列表接口和详情接口不要返回同一个大对象。比如宠物列表接口,如果返回宠物对象时还附带完整病历列表,数据量大时页面会卡顿。建议列表接口返回轻量VO,详情页再单独调接口。
日期筛选的边界。比如查“今日预约”,应该是在今天00:00到明天00:00之间。直接用new Date()做条件比较,会把明天的记录也查出来。常用解法是把LocalDate转成startOfDay和endOfDay,再作为查询条件传入。
数据统计的实现。项目如果有展示数据看板(如门诊量趋势、疫苗种类消费占比),不建议用复杂的SQL去查,可以先在Service层按业务需要写几个统计方法,或者用MySQL的GROUP BY外加Java8的Stream分组处理,查询内存量在演示级别完全够。如果数据量真的很大,再考虑Canal + 统计表方案,但这对毕设来说超纲了。
前端联调时的跨域。前端用localhost:5173访问后端localhost:8080,不配跨域必报警告。最简单是在后端写一个CorsConfig放行开发地址。记得生产环境要收紧,别用*。
6. 答辩与项目亮点的个人经验
最后说点实际的。这种毕业设计项目,代码写完只是第一步,更重要的是“讲得圆”。
我个人体会最深的一点是:评委通常不关心你用了什么新框架,而是关心你对业务是否有完整认知。所以答辩前,把自己代入几个角色,准备几个问题:
- “为什么疫苗接种要单独设计一张表?”回答要点:疫苗记录涉及批号追溯、下次接种提醒、疫苗证打印,单独成表才能支撑。
- “云端诊疗具体体现在哪些功能?”回答要点:宠物主人线上预约、在线查看档案与报告、医生在线录入诊疗记录,实现了从线下纸质记录到线上数据协同的转变。
- “权限你是怎么控制的?”回答要点:Spring Security负责认证,自定义注解加AOP控制角色权限,不同角色看不到无关数据。
- “如果有100万条宠物档案,系统会变慢吗?”技术深度不够就会被这个问题难住。提前给自己留后路:生产环境可在查询走索引的基础上,再加Redis缓存热点宠物档案,读多写少场景用读写分离也能缓解压力。
项目演示时,先走一遍用户主流程,再演示一个边缘场景(比如用户想给没建档的宠物预约,系统提示先建档),比一上来就堆功能更让人信服。数据上尽量造一些真实感较强的测试数据,比如给一只柯基配了三次接种记录、一次驱虫记录、一次皮肤病复诊记录,宠物详情页往下翻的时候有东西可看,这比空列表演示好得多。
再分享一个小的加分技巧:把诊所的“库存预警”功能做出来,即某种药品低于阈值时,在管理端首页出现预警提示。这个功能逻辑简单,但会让系统看起来更像一个真实商用的产品,而不是课程作业。该项目的核心是业务场景的闭环,不是技术炫技,把常规功能做扎实、把业务故事讲清楚,这个项目就能让评委满意,也能让你自己真正学到东西。