Files
leaudit-platform-backend/docs/文档管理/文档上传与列表接口分析.md
2026-05-09 20:04:08 +08:00

399 lines
13 KiB
Markdown
Raw Permalink 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.
# 文档上传与列表 — 接口分析 & 接入方案
> 所有信息已逐文件验证。源代码位置均已标注。
>
> 说明:这份文档已经吸收旧的 `新系统版_documents_list接口.md` 与 `文档上传与评查接口.md`,后续以本文件作为文档上传、列表、详情、更新、删除、评查触发与状态查询的统一说明。
---
## 一、后端接口(已验证)
### 1.1 POST /api/upload — 文档上传
**源码**: `fastapi_modules/fastapi_leaudit/controllers/documentController.py:20-45`
**实现**: `fastapi_modules/fastapi_leaudit/services/impl/documentServiceImpl.py:44-246`
```
URL: POST /api/upload
格式: multipart/form-data (扁平字段)
```
| 字段 | 类型 | 必填 | 默认值 | 说明 |
|------|------|------|--------|------|
| `file` | UploadFile | **是** | — | 文件二进制 |
| `typeId` | int | typeId/typeCode 二选一 | None | 文档类型 ID |
| `typeCode` | str | typeId/typeCode 二选一 | None | 文档类型编码,如 `contract.construction` |
| `region` | str | 否 | `"default"` | 地区标识 |
| `fileRole` | str | 否 | `"primary"` | 文件角色:primary/attachment/template |
| `createdBy` | int | 否 | None | 前端可传,但后端当前以 JWT 中的 `user_id` 为准 |
| `autoRun` | bool | 否 | `false` | 是否上传后自动触发评查 |
| `speed` | str | 否 | `"normal"` | urgent / normal |
**内部流程**:
1. 校验文件名/内容非空 → 解析文档类型(查 `leaudit_document_types`
2. SHA-256 去重(仅 primary 文件):同名+同类型+同区域+同 hash → 复用已有记录,`duplicateUpload=true`
3. 非重复:生成 `internalDocumentNo`,匹配前一版本,递增 `versionNo`,写 `leaudit_documents`
4. 存 MinIO OSS,路径 `bdocs/{Region}/{TypeCode}/{Year}/...`
5.`leaudit_document_files`
6.`autoRun=true` → 调 `AuditService.Run()` 触发评查
**响应** `Result[DocumentUploadVO]` (`domian/vo/documentVo.py:8-27`):
```json
{
"code": 200,
"message": "ok",
"data": {
"documentId": 1,
"internalDocumentNo": 1712345678000000001,
"versionGroupKey": "abc123...",
"versionNo": 2,
"previousVersionId": 1,
"rootVersionId": 1,
"duplicateUpload": false,
"fileId": 5,
"typeId": 1,
"typeCode": "contract.construction",
"region": "meizhou",
"fileName": "建设工程合同.pdf",
"ossUrl": "http://minio/bdocs/...",
"speed": "normal",
"processingStatus": "waiting",
"autoRunTriggered": true,
"run": { "runId": 10, "status": "pending", ... }
}
}
```
---
### 1.2 GET /api/documents/list — 文档列表
**源码**: `controllers/documentController.py:47-67`
**实现**: `documentServiceImpl.py:248-443`
```
URL: GET /api/documents/list
格式: query params
```
| 参数 | 类型 | 必填 | 说明 |
|------|------|------|------|
| `page` | int | 否 (1) | 页码 |
| `pageSize` | int | 否 (20, max 100) | 每页条数 |
| `keyword` | str | 否 | 搜索文件名或归一化名 |
| `typeCode` | str | 否 | 文档类型编码过滤 |
| `region` | str | 否 | 地区过滤 |
| `processingStatus` | str | 否 | waiting/processing/completed/failed |
| `resultStatus` | str | 否 | pass/fail/partial/review/error |
**内部流程**:
1. `leaudit_documents d` JOIN `leaudit_document_files f` ON `f.document_id = d.id`
2. 条件: `d.is_latest_version=true`, `d.deleted_at IS NULL`, `f.is_active=true`, `f.file_role='primary'`
3. LEFT JOIN `leaudit_document_types dt`, `leaudit_audit_runs ar`
4. 子查询统计 `total_versions`(按 `version_group_key` 分组)
5. 批量加载历史版本(`is_latest_version=false` 的同组文档)
**响应** `Result[DocumentListPageVO]` (`domian/vo/documentVo.py:77-84`):
```json
{
"code": 200, "message": "ok",
"data": {
"total": 100, "page": 1, "pageSize": 20, "totalPages": 5,
"documents": [
{
"documentId": 1, "internalDocumentNo": 1712345678000000001,
"versionGroupKey": "abc123", "versionNo": 2,
"typeId": 1, "typeCode": "contract.construction",
"region": "meizhou", "normalizedName": "建设工程合同",
"fileId": 5, "fileName": "建设工程合同.pdf",
"fileExt": ".pdf", "mimeType": "application/pdf",
"fileSize": 1048576, "ossUrl": "http://minio/...",
"processingStatus": "completed",
"runStatus": "completed", "resultStatus": "pass",
"totalScore": 85, "passedCount": 10,
"failedCount": 2, "skippedCount": 0,
"hasHistory": true, "totalVersions": 2,
"historyVersions": [
{ "documentId": 1, "versionNo": 1, "fileName": "...", ... }
]
}
]
}
}
```
---
### 1.3 文档详情 / 更新 / 删除(现已实现)
#### GET /api/documents/{id}
- 用途:获取单个文档详情
- 返回:`Result[DocumentDetailVO]`
- 关键字段:
- 继承列表接口的版本链、文件、run、统计字段
- 新增 `documentNumber``remark``isTestDocument``auditStatus``pageCount`
- 历史版本:`historyVersions` 返回同一 `version_group_key` 下除当前文档外的其它未删除版本
#### PUT /api/documents/{id}
- 用途:更新文档元数据
- 请求体:`DocumentUpdateDTO`
```json
{
"documentNumber": "粤烟合同〔2026001号",
"remark": "地市管理员修订备注",
"isTestDocument": false,
"auditStatus": 1
}
```
- 字段说明:
- `documentNumber`:若 `leaudit_documents.document_number` 列存在则写入
- `remark`:若 `leaudit_documents.remark` 列存在则写入
- `isTestDocument`:若 `leaudit_documents.is_test_document` 列存在则写入
- `auditStatus`:若 `leaudit_documents.audit_status` 列存在则写入;若当前环境未加该列,则接口忽略该字段但不报错
#### DELETE /api/documents/{id}
- 用途:软删除文档
- 行为:
- `leaudit_documents.deleted_at = now()`
- 当前文档的 active file 置为 `is_active = false`
- 若删除的是当前最新版本,则自动把同版本链中剩余的最高版本提升为 `is_latest_version = true`
#### 数据隔离规则(已在后端强制执行)
| 角色 | 范围 |
|------|------|
| `super_admin` / `provincial_admin` | 全量文档 |
| `admin` | **仅本地市 `region = user.area`** |
| `common` | **仅自己上传的文档 `created_by = current_user_id`** |
额外约束:
- `admin` 若在列表接口传入其他地市 `region`,后端直接返回空结果
- `common` 若试图访问、修改、删除他人文档,后端返回“文档不存在或无权访问”
#### 尚未完成
| 操作 | 当前 | 需要 |
|------|------|------|
| 附件追加 | ❌ | `POST /api/documents/{id}/attachments` — fileRole=attachment |
---
### 1.4 自动评查 / 手动评查 / 状态结果查询
#### POST /api/upload
- 上传成功且 `autoRun=true` 时,后端会直接调用 `AuditService.Run()` 创建 run
- 队列档位当前只保留:
- `urgent`
- `normal`
#### POST /api/audit/run
- 用途:对已存在文档手动触发评查
- 典型参数:
- `documentId`
- `speed=normal|urgent`
- 当前收口:
- 若同一文档已有活动 run,后端优先复用,不重复创建
#### GET /api/audit/run/{runId}
- 用途:查询评查任务状态
- 关键状态:
- `queued`
- `running`
- `completed`
- `failed`
- 关键阶段:
- `dispatch`
- `prepare`
- `ocr / extraction / evaluation / rescue / persist`
#### GET /api/audit/result/{runId}
- 用途:查询评查结果
- 返回来源:
- `leaudit_rule_results`
- `leaudit_field_results`
- `leaudit_run_metrics`
- `leaudit_run_errors`
#### 当前评查链路口径
1. 上传文档
2. 建立 `leaudit_documents / leaudit_document_files`
3. 创建 `leaudit_audit_runs`
4.`speed` 投递 `leaudit.urgent``leaudit.normal`
5. worker 按 `runId` 查库
6. bridge 下载文档与规则 YAML 到本地临时文件
7. `NativeRunner` 构建原生 `AuditCtx`
8. `AuditService.audit(ctx)` 执行
9. `StorageAdapter` 结果写回 `leaudit_*`
---
## 二、前端调用现状(已验证)
### 2.1 上传 — `uploadDocumentToServer()`
**源码**: `new_doc_review/app/api/files/files-upload.ts:374-443`
```
URL: ${UPLOAD_URL}/upload (例: http://172.16.0.59:8096/upload)
格式: FormData { file: Blob, upload_info: JSON字符串 }
```
前端实际发送的 FormData:
```
file: Blob (文件二进制)
upload_info: '{"type_id":1,"evaluation_level":"normal","document_number":null,"remark":null,"is_test_document":false,"document_id":null,"is_reupload":false,"attribute_type":null}'
```
**❌ 与后端完全不兼容。** 后端期望扁平字段 `typeId`/`typeCode`/`region`/`fileRole`/`autoRun`/`speed`,不接受嵌套的 `upload_info` JSON。
### 2.2 附件追加 — `appendContractAttachments()`
**源码**: `files-upload.ts:320-336`
```
URL: ${UPLOAD_URL}/contracts/{documentId}/append_attachments
格式: FormData { attachments: File[] }
```
后端没有对应接口,需要补。
### 2.3 合同模板上传 — `uploadContractTemplate()`
**源码**: `files-upload.ts:244-259`
```
URL: ${UPLOAD_URL}/upload_contract_template
格式: FormData { file: Blob }
```
后端没有对应接口。
### 2.4 列表 — `getDocumentsListFromAPI()`
**源码**: `new_doc_review/app/api/files/documents.ts:627-784`
```
URL: ${API_BASE_URL}/api/documents/list ✅ 正确
```
✅ 列表已接入新后端,但存在两个问题:
1. **字段映射层太厚**`LeauditListItem → DocumentUI` 转换 50+ 行,且把 `fileExt` 映射为 `fileType``ossUrl` 映射为 `path`
2. **筛选项不全**`documentNumber``auditStatus``dateFrom/dateTo` 在后端列表接口未支持,前端写了 `void` 占位
### 2.5 删除 — `deleteDocument()`
**源码**: `documents.ts:540-560`
```
URL: /api/postgrest/proxy/documents?id=eq.{id}&user_id=eq.{userId}
方法: DELETE
```
❌ 走 PostgREST 直删,未接入新后端。
### 2.6 编辑 — `updateDocument()`
**源码**: `documents.ts:485-510`
```
URL: /api/postgrest/proxy/documents?id=eq.{id}
方法: PATCH
```
❌ 走 PostgREST,未接入新后端。
---
## 三、接入方案
### 第 1 步:上传接口对齐 ⭐ 最高优先级
**前端改造** `uploadDocumentToServer()`
```
改前: FormData { file, upload_info: JSON.stringify({ type_id, evaluation_level, ... }) }
POST ${UPLOAD_URL}/upload
改后: FormData { file, typeId, typeCode, region, fileRole, createdBy, autoRun, speed }
POST ${API_BASE_URL}/api/upload
```
字段映射:
| 前端旧参数 | 新后端参数 | 说明 |
|-----------|-----------|------|
| `upload_info.type_id` | `typeId` | 直接传 |
| — | `typeCode` | 补充,从 document types 查 |
| — | `region` | 从 sessionStorage / user info 取当前用户地区 |
| — | `fileRole` | `"primary"` |
| — | `createdBy` | 从 JWT payload 取 user_id |
| `evaluation_level` | `speed` | urgent/normal 映射 |
| — | `autoRun` | 默认 true(参考前端现有流程:上传后自动 treatment) |
附件和模板上传合并方案:
- 主文件:`POST /api/upload` + `fileRole=primary`
- 附件:`POST /api/upload` + `fileRole=attachment`(或批量复用主上传)
- 模板:`POST /api/upload` + `fileRole=template`
影响文件:
- `new_doc_review/app/api/files/files-upload.ts``uploadDocumentToServer()` 重写
- `new_doc_review/app/routes/files.upload.tsx``startUpload()` 适配新参数
### 第 2 步:列表字段精简
**前端字段直接对齐**,逐步去掉 `mapLeauditDocToAuditStatus``mapProcessingStatusToFileStatus` 等映射函数。`DocumentUI` 接口增加新字段,旧字段保留兼容。
影响文件:
- `new_doc_review/app/api/files/documents.ts` — 简化 `LeauditListItem → DocumentUI` 转换
### 第 3 步:补后端 CRUD
后端新增:
```
DELETE /api/documents/{id} → 软删除 (deleted_at = now())
PUT /api/documents/{id} → 更新元数据 (备注、测试标记)
GET /api/documents/{id} → 文档详情(含所有版本)
POST /api/documents/{id}/attachments → 追加附件
```
影响文件:
- `fastapi_modules/fastapi_leaudit/controllers/documentController.py`
- `fastapi_modules/fastapi_leaudit/services/impl/documentServiceImpl.py`
### 第 4 步:前端切割 PostgREST
`deleteDocument``updateDocument` 从前端 PostgREST 调用改为走新后端 `/api/documents/...`
### 第 5 步:清理旧依赖
确认无其他 PostgREST 调用后,移除旧 RPC 函数。
---
## 四、涉及文件清单
| 层 | 文件 | 改动类型 |
|----|------|---------|
| 后端 | `documentController.py` | 新增 3 个端点 |
| 后端 | `documentServiceImpl.py` | 新增 Delete/Update/GetById/AppendAttachments |
| 后端 | `documentVo.py` | 可能新增 VO |
| 前端 | `files-upload.ts` | 重写 uploadDocumentToServer |
| 前端 | `files.upload.tsx` | 适配新上传参数 |
| 前端 | `documents.ts` | 简化列表映射 + 切割删除/编辑到新后端 |
| 前端 | `documents.list.tsx` | 小改适配新字段 |
| 前端 | `documents.edit.tsx` | 切割到新后端 PUT |