Files
label_backend/specs/001-label-backend-spec/tasks.md

311 lines
27 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 任务清单label_backend 知识图谱智能标注平台
**输入**: `/specs/001-label-backend-spec/` 全部设计文档
**前置条件**: plan.md ✅ | spec.md ✅ | research.md ✅ | data-model.md ✅ | contracts/ ✅ | quickstart.md ✅
## 格式说明
- **[P]**: 可并行执行(不同文件,无未完成任务的依赖)
- **[USn]**: 对应 spec.md 中的用户故事编号
- 每条任务包含精确的文件路径
---
## Phase 1: 项目初始化
**目标**: 创建 Maven 项目骨架、基础配置和 Docker 环境
- [ ] T001 创建 Maven 项目骨架(`com.label` GroupId`label-backend` ArtifactIdJava 17 编译目标)
- [ ] T002 配置 `pom.xml`Spring Boot 3、Apache Shiro 1.13.x、MyBatis Plus 3.5.x、Spring Data Redis、AWS S3 SDK v2、Testcontainers、Lombok
- [ ] T003 [P] 创建 `sql/init.sql`(按依赖顺序建全部 11 张表sys_company → sys_user → source_data → annotation_task → annotation_result → training_dataset → export_batch → sys_config → sys_operation_log → annotation_task_history → video_process_job含所有索引和初始配置数据
- [ ] T004 [P] 创建 `docker-compose.yml`postgres、redis、rustfs、backend、ai-service、frontend 六个服务,含健康检查)和后端 `Dockerfile`eclipse-temurin:17-jre-alpine
- [ ] T005 创建 `src/main/resources/application.yml`数据源、Redis、RustFS、AI 服务 base-url、Shiro 相关配置项)
**检查点**: Maven 编译通过(`mvn compile`Docker Compose `up -d` 全部服务健康
---
## Phase 2: 公共基础设施(阻塞性前置条件)
**目标**: 所有业务模块依赖的公共组件。**必须全部完成后用户故事阶段才能开始**
**⚠️ 重要**: 此阶段未完成前任何用户故事均不可开始实现
- [ ] T006 创建 `Result<T>``ResultCode``PageResult<T>``src/main/java/com/label/common/result/`(统一响应格式:`{"code":"SUCCESS","data":{...}}`
- [ ] T007 [P] 创建 `BusinessException`(含 `code``message``httpStatus`)和 `GlobalExceptionHandler``@RestControllerAdvice`)— `src/main/java/com/label/common/exception/`
- [ ] T008 [P] 创建 `CompanyContext`ThreadLocal`set/get/clear` 三个方法clear 必须在 finally 块调用)— `src/main/java/com/label/common/context/CompanyContext.java`
- [ ] T009 创建 `RedisKeyManager`(三个静态方法:`tokenKey``userPermKey``taskClaimKey`)和 `RedisService``src/main/java/com/label/common/redis/`
- [ ] T010 创建 MyBatis Plus 配置类 `MybatisPlusConfig`,注册 `TenantLineInnerInterceptor`(从 `CompanyContext` 获取 `companyId` 自动注入 WHERE 子句;`sys_company``sys_config` 加入忽略表列表)— `src/main/java/com/label/common/config/MybatisPlusConfig.java`
- [ ] T011 创建 `StateValidator``assertTransition` 泛型方法,违规时抛出 `BusinessException("INVALID_STATE_TRANSITION",...)`)— `src/main/java/com/label/common/statemachine/StateValidator.java`
- [ ] T012 [P] 创建 `SourceStatus` 枚举PENDING/PREPROCESSING/EXTRACTING/QA_REVIEW/APPROVED含 TRANSITIONS Map`src/main/java/com/label/common/statemachine/SourceStatus.java`
- [ ] T013 [P] 创建 `TaskStatus` 枚举UNCLAIMED/IN_PROGRESS/SUBMITTED/APPROVED/REJECTED含 TRANSITIONS Map含 IN_PROGRESS→IN_PROGRESS 用于 ADMIN 强制转移)— `src/main/java/com/label/common/statemachine/TaskStatus.java`
- [ ] T014 [P] 创建 `DatasetStatus` 枚举PENDING_REVIEW/APPROVED/REJECTED含 TRANSITIONS Map`src/main/java/com/label/common/statemachine/DatasetStatus.java`
- [ ] T015 [P] 创建 `VideoJobStatus` 枚举PENDING/RUNNING/SUCCESS/FAILED/RETRYING含 TRANSITIONS Map注释说明 FAILED→PENDING 由 ADMIN 手动触发)— `src/main/java/com/label/common/statemachine/VideoJobStatus.java`
- [ ] T016 创建 `@OperationLog` 注解(`type``targetType` 两个属性,`@Around` 级别)— `src/main/java/com/label/common/aop/OperationLog.java`
- [ ] T017 创建 `AuditAspect``@Around("@annotation(operationLog)")`,在 finally 块以独立操作写入 `sys_operation_log`;审计写入失败只记录 error 日志,禁止抛出异常回滚业务)— `src/main/java/com/label/common/aop/AuditAspect.java`
- [ ] T018 [P] 创建 `RustFsClient`AWS S3 SDK v2 封装endpoint 指向 RustFS实现 `upload``download``delete``getPresignedUrl`)— `src/main/java/com/label/common/storage/RustFsClient.java`
- [ ] T019 [P] 创建 `AiServiceClient``RestClient` 封装8 个端点:`extractText``extractImage``extractFrames``videoToText``genTextQa``genImageQa``startFinetune``getFinetuneStatus`)— `src/main/java/com/label/common/ai/AiServiceClient.java`
- [ ] T020 创建 Shiro 三件套:`TokenFilter`(解析 `Authorization: Bearer {uuid}`,查 Redis `token:{uuid}`,注入 `CompanyContext`,请求结束 finally 清理 ThreadLocal`UserRealm`(先查 Redis `user:perm:{userId}` TTL 5min未命中查 PG`addInheritedRoles`)、`ShiroConfig`(过滤器链:`/api/auth/login``anon``/api/**``tokenFilter`)— `src/main/java/com/label/common/shiro/`
- [ ] T021 创建 `AbstractIntegrationTest`Testcontainers启动真实 PostgreSQL + Redis 容器,执行 sql/init.sql注入测试用的公司和用户数据`src/test/java/com/label/AbstractIntegrationTest.java`
- [ ] T022 集成测试:`ShiroFilterIntegrationTest`(无 Token → 401有效 Token 但角色不足 → 403有效 Token 且角色满足 → 200`src/test/java/com/label/integration/ShiroFilterIntegrationTest.java`
- [ ] T023 单元测试:`StateMachineTest`(验证所有枚举的合法转换通过;非法转换抛出 `BusinessException("INVALID_STATE_TRANSITION")`)— `src/test/java/com/label/unit/StateMachineTest.java`
**检查点**: 基础设施就绪,所有 Phase 3+ 的用户故事可并行开始
---
## Phase 3: 用户故事 1 — 用户登录与身份认证(优先级: P1🎯 MVP
**目标**: 用户可以用用户名和密码登录,获得会话凭证,使用凭证访问受保护接口,退出后凭证立即失效
**独立测试**: 登录 → 获取 Token → 访问 `/api/auth/me` 返回用户信息 → 退出 → 再次访问返回 401
- [ ] T024 [P] [US1] 创建 `SysCompany` 实体MyBatis Plus `@TableName`)和 `SysCompanyMapper``src/main/java/com/label/module/user/entity/SysCompany.java` + `mapper/SysCompanyMapper.java`
- [ ] T025 [P] [US1] 创建 `SysUser` 实体(`passwordHash` 字段加 `@JsonIgnore`)和 `SysUserMapper`(含 `selectByCompanyAndUsername` 方法)— `src/main/java/com/label/module/user/entity/SysUser.java` + `mapper/SysUserMapper.java`
- [ ] T026 [US1] 实现 `AuthService``login()`BCrypt 校验密码 → UUID v4 Token → Redis Hash 存储 userId/role/companyId/username → 设置 TTL = `token_ttl_seconds` 配置值);`logout()`(删除 Redis Token Key`src/main/java/com/label/module/user/service/AuthService.java`
- [ ] T027 [US1] 实现 `AuthController``POST /api/auth/login``anon`,调用 `AuthService.login()`)、`POST /api/auth/logout`(已登录)、`GET /api/auth/me`(返回当前用户信息);所有响应用 `Result<T>` 包装 — `src/main/java/com/label/module/user/controller/AuthController.java`
- [ ] T028 [US1] 集成测试:正确密码登录返回 TokenToken 有效时 `/api/auth/me` 返回 200主动退出后再访问返回 401错误密码登录返回 401 — `src/test/java/com/label/integration/AuthIntegrationTest.java`
**检查点**: US1 独立可测试 — 登录/退出流程完整可用
---
## Phase 4: 用户故事 2 — 原始资料上传(优先级: P1
**目标**: 上传员可以上传文本/图片/视频,查询自己的资料列表;管理员可查看全公司资料
**独立测试**: 上传文本文件 → 列表查到 → 详情含预签名 URL → 管理员可删除
- [ ] T029 [P] [US2] 创建 `SourceData` 实体(含 `parentSourceId` 自引用字段)和 `SourceDataMapper`(含 `updateStatus` 方法)— `src/main/java/com/label/module/source/entity/SourceData.java` + `mapper/SourceDataMapper.java`
- [ ] T030 [US2] 实现 `SourceService``upload()`(先 insert 获取 ID → 构造路径 → 上传 RustFS → 更新 filePath`list()`UPLOADER 按 `uploaderId` 过滤ADMIN 不过滤,强制分页);`findById()`(含 15 分钟预签名 URL`delete()`(仅 PENDING 状态可删,同步删 RustFS 文件)— `src/main/java/com/label/module/source/service/SourceService.java`
- [ ] T031 [US2] 实现 `SourceController``POST /api/source/upload``GET /api/source/list``GET /api/source/{id}``DELETE /api/source/{id}``@RequiresRoles` 注解声明权限;所有响应 `Result<T>` 包装)— `src/main/java/com/label/module/source/controller/SourceController.java`
- [ ] T032 [US2] 集成测试UPLOADER 上传文本/图片 → 列表仅返回自己的资料ADMIN 查看列表返回全部;上传视频 → source_data 状态为 PENDING视频预处理 Phase 9 覆盖);已进入流水线的资料删除返回 409 — `src/test/java/com/label/integration/SourceIntegrationTest.java`
**检查点**: US2 独立可测试 — 上传/查询/删除流程完整可用
---
## Phase 5: 用户故事 3+4 — 提取阶段标注与审批(优先级: P1
**目标**: 标注员可以领取任务并发安全、AI 辅助预标注、编辑并提交;审批员可以通过(自动触发 QA 任务)或驳回(标注员可重领)
**独立测试**: 创建任务 → 标注员领取 → AI 预标注 → 提交 → 审批通过 → QA 任务自动出现在任务池
### 实体与数据层
- [ ] T033 [P] [US3] 创建 `AnnotationTask` 实体 + `AnnotationTaskMapper`(含 `claimTask(taskId, userId, companyId)` 方法SQL`UPDATE ... SET status='IN_PROGRESS', claimed_by=?, claimed_at=NOW() WHERE id=? AND status='UNCLAIMED' AND company_id=?`,返回影响行数)— `src/main/java/com/label/module/task/entity/AnnotationTask.java` + `mapper/AnnotationTaskMapper.java`
- [ ] T034 [P] [US3] 创建 `AnnotationTaskHistory` 实体 + `TaskHistoryMapper``src/main/java/com/label/module/task/entity/AnnotationTaskHistory.java` + `mapper/TaskHistoryMapper.java`
- [ ] T035 [P] [US3] 创建 `AnnotationResult` 实体 + `AnnotationResultMapper`(含 `updateResultJson` 整体覆盖方法和 `selectByTaskId` 方法)— `src/main/java/com/label/module/annotation/entity/AnnotationResult.java` + `mapper/AnnotationResultMapper.java`
### 任务管理服务与控制器
- [ ] T036 [US3] 实现 `TaskClaimService.claim()`(① Redis `SET NX task:claim:{taskId}` TTL 30s失败抛 `TASK_CLAIMED`;② DB `claimTask()` 影响行数为 0 时抛 `TASK_CLAIMED`;③ `insertHistory(UNCLAIMED→IN_PROGRESS)`)和 `unclaim()`StateValidator + 清 Redis 锁 + 历史)和 `reclaim()`(校验 REJECTED + claimedBy = 当前用户 + REJECTED→IN_PROGRESS + 历史)— `src/main/java/com/label/module/task/service/TaskClaimService.java`
- [ ] T037 [US3] 实现 `TaskService``createTask``getPool`按角色过滤ANNOTATOR→UNCLAIMED/EXTRACTIONREVIEWER→SUBMITTED`getMine`(含 IN_PROGRESS/SUBMITTED/REJECTED`getPendingReview`SUBMITTED分页`getById``reassign`ADMIN仅更新 claimedBy + 历史))— `src/main/java/com/label/module/task/service/TaskService.java`
- [ ] T038 [US3] 实现 `TaskController`10 个端点:`POST /api/tasks``GET /api/tasks/pool``POST /api/tasks/{id}/claim``POST /api/tasks/{id}/unclaim``GET /api/tasks/mine``POST /api/tasks/{id}/reclaim``GET /api/tasks/pending-review``GET /api/tasks/{id}``GET /api/tasks``PUT /api/tasks/{id}/reassign`)— `src/main/java/com/label/module/task/controller/TaskController.java`
### 提取标注服务与控制器
- [ ] T039 [US3] 实现 `ExtractionService.aiPreAnnotate()`(调用 `AiServiceClient.extractText/extractImage`,写入 `annotation_result`)和 `updateResult()`(整体覆盖 `result_json`,校验 JSON 格式)— `src/main/java/com/label/module/annotation/service/ExtractionService.java`
- [ ] T040 [US3] 实现 `ExtractionService.submit()``@Transactional`IN_PROGRESS→SUBMITTED + `submitted_at` + insertHistory`src/main/java/com/label/module/annotation/service/ExtractionService.java`
- [ ] T041 [US4] 创建 `ExtractionApprovedEvent`(携带 `taskId``sourceId``sourceType``companyId`)— `src/main/java/com/label/module/annotation/event/ExtractionApprovedEvent.java`
- [ ] T042 [US4] 实现 `ExtractionService.approve()``@Transactional`:① 自审校验;② `is_final=true`;③ SUBMITTED→APPROVED + `completedAt` + 历史;④ `publishEvent(ExtractionApprovedEvent)`AI 调用禁止在此事务内执行)— `src/main/java/com/label/module/annotation/service/ExtractionService.java`
- [ ] T043 [US4] 实现 `ExtractionApprovedEventListener``@TransactionalEventListener(AFTER_COMMIT)` + `@Transactional(REQUIRES_NEW)`:调用 AI 生成候选问答对 → 写 `training_dataset`PENDING_REVIEW→ 创建 QA_GENERATION 任务UNCLAIMED`source_data` 状态→ QA_REVIEW`src/main/java/com/label/module/annotation/service/ExtractionApprovedEventListener.java`
- [ ] T044 [US4] 实现 `ExtractionService.reject()``@Transactional`:① 自审校验;② StateValidator③ SUBMITTED→REJECTED + 历史)— `src/main/java/com/label/module/annotation/service/ExtractionService.java`
- [ ] T045 [US4] 实现 `ExtractionController`5 个端点:`GET /api/extraction/{taskId}``PUT /api/extraction/{taskId}``POST /api/extraction/{taskId}/submit``POST /api/extraction/{taskId}/approve``POST /api/extraction/{taskId}/reject`)— `src/main/java/com/label/module/annotation/controller/ExtractionController.java`
### 集成测试
- [ ] T046 [US3] 并发集成测试10 个线程同时争抢同一 UNCLAIMED 任务,验证恰好 1 人成功、其余均收到 `TASK_CLAIMED` 错误、DB 中 `claimed_by` 唯一 — `src/test/java/com/label/integration/TaskClaimConcurrencyTest.java`
- [ ] T047 [US4] 集成测试:审批通过 → QA 任务自动出现在任务池;自审返回 `SELF_REVIEW_FORBIDDEN` 403驳回后标注员可重领并再次提交 — `src/test/java/com/label/integration/ExtractionApprovalIntegrationTest.java`
**检查点**: US3+US4 独立可测试 — 完整提取流水线领取→标注→提交→审批→QA任务自动创建可用
---
## Phase 6: 用户故事 5 — 问答生成阶段标注与审批(优先级: P2
**目标**: 标注员领取 QA 任务、修改候选问答对并提交;审批员通过后训练样本入库,整条流水线完成
**独立测试**: 领取 QA 任务 → 修改问答对 → 提交 → 审批通过 → training_dataset 状态 APPROVEDsource_data 状态 APPROVED
- [ ] T048 [P] [US5] 创建 `TrainingDataset` 实体 + `TrainingDatasetMapper`(含 `approveByTaskId``deleteByTaskId` 方法)— `src/main/java/com/label/module/annotation/entity/TrainingDataset.java` + `mapper/TrainingDatasetMapper.java`
- [ ] T049 [US5] 实现 `QaService.updateResult()`(整体覆盖问答对 JSONB`submit()``@Transactional`IN_PROGRESS→SUBMITTED + 历史)— `src/main/java/com/label/module/annotation/service/QaService.java`
- [ ] T050 [US5] 实现 `QaService.approve()``@Transactional`:① `validateAndGetTask` 先于一切 DB 写入;② 自审校验;③ `training_dataset` → APPROVED`annotation_task` → APPROVED + 历史;⑤ `source_data` → APPROVED`src/main/java/com/label/module/annotation/service/QaService.java`
- [ ] T051 [US5] 实现 `QaService.reject()``@Transactional`:① 自审校验;② `deleteByTaskId` 清除候选问答对;③ SUBMITTED→REJECTED + 历史;④ `source_data` 保持 QA_REVIEW 不变)— `src/main/java/com/label/module/annotation/service/QaService.java`
- [ ] T052 [US5] 实现 `QaController`5 个端点:`GET /api/qa/{taskId}``PUT /api/qa/{taskId}``POST /api/qa/{taskId}/submit``POST /api/qa/{taskId}/approve``POST /api/qa/{taskId}/reject`)— `src/main/java/com/label/module/annotation/controller/QaController.java`
- [ ] T053 [US5] 集成测试QA 审批通过 → `training_dataset.status = APPROVED``source_data.status = APPROVED`QA 驳回 → 候选记录被删除,标注员可重领 — `src/test/java/com/label/integration/QaApprovalIntegrationTest.java`
**检查点**: US5 独立可测试 — 完整 QA 流水线可用training_dataset 产出验证通过
---
## Phase 7: 用户故事 6 — 训练数据导出与微调提交(优先级: P2
**目标**: 管理员将已审批样本批量导出为 JSONL并可提交 GLM 微调任务
**独立测试**: 选取已审批样本 → 创建批次 → RustFS 中存在 JSONL 文件 → 提交微调 → 可查询状态
- [ ] T054 [P] [US6] 创建 `ExportBatch` 实体 + `ExportBatchMapper``src/main/java/com/label/module/export/entity/ExportBatch.java` + `mapper/ExportBatchMapper.java`
- [ ] T055 [US6] 实现 `ExportService.createBatch()``@Transactional`:① 校验全部样本为 APPROVED② 生成 JSONL每行一个 `glm_format_json`);③ 上传 RustFS `finetune-export/export/{batchUuid}.jsonl`;④ 批量更新 `export_batch_id`/`exported_at`;⑤ 插入 `export_batch` 记录)— `src/main/java/com/label/module/export/service/ExportService.java`
- [ ] T056 [US6] 实现 `FinetuneService``trigger()`(调用 `AiServiceClient.startFinetune()`,更新 `glm_job_id``finetune_status = RUNNING`)和 `getStatus()`(调用 `AiServiceClient.getFinetuneStatus()`)— `src/main/java/com/label/module/export/service/FinetuneService.java`
- [ ] T057 [US6] 实现 `ExportController``GET /api/training/samples``POST /api/export/batch``POST /api/export/{batchId}/finetune``GET /api/export/{batchId}/status``GET /api/export/list`;全部 `@RequiresRoles("ADMIN")`)— `src/main/java/com/label/module/export/controller/ExportController.java`
- [ ] T058 [US6] 集成测试:成功创建批次后 JSONL 文件存在于 RustFS包含非 APPROVED 样本时返回 `INVALID_SAMPLES` 400 — `src/test/java/com/label/integration/ExportIntegrationTest.java`
**检查点**: US6 独立可测试 — 导出批次创建和微调提交流程可用
---
## Phase 8: 用户故事 7 — 用户与权限管理(优先级: P2
**目标**: 管理员可以创建用户、变更角色(立即生效)、禁用账号(立即失效)
**独立测试**: 创建标注员用户 → 验证其能领取任务 → 升为审批员 → 验证立即可以审批 → 禁用账号 → 已有 Token 立即失效
- [ ] T059 [US7] 实现 `UserService``createUser()`BCrypt 哈希密码,强度因子 ≥ 10`updateUser()``updateRole()`DB 写入后立即 `redisTemplate.delete(userPermKey(userId))``updateStatus()`(禁用时删 Redis Token + 权限缓存)— `src/main/java/com/label/module/user/service/UserService.java`
- [ ] T060 [US7] 实现 `UserController``GET /api/users``POST /api/users``PUT /api/users/{id}``PUT /api/users/{id}/status``PUT /api/users/{id}/role`;全部 `@RequiresRoles("ADMIN")`)— `src/main/java/com/label/module/user/controller/UserController.java`
- [ ] T061 [US7] 集成测试:变更角色后权限下一次请求立即生效(无需重新登录);禁用账号后现有 Token 下一次请求立即返回 401 — `src/test/java/com/label/integration/UserManagementIntegrationTest.java`
**检查点**: US7 独立可测试 — 用户管理和即时权限变更可用
---
## Phase 9: 用户故事 8 — 视频处理与系统配置(优先级: P3
**目标**: 上传视频后触发异步预处理(帧提取/转文字AI 回调幂等处理;管理员可配置 Prompt 模板等系统参数
**独立测试(视频)**: 上传视频 → 创建处理任务 → 模拟成功回调 → annotation_task 出现在任务池;重复成功回调 → 任务数量不增加
**独立测试(配置)**: 为公司设置专属 Prompt → 验证该公司使用新值;其他公司使用全局默认
- [ ] T062 [P] [US8] 创建 `VideoProcessJob` 实体 + `VideoProcessJobMapper``src/main/java/com/label/module/video/entity/VideoProcessJob.java` + `mapper/VideoProcessJobMapper.java`
- [ ] T063 [P] [US8] 创建 `SysConfig` 实体 + `SysConfigMapper`(含 `selectByCompanyAndKey(companyId, configKey)` 方法,支持 `companyId IS NULL` 查询)— `src/main/java/com/label/module/config/entity/SysConfig.java` + `mapper/SysConfigMapper.java`
- [ ] T064 [US8] 实现 `VideoProcessService``createJob()``@Transactional``source_data.status → PREPROCESSING` + 插入 job + 触发 AI 异步调用);`handleCallback()``@Transactional`:幂等检查 status==SUCCESS 则 return成功 → SUCCESS + `source_data.status → PENDING`;失败 → 按 retry_count 决定 RETRYING 或 FAILED`reset()`FAILED → PENDING`src/main/java/com/label/module/video/service/VideoProcessService.java`
- [ ] T065 [US8] 实现 `VideoController``POST /api/video/process``GET /api/video/jobs/{jobId}``POST /api/video/jobs/{jobId}/reset``POST /api/video/callback`内部接口IP 白名单或服务密钥保护))— `src/main/java/com/label/module/video/controller/VideoController.java`
- [ ] T066 [US8] 实现 `SysConfigService.get(configKey)`(先按 `(companyId, key)` 查;未命中按 `(NULL, key)` 查全局默认)和 `update(key, value)`UPSERT公司级配置不存在则创建存在则覆盖`src/main/java/com/label/module/config/service/SysConfigService.java`
- [ ] T067 [US8] 实现 `SysConfigController``GET /api/config`(合并公司级 + 全局,标注 scope`PUT /api/config/{key}`;均 `@RequiresRoles("ADMIN")`)— `src/main/java/com/label/module/config/controller/SysConfigController.java`
- [ ] T068 [US8] 集成测试:同一 jobId 两次成功回调,`annotation_task` 记录数为 1幂等达最大重试次数后 status = FAILED — `src/test/java/com/label/integration/VideoCallbackIdempotencyTest.java`
- [ ] T069 [US8] 集成测试:公司级配置覆盖同 Key 的全局默认;其他公司读取全局默认 — `src/test/java/com/label/integration/SysConfigIntegrationTest.java`
**检查点**: US8 独立可测试 — 视频处理幂等和配置管理可用
---
## Phase 10: 收尾与横切关注点
**目标**: 多租户隔离验证、整体合规检查、快速启动验证
- [ ] T070 集成测试:`MultiTenantIsolationTest`(公司 A 身份查询公司 B 的资料/任务 → 返回空列表或 404不泄露数据`src/test/java/com/label/integration/MultiTenantIsolationTest.java`
- [ ] T071 [P] 代码审查:检查所有 Controller 方法返回值均为 `Result<T>``Result<PageResult<T>>`,无裸 POJO 或裸 List 返回
- [ ] T072 [P] 代码审查:检查所有列表查询方法均含分页参数(`page`/`pageSize`),无 `selectAll()` 或不分页的查询
- [ ] T073 [P] 代码审查:检查 `sys_operation_log` 相关代码,确认应用层零处 UPDATE 或 DELETE
- [ ] T074 [P] 代码审查:检查所有 `@Transactional` 方法内无 `AiServiceClient` 的同步 HTTP 调用(审批触发 AI 必须通过 `@TransactionalEventListener`
- [ ] T075 运行 `quickstart.md` 端到端验证:`docker compose up -d` → 登录 → 上传文件 → 创建任务 → 领取 → 提交 → 审批通过 → 确认 QA 任务出现
---
## 依赖关系与执行顺序
### 阶段依赖
```
Phase 1初始化
Phase 2基础设施[全部完成后解锁所有用户故事]
Phase 3US1 认证) ← 可与 Phase 4/5/6/7/8/9 并行
Phase 4US2 上传) ← 依赖 Phase 2独立于其他用户故事
Phase 5US3+4 提取) ← 依赖 Phase 2上传已有资料的集成测试依赖 US2
Phase 6US5 QA ← 依赖 Phase 5 完成QA 任务由提取审批自动创建)
Phase 7US6 导出) ← 依赖 Phase 6 完成(需要 APPROVED 的 training_dataset
Phase 8US7 用户管理) ← 依赖 Phase 3UserService 在 AuthService 基础上扩展)
Phase 9US8 视频+配置) ← 依赖 Phase 2其余独立
Phase 10收尾
```
### 用户故事间依赖
- **US1认证**: 仅依赖 Phase 2完全独立
- **US2上传**: 仅依赖 Phase 2完全独立
- **US3+4提取**: 依赖 Phase 2集成测试中使用已上传资料需 US2
- **US5QA**: 依赖 US3+4QA 任务来源于提取阶段审批通过的级联触发)
- **US6导出**: 依赖 US5需要 APPROVED 状态的 training_dataset
- **US7用户管理**: 依赖 US1UserService 扩展 AuthService 的用户实体)
- **US8视频+配置)**: 仅依赖 Phase 2
### 阶段内并行机会
- Phase 2T007-T010、T012-T015、T018-T019 均可并行(独立文件)
- Phase 3T024、T025 可并行(独立文件)
- Phase 5T033、T034、T035 可并行(独立文件)
- Phase 9T062、T063 可并行(独立文件)
- Phase 10T071-T074 全部可并行(仅代码审查,无文件修改)
---
## 并行执行示例
### Phase 2 基础设施并行
```
同时启动:
任务: "创建 BusinessException、GlobalExceptionHandler — common/exception/" [T007]
任务: "创建 CompanyContextThreadLocal— common/context/" [T008]
任务: "创建 RustFsClient — common/storage/" [T018]
任务: "创建 AiServiceClient — common/ai/" [T019]
任务: "创建 SourceStatus 枚举" [T012]
任务: "创建 TaskStatus 枚举" [T013]
```
### Phase 5 提取阶段并行
```
同时启动(实体/Mapper
任务: "创建 AnnotationTask 实体 + Mapper" [T033]
任务: "创建 AnnotationTaskHistory 实体 + Mapper" [T034]
任务: "创建 AnnotationResult 实体 + Mapper" [T035]
```
---
## 实施策略
### MVP 优先(仅用户故事 1
1. 完成 Phase 1初始化
2. 完成 Phase 2基础设施**关键,阻塞所有故事**
3. 完成 Phase 3US1 认证)
4. **停止并验证**: 登录/退出/权限校验全流程可用
5. 可以独立部署演示认证功能
### 增量交付
1. Phase 1 + Phase 2 → 基础就绪
2. Phase 3US1→ 验证 → 演示MVP
3. Phase 4US2→ 验证 → 演示(上传功能)
4. Phase 5US3+4→ 验证 → 演示(标注流程)
5. Phase 6US5→ 验证 → 演示(完整双阶段流水线)
6. Phase 7US6→ 验证 → 演示(训练数据产出)
7. Phase 8+9 → 验证 → 演示(完整平台)
8. Phase 10 → 收尾
### 多人协作策略
Phase 2 完成后:
- 开发者 APhase 3US1 认证)+ Phase 8US7 用户管理)
- 开发者 BPhase 4US2 上传)+ Phase 5US3+4 提取)
- 开发者 CPhase 9US8 视频+配置)
Phase 5 完成后:
- 开发者 A/B 合力Phase 6US5 QA→ Phase 7US6 导出)
---
## 说明
- `[P]` 任务 = 不同文件,无依赖,可并行
- `[USn]` 标签将任务映射到具体用户故事,便于追踪
- 每个用户故事应独立可完成和可测试
- 每完成一个阶段后提交 git commit
- 在每个检查点停下来独立验证该用户故事
- 避免:模糊任务、同文件并发冲突、破坏独立性的跨故事依赖