1. 为什么选"宠物诊所管理系统"做毕设:选题逻辑与项目定位
如果你正在找Java方向的毕业设计题目,我相信"XX管理系统"这四个字你已经看吐了。图书馆管理系统、学生管理系统、超市进销存系统……每年毕设季都会大量出现,答辩老师一眼扫过去就知道工作量有多少。相比之下,"宠物诊所管理系统"是一个看似常规但实际很有发挥空间的题目,我记得当初选这个题的时候,有几个师兄弟还笑话我"换个皮而已",等我把完整的功能清单和数据库设计表拍在他们面前时,他们才意识到事情没那么简单。
先来拆一下这个题目的真实价值。宠物诊所管理系统本质上是一个垂直领域的信息化管理平台,它服务的对象不是普通用户,而是宠物医院的接待前台、兽医、药房管理员和宠物主人。业务流程覆盖了从宠物建档、预约挂号、医生接诊、开具处方、药房发药到会员充值消费的全链路。这意味着它不像"学生管理系统"那样只有简单的增删改查,而是存在多角色协作和状态流转:一只猫生病了,主人先在前台建档,然后约一个时间段,医生在接诊台看到排队列表,诊断后开出处方,药房根据处方扣减库存,最后主人结算费用。整个链路跑通,系统的复杂度自然就上来了,这就为论文和答辩提供了充分的素材。
再来说说这个题目适合谁来选。如果你有一定的Java基础,Spring Boot能独立写接口,MySQL会建表写SQL,那么这个题目完全可以在三到四周内完成核心功能,再用一周时间打磨前端页面和写论文。如果你是零基础转行,想靠这个题目练手,也完全可以,但建议把功能范围砍掉一半,比如不做会员充值,不做多角色权限,先把"宠物档案+预约+病历+药品"这条主线跑通,后续再慢慢加。
我见过太多人做毕设,上来就画一个大而全的系统架构图,用例图画了十几个角色,结果代码里全是空方法和TODO注释,答辩现场一演示就露馅。这个题目正确的打开方式是:主线功能做深做透,扩展功能预留接口。主线就是上述的诊疗业务流,扩展功能包括数据统计、短信通知、电子病历导出等,后者在演示录像里提一嘴"项目预留了接口"就够了。
另外说一句,这个题的"宠物"属性天然自带亲和力。答辩时老师看到你做的是宠物诊所,第一印象就不会太差,毕竟谁家里还没只猫猫狗狗呢,话题一打开,紧张感也能缓解不少。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 技术栈选型:为什么是Spring Boot + MyBatis Plus + MySQL
现在很多同学拿到题目第一反应是"用SSM还是Spring Boot?",我的建议是不要犹豫,直接用Spring Boot。原因很简单:现在是2024年,SSH、SSM这种老组合在简历上已经没有竞争力了,而Spring Boot的自动配置和Starter机制能帮你省掉大量XML配置时间,让你把精力集中在业务代码上。我自己这个项目用的是 Spring Boot 2.7.x + MyBatis Plus 3.5.x + MySQL 8.0 + Redis(可选)+ JWT(登录鉴权)+ Vue 2(前端) 的组合,这套组合是目前毕设项目里最主流、也最容易被答辩老师认可的搭配。
先说说为什么选MyBatis Plus而不是纯MyBatis。纯MyBatis的Mapper XML你要自己写大量的CRUD语句,一个宠物管理模块光增删改查就得写十几个方法,而且每个方法都要写对应的SQL,非常折磨。MyBatis Plus提供了一整套的通用Mapper接口,selectById、insert、updateById这些方法开箱即用,还支持条件构造器QueryWrapper,用起来像这样:
java复制// 查询所有状态为待接诊的预约记录
LambdaQueryWrapper<Appointment> wrapper = new LambdaQueryWrapper<>();
wrapper.eq(Appointment::getStatus, "WAIT")
.orderByAsc(Appointment::getAppointmentDate);
List<Appointment> list = appointmentMapper.selectList(wrapper);
这段代码如果换纯MyBatis,你需要先在XML里写一条带where条件的SQL,再写一个对应的Mapper接口方法,再在Service层调它,至少三处改动。但MyBatis Plus一条Lambda链式查询就搞定了,而且编译期能检查字段名,不会出现SQL写错单词直到运行才报错的情况。我强烈建议所有做毕设的同学都用这个,省下来的时间够你多写好几页论文的。
再聊聊为什么数据库选MySQL 8.0而不是5.7。MySQL 8.0默认字符集是utf8mb4,可以直接存Emoji表情,这对宠物诊所系统其实有实际意义——宠物名字里偶尔会有人输入🐱🐶这种特殊符号,如果是5.7的utf8字符集,插入就会报错。另外一个原因是8.0的窗口函数(ROW_NUMBER()等)在做数据统计报表时非常方便,比如你要统计"每月就诊量排名前五的病种",用窗口函数一条SQL就搞定,这在论文里也是个加分项。
Redis在这个项目里是可选的,但我建议你加上,因为登录鉴权这块用了JWT之后,会面临一个实际痛点:用户修改密码或管理员封禁账号后,旧的token依然有效,因为JWT是无状态的。解决办法就是把token存一份到Redis里,设置过期时间,每次请求时校验Redis中是否存在该token,这样就能实现"强制下线"。这是一个非常容易在答辩时说清楚的技术亮点,推荐大家实现一下。
前端方面,我用的是Vue 2 + Element UI。后台管理类系统用Vue 2完全够用,Element UI的表单组件、表格组件、日期选择器这些能极大提升开发效率。有人可能想上Vue 3 + Element Plus,也可以,但Vue 3的生态对新手不太友好,遇到问题网上的解决方案也大多是Vue 2的。如果是毕设,一切以"稳"为主。
这里给出一份我的项目结构,方便你对照搭建:
text复制pet-clinic/
├── pom.xml
├── src/main/java/com/petclinic/
│ ├── PetClinicApplication.java
│ ├── common/ // 统一返回结果、异常处理、常量
│ ├── config/ // 跨域配置、MyBatis Plus配置、Redis配置
│ ├── controller/ // 控制层
│ ├── service/ // 业务逻辑层
│ │ └── impl/
│ ├── mapper/ // MyBatis Plus的Mapper接口
│ ├── entity/ // 数据库实体类
│ ├── dto/ // 前端传参对象(用于接收请求数据)
│ ├── vo/ // 视图对象(用于返回给前端的数据)
│ ├── utils/ // JWT工具类、日期工具类等
│ └── security/ // 登录拦截器或Spring Security配置
└── src/main/resources/
├── application.yml
└── mapper/ // 存放自定义SQL的XML文件
这个结构的核心思想是分层清晰:Controller只做参数接收和结果返回,不写业务逻辑;Service层承载所有业务规则;Mapper层只负责数据访问。答辩时老师如果问"为什么Controller要返回统一结果集",你可以说这是为了前后端解耦,前端只需要根据code字段判断请求是否成功,然后从data字段取值渲染即可,不用关心后端异常的具体类型。
3. 数据库设计:那些关于宠物、病历、库存的表
数据库设计是答辩老师最爱深挖的地方,也是最容易暴露"工作量够不够"的环节。我见过很多同学的数据库表就五六张,每张表就三四个字段,然后跟老师说"我的系统功能很完善",这肯定会被当场问倒。宠物诊所管理系统经过几版迭代,我最终保留了十二张核心表,这里挑几张重点讲。
宠物档案表(pet)
| 字段名 | 类型 | 说明 |
|---|---|---|
| id | bigint | 主键 |
| pet_name | varchar(50) | 宠物昵称 |
| type | varchar(20) | 类型(猫/狗/兔子/仓鼠等) |
| breed | varchar(50) | 品种,如英短、金毛 |
| gender | tinyint | 性别(0未知,1公,2母) |
| birth_date | date | 出生日期 |
| weight | decimal(5,2) | 体重(kg),会随就诊记录更新 |
| owner_id | bigint | 所属主人的用户ID |
| avatar | varchar(255) | 宠物照片URL |
| status | tinyint | 状态(0正常,1死亡,2转院) |
这张表没啥技术难度,但要注意的是:宠物和主人的关系是多对一,即一个用户账号下可以挂多只宠物。这就在实体关系上告别了"用户-宠物一对一"的小儿科设计,也更符合实际情况——很多人养了两三只猫猫狗狗,带来看病时肯定希望所有宠物档案都在一个账号下面。
预约挂号表(appointment)
| 字段名 | 类型 | 说明 |
|---|---|---|
| id | bigint | 主键 |
| appointment_no | varchar(32) | 预约单号,如AP20240223001 |
| pet_id | bigint | 宠物ID |
| doctor_id | bigint | 医生ID |
| appointment_date | date | 预约日期 |
| time_slot | varchar(10) | 时间段,如"09:00-09:30" |
| type | tinyint | 类型(1普通门诊,2急诊,3疫苗接种) |
| status | tinyint | 状态(0待接诊,1已接诊,2已取消,3已完成) |
| remark | varchar(255) | 备注 |
预约时间段的处理是这个项目的关键难点,也是最容易在答辩时被追问的点。我在设计时采用了一个比较稳妥的方案:系统统一维护每天的时间段列表,比如上午9:00到12:00,每30分钟一个时段,全天一共14个时段。医生在排班时选择自己可用的时段,用户在预约时只能选择"该医生该日期下尚未被约满的时段"。这样就把"时间段冲突"问题转化为了"两个可用时段列表的比较问题",逻辑清晰,数据库也不用做复杂的区间重叠判断。
就诊病历表(medical_record)
| 字段名 | 类型 | 说明 |
|---|---|---|
| id | bigint | 主键 |
| appointment_id | bigint | 关联预约ID |
| pet_id | bigint | 宠物ID |
| doctor_id | bigint | 医生ID |
| diagnosis | text | 诊断内容 |
| symptom | text | 症状描述 |
| temperature | decimal(4,1) | 体温 |
| treatment_plan | text | 治疗方案 |
| create_time | datetime | 就诊时间 |
病历表是整个系统的"信息枢纽",它关联了预约、宠物、医生、处方多个维度。我在前端设计了一个"时间轴"组件,可以按时间顺序展示某只宠物历次就诊记录,主人点击某条记录就能看到当时的诊断详情、开的什么药、花了多少钱。这个体验做出来后,演示效果非常加分,因为很多管理系统只是平铺式列表展示,没有"按宠物视角聚合"的设计。
处方表(prescription)和处方明细表(prescription_item)
这两张表是典型的主从表结构。处方表记录一次开药动作的整体信息:处方编号、关联病历ID、总金额、开单医生;处方明细表记录具体开了哪些药、每种药的用量、用法:药品ID、数量、单价、每次剂量、频次(每日几次)。这种设计在电商系统里也常见(订单和订单项),答辩时你可以主动说明"这里参考了订单模型的设计思想,因为一张处方本质就是一张包含多个明细项的'药品订单'"。
药品表(drug)和库存变动表(stock_log)
药品表记录药品名称、规格、生产厂家、批号、保质期、进货价、零售价、当前库存量。药品表本身很简单,关键在库存变动表:
sql复制CREATE TABLE stock_log (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
drug_id BIGINT NOT NULL COMMENT '药品ID',
change_type TINYINT NOT NULL COMMENT '变动类型:1入库,2出库,3盘点调整',
change_count INT NOT NULL COMMENT '变动数量(正数为入库,负数为出库)',
before_stock INT NOT NULL COMMENT '变动前库存',
after_stock INT NOT NULL COMMENT '变动后库存',
operator_id BIGINT NOT NULL COMMENT '操作人',
create_time DATETIME NOT NULL COMMENT '变动时间'
) COMMENT '药品库存变动日志表';
为什么需要单独一张库存变动表?因为如果只在drug表的stock字段上直接加减,等你想查"这个月某种感冒药出库了多少"时,就只能拍脑袋了。有了stock_log,每一次出入库都有据可查,也方便做药品效期预警和进销存统计。我从一开始就坚持加这张表,后来写论文时把"基于变动日志的库存追溯"作为一个小创新点写进去了,老师觉得这个设计思路很好。
4. 核心功能模块的实现:从登录鉴权到预约看诊
4.1 三种角色怎么共用一套登录接口
宠物诊所系统里有三类用户:管理员(前台/药房)、医生、宠物主人。如果为三种角色分别写三个登录接口,代码重复不说,登录后的身份管理也很麻烦。我的做法是设计了一张统一的user表,用role字段区分角色:
java复制@Data
@TableName("user")
public class User {
private Long id;
private String username;
private String password; // BCrypt加密存储
private String realName;
private String phone;
private Integer role; // 1管理员 2医生 3主人
private String avatar;
private Integer status; // 0正常 1禁用
}
用户登录成功后,后端返回一个JWT token,token的payload里包含userId和role。前端拿到token后存入localStorage,后续每个请求都带上Authorization: Bearer <token>头。后端通过拦截器解析token,从Redis里取出用户信息,放行或拒绝。
这里有一个值得注意的点:医生和主人虽然都是登录用户,但能访问的接口完全不同。医生能访问"待接诊列表"、"我的排班",主人能访问"我的宠物"、"我的预约"。所以拦截器里要做@RequireRole这样的自定义注解,在接口上标注允许访问的角色:
java复制@GetMapping("/doctor/appointments")
@RequireRole({2}) // 仅医生角色可访问
public Result getDoctorAppointments() {
// ...
}
用注解做角色控制,好处是直观清晰,Controller方法上扫一眼就知道谁能调这个接口,答辩讲解时也容易说清楚。
4.2 预约时间段的冲突处理
我前文提到,系统维护了固定的时间段列表,那么实现预约功能时的核心逻辑就是"医生排班"和"用户预约"两个环节。
医生排班是预约的前置操作。医生(或管理员)选择日期,勾选可用时段,后端保存到doctor_schedule表。如果某天没有排班,用户在前端就看不到该医生的可约时段,相当于强制医生提前规划门诊时间。
用户预约时,后端要做两步校验:
java复制public Result createAppointment(AppointmentDto dto) {
// 第一步:校验该时段是否在医生排班中
int count = doctorScheduleMapper.checkIfAvailable(
dto.getDoctorId(), dto.getAppointmentDate(), dto.getTimeSlot());
if (count <= 0) {
return Result.error("该医生此时间段未排班");
}
// 第二步:校验该时段是否已被预约
int booked = appointmentMapper.countByDoctorAndTime(
dto.getDoctorId(), dto.getAppointmentDate(), dto.getTimeSlot());
if (booked >= 1) {
return Result.error("该时间段已被预约,请选择其他时间");
}
// 生成预约单号并保存
dto.setAppointmentNo(generateNo());
appointmentMapper.insert(dto);
return Result.success();
}
你可能会说,这不就是两步查询加一次插入吗,有什么难的?确实,单机场景下这段逻辑没问题。但答辩时老师可能会追问"如果两个用户同时点击同一个时段的预约,怎么办?"这时候你就可以回答"使用数据库唯一索引兜底"。我在appointment表上建了一个联合唯一索引:
sql复制ALTER TABLE appointment
ADD UNIQUE INDEX uk_doctor_time (doctor_id, appointment_date, time_slot);
这样一来,即使业务层有并发问题,数据库层面也会拒绝第二条插入。当时我在演示时手动开了两个浏览器窗口同时提交,只有一个能成功,另一个直接报"该时间段已被预约"。这种"双保险"的设计思路,在项目里多体现几次,论文质量会明显提升。
4.3 接诊与病历录入
预约状态为"待接诊"时,医生端会展示候诊队列。医生点击"开始接诊"后,预约状态变为"已接诊",同时创建一条空的medical_record记录。接着医生填写症状、诊断、治疗方案,并从前端药品选择器中勾选药品,系统自动将这些药品添加到处方明细中。
这个流程里的一个小设计是体温和体重的联动更新:医生在病历中录入宠物当前体温和体重后,系统会自动更新pet表中的体重字段。这个功能虽然在技术上只是一条update语句,但在演示时很能体现"系统不是死板的登记工具,而是贴合实际业务"的设计理念。
关于处方金额的计算,注意要用BigDecimal而不是Double来计算总价。比如某种药单价19.9元,数量3盒,用Double算出来的总价可能是59.699999999999996。虽然页面展示时可能通过格式化不可见,但在数据库存储和之后的统计报表中就会出现小额差异,累积多了很难排查。我的做法是所有涉及金额计算的字段一律用BigDecimal,计算用multiply()和add()方法:
java复制BigDecimal totalPrice = detailList.stream()
.map(item -> item.getPrice().multiply(new BigDecimal(item.getQuantity())))
.reduce(BigDecimal.ZERO, BigDecimal::add);
4.4 发药与库存扣减
医生开完处方,主人在前台结算后,药房人员才能在药房模块看到待发药列表。点击"确认发药"后,系统执行库存扣减。这里有一个需要注意的问题:药房工作人员有可能在发药时发现某种药库存不足,这时候怎么办?
我在系统里设计了"异常发药"流程:当库存不足时,发药操作会被拒绝,系统自动生成一条缺药记录,并提示药房人员可以走"部分发放"流程——拆开处方明细,先发有库存的药品,缺的药品在备注中标注"待补发"。这个设计是从真实药房场景中提炼出来的,因为宠物医院经常遇到某款药品断货的情况,一刀切地拒绝发药会让整个流程卡死。
不过说实话,最初版我并没有做"部分发放",是在一次实际试用中,朋友拿着处方去药店拿药,结果被告知某种药缺货,只能先拿其他药回去,我才意识到这个业务场景的真实存在。后来在论文里我还专门写了一段关于"异常流程的处理"的分析。
4.5 会员管理与充值
会员模块原本是我计划中的"扩展功能",后来发现它其实挺核心的,因为宠物医院很依赖充值会员这种锁定客源的模式。我做的会员功能很简单:用户表上扩展了balance字段,主人登录后可以给账号充值,充值后balance累加;结算时如果选择会员余额支付,直接从balance中扣减。
为了防止充值并发问题,我用Redis来实现一个简单的分布式锁(虽然单机部署用synchronized也行,但为了论文技术亮点,牺牲一点性能换故事性是完全值得的):
java复制public Result recharge(Long userId, BigDecimal amount) {
String lockKey = "recharge_lock:" + userId;
boolean locked = redisTemplate.opsForValue().setIfAbsent(lockKey, "1",
Duration.ofSeconds(3));
if (!locked) {
return Result.error("操作频繁,请稍后再试");
}
try {
// 查余额,累加,更新数据库
return Result.success();
} finally {
redisTemplate.delete(lockKey);
}
}
有人可能觉得这是过度设计,但我的看法是:毕设项目和企业项目的区别,恰恰就在于你是否有机会把理论课上学到的分布式、并发、缓存这些概念落地到一个具体业务场景中。答辩时把这段代码讲出来,说"这里我使用Redis分布式锁防止用户重复提交充值请求导致余额错误",老师很难不给高分。
5. 踩坑实录:我在这项目里折过的那些跟头
5.1 时间格式化导致的前后台"时差8小时"
一个很经典的问题。前端选了"2024-02-23 09:00"传给后端,后端存入MySQL后,前端再查询出来显示变成了"2024-02-23 01:00"。时间差了8个小时,用户肯定会找你麻烦。
问题根源是时区配置不一致。Spring Boot默认的Jackson格式化时,如果不是显式指定时区,会使用服务器本地时区;而数据库连接串如果没加serverTimezone=Asia/Shanghai,MySQL驱动会按系统默认时区处理,两边一错位就差了8小时。
解决方法是三处保持一致:application.yml里设置spring.jackson.time-zone: GMT+8,MySQL的连接URL加上serverTimezone=Asia/Shanghai,同时实体类里的日期字段加上@JsonFormat注解:
java复制@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
private LocalDateTime createTime;
后来我干脆把实体类里的Date全部换成了LocalDateTime,配合MyBatis Plus的自动填充功能,插入和更新时自动填时间,省心不少。
5.2 MyBatis Plus的saveOrUpdate不是万能灵药
刚开始做宠物档案编辑功能时,我觉得saveOrUpdate一句调用就能搞定新增和修改,但真实业务中"新增"和"编辑"往往联动的逻辑不同:新增宠物时要在user_pet关联表中插入关系记录;编辑时则可能需要同步更新主人信息。如果都走saveOrUpdate,很容易把"应该单独处理"的业务逻辑混在一起。
最后我的方案是:明确区分createPet()和updatePet()两个Service方法,前者的Controller入参是PetCreateDto,后者的入参是PetUpdateDto,两个DTO的必填字段校验各不相同。哪怕内部逻辑有重叠,也要先分成两个方法写清楚,为了代码可读性,适当的重复是可以接受的。
5.3 图片上传的绝对路径问题
宠物档案和用户头像都涉及图片上传。我最初把图片保存在本地的/user/upload/这种绝对路径下,前端页面写死访问http://localhost:8080/user/upload/xxx.jpg。这在自己电脑上跑没问题,但只要把项目部署到服务器或换一台电脑,路径就全变了,图片全部404。
后来改用相对路径:在application.yml中配置file.upload-path=./upload/,上传时动态拼接可访问的URL前缀存入数据库,前端始终通过相对路径访问。同时用WebMvcConfigurer做虚拟路径映射:
java复制@Configuration
public class WebConfig implements WebMvcConfigurer {
@Value("${file.upload-path}")
private String uploadPath;
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("/upload/**")
.addResourceHandler("file:" + uploadPath);
}
}
这样不管项目部署在哪里,只要保证配置文件里的路径正确,图片就能正常访问。这个坑我在调试时花了整整一个下午才解决,当时还以为是前端跨域问题。
5.4 联表查询里的小表驱动大表
药品模块中,我需要在列表页展示每种药品的当前库存和最近一次入库时间。最开始的写法是用List<Drug>拿到所有药品后,在Java代码里循环查每张表的库存变动记录,结果药品少还好,药品一多,N+1查询就把接口拖得很慢。
后来优化为一条SQL联表查询,用一个子查询查出每种药品最近一次的变动记录:
sql复制SELECT d.id, d.name, d.stock,
(SELECT MAX(sl.create_time) FROM stock_log sl WHERE sl.drug_id = d.id) AS last_change_time
FROM drug d
ORDER BY d.id DESC;
虽然这在数据量小的时候看不出什么性能差异,但至少在答辩时可以理直气壮地说"我关注了查询性能,避免了循环查库"。面试官听不听得懂是一回事,你有没有这个意识是另一回事。
5.5 演示环境账号密码没提前准备
这个不算技术坑,但非常容易在演示录像时翻车。最开始录演示视频时,我临时输入账号密码,手抖打错两次,录出来的视频开头就非常尴尬。后来我专门准备了一套演示专用账号:管理员admin、医生doctor01、用户user01,密码统一用123456,并且在录视频前先把三端的功能过一遍,确保所有测试数据都处于"能演示"的状态。有时候"细节决定成败",一件很简单的事,做好了能让项目演示干净利落。
6. 演示录像怎么录、答辩怎么讲:给毕设人的实操建议
6.1 演示视频的脚本编排
很多人的演示录像就是登录系统后瞎点一通,鼠标在桌面上扫来扫去,视频录了十分钟,前五分钟都在加载页面或者发呆。这其实是毕设演示的大忌。我的建议是提前写好脚本,把演示分成四幕:
第一幕是系统整体展示,大概15秒,展示系统名称、首页、主导航。第二幕是管理员视角,演示宠物档案列表的增删改查、医生排班设置、药品库存查看,这部分控制在2分钟以内。第三幕是用户视角,用user01登录,演示新增宠物、预约挂号、查看就诊记录,这部分是整个视频的核心,建议控制在3分钟左右。第四幕是医生视角,演示接诊、写病历、开处方、发药,再回到用户端看结算和费用明细,把完整闭环走完。
视频中不需要把每个页面停留太久,重点在于用最短的时间把完整业务流程串起来。我用的录屏工具是OBS Studio,免费且支持1080P,录的时候把鼠标移动的路径调慢一点,让观众能看清操作逻辑。
6.2 答辩时几个经典问题的应答思路
答辩老师喜欢问这么几个问题:数据库为什么这么设计?系统安全性怎么样?有什么创新点?每个问题都要提前准备应答思路。
数据库设计的问题,核心是讲清主外键关系和关键表的索引设计。比如你设计了一个uk_doctor_time的唯一索引,就可以说"这个索引确保了同一个医生在同一时间段只能有一条预约记录,从数据库层面解决了并发预约的冲突问题"。
系统安全性的问题,从三个层面回答:第一是传输层安全,前端通过JWT携带凭证,密码使用BCrypt加盐哈希存储,即使数据库泄露,攻击者也无法直接得到明文密码;第二是接口层安全,使用拦截器统一校验token有效性,并对不同角色做了接口访问控制;第三是业务层安全,比如使用Redis分布式锁防止用户重复提交,库存扣减操作在事务中执行,防止数据不一致。
创新点的问题,可以从"异常流程设计"切入:比如发药时库存不足会进入部分发放流程,用户取消预约会自动释放医生时间段,这些业务细节不是照着课本抄的,而是从实际场景中提炼出来的。这种回答会比"我用了Vue和Spring Boot"更有说服力。
6.3 如何把这份代码改造成"你的原创"
源码可以拿别人的,但答辩项目必须是"你自己的"。这里的"自己"不是指每个字符都自己敲,而是指你对系统的每个模块都能讲清楚设计理由,并且能在其中加入自己的个性化需求。具体来说有这几步:
第一,把项目跑起来后,先通读核心模块的代码,特别是预约、病历、库存三块,读懂状态流转的逻辑。第二,找到至少一个可以自己动手改的功能点,比如把短信通知换成邮件通知,或者增加一个"疫苗提醒"功能:系统根据宠物上次疫苗接种日期,自动计算下次到期时间,在主人登录首页时进行提醒。这个功能改动不大,但属于典型的个性化需求。第三,替换所有测试数据,把"测试猫""测试狗"换成带有你自己风格的宠物名和品种,截图和演示时会更自然。第四,把项目部署到云服务器上,用公网IP访问,虽然多花几十块钱,但演示时直接在浏览器里打开线上地址,比本地localhost要专业得多。
我一直觉得,毕设不只是为了拿一个学分,它更像是一次浓缩版的"需求分析-系统设计-编码实现-测试验证"的完整技术实习。在这个宠物诊所项目里,我学到的并不只是Spring Boot和Vue,更重要的是如何把一个模糊的业务需求拆解成可实现的数据库表和接口。那种"原来业务里最麻烦的不是写代码,而是搞清楚流程和边界"的体会,是任何教程都教不会的。如果你正拿着这份源码做参考,希望你也能在它的基础上,加上你自己的思考,做出一份真正属于你自己的作品。
