diff --git a/deploy/docker-compose.jiu.yml b/deploy/docker-compose.jiu.yml index 7960d43..8825c8c 100644 --- a/deploy/docker-compose.jiu.yml +++ b/deploy/docker-compose.jiu.yml @@ -1,5 +1,19 @@ version: "3.9" services: + # 限流/登录锁状态外置(后端 REDIS_ADDR=127.0.0.1:6379 时启用)。 + # 刻意无持久化(--save "" + appendonly no):状态丢了秒级重建,省小内存机的 + # fork/写盘开销;noeviction 防锁键被 LRU 逐出;仅绑回环无密码(同宿主 + # payd 容器经 bridge 网络够不着宿主回环),禁 CONFIG 命令兜底。 + jiu-redis: + image: redis:7-alpine + container_name: jiu_redis + restart: always + ports: + - "127.0.0.1:6379:6379" + command: > + redis-server --maxmemory 64mb --maxmemory-policy noeviction + --appendonly no --save "" --rename-command CONFIG "" + jiu-mysql: image: mysql:8.0 container_name: jiu_mysql diff --git a/deploy/nginx-jiu-ali.conf b/deploy/nginx-jiu-ali.conf index 36ffb35..8bd53fc 100644 --- a/deploy/nginx-jiu-ali.conf +++ b/deploy/nginx-jiu-ali.conf @@ -10,7 +10,27 @@ # 后端上游 = 127.0.0.1:8081(非 8080!ali 上 8080 已被 pay 项目 payd 占用; # 对应 /opt/jiu/config/production.env 的 SERVER_PORT=8081)。 limit_req_zone $binary_remote_addr zone=jiu_pub:10m rate=10r/s; +# 店铺公开商品列表专属(主爬取入口,更严) +limit_req_zone $binary_remote_addr zone=jiu_shoplist:10m rate=2r/s; +# per-IP 并发连接(公开路径 20 / 列表 10 / 下载 3) +limit_conn_zone $binary_remote_addr zone=jiu_conn:10m; +limit_conn_zone $binary_remote_addr zone=jiu_dl:10m; limit_req_status 429; +limit_conn_status 429; + +# 抓取库 UA / 空 UA 拦截(2026-07 反爬)。刻意不封 curl/wget(运维自用、改 UA 零成本)、 +# 不封泛 "bot"(微信/飞书等社交分享爬虫 UA 不可控)。 +# $jiu_bad_ua:API 公开路径用(含空 UA);$jiu_bad_ua_lib:OG 分享页用(不拦空 UA, +# 社交平台抓卡片的客户端五花八门,空 UA 误杀不可接受)。 +map $http_user_agent $jiu_bad_ua { + default 0; + "" 1; + ~*(python-requests|python-urllib|scrapy|go-http-client|apache-httpclient|libwww|aiohttp|okhttp|httpx|java/) 1; +} +map $http_user_agent $jiu_bad_ua_lib { + default 0; + ~*(python-requests|python-urllib|scrapy|go-http-client|apache-httpclient|libwww|aiohttp|okhttp|httpx|java/) 1; +} server { # ali nginx 1.24 无 `http2 on;`(1.25.1+),用 listen 参数旧语法 @@ -40,8 +60,11 @@ server { root /var/www/acme; } - # 商品图片静态文件 + # 商品图片静态文件(防盗链:none 放行空 Referer——微信 webview/App 直连 + # 不带 Referer 必须放行;只挡第三方网页热链。回滚=删 valid_referers+if 三行) location ^~ /images/ { + valid_referers none blocked server_names 51yanmei.com *.51yanmei.com; + if ($invalid_referer) { return 403; } alias /opt/jiu/images/; expires 30d; add_header Cache-Control "public, immutable"; @@ -56,9 +79,23 @@ server { proxy_read_timeout 300s; } + # 店铺公开商品列表(主爬取入口):更严的独立限流。 + # 正则 location 按文件顺序首匹配,本块必须在下面 (public|auth) 之前。 + location ~ ^/api/v1/public/shops/ { + if ($jiu_bad_ua) { return 403; } + limit_req zone=jiu_shoplist burst=5 nodelay; + limit_conn jiu_conn 10; + proxy_pass http://127.0.0.1:8081; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_read_timeout 30s; + } + # 未鉴权公开/登录接口:加最外层 per-IP 限流 location ~ ^/api/v1/(public|auth)/ { + if ($jiu_bad_ua) { return 403; } limit_req zone=jiu_pub burst=20 nodelay; + limit_conn jiu_conn 20; proxy_pass http://127.0.0.1:8081; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; @@ -88,9 +125,11 @@ server { expires 0; } - # 公开商品详情页(扫码跳转)→ 后端注入 OG 标签 + # 公开商品详情页(扫码跳转)→ 后端注入 OG 标签(UA 只拦抓取库,不拦空 UA) location ~ ^/product/ { + if ($jiu_bad_ua_lib) { return 403; } limit_req zone=jiu_pub burst=20 nodelay; + limit_conn jiu_conn 20; proxy_pass http://127.0.0.1:8081; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; @@ -99,7 +138,9 @@ server { # Flutter base-href=/app/ 会把 /product/:id 重写为 /app/product/:id location ~ ^/app/product/ { + if ($jiu_bad_ua_lib) { return 403; } limit_req zone=jiu_pub burst=20 nodelay; + limit_conn jiu_conn 20; rewrite ^/app(/product/.+)$ $1 break; proxy_pass http://127.0.0.1:8081; proxy_set_header Host $host; @@ -107,8 +148,11 @@ server { proxy_read_timeout 30s; } - # 桌面客户端安装包下载 + # 桌面客户端安装包下载(限并发+超 10m 后限速,防几个并发拖爆小水管带宽) location /downloads/ { + limit_conn jiu_dl 3; + limit_rate_after 10m; + limit_rate 4m; alias /opt/jiu/downloads/; add_header Content-Disposition "attachment"; add_header Cache-Control "no-cache"; diff --git a/deploy/production.env.template b/deploy/production.env.template index 9555143..51827a8 100644 --- a/deploy/production.env.template +++ b/deploy/production.env.template @@ -13,3 +13,5 @@ STORAGE_WEB_DIR=/opt/jiu/web DB_PASSWORD=CHANGE_ME_DB_PASS # pay 回调/下单 HMAC 共享密钥(与 pay 侧一致,Bitwarden 渲染) PAY_SECRET=CHANGE_ME_PAY_SECRET +# 限流/登录锁状态外置(jiu_redis 容器,仅回环;留空=进程内存模式) +REDIS_ADDR=127.0.0.1:6379