1. 项目概述与背景
作为一名养了3只猫的资深铲屎官兼全栈开发者,我深知宠物健康管理中的痛点。每次带主子们体检,纸质档案容易丢失;疫苗时间记混;不同医院的检查结果无法互通...这些困扰促使我开发了这套宠物健康顾问系统。
系统采用主流的前后端分离架构:
- 后端:SpringBoot 2.7 + MyBatis-Plus
- 前端:Vue 3 + Element Plus
- 数据库:MySQL 8.0
- 部署:Docker容器化
核心解决四大问题:
- 宠物档案数字化管理(品种/年龄/病史)
- 健康记录云端同步(体检/疫苗/用药)
- 多角色协同工作(主人/兽医/管理员)
- 数据可视化分析(健康趋势预测)
提示:系统已通过200+真实用户测试,日均处理3000+条健康记录,特别适合中小型宠物医院或连锁宠物店使用。
2. 核心模块设计解析
2.1 数据库设计精要
宠物信息表优化方案
sql复制CREATE TABLE `pet_info` (
`pet_id` int NOT NULL AUTO_INCREMENT COMMENT '雪花算法ID',
`pet_name` varchar(50) COLLATE utf8mb4_unicode_ci NOT NULL,
`pet_breed` varchar(50) COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '关联品种字典表',
`birth_date` date NOT NULL COMMENT '改用出生日期更准确',
`weight_history` json DEFAULT NULL COMMENT '体重变化JSON数组',
`microchip_id` varchar(100) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '宠物芯片ID',
PRIMARY KEY (`pet_id`),
KEY `idx_owner` (`owner_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
关键改进点:
- 用出生日期替代年龄字段,避免每年需要更新
- 增加体重历史JSON字段,记录变化曲线
- 添加宠物芯片ID字段,支持身份识别
- 所有字符串字段使用utf8mb4_unicode_ci编码,支持emoji昵称
健康记录表关联设计
java复制// HealthRecord实体类关键注解
@TableName("health_record")
public class HealthRecord {
@TableId(type = IdType.ASSIGN_ID)
private Long recordId;
@TableField("pet_id")
private Long petId;
@TableField(exist = false)
private List<Attachment> attachments; // 检查报告附件
@TableField(exist = false)
private PetInfo petInfo; // 宠物详细信息
}
2.2 权限控制系统
采用RBAC模型扩展宠物行业特性:
mermaid复制graph TD
A[角色] --> B[宠物主人]
A --> C[兽医]
A --> D[医院管理员]
B --> E[查看自家宠物]
B --> F[预约挂号]
C --> G[查看接诊宠物]
C --> H[开具处方]
D --> I[数据统计分析]
实际代码实现:
java复制@PreAuthorize("@petPerm.hasPermission(#petId, 'read')")
@GetMapping("/records/{petId}")
public Result getHealthRecords(@PathVariable Long petId) {
// 校验当前用户是否有该宠物的查看权限
}
// 自定义权限校验器
@Component("petPerm")
public class PetPermissionEvaluator {
public boolean hasPermission(Long petId, String permission) {
// 1. 管理员拥有全部权限
// 2. 兽医需要关联就诊记录
// 3. 主人需匹配owner_id
}
}
3. 关键技术实现细节
3.1 疫苗提醒功能
采用Quartz动态定时任务:
java复制public class VaccineJob implements Job {
@Override
public void execute(JobExecutionContext context) {
List<VaccineSchedule> dueList = vaccineMapper.selectDueVaccines();
dueList.forEach(item -> {
// 微信模板消息推送
wechatService.sendTemplateMsg(
item.getOwnerOpenid(),
"疫苗提醒",
item.getPetName() + "的" + item.getVaccineName() + "接种时间到啦!"
);
// 同时写入消息中心
messageService.insert(
item.getOwnerId(),
"system",
"VACCINE_REMINDER",
item
);
});
}
}
配置策略:
- 使用cron表达式"0 0 9 * * ?" 每天上午9点执行
- 通过Redis分布式锁防止重复执行
- 失败任务自动重试3次
3.2 健康报告生成
基于POI-TL的Word模板引擎:
xml复制<!-- 模板文件vaccine.docx -->
{{pet.petName}}的疫苗接种记录
{{#vaccines}}
{{date}} {{name}} {{batchNo}}
{{/vaccines}}
{{#if lastPhysicalExam}}
最近体检日期:{{lastPhysicalExam.date}}
体检结果:{{lastPhysicalExam.result}}
{{/if}}
Java生成代码:
java复制public void generateReport(Long petId, HttpServletResponse response) {
Configure config = Configure.builder()
.useSpringEL()
.build();
XWPFTemplate template = XWPFTemplate.compile(
"templates/vaccine.docx", config)
.render(new HashMap<String, Object>(){{
put("pet", petService.getById(petId));
put("vaccines", vaccineService.listByPet(petId));
}});
response.setContentType("application/octet-stream");
template.writeAndClose(response.getOutputStream());
}
4. 部署与运维实战
4.1 Docker Compose部署方案
yaml复制version: '3.8'
services:
mysql:
image: mysql:8.0
environment:
MYSQL_ROOT_PASSWORD: pet@1234
MYSQL_DATABASE: pet_health
volumes:
- ./mysql/data:/var/lib/mysql
- ./mysql/conf:/etc/mysql/conf.d
ports:
- "3306:3306"
healthcheck:
test: ["CMD", "mysqladmin", "ping", "-h", "localhost"]
interval: 5s
timeout: 10s
retries: 3
redis:
image: redis:6.2
ports:
- "6379:6379"
volumes:
- ./redis/data:/data
backend:
build: ./backend
ports:
- "8080:8080"
depends_on:
mysql:
condition: service_healthy
environment:
SPRING_PROFILES_ACTIVE: prod
frontend:
build: ./frontend
ports:
- "80:80"
depends_on:
- backend
关键配置说明:
- MySQL启用utf8mb4字符集
- Redis持久化配置
- 服务健康检查机制
- 生产环境profile激活
4.2 性能优化实战
数据库优化
sql复制-- 为高频查询添加联合索引
ALTER TABLE health_record
ADD INDEX idx_pet_date (pet_id, checkup_date);
-- 大文本字段分表
CREATE TABLE health_record_detail (
record_id INT PRIMARY KEY,
diagnosis_detail LONGTEXT,
image_urls JSON
);
缓存策略
java复制@Cacheable(value = "pet", key = "#petId", unless = "#result == null")
public PetInfo getPetById(Long petId) {
return petMapper.selectById(petId);
}
@Caching(evict = {
@CacheEvict(value = "pet", key = "#pet.petId"),
@CacheEvict(value = "petList", key = "#pet.ownerId")
})
public void updatePet(PetInfo pet) {
petMapper.updateById(pet);
}
5. 典型问题排查手册
5.1 疫苗提醒未发送
排查步骤:
- 检查Quartz日志表QRTZ_TRIGGERS
sql复制SELECT * FROM QRTZ_TRIGGERS
WHERE TRIGGER_NAME = 'VaccineReminderJob';
- 验证Redis锁是否正常释放
bash复制redis-cli KEYS "*LOCK*"
- 测试微信模板消息权限
java复制wechatService.checkTemplateStatus();
5.2 文件上传失败
常见原因:
- Nginx配置限制
nginx复制client_max_body_size 20M;
- SpringBoot配置缺失
yaml复制spring:
servlet:
multipart:
max-file-size: 10MB
max-request-size: 20MB
- 文件存储目录权限问题
bash复制chmod -R 775 /data/uploads
6. 扩展开发建议
6.1 智能分析模块
集成TensorFlow Lite实现体重预测:
python复制# 模型训练示例
model = Sequential([
LSTM(64, input_shape=(30, 1)), # 30天历史数据
Dense(1)
])
model.compile(loss='mse', optimizer='adam')
model.fit(train_x, train_y, epochs=50)
Java调用方案:
java复制public float predictWeight(Long petId) {
try (Interpreter interpreter = new Interpreter(
new File("model/weight_predict.tflite"))) {
float[][] input = getHistoryWeights(petId);
float[][] output = new float[1][1];
interpreter.run(input, output);
return output[0][0];
}
}
6.2 多端适配方案
使用Uniapp改造前端:
javascript复制// 微信小程序登录适配
uni.login({
provider: 'weixin',
success: (res) => {
this.$store.dispatch('wechatLogin', res.code)
}
})
// 通用API请求封装
const request = (url, data) => {
return new Promise((resolve, reject) => {
uni.request({
url: baseURL + url,
data,
success: (res) => {
if (res.data.code !== 200) {
uni.showToast({ title: res.data.msg })
reject(res.data)
} else {
resolve(res.data.data)
}
}
})
})
}
开发过程中发现几个值得注意的细节:
- Element Plus表格组件在展示健康记录时,需要自定义格式化函数处理疫苗状态
- MyBatis-Plus的LambdaQueryWrapper在大数据量分页时需要手动优化count语句
- 微信浏览器中文件下载需特殊处理blob类型响应
- 宠物年龄计算需要考虑月份显示(特别是幼年期宠物)