这个基于Spring Boot的中药材进存销管理系统是我在指导某医药企业信息化改造时开发的一个实战项目。中药材行业作为传统行业,长期以来存在库存管理混乱、采购销售数据不透明等问题。通过这个系统,我们实现了中药材从采购入库、库存管理到销售出库的全流程数字化管控。
系统采用当前主流的Spring Boot+Vue前后端分离架构,后端使用MyBatis Plus简化数据库操作,前端采用Element UI组件库。整个系统开发周期约3个月,目前已稳定运行1年多,日均处理业务单据200+,管理中药材品类500余种。
选择Spring Boot作为后端框架主要基于以下几个考量:
前端选择Vue.js+Element UI组合是因为:
系统采用经典的三层架构:
这种分层带来的优势:
采购流程实现要点:
关键代码片段:
java复制@PostMapping("/purchase")
public Result createPurchase(@Valid @RequestBody PurchaseDTO dto) {
// 校验供应商状态
Supplier supplier = supplierService.getById(dto.getSupplierId());
if(supplier.getStatus() != 1) {
throw new BusinessException("供应商未通过审核");
}
// 生成采购单
PurchaseOrder order = new PurchaseOrder();
BeanUtils.copyProperties(dto, order);
order.setOrderNo(IdUtil.simpleUUID());
order.setStatus(0); // 待审核
purchaseService.save(order);
// 启动审批流程
workflowService.startPurchaseApproval(order.getId());
return Result.success(order);
}
库存设计关键点:
库存扣减的并发控制:
java复制@Transactional
public void deductStock(Long skuId, Integer quantity) {
// 使用乐观锁控制并发
int updated = stockMapper.deductWithVersion(skuId, quantity);
if(updated == 0) {
throw new ConcurrentUpdateException("库存并发修改冲突");
}
// 记录库存变更流水
StockFlow flow = new StockFlow();
flow.setSkuId(skuId);
flow.setChangeQuantity(-quantity);
flow.setType(2); // 销售出库
stockFlowService.save(flow);
}
销售流程实现:
销售单状态机设计:
java复制public enum SaleOrderStatus {
DRAFT(0, "草稿"),
CONFIRMED(1, "已确认"),
DELIVERED(2, "已发货"),
COMPLETED(3, "已完成"),
CANCELLED(4, "已取消");
// 状态流转规则
private static final Map<Integer, List<Integer>> TRANSITION_RULES = Map.of(
0, List.of(1, 4),
1, List.of(2, 4),
2, List.of(3),
3, Collections.emptyList(),
4, Collections.emptyList()
);
public static boolean canTransition(int from, int to) {
return TRANSITION_RULES.get(from).contains(to);
}
}
系统采用RBAC模型进行权限控制:
权限注解实现:
java复制@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface RequiresPermission {
String value();
}
@Aspect
@Component
public class PermissionAspect {
@Before("@annotation(requiresPermission)")
public void checkPermission(RequiresPermission requiresPermission) {
String perm = requiresPermission.value();
if(!currentUser.hasPermission(perm)) {
throw new UnauthorizedException("无权限访问");
}
}
}
针对大数据量导出:
导出服务实现:
java复制public void exportData(Long taskId, ExportParams params) {
// 异步执行
executor.execute(() -> {
try {
// 创建临时文件
File tempFile = File.createTempFile("export_", ".xlsx");
// 使用SXSSF避免内存溢出
try (SXSSFWorkbook workbook = new SXSSFWorkbook(100)) {
Sheet sheet = workbook.createSheet();
// 分页查询写入
int page = 1;
while(true) {
Page<Data> pageData = queryPage(page, 1000);
if(pageData.getRecords().isEmpty()) break;
writePageToSheet(sheet, pageData.getRecords());
page++;
}
workbook.write(new FileOutputStream(tempFile));
}
// 上传到文件服务器
String url = uploadToOSS(tempFile);
// 更新任务状态
updateTaskStatus(taskId, url);
} catch (Exception e) {
updateTaskStatus(taskId, e.getMessage());
}
});
}
我们的部署方案:
Docker Compose配置示例:
yaml复制version: '3'
services:
app:
image: myapp:1.0
ports:
- "8080:8080"
environment:
- SPRING_PROFILES_ACTIVE=prod
depends_on:
- redis
- mysql
redis:
image: redis:6
ports:
- "6379:6379"
volumes:
- redis_data:/data
mysql:
image: mysql:8
ports:
- "3306:3306"
environment:
- MYSQL_ROOT_PASSWORD=123456
volumes:
- mysql_data:/var/lib/mysql
volumes:
redis_data:
mysql_data:
我们实施的优化措施:
具体优化手段:
java复制// 1. 二级缓存配置
@CacheConfig(cacheNames = "medicine")
@Service
public class MedicineServiceImpl {
@Cacheable(key = "#id")
public Medicine getById(Long id) {
return baseMapper.selectById(id);
}
}
// 2. SQL优化示例
@Select("SELECT * FROM stock WHERE sku_id = #{skuId} AND warehouse_id = #{warehouseId} FOR UPDATE")
Stock selectForUpdate(@Param("skuId") Long skuId, @Param("warehouseId") Long warehouseId);
// 3. 异步处理日志
@Async
public void saveOperationLog(OperationLog log) {
logMapper.insert(log);
}
在这个项目开发过程中,我总结了以下几点经验:
以下是系统上线后遇到的典型问题及解决方案:
对于想要进一步扩展系统的开发者,我建议:
这个项目让我深刻体会到,一个好的业务系统不仅要技术过关,更要深入理解行业特性。中药材管理有其特殊性,比如道地药材、采收时节、贮藏条件等都需要在系统中充分考虑。