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
This commit is contained in:
Steven
2026-10-28 02:38:53 +08:00
commit 0c53aa7062
72 changed files with 5320 additions and 0 deletions
+98
View File
@@ -0,0 +1,98 @@
# 开发环境(M0)
> 目标:新人在 20 分钟内把后端跑起来并让 `pytest` 全绿。
## 1. 依赖
| 组件 | 版本 | 说明 |
|---|---|---|
| Python | 3.10+(CI 用 3.11) | 后端 |
| Node | 20+ | 前端 |
| Docker Desktop | 任意近期版本 | 只用来跑 Postgres / Redis |
## 2. 端口约定(别改用默认端口)
| 服务 | 端口 | 为什么 |
|---|---|---|
| PostgreSQL | **5433** | 避开机器上可能已有的 5432 |
| Redis | **6380** | 同上 |
| 后端 API | **8100** | 前端 dev 代理指向它 |
| 前端 dev | **5180** | 与 CORS 默认值一致 |
## 3. 起基础设施
```bash
cd deploy
docker-compose -f docker-compose.dev.yml up -d # 老版 CLI 是 docker-compose,新版是 docker compose
```
首次初始化会自动执行 `deploy/postgres/init/01-roles.sql`,创建两个角色:
- `dadao_owner` —— 拥有库、可以建表,**只有 alembic 用**。
- `dadao_app` —— 应用连接用,无 DDL、无 BYPASSRLS,行级安全对它生效。
> 为什么必须两个角色:PostgreSQL 的 RLS 会对表 owner 和 superuser **直接跳过**。
> 如果应用用 owner 连库,多租户隔离就是摆设,而且测试会"假通过"。
## 4. 后端
```bash
cd backend
python -m venv .venv
.venv\Scripts\activate # Linux/macOS: source .venv/bin/activate
pip install -r requirements.txt
cp .env.example .env # Windows: copy .env.example .env
alembic upgrade head # 建表 + RLS 策略(人工复核过再上)
uvicorn app.main:app --reload --port 8100
```
自检:
```bash
curl http://127.0.0.1:8100/health
curl http://127.0.0.1:8100/version # commit 必须等于 git rev-parse --short HEAD
```
开发环境验证码固定为 `SMS_DEV_CODE`(默认 `123456`),且 `/auth/sms/send` 会把 `dev_code` 回显在响应里——
**只有 `ENV=dev` 才允许**,`ENV=prod` 时启动即失败。
## 5. 前端
```bash
cd web
npm install
npm run dev # http://127.0.0.1:5180,/api 代理到 8100
npm run build # 产物必须在仓库 HEAD 上构建,禁止本地构建后上传
```
## 6. 测试
```bash
cd backend
pytest -q # 必须全绿才能提交
ruff check . && ruff format --check .
```
测试会真连数据库、真跑迁移、真走 HTTP 栈。跑之前确保 Postgres 在 5433。
隔离测试(`tests/test_tenant_isolation.py`)是本仓库最重要的一组用例,改动任何租户表都必须重跑它。
## 7. 常见坑
| 现象 | 原因 | 处理 |
|---|---|---|
| `pytest` 报 `attached to a different loop` | 连接池跨事件循环复用 | 测试环境已用 NullPool,别改回去 |
| 迁移报 `permission denied for schema public` | 用的是应用角色 | `MIGRATION_DATABASE_URL` 必须指向 owner |
| 接口返回空列表而不是报错 | 忘了 `bind_tenant`,RLS 过滤掉了一切 | 走 `MemberDep` / `TenantDep` 依赖 |
| `pip` 报 `UnicodeDecodeError: 'gbk' codec` | requirements.txt 里有非 ASCII | 该文件保持纯 ASCII |
| `docker compose` 报 `unknown command` | 只装了 compose v1 | 用 `docker-compose` |
| Docker 起不来 | Docker Desktop 未启动 / 需要管理员 | 先启动 Desktop,`docker info` 有输出再继续 |
## 8. 提交前自检清单
1. `pytest` 全绿,`ruff` 无告警。
2. 新增租户表时:`workspace_id NOT NULL` + RLS 策略 + 跨租户测试,三者齐全。
3. 迁移文件提交前人工复核(`alembic revision --autogenerate` 只做草稿)。
4. 改了接口就更新 `2.Design/API.md`(M1 建立该文档)。
5. 推送到远端,生产部署后 `curl /version` 校验 commit。
+109
View File
@@ -0,0 +1,109 @@
# DadaoTask · 中文命名方案
> 工程名已经是 `DadaoTask`(达到系列)。本文只解决一件事:**对外叫什么**。
> 结论先给:**主品牌「达道」**,个人向备选「一件事」。
> 域名与商标的实测状态写在 §3,可用性是**当场查的**,商标必须你自查。
---
## 1. 命名要满足的四个硬条件
1. **和「达」家族连得上** —— 你已经有达到系列,新品牌不该是孤立的一座岛。
2. **能同时装下三件事** —— 任务、工单、知识。只像"待办清单"的名字,天花板就锁死在 todo 工具。
3. **口播一次能记住** —— 客户在电话里说一遍,对方能拼对。
4. **商标能拿到** —— 通用词("达成""任务通")看着好,注册时全是驳回。
---
## 2. 候选与打分
评分 1–5,越高越好。**"注册可行性"是经验判断,不是查询结果**。
| 候选 | 含义 | 记忆度 | 家族延续 | 气质匹配 | 注册可行性 | 口播 | 合计 |
|---|---|---|---|---|---|---|---|
| **达道** | 通达之道;事的路径 | 4 | 5 | 5 | 4 | 4 | **22** |
| **达岸** | 抵达、上岸、交付 | 4 | 5 | 4 | 4 | 4 | 21 |
| **一件事** | 三件事其实是一件事 | 5 | 2 | 5 | 3 | 5 | 20 |
| **达阵** | 橄榄球 touchdown=完成 | 5 | 5 | 2 | 4 | 3 | 19 |
| 达观 | 洞察、看得开 | 4 | 5 | 3 | 1 | 4 | 17 |
| 接得住 | 协同感(别人接住你的事) | 4 | 1 | 3 | 4 | 5 | 17 |
| 达务 | 事务通达 | 2 | 5 | 3 | 4 | 2 | 16 |
| 事有终 | 有始有终 | 3 | 1 | 4 | 4 | 2 | 14 |
### 推荐:达道(Dà Dào)
**为什么是它:**
- **一句话讲得清**:任务是起点,知识是终点,中间是工单 —— 这就是一条"道"。名字本身把产品哲学说完了,不需要额外解释"我们不只是 todo"。
- **和「达到」同源**:达到了,是结果;达道,是路径。系列感成立,且比"达到 XX"更有分量。
- **气质对得上买家**:给团队卖的协同工具,名字要稳。达道偏沉、偏专业,签合同时不掉价。
- **延展性好**:`达道 · 个人版`、`达道 · 团队版`、`达道云` 都能读顺;英文 `Dadao` 也好念。
**要接受的两个小瑕疵:**
- "达道"在佛道语境里也有"悟道"的意思,中文母语者可能有一丝联想 —— 但不足以干扰,反而添了点"想通了"的味道。
- 域名难拿(见 §3),得靠 `dadaotask.com` 这类组合域名过渡。
### 备选:一件事
如果日后想把 **个人用户** 单独拆成一条产品线(免费获客那一层),「一件事」是最好的 ToC 名字:
- "一件事,从头到尾。" —— 直接把"任务→工单→知识"三段讲成一句。
- 缺点:太通用,商标难过;也不适合挂在 ToB 合同抬头。
- 定位:可以做**功能名 / slogan**,不做公司主品牌。比如「达道 · 一件事」。
### 明确否掉的
| 名字 | 否掉的原因 |
|---|---|
| 达务 | 谐音"大雾""打无",口播致命 |
| 达索 | 和达索系统(Dassault)撞,法务风险 |
| 万事达 | Mastercard 中文名,绝对不能用 |
| 达摩 | 宗教人物,商用不敬 |
| 达令 | darling 谐音,气质错位 |
| 任务通 / 协同宝 | 通用词,商标基本无望,且天花板锁死在单点功能 |
---
## 3. 域名现状(2026-10-28 实测)
用 DNS 解析判断是否已被占用(能解析出 A 记录=已有人用):
| 域名 | 状态 | 说明 |
|---|---|---|
| `dadaotask.com` | **未解析,建议立即注册** | 和工程名完全一致,主域名就用它 |
| `dadao.com` | 已被占用(118.193.15.164) | — |
| `dadao.cn` | 已被占用(211.149.239.226) | — |
| `dadaodao.com` | 已被占用(104.21.4.10,Cloudflare) | 说明"达道"这个读音有人在做站 |
| `dadaoyun.com` | 已被占用(34.80.111.222) | "达道云"被占了 |
| `yijianshi.com` | 已被占用 | 备选名的主域名也没了 |
**建议趁早占的四个**:`dadaotask.com`(主)、`dadaotask.cn`(国内备案用)、`dadaotask.app`(安卓/移动落地页)、`dadao.team` 或 `dadao.work`(如果还空着,做品牌短链)。
> 注意:DNS 不解析 ≠ 一定可注册(可能被注册了但没配解析)。下单前在注册商页面再确认一次。
---
## 4. 商标(必须你自查,我无法联网)
- **建议类别**:第 9 类(计算机软件)、第 42 类(SaaS、软件设计与开发);若日后做企业服务打包,再加第 35 类。
- **建议申请形态**:「达道」文字商标 +「达道/Dadao」组合;图形 logo 单独再报一件。
- **自查入口**:中国商标网(sbj.cnipa.gov.cn)→ 商标查询 → 输入"达道",看第 9/42 类是否已被占。**这一步不做就签合同、印物料,风险自负。**
- 已知有公司名含"达道"(域名被占也印证了这一点),**第 9/42 类很可能是争夺点**,所以我把"注册可行性"只打了 4 分。
---
## 5. 一句话定稿(可直接用于官网/BP)
**达道 Dadao** —— 从任务到知识,一条道走到底。
副标:任务、工单、知识库,一个系统,一条流。
个人向 slogan:**一件事,从头到尾。**
---
## 6. 待你决策
- [ ] 主品牌用「达道」还是「达岸」?(我推荐达道,达岸更轻但 ToB 气场弱)
- [ ] 是否现在就注册 `dadaotask.com` 等四个域名?
- [ ] 是否要我按「达道」把 README / PRODUCT-PLAN 里的占位名全部替换掉?
+493
View File
@@ -0,0 +1,493 @@
# DadaoTask 产品与技术方案
> 版本 **v1.0** · 2026-10-28 · 状态:已确认关键技术决策,可进入 M0
> v0.1 → v1.0 的变化:单租户改为 **SaaS 多租户**;删除离线同步;LLM 明确为 **OpenAI 兼容网关 + BYOK**;新增微信/飞书集成层与合规章节。
---
## 1. 一页纸结论
**定位**:一个"个人工作流"内核 + 按需外扩的协作 + 自动沉淀的知识库,三者由 LLM 贯通。**SaaS 形态,个人版免费起步,团队版按席位收费。**
**核心判断**:任务、工单、知识库不是三个并列模块,而是**同一件事的三个阶段**。拆成三个独立产品(滴答清单/禅道/Notion)的代价是重复输入、信息断层、知识库沦为第二个垃圾场。
```
想法 ──▶ 任务 ──(需要别人)──▶ 工单 ──(验收通过)──▶ 知识卡
│ │ │ │
└────────┴────────────────────┴─────────────────────┘
LLM 在每一段之间自动搬运、结构化、建立索引
```
**三条不可妥协**:
| 原则 | 含义 | 反面 |
|---|---|---|
| 一条流 | 任务升级工单不搬数据,时间线不断裂,知识卡可回链源头 | 工单从任务"复制"出去,两边各改各的 |
| 闭环沉淀 | 知识由已完成工作自动生成草稿,人只做确认/丢弃 | 让人下班后回忆今天学了什么再手敲 |
| LLM 只出草稿 | AI 写入一律 `suggestion` 待审,人点确认才落库 | AI 自动建 200 条任务,用户从此关闭该功能 |
**工期**:2 人约 14 周;第 10 周末可对外测(有任务+工单+知识+AI 的完整闭环)。
---
## 2. 已确认的决策(决策记录)
| # | 决策 | 选择 | 对方案的影响 |
|---|---|---|---|
| 1 | 商业模式 | **SaaS,未来对外卖** | 多租户、注册引导、套餐计量、计费钩子必须进 M0;数据隔离是最高优先级风险 |
| 2 | 离线能力 | **不需要,在线优先** | 删掉 outbox/冲突合并/CRDT,省约 4 周;安卓变成"在线客户端 + 只读缓存" |
| 3 | LLM 接入 | **标准 OpenAI 兼容接口**;系统自带 DeepSeek,用户可配 Ollama 等 | 引入 LLM 网关:provider 注册、密钥加密、能力探测、按租户路由与计量 |
| 4 | 后端 | **FastAPI** | 直接支持 SSE 流式、async、OpenAPI 自动出两端 SDK |
| 5 | 集成 | **微信 + 飞书** | 新增集成层(SSO / 通知 / Bot 三平面);需先明确"微信"= 企业微信还是公众号(见 §10.0) |
| 6 | 历史数据 | **无** | 知识库冷启动要靠"闭环沉淀 + 示例内容 + IM 聊天抽取",不能指望导入 |
---
## 3. 服务谁(SaaS 下的定位)
不是"更好的 Jira",也不是"更全的 Notion"。定位:
> **给"一个人干很多事、偶尔要拉人协作"的角色用。** 先服务单个专业用户(免费),当他的协作半径变大,自然升级到团队版。
| 阶段 | 用户 | 付费理由 |
|---|---|---|
| 免费 | 独立开发者、技术负责人、一人公司 | 任务 + 知识 + 少量 AI 额度 |
| Pro | 重度个人用户 | AI 额度、语义检索、画像、周报 |
| Team | 3–50 人小团队 | 席位、工单 SLA、权限、飞书/企微集成、审计 |
**与现有产品的边界**(写清楚是为了不被打):
| 产品 | 我们不做 | 我们做它做不了的 |
|---|---|---|
| 滴答 / Todoist | 极致 GTD、习惯打卡 | 任务能升级成有验收的工单并沉淀知识 |
| 禅道 / Jira | 大型敏捷、sprint、缺陷全家桶 | 轻到一个人就能用,不需要流程专家 |
| Notion / 语雀 | 块编辑器、Wiki 治理 | 知识从工作流里自动长出来 |
| 飞书 / 企微 | IM 本体 | 把聊天里的"事"沉淀成可追踪对象(我们做它的插件) |
---
## 4. 九个核心设计决策
### D1 · 统一实体 `item`,任务与工单同表
`item` 存共有字段,`type ∈ {task, ticket, note}`;工单专属字段放 1:1 的 `ticket_ext`。
**理由**:两者 80% 字段重合;分表会导致升级搬数据、时间线断裂、搜索 UNION、知识来源挂两套外键。**放弃**:全单表 + JSONB(SLA 计算要写 JSON 路径,约束退化)。
### D2 · 一切皆事件:`activity` 是唯一真相源
所有变更写 `activity`(who/what/changed_fields)。一张表同时喂四个消费者:时间线、审计、知识沉淀素材、LLM 上下文。
### D3 · 知识是工作流的产物
`knowledge_doc` 独立成表(有审核/版本/过期生命周期),但**必须能**带 `source_item_id`。入口三个:工单关闭自动生成、手动随手记、IM 消息抽取。
**理由**:知识库失败从来不是功能少,是没人写。
### D4 · 人的画像 `person_profile`
由 activity/item/knowledge 聚合,LLM 定期生成结构化事实(负责领域、近期事项、待跟进),带生成时间与"过期重算"。
只描述**工作事实**,不做性格或绩效评价 —— 产品伦理,也避免用户反感。
### D5 · LLM 写入必须过审:`suggestion` 表
AI 一切结构性写入(建任务、改状态、打标签、合并知识)都落 `suggestion(status=pending)`,人接受/修改/忽略后才 apply。
**理由**:AI 直接写库是所有 AI 产品翻车的头号原因;审批流同时把 AI 从"黑箱"变成"助理"。
### D6 · 多租户:共享库 + `workspace_id` + PostgreSQL RLS
**这是 v1.0 最重要的变更。** 每个租户表都带 `workspace_id`,靠 RLS 做第二道防线(应用层照常过滤,RLS 兜底漏写条件的 bug)。
**放弃**:schema-per-tenant(迁移要跑 N 次、连接池难管)、DB-per-tenant(成本高)。
**逃生口**:大客户私有化 = 同一套代码换 DSN,即"cell 模式",不为它改代码。
**铁律**:多租户必须第一天做对,事后补是灾难 —— 一条漏写 `workspace_id` 的查询就是一次数据泄露事故。
### D7 · 在线优先,不做离线同步
写操作走乐观 UI + 幂等键重试;安卓用 Room 仅作**只读缓存**(不是真相源)。
**理由**:已确认不需要离线。省掉 outbox、冲突合并、墓碑、单调游标这 4 周工作量。
### D8 · 检索只用 Postgres
`pg_trgm` + 中文分词(`zhparser` / `pg_bigm`)+ `pgvector` HNSW。
**多租户细节**:向量检索必须带 `workspace_id` 过滤;用 pgvector ≥ 0.8 的 iterative scan,或按租户建**部分索引**,避免"跨租户召回再过滤"把 top-k 稀释。
### D9 · LLM 网关:OpenAI 兼容 + BYOK + 平台兜底
一个 `LLMProvider` 抽象层兼容所有 OpenAI 协议实现(DeepSeek / OpenAI / Ollama / 通义 / 智谱 / 月之暗面)。
平台自带 DeepSeek 按套餐给额度;用户可自带 key 与 base_url。密钥加密存储、永不回显、永不进日志。
### D10 · 集成层三平面
SSO(扫码登录)/ 通知(消息卡片)/ Bot(在 IM 里建任务)。三者各自是适配器,不耦合进领域逻辑;出站通知落库后由异步分发,IM 挂了不阻塞主流程。
---
## 5. 领域模型
### 5.1 实体关系
```mermaid
erDiagram
WORKSPACE ||--o{ MEMBER : has
USER ||--o{ MEMBER : joins
WORKSPACE ||--o| SUBSCRIPTION : "billed by"
WORKSPACE ||--o{ LLM_PROVIDER : configures
WORKSPACE ||--o{ INTEGRATION : connects
USER ||--o{ ITEM : owns
ITEM ||--o| TICKET_EXT : extends
ITEM ||--o{ ITEM_LINK : links
ITEM ||--o{ COMMENT : has
ITEM ||--o{ ACTIVITY : logs
ITEM ||--o| KNOWLEDGE_DOC : "distills to"
KNOWLEDGE_DOC ||--o{ CHUNK : "split into"
USER ||--o| PERSON_PROFILE : "profiled by"
SUGGESTION }o--|| ITEM : "applies to"
THREAD ||--o{ MESSAGE : contains
WORKSPACE ||--o{ USAGE_EVENT : meters
```
### 5.2 表清单(22 张;MVP 需 14 张)
| 表 | 作用 | 关键字段 | MVP |
|---|---|---|---|
| `workspace` | 租户 | id, name, slug, plan, status | ✅ |
| `user` | 账号(跨租户唯一) | id, phone, email, name, avatar, password_hash | ✅ |
| `member` | 成员与角色 | ws_id, user_id, role, status, invited_by | ✅ |
| `invitation` | 邀请 | ws_id, token, role, expires_at | ✅ |
| `item` | 统一实体 | id, ws_id, type, title, body_md, status, priority, owner_id, due_at, done_at, visibility, ext, version, deleted_at | ✅ |
| `item_assignee` | 多负责人 | item_id, user_id, role | ✅ |
| `ticket_ext` | 工单专属 | item_id, requester_id, sla_due_at, accepted_at, resolved_at, verified_at, verify_result | ✅ |
| `item_link` | 关系 | from_id, to_id, kind | ✅ |
| `comment` | 评论/内部备注 | item_id, author_id, body_md, internal | ✅ |
| `activity` | 事件流 | item_id, ws_id, actor_id, verb, changed_fields, at | ✅ |
| `tag` / `item_tag` | 标签 | — | ✅ |
| `knowledge_doc` | 知识卡 | ws_id, title, summary, body_md, source_item_id, status, review_at, version | ✅ |
| `suggestion` | AI 待审草稿 | ws_id, kind, payload, target_id, status, model, confidence | ✅ |
| `subscription` / `usage_event` | 套餐与计量 | plan, seats, period, kind, qty | ✅ |
| `chunk` | 切片 + vector(1024) | doc_id, seq, text, tsvector, embedding | V1 |
| `attachment` | 附件 | owner_id, oss_key, mime, size | V1 |
| `llm_provider` | 租户 LLM 配置 | ws_id, kind, base_url, model, key_enc, status | V1 |
| `integration` | 集成配置 | ws_id, provider, plane, config_enc, status | V1 |
| `thread` / `message` | AI 会话 | anchor_item_id, role, content, citations, tokens | V1 |
| `person_profile` | 人物画像 | user_id, ws_id, summary_md, facts, generated_at | V2 |
| `notification` | 出站消息队列 | ws_id, channel, payload, status, retry | V1 |
| `audit_log` | 管理操作审计 | ws_id, actor, action, target, ip | V1 |
### 5.3 状态机
**任务**:`inbox → todo → doing → done`,旁挂 `blocked`、`canceled`
**工单**:`open → accepted → in_progress → pending_verify → done`,旁挂 `reassigned`、`canceled`
**知识**:`draft → in_review → published → archived`
> **一条流的关键机关**:任务升级为工单后自动进 `blocked`;工单验收通过自动回流 `doing/done`。
> 用户不需要在两个地方各改一次状态 —— 比任何 AI 功能都更能说明"这是一个产品"。
---
## 6. 功能地图
### 6.1 MVP(第 1–10 周,目标:自己天天用 + 可对外测)
**任务**:一句话快速录入(全局快捷键/分享面板)、收件箱/今天/本周/全部四视图、优先级/截止/标签/子任务、看板拖拽、移动端列表。
**工单**:任务一键升级(选人+期望时间+说明)、我发起的/待我处理/我参与的、状态流转、评论与内部备注、验收通过或打回、关闭时触发沉淀。
**知识**:手动新建、从工单生成四段式草稿(问题/根因/处理/验证)、全文搜索、来源回链。
**AI 三场景**:NL → 结构化任务/工单;工单关闭 → 知识草稿;基于知识库问答(必须带引用,无来源就说不知道)。
**SaaS 必备**:手机号验证码注册、创建团队、邀请成员、四角色权限、免费额度与用量页、数据导出、管理后台(看租户/用量/封禁)。
**基础**:站内通知 + 邮件;安卓端(在线)。
### 6.2 V1(第 11–16 周)
- 语义检索(pgvector)+ 混合排序
- 对话式 Agent(对话里查任务/改状态,仍走审批)
- 人物画像、自动周报
- 飞书/企微:扫码登录、消息卡片通知、Bot 建任务
- 附件 + 图片 OCR 入知识
- 套餐升级与在线支付(支付宝/微信)
- 审计日志、知识卡过期提醒
### 6.3 V2
- 知识图谱与可视化
- 相似去重与自动归档(>0.92 提示合并)
- 自定义工单字段与流程模板
- 开放 API / Webhook
- 小程序端(承接微信生态的轻量入口)
- 安卓桌面小组件
### 6.4 明确不做
甘特图与资源负载 · BPMN 审批引擎 · 实时协同编辑 · 自建 IM · 离线模式 · 细粒度字段级权限 · 对外开放插件市场(至少两年内)
---
## 7. LLM 网关设计(v1.0 重写)
### 7.1 Provider 模型
```python
class LLMProvider(BaseModel):
ws_id: str | None # None = 平台内置
kind: Literal["openai_compatible"]
base_url: str # https://api.deepseek.com/v1 ; http://ollama:11434/v1
model: str # deepseek-chat / qwen2.5:14b ...
embedding_model: str | None
key_enc: bytes | None # AES-256-GCM 密文,永不回显
status: Literal["ok", "invalid", "unreachable"]
capabilities: dict # 探测结果:stream / tools / json_mode / vision
```
- **预置模板**:DeepSeek(平台默认,给额度)、OpenAI、Ollama(本地/内网)、通义千问、智谱、月之暗面、硅基流动 —— 全部是 OpenAI 兼容协议,只是 `base_url` 与模型名不同。
- **能力探测**:保存时调一次 `GET /models` + 一次 8 token 的 chat,标记 `capabilities`;流式/tools 不支持就自动降级(例如不支持的走两段式:先出 JSON 再渲染)。
- **密钥安全**:AES-256-GCM,主密钥来自环境变量或 KMS;前端只看到 `sk-****abcd`;日志、追踪、错误上报统一脱敏中间件。
- **分层覆盖**:租户默认 → 功能级覆盖(打标签用便宜模型,问答用强模型)→ 全局兜底(平台 key)。
- **网络可达性**:Ollama 场景下用户填的是自己内网地址,SaaS 后端根本连不上 —— **必须支持"客户端代理调用"模式**:浏览器/安卓直接请求用户的 Ollama,结果回传后端只落库不入模型链路。这是私有化用户最常见的坑,M4 就要处理。
### 7.2 六个能力
| 能力 | 输入 | 输出 | 边界 |
|---|---|---|---|
| A 结构化录入 | 一句话 | item 草稿 | 只建议不建 |
| B 闭环沉淀 | 工单 activity 流 | 四段式知识卡草稿 | 必须回链 source_item_id |
| C 自动索引 | 知识正文 | 标签/实体/相似卡 | 标签库受控,不许乱造 |
| D 问答 | 问题 + 检索 | 带引用回答 | 无来源就说不知道 |
| E 人物画像 | 某人 activity | 5–8 条事实 | 只写工作事实 |
| F 主动提醒 | 全局状态 | 风险项 | 每日最多 1 条 |
### 7.3 上下文组装
```
[系统] 角色 + 输出格式约束 + 工作区一句话摘要
[对象] 对话对象画像(≤5 条,各 1 行)
[事实] 相关 item 摘要(top 8,各 1 行)
[证据] 相关 chunk(top 12,各 ≤200 字,带 [doc#seq] 来源标记)
[历史] 本会话最近 6 轮
[输入] 用户当前话语
```
超预算裁剪顺序:chunk 细节 → item 数量 → 画像条数 → **绝不砍系统规则与来源标记**。
预算:输入 ≤ 8k tokens,输出 ≤ 1.5k。
### 7.4 计量、限流、降级
- 每次调用写 `usage_event`(tokens_in/out, model, latency, ws_id)→ 套餐月度汇总 → 超限只降级不硬断(降级为关键词检索 + 模板回答)。
- 租户级 QPS 与并发上限;平台 key 按租户配额,BYOK 只做滥用防护。
- 缓存:同问题 + 同检索结果哈希 → 命中即回,省 30%+。
- **隐私**:prompt/response 默认不落库;调试开关 7 天自动清除,且界面上明确告知。
- **评测**:20 条黄金用例(含 5 条"应当回答不知道"),改 prompt 必跑。
---
## 8. 多租户、权限与计费(SaaS 核心)
### 8.1 隔离
- **应用层**:所有仓储方法强制要求 `ws_id`(类型层面让它成为必填参数,而不是可选)。
- **数据库层**:`SET LOCAL app.ws_id = $1` 于每个请求事务开头,RLS 策略 `USING (workspace_id = current_setting('app.ws_id')::uuid)`。
- **测试层**:每个资源一套「跨租户越权测试」,断言必须 0 行 / 403。这条测试进 CI,红了不许合并。
- **对象存储**:key 前缀 `ws/{ws_id}/...`,签名 URL 绑定租户。
### 8.2 角色(4 个,不做矩阵)
`owner`(含计费与删除租户)/ `admin`(管人与配置)/ `member`(读写团队内容)/ `guest`(仅被指派的工单)。
实现:`PERMISSIONS` 集中式守卫表(资源 → 动作 → 最低角色),绝不散落 `if`。PosSync 的 `PERMISSION_GUARDS` 写法可直接搬。
### 8.3 套餐与计量
| 套餐 | 席位 | AI 额度 | 关键功能 |
|---|---|---|---|
| Free | 1 | 少量/月 | 任务、工单、知识、基础搜索 |
| Pro | 1 | 中 | 语义检索、画像、周报、更大附件 |
| Team | 3+ | 按席位 | SLA、集成、审计、SLA 提醒、优先支持 |
计量只做三样:AI tokens、存储、席位数。**MVP 不做在线支付**(先手工开通/邀请码),避免把 3 周花在支付对账上;V1 再接支付宝/微信。
### 8.4 注册与引导
手机号验证码 → 建个人工作区 → 3 条示例任务(可一键清空)→ "要不要接入 AI?"(默认平台额度)→ 完成。
**关键指标**:从注册到"完成第一个任务"的时间,目标 < 90 秒。
---
## 9. 技术架构
```mermaid
flowchart TB
subgraph C[客户端]
W["PC Web · React 18 + Vite + Tailwind"]
A["Android · Kotlin + Compose"]
B["飞书 / 企微 Bot"]
end
subgraph G[接入]
N["Nginx · TLS · 限流 · 静态资源"]
end
subgraph S[应用层 · FastAPI]
API["REST + SSE"]
AUTH["认证 · JWT · OAuth"]
SVC["领域服务 item / ticket / knowledge"]
AI["AI 编排 · suggestion 审批 · 上下文组装"]
JOB["异步任务 · arq + Redis"]
INT["集成适配器 · SSO / 通知 / Bot"]
end
subgraph D[数据层]
PG["PostgreSQL 16 · RLS · pgvector"]
R["Redis · 缓存 / 队列 / 限流"]
OSS["MinIO/S3 · 附件"]
end
AI --> GW["LLM 网关"]
GW --> P1["平台 DeepSeek"]
GW --> P2["用户 BYOK"]
GW --> P3["用户 Ollama · 客户端代理"]
W --> N --> API
A --> N
B --> N
API --> SVC --> PG
API --> AI
JOB --> SVC
INT --> B
```
| 层 | 选择 | 理由 |
|---|---|---|
| 后端 | FastAPI + Pydantic v2 | SSE 流式、OpenAPI 出 SDK、async |
| 数据库 | PostgreSQL 16 + pgvector | 关系/全文/向量一库;RLS 直接可用 |
| 队列 | Redis + arq | 轻量 asyncio;Celery 更重 |
| 前端 | React 18 + Vite + Tailwind | 沿用 PosSync 栈,熟 |
| 安卓 | Kotlin + Compose | 沿用已有经验;Flutter 收益不明显 |
| SDK | OpenAPI → TS/Kotlin 生成 | 手写两端必漂移 |
| 部署 | Docker Compose 单机起步 | 早期不上 K8s;按 cell 预留水平扩展 |
| 分发 | 官网 APK + 华为/小米/OPPO/vivo/应用宝 | 国内不上 Google Play |
**代码结构**
```
DadaoTask/
├─ 2.Design/ 设计文档、数据库、部署、事故记录
├─ backend/
│ ├─ app/api/ items, tickets, knowledge, ai, auth, admin, sync(无)
│ ├─ app/domain/ 状态机、权限、SLA、租户上下文
│ ├─ app/infra/ db, redis, oss, llm, integration
│ ├─ app/ai/ context builder, prompts, retriever, suggestion
│ └─ app/jobs/ embedding、周报、画像、通知分发
├─ web/ React
├─ android/ Kotlin Compose
└─ deploy/ compose、nginx、备份
```
**沿用 PosSync 的铁律**(写进 AGENTS.md):改完必须提交并 push;生产不得领先仓库;前端产物只能由仓库 HEAD 构建,部署后校验 build 标记。
---
## 10. 微信与飞书集成
### 10.0 纠正:个人微信**有**官方 Bot 通道
v1.0 初稿这里写错了(原判断"个人微信没有官方机器人接口")。核实后的事实:腾讯已开放 **微信 ClawBot / iLink Bot 协议**(`https://ilinkai.weixin.qq.com`),DSH 的 IM 插件 `@michengai/dsh-im-connect` 微信渠道就是基于它实现,源码里明确写着"腾讯官方产品,非逆向方案"。
已核实的技术事实(来源:`@michengai/dsh-im-connect` 的 `src/channels/weixin.ts`、`src/channels/qr/weixin.ts`、README 支持矩阵):
| 事实 | 内容 | 对产品的影响 |
|---|---|---|
| 登录方式 | `POST /ilink/bot/get_bot_qrcode?bot_type=3` 取二维码 → 轮询 `/ilink/bot/get_qrcode_status` → 拿到 `bot_token` | 用户扫码即绑定,不用填任何密钥,接入摩擦极低 |
| 收消息 | 长轮询(poller),必须持久化 `syncBuf` 游标防丢防重 | 一个绑定 = 一条常驻长轮询连接,这是架构约束 |
| 回消息 | `POST /ilink/bot/sendmessage`,按 `context_token` 回复 | — |
| 媒体 | 图片/语音/文件/视频,CDN AES-128-ECB 加密,单文件上限 50 MB | 可以语音记任务、直接把文件甩进知识库 |
| 会话范围 | **仅单聊**,不支持群 | 团队协作的消息面仍得靠飞书/企微 |
| 账号模型 | **一个微信号一个 poller**,官方建议用专用小号 | 见 10.3 的架构分歧,这是最关键的决策点 |
| 合规 | 官方 iLink,插件声明 "no reverse-engineered personal protocol";使用即同意《微信 ClawBot 功能使用条款》 | 不能用 hook/逆向;条款原文的商用边界需甲方自查(我无法联网检索条款全文) |
### 10.0.1 其他微信通道(仍然有效,按需用)
| 通道 | 能做什么 | 建议 |
|---|---|---|
| **企业微信** | 应用消息、审批、扫码登录、群机器人 | ✅ 团队协作首选 |
| **公众号/服务号** | 模板消息(提醒推送)、扫码关注绑定、H5 入口 | ✅ 个人用户的推送通道 |
| **小程序** | 轻量移动入口,可替代部分安卓场景 | 后续,若安卓分发成本过高再做 |
| **个人微信 iLink** | 扫码绑定、单聊收发、语音/文件 | ✅ 个人用户入口(见 10.3) |
### 10.1 三个平面
| 平面 | 飞书 | 企微 | MVP |
|---|---|---|---|
| SSO | 网页扫码/免登 | 网页授权扫码 | V1 |
| 通知 | 消息卡片(可带"接受/完成"按钮) | 应用消息 / 群机器人 | V1 |
| Bot | 事件订阅 → 建任务/查工单 | 群机器人 → 同上 | V2 |
### 10.2 设计要点
- `integration` 表存 provider + plane + 加密配置,每个租户可挂多个。
- 出站走 `notification` 队列 + 重试 + 死信,IM 故障不阻塞业务。
- 入站必须验签(飞书 encrypt key / 企微 token+sha1),并且**幂等**(同一 event_id 只处理一次)。
- Bot 收到的自然语言 → 走 §7 能力 A → 返回**卡片待确认**,用户点确认才建。避免"群里一句话建了十条任务"。
- 双向价值:群里的讨论也能反向抽成知识卡(V2 的差异化点,别人没有)。
### 10.3 个人微信在 SaaS 里的三种接法(要你拍板)
约束源自 10.0 表格最后两行:**单聊 only + 一个微信号一个 poller + 建议专用小号**。这直接推出三种互斥的架构:
| 接法 | 怎么做 | 优点 | 代价 |
|---|---|---|---|
| **A 用户自带微信号** | 每个用户自己扫码,把一个小号变成"我的助理",主号私聊它记任务 | 风险分散到用户;我们不用养号;天然多租户隔离 | 用户得有第二个微信号 → 对普通用户是转化率杀手,只适合重度用户 |
| **B 平台统一号** | 我们运营一个微信助理号,所有用户加它,按白名单识别身份 | 零门槛,注册即用,是真正能拉新的入口 | 一条 poller 承载全平台会话(要压测);好友数上限;**一个号被封 = 全员失联**;条款是否允许商用未知 |
| **C 本地 bridge** | 用户在自己电脑上跑轻量转发程序(DSH 插件就是这种形态),微信连接留在用户侧,云端只收结构化数据 | 最稳、最合规、服务器最省 | 要装东西;电脑关机就断;不适合纯手机用户 |
**我的建议**:MVP 走 **A**,但设计成"高级开关、默认关闭"——先把数据模型和 `integration` 表做对,接不接微信都不影响主流程。B 等条款核实 + 压测数据出来再评估,不要现在押注。C 等桌面端有了再说。
⚠️ **但选型前必须做 0.5 天 spike**,验证三件必须实测才知道的事(拍脑袋会返工):
1. **谁是 bot** —— 扫码的那个微信号自己变成 bot,还是被授权成某个服务实体?(决定"用户要不要备小号")
2. **能否多用户共用** —— 不同微信号私聊同一个 bot,是否都能收到、且会话互不串?(决定 B 是否可行)
3. **风控边界** —— 连续/并发发消息的限流阈值、长轮询断线重连行为、`bot_token` 有效期与失效时机。
结论写进 `2.Design/SPIKE-WECHAT-ILINK.md`,再回来定 10.3 的最终选型。
---
## 11. 安全与合规(SaaS 必答)
| 事项 | 现在要做 | 说明 |
|---|---|---|
| 个人信息保护法 | 隐私政策、最小采集、导出/删除接口、注销 | 卖给企业客户时会被问到 |
| 数据出境 | 默认用境内模型(DeepSeek 等);用境外模型时明确告知并取得同意 | 用户 BYOK 到 OpenAI 属于用户自选,需二次确认弹窗 |
| ICP 备案 | 域名必须备案;涉及在线交易可能需 EDI | 提前办,别卡上线 |
| 等保 | 面向企业客户大概率要求等保二级 | 初期至少做到:日志留存 6 个月、堡垒机、备份、漏洞扫描 |
| 密钥 | 加密入库、不进日志、支持轮换 | 一条脱敏中间件统一处理 |
| 越权 | RLS + 跨租户测试进 CI | 最高优先级 |
| 备份 | 每日全量 + WAL 归档,恢复演练每季度 | 复用现有备份脚本 |
---
## 12. 里程碑(2 人)
| 阶段 | 周 | 交付 | 验收 |
|---|---|---|---|
| M0 骨架 | 1–2 | 多租户认证、RLS、迁移、CI、Compose、部署 | 两个租户互不可见(有测试证明) |
| M1 任务 | 3–4 | 四视图、看板、标签、搜索 | 自己连续 3 天全量使用不别扭 |
| M2 工单 | 5–6 | 升级、状态机、评论、验收、blocked 联动 | 两人真实跑通一个工单闭环 |
| M3 知识 | 7–8 | 知识卡、来源回链、工单草稿、全文搜索 | 关一个工单能顺手产出一张有用卡 |
| M4 AI | 9–10 | 三场景、suggestion 审批、LLM 网关、BYOK | 黄金用例 ≥ 80%;换模型不改业务代码 |
| **对外测** | **10** | 邀请制内测,10–20 个真实用户 | 周留存与"第一个任务完成率"有数 |
| M5 安卓 | 11–12 | Compose 主界面、推送、在线缓存 | 弱网可用、崩溃率 < 1% |
| M6 集成 | 13 | 飞书/企微登录与通知卡片 | 群里能收到并一键处理工单 |
| M7 SaaS | 14 | 套餐计量、用量页、管理后台、导出 | 能手工开单、看用量、封禁滥用 |
| M8 打磨 | 15 | 性能、合规材料、文档、商店上架 | 可公开注册 |
> 提前到第 10 周对外测,是为了让真实用户决定 M5 之后做什么,而不是我猜。
---
## 13. 风险与对策
| 风险 | 概率 | 影响 | 对策 |
|---|---|---|---|
| 多租户越权泄露 | 中 | 致命 | 仓储层强制 ws_id + RLS + 跨租户测试进 CI |
| 范围蔓延(三产品叠加) | 高 | 致命 | §6.4 不做清单当合同;加功能必砍功能 |
| 免费用户刷 AI 额度 | 高 | 中 | 硬配额 + 每日衰减 + 异常检测(同 IP 多账号) |
| LLM 幻觉 | 中 | 高 | 强制引用、无来源不答、黄金用例回归 |
| Ollama 内网不可达 | 高 | 中 | 客户端代理调用模式(§7.1) |
| 知识冷启动无人写 | 中 | 高 | 闭环沉淀 + 示例内容 + IM 抽取;首批种子由我们代写 20 张 |
| 一个人维护三端 | 中 | 中 | OpenAPI 生成 SDK;安卓只做读写与推送 |
| 三个月后自己不用了 | 中 | 致命 | M1 起强制 dogfooding,每周记一条"用得不爽"并优先修 |
| 备案/等保卡上线 | 中 | 高 | 现在就去办 ICP;等保按客户要求提前准备 |
---
## 14. 下一步
1. **M0 骨架**:仓库结构、Compose(PG16/Redis/MinIO)、多租户认证、RLS、迁移、CI、部署脚本。
2. **`2.Design/DATABASE.md`**:22 张表落成 DDL + RLS 策略 + 索引。
3. **`2.Design/API.md`** + OpenAPI 导出脚本:三端并行开发的契约。
4. 确认三件小事:产品名/域名、端口分配(避开 PosSync 的 5888/5176)、Gitea 仓库创建。
> 顺序建议:**先让"任务"这条腿站住,再谈 AI**。反着做的项目,两周后演示很炫,用户却没有真实数据可用。
+166
View File
@@ -0,0 +1,166 @@
# 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
```