1. 项目背景与核心需求
作为一名在Java领域摸爬滚打多年的开发者,我最近完成了一个宠物管理系统的全栈项目——云宠之家。这个系统的诞生源于几个现实的痛点:我身边越来越多的朋友开始养宠物,但经常遇到宠物信息管理混乱、领养流程不透明、健康记录丢失等问题。传统的Excel记录方式已经无法满足现代宠物主的需求,而市面上的商业软件要么功能过剩,要么价格昂贵。
这个系统采用SpringBoot+Vue的主流技术栈,主要解决三类核心问题:
- 宠物信息数字化管理:将宠物档案(疫苗记录、病历、行为特征等)从纸质文档迁移到云端
- 标准化领养流程:建立从申请、审核到签约的完整线上流程
- 多终端协同:支持PC端管理后台和移动端用户界面,数据实时同步
技术选型思考:为什么选择SpringBoot+Vue?
- 后端需要快速构建RESTful API → SpringBoot的自动配置和起步依赖能极大提升开发效率
- 前端需要响应式界面 → Vue的组件化开发适合构建动态用户界面
- 团队技术栈匹配 → 团队成员对这两个框架最熟悉,降低学习成本
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 系统架构设计
2.1 整体技术架构
系统采用典型的前后端分离架构,分为四个逻辑层:
code复制[前端展示层] Vue3 + Element Plus
↑
[API网关层] Spring Cloud Gateway
↑
[业务逻辑层] SpringBoot 2.7 + MyBatis Plus
↑
[数据持久层] MySQL 8.0 + Redis 6.2
关键设计决策:
- 接口幂等性设计:所有写操作接口都添加了@Idempotent注解,防止重复提交
- 分布式会话管理:采用Redis存储JWT token,解决集群环境下的会话同步问题
- 文件存储方案:使用MinIO搭建私有对象存储,与阿里云OSS API兼容,便于后续迁移
2.2 数据库设计要点
宠物管理系统的数据模型有几个特殊设计:
sql复制CREATE TABLE `pet` (
`id` BIGINT NOT NULL AUTO_INCREMENT,
`name` VARCHAR(50) NOT NULL COMMENT '宠物名称',
`qr_code` VARCHAR(100) UNIQUE COMMENT '唯一二维码标识',
`genetic_info` JSON COMMENT '基因信息(结构化存储)',
`behavioral_traits` SET('friendly','shy','aggressive') COMMENT '行为特征多选',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
避坑指南:宠物行为特征字段为什么用SET类型?
- 传统方案是用逗号分隔的字符串,但查询效率低
- 改用SET类型后可以直接使用FIND_IN_SET()函数优化查询
- 注意:SET类型有64个成员的限制,适合固定选项的场景
3. 核心功能实现细节
3.1 宠物档案管理模块
3.1.1 多维度信息采集
宠物信息采集界面包含以下几个关键组件:
- 基础信息表单:采用Element Plus的Form组件,支持动态添加字段
- 健康记录时间轴:基于Vue-Calendar组件二次开发
- 图片上传组件:集成Cropper.js实现图片裁剪和压缩
后端接口实现示例:
java复制@PostMapping("/pets")
@Idempotent(expireTime = 30, timeUnit = TimeUnit.SECONDS)
public Result addPet(@Valid @RequestBody PetDTO petDTO) {
// 二维码生成逻辑
String qrCode = QRCodeUtil.generate(petDTO.getName());
petDTO.setQrCode(qrCode);
// 基因信息压缩存储
if(petDTO.getGeneticInfo() != null) {
String compressed = GzipUtil.compress(petDTO.getGeneticInfo());
petDTO.setGeneticInfo(compressed);
}
return Result.success(petService.save(petDTO));
}
3.1.2 疫苗提醒功能
采用Spring Schedule实现定时提醒:
- 每天凌晨2点扫描vaccine_records表
- 对7天内即将到期的疫苗记录生成提醒任务
- 通过阿里云短信服务发送提醒
配置示例:
properties复制# application.properties
spring.task.scheduling.pool.size=5
pet.vaccine.remind.days=7
sms.template.code=VACCINE_REMINDER
3.2 领养流程引擎
3.2.1 状态机设计
领养流程采用状态机模式管理,核心状态包括:
mermaid复制stateDiagram-v2
[*] --> APPLIED: 提交申请
APPLIED --> REVIEWING: 初审通过
REVIEWING --> INTERVIEWING: 资料审核通过
INTERVIEWING --> APPROVED: 面试通过
APPROVED --> SIGNED: 签约完成
SIGNED --> [*]
状态转换通过PetAdoptionService实现:
java复制public void changeStatus(Long adoptionId, AdoptionStatus newStatus) {
Adoption adoption = getById(adoptionId);
AdoptionStatus current = adoption.getStatus();
if(!statusMachine.canTransfer(current, newStatus)) {
throw new BusinessException("状态转换不合法");
}
// 记录状态变更日志
statusLogService.recordChange(adoptionId, current, newStatus);
// 触发相关事件
eventPublisher.publishEvent(new StatusChangeEvent(adoptionId, current, newStatus));
}
3.2.2 电子签约集成
采用法大大电子签约SDK实现:
- 前端通过Web组件嵌入签约页面
- 后端通过回调接口接收签约结果
- 签约文件自动归档到宠物档案
集成关键点:
java复制public class FadadaCallbackController {
@PostMapping("/api/fadada/callback")
public String handleCallback(@RequestBody CallbackData data,
HttpServletRequest request) {
// 验证签名
if(!FadadaSignUtil.verify(request)) {
throw new SecurityException("签名验证失败");
}
// 处理不同类型的回调
switch(data.getEventType()) {
case "SIGN_COMPLETE":
adoptionService.updateContractStatus(data.getContractId());
break;
case "SIGN_REJECT":
adoptionService.notifyRejection(data.getContractId());
break;
}
return "success";
}
}
4. 性能优化实践
4.1 宠物列表查询优化
原始方案的问题:
- 关联查询5张表导致SQL执行时间超过800ms
- 分页查询在大数据量时性能急剧下降
优化后的解决方案:
-
二级缓存策略:
- 第一层:Redis缓存热点数据(TTL 30分钟)
- 第二层:Caffeine本地缓存(最大1000条,过期时间5分钟)
-
分页优化:
java复制public Page<PetVO> queryPets(PetQuery query) {
// 使用基于游标的分页替代传统LIMIT分页
if(query.getLastId() != null) {
return petMapper.selectPageByCursor(query);
}
// 小数据量时使用常规分页
Page<Pet> page = new Page<>(query.getPageNum(), query.getPageSize());
return petMapper.selectPageWithAssociations(page, query);
}
4.2 图片加载优化
前端实现的懒加载方案:
vue复制<template>
<div class="pet-gallery">
<img
v-for="img in images"
:key="img.id"
:data-src="img.url"
class="lazy-img"
:alt="img.alt"
/>
</div>
</template>
<script>
import { useIntersectionObserver } from '@vueuse/core'
export default {
mounted() {
const images = document.querySelectorAll('.lazy-img')
images.forEach(img => {
useIntersectionObserver(img, ([{ isIntersecting }]) => {
if(isIntersecting) {
img.src = img.dataset.src
}
})
})
}
}
</script>
5. 部署与运维实践
5.1 容器化部署方案
Docker Compose文件关键配置:
yaml复制version: '3.8'
services:
backend:
image: pet-home-backend:${TAG:-latest}
ports:
- "8080:8080"
environment:
- SPRING_PROFILES_ACTIVE=prod
- REDIS_HOST=redis
depends_on:
- redis
- mysql
frontend:
image: pet-home-frontend:${TAG:-latest}
ports:
- "80:80"
volumes:
- ./nginx.conf:/etc/nginx/conf.d/default.conf
mysql:
image: mysql:8.0
environment:
- MYSQL_ROOT_PASSWORD=${DB_PASSWORD}
volumes:
- mysql_data:/var/lib/mysql
volumes:
mysql_data:
5.2 监控告警配置
Prometheus监控指标示例:
yaml复制# application.yml
management:
endpoints:
web:
exposure:
include: health,info,metrics,prometheus
metrics:
tags:
application: pet-home
export:
prometheus:
enabled: true
关键监控指标:
http_server_requests_seconds_count:接口调用次数jvm_memory_used_bytes:JVM内存使用hikaricp_connections_active:数据库连接池状态
6. 踩坑与经验总结
6.1 跨域问题的终极解决方案
开发中遇到的CORS问题及解决路径:
- 最初方案:@CrossOrigin注解 → 简单请求OK,复杂请求失败
- 中间方案:自定义Filter → 解决部分问题但不够灵活
- 最终方案:Spring Cloud Gateway统一处理:
java复制@Bean
public CorsWebFilter corsFilter() {
CorsConfiguration config = new CorsConfiguration();
config.addAllowedOriginPattern("*");
config.addAllowedMethod("*");
config.addAllowedHeader("*");
config.setAllowCredentials(true);
config.setMaxAge(3600L);
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", config);
return new CorsWebFilter(source);
}
6.2 MyBatis Plus乐观锁实践
宠物信息并发更新解决方案:
- 实体类添加@Version注解
java复制public class Pet {
@Version
private Integer version;
}
- 配置乐观锁插件
java复制@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
interceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor());
return interceptor;
}
- 前端处理冲突提示
vue复制<script setup>
const handleUpdateError = (error) => {
if(error.response?.data?.code === 'OPTIMISTIC_LOCK_ERROR') {
ElMessage.error('数据已被修改,请刷新后重试')
fetchLatestData()
}
}
</script>
这个项目从零开始到上线历时3个月,最大的体会是:技术方案没有绝对的好坏,关键是找到最适合当前团队和业务场景的平衡点。比如在状态机实现上,我们最初考虑过Activiti这样的专业工作流引擎,但考虑到学习成本和系统复杂度,最终选择了自研轻量级方案,事实证明这个决定让后续的迭代维护变得简单许多。
