feat: add document versioning and list API
This commit is contained in:
@@ -0,0 +1,307 @@
|
||||
# 新系统版 `documents/list` 接口
|
||||
|
||||
这份文档专门说明当前 `leaudit-platform` 里已经落地的“新系统版文档列表接口”。
|
||||
|
||||
目标很明确:
|
||||
|
||||
- 前端文档列表只看“最新版本”
|
||||
- 同名文档的历史版本直接归到同一个版本链
|
||||
- 列表接口直接返回历史版本摘要,前端不用自己再拼版本关系
|
||||
|
||||
---
|
||||
|
||||
## 1. 接口路径
|
||||
|
||||
```http
|
||||
GET /api/documents/list
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 2. 当前接口语义
|
||||
|
||||
这个接口不是“把所有上传记录平铺出来”。
|
||||
|
||||
它的语义是:
|
||||
|
||||
- 每个 `version_group_key` 只返回一条“最新版本文档”
|
||||
- 这条最新版本文档下面附带 `historyVersions`
|
||||
- `historyVersions` 里放的是同组下更老的版本摘要
|
||||
|
||||
也就是说,前端主列表看到的是:
|
||||
|
||||
- 当前版本
|
||||
- 是否有历史版本
|
||||
- 一共有多少版本
|
||||
- 历史版本有哪些
|
||||
|
||||
---
|
||||
|
||||
## 3. 请求参数
|
||||
|
||||
| 参数 | 类型 | 必填 | 说明 |
|
||||
|---|---|---:|---|
|
||||
| `page` | int | 否 | 页码,从 `1` 开始,默认 `1` |
|
||||
| `pageSize` | int | 否 | 每页数量,默认 `20`,最大 `100` |
|
||||
| `keyword` | string | 否 | 按文件名或归一化名称模糊搜索 |
|
||||
| `typeCode` | string | 否 | 文档类型编码,例如 `contract.sale` |
|
||||
| `region` | string | 否 | 区域 |
|
||||
| `processingStatus` | string | 否 | 文档处理状态 |
|
||||
| `resultStatus` | string | 否 | 最新 run 的结果状态 |
|
||||
|
||||
请求示例:
|
||||
|
||||
```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'
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 4. 返回结构
|
||||
|
||||
返回模型是分页结构:
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"message": "ok",
|
||||
"data": {
|
||||
"total": 1,
|
||||
"page": 1,
|
||||
"pageSize": 20,
|
||||
"totalPages": 1,
|
||||
"documents": []
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
其中 `documents[]` 的单条结构核心字段如下:
|
||||
|
||||
| 字段 | 说明 |
|
||||
|---|---|
|
||||
| `documentId` | 当前最新版本文档 ID |
|
||||
| `internalDocumentNo` | 平台内部追踪号 |
|
||||
| `versionGroupKey` | 版本归档组键 |
|
||||
| `versionNo` | 当前版本号 |
|
||||
| `rootVersionId` | 版本链根文档 ID |
|
||||
| `previousVersionId` | 上一版本文档 ID |
|
||||
| `typeId` | 文档类型 ID |
|
||||
| `typeCode` | 文档类型编码 |
|
||||
| `region` | 区域 |
|
||||
| `normalizedName` | 归一化后的名称 |
|
||||
| `fileId` | 当前主文件 ID |
|
||||
| `fileName` | 文件名 |
|
||||
| `fileExt` | 文件扩展名 |
|
||||
| `mimeType` | MIME 类型 |
|
||||
| `fileSize` | 文件大小 |
|
||||
| `ossUrl` | 对象存储路径 |
|
||||
| `processingStatus` | 文档处理状态 |
|
||||
| `currentRunId` | 当前 run ID |
|
||||
| `runStatus` | 当前 run 状态 |
|
||||
| `resultStatus` | 当前 run 结果状态 |
|
||||
| `totalScore` | 总分 |
|
||||
| `passedCount` | 通过数 |
|
||||
| `failedCount` | 失败数 |
|
||||
| `skippedCount` | 跳过数 |
|
||||
| `updatedAt` | 更新时间 |
|
||||
| `hasHistory` | 是否有历史版本 |
|
||||
| `totalVersions` | 总版本数 |
|
||||
| `historyVersions` | 历史版本摘要列表 |
|
||||
|
||||
`historyVersions[]` 结构:
|
||||
|
||||
| 字段 | 说明 |
|
||||
|---|---|
|
||||
| `documentId` | 历史版本文档 ID |
|
||||
| `fileId` | 历史版本文件 ID |
|
||||
| `versionNo` | 历史版本号 |
|
||||
| `fileName` | 文件名 |
|
||||
| `fileExt` | 文件扩展名 |
|
||||
| `processingStatus` | 处理状态 |
|
||||
| `runStatus` | 运行状态 |
|
||||
| `resultStatus` | 结果状态 |
|
||||
| `updatedAt` | 更新时间 |
|
||||
|
||||
---
|
||||
|
||||
## 5. SQL 逻辑
|
||||
|
||||
### 5.1 主列表计数 SQL
|
||||
|
||||
```sql
|
||||
SELECT COUNT(*)
|
||||
FROM leaudit_documents d
|
||||
JOIN leaudit_document_files f
|
||||
ON f.document_id = d.id
|
||||
LEFT JOIN leaudit_document_types dt
|
||||
ON dt.id = d.type_id
|
||||
LEFT JOIN leaudit_audit_runs ar
|
||||
ON ar.id = d.current_run_id
|
||||
WHERE d.is_latest_version = true
|
||||
AND d.deleted_at IS NULL
|
||||
AND f.is_active = true
|
||||
AND f.file_role = 'primary'
|
||||
-- 可选过滤:
|
||||
-- AND (f.file_name ILIKE :keyword OR d.normalized_name ILIKE :keyword)
|
||||
-- AND dt.code = :type_code
|
||||
-- AND d.region = :region
|
||||
-- AND d.processing_status = :processing_status
|
||||
-- AND ar.result_status = :result_status
|
||||
```
|
||||
|
||||
### 5.2 主列表分页 SQL
|
||||
|
||||
```sql
|
||||
SELECT
|
||||
d.id AS document_id,
|
||||
d.biz_document_id AS internal_document_no,
|
||||
d.version_group_key,
|
||||
d.version_no,
|
||||
d.root_version_id,
|
||||
d.previous_version_id,
|
||||
d.type_id,
|
||||
dt.code AS type_code,
|
||||
d.region,
|
||||
d.normalized_name,
|
||||
d.processing_status,
|
||||
d.current_run_id,
|
||||
d.updated_at,
|
||||
f.id AS file_id,
|
||||
f.file_name,
|
||||
f.file_ext,
|
||||
f.mime_type,
|
||||
f.file_size,
|
||||
f.oss_url,
|
||||
ar.status AS run_status,
|
||||
ar.result_status,
|
||||
ar.total_score,
|
||||
ar.passed_count,
|
||||
ar.failed_count,
|
||||
ar.skipped_count,
|
||||
vc.total_versions,
|
||||
COALESCE(vc.total_versions, 1) > 1 AS has_history
|
||||
FROM leaudit_documents d
|
||||
JOIN leaudit_document_files f
|
||||
ON f.document_id = d.id
|
||||
LEFT JOIN leaudit_document_types dt
|
||||
ON dt.id = d.type_id
|
||||
LEFT JOIN leaudit_audit_runs ar
|
||||
ON ar.id = d.current_run_id
|
||||
LEFT JOIN (
|
||||
SELECT version_group_key, COUNT(*) AS total_versions
|
||||
FROM leaudit_documents
|
||||
WHERE deleted_at IS NULL
|
||||
GROUP BY version_group_key
|
||||
) vc
|
||||
ON vc.version_group_key = d.version_group_key
|
||||
WHERE d.is_latest_version = true
|
||||
AND d.deleted_at IS NULL
|
||||
AND f.is_active = true
|
||||
AND f.file_role = 'primary'
|
||||
ORDER BY d.updated_at DESC, d.id DESC
|
||||
LIMIT :limit OFFSET :offset
|
||||
```
|
||||
|
||||
### 5.3 历史版本摘要 SQL
|
||||
|
||||
```sql
|
||||
SELECT
|
||||
d.version_group_key,
|
||||
d.id AS document_id,
|
||||
d.version_no,
|
||||
d.processing_status,
|
||||
d.updated_at,
|
||||
f.id AS file_id,
|
||||
f.file_name,
|
||||
f.file_ext,
|
||||
ar.status AS run_status,
|
||||
ar.result_status
|
||||
FROM leaudit_documents d
|
||||
JOIN leaudit_document_files f
|
||||
ON f.document_id = d.id
|
||||
AND f.is_active = true
|
||||
AND f.file_role = 'primary'
|
||||
LEFT JOIN leaudit_audit_runs ar
|
||||
ON ar.id = d.current_run_id
|
||||
WHERE d.version_group_key = ANY(:group_keys)
|
||||
AND d.is_latest_version = false
|
||||
AND d.deleted_at IS NULL
|
||||
ORDER BY d.version_group_key, d.version_no DESC, d.id DESC
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 6. 为什么新系统要这么做
|
||||
|
||||
因为现在我们已经不是老系统那种“文档记录 + 旁路版本信息”的模式了。
|
||||
|
||||
当前新系统已经有真正的版本链字段:
|
||||
|
||||
- `version_group_key`
|
||||
- `version_no`
|
||||
- `previous_version_id`
|
||||
- `root_version_id`
|
||||
- `is_latest_version`
|
||||
- `normalized_name`
|
||||
|
||||
所以列表天然应该是:
|
||||
|
||||
- 主列表 = 最新版本
|
||||
- 展开项 = 历史版本
|
||||
|
||||
而不是把所有版本平铺在一个列表里。
|
||||
|
||||
---
|
||||
|
||||
## 7. 当前代码落点
|
||||
|
||||
实现代码在这些文件:
|
||||
|
||||
- 路由:`fastapi_modules/fastapi_leaudit/controllers/documentController.py`
|
||||
- 服务接口:`fastapi_modules/fastapi_leaudit/services/documentService.py`
|
||||
- VO:`fastapi_modules/fastapi_leaudit/domian/vo/documentVo.py`
|
||||
- 具体实现:`fastapi_modules/fastapi_leaudit/services/impl/documentServiceImpl.py`
|
||||
|
||||
---
|
||||
|
||||
## 8. 当前验证结果
|
||||
|
||||
已经实际验证通过:
|
||||
|
||||
```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'
|
||||
```
|
||||
|
||||
验证结论:
|
||||
|
||||
- 接口正常返回
|
||||
- 主列表只返回最新版本
|
||||
- `historyVersions` 能正确带出历史版本摘要
|
||||
- 已验证版本组 `4e02e455aa504cb9b75a254727f1bb4c`
|
||||
- `documentId=13` 是当前最新 `v3`
|
||||
- `documentId=12` 是 `v2`
|
||||
- `documentId=11` 是 `v1`
|
||||
|
||||
---
|
||||
|
||||
## 9. 下一步建议
|
||||
|
||||
如果后面前端需要更完整的“版本展开页”,再补一个:
|
||||
|
||||
```http
|
||||
GET /api/documents/{documentId}/versions
|
||||
```
|
||||
|
||||
但当前列表页场景下,`/documents/list` 已经够用了。
|
||||
Reference in New Issue
Block a user