fnsvr-01 的 PostgreSQL 在 8432 端口(不是 5432),实测 PostgreSQL 17.6 / Alpine。 已在该实例建好独立库与角色: - dadaotask(开发)/ dadaotask_test(测试),owner 都是 dadao_owner - dadao_owner 迁移用;dadao_app 应用用(NOSUPERUSER NOBYPASSRLS) - 两个库都已 ALTER SCHEMA public OWNER TO dadao_owner + REVOKE CREATE FROM PUBLIC + GRANT 给 dadao_app 代码改动: - config 增加 test_database_url / test_migration_database_url,并给出 resolved_database_url() / resolved_migration_database_url():ENV=test 且有测试库时优先用测试库, 否则退回开发库(CI 里直接给 DATABASE_URL 即可,行为不变)。 - db.py / alembic/env.py / tests/conftest.py 统一走这两个解析方法。 原先 conftest 清表用的是 settings.migration_database_url,而应用连的是测试库, 一旦测试库与开发库不同,TRUNCATE 就清到另一个库上——表现为"测试库脏数据越攒越多、 列表类断言随机失败"。这是本次真踩到的坑,不是假想。 - tests/conftest.py 保留 E402 豁免(环境变量必须在 import app 模块前写好)。 验证: - 远程 dadaotask_test 上 pytest 33 passed,跑完表内 0 行(确认清表打在同一个库) - 远程 dadaotask 上 alembic upgrade head 成功,/health 与 /version 正常,db_role 为 dadao_app 且三项全 false - 本机便携 PG 16.4 上同样 33 passed(两个 PG 大版本都过) - ruff check / format 干净 已知限制:该实例没有 pgvector(只有 pg_trgm / pgcrypto),M3 向量检索前必须先定扩展来源, 已写进 2.Design/DEVELOPMENT.md。
6.0 KiB
开发环境(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)是本仓库最重要的一组用例,改动任何租户表都必须重跑它。
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 有输出再继续 |
迁移报 cannot insert multiple commands into a prepared statement |
asyncpg 走预备语句,一次只能一条 SQL | 多语句 DDL 拆成每语句一次 op.execute() |
团队共享开发库(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 成功,pytest 33 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=offStart-Process:父进程一退出,子进程就被带走;pg_ctl在本机会报could not create restricted token: error code 87。 - 装包只能走 HTTP 镜像:PyPI
http://mirrors.aliyun.com/pypi/simple/, npmhttp://mirrors.huaweicloud.com/repository/npm/。 - pip 必须加
--no-cache-dir:不加时写缓存会卡死(无输出、CPU 空转,实测 25 分钟无进展)。 - 安装类命令一律写日志文件再 tail,不要用管道截断输出,否则看不到进度也不知道卡在哪。
npm install要加--ignore-scripts:沙箱不允许 npm 的 lifecycle 脚本派生进程(EPERM spawn)。
8. 提交前自检清单
pytest全绿,ruff无告警。- 新增租户表时:
workspace_id NOT NULL+ RLS 策略 + 跨租户测试,三者齐全。 - 迁移文件提交前人工复核(
alembic revision --autogenerate只做草稿)。 - 改了接口就更新
2.Design/API.md(M1 建立该文档)。 - 推送到远端,生产部署后
curl /version校验 commit。