14 KiB
FTB 智能项目管理系统 — 架构文档
项目定位
FTB 是一个面向中小型研发团队的项目管理平台,集成 AI 能力辅助决策。核心层级:
产品(Product) → 项目(Project) → 版本(Version) → 需求/任务/用例/Bug
不是给测试团队的工具,也不是 Jira 替代品。核心目标是让产品/项目经理在一个页面看到:"开发完了吗 → 验收过了吗 → 还有多少 BUG → 能不能发布"。
心智模型(重要)
系统设计遵循三条维度线的分离:
语义线(Why) 执行线(How) 质量线(Quality)
───────────────── ───────────────── ────────────────
Requirement Version TestCase
解释为什么做 驱动状态流转 └─ Bug
不参与流程 所有状态看版本 质量闭环
Requirement(需求)= 语义层,解释功能范围和业务原因,不驱动流程。 Version(版本)= 执行主线,所有任务归属版本,状态由版本派生;需求只是可选语义分组。 TestCase + Bug = 质量闭环,挂在版本上验收。
技术栈
| 层 | 技术 | 说明 |
|---|---|---|
| 前端 | Next.js 14 (App Router) | TypeScript + 客户端组件为主 |
| UI | Tailwind CSS + Shadcn/ui | 紧凑信息密度、现代风格 |
| 状态 | Zustand | 每个领域一个 store |
| 持久化 | PostgreSQL AppData(V2.1) | 业务数据走 NestJS /data/:key,不再以浏览器存储为主 |
| 后端 | NestJS + Prisma + PostgreSQL(V2) | 已接入通用数据文档层,后续再逐表关系化 |
| AI | Anthropic SDK(V3 远景) | 健康度/风险预警/排期建议 |
模块结构
apps/web/
├── app/
│ ├── products/ # 产品列表 + 详情(点击项目可跳转)
│ ├── projects/[id]/ # 项目详情:版本卡片/状态胶囊
│ ├── versions/ # 版本列表 + 详情
│ ├── requirements/ # 需求池
│ ├── workspace/ # 与我相关(聚合工作台)
│ └── admin/ # 系统管理(任务类型字典等)
├── components/
│ ├── product/ # 产品组件
│ ├── version/ # 版本组件(PlanTab, CapsuleStages, MemberChips...)
│ ├── dev-task/ # 开发任务组件
│ ├── test-case/ # 测试用例组件
│ ├── bug/ # Bug 组件
│ └── requirement/ # 需求组件
├── lib/
│ ├── derive.ts # 派生数据(flattenVersions/flattenProjects)
│ ├── linkage-engine.ts # 跨模块联动引擎(需求↔DevTask 派生状态)
│ ├── workspace-engine.ts # 工作台聚合引擎(统一WorkItem)
│ ├── version-status.ts # 版本执行态推导
│ ├── version-plan.ts # 计划任务(调研/产品/UI)
│ ├── dev-task.ts # 开发任务
│ ├── test-case.ts # 测试用例
│ ├── bug.ts # Bug
│ └── requirement.ts # 需求
└── stores/ # Zustand stores(每个领域独立)
关键架构原则
1. 引擎模式(关键)
跨模块的数据派生通过引擎层处理,不是各 store 互相调用:
- linkage-engine.ts:从 DevTask 状态派生需求"实际进度",新建模块只需在此聚合
- workspace-engine.ts:聚合 WorkItem 给"与我相关"页面用
引擎是纯函数,输入多个 store 数据,输出统一视图。引擎使应用各模块松耦合,新增模块时不需要改其他模块的代码。
2. 派生胜过存储
时间字段、状态、进度都尽量从底层数据派生,而不是手填:
- 实际工时 =
startDate → completedAt时间差(精确到 0.5h) - 阶段耗时 = 所有阶段任务的最早开始 → 最晚完成
- 需求开发状态 = 从 DevTask 状态聚合
- 版本执行态 = 从所有 DevTask + TestCase + Bug 聚合
- 阶段进度 = 已完成子任务/总子任务
3. 时间戳精确到分钟
所有"实际开始/完成"时间都是 ISO 时间戳(含时分秒),不是日期。展示时统一 slice(0, 16).replace('T', ' ')。
4. 数据联动
修改 A 影响 B 时,B 通过 filter A 派生,不要双向写。例:
- 需求关联到版本:需求设
versionId,版本详情requirements.filter(r => r.versionId === id) - 任务/用例归属版本:新数据优先使用
versionId;旧 DevTask 可通过requirementId -> Requirement.versionId兼容推导 - 删除版本:清理孤儿数据(PlanTask/DevTask/TestCase/Bug + 释放 Requirement.versionId)
状态机概览
| 实体 | 状态流转 |
|---|---|
| Requirement | pending_review → adopted → planned → developing → testing → released → closed(rejected 可回 pending_review) |
| VersionPlan | pending → in_progress → completed |
| DevTask | todo → in_progress → testing → submitted(终态,提测=已完成) |
| TestCase | pending → running → passed/failed/blocked |
| Bug | open → fixing → fixed → verifying → closed/rejected |
DevTask 没有"已完成"状态,"已提测"就是终态——开发交付完成,后续 Bug 单独流转。
数据持久化
V2.1(当前): 通用服务端文档表 app_data
- 后端:
apps/server/src/modules/data/提供GET/PUT /api/v1/data/:key - 数据库:Prisma
AppData模型,表名app_data,key为主键,value为 JSONB - 一致性:
GET返回updatedAt派生的version;前端保存时带上最近读取的version,后端用key + updatedAt原子更新,版本不匹配返回409 APP_DATA_CONFLICT - 前端:各 Zustand store 保持现有数据形状,通过
apps/web/lib/server-data.ts读写服务端 - 覆盖范围:产品/项目/版本树、需求池、调研/产品方案/UI 计划、开发任务、测试用例、Bug、成员/角色/部门、任务类型、任务工时日志、加班记录
- 浏览器仅保留登录会话(
ftb_auth_session/ftb_auth_persist),不再作为业务数据主存储
后续 V2.2(计划): 将 app_data 中稳定的数据形状逐步拆成关系表和领域 CRUD API。拆表前不要恢复业务 localStorage 缓存,避免线上部署后出现多端数据分叉。
生产部署层(2026-07-01)
当前仓库已补齐云服务器生产部署基线:
Dockerfile.web:以 monorepo 根目录为 build context,构建@ftb/shared和 Next.js 前端,运行pnpm --filter web start。Dockerfile.server:构建@ftb/shared和 NestJS 后端,执行prisma generate,运行pnpm --filter server start:prod。docker-compose.prod.yml:编排web、server、postgres、redis、nginx五个服务,服务间通过 Docker 内网通信,对外只暴露 Nginx 80 端口。docker-compose.local.yml:本地服务器/局域网部署入口,同样编排五个服务,默认对外暴露8080,使用独立local_*volumes,避免与云服务器生产数据混用。deploy/nginx/default.conf.template:同域反向代理,/api/转发到 NestJS,其他路径转发到 Next.js。.env.production.example/.env.local-server.example:生产和本地服务器配置模板;正式部署复制为.env.production或.env.local-server,不提交真实密钥。
生产持久化边界:
- 业务主数据在 PostgreSQL
postgres_datavolume 中。 - Redis AOF 在
redis_datavolume 中。 - AI Provider 配置文件在
server_datavolume 中,对应容器路径/app/apps/server/data。
生产数据库初始化使用 Prisma migration:pnpm --filter server db:deploy。本地开发仍可使用 pnpm db:migrate。
权限模型(轻量)
V1 仅做前端校验,无后端鉴权:
- 版本
members字段限定参与者 - 版本列表/详情按
members.contains(currentUser)过滤 - 创建版本时自动加入创建者
- 版本
members为空时所有人可见(兼容旧数据)
V2 接入后端后改为基于 ProjectMember 表的 RBAC(Owner/Admin/Member/Viewer)。
AI Agent 层
详细规范见 agent-spec.md。要点:
- AI Agent 不是一个独立服务,而是嵌在前端的"特定调用入口"。当前 V3.1 仅 Prototype Decompose Agent。
- Agent 写入数据时必须带
aiDraft: true标记,列表中视觉区分(紫色边)。用户编辑后自动清除标记。 - DevTask / TestCase 加入
references[]字段,记录任务/用例的来源(需求 / 原型批注)。Agent 和人工创建均强制至少 1 条引用。 - 原型中有明确功能但没有匹配到关联需求时,AI 可以生成无需求ID分组草案:写入任务/用例的
requirementName,不创建 Requirement,不加入关联需求列表。 - 原型链接不在 Version 上独立存储,而是来自产品方案 (VersionPlan type=product) 已完成计划的
resultUrl。约定:提交产品方案的成果就是原型。 - AI 服务实现走 NestJS 后端(
apps/server/src/modules/ai/),不走 Next.js API Route。 - API Key 通过
/admin/ai-config页面配置(仅超管可见),存到apps/server/data/ai-config.json,不入 git;环境变量ANTHROPIC_API_KEY作为兜底。
新增涉及 AI 的实体字段:
| 实体 | 字段 | 类型 | 说明 |
|---|---|---|---|
| DevTask | versionId | string | 执行归属版本;无需求ID分组也通过它进入版本 |
| DevTask | requirementId | string? | 正式需求 ID;无需求ID分组为空 |
| DevTask | requirementName | string? | 无正式需求 ID 时的展示分组名 |
| DevTask | references | Reference[]? | 引用来源(需求/原型批注) |
| DevTask | aiDraft | boolean? | AI 草案标记 |
| DevTask | aiDraftAt | string? | AI 生成时间戳 |
| DevTask | aiEstimateHours | number? | AI 预估耗时;执行人预估仍写 estimateHours |
| TestCase | requirementName | string? | 无正式需求 ID 时的展示分组名 |
| TestCase | references | Reference[]? | 同上 |
| TestCase | aiDraft | boolean? | 同上 |
| TestCase | aiDraftAt | string? | 同上 |
| TestCase | aiEstimateHours | number? | AI 预估耗时;执行人预估仍写 estimateHours |
版本模块规则层(V2.2 设计约束)
版本详情里的计划完成、需求候选和任务类型映射必须走规则层:
version-plan-workflow.ts:调研/产品方案/UI 设计的子任务、需求覆盖、成果提交和完成条件。requirement-selector.ts:当前版本所属项目下可关联需求的候选筛选,默认只返回status === 'adopted'的项目需求。task-category.ts:DevTask/TestCase 共用任务类型字典,id用于存储,AI 输出的taskTypeName可在采纳时自动追加到字典,code仅作可选语义映射。
页面组件只消费规则层输出,不直接拼完成条件或候选筛选条件。
Work Activity Daily Report Layer (2026-06-26)
The personal daily report is derived from two inputs:
work-activities: append-only activity records created by successful business actions.task-worklogs: legacy/manual worklog records that still contribute hours and written work content.
work-activity-factory.ts owns the mapping from domain actions to reportable activity semantics. Zustand stores call this factory after a successful operation, then append the result through useWorkActivityStore.
workspace-daily-report.ts remains a pure aggregation engine. It groups today's current-user activity into delivery, progress, creation, risk, and note sections, and also detects in-progress work that started before today but has no activity or progress note today.
This is intentionally not a generic rules engine or event bus. The rule surface is explicit, typed, and local to the workspace/daily-report use case.
Xiaobao Warning Layer (2026-06-29)
Xiaobao Warning is a version-level release-risk capability shown above /workspace in the main navigation. It answers whether a version can ship on the expected release date, why it may not, roughly how long it may slip, and which release window is safer.
The rule surface stays in pure frontend engines:
xiaobao-risk.ts: risk score, level, forecast release date, confidence, and current snapshot.xiaobao-risk-evidence.ts: version work aggregation, daily report/activity evidence, and silent-risk detection.xiaobao-risk-trend.ts: daily snapshots, trend detection, and snapshot signatures.xiaobao-risk-ai.ts: AI trigger policy, cache signature, and backend request mapping.
Managers with xiaobao.warning:manage can see all unfinished versions. Non-managers with xiaobao.warning:view can only see unfinished versions where the current user is in version.members.
AI explains rule results only. It writes interpretation cache to xiaobao-risk-insights and never mutates Version, Requirement, DevTask, TestCase, Bug, or Member data. Risk snapshots are saved to xiaobao-risk-snapshots when the page is opened. The first version uses page-triggered analysis rather than a background scheduled Agent.
Per-user warning read state is saved to xiaobao-warning-views. The read marker stores userId + versionId + risk signature, so the sidebar can turn the Xiaobao badge blue when any visible risk has a completed unread update, then return to the red risk-count badge after the user opens every updated warning. AI interpretation that is still generating only shows the "updating" notice and must not produce the blue update badge yet.