Files
label_backend/specs/001-label-backend-spec/research.md
wh 4054a1133b feat(plan): 生成 label_backend 完整实施规划文档
Phase 0:research.md(10项技术决策,无需澄清项)
Phase 1:data-model.md(11张表+Redis结构),contracts/(8个模块API契约),quickstart.md(Docker Compose启动+流水线验证)
plan.md:宪章11条全部通过,项目结构确认
2026-04-09 12:27:16 +08:00

6.5 KiB
Raw Blame History

Phase 0 研究报告label_backend

日期: 2026-04-09
分支: 001-label-backend-spec


技术决策汇总

所有技术选型均由宪章强制约束,无需评估备选方案。本报告记录关键设计决策的理由,供后续实施参考。


决策 1认证机制

决策: UUID v4 Token 存储于 Redis滑动过期禁止 JWT

理由:

  • JWT 自包含令牌无法按需吊销,无法满足"管理员禁用账号立即生效"的安全要求
  • UUID Token 在 Redis 中可精确控制生命周期:退出登录或禁用账号时同步删除 Key下一次请求立即失效
  • 滑动过期(每次有效请求重置 TTL确保活跃用户不被意外踢出

备选方案放弃理由:

  • JWT无法即时吊销存在安全窗口
  • Session Cookie在无状态 REST API 架构中不适用
  • OAuth2过度设计当前场景无第三方授权需求

决策 2多租户隔离机制

决策: MyBatis Plus TenantLineInnerInterceptor + ThreadLocal CompanyContext

理由:

  • TenantLineInnerInterceptor 在 SQL 拦截器层自动在每条查询的 WHERE 子句中注入 company_id,覆盖范围广且无需逐方法手动添加条件
  • ThreadLocal 存储当前请求的 companyId,由 Shiro TokenFilter 在解析 Token 时从 Redis 会话数据注入,确保 companyId 来自服务端权威来源而非客户端参数
  • finally 块强制清理 ThreadLocal防止线程池复用时数据串漏

备选方案放弃理由:

  • 行级安全RLSPostgreSQL 原生支持,但与 MyBatis Plus 集成复杂,且宪章已指定 ThreadLocal 方案
  • 逐方法手动添加 WHERE容易遗漏维护成本高

决策 3任务并发领取控制

决策: Redis SET NX(分布式锁)+ 数据库乐观约束(WHERE status = 'UNCLAIMED')双重保障

理由:

  • 单纯使用数据库乐观锁在高并发下存在写放大问题(大量 UPDATE 竞争)
  • 单纯使用 Redis 锁若锁过期后 DB 写入失败可能导致数据不一致
  • 双重保障Redis 锁TTL 30s快速拦截大部分并发请求减少数据库压力DB 乐观约束作为最终一致性兜底

Key 命名: task:claim:{taskId}TTL 30s与宪章 Redis Key 规范一致)


决策 4审批触发 QA 任务的异步解耦

决策: Spring @TransactionalEventListener(phase = AFTER_COMMIT) + @Transactional(REQUIRES_NEW)

理由:

  • 提取阶段审批通过后需调用 AI HTTP 生成候选问答对,该 HTTP 调用延迟不确定(秒级到分钟级)
  • 若在 @Transactional 内同步调用,数据库连接被长时间占用,且 AI 失败会错误地回滚已完成的审批操作
  • AFTER_COMMIT 保证业务审批先提交再触发事件,避免事务回滚导致的幽灵任务
  • REQUIRES_NEW 为 QA 生成开启独立事务AI 失败仅影响 QA 任务创建,不影响审批结果

事件流: approve() → publish ExtractionApprovedEvent → 事务提交 → onExtractionApproved() 异步执行AI 调用 + 创建 QA 任务)


决策 5标注结果存储语义

决策: JSONB 整体覆盖PUT 语义),禁止局部 PATCH

理由:

  • 三元组/四元组条目具有强关联性(主语-谓语-宾语作为整体,或主体-关系-客体-修饰词作为整体),局部更新易导致不一致
  • 整体替换简化服务端逻辑,前端每次提交完整 items 数组,服务端直接执行 UPDATE result_json = ?
  • 避免局部追加导致的索引层数据不一致(如删除某条目后残留旧数据)

决策 6审计日志事务边界

决策: 审计日志写入不要求与业务操作在同一事务AOP finally 块中独立写入

理由:

  • 审计写入失败不应回滚业务操作(用户的标注/审批结果比审计日志更重要)
  • @Around 通知在业务方法执行完成commit 或 rollback后捕获最终 result,可记录准确的成功/失败状态
  • 审计失败仅 error 级别日志 + 告警,不影响用户体验

决策 7视频预处理幂等回调

决策: 回调处理时检查 video_process_job.status,已为 SUCCESS 则静默忽略

理由:

  • AI 服务可能因网络抖动对同一 jobId 发起多次成功回调
  • 幂等检查确保第一次成功回调创建标注任务,后续重复回调无任何副作用
  • 检查粒度:status == SUCCESS 即返回,不进行任何 DB 写入

决策 8对象存储路径规范

决策: RustFSS3 兼容),文件字节流禁止入库,路径按资源类型分桶分目录

路径规范:

资源 路径格式
文本文件 source-data text/{yyyyMM}/{source_id}.txt
图片 source-data image/{yyyyMM}/{source_id}.jpg
视频 source-data video/{yyyyMM}/{source_id}.mp4
视频帧 source-data frames/{source_id}/{frame_index}.jpg
视频转文本 source-data video-text/{parent_source_id}/{timestamp}.txt
bbox 裁剪图 source-data crops/{task_id}/{item_index}.jpg
导出 JSONL finetune-export export/{batchUuid}.jsonl

决策 9测试策略

决策: 集成测试使用 Testcontainers真实 PG + Redis不允许 Mock 数据库

必须覆盖的测试场景:

  1. 并发任务领取10 线程同时争抢同一任务,验证恰好 1 人成功Redis + DB 双重锁)
  2. 视频回调幂等:同一 jobId 两次成功回调,验证只创建 1 个 annotation_task
  3. 状态机越界拒绝:非法状态转换(如 APPROVED → IN_PROGRESS抛出 BusinessException
  4. 多租户隔离:公司 A 身份访问公司 B 资源,验证被拒绝
  5. Shiro 过滤器链:无 Token → 401Token 有效但角色不足 → 403

无需澄清事项汇总

项目 状态 来源
认证方案 已确定UUID Token 宪章原则三
数据库选型 已确定PostgreSQL 宪章原则一
ORM 已确定MyBatis Plus 宪章原则一
缓存/锁 已确定Redis 宪章原则一
对象存储 已确定RustFS S3 宪章原则一
AI 集成方式 已确定HTTP RestClient 宪章原则一
多租户隔离 已确定ThreadLocal + Interceptor 宪章原则二
并发控制 已确定(双重锁) 宪章原则七
审批事务边界 已确定(@TransactionalEventListener 宪章原则五
测试策略 已确定Testcontainers 宪章开发工作流