作为一名在宠物医疗行业摸爬滚打多年的技术负责人,我深知小型宠物诊所在数字化转型过程中面临的痛点。去年为本地一家连锁宠物诊所开发管理系统时,我们发现市面上的解决方案要么功能过剩(针对大型动物医院),要么关键模块缺失。这就是我们决定基于SpringBoot技术栈自主开发这套系统的初衷。
这套系统专门服务于哺乳类宠物诊所(猫、狗、兔等),核心解决三个业务难题:
技术选型上我们采用:
特别说明:系统设计时考虑了《动物诊疗机构管理办法》对病历保存期的要求(至少3年),所有删除操作均为逻辑删除,审计日志完整保留。
采用RBAC模型进行权限控制,但针对宠物诊所场景做了特殊优化:
java复制// 角色定义示例
public enum ClinicRole {
RECEPTIONIST(权限组合.前台权限),
VET(权限组合.医生权限),
PHARMACIST(权限组合.药师权限),
MANAGER(权限组合.管理权限);
private final Set<String> permissions;
}
关键设计点:
@PreAuthorize注解实现方法级控制病历数据结构设计是核心难点,我们采用"主表+扩展表"方案:
sql复制CREATE TABLE `pet_medical_record` (
`id` BIGINT PRIMARY KEY,
`pet_id` BIGINT NOT NULL,
`visit_date` DATETIME NOT NULL,
`symptoms` JSON COMMENT '症状描述',
`diagnosis` VARCHAR(500),
`treatment_plan` JSON COMMENT '治疗方案'
);
CREATE TABLE `medical_detail` (
`id` BIGINT PRIMARY KEY,
`record_id` BIGINT NOT NULL,
`item_type` ENUM('MEDICATION','INJECTION','SURGERY'),
`item_name` VARCHAR(100),
`dosage` VARCHAR(50)
);
业务亮点:
解决高峰期就诊排队混乱问题,我们实现了动态优先级队列:
java复制public class AppointmentQueue {
private final PriorityQueue<Appointment> queue =
new PriorityQueue<>(Comparator
.comparingInt(Appointment::getEmergencyLevel).reversed()
.thenComparing(Appointment::getCheckInTime));
// 急诊级别自动提升逻辑
public void addAppointment(Appointment appt) {
if(appt.getSymptoms().contains("呼吸困难")) {
appt.setEmergencyLevel(3);
}
queue.offer(apppt);
}
}
基于Guava的LoadingCache实现多级库存预警:
java复制LoadingCache<Long, DrugInventory> inventoryCache = CacheBuilder.newBuilder()
.refreshAfterWrite(1, TimeUnit.HOURS)
.build(new CacheLoader<>() {
@Override
public DrugInventory load(Long drugId) {
return checkExpiry(drugService.getInventory(drugId));
}
});
private DrugInventory checkExpiry(DrugInventory inv) {
long daysToExpire = ChronoUnit.DAYS.between(
LocalDate.now(),
inv.getExpiryDate());
inv.setExpiryStatus(
daysToExpire < 30 ? "WARNING" :
daysToExpire < 90 ? "NOTICE" : "NORMAL");
return inv;
}
针对宠物影像资料(X光片、超声图)上传场景,采用非阻塞IO提升吞吐量:
java复制@PostMapping("/upload")
public void upload(HttpServletRequest request) {
ServletFileUpload upload = new ServletFileUpload(
new DiskFileItemFactory(1024*1024,
new File("/tmp")));
upload.setFileSizeMax(50*1024*1024); // 50MB限制
upload.setHeaderEncoding("UTF-8");
// 使用NIO通道传输
FileChannel channel = FileChannel.open(
Paths.get("/data/upload/"+filename),
StandardOpenOption.CREATE,
StandardOpenOption.WRITE);
channel.transferFrom(
Channels.newChannel(item.getInputStream()),
0, item.getSize());
}
检验报告生成流程通过RabbitMQ实现异步处理:
java复制@RabbitListener(queues = "lab_report")
public void processLabReport(LabTask task) {
// 1. 调用检验设备接口
byte[] pdf = labDeviceService.generateReport(task);
// 2. 上传至云存储
String url = ossService.upload(pdf);
// 3. 更新数据库记录
reportService.updateStatus(task.getId(), url);
}
配置说明:
Docker Compose编排方案:
yaml复制version: '3'
services:
app:
image: clinic-system:1.2
ports:
- "8080:8080"
depends_on:
- redis
- mysql
mysql:
image: mysql:8.0
volumes:
- ./mysql:/var/lib/mysql
environment:
MYSQL_ROOT_PASSWORD: ${DB_PASSWORD}
redis:
image: redis:6
通过Micrometer对接Prometheus:
java复制@Bean
public MeterRegistryCustomizer<PrometheusMeterRegistry> configure() {
return registry -> {
registry.config().commonTags("application", "pet-clinic");
new JvmMemoryMetrics().bindTo(registry);
new UptimeMetrics().bindTo(registry);
};
}
关键监控项:
初期遇到预约时间显示错误,解决方案:
java复制@Configuration
public class DateTimeConfig {
@Bean
public Jackson2ObjectMapperBuilderCustomizer jacksonCustomizer() {
return builder -> {
builder.timeZone(TimeZone.getTimeZone("Asia/Shanghai"));
builder.simpleDateFormat("yyyy-MM-dd HH:mm");
};
}
}
原始方案性能差,改进后:
xml复制<insert id="batchInsert" useGeneratedKeys="true" keyProperty="id">
INSERT INTO treatment_item
(record_id, item_type, item_name)
VALUES
<foreach collection="list" item="item" separator=",">
(#{item.recordId}, #{item.type}, #{item.name})
</foreach>
</insert>
性能对比:
采用三级缓存策略:
缓存击穿处理:
java复制public Pet getPetWithCache(Long id) {
Pet pet = cache.get(id);
if(pet == null) {
synchronized(this) {
pet = cache.get(id);
if(pet == null) {
pet = db.get(id);
cache.put(id, pet);
}
}
}
return pet;
}
采用OAuth2.0保护API:
java复制@Configuration
@EnableAuthorizationServer
public class AuthConfig extends AuthorizationServerConfigurerAdapter {
@Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
clients.inMemory()
.withClient("mobile-app")
.secret(passwordEncoder.encode("secret"))
.scopes("read", "write")
.authorizedGrantTypes("password", "refresh_token");
}
}
基于Elasticsearch实现诊疗数据分析:
java复制@Repository
public interface TreatmentAnalysisRepository extends
ElasticsearchRepository<TreatmentRecord, Long> {
@Query("{\"bool\":{\"must\":[{\"term\":{\"petType\":\"?0\"}}]}}")
List<TreatmentRecord> findByPetType(String type);
}
典型分析场景:
这套系统在三个月的试运行期间,帮助合作诊所将平均候诊时间缩短40%,库存损耗率下降65%。最大的收获是看到技术真正解决了行业痛点,这也是我们持续迭代的动力。最近正在开发宠物主人小程序端,实现线上问诊预约功能,有兴趣的同行欢迎交流实战经验。