fix: 1. 继续对齐交叉评查的接口,完善创建交叉评查的逻辑 和 相关组件的渲染布局。

2. 文档的基本信息修改改用接口。      3. 重新完善角色权限管理的页面逻辑。     4.将评查点列表中的返回逻辑改用浏览器的记忆返回。
This commit is contained in:
2025-12-12 12:00:36 +08:00
parent a5c49a5c95
commit d4000cd292
25 changed files with 4750 additions and 28293 deletions
+499
View File
@@ -0,0 +1,499 @@
# 文档更新接口详细文档
> 文件路径: `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)