# 评查点分组 FastAPI v3 后端接口规范 ## 📚 目录 - [数据模型定义](#数据模型定义) - [接口列表](#接口列表) - [详细接口说明](#详细接口说明) - [错误处理规范](#错误处理规范) - [验证规则](#验证规则) - [完整测试用例](#完整测试用例) --- ## 数据模型定义 ### Python (FastAPI/Pydantic) 模型 ```python from pydantic import BaseModel, Field, validator from typing import Optional, List from datetime import datetime from enum import Enum # ==================== 基础模型 ==================== class EvaluationPointGroupBase(BaseModel): """评查点分组基础模型""" id: int = Field(..., description="分组ID") pid: Optional[int] = Field(None, description="父分组ID,null表示一级分组") name: str = Field(..., min_length=1, max_length=100, description="分组名称") code: str = Field(..., min_length=1, max_length=50, description="分组编码") description: Optional[str] = Field(None, max_length=500, description="分组描述") is_enabled: bool = Field(True, description="是否启用") created_at: datetime = Field(..., description="创建时间") updated_at: datetime = Field(..., description="更新时间") rule_count: Optional[int] = Field(0, ge=0, description="关联的评查点数量(包含子分组)") class Config: json_schema_extra = { "example": { "id": 1, "pid": None, "name": "合同管理类", "code": "CONTRACT_001", "description": "合同管理相关的评查点分组", "is_enabled": True, "created_at": "2025-01-15T10:30:00Z", "updated_at": "2025-01-15T10:30:00Z", "rule_count": 25 } } class EvaluationPointGroupTree(EvaluationPointGroupBase): """评查点分组树形结构模型""" children: List['EvaluationPointGroupTree'] = Field(default_factory=list, description="子分组列表") class Config: json_schema_extra = { "example": { "id": 1, "pid": None, "name": "合同管理类", "code": "CONTRACT_001", "description": "合同管理相关的评查点分组", "is_enabled": True, "created_at": "2025-01-15T10:30:00Z", "updated_at": "2025-01-15T10:30:00Z", "rule_count": 25, "children": [ { "id": 2, "pid": 1, "name": "合同审批", "code": "CONTRACT_001_001", "description": None, "is_enabled": True, "created_at": "2025-01-15T10:31:00Z", "updated_at": "2025-01-15T10:31:00Z", "rule_count": 10, "children": [] } ] } } # 启用递归模型 EvaluationPointGroupTree.model_rebuild() # ==================== 请求模型 ==================== class EvaluationPointGroupCreateRequest(BaseModel): """创建分组请求模型""" pid: Optional[int] = Field(None, description="父分组ID,null表示创建一级分组") name: str = Field(..., min_length=1, max_length=100, description="分组名称") code: str = Field(..., min_length=1, max_length=50, description="分组编码,必须唯一") description: Optional[str] = Field(None, max_length=500, description="分组描述") is_enabled: bool = Field(True, description="是否启用") @validator('name') def name_must_not_be_empty(cls, v): if not v or not v.strip(): raise ValueError('分组名称不能为空') return v.strip() @validator('code') def code_must_be_valid(cls, v): if not v or not v.strip(): raise ValueError('分组编码不能为空') # 可以添加更多编码格式验证 return v.strip().upper() class Config: json_schema_extra = { "example": { "pid": 1, "name": "新分组名称", "code": "NEW_GROUP_001", "description": "分组描述", "is_enabled": True } } class EvaluationPointGroupUpdateRequest(BaseModel): """更新分组请求模型""" pid: Optional[int] = Field(None, description="父分组ID") name: str = Field(..., min_length=1, max_length=100, description="分组名称") code: str = Field(..., min_length=1, max_length=50, description="分组编码") description: Optional[str] = Field(None, max_length=500, description="分组描述") is_enabled: bool = Field(..., description="是否启用") @validator('name') def name_must_not_be_empty(cls, v): if not v or not v.strip(): raise ValueError('分组名称不能为空') return v.strip() class Config: json_schema_extra = { "example": { "pid": 1, "name": "更新后的名称", "code": "UPDATED_001", "description": "更新后的描述", "is_enabled": False } } class BatchUpdateStatusRequest(BaseModel): """批量更新状态请求模型""" ids: List[int] = Field(..., min_items=1, description="要更新的分组ID列表") is_enabled: bool = Field(..., description="目标状态") class Config: json_schema_extra = { "example": { "ids": [1, 2, 3], "is_enabled": False } } class BatchDeleteRequest(BaseModel): """批量删除请求模型""" ids: List[int] = Field(..., min_items=1, description="要删除的分组ID列表") class Config: json_schema_extra = { "example": { "ids": [10, 11, 12] } } # ==================== 响应模型 ==================== class EvaluationPointGroupListResponse(BaseModel): """分组列表响应模型""" data: List[EvaluationPointGroupBase] = Field(..., description="分组列表") total: int = Field(..., ge=0, description="总记录数") page: int = Field(..., ge=1, description="当前页码") page_size: int = Field(..., ge=1, le=1000, description="每页记录数") class Config: json_schema_extra = { "example": { "data": [ { "id": 1, "pid": None, "name": "合同管理类", "code": "CONTRACT_001", "description": "合同管理相关的评查点分组", "is_enabled": True, "created_at": "2025-01-15T10:30:00Z", "updated_at": "2025-01-15T10:30:00Z", "rule_count": 25 } ], "total": 100, "page": 1, "page_size": 20 } } class EvaluationPointGroupTreeResponse(BaseModel): """树形结构响应模型""" data: List[EvaluationPointGroupTree] = Field(..., description="树形结构数据") class Config: json_schema_extra = { "example": { "data": [ { "id": 1, "pid": None, "name": "合同管理类", "code": "CONTRACT_001", "description": "合同管理相关的评查点分组", "is_enabled": True, "created_at": "2025-01-15T10:30:00Z", "updated_at": "2025-01-15T10:30:00Z", "rule_count": 25, "children": [ { "id": 2, "pid": 1, "name": "合同审批", "code": "CONTRACT_001_001", "description": None, "is_enabled": True, "created_at": "2025-01-15T10:31:00Z", "updated_at": "2025-01-15T10:31:00Z", "rule_count": 10, "children": [] } ] } ] } } class EvaluationPointGroupDetailResponse(BaseModel): """分组详情响应模型""" data: EvaluationPointGroupTree = Field(..., description="分组详情") class Config: json_schema_extra = { "example": { "data": { "id": 1, "pid": None, "name": "合同管理类", "code": "CONTRACT_001", "description": "合同管理相关的评查点分组", "is_enabled": True, "created_at": "2025-01-15T10:30:00Z", "updated_at": "2025-01-15T10:30:00Z", "rule_count": 25, "children": [] } } } class EvaluationPointGroupCreateResponse(BaseModel): """创建分组响应模型""" data: EvaluationPointGroupBase = Field(..., description="创建的分组信息") message: str = Field(default="创建成功", description="操作消息") class Config: json_schema_extra = { "example": { "data": { "id": 100, "pid": 1, "name": "新分组名称", "code": "NEW_GROUP_001", "description": "分组描述", "is_enabled": True, "created_at": "2025-01-20T14:30:00Z", "updated_at": "2025-01-20T14:30:00Z", "rule_count": 0 }, "message": "创建成功" } } class EvaluationPointGroupUpdateResponse(BaseModel): """更新分组响应模型""" data: EvaluationPointGroupBase = Field(..., description="更新后的分组信息") message: str = Field(default="更新成功", description="操作消息") class Config: json_schema_extra = { "example": { "data": { "id": 100, "pid": 1, "name": "更新后的名称", "code": "UPDATED_001", "description": "更新后的描述", "is_enabled": False, "created_at": "2025-01-20T14:30:00Z", "updated_at": "2025-01-20T14:35:00Z", "rule_count": 5 }, "message": "更新成功" } } class SimpleMessageResponse(BaseModel): """简单消息响应模型""" message: str = Field(..., description="操作消息") class Config: json_schema_extra = { "example": { "message": "删除成功" } } class BatchOperationResponse(BaseModel): """批量操作响应模型""" message: str = Field(..., description="操作消息") updated_count: Optional[int] = Field(None, description="成功更新的数量") deleted_count: Optional[int] = Field(None, description="成功删除的数量") failed_ids: Optional[List[int]] = Field(None, description="失败的ID列表") errors: Optional[dict] = Field(None, description="错误详情") class Config: json_schema_extra = { "example": { "message": "批量删除成功", "deleted_count": 2, "failed_ids": [11], "errors": { "11": "该分组下存在子分组,无法删除" } } } class ErrorResponse(BaseModel): """错误响应模型""" detail: str = Field(..., description="错误详情") code: Optional[int] = Field(None, description="错误代码") class Config: json_schema_extra = { "example": { "detail": "该分组不存在", "code": 404 } } ``` ### TypeScript (前端) 接口定义 ```typescript /** * 评查点分组基础接口 */ export interface EvaluationPointGroupResponse { id: number; pid: number | null; name: string; code: string; description: string | null; is_enabled: boolean; created_at: string; // ISO 8601 format updated_at: string; // ISO 8601 format rule_count?: number | null; children?: EvaluationPointGroupResponse[] | null; } /** * 列表响应接口 */ export interface EvaluationPointGroupListResponse { data: EvaluationPointGroupResponse[]; total: number; page: number; page_size: number; } /** * 树形结构响应接口 */ export interface EvaluationPointGroupTreeResponse { data: EvaluationPointGroupResponse[]; } /** * 详情响应接口 */ export interface EvaluationPointGroupDetailResponse { data: EvaluationPointGroupResponse; } /** * 创建/更新请求接口 */ export interface EvaluationPointGroupCreateUpdateDto { pid?: number | null; name: string; code: string; description?: string | null; is_enabled: boolean; } /** * 创建/更新响应接口 */ export interface EvaluationPointGroupCreateUpdateResponse { data: EvaluationPointGroupResponse; message: string; } /** * 批量更新状态请求接口 */ export interface BatchUpdateStatusRequest { ids: number[]; is_enabled: boolean; } /** * 批量删除请求接口 */ export interface BatchDeleteRequest { ids: number[]; } /** * 批量操作响应接口 */ export interface BatchOperationResponse { message: string; updated_count?: number; deleted_count?: number; failed_ids?: number[]; errors?: Record; } /** * 错误响应接口 */ export interface ErrorResponse { detail: string; code?: number; } ``` --- ## 接口列表 | 序号 | 方法 | 路径 | 说明 | |------|------|------|------| | 1 | GET | `/api/v3/evaluation-point-groups` | 获取一级分组列表(分页) | | 2 | GET | `/api/v3/evaluation-point-groups/all` | 获取完整树形结构 | | 3 | GET | `/api/v3/evaluation-point-groups/{id}` | 获取单个分组详情 | | 4 | GET | `/api/v3/evaluation-point-groups/{parent_id}/children` | 获取子分组列表(分页) | | 5 | POST | `/api/v3/evaluation-point-groups` | 创建分组 | | 6 | PUT | `/api/v3/evaluation-point-groups/{id}` | 更新分组 | | 7 | DELETE | `/api/v3/evaluation-point-groups/{id}` | 删除分组 | | 8 | PATCH | `/api/v3/evaluation-point-groups/batch/status` | 批量更新状态 | | 9 | DELETE | `/api/v3/evaluation-point-groups/batch` | 批量删除 | --- ## 详细接口说明 ### 1. 获取一级分组列表(分页) **请求** ```http GET /api/v3/evaluation-point-groups?pid=null&page=1&page_size=20&name=合同&code=CONTRACT&is_enabled=true Authorization: Bearer {jwt_token} ``` **查询参数** | 参数 | 类型 | 必填 | 说明 | 默认值 | |------|------|------|------|--------| | pid | string | 否 | 父分组ID筛选
- `pid=null`:只查询一级分组(**推荐**)
- 不传参数:查询所有分组
- `pid=60`:查询指定父分组的子分组 | - | | page | integer | 否 | 页码(从1开始) | 1 | | page_size | integer | 否 | 每页记录数 | 20 | | name | string | 否 | 分组名称模糊搜索 | - | | code | string | 否 | 分组编码模糊搜索 | - | | is_enabled | boolean | 否 | 是否启用筛选 | - | **⚠️ 重要说明:pid 参数处理** 前端会传递字符串 `"null"` 来表示查询一级分组,后端需要识别并转换: ```python # FastAPI 后端处理示例 @app.get("/api/v3/evaluation-point-groups") async def get_evaluation_point_groups( pid: Optional[str] = None, # 接收字符串类型 page: int = 1, page_size: int = 20, name: Optional[str] = None, code: Optional[str] = None, is_enabled: Optional[bool] = None ): # 🔑 将字符串 "null" 转换为 None if pid == "null": pid_value = None elif pid is not None: pid_value = int(pid) else: # 不传 pid 参数时,根据业务需求决定: # 选项1: 查询所有分组 # 选项2: 默认查询一级分组(推荐) pid_value = None # 使用 pid_value 构建查询 query = db.query(EvaluationPointGroup) if pid_value is None: # 查询一级分组: WHERE pid IS NULL query = query.filter(EvaluationPointGroup.pid.is_(None)) else: # 查询指定父分组的子分组 query = query.filter(EvaluationPointGroup.pid == pid_value) # ... 其他过滤条件 ``` **成功响应 (200)** ```json { "data": [ { "id": 1, "pid": null, "name": "合同管理类", "code": "CONTRACT_001", "description": "合同管理相关的评查点分组", "is_enabled": true, "created_at": "2025-01-15T10:30:00Z", "updated_at": "2025-01-15T10:30:00Z", "rule_count": 25 }, { "id": 5, "pid": null, "name": "合同审核类", "code": "CONTRACT_002", "description": null, "is_enabled": true, "created_at": "2025-01-16T09:20:00Z", "updated_at": "2025-01-16T09:20:00Z", "rule_count": 18 } ], "total": 2, "page": 1, "page_size": 20 } ``` **错误响应 (401)** ```json { "detail": "未授权,请先登录", "code": 401 } ``` --- ### 2. 获取完整树形结构 **请求** ```http GET /api/v3/evaluation-point-groups/all?flat=false&include_disabled=true Authorization: Bearer {jwt_token} ``` **查询参数** | 参数 | 类型 | 必填 | 说明 | 默认值 | |------|------|------|------|--------| | flat | boolean | 否 | 是否返回扁平结构(true:扁平数组,false:树形) | false | | include_disabled | boolean | 否 | 是否包含已禁用的分组 | true | **成功响应 (200) - 树形结构 (flat=false)** ```json { "data": [ { "id": 1, "pid": null, "name": "合同管理类", "code": "CONTRACT_001", "description": "合同管理相关的评查点分组", "is_enabled": true, "created_at": "2025-01-15T10:30:00Z", "updated_at": "2025-01-15T10:30:00Z", "rule_count": 25, "children": [ { "id": 2, "pid": 1, "name": "合同审批", "code": "CONTRACT_001_001", "description": null, "is_enabled": true, "created_at": "2025-01-15T10:31:00Z", "updated_at": "2025-01-15T10:31:00Z", "rule_count": 10, "children": [ { "id": 3, "pid": 2, "name": "审批流程", "code": "CONTRACT_001_001_001", "description": null, "is_enabled": true, "created_at": "2025-01-15T10:32:00Z", "updated_at": "2025-01-15T10:32:00Z", "rule_count": 5, "children": [] } ] }, { "id": 4, "pid": 1, "name": "合同签署", "code": "CONTRACT_001_002", "description": null, "is_enabled": true, "created_at": "2025-01-15T10:33:00Z", "updated_at": "2025-01-15T10:33:00Z", "rule_count": 15, "children": [] } ] } ] } ``` **成功响应 (200) - 扁平结构 (flat=true)** ```json { "data": [ { "id": 1, "pid": null, "name": "合同管理类", "code": "CONTRACT_001", "description": "合同管理相关的评查点分组", "is_enabled": true, "created_at": "2025-01-15T10:30:00Z", "updated_at": "2025-01-15T10:30:00Z", "rule_count": 25 }, { "id": 2, "pid": 1, "name": "合同审批", "code": "CONTRACT_001_001", "description": null, "is_enabled": true, "created_at": "2025-01-15T10:31:00Z", "updated_at": "2025-01-15T10:31:00Z", "rule_count": 10 }, { "id": 3, "pid": 2, "name": "审批流程", "code": "CONTRACT_001_001_001", "description": null, "is_enabled": true, "created_at": "2025-01-15T10:32:00Z", "updated_at": "2025-01-15T10:32:00Z", "rule_count": 5 } ] } ``` --- ### 3. 获取单个分组详情 **请求** ```http GET /api/v3/evaluation-point-groups/1?include_children=true Authorization: Bearer {jwt_token} ``` **路径参数** | 参数 | 类型 | 必填 | 说明 | |------|------|------|------| | id | integer | 是 | 分组ID | **查询参数** | 参数 | 类型 | 必填 | 说明 | 默认值 | |------|------|------|------|--------| | include_children | boolean | 否 | 是否包含子分组 | false | **成功响应 (200) - include_children=true** ```json { "data": { "id": 1, "pid": null, "name": "合同管理类", "code": "CONTRACT_001", "description": "合同管理相关的评查点分组", "is_enabled": true, "created_at": "2025-01-15T10:30:00Z", "updated_at": "2025-01-15T10:30:00Z", "rule_count": 25, "children": [ { "id": 2, "pid": 1, "name": "合同审批", "code": "CONTRACT_001_001", "description": null, "is_enabled": true, "created_at": "2025-01-15T10:31:00Z", "updated_at": "2025-01-15T10:31:00Z", "rule_count": 10, "children": [] } ] } } ``` **成功响应 (200) - include_children=false** ```json { "data": { "id": 1, "pid": null, "name": "合同管理类", "code": "CONTRACT_001", "description": "合同管理相关的评查点分组", "is_enabled": true, "created_at": "2025-01-15T10:30:00Z", "updated_at": "2025-01-15T10:30:00Z", "rule_count": 25 } } ``` **错误响应 (404)** ```json { "detail": "分组不存在", "code": 404 } ``` --- ### 4. 获取子分组列表(分页) **请求** ```http GET /api/v3/evaluation-point-groups/1/children?page=1&page_size=20 Authorization: Bearer {jwt_token} ``` **路径参数** | 参数 | 类型 | 必填 | 说明 | |------|------|------|------| | parent_id | integer | 是 | 父分组ID | **查询参数** | 参数 | 类型 | 必填 | 说明 | 默认值 | |------|------|------|------|--------| | page | integer | 否 | 页码(从1开始) | 1 | | page_size | integer | 否 | 每页记录数 | 20 | **成功响应 (200)** ```json { "data": [ { "id": 2, "pid": 1, "name": "合同审批", "code": "CONTRACT_001_001", "description": null, "is_enabled": true, "created_at": "2025-01-15T10:31:00Z", "updated_at": "2025-01-15T10:31:00Z", "rule_count": 10 }, { "id": 4, "pid": 1, "name": "合同签署", "code": "CONTRACT_001_002", "description": null, "is_enabled": true, "created_at": "2025-01-15T10:33:00Z", "updated_at": "2025-01-15T10:33:00Z", "rule_count": 15 } ], "total": 2, "page": 1, "page_size": 20 } ``` **错误响应 (404)** ```json { "detail": "父分组不存在", "code": 404 } ``` --- ### 5. 创建分组 **请求** ```http POST /api/v3/evaluation-point-groups Authorization: Bearer {jwt_token} Content-Type: application/json { "pid": 1, "name": "新分组名称", "code": "NEW_GROUP_001", "description": "分组描述", "is_enabled": true } ``` **请求体** | 字段 | 类型 | 必填 | 说明 | 限制 | |------|------|------|------|------| | pid | integer/null | 否 | 父分组ID,null表示创建一级分组 | 必须是存在的分组ID | | name | string | 是 | 分组名称 | 1-100字符,不能为空 | | code | string | 是 | 分组编码 | 1-50字符,必须唯一 | | description | string/null | 否 | 分组描述 | 最多500字符 | | is_enabled | boolean | 是 | 是否启用 | - | **成功响应 (201)** ```json { "data": { "id": 100, "pid": 1, "name": "新分组名称", "code": "NEW_GROUP_001", "description": "分组描述", "is_enabled": true, "created_at": "2025-01-20T14:30:00Z", "updated_at": "2025-01-20T14:30:00Z", "rule_count": 0 }, "message": "创建成功" } ``` **错误响应 (400) - 编码重复** ```json { "detail": "分组编码已存在", "code": 400 } ``` **错误响应 (404) - 父分组不存在** ```json { "detail": "父分组不存在", "code": 404 } ``` **错误响应 (422) - 验证失败** ```json { "detail": [ { "loc": ["body", "name"], "msg": "分组名称不能为空", "type": "value_error" } ] } ``` --- ### 6. 更新分组 **请求** ```http PUT /api/v3/evaluation-point-groups/100 Authorization: Bearer {jwt_token} Content-Type: application/json { "pid": 1, "name": "更新后的名称", "code": "UPDATED_001", "description": "更新后的描述", "is_enabled": false } ``` **路径参数** | 参数 | 类型 | 必填 | 说明 | |------|------|------|------| | id | integer | 是 | 分组ID | **请求体** | 字段 | 类型 | 必填 | 说明 | 限制 | |------|------|------|------|------| | pid | integer/null | 否 | 父分组ID | 不能设置为自己或自己的子孙节点 | | name | string | 是 | 分组名称 | 1-100字符 | | code | string | 是 | 分组编码 | 1-50字符,不能与其他分组重复 | | description | string/null | 否 | 分组描述 | 最多500字符 | | is_enabled | boolean | 是 | 是否启用 | - | **成功响应 (200)** ```json { "data": { "id": 100, "pid": 1, "name": "更新后的名称", "code": "UPDATED_001", "description": "更新后的描述", "is_enabled": false, "created_at": "2025-01-20T14:30:00Z", "updated_at": "2025-01-20T14:35:00Z", "rule_count": 0 }, "message": "更新成功" } ``` **错误响应 (400) - 父分组设置错误** ```json { "detail": "不能将分组设置为自己的子孙节点的父分组", "code": 400 } ``` **错误响应 (404)** ```json { "detail": "分组不存在", "code": 404 } ``` --- ### 7. 删除分组 **请求** ```http DELETE /api/v3/evaluation-point-groups/100 Authorization: Bearer {jwt_token} ``` **路径参数** | 参数 | 类型 | 必填 | 说明 | |------|------|------|------| | id | integer | 是 | 分组ID | **成功响应 (200)** ```json { "message": "删除成功" } ``` **错误响应 (400) - 存在子分组** ```json { "detail": "该分组下存在子分组,无法删除", "code": 400 } ``` **错误响应 (400) - 存在关联评查点** ```json { "detail": "该分组下存在评查点,无法删除", "code": 400 } ``` **错误响应 (404)** ```json { "detail": "分组不存在", "code": 404 } ``` --- ### 8. 批量更新状态 **请求** ```http PATCH /api/v3/evaluation-point-groups/batch/status Authorization: Bearer {jwt_token} Content-Type: application/json { "ids": [1, 2, 3], "is_enabled": false } ``` **请求体** | 字段 | 类型 | 必填 | 说明 | 限制 | |------|------|------|------|------| | ids | integer[] | 是 | 要更新的分组ID列表 | 至少包含1个ID | | is_enabled | boolean | 是 | 目标状态 | - | **成功响应 (200)** ```json { "message": "批量更新成功", "updated_count": 3 } ``` **部分成功响应 (200)** ```json { "message": "部分更新成功", "updated_count": 2, "failed_ids": [999], "errors": { "999": "分组不存在" } } ``` **错误响应 (400)** ```json { "detail": "ids 不能为空", "code": 400 } ``` --- ### 9. 批量删除 **请求** ```http DELETE /api/v3/evaluation-point-groups/batch Authorization: Bearer {jwt_token} Content-Type: application/json { "ids": [10, 11, 12] } ``` **请求体** | 字段 | 类型 | 必填 | 说明 | 限制 | |------|------|------|------|------| | ids | integer[] | 是 | 要删除的分组ID列表 | 至少包含1个ID | **成功响应 (200)** ```json { "message": "批量删除成功", "deleted_count": 3 } ``` **部分成功响应 (200)** ```json { "message": "部分删除成功", "deleted_count": 2, "failed_ids": [11], "errors": { "11": "该分组下存在子分组,无法删除" } } ``` **错误响应 (400)** ```json { "detail": "ids 不能为空", "code": 400 } ``` --- ## 错误处理规范 ### HTTP 状态码 | 状态码 | 说明 | 使用场景 | |--------|------|----------| | 200 | OK | 请求成功 | | 201 | Created | 资源创建成功 | | 400 | Bad Request | 请求参数错误、业务逻辑错误 | | 401 | Unauthorized | 未授权,JWT token无效或过期 | | 403 | Forbidden | 无权限访问 | | 404 | Not Found | 资源不存在 | | 422 | Unprocessable Entity | 数据验证失败 | | 500 | Internal Server Error | 服务器内部错误 | ### 错误响应格式 **标准错误格式** ```json { "detail": "错误描述信息", "code": 400 } ``` **验证错误格式 (422)** ```json { "detail": [ { "loc": ["body", "name"], "msg": "分组名称不能为空", "type": "value_error" }, { "loc": ["body", "code"], "msg": "分组编码长度必须在1-50之间", "type": "value_error" } ] } ``` ### 常见错误代码 | 错误代码 | 错误信息 | HTTP状态码 | |---------|---------|-----------| | 40001 | 分组编码已存在 | 400 | | 40002 | 该分组下存在子分组,无法删除 | 400 | | 40003 | 该分组下存在评查点,无法删除 | 400 | | 40004 | 不能将分组设置为自己的子孙节点的父分组 | 400 | | 40101 | 未授权,请先登录 | 401 | | 40102 | Token已过期 | 401 | | 40103 | Token无效 | 401 | | 40401 | 分组不存在 | 404 | | 40402 | 父分组不存在 | 404 | --- ## 验证规则 ### 字段验证规则 | 字段 | 验证规则 | |------|----------| | name | - 必填
- 长度:1-100字符
- 不能只包含空格
- 去除首尾空格后存储 | | code | - 必填
- 长度:1-50字符
- 唯一性校验
- 自动转换为大写
- 建议格式:字母数字下划线 | | description | - 可选
- 最大长度:500字符 | | is_enabled | - 必填
- 布尔值 | | pid | - 可选
- 必须是存在的分组ID
- 不能是自己
- 不能是自己的子孙节点 | ### 业务规则 1. **创建分组** - 编码必须唯一 - 父分组必须存在(如果指定了pid) - 创建后 rule_count 初始为 0 2. **更新分组** - 不能将自己设置为自己的子孙节点的父分组(避免循环引用) - 编码不能与其他分组重复(可以与自己原来的编码相同) - 更新时自动更新 updated_at 3. **删除分组** - 不能删除有子分组的分组 - 不能删除有关联评查点的分组 - 建议:删除前检查并提示用户 4. **rule_count 计算** - 包含该分组直接关联的评查点 - 包含所有子分组(递归)关联的评查点 - 在评查点创建/删除/移动时需要更新相关分组的 rule_count --- ## 完整测试用例 ### 测试用例 1: 创建一级分组 **请求** ```bash curl -X POST http://10.79.97.17:8000/api/v3/evaluation-point-groups \ -H "Authorization: Bearer eyJhbGc..." \ -H "Content-Type: application/json" \ -d '{ "pid": null, "name": "财务管理类", "code": "FINANCE_001", "description": "财务相关的评查点分组", "is_enabled": true }' ``` **预期响应 (201)** ```json { "data": { "id": 101, "pid": null, "name": "财务管理类", "code": "FINANCE_001", "description": "财务相关的评查点分组", "is_enabled": true, "created_at": "2025-01-20T15:30:00Z", "updated_at": "2025-01-20T15:30:00Z", "rule_count": 0 }, "message": "创建成功" } ``` --- ### 测试用例 2: 创建子分组 **请求** ```bash curl -X POST http://10.79.97.17:8000/api/v3/evaluation-point-groups \ -H "Authorization: Bearer eyJhbGc..." \ -H "Content-Type: application/json" \ -d '{ "pid": 101, "name": "预算管理", "code": "FINANCE_001_001", "description": null, "is_enabled": true }' ``` **预期响应 (201)** ```json { "data": { "id": 102, "pid": 101, "name": "预算管理", "code": "FINANCE_001_001", "description": null, "is_enabled": true, "created_at": "2025-01-20T15:31:00Z", "updated_at": "2025-01-20T15:31:00Z", "rule_count": 0 }, "message": "创建成功" } ``` --- ### 测试用例 3: 获取树形结构 **请求** ```bash curl -X GET "http://10.79.97.17:8000/api/v3/evaluation-point-groups/all?flat=false&include_disabled=false" \ -H "Authorization: Bearer eyJhbGc..." ``` **预期响应 (200)** ```json { "data": [ { "id": 101, "pid": null, "name": "财务管理类", "code": "FINANCE_001", "description": "财务相关的评查点分组", "is_enabled": true, "created_at": "2025-01-20T15:30:00Z", "updated_at": "2025-01-20T15:30:00Z", "rule_count": 0, "children": [ { "id": 102, "pid": 101, "name": "预算管理", "code": "FINANCE_001_001", "description": null, "is_enabled": true, "created_at": "2025-01-20T15:31:00Z", "updated_at": "2025-01-20T15:31:00Z", "rule_count": 0, "children": [] } ] } ] } ``` --- ### 测试用例 4: 更新分组(禁用) **请求** ```bash curl -X PUT http://10.79.97.17:8000/api/v3/evaluation-point-groups/102 \ -H "Authorization: Bearer eyJhbGc..." \ -H "Content-Type: application/json" \ -d '{ "pid": 101, "name": "预算管理", "code": "FINANCE_001_001", "description": "预算编制与执行", "is_enabled": false }' ``` **预期响应 (200)** ```json { "data": { "id": 102, "pid": 101, "name": "预算管理", "code": "FINANCE_001_001", "description": "预算编制与执行", "is_enabled": false, "created_at": "2025-01-20T15:31:00Z", "updated_at": "2025-01-20T15:35:00Z", "rule_count": 0 }, "message": "更新成功" } ``` --- ### 测试用例 5: 批量更新状态 **请求** ```bash curl -X PATCH http://10.79.97.17:8000/api/v3/evaluation-point-groups/batch/status \ -H "Authorization: Bearer eyJhbGc..." \ -H "Content-Type: application/json" \ -d '{ "ids": [101, 102], "is_enabled": true }' ``` **预期响应 (200)** ```json { "message": "批量更新成功", "updated_count": 2 } ``` --- ### 测试用例 6: 删除分组(有子分组,应该失败) **请求** ```bash curl -X DELETE http://10.79.97.17:8000/api/v3/evaluation-point-groups/101 \ -H "Authorization: Bearer eyJhbGc..." ``` **预期响应 (400)** ```json { "detail": "该分组下存在子分组,无法删除", "code": 400 } ``` --- ### 测试用例 7: 删除子分组(成功) **请求** ```bash curl -X DELETE http://10.79.97.17:8000/api/v3/evaluation-point-groups/102 \ -H "Authorization: Bearer eyJhbGc..." ``` **预期响应 (200)** ```json { "message": "删除成功" } ``` --- ### 测试用例 8: 重复编码(应该失败) **请求** ```bash curl -X POST http://10.79.97.17:8000/api/v3/evaluation-point-groups \ -H "Authorization: Bearer eyJhbGc..." \ -H "Content-Type: application/json" \ -d '{ "pid": null, "name": "重复编码测试", "code": "FINANCE_001", "description": null, "is_enabled": true }' ``` **预期响应 (400)** ```json { "detail": "分组编码已存在", "code": 400 } ``` --- ### 测试用例 9: 获取分页列表(带搜索) **请求** ```bash curl -X GET "http://10.79.97.17:8000/api/v3/evaluation-point-groups?page=1&page_size=10&name=财务&is_enabled=true" \ -H "Authorization: Bearer eyJhbGc..." ``` **预期响应 (200)** ```json { "data": [ { "id": 101, "pid": null, "name": "财务管理类", "code": "FINANCE_001", "description": "财务相关的评查点分组", "is_enabled": true, "created_at": "2025-01-20T15:30:00Z", "updated_at": "2025-01-20T15:30:00Z", "rule_count": 0 } ], "total": 1, "page": 1, "page_size": 10 } ``` --- ### 测试用例 10: 批量删除 **请求** ```bash curl -X DELETE http://10.79.97.17:8000/api/v3/evaluation-point-groups/batch \ -H "Authorization: Bearer eyJhbGc..." \ -H "Content-Type: application/json" \ -d '{ "ids": [101, 999] }' ``` **预期响应 (200) - 部分成功** ```json { "message": "部分删除成功", "deleted_count": 1, "failed_ids": [999], "errors": { "999": "分组不存在" } } ``` --- ## 附录 ### 数据库表结构建议 ```sql CREATE TABLE evaluation_point_groups ( id SERIAL PRIMARY KEY, pid INTEGER REFERENCES evaluation_point_groups(id) ON DELETE RESTRICT, name VARCHAR(100) NOT NULL, code VARCHAR(50) NOT NULL UNIQUE, description TEXT, is_enabled BOOLEAN NOT NULL DEFAULT true, created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT CURRENT_TIMESTAMP, updated_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT CURRENT_TIMESTAMP ); -- 索引 CREATE INDEX idx_evaluation_point_groups_pid ON evaluation_point_groups(pid); CREATE INDEX idx_evaluation_point_groups_code ON evaluation_point_groups(code); CREATE INDEX idx_evaluation_point_groups_is_enabled ON evaluation_point_groups(is_enabled); CREATE INDEX idx_evaluation_point_groups_name ON evaluation_point_groups USING gin(to_tsvector('simple', name)); -- 触发器:自动更新 updated_at CREATE OR REPLACE FUNCTION update_updated_at_column() RETURNS TRIGGER AS $$ BEGIN NEW.updated_at = CURRENT_TIMESTAMP; RETURN NEW; END; $$ language 'plpgsql'; CREATE TRIGGER update_evaluation_point_groups_updated_at BEFORE UPDATE ON evaluation_point_groups FOR EACH ROW EXECUTE FUNCTION update_updated_at_column(); ``` ### rule_count 计算SQL示例 ```sql -- 计算某个分组的 rule_count(包含所有子孙分组) WITH RECURSIVE group_tree AS ( -- 基础查询:选择目标分组 SELECT id FROM evaluation_point_groups WHERE id = :group_id UNION ALL -- 递归查询:选择所有子孙分组 SELECT g.id FROM evaluation_point_groups g INNER JOIN group_tree gt ON g.pid = gt.id ) SELECT COUNT(*) as rule_count FROM evaluation_points ep WHERE ep.group_id IN (SELECT id FROM group_tree); ``` --- ## 版本历史 | 版本 | 日期 | 说明 | |------|------|------| | 1.0.0 | 2025-01-20 | 初始版本 | --- ## 联系方式 如有疑问,请联系后端开发团队。