diff --git a/README.md b/README.md index 6588e36..0565b69 100644 --- a/README.md +++ b/README.md @@ -55,7 +55,9 @@ source/ ├── bilibili/ # 视频缓存 └── jm/ # jm 临时 ZIP(TTL 10 分钟,返回后删除) nginx/ -└── openapi.rrrosmontis.icu.conf # 统一入口(/pixiv/ /bilibili/ /img-master/ /img-original/,其余路径由 location / 兜底至聚合层) +├── openapi.rrrosmontis.icu.conf # 统一入口(/pixiv/ /bilibili/ /img-master/ /img-original/,其余路径由 location / 兜底至聚合层;含按 IP 限流) +├── 00-limits.conf # 限流 zone(limit_req_zone rate=10r/s、limit_conn_zone、429)——缺失会导致 nginx -t 失败 +└── 00-websocket.conf # $connection_upgrade map 定义——缺失会导致 nginx -t 失败 systemd/ ├── openapi.service # 聚合层 ├── pixiv.service # Pixiv 图片服务(waitress --threads=8) diff --git a/docs/部署与运维.md b/docs/部署与运维.md index 81423d0..7e91e05 100644 --- a/docs/部署与运维.md +++ b/docs/部署与运维.md @@ -113,7 +113,7 @@ 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 存在) +4. **nginx**:套用 `nginx/` 下三个文件到 `/etc/nginx/conf.d/`:`openapi.rrrosmontis.icu.conf` + `00-websocket.conf`(`$connection_upgrade` map,**必须**)+ `00-limits.conf`(限流 zone,**必须**,否则站点 conf 的 `limit_req zone=perip_req` 会使 `nginx -t` 失败),再 `nginx -t && systemctl reload nginx` 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` @@ -137,6 +137,13 @@ ## 变更记录 +**2026-09-23(仓库与线上一致性核对 + 完整性补齐)** + +- 逐文件 md5 核对:源码/main.py、systemd 全部 7 个单元、ginkgo/jm/bilibili/refresh 各模块、站点 nginx conf 均与仓库一致 +- **补录漂移**:`source/pixiv/pixiv.py` 线上版为 2026-09-17 的"图源错误码处理"增强(官方 API 404/401/403/429 分别返回明确错误、`fetch_illust_url` 返回 api_status、不再对确定性错误做无意义备用源重试),此前未入库;本次以线上版为准补录 +- **仓库补齐恢复依赖**:新增 `nginx/00-limits.conf`(限流 zone,站点 conf 引用 `perip_req`/`perip`,缺失会使 `nginx -t` 失败)与 `nginx/00-websocket.conf`(`$connection_upgrade` map,同样必须);迁移步骤第 4 步已同步 +- 凭据快照另存于本机备份目录(`tokens.json`、`cookies.txt`,0600),不入库 + **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//`,query 不入库,按 signInId 去重)→ 按时间序逐个自动签到 diff --git a/nginx/00-limits.conf b/nginx/00-limits.conf new file mode 100644 index 0000000..65d0bfe --- /dev/null +++ b/nginx/00-limits.conf @@ -0,0 +1,8 @@ +limit_conn_zone $binary_remote_addr zone=perip:10m; +limit_conn_zone $server_name zone=perserver:10m; + +# 2026-09-23 新增:按 IP 请求速率限制(轻防滥用档) +# openapi 域名 DNS 直连本机(无 CDN),$binary_remote_addr 即真实客户端 IP +limit_req_zone $binary_remote_addr zone=perip_req:10m rate=10r/s; +limit_req_status 429; +limit_conn_status 429; diff --git a/nginx/00-websocket.conf b/nginx/00-websocket.conf new file mode 100644 index 0000000..0cc431c --- /dev/null +++ b/nginx/00-websocket.conf @@ -0,0 +1,4 @@ +map $http_upgrade $connection_upgrade { + default upgrade; + '' close; +} diff --git a/source/pixiv/pixiv.py b/source/pixiv/pixiv.py index eecca02..2b91f7b 100644 --- a/source/pixiv/pixiv.py +++ b/source/pixiv/pixiv.py @@ -31,9 +31,11 @@ def get_access_token(): def fetch_illust_url(pid, page): + """返回 (original_url, api_status)。api_status: 200 成功 / 404 作品不存在 / + 401|403 认证失败 / 429 限流 / 0 网络等临时错误 / -1 数据异常。""" access_token = get_access_token() if not access_token: - return None + return None, 403 try: resp = requests.get( f"https://{API_HOST}/v1/illust/detail", @@ -47,16 +49,17 @@ def fetch_illust_url(pid, page): verify=False ) if resp.status_code != 200: - return None + return None, resp.status_code data = resp.json() if data.get("error"): - return None + return None, -1 illust = data.get("illust") if not illust: - return None + return None, -1 page_count = illust.get("page_count", 1) if page >= page_count: - return None + # 页码超出:作品存在但请求的分页不存在,按 404 语义处理 + return None, 404 original_url = None if page_count > 1: meta_pages = illust.get("meta_pages") @@ -69,10 +72,12 @@ def fetch_illust_url(pid, page): if not original_url: original_url = illust.get("image_urls", {}).get("large") if not original_url: - return None - return original_url.replace("i.pximg.net", IMAGE_PROXY_DOMAIN) + return None, -1 + return original_url.replace("i.pximg.net", IMAGE_PROXY_DOMAIN), 200 + except requests.RequestException: + return None, 0 # 网络等临时错误:可继续尝试备用源 except Exception: - return None + return None, -1 def make_re_url(pid, page): @@ -146,9 +151,19 @@ def serve_image(path): return send_file(cached_file, mimetype=mime, max_age=3600) urls_to_try = [] - official_url = fetch_illust_url(pid, page) + official_url, api_status = fetch_illust_url(pid, page) if official_url: urls_to_try.append(official_url) + # 明确的作品级错误直接返回,不再做无意义的备用源重试: + # 404 = 作品不存在/已删除/分页不存在(官方 API 已裁决) + # 401/403 = token 失效(refresh 由独立服务负责,这里明确报错) + # 429 = pixiv API 限流(备用源同样是 pixiv 数据,重试无意义) + if api_status == 404: + return jsonify(error="作品不存在或已删除", code="not_found"), 404 + if api_status in (401, 403): + return jsonify(error="pixiv 认证失败,token 可能失效", code="auth_error"), 502 + if api_status == 429: + return jsonify(error="pixiv API 限流,请稍后再试", code="rate_limited"), 503 urls_to_try.append(make_re_url(pid, page)) urls_to_try.append(make_cat_url(pid, page)) @@ -157,7 +172,7 @@ def serve_image(path): if img_path: return send_file(img_path, mimetype=mime, max_age=3600) - return jsonify(error="Failed to fetch image from all sources"), 500 + return jsonify(error="所有图源均取图失败,请稍后再试", code="all_sources_failed"), 500 @app.route('/health')