# 文档上传与列表 — 接口分析 & 接入方案 > 所有信息已逐文件验证。源代码位置均已标注。 --- ## 一、后端接口(已验证) ### 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 | 上传用户 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 缺失的后端接口 | 操作 | 当前 | 需要 | |------|------|------| | 删除文档 | ❌ | `DELETE /api/documents/{id}` — 软删除 | | 编辑元数据 | ❌ | `PUT /api/documents/{id}` — 更新备注、测试标记等 | | 文档详情 | ❌ | `GET /api/documents/{id}` — 单文档查询 | | 附件追加 | ❌ | `POST /api/documents/{id}/attachments` — fileRole=attachment | --- ## 二、前端调用现状(已验证) ### 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 |