Files
lablesys_backend/README.md
2026-04-27 23:26:25 +08:00

63 KiB
Raw Permalink Blame History

标注平台后端设计方案

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:轮询待执行任务,拉取资源,完成抽取、校验、比对,并写回结果。

3.2 核心设计原则

  1. 资源生命周期、任务执行生命周期、审核归档生命周期分开建模。
  2. 大字段优先支持表内存储,但必须预留外置存储能力。
  3. 后端创建任务时只负责写入 PENDING;执行态状态全部由 ai-service 驱动。
  4. annotation_result 只保存“当前待处理结果”;归档后的最终结果统一进入 annotation_result_history
  5. 运行态结果不再依赖 review_status,而是通过 requires_manual_reviewis_deleted 和历史归档记录组合表达状态。

3.3 一期包结构

一期包结构按 微服务开发规范文档 采用“按技术层分包”的标准结构,不再使用 modules/resourcemodules/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

补充约定:

  1. 控制器、DTO、实体、Mapper、Service 统一放在规范文档要求的根层目录下,业务边界通过类名前缀区分,例如 AnnotationTaskControllerSourceResourceMapper
  2. 定时任务统一进入 scheduled 包,不再在业务模块下单独定义 job 子目录。
  3. Mapper XML 统一放在 src/main/resources/mapperSQL 基准文件统一放在 src/main/resources/sql
  4. DTO 进一步细分为 dto/requestdto/responsedto/common,避免不同模块的交互对象混放。

3.4 接口对象与数据库实体分层

  1. 数据库实体仅用于 Repository / Mapper 层持久化,统一命名为 *Entity,不直接作为 Controller 入参或出参。
  2. Controller 层只接收 *Request*Query,只返回 *Response*ViewPageResult<T>
  3. Service 层负责实体转换与聚合装配,例如任务详情需要把 annotation_taskannotation_task_resourcesys_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. 审核类写操作额外校验岗位是否为 REVIEWERADMIN

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:资源已归档,不再允许创建新任务

不建议把 EXTRACTINGQA_REVIEWAPPROVEDREJECTED 放进资源表,因为这些属于任务或结果生命周期。

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 用途
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 命名规则

  1. resourceIdtaskIdresultId 使用数据库主键或雪花 ID。
  2. yyyyMM 例如 202604
  3. 文件扩展名按后端识别后的标准扩展名写入,不沿用用户大小写。
  4. 资源删除时优先删对象,再更新数据库状态。

7. source 场景流程与状态流转/回退

7.1 文本/图片上传场景

流程:

  1. 前端上传文件到后端。
  2. 后端完成文件校验和对象存储上传。
  3. 后端创建 source_resource,状态写入 UPLOADED
  4. 当前一期不引入视频预处理作业,资源校验通过后直接置为 READY

状态流转:

UPLOADED -> READY -> ARCHIVED

回退规则:

  • 如果对象存储上传失败:不写库。
  • 如果写库失败:回滚并删除已上传对象。
  • 如果资源已被任务引用,不允许直接物理删除,只允许逻辑归档到 ARCHIVED

7.2 视频资源当前处理策略

一期暂不建设独立的视频资源预处理作业,视频资源和文本/图片资源保持同样的主流程:

  1. 视频上传成功后创建 source_resource,状态为 UPLOADED
  2. 完成基础校验后直接改为 READY
  3. 当前不做拆帧、转写、衍生文件生产。

状态流转:

UPLOADED -> READY -> ARCHIVED

7.3 任务驱动场景

资源与任务解耦,资源不会因为某个任务开始执行就改成任务态。即:

  • 创建任务不会把资源状态从 READY 改成 EXTRACTING
  • 任务重跑不会污染资源主状态

这是本次重写的核心结论之一。

8. 模块详细设计

8.1 资源管理模块

8.1.1 模块职责

  1. 接收文本、图片、视频文件上传。
  2. 保存资源元数据与对象存储定位信息。
  3. 提供分页查询、详情查询、删除能力。
  4. 为任务创建与结果审核提供统一的资源视图,不引入独立视频预处理作业。

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 关键规则

  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 均支持“选择已有配置”与“手动输入”两种方式:

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 的模型交互流程明确如下:

  1. extractModelverifyModel 均采用统一对象结构 TaskModelConfigRequest,通过 mode 显式表达本次是“下拉选择已有配置”还是“手动输入模型配置”。
  2. mode = SELECT 时,前端展示 MODEL 类型配置下拉框,并把用户选中的 selectedConfigName 传给后端;后端按 company_id + config_name 查询 sys_config,校验通过后写入 extract_model_config_idverify_model_config_id,并同步固化模型快照字段。
  3. mode = MANUAL 时,前端展示三个手输字段:modelNamemodelUrlapiKey,并通过 manualConfig 传给后端。
  4. 对于手动输入的 extractModel,后端先按 company_id + config_name = modelName 查询 sys_configMODEL 类型配置:
    • 若存在:直接复用现有配置记录的 id 作为 extract_model_config_id
    • 若不存在:自动在 sys_config 新增一条 MODEL 配置记录,config_name = modelNameconfig_value 保存 modelName + modelUrl + apiKey 组成的 JSON再把新配置 id 写入 extract_model_config_id
    • 无论配置是复用还是新建,任务表都要固化本次请求的模型快照字段 extract_model_nameextract_model_urlextract_model_api_key
  5. 对于手动输入的 verifyModel,后端执行与 extractModel 完全一致的处理逻辑,写入 verify_model_config_id 及对应快照字段;若 modelName 不存在,同样自动创建 sys_config 记录。
  6. extractPromptverifyPrompt 继续支持二选一:要么按 configName 选择已有 PROMPT 配置,要么直接传 promptContent 作为任务快照。
  7. 手动输入 Prompt 时,仅固化到任务快照字段 extract_prompt / verify_prompt,对应 *_prompt_config_id 允许为空。
  8. 创建接口和修改接口遵循完全相同的配置解析规则。
  9. mode = SELECT 时仍传入 manualConfig,或 mode = MANUAL 时仍传入 selectedConfigName,后端直接按参数非法处理,避免“选中配置”和“手工输入”语义冲突。
  10. mode = MANUALmodelNamemodelUrlapiKey 任一为空,后端直接按参数非法处理。

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

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 接口统一返回当前运行态记录中的 qaContentJsondiffSummary 供界面编辑;当 qaContentStorageMode = EXTERNAL 时,额外返回 qaContentFilePath,用于界面展示当前外置文件来源。
  4. merge-review 接口接收前端修订后的 qaContentJsondiffSummaryreviewComment,后端据此生成最终归档内容。
  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 时:
    • 后端将用户最终确认的 qaContentJsondiffSummary 合并为一个归档 JSON
    • 建议结构为 {"qaContent": <qaContentJson>, "diffSummary": <diffSummary>}
    • 将该合并结果写入 annotation_result_history.qa_content_json
    • annotation_result_history.qa_content_storage_mode 继续写入 INLINE
  7. 人工审核归档成功后:
    • 写入 annotation_result_history
    • 回写运行态结果 reviewer_idreview_commentreviewed_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

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至少包含 modelNamemodelUrlapiKey
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_typeMODELPROMPT 还是 SYSTEM,都直接返回 CONFIG_DUPLICATE
  5. 修改配置时,如 configName 发生变化,后端同样必须校验目标名称是否已被同公司下其他配置占用;若占用则拒绝修改。
  6. 该约束适用于人工维护配置和任务创建时的模型自动建档两条路径,二者共用同一唯一性规则。

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

结论如下:

  1. 当前项目已经使用 MyBatis-Plus继续使用其分页能力更符合现有技术栈。
  2. 不建议再引入 PageHelper,否则会形成两套分页机制并存,增加维护成本和排障复杂度。
  3. 也不建议在每个列表查询里手写一套 LIMIT/OFFSET + COUNT(*) 作为默认方案,重复代码过多。

推荐实现方式:

  1. 在 Spring 配置中启用 MyBatis-Plus PaginationInnerInterceptor
  2. 列表查询统一接收 pageNopageSize
  3. Mapper 自定义 SQL 场景使用 Page<T> + 查询对象参数的方式,由分页插件自动追加分页 SQL。
  4. 对需要聚合的列表,例如任务列表里的 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;
    }
}

约束说明:

  1. DbType 使用 POSTGRE_SQL
  2. maxLimit 建议限制为 200,防止大页拖垮查询。
  3. 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 设计原则:

  1. Page<T> 放在第一个参数位置。
  2. 过滤条件统一从 query.xxx 取值。
  3. 列表查询只查当前页需要字段,不做 select *
  4. 排序规则必须显式写出,避免分页结果不稳定。

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>

设计结论:

  1. 聚合字段允许在 XML 中显式计算。
  2. 资源过滤推荐用 EXISTS,避免直接 join 过滤导致重复行干扰分页。
  3. 排序建议固定为 created_at DESC, id DESC,保证翻页稳定。

9.2.6 Count SQL 评审

对于简单列表MyBatis-Plus 可自动生成 count 查询。

对于复杂聚合分页,有两条约束:

  1. 先使用分页插件默认 count 机制。
  2. 如果后续发现 count SQL 在复杂 GROUP BY 或多层子查询下性能差,再针对具体接口单独拆 count 查询,不要在一期一开始就全量手写。

这也是主流团队的常见做法,先统一插件方案,遇到瓶颈再对个别热点查询做定向优化。

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_taskannotation_result 均采用软删除字段 is_deleted 表达逻辑删除或归档后的不可见状态。
  4. 一期 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)

约束说明:

  1. uq_sys_config_company_name(company_id, config_name) 是一期强约束,表示同一公司下 config_name 全局唯一。
  2. 该唯一性不区分 config_type,例如同一公司下不能同时存在名为 qwen-maxMODEL 配置和名为 qwen-maxSYSTEM 配置。
  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. extractModelverifyModel 在接口层不再复用单层平铺字段,而是统一使用 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_taskannotation_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 处理到结果入库

  1. ai-service 轮询 PENDING 任务。
  2. 拉取资源并处理,任务改为 RUNNING
  3. 生成抽取结果和校验结果。
  4. 对比差异后写入 annotation_result,同时落库 qa_content_jsondiff_summaryqa_content_storage_mode 等运行态字段。
  5. 若结果必须由人工确认后才能进入最终归档,则标记 requires_manual_review = true
  6. 若结果允许系统在超时后直接归档,则标记 requires_manual_review = false
  7. 两种结果在 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 人工审核归档

  1. 审核员打开结果比对页。
  2. 查询 compare 接口获取资源预览、当前 qaContentJsondiffSummaryqaContentStorageModeqaContentFilePath
  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
    • 后端将 qaContentJsondiffSummary 合并
    • 将合并结果写入 annotation_result_history.qa_content_json
  7. 归档成功后,运行态 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 自动归档

  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 的记录不参与自动归档扫描。
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. 典型任务与定时任务

一期当前仅保留一个后台任务:

  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_idverify_model_config_idextract_prompt_config_idverify_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. AnnotationTaskResponseSourceResourceResponseSysConfigResponseAnnotationResultResponse 分别统一列表和详情返回结构。
  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 调整和接口落地的基准版本。