SpringBoot+Vue3中药实验管理系统开发实践
2026/9/16 6:58:28 网站建设 项目流程

1. 项目背景与技术选型解析

这个中药实验管理系统采用前后端分离架构,后端基于SpringBoot 2框架,前端使用Vue 3组合式API,数据持久层采用MyBatis-Plus,数据库为MySQL 8.0。整套系统源码附带完整开发文档,适合作为医药信息化领域的二次开发基础框架。

在医药行业数字化转型背景下,传统中药实验室面临三大痛点:

  1. 实验数据记录依赖纸质文档,难以追溯和统计分析
  2. 药材库存管理缺乏实时监控,易造成过期浪费
  3. 实验流程标准化程度低,质量控制存在风险

技术栈选型考量:

  • SpringBoot 2.x:提供自动配置、嵌入式Tomcat等开箱即用特性,快速构建RESTful API
  • Vue 3:组合式API更适合复杂业务逻辑组织,配合TypeScript提升前端工程化水平
  • MyBatis-Plus:内置通用CRUD操作,支持Lambda表达式查询,减少30%以上SQL编写量
  • MySQL 8.0:JSON字段支持完善,窗口函数等特性便于实验数据分析报表生成

提示:实际部署时建议使用MySQL 8.0.23以上版本,该版本对GIS空间数据的支持更适合药材溯源场景

2. 系统核心模块设计

2.1 药材库存管理模块

采用树形结构存储药材分类(根茎类、果实类等),关键字段设计:

@Entity public class Herb { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; private String name; // 药材名称 private String latinName; // 拉丁学名 private String category; // 分类 private String origin; // 产地 private Date harvestDate; // 采收日期 private Date expiryDate; // 有效期 @Column(columnDefinition = "json") private String storageCondition; // 存储条件JSON }

库存预警实现方案:

  1. 使用Spring Scheduled每天凌晨扫描expiryDate字段
  2. 对临近过期的药材生成预警任务
  3. 通过WebSocket实时推送到前端看板

2.2 实验流程管理模块

采用状态机模式设计实验生命周期:

stateDiagram [*] --> 待分配 待分配 --> 进行中: 分配实验员 进行中 --> 数据录入: 完成实验 数据录入 --> 审核中: 提交报告 审核中 --> 已归档: 审核通过 审核中 --> 已驳回: 审核不通过

2.3 数据统计分析模块

利用MySQL 8.0窗口函数实现实验成功率统计:

SELECT experiment_type, COUNT(*) as total_count, SUM(CASE WHEN status = 'SUCCESS' THEN 1 ELSE 0 END) as success_count, SUM(CASE WHEN status = 'SUCCESS' THEN 1 ELSE 0 END) / COUNT(*) as success_rate FROM t_experiment GROUP BY experiment_type ORDER BY success_rate DESC

3. 关键技术实现细节

3.1 前后端鉴权方案

采用JWT+RBAC的混合模式:

  1. 前端Vue3使用axios拦截器自动携带token
  2. 后端Spring Security配置白名单路由
  3. 权限注解示例:
@PreAuthorize("hasRole('LAB_MANAGER') || hasPermission('experiment:review')") @PostMapping("/review") public Result reviewExperiment(@RequestBody ReviewDTO dto) { // 审核逻辑 }

3.2 文件导入导出优化

针对药材批量导入场景:

  • 使用EasyExcel处理万级数据导入
  • 采用数据库批量插入提升性能
  • 内存优化方案:
// 分批次读取Excel List<Herb> data = EasyExcel.read(inputStream) .head(Herb.class) .sheet() .doReadSync(); // 每500条批量插入 List<List<Herb>> partitions = Lists.partition(data, 500); partitions.forEach(batch -> herbMapper.insertBatchSomeColumn(batch));

3.3 报表生成方案

基于Apache POI和JasperReport的双引擎策略:

  1. 简单报表:直接POI动态生成
  2. 复杂报表:预编译Jasper模板
  3. 性能关键点:
    • 启用缓存已编译模板
    • 使用JRBeanCollectionDataSource避免全表加载
    • 异步生成后邮件通知下载

4. 部署与运维实践

4.1 生产环境配置建议

推荐使用Docker Compose部署:

version: '3' services: mysql: image: mysql:8.0 environment: MYSQL_ROOT_PASSWORD: ${DB_PASSWORD} volumes: - ./mysql/data:/var/lib/mysql - ./mysql/conf:/etc/mysql/conf.d backend: build: ./backend ports: - "8080:8080" depends_on: - mysql frontend: build: ./frontend ports: - "80:80"

4.2 常见问题排查指南

  1. 中文乱码问题:
    • MySQL连接串添加characterEncoding=utf8mb4
    • 确保Docker容器locale环境变量正确
  2. 跨域配置:
    @Bean public CorsFilter corsFilter() { UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(); CorsConfiguration config = new CorsConfiguration(); config.addAllowedOriginPattern("*"); config.addAllowedHeader("*"); config.addAllowedMethod("*"); source.registerCorsConfiguration("/**", config); return new CorsFilter(source); }
  3. MyBatis-Plus分页失效: 检查是否配置了分页拦截器:
    @Bean public MybatisPlusInterceptor mybatisPlusInterceptor() { MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor(); interceptor.addInnerInterceptor(new PaginationInnerInterceptor()); return interceptor; }

5. 二次开发建议

  1. 扩展字段策略:
    • 基础字段放在主表
    • 动态属性使用JSON字段存储
    • 关联数据使用中间表
  2. 工作流引擎集成: 可考虑接入Activiti或Flowable实现复杂审批流
  3. 移动端适配方案:
    • 基于Vue3的响应式布局
    • 或单独开发uni-app版本
  4. 智能分析扩展:
    • 集成Python机器学习模型
    • 使用Spring Cloud Stream实现数据管道

实际开发中我们发现,药材图片上传功能需要特别注意:

  • 使用MD5校验文件完整性
  • 限制上传格式为jpg/png
  • 建议采用MinIO作为文件存储
  • 前端需实现压缩上传:
const compressImage = async (file) => { return new Promise((resolve) => { const reader = new FileReader() reader.onload = (event) => { const img = new Image() img.onload = () => { const canvas = document.createElement('canvas') // 保持宽高比压缩到800px宽度 const MAX_WIDTH = 800 const scale = MAX_WIDTH / img.width canvas.width = MAX_WIDTH canvas.height = img.height * scale const ctx = canvas.getContext('2d') ctx.drawImage(img, 0, 0, canvas.width, canvas.height) canvas.toBlob(resolve, 'image/jpeg', 0.8) } img.src = event.target.result } reader.readAsDataURL(file) }) }

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询