社区医疗机构作为基层医疗服务的重要载体,每天面临着大量的药品流通管理需求。传统的手工记账方式不仅效率低下,还容易出现库存不准确、过期药品难追溯等问题。我在参与某社区卫生服务中心信息化改造时,亲眼见过药房工作人员用Excel表格记录药品进销存,经常出现库存数据与实际货架不符的情况,甚至发生过给患者发错批号药品的险情。
这套基于SpringBoot的药品管理系统正是为解决这些痛点而生。它采用B/S架构,将药品管理的全流程数字化,主要实现以下核心价值:
库存精准化管理:实时监控药品批次、效期、库存量,设置智能预警机制。当某批次药品库存低于安全阈值或临近效期时,系统自动触发提醒,避免断货或过期浪费。
业务流程规范化:通过系统固化采购申请-审批-入库-销售的标准流程,每个环节都有迹可循。特别是对特殊药品(如精神类药物)可实现严格的"双人核对"机制。
数据可视化分析:自动生成各类报表,如药品周转率分析、滞销药品统计等,为采购决策提供数据支持。某试点医院使用后,药品库存周转率提升了37%。
系统采用经典的三层架构,技术选型充分考虑社区医院的实际IT环境:
code复制表示层:Thymeleaf + Bootstrap
业务层:SpringBoot 2.7 + Spring Security
数据层:MySQL 5.7 + MyBatis-Plus
选型理由:
药品批次管理模型:
java复制@Entity
public class MedicineBatch {
@Id
private String batchNo; // 批次号(规则:药品ID+入库日期+序号)
private String medicineId;
private LocalDate productionDate;
private LocalDate expiryDate;
private Integer stock;
private String storageLocation; // 货架位置
private BigDecimal purchasePrice;
private BigDecimal sellingPrice;
// 审计字段
private String creator;
private LocalDateTime createTime;
}
这个设计实现了:
库存扣减的原子性设计:
药品销售时需保证库存扣减、销售记录生成、财务流水记录三个操作的原子性。我们采用Spring声明式事务管理:
java复制@Transactional(rollbackFor = Exception.class)
public SaleResult sellMedicine(SaleRequest request) {
// 1. 校验库存
MedicineBatch batch = batchMapper.selectById(request.getBatchNo());
if (batch.getStock() < request.getQuantity()) {
throw new BusinessException("库存不足");
}
// 2. 扣减库存(乐观锁)
int update = batchMapper.updateStock(
request.getBatchNo(),
request.getQuantity(),
batch.getVersion());
if (update == 0) {
throw new ConcurrentUpdateException("请重试");
}
// 3. 生成销售记录
SalesRecord record = buildRecord(request);
salesMapper.insert(record);
// 4. 财务流水
financeService.recordTransaction(record);
return buildResult(record);
}
避坑经验:
预警规则配置表设计:
sql复制CREATE TABLE `alert_rule` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`medicine_type` varchar(20) DEFAULT NULL COMMENT '药品分类',
`days_before_expire` int(11) DEFAULT NULL COMMENT '效期预警天数',
`min_stock` int(11) DEFAULT NULL COMMENT '最低库存阈值',
`recipients` varchar(255) DEFAULT NULL COMMENT '预警接收人',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
预警任务调度配置:
java复制@Scheduled(cron = "0 0 9 * * ?") // 每天上午9点执行
public void checkExpiryAlert() {
List<AlertRule> rules = alertRuleMapper.selectAll();
rules.forEach(rule -> {
LocalDate alertDate = LocalDate.now().plusDays(rule.getDaysBeforeExpire());
List<MedicineBatch> batches = batchMapper.selectExpiringSoon(
rule.getMedicineType(),
alertDate);
if (!batches.isEmpty()) {
sendAlert(rule.getRecipients(), batches);
}
});
}
性能优化点:
系统定义五种角色:
权限注解的使用示例:
java复制@PreAuthorize("hasRole('PHARMACIST') or hasRole('PHARMACY_DIRECTOR')")
@PostMapping("/medicine/out")
public ResponseEntity<?> sellMedicine(@Valid @RequestBody SaleRequest request) {
// 销售逻辑
}
采用Spring AOP记录关键操作日志:
java复制@Aspect
@Component
public class AuditLogAspect {
@AfterReturning(
pointcut = "@annotation(com.medicine.system.RequireAudit)",
returning = "result")
public void afterReturning(JoinPoint jp, Object result) {
String operation = ((MethodSignature)jp.getSignature())
.getMethod()
.getAnnotation(RequireAudit.class)
.value();
AuditLog log = new AuditLog();
log.setOperation(operation);
log.setOperator(SecurityUtils.getCurrentUsername());
log.setParameters(JsonUtils.toJson(jp.getArgs()));
log.setResult(JsonUtils.toJson(result));
auditLogMapper.insert(log);
}
}
注意事项:
推荐配置:
properties复制server.tomcat.max-threads=200
server.tomcat.accept-count=50
spring.datasource.hikari.maximum-pool-size=20
启动参数示例:
bash复制java -Xms512m -Xmx1024m -XX:+UseG1GC \
-Dspring.profiles.active=prod \
-jar medicine-system.jar
二级缓存配置:
本地缓存:使用Caffeine缓存基础数据
java复制@Bean
public CacheManager cacheManager() {
CaffeineCacheManager manager = new CaffeineCacheManager();
manager.setCaffeine(Caffeine.newBuilder()
.expireAfterWrite(30, TimeUnit.MINUTES)
.maximumSize(1000));
return manager;
}
Redis缓存:缓存药品目录等高频访问数据
yaml复制spring:
cache:
type: redis
redis:
host: 127.0.0.1
port: 6379
timeout: 3000ms
避坑指南:
现象:系统显示库存为5,实际货架上只剩3盒
排查步骤:
最终发现:某采购退货操作直接执行了SQL更新,绕过了Service层的事务控制
解决方案:
sql复制CREATE TABLE `stock_change_log` (
`id` bigint(20) NOT NULL AUTO_INCREMENT,
`batch_no` varchar(32) NOT NULL,
`change_type` varchar(10) NOT NULL COMMENT 'IN/OUT/ADJUST',
`before_quantity` int(11) NOT NULL,
`change_quantity` int(11) NOT NULL,
`after_quantity` int(11) NOT NULL,
`operator` varchar(32) NOT NULL,
`operate_time` datetime NOT NULL,
PRIMARY KEY (`id`),
KEY `idx_batch_no` (`batch_no`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
现象:某批次药品已过期但未触发预警
原因分析:
解决方案:
java复制@PostConstruct
public void checkSystemTime() {
LocalDateTime dbTime = jdbcTemplate.queryForObject(
"SELECT NOW()", LocalDateTime.class);
if (Duration.between(LocalDateTime.now(), dbTime).abs()
.toMinutes() > 5) {
throw new IllegalStateException("系统时间不同步");
}
}
在实际运行中,我们收集到了一些有价值的改进建议:
移动端支持:开发微信小程序,实现药品扫码快速入库/出库。技术方案:
智能采购预测:基于历史销售数据,使用时间序列算法预测采购量。可集成Prophet库:
python复制# 示例预测代码
from prophet import Prophet
model = Prophet(seasonality_mode='multiplicative')
model.fit(df) # df包含日期和销售数量
future = model.make_future_dataframe(periods=30)
forecast = model.predict(future)
电子处方对接:与HIS系统对接,实现处方药品自动扣减库存。需要:
这个系统在三个社区医院试点运行半年后,药品盘点误差率从原来的5.3%降至0.8%,过期药品损耗减少62%。特别在疫情期间,精准的库存管理帮助医院合理调配防疫物资,避免了资源挤兑。