Files
live-hub-py/README.md
T
yml2213 c3fd299f3a test(db): 测试改用真实 MySQL,方言差异不再漏到生产
生产是 MySQL 8、测试是 SQLite,两者的方言差异在本地完全测不出来。今天为此
付出了两次代价:

1. `ORDER BY ck_checked_at ASC NULLS FIRST` 在 SQLite 上完全正常,本地 258 条
   全绿,一部署到生产第一次巡检就 1064 语法错误(MySQL 不支持 NULLS FIRST);
2. P1b 自动重登的结算复用调度器事务读取,在 SQLite 下看不出问题,但在 MySQL 的
   REPEATABLE READ 下读到的是批次开始前的快照 —— 10 条实际成功的重登全部被记成
   失败。SQLite 的读一致性与 MySQL 不同,这类问题跑一万遍也复现不出来。

改动:
- 新增 tests/support/db.py:测试专用引擎/会话工具,连接串从 .env 读取,
  默认指向独立的 douyu_login_test 库;
- 新增 tests/conftest.py:会话级建好表结构,每个用例前后清空所有表做隔离;
- 根 conftest.py 接管连接串设置:它在 pytest 收集阶段最先执行,在这里把
  DATABASE_URL 指向测试库,应用模块首次 import 拿到就是 MySQL,不依赖任何
  import 顺序(原先这里是 sqlite:// 兜底,改造后会变成「先建 SQLite engine
  再打补丁换成 MySQL」,顺序一变就静默跑错库);
- 21 个测试文件的 setup 样板统一改用共享测试引擎;
- 两个迁移测试改为在临时库 douyu_login_mig_<随机> 上跑完整 alembic upgrade head,
  跑完 drop —— 迁移脚本本身也终于在 MySQL 上被验证;
- dev.sh 与 README 的过时说明一并更新(原先写着「不需要 MySQL / .env 配置」;
  「测试各自建 SQLite 内存库」)。

安全与易用:
- 测试库名与开发库同名时**直接拒绝运行**(clear_tables 会 DELETE 所有表,
  一旦 TEST_MYSQL_DATABASE 被指到 douyu_login 就是数据灾难);
- 连不上 MySQL 时给出可操作提示(先 ./dev.sh 起库 / 用 TEST_MYSQL_DATABASE 覆盖),
  而不是抛一段 SQLAlchemy 原始堆栈。

验收:
- 全量 301 条通过,连跑两次结果一致(隔离有效),耗时约 12.5 秒(原 SQLite 约 3.6 秒);
- 单文件独立运行通过(不依赖全量执行顺序);
- `grep -rn sqlite tests/ conftest.py` 已无实际用法;
- **方言确实生效**:临时把一条查询改回 `NULLS FIRST`,13 条测试立刻变红(含运行期
  失败),还原即恢复;这条由 test_generated_sql_is_mysql_compatible 长期守着。

代价(已在 README 与规划附录 C.2 记明):跑测试前必须先有 MySQL,pre-push 钩子
同理;CI 需要 service container。
2026-10-02 21:22:08 +08:00

159 lines
7.2 KiB
Markdown
Raw 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.
# 直播账号运营后台
斗鱼 / 虎牙账号运营 Web 平台:批量登录拿 Cookie、按工作台跑活动任务、应用宝点券充值。版本见 `pyproject.toml`(当前 0.3.0)。
## 能做什么
- **斗鱼**:极验滑块自动过验、邮箱验证码、批量登录与 CK 检测/重登;精英宝典 / 电竞手册 / 和平小店三套工作台(绑定、开通、充值、兑换);鱼翅可由微信二维码或供应商直充。
- **虎牙**:Cookie / 密码 / App / 短信登录、自动注册、设备绑定、精英宝典、浏览器人工兑换;网页运行 Cookie 与 App 原始凭据(`raw_cookie`)分离,可从原始凭据重建网页 Cookie。
- **应用宝**:和平精英点券扫码登录、选区服角色、支付二维码。Worker 合在 Web 容器内,只监听 `127.0.0.1:8810`。
- **权限**:超级管理员 / 运营 / 客服;可按权限点覆盖。客服只看分配给自己的账号,虎牙侧默认只开放浏览器兑换。
- **安全**:密码、邮箱、Cookie、代理密钥等敏感字段落盘加密;操作写审计日志。
## 项目结构
```
live-hub-py/
├── core/ # 协议客户端(不经过 Web 也可单独调用)
│ ├── douyu/ # 登录、活动、鱼翅直充、代理
│ ├── huya/ # App/Web/短信登录、WSS、设备指纹、网页 Cookie 补齐
│ └── geetest/ # 极验 v3 滑块
├── web/
│ ├── backend/ # FastAPI + SQLAlchemy + Alembic
│ │ ├── routers/ # HTTP / WebSocket
│ │ ├── services/ # 批次 runner(进程内线程池)
│ │ └── migrations/ # 数据库迁移
│ ├── frontend/ # React + Ant Design + Vite
│ └── extension/ # 虎牙浏览器兑换助手
├── services/yyb-worker/ # 应用宝协议运行时(随主镜像部署)
├── tests/ # pytest(跑在真实 MySQL 测试库上)
├── docs/ # 协议与联调文档(结论以「虎牙 Cookie 路线状态」为准)
├── deploy.sh / dev.sh # 生产部署 / 本机热更新
└── docker-compose.yml # 应用容器 + MySQL 8.4
```
## 快速开始
```bash
# Docker 部署(推荐)
./deploy.sh
```
访问 `http://localhost:8800`(compose 默认把容器 8800 映到宿主机 `APP_PORT`,缺省 8000)。默认管理员 `admin / admin123`,生产环境务必改掉。
本机热更新用 `./dev.sh`(或 `./deploy.sh dev`)。它会拉起绑定 `127.0.0.1:8810` 的开发 Worker,后端自动连该地址;Worker 数据在 `data/yyb-worker-dev/`。`Ctrl+C` 会一并停掉。应用日志按日期写到 `logs/app-YYYY-MM-DD.log`,可用 `./dev.sh logs` 跟踪当天文件。
```bash
./deploy.sh # 构建并部署
./deploy.sh logs # 当天持久化应用日志
./deploy.sh logs container # Docker 容器输出(含 Worker)
./deploy.sh stop
./deploy.sh restart
./deploy.sh reset # 重置所有数据(需二次确认)
```
## 运行时与密钥
生产是 **单个应用容器 + 内网 MySQL 8.4**。数据库不映射公网;本机 `./dev.sh` 通过 `127.0.0.1:3307` 连 compose 里的 MySQL。
在项目 `.env` 里准备:
```dotenv
MYSQL_PASSWORD=...
MYSQL_ROOT_PASSWORD=...
JWT_SECRET_KEY=随机密钥
APP_ENCRYPTION_KEY=随机密钥 # 敏感字段加密,更换后历史密文无法解密
YYB_WORKER_KEY=随机密钥 # 本机回环访问 Worker
```
生成随机密钥:
```bash
python3 -c 'import secrets; print(secrets.token_urlsafe(32))'
```
可选:
- `FISH_FIN_RECHARGE_*`:鱼翅供应商直充,只走环境变量,不入库、不回前端。
- `MAIL_ROUNDCUBE_URL`:邮件验证码服务;不填则用代码默认 Roundcube 地址。
- `CORS_ORIGINS`、`COOKIE_SECURE`:HTTPS 部署时按实际填写。
应用宝扫码图和支付二维码只经后台授权接口返回。服务器只需要本仓库,不必再部署 `js_reverse`。Worker 任务数据在 `data/yyb-worker-jobs/`。
## 角色权限
| 角色 | 权限 |
|---|---|
| 超级管理员 | 全部功能 + 用户管理 + 权限分配 + 审计日志 |
| 运营 | 账号管理、批量登录、代理、Cookie、双平台任务、应用宝 |
| 客服 | 仅查看分配给自己的账号;虎牙只保留浏览器兑换 |
## 登录与任务流
斗鱼登录:
```
获取极验参数 → 滑块识别 → 邮箱验证码 → 提交 → Cookie
```
斗鱼工作台按 `handbook_scope`(elite / esports / peace)隔离任务和支付二维码。虎牙精英宝典走活动 WSS;缺少网页设备字段时先 `wsLaunch` 补齐,再把补齐后的 Cookie 回写账号。
进程重启会把内存里丢失的 pending/running 任务标成中断,避免前端被假活跃批次锁死。
## 运行测试
```bash
uv run --group dev pytest # 或 ./dev.sh test
uv run --group dev pytest tests/test_cookie_operations.py
./dev.sh format tests conftest.py
./dev.sh format-check tests conftest.py
./dev.sh type-check # 渐进式 Pyright
docker compose --profile test run --rm test
```
测试跑在**真实 MySQL** 上(不再使用 SQLite):生产是 MySQL,SQLite 与它有方言差异,
曾出现「`ORDER BY ... NULLS FIRST` 本地全绿、生产直接 1064」的事故。因此:
- 先起数据库:`./dev.sh`(docker compose,MySQL 映射到 `127.0.0.1:3307`);
- 测试库默认 `douyu_login_test`,可用 `TEST_MYSQL_DATABASE` 覆盖;**库名与开发库相同时
会被 `tests/support/db.py` 拒绝运行**(每个用例前后会清空所有表,防误清);
- `tests/conftest.py` 在导入应用模块前把连接串指向测试库,并把每用例的表清空交给 autouse fixture;
- 迁移链冒烟见 `tests/test_migrations.py`:它在临时库 `douyu_login_mig_<随机>` 上跑完整
`alembic upgrade head`,跑完 drop。
兑换、下单、支付属于写操作,默认用 mock 做契约测试,不打真实支付。
启用提交/推送门禁(改过前端会跑 TypeScript,与 Docker `npm run build` 的 `tsc -b` 一致):
```bash
./scripts/install-git-hooks.sh
./dev.sh frontend-check # 单独跑前端 TypeScript
```
- **pre-commit**:暂存的 Python 文件做 Ruff 格式检查;改了 `web/frontend/` 则跑 `tsc -b`
- **pre-push**:上述检查 + 全量 pytest
- Pyright 默认不开(噪音大):`TYPECHECK=1 git push`
- 紧急绕过:`git push --no-verify` 或 `SKIP_TESTS=1 git push`
## 技术栈
- 后端:FastAPI + SQLAlchemy + MySQL 8.4(测试同样跑 MySQL)+ JWT
- 前端:React + Ant Design + Vite
- 核心:Python 3.12 + OpenCV + ONNX + pycryptodome + curl-cffi
- 迁移:Alembic
- 部署:Docker Compose
## 代理
在「代理配置」页填写代理 API(JSON)和白名单。移动出口 IP 漂移时,系统会保留多个出口 IP。示例:
```json
{"code":0,"success":"true","msg":"","data":[{"IP":"1.2.3.4","Port":8080}]}
```
白名单错误时接口会带上当前出口 IP,系统按平台凭证自动同步。
## 文档
协议与联调笔记在 `docs/`。当前虎牙 Cookie 行为以 [虎牙 Cookie 路线状态](docs/HUYA_COOKIE路线状态.md) 为准。