fix: 1. 继续对齐交叉评查的接口,完善创建交叉评查的逻辑 和 相关组件的渲染布局。
2. 文档的基本信息修改改用接口。 3. 重新完善角色权限管理的页面逻辑。 4.将评查点列表中的返回逻辑改用浏览器的记忆返回。
This commit is contained in:
@@ -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 | 是 | 文档ID,PostgREST格式 | `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)
|
||||
Reference in New Issue
Block a user