Files
dadao fbdf54cb4c
CI / backend (push) Canceled after 0s
feat(web): 富文本编辑器换成 Vditor,不再自研轮子
上一版我自己写了 textarea + 正则工具栏,看着能用,一到粘贴图片、
中文输入法、撤销栈就露馅——这几样正是成熟组件积累的部分。按要求改成品。

- 编辑器开关 def: RichTextEditor = Vditor 4.0.1(MIT,ir 模式)
  * 运行时资源自托管:web/scripts/vendor-vditor.mjs 把 lute/i18n/icons
    同步到 public/vditor,predev/prebuild 自动执行,不走 unpkg CDN
    (内网会一直卡在加载)
- 只读渲染换 markdown-it + dompurify,删掉自写的正则渲染器
- 附件直链的令牌只在内存里存在:落库前 stripAttachmentTokens,
  渲染/装载前补回(<img> 带不了 Authorization 头,raw?token= 是必要折中)
- Vditor 不会替我们插入 upload.handler 的返回值(只当提示语 tip.show),
  所以上传完自己 insertMD

验证:pytest 70 passed;e2e richtext 13 项、smoke 7、tickets 8、
knowledge 7 全绿;tsc 0 错;主包 299 kB/gzip 75 kB,Vditor 独立
chunk 385 kB 按需加载。
2026-10-29 02:30:10 +08:00

232 lines
10 KiB
Markdown
Raw Permalink 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)
> 目标:新人在 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`)是本仓库最重要的一组用例,改动任何租户表都必须重跑它。
### 真浏览器 e2e
需要后端(8100)与前端(5180)都在跑:
```bash
cd web
node e2e/smoke.mjs # 登录→建任务→详情保存→看板拖拽→非法流转被拒,7 项断言
node e2e/tickets.mjs # 双人:升级工单→受理→提交→验收→原任务回流,8 项断言
node e2e/knowledge.mjs # 沉淀成知识→待核对→发布,7 项断言
```
用本机缓存的 Chromium(`playwright-core`,不下载浏览器),截图落在 `web/e2e/artifacts/`。
> e2e 抓的都是"手动点几下看不出来"的问题:字段缺失导致整页白屏、请求竞态导致的空列表、
> 点错按钮的 404。加功能时**先想一条能失败的断言**,再动手写页面。
### 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. 让安卓真机连上本地后端
手机连不上后端,90% 是这三件事之一,按顺序查:
**① 后端有没有监听所有网卡**
```bash
cd backend
.venv\Scripts\python.exe -m uvicorn app.main:app --host 0.0.0.0 --port 8100
```
`--host 127.0.0.1`(uvicorn 默认)只对本机可见,手机必然连不上。用 `0.0.0.0` 之后:
```powershell
Invoke-RestMethod http://192.168.8.151:8100/health # 换成你自己的局域网 IP
```
**② 手机上填的地址对不对**
APK 里默认写的是 `http://10.0.2.2:8100`(模拟器映射宿主机的地址),真机连不上它。
在**登录页 → 服务器:… → 改地址**里填电脑的局域网 IP,点「测试连接」看到「连得通」再登录。
地址存在本地,改一次即可,不需要重新打包。
**③ 防火墙**
Windows 上 python.exe 首次监听端口时如果没弹「允许访问」,手机连过来的包会被丢。
以**管理员**运行一次:
```powershell
New-NetFirewallRule -DisplayName "DadaoTask backend 8100" -Direction Inbound `
-Action Allow -Protocol TCP -LocalPort 8100 -Profile Private
```
**关于明文 HTTP**:Android 从 9 开始默认禁止明文流量。主清单里配了
`network_security_config`(只放行 `10.0.2.2/127.0.0.1/localhost`),而
`android:usesCleartextTraffic` 一旦和它同时存在就不再生效——这就是"真机怎么都登不上、
但模拟器正常"的根因。所以 debug 变体单独放了一份
`android/app/src/debug/res/xml/network_security_config.xml` 允许明文;
release 仍走白名单,线上域名必须 HTTPS。
## 9. 常见坑
| 现象 | 原因 | 处理 |
|---|---|---|
| `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` 有输出再继续 |
| 迁移报 `cannot insert multiple commands into a prepared statement` | asyncpg 走预备语句,一次只能一条 SQL | 多语句 DDL 拆成每语句一次 `op.execute()` |
| 源文件里的中文变成 `宸ュ崟鍩?` | 用 PowerShell `Get-Content`/`Set-Content` 批量改文件:读时按 GBK 解码,写回 UTF-8,不可逆 | **含中文的源文件只能用编辑器/`edit` 工具改**;真要写脚本,必须显式 `[IO.File]::ReadAllText($p, [Text.Encoding]::UTF8)` + `WriteAllText(..., UTF8Encoding($false))` |
| `npm` 报 `无法加载文件 npm.ps1,因为在此系统上禁止运行脚本` | PowerShell 执行策略 | 用 `npm.cmd`,或直接 `node node_modules/vite/bin/vite.js build` |
| 富文本编辑器一直转圈、没有工具栏 | `web/public/vditor` 缺失(它被 gitignore,由脚本生成) | `cd web && npm run vendor`,或走一遍 `predev`/`prebuild` |
| 编辑器能开但排版是源码、不是所见即所得 | `lute.min.js` 没自托管成功,Vditor 退回纯文本模式 | 打开控制台看 `/vditor/dist/js/lute/lute.min.js` 是不是 404 |
### 团队共享开发库(fnsvr-01:8432)
`fnsvr-01`(`192.168.8.160`)上有一个 **PostgreSQL 17.6**(Alpine),端口是 **8432**(不是 5432),
已为本项目建好独立库与角色:
| 库 | 用途 | owner |
|---|---|---|
| `dadaotask` | 开发库 | `dadao_owner` |
| `dadaotask_test` | 测试库(**测试会 TRUNCATE 所有表,绝不能用开发库**) | `dadao_owner` |
角色:`dadao_owner`(迁移用,库 owner)与 `dadao_app`(应用用,非 superuser / 无 BYPASSRLS)。
连接串见 `backend/.env.example` 里的注释行,直接拷进 `backend/.env` 即可。
已验证:在该库上 `alembic upgrade head`(到 0003)成功,`pytest` **57 passed**(服务端 PG 17.6 vs 本机 16.4,两版都过)。
> ⚠️ **这台服务器没有 pgvector**(`pg_available_extensions` 里只有 `pg_trgm`、`pgcrypto`)。
> M3 知识库要做向量检索的话,必须先解决扩展来源:装 pgvector(需要服务器 root 装 OS 包)/
> 给知识库单独跑一个带 pgvector 的容器 / 或退回「纯全文检索 + trigram」先不上语义检索。
> 这件事必须在写 `chunk` 表和检索接口之前定下来,否则要返工。
### 本机特例(2026-10 实测,换机器可跳过)
这台开发机的 Docker Desktop 起不来,且外网 TLS 被拦,实际用的是:
- **便携 PostgreSQL 16.4** 放在 `.local/pg`(已在 `.gitignore`),`initdb` 后用**后台作业**前台跑:
```powershell
.\.local\pg\pgsql\bin\postgres.exe -D .\.local\pg\pgdata -p 5433 `
-c listen_addresses=127.0.0.1 -c fsync=off -c synchronous_commit=off
```
不要用 `Start-Process`:父进程一退出,子进程就被带走;`pg_ctl` 在本机会报
`could not create restricted token: error code 87`。
- **装包只能走 HTTP 镜像**:PyPI `http://mirrors.aliyun.com/pypi/simple/`,
npm `http://mirrors.huaweicloud.com/repository/npm/`。
- **pip 必须加 `--no-cache-dir`**:不加时写缓存会卡死(无输出、CPU 空转,实测 25 分钟无进展)。
- **安装类命令一律写日志文件再 tail**,不要用管道截断输出,否则看不到进度也不知道卡在哪。
- `npm install` 要加 `--ignore-scripts`:沙箱不允许 npm 的 lifecycle 脚本派生进程(`EPERM spawn`)。
## 10. 提交前自检清单
1. `pytest` 全绿,`ruff` 无告警。
2. 新增租户表时:`workspace_id NOT NULL` + RLS 策略 + 跨租户测试,三者齐全。
3. 迁移文件提交前人工复核(`alembic revision --autogenerate` 只做草稿)。
4. 改了接口就更新 `2.Design/API.md`(M1 建立该文档)。
5. 推送到远端,生产部署后 `curl /version` 校验 commit。