一、背景与需求分析

1.1 业务背景

在智能医疗诊断系统中,诊断书文档是核心数据载体。用户上传一张诊断书图片或 PDF 后,系统需要完成文件存储、AI 视觉识别、结构化数据提取、报告生成等一系列操作。这个过程涉及多个环节:

  • 文件上传:支持图片(jpg、png、jpeg)和 PDF 格式,大小限制 10MB
  • 文件存储:上传后的文件需要安全、持久化保存,并能生成临时访问链接供 AI 模型读取
  • AI 分析:调用通义千问 VL 多模态大模型对图片内容进行识别,提取结构化诊断信息
  • 结果持久化:将分析结果存入数据库,支持历史追溯
  • 报告导出:将分析结果生成 PDF 诊断报告,可供下载和打印

这一系列流程若采用同步阻塞方式实现,会带来三个突出问题:

  1. 用户体验差:大文件或高并发场景下,请求耗时可能超过 30 秒,前端需要长时间等待
  2. 系统脆弱:HTTP 线程被长时间占用,一旦中间环节失败整个请求失败,无重试机制
  3. 不可追踪:分析中的状态、失败原因均无记录,用户无法查看历史分析进度

因此,设计一个文档全生命周期管理系统,引入异步任务队列和状态跟踪机制,是提升系统健壮性和用户体验的关键。

1.2 核心需求

模块 需求 优先级
文件存储 支持诊断书文件上传、安全存储、临时访问、过期清理 P0
任务队列 异步处理分析任务,支持排队、重试、超时控制 P0
状态跟踪 跟踪每份文档的分析进度(待处理/分析中/成功/失败) P0
API 接口 提供标准的 RESTful API 用于触发分析、查询结果、下载文档 P0
文件管理 用户文件列表、单条/批量删除、存储统计 P1

二、系统架构设计

2.1 整体架构分层

整个文档管理子系统采用 四层架构,自顶向下依次为:

┌─────────────────────────────────────────────────────┐
│                  表现层 (UI Layer)                    │
│   medical-scan.html / analysis-history.html          │
│   (上传页面、历史记录页面、结果展示页面)                │
├─────────────────────────────────────────────────────┤
│                  API 层 (Controller)                  │
│   MedicalScanController                              │
│   └─ /api/medical-record/analyze    触发分析任务      │
│   └─ /api/analysis-history/list     查询历史列表      │
│   └─ /api/analysis-history/detail   查询任务详情      │
│   └─ /api/medical-record/export-pdf 导出PDF报告       │
├─────────────────────────────────────────────────────┤
│                 业务层 (Service)                      │
│   ┌─────────────────┐  ┌──────────────────────┐     │
│   │ TaskService     │  │ AnalysisHistoryService│     │
│   │ 任务调度、队列管理│  │ 历史记录CRUD         │     │
│   └────────┬────────┘  └──────────────────────┘     │
│            │                                         │
│   ┌────────▼────────┐                                │
│   │ AsyncTaskRunner │ 异步任务执行器                  │
│   │ (线程池 + 队列)  │                                │
│   └────────┬────────┘                                │
│            │                                         │
│   ┌────────▼────────┐  ┌──────────────────────┐     │
│   │ MedicalRecord   │  │ PdfExportUtil        │     │
│   │ AnalysisService │  │ PDF报告生成           │     │
│   │ AI模型调用      │  │                      │     │
│   └─────────────────┘  └──────────────────────┘     │
├─────────────────────────────────────────────────────┤
│                 数据层 (DAO)                          │
│   AnalysisHistoryDao (MyBatis-Plus)                  │
│   ┌──────────────────────────────────────────────┐  │
│   │          阿里云 OSS (文件存储)                │  │
│   └──────────────────────────────────────────────┘  │
└─────────────────────────────────────────────────────┘

各层职责说明:

层级 核心组件 职责
表现层 Thymeleaf 模板 用户交互界面,上传入口、进度展示、历史查询
API 层 MedicalScanController RESTful 接口,参数校验,响应封装
业务层 异步任务引擎 任务调度、队列管理、状态变更、AI 调用编排
数据层 MyBatis-Plus + OSS 结构化数据持久化、文件对象存储

2.2 核心业务流程

一个诊断书从上传到生成报告的完整生命周期:

用户上传 ──> 文件存储OSS ──> 创建分析任务 ──> 进入队列
                                                 │
                                          ┌──────▼──────┐
                                          │   任务调度器   │
                                          │ (线程池消费)   │
                                          └──────┬──────┘
                                                 │
                                          ┌──────▼──────┐
                                          │  AI 模型分析  │
                                          │ (通义千问VL)  │
                                          └──────┬──────┘
                                                 │
                                    ┌────────────┼────────────┐
                                    ▼            ▼            ▼
                                分析成功      部分识别      完全失败
                                    │            │            │
                                    ▼            ▼            ▼
                              保存结构化结果  保存部分结果  记录错误信息
                                    │            │            │
                                    ▼            ▼            ▼
                               状态: SUCCESS  状态: WARNING  状态: FAILED

三、数据模型设计

3.1 数据库表设计

核心表 analysis_history(分析历史记录表)设计如下:

CREATE TABLE `analysis_history` (
  `id`              INT           NOT NULL AUTO_INCREMENT  COMMENT '主键ID',
  `user_id`         INT           NOT NULL                 COMMENT '用户ID',
  `file_name`       VARCHAR(255)  NOT NULL                 COMMENT '原始文件名',
  `file_type`       VARCHAR(50)   NOT NULL                 COMMENT '文件类型(image/pdf)',
  `file_size`       BIGINT        DEFAULT NULL             COMMENT '文件大小(字节)',
  `image_url`       VARCHAR(1024) DEFAULT NULL             COMMENT 'OSS存储URL',
  `status`          VARCHAR(20)   NOT NULL DEFAULT 'PENDING' COMMENT '任务状态:PENDING/PROCESSING/SUCCESS/FAILED',
  `progress`        INT           NOT NULL DEFAULT 0       COMMENT '分析进度(0-100)',
  `error_message`   VARCHAR(1000) DEFAULT NULL             COMMENT '失败原因',
  `analysis_result` JSON         DEFAULT NULL              COMMENT '分析结果(JSON)',
  `patient_name`    VARCHAR(100)  DEFAULT NULL             COMMENT '患者姓名',
  `primary_diagnosis` VARCHAR(500) DEFAULT NULL            COMMENT '主要诊断',
  `confidence`      DOUBLE        DEFAULT NULL             COMMENT 'AI置信度',
  `create_time`     DATETIME      NOT NULL                 COMMENT '创建时间',
  `update_time`     DATETIME      NOT NULL                 COMMENT '更新时间',
  PRIMARY KEY (`id`),
  KEY `idx_user_id` (`user_id`),
  KEY `idx_status` (`status`),
  KEY `idx_create_time` (`create_time`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='诊断书分析历史记录表';

关键设计说明:

  • status 字段:任务状态枚举,驱动业务流程流转
  • progress 字段:分析进度百分比,支持前端进度条展示
  • error_message 字段:失败原因持久化,便于排查问题
  • analysis_result 字段:JSON 类型存储完整分析结果,灵活扩展

3.2 状态机设计

分析任务的状态转换遵循有限状态机模型:

                  ┌──────────┐
                  │ PENDING  │  (待处理)
                  └────┬─────┘
                       │ 线程池调度
                  ┌────▼─────┐
           ┌──────│PROCESSING│  (处理中)
           │      └────┬─────┘
           │           │
    ┌──────▼──┐  ┌─────▼────┐  ┌────────┐
    │ SUCCESS │  │ WARNING  │  │ FAILED │
    │ (成功)  │  │ (部分识别)│  │ (失败) │
    └─────────┘  └──────────┘  └────────┘

状态转换规则:

当前状态 触发条件 目标状态
PENDING 线程池获取任务开始分析 PROCESSING
PROCESSING AI 返回完整结构化结果 SUCCESS
PROCESSING AI 返回部分结果(置信度低) WARNING
PROCESSING 分析异常/超时/API 调用失败 FAILED
SUCCESS / WARNING / FAILED 用户重新分析 PENDING

WARNING 状态是一个增强设计:当 AI 模型返回内容但结构化提取不完全时,标记为 WARNING 而非直接 FAILED,保留部分提取结果供用户参考。


四、文件存储策略设计

4.1 存储层级

文件存储采用 两层架构

诊断书文件
    │
    ├── 热存储 (阿里云 OSS)
    │   └── bucket: smart-medicine-docs
    │       └── /medical_records/{userId}/{uuid}.{ext}
    │
    └── 冷存储 (OSS Lifecycle 自动沉降)
        └── 30天后自动转为归档存储

4.2 文件命名规范

{业务前缀}/{用户ID}/{唯一标识符}.{扩展名}

示例:
medical_records/42/a1b2c3d4e5f6.jpg
  • 业务前缀medical_records/ 区分不同业务模块
  • 用户 ID:按用户隔离,便于权限管理和清理
  • UUID:使用 Hutool IdUtil.simpleUUID() 生成,避免文件名冲突和中文乱码

4.3 访问策略

  • 上传:服务端直传到 OSS(通过服务端签名或 SDK 直传)
  • 读取:生成 Presigned URL(签名 URL),有效期 30 分钟
    • AI 分析服务使用签名 URL 读取图片
    • 前端展示使用签名 URL 预览
  • 过期清理:OSS Lifecycle 规则,30 天转为归档存储,60 天自动删除

4.4 安全性设计

┌──────────┐     ┌──────────┐     ┌──────────┐
│  用户上传  │ ──> │ 后端校验  │ ──> │ OSS存储  │
│          │     │ 文件类型  │     │ 私有可能 │
│          │     │ 文件大小  │     │ 签名访问 │
└──────────┘     └──────────┘     └──────────┘
  • 服务端校验文件类型(白名单:jpg/jpeg/png/pdf)
  • 服务端校验文件大小(上限 10MB)
  • OSS Bucket 设置为私有,所有访问通过签名 URL 进行
  • 签名 URL 设置有效期,避免 URL 泄露后长期可访问

五、异步任务队列架构设计

5.1 设计目标

引入异步任务队列要解决三个核心问题:

  1. 解耦:上传请求与 AI 分析分离,用户上传后立即获得响应
  2. 削峰:请求突增时,任务队列起到缓冲作用,防止 AI 服务被打满
  3. 可靠:失败任务支持重试,避免一次失败丢失全部数据

5.2 架构方案

考虑到当前系统规模(单体 Spring Boot 应用),采用 内存队列 + 线程池 的方案,不引入外部 MQ 依赖,降低架构复杂度:

         ┌─────────────┐
         │   Controller │  (接收上传请求)
         └──────┬──────┘
                │ ① 创建任务记录(状态=PENDING)
         ┌──────▼──────┐
         │  Analysis   │
         │  History    │  (写入数据库)
         │  Service    │
         └──────┬──────┘
                │ ② 提交到线程池
         ┌──────▼──────┐
         │  ThreadPool │  (LinkedBlockingQueue)
         │  Executor   │
         └──────┬──────┘
                │ ③ 消费任务
         ┌──────▼──────┐
         │ AsyncTask   │  (状态 → PROCESSING)
         │ Runner      │
         └──────┬──────┘
                │ ④ 调用AI模型
         ┌──────▼──────┐
         │ MedicalRecord│
         │ Analysis    │  (通义千问VL)
         │ Service     │
         └──────┬──────┘
                │ ⑤ 更新结果
         ┌──────▼──────┐
         │  Analysis   │  (状态 → SUCCESS/FAILED)
         │  History    │
         │  Service    │
         └─────────────┘

5.3 线程池配置

@Configuration
@EnableAsync
public class AsyncConfig {
    
    @Bean("analysisExecutor")
    public ThreadPoolTaskExecutor analysisExecutor() {
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        executor.setCorePoolSize(2);        // 核心线程数
        executor.setMaxPoolSize(4);         // 最大线程数
        executor.setQueueCapacity(50);      // 队列容量
        executor.setThreadNamePrefix("analysis-");
        executor.setRejectedExecutionHandler(
            new ThreadPoolExecutor.CallerRunsPolicy()  // 满队列时由主线程执行
        );
        executor.initialize();
        return executor;
    }
}

参数设计依据:

参数 理由
corePoolSize 2 AI 分析为 IO 密集型,保留线程切换开销
maxPoolSize 4 实测单次分析约 5-15s,4 线程并发吞吐足够
queueCapacity 50 等待队列长度,防止内存溢出
CallerRunsPolicy 满队列时主线程同步执行,降级而非丢弃

5.4 重试机制

失败 → 重试(3次) → 均失败 → 状态=FAILED,记录错误信息
         ↑
    指数退避: 10s → 30s → 60s
  • 每次任务最多重试 3 次
  • 重试间隔采用指数退避(10 秒、30 秒、60 秒)
  • 三次均失败则将状态更新为 FAILED,记录完整错误信息

六、RESTful API 接口规范

6.1 接口列表

POST   /api/medical-record/analyze      # 上传诊断书并触发分析
GET    /api/analysis-history/list       # 查询历史分析记录
GET    /api/analysis-history/detail     # 查询单条分析详情
GET    /api/analysis-history/status     # 查询指定任务状态
POST   /api/analysis-history/retry      # 重新分析失败的任务
POST   /api/analysis-history/delete     # 删除历史记录
POST   /api/analysis-history/clear      # 清空所有历史记录
POST   /api/medical-record/export-pdf   # 导出诊断报告 PDF

6.2 接口设计示例

上传并触发分析
POST /api/medical-record/analyze
Content-Type: multipart/form-data

参数:
  - file: 诊断书文件 (MultipartFile)

响应:
{
    "code": "SUCCESS",
    "message": "分析任务已提交",
    "data": {
        "taskId": 10042,
        "status": "PENDING",
        "estimatedWait": "约 10-30 秒完成"
    }
}
查询任务状态
GET /api/analysis-history/status?id=10042

响应:
{
    "code": "SUCCESS",
    "data": {
        "id": 10042,
        "status": "PROCESSING",
        "progress": 60,
        "createTime": "2026-05-24 10:00:00",
        "updateTime": "2026-05-24 10:00:15"
    }
}
查询分析详情
GET /api/analysis-history/detail?id=10042

响应:
{
    "code": "SUCCESS",
    "data": {
        "id": 10042,
        "fileName": "诊断报告_20260524.jpg",
        "patientName": "张三",
        "primaryDiagnosis": "急性上呼吸道感染",
        "status": "SUCCESS",
        "analyisResult": { ... },
        "createTime": "2026-05-24 10:00:00"
    }
}

6.3 接口设计规范

规范 约定
响应格式 统一使用 { code, message, data } 结构
HTTP 方法 POST 用于写操作,GET 用于读操作
参数传递 GET 用 URL 参数,POST 用 JSON body 或 multipart
状态码 业务状态用 code 字段区分,HTTP 统一 200
分页 列表接口统一使用 pageNum/pageSize 参数

七、技术选型对比

7.1 任务队列方案对比

方案 复杂度 可靠性 部署依赖 推荐场景
内存队列+线程池 ★☆☆ ★★★ 单体应用、初期阶段
Redis List + 定时任务 ★★☆ ★★★ Redis 中等规模、需要持久化
RabbitMQ ★★★ ★★★★ RabbitMQ 分布式、高可靠场景
RocketMQ ★★★★ ★★★★★ RocketMQ 大规模、事务消息

选型结论:当前系统为单体 Spring Boot 应用,且并发量可控,采用内存队列+线程池方案,性价比最高。

7.2 文件存储方案对比

方案 成本 扩展性 运维 推荐场景
本地磁盘 开发测试、小规模
阿里云 OSS 生产环境、云部署
MinIO(自建 S3) 私有化部署
阿里云 OSS + CDN 最好 高并发访问场景

选型结论:项目已使用阿里云部署,OSS 是最优选择,零运维成本,支持生命周期管理。


八、总结与展望

本文从文档全生命周期管理的业务需求出发,完成了以下设计工作:

  1. 四层架构:UI → API → Service → DAO,职责清晰,层次分明
  2. 数据模型:扩展 analysis_history 表,引入状态枚举和进度跟踪
  3. 文件存储:基于阿里云 OSS,设计了存储层级、命名规范和访问策略
  4. 异步任务:采用线程池 + 内存队列,实现异步解耦和削峰填谷
  5. API 规范:统一响应格式,设计了 8 个标准 RESTful 接口

下一篇博客:将进入具体的编码实现环节,包括数据库迁移脚本、线程池配置、异步任务执行器、状态机逻辑,以及完整的 API 控制器代码实现。

Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐