跳到正文
vivo互联网技术· vivo互联网技术·· 2 小时前精选AI 评分73

域名埋点零侵入实时校验实践:基于代理转发与规则驱动的工具设计

AI 导读

vivo 互联网前端团队介绍 Flyheart Monitor,通过代理转发和规则驱动校验,在不修改业务代码的情况下实时检查 H5 域名埋点。工具以 Node.js 接收请求并通过 WebSocket 推送至浏览器,按 cfrom 匹配规则、检查参数缺失与类型异常,并支持将请求和校验结果导出为 Canvas 宫格图。

推荐理由

通过代理转发接入已有请求,再用声明式规则完成校验与结果导出,为不便改动业务代码的测试场景提供了可迁移的实现思路。

正文

作者:vivo 互联网前端团队- Zhu Qinchuan

目录

01. 埋点测试的痛点

02. Flyheart Monitor 简介

03. Flyheart Monitor 的三个巧思

04. AI 辅助:把录入规则这件苦差事交给机器

05. 结语 & 未来展望

埋点是数据驱动的地基,成熟平台大多自带可视化测试后台,但仍有大量项目用的是没有配套后台的域名埋点,测试只能靠"抓包+肉眼比对"。自建后台要侵入业务代码,代价太高。本文介绍 Flyheart Monitor,一套零侵入的域名埋点实时校验工具:它不在 H5 页面端主动上报,而是在请求的必经之路上拦截一份数据去校验,且不绑定任何特定抓包代理(只要支持接口转发即可接入)。文章聚焦几个解决实际问题的巧思:把抓包请求变成实时数据流、用规则代替人脑校验、把验证结果沉淀为可归档的图片。

1分钟看图掌握核心要点👇

01

埋点测试的痛点

埋点是数据驱动的地基。数据分析、AB 实验、广告归因、用户增长,几乎所有下游决策都建立在埋点数据之上。一旦埋点漏报或错报,下游数据会全线失真,而这种失真往往在几周后才被发现,代价高昂。

正因如此,埋点上线前的测试至关重要。不过,受历史遗留问题、项目优先级等因素影响,部分项目在这一环节积累了技术债务,测试工作仍主要依靠人工完成:手机连上抓包代理(比如 Charles),操作页面触发埋点,然后在代理工具的请求列表里逐条查找,找到目标埋点后,仔细核对参数是否齐全、类型是否正确。

这套流程有几个绕不开的问题:

  1. 定位困难:埋点请求淹没在海量网络请求中,靠肉眼逐条查找既慢又累

  2. 容易漏报:一次操作可能触发多个埋点,事件密集时人工很难覆盖全部

  3. 校验繁琐:某个参数该是字符串还是数字、必填项是否缺失,全凭测试人员逐字判断,稍一分心就会看漏

  4. 交付低效:验证结果需要人工对每个埋点、每种场景逐一截图,整理后再传递给下游,心智负担重

这些问题的根源,是埋点测试缺少一个"把请求、规则、校验结果三者串起来"的工具。测试人员既充当抓包器,又充当规则库,又充当校验引擎,全靠人脑串联。

常见解法:带测试后台的埋点平台

面对这类痛点,业界成熟的思路是让埋点平台自带一个可视化的埋点测试后台 ——集中维护埋点规范、实时接收上报数据、自动比对校验,规则、上报、校验一站式打通。对于已经接入这类平台的项目来说,埋点测试早已不再是难题。

现实困境:大量项目仍在用域名埋点

理想虽好,现实却存在一个绕不开的前提。公司内有许多项目使用的是域名埋点,这类埋点方式没有上述配套的测试后台:埋点规范通常只记录在文档中,上报的数据也缺少一个能实时接收并自动校验的系统。因此,测试人员在面对这些项目时,往往只能回到最基础的方式——抓包,再逐条用肉眼比对。

于是一个自然的想法浮现出来:既然域名埋点没有测试后台,能不能自己为它补一个?

自建后台的代价:侵入业务代码

从技术上讲,自建一个埋点测试后台完全可行。但真正动手时会发现,最大的障碍不在后台本身,而在"数据怎么进后台"这一步。

要让后台能校验,就得让 H5 页面在上报埋点的同时,额外向这个后台再报一份数据。这意味着必须修改业务代码:接入一套新的上报 SDK、修改现有的埋点上报逻辑,甚至对全量埋点点位做一遍改造。对于这些已经稳定运行、点位众多的老项目来说,这样的侵入式改造成本高、回归风险大,还需要协调各个业务方配合排期——投入与产出很难成正比。

有没有更优雅的办法?

于是问题就变成了:有没有一种方案,既能实时校验埋点,又完全不碰业务代码?

自建后台之所以要侵入业务代码,症结在于"让 H5 页面主动把数据多报一份给后台"。如果把这个方向反过来,问题就迎刃而解了:数据无需主动上报,而是在它上报的必经之路上被拦截下来。H5 页面的埋点请求本来就要发往网络,只需在链路中间拦截一次、复制一份拿去校验,业务代码一行都不用改。Flyheart Monitor 正是沿着这个思路诞生的——让机器负责抓取和校验,让人只负责看结论。

02

Flyheart Monitor 简介

用一句话概括:Flyheart Monitor 是一套面向 H5 页面的域名埋点抓包与参数校验工具。它把 H5 页面上报的埋点请求实时"喂"到浏览器页面上,并根据预先配置的测试计划自动校验参数是否合规。

这里要特别强调一个定位:这套工具并不与 Charles 绑定。它对抓包代理的唯一要求,是代理能提供"接口转发/重定向"这个通用能力——这几乎是所有主流抓包代理的标配功能,手边惯用的任意一款支持接口转发的代理都可以搭配使用。下文选择以 Charles 举例,只是因为它在移动端测试中较为常见。

整体数据流非常直白——请求从手机出发,经过一次代理拦截、一次转发、一次广播,最终落到浏览器上被校验和展示:

手机 App
  → 抓包代理(Charles / Fiddler / Whistle 等,拦截 HTTPS 流量)
  → 接口转发/重定向到 http://localhost:9999/webhook
  → node-forwarder(HTTP 接收 + WebSocket 广播)
  → ws://localhost:8765
  → 浏览器页面实时展示 + 参数校验

整个系统只有两个轻量模块,技术选型也刻意保持简单:

模块

定位

技术选型

node-forwarder

HTTP Webhook 接收 + WebSocket 广播

Node.js 18+ / ws

web

实时监控与校验前端

Vue 3 + Vite 6

抓包链路

HTTPS 流量拦截与接口转发

任意支持转发的代理

(Charles/Fiddler/Whistle…)

值得一提的是,这套工具没有引入任何重量级的后端框架、数据库或消息队列。node-forwarder 是一个不到三百行的原生 Node.js 脚本,前端是一个纯静态的 Vue 应用。这种"轻"是刻意的——它要能让测试人员在自己的电脑上一分钟内启动(后面还会看到,我们甚至把它打包成了双击即用的可执行文件),而不是依赖一套需要运维的服务。真正的价值不在架构的复杂度,而在于对几个具体问题的处理方式。下面几章就来拆解这些巧思。

03

Flyheart Monitor 的三个巧思

3.1 把抓包请求变成一条实时数据流

这套工具对抓包代理的要求,简单到只有一条:把目标埋点接口的请求整体转发/重定向到本地的一个 webhook 地址。这样一来,页面每次上报埋点,请求就会自动流向本地服务,形成一个天然的拦截入口,测试人员不需要在代理里做任何逐条的手动操作。

正因为只依赖"接口转发"这个所有主流代理都具备的基础能力,工具才没有与某一款代理绑定——Charles 里叫 Map Remote,Fiddler 里是重定向规则,Whistle 里是转发规则,配置方式不同,能力本质一样。测试人员用哪款顺手就用哪款,一行转发规则即可接入。

接口转发解决了"请求从哪里来"的问题,node-forwarder 则负责"请求怎么到浏览器"。它的设计很紧凑——同一个进程里同时运行着两套服务:HTTP 服务监听 9999 端口的 /webhook 接收代理转发的请求,WebSocket 服务监听 8765 端口向所有已连接的浏览器页面实时广播。这条"HTTP 进、WebSocket 出"的链路,就是把静态的抓包动作变成一条实时数据流的核心。

这里有个容易被忽略的细节:埋点上报的请求体格式多种多样,有的是 JSON,有的是 URL 编码表单,还有的是纯文本。为此,工具采用了一套逐层兜底的解析策略——先尝试 JSON 解析,失败则尝试表单解析,再失败就原样包装成文本。这样一来,无论 H5 页面用什么格式上报,请求都不会因解析失败而丢失。

function parsePayload(bodyText, contentType){
  if (!bodyText) return {}
  // 尝试 JSON 解析
  try {
    return JSON.parse(bodyText)
  } catch (_) {}
  // 尝试 URL 编码表单解析(只要内容包含 = 就尝试)
  if (bodyText.includes('=')) {
    try {
      const formPayload = {}
      const params = new URLSearchParams(bodyText)
      for (const [key, value] of params.entries()) {
        formPayload[key] = value
      }
      if (Object.keys(formPayload).length > 0) return formPayload
    } catch (_) {}
  }
  // 兜底:原始文本
  return { raw_data: bodyText }
}
  • 多台设备并行测试时,请求列表里全是相同的埋点,难以区分来源。工具通过 User-Agent 调用内部服务,把难以辨认的 UA 字符串翻译成"iQOO 13"这样的机型名称展示在请求上,并用 Map 缓存避免重复查询。前端因此能按机型维度筛选请求,在多设备回归测试时尤为实用。

此外,前端支持通过 URL 参数指定服务地址(?ws=电脑IP:8765),让工具由单人使用扩展为团队共享;WebSocket 断线后每 3 秒自动重连,连接状态实时显示在页面顶部。

最后还有一个面向非开发用户的务实设计:借助 pkg 把 node-forwarder 连同 Node 运行时、依赖一起打包成单个可执行文件,分别产出 macOS arm64、macOS x64 和 Windows 三个平台的版本。测试人员双击即可启动,无需预装任何环境。

3.2 用规则把"人脑校验"变成"自动校验"

抓包请求实时流进来只是第一步。真正把测试人员从"肉眼比对"中解放出来的,是一套规则驱动的校验机制。

工具的测试计划模型非常克制,只有两个核心概念:

  • cfrom:埋点事件的 ID,作为请求和规则之间的匹配键。每条上报请求都带 cfrom,每条规则也绑定一个 cfrom,两者一对上就能关联起来。

  • coreParams:一组参数声明,每项包含 name(参数名)、type(类型)、desc(描述)。它本质上是在声明"这个埋点应该有哪些参数、每个参数是什么类型"。

请求进来后,会按 cfrom 自动分组存储——同一个埋点的所有历史请求聚在一起,天然形成"一个埋点一档案"的结构。测试人员点开某条规则,就能看到这个埋点收到过的全部请求。

一份测试计划就是一个规则数组,结构如下:

[
  {
    "ruleName": "首页 Banner 点击",
    "cfrom": "01|03|029|008",
    "coreParams": [
      { "name": "series", "type": "string", "desc": "系列编码" },
      { "name": "position", "type": "number", "desc": "位置序号" }
    ]
  }
]

有了声明式的规则,校验逻辑就变得很直接。对每条收到的请求,逐个检查其 coreParams:参数是否缺失?值的类型是否正确(字符串、数字、布尔、数组、对象)?

export function validatePayload(payload, coreParams){
  const errors = []
  for (const param of coreParams) {
    const val = payload[param.name]
    if (val === undefined || val === null || val === '') {
      errors.push(`缺少参数 ${param.name}`)
      continue
    }
    const actualType = Array.isArray(val) ? 'array' : typeof val
    if (actualType !== param.type) {
      errors.push(`${param.name} 应为 ${param.type},实际为 ${actualType}`)
    }
  }
  return { status: errors.length ? 'fail' : 'pass', errors }
}

校验结果被直观地转化为视觉信号。点开请求详情,JSON 会高亮展示:类型不匹配的字段直接标红并加波浪下划线,鼠标悬停还能看到"预期类型"的提示。对存在类型异常的请求,列表上会显示一个跳动的"⚠️ 参数类型异常"脉冲标记,让问题请求在一堆正常请求里一眼可辨。

从"逐字比对每个字段"到"扫一眼有没有红点",这就是规则化校验带来的效率跃迁。

工具还有一个反向发现的设计值得一提:收到请求但 cfrom 不在测试计划中的埋点,会被单独归入"未匹配规则"分组,用醒目的黄色标记出来。这让测试人员能反向发现"多报""错报"这类问题——它们在传统的逐条核对模式下极易被忽略,因为人往往只会关注计划之内的埋点。

当请求量增大,快速定位就成了刚需。工具提供了多个维度的筛选:按规则名称筛选、点击 cfrom 标签快速过滤、关键词搜索、按机型/UA 筛选设备、请求按时间正序或倒序排列。这些筛选组合起来,测试人员能在几秒内定位到目标请求。

3.3 把"验证通过"沉淀成一张图

测试做完,还有最后一步——如何把结果交付给开发和产品?传统做法是截取大量图片、配上文字说明,既零散又难以归档。

工具内置了一个 Canvas 宫格图导出功能。测试人员在请求详情里勾选若干条请求,点击"宫格图预览",程序就用 Canvas 把这些请求拼接成一张排版整齐的宫格截图,确认无误后一键下载成 PNG。

为什么不直接用浏览器截图或 DOM 转图片的现成方案?因为那些方案要么受限于可视区域(长列表截不全),要么依赖页面当前样式(导出的图会带上一堆无关的 UI 元素)。这里选择用 Canvas 逐像素重新绘制,好处是完全掌控排版——绘制哪些内容、如何布局、清晰度多高,都可以自主决定。

这张图不是简单的页面截屏,而是专门为"汇报"重新排版的成果。它的绘制过程藏着几个值得一提的细节。

先测量尺寸,再正式绘制。 Canvas 在绘制文本前并不知道一段文字最终会占据多少高度,而每条请求的参数数量不一,格子高度必须动态计算。这里的做法是先用一个离屏 Canvas 对每条请求的内容逐行测量文本宽度、算出换行后的总行数,得到每个格子的真实高度,再据此规划整张图的布局。宫格的行列数按请求数量开平方自动决定,尽量接近正方形;每一行的高度取该行最高格子的高度,保证对齐整齐。

// 离屏量算换行后高度
const mCanvas = document.createElement('canvas')
const mCtx = mCanvas.getContext('2d')

const cellData = reqs.map((req, idx) => {
  const { deviceLabel, timeLabel, rows } = buildRows(req, idx)
  const expanded = []
  for (const row of rows) {
    setFont(mCtx, row.type)
    // 逐行量算,超宽则自动换行
    wrapLine(mCtx, row.text, MAX_TEXT_W).forEach((t, wi) =>
      expanded.push({ text: t, type: wi === 0 ? row.type : 'kv', isError: row.isError })
    )
  }
  const contentH = expanded.reduce((s, r) => s + LINE_HEIGHT, 0)
  return { deviceLabel, timeLabel, expanded, cellH: TITLE_H + PADDING + contentH + PADDING }
})

// 行列数按数量开平方,尽量接近正方形
const COLS = Math.ceil(Math.sqrt(cellData.length))
const ROWS = Math.ceil(cellData.length / COLS)

为高清屏而绘。 直接按逻辑尺寸绘制,导出的图在高分屏上会显得模糊。工具取设备像素比(且不低于 2)放大 Canvas 的物理尺寸,再对绘图上下文做等比缩放。这样绘制逻辑仍按逻辑坐标编写,产出的却是一张分辨率翻倍的高清图,放大查看参数值也依然清晰。

const DPR = Math.max(window.devicePixelRatio || 1, 2)
canvas.width = logicW * DPR
canvas.height = logicH * DPR
const ctx = canvas.getContext('2d')
ctx.scale(DPR, DPR)

综合下来,这张宫格图具备以下几个特点:

  • 高清渲染:按设备像素比做 HiDPI 渲染,图片放大也不模糊

  • 自动换行:长参数值会先测量宽度再逐字换行,不会溢出格子

  • 分组展示:每个格子里,"关注参数"和"其他参数"分区展示,并按 key 排序,一目了然

  • 异常标红:类型不匹配的字段在图里绘上红色底色和红字,与页面上的校验结果保持一致

  • 信息完整:每格顶部带机型名称、序号(第几条)和精确到秒的时间戳,可追溯

值得一提的是,导出流程被拆成了"预览"和"下载"两步——点击后先弹出预览,让用户确认排版和内容,满意后再下载。这就避免了"直接存成文件、发现排版不对又要重来"的返工。

这样一来,"验证通过"就从一句口头描述,变成了一张信息完整、清晰可读、可直接归档进测试报告的图片凭证。一个看似不起眼的导出功能,实际上补齐了埋点测试"验证—汇报"闭环的最后一环。

04

AI 辅助:把录入规则这件苦差事交给机器

前面三个巧思构成了工具的主干。在此之上,还有一处锦上添花的效率优化值得一提。

配置测试计划有一项隐藏的体力活:埋点详情通常整理在 Excel 中,要把它们逐条录入成规则,既枯燥又容易出错。为此,工具提供了一个 AI 辅助入口来简化这件事。

用法很简单:把 Excel 里的埋点详情整段复制、粘贴到输入框,点击生成,AI 就会把它解析成规则 JSON。工程上有几个处理细节让体验更顺滑:

  • 流式响应:采用 SSE 边生成边预览,用户能实时看到规则逐条生成,而不必等待完整结果

  • 容错解析:AI 输出有时会夹带 Markdown 代码块标记,前端用正则把其中的 JSON 部分提取出来,避免解析失败

  • 状态提示:解析成功或失败有明确的标签提示,确认无误后一键应用到测试计划

需要强调的是,AI 在这里只是一个"录入加速器",是锦上添花的辅助功能,而非平台的核心。它的价值在于省去重复的手工录入,真正解决测试痛点的仍然是前面那套抓包链路和规则校验机制。

此外,规则还支持页面手动新增、导入/导出 config.json。导入时会做规则规范化处理——不管来源配置格式多么不规范(coreParams 缺字段、格式不标准),都会被统一整理成标准结构再入库。这让不同人、不同来源的测试计划都能顺畅导入,不会因格式差异报错。测试计划因此变成一份可复用、可版本化、可在团队间传递的资产,而不是每次测试都要从头配置的一次性文件。

05

结语 & 未来展望

Flyheart Monitor 的架构并不复杂——两个轻量模块,几百行代码。它的核心思路也很简单:不改业务代码,只借助代理的接口转发能力在链路中间拦截一份数据,再通过规则自动比对。围绕这个思路,文章介绍了几个解决具体问题的设计,它们共同完成了一件事:把域名埋点的测试从"人工逐条肉眼核对"升级为"规则驱动的实时可视化校验"。测试人员不再需要同时充当抓包器、规则库和校验引擎,而是回归到只需要看结论、做判断的角色。

目前这套工具还有一些可以继续打磨的方向:

  • 规则云端化:测试计划目前存储在浏览器本地,未来可以升级为服务端存储,支持团队共享与版本管理

  • 历史留存与回放:请求记录持久化,支持跨会话查询和回归对比

  • 自动化校验报告:一轮测试结束后自动生成通过率、异常清单等报告

工具的意义,是把人从重复劳动中解放出来,把精力放到真正需要判断的地方。

END

猜你喜欢

来源:vivo互联网技术 · mp.weixin.qq.com