- mode=auto: 通知列表(getMessageAll+busiMessage/list)发现签到活动 (bot ginkgo 同款过滤, 按 signInId 去重) → 逐个自动签到 - 连续重试: 首次+最多5次间隔3s (对齐 bot MAX_SIGNIN_ATTEMPTS/SIGNIN_BACKOFF), token 失效重登录一次立即重试; 限流中断本轮; 结果聚合 data.results 列表 - 基准同步 (bot ginkgo): 移植限流防护体系(check_response/is_rate_limited/ safe_error/_LoginErrors/_request), parse_course_url courseId 放宽 [0-9A-Za-z]+, commit_sign_in 错误包装, fetch_sign_in_info 增加 check_response 与 sess/token 注入 (auto 模式请求内复用登录态) - 验证: 免凭据单测 + 假凭据 401 线路测试 + bj 真账密只读联调 (notices=20/busi=18/活动12 类型解析正确)
183 lines
13 KiB
Markdown
183 lines
13 KiB
Markdown
# 线上部署与运维(hk)
|
||
|
||
> 记录线上服务器 `hk`(`103.24.218.119:10526`)的实际路径、缓存策略与迁移步骤。
|
||
> 架构与代码结构见根目录 [README](../README.md)。
|
||
|
||
## 对外入口
|
||
|
||
统一入口:`https://openapi.rrrosmontis.icu`(nginx 监听 80/443)
|
||
|
||
| 路径 | 链路 |
|
||
|---|---|
|
||
| `/pixiv/get_image?id=xxx` | `127.0.0.1:9008`(main.py 聚合层)→ `127.0.0.1:9007`(pixiv) |
|
||
| `/bilibili/get_video?bv=xxx` | `127.0.0.1:9008`(main.py 聚合层)→ `127.0.0.1:9006`(bilibili) |
|
||
| `/dufe/ginkgostu_sign_in` | `127.0.0.1:9008`(main.py 聚合层)→ `127.0.0.1:9005`(dufe 签到码查询,支持 GET\|POST;`&mode=auto` 自动签到) |
|
||
| `/jm/get_comic?id=xxx[&range=起-止]` | `127.0.0.1:9008`(main.py 聚合层)→ `127.0.0.1:9004`(jm 漫画下载打包) |
|
||
| `/img-master/`、`/img-original/` | nginx 直接反代 `i.pximg.net`(无本地后端) |
|
||
| `/` | `127.0.0.1:9008`(302 跳转文章页) |
|
||
|
||
- 其余在服站点:`sqy.bu4.ren`、`xxx.aeolianchi.me`
|
||
- 已删除的 vhost:`kim1.work`、`zxx.aeolianchi.me`(2026-09-12)
|
||
- 历史域名 `api.bu4.ren` / `pixiv.bu4.ren` / `apixiv.bu4.ren` 已合并进 `openapi.rrrosmontis.icu`
|
||
|
||
## 进程与端口
|
||
|
||
五个服务**全部只绑 `127.0.0.1`**,对外仅 nginx 的 443/80。
|
||
|
||
| systemd 单元 | 源码 | 监听 | 说明 |
|
||
|---|---|---|---|
|
||
| `openapi.service` | `source/main.py` | `:9008` | 聚合层(aiohttp,纯转发,唯一入口) |
|
||
| `pixiv.service` | `source/pixiv/pixiv.py` | `:9007` | Flask,由 waitress(8 线程)承载 |
|
||
| `bilibili.service` | `source/bilibili/bilibili.py` | `:9006` | aiohttp + yt-dlp |
|
||
| `ginkgo_sign_in.service` | `source/dufe/ginkgostu_sign_in/ginkgo_sign_in.py` | `:9005` | 银杏(DUFE)签到码查询,Flask + waitress(16 线程),`GET\|POST` |
|
||
| `jm.service` | `source/jm/jm.py` | `:9004` | JM 漫画下载打包,aiohttp + jmcomic |
|
||
| `pixiv-token-refresh.service` | `source/pixiv/refresh_token.py` | — | 常驻刷新 Pixiv OAuth token |
|
||
| `bilibili-refresh.service` | `source/bilibili/refresh_cookie.py` | — | 常驻刷新 bilibili cookie |
|
||
|
||
## 目录结构
|
||
|
||
```
|
||
/home/openapi/
|
||
├── source/
|
||
│ ├── main.py 聚合层源码(2026-09-12 从 source/openapi/ 上移至此)
|
||
│ ├── pixiv/
|
||
│ │ ├── pixiv.py 图像代理(Flask)
|
||
│ │ ├── refresh_token.py token 刷新守护
|
||
│ │ └── tokens.json Pixiv OAuth token(敏感,权限 600,不入库)
|
||
│ ├── bilibili/
|
||
│ │ ├── bilibili.py 视频代理(aiohttp + yt-dlp)
|
||
│ │ ├── cookies.txt bilibili cookie(敏感,不入库)
|
||
│ │ ├── refresh_cookie.py cookie 刷新守护
|
||
│ │ └── qr_login*.py 扫码登录工具(cookie 失效时人工重登用)
|
||
│ ├── dufe/
|
||
│ │ └── ginkgostu_sign_in/
|
||
│ │ └── ginkgo_sign_in.py 银杏(DUFE)签到码查询服务(127.0.0.1:9005,GET|POST)
|
||
│ ├── jm/
|
||
│ │ └── jm.py JM 漫画下载打包服务(127.0.0.1:9004)
|
||
│ └── cache/ 运行时缓存(不入库)
|
||
│ ├── pixiv/images pixiv 图片缓存(代码中为 ../cache/pixiv/images)
|
||
│ ├── bilibili/ bilibili 视频缓存(上限 2 GiB / TTL 7 天)
|
||
│ └── jm/ jm 临时 ZIP(TTL 10 分钟,返回后删除)
|
||
├── systemd/ 7 个单元文件的归档副本(与 /etc/systemd/system/ 保持一致)
|
||
├── nginx/ 宝塔导出的历史存档,非生效路径
|
||
├── cert/ SSL 证书
|
||
├── env/ Python 环境(general + env_general.tar.gz 整包)
|
||
└── 备份说明.txt 本文档在服务器上的纯文本副本(随本文档同步维护)
|
||
|
||
/home/images -> /home/openapi/source/cache/pixiv/images 便捷软链
|
||
```
|
||
|
||
## 缓存机制
|
||
|
||
**pixiv(图片)**:`check_cache()` 以 glob 命中即直接回文件;未命中按 `官方 App API → pixiv.re → pixiv.cat` 三级回退抓取,落盘用 `.tmp` + `os.replace` 原子写。
|
||
|
||
**bilibili(视频)**:以 BV 号为键缓存到 `source/cache/bilibili/{BV}.mp4`
|
||
|
||
- 命中即复用、不再下载
|
||
- 未命中才调 yt-dlp,先下载到缓存目录内的隐藏暂存目录 `.dl-XXXX`,成功后原子 rename 落盘
|
||
- 无论成功 / 失败 / 超时都 `rmtree` 该暂存目录
|
||
(2026-09-12 修复:原实现从不清理 tmpdir,每次取视频都会把完整视频永久留在 `/tmp`,属无上限磁盘泄漏)
|
||
- 淘汰策略:单文件 7 天未访问过期 + 总量超过 2 GiB 时按 mtime 从最旧开始删
|
||
- 运维接口:`curl -s 127.0.0.1:9006/bilibili/cache_info`
|
||
|
||
**jm(漫画)**:**非持久临时产物**,移植自 qqbot 的 jm 模块(bj `C:/call/bot/utility/jm.py`)
|
||
|
||
- `GET /jm/get_comic?id=xxx[&range=起-止]`:jmcomic 下载 → `cache/jm/` 内隐藏暂存目录 `.dl-<id>-XXXX` → webp 统一转 jpg 打包 ZIP(`{标题}_{id}_ch{起}-{止}.zip`)→ HTTP 直返
|
||
- 单次上限 30 章(与 qqbot 一致,同时避免超过聚合层 300s 超时);总章节 ≤30 可省略 range 自动全量
|
||
- 打包完成即 `rmtree` 图片暂存目录;ZIP 在返回后 600 秒(`ZIP_TTL_SECONDS`)延时删除
|
||
- TTL 窗口内同名 ZIP 重复请求直接复用并 touch 重新计时;每次请求附带 `_cache_sweep()` 兜底清理过期 ZIP(防进程重启残留)
|
||
- 同一本子串行处理(per-album asyncio.Lock),并发请求同本返回 409
|
||
- hk 直连源站,无需代理(jmcomic 自动更新 API 域名);qqbot 侧的代理轮换 / 上传 cf / QQ 回复逻辑均未带入
|
||
- 运维接口:`curl -s 127.0.0.1:9004/jm/cache_info`
|
||
|
||
## nginx
|
||
|
||
| 文件 | 作用 |
|
||
|---|---|
|
||
| `/etc/nginx/conf.d/openapi.rrrosmontis.icu.conf` | 统一入口站点(`proxy_pass` → `127.0.0.1:9008`) |
|
||
| `/etc/nginx/conf.d/00-websocket.conf` | `$connection_upgrade` map 定义(**必须存在**) |
|
||
| `/home/openapi/nginx/**` | 宝塔导出的历史存档,非生效路径 |
|
||
|
||
## Python 环境
|
||
|
||
- `/home/openapi/env/general` —— 五服务共用(flask / waitress / aiohttp / httpx / yt-dlp / jmcomic / pillow)
|
||
- `/home/openapi/env/env_general.tar.gz` —— 环境整包(迁移用)
|
||
- 注意:pixiv 现由 **waitress** 承载(`pip install waitress`,纯 Python 线程池),不再使用 Flask 开发服务器
|
||
(开发服务器是 thread-per-request 无上限,受控线程池更适合长期运行)
|
||
|
||
## 迁移到新服务器
|
||
|
||
1. **环境**:解压 `env/env_general.tar.gz`,直接使用其 `bin/python`(依赖齐全,含 waitress);若整包过旧需补 `pip install jmcomic`
|
||
2. **源码**:恢复 `source/{main.py,pixiv/,bilibili/,dufe/,jm/}` 到 `/home/openapi/source/`
|
||
3. **systemd**:恢复 `systemd/*.service` 到 `/etc/systemd/system/`,`daemon-reload` + `enable` + `start`
|
||
4. **nginx**:套用 `conf.d/openapi.rrrosmontis.icu.conf`(并确保 `00-websocket.conf` 的 map 存在)
|
||
5. **SSL**:恢复 `cert/<域名>/` 到 `/etc/nginx/cert/<域名>/`
|
||
6. **凭据**:放回 `source/pixiv/tokens.json`(600) 与 `source/bilibili/cookies.txt`
|
||
7. **软链**:`ln -s /home/openapi/source/cache/pixiv/images /home/images`
|
||
8. **验证**:
|
||
```bash
|
||
curl -k "https://openapi.rrrosmontis.icu/pixiv/get_image?id=xxx"
|
||
curl -k "https://openapi.rrrosmontis.icu/bilibili/get_video?bv=BV16a41157Q4"
|
||
curl -k "https://openapi.rrrosmontis.icu/dufe/ginkgostu_sign_in" # 无参应返回 400 JSON
|
||
curl -k "https://openapi.rrrosmontis.icu/jm/get_comic?id=350234" -o jm.zip # 返回 ZIP
|
||
```
|
||
|
||
## 日志
|
||
|
||
```
|
||
/var/log/openapi.log /var/log/pixiv.log /var/log/bilibili.log
|
||
/var/log/ginkgo_sign_in.log /var/log/bilibili_refresh.log /var/log/pixiv_token_refresh.log
|
||
/var/log/jm.log
|
||
```
|
||
|
||
缓存命中/落盘会在 `bilibili.log` 打印 `cache HIT / MISS / STORE`(已加 `flush`,实时可见)。
|
||
|
||
## 变更记录
|
||
|
||
**2026-09-23(dufe mode=auto + bot 基准同步)**
|
||
|
||
- dufe 签到服务新增 **`&mode=auto`**(url 参数可省略):SSO 登录一次后复用登录态 → 拉取通知列表(`GET /student/message/getMessageAll` pageSize=20/readFlag=0 + `POST /student/busiMessage/list`)→ 按 bot ginkgo 同款规则发现活动(仅互动消息、文本含"签到"、jumpUrl 可解析为 `/course/detail/.../signin/<id>/<type>`,query 不入库,按 signInId 去重)→ 按时间序逐个自动签到
|
||
- **连续重试**:每活动首次 + 最多 5 次重试、间隔 3 秒(`MAX_SIGNIN_ATTEMPTS=6` / `SIGNIN_BACKOFF=(3,3,3,3,3)`,对齐 bot);上游错误/登录错时重登录一次立即重试(对齐 bot `_call_sign_in_local` 的 refresh 语义);**除限流外任何未确认成功都消耗重试次数**(对齐 bot `complete_signin`);限流即中断本轮,已完成结果照常返回(`data.rate_limited=true`)
|
||
- **结果聚合**:`data.results` 列表逐活动返回(activityKey/signInId/type/typeName/title/signInCode/status/success/message/attempts),另附 total/succeeded/failed/activities 计数
|
||
- **与 bot ginkgo 非签到码签到逻辑比对后以 bot 为基准同步**:
|
||
- 移植限流防护体系:`RateLimitedError` / `_RATE_MESSAGE` / `_RATE_CODES` / `is_rate_limited` / `_valid_retry_after` / `_LoginErrors` / `check_response` / `safe_error` / `_request`(原实现完全无限流检测)
|
||
- `parse_course_url`:courseId 由 `\d+` 放宽为 `[0-9A-Za-z]+`,报错文案对齐
|
||
- `commit_sign_in`:经 `_request` 包装(`check_response`),失败消息 "request to InTheSign failed: %s" % safe_error(e)(原为 "upstream request failed")
|
||
- `fetch_sign_in_info`:getSignInInfo 响应过 `check_response`;新增 `sess`/`token` 参数注入(auto 模式请求内复用登录态,省去每活动一次 SSO);补齐 "Malformed getSignInInfo response" 非 dict 校验
|
||
- 验证:免凭据单测(发现/过滤正反用例)+ 假凭据 401 线路测试 + bj 真账密**只读**联调(登录 OK,notices=20/busi=18,发现 12 个活动含 1 个普通签到,类型/标题解析正确);未做真实提交(避免与运行中的 bot 轮询交叉签到)
|
||
|
||
**2026-09-23(早些)**
|
||
|
||
- 新增 **jm 漫画下载打包服务**:`source/jm/jm.py` + `jm.service`(`127.0.0.1:9004`,aiohttp + jmcomic,端点 `GET /jm/get_comic?id=xxx[&range=起-止]`)
|
||
- 移植自 qqbot 的 jm 模块(bj `C:/call/bot/utility/jm.py`):保留章节范围解析 / 文件名清洗 / webp→jpg 打包逻辑;去掉 QQ 回复、代理轮换(hk 可直连源站)、上传 cf 文件服务器三部分,改为 HTTP 直返 ZIP
|
||
- ZIP 为临时产物:返回后 600 秒延时删除(`asyncio` 延时任务 + 每次请求 `_cache_sweep()` 兜底清理,防进程重启残留);TTL 窗口内同名请求直接复用
|
||
- 单次上限 30 章(`MAX_CHAPTERS`,与 qqbot 一致;也避免超过聚合层 `proxy()` 的 300s 超时)
|
||
- 在聚合层 `main.py` 注册该端点:新增 `JM_BACKEND`,复用既有 `proxy()`(固定 GET),零改动既有行为
|
||
- nginx **无需改动**:`location /` 兜底已把 `/jm/...` 转发给 `127.0.0.1:9008`(与 dufe 相同)
|
||
- `env/general` 新增依赖 `jmcomic`(2.7.7,`pip install jmcomic`)
|
||
|
||
**2026-09-13**
|
||
|
||
- 新增 **dufe 银杏签到码查询服务**:`source/dufe/ginkgostu_sign_in/ginkgo_sign_in.py` + `ginkgo_sign_in.service`(`127.0.0.1:9005`,Flask + waitress 16 线程,端点 `GET|POST /dufe/ginkgostu_sign_in`)
|
||
- 在聚合层 `main.py` 注册该端点:新增 `DUFE_BACKEND` 与 `proxy_forward()`(保留请求方法/请求体;原 `proxy()` 固定 GET 行为未改动)
|
||
- nginx **无需改动**:`location /` 兜底已把 `/dufe/...` 转发给 `127.0.0.1:9008`
|
||
- 修复 `ginkgo_sign_in.py` 两处缺陷:
|
||
- `_b64_decode` 先做 `urllib.parse.unquote`(iOS 快捷指令会把 base64 的 `=` 编码成 `%3D`,旧实现直接 `binascii.Error`);顺序为先 unquote、再 `" "→"+"`
|
||
- `commit` 判定由 fail-open 改为 fail-closed:须 `isinstance(dict)` 且 `success is True`,否则一律判失败并返回 `msg = "sign-in failed: <原因>"`
|
||
- `parse_course_url` 报错文案改为 `URL is not a sign-in page (missing /signin/<signInId>/<type>): <url>`
|
||
|
||
**2026-09-12**
|
||
|
||
- 端口重排 `9888 / 9088 / 19990 → 9008 / 9007 / 9006`(全部仅 `127.0.0.1`)
|
||
- pixiv 曾误绑 `0.0.0.0:9088` 对外裸奔(绕过 nginx 日志与限流),已收口为 `127.0.0.1`
|
||
- 主入口 `main.py` 由 `source/openapi/` 上移到 `source/`;systemd `WorkingDirectory` 同步
|
||
- pixiv 的 `IMAGE_DIR` 由硬编码 `/home/pixiv/images` 改为相对 `../cache/pixiv/images`,并建 `/home/images` 软链
|
||
- bilibili 新增真缓存(`../cache/bilibili`)并修复 `/tmp` 泄漏,新增 `cache_info` 接口
|
||
- pixiv 改用 waitress 8 线程
|
||
- 删除 `kim1.work` / `zxx.aeolianchi.me` 两个 vhost
|
||
- 清理历史 `.bak` / `__pycache__` / 空日志等冗余文件
|
||
|
||
**2026-09-02** 新增 `pixiv-token-refresh`;补 `/img-original/` 反代(App API 原图路径)
|
||
|
||
**2026-07-31** 三服务迁移落位(Python 环境合并为单一 `general`)
|