fix(covers): 封面地址是签名过期而非防盗链,改为本地缓存
实测推翻了之前的诊断。同一批图:
当天签发的地址 /202610080841/... -> 200,带不带 Referer 都一样
隔天的地址 /202610070837/... -> 403,带不带 Referer 都一样
路径里那段时间戳就是签发时刻。所以这是**过期**,Referer 根本不是那个维度 ——
上一轮加 referrerPolicy 是照着错误结论改的,白改。
修法:
- 采集入库时每轮刷新 cover 地址。原先只在首次入库写一次,旧作品的地址烂在库里,
而且再怎么重跑也修不回来
- 新增 api/monitor/covers.py:把图下载落盘。图一旦落盘就与签名无关,永远可读
- 下载放在 runner 的 Phase 5(事务已提交之后),不放 ingest —— ingest 的文档写明
No network,往里塞网络请求会毁掉它可离线测试这一点
- 新增 GET /api/monitor/covers/{note_id} 取图。这条路由带鉴权,封面不会被匿名读走
- service 返回本地地址优先,没有缓存时才退回远程
- 每轮只补一批(60 张):一次跑几百张既慢又会给图床压力,而旧地址本来就在陆续过期,
分摊到几轮反而更稳
顺带修正 NoteCover 的注释 —— 它写着防盗链,而那个结论已被推翻,留个错的注释比没有更糟。
This commit is contained in:
+31
-1
@@ -22,8 +22,9 @@ from datetime import date, timedelta
|
||||
from typing import Any, Dict, List, Optional
|
||||
|
||||
from fastapi import APIRouter, HTTPException, Query, Response
|
||||
from fastapi.responses import FileResponse
|
||||
|
||||
from ..monitor import notify, qrlogin, report, service
|
||||
from ..monitor import covers, notify, qrlogin, report, service
|
||||
from ..monitor.db import get_session
|
||||
from ..monitor.platforms import PLATFORM_XHS
|
||||
from ..monitor.settings import (
|
||||
@@ -252,6 +253,35 @@ async def clear_cookie_endpoint(platform: str = Query(default=PLATFORM_XHS)):
|
||||
# Scanning once is what makes later unattended runs logged in.
|
||||
|
||||
|
||||
@router.get("/covers/{note_id}")
|
||||
async def get_cover(note_id: str):
|
||||
"""作品封面,从本地缓存读。
|
||||
|
||||
**为什么不让前端直连图床**:图床地址是带签名、会过期的 —— 实测隔天即 403,
|
||||
而且带不带 Referer 都一样,所以那是过期而不是防盗链。本地那份与签名无关。
|
||||
|
||||
这个路由是带鉴权的(整条 monitor 路由都挂了 require_auth),所以封面不会被
|
||||
匿名读走;前端用同源的 <img> 请求会自动带上会话 cookie。
|
||||
"""
|
||||
path = covers.find_cached(note_id)
|
||||
if path is None:
|
||||
raise HTTPException(status_code=404, detail="封面未缓存")
|
||||
|
||||
media_types = {
|
||||
".jpg": "image/jpeg",
|
||||
".png": "image/png",
|
||||
".webp": "image/webp",
|
||||
".gif": "image/gif",
|
||||
".heic": "image/heic",
|
||||
}
|
||||
return FileResponse(
|
||||
path,
|
||||
media_type=media_types.get(path.suffix.lower(), "application/octet-stream"),
|
||||
# 本地文件不会变(note_id 唯一),让浏览器自己缓存,省掉重复请求。
|
||||
headers={"Cache-Control": "private, max-age=86400"},
|
||||
)
|
||||
|
||||
|
||||
@router.post("/login/qr")
|
||||
async def start_qr_login(platform: str = Query(default=PLATFORM_XHS)):
|
||||
"""Open the login page in the CDP browser and return its QR code.
|
||||
|
||||
Reference in New Issue
Block a user