1. 项目概述:基于SpringBoot2+Vue3的药品管理系统架构解析
这套药品管理系统源码采用当前主流的前后端分离架构,后端基于SpringBoot2框架构建RESTful API服务,前端使用Vue3组合式API开发响应式界面,数据持久层采用MyBatis-Plus简化CRUD操作,数据库选用MySQL8.0利用其窗口函数、CTE等高级特性。整套技术栈选型考虑了医药行业对数据一致性、操作审计和响应速度的特殊要求,比如药品批次管理需要事务支持,库存预警需要实时计算,这些都在架构设计层面得到了体现。
我在实际医疗信息化项目实施中发现,药品管理系统相比普通CRUD系统需要特别注意三个核心点:药品效期管理的批次追踪机制、处方药品的配伍禁忌检查、以及毒麻药品的特殊权限控制。本系统在这些业务关键点上都提供了可扩展的实现方案,比如通过MyBatis-Plus的自动填充功能实现操作日志追踪,利用MySQL8.0的JSON字段存储药品配伍规则等。
2. 技术栈深度解析与配置实践
2.1 SpringBoot2后端核心配置
在application.yml中需要特别关注的医药行业相关配置:
yaml复制spring:
datasource:
druid:
filters: stat,wall # 医药系统必须开启SQL防火墙
validation-query: SELECT 1 FROM dual
test-on-borrow: true # 保证处方提交时连接可用
jackson:
date-format: yyyy-MM-dd HH:mm:ss
time-zone: GMT+8 # 药品效期必须统一时区
cache:
type: redis # 药品目录缓存使用Redis
redis:
time-to-live: 1800000 # 30分钟更新基础数据
药品批号管理的实体类设计示例:
java复制@Entity
@Table(name = "drug_batch")
public class DrugBatch {
@Id
@TableId(type = IdType.ASSIGN_ID)
private Long batchId;
@TableField(fill = FieldFill.INSERT)
private LocalDateTime createTime;
@Version // 乐观锁用于库存扣减
private Integer version;
@TableLogic // 逻辑删除标记
private Integer deleted;
}
2.2 Vue3前端工程化实践
药品表单验证采用Vue3的composition API实现:
javascript复制// 药品基础信息校验规则
const drugRules = reactive({
drugName: [
{ required: true, message: '药品名称不能为空', trigger: 'blur' },
{ pattern: /^[\u4e00-\u9fa5a-zA-Z0-9]+$/, message: '不能包含特殊字符' }
],
spec: [
{ validator: (rule, value) =>
value.includes('×') && value.split('×').every(Number),
message: '规格格式如: 10×20'
}
]
})
// 配伍禁忌检查
const checkConflict = async (drugIds) => {
const { data } = await getConflictList(drugIds)
if(data.length > 0) {
ElMessage.warning(`存在配伍禁忌: ${data.join('、')}`)
}
}
3. 药品业务核心模块实现
3.1 药品效期批次管理
采用MySQL8.0的窗口函数实现近效期预警:
sql复制SELECT
drug_id, batch_no,
expiry_date,
DATEDIFF(expiry_date, CURDATE()) AS remain_days,
RANK() OVER(PARTITION BY drug_id ORDER BY expiry_date) AS batch_rank
FROM drug_batch
WHERE deleted = 0
HAVING remain_days < 180 -- 半年内将过期
SpringBoot中对应的分页查询实现:
java复制public Page<DrugBatchVO> getExpiringBatch(PageQuery query) {
return drugBatchMapper.selectPage(query,
Wrappers.<DrugBatch>lambdaQuery()
.apply("DATEDIFF(expiry_date, NOW()) < {0}", 180)
.orderByAsc(DrugBatch::getExpiryDate)
);
}
3.2 智能库存预警系统
库存计算采用Redis+Lua保证原子性:
lua复制-- inventory.lua
local key = KEYS[1]
local change = tonumber(ARGV[1])
local threshold = tonumber(ARGV[2])
local current = redis.call('GET', key)
if not current then
return {err = 'no inventory data'}
end
current = tonumber(current)
if current + change < 0 then
return {err = 'insufficient stock'}
end
redis.call('SET', key, current + change)
if current + change < threshold then
redis.call('PUBLISH', 'inventory:alert', key)
end
return {ok = current + change}
4. 系统部署与性能优化
4.1 MySQL8.0医药数据优化
药品目录表索引设计建议:
sql复制CREATE TABLE `drug_info` (
`drug_id` BIGINT NOT NULL COMMENT '药品ID',
`drug_code` VARCHAR(20) COLLATE utf8mb4_bin NOT NULL COMMENT '药品编码',
`drug_name` VARCHAR(100) NOT NULL COMMENT '通用名称',
`spec` VARCHAR(50) NOT NULL COMMENT '规格',
`unit` VARCHAR(10) NOT NULL COMMENT '单位',
`manufacturer` VARCHAR(200) NOT NULL COMMENT '生产厂家',
`approval_no` VARCHAR(50) NOT NULL COMMENT '批准文号',
`drug_type` TINYINT NOT NULL COMMENT '药品类型',
`status` TINYINT NOT NULL DEFAULT 1 COMMENT '状态',
PRIMARY KEY (`drug_id`),
UNIQUE KEY `uk_code` (`drug_code`),
KEY `idx_name` (`drug_name`),
KEY `idx_type_status` (`drug_type`,`status`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci
COMMENT='药品基础信息表';
4.2 高并发处方提交方案
采用Spring事务+Redis分布式锁保证处方提交的原子性:
java复制@Transactional(rollbackFor = Exception.class)
public PrescriptionResult submitPrescription(PrescriptionDTO dto) {
String lockKey = "prescription:lock:" + dto.getPatientId();
RedisLock lock = redisLockHelper.tryLock(lockKey, 10, TimeUnit.SECONDS);
try {
// 1. 检查药品库存
checkStock(dto.getItems());
// 2. 校验配伍禁忌
checkConflict(dto.getDrugIds());
// 3. 保存处方主数据
Prescription prescription = buildPrescription(dto);
prescriptionMapper.insert(prescription);
// 4. 扣减库存
reduceStock(dto.getItems());
// 5. 记录处方流转
recordPrescriptionFlow(prescription);
return PrescriptionResult.success(prescription.getId());
} finally {
lock.unlock();
}
}
5. 医疗行业特殊需求处理
5.1 毒麻药品双人审核
通过Spring Security实现RBAC+ABAC混合控制:
java复制@PreAuthorize("hasRole('PHARMACIST') && "
+ "@drugService.isControlledDrug(#drugId) ? "
+ "hasRole('SUPERVISOR') : true")
@PostMapping("/dispense")
public Result dispenseDrug(@RequestParam Long drugId,
@RequestParam Double quantity) {
// 发放药品逻辑
}
5.2 药品追溯系统实现
利用MyBatis-Plus的MetaObjectHandler实现自动审计:
java复制@Component
public class DrugAuditMetaHandler implements MetaObjectHandler {
@Override
public void insertFill(MetaObject metaObject) {
this.strictInsertFill(metaObject, "createBy",
String.class, SecurityUtils.getUsername());
this.strictInsertFill(metaObject, "createTime",
LocalDateTime.class, LocalDateTime.now());
}
@Override
public void updateFill(MetaObject metaObject) {
this.strictUpdateFill(metaObject, "updateBy",
String.class, SecurityUtils.getUsername());
this.strictUpdateFill(metaObject, "updateTime",
LocalDateTime.class, LocalDateTime.now());
}
}
6. 常见问题排查指南
-
SpringBoot启动报找不到application.properties
- 检查resources目录是否标记为Resources Root
- 确认文件编码为UTF-8
- 尝试使用spring.config.import显式导入
-
MyBatis-Plus分页失效
- 确保配置了分页插件:
java复制@Bean public MybatisPlusInterceptor mybatisPlusInterceptor() { MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor(); interceptor.addInnerInterceptor(new PaginationInnerInterceptor()); return interceptor; }- 检查Page参数是否作为方法第一个参数
-
Vue3样式污染问题
- 使用scoped样式:
<style scoped> - 对于组件库覆盖使用
:deep()选择器 - 考虑使用CSS Modules
- 使用scoped样式:
-
MySQL8.0连接失败
- 确认驱动版本匹配:
mysql-connector-java8.0.x - 检查时区设置:
serverTimezone=Asia/Shanghai - 更新连接池配置中的validationQuery
- 确认驱动版本匹配:
-
药品批次并发修改冲突
- 实体类添加@Version注解
- 前端提交携带version字段
- 捕获OptimisticLockingFailureException提示用户
这套系统在实际部署时,建议医药企业根据自身业务流程调整以下参数:药品效期预警阈值(默认180天)、库存安全库存量(建议按周平均用量设置)、处方审核超时时间(通常设置为5分钟)。对于三级医院等大型医疗机构,还需要考虑增加Redis集群部署和MySQL读写分离配置。
