67aa9be3dae535a71d449009e951a9dfb8f3c223
- 00-limits.conf 新增 limit_req_zone perip_req 10r/s + 429 状态码 (zone 定义属服务器配置, 不入库) - 站点 conf 443 server 块挂 limit_req burst=20 nodelay + limit_conn perip 10 - 域名直连无 CDN, binary_remote_addr 即真实 IP; 后端全绑 127.0.0.1 无死角 - 验证: 30 并发部分 429, 桶恢复后正常
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;&mode=auto 时拉取通知列表自动签到(与 bot ginkgo 同逻辑,连续重试,结果列表聚合返回) |
| 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
部署
- Python 环境:各服务共用一个环境,需含
flask、waitress、aiohttp、httpx、yt-dlp、jmcomic、pillow - 凭据:
- 复制
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 域名)
- 复制
- 服务:将
systemd/*.service安装到/etc/systemd/system/,然后systemctl daemon-reload && systemctl enable --now openapi pixiv bilibili ginkgo_sign_in jm bilibili-refresh pixiv-token-refresh - nginx:将
nginx/openapi.rrrosmontis.icu.conf放入/etc/nginx/conf.d/(需存在$connection_upgrade的 map 定义),nginx -t && systemctl reload nginx - 软链(可选):
ln -s <部署根>/source/cache/pixiv/images /home/images - 定时任务:无 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
- dufe 签到服务新增
mode=auto:拉取通知列表(getMessageAll + busiMessage/list)→ 按 bot ginkgo 同款规则发现签到活动(互动消息 + 文本含"签到" + 可解析课程链接,按 signInId 去重)→ 逐个自动签到;连续重试(首次 + 最多 5 次,间隔 3s,对齐 botMAX_SIGNIN_ATTEMPTS=6 / SIGNIN_BACKOFF;token 疑似失效时重登录一次立即重试,对齐 bot refresh 语义);所有结果聚合为data.results列表返回(每活动含 success/message/attempts/title/signInCode 等) - 非签到码签到逻辑与 bot ginkgo 比对后以 bot 为基准同步:移植限流防护体系(
check_response/is_rate_limited/safe_error/_LoginErrors)、parse_course_url的 courseId 放宽至[0-9A-Za-z]+、commit_sign_in的错误包装、fetch_sign_in_info的 check_response 与外部登录态注入(sess/token参数,auto 模式请求内复用同一登录态)
- dufe 签到服务新增
- 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/,systemdWorkingDirectory同步为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)
Languages
Python
100%