chore: 添加 .gitignore 和 tasks.md 到版本控制

This commit is contained in:
wh
2026-04-09 13:25:50 +08:00
parent 52d5dd9c24
commit 42fb748949
2 changed files with 342 additions and 0 deletions

View File

@@ -0,0 +1,310 @@
# 任务清单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
- 在每个检查点停下来独立验证该用户故事
- 避免:模糊任务、同文件并发冲突、破坏独立性的跨故事依赖