Files
leaudit-platform-frontend/auth_doc/document_update_api.md
T
LiangShiyong d4000cd292 fix: 1. 继续对齐交叉评查的接口,完善创建交叉评查的逻辑 和 相关组件的渲染布局。
2. 文档的基本信息修改改用接口。      3. 重新完善角色权限管理的页面逻辑。     4.将评查点列表中的返回逻辑改用浏览器的记忆返回。
2025-12-12 12:00:36 +08:00

500 lines
13 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.
# 文档更新接口详细文档
> 文件路径: `app/routes/postgrest.py` (PostgREST代理)
>
> 版本: v2.0 (RBAC-Enabled)
## 接口概述
| 项目 | 说明 |
|------|------|
| **接口名称** | 更新文档信息 |
| **请求方式** | `PATCH` |
| **接口路径** | `/api/postgrest/proxy/documents` |
| **认证方式** | JWT Bearer Token (完整签名验证) |
| **权限要求** | 需要 `document:update` 权限 |
---
## 请求说明
### 请求头 (Headers)
| 参数名 | 类型 | 必填 | 说明 |
|--------|------|------|------|
| `Authorization` | string | 是 | Bearer Token,格式:`Bearer {jwt_token}` |
| `Content-Type` | string | 是 | `application/json` |
### 查询参数 (Query Parameters)
| 参数名 | 类型 | 必填 | 说明 | 示例 |
|--------|------|------|------|------|
| `id` | string | 是 | 文档IDPostgREST格式 | `eq.123` |
> **安全说明**: 系统会自动注入 `user_id` 过滤条件,确保用户只能更新自己的文档。
### 请求体 (Request Body)
```json
{
"document_number": "string", // 可选,文档编号
"audit_status": 0, // 可选,审核状态
"is_test_document": false, // 可选,是否测试文档
"remark": "string" // 可选,备注信息
}
```
#### 可更新字段详情
| 字段名 | 类型 | 最大长度 | 说明 |
|--------|------|----------|------|
| `document_number` | varchar | 100 | 合同编号、许可证号等 |
| `audit_status` | integer | - | 审核状态:0=待审核, 1=通过, 2=审核中, -1=不通过, -2=警告 |
| `is_test_document` | boolean | - | 是否为测试文档,默认 false |
| `remark` | text | 255 | 备注信息 |
---
## 响应说明
### 成功响应 (HTTP 200)
返回更新后的文档完整信息(数组格式):
```json
[
{
"id": 123,
"user_id": 5,
"type_id": 1,
"name": "合同文档.pdf",
"document_number": "HT-2024-001",
"path": "documents/2024/01/abc123.pdf",
"storage_type": "minio",
"file_size": 1024000,
"upload_time": "2024-01-15T10:30:00",
"is_test_document": false,
"evaluation_level": "普通",
"status": "processed",
"evaluations_status": 1,
"audit_status": 1,
"remark": "已审核通过",
"created_at": "2024-01-15T10:30:00+08:00",
"updated_at": "2024-01-16T14:20:00+08:00"
}
]
```
### 错误响应
#### 401 未授权
```json
{
"detail": "Token已过期"
}
```
```json
{
"detail": "无效的Token"
}
```
#### 403 权限不足
```json
{
"detail": "权限不足:需要 'document:update' 权限才能访问 'documents' 表"
}
```
#### 404 文档不存在或无权访问
```json
[]
```
> **注意**: 返回空数组表示没有匹配的记录(可能是文档不存在,或用户无权更新该文档)
---
## 数据库字段完整说明
### documents 表结构
| 字段名 | 类型 | 可空 | 默认值 | 说明 |
|--------|------|------|--------|------|
| `id` | integer | 否 | 自增 | 文档ID,主键 |
| `user_id` | integer | 是 | - | 上传用户ID,外键→sso_users |
| `type_id` | integer | 否 | - | 文档类型ID,外键→document_types |
| `name` | varchar(255) | 否 | - | 原始文件名 |
| `document_number` | varchar(100) | 是 | - | 合同编号/许可证号 |
| `path` | varchar(255) | 否 | - | MinIO存储路径 |
| `storage_type` | varchar(20) | 否 | 'minio' | 存储方式 |
| `file_size` | integer | 是 | - | 文件大小(字节) |
| `upload_time` | timestamp | 是 | CURRENT_TIMESTAMP | 上传时间 |
| `is_test_document` | boolean | 是 | false | 是否测试文档 |
| `evaluation_level` | varchar(255) | 是 | - | 评查级别 |
| `status` | varchar(20) | 是 | 'waiting' | 处理状态 |
| `ocr_result` | jsonb | 是 | - | OCR处理结果 |
| `extracted_results` | jsonb | 是 | - | 内容抽取结果 |
| `sumary` | text | 是 | - | 评查结果摘要 |
| `evaluations_status` | integer | 是 | - | 评查状态 |
| `audit_status` | integer | 是 | - | 审核状态 |
| `error_massage` | varchar(500) | 是 | - | 错误信息 |
| `remark` | text | 是 | - | 备注 |
| `awareness_enabled` | boolean | 是 | false | 是否启用实体感知 |
| `awareness_result` | jsonb | 是 | - | VLM实体感知结果 |
| `awareness_execution_time` | double | 是 | - | 感知执行耗时(秒) |
| `awareness_created_at` | timestamp | 是 | - | 感知执行时间 |
| `original_word_path` | varchar(255) | 是 | - | 原始Word文件路径 |
| `created_by` | integer | 是 | - | 创建者ID |
| `created_at` | timestamptz | 是 | now() | 创建时间 |
| `updated_at` | timestamptz | 是 | now() | 更新时间(自动触发器) |
---
## 权限与安全机制
### 1. JWT 验证流程
```
请求 → JWT签名验证 → 有效期验证 → Audience验证 → 用户身份提取 → RBAC权限检查
```
**验证内容**:
- JWT签名验证:使用 `JWT_SECRET` 验证Token未被篡改
- 有效期验证:检查 `exp` 字段确保Token未过期
- Audience验证:检查 `aud` 字段匹配 `JWT_AUDIENCE`
- 必填字段:验证 `user_id`, `username`, `user_role`, `exp` 存在
### 2. RBAC 权限检查
```python
# 权限检查逻辑
permission_key = PostgRESTRBACMapping.get_permission_key("documents", "PATCH")
# → 返回 "document:update"
has_permission = await PermissionChecker.check_permission(
user_id=user_id,
permission_key="document:update"
)
```
### 3. 数据范围控制 (Data Scope)
| 角色 | 数据范围 | 说明 |
|------|----------|------|
| `provincial_admin` | ALL | 可访问所有文档,无过滤 |
| `admin` | DEPT | 仅本地区文档 (area=用户地区) |
| `user` | SELF | 仅本人文档 (user_id=当前用户ID) |
**系统自动注入过滤条件**:
```python
# SELF 范围
request_data.params["user_id"] = f"eq.{user_id}"
# DEPT 范围 (V2: 使用area而非ou_id)
request_data.params["area"] = f"eq.{user_area}"
```
### 4. 交叉评查权限
若用户参与交叉评查任务,可访问任务相关的跨地区文档:
```python
# 获取用户可访问的交叉评查文档
accessible_doc_ids = await CrossReviewPermission.get_user_accessible_documents(
user_id=user_id,
use_cache=True
)
# PATCH请求:检查目标文档是否在交叉评查权限范围
if target_doc_id in accessible_doc_ids:
# 移除数据范围限制,允许更新
del request_data.params["user_id"]
```
### 5. 安全过滤
系统自动过滤危险参数,防止权限绕过:
```python
DANGEROUS_PARAMS = ["select", "columns", "on_conflict", "resolution", ...]
# 自动移除危险参数
for dangerous_param in DANGEROUS_PARAMS:
if dangerous_param in request_data.params:
del request_data.params[dangerous_param]
```
---
## 调用示例
### cURL 示例
```bash
curl -X PATCH \
'https://api.example.com/api/postgrest/proxy/documents?id=eq.123' \
-H 'Authorization: Bearer eyJhbGciOiJIUzI1NiIs...' \
-H 'Content-Type: application/json' \
-d '{
"document_number": "HT-2024-001",
"audit_status": 1,
"remark": "审核通过"
}'
```
### TypeScript/JavaScript 示例
```typescript
/**
* 更新文档信息
* @param id 文档ID
* @param document 部分文档数据
* @param userId 用户ID
* @param frontendJWT JWT Token
* @returns 更新结果
*/
export async function updateDocument(
id: string,
document: Partial<DocumentUI> & { remark?: string },
userId: string,
frontendJWT?: string
): Promise<{ data?: DocumentUI; error?: string; status?: number }> {
try {
if (!id) {
return { error: '文档ID不能为空', status: 400 };
}
if (!userId) {
return { error: '用户身份验证失败', status: 401 };
}
// 准备API数据 - 将UI数据转换为API格式
const apiDocument: Partial<Document> = {};
if (document.documentNumber !== undefined) {
apiDocument.document_number = document.documentNumber;
}
if (document.auditStatus !== undefined) {
apiDocument.audit_status = document.auditStatus;
}
if (document.isTest !== undefined) {
apiDocument.is_test_document = document.isTest;
}
if (document.remark !== undefined) {
apiDocument.remark = document.remark;
}
// 发送PATCH请求
const response = await postgrestPut<Document, Partial<Document>>(
'/api/postgrest/proxy/documents',
apiDocument,
{
id: parseInt(id),
user_id: parseInt(userId) // 确保只能更新自己的文档
},
frontendJWT
);
if (response.error) {
console.error('更新文档API错误:', response.error);
return { error: response.error, status: response.status };
}
// 获取更新后的完整文档数据
const updatedResponse = await getDocument(id, userId, frontendJWT);
return updatedResponse;
} catch (error) {
console.error('更新文档信息失败:', error);
return {
error: error instanceof Error ? error.message : '更新文档信息失败',
status: 500
};
}
}
```
### Python 示例
```python
import httpx
async def update_document(
document_id: int,
update_data: dict,
jwt_token: str
) -> dict:
"""
更新文档信息
Args:
document_id: 文档ID
update_data: 要更新的字段
jwt_token: JWT Token
Returns:
更新后的文档数据
"""
async with httpx.AsyncClient() as client:
response = await client.patch(
f"https://api.example.com/api/postgrest/proxy/documents",
params={"id": f"eq.{document_id}"},
json=update_data,
headers={
"Authorization": f"Bearer {jwt_token}",
"Content-Type": "application/json"
}
)
if response.status_code == 200:
return {"success": True, "data": response.json()}
else:
return {"success": False, "error": response.text}
# 使用示例
result = await update_document(
document_id=123,
update_data={
"document_number": "HT-2024-001",
"audit_status": 1,
"remark": "审核通过"
},
jwt_token="eyJhbGciOiJIUzI1NiIs..."
)
```
---
## 状态枚举值
### status (处理状态)
| 值 | 说明 | 触发条件 |
|----|------|----------|
| `waiting` | 上传后默认状态,等待处理 | 文档上传完成 |
| `Cutting` | 切分+OCR处理中 | OCR任务开始 |
| `extractioning` | 大模型抽取信息中 | 信息抽取任务开始 |
| `evaluationing` | 评查中 | 评查任务开始 |
| `processed` | 处理完成,等待审核 | 所有处理完成 |
### audit_status (审核状态)
| 值 | 说明 | 操作权限 |
|----|------|----------|
| 0 | 待审核 | 等待审核员处理 |
| 1 | 通过 | 审核通过,流程结束 |
| 2 | 审核中 | 审核员正在处理 |
| -1 | 不通过 | 需要修改后重新提交 |
| -2 | 警告 | 有问题但可继续流程 |
### evaluations_status (评查状态)
| 值 | 说明 | 后续操作 |
|----|------|----------|
| 1 | 通过 | 无需人工干预 |
| 0 | 待人工确认 | 需要人工审核确认 |
| -1 | 不通过 | 存在严重问题 |
| -2 | 警告 | 存在轻微问题 |
---
## 后端处理流程
```
1. 接收 PATCH 请求
2. JWT 完整验证 (签名+有效期+Audience)
3. 提取用户信息 (user_id, username, user_role, area)
4. RBAC 权限检查 (document:update)
5. 安全过滤 (移除危险参数)
6. 数据范围注入 (根据角色添加过滤条件)
7. 交叉评查权限检查 (如适用)
8. 转发请求到 PostgREST
9. 返回更新结果
```
---
## 注意事项
1. **自动更新时间**: `updated_at` 字段由数据库触发器 `update_documents_updated_at` 自动更新,无需手动传入
2. **字段长度限制**:
- `document_number`: 最大100字符
- `remark`: 前端限制为255字符
- `error_massage`: 最大500字符
3. **只读字段**: 以下字段不可通过此接口修改
- `id`, `user_id`, `type_id`
- `name`, `path`, `storage_type`, `file_size`
- `upload_time`, `created_at`
- `ocr_result`, `extracted_results` (由系统处理任务更新)
4. **权限隔离**: 普通用户只能更新自己上传的文档,系统会强制校验 `user_id`
5. **PostgREST 格式**: 查询参数需使用 PostgREST 格式
- 等于: `eq.值` (如 `id=eq.123`)
- 不等于: `neq.值`
- 大于: `gt.值`
- 包含: `in.(值1,值2,值3)`
---
## 审计日志
所有更新操作都会记录审计日志:
```python
await AuditLogger.log_permission_check(
user_id=user_id,
permission_key="document:update",
is_success=True,
ip_address=client_ip,
user_agent=user_agent,
context={
'table': 'documents',
'method': 'PATCH',
'path': 'documents'
}
)
```
---
## 错误码说明
| HTTP状态码 | 错误类型 | 说明 | 解决方案 |
|-----------|---------|------|---------|
| 400 | Bad Request | 请求参数错误 | 检查请求体格式和字段类型 |
| 401 | Unauthorized | Token无效或过期 | 重新登录获取新Token |
| 403 | Forbidden | 权限不足 | 联系管理员分配权限 |
| 404 | Not Found | 文档不存在 | 检查document_id是否正确 |
| 500 | Internal Error | 服务器内部错误 | 查看服务器日志 |
---
## 相关文档
- [PostgREST代理说明](../14_postgrest_proxy/README.md)
- [RBAC权限系统](../../rbac/README.md)
- [文档管理概述](./README.md)
- [交叉评查权限](../../rbac/cross_review_permissions_design.md)