Files
DadaoTask/2.Design/ROADMAP-M0-M1.md
T
Steven 0c53aa7062 feat(m0): 后端骨架 + Web/安卓前端骨架 + 设计文档
- backend: FastAPI 应用工厂、统一错误信封、JWT、短信登录、工作区/成员/邀请、幂等中间件
- alembic 0001: workspace/member/invitation/audit_log/app_user + RLS 策略 + 双角色 GRANT
- tests: 认证/工作区/租户隔离/幂等 四组用例(尚未在本机执行)
- web: React18+Vite 骨架(登录、工作区、邀请落地页)
- android: Compose 骨架(登录、工作区列表)
- 2.Design: PRODUCT-PLAN / ROADMAP-M0-M1 / NAMING / DEVELOPMENT
2026-10-28 02:38:53 +08:00

167 lines
8.1 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.
# M0–M1 实施清单
> 来源:`2.Design/PRODUCT-PLAN.md` v1.0 第 12 节
> 粒度:每项 0.3–1.5 人日,2 人并行 ≈ 2 周一个里程碑
> 状态标记:`[ ]` 未开始 `[~]` 进行中 `[x]` 完成
---
## M0 · 骨架(第 1–2 周,10 人日)
**里程碑目标**:一个能注册、能建团队、能证明"两个租户互相看不见"的空壳,部署在服务器上,CI 全绿。
### 0.1 仓库与约定(0.5d)
- [ ] 目录结构:`backend/ web/ android/ deploy/ 2.Design/`
- [ ] `AGENTS.md`:提交纪律(改完必 push)、前端产物只能由 HEAD 构建、文档落点
- [ ] `.gitignore` / `.editorconfig` / `README.md`
- [ ] 分支策略:`main` 受保护,功能走 `feat/*`;提交信息 `feat(module): 中文描述`
- **验收**:Gitea 上能看到仓库,PR 模板可用
### 0.2 本地环境(1d)
- [ ] `deploy/docker-compose.dev.yml`:postgres:16(含 pgvector)+ redis:7 + minio
- [ ] `postgres` 初始化脚本:`CREATE EXTENSION vector, pg_trgm, pgcrypto`
- [ ] `.env.example` 全量注释
- **验收**:`docker compose up -d` 后 `psql` 能连、扩展已装
### 0.3 配置与日志(0.5d)
- [ ] `pydantic-settings` 分层配置(dev/prod),启动即校验必填项
- [ ] 结构化日志(JSON)+ `request_id` 贯穿 + 敏感字段脱敏中间件(`api_key`/`token`/`password`)
- **验收**:一个请求在日志里能用 request_id 串起来,密钥不会出现在日志里
### 0.4 数据库基座(1d)
- [ ] `alembic init`,autogenerate 接入,`env.py` 读应用配置
- [ ] 基础表迁移:`workspace` `user` `member` `invitation` `audit_log`
- [ ] 通用列约定:`id uuid`、`created_at`、`updated_at`、`version`、`deleted_at`
- **验收**:空库 `alembic upgrade head` 一次成功;`downgrade` 可回滚
### 0.5 租户上下文 + RLS(1.5d)★ 核心
- [ ] 请求依赖:解析 JWT → 校验成员身份 → `SET LOCAL app.ws_id`
- [ ] 全部租户表启用 RLS,策略 `workspace_id = current_setting('app.ws_id')::uuid`
- [ ] 仓储基类强制 `ws_id` 为必填构造参数(不给默认值)
- [ ] 测试:租户 A 的 token 读租户 B 的资源 → 0 行 / 403
- **验收**:`pytest -k tenant_isolation` 全绿;故意删掉一条 where 条件时测试必须变红(用变异测试验证一次)
### 0.6 认证(1.5d)
- [ ] 手机号 + 验证码注册/登录(开发环境固定码 `123456`,短信通道留接口)
- [ ] 邮箱 + 密码(argon2)作为备选
- [ ] JWT:access 15min / refresh 30d 轮换,refresh 落库可撤销
- [ ] 登录限流(IP + 账号维度)
- **验收**:注册 → 登录 → 刷新 → 登出 全链路可用;错误码清晰(不是 500)
### 0.7 权限守卫(0.5d)
- [ ] `PERMISSIONS` 字典:`(资源, 动作) → 最低角色`,FastAPI 依赖注入统一执行
- [ ] 四个角色:owner / admin / member / guest
- **验收**:guest 调管理接口 403;member 调 admin 接口 403
### 0.8 接口规范(0.5d)
- [ ] 统一响应体 `{code, message, data, request_id}`
- [ ] 错误码表(`2.Design/API.md` 里维护)
- [ ] 写接口支持 `Idempotency-Key`(Redis 落 24h)
- [ ] 分页规范:cursor 优先,`limit` 上限 100
### 0.9 健康与版本(0.3d)
- [ ] `/health`(DB/Redis 探活)、`/version`(返回 commit + 构建时间)
- **验收**:部署后 `curl /version` 与 `git rev-parse --short HEAD` 一致
### 0.10 CI(0.7d)
- [ ] `ruff` + `mypy`(宽松起步)+ `pytest`
- [ ] 迁移检查:`alembic upgrade head` 后再 `alembic check` 无差异
- [ ] 镜像构建 + 推送到私有 registry(或直接在服务器构建)
- **验收**:坏代码推上去 CI 变红,前端产物不入库
### 0.11 部署(1d)
- [ ] `deploy/docker-compose.prod.yml` + nginx 反代 + TLS
- [ ] 端口分配登记到 `2.Design/DEPLOY.md`(避开 PosSync 的 5888 / 5176)
- [ ] 备份脚本接入现有每日备份(pg_dump + 保留 14 天)
- **验收**:从零一台机器 30 分钟内起完;重启后数据在;备份可恢复到临时库
### 0.12 三端契约(0.5d)
- [ ] 导出 `openapi.json`,生成 TS 客户端与 Kotlin 客户端
- [ ] `make sdk` 一键重生成
- **验收**:改一个后端字段,两端编译报错(说明契约真的在生效)
### 0.13 客户端壳(1d)
- [ ] Web:Vite 起项目、路由、登录页、空白任务页、请求封装(含 refresh 重试)
- [ ] Android:Compose 工程、登录页、空列表页、Retrofit+OkHttp 封装
- **验收**:两端都能真实登录并拿到空列表
### 0.14 微信 iLink 通道 spike(0.5d)★ 决策前置
> 不阻塞 M0 交付,但必须在写 `integration` 表之前做完,否则接法选错要返工。详见 `PRODUCT-PLAN.md` §10.3。
- [ ] 用 DSH 插件(`@michengai/dsh-im-connect`)实际扫码绑定一个微信号,观察 bot 身份归属
- [ ] 用第二个微信号私聊该 bot,确认多用户共用一号时消息是否互不串
- [ ] 压一下:连续 20 条消息的延迟与是否触发风控;断网 5 分钟后 `syncBuf` 能否续上
- [ ] 记录 `bot_token` 失效表现(多久失效、失效后是报错还是静默)
- **产出**:`2.Design/SPIKE-WECHAT-ILINK.md`,明确回答"用户自带号"还是"平台统一号"
- **验收**:文档里三个问题都有实测结论,不是推测
---
## M1 · 任务域(第 3–4 周,10 人日)
**里程碑目标**:自己开始每天用它记待办,连续 3 天不别扭。
### 1.1 数据(1d)
- [ ] `item` `item_assignee` `tag` `item_tag` `comment` `activity` 迁移
- [ ] 索引:`(ws_id, status, due_at)`、`(ws_id, owner_id, status)`、`deleted_at` 部分索引
- [ ] 全文索引:`zhparser` 或 `pg_bigm` 建在 `title + body_md`
### 1.2 领域(1.5d)
- [ ] 状态机 `inbox→todo→doing→done`(+ blocked/canceled),非法流转拒绝并给提示
- [ ] 所有变更写 `activity`(含 `changed_fields` 增量)
- [ ] `version` 乐观锁,冲突返回 409 带当前版本
### 1.3 API(1.5d)
- [ ] 增删改查、批量状态变更、排序(手动 sort_order)
- [ ] 视图查询:inbox / today / week / all,支持标签与关键词过滤
- [ ] 搜索接口(标题 + 正文,返回高亮片段)
### 1.4 Web(3d)
- [ ] 四视图 + 看板拖拽(列 = 状态)
- [ ] 详情侧栏:优先级/截止/标签/备注/子任务(一层)/评论
- [ ] 全局快速录入(`Ctrl+K` 或顶部输入框,支持 `#标签` `!优先级` `@人` 语法)
- [ ] 键盘流:`j/k` 上下、`Enter` 完成、`e` 编辑
### 1.5 安卓(1.5d)
- [ ] 列表 + 详情 + 勾选完成
- [ ] 下拉刷新、只读缓存(Room,仅离线展示,改动必须联网)
### 1.6 通知与提醒(0.8d)
- [ ] 站内通知表与未读角标
- [ ] 每日摘要邮件(可关)
### 1.7 测试与验收(0.7d)
- [ ] 每个接口一条 happy path + 一条越权用例
- [ ] 前端关键交互 e2e(Playwright 起 3 条:登录、建任务、拖动状态)
### M1 完成定义(DOD)
- [ ] 连续 3 天所有待办都记在里面,没有回到微信/备忘录
- [ ] 冷启动到"建完第一个任务"< 90 秒(新账号实测)
- [ ] 1000 条任务下四视图首屏 < 300ms
- [ ] 跨租户测试、越权测试全绿
---
## 关键验收用例(M0–M1 必须通过)
| # | 用例 | 期望 |
|---|---|---|
| 1 | 租户 A 用户请求租户 B 的 item id | 404(不是 403,避免泄露存在性) |
| 2 | 同一手机号注册两次 | 第二次提示已存在并引导登录 |
| 3 | 被移出团队的成员拿旧 token 请求 | 401,且 refresh 失效 |
| 4 | 并发两次提交同一任务的不同状态 | 一次成功,一次 409 并可重试 |
| 5 | 网络中断后重新提交 | 幂等键保证不产生两条 |
| 6 | 删除任务后全局搜索 | 搜不到,且回收站可恢复 |
| 7 | 停掉 Redis | `/health` 变 503,业务接口给明确错误而非 500 |
| 8 | 部署后 `/version` | commit 与仓库 HEAD 一致 |
---
## 下一步排期建议
```
第1周 0.1–0.7(另一个人并行做 0.13 客户端壳)
第2周 0.8–0.12(另一个人并行做 Web 登录与请求封装)
第3周 1.1–1.3 + 1.4 开工
第4周 1.4–1.7 + 自己开始 dogfooding
```