Files
AIclinicalresearch/docs/03-业务模块/DC-数据清洗整理/02-技术设计/API设计文档-DC模块(完整版).md
HaHafeng 66255368b7 feat(admin): Add user management and upgrade to module permission system
Features - User Management (Phase 4.1):
- Database: Add user_modules table for fine-grained module permissions
- Database: Add 4 user permissions (view/create/edit/delete) to role_permissions
- Backend: UserService (780 lines) - CRUD with tenant isolation
- Backend: UserController + UserRoutes (648 lines) - 13 API endpoints
- Backend: Batch import users from Excel
- Frontend: UserListPage (412 lines) - list/filter/search/pagination
- Frontend: UserFormPage (341 lines) - create/edit with module config
- Frontend: UserDetailPage (393 lines) - details/tenant/module management
- Frontend: 3 modal components (592 lines) - import/assign/configure
- API: GET/POST/PUT/DELETE /api/admin/users/* endpoints

Architecture Upgrade - Module Permission System:
- Backend: Add getUserModules() method in auth.service
- Backend: Login API returns modules array in user object
- Frontend: AuthContext adds hasModule() method
- Frontend: Navigation filters modules based on user.modules
- Frontend: RouteGuard checks requiredModule instead of requiredVersion
- Frontend: Remove deprecated version-based permission system
- UX: Only show accessible modules in navigation (clean UI)
- UX: Smart redirect after login (avoid 403 for regular users)

Fixes:
- Fix UTF-8 encoding corruption in ~100 docs files
- Fix pageSize type conversion in userService (String to Number)
- Fix authUser undefined error in TopNavigation
- Fix login redirect logic with role-based access check
- Update Git commit guidelines v1.2 with UTF-8 safety rules

Database Changes:
- CREATE TABLE user_modules (user_id, tenant_id, module_code, is_enabled)
- ADD UNIQUE CONSTRAINT (user_id, tenant_id, module_code)
- INSERT 4 permissions + role assignments
- UPDATE PUBLIC tenant with 8 module subscriptions

Technical:
- Backend: 5 new files (~2400 lines)
- Frontend: 10 new files (~2500 lines)
- Docs: 1 development record + 2 status updates + 1 guideline update
- Total: ~4900 lines of code

Status: User management 100% complete, module permission system operational
2026-01-16 13:42:10 +08:00

728 lines
16 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# API设计文档 - 工具B病历结构化机器人
> **模块**: DC数据清洗整理 - 工具B
> **版本**: V2.0 (MVP)
> **Base URL**: `/api/v1/dc/tool-b`
> **更新日期**: 2025-12-03
> **状态**: ✅ MVP完成8个API端点全部可用已验证
---
## 📋 目录
- [一、API概览](#一api概览)
- [二、认证与鉴权](#二认证与鉴权)
- [三、API端点详情](#三api端点详情)
- [四、数据模型](#四数据模型)
- [五、错误处理](#五错误处理)
- [六、性能指标](#六性能指标)
---
## 一、API概览
### 1.1 端点列表
| # | 方法 | 路径 | 说明 | 后端状态 | 前端状态 | 测试状态 |
|---|------|------|------|---------|---------|---------|
| 0 | POST | `/upload` | 文件上传 | ✅ 已完成 | ✅ 已对接 | ✅ 通过 |
| 1 | POST | `/health-check` | 健康检查 | ✅ 已完成 | ✅ 已对接 | ✅ 通过 |
| 2 | GET | `/templates` | 获取模板列表 | ✅ 已完成 | ✅ 已对接 | ✅ 通过 |
| 3 | POST | `/tasks` | 创建提取任务 | ✅ 已完成 | ✅ 已对接 | ✅ 通过 |
| 4 | GET | `/tasks/:taskId/progress` | 查询任务进度 | ✅ 已完成 | ✅ 已对接 | ✅ 通过 |
| 5 | GET | `/tasks/:taskId/items` | 获取验证网格数据 | ✅ 已完成 | ✅ 已对接 | ✅ 通过 |
| 6 | POST | `/items/:itemId/resolve` | 裁决冲突 | ✅ 已完成 | ✅ 已对接 | ✅ 通过 |
| 7 | GET | `/tasks/:taskId/export` | 导出Excel结果 | ✅ 已完成 | ✅ 已对接 | ✅ 通过 |
**✅ MVP完成状态2025-12-03**
- 后端代码:~2200行含Service、Controller、Routes
- 前端代码:~1400行5步工作流完整实现
- 数据库表4张表已创建3个预设模板已就绪
- API对接8个端点全部集成并测试通过
- LLM调用DeepSeek-V3 + Qwen-Max 双模型验证成功
- 真实测试9条病理数据提取成功Token消耗~10k
- **已知问题**4个技术债务`07-技术债务/Tool-B技术债务清单.md`
### 1.2 通用规范
**请求头**
```http
Content-Type: application/json
Authorization: Bearer {token} #
```
**响应格式**
```json
{
"data": {...}, // 成功时返回
"error": "...", // 失败时返回
"code": 200
}
```
**HTTP状态码**
- `200`: 成功
- `400`: 请求参数错误
- `401`: 未认证
- `403`: 无权限
- `404`: 资源不存在
- `500`: 服务器内部错误
---
## 二、认证与鉴权
### 2.1 认证机制
**当前阶段MVP**
- ❌ 暂不实现认证
- 使用临时`userId`标识(从请求上下文获取)
**未来实现V1.0**
```http
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
```
### 2.2 权限模型
| 操作 | 权限要求 | 说明 |
|------|---------|------|
| 健康检查 | user | 所有用户 |
| 查看模板 | user | 所有用户 |
| 创建任务 | user | 所有用户 |
| 查询任务 | owner | 仅任务创建者 |
| 裁决冲突 | owner | 仅任务创建者 |
---
## 三、API端点详情
### 3.1 健康检查
**端点**: `POST /api/v1/dc/tool-b/health-check`
**用途**: 检查Excel列的数据质量拦截低质量数据
**请求体**
```json
{
"fileKey": "uploads/user123/data.xlsx",
"columnName": "病历文本"
}
```
**请求参数**
| 字段 | 类型 | 必填 | 说明 |
|------|------|------|------|
| `fileKey` | string | ✅ | Storage中的文件路径 |
| `columnName` | string | ✅ | 要检查的列名 |
**响应**(成功 - 200
```json
{
"status": "good",
"emptyRate": 0.12,
"avgLength": 256.8,
"totalRows": 500,
"estimatedTokens": 150000,
"message": "健康度良好,预计消耗约 150.0k Token双模型约 300.0k Token"
}
```
**响应**(失败 - 200但status=bad
```json
{
"status": "bad",
"emptyRate": 0.85,
"avgLength": 256.8,
"totalRows": 500,
"estimatedTokens": 0,
"message": "空值率过高85.0%),该列不适合提取"
}
```
**响应字段**
| 字段 | 类型 | 说明 |
|------|------|------|
| `status` | string | `good``bad` |
| `emptyRate` | number | 空值率 (0-1) |
| `avgLength` | number | 平均文本长度 |
| `totalRows` | number | 总行数 |
| `estimatedTokens` | number | 预估Token数 |
| `message` | string | 提示信息 |
**业务规则**
- 空值率 > 80% → `status = 'bad'`
- 平均长度 < 10 → `status = 'bad'`
- 只检查前100行性能优化
**错误响应**
```json
{
"error": "列'病历文本'不存在",
"code": 400
}
```
---
### 3.2 获取模板列表
**端点**: `GET /api/v1/dc/tool-b/templates`
**用途**: 获取所有预设的提取模板
**请求**: 无参数
**响应**200
```json
{
"templates": [
{
"diseaseType": "lung_cancer",
"reportType": "pathology",
"displayName": "肺癌病理报告",
"fields": [
{
"name": "病理类型",
"desc": "如:浸润性腺癌、鳞状细胞癌",
"width": "w-40"
},
{
"name": "分化程度",
"desc": "高/中/低分化",
"width": "w-32"
}
]
},
{
"diseaseType": "diabetes",
"reportType": "admission",
"displayName": "糖尿病入院记录",
"fields": [...]
}
]
}
```
**响应字段**
| 字段 | 类型 | 说明 |
|------|------|------|
| `templates` | array | 模板列表 |
| `templates[].diseaseType` | string | 疾病类型 |
| `templates[].reportType` | string | 报告类型 |
| `templates[].displayName` | string | 显示名称 |
| `templates[].fields` | array | 提取字段配置 |
**缓存策略**
- 客户端缓存1小时
- 服务端缓存:永久(直到重启)
---
### 3.3 创建提取任务
**端点**: `POST /api/v1/dc/tool-b/tasks`
**用途**: 创建批量提取任务,推送到异步队列
**请求体**
```json
{
"projectName": "肺癌病理数据提取-2025Q1",
"fileKey": "uploads/user123/lung_cancer_pathology.xlsx",
"textColumn": "病历文本",
"diseaseType": "lung_cancer",
"reportType": "pathology",
"targetFields": [
{
"name": "病理类型",
"desc": "如:浸润性腺癌、鳞状细胞癌"
},
{
"name": "分化程度",
"desc": "高/中/低分化"
}
]
}
```
**请求参数**
| 字段 | 类型 | 必填 | 说明 |
|------|------|------|------|
| `projectName` | string | ✅ | 任务名称 |
| `fileKey` | string | ✅ | Storage中的文件路径 |
| `textColumn` | string | ✅ | 文本列名 |
| `diseaseType` | string | ✅ | 疾病类型 |
| `reportType` | string | ✅ | 报告类型 |
| `targetFields` | array | ✅ | 提取字段配置 |
**响应**200
```json
{
"taskId": "550e8400-e29b-41d4-a716-446655440000"
}
```
**流程**
1. 验证文件存在
2. 解析Excel统计总行数
3. 创建任务记录status=pending
4. 推送到BullMQ队列
5. 立即返回taskId
**错误响应**
```json
{
"error": "文件不存在: uploads/user123/lung_cancer_pathology.xlsx",
"code": 404
}
```
---
### 3.4 查询任务进度
**端点**: `GET /api/v1/dc/tool-b/tasks/:taskId/progress`
**用途**: 实时查询任务处理进度
**请求**:
```
GET /api/v1/dc/tool-b/tasks/550e8400-e29b-41d4-a716-446655440000/progress
```
**响应**200
```json
{
"taskId": "550e8400-e29b-41d4-a716-446655440000",
"status": "processing",
"progress": 50,
"totalCount": 500,
"processedCount": 250,
"cleanCount": 200,
"conflictCount": 45,
"failedCount": 5,
"totalTokens": 75000,
"totalCost": 0.135,
"startedAt": "2025-11-27T10:00:00.000Z",
"completedAt": null
}
```
**响应字段**
| 字段 | 类型 | 说明 |
|------|------|------|
| `status` | string | `pending/processing/completed/failed` |
| `progress` | number | 进度百分比 (0-100) |
| `totalCount` | number | 总记录数 |
| `processedCount` | number | 已处理数 |
| `cleanCount` | number | 一致记录数 |
| `conflictCount` | number | 冲突记录数 |
| `failedCount` | number | 失败记录数 |
| `totalTokens` | number | 累计Token数 |
| `totalCost` | number | 累计成本($) |
**轮询建议**
- 客户端每3秒轮询一次
-`status = 'completed'`时停止轮询
---
### 3.5 获取验证网格数据
**端点**: `GET /api/v1/dc/tool-b/tasks/:taskId/items`
**用途**: 获取双模型提取结果,用于人工裁决
**请求**:
```
GET /api/v1/dc/tool-b/tasks/550e8400.../items?page=1&limit=50&status=conflict
```
**查询参数**
| 参数 | 类型 | 必填 | 默认值 | 说明 |
|------|------|------|--------|------|
| `page` | number | ❌ | 1 | 页码 |
| `limit` | number | ❌ | 50 | 每页数量 |
| `status` | string | ❌ | - | 过滤状态 |
**响应**200
```json
{
"items": [
{
"id": "item-123",
"rowIndex": 5,
"originalText": "患者45岁诊断为浸润性腺癌中分化肿瘤最大径3cm...",
"resultA": {
"病理类型": "浸润性腺癌",
"分化程度": "中分化",
"肿瘤大小": "3cm"
},
"resultB": {
"病理类型": "浸润性腺癌",
"分化程度": "中分化",
"肿瘤大小": "3.0cm"
},
"status": "conflict",
"conflictFields": ["肿瘤大小"],
"finalResult": null
}
],
"pagination": {
"total": 45,
"page": 1,
"pageSize": 50,
"totalPages": 1
}
}
```
**响应字段**
| 字段 | 类型 | 说明 |
|------|------|------|
| `items` | array | 记录列表 |
| `items[].status` | string | `clean/conflict/resolved/failed` |
| `items[].conflictFields` | array | 冲突字段列表 |
| `pagination` | object | 分页信息 |
---
### 3.6 裁决冲突
**端点**: `POST /api/v1/dc/tool-b/items/:itemId/resolve`
**用途**: 人工选择正确的提取结果
**请求**:
```json
{
"field": "肿瘤大小",
"chosenValue": "3cm"
}
```
**请求参数**
| 字段 | 类型 | 必填 | 说明 |
|------|------|------|------|
| `field` | string | ✅ | 冲突字段名 |
| `chosenValue` | string | ✅ | 选择的值 |
**响应**200
```json
{
"success": true
}
```
**业务逻辑**
1. 更新`finalResult[field] = chosenValue`
2.`conflictFields`中移除该字段
3. 如果所有冲突解决,更新`status = 'resolved'`
---
### 3.7 导出结果
**端点**: `GET /api/v1/dc/tool-b/tasks/:taskId/export`
**用途**: 导出最终提取结果为Excel
**请求**:
```
GET /api/v1/dc/tool-b/tasks/550e8400.../export?format=xlsx
```
**查询参数**
| 参数 | 类型 | 必填 | 默认值 | 说明 |
|------|------|------|--------|------|
| `format` | string | ❌ | `xlsx` | 导出格式:`xlsx/csv` |
**响应**200
- 文件流下载
- Content-Type: `application/vnd.openxmlformats-officedocument.spreadsheetml.sheet`
- Content-Disposition: `attachment; filename="extraction_result_2025-11-27.xlsx"`
**导出内容**
- 包含原始列 + 所有提取字段
- 只包含`clean``resolved`状态的记录
- 冲突记录不导出(需人工裁决)
---
## 四、数据模型
### 4.1 HealthCheckResult
```typescript
interface HealthCheckResult {
status: 'good' | 'bad';
emptyRate: number;
avgLength: number;
totalRows: number;
estimatedTokens: number;
message: string;
}
```
### 4.2 Template
```typescript
interface Template {
diseaseType: string;
reportType: string;
displayName: string;
fields: TemplateField[];
}
interface TemplateField {
name: string;
desc: string;
width?: string;
}
```
### 4.3 ExtractionTask
```typescript
interface ExtractionTask {
id: string;
userId: string;
projectName: string;
sourceFileKey: string;
textColumn: string;
diseaseType: string;
reportType: string;
targetFields: TemplateField[];
status: 'pending' | 'processing' | 'completed' | 'failed';
totalCount: number;
processedCount: number;
cleanCount: number;
conflictCount: number;
failedCount: number;
totalTokens: number;
totalCost: number;
createdAt: Date;
startedAt?: Date;
completedAt?: Date;
}
```
### 4.4 ExtractionItem
```typescript
interface ExtractionItem {
id: string;
taskId: string;
rowIndex: number;
originalText: string;
resultA?: Record<string, any>;
resultB?: Record<string, any>;
status: 'pending' | 'clean' | 'conflict' | 'resolved' | 'failed';
conflictFields: string[];
finalResult?: Record<string, any>;
tokensA: number;
tokensB: number;
}
```
---
## 五、错误处理
### 5.1 错误响应格式
```json
{
"error": "错误描述",
"code": 400,
"details": {
"field": "fileKey",
"reason": "文件不存在"
}
}
```
### 5.2 常见错误码
| HTTP状态 | code | 说明 | 示例 |
|----------|------|------|------|
| 400 | `INVALID_PARAMS` | 参数错误 | 缺少fileKey |
| 400 | `COLUMN_NOT_FOUND` | 列不存在 | 列"病历文本"不存在 |
| 400 | `BAD_HEALTH` | 健康检查未通过 | 空值率过高 |
| 404 | `FILE_NOT_FOUND` | 文件不存在 | 文件路径无效 |
| 404 | `TASK_NOT_FOUND` | 任务不存在 | taskId无效 |
| 403 | `FORBIDDEN` | 无权访问 | 只能访问自己的任务 |
| 500 | `INTERNAL_ERROR` | 服务器错误 | 数据库连接失败 |
### 5.3 错误处理最佳实践
**客户端**
```typescript
try {
const response = await fetch('/api/v1/dc/tool-b/health-check', {
method: 'POST',
body: JSON.stringify({ fileKey, columnName })
});
if (!response.ok) {
const error = await response.json();
throw new Error(error.error);
}
const data = await response.json();
if (data.status === 'bad') {
alert(data.message); // 健康检查未通过
return;
}
// 继续下一步
} catch (error) {
console.error('健康检查失败:', error);
}
```
---
## 六、性能指标
### 6.1 响应时间目标
| API | 目标 | 说明 |
|-----|------|------|
| `/health-check` | < 3秒 | Excel解析+统计 |
| `/templates` | < 100ms | 内存缓存 |
| `/tasks` (create) | < 500ms | 快速创建并返回 |
| `/tasks/:id/progress` | < 100ms | 数据库单查询 |
| `/tasks/:id/items` | < 500ms | 分页查询 |
| `/items/:id/resolve` | < 200ms | 单行更新 |
| `/tasks/:id/export` | < 10秒 | 生成Excel文件 |
### 6.2 并发处理能力
- **健康检查**: 10 req/sIO密集
- **任务创建**: 5 req/s写入数据库
- **进度查询**: 100 req/s读密集可缓存
- **验证网格**: 50 req/s分页查询
### 6.3 优化策略
**缓存**
- `/templates` → 永久缓存(内存)
- `/tasks/:id/progress` → Redis缓存5秒TTL
**异步处理**
- 任务处理使用BullMQ后台队列
- 避免阻塞用户请求
**分页**
- 验证网格默认50条/页
- 最大1000条/页
---
## 七、版本控制
### 7.1 API版本策略
**当前版本**: `v1`
**URL格式**: `/api/v1/dc/tool-b/*`
**向后兼容承诺**
- v1版本在2026年前保持稳定
- 新功能通过可选参数添加
- 破坏性变更发布v2
### 7.2 废弃通知
当API需要废弃时
```http
HTTP/1.1 200 OK
X-API-Deprecated: true
X-API-Sunset: 2026-12-31
X-API-Replacement: /api/v2/dc/tool-b/health-check
```
---
## 八、测试
### 8.1 Postman Collection
完整的API测试集合
```
docs/03-业务模块/DC-数据清洗整理/02-技术设计/ToolB-API.postman_collection.json
```
### 8.2 示例请求
**健康检查**
```bash
curl -X POST http://localhost:3001/api/v1/dc/tool-b/health-check \
-H "Content-Type: application/json" \
-d '{
"fileKey": "uploads/test.xlsx",
"columnName": "病历文本"
}'
```
**获取模板**
```bash
curl http://localhost:3001/api/v1/dc/tool-b/templates
```
**创建任务**
```bash
curl -X POST http://localhost:3001/api/v1/dc/tool-b/tasks \
-H "Content-Type: application/json" \
-d '{
"projectName": "测试任务",
"fileKey": "uploads/test.xlsx",
"textColumn": "病历文本",
"diseaseType": "lung_cancer",
"reportType": "pathology",
"targetFields": [{"name": "病理类型", "desc": "..."}]
}'
```
---
## 九、附录
### 9.1 相关文档
- [数据库设计文档](./数据库设计文档-工具B.md)
- [PRD文档](../01-需求分析/PRDTool B - 病历结构化机器人 (The AI Structurer).md)
- [开发计划](../04-开发计划/工具B开发计划-病历结构化机器人.md)
### 9.2 变更日志
| 版本 | 日期 | 变更内容 |
|------|------|---------|
| V1.0 | 2025-11-27 | 初始版本7个API端点 |
---
**文档结束**