From a49a962161521b28c468944f100b22aa3f91b424 Mon Sep 17 00:00:00 2001 From: wangjia <809946525@qq.com> Date: Sun, 5 Jul 2026 10:42:23 +0800 Subject: [PATCH] =?UTF-8?q?devops:=20=E8=BE=B9=E7=BC=98=E5=B1=82=E5=8A=A0?= =?UTF-8?q?=E5=9B=BA=E2=80=94=E2=80=94nginx=20limit=5Fconn/UA=20=E6=8B=A6?= =?UTF-8?q?=E6=88=AA/=E9=98=B2=E7=9B=97=E9=93=BE/=E4=B8=8B=E8=BD=BD?= =?UTF-8?q?=E9=99=90=E9=80=9F=20+=20jiu=5Fredis=20=E5=AE=B9=E5=99=A8?= =?UTF-8?q?=EF=BC=88todo=20#1/#2/#3=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit nginx:店铺列表专属 2r/s zone、公开路径 limit_conn、抓取库/空 UA 403(OG 页 不拦空 UA 防误杀社交爬虫)、/images 防盗链(none 放行微信 webview)、 /downloads 限并发+限速。compose 新增 jiu_redis(64mb noeviction 无持久化, 仅回环);env 模板加 REDIS_ADDR。 Co-Authored-By: Claude Fable 5 --- deploy/docker-compose.jiu.yml | 14 ++++++++++ deploy/nginx-jiu-ali.conf | 50 ++++++++++++++++++++++++++++++++-- deploy/production.env.template | 2 ++ 3 files changed, 63 insertions(+), 3 deletions(-) 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