Files
openapi/docs/部署与运维.md
T
RRRosmontis 9b9c501fa9 feat(dufe): 新增 mode=auto 自动签到,非签到码逻辑以 bot 为基准同步
- 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 类型解析正确)
2026-09-23 11:53:12 +08:00

13 KiB
Raw Blame History

线上部署与运维(hk)

记录线上服务器 hk(103.24.218.119:10526)的实际路径、缓存策略与迁移步骤。 架构与代码结构见根目录 README。

对外入口

统一入口: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. 验证:
    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)