1. 项目背景与核心需求
网上药品商城系统是当前医药电商领域的基础设施建设项目。随着"互联网+医疗健康"政策的持续推进,2023年我国医药电商市场规模已突破3000亿元,年增长率保持在20%以上。在这样的背景下,基于SpringBoot框架构建药品商城系统具有显著的现实意义和技术优势。
药品电商系统与普通电商平台相比存在三个核心差异点:
- 严格的合规性要求(需对接医保系统、处方审核等)
- 特殊的商品管理需求(药品分类、批号追踪、效期管理)
- 复杂的业务流程(处方药需医生审核、医保报销对接)
SpringBoot框架的选择主要基于以下考量:
- 快速构建特性:自动配置机制可快速搭建符合医药行业规范的系统
- 微服务友好:便于后期扩展医保对接、在线问诊等模块
- 生态完整性:Spring生态提供从安全控制到事务管理的全套解决方案
2. 系统架构设计
2.1 技术栈选型
code复制前端:Vue.js + ElementUI
后端:SpringBoot 2.7 + Spring Security
数据库:MySQL 8.0(主业务)+ Redis(缓存)
中间件:RocketMQ(订单消息)
监控:Prometheus + Grafana
药品类系统需要特别注意数据一致性,我们采用Saga模式处理分布式事务。以订单创建为例:
- 订单服务创建主订单(状态:处理中)
- 库存服务预扣减(生成预扣减记录)
- 支付服务冻结金额
- 所有服务确认后提交最终状态
2.2 核心业务流程图
mermaid复制graph TD
A[用户登录] --> B{处方药?}
B -->|是| C[上传处方]
B -->|否| D[加入购物车]
C --> E[药师审核]
E -->|通过| D
D --> F[生成订单]
F --> G[支付]
G --> H[物流配送]
重要提示:处方审核环节必须保留完整的操作日志,包括审核人、审核时间、审核意见,这是医疗系统合规的基本要求。
3. 关键模块实现
3.1 药品信息管理
药品数据模型需要特殊设计:
java复制@Entity
public class Medicine {
@Id
private String drugCode; // 国药准字编号
@Column(nullable = false)
private String name;
@Enumerated(EnumType.STRING)
private DrugType type; // 处方药/OTC
private String specification; // 规格(如0.5g*12片)
@Temporal(TemporalType.DATE)
private Date productionDate;
private Integer shelfLife; // 保质期(月)
@Transient
public boolean isExpired() {
// 计算是否过期逻辑
}
}
3.2 处方审核流程
采用状态机模式实现审核工作流:
java复制public class PrescriptionService {
private final StateMachine<PrescriptionState, PrescriptionEvent> stateMachine;
public void reviewPrescription(Long prescriptionId, ReviewResult result) {
if(result == ReviewResult.APPROVED) {
stateMachine.sendEvent(PrescriptionEvent.APPROVE);
} else {
stateMachine.sendEvent(PrescriptionEvent.REJECT);
}
}
}
public enum PrescriptionState {
INIT, UNDER_REVIEW, APPROVED, REJECTED
}
public enum PrescriptionEvent {
SUBMIT, APPROVE, REJECT
}
4. 特殊业务处理
4.1 医保对接方案
与医保系统对接需要处理以下关键点:
- 加密通信:采用SM4国密算法加密传输
- 数据格式:严格按照《医保电子凭证数据规范》
- 对账机制:每日定时核对医保结算记录
典型报销接口示例:
java复制@PostMapping("/insurance/claim")
public ResponseEntity<ClaimResponse> medicalInsuranceClaim(
@RequestBody @Valid ClaimRequest request) {
// 1. 验证医保电子凭证
InsuranceCertificate cert = verifyCertificate(request.getCertToken());
// 2. 计算报销金额
BigDecimal reimbursableAmount = calculateReimbursement(
request.getDrugCodes(),
cert.getInsuranceType());
// 3. 调用医保系统接口
InsuranceSystemResponse resp = insuranceClient.submitClaim(
cert.getUserId(),
reimbursableAmount);
return ResponseEntity.ok(
new ClaimResponse(resp.getStatus(), resp.getSettlementNo()));
}
4.2 药品效期预警
通过定时任务实现效期管理:
java复制@Scheduled(cron = "0 0 9 * * ?") // 每天上午9点执行
public void checkExpiration() {
LocalDate warningDate = LocalDate.now().plusMonths(3);
List<Medicine> expiringSoon = medicineRepository
.findByExpirationDateBetween(LocalDate.now(), warningDate);
expiringSoon.forEach(med -> {
log.warn("药品即将过期: {} - 过期日期: {}",
med.getName(), med.getExpirationDate());
// 发送预警通知给采购人员
});
}
5. 安全与合规设计
5.1 敏感数据保护
医疗数据需要特殊保护措施:
- 存储加密:使用Jasypt对患者姓名、诊断信息等加密
- 访问日志:记录所有敏感数据的查询操作
- 脱敏显示:前端展示时自动处理(如:张**)
安全配置示例:
java复制@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/api/prescriptions/**").hasRole("PHARMACIST")
.antMatchers("/api/medicines/controlled").hasRole("ADMIN")
.and()
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.IF_REQUIRED)
.and()
.csrf().disable()
.addFilter(new AuditLogFilter());
}
}
5.2 审计日志实现
采用AOP记录关键操作:
java复制@Aspect
@Component
public class AuditLogAspect {
@AfterReturning(
pointcut = "@annotation(auditable)",
returning = "result")
public void logAuditEvent(JoinPoint jp, Auditable auditable, Object result) {
AuditLog log = new AuditLog();
log.setOperation(auditable.value());
log.setOperator(SecurityContext.getCurrentUser());
log.setParameters(JsonUtils.toJson(jp.getArgs()));
log.setResult(JsonUtils.toJson(result));
auditLogRepository.save(log);
}
}
6. 性能优化实践
6.1 药品搜索优化
针对药品搜索的特殊需求:
- 建立多字段组合索引:名称、拼音首字母、适应症
- 使用Elasticsearch实现语义搜索
- 缓存热门搜索词条
搜索接口示例:
java复制@GetMapping("/medicines/search")
public Page<MedicineDTO> searchMedicines(
@RequestParam String keyword,
@PageableDefault Pageable pageable) {
// 先尝试从缓存获取
String cacheKey = "search:" + keyword;
Page<MedicineDTO> cached = cacheManager.get(cacheKey);
if(cached != null) {
return cached;
}
// ES复合查询
NativeSearchQuery query = new NativeSearchQueryBuilder()
.withQuery(QueryBuilders.multiMatchQuery(keyword,
"name", "pinyin", "indications"))
.withPageable(pageable)
.build();
Page<Medicine> medicines = elasticsearchTemplate.queryForPage(query, Medicine.class);
Page<MedicineDTO> result = medicines.map(this::convertToDTO);
// 缓存结果(5分钟过期)
cacheManager.put(cacheKey, result, Duration.ofMinutes(5));
return result;
}
6.2 高并发订单处理
采用分级库存策略:
- 展示库存:Redis缓存(可超卖)
- 预扣库存:MySQL行锁控制
- 实际库存:最终一致性检查
订单创建核心逻辑:
java复制@Transactional
public Order createOrder(OrderRequest request) {
// 1. 验证库存(Redis)
if(!redisStockService.checkStock(request.getItems())) {
throw new BusinessException("库存不足");
}
// 2. 预扣库存(MySQL行锁)
stockService.freezeStock(request.getItems());
// 3. 创建订单
Order order = orderRepository.save(buildOrder(request));
// 4. 发送创建消息
rocketMQTemplate.send("order-topic",
MessageBuilder.withPayload(order.getId()).build());
return order;
}
7. 部署与监控方案
7.1 容器化部署
Docker Compose配置示例:
yaml复制version: '3'
services:
app:
image: drugstore-backend:${VERSION}
ports:
- "8080:8080"
environment:
- SPRING_PROFILES_ACTIVE=prod
depends_on:
- redis
- mysql
mysql:
image: mysql:8.0
volumes:
- db_data:/var/lib/mysql
environment:
- MYSQL_ROOT_PASSWORD=${DB_PASSWORD}
redis:
image: redis:6-alpine
ports:
- "6379:6379"
7.2 监控指标设计
关键监控指标包括:
- 处方审核平均耗时
- 订单创建成功率
- 医保接口响应时间
- 库存准确率
Prometheus配置示例:
yaml复制scrape_configs:
- job_name: 'drugstore'
metrics_path: '/actuator/prometheus'
static_configs:
- targets: ['app:8080']
8. 项目演进方向
在实际开发中,我们发现以下几个值得持续优化的方向:
- 智能推荐系统:基于患者历史购药记录和电子病历,构建推荐模型
- 冷链物流监控:对需要冷藏的药品实现运输全程温控
- 区块链存证:将处方审核记录上链,确保不可篡改
- 慢病管理:为慢性病患者提供用药提醒和复诊提示服务
一个典型的用药提醒实现:
java复制public void scheduleReminder(Long userId, String drugName,
LocalTime takeTime, int intervalDays) {
ReminderTask task = new ReminderTask();
task.setUserId(userId);
task.setDrugName(drugName);
task.setCronExpression(String.format(
"0 %d %d */%d * ?",
takeTime.getMinute(),
takeTime.getHour(),
intervalDays));
reminderRepository.save(task);
// 注册到Quartz调度器
scheduler.scheduleJob(
buildJobDetail(task),
buildTrigger(task));
}
