Преглед на файлове

feat: 测试SW资源异常修复

zhangyuhan преди 2 години
родител
ревизия
4352f38cbe

+ 1 - 1
src/seo.json

@@ -1,5 +1,5 @@
 {
-    "cdn": "",
+    "cdn": "https://cdn-jybx-webtest.jydev.jianyu360.com",
     "qfw": {
         "swordfish": {
             "description": "剑鱼标讯是国内专业的招标大数据服务平台,专注于全国招标采购信息的搜索查询、订阅推送和数据定制化服务。提供涵盖拟在建项目、招标预告、招标公告、中标公告、政府采购、企业工商信息等多种信息类型,帮助企业全方位掌握市场动态变化。招投标大数据平台就用剑鱼标讯。",

+ 197 - 0
src/web/staticres/brand/js/detection.js

@@ -0,0 +1,197 @@
+// 异常资源数组
+const failedResources = [];
+
+// 异常域名数组
+const domains = [];
+
+// 备用域名映射
+const BackCDNs = {
+  'cdn-jybx-webtest.jydev.jianyu360.com': 'jybx2-webtest.jydev.jianyu360.com',
+  'cdn-ali222.jianyu360.com': 'cdn-ali2.jianyu360.com',
+  'cdn-ali222.jianyu360.cn': 'cdn-ali2.jianyu360.com',
+  'cdn-ali.jianyu360.cn': 'cdn-ali.jianyu360.com',
+  'cdn-ali2.jianyu360.cn': 'cdn-ali2.jianyu360.com',
+  'cdn-ali3.jianyu360.cn': 'cdn-ali3.jianyu360.com',
+  'cdn-ali4.jianyu360.cn': 'cdn-ali4.jianyu360.com',
+  'cdn-common.jianyu360.cn': 'cdn-common.jianyu360.com',
+}
+
+// 处理异常资源
+const handleResourceError = (url) => {
+  failedResources.push(url);
+
+  // 提取域名
+  const domain = new URL(url).hostname;
+  if(!domains.includes(domain)) {
+    domains.push(domain);
+  }
+}
+
+// 监听错误事件
+window.addEventListener('error', (e) => {
+  // 脚本错误
+  if(e.target.src && e.target.tagName === 'SCRIPT') {
+    handleResourceError(e.target.src);
+  }
+
+  // CSS错误
+  if(e.target.href && e.target.tagName === 'LINK') {
+    handleResourceError(e.target.href);
+  }
+
+  // 图片错误
+  if(e.target.src && e.target.tagName === 'IMG') {
+    handleResourceError(e.target.src);
+  }
+
+}, true);
+
+// 插入弹窗 Node
+let initCDNDialog = false
+function addCDNDialogNode () {
+  if (initCDNDialog) {
+    return
+  }
+  initCDNDialog = true
+  const cdnStyle = document.createElement('style')
+  cdnStyle.innerHTML = `
+    <style>
+
+    .cdn-tip--dialog.show.action-tip .tip-button--submit {
+      display: none;
+    }
+    .cdn-tip--dialog.show,
+    .cdn-tip--dialog.show button{
+      display: inline-block;
+    }
+    .cdn-tip--dialog {
+      position: fixed;
+      top: 20%;
+      left: 50%;
+      transform: translateX(-50%);
+      display: none;
+      box-shadow: 0 0 2px 1px #e0e0e0;
+      border-radius: 8px;
+      background: #FFF;
+      padding: 32px;
+      color: #686868;
+      text-align: center;
+      font-size: 14px;
+      font-style: normal;
+      font-weight: 400;
+      line-height: 22px;
+    }
+    .cdn-tip--dialog button.tip-button--submit {
+      background: #2ABED1;
+      color: #FFF;
+    }
+    .cdn-tip--dialog button {
+      display: none;
+      border-radius: 6px;
+      width: 132px;
+      height: 36px;
+      flex-shrink: 0;
+      line-height: 36px;
+      border: none;
+      margin: 6px;
+      cursor: pointer;
+    }
+  </style>
+`
+  document.head.appendChild(cdnStyle)
+
+  const cdnNode = document.createElement('div')
+  cdnNode.innerHTML = `
+  <div class="cdn-tip--dialog" id="cdn-tip">
+    <p>
+      检测到当前页面故障,是否尝试进行修复?
+      <br>
+      您也可自行联系您的网络管理员获得支持
+    </p>
+    <div>
+      <button class="tip-button--submit" onclick="location.reload()">尝试修复</button>
+      <button class="tip-button--cancel" onclick="location.href = 'https://www.jianyu360.cn/assets-inspect.html'">联系网络管理员</button>
+    </div>
+  </div>
+  `
+  document.body.appendChild(cdnNode)
+}
+
+window.addEventListener('load', () => {
+  // 页面完全加载完毕
+  console.log('load Failed resources:', failedResources)
+  console.log('load Extracted domains:', domains)
+
+  // 存储
+  caches.open('CDN_INSPECT_2SW').then(cache => {
+    cache.put('data', new Response(JSON.stringify({
+      FailedCDNs: domains,
+      BackCDNs: BackCDNs
+    })))
+  })
+
+
+
+  function removeServiceWorker () {
+    if ("serviceWorker" in navigator) {
+      navigator.serviceWorker
+        .register("/sw.js")
+        .then(function (registration) {
+          // registration worked
+          console.log("Registration succeeded.");
+          registration.unregister().then(function (boolean) {
+            // if boolean = true, unregister is successful
+            console.log('b', boolean)
+          });
+        })
+        .catch(function (error) {
+          // registration failed
+          console.log("Registration failed with " + error);
+        });
+    }
+  }
+
+  function addServiceWorker () {
+    // 注册 Service Worker
+    if ('serviceWorker' in navigator) {
+      const cdnTipNode = document.querySelector('#cdn-tip')
+      navigator.serviceWorker.register('/sw.js')
+        .then(() => {
+          // 通知用户
+          cdnTipNode.classList.add('show')
+        })
+        .catch(err => {
+          // 通知用户
+          cdnTipNode.classList.add('show')
+          cdnTipNode.classList.add('action-tip')
+
+          console.log('SW failed', err)
+        });
+
+      navigator.serviceWorker.addEventListener('controllerchange', () => {
+        // 当service worker管控页面时触发
+        alert("检测到SW更新,请刷新页面");
+      })
+    }
+  }
+
+  const stopSw = localStorage.getItem('stop-cdn') === 'true'
+
+  console.log('是否禁用拦截', stopSw)
+
+  if (stopSw) {
+    removeServiceWorker()
+  }
+
+
+  const hasBackCDN = Object.keys(BackCDNs).some(v => domains.includes(v))
+
+  console.log('异常域名是否包含备用', hasBackCDN)
+
+  if (!stopSw && domains.length && hasBackCDN) {
+    addCDNDialogNode()
+    addServiceWorker()
+  }
+});
+
+

+ 81 - 0
src/web/staticres/brand/js/sw.js

@@ -0,0 +1,81 @@
+// 获取历史数据或缓存
+let data = {}
+
+self.addEventListener('install', async function (event) {
+  console.log('install v21');
+  data = await getCacheShareData()
+  self.skipWaiting();
+});
+
+self.addEventListener('activate', function (event) {
+  console.log('activate');
+});
+
+
+
+
+/**
+ * 根据 Key 读取缓存
+ * @param key
+ * @return {Promise<any>}
+ */
+async function getCacheShareData (key = 'CDN_INSPECT_2SW') {
+  return await caches.open(key).then(async cache => {
+    const res = await cache.match('data')
+    const data = await res.json();
+
+    console.log(`get ${key} data`, data)
+
+    return data
+  });
+}
+
+/**
+ * 设置缓存
+ * @param data
+ * @param key
+ */
+function setCacheShareData (data, key = 'CDN_INSPECT_2PAGE') {
+  // 写入缓存
+  caches.open(key).then(cache => {
+    cache.put('data', new Response(JSON.stringify(data)))
+  })
+}
+
+
+// 已拦截的CDN
+const ProxyCDNS = []
+
+
+self.addEventListener('fetch',(event) => {
+
+
+  // TODO 清除缓存逻辑
+
+
+  const {
+    FailedCDNs = [],
+    BackCDNs = {}
+  } = data || {};
+
+  console.warn('[Cache]: ', data)
+
+
+  const url = new URL(event.request.url);
+  const domain = url.hostname;
+
+  console.warn(`[Fetch 拦截]: ${domain} -- ${url}`)
+
+
+
+  if(FailedCDNs.includes(domain)) {
+    url.hostname = BackCDNs[domain] || '';
+    console.warn(`[>>>>>>>>>>>>>>>>>>>>> CDN 异常转发]: ${domain} -- ${url}`)
+    if (!ProxyCDNS.includes(domain)) {
+      ProxyCDNS.push(domain)
+      setCacheShareData(ProxyCDNS)
+    }
+    event.respondWith(fetch(url));
+  }
+
+});

+ 1 - 1
src/web/templates/common/pnc.html

@@ -12,7 +12,7 @@
 <link rel="stylesheet" href="{{Msg "seo" "cdn"}}/css/unicorn.main.css" />
 <link rel="stylesheet" href="{{Msg "seo" "cdn"}}/css/unicorn.grey.css" />
 <link rel="stylesheet" href='{{Msg "seo" "cdn"}}/public-pc/css/sub-page.css?v={{Msg "seo" "version"}}' />
-
+<script src="/brand/js/detection.js"></script>
 <script src='{{Msg "seo" "cdn"}}/common-module/js/JianyuIframe.umd.min.js?v={{Msg "seo" "version"}}'></script>
 <script src="https://cdn-common.jianyu360.com/cdn/lib/jquery/3.6.0/jquery.min.js"></script>
 <script>

+ 124 - 0
src/web/templates/frontRouter/pc/brand/free/dev-test.html

@@ -0,0 +1,124 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+  <meta charset="UTF-8">
+  <meta http-equiv="X-UA-Compatible" content="IE=edge">
+  <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0, user-scalable=no">
+  <title>剑鱼CDN资源异常测试页面</title>
+  <script src="/brand/js/detection.js"></script>
+  <style>
+    html, body {
+      font-size: 14px;
+    }
+    img {
+      width: 120px;
+    }
+    li {
+      word-break: break-word;
+    }
+  </style>
+
+
+  <link href="https://cdn-ali222.jianyu360.com/css/bootstrap.min.css" rel="stylesheet">
+  <link href="https://cdn-ali222.jianyu360.com/css/animate.css" rel="stylesheet">
+
+
+  <script src='https://cdn-ali222.jianyu360.com/common-module/js/JianyuIframe.umd.min.js?v=24512'></script>
+
+
+  <script src="https://cdn-common.jianyu360.cn/cdn/lib/jquery/3.6.0/jquery.min.js"></script>
+  <script>
+    console.log('window.JianyuIframe', window.JianyuIframe)
+    console.log('window.jQuery.fn.jquery', window.jQuery.fn.jquery)
+  </script>
+</head>
+<body>
+<h1>剑鱼CDN资源异常测试页面</h1>
+<p>页面中静态资源路径为 https://cdn-ali222.jianyu360.com,经过拦截修复后可正常访问</p>
+
+<button onclick="location.reload()">重新测试</button>
+
+<h2>图片检测</h2>
+<div>
+  <h3>Logo</h3>
+  <img src="https://cdn-ali222.jianyu360.cn/images/index/logo_main.png?v=24501" alt="cdn-ali222.jianyu360.cn">
+  <img src="https://cdn-ali222.jianyu360.cn/images/index/logo_main.png?v=24501" alt="cdn-ali222.jianyu360.cn">
+
+  <img src="https://cdn-ali222.jianyu360.com/images/index/logo_main.png?v=24501" alt="cdn-ali222.jianyu360.com">
+  <img src="https://cdn-ali222.jianyu360.com/images/index/logo_main.png?v=24501" alt="cdn-ali222.jianyu360.com">
+
+  <h4>Logo2</h4>
+
+  <img src="https://cdn-ali222.jianyu360.cn/jyapp/me/images/register_logo.png?v=2263" alt="cdn-ali222.jianyu360.cn">
+  <img src="https://cdn-ali222.jianyu360.cn/jyapp/me/images/register_logo.png?v=2263" alt="cdn-ali222.jianyu360.cn">
+
+  <img src="https://cdn-ali222.jianyu360.com/jyapp/me/images/register_logo.png?v=2263" alt="cdn-ali222.jianyu360.com">
+  <img src="https://cdn-ali222.jianyu360.com/jyapp/me/images/register_logo.png?v=2263" alt="cdn-ali222.jianyu360.com">
+
+</div>
+
+
+
+<h2>其他检测日志</h2>
+<ul id="logs">
+
+</ul>
+
+<script>
+  function validateCDNFiles() {
+    var files = [
+      'https://cdn-ali222.jianyu360.cn/js/jquery.cookie.js',
+      'https://cdn-ali222.jianyu360.com/js/jquery.cookie.js',
+    ];
+
+    var promises = files.map(function(file) {
+      return new Promise(function(resolve, reject) {
+        var element;
+        if (file.endsWith('.js')) {
+          element = document.createElement('script');
+          element.src = file;
+        } else if (file.endsWith('.css')) {
+          element = document.createElement('link');
+          element.href = file;
+          element.rel = 'stylesheet';
+        } else {
+          reject(new Error('Unsupported file type: ' + file));
+        }
+
+        element.onload = function() {
+          resolve({ file: file, status: '加载成功' });
+        };
+
+        element.onerror = function() {
+          resolve({ file: file, status: '加载异常 ❌' });
+        };
+
+        document.head.appendChild(element);
+      });
+    });
+
+    return Promise.all(promises);
+  }
+
+  function displayValidationResults(results) {
+    var ul = document.querySelector('ul#logs');
+
+    results.forEach(function(result) {
+      var li = document.createElement('li');
+      li.textContent = result.file + ' -- ' + result.status;
+      ul.appendChild(li);
+    });
+  }
+
+  validateCDNFiles()
+    .then(function(results) {
+      displayValidationResults(results);
+    })
+    .catch(function(error) {
+      console.error('Validation error:', error);
+    });
+
+</script>
+
+</body>
+</html>

+ 133 - 0
src/web/templates/frontRouter/pc/brand/free/dev.html

@@ -0,0 +1,133 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+  <meta charset="UTF-8">
+  <meta http-equiv="X-UA-Compatible" content="IE=edge">
+  <meta name="viewport"
+        content="width=device-width, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0, user-scalable=no">
+  <title>剑鱼异常检测帮助页面</title>
+  <style>
+    html, body {
+      font-size: 14px;
+    }
+
+    img {
+      width: 120px;
+    }
+
+    li {
+      word-break: break-word;
+    }
+
+    button {
+      background: #2ABED1;
+      color: #FFF;
+      border-radius: 6px;
+      width: 132px;
+      height: 36px;
+      flex-shrink: 0;
+      line-height: 36px;
+      border: none;
+      margin: 6px;
+      cursor: pointer;
+    }
+  </style>
+
+</head>
+<body>
+<h1>剑鱼相关资源异常修复工具</h1>
+
+<h3>当前启用状态:<span style="color: #0A9AEA"></span></h3>
+
+<button onclick="changeStorageStatus(false)">启用修复</button>
+<button style="background: #ff0000c2" onclick="changeStorageStatus(true)">禁用修复</button>
+
+
+<h2>已启用拦截映射的资源域名:</h2>
+<ul id="logs">
+
+</ul>
+
+<h2>拦截映射规则配置:</h2>
+<ul id="configs">
+
+</ul>
+
+<br>
+<button style="background: #ff0000c2" onclick="removeStorageStatus(true)">重置缓存</button>
+<button onclick="location.reload()">刷新页面</button>
+
+<script>
+  // 是否禁用修复的缓存 key
+  const key = 'stop-cdn'
+
+  /**
+   * 改变是否禁用修复缓存状态
+   * @param state
+   */
+  function changeStorageStatus(state) {
+    localStorage.setItem(key, state)
+    updateStorageStatus()
+  }
+
+  /**
+   * 更新页面是否禁用修复缓存状态
+   */
+  function updateStorageStatus() {
+    const stopState = localStorage.getItem(key) === 'true'
+    document.querySelector('h3 span').innerText = stopState ? '未启用' : '启用中'
+  }
+
+  updateStorageStatus()
+
+  /**
+   * 重置缓存
+   */
+  function removeStorageStatus () {
+    localStorage.removeItem(key)
+    caches.delete('CDN_INSPECT_2PAGE')
+    caches.delete('CDN_INSPECT_2SW')
+    location.reload()
+  }
+
+
+  /**
+   * 读取 Cache SW 缓存数据并展示
+   */
+  caches.open('CDN_INSPECT_2PAGE').then(cache => {
+    return cache.match('data')
+  }).then(async res => {
+    const cacheList = await res?.json()
+    console.warn('已经被拦截过的CDN:', cacheList)
+    cacheList && displayValidationResults(cacheList)
+  })
+
+
+  caches.open('CDN_INSPECT_2SW').then(cache => {
+    return cache.match('data')
+  }).then(async res => {
+    const {
+      BackCDNs = {}
+    } = (await res?.json() || {})
+    console.warn('配置:', BackCDNs)
+    BackCDNs && displayValidationResults(Object.keys(BackCDNs).map(v => {
+      return `转发 ${v}  -->  ${BackCDNs[v]}`
+    }), 'ul#configs')
+  })
+
+  /**
+   * 输出 Logs 到页面
+   * @param results
+   * @param selector
+   */
+  function displayValidationResults(results, selector = 'ul#logs') {
+    const ul = document.querySelector(selector)
+    results.forEach(function (result) {
+      const li = document.createElement('li')
+      li.textContent = result
+      ul.appendChild(li);
+    })
+  }
+</script>
+</body>
+</html>