前端工具栏原来一个按钮同时查两件事,拆开后每次只查一半,所以重点在 「没查的那一半不能被当成没查到」: - 接口加 scope(both / score / records),默认 both 保持原行为; 只查积分时不产生判定(空串「未判定」,与 unknown「无法判定」分开), 也**不会**自动改业务状态(连判定都没有,凭什么改)。 - 列表的积分与判定各取「最近一条查到过它的」快照:点「查积分」不再把 整页判定徽标冲成未判定,点「查兑换记录」也不再清空积分;但新的 unknown 仍会盖掉旧的 success(抖动结果不许藏)。 - 结果弹窗 / 积分列 Tooltip / 生命周期时间线都按 scope 显示, 不再把「本次没查」显示成「积分 -」「兑换记录 0 条」。 - 新增 12 例测试(TestCollabActivityScopeSplit),并做了 4 次反向验证 (去掉判定分支 / 判定跟随最新 / 积分跟随最新 / 删掉响应字段),全部确认变红。 顺带修掉文档漂移:AGENTS.md 的迁移 head 早就过期(main 实际 20261016_0059、dev 20261008_0053),并补上「两侧已分叉成两个 head、 合并 dev 前必须先 alembic merge」这条;设计文档补 §11.9。
124 lines
9.8 KiB
Markdown
124 lines
9.8 KiB
Markdown
# 协作约定(给 AI 助手 / 协作者)
|
||
|
||
> 用户明确表达过的偏好,加上**已经用事故买来的教训**。每次在这个仓库里干活前先读一遍。
|
||
> 每条都对应一个真实踩过的坑 —— 删之前先确认那个坑不会再犯。
|
||
|
||
## 1. 分支与提交
|
||
|
||
- `main` = **已发布**(生产跑的就是它)。功能开发**从 main 开分支**:
|
||
`git checkout -b feat/<功能名> main`,做完合回 `main`。
|
||
- `dev` 是**历史工作分支,已与 main 分叉**(dev 上有未上线的账号自动分配功能)。
|
||
⚠️ 旧的「推完 main 再 `dev merge --ff-only main`」约定**已作废** ——
|
||
分叉后这么做会失败;以后要同步 dev 只能真合并,并且必须显式确认。
|
||
- **不要改一点就提交、推送。** 小改动先攒着、攒够一批再提交;只有用户明确说
|
||
「提交 / 推送」,或到了一个自然的里程碑(一个完整功能做完、一轮反馈全部改完)
|
||
才 commit / push。一次提交要**整合**相关改动,别拆成一堆碎提交。
|
||
- 远端名不统一:本地是 `gitea`,生产服务器上的同一个仓库叫 `origin`(URL 相同)。
|
||
|
||
## 2. 功能改动
|
||
|
||
- **新功能是「增加」,不是「替换」。** 旧路径要完整保留(例:账号检测保留了手动粘贴,
|
||
新增「按账号选择」;旧的手动输入逻辑一行未动)。做新功能时先想清楚**旧路径会不会被影响**,
|
||
需要扩展的地方用**可选参数 / 并集**,不要改默认行为。
|
||
- 改动要**务实、克制**:不做没人要的重构,不加用不上的抽象。
|
||
- **不要重复展示同一份数据。** 数据库里已有的字段(如 `accounts.points` / `gold_balance` /
|
||
`elite_opened`)就让已有界面显示,查询去**刷新**它们,不要另开一份副本 ——
|
||
同一屏两份会让人分不清哪份是准的。
|
||
- **单项外部调用失败不要让整个流程失败**,但要**如实暴露**(错误写进 `errors` / 返回值
|
||
并在 UI 展示),别让「没查到」被读成「没有」。
|
||
- 旁路动作(写事件、写审计、释放资源)**绝不能带崩主流程**:单独 try/except + 记 warning,
|
||
但要留兜底(如定时作业)把漏掉的补上。
|
||
|
||
## 3. 验证要求(怎么证明你做对了)
|
||
|
||
- **结论要有验证**:能实测就实测(有生产镜像库 `douyu_login_prod` 可只读查),
|
||
关键是**反向验证** —— 把 bug 注入回去,确认用例真的会变红,再还原。
|
||
「跑了一下没报错」不算验证。
|
||
- **提交/推送会跑钩子**,所以本地必须按同样标准自查:
|
||
- `pre-commit`:`ruff format --check`(**只跑 `ruff check` 不够**,格式不对会直接拦下提交)
|
||
+ 前端 `tsc -b`;
|
||
- `pre-push`:上面这些 + **全量 pytest**。
|
||
- **断言要看 `model_dump()`(序列化结果)**,只看属性会假绿(原因见坑 #2)。
|
||
- **别信「测试通过」** —— 用例可能是假绿的(原因见坑 #3)。
|
||
|
||
## 4. 数据库与迁移
|
||
|
||
- 迁移编号**按落地顺序取,不预留**;**已应用的迁移不能改名**。
|
||
- 加列/加表后,**测试库不会自己长出新结构** —— `tests/support/db.py` 用的是
|
||
`Base.metadata.create_all`,它**只建缺失的表,不会 ALTER 已有表**。
|
||
症状是 `Unknown column`,处理办法:重建测试库(DROP/CREATE `douyu_login_test`)。
|
||
- **本地 `dev.sh` 在跑时,改迁移文件会被它自动应用到你正在用的库**。
|
||
所以切分支前先确认「库的迁移版本 == 目标分支的 head」,否则应用会因为
|
||
alembic 找不到 revision **直接起不来**(`run_migrations` 没有兜底,会带崩 lifespan)。
|
||
- **`main` 与 `dev` 的迁移链已经分叉成两个 head**(共同祖先是 `20261008_0052`):
|
||
`main` 走 `20261015_0053` → … → `20261016_0059`,`dev` 走 `20261008_0053`(账号租约)。
|
||
两个 0053 的 **revision id 不同**,所以 alembic 不会报「重复 revision」,
|
||
而是报 **Multiple head revisions are present** —— 合并 `dev` 时必须先补一个
|
||
`alembic merge` 节点,否则 `alembic upgrade head` 会直接失败(部署会卡在迁移那一步)。
|
||
顺带:本地 `douyu_login` 现在停在 `main` 的 head,**切到 dev 分支前必须先回退/重建库**。
|
||
- 本地库:`douyu_login`(dev)、`douyu_login_test`(测试,**不要**拿别的库跑测试套件)、
|
||
`douyu_login_prod`(生产镜像)。
|
||
另有 `douyu_login_prodclone`(停在 `20261003_0040`)与 `douyu_login_rehearsal`
|
||
(停在 `20261004_0049`):是 S1–S7 上线演练留下的**生产克隆快照**(各 15,182 账号),
|
||
代码里没有任何引用 —— 只读参考,别当测试库用。
|
||
|
||
## 5. 部署与生产
|
||
|
||
生产:SSH 别名 `order+douyu_huya`,目录 `/root/live-hub-py`,跑的就是 `main`。
|
||
**完整清单见 `docs/上线清单-S1-S7部署.md`**,核心步骤:
|
||
|
||
```bash
|
||
# ① 部署前(缺一不可):备份 + 采基线
|
||
docker exec live-hub-py-mysql-1 sh -c 'mysqldump ... douyu_login' | gzip > /root/backup_$(date +%m%d_%H%M).sql.gz
|
||
gzip -t /root/backup_*.sql.gz
|
||
# 采 CK 基线:样本解密长度 + 持有 CK 账号数(部署后要一字不差地对比)
|
||
# 查在途任务:正在跑的批次会被重启打断,先看能不能等
|
||
|
||
# ② 执行:先停 → 再迁移 → 再起
|
||
docker compose stop douyu-login
|
||
docker compose run --rm -T douyu-login alembic upgrade head </dev/null
|
||
docker compose up -d douyu-login
|
||
|
||
# ③ 部署后:版本 / CK 基线 / 新接口 / 前端产物 / 日志无 ERROR,逐条核对
|
||
```
|
||
|
||
- 部署前**必须备份**(即使这次零迁移);**CK 检查是唯一不能省的功能性验证**
|
||
(CK 丢了补不回来)。
|
||
- `docker compose build douyu-login` 内部会 `npm run build` 前端 —— 改了前端不用手工构建,
|
||
但部署后要**去产物里确认新代码真在里面**(`grep` 一个新增的界面文案)。
|
||
- 只动 app 容器,**别碰 mysql**(历史上 `up -d --force-recreate` 把 mysql 一起重建,
|
||
每次部署 90 秒 502)。
|
||
|
||
## 6. 汇报
|
||
|
||
- **如实报告,包括自己的错误、误判和局限。** 不确定就说不确定,别猜;
|
||
报错了要主动更正(数字尤其要能复现:说「新增 N 个用例 / 基线 M」之前先跑一遍数出来)。
|
||
- 用中文回答。
|
||
|
||
## 7. 已踩过的坑(速查)
|
||
|
||
| # | 坑 | 应对 |
|
||
| --- | --- | --- |
|
||
| 1 | **别拿不同时刻的快照做对比** —— 曾把相隔两个月的两条记录对比,把「时间差异」当成「账号差异」,得出错误规则写进设计文档 | 对比必须在**同一时刻**;时间跨度大就重新采数 |
|
||
| 2 | **Pydantic 静默丢字段**(本仓库踩过三次,表现各不相同):① 构造函数传了、模型没声明 → `extra='ignore'` 丢掉;② 模型声明了、手写 `@model_serializer` 里没列 → 属性有值、JSON 没有;③ 字段名与 schema 不一致(`elite_bind_status` vs `role_bind_status`) | 断言看 **`model_dump()`**;改完接口先在 Python 里打一遍确认字段真在里面 |
|
||
| 3 | **用例假绿**:缺 `session.commit()` 时 MySQL REPEATABLE READ 让本会话看不到别的会话刚提交的行(脚本改没改都显示没改) | 跨会话断言前先 `rollback()`/`commit()` **结束当前事务**再读;关键用例做反向验证 |
|
||
| 4 | **用「从 A 到 B」的区间删除删代码块很危险**,会连带删掉中间的行(曾把 `**bind_payload(acc),` 一起删掉) | 改完**读回来确认**;能用精确替换就别用区间 |
|
||
| 5 | **`docker compose run` 默认读 stdin**:用 `ssh 'bash -s' < 脚本` 喂进去时,它会把脚本剩下的行吃掉 | 用 `docker compose run -T --rm ... </dev/null` |
|
||
| 6 | **镜像同步脚本会白等 15 分钟**:`pgrep -f 'mysqldump.*douyu_login'` 匹配到了自己所在的 shell,等待循环永不提前退出 | 判定远端进程存活别用会自匹配的 `pgrep -f` 模式 |
|
||
| 7 | **`set -e` + 输出过滤(管道接 `grep -v`)会半途静默中止**:过滤把输出全吃掉时 grep 返回 1,被 `set -e` 当成失败(曾在「已删库、还没导入」处中止,镜像库一度是空的) | 过滤输出后别让 `set -e` 接管退出码;关键步骤显式检查成功 |
|
||
| 8 | **`ruff check` 通过 ≠ 能提交**:钩子查的是 `ruff format` | 提交前本地跑一遍 `.githooks/pre-commit` |
|
||
| 9 | **偷换概念把「账号级状态」当成「全局字段」**:斗鱼商城 `storehome` 的 `count`(库存)确实是全局的,但 **`status` 字段是强账号维度的!`status=1` 就是「已达兑换上限」(限量 1 件已满/已兑换)**。曾把两者混为一谈,误以为商品列表全不可信,跑去依赖早已被官方清空的 `exchangeList`,导致已兑换的账号被错误打上「两个都可兑换」绿标 | 判定高价值资产准入:**以 `storehome.status==1` 为最高裁判**;`exchangeList` 仅做历史展示兜底,绝不可当作否定已拥有的依据 |
|
||
|
||
## 8. 仓库事实(速查)
|
||
|
||
| | |
|
||
| --- | --- |
|
||
| 分支 | `main` = 已发布(生产跑它);`dev` = 已分叉的历史工作分支;新功能从 main 开 `feat/*` |
|
||
| 生产 | SSH `order+douyu_huya`(47.114.114.225),目录 `/root/live-hub-py`,容器 `live-hub-py-douyu-login-1` |
|
||
| 本地服务 | `./dev.sh`(8800 端口,`--reload`);**启动时会自动跑迁移** |
|
||
| 本地 MySQL | 容器 `live-hub-py-mysql-1`,端口 3307;库见 §4 |
|
||
| 生产镜像库 | `douyu_login_prod`(`scripts/refresh_local_prod_mirror.sh` 同步,只读用) |
|
||
| 迁移 head | `main` = `20261016_0059`;`dev` = `20261008_0053`(**已经分叉成两个 head**,见 §4) |
|
||
| 前端 | `tsc -b` 开了 `noUnusedLocals`,死代码/未用导入直接报 TS6133;镜像构建时会 `npm run build` |
|
||
| 测试 | `pytest tests/ -q`(用 `douyu_login_test`);`ruff format` + `ruff check` |
|