1. 项目背景与需求分析
社区医院作为基层医疗服务的重要载体,每天需要处理大量的药品流通数据。传统的手工记录方式不仅效率低下,还容易出现错漏。我在实际工作中就遇到过多次因人工记录错误导致的药品库存不符情况,最严重的一次差点影响了患者的正常用药。
这个药品管理系统正是为了解决这些痛点而设计的。核心需求可以归纳为"四个准确":药品信息准确、库存数量准确、效期管理准确、处方关联准确。系统需要覆盖从药品采购入库到门诊发药的全流程管理,同时要兼顾社区医院工作人员普遍计算机水平不高的实际情况。
提示:社区医院药品管理有个特点 - 单品种数量不大但品类繁多,这与大型医院有很大不同。系统设计时要特别注意这个特性。
2. 技术选型与架构设计
2.1 为什么选择SpringBoot
SpringBoot的自动配置特性特别适合社区医院这种IT力量薄弱的场景。我选择的是2.7.12版本,这个长期支持版既稳定又兼容性强。实测下来,从零开始搭建一个基础服务只需要15分钟,这大大降低了部署门槛。
技术栈组合:
- 后端:SpringBoot + MyBatis Plus
- 前端:Vue.js + ElementUI
- 数据库:MySQL 8.0
- 缓存:Redis 6.2
2.2 微服务还是单体架构
考虑到社区医院的规模,我最终选择了单体架构。这不是技术上的妥协,而是基于实际场景的考量:
- 日均处方量通常在200-500之间,单体完全能胜任
- 运维复杂度低,适合技术人员有限的场景
- 开发部署成本更低
不过我在代码结构上做了清晰的模块划分,为将来可能的扩展留有余地。
3. 核心功能实现细节
3.1 药品基础信息管理
药品字典是系统的基石,我设计了以下关键字段:
java复制public class Drug {
private String drugCode; // 药品编码(国标)
private String genericName; // 通用名
private String tradeName; // 商品名
private String specification; // 规格
private String manufacturer; // 生产厂家
private String approvalNumber; // 批准文号
private String drugType; // 药品类型
private BigDecimal purchasePrice; // 采购价
private BigDecimal retailPrice; // 零售价
// 其他字段...
}
特别注意:
- 药品编码必须遵循国家标准
- 规格字段要支持自由录入但要有格式校验
- 价格字段使用BigDecimal避免精度问题
3.2 库存管理实现
库存管理是系统的核心难点,我采用了"批次管理+实时库存"的双层设计:
sql复制CREATE TABLE `drug_stock` (
`id` bigint NOT NULL AUTO_INCREMENT,
`drug_code` varchar(20) NOT NULL COMMENT '药品编码',
`batch_no` varchar(30) NOT NULL COMMENT '批次号',
`quantity` int NOT NULL COMMENT '当前数量',
`production_date` date NOT NULL COMMENT '生产日期',
`expiry_date` date NOT NULL COMMENT '有效期至',
`location` varchar(20) DEFAULT NULL COMMENT '货位',
PRIMARY KEY (`id`),
UNIQUE KEY `idx_drug_batch` (`drug_code`,`batch_no`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
库存扣减的伪代码逻辑:
java复制@Transactional
public boolean deductStock(String drugCode, String batchNo, int quantity) {
// 1. 查询批次库存
DrugStock stock = stockMapper.selectByDrugAndBatch(drugCode, batchNo);
// 2. 校验库存是否充足
if(stock == null || stock.getQuantity() < quantity) {
throw new BusinessException("库存不足");
}
// 3. 扣减库存
stock.setQuantity(stock.getQuantity() - quantity);
return stockMapper.updateById(stock) > 0;
}
3.3 效期预警机制
药品过期是社区医院最头疼的问题之一。我实现了一个三级预警机制:
- 临期预警(距离过期3个月)
- 紧急预警(距离过期1个月)
- 过期锁定(已过期药品自动停用)
核心SQL查询:
sql复制SELECT
d.drug_code, d.generic_name, d.specification,
s.batch_no, s.expiry_date, s.quantity,
DATEDIFF(s.expiry_date, CURDATE()) AS remaining_days
FROM
drug_stock s
JOIN
drug_info d ON s.drug_code = d.drug_code
WHERE
s.quantity > 0
AND s.expiry_date BETWEEN CURDATE() AND DATE_ADD(CURDATE(), INTERVAL 3 MONTH)
ORDER BY
s.expiry_date ASC;
4. 特色功能实现
4.1 智能采购建议
基于历史消耗数据自动生成采购建议:
java复制public List<PurchaseSuggestion> generateSuggestions() {
// 获取过去3个月的平均消耗量
List<DrugConsumption> consumptions = statisticMapper.getRecentConsumptions(3);
// 获取当前库存
List<DrugStock> stocks = stockMapper.selectAll();
// 计算建议采购量
return consumptions.stream()
.map(c -> {
int currentStock = stocks.stream()
.filter(s -> s.getDrugCode().equals(c.getDrugCode()))
.mapToInt(DrugStock::getQuantity)
.sum();
int suggested = (int)(c.getMonthlyAvg() * 2 - currentStock); // 保留2个月用量
return new PurchaseSuggestion(c.getDrugCode(), suggested);
})
.filter(s -> s.getSuggestedQuantity() > 0)
.collect(Collectors.toList());
}
4.2 处方关联与发药流程
处方发药是药品流出的主要途径,必须确保流程严谨:
- 医生开处方时实时检查药品库存
- 发药时二次确认库存
- 支持部分发药(当库存不足时)
- 自动记录发药人和发药时间
关键事务处理:
java复制@Transactional
public boolean dispenseDrug(Long prescriptionId, String operator) {
// 1. 获取处方明细
List<PrescriptionDetail> details = detailMapper.selectByPrescriptionId(prescriptionId);
// 2. 预扣库存(生成预扣记录)
details.forEach(detail -> {
stockService.preDeduct(detail.getDrugCode(), detail.getBatchNo(), detail.getQuantity());
});
// 3. 更新处方状态
prescriptionMapper.updateStatus(prescriptionId, "DISPENSED", operator);
// 4. 记录发药日志
logService.recordDispenseLog(prescriptionId, operator);
return true;
}
5. 系统安全与性能优化
5.1 权限控制设计
采用RBAC模型,但针对社区医院做了简化:
- 角色分为:管理员、药房人员、医生、护士
- 权限粒度控制到菜单级别
- 关键操作(如库存调整)需要双人复核
Spring Security配置示例:
java复制@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers("/drug/**").hasAnyRole("PHARMACIST", "ADMIN")
.antMatchers("/prescription/dispense").hasRole("PHARMACIST")
.antMatchers("/stock/adjust").hasRole("ADMIN")
.anyRequest().authenticated()
.and()
.formLogin()
.loginPage("/login")
.defaultSuccessUrl("/dashboard")
.permitAll();
}
5.2 缓存策略
针对高频访问但变化不频繁的数据使用Redis缓存:
- 药品基础信息:缓存24小时
- 库存汇总数据:缓存1小时
- 药品分类树:缓存12小时
使用Spring Cache抽象层:
java复制@Cacheable(value = "drugInfo", key = "#drugCode", unless = "#result == null")
public Drug getDrugByCode(String drugCode) {
return drugMapper.selectByCode(drugCode);
}
@CacheEvict(value = "drugInfo", key = "#drug.drugCode")
public void updateDrug(Drug drug) {
drugMapper.updateById(drug);
}
6. 部署与运维实践
6.1 服务器配置建议
经过实测,推荐配置:
- CPU:4核以上
- 内存:8GB以上
- 磁盘:100GB SSD(药品图片较多时需要更大空间)
- 操作系统:CentOS 7.6+
JVM参数优化:
bash复制java -jar -Xms2g -Xmx4g -XX:MetaspaceSize=256m \
-XX:MaxMetaspaceSize=512m -XX:+UseG1GC \
-Dspring.profiles.active=prod drug-system.jar
6.2 数据备份方案
社区医院往往没有专业DBA,因此我设计了简单的备份策略:
- 每日凌晨2点全量备份
- 保留最近7天的备份
- 备份脚本示例:
bash复制#!/bin/bash
BACKUP_DIR=/data/backups
DATE=$(date +%Y%m%d)
mysqldump -uroot -p$DB_PASSWORD drug_db > $BACKUP_DIR/drug_db_$DATE.sql
find $BACKUP_DIR -name "*.sql" -mtime +7 -exec rm {} \;
7. 踩坑经验分享
7.1 药品规格标准化问题
初期没有对规格字段做规范化处理,导致出现多种表达方式:
- "10mg×12片/盒"
- "10mg*12T"
- "10毫克12片"
解决方案:
- 制定规格录入规范
- 在入库时自动标准化处理
- 增加规格校验规则
7.2 库存并发问题
在高并发场景下出现过库存扣减异常,解决方案:
- 使用数据库悲观锁:
sql复制SELECT * FROM drug_stock WHERE drug_code = ? AND batch_no = ? FOR UPDATE
- 引入Redis分布式锁
- 增加库存操作日志表用于对账
7.3 效期预警误报
曾因时区问题导致效期预警不准确,最终解决方案:
- 统一使用服务器时区
- 在查询时显式指定时区:
sql复制CONVERT_TZ(expiry_date, '+00:00', '+08:00')
- 增加人工确认环节
8. 系统扩展方向
在实际使用过程中,我发现还可以进一步扩展以下功能:
- 移动端查询:方便医护人员随时查看药品信息
- 供应商管理:与药品采购流程打通
- 药品不良反应上报:对接公共卫生系统
- 智能货位引导:通过LED屏显示药品位置
技术实现上,可以考虑:
- 使用Spring Cloud逐步向微服务演进
- 引入Elasticsearch提升检索效率
- 增加WebSocket实现实时库存提醒
这个系统在三个社区医院实际运行半年后,药品盘点差异率从原来的1.2%降到了0.05%以下,过期药品损失减少了80%。最大的收获是认识到:技术方案不在于多先进,而在于是否真正解决了实际问题。