[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"blog-strapi-article:zh:websocket-proxy-egress-switch-recovery-testing":3,"blog-article:\u002Fblog\u002Fwebsocket-proxy-egress-switch-recovery-testing":676,"blog-related:\u002Fblog\u002Fwebsocket-proxy-egress-switch-recovery-testing":677,"blog-translations:\u002Fblog\u002Fwebsocket-proxy-egress-switch-recovery-testing":678},{"available":4,"article":5,"relatedArticles":187,"translations":560},true,{"source":6,"id":7,"documentId":8,"translationKey":8,"locale":9,"slug":10,"path":11,"_path":11,"title":12,"description":13,"excerpt":13,"image":14,"publishedAt":15,"updatedAt":16,"author":17,"tags":18,"featured":19,"sortOrder":20,"readingTime":21,"bodyType":22,"bodyBlocks":23,"bodyHtml":186},"strapi",3978,"imq8i0d8ejot2kmx300y2wpq","zh","websocket-proxy-egress-switch-recovery-testing","\u002Fblog\u002Fwebsocket-proxy-egress-switch-recovery-testing","实时应用切换代理 IP 出口后，如何检查连接重建与状态恢复","换出口后按关闭码、业务不变量和补发去重判断是否真的恢复；只看到连接建立就宣布恢复，是这类测试最常见的误判，必须补验订阅。","https:\u002F\u002Fcms.quanqiudaili.com\u002Fuploads\u002Flarge_cover_8476204254.webp","2026-11-06T07:35:37.035Z","2026-11-06T07:35:37.013Z","Global Proxies Team",[],false,0,2,"blocks",[24,37,42,50,58,66,74,82,94,98,102,106,114,122,126,130,134,138,149,153,165,169,174,178,182],{"type":25,"children":26},"paragraph",[27,30,35],{"text":28,"type":29},"换出口后，“连上了”不等于“恢复了”。连接重建要验握手、鉴权、重新订阅三步，状态恢复要验补齐、补多、去重三件事。","text",{"text":31,"type":29,"marks":32},"代理IP",[33],{"type":34},"bold",{"text":36,"type":29}," 切换时，旧连接不一定立刻断开，是否断开取决于切换方式，所以实验要拆成两类：一类保留旧连接、只改新连接配置，观察新请求走哪个出口；另一类主动关闭或注入故障，观察重建与补发。关闭码和补发区间是判断恢复质量的关键证据，但要结合两端日志一起看。",{"type":38,"level":21,"children":39},"heading",[40],{"text":41,"type":29},"记录代理出口切换时的 WebSocket 关闭码和连接事件",{"type":25,"children":43},[44,48],{"text":45,"type":29,"marks":46},"关闭码 1000：",[47],{"type":34},{"text":49,"type":29}," 正常关闭；查是否为主动切换。",{"type":25,"children":51},[52,56],{"text":53,"type":29,"marks":54},"关闭码 1001：",[55],{"type":34},{"text":57,"type":29}," 端点离开；查服务端维护或客户端退出。",{"type":25,"children":59},[60,64],{"text":61,"type":29,"marks":62},"关闭码 1006：",[63],{"type":34},{"text":65,"type":29}," 客户端本地合成的异常关闭，不会出现在收到的 Close 帧；结合浏览器网络错误、TCP\u002F代理日志和服务端记录定位。",{"type":25,"children":67},[68,72],{"text":69,"type":29,"marks":70},"关闭码 1011：",[71],{"type":34},{"text":73,"type":29}," 服务端内部错误；查服务端日志。",{"type":25,"children":75},[76,80],{"text":77,"type":29,"marks":78},"关闭码 4000+：",[79],{"type":34},{"text":81,"type":29}," 应用自定义；查业务定义。\n只记录“连接断了”没有信息量。关闭码是线索，不是判决书：1006 只能说明客户端本地观察到了异常断开，无法单凭它区分网络层、代理还是服务端；1001、1000 也可能来自服务端部署或客户端主动退出。定位时要同时看浏览器网络事件、代理连接日志和服务端侧记录，并把切换时刻、旧连接 ID、新连接 ID 一起记下来。",{"type":25,"children":83},[84,86,92],{"text":85,"type":29},"开始切换实验前，可通过",{"text":87,"type":29,"marks":88},"代理设置帮助",[89],{"href":90,"type":91},"https:\u002F\u002Fhelp.quanqiudaili.com\u002Fdocs\u002Fproxy-Settings","link",{"text":93,"type":29},"核对出口和接入参数。",{"type":38,"level":21,"children":95},[96],{"text":97,"type":29},"分别验证重连握手、身份认证和消息订阅",{"type":25,"children":99},[100],{"text":101,"type":29},"握手成功只说明 TCP 和 WebSocket 建立；鉴权成功说明身份恢复；重新订阅成功才说明消息流恢复。三步各自的观测点要分开记录，只看到连接建立就宣布恢复，是这类测试最常见的误判。",{"type":38,"level":21,"children":103},[104],{"text":105,"type":29},"检查重连后快照同步或增量消息补发",{"type":25,"children":107},[108,112],{"text":109,"type":29,"marks":110},"快照对齐（适用于状态小、可整体拉取）：",[111],{"type":34},{"text":113,"type":29}," 检查重连后全量版本号是否与服务端一致；若出现状态回退或重复则未恢复完整。",{"type":25,"children":115},[116,120],{"text":117,"type":29,"marks":118},"增量补发（适用于消息流、按序追加）：",[119],{"type":34},{"text":121,"type":29}," 检查`last_seq` 之后是否连续、有无补多；若出现缺口、重复则未恢复完整。\n两种都要验“补齐没有、补多没有”：补齐看序列连续性，补多看重复计数。重复消息通常来自三个地方：重新订阅导致双份推送、补发区间重叠、客户端重试重放。去重键要选业务键，不要用时间戳。判定前先按快照\u002F增量契约写清版本号、游标、应恢复集合和恢复窗口；原始重放出现重复是协议允许的，真正要看的是去重后的业务效果，不能一律拿“重复数=0”当硬门槛。",{"type":38,"level":21,"children":123},[124],{"text":125,"type":29},"对比切换前后的订阅、消息连续性和业务状态",{"type":25,"children":127},[128],{"text":129,"type":29},"比较切换前后的连接数、订阅数、消息序列和业务状态。连接数短暂变化不一定代表失败；判定标准是业务不变量，而不是“前后完全一致”：订阅最终恢复、缺口按协议补齐、没有重复副作用、状态版本达到基线预期，恢复耗时的上限由项目自己的 SLA 定义。",{"type":38,"level":21,"children":131},[132],{"text":133,"type":29},"记录连接恢复过程并判断是否通过",{"type":25,"children":135},[136],{"text":137,"type":29},"记录切换时间和方式、关闭码、重连耗时、补发区间及重复消息数。上面列出的四项业务不变量全部满足后，才可判定恢复；耗时按项目 SLA 判断。切换影响到生产订阅者或补发量超出预算时，立即停止并回滚出口。",{"type":25,"children":139},[140,142,147],{"text":141,"type":29},"切换后若仍无法恢复连接，可按",{"text":143,"type":29,"marks":144},"全球代理使用流程",[145],{"href":146,"type":91},"https:\u002F\u002Fhelp.quanqiudaili.com\u002Fdocs\u002FProcess",{"text":148,"type":29},"回查获取代理、设置参数和建立连接的步骤。",{"type":25,"children":150},[151],{"text":152,"type":29},"关闭码定义见：",{"type":154,"format":155,"children":156},"list","unordered",[157],{"type":158,"children":159},"list-item",[160],{"text":161,"type":29,"marks":162},"RFC 6455 第 7.4 节（状态码）",[163],{"href":164,"type":91},"https:\u002F\u002Fwww.rfc-editor.org\u002Frfc\u002Frfc6455.html",{"type":38,"level":21,"children":166},[167],{"text":168,"type":29},"常见问题",{"type":38,"level":170,"children":171},3,[172],{"text":173,"type":29},"连接重建成功就代表状态恢复了吗？",{"type":25,"children":175},[176],{"text":177,"type":29},"不代表。订阅和业务状态可能没恢复，要看补发与去重结果。",{"type":38,"level":170,"children":179},[180],{"text":181,"type":29},"1006 和 1001 有什么区别？",{"type":25,"children":183},[184],{"text":185,"type":29},"1006 是客户端在本地合成的异常关闭状态，不会由服务端通过 Close 帧发来；1001 是端点主动离开。","",[188,321,443],{"source":6,"id":189,"documentId":190,"translationKey":190,"locale":9,"slug":191,"path":192,"_path":192,"title":193,"description":194,"excerpt":194,"image":195,"publishedAt":196,"updatedAt":197,"author":17,"tags":198,"featured":19,"sortOrder":20,"readingTime":21,"bodyType":22,"bodyBlocks":199,"bodyHtml":186},3987,"ehyrf4ygtsshhwkd0o87xat6","tls-certificate-compatibility-proxy-testing","\u002Fblog\u002Ftls-certificate-compatibility-proxy-testing","应用发布前，如何检查代理环境下的 TLS 证书兼容性","先分清直连、隧道转发和代理中间人三类链路，再逐级核对证书链、信任库位置与 SAN；本机正常而容器失败的，多为 CA 没进镜像。","https:\u002F\u002Fcms.quanqiudaili.com\u002Fuploads\u002Flarge_TLS_cover_ad09ec52a5.webp","2026-11-06T07:43:24.293Z","2026-11-06T07:43:24.278Z",[],[200,209,213,217,226,230,234,238,242,246,250,254,258,262,266,270,274,278,289,293,302,305,309,313,317],{"type":25,"children":201},[202,204,207],{"text":203,"type":29},"代理环境下的 TLS 问题，先要分清证书由谁签发：直连看目标证书，隧道转发看目标证书，代理中间人看代理自签 CA。",{"text":31,"type":29,"marks":205},[206],{"type":34},{"text":208,"type":29}," 本身不改变 TLS 握手，但企业代理或抓包工具会替换证书链，发布前必须把这条链路完整验一遍。",{"type":38,"level":21,"children":210},[211],{"text":212,"type":29},"先确认代理是否会替换目标站的 TLS 证书",{"type":25,"children":214},[215],{"text":216,"type":29},"直连或 HTTP CONNECT 隧道通常保留目标站点的证书，应检查证书链、有效期和 SAN（证书允许使用的域名）。如果企业代理会解密并重新签发 TLS 流量，客户端看到的是企业代理签发的站点证书；此时要确认该证书能连到受信任的企业 CA。先判断代理是否替换了证书，再确定检查对象。",{"type":25,"children":218},[219,221,224],{"text":220,"type":29},"核对代理协议与客户端接入方式时，可参考",{"text":87,"type":29,"marks":222},[223],{"href":90,"type":91},{"text":225,"type":29},"。",{"type":38,"level":21,"children":227},[228],{"text":229,"type":29},"使用 OpenSSL 查看代理连接返回的证书链",{"type":25,"children":231},[232],{"text":233,"type":29},"```bash\nopenssl s_client -proxy proxy.example.com:8080 \\\n-connect target.example.com:443 \\\n-servername target.example.com -showcerts \\\n-verify_hostname target.example.com -verify_return_error \\\n-CAfile \u002Fpath\u002Fto\u002Fca-bundle.pem\n```",{"type":25,"children":235},[236],{"text":237,"type":29},"查看输出中的 `issuer`、有效期和 SAN，并重点看两件事：证书链能否构建到受信锚、主机名是否匹配。服务端通常只提供叶证书和中间证书，不要求它回传根证书；根证书应该来自你信任的本地信任库（`-CAfile` 或 `-CApath`），而不是从连接里“捡”到一个就信。验收看退出码和验证输出（`Verify return code`），不要只看“连接成功”；最后仍在真实应用运行时或容器里做一次正负对照。",{"type":25,"children":239},[240],{"text":241,"type":29},"`-proxy` 参数需要 OpenSSL 1.1.0 或更高版本，且默认只支持 HTTP CONNECT；SOCKS5 场景改用支持该协议的工具（如 curl 的 `--proxy socks5h:\u002F\u002F`）或对应的客户端验证。运行 `openssl version` 可先检查本机版本。",{"type":38,"level":21,"children":243},[244],{"text":245,"type":29},"检查系统、运行时和容器使用的证书信任库",{"type":25,"children":247},[248],{"text":249,"type":29},"信任库位置取决于应用使用的运行时：Linux 系统证书通常位于 `\u002Fetc\u002Fssl\u002Fcerts`，Java 使用自己的 `cacerts`，Node 可通过 `NODE_EXTRA_CA_CERTS` 指定额外 CA，Python 的 `requests` 常使用 `certifi`。容器还要单独检查镜像内的证书。本机正常而容器失败时，先比较两边实际加载的 CA。",{"type":38,"level":21,"children":251},[252],{"text":253,"type":29},"根据 TLS 错误定位证书链和主机名问题",{"type":25,"children":255},[256],{"text":257,"type":29},"出现 `unable to get local issuer certificate` 时，先检查证书链是否缺少中间 CA；出现 `self signed certificate` 时，确认它是否为预期的企业 CA 或测试证书。`hostname mismatch` 通常表示证书 SAN 不包含目标域名；`protocol version` 或 `cipher mismatch` 则需要核对客户端与服务端支持的 TLS 版本和加密套件。",{"type":38,"level":21,"children":259},[260],{"text":261,"type":29},"发布前检查证书有效期、信任链和轮换结果",{"type":38,"level":170,"children":263},[264],{"text":265,"type":29},"在预发布环境验证证书更新和轮换",{"type":25,"children":267},[268],{"text":269,"type":29},"发布前记录证书到期时间，证书轮换后复测一次；灰度环境的信任库要和生产一致，否则灰度通过、生产失败。",{"type":38,"level":170,"children":271},[272],{"text":273,"type":29},"记录证书签发者、有效期和信任库位置",{"type":25,"children":275},[276],{"text":277,"type":29},"发布前记录目标域名和端口、证书签发者与到期时间、信任库位置，以及代理是否会重新签发证书。\n检查清单四项：链能构建到受信锚、CA 在信任库、主机名匹配 SAN、证书未过期。任一项不满足就不能发布；发现证书替换影响生产流量时，立即回滚配置。",{"type":25,"children":279},[280,282,287],{"text":281,"type":29},"证书链异常仍无法归因时，可从",{"text":283,"type":29,"marks":284},"全球代理帮助中心",[285],{"href":286,"type":91},"https:\u002F\u002Fhelp.quanqiudaili.com\u002F",{"text":288,"type":29},"查找对应接入文档。",{"type":25,"children":290},[291],{"text":292,"type":29},"OpenSSL 参数以手册为准：",{"type":154,"format":155,"children":294},[295],{"type":158,"children":296},[297],{"text":298,"type":29,"marks":299},"`openssl s_client` 手册（`-proxy`、`-verify_hostname`、`-verify_return_error`）",[300],{"href":301,"type":91},"https:\u002F\u002Fdocs.openssl.org\u002F3.5\u002Fman1\u002Fopenssl-s_client\u002F",{"type":38,"level":21,"children":303},[304],{"text":168,"type":29},{"type":38,"level":170,"children":306},[307],{"text":308,"type":29},"代理环境下证书报错一定是代理问题吗？",{"type":25,"children":310},[311],{"text":312,"type":29},"不一定。先看证书链和信任库，主机名不符或 CA 缺失更常见。",{"type":38,"level":170,"children":314},[315],{"text":316,"type":29},"为什么本机正常、容器里失败？",{"type":25,"children":318},[319],{"text":320,"type":29},"容器镜像没有导入同一个 CA，信任库不一致。",{"source":6,"id":322,"documentId":323,"translationKey":323,"locale":9,"slug":324,"path":325,"_path":325,"title":326,"description":327,"excerpt":327,"image":328,"publishedAt":329,"updatedAt":330,"author":17,"tags":331,"featured":19,"sortOrder":20,"readingTime":21,"bodyType":22,"bodyBlocks":332,"bodyHtml":186},3986,"g5ee4zs0pa8hyuc307nk5nwf","proxy-error-handling-mock-testing","\u002Fblog\u002Fproxy-error-handling-mock-testing","如何在不依赖真实代理服务时测试应用的代理错误处理","用可注入的代理桩和故障矩阵覆盖超时、拒绝、认证失败，在受控环境验证重试、退避与降级；不接真实代理，也不记录任何真实凭据。","https:\u002F\u002Fcms.quanqiudaili.com\u002Fuploads\u002Flarge_cover_5ce09c130c.webp","2026-11-06T07:42:03.722Z","2026-11-06T07:42:03.708Z",[],[333,342,346,350,354,358,362,366,370,374,378,387,391,395,404,408,424,427,431,435,439],{"type":25,"children":334},[335,337,340],{"text":336,"type":29},"代理错误处理最适合用本地 mock 验证。407、超时、连接重置、TLS 失败、慢响应、半关闭六类故障都能稳定复现，测试不再依赖服务商侧的偶发故障。",{"text":31,"type":29,"marks":338},[339],{"type":34},{"text":341,"type":29}," 的真实链路不可控，mock 的价值就是把“偶发”变成“可重复”，但结论只对应用逻辑成立。",{"type":38,"level":21,"children":343},[344],{"text":345,"type":29},"代理错误测试需要覆盖的六种故障",{"type":25,"children":347},[348],{"text":349,"type":29},"可先覆盖六种常见故障：让 mock 返回带 `Proxy-Authenticate` 的 407，检查客户端是否重试或报告认证错误；延迟超过客户端超时时，检查超时处理；连接重置时，检查中断后的动作；TLS 负例一次只改一个变量（未受信 CA、可信 CA 但 SAN 不对、证书过期），并保持校验开启，看客户端实际报什么错；分段延迟发送时，记录首字节和完整响应耗时；测“响应不完整”时，用声明了 `Content-Length` 却提前 EOF、或缺少 chunked 终止块的响应做负例，完整的 close-delimited 响应只作正常对照——单纯关闭写方向并不等于响应不完整。之后再按应用实际支持的错误分支补充测试。\n矩阵按应用实际支持的错误分支建立，上表是起步集合，可以补代理 DNS 失败、认证挑战格式异常和重试上限三类用例。矩阵未覆盖的分支，不会被这一组测试验证。",{"type":38,"level":21,"children":351},[352],{"text":353,"type":29},"如何搭建可重复触发故障的 mock 代理",{"type":25,"children":355},[356],{"text":357,"type":29},"mitmproxy、toxiproxy 或自建 TCP 服务都可以用于 mock，但注入点要和故障所在层对应。407 需要能处理 CONNECT 并返回 `Proxy-Authenticate` 的受控代理；TCP 延迟和连接重置可在传输层注入；SOCKS 认证错误则要按 SOCKS 协议构造，不能用 HTTP 的 407 代替。记录工具、客户端版本、触发步骤和清理方法，其他人才能复现。",{"type":38,"level":21,"children":359},[360],{"text":361,"type":29},"除了错误码，还要验证应用如何处理故障",{"type":25,"children":363},[364],{"text":365,"type":29},"收到 407 只说明应用识别到了代理认证错误。还要检查重试次数和间隔、熔断或降级是否符合预期，以及日志是否隐藏凭据。407 和 401 要分开判断：前者由代理要求认证，后者由目标服务要求认证。",{"type":25,"children":367},[368],{"text":369,"type":29},"407 和 401 必须分开断言：`Proxy-Authenticate` 是代理要求认证，`WWW-Authenticate` 是目标服务要求认证。混在一起，会把代理认证失败记成应用登录失败。",{"type":38,"level":21,"children":371},[372],{"text":373,"type":29},"避免 mock 测试漏掉真实故障分支",{"type":25,"children":375},[376],{"text":377,"type":29},"要触发客户端超时，mock 延迟必须超过客户端配置的超时值；要测试请求中断后的处理，应在连接建立后再重置。mock 结果只说明应用对模拟故障的处理方式，不能直接推断真实代理链路表现。",{"type":25,"children":379},[380,382,385],{"text":381,"type":29},"mock 通过后若要转入真实环境验证，可按",{"text":143,"type":29,"marks":383},[384],{"href":146,"type":91},{"text":386,"type":29},"依次核对产品、参数和接入步骤。",{"type":38,"level":21,"children":388},[389],{"text":390,"type":29},"记录 mock 故障参数和应用处理结果",{"type":25,"children":392},[393],{"text":394,"type":29},"每个用例记录故障类型、注入参数、期望动作、实际结果，以及日志是否隐藏凭据。\n用例边界是应用逻辑，不覆盖真实服务商的 SLA 和地区行为。出现真实凭据进入 mock 日志、或测试影响到共享环境时，立即停止并清理。",{"type":25,"children":396},[397,399,402],{"text":398,"type":29},"开始真实环境验证前，可用",{"text":87,"type":29,"marks":400},[401],{"href":90,"type":91},{"text":403,"type":29},"核对协议、地址和认证参数。",{"type":25,"children":405},[406],{"text":407,"type":29},"故障注入工具与协议定义：",{"type":154,"format":155,"children":409},[410,417],{"type":158,"children":411},[412],{"text":413,"type":29,"marks":414},"toxiproxy（TCP 延迟、重置、带宽限制）",[415],{"href":416,"type":91},"https:\u002F\u002Fgithub.com\u002FShopify\u002Ftoxiproxy",{"type":158,"children":418},[419],{"text":420,"type":29,"marks":421},"RFC 9110《HTTP Semantics》（407 与 `Proxy-Authenticate`）",[422],{"href":423,"type":91},"https:\u002F\u002Fwww.rfc-editor.org\u002Frfc\u002Frfc9110.html",{"type":38,"level":21,"children":425},[426],{"text":168,"type":29},{"type":38,"level":170,"children":428},[429],{"text":430,"type":29},"mock 代理能替代真实代理测试吗？",{"type":25,"children":432},[433],{"text":434,"type":29},"不能。它验证应用逻辑，不验证真实链路的稳定性和地区行为。",{"type":38,"level":170,"children":436},[437],{"text":438,"type":29},"407 和 401 有什么区别？",{"type":25,"children":440},[441],{"text":442,"type":29},"407 是代理要求认证，401 是目标服务要求认证。",{"source":6,"id":444,"documentId":445,"translationKey":445,"locale":9,"slug":446,"path":447,"_path":447,"title":448,"description":449,"excerpt":449,"image":450,"publishedAt":451,"updatedAt":452,"author":17,"tags":453,"featured":19,"sortOrder":20,"readingTime":21,"bodyType":22,"bodyBlocks":454,"bodyHtml":186},3985,"bpamp8c4gnpz3p9kslssa97y","ci-runner-proxy-build-impact-testing","\u002Fblog\u002Fci-runner-proxy-build-impact-testing","如何在 CI Runner 中验证代理不会影响构建步骤","在 Runner 内固定镜像、构建输入与测试时段，用出口 IP、DNS、TLS 和依赖清单作为证据，区分代理影响与构建环境波动；产物哈希只在构建可复现时才作对照。","https:\u002F\u002Fcms.quanqiudaili.com\u002Fuploads\u002Flarge_CI_Runner_cover_023a325e36.webp","2026-11-06T07:41:47.066Z","2026-11-06T07:41:47.049Z",[],[455,459,463,467,471,475,484,488,492,496,500,504,508,512,521,525,541,544,548,552,556],{"type":25,"children":456},[457],{"text":458,"type":29},"判断代理是否影响 CI，不能只看最终构建是否成功。应按实际流程检查代码拉取、依赖安装、单元测试、构建和产物上传。代理配置可能来自环境变量、包管理器或容器后台服务；配置不一致时，本地命令与流水线可能表现不同。",{"type":38,"level":21,"children":460},[461],{"text":462,"type":29},"如何判断代理没有影响 CI 构建",{"type":25,"children":464},[465],{"text":466,"type":29},"按顺序检查代码是否拉取到相同 commit、依赖校验是否通过、单元测试结果是否与基线一致。构建可复现时比较产物哈希；否则比较关键文件和依赖清单。上传环节要确认远端文件实际存在，并在支持时核对摘要或完整性校验。各阶段记录退出码和耗时；确需记录出口 IP 时，应使用受控日志并隐藏代理凭据。",{"type":38,"level":21,"children":468},[469],{"text":470,"type":29},"检查 CI 环境变量、包管理器和容器的代理配置",{"type":25,"children":472},[473],{"text":474,"type":29},"先检查构建进程是否读取 `HTTP_PROXY` 和 `HTTPS_PROXY`，再确认 `NO_PROXY` 中哪些地址会绕过代理。npm、pip 和 Maven 也可能分别读取 `.npmrc`、`pip.conf` 或 `settings.xml`；容器拉取镜像则由容器后台服务的配置决定。不要只凭宿主机环境变量判断代理已经生效。",{"type":25,"children":476},[477,479,482],{"text":478,"type":29},"配置入口和协议参数可对照",{"text":87,"type":29,"marks":480},[481],{"href":90,"type":91},{"text":483,"type":29},"逐项核实。",{"type":38,"level":21,"children":485},[486],{"text":487,"type":29},"对比启用代理和不启用代理时的构建结果",{"type":25,"children":489},[490],{"text":491,"type":29},"在相同 commit、Runner 镜像、依赖来源和资源规格下，分别运行启用代理与不启用代理的任务。冷缓存和暖缓存应分开比较，并使用隔离的缓存目录；每组可重复运行并保留原始结果。除代理开关外，其他条件尽量保持一致，便于判断差异来源。",{"type":38,"level":21,"children":493},[494],{"text":495,"type":29},"区分正常的耗时变化与构建产物差异",{"type":25,"children":497},[498],{"text":499,"type":29},"代理会让下载耗时发生变化，但不能预设“代理一定更慢”。判断是否影响构建，用各阶段耗时分布和事前设定的预算，不用单次耗时下结论；产物对照的前提是构建可复现、非确定性元数据已规范化；做不到时改比依赖清单、测试结果、关键文件和构建日志，不能仅凭整体哈希变化判定代理造成回归。",{"type":25,"children":501},[502],{"text":503,"type":29},"两类误判最常见：`NO_PROXY` 漏配，内部制品库被送进代理，出现 407 或超时；缓存掩盖失败，上一次的缓存让本次跳过了真实下载，清缓存复跑才暴露问题。",{"type":38,"level":21,"children":505},[506],{"text":507,"type":29},"记录 CI 代理测试结果并设定验收条件",{"type":25,"children":509},[510],{"text":511,"type":29},"记录 Runner 镜像、commit、实际生效的代理配置和各阶段结果，并注明缓存命中情况。被缓存跳过的步骤应标为未覆盖，必要时单独补跑。所有相关阶段通过后，再依据构建是否可复现判断产物差异。若影响其他流水线、日志出现凭据或耗时超过预算，应停止并回滚配置。",{"type":25,"children":513},[514,516,519],{"text":515,"type":29},"CI 中出现连接失败时，可从",{"text":283,"type":29,"marks":517},[518],{"href":286,"type":91},{"text":520,"type":29},"查找官方接入说明。",{"type":25,"children":522},[523],{"text":524,"type":29},"相关机制说明：",{"type":154,"format":155,"children":526},[527,534],{"type":158,"children":528},[529],{"text":530,"type":29,"marks":531},"`npm ci` 的安装与校验行为",[532],{"href":533,"type":91},"https:\u002F\u002Fdocs.npmjs.com\u002Fcli\u002Fv10\u002Fcommands\u002Fnpm-ci",{"type":158,"children":535},[536],{"text":537,"type":29,"marks":538},"GitHub Actions 依赖缓存",[539],{"href":540,"type":91},"https:\u002F\u002Fdocs.github.com\u002Factions\u002Fusing-workflows\u002Fcaching-dependencies-to-speed-up-workflows",{"type":38,"level":21,"children":542},[543],{"text":168,"type":29},{"type":38,"level":170,"children":545},[546],{"text":547,"type":29},"CI 里设了 HTTP_PROXY 为什么还是不走代理？",{"type":25,"children":549},[550],{"text":551,"type":29},"可能被 NO_PROXY 覆盖，或包管理器有独立配置，以实际生效的配置为准。",{"type":38,"level":170,"children":553},[554],{"text":555,"type":29},"构建成功就说明代理没影响吗？",{"type":25,"children":557},[558],{"text":559,"type":29},"不一定。缓存可能掩盖下载失败，需要清缓存复跑并比对产物。",[561],{"source":6,"id":7,"documentId":8,"translationKey":8,"locale":9,"slug":10,"path":11,"_path":11,"title":12,"description":13,"excerpt":13,"image":14,"publishedAt":15,"updatedAt":16,"author":17,"tags":562,"featured":19,"sortOrder":20,"readingTime":21,"bodyType":22,"bodyBlocks":563,"bodyHtml":186},[],[564,571,574,580,586,592,598,604,611,614,617,620,626,632,635,638,641,644,651,654,661,664,667,670,673],{"type":25,"children":565},[566,567,570],{"text":28,"type":29},{"text":31,"type":29,"marks":568},[569],{"type":34},{"text":36,"type":29},{"type":38,"level":21,"children":572},[573],{"text":41,"type":29},{"type":25,"children":575},[576,579],{"text":45,"type":29,"marks":577},[578],{"type":34},{"text":49,"type":29},{"type":25,"children":581},[582,585],{"text":53,"type":29,"marks":583},[584],{"type":34},{"text":57,"type":29},{"type":25,"children":587},[588,591],{"text":61,"type":29,"marks":589},[590],{"type":34},{"text":65,"type":29},{"type":25,"children":593},[594,597],{"text":69,"type":29,"marks":595},[596],{"type":34},{"text":73,"type":29},{"type":25,"children":599},[600,603],{"text":77,"type":29,"marks":601},[602],{"type":34},{"text":81,"type":29},{"type":25,"children":605},[606,607,610],{"text":85,"type":29},{"text":87,"type":29,"marks":608},[609],{"href":90,"type":91},{"text":93,"type":29},{"type":38,"level":21,"children":612},[613],{"text":97,"type":29},{"type":25,"children":615},[616],{"text":101,"type":29},{"type":38,"level":21,"children":618},[619],{"text":105,"type":29},{"type":25,"children":621},[622,625],{"text":109,"type":29,"marks":623},[624],{"type":34},{"text":113,"type":29},{"type":25,"children":627},[628,631],{"text":117,"type":29,"marks":629},[630],{"type":34},{"text":121,"type":29},{"type":38,"level":21,"children":633},[634],{"text":125,"type":29},{"type":25,"children":636},[637],{"text":129,"type":29},{"type":38,"level":21,"children":639},[640],{"text":133,"type":29},{"type":25,"children":642},[643],{"text":137,"type":29},{"type":25,"children":645},[646,647,650],{"text":141,"type":29},{"text":143,"type":29,"marks":648},[649],{"href":146,"type":91},{"text":148,"type":29},{"type":25,"children":652},[653],{"text":152,"type":29},{"type":154,"format":155,"children":655},[656],{"type":158,"children":657},[658],{"text":161,"type":29,"marks":659},[660],{"href":164,"type":91},{"type":38,"level":21,"children":662},[663],{"text":168,"type":29},{"type":38,"level":170,"children":665},[666],{"text":173,"type":29},{"type":25,"children":668},[669],{"text":177,"type":29},{"type":38,"level":170,"children":671},[672],{"text":181,"type":29},{"type":25,"children":674},[675],{"text":185,"type":29},null,[],[]]