返回 Skill 列表
extension
分类: 开发与工程API Key 暂未确认

release-gate-check

对外上线前的"实跑测试 + 压力测试"门禁。任何站点/页面/资产索引上线前跑一遍,出 PASS/FAIL 报告;有 FAIL 就不许上线。

person作者: StevenZhao26hubOpenAPI

上线门禁检查(Release Gate)

何时用

  • 任何对外上线前:站点/页面、技能包、专家包、资产索引、API 端点、小程序版本、对外文档。
  • 用户说"上线/发布/部署前检查一下"、"跑个门禁"、"有没有死链"、"压一下试试"。
  • 事故复盘后:把新教训补成检查项 + 案例。

铁律(不可协商)

  1. 有任一 FAIL → 禁止上线。 不许"先上后修"。
  2. 失败项必须复测 3 次才可定 FAIL;偶发失败记 WARN(疑 CDN 抖动)。禁一次判死。
  3. 页码能打开 ≠ 功能能用。 CTA / 表单 / 第三方表单(kdocs、飞书、typeform、金数据…)必须逐个实测状态码
  4. 上线后必须复验,报告落盘留痕,作为"已实跑"的证据。
  5. 测试工具自身也要被证伪。 结果出来先问:"这条是真的吗?会不会是我解析错了?"
  6. 🔴 部署前必查"基地是否真的更新":本地工作副本可能远落后于线上(实测差过 23 倍)。覆盖式上传会摧毁线上内容。
    • 规则:一律以「线上当前版本」为基底做增量合并,绝不用本地旧副本整文件覆盖。
    • 落地:部署前先 md5(线上) vs md5(本地) 逐文件比对;不一致就拉线上当基底。
    • 参考实现:D:/Workbuddy/10-内容发布/uibc_entry_fix_20260914/deploy_safe.py(默认 dry-run,带 backup,可 --upload)。

用法

# 全量六组
python D:/Workbuddy/05-工具/release_gate/release_gate_check.py --site https://medxpert.cn

# 只跑关键组(上线后复验 / 快速)
python ... --site <URL> --only gate,links,drift --file <本地待发文件>

# 只体检,不出报告
python ... --site <URL> --only net,seo

参数:--limit(爬页上限,默认 60)--concurrency(默认 8)--stress(压测次数,默认 20)--outdir(默认脚本同级 reports/)。

六组检查项

| 组 | 查什么 | 判定 | |---|---|---| | gate 功能链路(一票否决) | 全部 CTA / 表单 / 外呼表单逐个实测;#锚点须存在同页目标 id | 任一断链 = FAIL | | net | 关键页可达 / 体积 / 耗时 | 非 200 = FAIL | | links | sitemap+首页爬取,站内链接逐条实测,带来源页溯源 | 复测 3 次全败 = FAIL | | seo | robots / sitemap / llms.txt / llms-full.txt / JSON-LD 合法性 / title / description / canonical / hreflang | 核心缺失 = FAIL | | stress | 并发请求 p50 / p95 / 失败率 | 失败>0 或 p95>3000ms | | drift | 本地文件 vs 线上文件逐字比对 | 不一致 = FAIL(改了没上线/被回退) |

坑(实测踩过,务必避开)

  1. 必须剥离 <script>/<style> 再提取链接。页面 JS 里常见 var src = "...",会被 (?:href|src)\s*= 误当成 HTML 属性 → 产生大量"幽灵死链"。

  2. 页内锚点 #xxx 不是死链,只有当同页不存在对应 id 时才是。

  3. 别用 [^<\s]+ 提取 <loc>:会永久漏检含空格的 URL(探测方法自带盲区)。用 <loc>(.*?)</loc> + strip。

  4. . 不跨行.{70}关键词.{70} 这类上下文正则,遇跨行会静默漏检。要探测就用属性级正则。

  5. 本机外呼必先清 proxy 环境变量HTTP_PROXY/HTTPS_PROXY/ALL_PROXY…),否则被代理劫持报错。

  6. Windows 上 git 可能不在 PATHgh 配置在 %APPDATA%\GitHub CLI\hosts.yml(不是 ~/.config/gh)。

  7. 🔴 门禁默认不覆盖静态资源(图片/CSS/JS/字体):links 组只扫 HTML 的 href 链接。必须单独跑资源扫描: 提取 (?:src|href)="...\.(png|jpe?g|svg|webp|gif|ico|css|js|woff2?|ttf)" → 逐条实测 → 漏检过 /assets/qrcode.png/skills/assets/logo.png

  8. 🔴 子目录页面必须用绝对路径:从根目录复制页面到子目录(如 /skills/index.html)时,相对路径(assets/logo.pngabout.htmlllms.txt)会解析到子目录下 → 整站导航全 404

    • 实测:/skills/ 页面 10 个导航链接 8 个 404,且页面本身 200,肉眼完全看不出来。
    • 检查法:对每个子目录页,把其相对链接按 urljoin(页面URL, 链接) 解析后实测;并对比 urljoin(根URL, 链接) 是否 200(是则说明"本该走根路径")。
    • 修法:把该页 href/src 中的相对路径改写为根绝对路径/assets/.../about.html),保留 fragment。
    • 注意 canonical:复制页通常已 canonical 指向主版本,修路径时不要动它。
  9. 🔴 部署前必查"源目录 ≠ stage 目录":新文件写在源目录,上传却走 stage 目录 → 漏同步 → 上传失败/404,而你以为已上线

    • 落地:上传前先出一份"stage vs 源目录差异清单"(文件名 + 大小 + md5),差异项要么补齐、要么确认是有意排除。
    • 实测:apply.html / agent/index.html 写进源目录却未进 stage,上传连续失败两次。
  10. 本机 bash 可能缺 coreutils(实测 tail / dirname / sleep 全部 command not found):不要依赖 shell 管道与延时,改走 Python(subprocess.run([...], capture_output=True) + time.sleep())。

    • 症状:命令 exit 127、stdout 为空、stderr 只有 shim 报错 → 换 Python 立即恢复。
  11. 🔴 不要同时跑两个网络密集测试(如门禁 + 巡检并行):二者会互相抢连接,制造大量假"不可达"

    • 实测:门禁与巡检并行时,门禁报 10 条"页面不可达/握手超时";随后串行复测 10/10 全部 200
    • 规则:网络测试一次只跑一个;跑完再跑下一个。若必须并行,结果只能当参考,不能当判定。
  12. 🔴 并发爬取本身会产生假"不可达"ThreadPoolExecutor 并发拉 60 页时,握手超时会直接把页面判为不可达。

    • 正确做法:先并发收集失败项 → 再串行复测fetch_retry()),只有串行复测仍失败才判 FAIL。
    • 已固化:release_gate_check.pycheck_net()links 组页面可达性均已内置复测;uibc_daily_check.pycheck() 同样内置。
    • 自查提醒:立了"失败必复测"的规矩,就要检查自己的每个工具是否都遵守——实测曾发现巡检脚本一次判死,产生假阳性 FAIL(robots.txt / /uibc/prize/)。
  13. 🔴 验证 DNS 切换必须避开本机缓存(2026-09-15 实测踩过)

    • 现象:切 www 到 CDN 后 5 小时,本机 https://www.medxpert.cn/403;而 223.5.5.5 / 119.29.29.29 / 8.8.8.8 三个公共解析器全都已是 CDN 边缘 IP
    • 误导性:403 恰是那条已被删除的旧记录cos.-website 端点)的典型返回码 → 极易误判成"切换失败/源站配错"。
    • 根因:本机解析器缓存(TTL 600s)。执行 ipconfig /flushdns 后立即恢复 200。
    • 正确验证顺序(不要跳步)
      1. 查 CDN 侧状态:tccli cdn DescribeDomainsStatus 应为 online,且 Origin 指向正确端点
      2. 公共 DNSnslookup <域名> 223.5.5.5)—— 这是"是否已生效"的权威依据,不要用本机解析判断
      3. 绕过 DNS 直连 CDN:连 Host 头 + 不校验证书 → 用于区分"CDN 配置问题"与"DNS 问题"
      4. 最后才测本机域名访问;若与第 2/3 步结论矛盾 → 先 ipconfig /flushdns 再下结论
    • 判据:响应含 X-NWS-LOG-UUID = 走腾讯云 CDN;没有它才是没走 CDN(Server: tencent-cos 两者都有,不能单看)。
  14. 🔴 coscmd upload 会重置对象元数据(Cache-Control 丢失)(2026-09-15 实测)

    • 现象:上传 uibc/index.htmluibc/en/index.htmluibc/llms.txtsitemap.xml 后,Cache-Control 全部变回「(无)」 —— 之前批量设过的值被静默清掉。
    • 后果:CDN/浏览器失去缓存策略 → 回到"每次回源"或"按默认策略长期缓存"两种极端(后者会导致部分节点长期返回旧版)。
    • 规则:凡用 coscmd 上传过的文件,必须重设 Cache-Control
    • 工具:05-工具/cos_setmeta.py --set "/path=public, max-age=600"(按路径精确设;支持 --get;幂等;内容字节不变,仅改元数据)。
  15. 🔴 CDN 路径刷新「done」≠ 生效(2026-09-15 实测,最反直觉的一条)

    • 现象:tccli cdn PurgePathCache 提交 4 次(flush ×3 + delete ×1)全部返回 status: done,但 /sitemap.xml 的缓存对象 Age 持续增长(1690→2077s),内容仍是 2 小时前的旧版。
    • 已逐项排除:源站正确(加 ?probe=1 换新 key → 立刻返回新版);响应确实走 CDN(含 X-NWS-LOG-UUID);任务日志逐条 done
    • → 结论:路径刷新对个别对象存在不生效且不报错的情况。别把 done 当生效凭证。
    • 规避:更新型文件(sitemap / llms.txt / 各类索引)设**短 max-age(≤300s)**兜底,把刷新只当"加速手段"而非"唯一手段"。
    • 该信的凭证Age 是否归零、Last-Modified 是否为新值。二者不变 = 没生效。
  16. 🔴 测量方法学:每次新建 TLS 连接会伪造出 14 倍的"慢"(2026-09-15 实测)

    • 反例:urllib.urlopen(每次新建连接)+ 并发 8 测本站 → p50 4080ms / 0.2rps
    • 正解:http.client.HTTPSConnection keep-alive 复用 + 压测前预热 N 次并丢弃样本p50 291ms / 13.1rps
    • 差 14 倍,全部来自"握手成本被算进服务延迟"。
    • 交叉验证法(必做):同刻对第三方站点(如 bing.com)跑同一方法 → 若第三方 87.9rps / p50 116ms,则本机出口没问题,瓶颈在测量方式而非被测站点。
    • 落地:05-工具/uibc_health_page.pyConn 类 + stress(warmup=3)顺序稳态并发压测分成两个独立指标报告,不混为一个 p50。
  17. 🔴 测试 URL 带 ?v=<时间戳> 会绕过 CDN 缓存 → 缓存类问题永远测不出(2026-09-15 实测)

    • 背景:为防误报,巡检/门禁习惯给 URL 加时间戳参数(本身是好习惯,用于判断"服务是否活着")。
    • 盲区:返回旧版、多节点内容不一致这类问题只有裸 URL 才能暴露
    • 实测:巡检全绿(带 ?v=),但裸 URL /sitemap.xml 仍是 2 小时前旧版。
    • 规则:可用性用带参 URL;内容/缓存一致性必须用裸 URL。两套都要跑,缺一套就有盲区。
  18. 🔴 GraphQL 字符串不允许裸换行(2026-09-15 发 GitHub Discussions 踩到)

    • 把多行 Markdown 直接塞进 mutation{createDiscussion(...body:"<多行>")} → 报 Expected string or block string, but it was malformed
    • 修:转义顺序 \ → 再 " → 最后 \n/\t\n\\n\t\\t、去掉 \r)。
    • 附带:本机直连 github.comTimeoutError WinError 10060走 API(ghx.py api ...)正常 → 验证一律用 API,别用浏览器访问判断成败。
    • 另:tccli cdn 的域名配置接口是 DescribeDomainsConfig(复数),参数只有 --Filters/--Limit/--Offset/--Sort--Domain/--Domains 都是 Unknown option)。
    • 缓存层反直觉实测:源站 max-age=3600,CDN 上 Age=23015s(≈6.4h) 仍是 Cache Hit不能假设 CDN 会按源站 max-age 过期;判断真实生效只看 Age 是否归零 + 内容是否为新。

性能根因诊断("三头一拆",5 分钟定位托管层问题)

页面全 200 但压测 p95 爆炸时,别急着改页面,先拆响应头:

| 看什么 | 怎么判 | 缺失意味着 | |---|---|---| | Server | 直出 tencent-cos / nginx X-Cache / Via / X-NWS-* | 请求直达源站、没有 CDN 边缘缓存(通常是主因) | | Content-Encoding | 请求带 Accept-Encoding: gzip, deflate, br,响应是否回 gzip/br | 未开压缩,文本资产原样传输 | | Cache-Control | 是否有 max-age / immutable | 无法缓存,每次回源 | | TTFB vs 总耗时 | urlopen 返回时计时 vs read() 完成后计时 | TTFB ≈ 总耗时 → 瓶颈在"到达源站",不在传输 |

验收目标(静态站):p50 < 300ms · p95 < 800ms · 吞吐 > 100 rps · Content-Encoding 有值 · Cache-Control 有值。 归属提醒 —— 🔴 先枚举本机能力,再断言"做不到"(实测踩坑:曾误判"CDN 需登控制台",实际三项全可自动化):

| 层 | 本机可用手段 | 实测结果 | |---|---|---| | COS · Cache-Control | qcloud_cos SDK copy_object(CopyStatus='Replaced') 批量改元数据(CacheControl= / 保留原 ContentType) | ✅ 361 对象 76 秒完成,内容 md5 前后一致;工具 05-工具/cos_set_cache_control.py(幂等/可回滚 --clear) | | COS · 压缩 | COS 不支持动态压缩 → 必须交给 CDN | — | | CDN · 加速/缓存/压缩/安全头 | tccli cdn AddCdnDomain / UpdateDomainConfigpip install tccli,入口在 venv Scripts/tccli.exe,不是 python -m tccli) | 命令与前提已备好,待点头 | | DNS · 切 CNAME | tccli dnspod(先 DescribeDomainList 确认域名是否在 DNSPod) | medxpert.cn 实测 DNSPod |

枚举清单shutil.which(<cli>) + importlib.util.find_spec(<sdk>) + 凭据文件(~/.cos.conf%APPDATA%\GitHub CLI\hosts.yml~/.tccli)+ 环境变量。 :区分"客观失效"与"我不试" —— 例:gh api user 返回 401 Bad credentials 是 token 客观失效(需人工重新授权),而 tccli 未安装只是"没装",pip install 即可。

⚠️ 加 CSP 前务必清点页面内联脚本,否则白屏;建议先加 HSTS / nosniff / Referrer-Policy / frame-ancestors,完整 CSP 另立一项并在测试域名先验。 参考:D:/Workbuddy/10-内容发布/UIBC_站点性能诊断与修复建议_20260914.md · D:/Workbuddy/10-内容发布/UIBC_性能修复_已完成的COS层与待决的CDN层_20260915.md

上线流程(每次照做)

  1. 改了东西 → 先跑 links + gate 本地/预发。
  2. 部署 → 跑全量门禁 → 报告落盘。
  3. BLOCKED → 修 → 重跑,直到 PASS/PASS_WITH_WARN
  4. 上线后复验:--only gate,links,drift --file <本地文件>
  5. 任何线上事故 → 回填一条 CASE(事件 / 教训 / 已固化的规则)。

事故案例库(每次事故回填一条)

| CASE | 事件 | 教训 | 已固化的规则 | |---|---|---|---| | CASE-006 | medxpert.cn/assets/qrcode.png 404,被 ≥4 页引用(cases/faq/articles×2),导航区"扫码关注"破图;门禁没报 | 门禁只扫 HTML href,不扫 src 静态资源 | 坑 #7:上线前必跑资源扫描 | | CASE-007 | /skills/ 页面 10 个导航链接 8 个 404(logo/about/products/services/knowledge/llms/sitemap/skills.html),页面本身 200,肉眼无感 | 页面从根目录复制到子目录,相对路径未改 | 坑 #8:子目录页必须用根绝对路径 | | CASE-008 | 拟部署的本地副本 llms-full.txt 仅 13KB,而线上 301KB(差 23 倍);若整文件覆盖将摧毁线上内容 | 本地工作副本可能远落后于线上 | 铁律 #6:以线上为基底增量合并 + deploy_safe.py | | CASE-009 | 新写的 apply.html / agent/index.html 只在源目录,未进 stage → 上传失败两次;误以为已上线 | 源目录 ≠ stage 目录 | 坑 #9:上传前出 stage 差异清单 | | CASE-010 | 页面全 200,但压测 p95=10065ms、吞吐 ~0.9rpsrobots.txt 偶发 18.6s。响应头显示 Server: tencent-cos、无 Content-Encoding、无 Cache-Control、TTFB≈总耗时 → 站点直连对象存储源站,无 CDN 边缘缓存 | "能打开" ≠ "扛得住";性能根因常在托管层 | 新增「性能根因诊断(三头一拆)」节 | | CASE-011 | verify.html 声称"指纹重算,改过的证书会被拦下",但实现只调了台账接口 | 文案声称了实现没做的事 — 比缺功能更危险 | 上线前对齐"页面声称 vs 代码实际",声称以能实证为限 |

配套

  • SOP:D:/Workbuddy/03-方案文档/测试专家能力建设_上线门禁SOP_v1.0_20260914.md
  • 修复工具(表单/CTA 死链):D:/Workbuddy/05-工具/site_lead_fix/fix_lead_links.py
  • 首选外部交叉验证器:linkinator(npx linkinator <URL> --recurse,零成本本地跑)