- 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
8.1 KiB
8.1 KiB
M0–M1 实施清单
来源:
2.Design/PRODUCT-PLAN.mdv1.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 + miniopostgres初始化脚本: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读应用配置- 基础表迁移:
workspaceusermemberinvitationaudit_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)
itemitem_assigneetagitem_tagcommentactivity迁移- 索引:
(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