272 lines
8.7 KiB
Plaintext
272 lines
8.7 KiB
Plaintext
# ============================================================
|
||
# Cookie + sub_filter 方案
|
||
#
|
||
# 流程:
|
||
# 1. 访问 /0.5.10/ -> 设置 cookie,sub_filter 替换 SW scope
|
||
# 2. 页面加载 /assets/xxx.js -> 根据 cookie 重定向到正确版本
|
||
# 3. SW 注册时 scope 已被替换为 /0.5.10/,实现隔离
|
||
# 4. SW 发起的请求(无 cookie)从 referer 提取版本号
|
||
# ============================================================
|
||
|
||
# 从 referer 中提取版本号(用于 SW 请求,无 cookie 时)
|
||
map $http_referer $referer_version {
|
||
default "";
|
||
~^https?://[^/]+/(?<version>[0-9]+\.[0-9]+\.[0-9]+)/? $version;
|
||
}
|
||
|
||
server {
|
||
listen 7580;
|
||
root /home/aitu/nginx/releases;
|
||
index index.html;
|
||
|
||
# sub_filter 配置(用于替换 SW scope)
|
||
sub_filter_once off;
|
||
sub_filter_types application/javascript text/javascript;
|
||
|
||
# 安全:禁止访问敏感文件
|
||
location ~ /\.(env|git) {
|
||
return 404;
|
||
}
|
||
|
||
# 根目录:显示版本列表
|
||
location = / {
|
||
default_type text/html;
|
||
return 200 '<!DOCTYPE html>
|
||
<html>
|
||
<head>
|
||
<meta charset="utf-8">
|
||
<title>Opentu Releases</title>
|
||
<style>
|
||
body { font-family: -apple-system, BlinkMacSystemFont, sans-serif; max-width: 600px; margin: 50px auto; padding: 20px; }
|
||
h1 { color: #333; }
|
||
ul { list-style: none; padding: 0; }
|
||
li { margin: 10px 0; }
|
||
a { color: #0066cc; text-decoration: none; font-size: 18px; }
|
||
a:hover { text-decoration: underline; }
|
||
.note { color: #666; font-size: 14px; margin-top: 30px; }
|
||
.loading { color: #999; }
|
||
</style>
|
||
</head>
|
||
<body>
|
||
<h1>Opentu Releases</h1>
|
||
<ul id="versions"><li class="loading">Loading...</li></ul>
|
||
<p class="note">每个版本有独立的 Service Worker,同域名下数据互通</p>
|
||
<script>
|
||
fetch("/list/").then(function(r){return r.text()}).then(function(html){
|
||
var el=document.createElement("div");el.innerHTML=html;
|
||
var links=el.querySelectorAll("a"),arr=[];
|
||
for(var i=0;i<links.length;i++){
|
||
var t=links[i].textContent.replace("/","");
|
||
if(t.match(/^[0-9]+\\.[0-9]+\\.[0-9]+/))arr.push(t);
|
||
}
|
||
arr.sort(function(a,b){
|
||
var pa=a.split("."),pb=b.split(".");
|
||
for(var j=0;j<3;j++){if(+pa[j]!=+pb[j])return +pb[j]-+pa[j];}
|
||
return 0;
|
||
});
|
||
var out="";
|
||
for(var k=0;k<arr.length;k++){
|
||
out+="<li><a href=/"+arr[k]+"/>v"+arr[k]+"</a></li>";
|
||
}
|
||
document.getElementById("versions").innerHTML=out;
|
||
});
|
||
</script>
|
||
</body>
|
||
</html>';
|
||
}
|
||
|
||
# 内部接口:返回目录列表
|
||
location = /list/ {
|
||
alias /home/aitu/nginx/releases/;
|
||
autoindex on;
|
||
}
|
||
|
||
# 版本目录入口:无尾斜杠时先规范化为 /x.x.x/
|
||
# 否则旧版本包中的 ./assets 会按浏览器 URL 解析到根 /assets,
|
||
# 依赖 cookie/referer 兜底,移动端偶发动态 import 失败。
|
||
location ~ ^/(?<version>[0-9]+\.[0-9]+\.[0-9]+)$ {
|
||
add_header Set-Cookie "aitu_version=$version; Path=/; Max-Age=86400; SameSite=Lax";
|
||
return 308 /$version/$is_args$args;
|
||
}
|
||
|
||
# 版本目录入口(精确匹配 /x.x.x/)
|
||
# 设置 cookie 并返回 index.html(带 SW 路径替换实现隔离)
|
||
location ~ ^/(?<version>[0-9]+\.[0-9]+\.[0-9]+)/$ {
|
||
# 设置 cookie,有效期 1 天
|
||
add_header Set-Cookie "aitu_version=$version; Path=/; Max-Age=86400; SameSite=Lax";
|
||
|
||
# SW 路径替换(sw.js 路径决定了 scope,改成版本路径实现隔离)
|
||
# navigator.serviceWorker.register('/sw.js') -> register('/0.5.10/sw.js')
|
||
# 这样 SW 的默认 scope 就是 /0.5.10/
|
||
sub_filter "'/sw.js'" "'/$version/sw.js'";
|
||
sub_filter '"/sw.js"' '"/$version/sw.js"';
|
||
|
||
# 返回该版本的 index.html
|
||
try_files /$version/index.html =404;
|
||
}
|
||
|
||
# 版本目录内的子路径(SPA 路由 + 静态资源)
|
||
location ~ ^/(?<version>[0-9]+\.[0-9]+\.[0-9]+)/(?<subpath>.+)$ {
|
||
# 设置 cookie(刷新时保持版本)
|
||
add_header Set-Cookie "aitu_version=$version; Path=/; Max-Age=86400; SameSite=Lax";
|
||
|
||
# SW 路径替换(JS chunk 中可能有 SW 注册代码)
|
||
sub_filter "'/sw.js'" "'/$version/sw.js'";
|
||
sub_filter '"/sw.js"' '"/$version/sw.js"';
|
||
|
||
# 静态资源或 SPA 回退
|
||
try_files /$version/$subpath /$version/index.html;
|
||
|
||
# 缓存策略
|
||
expires 7d;
|
||
add_header Cache-Control "public";
|
||
}
|
||
|
||
# 不带版本的 /assets/ 请求 - 根据 cookie 或 referer 重定向
|
||
location /assets/ {
|
||
# 优先使用 cookie,其次使用 referer 中的版本号
|
||
set $version $cookie_aitu_version;
|
||
if ($version = "") {
|
||
set $version $referer_version;
|
||
}
|
||
if ($version != "") {
|
||
rewrite ^/assets/(.*)$ /$version/assets/$1 last;
|
||
}
|
||
return 404;
|
||
}
|
||
|
||
# 不带版本的 /icons/ 请求
|
||
location /icons/ {
|
||
set $version $cookie_aitu_version;
|
||
if ($version = "") {
|
||
set $version $referer_version;
|
||
}
|
||
if ($version != "") {
|
||
rewrite ^/icons/(.*)$ /$version/icons/$1 last;
|
||
}
|
||
return 404;
|
||
}
|
||
|
||
# 不带版本的 /sw-debug/ 请求(调试页面资源)
|
||
location /sw-debug/ {
|
||
set $version $cookie_aitu_version;
|
||
if ($version = "") {
|
||
set $version $referer_version;
|
||
}
|
||
if ($version != "") {
|
||
rewrite ^/sw-debug/(.*)$ /$version/sw-debug/$1 last;
|
||
}
|
||
return 404;
|
||
}
|
||
|
||
# 不带版本的 /logo/ 请求(Logo 资源)
|
||
location /logo/ {
|
||
set $version $cookie_aitu_version;
|
||
if ($version = "") {
|
||
set $version $referer_version;
|
||
}
|
||
if ($version != "") {
|
||
rewrite ^/logo/(.*)$ /$version/logo/$1 last;
|
||
}
|
||
return 404;
|
||
}
|
||
|
||
# 不带版本的 /product_showcase/ 请求(产品展示图片)
|
||
location /product_showcase/ {
|
||
set $version $cookie_aitu_version;
|
||
if ($version = "") {
|
||
set $version $referer_version;
|
||
}
|
||
if ($version != "") {
|
||
rewrite ^/product_showcase/(.*)$ /$version/product_showcase/$1 last;
|
||
}
|
||
return 404;
|
||
}
|
||
|
||
# 不带版本的 sw.js 请求
|
||
location = /sw.js {
|
||
set $version $cookie_aitu_version;
|
||
if ($version = "") {
|
||
set $version $referer_version;
|
||
}
|
||
if ($version != "") {
|
||
rewrite ^/sw.js$ /$version/sw.js last;
|
||
}
|
||
return 404;
|
||
}
|
||
|
||
# 不带版本的 manifest.json 请求(支持查询参数,如 ?v=0.1.0)
|
||
location ~ ^/manifest\.json(\?.*)?$ {
|
||
set $version $cookie_aitu_version;
|
||
if ($version = "") {
|
||
set $version $referer_version;
|
||
}
|
||
if ($version != "") {
|
||
# rewrite 会自动保留查询参数
|
||
rewrite ^/manifest\.json(\?.*)?$ /$version/manifest.json last;
|
||
}
|
||
return 404;
|
||
}
|
||
|
||
# 不带版本的 version.json 请求
|
||
location = /version.json {
|
||
set $version $cookie_aitu_version;
|
||
if ($version = "") {
|
||
set $version $referer_version;
|
||
}
|
||
if ($version != "") {
|
||
rewrite ^/version.json$ /$version/version.json last;
|
||
}
|
||
return 404;
|
||
}
|
||
|
||
# 不带版本的其他 HTML 文件(如 sw-debug.html)- 根据 cookie 或 referer 重定向
|
||
location ~ ^/([^/]+\.html)$ {
|
||
set $version $cookie_aitu_version;
|
||
if ($version = "") {
|
||
set $version $referer_version;
|
||
}
|
||
if ($version != "") {
|
||
rewrite ^/([^/]+\.html)$ /$version/$1 last;
|
||
}
|
||
return 404;
|
||
}
|
||
|
||
|
||
# 不带版本的 favicon.ico 请求
|
||
location = /favicon.ico {
|
||
set $version $cookie_aitu_version;
|
||
if ($version = "") {
|
||
set $version $referer_version;
|
||
}
|
||
if ($version != "") {
|
||
rewrite ^/favicon.ico$ /$version/favicon.ico last;
|
||
}
|
||
return 404;
|
||
}
|
||
|
||
# 不带版本的 robots.txt 请求
|
||
location = /robots.txt {
|
||
set $version $cookie_aitu_version;
|
||
if ($version = "") {
|
||
set $version $referer_version;
|
||
}
|
||
if ($version != "") {
|
||
rewrite ^/robots.txt$ /$version/robots.txt last;
|
||
}
|
||
return 404;
|
||
}
|
||
|
||
# 不带版本的 sitemap.xml 请求
|
||
location = /sitemap.xml {
|
||
set $version $cookie_aitu_version;
|
||
if ($version = "") {
|
||
set $version $referer_version;
|
||
}
|
||
if ($version != "") {
|
||
rewrite ^/sitemap.xml$ /$version/sitemap.xml last;
|
||
}
|
||
return 404;
|
||
}
|
||
}
|