# 文档上传与评查接口 这份文档描述当前已经落地的文档上传、文档列表、自动评查、手动评查、状态查询、结果查询接口。 当前接口围绕以下业务语义设计: - 每次前端上传都会形成一个平台内部文档实例 - 同名文档会尝试归入同一个版本组 - 同名且内容相同: - 不新建版本 - `duplicateUpload=true` - 如果 `autoRun=true`,仍然可以重新走一次评查流程 - 同名但内容变化: - 新建版本 - 形成 `v2 / v3 / ...` - 评查任务走 worker 异步执行 - 队列只有两档: - `urgent` - `normal` --- ## 1. 上传接口 ### 路径 ```http POST /upload ``` ### Content-Type ```http multipart/form-data ``` ### 用途 - 上传文档 - 创建或命中文档版本 - 建立 `leaudit_documents / leaudit_document_files` - 可选自动触发评查 ### 请求参数 | 参数 | 类型 | 必填 | 说明 | |---|---|---:|---| | `file` | file | 是 | 上传文件 | | `typeId` | int | 否 | 文档类型 ID,和 `typeCode` 二选一至少传一个 | | `typeCode` | string | 否 | 文档类型编码,例如 `contract.sale` | | `region` | string | 否 | 区域,默认 `default` | | `fileRole` | string | 否 | 文件角色,默认 `primary` | | `createdBy` | int | 否 | 上传用户 ID | | `autoRun` | bool | 否 | 是否上传后自动触发评查,默认 `false` | | `speed` | string | 否 | 执行速度档位:`normal` / `urgent`,默认 `normal` | ### 版本匹配逻辑 上传时会先做版本候选匹配: 1. 归一化文件名,得到 `normalized_name` 2. 按以下条件查找最新版本候选: - `type_id` 相同 - `region` 相同 - `normalized_name` 相同 - `is_latest_version = true` - 主文件 `file_role = 'primary'` 3. 比较最新版本主文件的 `sha256` 结果分三种: - 找不到候选 - 新建版本组 - 当前版本为 `v1` - 找到候选且 `sha256` 相同 - 视为重复上传 - 不新建版本 - `duplicateUpload=true` - 找到候选但 `sha256` 不同 - 新建版本 - 当前版本为 `v2 / v3 / ...` - 旧版本 `is_latest_version=false` - 新版本 `is_latest_version=true` ### 队列路由逻辑 - `speed=urgent` -> 投递 `leaudit.urgent` - `speed=normal` -> 投递 `leaudit.normal` ### 请求示例:普通上传,不自动评查 ```bash curl -X POST 'http://127.0.0.1:8096/api/upload' \ -F 'file=@/path/to/合同.docx' \ -F 'typeCode=contract.sale' \ -F 'region=default' \ -F 'fileRole=primary' \ -F 'autoRun=false' \ -F 'speed=normal' ``` ### 返回示例:首次上传,命中 `v1` ```json { "code": 200, "message": "ok", "data": { "documentId": 11, "internalDocumentNo": 1777426812904262854, "versionGroupKey": "4e02e455aa504cb9b75a254727f1bb4c", "versionNo": 1, "previousVersionId": null, "rootVersionId": 11, "duplicateUpload": false, "fileId": 12, "typeId": 9, "typeCode": "contract.sale", "region": "default", "fileName": "版本归档验证合同.docx", "ossUrl": "bdocs/default/contract.sale/2026/04/11/v1/primary__版本归档验证合同.docx", "speed": "normal", "processingStatus": "waiting", "autoRunTriggered": false, "run": null } } ``` ### 返回示例:重复上传,不升版 ```json { "code": 200, "message": "ok", "data": { "documentId": 11, "internalDocumentNo": 1777426812904262854, "versionGroupKey": "4e02e455aa504cb9b75a254727f1bb4c", "versionNo": 1, "previousVersionId": null, "rootVersionId": 11, "duplicateUpload": true, "fileId": 12, "typeId": 9, "typeCode": "contract.sale", "region": "default", "fileName": "版本归档验证合同.docx", "ossUrl": "bdocs/default/contract.sale/2026/04/11/v1/primary__版本归档验证合同.docx", "speed": "normal", "processingStatus": "waiting", "autoRunTriggered": false, "run": null } } ``` ### 返回示例:同名但内容变化,自动形成 `v2` ```json { "code": 200, "message": "ok", "data": { "documentId": 12, "internalDocumentNo": 1777426813574315361, "versionGroupKey": "4e02e455aa504cb9b75a254727f1bb4c", "versionNo": 2, "previousVersionId": 11, "rootVersionId": 11, "duplicateUpload": false, "fileId": 13, "typeId": 9, "typeCode": "contract.sale", "region": "default", "fileName": "版本归档验证合同.docx", "ossUrl": "bdocs/default/contract.sale/2026/04/12/v2/primary__版本归档验证合同.docx", "speed": "normal", "processingStatus": "waiting", "autoRunTriggered": false, "run": null } } ``` ### 返回示例:重复上传但自动重新评查 ```json { "code": 200, "message": "ok", "data": { "documentId": 13, "internalDocumentNo": 1777427235286905027, "versionGroupKey": "4e02e455aa504cb9b75a254727f1bb4c", "versionNo": 3, "previousVersionId": 12, "rootVersionId": 11, "duplicateUpload": true, "fileId": 14, "typeId": 9, "typeCode": "contract.sale", "region": "default", "fileName": "版本归档验证合同.docx", "ossUrl": "bdocs/default/contract.sale/2026/04/13/v3/primary__版本归档验证合同.docx", "speed": "normal", "processingStatus": "queued", "autoRunTriggered": true, "run": { "runId": 13, "documentId": 13, "runNo": 2, "documentFileId": 14, "status": "queued", "phase": "dispatch", "resultStatus": null, "ruleSetId": 29, "ruleVersionId": 9, "ruleTypeId": "contract.sale", "rescueApplied": false, "totalScore": null, "passedCount": null, "failedCount": null, "skippedCount": null, "startedAt": null, "finishedAt": null } } } ``` --- ## 2. 文档列表接口 ### 路径 ```http GET /documents/list ``` ### 用途 - 返回文档主列表 - 只返回每个版本组的最新版本 - 每条记录附带历史版本摘要,前端可以直接做“展开历史版本” ### 查询参数 | 参数 | 类型 | 必填 | 说明 | |---|---|---:|---| | `page` | int | 否 | 页码,从 `1` 开始,默认 `1` | | `pageSize` | int | 否 | 每页数量,默认 `20`,最大 `100` | | `keyword` | string | 否 | 文件名 / 归一化名称模糊搜索 | | `typeCode` | string | 否 | 文档类型编码,例如 `contract.sale` | | `region` | string | 否 | 区域过滤 | | `processingStatus` | string | 否 | 文档处理状态过滤 | | `resultStatus` | string | 否 | 最新 run 的结果状态过滤 | ### 查询逻辑 - 主查询只看 `leaudit_documents.is_latest_version = true` - 只关联主文件: - `leaudit_document_files.is_active = true` - `leaudit_document_files.file_role = 'primary'` - 当前评查状态来自 `leaudit_audit_runs` - 历史版本按 `version_group_key` 再查一次并挂到 `historyVersions` ### 请求示例 ```bash curl 'http://127.0.0.1:8096/api/documents/list?page=1&pageSize=5' ``` ### 带筛选请求示例 ```bash curl 'http://127.0.0.1:8096/api/documents/list?page=1&pageSize=2&keyword=版本归档&typeCode=contract.sale®ion=default' ``` ### 返回示例 ```json { "code": 200, "message": "ok", "data": { "total": 1, "page": 1, "pageSize": 2, "totalPages": 1, "documents": [ { "documentId": 13, "internalDocumentNo": 1777427235286905027, "versionGroupKey": "4e02e455aa504cb9b75a254727f1bb4c", "versionNo": 3, "rootVersionId": 11, "previousVersionId": 12, "typeId": 9, "typeCode": "contract.sale", "region": "default", "normalizedName": "版本归档验证合同", "fileId": 14, "fileName": "版本归档验证合同.docx", "fileExt": "docx", "mimeType": "application/octet-stream", "fileSize": 587279, "ossUrl": "bdocs/default/contract.sale/2026/04/13/v3/primary__版本归档验证合同.docx", "processingStatus": "completed", "currentRunId": 13, "runStatus": "completed", "resultStatus": "review", "totalScore": 92.0, "passedCount": 25, "failedCount": 3, "skippedCount": 0, "updatedAt": "2026-04-29T01:50:05.241397+00:00", "hasHistory": true, "totalVersions": 3, "historyVersions": [ { "documentId": 12, "fileId": 13, "versionNo": 2, "fileName": "版本归档验证合同.docx", "fileExt": "docx", "processingStatus": "waiting", "runStatus": null, "resultStatus": null, "updatedAt": "2026-04-29T01:47:15.250697+00:00" }, { "documentId": 11, "fileId": 12, "versionNo": 1, "fileName": "版本归档验证合同.docx", "fileExt": "docx", "processingStatus": "waiting", "runStatus": null, "resultStatus": null, "updatedAt": "2026-04-29T01:40:13.538839+00:00" } ] } ] } } ``` ### 返回字段说明 | 字段 | 说明 | |---|---| | `documents[]` | 主列表,仅最新版本 | | `versionGroupKey` | 同一版本链的归档组键 | | `versionNo` | 当前版本号 | | `rootVersionId` | 版本链根文档 ID | | `previousVersionId` | 上一版本文档 ID | | `hasHistory` | 是否存在历史版本 | | `totalVersions` | 该版本组的总版本数 | | `historyVersions[]` | 历史版本摘要,按 `versionNo DESC` 排序 | --- ## 3. 手动触发评查 ### 路径 ```http POST /audit/run ``` ### 用途 - 对指定 `documentId` 手动触发一次新的评查 run - 不改变文档版本 - 只新增 `leaudit_audit_runs` ### 请求体 ```json { "documentId": 13, "ruleType": null, "force": false, "speed": "normal" } ``` ### 参数说明 | 字段 | 类型 | 必填 | 说明 | |---|---|---:|---| | `documentId` | int | 是 | 文档 ID | | `ruleType` | string/null | 否 | 指定规则类型编码 | | `force` | bool | 否 | 是否强制重跑 | | `speed` | string | 否 | `normal` / `urgent` | ### 请求示例 ```bash curl -X POST 'http://127.0.0.1:8096/api/audit/run' \ -H 'Content-Type: application/json' \ -d '{ "documentId": 13, "force": false, "speed": "urgent" }' ``` ### 返回示例 ```json { "code": 200, "message": "ok", "data": { "runId": 15, "documentId": 13, "runNo": 3, "documentFileId": 14, "status": "queued", "phase": "dispatch", "resultStatus": null, "ruleSetId": 29, "ruleVersionId": 9, "ruleTypeId": "contract.sale", "rescueApplied": false, "totalScore": null, "passedCount": null, "failedCount": null, "skippedCount": null, "startedAt": null, "finishedAt": null } } ``` --- ## 4. 查询运行状态 ### 路径 ```http GET /audit/run/{runId} ``` ### 用途 - 查询 run 当前状态 - 适合前端轮询 ### 状态说明 常见状态: - `queued` - `running` - `completed` - `failed` 常见阶段: - `dispatch` - `prepare` - `ocr` - `extract` - `evaluate` - `rescue` - `persist` - `executed` ### 请求示例 ```bash curl 'http://127.0.0.1:8096/api/audit/run/11' ``` ### 返回示例 ```json { "code": 200, "message": "ok", "data": { "runId": 11, "documentId": 10, "runNo": 1, "documentFileId": 11, "status": "completed", "phase": "executed", "resultStatus": "review", "ruleSetId": 29, "ruleVersionId": 9, "ruleTypeId": "contract.sale", "rescueApplied": true, "totalScore": 89.0, "passedCount": 24, "failedCount": 4, "skippedCount": 0, "startedAt": "2026-04-28T19:01:01.766352+08:00", "finishedAt": "2026-04-28T19:03:11.044894+08:00" } } ``` --- ## 5. 查询评查结果 ### 路径 ```http GET /audit/result/{runId} ``` ### 用途 - 查询本次 run 的完整结果 - 包括: - 规则结果 - 抽取字段 - 运行错误 - rescue 结果 - metrics - artifacts ### 请求示例 ```bash curl 'http://127.0.0.1:8096/api/audit/result/11' ``` ### 返回结构说明 顶层字段: | 字段 | 说明 | |---|---| | `runId` | 运行 ID | | `documentId` | 文档 ID | | `documentFileId` | 本次锁定的文件 ID | | `status` | 运行状态 | | `totalScore` | 总分 | | `passedCount` | 通过数 | | `failedCount` | 失败数 | | `skippedCount` | 跳过数 | | `phase` | 当前阶段 | | `resultStatus` | 总体结果 | | `rescueApplied` | 是否执行 rescue | | `ruleSetId` | 规则集 ID | | `ruleVersionId` | 规则版本 ID | | `startedAt` / `finishedAt` | 起止时间 | | `rules` | 规则结果列表 | | `fields` | 抽取字段列表 | | `errors` | 错误列表 | | `rescueOutcomes` | 补救结果列表 | | `metrics` | 阶段指标 | | `artifacts` | 产物列表 | ### 返回示例(节选) ```json { "code": 200, "message": "ok", "data": { "runId": 11, "documentId": 10, "documentFileId": 11, "status": "completed", "totalScore": 89.0, "passedCount": 24, "failedCount": 4, "skippedCount": 0, "phase": "executed", "resultStatus": "review", "rescueApplied": true, "ruleSetId": 29, "ruleVersionId": 9, "startedAt": "2026-04-28T19:01:01.766352+08:00", "finishedAt": "2026-04-28T19:03:11.044894+08:00", "rules": [ { "ruleId": "MM-SALE-012", "ruleName": "甲方信用代码校验", "passed": false, "status": "executed", "risk": "medium", "score": 3.0, "failMessage": "甲方统一社会信用代码校验位错误" } ], "fields": [ { "fieldName": "合同名称", "valueText": "智慧法务平台建设采购项目合同", "confidence": 0.9991 } ], "rescueOutcomes": [ { "ruleId": "MM-SALE-012", "status": "final_fail", "finalStatus": "review", "requiresHumanReview": true, "failureReason": "Agent (4 iter, requires_human): token_budget_exhausted" } ], "metrics": { "ocrSeconds": 79.06, "extractSeconds": 11.87, "evaluateSeconds": 9.9, "totalSeconds": 100.83, "pageCount": 2, "fieldCount": 35, "ruleCount": 28, "rescueRuleCount": 5, "artifactCount": 8 }, "artifacts": [ { "artifactType": "ocr_json", "fileName": "ocr_result.json", "fileExt": "json", "mimeType": "application/json" } ] } } ``` --- ## 6. worker 日志怎么看 worker 关键日志已经做了可读化,重点看这两类: ### 投递日志 ```text run_id=13 已投递到 worker 队列: queue=leaudit.normal, speed=normal, task_id=... ``` ### 执行日志 ```text run_id=13 worker开始执行: queue=leaudit.normal, speed=normal, filename=版本归档验证合同.docx ``` 结合状态查询接口可以快速判断: - 是否已经成功投递 - 是否已被 worker 消费 - 跑的是 `urgent` 还是 `normal` --- ## 7. 前端建议接法 ### 文档上传页 1. 调 `POST /upload` 2. 读取返回: - `documentId` - `versionGroupKey` - `versionNo` - `duplicateUpload` - `run` ### 自动评查场景 如果 `autoRun=true` 且返回里 `run != null`: 1. 取 `run.runId` 2. 轮询 `GET /audit/run/{runId}` 3. `status=completed/failed` 后停止轮询 4. 再调 `GET /audit/result/{runId}` ### 列表页 列表页建议默认只展示: - `is_latest_version = true` 的 document 点击某条后,再按: - `versionGroupKey` 展开其历史版本。