From 2451f98b32693deae3f81b7a633716996444d2ea Mon Sep 17 00:00:00 2001 From: rohow Date: Wed, 13 Aug 2025 19:36:52 +0800 Subject: [PATCH] =?UTF-8?q?feat(cdn):=20cdn=E5=8A=9F=E8=83=BD=E6=8F=90?= =?UTF-8?q?=E4=BA=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/apps/halo/ingress-static.yaml | 26 -- apps/infra/net/nginx/captcha.html | 301 ----------------- apps/infra/net/nginx/certificate-cdn.yaml | 18 -- .../net/nginx/configmap-static-update.sh | 21 ++ ...figmap-html.yaml => configmap-static.yaml} | 237 +++++++++++--- apps/infra/net/nginx/helmchart.yaml | 33 +- apps/infra/net/nginx/ingress-cdn.yaml | 69 ++++ apps/infra/net/nginx/static/captcha.html | 302 ++++++++++++++++++ apps/infra/net/nginx/static/pwa-cdn.js | 59 ++++ apps/infra/net/nginx/static/sw-cdn.js | 97 ++++++ 10 files changed, 772 insertions(+), 391 deletions(-) delete mode 100644 apps/infra/net/nginx/captcha.html delete mode 100644 apps/infra/net/nginx/certificate-cdn.yaml create mode 100644 apps/infra/net/nginx/configmap-static-update.sh rename apps/infra/net/nginx/{configmap-html.yaml => configmap-static.yaml} (60%) create mode 100644 apps/infra/net/nginx/ingress-cdn.yaml create mode 100644 apps/infra/net/nginx/static/captcha.html create mode 100644 apps/infra/net/nginx/static/pwa-cdn.js create mode 100644 apps/infra/net/nginx/static/sw-cdn.js diff --git a/apps/apps/halo/ingress-static.yaml b/apps/apps/halo/ingress-static.yaml index 1adb9fd..c3099a5 100644 --- a/apps/apps/halo/ingress-static.yaml +++ b/apps/apps/halo/ingress-static.yaml @@ -28,29 +28,3 @@ spec: name: halo port: number: 80 - ---- -apiVersion: networking.k8s.io/v1 -kind: Ingress -metadata: - name: halo-static-cdn - namespace: apps - annotations: - nginx.ingress.kubernetes.io/use-regex: "true" - nginx.ingress.kubernetes.io/rewrite-target: /$2 - # 添加允许跨域请求 - nginx.ingress.kubernetes.io/enable-cors: "true" - nginx.ingress.kubernetes.io/cors-allow-origin: "https://dev.cm, https://*.dev.cm, https://fillcode.cm, https://*.fillcode.cm" -spec: - ingressClassName: nginx - rules: - - host: cdn.fillcode.com - http: - paths: - - path: /dev-cm(/|$)(.*) - pathType: ImplementationSpecific - backend: - service: - name: halo - port: - number: 80 diff --git a/apps/infra/net/nginx/captcha.html b/apps/infra/net/nginx/captcha.html deleted file mode 100644 index f010b3c..0000000 --- a/apps/infra/net/nginx/captcha.html +++ /dev/null @@ -1,301 +0,0 @@ - - - - - - 出于安全原因 请完成验证 - - - - -
-
-
-
-
-
-
-
-
-

FillCode

-
-

请完成验证

-

请完成下面验证, 页面将会自动跳转到访问页面。

-
-
-
验证码加载中, 请稍等...
-
- 联系我们 -
-
- - - diff --git a/apps/infra/net/nginx/certificate-cdn.yaml b/apps/infra/net/nginx/certificate-cdn.yaml deleted file mode 100644 index 53c0e5b..0000000 --- a/apps/infra/net/nginx/certificate-cdn.yaml +++ /dev/null @@ -1,18 +0,0 @@ -apiVersion: cert-manager.io/v1 -kind: Certificate -metadata: - name: cdn-crt - namespace: infra-net -spec: - secretName: cdn-crt - issuerRef: - name: dnspod - kind: ClusterIssuer - group: cert-manager.io - dnsNames: - - "fillcode.com" - - "*.fillcode.com" - - "sinceai.com" - - "*.sinceai.com" - - "dev.cm" - - "*.dev.cm" diff --git a/apps/infra/net/nginx/configmap-static-update.sh b/apps/infra/net/nginx/configmap-static-update.sh new file mode 100644 index 0000000..f1544a9 --- /dev/null +++ b/apps/infra/net/nginx/configmap-static-update.sh @@ -0,0 +1,21 @@ +#!/bin/bash + +# 更新 ConfigMap 中的静态文件 +cat > configmap-static.yaml << 'EOF' +apiVersion: v1 +kind: ConfigMap +metadata: + name: static + namespace: infra-net +data: +EOF + +# 直接遍历 static 目录并追加到文件 +for file in static/*; do + filename=$(basename "$file") + echo " $filename: |" >> configmap-static.yaml + sed 's/^/ /' "$file" >> configmap-static.yaml + echo "" >> configmap-static.yaml +done + +echo "ConfigMap updated successfully!" diff --git a/apps/infra/net/nginx/configmap-html.yaml b/apps/infra/net/nginx/configmap-static.yaml similarity index 60% rename from apps/infra/net/nginx/configmap-html.yaml rename to apps/infra/net/nginx/configmap-static.yaml index 770541d..26a7c6b 100644 --- a/apps/infra/net/nginx/configmap-html.yaml +++ b/apps/infra/net/nginx/configmap-static.yaml @@ -1,7 +1,7 @@ apiVersion: v1 kind: ConfigMap metadata: - name: custom-html + name: static namespace: infra-net data: captcha.html: | @@ -17,17 +17,17 @@ data: -webkit-box-sizing: border-box; box-sizing: border-box } - + body { padding: 0; margin: 0 } - + #error { position: relative; height: 100vh } - + #error .error { position: absolute; left: 50%; @@ -36,7 +36,7 @@ data: -ms-transform: translate(-50%, -50%); transform: translate(-50%, -50%) } - + #error .error-bg { position: absolute; left: 0; @@ -45,7 +45,7 @@ data: bottom: 0; overflow: hidden } - + #error .error-bg > div { position: absolute; top: 0; @@ -53,23 +53,23 @@ data: width: 1px; background-color: #eee } - + #error .error-bg > div:nth-child(1) { left: 20% } - + #error .error-bg > div:nth-child(2) { left: 40% } - + #error .error-bg > div:nth-child(3) { left: 60% } - + #error .error-bg > div:nth-child(4) { left: 80% } - + #error .error-bg > div:after { content: ''; position: absolute; @@ -82,71 +82,71 @@ data: width: 2px; background-color: #1cfafe } - + @-webkit-keyframes drop { 90% { height: 20px } - + 100% { height: 160px; -webkit-transform: translateY(calc(100vh + 160px)); transform: translateY(calc(100vh + 160px)) } } - + @keyframes drop { 90% { height: 20px } - + 100% { height: 160px; -webkit-transform: translateY(calc(100vh + 160px)); transform: translateY(calc(100vh + 160px)) } } - + #error .error-bg > div:nth-child(1):after { -webkit-animation: drop 3s infinite linear; animation: drop 3s infinite linear; -webkit-animation-delay: .2s; animation-delay: .2s } - + #error .error-bg > div:nth-child(2):after { -webkit-animation: drop 2s infinite linear; animation: drop 2s infinite linear; -webkit-animation-delay: .7s; animation-delay: .7s } - + #error .error-bg > div:nth-child(3):after { -webkit-animation: drop 3s infinite linear; animation: drop 3s infinite linear; -webkit-animation-delay: .9s; animation-delay: .9s } - + #error .error-bg > div:nth-child(4):after { -webkit-animation: drop 2s infinite linear; animation: drop 2s infinite linear; -webkit-animation-delay: 1.2s; animation-delay: 1.2s } - + .error { max-width: 520px; width: 100%; padding: 20px; text-align: center } - + .error .error-code { height: 210px; line-height: 210px } - + .error .error-code h1 { font-family: oswald, sans-serif; font-size: 80px; @@ -154,7 +154,7 @@ data: margin: 0; text-shadow: 4px 4px 0 #1cfafe } - + .error h2 { font-family: oswald, sans-serif; font-size: 42px; @@ -163,7 +163,7 @@ data: text-transform: uppercase; letter-spacing: 1.6px } - + .error p { font-family: lato, sans-serif; color: #000; @@ -171,7 +171,7 @@ data: margin-top: 20px; margin-bottom: 25px } - + .error a { font-family: lato, sans-serif; padding: 10px 30px; @@ -185,17 +185,17 @@ data: -webkit-transition: .2s all; transition: .2s all } - + .error a:not(:first-of-type) { margin-left: 20px } - + .error a:hover { background-color: #1cfafe; -webkit-box-shadow: 0 0 0 0 #000, 0 0 0 2px #1cfafe; box-shadow: 0 0 0 0 #000, 0 0 0 2px #1cfafe } - + .error-social > a { width: 40px; height: 40px; @@ -203,13 +203,13 @@ data: padding: 0; margin: 0 5px } - + .error-social > a:hover { background-color: #1cfafe; -webkit-box-shadow: 0 0 0 0 #000, 0 0 0 2px #1cfafe; box-shadow: 0 0 0 0 #000, 0 0 0 2px #1cfafe } - + #captcha-form { position: relative; width: 300px; @@ -220,7 +220,7 @@ data: -webkit-box-shadow: 0 0 0 2px #000, 2px 2px 0 2px #1cfafe; box-shadow: 0 0 0 2px #000, 2px 2px 0 2px #1cfafe; } - + .loading { position: absolute; top: 0; @@ -233,11 +233,11 @@ data: height: 65px; gap: 10px; } - + .loading:has(+ *) { display: none; } - + .loading::before { content: ""; width: 20px; @@ -247,12 +247,12 @@ data: border-radius: 50%; animation: spin 1s linear infinite; } - + #captcha { position: relative; z-index: 2; } - + @keyframes spin { from { transform: rotate(0deg); @@ -261,17 +261,17 @@ data: transform: rotate(360deg); } } - + @media only screen and (max-width: 480px) { .error .error-code { height: 122px; line-height: 122px } - + .error .error-code h1 { font-size: 60px } - + .error h2 { font-size: 26px } @@ -293,7 +293,8 @@ data:

请完成验证

请完成下面验证, 页面将会自动跳转到访问页面。

-
+
验证码加载中, 请稍等...
联系我们 @@ -307,5 +308,163 @@ data: + pwa-cdn.js: | + 'use strict' + // 配置 + const pwaCdnConfig = { + cdnUrl: 'https://cdn.fillcode.com/', + serviceWorkerUrl: '/__static/sw-cdn.js', + staticRegex: /\.(js|css|png|jpg|jpeg|gif|svg|webp|woff|woff2|ttf|ico)$/, + debug: true, + } + + /** + * PWA 初始化函数 + */ + async function initializePWA() { + // 检查支持 + if (!('serviceWorker' in navigator)) return console.log('PWA-CDN: Service Worker not supported') + + let registration; + + try { + // 注册Service Worker - 使用相对路径 + registration = await navigator.serviceWorker.register(pwaCdnConfig.serviceWorkerUrl, {scope: '/'}) + + console.log('PWA-CDN: Service Worker registered') + } catch (error) { + console.error('PWA-CDN: Failed to register Service Worker:', error) + } + + // 发送初始配置 + const sendConfig = () => { + registration.active.postMessage({type: 'CONFIG', config: pwaCdnConfig}) + } + + // 如果注册失败,直接返回错误 + if(!registration) return console.error('PWA-CDN: Service Worker registration failed, cannot send config') + + // 更新配置函数 + window.updatePWACDNConfig = (newConfig) => { + Object.assign(pwaCdnConfig, newConfig) + sendConfig() + } + + // 等待Service Worker激活后发送配置 + if (registration.active) sendConfig() + + // 监听Service Worker更新事件 + registration.addEventListener('updatefound', () => { + const newWorker = registration.installing + + newWorker.addEventListener('statechange', () => { + if (newWorker.state === 'activated') sendConfig() + }) + }) + } + + /** + * 启动 PWA-CDN + * */ + initializePWA().catch(console.error) + + sw-cdn.js: | + 'use strict' + + // Service Worker 配置 - 默认值 + let config = { + cdnUrl: 'https://cdn.fillcode.com/', + serviceWorkerUrl: '/__static/sw-cdn.js', + staticRegex: /\.(js|css|png|jpg|jpeg|gif|svg|webp|woff|woff2|ttf|ico)$/, + debug: true, + } + + // 监听配置更新消息 + self.addEventListener('message', e => { + if (e.data.type !== 'CONFIG') return + + config = e.data.config + + if (config.debug) console.log('PWA-CDN: Config updated', config) + }) + + // 拦截网络请求 + self.addEventListener('fetch', e => { + const url = new URL(e.request.url) + + // 如果请求不是GET方法,直接返回 + if (e.request.method !== 'GET') return + + // 如果请求的域名不是当前页面的域名 + if (url.origin !== self.location.origin) return + + // 过滤__static路径下的请求 + if (url.pathname.startsWith('/__static/')) return + + // 如果请求的路径不匹配静态资源正则表达式,直接返回 + if (!config.staticRegex.test(url.pathname)) return + + // 判断是否是强制需要同源请求 + const requiresSameOrigin = ['worker', 'sharedworker', 'serviceworker'].includes(e.request.destination) + + // 如果是强制需要同源请求的资源类型,直接返回 + if (requiresSameOrigin) return + + // 开始处理静态资源请求 + e.respondWith(handleStaticResource(e.request, url)) + }) + + // 处理静态资源请求 + async function handleStaticResource(request, url) { + // 生成CDN子路径 + const hostname = self.location.hostname + const cdnPath = hostname.replace(/\./g, '-') + + const targetUrl = config.cdnUrl + cdnPath + url.pathname + url.search + + if (config.debug) console.log('PWA-CDN:', url.href, '->', targetUrl) + + try { + // 创建新请求,保留原始缓存策略 + const newRequest = new Request(targetUrl, {...request, mode: 'cors'}) + + // 请求目标域名,浏览器会自动处理缓存 + const response = await fetch(newRequest) + + if (!response.ok) throw new Error('Target domain response not ok: ' + response.status) + + // 修复重定向问题 + if([301, 302, 307, 308].includes(response.status)) { + const location = response.headers.get('location') + + // 如果重定向的地址不是CDN地址,或者已经包含了CDN子路径,则返回原始响应 + if(!location?.startsWith('/') || location.startsWith(`/${cdnPath}`)) return response + + // 否则,重定向到新的CDN地址 + const redirectUrl = new URL(location, config.cdnUrl + cdnPath) + + if (config.debug) console.log('PWA-CDN: Redirecting to', redirectUrl.href) + return Response.redirect(redirectUrl.href, response.status) + } + + return response + } catch (error) { + if (config.debug) console.warn('PWA-CDN: Fallback to original request for', url.href, error) + + // 失败时回退到原始请求 + return fetch(request) + } + } + + // Service Worker 生命周期 + self.addEventListener('install', () => { + if (config.debug) console.log('PWA-CDN: Service Worker installing') + self.skipWaiting().catch(console.error) + }) + + self.addEventListener('activate', () => { + if (config.debug) console.log('PWA-CDN: Service Worker activated') + self.clients.claim().catch(console.error) + }) diff --git a/apps/infra/net/nginx/helmchart.yaml b/apps/infra/net/nginx/helmchart.yaml index cab36bc..67c80a6 100644 --- a/apps/infra/net/nginx/helmchart.yaml +++ b/apps/infra/net/nginx/helmchart.yaml @@ -23,6 +23,12 @@ spec: hostNetwork: true hostPort: enabled: true + # 添加 DNS 配置 + dnsPolicy: "None" + dnsConfig: + nameservers: + - "169.254.20.10" + - "10.43.0.10" service: enabled: false publishService: @@ -52,6 +58,12 @@ spec: server-snippet: | # dns配置 配置在http块下会出现重复配置 所以配置在server块下 resolver 169.254.20.10 10.43.0.10 ipv6=off; + # 代理全局静态资源 可提供serviceWorker的支持 + location ^~ /__static/ { + proxy_pass http://ingress-nginx-defaultbackend.infra-net.svc.cluster.local/static/; + proxy_set_header Host $host; + add_header Service-Worker-Allowed "/"; + } # 启用geoip2模块 use-geoip: "false" use-geoip2: "true" @@ -107,7 +119,7 @@ spec: plugins: "crowdsec" lua-shared-dicts: "crowdsec_cache: 50m" # 启用geoip2模块 - maxmindLicenseKey: "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" + maxmindLicenseKey: "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" extraArgs: default-ssl-certificate: "infra-net/dev-cm-crt" # crowdsec插件配置 @@ -137,7 +149,7 @@ spec: - name: SECRET_KEY value: "0x4AAAAAAAxJ2dwFOaNg5ae3c6wYTmWH0bU" - name: CAPTCHA_TEMPLATE_PATH - value: /etc/nginx/lua/plugins/crowdsec/templates/captcha.html + value: /etc/nginx/static/captcha.html command: ['sh', '-c', "bash /docker_start.sh; mkdir -p /lua_plugins/crowdsec/; cp -R /crowdsec/* /lua_plugins/crowdsec/"] volumeMounts: - name: crowdsec-bouncer-plugin @@ -145,26 +157,33 @@ spec: extraVolumes: - name: crowdsec-bouncer-plugin emptyDir: {} - - name: custom-html + - name: static configMap: - name: custom-html + name: static extraVolumeMounts: - name: crowdsec-bouncer-plugin mountPath: /etc/nginx/lua/plugins/crowdsec subPath: crowdsec - - name: custom-html - mountPath: /etc/nginx/lua/plugins/crowdsec/templates + - name: static + mountPath: /etc/nginx/static defaultBackend: enabled: true image: registry: docker.io image: devcm/default-backend - tag: v0.1.0 + tag: v0.2.0 autoscaling: enabled: true minReplicas: 1 maxReplicas: 3 targetCPUUtilizationPercentage: 80 + extraVolumes: + - name: static + configMap: + name: static + extraVolumeMounts: + - name: static + mountPath: /app/static affinity: nodeAffinity: requiredDuringSchedulingIgnoredDuringExecution: diff --git a/apps/infra/net/nginx/ingress-cdn.yaml b/apps/infra/net/nginx/ingress-cdn.yaml new file mode 100644 index 0000000..fadf4e3 --- /dev/null +++ b/apps/infra/net/nginx/ingress-cdn.yaml @@ -0,0 +1,69 @@ +--- +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + name: cdn + namespace: infra-net + annotations: + nginx.ingress.kubernetes.io/use-regex: "true" + nginx.ingress.kubernetes.io/rewrite-target: /$2 + # 添加允许跨域请求 + nginx.ingress.kubernetes.io/enable-cors: "true" + nginx.ingress.kubernetes.io/cors-allow-origin: "https://dev.cm, https://*.dev.cm, https://fillcode.cm, https://*.fillcode.cm" +spec: + ingressClassName: nginx + rules: + - host: cdn.fillcode.com + http: + paths: + - path: /dev-cm(/|$)(.*) + pathType: ImplementationSpecific + backend: + service: + name: cdn-halo + port: + number: 80 + - path: /git-dev-cm(/|$)(.*) + pathType: ImplementationSpecific + backend: + service: + name: cdn-gitea-http + port: + number: 3000 + - path: /monitor-dev-cm(/|$)(.*) + pathType: ImplementationSpecific + backend: + service: + name: cdn-prometheus-grafana + port: + number: 80 + +--- +apiVersion: v1 +kind: Service +metadata: + name: cdn-halo + namespace: infra-net +spec: + type: ExternalName + externalName: halo.apps.svc.cluster.local + +--- +apiVersion: v1 +kind: Service +metadata: + name: cdn-gitea-http + namespace: infra-net +spec: + type: ExternalName + externalName: gitea-http.infra-devops.svc.cluster.local + +--- +apiVersion: v1 +kind: Service +metadata: + name: cdn-prometheus-grafana + namespace: infra-net +spec: + type: ExternalName + externalName: prometheus-grafana.infra-monitor.svc.cluster.local diff --git a/apps/infra/net/nginx/static/captcha.html b/apps/infra/net/nginx/static/captcha.html new file mode 100644 index 0000000..35a6cad --- /dev/null +++ b/apps/infra/net/nginx/static/captcha.html @@ -0,0 +1,302 @@ + + + + + + 出于安全原因 请完成验证 + + + + +
+
+
+
+
+
+
+
+
+

FillCode

+
+

请完成验证

+

请完成下面验证, 页面将会自动跳转到访问页面。

+
+
+
验证码加载中, 请稍等...
+
+ 联系我们 +
+
+ + + diff --git a/apps/infra/net/nginx/static/pwa-cdn.js b/apps/infra/net/nginx/static/pwa-cdn.js new file mode 100644 index 0000000..69c130b --- /dev/null +++ b/apps/infra/net/nginx/static/pwa-cdn.js @@ -0,0 +1,59 @@ +'use strict' + +// 配置 +const pwaCdnConfig = { + cdnUrl: 'https://cdn.fillcode.com/', + serviceWorkerUrl: '/__static/sw-cdn.js', + staticRegex: /\.(js|css|png|jpg|jpeg|gif|svg|webp|woff|woff2|ttf|ico)$/, + debug: true, +} + +/** + * PWA 初始化函数 + */ +async function initializePWA() { + // 检查支持 + if (!('serviceWorker' in navigator)) return console.log('PWA-CDN: Service Worker not supported') + + let registration; + + try { + // 注册Service Worker - 使用相对路径 + registration = await navigator.serviceWorker.register(pwaCdnConfig.serviceWorkerUrl, {scope: '/'}) + + console.log('PWA-CDN: Service Worker registered') + } catch (error) { + console.error('PWA-CDN: Failed to register Service Worker:', error) + } + + // 发送初始配置 + const sendConfig = () => { + registration.active.postMessage({type: 'CONFIG', config: pwaCdnConfig}) + } + + // 如果注册失败,直接返回错误 + if(!registration) return console.error('PWA-CDN: Service Worker registration failed, cannot send config') + + // 更新配置函数 + window.updatePWACDNConfig = (newConfig) => { + Object.assign(pwaCdnConfig, newConfig) + sendConfig() + } + + // 等待Service Worker激活后发送配置 + if (registration.active) sendConfig() + + // 监听Service Worker更新事件 + registration.addEventListener('updatefound', () => { + const newWorker = registration.installing + + newWorker.addEventListener('statechange', () => { + if (newWorker.state === 'activated') sendConfig() + }) + }) +} + +/** + * 启动 PWA-CDN + * */ +initializePWA().catch(console.error) diff --git a/apps/infra/net/nginx/static/sw-cdn.js b/apps/infra/net/nginx/static/sw-cdn.js new file mode 100644 index 0000000..d8947db --- /dev/null +++ b/apps/infra/net/nginx/static/sw-cdn.js @@ -0,0 +1,97 @@ +'use strict' + +// Service Worker 配置 - 默认值 +let config = { + cdnUrl: 'https://cdn.fillcode.com/', + serviceWorkerUrl: '/__static/sw-cdn.js', + staticRegex: /\.(js|css|png|jpg|jpeg|gif|svg|webp|woff|woff2|ttf|ico)$/, + debug: true, +} + +// 监听配置更新消息 +self.addEventListener('message', e => { + if (e.data.type !== 'CONFIG') return + + config = e.data.config + + if (config.debug) console.log('PWA-CDN: Config updated', config) +}) + +// 拦截网络请求 +self.addEventListener('fetch', e => { + const url = new URL(e.request.url) + + // 如果请求不是GET方法,直接返回 + if (e.request.method !== 'GET') return + + // 如果请求的域名不是当前页面的域名 + if (url.origin !== self.location.origin) return + + // 过滤__static路径下的请求 + if (url.pathname.startsWith('/__static/')) return + + // 如果请求的路径不匹配静态资源正则表达式,直接返回 + if (!config.staticRegex.test(url.pathname)) return + + // 判断是否是强制需要同源请求 + const requiresSameOrigin = ['worker', 'sharedworker', 'serviceworker'].includes(e.request.destination) + + // 如果是强制需要同源请求的资源类型,直接返回 + if (requiresSameOrigin) return + + // 开始处理静态资源请求 + e.respondWith(handleStaticResource(e.request, url)) +}) + +// 处理静态资源请求 +async function handleStaticResource(request, url) { + // 生成CDN子路径 + const hostname = self.location.hostname + const cdnPath = hostname.replace(/\./g, '-') + + const targetUrl = config.cdnUrl + cdnPath + url.pathname + url.search + + if (config.debug) console.log('PWA-CDN:', url.href, '->', targetUrl) + + try { + // 创建新请求,保留原始缓存策略 + const newRequest = new Request(targetUrl, {...request, mode: 'cors'}) + + // 请求目标域名,浏览器会自动处理缓存 + const response = await fetch(newRequest) + + if (!response.ok) throw new Error('Target domain response not ok: ' + response.status) + + // 修复重定向问题 + if([301, 302, 307, 308].includes(response.status)) { + const location = response.headers.get('location') + + // 如果重定向的地址不是CDN地址,或者已经包含了CDN子路径,则返回原始响应 + if(!location?.startsWith('/') || location.startsWith(`/${cdnPath}`)) return response + + // 否则,重定向到新的CDN地址 + const redirectUrl = new URL(location, config.cdnUrl + cdnPath) + + if (config.debug) console.log('PWA-CDN: Redirecting to', redirectUrl.href) + return Response.redirect(redirectUrl.href, response.status) + } + + return response + } catch (error) { + if (config.debug) console.warn('PWA-CDN: Fallback to original request for', url.href, error) + + // 失败时回退到原始请求 + return fetch(request) + } +} + +// Service Worker 生命周期 +self.addEventListener('install', () => { + if (config.debug) console.log('PWA-CDN: Service Worker installing') + self.skipWaiting().catch(console.error) +}) + +self.addEventListener('activate', () => { + if (config.debug) console.log('PWA-CDN: Service Worker activated') + self.clients.claim().catch(console.error) +})