每年到毕设选题的季节,总能看到一堆"XX管理系统"扎堆出现,其中宠物医院管理系统是一个特别值得聊的题目。它看起来和人尽皆知的图书馆管理系统、学生管理系统差不多,都是增删改查,但真正做进去就会发现,宠物诊疗流程里有预约、挂号、病历、开药、收费、库存这一整条业务链,每一环都能拆成独立模块,做完之后整篇论文都有东西可写。这篇内容就围绕一个基于Spring Boot的宠物医院管理系统的设计与实现,把从选题、需求分析、数据库设计到核心代码实现,再到论文写作和答辩准备的完整思路摊开来讲。
这篇文章主要面向正在做毕业设计、想要用Spring Boot做一个能体现业务深度的管理系统的同学,也适合刚接触Java Web开发、想找一个完整项目练手的初级开发者。技术栈并不刁钻:Spring Boot做后端,Thymeleaf做页面,MySQL存数据,权限控制用Spring Security,每个环节都是国内招聘里经常被问到的知识点。我会把项目里最容易出错、最容易被答辩老师追问的地方单独拿出来讲,尽量让你拿到这份内容之后,能直接照着把系统搭出来,论文也有素材可写。
1. 毕设选题:宠物医院管理系统为什么值得做
1.1 业务复杂度刚好卡在"能毕业"和"有东西写"之间
选题最怕两件事:太简单导致论文没深度,太难导致做不完。宠物医院管理系统的业务复杂度正好落在中间偏上的位置。它的核心不是管理图书、管理学生这种单纯的信息登记,而是围绕一次完整的宠物就诊过程来做系统建模:主人带着宠物到前台挂号,医生接诊之后要写病历、做检查、开处方,主人拿着处方去缴费取药,药品从库存里扣掉。这个流程里涉及多个角色、多个状态、多张表的联动,做出来的系统天然就有逻辑可讲。
对比传统的图书管理系统,业务上只需要处理"借"和"还"两个动作;宠物医院系统至少要处理"预约""接诊""开单""缴费""出库""复诊"多个动作,而且这些动作之间有先后依赖。论文里写需求分析的时候,用例图、流程图、状态图都能画得出来,不用硬凑篇幅。
1.2 行业背景给论文提供了一个天然的故事入口
这几年养宠物的家庭越来越多,宠物医院数量也在增长。但很多小型宠物诊所的信息化程度并不高,宠物档案靠纸质本子、疫苗记录靠手写、药品库存靠月底盘点。这种行业现状用到论文绪论里非常自然,不需要编造,只需要去搜几篇关于宠物诊疗信息化、HIS(医院信息系统)发展的文献,把行业背景和存在问题简单梳理一下,研究意义那一节就能写得有血有肉。
我见过不少学生选题时纠结要不要做在线问诊、远程监控这种热门方向,其实这些方向对毕设来说过于发散,数据从哪来、效果怎么验证都是问题。宠物医院管理系统是已经有成熟业务模式支撑的题目,你不需要发明业务,只需要把现有线下流程搬到线上,这就是毕设该有的定位。
1.3 隐藏门槛:不懂诊疗流程的人容易做成纯CRUD
这个题目最大的坑在于,如果只把"宠物""主人""医生"当成普通实体来做,最后就是一个换皮的增删改查系统。答辩老师问一句"处方和库存怎么联动""复诊病人的历史病历怎么调出来",如果系统里没有对应设计,现场就很尴尬。
所以做这个选题之前,至少要理解三个医疗业务概念:病历是跟随宠物走的,不是跟随主人;处方是医生开出的诊疗指令,收费是财务动作,两者有先后关系;药品出库必须和收费绑定,不能出现钱没交药先拿走的情况。后面的数据库设计和代码实现,都是围绕这三条规则展开的。
2. 需求分析:先把医院日常流程翻译成功能清单
2.1 角色与完整业务链路
我在设计需求时,先把用户角色拆成了四类:系统管理员、前台接待员、医生、药房/收银员(小诊所里这两个角色经常合并)。这四类角色对应四种完全不同的操作界面和权限范围,绝对不能合并成一整套通用页面。
完整的业务链路是这样的:宠物主人到店或者提前电话预约,前台在系统里查询主人和宠物档案,没有档案就先建档;挂号之后医生接诊,接诊界面能看到该宠物的历史病历和过敏信息;医生填写症状、诊断、治疗方案,选择药品或者检查项目生成处方;主人去收费窗口缴费,缴费成功之后药品出库、库存扣减;如果是疫苗或者驱虫,系统还要自动提示下次到期时间。这个链路定了,功能模块基本也就定了。
2.2 模块划分与功能边界
| 功能模块 | 核心功能 | 是否本系统实现 | 说明 |
|---|---|---|---|
| 系统登录与权限 | 不同角色登录、菜单权限控制、密码加密 | 必做 | 答辩必问,用Spring Security实现 |
| 宠物建档 | 宠物基本信息、品种、过敏史、疫苗记录 | 必做 | 和主人档案分开存储 |
| 预约挂号 | 按医生、日期、时段预约,支持取消 | 必做 | 尽量做上,体现并发控制 |
| 诊疗记录 | 病历填写、历史病历查询 | 必做 | 核心亮点,不能只做增删改查 |
| 处方管理 | 医生开处方、处方明细、模板复用 | 必做 | 建议保留常用处方模板功能 |
| 药品库存 | 药品信息维护、入库出库、库存预警 | 必做 | 重点设计收费自动出库 |
| 收费管理 | 收费单生成、金额计算、收费记录 | 必做 | 金额必须后端计算 |
| 会员管理 | 充值、余额消费、充值赠送 | 可选 | 有时间再做,能丰富论文 |
| 排队叫号、在线支付 | 排队屏、支付宝/微信支付 | 不实现 | 写进展望,避免工作量失控 |
这个表格不仅是开发需求清单,还是论文里"系统功能结构图"的直接来源。很多同学做系统时巴不得把所有功能都加上,我的建议相反:把必做功能做扎实,可选项只做成"已在系统中预留接口、后续可扩展"的状态,这样工作量可控,论文的"不足与展望"章节也好写。
2.3 非功能需求在论文里怎么呈现
非功能需求经常被忽略,但答辩老师很爱问。我在需求分析阶段就直接明确了三点:第一,药品库存不能出现负数,收费和出库必须在一个事务里;第二,同一时段同一医生不能接受两个预约,必须通过唯一索引或者锁来保证;第三,金额计算统一用BigDecimal,避免浮点数精度问题。这三点都不是空话,后面在数据库设计和代码实现里全部落实,论文里写出来之后,答辩时会有一种"每说一句话都有代码兜底"的底气。
3. 技术选型与项目结构:Spring Boot搭台,怎么搭能自圆其说
3.1 为什么是Spring Boot + Thymeleaf,而不是前后端分离
现在很多同学一上来就想用Vue做前后端分离,觉得更"现代"。但对于毕设这个场景,我非常推荐Spring Boot + Thymeleaf + Bootstrap的组合。原因很实在:前后端分离意味着你要维护两套项目、处理跨域、设计接口文档,工作量直接翻倍;而Thymeleaf服务端渲染只需要一套代码,登录状态用Session维护,开发链路短,出问题容易排查。答辩老师更关注业务逻辑,而不是你用了多新的前端框架。
如果你导师明确要求前端用Vue,那就选Spring Boot写接口、Vue + Element UI做管理后台。这种情况下,统一返回Result对象、JWT做鉴权、接口全局异常处理这些配套设计就必须做好。走这条路也能做,但心里要有数,多出来的工作量要做进计划。
3.2 依赖与关键中间件选型
我的推荐依赖组合如下:
- 基础框架:Spring Boot 2.7.x,不用最新版,稳定优先
- 持久层:MyBatis-Plus,单表CRUD不用写SQL,复杂查询用LambdaQueryWrapper很方便
- 数据库:MySQL 8.0,存储引擎用InnoDB,字符集utf8mb4
- 权限:Spring Security + JWT,或者Spring Security + Session,后者更简单
- 工具类:Hutool、Lombok,减少重复代码
- 文档:springfox或springdoc生成Swagger接口文档,论文附录能直接复用
选型不需要花哨,但每个选择要能说出理由。比如MyBatis-Plus的好处是开发效率高,适合毕业设计这种需要快速出成果的场景;缺点是SQL可读性弱一点,所以复杂联表查询我仍然写XML,保证关键SQL可控。论文里的"相关技术介绍"章节就按照这个逻辑写,不要抄博客上的技术简介,而是写"我在系统里用它做了什么"。
3.3 后端包结构与统一定位
包结构一定要提前规划。我用的是这个分层方式:
com.petclinic ├── common // 统一返回结果、全局异常处理、常量 ├── config // 配置类:安全配置、资源映射、Jackson配置 ├── controller // 接口层 ├── service // 业务层,接口+实现 ├── mapper // MyBatis-Plus数据访问层 ├── entity // 数据库实体 ├── dto // 请求/响应对象,避免实体直接暴露 ├── vo // 视图模型,比如预约列表页的数据对象 └── utils // 工具类这个结构最直接的好处是论文画"系统架构图"和"包结构图"时特别清晰,评审老师一眼就能看出你懂分层。实际开发中还要配一个统一返回结果类Result,里面放code、message、data三个字段,接口层返回值统一用Result包装,前端拿到之后判断code是否为200。全局异常处理用@RestControllerAdvice,捕获业务异常、参数校验异常、未知异常,避免把堆栈直接抛给前端。
4. 数据库设计:核心表怎么拆,字段为什么这么定
4.1 主人、宠物、医生三张基础档案表
宠物医院管理系统最核心的设计原则是:主人信息与宠物信息分离,宠物信息才是真正的病例主体。因为一个主人可能带三只宠物看病,每次就诊的都是宠物本身,如果只按主人维度管理就会乱套。
宠物表的字段设计很有讲究,我当时是这样建的:
CREATE TABLE pet ( id BIGINT PRIMARY KEY AUTO_INCREMENT, owner_id BIGINT NOT NULL COMMENT '主人ID', name VARCHAR(30) NOT NULL COMMENT '宠物昵称', type TINYINT NOT NULL COMMENT '动物类型:1狗 2猫 3其他', breed VARCHAR(50) COMMENT '品种', gender TINYINT COMMENT '0母 1公', birthday DATE COMMENT '出生日期', weight DECIMAL(5,2) COMMENT '最新体重(kg)', sterilized TINYINT DEFAULT 0 COMMENT '是否绝育', allergy TEXT COMMENT '过敏史', create_time DATETIME DEFAULT CURRENT_TIMESTAMP, update_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, deleted TINYINT DEFAULT 0 COMMENT '逻辑删除', INDEX idx_owner_id (owner_id) ) COMMENT '宠物档案表';注意几个容易被忽略的点:过敏史是医生的安全底线,必须有但可以为空;体重不应该存在宠物基本信息里,它每次就诊都可能变化,所以我在病历表里也存了一个体重字段,基本信息里的体重只作为"最近一次体重"展示;deleted字段用于逻辑删除,病历数据不允许物理删除,这是医疗系统的合规要求,论文里可以写一笔。
主人表、医生表就不展开写了,主人表字段主要是姓名、电话、微信、地址,医生表主要是姓名、职称、科室、座诊时间。有一个细节一定要做:医生和坐诊时间表分开,不要直接在医生表里用一个varchar存"周一到周五09:00-17:00",因为排班可能变化,而且后面的预约功能要按医生、日期、时段做唯一约束,排班表拆出来才能在SQL层面做校验。
4.2 预约挂号与病历主线的表关系
就诊链路的主线是:预约单 → 病历 → 处方。这条主线的表关系如下:
appointment(预约单) ├── pet_id // 关联宠物 ├── doctor_id // 关联医生 ├── appoint_date // 预约日期 ├── time_slot // 时段编码,如 AM/PM ├── status // 0待就诊 1已接诊 2已完成 3已取消 4爽约 medical_record(病历) ├── pet_id ├── doctor_id ├── appointment_id // 可选关联,门诊直接看病也可以没有预约 ├── symptom // 主诉症状 ├── diagnosis // 诊断结果 ├── treatment_plan // 治疗方案 ├── temperature // 体温 ├── weight // 本次体重 ├── record_time // 接诊时间 prescription(处方单) ├── medical_record_id ├── doctor_id ├── total_amount ├── status // 0待收费 1已收费 2已作废预约表一定要加唯一索引:(doctor_id, appoint_date, time_slot, pet_id)联合唯一,再加一个(doctor_id, appoint_date, time_slot)查重。按医生和时段反查这个医生该时段是否已满,用唯一索引直接从数据库层防止重复预约。病历表和预约表关联,但设定为可空,这样现场直接挂号没有预约的病例也能录入,业务上更灵活。
有一个常见错误是把病历当成宠物的一个"子表",直接关联pet_id,随后在宠物详情页面里把病历以列表形式展示。这个思路没有错,但要注意,病历表中的诊断结果最好用TEXT,不要用VARCHAR(255),因为医生录入的诊断和治疗方案可能很长。此外,每次就诊的体重、体温都存在病历表里,这样在论文的功能展示里就可以写"根据历史病历曲线查看宠物体重变化",做一个很简单的ECharts折线图,效果非常好。
4.3 药品、处方、收费与库存的联动
药品、处方明细、收费单这三张表是整个系统里业务逻辑最多的部分。设计思路是:
处方明细表(prescription_item)记录每一项药品或检查项目,关联药品表drug。drug表字段至少有:药品名称、规格、单位、进价、售价、库存数量、预警库存、是否需要处方、有效期。药品出库不直接操作drug表的stock字段,而是写一张stock_log库存变动日志表,记录变动类型(入库/出库/盘点)和关联单号,这样做的好处是月底对账有据可查,答辩时是一个很加分的点。
收费单表(bill)设计得更像财务单据:关联主人、总金额、折扣、实收金额、支付方式、收费员、收费时间。处方和收费的关系是:医生开完处方先不扣库存,主人到收费窗口缴费成功那一刻才扣库存并生成出库日志。如果主人开完药不交钱就离店,发现库存被扣了,之后还得做负向调整。所以事务边界一定是"收费单创建+库存扣减"在同一个@Transactional里。金额字段全部用DECIMAL(10,2),如果用了double,等答辩老师问"0.1+0.2怎么不等于0.3"就难受了。
5. 核心功能实现:预约防重、诊疗开单、扣库存事务
5.1 预约号源:唯一索引兜底 + 异常捕获
预约模块很容易做成"用户选个时间点提交,然后直接插一条记录",但如果同一时段有两个人同时提交,就可能出现超约。解决思路分两层:第一层是查询时校验,第二层是写入时靠数据库唯一索引兜底。
核心代码逻辑可以这样写:
@Transactional public void createAppointment(AppointmentDTO dto) { // 防止同一宠物同一时段重复预约 LocalDate date = dto.getAppointDate(); String slot = dto.getTimeSlot(); Long doctorId = dto.getDoctorId(); Long petId = dto.getPetId(); Long count = appointmentMapper.selectCount( new LambdaQueryWrapper<Appointment>() .eq(Appointment::getDoctorId, doctorId) .eq(Appointment::getAppointDate, date) .eq(Appointment::getTimeSlot, slot) .eq(Appointment::getStatus, 0)); if (count > 0) { throw new BusinessException("该医生该时段已被预约"); } // 数据库唯一索引兜底,插入时如果没有业务校验直接拦截 try { appointmentMapper.insert(buildEntity(dto)); } catch (DuplicateKeyException e) { throw new BusinessException("该时段存在重复预约,请刷新后重试"); } }这里有个细节:查询时用status=0是因为一个时段只能有一个"待就诊"预约,已经接诊的时段可以由前台重开。如果你把所有状态的预约都算进去,就会出现"已经看完了还不让人家下午再挂同一个医生号"的尴尬情况。唯一索引本身是全状态的,所以我在索引设计里加了pet_id进入联合唯一索引,这样同一宠物同一时段不会重复,但不同宠物可以先后预约,业务上更合理。
5.2 诊疗开单与常用模板:一个DTO、一个事务
诊疗开单是医生最常用的功能。设计时我建议用一个大表格提交,一次性带上病历信息和处方明细数组,避免分多个接口提交导致数据不完整。前端界面上医生可以勾选常用药品,点击之后自动填充到明细表格里,后端在保存时同步生成模板,下次直接套用。
后端这样处理事务:
@Transactional public void saveMedicalRecord(MedicalRecordSaveDTO dto) { // 1. 保存病历主表 MedicalRecord record = new MedicalRecord(); record.setPetId(dto.getPetId()); record.setDoctorId(dto.getDoctorId()); record.setSymptom(dto.getSymptom()); record.setDiagnosis(dto.getDiagnosis()); record.setTreatmentPlan(dto.getTreatmentPlan()); medicalRecordMapper.insert(record); // 2. 保存处方单及其明细 List<PrescriptionItemDTO> items = dto.getItems(); if (items != null && !items.isEmpty()) { Prescription prescription = new Prescription(); prescription.setMedicalRecordId(record.getId()); prescription.setDoctorId(dto.getDoctorId()); prescription.setStatus(0); // 待收费 prescription.setTotalAmount(calTotalAmount(items)); prescriptionMapper.insert(prescription); for (PrescriptionItemDTO item : items) { PrescriptionItem pi = new PrescriptionItem(); pi.setPrescriptionId(prescription.getId()); pi.setDrugId(item.getDrugId()); pi.setQuantity(item.getQuantity()); pi.setUnitPrice(item.getUnitPrice()); pi.setSubtotal(pi.getUnitPrice().multiply(new BigDecimal(pi.getQuantity()))); prescriptionItemMapper.insert(pi); } } // 更新预约状态为已接诊 if (dto.getAppointmentId() != null) { appointmentMapper.updateStatus(dto.getAppointmentId(), 1); } }这里最关键的封装点是:前台提交过来的unitPrice是不能直接信任的。后端应该根据drugId重新从数据库里查售价,这里用传入价格是为了展示方便,真正记账时必须重查。我实际项目里是把"重查价格"这一步写在service里的,前端传来的价格只作为展示参考,后端再算一次总价,两遍核对,防止有人改价格。
5.3 收费确认扣库存:条件更新防超卖
收费模块的代码是整个系统里最需要"手术刀式精确"的地方。我的实现思路是:收费单状态变为已收费的同时,循环处方明细,逐条执行条件更新语句扣减库存,然后写入库日志。库存扣减的SQL一定要写成带条件的形式:
@Transactional public void payBill(Long billId) { Bill bill = billMapper.selectById(billId); if (bill == null || bill.getStatus() != 0) { throw new BusinessException("收费单不存在或已收费"); } // 获取处方明细,扣库存 List<PrescriptionItem> items = prescriptionItemMapper.selectByPrescriptionId(bill.getPrescriptionId()); for (PrescriptionItem item : items) { Drug drug = drugMapper.selectById(item.getDrugId()); if (drug.getStock() < item.getQuantity()) { throw new BusinessException("药品库存不足:" + drug.getName()); } // 条件更新:库存必须大于等于购买数量才扣减 int rows = drugMapper.deductStock(item.getDrugId(), item.getQuantity()); if (rows == 0) { throw new BusinessException("药品库存不足,请刷新后重试"); } // 写入库存日志 stockLogMapper.insert(StockLog.of(drug.getId(), item.getQuantity(), "OUT", billId)); } bill.setStatus(1); // 已收费 billMapper.updateById(bill); }这种"条件更新+影响行数判断"的写法比先查再减要安全得多,因为它在数据库层面让"查询和更新"变成了原子操作,有效防止两个人同时买最后一件药品时出现超卖。@Transactional保证如果库存不够抛出业务异常,之前插入的日志和状态修改全部回滚,不会出现钱收了、库存没扣的脏数据。
6. 论文结构、测试与答辩准备
6.1 论文目录和每个章节写多少页
很多同学系统做完了,卡在论文上。我给一个经过验证的目录结构建议:
| 论文章节 | 主要内容 | 建议篇幅 |
|---|---|---|
| 绪论 | 行业背景、研究意义、国内外现状、研究内容 | 4-6页 |
| 相关技术介绍 | Spring Boot、MyBatis-Plus、MySQL、Spring Security在系统中的用途 | 3-4页 |
| 系统需求分析 | 可行性分析、系统用例图、业务流程、功能需求、非功能需求 | 6-8页 |
| 系统设计 | 总体架构图、功能模块设计、数据库详细设计 | 8-10页 |
| 系统实现 | 每个核心模块的界面截图+关键代码+实现说明 | 8-12页 |
| 系统测试 | 测试环境、功能测试用例表、性能测试结果 | 4-6页 |
| 总结与展望 | 完成工作、遇到的问题、不足与改进方向 | 2-3页 |
这里最容易出问题的是"系统实现"章节容易写成代码说明书。我的建议是:每贴一个功能截图,配三句话——这个页面解决什么问题、关键操作流程是什么、核心代码哪一段最关键。不要大段贴Controller层代码,那是最没有信息量的部分。把"数据库设计的表结构""预约防重复的段代码""收费扣库存的事务段代码"这几个亮点展示出来就够了。
6.2 系统测试章节:用例表和JMeter压测
功能测试用例表是论文基本功。我建议至少列出一二十个用例,覆盖登录、权限、宠物建档、预约、开单、收费、库存预警、统计查询这些核心功能。每条用例必须包含编号、测试模块、操作步骤、预期结果、实际结果,哪怕你实际只手工测了三遍,也要把这二十条用例记录得整整齐齐。
性能测试是很多人忽略的加分项。用JMeter对预约接口做并发测试非常容易:开一个200线程的线程组,让所有线程同时请求同一个预约接口,观察失败率和响应时间。如果前面做了事务和唯一索引,200个并发请求里大约只会成功一个,其余全部返回"该时段已被预约",这个结果本身就是并发控制的证据,写到论文里非常有说服力。注意写清楚测试环境配置(CPU、内存)和测试工具版本,避免答辩时被追问细节答不上来。
6.3 答辩演示脚本与高频提问
答辩现场最怕冷场和操作失误,提前准备演示脚本非常必要。我的建议脚本是:先用管理员登录,展示角色权限和用户管理;再登录前台角色,演示"建档→预约→查询预约列表";切换到医生角色,演示"接诊→写病历→开处方";再回到收费角色,演示"收费→库存扣减→查看库存日志";最后展示ECharts统计报表,比如近一个月宠物就诊量趋势。整个流程控制在八分钟以内,数据全部用提前准备好的演示账号和演示数据。
答辩高频提问我整理过几个:
- 为什么选Spring Boot?答:简化配置、自动装配、内嵌Tomcat,适合快速构建独立服务,生态成熟。
- 数据库主键为什么用自增ID?答:业务主键如"病历号"如果用自增会有暴露业务量的问题,但本系统为简单起见使用自增ID,通过索引保证查询性能,论文里可以坦率承认这是简化的取舍。
- 预约并发怎么保证不超约?答:数据库唯一索引+事务+业务前置校验,三条防线逐层兜底。
- 药品库存不足怎么处理?答:抛业务异常、事务回滚,界面提示库存不足。
- 为什么不实现在线支付?答:涉及支付牌照和第三方接口对接复杂度,因此作为后续扩展方向。这个问题一定要提前准备好,几乎必问。
7. 实测踩坑记录与收尾提醒
7.1 时间显示、金额精度和文件上传三个老坑
时间问题几乎每个Spring Boot项目都会遇到。使用LocalDateTime时,如果数据库连接串没有指定serverTimezone=Asia/Shanghai,查询出来的时间可能差八个小时;如果前端页面用Thymeleaf渲染,还需要在application.yml里配置jackson的date-format。最简单的做法是统一用LocalDateTime传String,前端直接显示;在数据库连接URL里显式加时区参数,避免服务端和数据库时区不一致。
金额精度问题主要是double导致的,我在收费计算时统一用BigDecimal,前端传入的价格一律作为参考,后端重新从数据库查药品单价再计算。文件上传问题包括两类:一是Spring Boot默认上传大小限制是1MB,宠物检查图片往往超限,需要在配置里调大spring.servlet.multipart.max-file-size;二是上传的图片要能被页面访问到,需要重写WebMvcConfigurer的addResourceHandlers方法,把本地目录映射成访问路径,否则图片存到服务器了却打不开。
7.2 逻辑删除与外键、唯一索引的组合坑
医疗场景下病历和处方不允许物理删除,所以我在实体上都加了deleted字段做逻辑删除。但使用MyBatis-Plus逻辑删除后有一个很隐蔽的坑:如果业务上有唯一索引,比如"一个宠物只能有一条默认预约"这种设计,逻辑删除的记录仍然占着唯一索引的位置,导致第二次插入同样数据时报错。解决思路有三种:第一种是联合唯一索引里不包含逻辑删除字段;第二种是应用层做校验而不是依赖数据库唯一约束;第三种是设计上就不用唯一索引,靠业务代码控制。我在预约场景里采用了"业务代码校验+唯一索引兜底"的方案,逻辑删除字段不参与唯一判断,同时在代码里先把已取消的预约记录的状态改掉,再插入新记录。
物理外键我也建议少用。数据库设置了外键约束,插入删除的顺序稍有不慎就会报错,而且逻辑删除下外键约束意义有限。把表与表的关系体现在业务代码和ER图里,比强依赖数据库外键更好维护,这也是答辩时能讲清楚的取舍。
7.3 演示数据准备与账号设计
系统做完之后,演示数据特别影响答辩第一印象。我习惯写一个data.sql脚本,里面放好三四个主人账号、五六只不同品种的宠物、三个医生账号、一个完整的预约和病历记录,以及一批药品数据。再准备一个"只读演示账号",答辩老师想上手点两下时,给他一个密码简单的只读账号,可以随意点来点去,不需要担心误删数据。演示当天先把浏览器调到预约页面、病历页面、库存页面这几个核心页面的书签,避免现场还要层层点进去,这些细节看着很小,但真正答辩时节省的时间非常可观。
最后再提醒一点:不管是自己做还是照着参考做,一定要把每个模块的代码从头到尾读一遍,尤其是service层的事务和权限配置。答辩老师不一定会逐行看代码,但问的问题大概率是从你的系统里现场发起的。能把"预约为什么不会超约""扣库存为什么不会超卖""医生为什么看不到收费按钮"这几个问题讲明白,整个项目的技术深度就立住了,论文也就有了真正的底气。