277 lines
12 KiB
Plaintext
277 lines
12 KiB
Plaintext
server {
|
||
listen 80;
|
||
server_name localhost;
|
||
root /usr/share/nginx/html;
|
||
index index.html;
|
||
server_tokens off;
|
||
|
||
add_header X-Content-Type-Options "nosniff" always;
|
||
add_header X-Frame-Options "SAMEORIGIN" always;
|
||
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
|
||
|
||
# API 目录封面等同源静态资源:必须优先于 SPA fallback,避免返回 index.html。
|
||
# 文件来自 frontend public/api-directory/,构建后位于 /usr/share/nginx/html/api-directory/。
|
||
location ^~ /api-directory/ {
|
||
try_files $uri =404;
|
||
expires 1y;
|
||
add_header Cache-Control "public, max-age=31536000, immutable" always;
|
||
# nginx 的 add_header 按层级整体覆盖继承:自带 add_header 的 location 必须重复安全头
|
||
add_header X-Content-Type-Options "nosniff" always;
|
||
add_header X-Frame-Options "SAMEORIGIN" always;
|
||
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
|
||
types {
|
||
image/avif avif;
|
||
image/gif gif;
|
||
image/jpeg jpg jpeg;
|
||
image/png png;
|
||
image/svg+xml svg;
|
||
image/webp webp;
|
||
}
|
||
default_type application/octet-stream;
|
||
access_log off;
|
||
}
|
||
|
||
# 开启gzip压缩
|
||
gzip on;
|
||
gzip_vary on;
|
||
gzip_min_length 1024;
|
||
gzip_types text/plain text/css text/xml text/javascript application/x-javascript application/xml+rss application/javascript application/json;
|
||
|
||
# WebSocket 反代(聊天 ws/chat、扫码登录 ws/qr-status → Granian + Channels)
|
||
location /ws/ {
|
||
proxy_pass http://backend:8000;
|
||
proxy_http_version 1.1;
|
||
proxy_set_header Upgrade $http_upgrade;
|
||
proxy_set_header Connection "upgrade";
|
||
proxy_set_header Host $host;
|
||
proxy_set_header X-Real-IP $remote_addr;
|
||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||
proxy_set_header X-Forwarded-Proto $scheme;
|
||
proxy_read_timeout 3600s;
|
||
proxy_send_timeout 3600s;
|
||
}
|
||
|
||
# API 反代 → Granian(ADRF 异步视图)
|
||
# C-01:精确收窄到后端真实存在的 API 前缀。SPA 路由(/open-api*、/post/*、
|
||
# /user-home*、/articles、/learn 列表页等)一律落到下面的 `location /` 回退,
|
||
# 不再被宽正则劫持。旧分享链接(/article/:id、/user/:id、/api-detail/* 等)
|
||
# 由前端 SPA 内 <Navigate> 重定向承接,nginx 直接放行到 index.html 即可。
|
||
location ~ ^/api/ {
|
||
proxy_pass http://backend:8000;
|
||
proxy_http_version 1.1;
|
||
proxy_set_header Host $host;
|
||
proxy_set_header X-Real-IP $remote_addr;
|
||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||
proxy_set_header X-Forwarded-Proto $scheme;
|
||
proxy_set_header Content-Type $http_content_type;
|
||
proxy_read_timeout 60s;
|
||
proxy_send_timeout 60s;
|
||
client_max_body_size 50M;
|
||
}
|
||
|
||
# 后端 user.urls(/user/<action>/):SPA 已迁到 /user-home*,旧 /user/:id 分享
|
||
# 链接由前端重定向承接;数字 id 形态放行到 SPA,其余代理到后端
|
||
location ~ ^/user/\d+/?$ {
|
||
try_files $uri $uri/ /index.html;
|
||
}
|
||
location ~ ^/user/ {
|
||
proxy_pass http://backend:8000;
|
||
proxy_http_version 1.1;
|
||
proxy_set_header Host $host;
|
||
proxy_set_header X-Real-IP $remote_addr;
|
||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||
proxy_set_header X-Forwarded-Proto $scheme;
|
||
proxy_set_header Content-Type $http_content_type;
|
||
proxy_read_timeout 60s;
|
||
proxy_send_timeout 60s;
|
||
client_max_body_size 50M;
|
||
}
|
||
|
||
# 后端 bug.urls 仅 /bug/reports/*;SPA /bug 与 /bug-detail 放行
|
||
location ~ ^/bug/reports/ {
|
||
proxy_pass http://backend:8000;
|
||
proxy_http_version 1.1;
|
||
proxy_set_header Host $host;
|
||
proxy_set_header X-Real-IP $remote_addr;
|
||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||
proxy_set_header X-Forwarded-Proto $scheme;
|
||
proxy_read_timeout 60s;
|
||
proxy_send_timeout 60s;
|
||
client_max_body_size 50M;
|
||
}
|
||
|
||
# 后端 article.urls 仅 articles|my-articles|comments;SPA /post/*、/articles 放行
|
||
location ~ ^/article/(articles|my-articles|comments)/ {
|
||
proxy_pass http://backend:8000;
|
||
proxy_http_version 1.1;
|
||
proxy_set_header Host $host;
|
||
proxy_set_header X-Real-IP $remote_addr;
|
||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||
proxy_set_header X-Forwarded-Proto $scheme;
|
||
proxy_read_timeout 60s;
|
||
proxy_send_timeout 60s;
|
||
client_max_body_size 50M;
|
||
}
|
||
|
||
# 后端 learn.urls 子段;SPA /learn 列表页放行
|
||
location ~ ^/learn/(courses|chapters|my-courses|my-progress|materials|favorites|cdn)/ {
|
||
proxy_pass http://backend:8000;
|
||
proxy_http_version 1.1;
|
||
proxy_set_header Host $host;
|
||
proxy_set_header X-Real-IP $remote_addr;
|
||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||
proxy_set_header X-Forwarded-Proto $scheme;
|
||
proxy_read_timeout 60s;
|
||
proxy_send_timeout 60s;
|
||
client_max_body_size 50M;
|
||
}
|
||
|
||
# 后端 message.urls(/message/*);SPA /messages(多 s)天然不命中
|
||
location ~ ^/message/ {
|
||
proxy_pass http://backend:8000;
|
||
proxy_http_version 1.1;
|
||
proxy_set_header Host $host;
|
||
proxy_set_header X-Real-IP $remote_addr;
|
||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||
proxy_set_header X-Forwarded-Proto $scheme;
|
||
proxy_read_timeout 60s;
|
||
proxy_send_timeout 60s;
|
||
client_max_body_size 50M;
|
||
}
|
||
|
||
# 后端 chat.urls 子段;SPA /chat 本体放行
|
||
location ~ ^/chat/(friend-requests|friends|users|conversations|messages|upload|favorite-stickers)/ {
|
||
proxy_pass http://backend:8000;
|
||
proxy_http_version 1.1;
|
||
proxy_set_header Host $host;
|
||
proxy_set_header X-Real-IP $remote_addr;
|
||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||
proxy_set_header X-Forwarded-Proto $scheme;
|
||
proxy_read_timeout 60s;
|
||
proxy_send_timeout 60s;
|
||
client_max_body_size 50M;
|
||
}
|
||
|
||
# 后端 tool.urls(/tool/*);SPA /tool-detail 放行(不以 /tool/ 开头)
|
||
location ~ ^/tool/ {
|
||
proxy_pass http://backend:8000;
|
||
proxy_http_version 1.1;
|
||
proxy_set_header Host $host;
|
||
proxy_set_header X-Real-IP $remote_addr;
|
||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||
proxy_set_header X-Forwarded-Proto $scheme;
|
||
proxy_read_timeout 60s;
|
||
proxy_send_timeout 60s;
|
||
client_max_body_size 50M;
|
||
}
|
||
|
||
# 后端 history.urls 仅 records/;SPA /history 列表页放行
|
||
location ~ ^/history/records/ {
|
||
proxy_pass http://backend:8000;
|
||
proxy_http_version 1.1;
|
||
proxy_set_header Host $host;
|
||
proxy_set_header X-Real-IP $remote_addr;
|
||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||
proxy_set_header X-Forwarded-Proto $scheme;
|
||
proxy_read_timeout 60s;
|
||
proxy_send_timeout 60s;
|
||
client_max_body_size 50M;
|
||
}
|
||
|
||
# 后端 search 查询串形态(GET /search/?q=);裸 /search(页)与无参 /search/ 放行到 SPA。
|
||
# location 按 URI 匹配、看不见 query string,故用 $args 分流。
|
||
location = /search/ {
|
||
error_page 418 = @spa_fallback;
|
||
if ($args = "") { return 418; }
|
||
proxy_pass http://backend:8000;
|
||
proxy_http_version 1.1;
|
||
proxy_set_header Host $host;
|
||
proxy_set_header X-Real-IP $remote_addr;
|
||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||
proxy_set_header X-Forwarded-Proto $scheme;
|
||
proxy_read_timeout 60s;
|
||
proxy_send_timeout 60s;
|
||
}
|
||
location ~ ^/search/(suggestions|hot-keywords)/ {
|
||
proxy_pass http://backend:8000;
|
||
proxy_http_version 1.1;
|
||
proxy_set_header Host $host;
|
||
proxy_set_header X-Real-IP $remote_addr;
|
||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||
proxy_set_header X-Forwarded-Proto $scheme;
|
||
proxy_read_timeout 60s;
|
||
proxy_send_timeout 60s;
|
||
}
|
||
|
||
# 后端 app(changelog)/ logs / shorturl
|
||
location ~ ^/(app|logs/api|shorturl)/ {
|
||
proxy_pass http://backend:8000;
|
||
proxy_http_version 1.1;
|
||
proxy_set_header Host $host;
|
||
proxy_set_header X-Real-IP $remote_addr;
|
||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||
proxy_set_header X-Forwarded-Proto $scheme;
|
||
proxy_read_timeout 60s;
|
||
proxy_send_timeout 60s;
|
||
client_max_body_size 50M;
|
||
}
|
||
|
||
# 短链跳转 /s/<code>/:仅短码形态代理,其余(/settings、/shorturl-detail)放行
|
||
location ~ ^/s/[^/]+/?$ {
|
||
proxy_pass http://backend:8000;
|
||
proxy_http_version 1.1;
|
||
proxy_set_header Host $host;
|
||
proxy_set_header X-Real-IP $remote_addr;
|
||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||
proxy_set_header X-Forwarded-Proto $scheme;
|
||
proxy_read_timeout 60s;
|
||
proxy_send_timeout 60s;
|
||
}
|
||
|
||
# Django admin / swagger / redoc / i18n / 测试页
|
||
# 生产默认仅内网可达;如需公网管理入口请走独立域名 + IP 白名单 + MFA。
|
||
location /admin/ { allow 127.0.0.1; allow 10.0.0.0/8; allow 172.16.0.0/12; allow 192.168.0.0/16; deny all; proxy_pass http://backend:8000; proxy_set_header Host $host; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; client_max_body_size 5M; }
|
||
location /swagger { allow 127.0.0.1; allow 10.0.0.0/8; allow 172.16.0.0/12; allow 192.168.0.0/16; deny all; proxy_pass http://backend:8000; proxy_set_header Host $host; }
|
||
location /swagger.json { allow 127.0.0.1; allow 10.0.0.0/8; allow 172.16.0.0/12; allow 192.168.0.0/16; deny all; proxy_pass http://backend:8000; proxy_set_header Host $host; }
|
||
location /redoc/ { allow 127.0.0.1; allow 10.0.0.0/8; allow 172.16.0.0/12; allow 192.168.0.0/16; deny all; proxy_pass http://backend:8000; proxy_set_header Host $host; }
|
||
location /i18n/ { proxy_pass http://backend:8000; proxy_set_header Host $host; }
|
||
location /test/ { allow 127.0.0.1; allow 10.0.0.0/8; allow 172.16.0.0/12; allow 192.168.0.0/16; deny all; proxy_pass http://backend:8000; proxy_set_header Host $host; }
|
||
|
||
# 带哈希指纹的构建产物:永久强缓存(immutable)
|
||
location ~* ^/assets/.*\.(js|css|png|jpg|jpeg|gif|webp|avif|ico|svg|woff|woff2|ttf|eot)$ {
|
||
expires 1y;
|
||
add_header Cache-Control "public, immutable";
|
||
add_header X-Content-Type-Options "nosniff" always;
|
||
add_header X-Frame-Options "SAMEORIGIN" always;
|
||
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
|
||
access_log off;
|
||
}
|
||
|
||
# 其他静态资源(public/ 目录:图片/字体等无指纹文件):适中缓存
|
||
location ~* \.(png|jpg|jpeg|gif|webp|ico|svg|woff|woff2|ttf|eot)$ {
|
||
expires 7d;
|
||
add_header Cache-Control "public";
|
||
add_header X-Content-Type-Options "nosniff" always;
|
||
add_header X-Frame-Options "SAMEORIGIN" always;
|
||
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
|
||
access_log off;
|
||
}
|
||
|
||
# index.html 不缓存,保证发版后立即生效
|
||
location = /index.html {
|
||
add_header Cache-Control "no-cache, no-store, must-revalidate";
|
||
add_header X-Content-Type-Options "nosniff" always;
|
||
add_header X-Frame-Options "SAMEORIGIN" always;
|
||
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
|
||
}
|
||
|
||
# SPA 路由回退(放在最后,避免拦截上面的 API 请求)
|
||
location / {
|
||
try_files $uri $uri/ /index.html;
|
||
}
|
||
|
||
location @spa_fallback {
|
||
try_files $uri $uri/ /index.html;
|
||
}
|
||
}
|