Files
DadaoTask/2.Design/DEVELOPMENT.md
T
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

10 KiB
Raw Blame History

开发环境(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. 起基础设施

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. 后端

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

自检:

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. 前端

cd web
npm install
npm run dev          # http://127.0.0.1:5180,/api 代理到 8100
npm run build        # 产物必须在仓库 HEAD 上构建,禁止本地构建后上传

6. 测试

cd backend
pytest -q                          # 必须全绿才能提交
ruff check . && ruff format --check .

测试会真连数据库、真跑迁移、真走 HTTP 栈。跑之前确保 Postgres 在 5433。

隔离测试(tests/test_tenant_isolation.py)是本仓库最重要的一组用例,改动任何租户表都必须重跑它。

真浏览器 e2e

需要后端(8100)与前端(5180)都在跑:

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 条下的首屏耗时

cd backend && python scripts/bench_views.py     # 连开发库,会自己造 1000 条

7. 知识库向量库(独立实例)

知识库的语义检索需要一个带 pgvector 的 PostgreSQL,它不是业务库:

cd deploy
docker-compose -f docker-compose.vector.yml up -d      # 127.0.0.1:5434,库名 dadao_knowledge

然后在 backend/.env 里打开:

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% 是这三件事之一,按顺序查:

① 后端有没有监听所有网卡

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 之后:

Invoke-RestMethod http://192.168.8.151:8100/health     # 换成你自己的局域网 IP

② 手机上填的地址对不对

APK 里默认写的是 http://10.0.2.2:8100(模拟器映射宿主机的地址),真机连不上它。 在登录页 → 服务器:… → 改地址里填电脑的局域网 IP,点「测试连接」看到「连得通」再登录。 地址存在本地,改一次即可,不需要重新打包。

③ 防火墙

Windows 上 python.exe 首次监听端口时如果没弹「允许访问」,手机连过来的包会被丢。 以管理员运行一次:

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 后用后台作业前台跑:
    .\.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。