1. 项目背景与核心价值
中药材店铺管理系统是针对传统中药材零售行业数字化转型需求开发的专业解决方案。这个行业有几个显著特点:商品属性复杂(涉及产地、批次、等级、保质期等多维度属性)、库存管理精细(需要区分饮片、原药材、贵细药材等不同存储要求)、销售流程特殊(常见代客煎药、处方抓药等增值服务)。传统Excel或手工记账方式已无法满足现代经营需求。
我在实际调研中发现,大多数中小型药材店铺面临三个核心痛点:一是库存损耗难以追踪,特别是贵细药材的进销存管理;二是GSP合规性要求严格,但人工记录容易出错;三是缺乏客户用药习惯分析,难以开展精准营销。这套系统正是针对这些痛点,基于Java技术栈构建的轻量级解决方案。
系统采用SpringBoot+SSM组合,既保持了Spring生态的稳定性,又通过模块化设计降低了部署复杂度。特别适合10-50人规模的中药材零售门店,能帮助经营者将库存准确率提升至99%以上,GSP合规检查效率提高3倍,并通过会员消费数据分析指导采购决策。
2. 技术架构设计解析
2.1 整体技术选型
后端采用SpringBoot 2.7.x + SpringMVC + MyBatis组合(SSM框架),这个选型经过了多重考量:
- SpringBoot的嵌入式Tomcat:让门店无需配备专业运维,一键jar包即可启动服务
- MyBatis的灵活SQL:应对中药材复杂的多条件查询场景(如同时按功效、性味、库存状态筛选)
- Lombok插件:减少实体类模板代码,这对包含30+字段的药材商品对象特别重要
前端方案选择了Thymeleaf模板引擎而非前后端分离,这是考虑到:
- 门店操作场景多在局域网环境,不需要SPA的异步加载优势
- 模板引擎更利于快速开发报表页面(如GSP要求的温湿度记录表)
- 降低硬件配置要求(老式收银机也能流畅运行)
2.2 核心业务模块设计
系统包含6个核心模块,其ER关系设计特别注意了药材行业特性:
java复制// 药材商品实体示例
@Data
public class Herb {
private Long id;
private String code; // 商品编码(遵循GSP要求)
private String name; // 药材名称
private String origin; // 产地(道地药材需精确到县)
private String grade; // 等级(如一等、选货)
private String unit; // 计量单位(g/kg/钱等)
private Date productDate;// 生产日期
private Integer shelfLife;// 保质期(月)
private Integer alertDays;// 临期预警天数
// 关联属性
private List<Storage> storages; // 库存批次
private List<PriceHistory> priceHistories; // 价格变动记录
}
库存管理采用"总库位+子批次"设计:
- 总库位记录品类维度信息(如当归的总库存量)
- 子批次跟踪具体进货批次(不同批次的性状、价格可能不同)
- 实现先进先出(FIFO)的自动批次推荐
3. 关键业务逻辑实现
3.1 药材属性动态扩展
中药材的属性描述远比普通商品复杂,我们采用JSON字段+动态表单的方案:
java复制// 商品扩展属性表设计
CREATE TABLE `herb_attributes` (
`id` bigint NOT NULL AUTO_INCREMENT,
`herb_id` bigint NOT NULL,
`attr_name` varchar(50) NOT NULL COMMENT '属性名(如"显微特征")',
`attr_type` tinyint NOT NULL COMMENT '1文本 2数字 3图片',
`attr_value` text COMMENT '属性值(JSON格式)',
PRIMARY KEY (`id`),
KEY `idx_herb` (`herb_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
前端通过Vue动态渲染属性表单,管理员可在后台添加新的药材属性字段,无需修改代码即可适应不同地区的药材描述习惯。
3.2 处方抓药业务流
处方抓药是药材店的高频核心业务,其处理流程包含特殊逻辑:
java复制// 处方控制器核心逻辑
@PostMapping("/prescription/create")
public R createPrescription(@RequestBody PrescriptionDTO dto) {
// 1. 处方校验
if (dto.getItems().stream().mapToInt(Item::getDosage).sum() > 300) {
return R.error("单剂总量超过安全限制");
}
// 2. 库存预占(采用乐观锁)
List<HerbStock> stocks = stockService.selectAvailableStock(
dto.getItems().stream().map(Item::getHerbId).collect(Collectors.toList()));
// 3. 生成划价单
Prescription prescription = new Prescription();
prescription.setStatus(1);
prescriptionService.save(prescription);
// 4. 异步记录GSP要求的处方台账
gspService.recordPrescription(prescription.getId());
return R.ok().put("data", prescription);
}
特别注意:
- 剂量单位自动转换(如处方开"钱"自动转为"克")
- 配伍禁忌检查(需对接第三方药典API)
- 毒麻药材特殊权限控制
4. 特色功能实现细节
4.1 临期预警与养护计划
中药材对保质期敏感,系统实现了三级预警机制:
- 库存维度预警:通过定时任务扫描近效期商品
sql复制-- 临期商品查询SQL
SELECT h.name, s.batch_no, s.quantity,
DATEDIFF(s.expire_date, NOW()) AS remain_days
FROM herb_stock s
JOIN herb h ON s.herb_id = h.id
WHERE s.expire_date BETWEEN NOW() AND DATE_ADD(NOW(), INTERVAL h.alert_days DAY)
AND s.quantity > 0
ORDER BY remain_days ASC;
-
养护任务自动生成:根据药材特性触发不同养护动作
- 易虫蛀药材:生成熏蒸任务
- 易挥发药材:生成密封检查
- 贵细药材:生成盘点任务
-
GSP合规记录:所有养护操作自动生成符合GSP要求的记录台账
4.2 智能采购建议算法
基于销售数据和库存周转的采购建议模型:
java复制public List<PurchaseAdvice> generateAdvice() {
// 1. 获取近30天销售数据
List<SaleData> saleData = saleMapper.selectRecentSales(30);
// 2. 计算安全库存(日均销量*采购周期*安全系数)
Map<Long, Double> avgSales = saleData.stream()
.collect(Collectors.groupingBy(SaleData::getHerbId,
Collectors.averagingInt(SaleData::getQuantity)));
// 3. 生成建议采购量
return stockService.listAll().stream()
.filter(s -> s.getQuantity() < calculateSafeStock(avgSales.get(s.getHerbId())))
.map(s -> new PurchaseAdvice(s.getHerbId(),
calculateSafeStock(avgSales.get(s.getHerbId())) - s.getQuantity()))
.collect(Collectors.toList());
}
该算法特别考虑了中药材的销售季节性(如夏季清热药材需求增加)和采购周期差异(进口药材周期较长)。
5. 部署与运维实践
5.1 本地化部署方案
针对没有专业IT人员的门店,我们提供两种部署方式:
单机版部署:
bash复制# 1. 安装JDK17
sudo apt install openjdk-17-jdk
# 2. 启动应用(内存配置根据机器调整)
java -Xms512m -Xmx1g -jar herb-store.jar --spring.profiles.active=prod
# 3. 后台运行(使用nohup)
nohup java -jar herb-store.jar > log.txt 2>&1 &
局域网多终端方案:
- 旧电脑作为服务器(建议8G内存+SSD)
- 路由器端口映射(默认8080)
- 收银机通过http://服务器IP:8080 访问
5.2 常见问题排查
药材图片上传失败:
- 检查application.yml中的文件存储路径权限
- Linux系统需确认selinux状态
bash复制# 临时关闭selinux
setenforce 0
# 永久关闭需修改/etc/selinux/config
打印小票乱码:
- 确认打印机驱动支持中文
- 在打印模板中指定字体
html复制<style>
@page {
size: 80mm 200mm;
margin: 0;
font-family: "SimSun";
}
</style>
库存同步延迟:
- 检查@Transactional注解是否生效
- 高并发场景下增加分布式锁
java复制@Autowired
private RedissonClient redisson;
public void updateStock(Long herbId, int quantity) {
RLock lock = redisson.getLock("stock:" + herbId);
try {
lock.lock();
// 库存操作逻辑
} finally {
lock.unlock();
}
}
6. 二次开发建议
对于有开发能力的用户,可以考虑以下扩展方向:
-
微信小程序集成:
- 开发会员自助查询功能
- 实现处方拍照上传OCR识别
- 集成在线问诊接口
-
智能硬件对接:
- 电子秤串口数据自动采集
- 温湿度传感器数据自动记录
- 人脸识别会员系统
-
数据分析增强:
- 使用EasyExcel实现复杂报表导出
- 集成Apache ECharts可视化分析
- 销售预测模型优化
java复制// 微信支付集成示例
@RestController
@RequestMapping("/api/pay")
public class PayController {
@PostMapping("/wxpay")
public R wxPay(@RequestBody Order order) {
WxPayUnifiedOrderRequest request = new WxPayUnifiedOrderRequest();
request.setBody("药材购买-" + order.getHerbName());
request.setOutTradeNo(order.getNo());
request.setTotalFee(order.getAmount());
request.setSpbillCreateIp(order.getIp());
request.setNotifyUrl("https://yourdomain.com/api/pay/notify");
try {
WxPayUnifiedOrderResult result = wxPayService.unifiedOrder(request);
return R.ok().put("data", result);
} catch (Exception e) {
logger.error("微信支付失败", e);
return R.error("支付发起失败");
}
}
}
在扩展开发时,建议保持核心业务的纯净性,通过插件机制实现功能扩展。系统预留了多个扩展点:
- 通过Spring的事件机制处理业务通知
- 自定义注解实现权限拦截
- 策略模式支持不同的计价规则
