Files
lablesys_backend/README.md

1497 lines
63 KiB
Markdown
Raw Permalink Normal View History

2026-04-27 23:26:25 +08:00
# 标注平台后端设计方案
2026-04-23 12:11:13 +08:00
2026-04-27 23:26:25 +08:00
## 1. 文档目标
本文档只覆盖一期 Java Backend 需要落地的模块,不扩展到训练集导出、微调任务编排二期能力。
## 2. 一期范围
### 2.1 一期要落地的模块
一期后端只建设以下四个业务模块:
1. 资源管理模块 `source_resource`
2. 任务管理模块 `annotation_task`
3. 审批标注结果模块 `annotation_result` / `annotation_result_history`
4. 系统配置模块 `sys_config`
一期继续复用现有认证鉴权基线:
- 用户登录、Token、会话版本控制沿用现有认证实现
- 岗位权限沿用 `position`
- 数据权限沿用 `role`
- 租户隔离沿用 `company_id`
### 2.2 一期不做的内容
以下内容不纳入本次一期后端设计范围:
- 新建部门体系
- 新建复杂工作流引擎
- 训练集导出中心的完整业务闭环
- 面向前台的复杂配置编排 UI
- AI Service 内部推理链路实现细节
## 3. 总体架构
### 3.1 系统组成
一期采用“四段式协作”:
1. `lb_backend`:对前端暴露 REST API负责资源、任务、结果审核、系统配置。
2. `PostgreSQL`:持久化元数据、任务快照、审核结果、归档结果。
3. `RustFS`:保存原始资源、视频衍生文件、外置问答 JSON、导出文件。
4. `ai-service`:轮询待执行任务,拉取资源,完成抽取、校验、比对,并写回结果。
2026-04-23 12:11:13 +08:00
2026-04-27 23:26:25 +08:00
### 3.2 核心设计原则
1. 资源生命周期、任务执行生命周期、审核归档生命周期分开建模。
2. 大字段优先支持表内存储,但必须预留外置存储能力。
3. 后端创建任务时只负责写入 `PENDING`;执行态状态全部由 `ai-service` 驱动。
4. `annotation_result` 只保存“当前待处理结果”;归档后的最终结果统一进入 `annotation_result_history`
5. 运行态结果不再依赖 `review_status`,而是通过 `requires_manual_review``is_deleted` 和历史归档记录组合表达状态。
### 3.3 一期包结构
一期包结构按 [微服务开发规范文档](D:/workspace/labeling/docs/微服务开发规范文档.md) 采用“按技术层分包”的标准结构,不再使用 `modules/resource``modules/task` 这类按大模块纵向拆包的目录形式:
2026-04-23 12:11:13 +08:00
```text
2026-04-27 23:26:25 +08:00
lb_backend/src/main/java/com/labelsys/backend
├── annotation
├── common
│ ├── exception
│ ├── Result.java
│ └── ResultCode.java
├── config
├── constant
├── context
├── controller
│ ├── AnnotationResultController.java
│ ├── AnnotationTaskController.java
│ ├── SourceResourceController.java
│ └── SysConfigController.java
├── dto
│ ├── common
│ │ ├── ApiResponse.java
│ │ └── PageResult.java
│ ├── request
│ │ ├── SourceUploadRequest.java
│ │ ├── SourceResourcePageQuery.java
│ │ ├── ManualModelConfigRequest.java
│ │ ├── TaskModelConfigRequest.java
│ │ ├── PromptConfigOptionRequest.java
│ │ ├── CreateAnnotationTaskRequest.java
│ │ ├── UpdateAnnotationTaskRequest.java
│ │ ├── AnnotationTaskPageQuery.java
│ │ ├── AnnotationResultPageQuery.java
│ │ ├── MergeReviewResultRequest.java
│ │ ├── SaveSysConfigRequest.java
│ │ └── SysConfigPageQuery.java
│ └── response
│ ├── SourceUploadResponse.java
│ ├── SourceResourceResponse.java
│ ├── TaskModelConfigResponse.java
│ ├── TaskPromptConfigResponse.java
│ ├── AnnotationResultResponse.java
│ ├── AnnotationResultCompareResponse.java
│ ├── AnnotationTaskResponse.java
│ ├── MergeReviewResultResponse.java
│ └── SysConfigResponse.java
├── entity
├── mapper
├── scheduled
├── service
├── typehandler
└── util
2026-04-23 12:11:13 +08:00
```
2026-04-27 23:26:25 +08:00
补充约定:
1. 控制器、DTO、实体、Mapper、Service 统一放在规范文档要求的根层目录下,业务边界通过类名前缀区分,例如 `AnnotationTaskController``SourceResourceMapper`
2. 定时任务统一进入 `scheduled` 包,不再在业务模块下单独定义 `job` 子目录。
3. Mapper XML 统一放在 `src/main/resources/mapper`SQL 基准文件统一放在 `src/main/resources/sql`
4. DTO 进一步细分为 `dto/request``dto/response``dto/common`,避免不同模块的交互对象混放。
### 3.4 接口对象与数据库实体分层
1. 数据库实体仅用于 Repository / Mapper 层持久化,统一命名为 `*Entity`,不直接作为 Controller 入参或出参。
2. Controller 层只接收 `*Request``*Query`,只返回 `*Response``*View``PageResult<T>`
3. Service 层负责实体转换与聚合装配,例如任务详情需要把 `annotation_task``annotation_task_resource``sys_config` 组合成前端可直接消费的交互对象。
4. API 字段命名遵循 Java 驼峰风格,例如数据库列 `is_deleted` 在接口层统一映射为 `isDeleted`
5. 敏感字段不直接透出到界面层,模型 `apiKey` 只在任务快照和配置表中保存,接口返回时必须脱敏或不返回。
## 4. 权限模型
### 4.1 岗位权限
一期沿用现有岗位枚举:
- `ANNOTATOR`
- `DATA_TRAINER`
- `REVIEWER`
- `ADMIN`
权限建议如下:
| 模块 | 查询 | 新增/修改 | 删除 | 特殊说明 |
|---|---|---|---|---|
| 资源管理 | 全岗位可查 | 全岗位可上传 | 全岗位可删自身可见资源 | 删除时仍受数据权限约束 |
| 任务管理 | 全岗位可查 | 全岗位可创建 | 全岗位可删未执行任务 | backend 只创建 `PENDING` |
| 结果查询 | 全岗位可查可见数据 | 无 | 无 | 仅查询本人/本层级可见数据 |
| 人工审核 | `REVIEWER` 及以上 | `REVIEWER` 及以上 | 无 | 对所有未归档运行态结果开放;其中 `requires_manual_review = true` 的结果必须审核后才能归档 |
| 系统配置 | 全岗位可查 | `ADMIN` 建议可写 | 无 | 若沿需求“所有岗位可操作”,需由产品再次确认风险 |
说明:需求原文提到“系统配置模块对所有岗位可以操作”,这在安全上风险较高。本文档保留查询全开放,但建议写权限收敛到 `ADMIN`。如果必须全岗位可写,则需额外增加配置白名单和审计。
### 4.2 数据权限
一期沿用现有数据权限角色:
- `EMPLOYEE`:只看自己创建的数据
- `MANAGER`:看本公司 `EMPLOYEE + MANAGER`
- `ENGINEER`:看本公司全部数据
业务表统一保留:
- `company_id`
- `creator_id`
- `creator_role`
### 4.3 权限落地规则
1. 系统表以 `company_id` 作为租户边界。
2. 业务列表接口默认按 `company_id` 过滤。
3. 再按 `creator_role + creator_id` 追加数据权限过滤。
4. 审核类写操作额外校验岗位是否为 `REVIEWER``ADMIN`
## 5. 领域对象与状态归属
### 5.1 领域对象
一期核心领域对象如下:
1. `SourceResource`:原始资源元数据。
2. `AnnotationTask`:某一次抽取/校验任务快照。
3. `AnnotationTaskResource`:任务与资源的多对多绑定关系。
4. `AnnotationResult`:当前待审核或待自动归档的运行态结果。
5. `AnnotationResultHistory`:归档后的最终结果。
6. `SysConfig`模型、Prompt、系统参数配置。
### 5.2 状态归属评审结论
旧方案里 `source_data` 参考设计把资源、抽取、审核状态揉在一张表里,这不适合一期真实落地。重写后按以下原则拆分:
| 责任对象 | 负责状态 | 不负责状态 |
|---|---|---|
| `source_resource` | 文件是否上传完成、是否已归档 | 任务执行进度、审核结果 |
| `annotation_task` | AI 执行进度、失败原因、开始结束时间 | 资源存储状态、人工审核结论 |
| `annotation_task_resource` | 任务与资源的绑定关系 | 任务执行进度、审核结论 |
| `annotation_result` | 是否强制人工审核、是否软删除、审核人和审核时间 | 任务执行进度 |
| `annotation_result_history` | 最终归档结果、归档原因、归档人 | 运行态处理中的瞬时状态 |
### 5.3 一期状态定义
#### 5.3.1 资源状态 `source_status`
当前 SQL 中资源状态仍按以下值设计:
- `UPLOADED`:原始文件已上传,元数据已入库
- `PROCESSING`:预留给后续异步资源处理流程,一期暂不引入专门视频预处理作业
- `READY`:资源可用于创建任务
- `ARCHIVED`:资源已归档,不再允许创建新任务
不建议把 `EXTRACTING``QA_REVIEW``APPROVED``REJECTED` 放进资源表,因为这些属于任务或结果生命周期。
#### 5.3.2 任务状态 `task_status`
当前 SQL 中任务状态使用:
- `PENDING`
- `RUNNING`
- `COMPLETED`
- `FAILED`
#### 5.3.3 结果运行态
`annotation_result` 不再保存 `review_status`。运行态按下列规则推导:
| 运行态名称 | 计算规则 |
|---|---|
| `MANUAL_REVIEW_PENDING` | `requires_manual_review = true and is_deleted = false`,表示必须人工审核,禁止自动归档 |
| `AUTO_ARCHIVE_PENDING` | `requires_manual_review = false and is_deleted = false`,表示允许人工审核,也允许超时自动归档 |
| `ARCHIVED` | 对应记录已软删除,且已有 `annotation_result_history` 归档记录 |
## 6. 对象存储路径规范
### 6.1 评审结论
旧参考路径存在三个问题:
1. 直接把业务含义写死在一级目录,后续跨租户清理困难。
2. 部分路径使用原始文件名,不利于重名控制和脱敏。
3. 原始资源、衍生资源、审核外置文件、导出文件没有统一前缀策略。
### 6.2 一期统一规范
统一规则:
1. 路径全部使用小写。
2. 不直接使用用户原始文件名做对象 Key。
3. 优先使用业务主键和年月做路径分层。
4. 按“租户 / 资源域 / 类型 / 年月 / 业务 ID”组织。
### 6.3 Bucket 规划
| Bucket | 用途 |
2026-04-23 12:11:13 +08:00
|---|---|
2026-04-27 23:26:25 +08:00
| `source-data` | 原始文本、图片、视频及视频衍生物 |
| `annotation-artifacts` | 外置问答 JSON、diff JSON、人工审核快照 |
| `finetune-export` | 导出 JSONL 文件 |
### 6.4 路径格式
| 场景 | Bucket | 路径格式 |
|---|---|---|
| 文本原始文件 | `source-data` | `source/{companyId}/text/{yyyyMM}/{resourceId}/original.{ext}` |
| 图片原始文件 | `source-data` | `source/{companyId}/image/{yyyyMM}/{resourceId}/original.{ext}` |
| 视频原始文件 | `source-data` | `source/{companyId}/video/{yyyyMM}/{resourceId}/original.{ext}` |
| 外置问答 JSON | `annotation-artifacts` | `result/{companyId}/{yyyyMM}/{taskId}/{resultId}/qa.json` |
| 外置 diff JSON | `annotation-artifacts` | `result/{companyId}/{yyyyMM}/{taskId}/{resultId}/diff.json` |
| 导出 JSONL | `finetune-export` | `export/{companyId}/{yyyyMM}/{batchNo}/dataset.jsonl` |
2026-04-23 12:11:13 +08:00
2026-04-27 23:26:25 +08:00
### 6.5 命名规则
2026-04-23 12:11:13 +08:00
2026-04-27 23:26:25 +08:00
1. `resourceId``taskId``resultId` 使用数据库主键或雪花 ID。
2. `yyyyMM` 例如 `202604`
3. 文件扩展名按后端识别后的标准扩展名写入,不沿用用户大小写。
4. 资源删除时优先删对象,再更新数据库状态。
2026-04-23 12:11:13 +08:00
2026-04-27 23:26:25 +08:00
## 7. source 场景流程与状态流转/回退
2026-04-23 12:11:13 +08:00
2026-04-27 23:26:25 +08:00
### 7.1 文本/图片上传场景
2026-04-23 12:11:13 +08:00
2026-04-27 23:26:25 +08:00
流程:
2026-04-23 12:11:13 +08:00
2026-04-27 23:26:25 +08:00
1. 前端上传文件到后端。
2. 后端完成文件校验和对象存储上传。
3. 后端创建 `source_resource`,状态写入 `UPLOADED`
4. 当前一期不引入视频预处理作业,资源校验通过后直接置为 `READY`
2026-04-23 12:11:13 +08:00
2026-04-27 23:26:25 +08:00
状态流转:
2026-04-23 12:11:13 +08:00
2026-04-27 23:26:25 +08:00
`UPLOADED -> READY -> ARCHIVED`
2026-04-23 12:11:13 +08:00
2026-04-27 23:26:25 +08:00
回退规则:
2026-04-23 12:11:13 +08:00
2026-04-27 23:26:25 +08:00
- 如果对象存储上传失败:不写库。
- 如果写库失败:回滚并删除已上传对象。
- 如果资源已被任务引用,不允许直接物理删除,只允许逻辑归档到 `ARCHIVED`
2026-04-23 12:11:13 +08:00
2026-04-27 23:26:25 +08:00
### 7.2 视频资源当前处理策略
2026-04-23 12:11:13 +08:00
2026-04-27 23:26:25 +08:00
一期暂不建设独立的视频资源预处理作业,视频资源和文本/图片资源保持同样的主流程:
2026-04-23 12:11:13 +08:00
2026-04-27 23:26:25 +08:00
1. 视频上传成功后创建 `source_resource`,状态为 `UPLOADED`
2. 完成基础校验后直接改为 `READY`
3. 当前不做拆帧、转写、衍生文件生产。
2026-04-23 12:11:13 +08:00
2026-04-27 23:26:25 +08:00
状态流转:
2026-04-23 12:11:13 +08:00
2026-04-27 23:26:25 +08:00
`UPLOADED -> READY -> ARCHIVED`
2026-04-23 12:11:13 +08:00
2026-04-27 23:26:25 +08:00
### 7.3 任务驱动场景
2026-04-23 12:11:13 +08:00
2026-04-27 23:26:25 +08:00
资源与任务解耦,资源不会因为某个任务开始执行就改成任务态。即:
2026-04-23 12:11:13 +08:00
2026-04-27 23:26:25 +08:00
- 创建任务不会把资源状态从 `READY` 改成 `EXTRACTING`
- 任务重跑不会污染资源主状态
2026-04-23 12:11:13 +08:00
2026-04-27 23:26:25 +08:00
这是本次重写的核心结论之一。
2026-04-23 12:11:13 +08:00
2026-04-27 23:26:25 +08:00
## 8. 模块详细设计
2026-04-23 12:11:13 +08:00
2026-04-27 23:26:25 +08:00
## 8.1 资源管理模块
2026-04-23 12:11:13 +08:00
2026-04-27 23:26:25 +08:00
### 8.1.1 模块职责
2026-04-23 12:11:13 +08:00
2026-04-27 23:26:25 +08:00
1. 接收文本、图片、视频文件上传。
2. 保存资源元数据与对象存储定位信息。
3. 提供分页查询、详情查询、删除能力。
4. 为任务创建与结果审核提供统一的资源视图,不引入独立视频预处理作业。
2026-04-23 12:11:13 +08:00
2026-04-27 23:26:25 +08:00
### 8.1.2 接口列表
2026-04-23 12:11:13 +08:00
2026-04-27 23:26:25 +08:00
| 方法 | 路径 | 说明 |
|---|---|---|
| `POST` | `/api/source-resources/upload` | 上传资源 |
| `GET` | `/api/source-resources` | 分页查询资源 |
| `GET` | `/api/source-resources/{id}` | 查询资源详情 |
| `DELETE` | `/api/source-resources/{id}` | 删除资源 |
2026-04-23 12:11:13 +08:00
2026-04-27 23:26:25 +08:00
### 8.1.3 接口交互对象
2026-04-23 12:11:13 +08:00
2026-04-27 23:26:25 +08:00
以下对象均为接口层 DTO不直接复用数据库实体列表和详情统一返回 `SourceResourceResponse`,保持前端交互对象一致:
2026-04-23 12:11:13 +08:00
2026-04-27 23:26:25 +08:00
```java
public class SourceUploadRequest {
private String resourceName;
private String resourceType; // TEXT / IMAGE / VIDEO
private String remark;
}
2026-04-23 12:11:13 +08:00
2026-04-27 23:26:25 +08:00
public class SourceUploadResponse {
private Long id;
private String resourceName;
private String resourceType;
private String bucketName;
private String filePath;
private Long fileSize;
private String sourceStatus;
private LocalDateTime createdAt;
}
2026-04-23 12:11:13 +08:00
2026-04-27 23:26:25 +08:00
public class SourceResourcePageQuery {
private String keyword;
private String resourceType;
private String sourceStatus;
private Integer pageNo;
private Integer pageSize;
}
public class SourceResourceResponse {
private Long id;
private String resourceName;
private String resourceType;
private String bucketName;
private String filePath;
private Long fileSize;
private String sourceStatus;
private String storageProvider;
private String remark;
private String creatorName;
private LocalDateTime createdAt;
private LocalDateTime updatedAt;
}
```
### 8.1.4 关键规则
1. 只允许上传 `TEXT / IMAGE / VIDEO`
2. `source_status = READY` 的资源才能创建任务。
3. 已被任务引用的资源删除时,优先做逻辑归档,不直接删除数据库记录。
4. 删除资源时必须同步清理 RustFS 对象;如清理失败则整体删除失败。
## 8.2 任务管理模块
### 8.2.1 模块职责
1. 基于一个或多个资源创建抽取任务。
2. 保存模型配置 ID、Prompt 配置 ID并在创建时固化模型与 Prompt 快照。
3. 提供任务分页、详情、修改、软删除。
4. 后端只负责创建 `PENDING`;状态推进由 `ai-service` 执行。
### 8.2.2 接口列表
| 方法 | 路径 | 说明 |
|---|---|---|
| `POST` | `/api/annotation-tasks` | 创建任务 |
| `PUT` | `/api/annotation-tasks/{id}` | 修改任务 |
| `GET` | `/api/annotation-tasks` | 分页查询任务 |
| `GET` | `/api/annotation-tasks/{id}` | 查询任务详情 |
| `DELETE` | `/api/annotation-tasks/{id}` | 删除任务 |
### 8.2.3 接口交互对象
任务模块查询与界面交互不直接使用 `annotation_task` 数据库实体,而是通过聚合视图返回。创建和修改任务时,模型与 Prompt 均支持“选择已有配置”与“手动输入”两种方式:
```java
public class ManualModelConfigRequest {
private String modelName;
private String modelUrl;
private String apiKey;
}
public class TaskModelConfigRequest {
private String mode; // SELECT / MANUAL
private String selectedConfigName; // mode = SELECT 时必填
private ManualModelConfigRequest manualConfig; // mode = MANUAL 时必填
}
public class PromptConfigOptionRequest {
private String configName; // 下拉选择 sys_config 中已有 PROMPT 配置时传
private String promptContent; // 手动输入 Prompt 时传
}
public class CreateAnnotationTaskRequest {
private String taskName;
private String industryType; // 行业类型简写,默认 transport示例 electricity
private String taskType; // 默认 EXTRACT_QA
private List<Long> resourceIds;
private TaskModelConfigRequest extractModel;
private TaskModelConfigRequest verifyModel;
private PromptConfigOptionRequest extractPrompt;
private PromptConfigOptionRequest verifyPrompt;
}
public class UpdateAnnotationTaskRequest {
private String industryType; // 行业类型简写,默认 transport示例 electricity
private String taskType;
private List<Long> resourceIds;
private TaskModelConfigRequest extractModel;
private TaskModelConfigRequest verifyModel;
private PromptConfigOptionRequest extractPrompt;
private PromptConfigOptionRequest verifyPrompt;
}
public class AnnotationTaskPageQuery {
private String keyword;
private String taskType;
private String taskStatus;
private Long resourceId;
private Boolean isDeleted;
private Integer pageNo;
private Integer pageSize;
}
public class TaskModelConfigResponse {
private Long configId;
private String configName;
private String modelName;
private String modelUrl;
private String apiKeyMasked;
}
public class TaskPromptConfigResponse {
private Long configId;
private String configName;
private String promptContent;
private String promptPreview;
}
public class AnnotationTaskResponse {
private Long id;
private String taskName;
private String industryType;
private String taskType;
private List<Long> resourceIds;
private Integer resourceCount;
private String creatorName;
private TaskModelConfigResponse extractModel;
private TaskModelConfigResponse verifyModel;
private TaskPromptConfigResponse extractPrompt;
private TaskPromptConfigResponse verifyPrompt;
private String taskStatus;
private Boolean isDeleted;
private LocalDateTime startedAt;
private LocalDateTime finishedAt;
private String errorMessage;
private LocalDateTime createdAt;
private LocalDateTime updatedAt;
}
2026-04-23 12:11:13 +08:00
```
2026-04-27 23:26:25 +08:00
交互与落库规则:
2026-04-23 12:11:13 +08:00
2026-04-27 23:26:25 +08:00
前端界面与 `CreateAnnotationTaskRequest` 的模型交互流程明确如下:
2026-04-23 12:11:13 +08:00
2026-04-27 23:26:25 +08:00
1. `extractModel``verifyModel` 均采用统一对象结构 `TaskModelConfigRequest`,通过 `mode` 显式表达本次是“下拉选择已有配置”还是“手动输入模型配置”。
2.`mode = SELECT` 时,前端展示 `MODEL` 类型配置下拉框,并把用户选中的 `selectedConfigName` 传给后端;后端按 `company_id + config_name` 查询 `sys_config`,校验通过后写入 `extract_model_config_id``verify_model_config_id`,并同步固化模型快照字段。
3.`mode = MANUAL` 时,前端展示三个手输字段:`modelName``modelUrl``apiKey`,并通过 `manualConfig` 传给后端。
4. 对于手动输入的 `extractModel`,后端先按 `company_id + config_name = modelName` 查询 `sys_config``MODEL` 类型配置:
- 若存在:直接复用现有配置记录的 `id` 作为 `extract_model_config_id`
- 若不存在:自动在 `sys_config` 新增一条 `MODEL` 配置记录,`config_name = modelName``config_value` 保存 `modelName + modelUrl + apiKey` 组成的 JSON再把新配置 `id` 写入 `extract_model_config_id`
- 无论配置是复用还是新建,任务表都要固化本次请求的模型快照字段 `extract_model_name``extract_model_url``extract_model_api_key`
5. 对于手动输入的 `verifyModel`,后端执行与 `extractModel` 完全一致的处理逻辑,写入 `verify_model_config_id` 及对应快照字段;若 `modelName` 不存在,同样自动创建 `sys_config` 记录。
6. `extractPrompt``verifyPrompt` 继续支持二选一:要么按 `configName` 选择已有 `PROMPT` 配置,要么直接传 `promptContent` 作为任务快照。
7. 手动输入 Prompt 时,仅固化到任务快照字段 `extract_prompt` / `verify_prompt`,对应 `*_prompt_config_id` 允许为空。
8. 创建接口和修改接口遵循完全相同的配置解析规则。
9.`mode = SELECT` 时仍传入 `manualConfig`,或 `mode = MANUAL` 时仍传入 `selectedConfigName`,后端直接按参数非法处理,避免“选中配置”和“手工输入”语义冲突。
10.`mode = MANUAL``modelName``modelUrl``apiKey` 任一为空,后端直接按参数非法处理。
2026-04-23 12:11:13 +08:00
2026-04-27 23:26:25 +08:00
### 8.2.4 状态流转
标准流转:
`PENDING -> RUNNING -> COMPLETED`
失败流转:
`PENDING -> RUNNING -> FAILED`
重试流转:
`FAILED -> PENDING`
删除规则:
- `PENDING / FAILED / COMPLETED` 允许软删除
- `RUNNING` 不允许删除
- 删除接口统一写入 `is_deleted = true`,列表查询默认过滤软删除任务
### 8.2.5 任务修改规则
修改任务接口覆盖以下属性:
- `industryType`
- `taskType`
- `resourceIds`
- `extractModel / verifyModel`
- `extractPrompt / verifyPrompt`
字段级状态约束如下:
| 任务状态 | `taskName` | `industryType` / `taskType` / `extractModel` / `verifyModel` / `extractPrompt` / `verifyPrompt` | `resourceIds` |
|---|---|---|---|
| `PENDING` | 不可修改 | 可修改 | 可修改 |
| `FAILED` | 不可修改 | 可修改 | 可修改 |
| `RUNNING` | 不可修改 | 可修改 | 不可修改 |
| `COMPLETED` | 不可修改 | 可修改 | 可修改 |
设计结论:
1. 修改接口不只是绑定资源,还覆盖任务类型、模型配置和 Prompt 配置。
2. `taskName` 作为任务标识一经创建不可修改,修改接口不接收该字段。
3. 资源绑定采用 `1A` 规则:仅当当前任务状态为 `RUNNING` 时禁止修改 `resourceIds`,其他状态允许修改。
4. `RUNNING` 状态下,后端必须校验本次更新前后的 `resourceIds` 是否变化,变化则直接拒绝。
5. 修改 `resourceIds` 时,后端采用“删旧关联 + 重建新关联”的方式维护 `annotation_task_resource`
6. 更新配置类字段时,后端先按 `TaskModelConfigRequest.mode` 解析“选配”或“手输”路径;对于手动输入模型,按 `modelName` 自动创建 / 复用 `sys_config`,再同步刷新任务快照字段,保证任务表中的执行快照与配置引用一致。
## 8.3 审批标注结果模块
### 8.3.1 模块职责
1. 承接 AI 抽取与校验差异结果。
2. 查询运行态结果列表与详情。
3. 对所有未归档运行态结果提供人工审核比对和合并能力。
4.`requires_manual_review = true` 的结果强制走人工审核闭环。
5.`requires_manual_review = false` 的结果提供超时自动归档能力。
### 8.3.2 接口列表
| 方法 | 路径 | 说明 |
|---|---|---|
| `GET` | `/api/annotation-results` | 分页查询结果 |
| `GET` | `/api/annotation-results/{id}` | 查询结果详情 |
| `GET` | `/api/annotation-results/{id}/compare` | 查询人工审核比对视图,适用于全部未归档运行态结果 |
| `POST` | `/api/annotation-results/{id}/merge-review` | 提交人工审核结果并归档 |
### 8.3.3 接口交互对象
以下结果对象均为接口层 Response / View数据库字段 `is_deleted` 在接口层统一转换为 `isDeleted`。列表和详情统一返回 `AnnotationResultResponse`
```java
public class AnnotationResultPageQuery {
private Long taskId;
private Long resourceId;
private Boolean requiresManualReview;
private String runtimeStatus; // MANUAL_REVIEW_PENDING / AUTO_ARCHIVE_PENDING / ARCHIVED
private Integer pageNo;
private Integer pageSize;
}
public class AnnotationResultResponse {
private Long id;
private Long taskId;
private Long resourceId;
private String qaContentJson;
private String qaContentStorageMode;
private String qaContentFilePath;
private String diffSummary;
private Boolean requiresManualReview;
private Boolean isDeleted;
private Long reviewerId;
private String reviewComment;
private LocalDateTime reviewedAt;
private String runtimeStatus;
private LocalDateTime createdAt;
private LocalDateTime updatedAt;
}
public class AnnotationResultCompareResponse {
private Long id;
private Long taskId;
private Long resourceId;
private String currentQaContentJson;
private String qaContentStorageMode;
private String qaContentFilePath;
private String diffSummary;
private String originalResourcePreview;
private Boolean requiresManualReview;
}
public class MergeReviewResultRequest {
private String diffSummary;
private String qaContentJson;
private String reviewComment;
}
public class MergeReviewResultResponse {
private Long resultId;
private Long historyId;
private String archiveReason;
private LocalDateTime archivedAt;
}
```
### 8.3.4 合并审核规则
1. 只要 `annotation_result.is_deleted = false`,审核员都可以进入人工审核流程,不要求 `requires_manual_review` 必须为 `true`
2. `requires_manual_review = true` 仅表示“强制人工审核”,这类结果不允许走自动归档。
3. `compare` 接口统一返回当前运行态记录中的 `qaContentJson``diffSummary` 供界面编辑;当 `qaContentStorageMode = EXTERNAL` 时,额外返回 `qaContentFilePath`,用于界面展示当前外置文件来源。
4. `merge-review` 接口接收前端修订后的 `qaContentJson``diffSummary``reviewComment`,后端据此生成最终归档内容。
5.`annotation_result.qa_content_storage_mode = EXTERNAL` 时:
- 后端以当前外置文件名为基准,在本地临时目录生成同名临时文件
- 将用户修改后的最终内容写入该临时文件
- 再把该临时文件上传到 RustFS 的归档路径
- 新建 `annotation_result_history` 记录,写入 `qa_content_storage_mode = EXTERNAL`
- `annotation_result_history.qa_content_file_path` 保存重新上传后的 RustFS 路径
- `annotation_result_history.qa_content_json` 仅保留精简摘要或默认值 `{}`
6.`annotation_result.qa_content_storage_mode = INLINE` 时:
- 后端将用户最终确认的 `qaContentJson``diffSummary` 合并为一个归档 JSON
- 建议结构为 `{"qaContent": <qaContentJson>, "diffSummary": <diffSummary>}`
- 将该合并结果写入 `annotation_result_history.qa_content_json`
- `annotation_result_history.qa_content_storage_mode` 继续写入 `INLINE`
7. 人工审核归档成功后:
- 写入 `annotation_result_history`
- 回写运行态结果 `reviewer_id``review_comment``reviewed_at`
- 将运行态结果标记为 `is_deleted = true`,等价于从运行态结果集中删除
### 8.3.5 自动归档规则
仅对 `requires_manual_review = false and is_deleted = false` 的运行态结果开放自动归档:
1. 创建后进入 `AUTO_ARCHIVE_PENDING`
2. 即使处于 `AUTO_ARCHIVE_PENDING`,审核员仍可在超时前主动进入人工审核并抢占归档路径
3. 超过默认 2 小时且未被人工审核接管时,定时任务把记录迁移到 `annotation_result_history`
4. `requires_manual_review = true` 的记录一律跳过,不允许自动归档
5. 自动归档完成后将运行态结果 `is_deleted = true`
### 8.3.6 审核状态设计结论
一期不再单独保留 `review_status` 字段,原因如下:
1. 当前业务并不存在“审核通过后仍停留在运行表”的必要。
2. 一旦人工确认完成或满足自动归档条件,结果就应该进入历史归档表。
3. 运行态结果只关心“是否强制人工审核”和“是否已经归档”;是否允许人工审核由 `is_deleted = false` 统一决定。
## 8.4 系统配置模块
### 8.4.1 模块职责
1. 维护模型配置、Prompt 配置、系统参数。
2. 支持按类型分页查询和详情查看。
3. 支持新增和修改。
### 8.4.2 接口列表
| 方法 | 路径 | 说明 |
|---|---|---|
| `POST` | `/api/sys-configs` | 新增配置 |
| `PUT` | `/api/sys-configs/{id}` | 修改配置 |
| `GET` | `/api/sys-configs` | 分页查询配置 |
| `GET` | `/api/sys-configs/{id}` | 查询配置详情 |
### 8.4.3 接口交互对象
配置模块同样使用独立 DTO不直接向外暴露数据库实体列表和详情统一返回 `SysConfigResponse`
```java
public class SaveSysConfigRequest {
private String configType; // MODEL / PROMPT / SYSTEM
private String configName; // 同一 company 下全局唯一,不能与任意类型配置重名
private String configValue;
private String status;
}
public class SysConfigPageQuery {
private String configType;
private String configName;
private String status;
private Integer pageNo;
private Integer pageSize;
}
public class SysConfigResponse {
private Long id;
private String configType;
private String configName;
private String configValue;
private String status;
private String creatorName;
private LocalDateTime createdAt;
private LocalDateTime updatedAt;
}
```
### 8.4.4 配置值约束
| 类型 | `config_name` | `config_value` |
|---|---|---|
| `MODEL` | 模型配置名,直接使用模型名称,如 `qwen-max` | JSON至少包含 `modelName``modelUrl``apiKey` |
| `PROMPT` | Prompt 名称,如 `extractPrompt` | 纯文本 |
| `SYSTEM` | 系统参数名 | 文本或 JSON |
补充约束:
1. `sys_config` 在同一 `company_id` 下,`config_name` 不允许重复,不再区分 `config_type` 后再重复命名。
2. 任务创建/修改界面的模型和 Prompt 下拉框均以 `configName` 作为选择值。
3. 对于 `MODEL` 类型配置,`config_name` 同时承担任务手动录入模型时的自动建档主键语义。
4. 新增配置时,后端必须先按 `company_id + config_name` 做重复校验;若已存在同名配置,不论其 `config_type``MODEL``PROMPT` 还是 `SYSTEM`,都直接返回 `CONFIG_DUPLICATE`
5. 修改配置时,如 `configName` 发生变化,后端同样必须校验目标名称是否已被同公司下其他配置占用;若占用则拒绝修改。
6. 该约束适用于人工维护配置和任务创建时的模型自动建档两条路径,二者共用同一唯一性规则。
## 9. 通用返回结构
一期统一建议:
```java
public class ApiResponse<T> {
private String code;
private String message;
private T data;
}
public class PageResult<T> {
private List<T> records;
private Long total;
private Integer pageNo;
private Integer pageSize;
}
```
### 9.1 分页机制评审结论
一期分页机制明确采用 MyBatis-Plus 分页插件方案,不采用 `PageHelper`
结论如下:
1. 当前项目已经使用 MyBatis-Plus继续使用其分页能力更符合现有技术栈。
2. 不建议再引入 `PageHelper`,否则会形成两套分页机制并存,增加维护成本和排障复杂度。
3. 也不建议在每个列表查询里手写一套 `LIMIT/OFFSET + COUNT(*)` 作为默认方案,重复代码过多。
推荐实现方式:
1. 在 Spring 配置中启用 MyBatis-Plus `PaginationInnerInterceptor`
2. 列表查询统一接收 `pageNo``pageSize`
3. Mapper 自定义 SQL 场景使用 `Page<T>` + 查询对象参数的方式,由分页插件自动追加分页 SQL。
4. 对需要聚合的列表,例如任务列表里的 `resourceCount`,允许在 Mapper XML 中写聚合查询,但仍通过 MyBatis-Plus 分页插件处理分页。
分页设计约束:
- 默认分页方案MyBatis-Plus `PaginationInnerInterceptor`
- 明确不使用:`PageHelper`
- 明确不推荐:每个列表接口手写裸 SQL 分页
### 9.2 分页实现设计
#### 9.2.1 Spring 配置
一期建议新增统一分页配置类,示例:
```java
@Configuration
public class MybatisPlusConfig {
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
PaginationInnerInterceptor paginationInnerInterceptor =
new PaginationInnerInterceptor(DbType.POSTGRE_SQL);
paginationInnerInterceptor.setOverflow(false);
paginationInnerInterceptor.setMaxLimit(200L);
interceptor.addInnerInterceptor(paginationInnerInterceptor);
return interceptor;
}
}
```
约束说明:
1. `DbType` 使用 `POSTGRE_SQL`
2. `maxLimit` 建议限制为 `200`,防止大页拖垮查询。
3. `overflow(false)`,页码越界时返回空列表,不自动回退到第一页。
#### 9.2.2 通用分页请求约束
所有分页查询 DTO 统一约束如下:
```java
public class PageQuery {
@Min(1)
private Integer pageNo = 1;
@Min(1)
@Max(200)
private Integer pageSize = 20;
}
```
业务分页 DTO 在此基础上扩展过滤字段,例如:
```java
public class AnnotationTaskPageQuery extends PageQuery {
private String keyword;
private String taskType;
private String taskStatus;
private Long resourceId;
}
```
#### 9.2.3 Service 层调用约定
Service 层统一构造 MyBatis-Plus `Page<T>`
```java
public PageResult<AnnotationTaskResponse> pageTasks(AnnotationTaskPageQuery query) {
Page<AnnotationTaskResponse> page = new Page<>(query.getPageNo(), query.getPageSize());
IPage<AnnotationTaskResponse> result = annotationTaskMapper.selectTaskPage(page, query);
return PageResult.of(result);
}
```
建议补一个统一转换方法:
```java
public class PageResult<T> {
private List<T> records;
private Long total;
private Integer pageNo;
private Integer pageSize;
public static <T> PageResult<T> of(IPage<T> page) {
PageResult<T> result = new PageResult<>();
result.setRecords(page.getRecords());
result.setTotal(page.getTotal());
result.setPageNo((int) page.getCurrent());
result.setPageSize((int) page.getSize());
return result;
}
}
```
#### 9.2.4 Mapper 设计
分页查询建议采用“自定义 XML + `Page<T>` 首参”的方式。
Mapper 接口示例:
```java
public interface AnnotationTaskMapper extends BaseMapper<AnnotationTask> {
IPage<AnnotationTaskResponse> selectTaskPage(
Page<AnnotationTaskResponse> page,
@Param("query") AnnotationTaskPageQuery query
);
}
2026-04-23 12:11:13 +08:00
```
2026-04-27 23:26:25 +08:00
Mapper XML 设计原则:
1. `Page<T>` 放在第一个参数位置。
2. 过滤条件统一从 `query.xxx` 取值。
3. 列表查询只查当前页需要字段,不做 `select *`
4. 排序规则必须显式写出,避免分页结果不稳定。
#### 9.2.5 聚合分页场景
任务列表需要返回 `resourceCount`,属于典型聚合分页场景。推荐写法:
```xml
<select id="selectTaskPage" resultType="com.labelsys.backend.dto.response.AnnotationTaskResponse">
SELECT
t.id,
t.task_name AS taskName,
t.task_type AS taskType,
t.task_status AS taskStatus,
t.is_deleted AS isDeleted,
t.created_at AS createdAt,
COUNT(tr.id) AS resourceCount
FROM annotation_task t
LEFT JOIN annotation_task_resource tr ON tr.task_id = t.id
WHERE t.company_id = #{query.companyId}
AND t.is_deleted = COALESCE(#{query.isDeleted}, FALSE)
<if test="query.keyword != null and query.keyword != ''">
AND t.task_name ILIKE CONCAT('%', #{query.keyword}, '%')
</if>
<if test="query.taskType != null and query.taskType != ''">
AND t.task_type = #{query.taskType}
</if>
<if test="query.taskStatus != null and query.taskStatus != ''">
AND t.task_status = #{query.taskStatus}
</if>
<if test="query.resourceId != null">
AND EXISTS (
SELECT 1
FROM annotation_task_resource tr2
WHERE tr2.task_id = t.id
AND tr2.resource_id = #{query.resourceId}
)
</if>
GROUP BY t.id, t.task_name, t.task_type, t.task_status, t.is_deleted, t.created_at
ORDER BY t.created_at DESC, t.id DESC
</select>
```
设计结论:
1. 聚合字段允许在 XML 中显式计算。
2. 资源过滤推荐用 `EXISTS`,避免直接 join 过滤导致重复行干扰分页。
3. 排序建议固定为 `created_at DESC, id DESC`,保证翻页稳定。
#### 9.2.6 Count SQL 评审
2026-04-23 12:11:13 +08:00
2026-04-27 23:26:25 +08:00
对于简单列表MyBatis-Plus 可自动生成 count 查询。
2026-04-23 12:11:13 +08:00
2026-04-27 23:26:25 +08:00
对于复杂聚合分页,有两条约束:
2026-04-23 12:11:13 +08:00
2026-04-27 23:26:25 +08:00
1. 先使用分页插件默认 count 机制。
2. 如果后续发现 count SQL 在复杂 `GROUP BY` 或多层子查询下性能差,再针对具体接口单独拆 `count` 查询,不要在一期一开始就全量手写。
2026-04-23 12:11:13 +08:00
2026-04-27 23:26:25 +08:00
这也是主流团队的常见做法,先统一插件方案,遇到瓶颈再对个别热点查询做定向优化。
2026-04-23 12:11:13 +08:00
2026-04-27 23:26:25 +08:00
#### 9.2.7 落地范围
一期以下分页接口统一采用同一机制:
1. `/api/source-resources`
2. `/api/annotation-tasks`
3. `/api/annotation-results`
4. `/api/sys-configs`
## 10. 数据库设计
## 10.1 设计说明
1. 数据库以 `D:\workspace\labeling\lb_backend\src\main\resources\sql` 目录下的 SQL 文件为唯一基准来源。
2. `annotation_task` 当前真实建表同时保存配置引用字段和执行快照字段:前者用于界面回显与配置追溯,后者用于执行时解耦配置变更影响。
3. `annotation_task``annotation_result` 均采用软删除字段 `is_deleted` 表达逻辑删除或归档后的不可见状态。
4. 一期 ID 统一使用 `BIGINT`,由应用层生成,不使用自增。
## 10.2 表关系
```text
sys_company 1 --- n sys_user
sys_company 1 --- n sys_menu
sys_company 1 --- n sys_config
sys_company 1 --- n source_resource
sys_company 1 --- n annotation_task
sys_company 1 --- n annotation_result
sys_company 1 --- n annotation_result_history
sys_company 1 --- n training_dataset
sys_company 1 --- n export_batch
sys_user 1 --- n sys_config(creator_id)
sys_user 1 --- n source_resource(creator_id)
sys_user 1 --- n annotation_task(creator_id)
sys_user 1 --- n annotation_result(creator_id / reviewer_id)
sys_user 1 --- n annotation_result_history(creator_id / archived_by)
sys_user 1 --- n training_dataset(creator_id)
sys_user 1 --- n export_batch(creator_id)
sys_config 1 --- n annotation_task(extract_model_config_id)
sys_config 1 --- n annotation_task(verify_model_config_id)
sys_config 1 --- n annotation_task(extract_prompt_config_id)
sys_config 1 --- n annotation_task(verify_prompt_config_id)
annotation_task 1 --- n annotation_task_resource
source_resource 1 --- n annotation_task_resource
annotation_task 1 --- n annotation_result
source_resource 1 --- n annotation_result
annotation_result 1 --- n annotation_result_history
annotation_result_history 1 --- n training_dataset
export_batch 1 --- n export_batch_item
training_dataset 1 --- n export_batch_item
2026-04-23 12:11:13 +08:00
```
2026-04-27 23:26:25 +08:00
## 10.3 `sys_company`
| 字段 | 类型 | 可空 | 默认值 | 索引/约束 | 说明 |
|---|---|---|---|---|---|
| `id` | `BIGINT` | 否 | 无 | PK | 公司主键 |
| `company_code` | `VARCHAR(64)` | 否 | 无 | UNIQUE | 公司编码 |
| `company_name` | `VARCHAR(128)` | 否 | 无 | 无 | 公司名称 |
| `status` | `VARCHAR(32)` | 否 | `ENABLED` | 无 | 公司状态 |
| `created_at` | `TIMESTAMP` | 是 | `CURRENT_TIMESTAMP` | 无 | 创建时间 |
| `updated_at` | `TIMESTAMP` | 是 | `CURRENT_TIMESTAMP` | 无 | 更新时间 |
## 10.4 `sys_user`
| 字段 | 类型 | 可空 | 默认值 | 索引/约束 | 说明 |
|---|---|---|---|---|---|
| `id` | `BIGINT` | 否 | 无 | PK | 用户主键 |
| `company_id` | `BIGINT` | 否 | 无 | FK索引列 | 公司 ID |
| `phone` | `VARCHAR(32)` | 否 | 无 | 同公司唯一约束 | 手机号 |
| `username` | `VARCHAR(64)` | 是 | 无 | 无 | 用户名或展示别名 |
| `role` | `VARCHAR(32)` | 否 | `EMPLOYEE` | 组合索引列 | 数据权限角色 |
| `position` | `VARCHAR(32)` | 否 | `ANNOTATOR` | 组合索引列 | 岗位 |
| `real_name` | `VARCHAR(64)` | 否 | 无 | 无 | 真实姓名 |
| `password_hash` | `VARCHAR(255)` | 否 | 无 | 无 | 密码哈希 |
| `must_change_password` | `BOOLEAN` | 否 | `TRUE` | 无 | 是否首次登录强制改密 |
| `status` | `VARCHAR(32)` | 否 | `ENABLED` | 无 | 用户状态 |
| `session_version` | `INTEGER` | 否 | `1` | 无 | 会话版本号 |
| `created_at` | `TIMESTAMP` | 是 | `CURRENT_TIMESTAMP` | 无 | 创建时间 |
| `updated_at` | `TIMESTAMP` | 是 | `CURRENT_TIMESTAMP` | 无 | 更新时间 |
实际索引/约束:
- `uq_sys_user_company_phone(company_id, phone)`
- `idx_sys_user_company(company_id)`
- `idx_sys_user_role(company_id, role)`
- `idx_sys_user_position(company_id, position)`
## 10.5 `sys_menu`
| 字段 | 类型 | 可空 | 默认值 | 索引/约束 | 说明 |
|---|---|---|---|---|---|
| `id` | `BIGINT` | 否 | 无 | PK | 菜单主键 |
| `company_id` | `BIGINT` | 否 | 无 | FK组合索引前缀列 | 公司 ID |
| `menu_code` | `VARCHAR(64)` | 否 | 无 | 无 | 菜单编码 |
| `menu_name` | `VARCHAR(128)` | 否 | 无 | 无 | 菜单名称 |
| `path` | `VARCHAR(255)` | 否 | 无 | 无 | 路由路径 |
| `visible_positions` | `VARCHAR(255)` | 否 | `ADMIN` | 无 | 可见岗位列表 |
| `sort_order` | `INTEGER` | 否 | `0` | 组合索引列 | 排序号 |
| `created_at` | `TIMESTAMP` | 是 | `CURRENT_TIMESTAMP` | 无 | 创建时间 |
| `updated_at` | `TIMESTAMP` | 是 | `CURRENT_TIMESTAMP` | 无 | 更新时间 |
实际索引/约束:
- `idx_sys_menu_company_sort(company_id, sort_order)`
## 10.6 `sys_config`
| 字段 | 类型 | 可空 | 默认值 | 索引/约束 | 说明 |
|---|---|---|---|---|---|
| `id` | `BIGINT` | 否 | 无 | PK | 主键,非自增 |
| `company_id` | `BIGINT` | 否 | 无 | FK索引前缀列 | 公司 ID |
| `config_type` | `VARCHAR(32)` | 否 | `SYSTEM` | 组合索引列 | 配置类型 |
| `config_name` | `VARCHAR(128)` | 否 | 无 | 同公司唯一索引,不允许同名 | 配置名称,也是前端下拉选择值 |
| `config_value` | `TEXT` | 否 | 无 | 无 | 配置值;`MODEL` 类型保存 `modelName/modelUrl/apiKey` JSON |
| `status` | `VARCHAR(32)` | 否 | `ENABLED` | 无 | 状态 |
| `creator_id` | `BIGINT` | 否 | 无 | FK | 创建人 |
| `created_at` | `TIMESTAMP` | 是 | `CURRENT_TIMESTAMP` | 无 | 创建时间 |
| `updated_at` | `TIMESTAMP` | 是 | `CURRENT_TIMESTAMP` | 无 | 更新时间 |
实际索引/约束:
- `uq_sys_config_company_name(company_id, config_name)`
- `idx_sys_config_company_type(company_id, config_type)`
约束说明:
1. `uq_sys_config_company_name(company_id, config_name)` 是一期强约束,表示同一公司下 `config_name` 全局唯一。
2. 该唯一性不区分 `config_type`,例如同一公司下不能同时存在名为 `qwen-max``MODEL` 配置和名为 `qwen-max``SYSTEM` 配置。
3. 任务创建时若用户手动输入模型名称,自动写入 `sys_config` 前也必须先经过这条唯一性校验。
## 10.7 `source_resource`
| 字段 | 类型 | 可空 | 默认值 | 索引/约束 | 说明 |
|---|---|---|---|---|---|
| `id` | `BIGINT` | 否 | 无 | PK | 资源主键 |
| `company_id` | `BIGINT` | 否 | 无 | FK索引前缀列 | 公司 ID |
| `creator_id` | `BIGINT` | 否 | 无 | FK组合索引列 | 上传人 |
| `creator_role` | `VARCHAR(32)` | 否 | `EMPLOYEE` | 无 | 创建时角色快照 |
| `resource_name` | `VARCHAR(255)` | 否 | 无 | 无 | 展示名称 |
| `resource_type` | `VARCHAR(32)` | 否 | `TEXT` | 组合索引列 | `TEXT / IMAGE / VIDEO` |
| `bucket_name` | `VARCHAR(128)` | 否 | 无 | 无 | 对象存储桶 |
| `file_path` | `VARCHAR(512)` | 否 | 无 | 无 | 对象路径 |
| `file_size` | `BIGINT` | 否 | `0` | 无 | 文件大小 |
| `source_status` | `VARCHAR(32)` | 否 | `UPLOADED` | 组合索引列 | 资源状态 |
| `storage_provider` | `VARCHAR(64)` | 否 | `rustfs` | 无 | 存储提供方 |
| `remark` | `VARCHAR(255)` | 是 | 无 | 无 | 备注 |
| `created_at` | `TIMESTAMP` | 是 | `CURRENT_TIMESTAMP` | 无 | 创建时间 |
| `updated_at` | `TIMESTAMP` | 是 | `CURRENT_TIMESTAMP` | 无 | 更新时间 |
实际索引/约束:
- `idx_source_resource_company_type(company_id, resource_type)`
- `idx_source_resource_company_status(company_id, source_status)`
- `idx_source_resource_creator(company_id, creator_id)`
## 10.8 `annotation_task`
| 字段 | 类型 | 可空 | 默认值 | 索引/约束 | 说明 |
|---|---|---|---|---|---|
| `id` | `BIGINT` | 否 | 无 | PK | 任务主键 |
| `company_id` | `BIGINT` | 否 | 无 | FK索引前缀列 | 公司 ID |
| `creator_id` | `BIGINT` | 否 | 无 | FK组合索引列 | 创建人 |
| `creator_role` | `VARCHAR(32)` | 否 | `EMPLOYEE` | 无 | 创建时角色快照 |
| `task_name` | `VARCHAR(255)` | 否 | 无 | 无 | 任务名称 |
| `industry_type` | `VARCHAR(32)` | 否 | `transport` | 无 | 行业类型简写,默认 transport示例 electricity |
| `task_type` | `VARCHAR(32)` | 否 | `EXTRACT_QA` | 无 | 任务类型 |
| `extract_model_config_id` | `BIGINT` | 是 | 无 | FK | 抽取模型配置 ID关联 `sys_config.id` |
| `extract_model_name` | `VARCHAR(128)` | 是 | 无 | 无 | 抽取模型名 |
| `extract_model_url` | `VARCHAR(255)` | 是 | 无 | 无 | 抽取模型地址 |
| `extract_model_api_key` | `VARCHAR(255)` | 是 | 无 | 无 | 抽取模型 Key |
| `verify_model_config_id` | `BIGINT` | 是 | 无 | FK | 校验模型配置 ID关联 `sys_config.id` |
| `verify_model_name` | `VARCHAR(128)` | 是 | 无 | 无 | 校验模型名 |
| `verify_model_url` | `VARCHAR(255)` | 是 | 无 | 无 | 校验模型地址 |
| `verify_model_api_key` | `VARCHAR(255)` | 是 | 无 | 无 | 校验模型 Key |
| `extract_prompt_config_id` | `BIGINT` | 是 | 无 | FK | 抽取 Prompt 配置 ID关联 `sys_config.id` |
| `extract_prompt` | `TEXT` | 是 | 无 | 无 | 抽取 Prompt 快照 |
| `verify_prompt_config_id` | `BIGINT` | 是 | 无 | FK | 校验 Prompt 配置 ID关联 `sys_config.id` |
| `verify_prompt` | `TEXT` | 是 | 无 | 无 | 校验 Prompt 快照 |
| `task_status` | `VARCHAR(32)` | 否 | `PENDING` | 组合索引列 | 任务状态 |
| `is_deleted` | `BOOLEAN` | 否 | `FALSE` | 组合索引列 | 任务软删除标记 |
| `started_at` | `TIMESTAMP` | 是 | 无 | 无 | 开始时间 |
| `finished_at` | `TIMESTAMP` | 是 | 无 | 无 | 结束时间 |
| `error_message` | `TEXT` | 是 | 无 | 无 | 错误信息 |
| `created_at` | `TIMESTAMP` | 是 | `CURRENT_TIMESTAMP` | 无 | 创建时间 |
| `updated_at` | `TIMESTAMP` | 是 | `CURRENT_TIMESTAMP` | 无 | 更新时间 |
实际索引/约束:
- `idx_annotation_task_company_status(company_id, task_status)`
- `idx_annotation_task_company_deleted(company_id, is_deleted)`
- `idx_annotation_task_creator(company_id, creator_id)`
设计说明:
1. `annotation_task` 主表不再直接保存 `resource_id`
2. 一个任务可绑定多个资源,具体绑定关系落在 `annotation_task_resource`
3. `task_type` 默认值为 `EXTRACT_QA`
4. 配置 ID 字段用于关联按 `configName` 选中的模型/Prompt 配置名称、URL、Key、Prompt 文本字段用于固化执行快照。
5. `industry_type` 用于保存任务所属行业类型简写,默认值为 `transport`,可选值按业务枚举扩展,例如 `electricity`
6. `extractModel``verifyModel` 在接口层不再复用单层平铺字段,而是统一使用 `TaskModelConfigRequest(mode + selectedConfigName + manualConfig)` 显式表达页面交互模式。
7. 手动输入模型配置时,后端先按模型名称匹配 `sys_config.config_name`;不存在则自动创建 `MODEL` 配置后再写入任务;存在则复用已有配置 ID。
8. 手动输入 Prompt 时,仅保存快照文本,不强制自动创建 `PROMPT` 配置。
9. `task_name` 只允许创建时写入,后续更新接口不可修改。
10. 删除接口不物理删除任务,统一更新 `is_deleted = true`
## 10.9 `annotation_task_resource`
| 字段 | 类型 | 可空 | 默认值 | 索引/约束 | 说明 |
|---|---|---|---|---|---|
| `id` | `BIGINT` | 否 | 无 | PK | 任务资源关联主键 |
| `company_id` | `BIGINT` | 否 | 无 | FK组合索引前缀列 | 公司 ID |
| `task_id` | `BIGINT` | 否 | 无 | FK唯一约束组成列组合索引列 | 任务 ID |
| `resource_id` | `BIGINT` | 否 | 无 | FK唯一约束组成列组合索引列 | 资源 ID |
| `created_at` | `TIMESTAMP` | 是 | `CURRENT_TIMESTAMP` | 无 | 创建时间 |
实际索引/约束:
- `uq_annotation_task_resource(task_id, resource_id)`
- `idx_annotation_task_resource_company_task(company_id, task_id)`
- `idx_annotation_task_resource_company_resource(company_id, resource_id)`
## 10.10 `annotation_result`
| 字段 | 类型 | 可空 | 默认值 | 索引/约束 | 说明 |
|---|---|---|---|---|---|
| `id` | `BIGINT` | 否 | 无 | PK | 运行态结果主键 |
| `company_id` | `BIGINT` | 否 | 无 | FK索引前缀列 | 公司 ID |
| `creator_id` | `BIGINT` | 否 | 无 | FK | 结果创建人 |
| `creator_role` | `VARCHAR(32)` | 否 | `EMPLOYEE` | 无 | 创建时角色快照 |
| `task_id` | `BIGINT` | 否 | 无 | FK组合索引列 | 任务 ID |
| `resource_id` | `BIGINT` | 否 | 无 | FK | 资源 ID |
| `qa_content_json` | `TEXT` | 否 | `'{}'` | 无 | 问答 JSON |
| `qa_content_storage_mode` | `VARCHAR(32)` | 否 | `INLINE` | 无 | `INLINE / EXTERNAL` |
| `qa_content_file_path` | `VARCHAR(512)` | 是 | 无 | 无 | 外置问答文件路径 |
| `diff_summary` | `TEXT` | 否 | `'{}'` | 无 | 差异摘要 JSON |
| `requires_manual_review` | `BOOLEAN` | 否 | `FALSE` | 组合索引列 | 是否强制人工审核 |
| `is_deleted` | `BOOLEAN` | 否 | `FALSE` | 组合索引列 | 软删除标记 |
| `reviewer_id` | `BIGINT` | 是 | 无 | FK | 审核人 |
| `review_comment` | `TEXT` | 是 | 无 | 无 | 审核备注 |
| `reviewed_at` | `TIMESTAMP` | 是 | 无 | 无 | 审核时间 |
| `created_at` | `TIMESTAMP` | 是 | `CURRENT_TIMESTAMP` | 无 | 创建时间 |
| `updated_at` | `TIMESTAMP` | 是 | `CURRENT_TIMESTAMP` | 无 | 更新时间 |
实际索引/约束:
- `idx_annotation_result_company_deleted(company_id, is_deleted)`
- `idx_annotation_result_company_manual(company_id, requires_manual_review)`
- `idx_annotation_result_task(company_id, task_id)`
设计说明:
1. 去掉 `result_type`,结果类型由任务和资源类型推导。
2. 去掉 `review_status`,审核完成后直接归档。
3. 新增 `is_deleted` 作为软删除标记,配合历史表形成完整生命周期。
## 10.11 `annotation_result_history`
| 字段 | 类型 | 可空 | 默认值 | 索引/约束 | 说明 |
|---|---|---|---|---|---|
| `id` | `BIGINT` | 否 | 无 | PK | 历史记录主键 |
| `company_id` | `BIGINT` | 否 | 无 | FK普通索引 | 公司 ID |
| `creator_id` | `BIGINT` | 否 | 无 | FK | 原结果创建人 |
| `creator_role` | `VARCHAR(32)` | 否 | `EMPLOYEE` | 无 | 原创建角色 |
| `source_result_id` | `BIGINT` | 是 | 无 | FK | 来源运行态结果 ID |
| `task_id` | `BIGINT` | 否 | 无 | FK组合索引列 | 任务 ID |
| `resource_id` | `BIGINT` | 否 | 无 | FK组合索引列 | 资源 ID |
| `qa_content_json` | `TEXT` | 否 | `'{}'` | 无 | 归档后的问答 JSON |
| `qa_content_storage_mode` | `VARCHAR(32)` | 否 | `INLINE` | 无 | 归档后的问答存储模式 |
| `qa_content_file_path` | `VARCHAR(512)` | 是 | 无 | 无 | 归档后的外置问答文件路径 |
| `archive_reason` | `VARCHAR(255)` | 是 | 无 | 无 | 归档原因 |
| `archived_by` | `BIGINT` | 是 | 无 | FK | 归档操作人 |
| `archived_at` | `TIMESTAMP` | 是 | 无 | 无 | 归档时间 |
| `created_at` | `TIMESTAMP` | 是 | `CURRENT_TIMESTAMP` | 无 | 创建时间 |
实际索引/约束:
- `idx_annotation_result_history_company(company_id)`
- `idx_annotation_result_history_task(company_id, task_id)`
- `idx_annotation_result_history_resource(company_id, resource_id)`
## 10.12 `training_dataset`
| 字段 | 类型 | 可空 | 默认值 | 索引/约束 | 说明 |
|---|---|---|---|---|---|
| `id` | `BIGINT` | 否 | 无 | PK | 训练样本主键 |
| `company_id` | `BIGINT` | 否 | 无 | FK | 公司 ID |
| `creator_id` | `BIGINT` | 否 | 无 | FK | 创建人 |
| `creator_role` | `VARCHAR(32)` | 否 | `EMPLOYEE` | 无 | 创建人数据权限角色 |
| `result_history_id` | `BIGINT` | 否 | 无 | FK | 来源历史结果 ID |
| `sample_type` | `VARCHAR(32)` | 否 | `TEXT` | 无 | 样本类型 |
| `glm_format_json` | `TEXT` | 否 | 无 | 无 | GLM 微调格式 JSON |
| `dataset_status` | `VARCHAR(32)` | 否 | `DRAFT` | 组合索引列 | 样本状态 |
| `created_at` | `TIMESTAMP` | 是 | `CURRENT_TIMESTAMP` | 无 | 创建时间 |
| `updated_at` | `TIMESTAMP` | 是 | `CURRENT_TIMESTAMP` | 无 | 更新时间 |
实际索引/约束:
- `idx_training_dataset_company_status(company_id, dataset_status)`
## 10.13 `export_batch`
| 字段 | 类型 | 可空 | 默认值 | 索引/约束 | 说明 |
|---|---|---|---|---|---|
| `id` | `BIGINT` | 否 | 无 | PK | 导出批次主键 |
| `company_id` | `BIGINT` | 否 | 无 | FK | 公司 ID |
| `creator_id` | `BIGINT` | 否 | 无 | FK | 创建人 |
| `creator_role` | `VARCHAR(32)` | 否 | `EMPLOYEE` | 无 | 创建人数据权限角色 |
| `batch_no` | `VARCHAR(64)` | 否 | 无 | UNIQUE | 批次编号 |
| `dataset_file_path` | `VARCHAR(512)` | 是 | 无 | 无 | 导出文件路径 |
| `sample_count` | `INTEGER` | 否 | `0` | 无 | 样本数 |
| `finetune_job_id` | `VARCHAR(128)` | 是 | 无 | 无 | 微调任务 ID |
| `finetune_status` | `VARCHAR(32)` | 否 | `NOT_STARTED` | 组合索引列 | 微调状态 |
| `created_at` | `TIMESTAMP` | 是 | `CURRENT_TIMESTAMP` | 无 | 创建时间 |
| `updated_at` | `TIMESTAMP` | 是 | `CURRENT_TIMESTAMP` | 无 | 更新时间 |
实际索引/约束:
- `idx_export_batch_company_status(company_id, finetune_status)`
## 10.14 `export_batch_item`
| 字段 | 类型 | 可空 | 默认值 | 索引/约束 | 说明 |
|---|---|---|---|---|---|
| `id` | `BIGINT` | 否 | 无 | PK | 批次样本关系主键 |
| `batch_id` | `BIGINT` | 否 | 无 | FK唯一约束组成列 | 导出批次 ID |
| `dataset_id` | `BIGINT` | 否 | 无 | FK唯一约束组成列 | 训练样本 ID |
| `created_at` | `TIMESTAMP` | 是 | `CURRENT_TIMESTAMP` | 无 | 创建时间 |
实际索引/约束:
- `uq_export_batch_item(batch_id, dataset_id)`
## 11. 一期关键流程
### 11.1 资源上传到任务创建
1. 用户上传资源。
2. 后端将文件写入 RustFS并生成 `source_resource`
3. 当前无论文本、图片还是视频,校验通过后均直接置为 `READY`
4. 用户选择一个或多个资源、模型、Prompt 创建任务。
5. 后端写入 `annotation_task``annotation_task_resource`,任务状态固定为 `PENDING`
```mermaid
flowchart TD
A[用户上传资源] --> B[backend 写入 RustFS]
B --> C[创建 source_resource]
C --> D[资源校验通过]
D --> E[source_status = READY]
E --> F[用户选择资源/模型/Prompt]
F --> G[创建 annotation_task]
G --> H[创建 annotation_task_resource]
H --> I[task_status = PENDING]
```
2026-04-23 12:11:13 +08:00
2026-04-27 23:26:25 +08:00
### 11.2 AI 处理到结果入库
1. `ai-service` 轮询 `PENDING` 任务。
2. 拉取资源并处理,任务改为 `RUNNING`
3. 生成抽取结果和校验结果。
4. 对比差异后写入 `annotation_result`,同时落库 `qa_content_json``diff_summary``qa_content_storage_mode` 等运行态字段。
5. 若结果必须由人工确认后才能进入最终归档,则标记 `requires_manual_review = true`
6. 若结果允许系统在超时后直接归档,则标记 `requires_manual_review = false`
7. 两种结果在 `is_deleted = false` 期间都允许审核员主动发起人工审核;差异仅在于是否允许后续自动归档。
```mermaid
flowchart TD
A[ai-service 轮询 PENDING 任务] --> B[任务置为 RUNNING]
B --> C[抽取与校验]
C --> D[生成 qa_content_json 和 diff_summary]
D --> E{是否必须人工审核?}
E -- 是 --> F[写入 annotation_result<br/>requires_manual_review = true]
E -- 否 --> G[写入 annotation_result<br/>requires_manual_review = false]
F --> H[等待人工审核归档]
G --> I[等待人工审核或自动归档]
```
2026-04-23 12:11:13 +08:00
2026-04-27 23:26:25 +08:00
### 11.3 人工审核归档
1. 审核员打开结果比对页。
2. 查询 `compare` 接口获取资源预览、当前 `qaContentJson``diffSummary``qaContentStorageMode``qaContentFilePath`
3. 无论 `requires_manual_review = true` 还是 `false`,只要记录尚未归档,审核员都可以修改 `qaContentJson` 和必要的 `diffSummary`
4. 调用 `merge-review` 接口提交最终内容。
5. 若当前记录 `qa_content_storage_mode = EXTERNAL`
- 后端在本地生成与当前外置文件同名的临时文件
- 将用户最终内容写入该临时文件
- 上传到 RustFS 归档路径
- 新建 `annotation_result_history`,写入 `qa_content_storage_mode = EXTERNAL` 和新的 `qa_content_file_path`
6. 若当前记录 `qa_content_storage_mode = INLINE`
- 后端将 `qaContentJson``diffSummary` 合并
- 将合并结果写入 `annotation_result_history.qa_content_json`
7. 归档成功后,运行态 `annotation_result` 回写审核人信息并标记 `is_deleted = true`,从运行态结果集中移除。
```mermaid
flowchart TD
A[审核员打开 compare 页面] --> B[读取 qaContentJson / diffSummary / storageMode]
B --> C[审核员修改最终内容]
C --> D[调用 merge-review]
D --> E{qa_content_storage_mode}
E -- EXTERNAL --> F[本地生成同名临时文件]
F --> G[写入最终内容]
G --> H[上传 RustFS 归档路径]
H --> I[写 annotation_result_history<br/>storage_mode = EXTERNAL]
E -- INLINE --> J[合并 qaContentJson + diffSummary]
J --> K[写 annotation_result_history<br/>storage_mode = INLINE]
I --> L[回写 reviewer 信息]
K --> L
L --> M[annotation_result.is_deleted = true]
```
2026-04-23 12:11:13 +08:00
2026-04-27 23:26:25 +08:00
### 11.4 自动归档
1. 定时任务仅扫描 `requires_manual_review = false and is_deleted = false` 的运行态结果。
2. 若结果已被审核员人工处理接管,则本次不再进入自动归档。
3.`created_at + 2h < now()`,则执行自动归档。
4. 自动归档完成后写入 `annotation_result_history`
5. 回写运行态结果 `is_deleted = true`
6. `requires_manual_review = true` 的记录不参与自动归档扫描。
```mermaid
flowchart TD
A[AutoArchiveAnnotationResultJob 扫描运行态结果] --> B{requires_manual_review = false?}
B -- 否 --> C[跳过]
B -- 是 --> D{is_deleted = false?}
D -- 否 --> C
D -- 是 --> E{是否已被人工审核接管?}
E -- 是 --> C
E -- 否 --> F{是否超过2小时?}
F -- 否 --> C
F -- 是 --> G[写 annotation_result_history]
G --> H[annotation_result.is_deleted = true]
2026-04-23 12:11:13 +08:00
```
2026-04-27 23:26:25 +08:00
## 12. 典型任务与定时任务
一期当前仅保留一个后台任务:
1. `AutoArchiveAnnotationResultJob`
- 仅扫描 `requires_manual_review = false` 且超时的运行态结果
- 跳过已被人工审核接管或已归档的记录
- 迁移到历史表
- 回写 `is_deleted = true`
## 13. 错误处理与审计
### 13.1 错误码建议
| 错误码 | 场景 |
|---|---|
| `RESOURCE_NOT_READY` | 资源未就绪不可创建任务 |
| `TASK_STATUS_INVALID` | 当前任务状态不允许删除或重试 |
| `RESULT_NOT_REVIEWABLE` | 结果已归档、已从运行态移除,或当前记录不允许继续进入审核提交 |
| `CONFIG_DUPLICATE` | 同公司下 `configName` 重复 |
| `OBJECT_STORAGE_ERROR` | RustFS 上传/删除失败 |
### 13.2 审计建议
以下操作必须记录操作日志:
1. 资源删除
2. 任务创建与删除
3. 人工审核合并
4. 系统配置新增与修改
## 14. 一期验收标准
1. 资源、任务、结果、配置四个模块均有清晰 REST 接口。
2. 请求 DTO、响应 DTO、分页结构与数据库实体解耦可直接映射 Controller 层代码。
3. 数据表字段类型、默认值、索引、约束与业务语义一致。
4. 对象存储路径能覆盖原始资源、外置问答结果和导出文件。
5. `annotation_result` 生命周期遵循“运行态在当前表、最终态进历史表”的原则。
6. source 相关状态只描述资源本身,不再混入任务和审核状态。
## 15. 与当前代码基线的差异说明
为满足最新需求,本文档相对旧版设计的关键调整如下:
1. `annotation_result` 去掉 `result_type`
2. `annotation_result` 去掉 `review_status`
3. `annotation_result` 增加 `is_deleted`
4. `annotation_task` 改为任务主表 + `annotation_task_resource` 关联表,一个任务可绑定多个资源。
5. `annotation_task.task_type` 默认值调整为 `EXTRACT_QA`
6. `annotation_task` 新增 `extract_model_config_id``verify_model_config_id``extract_prompt_config_id``verify_prompt_config_id`
7. `annotation_task` 新增软删除字段 `is_deleted`,删除接口采用逻辑删除。
8. 任务修改接口明确不允许修改 `taskName`,且 `RUNNING` 状态不可修改绑定资源。
9. 任务、资源、结果、配置均通过独立接口 DTO/VO 对外提供,不直接暴露数据库实体。
10. 任务状态使用 `PENDING / RUNNING / COMPLETED / FAILED`
11. `source_resource` 只保留资源状态,不承接抽取和审核流转。
12. 当前不引入独立的视频预处理作业设计。
13. 对象存储规范重新分桶、重新分层。
14. 创建和修改任务的请求对象改为支持按 `configName` 选择已有配置,或手动输入模型 / Prompt模型名不存在时自动创建 `sys_config`
15. `AnnotationTaskResponse``SourceResourceResponse``SysConfigResponse``AnnotationResultResponse` 分别统一列表和详情返回结构。
16. `sys_config` 改为同公司下 `config_name` 唯一,不再允许不同类型复用同名配置。
17. `annotation_task` 新增 `industry_type` 字段,默认值 `transport`,用于保存行业类型简写;`source_resource` 去掉 `content_type` 字段。
## 15. 开发规范
### 15.1 包命名规范
- 基础包名: `com.{service-name}`
- 服务名使用小写字母和连字符,如: `user-service`, `order-service`
### 15.2 类命名规范
- 实体类: 使用名词,如 `User`, `Order`
- 控制器: 以 `Controller` 结尾,如 `UserController`
- 服务类: 以 `Service` 结尾,如 `UserService`
- DTO类: 以 `Request`/`Response` 结尾,如 `UserRequest`, `UserResponse`
- 常量类: 以 `Constants` 结尾,如 `UserConstants`
### 15.3 方法命名规范
- 查询方法: `get`, `find`, `list`, `query`
- 创建方法: `create`, `add`, `save`
- 更新方法: `update`, `modify`
- 删除方法: `delete`, `remove`
### 15.4 异常处理规范
- 使用统一的异常处理机制
- 自定义业务异常继承 `RuntimeException`
- 使用 `@ControllerAdvice` 进行全局异常处理
### 15.5 日志规范
- 使用 `@Slf4j` 注解
- 日志级别: DEBUG(开发), INFO(生产)
- 关键操作必须记录日志
## 16 swagger 接口规范
### 16.1 接口设计原则:
- 所有接口和固定结构的注释请使用中文描述性注释,如 `@ApiModelProperty("用户名")`
- 如果接口返回的是稳定字段集合,应使用明确 DTO 或明确对象字段注解。
- 所有公开接口都能展示清晰的路径参数、查询参数、请求头参数、表单参数说明
### 16.2 swagger规范
- 所有接口、固定机构注释和说明统一使用中文
- 所有固定结构的请求体都使用 DTO 建模,并为每个字段提供名称、类型、必填性和含义说明
- 如果业务内部结果本身仍是动态 JSON则至少提供一个固定外层 DTO把最外层字段含义说明清楚避免 Swagger 展示匿名 `Map`
- 所有固定结构的主要响应对象都能展示字段说明
- 所有分页与统一返回包装的字段含义清晰可见
这份设计文档应作为后续一期实现、SQL 调整和接口落地的基准版本。