63 KiB
标注平台后端设计方案
1. 文档目标
本文档只覆盖一期 Java Backend 需要落地的模块,不扩展到训练集导出、微调任务编排二期能力。
2. 一期范围
2.1 一期要落地的模块
一期后端只建设以下四个业务模块:
- 资源管理模块
source_resource - 任务管理模块
annotation_task - 审批标注结果模块
annotation_result/annotation_result_history - 系统配置模块
sys_config
一期继续复用现有认证鉴权基线:
- 用户登录、Token、会话版本控制沿用现有认证实现
- 岗位权限沿用
position - 数据权限沿用
role - 租户隔离沿用
company_id
2.2 一期不做的内容
以下内容不纳入本次一期后端设计范围:
- 新建部门体系
- 新建复杂工作流引擎
- 训练集导出中心的完整业务闭环
- 面向前台的复杂配置编排 UI
- AI Service 内部推理链路实现细节
3. 总体架构
3.1 系统组成
一期采用“四段式协作”:
lb_backend:对前端暴露 REST API,负责资源、任务、结果审核、系统配置。PostgreSQL:持久化元数据、任务快照、审核结果、归档结果。RustFS:保存原始资源、视频衍生文件、外置问答 JSON、导出文件。ai-service:轮询待执行任务,拉取资源,完成抽取、校验、比对,并写回结果。
3.2 核心设计原则
- 资源生命周期、任务执行生命周期、审核归档生命周期分开建模。
- 大字段优先支持表内存储,但必须预留外置存储能力。
- 后端创建任务时只负责写入
PENDING;执行态状态全部由ai-service驱动。 annotation_result只保存“当前待处理结果”;归档后的最终结果统一进入annotation_result_history。- 运行态结果不再依赖
review_status,而是通过requires_manual_review、is_deleted和历史归档记录组合表达状态。
3.3 一期包结构
一期包结构按 微服务开发规范文档 采用“按技术层分包”的标准结构,不再使用 modules/resource、modules/task 这类按大模块纵向拆包的目录形式:
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
补充约定:
- 控制器、DTO、实体、Mapper、Service 统一放在规范文档要求的根层目录下,业务边界通过类名前缀区分,例如
AnnotationTaskController、SourceResourceMapper。 - 定时任务统一进入
scheduled包,不再在业务模块下单独定义job子目录。 - Mapper XML 统一放在
src/main/resources/mapper,SQL 基准文件统一放在src/main/resources/sql。 - DTO 进一步细分为
dto/request、dto/response和dto/common,避免不同模块的交互对象混放。
3.4 接口对象与数据库实体分层
- 数据库实体仅用于 Repository / Mapper 层持久化,统一命名为
*Entity,不直接作为 Controller 入参或出参。 - Controller 层只接收
*Request、*Query,只返回*Response、*View、PageResult<T>。 - Service 层负责实体转换与聚合装配,例如任务详情需要把
annotation_task、annotation_task_resource、sys_config组合成前端可直接消费的交互对象。 - API 字段命名遵循 Java 驼峰风格,例如数据库列
is_deleted在接口层统一映射为isDeleted。 - 敏感字段不直接透出到界面层,模型
apiKey只在任务快照和配置表中保存,接口返回时必须脱敏或不返回。
4. 权限模型
4.1 岗位权限
一期沿用现有岗位枚举:
ANNOTATORDATA_TRAINERREVIEWERADMIN
权限建议如下:
| 模块 | 查询 | 新增/修改 | 删除 | 特殊说明 |
|---|---|---|---|---|
| 资源管理 | 全岗位可查 | 全岗位可上传 | 全岗位可删自身可见资源 | 删除时仍受数据权限约束 |
| 任务管理 | 全岗位可查 | 全岗位可创建 | 全岗位可删未执行任务 | backend 只创建 PENDING |
| 结果查询 | 全岗位可查可见数据 | 无 | 无 | 仅查询本人/本层级可见数据 |
| 人工审核 | REVIEWER 及以上 |
REVIEWER 及以上 |
无 | 对所有未归档运行态结果开放;其中 requires_manual_review = true 的结果必须审核后才能归档 |
| 系统配置 | 全岗位可查 | ADMIN 建议可写 |
无 | 若沿需求“所有岗位可操作”,需由产品再次确认风险 |
说明:需求原文提到“系统配置模块对所有岗位可以操作”,这在安全上风险较高。本文档保留查询全开放,但建议写权限收敛到 ADMIN。如果必须全岗位可写,则需额外增加配置白名单和审计。
4.2 数据权限
一期沿用现有数据权限角色:
EMPLOYEE:只看自己创建的数据MANAGER:看本公司EMPLOYEE + MANAGERENGINEER:看本公司全部数据
业务表统一保留:
company_idcreator_idcreator_role
4.3 权限落地规则
- 系统表以
company_id作为租户边界。 - 业务列表接口默认按
company_id过滤。 - 再按
creator_role + creator_id追加数据权限过滤。 - 审核类写操作额外校验岗位是否为
REVIEWER或ADMIN。
5. 领域对象与状态归属
5.1 领域对象
一期核心领域对象如下:
SourceResource:原始资源元数据。AnnotationTask:某一次抽取/校验任务快照。AnnotationTaskResource:任务与资源的多对多绑定关系。AnnotationResult:当前待审核或待自动归档的运行态结果。AnnotationResultHistory:归档后的最终结果。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 中任务状态使用:
PENDINGRUNNINGCOMPLETEDFAILED
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 评审结论
旧参考路径存在三个问题:
- 直接把业务含义写死在一级目录,后续跨租户清理困难。
- 部分路径使用原始文件名,不利于重名控制和脱敏。
- 原始资源、衍生资源、审核外置文件、导出文件没有统一前缀策略。
6.2 一期统一规范
统一规则:
- 路径全部使用小写。
- 不直接使用用户原始文件名做对象 Key。
- 优先使用业务主键和年月做路径分层。
- 按“租户 / 资源域 / 类型 / 年月 / 业务 ID”组织。
6.3 Bucket 规划
| Bucket | 用途 |
|---|---|
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 |
6.5 命名规则
resourceId、taskId、resultId使用数据库主键或雪花 ID。yyyyMM例如202604。- 文件扩展名按后端识别后的标准扩展名写入,不沿用用户大小写。
- 资源删除时优先删对象,再更新数据库状态。
7. source 场景流程与状态流转/回退
7.1 文本/图片上传场景
流程:
- 前端上传文件到后端。
- 后端完成文件校验和对象存储上传。
- 后端创建
source_resource,状态写入UPLOADED。 - 当前一期不引入视频预处理作业,资源校验通过后直接置为
READY。
状态流转:
UPLOADED -> READY -> ARCHIVED
回退规则:
- 如果对象存储上传失败:不写库。
- 如果写库失败:回滚并删除已上传对象。
- 如果资源已被任务引用,不允许直接物理删除,只允许逻辑归档到
ARCHIVED。
7.2 视频资源当前处理策略
一期暂不建设独立的视频资源预处理作业,视频资源和文本/图片资源保持同样的主流程:
- 视频上传成功后创建
source_resource,状态为UPLOADED。 - 完成基础校验后直接改为
READY。 - 当前不做拆帧、转写、衍生文件生产。
状态流转:
UPLOADED -> READY -> ARCHIVED
7.3 任务驱动场景
资源与任务解耦,资源不会因为某个任务开始执行就改成任务态。即:
- 创建任务不会把资源状态从
READY改成EXTRACTING - 任务重跑不会污染资源主状态
这是本次重写的核心结论之一。
8. 模块详细设计
8.1 资源管理模块
8.1.1 模块职责
- 接收文本、图片、视频文件上传。
- 保存资源元数据与对象存储定位信息。
- 提供分页查询、详情查询、删除能力。
- 为任务创建与结果审核提供统一的资源视图,不引入独立视频预处理作业。
8.1.2 接口列表
| 方法 | 路径 | 说明 |
|---|---|---|
POST |
/api/source-resources/upload |
上传资源 |
GET |
/api/source-resources |
分页查询资源 |
GET |
/api/source-resources/{id} |
查询资源详情 |
DELETE |
/api/source-resources/{id} |
删除资源 |
8.1.3 接口交互对象
以下对象均为接口层 DTO,不直接复用数据库实体;列表和详情统一返回 SourceResourceResponse,保持前端交互对象一致:
public class SourceUploadRequest {
private String resourceName;
private String resourceType; // TEXT / IMAGE / VIDEO
private String remark;
}
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;
}
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 关键规则
- 只允许上传
TEXT / IMAGE / VIDEO。 source_status = READY的资源才能创建任务。- 已被任务引用的资源删除时,优先做逻辑归档,不直接删除数据库记录。
- 删除资源时必须同步清理 RustFS 对象;如清理失败则整体删除失败。
8.2 任务管理模块
8.2.1 模块职责
- 基于一个或多个资源创建抽取任务。
- 保存模型配置 ID、Prompt 配置 ID,并在创建时固化模型与 Prompt 快照。
- 提供任务分页、详情、修改、软删除。
- 后端只负责创建
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 均支持“选择已有配置”与“手动输入”两种方式:
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;
}
交互与落库规则:
前端界面与 CreateAnnotationTaskRequest 的模型交互流程明确如下:
extractModel和verifyModel均采用统一对象结构TaskModelConfigRequest,通过mode显式表达本次是“下拉选择已有配置”还是“手动输入模型配置”。- 当
mode = SELECT时,前端展示MODEL类型配置下拉框,并把用户选中的selectedConfigName传给后端;后端按company_id + config_name查询sys_config,校验通过后写入extract_model_config_id或verify_model_config_id,并同步固化模型快照字段。 - 当
mode = MANUAL时,前端展示三个手输字段:modelName、modelUrl、apiKey,并通过manualConfig传给后端。 - 对于手动输入的
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
- 若存在:直接复用现有配置记录的
- 对于手动输入的
verifyModel,后端执行与extractModel完全一致的处理逻辑,写入verify_model_config_id及对应快照字段;若modelName不存在,同样自动创建sys_config记录。 extractPrompt、verifyPrompt继续支持二选一:要么按configName选择已有PROMPT配置,要么直接传promptContent作为任务快照。- 手动输入 Prompt 时,仅固化到任务快照字段
extract_prompt/verify_prompt,对应*_prompt_config_id允许为空。 - 创建接口和修改接口遵循完全相同的配置解析规则。
- 若
mode = SELECT时仍传入manualConfig,或mode = MANUAL时仍传入selectedConfigName,后端直接按参数非法处理,避免“选中配置”和“手工输入”语义冲突。 - 若
mode = MANUAL但modelName、modelUrl、apiKey任一为空,后端直接按参数非法处理。
8.2.4 状态流转
标准流转:
PENDING -> RUNNING -> COMPLETED
失败流转:
PENDING -> RUNNING -> FAILED
重试流转:
FAILED -> PENDING
删除规则:
PENDING / FAILED / COMPLETED允许软删除RUNNING不允许删除- 删除接口统一写入
is_deleted = true,列表查询默认过滤软删除任务
8.2.5 任务修改规则
修改任务接口覆盖以下属性:
industryTypetaskTyperesourceIdsextractModel / verifyModelextractPrompt / verifyPrompt
字段级状态约束如下:
| 任务状态 | taskName |
industryType / taskType / extractModel / verifyModel / extractPrompt / verifyPrompt |
resourceIds |
|---|---|---|---|
PENDING |
不可修改 | 可修改 | 可修改 |
FAILED |
不可修改 | 可修改 | 可修改 |
RUNNING |
不可修改 | 可修改 | 不可修改 |
COMPLETED |
不可修改 | 可修改 | 可修改 |
设计结论:
- 修改接口不只是绑定资源,还覆盖任务类型、模型配置和 Prompt 配置。
taskName作为任务标识一经创建不可修改,修改接口不接收该字段。- 资源绑定采用
1A规则:仅当当前任务状态为RUNNING时禁止修改resourceIds,其他状态允许修改。 RUNNING状态下,后端必须校验本次更新前后的resourceIds是否变化,变化则直接拒绝。- 修改
resourceIds时,后端采用“删旧关联 + 重建新关联”的方式维护annotation_task_resource。 - 更新配置类字段时,后端先按
TaskModelConfigRequest.mode解析“选配”或“手输”路径;对于手动输入模型,按modelName自动创建 / 复用sys_config,再同步刷新任务快照字段,保证任务表中的执行快照与配置引用一致。
8.3 审批标注结果模块
8.3.1 模块职责
- 承接 AI 抽取与校验差异结果。
- 查询运行态结果列表与详情。
- 对所有未归档运行态结果提供人工审核比对和合并能力。
- 对
requires_manual_review = true的结果强制走人工审核闭环。 - 对
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:
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 合并审核规则
- 只要
annotation_result.is_deleted = false,审核员都可以进入人工审核流程,不要求requires_manual_review必须为true。 requires_manual_review = true仅表示“强制人工审核”,这类结果不允许走自动归档。compare接口统一返回当前运行态记录中的qaContentJson与diffSummary供界面编辑;当qaContentStorageMode = EXTERNAL时,额外返回qaContentFilePath,用于界面展示当前外置文件来源。merge-review接口接收前端修订后的qaContentJson、diffSummary和reviewComment,后端据此生成最终归档内容。- 当
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仅保留精简摘要或默认值{}
- 当
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
- 后端将用户最终确认的
- 人工审核归档成功后:
- 写入
annotation_result_history - 回写运行态结果
reviewer_id、review_comment、reviewed_at - 将运行态结果标记为
is_deleted = true,等价于从运行态结果集中删除
- 写入
8.3.5 自动归档规则
仅对 requires_manual_review = false and is_deleted = false 的运行态结果开放自动归档:
- 创建后进入
AUTO_ARCHIVE_PENDING - 即使处于
AUTO_ARCHIVE_PENDING,审核员仍可在超时前主动进入人工审核并抢占归档路径 - 超过默认 2 小时且未被人工审核接管时,定时任务把记录迁移到
annotation_result_history requires_manual_review = true的记录一律跳过,不允许自动归档- 自动归档完成后将运行态结果
is_deleted = true
8.3.6 审核状态设计结论
一期不再单独保留 review_status 字段,原因如下:
- 当前业务并不存在“审核通过后仍停留在运行表”的必要。
- 一旦人工确认完成或满足自动归档条件,结果就应该进入历史归档表。
- 运行态结果只关心“是否强制人工审核”和“是否已经归档”;是否允许人工审核由
is_deleted = false统一决定。
8.4 系统配置模块
8.4.1 模块职责
- 维护模型配置、Prompt 配置、系统参数。
- 支持按类型分页查询和详情查看。
- 支持新增和修改。
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:
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 |
补充约束:
sys_config在同一company_id下,config_name不允许重复,不再区分config_type后再重复命名。- 任务创建/修改界面的模型和 Prompt 下拉框均以
configName作为选择值。 - 对于
MODEL类型配置,config_name同时承担任务手动录入模型时的自动建档主键语义。 - 新增配置时,后端必须先按
company_id + config_name做重复校验;若已存在同名配置,不论其config_type是MODEL、PROMPT还是SYSTEM,都直接返回CONFIG_DUPLICATE。 - 修改配置时,如
configName发生变化,后端同样必须校验目标名称是否已被同公司下其他配置占用;若占用则拒绝修改。 - 该约束适用于人工维护配置和任务创建时的模型自动建档两条路径,二者共用同一唯一性规则。
9. 通用返回结构
一期统一建议:
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。
结论如下:
- 当前项目已经使用 MyBatis-Plus,继续使用其分页能力更符合现有技术栈。
- 不建议再引入
PageHelper,否则会形成两套分页机制并存,增加维护成本和排障复杂度。 - 也不建议在每个列表查询里手写一套
LIMIT/OFFSET + COUNT(*)作为默认方案,重复代码过多。
推荐实现方式:
- 在 Spring 配置中启用 MyBatis-Plus
PaginationInnerInterceptor。 - 列表查询统一接收
pageNo、pageSize。 - Mapper 自定义 SQL 场景使用
Page<T>+ 查询对象参数的方式,由分页插件自动追加分页 SQL。 - 对需要聚合的列表,例如任务列表里的
resourceCount,允许在 Mapper XML 中写聚合查询,但仍通过 MyBatis-Plus 分页插件处理分页。
分页设计约束:
- 默认分页方案:MyBatis-Plus
PaginationInnerInterceptor - 明确不使用:
PageHelper - 明确不推荐:每个列表接口手写裸 SQL 分页
9.2 分页实现设计
9.2.1 Spring 配置
一期建议新增统一分页配置类,示例:
@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;
}
}
约束说明:
DbType使用POSTGRE_SQL。maxLimit建议限制为200,防止大页拖垮查询。overflow(false),页码越界时返回空列表,不自动回退到第一页。
9.2.2 通用分页请求约束
所有分页查询 DTO 统一约束如下:
public class PageQuery {
@Min(1)
private Integer pageNo = 1;
@Min(1)
@Max(200)
private Integer pageSize = 20;
}
业务分页 DTO 在此基础上扩展过滤字段,例如:
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>:
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);
}
建议补一个统一转换方法:
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 接口示例:
public interface AnnotationTaskMapper extends BaseMapper<AnnotationTask> {
IPage<AnnotationTaskResponse> selectTaskPage(
Page<AnnotationTaskResponse> page,
@Param("query") AnnotationTaskPageQuery query
);
}
Mapper XML 设计原则:
Page<T>放在第一个参数位置。- 过滤条件统一从
query.xxx取值。 - 列表查询只查当前页需要字段,不做
select *。 - 排序规则必须显式写出,避免分页结果不稳定。
9.2.5 聚合分页场景
任务列表需要返回 resourceCount,属于典型聚合分页场景。推荐写法:
<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>
设计结论:
- 聚合字段允许在 XML 中显式计算。
- 资源过滤推荐用
EXISTS,避免直接 join 过滤导致重复行干扰分页。 - 排序建议固定为
created_at DESC, id DESC,保证翻页稳定。
9.2.6 Count SQL 评审
对于简单列表,MyBatis-Plus 可自动生成 count 查询。
对于复杂聚合分页,有两条约束:
- 先使用分页插件默认 count 机制。
- 如果后续发现 count SQL 在复杂
GROUP BY或多层子查询下性能差,再针对具体接口单独拆count查询,不要在一期一开始就全量手写。
这也是主流团队的常见做法,先统一插件方案,遇到瓶颈再对个别热点查询做定向优化。
9.2.7 落地范围
一期以下分页接口统一采用同一机制:
/api/source-resources/api/annotation-tasks/api/annotation-results/api/sys-configs
10. 数据库设计
10.1 设计说明
- 数据库以
D:\workspace\labeling\lb_backend\src\main\resources\sql目录下的 SQL 文件为唯一基准来源。 annotation_task当前真实建表同时保存配置引用字段和执行快照字段:前者用于界面回显与配置追溯,后者用于执行时解耦配置变更影响。annotation_task、annotation_result均采用软删除字段is_deleted表达逻辑删除或归档后的不可见状态。- 一期 ID 统一使用
BIGINT,由应用层生成,不使用自增。
10.2 表关系
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
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)
约束说明:
uq_sys_config_company_name(company_id, config_name)是一期强约束,表示同一公司下config_name全局唯一。- 该唯一性不区分
config_type,例如同一公司下不能同时存在名为qwen-max的MODEL配置和名为qwen-max的SYSTEM配置。 - 任务创建时若用户手动输入模型名称,自动写入
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)
设计说明:
annotation_task主表不再直接保存resource_id。- 一个任务可绑定多个资源,具体绑定关系落在
annotation_task_resource。 task_type默认值为EXTRACT_QA。- 配置 ID 字段用于关联按
configName选中的模型/Prompt 配置;名称、URL、Key、Prompt 文本字段用于固化执行快照。 industry_type用于保存任务所属行业类型简写,默认值为transport,可选值按业务枚举扩展,例如electricity。extractModel、verifyModel在接口层不再复用单层平铺字段,而是统一使用TaskModelConfigRequest(mode + selectedConfigName + manualConfig)显式表达页面交互模式。- 手动输入模型配置时,后端先按模型名称匹配
sys_config.config_name;不存在则自动创建MODEL配置后再写入任务;存在则复用已有配置 ID。 - 手动输入 Prompt 时,仅保存快照文本,不强制自动创建
PROMPT配置。 task_name只允许创建时写入,后续更新接口不可修改。- 删除接口不物理删除任务,统一更新
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)
设计说明:
- 去掉
result_type,结果类型由任务和资源类型推导。 - 去掉
review_status,审核完成后直接归档。 - 新增
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 资源上传到任务创建
- 用户上传资源。
- 后端将文件写入 RustFS,并生成
source_resource。 - 当前无论文本、图片还是视频,校验通过后均直接置为
READY。 - 用户选择一个或多个资源、模型、Prompt 创建任务。
- 后端写入
annotation_task和annotation_task_resource,任务状态固定为PENDING。
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]
11.2 AI 处理到结果入库
ai-service轮询PENDING任务。- 拉取资源并处理,任务改为
RUNNING。 - 生成抽取结果和校验结果。
- 对比差异后写入
annotation_result,同时落库qa_content_json、diff_summary、qa_content_storage_mode等运行态字段。 - 若结果必须由人工确认后才能进入最终归档,则标记
requires_manual_review = true。 - 若结果允许系统在超时后直接归档,则标记
requires_manual_review = false。 - 两种结果在
is_deleted = false期间都允许审核员主动发起人工审核;差异仅在于是否允许后续自动归档。
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[等待人工审核或自动归档]
11.3 人工审核归档
- 审核员打开结果比对页。
- 查询
compare接口获取资源预览、当前qaContentJson、diffSummary、qaContentStorageMode和qaContentFilePath。 - 无论
requires_manual_review = true还是false,只要记录尚未归档,审核员都可以修改qaContentJson和必要的diffSummary。 - 调用
merge-review接口提交最终内容。 - 若当前记录
qa_content_storage_mode = EXTERNAL:- 后端在本地生成与当前外置文件同名的临时文件
- 将用户最终内容写入该临时文件
- 上传到 RustFS 归档路径
- 新建
annotation_result_history,写入qa_content_storage_mode = EXTERNAL和新的qa_content_file_path
- 若当前记录
qa_content_storage_mode = INLINE:- 后端将
qaContentJson与diffSummary合并 - 将合并结果写入
annotation_result_history.qa_content_json
- 后端将
- 归档成功后,运行态
annotation_result回写审核人信息并标记is_deleted = true,从运行态结果集中移除。
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]
11.4 自动归档
- 定时任务仅扫描
requires_manual_review = false and is_deleted = false的运行态结果。 - 若结果已被审核员人工处理接管,则本次不再进入自动归档。
- 若
created_at + 2h < now(),则执行自动归档。 - 自动归档完成后写入
annotation_result_history。 - 回写运行态结果
is_deleted = true。 requires_manual_review = true的记录不参与自动归档扫描。
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]
12. 典型任务与定时任务
一期当前仅保留一个后台任务:
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 审计建议
以下操作必须记录操作日志:
- 资源删除
- 任务创建与删除
- 人工审核合并
- 系统配置新增与修改
14. 一期验收标准
- 资源、任务、结果、配置四个模块均有清晰 REST 接口。
- 请求 DTO、响应 DTO、分页结构与数据库实体解耦,可直接映射 Controller 层代码。
- 数据表字段类型、默认值、索引、约束与业务语义一致。
- 对象存储路径能覆盖原始资源、外置问答结果和导出文件。
annotation_result生命周期遵循“运行态在当前表、最终态进历史表”的原则。- source 相关状态只描述资源本身,不再混入任务和审核状态。
15. 与当前代码基线的差异说明
为满足最新需求,本文档相对旧版设计的关键调整如下:
annotation_result去掉result_type。annotation_result去掉review_status。annotation_result增加is_deleted。annotation_task改为任务主表 +annotation_task_resource关联表,一个任务可绑定多个资源。annotation_task.task_type默认值调整为EXTRACT_QA。annotation_task新增extract_model_config_id、verify_model_config_id、extract_prompt_config_id、verify_prompt_config_id。annotation_task新增软删除字段is_deleted,删除接口采用逻辑删除。- 任务修改接口明确不允许修改
taskName,且RUNNING状态不可修改绑定资源。 - 任务、资源、结果、配置均通过独立接口 DTO/VO 对外提供,不直接暴露数据库实体。
- 任务状态使用
PENDING / RUNNING / COMPLETED / FAILED。 source_resource只保留资源状态,不承接抽取和审核流转。- 当前不引入独立的视频预处理作业设计。
- 对象存储规范重新分桶、重新分层。
- 创建和修改任务的请求对象改为支持按
configName选择已有配置,或手动输入模型 / Prompt;模型名不存在时自动创建sys_config。 AnnotationTaskResponse、SourceResourceResponse、SysConfigResponse、AnnotationResultResponse分别统一列表和详情返回结构。sys_config改为同公司下config_name唯一,不再允许不同类型复用同名配置。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 调整和接口落地的基准版本。