# **技术设计文档:工具 A \- 医疗数据超级合并?(The Super Merger)** | 文档类型 | Technical Design Document (TDD) | | :---- | :---- | | **对应 PRD** | **PRD\_工具A\_超级合并器\_V2.md** | | **版本** | **V2.0** (架构升级:访视基?\+ 时间? | | **状?* | Draft | | **核心目标** | 构建一个基?Web ?ETL 工具,解决临床科研中“一对多”数据对齐难题,实现基于时间窗的精准合并?| ## **1\. 总体架构设计 (Architecture Overview)** 鉴于处理 Excel 文件(解析、合并、写入)?CPU 密集型和内存敏感型操作,为了避免阻塞 Node.js 主线程,我们采用 **“异步任务队?\+ 流式处理?* 的架构模式? ### **1.1 系统架构?* graph TD Client\[React 前端 (Wizard UI)\] subgraph API\_Server \[Fastify API 服务\] UploadAPI\[上传接口\] TaskAPI\[任务状态接口\] ConfigAPI\[配置接口\] end subgraph Async\_Worker \[后台处理 Worker\] BullMQ\[BullMQ 队列\] Merger\[智能合并引擎 (Time-Window Joiner)\] ExcelParser\[ExcelJS 解析器\] DateEngine\[日期归一化引擎\] end subgraph Storage \[数据存储\] PG\[(PostgreSQL 业务?\] FileSys\[临时文件存储 (Local/S3)\] Redis\[(Redis 缓存/队列)\] end Client \--1.上传文件--\> UploadAPI UploadAPI \--保存临时文件--\> FileSys Client \--2.提交基准与时间窗配置--\> ConfigAPI ConfigAPI \--创建任务--\> PG ConfigAPI \--推入队列--\> BullMQ BullMQ \--消费任务--\> Merger Merger \--读取辅表(全量)--\> FileSys Merger \--读取主表(流式)--\> FileSys Merger \--流式合并与写?-\> FileSys Merger \--更新状?-\> PG Client \--3.轮询/WS 进度--\> TaskAPI Client \--4.下载结果--\> API\_Server ## **2\. 技术选型 (Tech Stack)** 基于现有技术栈的针对性选择? | 层级 | 技术组?| 选型理由 | | :---- | :---- | :---- | | **前端** | **React 19 \+ Ant Design 5** | 利用 AntD ?Steps, Upload, Tree (树状选择? 快速构?UI?| | **后端框架** | **Fastify 5.x** | 高性能 HTTP 框架,适合高并?I/O?| | **Excel 处理** | **ExcelJS** | **核心组件**。支持流式读?(Streaming I/O),这是处理大数据量不崩的关键?| | **日期处理** | **Day.js \+ CustomParseFormat** | **新增**。处理“时间地狱”的核心库,需要极强的容错解析能力?| | **任务队列** | **BullMQ \+ Redis** | 必须异步处理。合并逻辑复杂,耗时较长,必须用队列?| | **数据?* | **PostgreSQL 15 \+ Prisma** | 存储任务状态、文件元数据?*不建议将原始 Excel 数据存入 PG**?| | **验证?* | **Zod** | 用于校验前端提交的复杂映射配置结构?| ### **2.1 关键技术决?(ADR): 为什么不?Python (Pandas)?** 虽然 Python Pandas 在数据合并上代码更简洁,但针?*本工?*的场景,我们决定坚持使用 **Node.js**,理由如下: 1. **流式处理优势?* Pandas 倾向于全量加载内存,容易 OOM。Node.js ?Stream API 天然支持背压,能稳定处理“数据膨胀”问题? 2. **架构一致性:** 避免引入 Python Runtime 带来的运维成本和 IPC 开销? 3. **结论?* 对于精确匹配和逻辑清洗,Node.js 性能足够且更可控? ## **3\. 数据库设?(Database Schema)** ### **Prisma Schema 定义** // 任务状态枚? enum TaskStatus { PENDING PROCESSING COMPLETED FAILED } // 合并任务? model MergeTask { id String @id @default(uuid()) userId String status TaskStatus @default(PENDING) progress Int @default(0) // 核心配置字段 (V2 更新) // 结构: { // anchorFileId: string, // anchorKeys: { id: "住院?, time: "入院日期" }, // window: { daysBefore: 7, daysAfter: 7 }, // files: \[{ id: "f2", timeCol: "报告时间", columns: \["白细?\] }\] // } config Json? resultUrl String? report Json? // 质量报告 { totalRows: 1000, dropped: 50, matchRate: "95%" } errorMsg String? createdAt DateTime @default(now()) files SourceFile\[\] } // 源文件表 model SourceFile { id String @id @default(uuid()) taskId String task MergeTask @relation(fields: \[taskId\], references: \[id\]) filename String filepath String headers Json // \["住院?, "姓名", "入院日期"\] rowCount Int fileSize Int uploadedAt DateTime @default(now()) }