# 文档更新接口详细文档 > 文件路径: `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 & { 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 = {}; 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>( '/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)