1. 项目概述:当SpringBoot遇上宠物医疗管理
去年帮朋友改造他那家经营了五年的宠物诊所时,我深刻体会到传统纸质档案的痛点:预约信息错漏、病历查询耗时、药品库存混乱。这正是我们开发这套系统的初衷——用SpringBoot技术栈为中小型宠物医院打造轻量级数字化解决方案。不同于通用医疗系统,我们针对宠物医疗特有的疫苗追踪、宠物档案、美容服务等场景做了深度定制。
系统采用经典三层架构,前端Vue+ElementUI实现响应式布局,后端SpringBoot 2.7.0+MyBatis-Plus 3.5.2构建RESTful API,数据库选用MySQL 8.0兼顾事务性能与成本。特别在权限控制方面,采用Shiro+JWT实现细粒度的菜单和按钮级权限,确保兽医、美容师、前台等不同角色只能访问对应功能模块。
关键设计原则:模块化(可单独部署预约/医疗/库存模块)、无状态化(方便横向扩展)、配置化(疫苗周期等参数可动态调整)
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 核心模块设计与技术实现
2.1 宠物档案管理子系统
采用DDD领域驱动设计,将"宠物"聚合根拆解为:
- 基础信息(芯片号、品种、生日)
- 健康档案(过敏史、既往病史)
- 监护人信息(多主人支持)
- 影像资料(X光片等附件存储)
技术亮点:
java复制// 使用MyBatis-Plus的TypeHandler处理枚举
@TableField(typeHandler = PetGenderTypeHandler.class)
private Gender gender;
// 动态SQL构建病例查询
LambdaQueryWrapper<MedicalRecord> wrapper = Wrappers.lambdaQuery();
wrapper.eq(MedicalRecord::getPetId, petId)
.between(MedicalRecord::getVisitDate, startDate, endDate);
2.2 智能预约调度引擎
解决传统宠物医院的三大痛点:
- 急诊插队逻辑:通过优先级队列实现
- 医生专长匹配:标签化医生技能(如"猫科专家")
- 资源冲突检测:基于时间片的诊室占用算法
核心表设计:
sql复制CREATE TABLE `appointment` (
`id` bigint NOT NULL AUTO_INCREMENT,
`pet_id` bigint NOT NULL COMMENT '关联宠物ID',
`doctor_id` int DEFAULT NULL COMMENT '指定医生ID',
`time_slot` varchar(20) NOT NULL COMMENT '时间段编码',
`status` enum('PENDING','CONFIRMED','CANCELED') DEFAULT 'PENDING',
`emergency_level` tinyint DEFAULT '0' COMMENT '0-普通 1-紧急',
`create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
KEY `idx_doctor_time` (`doctor_id`,`time_slot`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
2.3 药品库存智能预警
结合宠物医疗特点实现的特色功能:
- 批次管理:关联疫苗有效期
- 拆零销售:支持药品分装记录
- 智能补货:基于历史消耗的预测模型
库存检查核心逻辑:
java复制public boolean checkInventory(Long medicineId, BigDecimal amount) {
MedicineStock stock = stockMapper.selectById(medicineId);
if (stock == null) throw new BizException("药品不存在");
// 考虑拆零库存的情况
BigDecimal available = stock.getTotalAmount()
.subtract(stock.getLockedAmount());
return available.compareTo(amount) >= 0;
}
3. 关键技术深度解析
3.1 多租户数据隔离方案
针对连锁宠物医院场景,我们测试了三种方案:
- 独立数据库:成本高但隔离彻底
- Schema分离:中等运维复杂度
- 字段标记:最终采用的方案,通过tenant_id字段过滤
实现代码示例:
java复制@Configuration
public class MybatisPlusConfig {
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
// 添加租户处理器
interceptor.addInnerInterceptor(new TenantLineInnerInterceptor(
new TenantLineHandler() {
@Override
public String getTenantIdColumn() {
return "tenant_id";
}
@Override
public Expression getTenantId() {
return new LongValue(1); // 实际从上下文中获取
}
}
));
return interceptor;
}
}
3.2 宠物健康数据分析
利用SpringBoot Schedule定时分析:
- 品种易发病统计
- 季节性疾病预测
- 疫苗接种到期提醒
健康评分计算模型:
java复制public PetHealthScore calculateHealthScore(Long petId) {
List<MedicalRecord> records = recordMapper.selectByPetId(petId);
// 基础分
double score = 100.0;
// 最近3次就诊扣分规则
records.stream().limit(3).forEach(record -> {
score -= record.getDiseaseLevel() * 5;
if ("手术".equals(record.getTreatmentType())) {
score -= 10;
}
});
// 疫苗接种加分
long vaccineCount = vaccineMapper.countByPetId(petId);
score += Math.min(vaccineCount * 2, 10);
return new PetHealthScore(petId, Math.max(score, 0));
}
4. 系统部署与性能优化
4.1 混合部署方案
经过压力测试,我们最终采用:
- 开发环境:IDEA内置Tomcat
- 测试环境:Docker Compose(MySQL+Redis+应用)
- 生产环境:Kubernetes集群(3节点)
关键部署配置:
yaml复制# application-prod.yml
spring:
datasource:
url: jdbc:mysql://mysql-cluster:3306/pet_hospital?useSSL=false
hikari:
maximum-pool-size: 20
connection-timeout: 30000
redis:
cluster:
nodes: redis-01:6379,redis-02:6379,redis-03:6379
4.2 性能优化实战记录
-
慢SQL治理:
- 为宠物档案表添加了组合索引 (owner_id, create_time)
- 将病历详情从主表拆分到大字段表
-
缓存策略:
- 使用Redis二级缓存医生排班表
- 药品基础信息本地缓存30分钟
-
接口优化:
- 预约列表接口从15s降到800ms
- 采用Hutool的Excel导出替代POI
优化前后对比数据:
| 场景 | 优化前 | 优化后 |
|---|---|---|
| 同时发起100个预约 | 22s | 3.2s |
| 导出年度病历统计 | 内存溢出 | 45s完成 |
| 高峰期系统CPU占用 | 85% | 45% |
5. 典型问题排查实录
5.1 疫苗提醒重复发送
现象:客户反映收到3次相同的疫苗提醒
排查过程:
- 检查定时任务日志发现单次执行正常
- 查看K8s Pod数量扩容到了3个实例
- 确认@Scheduled未配置分布式锁
解决方案:
java复制@Scheduled(cron = "0 0 9 * * ?")
public void sendVaccineReminder() {
if (lock.tryLock("vaccine-reminder", 60)) {
try {
// 业务逻辑
} finally {
lock.unlock();
}
}
}
5.2 库存扣减并发问题
复现步骤:
- 美容师A查看洗发水剩余5瓶
- 美容师B同时查看也是5瓶
- 都发起3瓶的领用申请
- 最终库存变为-1瓶
最终采用方案:
sql复制UPDATE medicine_stock
SET amount = amount - 3
WHERE id = 1001 AND amount >= 3
配合业务层重试机制:
java复制int retry = 0;
while (retry < 3) {
int updated = medicineMapper.deductStock(id, amount);
if (updated > 0) break;
retry++;
Thread.sleep(100);
}
6. 扩展与演进方向
目前正在实施的改进:
- 接入微信小程序实现主人端自助服务
- 试验Spring AI实现智能问诊辅助
- 用ELK搭建诊疗知识库
对于想自行开发的同行,建议从最小闭环开始:
- 先实现核心的宠物档案+预约功能
- 再逐步添加药品管理和财务模块
- 最后考虑多门店和数据分析
项目中的几个经验之谈:
- 宠物体重单位要同时支持kg和lbs
- 品种数据建议预置国际标准分类
- 病历模板最好支持诊所自定义
- 一定要做宠物头像压缩功能
