RRRosmontis 9ecbbbc6d8 feat(jm): 新增 JM 漫画下载打包服务(9004,移植自 qqbot jm 模块)
- source/jm/jm.py:aiohttp + jmcomic,GET /jm/get_comic?id=xxx[&range=起-止]
  下载至 cache/jm 隐藏暂存目录 → webp 转 jpg 打包 ZIP → HTTP 直返 → 600s 延时删除
  单次上限 30 章;per-album 锁防并发;cache_sweep 兜底清理;/jm/cache_info 运维接口
- source/main.py:聚合层注册 /jm/get_comic(新增 JM_BACKEND,复用 proxy())
- systemd/jm.service:独立系统服务(Restart=always,日志 /var/log/jm.log)
- README / 部署与运维文档同步更新
2026-09-23 10:58:48 +08:00

OpenAPI

统一接口服务。线上入口:https://openapi.rrrosmontis.icu

聚合层把 pixiv 图片服务、bilibili 视频服务、dufe 银杏签到码查询服务与 jm 漫画下载服务收敛到单一对外入口。五个后端进程全部只监听 127.0.0.1,对外仅由 nginx 暴露 80/443——即"一个入口、零暴露端口"。

架构

公网 443/80  nginx (server_name openapi.rrrosmontis.icu)
  ├── /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)
  ├── /jm/get_comic?id=xxx         -> 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 跳转文章页)

端口与进程

服务 监听 入口源码 说明
openapi 127.0.0.1:9008 source/main.py(aiohttp) 聚合层,纯转发,唯一入口
pixiv 127.0.0.1:9007 source/pixiv/pixiv.py(Flask + waitress) 图片代理
bilibili 127.0.0.1:9006 source/bilibili/bilibili.py(aiohttp + yt-dlp) 视频代理
dufe 签到 127.0.0.1:9005 source/dufe/ginkgostu_sign_in/ginkgo_sign_in.py(Flask + waitress 16 线程) 银杏(DUFE)签到码查询,GET|POST /dufe/ginkgostu_sign_in
jm 127.0.0.1:9004 source/jm/jm.py(aiohttp + jmcomic) 漫画下载打包(移植自 qqbot jm 模块),GET /jm/get_comic?id=xxx[&range=起-止]
pixiv-token-refresh — source/pixiv/refresh_token.py token 刷新守护(每 30–60 分钟随机)
bilibili-refresh — source/bilibili/refresh_cookie.py cookie 刷新守护(每 50–70 分钟随机)

历史端口 9888 / 9088 / 19990 已于 2026-09-12 废弃,全部改为上述 900x。 pixiv 早期误绑 0.0.0.0:9088 对公网裸奔(绕过 nginx 的日志与限流),现已收口为 127.0.0.1。

结构

source/
├── main.py                       # 聚合层 (127.0.0.1:9008),五条路由:/ /pixiv/get_image /bilibili/get_video /dufe/ginkgostu_sign_in /jm/get_comic
├── pixiv/
│   ├── pixiv.py                  # Pixiv 图片服务 (127.0.0.1:9007)
│   ├── refresh_token.py          # Pixiv OAuth token 刷新守护
│   └── tokens.template.json      # token 模板(实际 tokens.json 不入库)
├── bilibili/
│   ├── bilibili.py               # bilibili 视频服务 (127.0.0.1:9006)
│   ├── refresh_cookie.py         # cookie 定时刷新守护
│   ├── qr_login.py               # 扫码登录获取 cookie
│   └── qr_login_fast.py
├── 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              # 图片缓存
    ├── bilibili/                 # 视频缓存
    └── jm/                       # jm 临时 ZIP(TTL 10 分钟,返回后删除)
nginx/
└── openapi.rrrosmontis.icu.conf   # 统一入口(/pixiv/ /bilibili/ /img-master/ /img-original/,其余路径由 location / 兜底至聚合层)
systemd/
├── openapi.service                # 聚合层
├── pixiv.service                  # Pixiv 图片服务(waitress --threads=8)
├── bilibili.service               # bilibili 视频服务
├── bilibili-refresh.service       # cookie 刷新守护
├── pixiv-token-refresh.service    # Pixiv token 刷新守护
├── ginkgo_sign_in.service         # 银杏(DUFE)签到码查询服务
└── jm.service                     # JM 漫画下载打包服务
docs/
└── 部署与运维.md                   # 线上服务器的路径、缓存、迁移步骤与变更记录

缓存机制

pixiv(图片):check_cache() 以 glob 命中即直接回文件;未命中按 官方 App API → pixiv.re → pixiv.cat 三级回退抓取,落盘用 .tmp + os.replace 原子写。

缓存目录不再硬编码,由模块位置推导:

IMAGE_DIR = (Path(__file__).resolve().parent / "../cache/pixiv/images").resolve()
IMAGE_DIR.mkdir(parents=True, exist_ok=True)

即 source/cache/pixiv/images;线上常用 /home/images 软链指向它,便于访问。

bilibili(视频):以 BV 号为键缓存到 source/cache/bilibili/{BV}.mp4

  • 命中即直接 FileResponse,不再调用 yt-dlp
  • 未命中才下载,且先落到缓存目录内的隐藏暂存目录 .dl-XXXX,成功后 os.replace 原子落盘
  • 无论成功 / 失败 / 超时都会 shutil.rmtree(tmpdir) —— 早期实现只清理 cookie 临时文件、从不清理视频暂存目录,导致每次取视频都把完整视频永久留在 /tmp(无上限磁盘泄漏),已修复
  • 淘汰策略:单文件 CACHE_TTL_SECONDS(默认 7 天)未访问即过期;总量超过 CACHE_MAX_BYTES(默认 2 GiB)时按 mtime 从最旧开始删除
  • 命中/落盘会在日志打印 cache HIT / MISS / STORE(已 flush=True,实时可见)

运维接口(只绑本机):

curl -s 127.0.0.1:9006/bilibili/cache_info
# {cache_dir, cache_max_bytes, cache_ttl_seconds, count, total_bytes, free_bytes, files[]}

jm(漫画):非持久缓存,与 bilibili 不同——ZIP 仅作临时产物

  • 请求 id(+ 可选 range=起-止,单次 ≤30 章)→ jmcomic 下载到 cache/jm/ 内隐藏暂存目录 .dl-<id>-XXXX → webp 统一转 jpg 打包 ZIP({标题}_{id}_ch{起}-{止}.zip)→ HTTP 返回
  • 打包完成即 rmtree 图片暂存目录;ZIP 在返回后 ZIP_TTL_SECONDS(默认 600 秒)延时删除
  • TTL 窗口内重复请求同一 ZIP 直接复用并重新计时;每次请求附带 _cache_sweep() 兜底清理过期 ZIP(防进程重启残留)
  • 同一本子串行处理(per-album asyncio.Lock),并发请求同本返回 409
  • 运维接口:curl -s 127.0.0.1:9004/jm/cache_info

部署

  1. Python 环境:各服务共用一个环境,需含 flask、waitress、aiohttp、httpx、yt-dlp、jmcomic、pillow
  2. 凭据:
    • 复制 source/pixiv/tokens.template.json 为 source/pixiv/tokens.json(chmod 600)并填入实际 pixiv token
    • 放置 source/bilibili/cookies.txt(name=value 格式),或运行 qr_login.py 扫码生成
    • jm 无需凭据,但要求部署机可直连源站(jmcomic 会自动更新 API 域名)
  3. 服务:将 systemd/*.service 安装到 /etc/systemd/system/,然后 systemctl daemon-reload && systemctl enable --now openapi pixiv bilibili ginkgo_sign_in jm bilibili-refresh pixiv-token-refresh
  4. nginx:将 nginx/openapi.rrrosmontis.icu.conf 放入 /etc/nginx/conf.d/(需存在 $connection_upgrade 的 map 定义),nginx -t && systemctl reload nginx
  5. 软链(可选):ln -s <部署根>/source/cache/pixiv/images /home/images
  6. 定时任务:无 cron/timer——两个刷新守护均为常驻循环(内部随机间隔),systemd Restart=always 兜底

验证

curl -k "https://openapi.rrrosmontis.icu/pixiv/get_image?id=147113746"
curl -k "https://openapi.rrrosmontis.icu/bilibili/get_video?bv=BV16a41157Q4"
curl -kI "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
curl -s 127.0.0.1:9006/bilibili/cache_info
curl -s 127.0.0.1:9004/jm/cache_info

端口暴露自检(五个后端都应"不可达",仅 443/80 通):

for p in 9004 9005 9006 9007 9008; do timeout 3 bash -c "cat < /dev/null > /dev/tcp/<host>/$p" 2>/dev/null \
  && echo "$p 可连通(异常)" || echo "$p 不可达(正常)"; done

版本要点

  • 2026-09-23:新增 jm 漫画下载打包服务(source/jm/jm.py,aiohttp + jmcomic,127.0.0.1:9004,端点 GET /jm/get_comic?id=xxx[&range=起-止]),移植自 qqbot 的 jm 模块(去掉 QQ/代理轮换/上传 cf 逻辑,改为 HTTP 直返 ZIP;hk 可直连源站无需代理)。ZIP 为临时产物:返回后 600 秒延时删除,TTL 窗口内同名请求直接复用。聚合层 main.py 注册路由(新增 JM_BACKEND,复用既有 proxy()),nginx 无需改动(location / 兜底)
  • 2026-09-13:新增 dufe 银杏签到码查询服务(source/dufe/ginkgostu_sign_in/ginkgo_sign_in.py,Flask + waitress 16 线程,127.0.0.1:9005,端点 GET|POST /dufe/ginkgostu_sign_in),并在聚合层 main.py 注册该路由——为此新增 proxy_forward()(保留请求方法与请求体,原 proxy() 保持固定 GET 不变,零改动既有行为)。同期修复该服务两处缺陷:base64 参数补 urllib.parse.unquote(兼容 iOS 快捷指令的 %3D 编码),commit 判定改为 fail-closed(须 commit.success is True 才算签到成功)
  • 2026-09-12
    • 端口重排 9888/9088/19990 → 9008/9007/9006,全部仅绑 127.0.0.1;pixiv 由 0.0.0.0:9088 收口
    • 主入口 main.py 从 source/openapi/ 上移到 source/,systemd WorkingDirectory 同步为 source/
    • pixiv 缓存路径由硬编码 /home/pixiv/images 改为相对 ../cache/pixiv/images
    • pixiv 改由 waitress 线程池承载(--threads=8),替代 Flask 开发服务器(thread-per-request 无上限)
    • bilibili 新增 BV 真缓存(../cache/bilibili,2 GiB / 7 天淘汰),并修复视频暂存目录永不清理导致的 /tmp 泄漏
    • 新增 /bilibili/cache_info 运维接口;缓存日志加 flush=True
  • 2026-09-02:新增 pixiv token 刷新守护;补 /img-original/ 反代(App API 原图路径)
  • 2026-07-31:三服务迁移落位(Python 环境合并为单一 general)
S
Description
No description provided
Readme
223 KiB
Languages
Python 100%