CI / backend (push) Canceled after 0s
用户反馈"app 端登录不上去"。查下来是两个叠在一起的问题,都不是"后台没开": 1) 主清单设了 android:networkSecurityConfig(白名单只有 10.0.2.2 / 127.0.0.1 / localhost), 而它的优先级高于 debug 清单里的 android:usesCleartextTraffic="true"。 所以真机连 http://192.168.8.x:8100 会被系统拦成 CLEARTEXT communication not permitted, 表现成登录无反应;模拟器因为地址在白名单里,一切正常,很容易误判。 → 新增 app/src/debug/res/xml/network_security_config.xml,debug 放开明文; release 仍只有白名单,线上必须 HTTPS。 2) 后端地址编译期写死 BuildConfig.API_BASE = http://10.0.2.2:8100,那是模拟器专用地址。 → 登录页加「服务器:… / 改地址」:可改、可「测试连接」、存在 TokenStore 里, 改一次就记住,不用重新打包。地址缺失协议时自动补 http://。 顺带把网络异常翻成人话:以前 OkHttp 抛 IOException 原样冒到界面, 现在统一成「连不上 <地址> —— 具体该去查什么」,并区分了 "10.0.2.2 是模拟器地址"、"127.0.0.1 在手机上指的是手机自己"两种情况; 连不上时自动展开地址面板。 后端这边没有代码改动,但真机要用 --host 0.0.0.0 启动(原来只监听 127.0.0.1), 已写进 DEVELOPMENT.md 与 android/README.md,附防火墙放行命令。 实测:模拟器内把地址改成 http://192.168.8.151:8100 → 测试连接「连得通」→ 取码(开发环境验证码回显)→ 登录成功进入工作区列表。
230 lines
10 KiB
Markdown
230 lines
10 KiB
Markdown
# 开发环境(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` |
|
||
|
||
### 团队共享开发库(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。
|