docs+chore: M1 状态校准、向量库独立实例、gitignore 修复
CI / backend (push) Canceled after 0s

- ROADMAP:勾选与实际证据一一对应,未做的写明原因与去向(通知推迟到 M2、子任务与 @人 合并进 item_link)
- DEVELOPMENT:新增向量库、e2e、性能实测三节
- API:补批量状态与看板接口,重排小节编号
- deploy:docker-compose.vector.yml + vector-init(独立 PG+pgvector,5434,不碰 fnsvr-01)
- .gitignore:注释乱码修正,补 *.tsbuildinfo / e2e 产物
This commit is contained in:
dadao
2026-10-28 04:05:47 +08:00
parent 6e33e1024d
commit 3d02fe845a
5 changed files with 116 additions and 28 deletions
+2 -1
View File
@@ -19,6 +19,8 @@ htmlcov/
node_modules/ node_modules/
web/dist/ web/dist/
web/.vite/ web/.vite/
*.tsbuildinfo
web/e2e/artifacts/
# 安卓 # 安卓
android/.gradle/ android/.gradle/
@@ -38,4 +40,3 @@ tmprfx*
# 本机开发用的便携 PostgreSQL / 临时目录(不入库) # 本机开发用的便携 PostgreSQL / 临时目录(不入库)
.local/ .local/
+32 -3
View File
@@ -173,18 +173,47 @@ canceled→ todo (恢复)
**前端可以据此直接渲染"下一步能点什么",不用自己维护一份规则。** **前端可以据此直接渲染"下一步能点什么",不用自己维护一份规则。**
目标状态与当前相同:直接返回,版本号不变。 目标状态与当前相同:直接返回,版本号不变。
### 3.5 `DELETE /items/{item_id}?version=3` ### 3.5 `POST /items/batch-status`(看板多选拖拽)
```json
{ "status": "done",
"items": [{ "id": "…", "version": 3 }, { "id": "…", "version": 7 }] }
```
```json
{ "updated": [{ "id": "…", "version": 4, "unchanged": false }],
"failed": [{ "id": "…", "code": 4009, "message": "…", "current_version": 8 }] }
```
- **不是全成功或全回滚**:拖 10 条里有 1 条被同事改过,其余 9 条照样生效。
全回滚会让用户白干一次,而且更难解释"为什么什么都没变"。
- 单次最多 50 条;`failed[].code` 只会是 `4009`(版本冲突)、`5002`(状态不允许)、`5001`(不存在)。
- 已经是目标状态的条目返回 `unchanged: true`,计入 `updated` 但不产生事件。
### 3.6 `GET /items/board`(看板一屏数据)
```json
{ "columns": { "todo": [...], "doing": [...], "blocked": [...], "done": [...] },
"order": ["todo", "doing", "blocked", "done"],
"labels": { "todo": "待办", "doing": "进行中", "blocked": "被阻塞", "done": "已完成" } }
```
- 每列默认 30 条(`per_column` 1–100),按"截止早的在前、无截止靠后",再按更新时间。
- **收件箱不在看板上**:没归类的东西不该占着"正在做的事"的位置。
- 一次请求拿全,避免拖拽时四列数据不同步。
### 3.7 `DELETE /items/{item_id}?version=3`
软删(写 `deleted_at`),只在 `view=deleted` / `include_deleted=true` 时可见。 软删(写 `deleted_at`),只在 `view=deleted` / `include_deleted=true` 时可见。
### 3.6 评论 ### 3.8 评论
| 方法 | 路径 | 说明 | | 方法 | 路径 | 说明 |
|---|---|---| |---|---|---|
| GET | `/items/{item_id}/comments` | 时间正序,游标分页 | | GET | `/items/{item_id}/comments` | 时间正序,游标分页 |
| POST | `/items/{item_id}/comments` | `{body_md}` | | POST | `/items/{item_id}/comments` | `{body_md}` |
### 3.7 标签 `/workspaces/{ws_id}/tags` ### 3.9 标签 `/workspaces/{ws_id}/tags`
| 方法 | 路径 | 说明 | | 方法 | 路径 | 说明 |
|---|---|---| |---|---|---|
+48 -2
View File
@@ -78,7 +78,53 @@ ruff check . && ruff format --check .
隔离测试(`tests/test_tenant_isolation.py`)是本仓库最重要的一组用例,改动任何租户表都必须重跑它。 隔离测试(`tests/test_tenant_isolation.py`)是本仓库最重要的一组用例,改动任何租户表都必须重跑它。
## 7. 常见坑 ### 真浏览器 e2e
需要后端(8100)与前端(5180)都在跑:
```bash
cd web
node e2e/smoke.mjs # 登录→建任务→详情保存→看板拖拽→非法流转被拒,7 项断言
```
用本机缓存的 Chromium(`playwright-core`,不下载浏览器),截图落在 `web/e2e/artifacts/`。
### 1000 条下的首屏耗时
```bash
cd backend && python scripts/bench_views.py # 连开发库,会自己造 1000 条
```
## 7. 知识库向量库(独立实例)
知识库的语义检索需要一个带 **pgvector** 的 PostgreSQL,它**不是**业务库:
```bash
cd deploy
docker-compose -f docker-compose.vector.yml up -d # 127.0.0.1:5434,库名 dadao_knowledge
```
然后在 `backend/.env` 里打开:
```bash
VECTOR_DATABASE_URL=postgresql+asyncpg://dadao_app:dadao_dev_pw@127.0.0.1:5434/dadao_knowledge
```
三条设计约束(别绕):
1. **不碰 fnsvr-01**。那台 PG 17 没有 pgvector,装扩展要动生产机;向量检索吃内存,
和任务库挤一起互相拖累。
2. **不配就是没有**。`VECTOR_DATABASE_URL` 为空时,M3 的语义检索直接报"未配置",
不许悄悄退化成"全表扫描 + 假装是语义匹配"——那种降级没人能发现。
3. **向量表同样开 RLS**。知识库串租户比任务串租户严重得多:等于把别人的文档喂给你的模型。
`EMBEDDING_DIM` 一旦定下来改起来很痛(要重算全部切片),默认 `1024`(bge-m3)。
换模型 = 重建 `chunk` 表 + 重新向量化,写进 M3 的迁移说明。
> 当前这台机器 Docker 起不来(见下),所以向量库还没真正跑起来,
> 上面是配置与脚本,M3 开工前必须先把它跑通。
## 8. 常见坑
| 现象 | 原因 | 处理 | | 现象 | 原因 | 处理 |
|---|---|---| |---|---|---|
@@ -127,7 +173,7 @@ ruff check . && ruff format --check .
- **安装类命令一律写日志文件再 tail**,不要用管道截断输出,否则看不到进度也不知道卡在哪。 - **安装类命令一律写日志文件再 tail**,不要用管道截断输出,否则看不到进度也不知道卡在哪。
- `npm install` 要加 `--ignore-scripts`:沙箱不允许 npm 的 lifecycle 脚本派生进程(`EPERM spawn`)。 - `npm install` 要加 `--ignore-scripts`:沙箱不允许 npm 的 lifecycle 脚本派生进程(`EPERM spawn`)。
## 8. 提交前自检清单 ## 9. 提交前自检清单
1. `pytest` 全绿,`ruff` 无告警。 1. `pytest` 全绿,`ruff` 无告警。
2. 新增租户表时:`workspace_id NOT NULL` + RLS 策略 + 跨租户测试,三者齐全。 2. 新增租户表时:`workspace_id NOT NULL` + RLS 策略 + 跨租户测试,三者齐全。
+30 -19
View File
@@ -128,8 +128,9 @@
| 外网 TLS | 被拦:`schannel SEC_E_NO_CREDENTIALS`、`基础连接已经关闭` | 走 HTTP 镜像:PyPI 用 `http://mirrors.aliyun.com/pypi/simple/`,npm 用 `http://mirrors.huaweicloud.com/repository/npm/` | | 外网 TLS | 被拦:`schannel SEC_E_NO_CREDENTIALS`、`基础连接已经关闭` | 走 HTTP 镜像:PyPI 用 `http://mirrors.aliyun.com/pypi/simple/`,npm 用 `http://mirrors.huaweicloud.com/repository/npm/` |
| pip 缓存 | 装包时写缓存会**卡死**(无输出、CPU 空转) | 必须加 `--no-cache-dir`;装包命令输出要重定向到文件再 tail,不要管道截断 | | pip 缓存 | 装包时写缓存会**卡死**(无输出、CPU 空转) | 必须加 `--no-cache-dir`;装包命令输出要重定向到文件再 tail,不要管道截断 |
| `pg_ctl` | `could not create restricted token: error code 87` | 直接跑 `postgres.exe`,且必须放在**后台作业**里(父进程退出会连带杀死) | | `pg_ctl` | `could not create restricted token: error code 87` | 直接跑 `postgres.exe`,且必须放在**后台作业**里(父进程退出会连带杀死) |
| fnsvr-01 (192.168.8.160) | 只开放 8418(Gitea) / 3306(MySQL);5432/5433/22 全关 | 该机没有可用的 PostgreSQL 对外端口;SSH 也不通 | | fnsvr-01 (192.168.8.160) | 只开放 8418(Gitea) / 3306(MySQL) / **8432(PG 17.6,dbadmin/superuser)** | 用途定死:只有 `dadaotask` 与 `dadaotask_test` 两个库,**不再往上装扩展**;能力:无 pgvector,只有 pg_trgm/pgcrypto |
| Android | 本机无 Android SDK、无 gradle-wrapper.jar | 交给 Android Studio 打开 `android/` 自动补全 | | Android | **无 JDK、无 Gradle**(`java -version`/`gradle -v` 都不存在),SDK 平台目录在但缺构建链 | 装 JDK 17 + Gradle 8.x 后 `./gradlew assembleDebug`;当前安卓代码未被编译器验证过 |
| 向量库 | Docker 起不来 → `deploy/docker-compose.vector.yml` 写了但没跑过 | 等 Docker 可用(或换一台机器)再 `up -d`,端口 5434 |
便携 PG 起法(本机专用): 便携 PG 起法(本机专用):
@@ -164,39 +165,49 @@
- [x] 增删改查(`backend/app/api/items.py`)、`POST /{id}/status`、软删(带 version) - [x] 增删改查(`backend/app/api/items.py`)、`POST /{id}/status`、软删(带 version)
- [x] 视图:`inbox|active|today|week|done|canceled|all|deleted`;`today/week` 按 `Asia/Shanghai` 切日界(`settings.default_timezone`),不按 UTC - [x] 视图:`inbox|active|today|week|done|canceled|all|deleted`;`today/week` 按 `Asia/Shanghai` 切日界(`settings.default_timezone`),不按 UTC
- [x] 过滤:`status`(可重复)、`assignee=me|<uuid>`、`tag`、`q`(标题+正文 ILIKE,实测中文 `周报` 命中) - [x] 过滤:`status`(可重复)、`assignee=me|<uuid>`、`tag`、`q`(标题+正文 ILIKE,实测中文 `周报` 命中)
- [ ] **批量状态变更**未做(M1 先做单条,批量等看板拖拽一起上) - [x] **批量状态变更**(`POST /items/batch-status`,逐条返回成败,不做全回滚)+ 看板数据 `GET /items/board`
- [~] 搜索返回高亮片段:未做,只返回命中行。理由:高亮要在客户端按 query 二次切词,纯展示逻辑,等 Web 有真实使用反馈再补 - [~] 搜索返回高亮片段:未做,只返回命中行。理由:高亮要在客户端按 query 二次切词,纯展示逻辑,等 Web 有真实使用反馈再补
- [x] 分页:`(sort_key DESC, id DESC)` keyset 游标;`sort=due` 不分页(NULLS LAST 会把 NULL 行切碎,只有今日/逾期短列表用) - [x] 分页:`(sort_key DESC, id DESC)` keyset 游标;`sort=due` 不分页(NULLS LAST 会把 NULL 行切碎,只有今日/逾期短列表用)
### 1.4 Web(3d) ### 1.4 Web(3d)
- [x] 视图 tab(今天/进行中/收件箱/已完成/全部)+ 搜索框 + 回车快速新建(`web/src/pages/Tasks.tsx`) - [x] 视图 tab(今天/进行中/收件箱/已完成/全部)+ 搜索框 + 回车快速新建(`web/src/pages/Tasks.tsx`)
- [x] 勾选完成/重开、`收下`(inbox→todo)、`开工`(todo→doing)、`阻塞` - [x] 勾选完成/重开、`收下`(inbox→todo)、`开工`(todo→doing)、`阻塞`
- [x] 点标题展开:正文、标签、最近 8 条事件流;`409` 时提示"刚被别人改过"并重载 - [x] **看板拖拽**(`web/src/pages/Board.tsx`):HTML5 拖放,四列,非法流转当场拦下并说明原因;勾选后可批量移动
- [x] 加载更多(游标),空态文案("今天没有到期的事,挺好。") - [x] **详情侧栏可编辑**(`web/src/components/TaskDetail.tsx`):标题/优先级/截止时间/标签/备注/评论,全部落库;409 时自动刷新为最新内容
- [x] `npm run build` 通过:38 modules,`183.26 kB │ gzip 60.08 kB` - [x] 快速录入语法:`#标签` `!高/!中/!低/!1..!3`(`web/src/lib/parse.ts`,语法从标题里剥离)
- [ ] 看板拖拽(列 = 状态)未做 - [x] 键盘流:`j/k` 选择、`Enter` 完成、`e` 编辑、`Esc` 关闭
- [ ] 详情侧栏(优先级/截止日期选择器/评论输入/子任务)未做——当前详情只读 - [x] `npm run build` 通过:41 modules,`195.20 kB │ gzip 63.73 kB`
- [ ] 全局快速录入 `#标签 !优先级 @人` 语法未做 - [~] `@人` 指派:未做。理由:重名的人比标签多,悄悄指错负责人比不支持更糟;等 M2 有成员选择器再做
- [ ] 键盘流 `j/k`、`Enter`、`e` 未做 - [~] 子任务(一层):未做,排到 M2 与工单的 `item_link` 一起做——两者本质是同一种"事项之间的边"
### 1.5 安卓(1.5d) ### 1.5 安卓(1.5d)
- [ ] 任务列表 + 详情 + 勾选完成(`android/` 目前只有 M0 的登录/工作区壳) - [x] 任务列表 + 详情弹窗 + 勾选完成 + 快速新建(`android/.../ui/TaskListScreen.kt`)
- [x] 视图切换(今天/进行中/收件箱/已完成)、乐观锁冲突提示与自动刷新
- [ ] **未编译**:本机没有 JDK、没有 Gradle(`java -version` / `gradle -v` 均不存在),
Android SDK 平台目录存在但缺构建工具链。代码按 Compose 常规写法写,**尚未经过编译器验证**。
补 JDK 17 + Gradle 后第一件事就是 `./gradlew assembleDebug`,那时的报错才算数。
- [ ] 下拉刷新、只读缓存(Room)未做
### 1.6 通知与提醒(0.8d) ### 1.6 通知与提醒(0.8d)
- [ ] 站内通知表与未读角标(表在 M2 随工单一起建) - [ ] 站内通知表与未读角标 —— **主动推迟到 M2**:通知的绝大多数触发源是工单(被指派、被催、验收),
- [ ] 每日摘要邮件 M1 只有自己的任务,做出来也没人收到东西。表随 M2 的工单一起建,避免建两次。
- [ ] 每日摘要邮件(同上推迟)
### 1.7 测试与验收(0.7d) ### 1.7 测试与验收(0.7d)
- [x] `backend/tests/test_items.py` 10 个用例:默认收件箱、状态机 happy path 与非法流转、版本冲突、跨租户 3003 且他人列表为空、搜索+标签、`today` 时区边界、软删、评论与事件流、游标翻页不重复 - [x] `backend/tests/test_items.py` 13 个用例:默认收件箱、状态机 happy path 与非法流转、版本冲突、
- [x] 全量 `pytest -q` → **43 passed**;`ruff check` + `ruff format --check` 全 0 跨租户 3003 且他人列表为空、搜索+标签、`today` 时区边界、软删、评论与事件流、游标翻页、
- [ ] Playwright e2e 未做 批量改状态的部分成功、批量非法流转、看板分列
- [ ] 性能口径(1000 条下首屏 < 300ms)未实测 - [x] 全量 `pytest -q` → **46 passed**;`ruff check` + `ruff format --check` 全 0
- [x] **Playwright 真浏览器 e2e**(`web/e2e/smoke.mjs`,7 项断言全绿):登录 → 建工作区 → 快速录入解析
`#标签`/`!优先级` → 详情抽屉保存并回写列表 → 看板拖拽改状态 → 非法拖拽被拦 → 控制台无报错
- [x] 性能:1000 条任务下 P50 —— 今天 43.1ms / 进行中 42.4ms / 收件箱 42.9ms / 全部 38.4ms /
搜索 45.0ms / 看板 55.4ms(含本机网络与鉴权中间件,是**下限**)
### M1 完成定义(DOD) ### M1 完成定义(DOD)
- [ ] 连续 3 天所有待办都记在里面,没有回到微信/备忘录 —— 只有真实使用能证伪,代码侧无法替代 - [ ] 连续 3 天所有待办都记在里面,没有回到微信/备忘录 —— 只有真实使用能证伪,代码侧无法替代
- [ ] 冷启动到"建完第一个任务"< 90 秒(新账号实测) - [ ] 冷启动到"建完第一个任务"< 90 秒(新账号实测)
- [ ] 1000 条任务下四视图首屏 < 300ms - [x] 1000 条任务下四视图首屏 < 300ms —— 实测 P50 38–55ms,最慢的看板 55.4ms
- [x] 跨租户测试、越权测试全绿(`test_tenant_isolation.py` + `test_items.py::test_cross_tenant_access_denied`) - [x] 跨租户测试、越权测试全绿(`test_tenant_isolation.py` + `test_items.py` 的跨租户与批量用例)
--- ---
+4 -3
View File
@@ -21,10 +21,11 @@
|---|---| |---|---|
| 手机号验证码登录、刷新令牌、幂等请求 | ✅ 已实现并有测试 | | 手机号验证码登录、刷新令牌、幂等请求 | ✅ 已实现并有测试 |
| 工作区 / 成员 / 邀请码加入,跨租户隔离(RLS) | ✅ 同上 | | 工作区 / 成员 / 邀请码加入,跨租户隔离(RLS) | ✅ 同上 |
| 任务:收件箱→待办→进行中→完成、截止时间、标签、评论、事件流、乐观锁 | ✅ 后端 + Web 已通,43 个测试全绿 | | 任务:收件箱→待办→进行中→完成、截止时间、标签、评论、事件流、乐观锁 | ✅ 后端 + Web 已通,46 个测试全绿 |
| Web 构建 | ✅ `npm run build` 通过 | | Web 列表 / 看板拖拽 / 详情侧栏编辑 / 键盘流 / 快速录入语法 | ✅ e2e 7 项断言全绿,构建通过 |
| 1000 条任务下的首屏性能 | ✅ 实测 P50 38–55ms(目标 < 300ms) |
| 安卓端任务列表与详情 | ⚠️ 代码已写,**本机无 JDK/Gradle,未编译验证** |
| 工单、知识库、LLM 能力 | ⬜ M2–M4 | | 工单、知识库、LLM 能力 | ⬜ M2–M4 |
| 安卓端任务列表 | ⬜ |
## 快速开始 ## 快速开始