中医药行业作为我国传统医学的重要组成部分,近年来随着健康意识的提升而快速发展。然而在数字化转型浪潮中,许多中小型中医药店仍停留在手工记账、Excel管理的阶段,面临着以下痛点:
我们开发的SpringBoot中医药店管理系统,正是针对这些痛点设计的轻量级解决方案。系统采用B/S架构,员工通过浏览器即可完成所有操作,主要实现三大核心功能:
实际开发中发现,中药材与西药的管理有显著差异:同一药材可能有多个别名(如"金银花"又称"忍冬花"),系统特别设计了药品别名映射功能,确保无论输入哪个名称都能准确检索。
经过对三种主流技术方案的对比测试,最终选型如下:
| 技术组件 | 选型理由 | 替代方案对比 |
|---|---|---|
| Spring Boot 2.7 | 内嵌Tomcat简化部署,Starter依赖自动配置,特别适合快速迭代的中小型项目 | 传统SSM框架配置复杂 |
| MySQL 8.0 | 支持JSON字段存储药品的复杂属性,事务性能满足高频库存更新需求 | MongoDB在事务支持上不足 |
| Vue 3 | 组合式API更适合复杂业务逻辑,配合Element Plus组件库快速构建管理界面 | React学习成本较高 |
| Redis | 缓存热门药品信息,将药品详情页的响应时间从800ms降低到120ms | 本地缓存难以集群部署 |
系统采用经典的三层架构,但针对中医药业务特点做了特殊优化:
code复制[浏览器] ←HTTP→ [Nginx] ←→ [Spring Boot]
↑ ↑
| ↓
[静态资源] [Service层] ←→ [Redis]
↓
[MyBatis-Plus]
↓
[MySQL 8.0]
关键设计亮点:
保存条件JSON字段,存储温度、湿度、避光等要求java复制@Data
public class Medicine {
private Long id;
private String name;
private String type;
private JSONObject storageCondition; // {"temperature":"15-25℃","humidity":"<60%"}
}
库存管理采用"双重校验"机制确保数据一致性:
入库流程:
mermaid复制graph TD
A[供应商送货] --> B(扫码验货)
B --> C{质检合格?}
C -->|是| D[录入系统]
C -->|否| E[退货处理]
D --> F[生成入库单]
F --> G[更新库存缓存]
并发控制方案:
java复制@Transactional
public void reduceStock(Long medicineId, int quantity) {
// 使用悲观锁确保数据一致性
Medicine medicine = medicineMapper.selectByIdForUpdate(medicineId);
if (medicine.getStock() < quantity) {
throw new BusinessException("库存不足");
}
medicine.setStock(medicine.getStock() - quantity);
medicineMapper.updateById(medicine);
// 记录操作日志
operateLogService.saveReduceLog(medicineId, quantity);
}
煎药流程需要考虑三大因素:
调度算法核心逻辑:
python复制def schedule_herb_boiling(task):
# 计算紧急程度得分
urgency_score = 0
if task.patient_type == '住院':
urgency_score += 3
elif task.patient_type == '急诊':
urgency_score += 5
# 计算复杂度得分
complexity_score = len(task.herbs) * 0.5
# 选择最优药房
best_room = None
min_load = float('inf')
for room in available_rooms:
current_load = calculate_room_load(room)
if current_load < min_load:
min_load = current_load
best_room = room
return {
'room': best_room,
'start_time': calculate_start_time(best_room),
'priority': urgency_score + complexity_score
}
在实际运营中发现,不同供应商对同一药材使用不同名称。我们建立了药材别名库:
sql复制CREATE TABLE `medicine_alias` (
`id` bigint NOT NULL AUTO_INCREMENT,
`medicine_id` bigint NOT NULL,
`alias_name` varchar(100) NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `idx_alias` (`alias_name`),
KEY `fk_medicine` (`medicine_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
查询时使用联合查询:
java复制@Select("SELECT m.* FROM medicine m LEFT JOIN medicine_alias a ON m.id = a.medicine_id " +
"WHERE m.name = #{name} OR a.alias_name = #{name}")
Medicine findByNameOrAlias(String name);
实现基于规则引擎的自动检测:
java复制public class CompatibilityChecker {
private static final Map<String, List<String>> INCOMPATIBLE_MAP = Map.of(
"乌头", List.of("贝母","瓜蒌","半夏"),
"甘草", List.of("甘遂","海藻")
);
public static void check(List<String> herbs) {
for (String herb : herbs) {
List<String> incompatible = INCOMPATIBLE_MAP.get(herb);
if (incompatible != null) {
for (String other : herbs) {
if (incompatible.contains(other)) {
throw new IncompatibleException(herb + "与" + other + "存在配伍禁忌");
}
}
}
}
}
}
采用多级缓存架构提升响应速度:
本地缓存:使用Caffeine缓存热门药品信息(有效期5分钟)
java复制@Bean
public Cache<String, Medicine> medicineCache() {
return Caffeine.newBuilder()
.maximumSize(1000)
.expireAfterWrite(5, TimeUnit.MINUTES)
.build();
}
分布式缓存:Redis存储库存实时数据,解决超卖问题
java复制public boolean tryLockStock(Long medicineId, int quantity) {
String key = "stock:lock:" + medicineId;
String value = String.valueOf(System.currentTimeMillis());
// 设置分布式锁,有效期30秒
return redisTemplate.opsForValue().setIfAbsent(key, value, 30, TimeUnit.SECONDS);
}
针对药品查询特点进行优化:
索引设计:
sql复制ALTER TABLE `medicine` ADD INDEX `idx_name_type` (`name`, `type`);
ALTER TABLE `inventory` ADD INDEX `idx_medicine_stock` (`medicine_id`, `stock`);
查询优化:
java复制// 使用覆盖索引避免回表
@Select("SELECT id, name, type FROM medicine WHERE name LIKE CONCAT(#{prefix},'%')")
List<Medicine> findByPrefix(String prefix);
使用Docker Compose编排服务:
yaml复制version: '3'
services:
app:
image: clinic-app:1.0
ports:
- "8080:8080"
depends_on:
- redis
- mysql
redis:
image: redis:6-alpine
ports:
- "6379:6379"
mysql:
image: mysql:8.0
environment:
MYSQL_ROOT_PASSWORD: ${DB_PASSWORD}
ports:
- "3306:3306"
通过Spring Boot Actuator暴露健康检查端点:
properties复制management.endpoints.web.exposure.include=health,metrics,prometheus
management.metrics.export.prometheus.enabled=true
配合Grafana仪表板监控关键指标:
在开发过程中,最大的收获是认识到传统行业数字化转型必须深入业务场景。比如最初设计的库存预警只考虑数量,实际使用中老药师更关心药材质量变化,后来我们增加了视觉识别的霉变检测功能。这种业务洞察只有通过持续迭代和用户反馈才能获得。