1478 lines
35 KiB
Markdown
1478 lines
35 KiB
Markdown
# 文档类型管理 CRUD 详细分析
|
||
|
||
## 📋 概述
|
||
|
||
**文件位置**:`app/api/document-types/document-types.ts`
|
||
|
||
**功能**:提供文档类型的完整增删改查(CRUD)操作,包括关联的评查点分组、入口模块、提示词模板等管理功能。
|
||
|
||
**核心数据库表**:
|
||
- `document_types` - 文档类型表(主表)
|
||
- `evaluation_point_groups` - 评查点分组表
|
||
- `entry_modules` - 入口模块表
|
||
|
||
---
|
||
|
||
## 📊 数据结构定义
|
||
|
||
### 1. DocumentType(数据库实体)
|
||
|
||
```typescript
|
||
interface DocumentType {
|
||
id: number;
|
||
name: string; // 文档类型名称
|
||
description: string | null; // 描述
|
||
evaluation_point_groups_ids: number[]; // 关联的评查点分组ID数组(JSONB)
|
||
prompt_config?: { // 提示词配置(JSONB)
|
||
summary_template?: number; // 总结模板ID
|
||
llm_extract_template?: number; // LLM抽取模板ID
|
||
vlm_extract_template?: number; // VLM抽取模板ID
|
||
evaluation_template?: number; // 评查模板ID
|
||
execution_template?: number; // 执行模板ID(与evaluation_template等价)
|
||
} | null;
|
||
created_at: string;
|
||
updated_at: string;
|
||
code?: string | null; // 文档类型代码
|
||
}
|
||
```
|
||
|
||
### 2. DocumentTypeUI(前端展示)
|
||
|
||
```typescript
|
||
interface DocumentTypeUI {
|
||
id: number;
|
||
name: string;
|
||
description: string;
|
||
groups: DocumentTypeGroup[]; // 关联的评查点分组列表
|
||
entry_module?: { // 入口模块
|
||
id: number;
|
||
name: string;
|
||
} | null;
|
||
llm_extraction_template_id?: number | null;
|
||
vlm_extraction_template_id?: number | null;
|
||
evaluation_template_id?: number | null;
|
||
summary_template_id?: number | null;
|
||
created_at: string; // 格式化后的日期
|
||
updated_at: string; // 格式化后的日期
|
||
code?: string | null;
|
||
}
|
||
```
|
||
|
||
### 3. 辅助接口
|
||
|
||
```typescript
|
||
// 文档类型分组
|
||
interface DocumentTypeGroup {
|
||
id: string;
|
||
name: string;
|
||
}
|
||
|
||
// 创建DTO
|
||
interface DocumentTypeCreateDTO {
|
||
name: string;
|
||
description?: string;
|
||
group_ids: string[]; // 评查点分组ID数组
|
||
entry_module_id?: number | null;
|
||
llm_extraction_template_id?: number | null;
|
||
vlm_extraction_template_id?: number | null;
|
||
evaluation_template_id?: number | null;
|
||
summary_template_id?: number | null;
|
||
code?: string | null;
|
||
}
|
||
|
||
// 更新DTO
|
||
interface DocumentTypeUpdateDTO extends DocumentTypeCreateDTO {
|
||
id: number;
|
||
}
|
||
|
||
// 搜索参数
|
||
interface DocumentTypeSearchParams {
|
||
name?: string; // 名称模糊搜索
|
||
ruleType?: string; // 按评查点分组类型筛选
|
||
groupId?: string; // 按评查点分组ID筛选
|
||
page?: number; // 页码(默认1)
|
||
pageSize?: number; // 每页数量(默认10)
|
||
documentTypeIds?: number[]; // 文档类型ID数组
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## 🗄️ 数据库表结构
|
||
|
||
### document_types 表
|
||
|
||
```sql
|
||
CREATE TABLE document_types (
|
||
id SERIAL PRIMARY KEY,
|
||
name VARCHAR(255) NOT NULL,
|
||
description TEXT,
|
||
evaluation_point_groups_ids JSONB, -- 评查点分组ID数组
|
||
entry_module_id INTEGER,
|
||
prompt_config JSONB, -- 提示词配置
|
||
code VARCHAR(100),
|
||
created_at TIMESTAMP DEFAULT NOW(),
|
||
updated_at TIMESTAMP DEFAULT NOW(),
|
||
FOREIGN KEY (entry_module_id) REFERENCES entry_modules(id)
|
||
);
|
||
```
|
||
|
||
### evaluation_point_groups 表
|
||
|
||
```sql
|
||
CREATE TABLE evaluation_point_groups (
|
||
id SERIAL PRIMARY KEY,
|
||
name VARCHAR(255) NOT NULL,
|
||
pid INTEGER DEFAULT 0, -- 父级分组ID(0表示根分组)
|
||
created_at TIMESTAMP DEFAULT NOW(),
|
||
updated_at TIMESTAMP DEFAULT NOW()
|
||
);
|
||
```
|
||
|
||
### entry_modules 表
|
||
|
||
```sql
|
||
CREATE TABLE entry_modules (
|
||
id SERIAL PRIMARY KEY,
|
||
name VARCHAR(255) NOT NULL,
|
||
created_at TIMESTAMP DEFAULT NOW(),
|
||
updated_at TIMESTAMP DEFAULT NOW()
|
||
);
|
||
```
|
||
|
||
---
|
||
|
||
## 🔧 辅助函数
|
||
|
||
### 1. extractApiData<T>
|
||
|
||
**功能**:从不同格式的 API 响应中提取数据
|
||
|
||
**代码位置**:lines 81-94
|
||
|
||
**支持的响应格式**:
|
||
1. `{ code: number, msg: string, data: T }` - 标准响应格式
|
||
2. 直接返回数据对象
|
||
|
||
```typescript
|
||
function extractApiData<T>(responseData: unknown): T | null {
|
||
// 格式1: 标准响应格式
|
||
if (responseData有code和data字段) {
|
||
return responseData.data;
|
||
}
|
||
|
||
// 格式2: 直接是数据
|
||
return responseData as T;
|
||
}
|
||
```
|
||
|
||
### 2. convertToUIDocumentType
|
||
|
||
**功能**:将数据库实体转换为前端UI格式
|
||
|
||
**代码位置**:lines 504-552
|
||
|
||
**转换逻辑**:
|
||
- 提取 `prompt_config` 中的各个模板ID
|
||
- 格式化日期字段(`created_at`, `updated_at`)
|
||
- 处理 `entry_modules` 关联数据
|
||
- 将 `groups` 添加到返回对象
|
||
|
||
---
|
||
|
||
## 📖 查询操作(Read)
|
||
|
||
### 1. getAllEvaluationPointGroups
|
||
|
||
**功能**:获取所有评查点分组
|
||
|
||
**代码位置**:lines 101-142
|
||
|
||
**数据库表**:`evaluation_point_groups`
|
||
|
||
**查询参数**:
|
||
```typescript
|
||
{
|
||
select: 'id, name',
|
||
token?: string // JWT token(可选)
|
||
}
|
||
```
|
||
|
||
**SQL 等价查询**:
|
||
```sql
|
||
SELECT id, name
|
||
FROM evaluation_point_groups;
|
||
```
|
||
|
||
**PostgREST API 请求**:
|
||
```http
|
||
GET /evaluation_point_groups?select=id,name
|
||
Authorization: Bearer {token}
|
||
```
|
||
|
||
**返回数据**:
|
||
```typescript
|
||
{
|
||
data?: DocumentTypeGroup[]; // [{ id: "1", name: "合同形式要素" }, ...]
|
||
error?: string;
|
||
status?: number;
|
||
}
|
||
```
|
||
|
||
**使用场景**:
|
||
- 文档类型创建/编辑时选择评查点分组
|
||
- 获取所有可用的评查点分组列表
|
||
|
||
---
|
||
|
||
### 2. getParentEvaluationPointGroups
|
||
|
||
**功能**:获取父级评查分组(pid=0 的根分组)
|
||
|
||
**代码位置**:lines 149-194
|
||
|
||
**数据库表**:`evaluation_point_groups`
|
||
|
||
**查询参数**:
|
||
```typescript
|
||
{
|
||
select: 'id, name',
|
||
filter: {
|
||
'pid': 'eq.0' // 只查询父级分组
|
||
},
|
||
order: 'id.asc',
|
||
token?: string
|
||
}
|
||
```
|
||
|
||
**SQL 等价查询**:
|
||
```sql
|
||
SELECT id, name
|
||
FROM evaluation_point_groups
|
||
WHERE pid = 0
|
||
ORDER BY id ASC;
|
||
```
|
||
|
||
**PostgREST API 请求**:
|
||
```http
|
||
GET /evaluation_point_groups?pid=eq.0&select=id,name&order=id.asc
|
||
Authorization: Bearer {token}
|
||
```
|
||
|
||
**返回数据**:
|
||
```typescript
|
||
{
|
||
data?: DocumentTypeGroup[];
|
||
error?: string;
|
||
status?: number;
|
||
}
|
||
```
|
||
|
||
**使用场景**:
|
||
- 获取顶级评查点分组
|
||
- 构建树形结构的第一层
|
||
|
||
---
|
||
|
||
### 3. getEntryModules
|
||
|
||
**功能**:获取所有入口模块
|
||
|
||
**代码位置**:lines 201-237
|
||
|
||
**数据库表**:`entry_modules`
|
||
|
||
**查询参数**:
|
||
```typescript
|
||
{
|
||
select: 'id, name',
|
||
order: 'id.asc',
|
||
token?: string
|
||
}
|
||
```
|
||
|
||
**SQL 等价查询**:
|
||
```sql
|
||
SELECT id, name
|
||
FROM entry_modules
|
||
ORDER BY id ASC;
|
||
```
|
||
|
||
**PostgREST API 请求**:
|
||
```http
|
||
GET /entry_modules?select=id,name&order=id.asc
|
||
Authorization: Bearer {token}
|
||
```
|
||
|
||
**返回数据**:
|
||
```typescript
|
||
{
|
||
data?: Array<{ id: number; name: string }>;
|
||
error?: string;
|
||
status?: number;
|
||
}
|
||
```
|
||
|
||
**数据示例**:
|
||
```json
|
||
{
|
||
"data": [
|
||
{ "id": 1, "name": "多模态抽取" },
|
||
{ "id": 2, "name": "智能评查" }
|
||
]
|
||
}
|
||
```
|
||
|
||
**使用场景**:
|
||
- 文档类型创建/编辑时选择入口模块
|
||
- 确定文档处理的入口流程
|
||
|
||
---
|
||
|
||
### 4. getEvaluationPointGroupsByIds
|
||
|
||
**功能**:根据ID批量获取评查点分组信息
|
||
|
||
**代码位置**:lines 245-304
|
||
|
||
**数据库表**:`evaluation_point_groups`
|
||
|
||
**参数**:
|
||
- `ids: number[] | number` - 分组ID或ID数组
|
||
- `token?: string` - JWT token
|
||
|
||
**查询参数**:
|
||
```typescript
|
||
{
|
||
select: 'id, name',
|
||
filter: {
|
||
'id': `in.(${idsArray.join(',')})` // IN操作符批量查询
|
||
},
|
||
token?: string
|
||
}
|
||
```
|
||
|
||
**SQL 等价查询**:
|
||
```sql
|
||
SELECT id, name
|
||
FROM evaluation_point_groups
|
||
WHERE id IN (1, 2, 3);
|
||
```
|
||
|
||
**PostgREST API 请求**:
|
||
```http
|
||
GET /evaluation_point_groups?id=in.(1,2,3)&select=id,name
|
||
Authorization: Bearer {token}
|
||
```
|
||
|
||
**返回数据**:
|
||
```typescript
|
||
{
|
||
data?: DocumentTypeGroup[];
|
||
error?: string;
|
||
status?: number;
|
||
}
|
||
```
|
||
|
||
**使用场景**:
|
||
- 获取文档类型关联的所有评查点分组详情
|
||
- 批量查询避免N+1查询问题
|
||
|
||
---
|
||
|
||
### 5. getDocumentTypes(核心查询)
|
||
|
||
**功能**:获取文档类型列表(支持搜索、分页、筛选)
|
||
|
||
**代码位置**:lines 311-458
|
||
|
||
**数据库表**:
|
||
- `document_types`(主表)
|
||
- `entry_modules`(关联查询)
|
||
- `evaluation_point_groups`(后续批量查询)
|
||
|
||
**参数**:`DocumentTypeSearchParams`
|
||
```typescript
|
||
{
|
||
name?: string; // 名称模糊搜索
|
||
ruleType?: string; // 按评查点分组类型筛选
|
||
groupId?: string; // 按评查点分组ID筛选
|
||
page?: number; // 页码(默认1)
|
||
pageSize?: number; // 每页数量(默认10)
|
||
documentTypeIds?: number[]; // 文档类型ID数组过滤
|
||
}
|
||
```
|
||
|
||
**查询参数**:
|
||
```typescript
|
||
{
|
||
select: `
|
||
id,
|
||
name,
|
||
description,
|
||
evaluation_point_groups_ids,
|
||
entry_module_id,
|
||
entry_modules!fk_document_types_entry_module(id, name), // 关联查询
|
||
prompt_config,
|
||
created_at,
|
||
updated_at,
|
||
code
|
||
`,
|
||
order: 'updated_at.desc',
|
||
headers: {
|
||
'Prefer': 'count=exact' // 获取总数
|
||
},
|
||
limit: pageSize,
|
||
offset: (page - 1) * pageSize,
|
||
filter: {
|
||
'name'?: `ilike.%${name}%`, // 名称模糊搜索
|
||
'evaluation_point_groups_ids'?: `cs.[${groupId}]`, // 包含指定分组
|
||
'id'?: `in.(${documentTypeIds.join(',')})` // ID过滤
|
||
},
|
||
token?: string
|
||
}
|
||
```
|
||
|
||
**SQL 等价查询**(简化版):
|
||
```sql
|
||
SELECT
|
||
dt.id,
|
||
dt.name,
|
||
dt.description,
|
||
dt.evaluation_point_groups_ids,
|
||
dt.entry_module_id,
|
||
dt.prompt_config,
|
||
dt.created_at,
|
||
dt.updated_at,
|
||
dt.code,
|
||
em.id as entry_module_id,
|
||
em.name as entry_module_name
|
||
FROM document_types dt
|
||
LEFT JOIN entry_modules em ON dt.entry_module_id = em.id
|
||
WHERE
|
||
dt.name ILIKE '%searchName%' -- 可选:名称模糊搜索
|
||
AND dt.evaluation_point_groups_ids @> '[5]' -- 可选:包含指定分组ID
|
||
AND dt.id IN (1, 2, 3) -- 可选:ID过滤
|
||
ORDER BY dt.updated_at DESC
|
||
LIMIT 10 OFFSET 0;
|
||
|
||
-- 同时获取总数
|
||
SELECT COUNT(*) FROM document_types WHERE ...;
|
||
```
|
||
|
||
**PostgREST API 请求**:
|
||
```http
|
||
GET /document_types?select=id,name,description,evaluation_point_groups_ids,entry_module_id,entry_modules!fk_document_types_entry_module(id,name),prompt_config,created_at,updated_at,code&order=updated_at.desc&limit=10&offset=0&name=ilike.%合同%&evaluation_point_groups_ids=cs.[5]
|
||
Prefer: count=exact
|
||
Authorization: Bearer {token}
|
||
```
|
||
|
||
**查询逻辑流程**:
|
||
```
|
||
1. 查询 document_types 表(主查询)
|
||
├─ 关联查询 entry_modules(LEFT JOIN)
|
||
├─ 应用筛选条件
|
||
├─ 分页处理
|
||
└─ 获取总数(通过 Content-Range header)
|
||
|
||
2. 收集所有文档类型的 evaluation_point_groups_ids
|
||
└─ 去重后批量查询 evaluation_point_groups
|
||
|
||
3. 构建 Map 映射(O(1)查找)
|
||
└─ groupsMap: Map<number, DocumentTypeGroup>
|
||
|
||
4. 遍历文档类型列表,合并数据
|
||
├─ 添加 entry_module 信息
|
||
├─ 添加 groups 信息(从 groupsMap 查找)
|
||
└─ 转换为 DocumentTypeUI 格式
|
||
|
||
5. 返回完整数据
|
||
├─ types: DocumentTypeUI[]
|
||
└─ total: number(总数)
|
||
```
|
||
|
||
**返回数据**:
|
||
```typescript
|
||
{
|
||
data?: {
|
||
types: DocumentTypeUI[];
|
||
total: number;
|
||
};
|
||
error?: string;
|
||
status?: number;
|
||
}
|
||
```
|
||
|
||
**数据示例**:
|
||
```json
|
||
{
|
||
"data": {
|
||
"types": [
|
||
{
|
||
"id": 1,
|
||
"name": "烟草专卖行政处罚案卷",
|
||
"description": "烟草专卖行政处罚案卷的智能评查",
|
||
"groups": [
|
||
{ "id": "5", "name": "程序合法性" },
|
||
{ "id": "8", "name": "证据完整性" }
|
||
],
|
||
"entry_module": {
|
||
"id": 1,
|
||
"name": "多模态抽取"
|
||
},
|
||
"llm_extraction_template_id": 10,
|
||
"vlm_extraction_template_id": 15,
|
||
"evaluation_template_id": 20,
|
||
"summary_template_id": 25,
|
||
"created_at": "2024-01-15 10:00:00",
|
||
"updated_at": "2024-01-20 15:30:00",
|
||
"code": "TOBACCO_CASE"
|
||
}
|
||
],
|
||
"total": 25
|
||
}
|
||
}
|
||
```
|
||
|
||
**性能优化**:
|
||
- ✅ 使用 PostgREST 的资源嵌入语法关联查询(避免N+1)
|
||
- ✅ 批量查询评查点分组(一次查询获取所有分组)
|
||
- ✅ 使用 Map 进行快速查找(O(1)时间复杂度)
|
||
- ✅ 分页查询减少数据传输量
|
||
|
||
**使用场景**:
|
||
- 文档类型管理列表页
|
||
- 文档上传时选择文档类型
|
||
- 文档类型搜索和筛选
|
||
|
||
---
|
||
|
||
### 6. getDocumentType
|
||
|
||
**功能**:获取单个文档类型的详细信息
|
||
|
||
**代码位置**:lines 560-648
|
||
|
||
**数据库表**:
|
||
- `document_types`
|
||
- `entry_modules`(关联查询)
|
||
- `evaluation_point_groups`(后续查询)
|
||
|
||
**参数**:
|
||
- `id: string` - 文档类型ID
|
||
- `frontendJWT?: string` - JWT token
|
||
|
||
**查询参数**:
|
||
```typescript
|
||
{
|
||
select: `
|
||
id,
|
||
name,
|
||
description,
|
||
evaluation_point_groups_ids,
|
||
entry_module_id,
|
||
entry_modules!fk_document_types_entry_module(id, name),
|
||
prompt_config,
|
||
created_at,
|
||
updated_at,
|
||
code
|
||
`,
|
||
filter: {
|
||
'id': `eq.${id}` // 精确匹配ID
|
||
},
|
||
token?: string
|
||
}
|
||
```
|
||
|
||
**SQL 等价查询**:
|
||
```sql
|
||
SELECT
|
||
dt.id,
|
||
dt.name,
|
||
dt.description,
|
||
dt.evaluation_point_groups_ids,
|
||
dt.entry_module_id,
|
||
dt.prompt_config,
|
||
dt.created_at,
|
||
dt.updated_at,
|
||
dt.code,
|
||
em.id as entry_module_id,
|
||
em.name as entry_module_name
|
||
FROM document_types dt
|
||
LEFT JOIN entry_modules em ON dt.entry_module_id = em.id
|
||
WHERE dt.id = :id
|
||
LIMIT 1;
|
||
```
|
||
|
||
**PostgREST API 请求**:
|
||
```http
|
||
GET /document_types?id=eq.1&select=id,name,description,evaluation_point_groups_ids,entry_module_id,entry_modules!fk_document_types_entry_module(id,name),prompt_config,created_at,updated_at,code
|
||
Authorization: Bearer {token}
|
||
```
|
||
|
||
**查询逻辑流程**:
|
||
```
|
||
1. 查询 document_types 表(单条记录)
|
||
└─ 关联查询 entry_modules
|
||
|
||
2. 解析 evaluation_point_groups_ids
|
||
├─ 字符串 → JSON.parse()
|
||
├─ 数组 → 直接使用
|
||
└─ 其他 → 转换为数组
|
||
|
||
3. 批量查询评查点分组
|
||
└─ getEvaluationPointGroupsByIds(groupIds)
|
||
|
||
4. 合并数据并转换为 DocumentTypeUI
|
||
└─ convertToUIDocumentType()
|
||
```
|
||
|
||
**返回数据**:
|
||
```typescript
|
||
{
|
||
data?: DocumentTypeUI;
|
||
error?: string;
|
||
status?: number;
|
||
}
|
||
```
|
||
|
||
**数据示例**:
|
||
```json
|
||
{
|
||
"data": {
|
||
"id": 1,
|
||
"name": "烟草专卖行政处罚案卷",
|
||
"description": "烟草专卖行政处罚案卷的智能评查",
|
||
"groups": [
|
||
{ "id": "5", "name": "程序合法性" },
|
||
{ "id": "8", "name": "证据完整性" }
|
||
],
|
||
"entry_module": {
|
||
"id": 1,
|
||
"name": "多模态抽取"
|
||
},
|
||
"llm_extraction_template_id": 10,
|
||
"vlm_extraction_template_id": 15,
|
||
"evaluation_template_id": 20,
|
||
"summary_template_id": 25,
|
||
"created_at": "2024-01-15 10:00:00",
|
||
"updated_at": "2024-01-20 15:30:00",
|
||
"code": "TOBACCO_CASE"
|
||
}
|
||
}
|
||
```
|
||
|
||
**错误处理**:
|
||
- ❌ ID为空 → 400 错误
|
||
- ❌ 未找到记录 → 404 错误
|
||
- ❌ 分组ID解析失败 → 记录错误日志,返回空数组
|
||
|
||
**使用场景**:
|
||
- 文档类型编辑页面
|
||
- 文档类型详情查看
|
||
|
||
---
|
||
|
||
## ➕ 创建操作(Create)
|
||
|
||
### createDocumentType
|
||
|
||
**功能**:创建新的文档类型
|
||
|
||
**代码位置**:lines 655-774
|
||
|
||
**数据库表**:
|
||
- `document_types`(主表,INSERT)
|
||
- `evaluation_point_groups`(关联查询)
|
||
|
||
**参数**:`DocumentTypeCreateDTO`
|
||
```typescript
|
||
{
|
||
name: string; // 必填:文档类型名称
|
||
description?: string; // 可选:描述
|
||
group_ids: string[]; // 必填:评查点分组ID数组
|
||
entry_module_id?: number | null; // 可选:入口模块ID
|
||
llm_extraction_template_id?: number | null;
|
||
vlm_extraction_template_id?: number | null;
|
||
evaluation_template_id?: number | null;
|
||
summary_template_id?: number | null;
|
||
code?: string | null;
|
||
}
|
||
```
|
||
|
||
**数据验证**:
|
||
```typescript
|
||
1. name 不能为空
|
||
2. group_ids 至少选择一个
|
||
3. group_ids[0] 必须是有效的数字ID
|
||
4. 各模板ID必须是有效数字(如果提供)
|
||
```
|
||
|
||
**数据转换逻辑**:
|
||
```typescript
|
||
// 1. 提取第一个分组ID(目前只支持单选)
|
||
const groupId = documentType.group_ids[0];
|
||
const groupIds = [parseInt(groupId, 10)];
|
||
|
||
// 2. 构建 prompt_config 对象
|
||
const promptConfig = {
|
||
llm_extract_template: documentType.llm_extraction_template_id || null,
|
||
vlm_extract_template: documentType.vlm_extraction_template_id || null,
|
||
execution_template: documentType.evaluation_template_id || null, // 注意字段名转换
|
||
summary_template: documentType.summary_template_id || null
|
||
};
|
||
|
||
// 3. 构建 API 请求数据
|
||
const apiDocumentType = {
|
||
name: documentType.name.trim(),
|
||
description: documentType.description || '',
|
||
evaluation_point_groups_ids: groupIds, // 数组格式
|
||
entry_module_id: documentType.entry_module_id || null,
|
||
prompt_config: promptConfig // JSONB对象
|
||
};
|
||
```
|
||
|
||
**SQL 等价操作**:
|
||
```sql
|
||
INSERT INTO document_types (
|
||
name,
|
||
description,
|
||
evaluation_point_groups_ids,
|
||
entry_module_id,
|
||
prompt_config,
|
||
created_at,
|
||
updated_at
|
||
) VALUES (
|
||
'烟草专卖行政处罚案卷',
|
||
'烟草专卖行政处罚案卷的智能评查',
|
||
'[5]'::jsonb,
|
||
1,
|
||
'{
|
||
"llm_extract_template": 10,
|
||
"vlm_extract_template": 15,
|
||
"execution_template": 20,
|
||
"summary_template": 25
|
||
}'::jsonb,
|
||
NOW(),
|
||
NOW()
|
||
)
|
||
RETURNING *;
|
||
```
|
||
|
||
**PostgREST API 请求**:
|
||
```http
|
||
POST /document_types
|
||
Content-Type: application/json
|
||
Authorization: Bearer {token}
|
||
|
||
{
|
||
"name": "烟草专卖行政处罚案卷",
|
||
"description": "烟草专卖行政处罚案卷的智能评查",
|
||
"evaluation_point_groups_ids": [5],
|
||
"entry_module_id": 1,
|
||
"prompt_config": {
|
||
"llm_extract_template": 10,
|
||
"vlm_extract_template": 15,
|
||
"execution_template": 20,
|
||
"summary_template": 25
|
||
}
|
||
}
|
||
```
|
||
|
||
**创建流程**:
|
||
```
|
||
1. 验证必填字段
|
||
├─ name 不为空
|
||
├─ group_ids 至少有一个
|
||
└─ 各ID格式有效
|
||
|
||
2. 转换数据格式
|
||
├─ group_ids[0] → groupIds: [number]
|
||
└─ 各模板ID → prompt_config: JSONB
|
||
|
||
3. 发送 POST 请求到 document_types 表
|
||
└─ 使用 postgrestPost
|
||
|
||
4. 提取返回的新记录
|
||
└─ extractApiData<DocumentType>
|
||
|
||
5. 查询关联的评查点分组信息
|
||
└─ getEvaluationPointGroupsByIds(groupIds)
|
||
|
||
6. 合并数据并转换为 DocumentTypeUI
|
||
└─ convertToUIDocumentType()
|
||
|
||
7. 返回创建的文档类型
|
||
```
|
||
|
||
**返回数据**:
|
||
```typescript
|
||
{
|
||
data?: DocumentTypeUI; // 新创建的文档类型(包含ID)
|
||
error?: string;
|
||
status?: number;
|
||
}
|
||
```
|
||
|
||
**数据示例**:
|
||
```json
|
||
{
|
||
"data": {
|
||
"id": 26, // 自动生成的ID
|
||
"name": "烟草专卖行政处罚案卷",
|
||
"description": "烟草专卖行政处罚案卷的智能评查",
|
||
"groups": [
|
||
{ "id": "5", "name": "程序合法性" }
|
||
],
|
||
"entry_module": null,
|
||
"llm_extraction_template_id": 10,
|
||
"vlm_extraction_template_id": 15,
|
||
"evaluation_template_id": 20,
|
||
"summary_template_id": 25,
|
||
"created_at": "2024-01-26 16:45:00",
|
||
"updated_at": "2024-01-26 16:45:00",
|
||
"code": null
|
||
}
|
||
}
|
||
```
|
||
|
||
**错误处理**:
|
||
- ❌ name 为空 → `{ error: '文档类型名称不能为空', status: 400 }`
|
||
- ❌ group_ids 为空 → `{ error: '请至少选择一个关联的评查点分组', status: 400 }`
|
||
- ❌ group_id 无效 → `{ error: '无效的评查点分组ID', status: 400 }`
|
||
- ❌ 模板ID无效 → `{ error: '无效的xxx提示词模板ID', status: 400 }`
|
||
- ❌ API返回错误 → 返回 API 错误信息
|
||
|
||
**使用场景**:
|
||
- 文档类型管理页面的"新建"操作
|
||
- 系统初始化时批量创建文档类型
|
||
|
||
---
|
||
|
||
## ✏️ 更新操作(Update)
|
||
|
||
### updateDocumentType
|
||
|
||
**功能**:更新现有文档类型
|
||
|
||
**代码位置**:lines 782-896
|
||
|
||
**数据库表**:
|
||
- `document_types`(主表,UPDATE)
|
||
- `evaluation_point_groups`(关联查询)
|
||
|
||
**参数**:
|
||
- `id: string` - 文档类型ID
|
||
- `documentType: DocumentTypeUpdateDTO` - 更新数据
|
||
|
||
```typescript
|
||
{
|
||
id: number; // 包含在DTO中,但实际使用函数参数的id
|
||
name: string;
|
||
description?: string;
|
||
group_ids: string[];
|
||
entry_module_id?: number | null;
|
||
llm_extraction_template_id?: number | null;
|
||
vlm_extraction_template_id?: number | null;
|
||
evaluation_template_id?: number | null;
|
||
summary_template_id?: number | null;
|
||
code?: string | null;
|
||
}
|
||
```
|
||
|
||
**数据验证**:
|
||
```typescript
|
||
1. id 不能为空
|
||
2. name 不能为空
|
||
3. group_ids 至少选择一个
|
||
4. 各模板ID必须是有效数字(如果提供)
|
||
```
|
||
|
||
**数据转换逻辑**:
|
||
```typescript
|
||
// 1. 将 group_ids 转换为数字数组
|
||
const groupIds = documentType.group_ids.map(id => parseInt(id, 10));
|
||
|
||
// 2. 构建 prompt_config 对象(与创建逻辑相同)
|
||
const promptConfig = {
|
||
llm_extract_template: documentType.llm_extraction_template_id || null,
|
||
vlm_extract_template: documentType.vlm_extraction_template_id || null,
|
||
execution_template: documentType.evaluation_template_id || null,
|
||
summary_template: documentType.summary_template_id || null
|
||
};
|
||
|
||
// 3. 构建 API 请求数据
|
||
const apiDocumentType = {
|
||
name: documentType.name.trim(),
|
||
description: documentType.description || '',
|
||
evaluation_point_groups_ids: groupIds,
|
||
entry_module_id: documentType.entry_module_id || null,
|
||
prompt_config: promptConfig
|
||
};
|
||
```
|
||
|
||
**SQL 等价操作**:
|
||
```sql
|
||
UPDATE document_types
|
||
SET
|
||
name = '烟草专卖行政处罚案卷(修订版)',
|
||
description = '更新的描述',
|
||
evaluation_point_groups_ids = '[5, 8]'::jsonb,
|
||
entry_module_id = 2,
|
||
prompt_config = '{
|
||
"llm_extract_template": 11,
|
||
"vlm_extract_template": 16,
|
||
"execution_template": 21,
|
||
"summary_template": 26
|
||
}'::jsonb,
|
||
updated_at = NOW()
|
||
WHERE id = 1
|
||
RETURNING *;
|
||
```
|
||
|
||
**PostgREST API 请求**:
|
||
```http
|
||
PATCH /document_types?id=eq.1
|
||
Content-Type: application/json
|
||
Authorization: Bearer {token}
|
||
|
||
{
|
||
"name": "烟草专卖行政处罚案卷(修订版)",
|
||
"description": "更新的描述",
|
||
"evaluation_point_groups_ids": [5, 8],
|
||
"entry_module_id": 2,
|
||
"prompt_config": {
|
||
"llm_extract_template": 11,
|
||
"vlm_extract_template": 16,
|
||
"execution_template": 21,
|
||
"summary_template": 26
|
||
}
|
||
}
|
||
```
|
||
|
||
**更新流程**:
|
||
```
|
||
1. 验证必填字段
|
||
├─ id 不为空
|
||
├─ name 不为空
|
||
└─ group_ids 至少有一个
|
||
|
||
2. 转换数据格式
|
||
├─ group_ids → groupIds: number[]
|
||
└─ 各模板ID → prompt_config: JSONB
|
||
|
||
3. 发送 PUT/PATCH 请求到 document_types 表
|
||
└─ 使用 postgrestPut(WHERE id=:id)
|
||
|
||
4. 提取返回的更新后记录
|
||
└─ extractApiData<DocumentType>
|
||
|
||
5. 查询关联的评查点分组信息
|
||
└─ getEvaluationPointGroupsByIds(groupIds)
|
||
|
||
6. 合并数据并转换为 DocumentTypeUI
|
||
└─ convertToUIDocumentType()
|
||
|
||
7. 返回更新后的文档类型
|
||
```
|
||
|
||
**返回数据**:
|
||
```typescript
|
||
{
|
||
data?: DocumentTypeUI; // 更新后的文档类型
|
||
error?: string;
|
||
status?: number;
|
||
}
|
||
```
|
||
|
||
**数据示例**:
|
||
```json
|
||
{
|
||
"data": {
|
||
"id": 1,
|
||
"name": "烟草专卖行政处罚案卷(修订版)",
|
||
"description": "更新的描述",
|
||
"groups": [
|
||
{ "id": "5", "name": "程序合法性" },
|
||
{ "id": "8", "name": "证据完整性" }
|
||
],
|
||
"entry_module": {
|
||
"id": 2,
|
||
"name": "智能评查"
|
||
},
|
||
"llm_extraction_template_id": 11,
|
||
"vlm_extraction_template_id": 16,
|
||
"evaluation_template_id": 21,
|
||
"summary_template_id": 26,
|
||
"created_at": "2024-01-15 10:00:00",
|
||
"updated_at": "2024-01-26 17:30:00", // 自动更新
|
||
"code": "TOBACCO_CASE"
|
||
}
|
||
}
|
||
```
|
||
|
||
**错误处理**:
|
||
- ❌ id 为空 → `{ error: '文档类型ID不能为空', status: 400 }`
|
||
- ❌ name 为空 → `{ error: '文档类型名称不能为空', status: 400 }`
|
||
- ❌ group_ids 为空 → `{ error: '请至少选择一个关联的评查点分组', status: 400 }`
|
||
- ❌ 模板ID无效 → `{ error: '无效的xxx提示词模板ID', status: 400 }`
|
||
- ❌ API返回错误 → 返回 API 错误信息
|
||
- ❌ 记录不存在 → `{ error: '更新文档类型失败: 无法获取更新后的数据', status: 500 }`
|
||
|
||
**使用场景**:
|
||
- 文档类型管理页面的"编辑"操作
|
||
- 批量更新文档类型配置
|
||
|
||
---
|
||
|
||
## ❌ 删除操作(Delete)
|
||
|
||
### deleteDocumentType
|
||
|
||
**功能**:删除指定的文档类型
|
||
|
||
**代码位置**:lines 466-499
|
||
|
||
**数据库表**:
|
||
- `document_types`(DELETE操作)
|
||
|
||
**参数**:
|
||
- `id: string` - 文档类型ID
|
||
- `frontendJWT?: string` - JWT token
|
||
|
||
**查询参数**:
|
||
```typescript
|
||
{
|
||
filter: {
|
||
'id': `eq.${id}` // 精确匹配要删除的ID
|
||
},
|
||
token?: string
|
||
}
|
||
```
|
||
|
||
**SQL 等价操作**:
|
||
```sql
|
||
DELETE FROM document_types
|
||
WHERE id = :id;
|
||
```
|
||
|
||
**PostgREST API 请求**:
|
||
```http
|
||
DELETE /document_types?id=eq.1
|
||
Authorization: Bearer {token}
|
||
```
|
||
|
||
**删除流程**:
|
||
```
|
||
1. 验证 ID 不为空
|
||
└─ 如果为空 → 返回 400 错误
|
||
|
||
2. 发送 DELETE 请求到 document_types 表
|
||
└─ 使用 postgrestDelete(WHERE id=:id)
|
||
|
||
3. 检查是否有错误
|
||
└─ 如果有错误 → 返回错误信息
|
||
|
||
4. 返回删除成功标识
|
||
└─ { success: true }
|
||
```
|
||
|
||
**返回数据**:
|
||
```typescript
|
||
{
|
||
success?: boolean; // true 表示删除成功
|
||
error?: string;
|
||
status?: number;
|
||
}
|
||
```
|
||
|
||
**成功响应示例**:
|
||
```json
|
||
{
|
||
"success": true
|
||
}
|
||
```
|
||
|
||
**错误处理**:
|
||
- ❌ ID为空 → `{ error: '文档类型ID不能为空', status: 400 }`
|
||
- ❌ 记录不存在 → PostgREST 不会报错,返回成功
|
||
- ❌ 外键约束 → `{ error: '无法删除,该文档类型正在被使用', status: 409 }`(数据库级错误)
|
||
- ❌ 权限不足 → `{ error: '权限不足', status: 403 }`
|
||
|
||
**注意事项**:
|
||
- ⚠️ 删除操作是**物理删除**,不可恢复
|
||
- ⚠️ 如果有文档关联到此类型,删除可能失败(外键约束)
|
||
- ⚠️ 建议前端弹窗确认后再执行删除
|
||
|
||
**使用场景**:
|
||
- 文档类型管理页面的"删除"操作
|
||
- 清理无用的文档类型
|
||
|
||
---
|
||
|
||
## 🔄 数据流转图
|
||
|
||
### 创建文档类型数据流
|
||
|
||
```mermaid
|
||
graph TD
|
||
A[前端提交表单] --> B[createDocumentType]
|
||
B --> C{验证数据}
|
||
C -->|失败| D[返回400错误]
|
||
C -->|成功| E[转换数据格式]
|
||
E --> F[构建prompt_config]
|
||
F --> G[POST /document_types]
|
||
G --> H{创建成功?}
|
||
H -->|否| I[返回错误]
|
||
H -->|是| J[提取新记录]
|
||
J --> K[查询评查点分组]
|
||
K --> L[合并数据]
|
||
L --> M[转换为UI格式]
|
||
M --> N[返回给前端]
|
||
|
||
style A fill:#90EE90
|
||
style N fill:#FFD700
|
||
style D fill:#FF6B6B
|
||
style I fill:#FF6B6B
|
||
```
|
||
|
||
### 查询文档类型列表数据流
|
||
|
||
```mermaid
|
||
graph LR
|
||
A[getDocumentTypes] --> B[查询 document_types]
|
||
B --> C[关联查询 entry_modules]
|
||
C --> D[收集所有 group_ids]
|
||
D --> E[批量查询 evaluation_point_groups]
|
||
E --> F[构建 groupsMap]
|
||
F --> G[遍历文档类型]
|
||
G --> H[合并关联数据]
|
||
H --> I[转换为UI格式]
|
||
I --> J[返回列表和总数]
|
||
|
||
style A fill:#87CEEB
|
||
style J fill:#FFD700
|
||
```
|
||
|
||
---
|
||
|
||
## 📋 关键业务逻辑
|
||
|
||
### 1. prompt_config 字段处理
|
||
|
||
**数据库存储格式**(JSONB):
|
||
```json
|
||
{
|
||
"llm_extract_template": 10,
|
||
"vlm_extract_template": 15,
|
||
"execution_template": 20, // 注意:数据库字段名
|
||
"summary_template": 25
|
||
}
|
||
```
|
||
|
||
**前端使用格式**(DocumentTypeUI):
|
||
```typescript
|
||
{
|
||
llm_extraction_template_id: 10,
|
||
vlm_extraction_template_id: 15,
|
||
evaluation_template_id: 20, // 注意:前端字段名
|
||
summary_template_id: 25
|
||
}
|
||
```
|
||
|
||
**字段映射关系**:
|
||
| 数据库字段 | 前端字段 | 说明 |
|
||
|-----------|---------|------|
|
||
| `llm_extract_template` | `llm_extraction_template_id` | LLM抽取模板 |
|
||
| `vlm_extract_template` | `vlm_extraction_template_id` | VLM抽取模板 |
|
||
| `execution_template` 或 `evaluation_template` | `evaluation_template_id` | 评查模板 |
|
||
| `summary_template` | `summary_template_id` | 总结模板 |
|
||
|
||
**转换逻辑**(convertToUIDocumentType):
|
||
```typescript
|
||
// 优先使用 evaluation_template,如果不存在则使用 execution_template
|
||
if (type.prompt_config.evaluation_template !== undefined && type.prompt_config.evaluation_template !== null) {
|
||
evaluationTemplateId = type.prompt_config.evaluation_template;
|
||
} else if (type.prompt_config.execution_template !== undefined && type.prompt_config.execution_template !== null) {
|
||
evaluationTemplateId = type.prompt_config.execution_template;
|
||
}
|
||
```
|
||
|
||
### 2. evaluation_point_groups_ids 处理
|
||
|
||
**数据类型**:JSONB数组
|
||
**存储格式**:`[5, 8, 12]`
|
||
|
||
**解析逻辑**(lines 609-624):
|
||
```typescript
|
||
if (typeof evaluation_point_groups_ids === 'string') {
|
||
// 情况1:JSON字符串
|
||
groupIds = JSON.parse(evaluation_point_groups_ids);
|
||
} else if (Array.isArray(evaluation_point_groups_ids)) {
|
||
// 情况2:已经是数组
|
||
groupIds = evaluation_point_groups_ids;
|
||
} else if (evaluation_point_groups_ids) {
|
||
// 情况3:单个值
|
||
groupIds = [evaluation_point_groups_ids];
|
||
}
|
||
```
|
||
|
||
**当前限制**:
|
||
- 创建时只使用 `group_ids[0]`(第一个分组)
|
||
- 更新时支持多个分组(`groupIds = group_ids.map(...)`)
|
||
|
||
### 3. PostgREST 资源嵌入语法
|
||
|
||
**关联查询 entry_modules**:
|
||
```typescript
|
||
select: `
|
||
entry_modules!fk_document_types_entry_module(id, name)
|
||
`
|
||
```
|
||
|
||
**说明**:
|
||
- `!fk_document_types_entry_module` - 外键约束名称
|
||
- 自动执行 LEFT JOIN
|
||
- 返回嵌套对象
|
||
|
||
**等价 SQL**:
|
||
```sql
|
||
LEFT JOIN entry_modules em
|
||
ON dt.entry_module_id = em.id
|
||
```
|
||
|
||
### 4. 分页和总数获取
|
||
|
||
**分页参数**:
|
||
```typescript
|
||
limit: pageSize, // 每页数量
|
||
offset: (page - 1) * pageSize // 跳过记录数
|
||
```
|
||
|
||
**获取总数**:
|
||
```typescript
|
||
headers: {
|
||
'Prefer': 'count=exact' // 请求返回总数
|
||
}
|
||
|
||
// 从响应头读取总数
|
||
const rangeHeader = response.headers['content-range'];
|
||
// 格式: "0-9/25" → 总数是25
|
||
const total = rangeHeader.split('/')[1];
|
||
```
|
||
|
||
### 5. 批量查询优化
|
||
|
||
**问题**:N+1查询
|
||
```typescript
|
||
// ❌ 不好的做法
|
||
for (const type of documentTypes) {
|
||
const groups = await getEvaluationPointGroupsByIds(type.evaluation_point_groups_ids);
|
||
}
|
||
```
|
||
|
||
**解决方案**:批量查询
|
||
```typescript
|
||
// ✅ 好的做法
|
||
// 1. 收集所有分组ID
|
||
const allGroupIds = new Set<number>();
|
||
documentTypes.forEach(type => {
|
||
type.evaluation_point_groups_ids.forEach(id => allGroupIds.add(id));
|
||
});
|
||
|
||
// 2. 一次性查询所有分组
|
||
const groupsResponse = await getEvaluationPointGroupsByIds(Array.from(allGroupIds));
|
||
|
||
// 3. 构建Map快速查找
|
||
const groupsMap = new Map<number, DocumentTypeGroup>();
|
||
groupsResponse.data.forEach(group => {
|
||
groupsMap.set(parseInt(group.id, 10), group);
|
||
});
|
||
|
||
// 4. 遍历时O(1)查找
|
||
const typeGroups = ids.map(id => groupsMap.get(id)).filter(Boolean);
|
||
```
|
||
|
||
---
|
||
|
||
## 🎯 使用示例
|
||
|
||
### 前端调用示例
|
||
|
||
#### 1. 获取文档类型列表
|
||
|
||
```typescript
|
||
import { getDocumentTypes } from '~/api/document-types/document-types';
|
||
|
||
// 在 Remix loader 中
|
||
export async function loader({ request }: LoaderFunctionArgs) {
|
||
const url = new URL(request.url);
|
||
const page = parseInt(url.searchParams.get('page') || '1');
|
||
const name = url.searchParams.get('name') || '';
|
||
|
||
const { userInfo, frontendJWT } = await getUserSession(request);
|
||
|
||
const result = await getDocumentTypes({
|
||
name,
|
||
page,
|
||
pageSize: 10
|
||
}, frontendJWT);
|
||
|
||
if (result.error) {
|
||
return json({ error: result.error }, { status: result.status || 500 });
|
||
}
|
||
|
||
return json(result.data);
|
||
}
|
||
|
||
// 前端组件使用
|
||
const { types, total } = useLoaderData<typeof loader>();
|
||
```
|
||
|
||
#### 2. 创建文档类型
|
||
|
||
```typescript
|
||
import { createDocumentType } from '~/api/document-types/document-types';
|
||
|
||
// 在 Remix action 中
|
||
export async function action({ request }: ActionFunctionArgs) {
|
||
const formData = await request.formData();
|
||
const { frontendJWT } = await getUserSession(request);
|
||
|
||
const documentType: DocumentTypeCreateDTO = {
|
||
name: formData.get('name') as string,
|
||
description: formData.get('description') as string,
|
||
group_ids: formData.getAll('group_ids') as string[],
|
||
entry_module_id: parseInt(formData.get('entry_module_id') as string) || null,
|
||
llm_extraction_template_id: parseInt(formData.get('llm_template') as string) || null,
|
||
// ...
|
||
};
|
||
|
||
const result = await createDocumentType(documentType, frontendJWT);
|
||
|
||
if (result.error) {
|
||
return json({ error: result.error }, { status: result.status || 500 });
|
||
}
|
||
|
||
return redirect('/document-types');
|
||
}
|
||
```
|
||
|
||
#### 3. 更新文档类型
|
||
|
||
```typescript
|
||
import { updateDocumentType } from '~/api/document-types/document-types';
|
||
|
||
export async function action({ params, request }: ActionFunctionArgs) {
|
||
const id = params.id!;
|
||
const formData = await request.formData();
|
||
const { frontendJWT } = await getUserSession(request);
|
||
|
||
const documentType: DocumentTypeUpdateDTO = {
|
||
id: parseInt(id),
|
||
name: formData.get('name') as string,
|
||
// ...
|
||
};
|
||
|
||
const result = await updateDocumentType(id, documentType, frontendJWT);
|
||
|
||
if (result.error) {
|
||
return json({ error: result.error }, { status: result.status || 500 });
|
||
}
|
||
|
||
return json(result.data);
|
||
}
|
||
```
|
||
|
||
#### 4. 删除文档类型
|
||
|
||
```typescript
|
||
import { deleteDocumentType } from '~/api/document-types/document-types';
|
||
|
||
export async function action({ params, request }: ActionFunctionArgs) {
|
||
const id = params.id!;
|
||
const { frontendJWT } = await getUserSession(request);
|
||
|
||
const result = await deleteDocumentType(id, frontendJWT);
|
||
|
||
if (result.error) {
|
||
return json({ error: result.error }, { status: result.status || 500 });
|
||
}
|
||
|
||
return redirect('/document-types');
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## 🔍 数据库索引建议
|
||
|
||
为了优化查询性能,建议创建以下索引:
|
||
|
||
```sql
|
||
-- document_types 表
|
||
CREATE INDEX idx_document_types_name ON document_types(name);
|
||
CREATE INDEX idx_document_types_updated_at ON document_types(updated_at DESC);
|
||
CREATE INDEX idx_document_types_entry_module_id ON document_types(entry_module_id);
|
||
|
||
-- 使用 GIN 索引优化 JSONB 查询
|
||
CREATE INDEX idx_document_types_evaluation_groups
|
||
ON document_types USING GIN (evaluation_point_groups_ids);
|
||
|
||
-- evaluation_point_groups 表
|
||
CREATE INDEX idx_evaluation_point_groups_pid ON evaluation_point_groups(pid);
|
||
CREATE INDEX idx_evaluation_point_groups_name ON evaluation_point_groups(name);
|
||
|
||
-- entry_modules 表
|
||
CREATE INDEX idx_entry_modules_name ON entry_modules(name);
|
||
```
|
||
|
||
---
|
||
|
||
## 📝 总结
|
||
|
||
### 核心功能
|
||
|
||
1. **完整的CRUD操作**:创建、读取、更新、删除文档类型
|
||
2. **关联查询**:自动关联评查点分组和入口模块
|
||
3. **搜索和筛选**:支持名称模糊搜索、分组筛选
|
||
4. **分页查询**:支持大数据量的分页展示
|
||
5. **批量查询优化**:避免N+1查询问题
|
||
|
||
### 数据库表
|
||
|
||
| 表名 | 操作类型 | 说明 |
|
||
|-----|---------|------|
|
||
| `document_types` | CRUD | 主表,存储文档类型信息 |
|
||
| `evaluation_point_groups` | R | 评查点分组表,用于关联查询 |
|
||
| `entry_modules` | R | 入口模块表,用于关联查询 |
|
||
|
||
### 关键字段
|
||
|
||
| 字段名 | 类型 | 说明 |
|
||
|-------|------|------|
|
||
| `evaluation_point_groups_ids` | JSONB数组 | 关联的评查点分组ID |
|
||
| `prompt_config` | JSONB对象 | 提示词模板配置 |
|
||
| `entry_module_id` | INTEGER | 入口模块外键 |
|
||
|
||
### 性能优化
|
||
|
||
- ✅ PostgREST 资源嵌入语法(避免多次查询)
|
||
- ✅ 批量查询评查点分组(避免N+1查询)
|
||
- ✅ Map 映射快速查找(O(1)时间复杂度)
|
||
- ✅ 分页查询(减少数据传输)
|
||
- ✅ GIN 索引优化 JSONB 查询
|
||
|
||
### 使用场景
|
||
|
||
- 文档类型管理页面(列表、创建、编辑、删除)
|
||
- 文档上传时选择文档类型
|
||
- 配置文档处理流程(入口模块、提示词模板)
|
||
- 评查点分组管理
|
||
|
||
---
|
||
|
||
**最后更新**:2025-11-26
|
||
**文档版本**:v1.0
|
||
**代码文件**:`app/api/document-types/document-types.ts`
|