Files
jiu/deploy/nginx-jiu-ali.conf
T
wangjia 451947c8d1 devops: nginx 运行时 gzip + /app 协商缓存 + /shop SSR 页边缘闸
- gzip on(js/wasm/json/css,图片刻意不压):Web 版首包 12.5MB → ~4.4MB
- /app/ 加 Cache-Control no-cache(etag 304 协商,产物无 hash 不做长缓存)
- 新增 /shop/ location:与 shops API 同参数(jiu_shoplist 2r/s + UA 拦截 + limit_conn 10)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JJ1g8XV1YhhmHRzhwWEW7o
2026-07-07 11:02:21 +08:00

213 lines
9.0 KiB
Plaintext
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 阿里云独立 nginx 版(宿主机直接终结 TLS,不经 pangolin)。
# 与 nginx-jiu.confEC2 pangolin 容器版,listen 127.0.0.1:8445)等价。
# CI deploy-server.sh 的 ali 分支落到 /etc/nginx/conf.d/jiu.conf。
#
# 2026-07-03 备案通过回切:443 ssljiu.51yanmei.com)为唯一正式入口 + 80→443 跳转。
# 8443 明文过渡口已拆(用户拍板:裸 IP 版 client 从未发过,存量客户端都是 https
# 域名 BASE_URLDNS 回切后自动恢复,8443 无人使用)。
# 证书:/etc/nginx/ssl/jiu.51yanmei.com/2026-07-03 从 EC2 letsencrypt 同步,
# 2026-08-28 到期;续期方案=DNS 已指 ali 后 certbot HTTP-01 webroot,见 baize)。
# 后端上游 = 127.0.0.1:8081(非 8080ali 上 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_uaAPI 公开路径用(含空 UA);$jiu_bad_ua_libOG 分享页用(不拦空 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 参数旧语法
listen 443 ssl http2 default_server; # 正式入口(备案后回切 2026-07-03
server_name jiu.51yanmei.com _;
# 证书:certbot webroot 自动续期(certbot-renew.timer 每日 + deploy 钩子 reload
# 2026-07-03 签发,authenticator=webrootdry-run 验证通过)
ssl_certificate /etc/letsencrypt/live/jiu.51yanmei.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/jiu.51yanmei.com/privkey.pem;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers HIGH:!aNULL:!MD5;
ssl_prefer_server_ciphers on;
# 安全响应头(SEC-P02)。注意 nginx add_header 继承规则:location 内有自己的
# add_header 时会整体覆盖 server 级——故 /images、/app/index.html、/downloads
# 三处各自补了 HSTS。
add_header Strict-Transport-Security "max-age=31536000" always;
add_header X-Frame-Options SAMEORIGIN always;
add_header X-Content-Type-Options nosniff always;
client_max_body_size 20m;
# 传输压缩(2026-07-07 公开页提速方案 A′):运行时 gzip,覆盖 Flutter Web 静态
# 大文件(main.dart.js 5.3MB→~1.3MB、canvaskit.wasm 7.2MB→~3MB)与反代 JSON。
# 图片(JPEG/PNG)刻意不在列——本身已是压缩格式,上传侧另有 1200px+q85 压缩。
gzip on;
gzip_comp_level 5;
gzip_min_length 1k;
gzip_vary on;
gzip_proxied any;
gzip_types application/javascript application/wasm application/json
text/css text/plain image/svg+xml application/octet-stream;
# certbot HTTP-01 续期通道(webroot 独立目录,不放 marketing——
# deploy-site 的 rsync --delete 会清掉站点目录里的挑战文件)
location ^~ /.well-known/acme-challenge/ {
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";
add_header Strict-Transport-Security "max-age=31536000" always;
}
# 文件导入接口(超时更长)
location ~ ^/api/v1/import/ {
proxy_pass http://127.0.0.1:8081;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
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;
proxy_read_timeout 30s;
}
# API 反向代理
location ~ ^/(api|health|version) {
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;
}
# Flutter 管理端(仅 /app/ 前缀)
location /app/ {
alias /opt/jiu/web/;
index index.html;
try_files $uri $uri/ /app/index.html;
# 协商缓存(2026-07-07 方案 A′):Flutter 产物文件名无 hash,长缓存会在发版后
# 新旧错配白屏,故走 etag 304——复访零字节,首访靠上面的 gzip。
# 注意 add_header 覆盖继承:补回 server 级安全头。
add_header Cache-Control "no-cache";
add_header Strict-Transport-Security "max-age=31536000" always;
add_header X-Frame-Options SAMEORIGIN always;
add_header X-Content-Type-Options nosniff always;
}
location = /app/index.html {
alias /opt/jiu/web/index.html;
add_header Cache-Control "no-cache, no-store, must-revalidate";
add_header Pragma "no-cache";
add_header Strict-Transport-Security "max-age=31536000" always;
expires 0;
}
# 店铺公开商品列表页 SSR(2026-07-07 新增短链):与 shops API 同一套边缘闸
location ~ ^/shop/ {
if ($jiu_bad_ua_lib) { 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;
}
# 公开商品详情页(扫码跳转)→ 后端 SSR 轻量页(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;
proxy_read_timeout 30s;
}
# 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;
proxy_set_header X-Real-IP $remote_addr;
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";
add_header Strict-Transport-Security "max-age=31536000" always;
autoindex off;
}
# 营销站点兜底
location / {
root /opt/jiu/marketing;
try_files $uri $uri.html $uri/index.html /index.html;
}
}
# 80 → 443 跳转(仅 jiu 域名;51yanmei.com 门户 80 在 51yanmei.conf 自管)。
# ACME 挑战必须在跳转前放行(HTTP-01 走 80)。
server {
listen 80;
server_name jiu.51yanmei.com;
location ^~ /.well-known/acme-challenge/ {
root /var/www/acme;
}
location / {
return 301 https://$host$request_uri;
}
}