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