关于前端:卡口服务-基于前端巡检系统的拓展实践|得物技术

2次阅读

共计 8234 个字符,预计需要花费 21 分钟才能阅读完成。

1 背景

体验是得物的业务关键词之一,对于前端开发而言,进步用户体验更是重要工作内容之一。

得物前端平台目前有巡检零碎、监控平台等多种手段保障线上页面稳固运行,然而仍有一部分问题处于“监控死角”,而且巡检、监控都属于后置告警伎俩,为了确保页面上线前就能失去肯定的用户体验保障,联合公司的战略目标,咱们决定开发一个 H5 页面检测服务,用来前置检测行将上线的页面,提前裸露该页面可能存在的问题反馈给对应的开发 / 经营,咱们将这个服务称之为:“体验卡口”。

本文从这次“体验卡口”服务的开发实际登程,同时介绍得物巡检零碎的架构和设计,心愿能给参加稳定性建设的开发小伙伴提供肯定的学习和参考价值。

2 用户体验量化规范

当咱们试图量化影响用户体验的问题时,须要思考以下两个次要问题:

什么影响了用户体验?

咱们曾经通过丰盛的数据撑持和实践经验,对影响用户体验的因素有了深刻理解。从过来的线上问题反馈收集和开发教训中,咱们将体验问题大抵分为两个等级:

P0 级:这些问题重大影响页面加载速度或波及到平安危险,例如页面蕴含超大的图片 / 媒体资源、页面中含有个人隐私信息;

P1 级:这些问题可能对用户体验造成潜在影响,例如页面中存在响应工夫超过 300ms 的接口申请。

如何检测以及量化问题?

一旦咱们对体验问题进行了定义和分级,接下来须要建设适当的机制来检测这些问题。对于卡口服务,咱们能够采取以下步骤来量化问题、转换为可执行的检测代码,并通过卡口服务生成相应的检测报告供调用方应用:

  1. 确定指标和规范:首先,咱们须要确定用于量化体验问题的指标和规范。例如,对于接口申请速度问题,能够应用接口响应工夫作为指标,同时设定肯定的规范,例如超过特定工夫阈值即视为问题。
  2. 编写自动化脚本:基于指标和规范,咱们能够编写自动化脚本来模仿用户在无头浏览器中执行相干操作,例如加载页面、点击按钮、发送申请等。这些脚本将依据设定的指标进行性能测量和问题检测。
  3. 应用无头浏览器执行测试:咱们能够在无头浏览器中运行自动化脚本,模仿用户行为并收集相应的性能数据。
  4. 后果剖析和报告生成:通过收集的性能数据,咱们能够进行后果剖析,并将问题和相干数据转化为检测报告。该报告能够包含问题的详细描述、问题等级、相干性能指标和数据。

提供给调用方:最初,通过卡口服务,咱们能够将生成的检测报告提供给调用方。调用方能够依据报告中的问题和数据进行相应的优化和改良,以晋升用户体验。

这样的机制能够帮忙咱们自动化地检测和量化体验问题,并提供可执行的检测代码和相干报告。这样一来,咱们能够更无效地辨认和解决问题,并提供精确的数据给予开发团队进行优化。

以下是咱们整顿出须要具体实现的检测 case:

收集完具体的影响用户体验的 case 之后,要确定具体的开发计划,因为卡口服务与得物前端平台巡检零碎有很多技术实现重合的局部,所以咱们决定利用现有的巡检架构,将“体验卡口”集成到现有的巡检零碎中,能够节俭大量的开发工夫。

3 巡检零碎基础架构

巡检零碎的程序指标一句话总结:定时从数据源获取待检测页面地址列表,而后进行批量检测并生成报告。

为了应答不同场景下的个性化需要,巡检零碎形象出了三个巡检器基类,各场景继承基类实现定制需要。

3.1 巡检器基类

1. DataProviderBase(数据提供基类):

dataSlim(): 简化冗余数据;

fetchData(): 获取近程数据,解决并返回待检测页面 url 列表;

isSkipTime(): 用来设置条件,在某些特定条件下跳过定时工作;

schedule(): 设置定时工作运行区间;

2. PageInspectorBase(页面查看器基类):

check(): 查看器入口,用来关上指定的检测页面,并初始化各种资源的监听;

injectRequestHeaders(): 注入页面接口申请须要的 cookie、token 等;

urlCheck(): url 地址查看;

onRequest(): 监听页面申请;

onResponse(): 监听页面响应;

onPageError(): 监听页面谬误;

3. DataReporterBase(数据报告基类):

buildReporter(): 依据采集到的错误信息生成检测报告;

feishuNotify(): 将生成的报告通过飞书发送到指定的告诉群;

getHTMLReporterUrl(): 依据 ejs 模板将报告生成 html 动态文件并上传,返回在线报告地址;

咱们能够形象地将这三个基类比成一家饭店的三个不同分工的部门,能更不便地去了解它:

饭店前台负责接管顾客提供的订单,后厨依据订单下料炒菜装盘,服务员将做好的饭菜提供给顾客。

DataProviderBase(数据提供基类): 负责定时轮询接管内部提供的待检测页面列表。这个组件相似于饭店前台,接管顾客提供的订单。它负责从内部获取待检测的页面列表,并将这些页面传递给检测器进行检测。

PageInspectorBase(页面查看器基类): 逐个检测页面列表中的每一个 URL,并检测页面中的潜在问题。相似于后厨依据订单下料、炒菜和装盘的过程,这个组件负责一一检测待检测页面列表中的 URL,并对每个页面进行问题检测。它能够应用一系列的检测办法和规定,以确定页面是否存在潜在问题。

DataReporterBase(数据报告基类): 将检测收集的问题进一步整顿后发送报告。相似于服务员将做好的饭菜提供给顾客,这个组件负责将通过检测的问题进行整顿和汇总,并生成相应的报告。报告能够包含问题的形容、重大水平、相干页面 URL 等信息。而后,报告能够被发送给相干的利益相关者,例如开发或经营。

3.2 巡检器

基于以上三个基类,依据不同巡检场景开发不同的巡检器(inspector),每一个巡检器都蕴含了别离继承以上三个基类的三个子类,继承了基类的子类巡检器通过覆写 / 拓展基类办法以实现本人的个性化需要,以下是一个极简的巡检器例子:

// data-provider.ts
export class DataProvider extends DataProviderBase {
  // 实现特定的页面列表获取逻辑
  async fetchData(args) {return await axios.get('https://xxx.xxx').then(res => res.data.urlList)
  }
  // 每隔 15 分钟获取一次待检测列表
  async schedule() {return [{cron: '*/15 * * * *',args: {}}]
  }
}

// page-inspector.ts
export class PageInspector extends PageInspectorBase {async onPageOpen(page, reporter: PageReporter, data) {const pageTitle = await page.evaluate('window.document.title')
    console.log('这里能够获取到页面 title', pageTitle)
  }
}

// data-reporter.ts
export class DataReporter extends DataReporterBase {async beforeFeishuNotify(data: InspectorReportBase) {console.log('在飞书告诉前做点什么', data)
    return data
  }
}

3.3 巡检主程序

在巡检零碎中,每个页面的检测工作都是独立的异步工作,并且每份检测报告的整顿和发送也是独立的异步工作。为了方便管理和保护这些异步工作以及工作音讯的存储和传递,巡检零碎应用 Redis 联合 Bull 作为巡检零碎的异步工作管理工具。

Redis 是一个内存数据库,它提供高性能的数据存储和拜访能力。

Bull 是一个基于 Redis 的工作队列库,它提供了工作的调度、执行和消息传递的性能。

有了巡检器和异步工作治理能力,主程序的次要工作如下:

  1. 定义工作:应用 Bull 创立两个工作队列,page\_queue 用于寄存“页面检测工作”,reporter\_queue 用于寄存“报告生成工作”。
  2. 生产工作:在巡检零碎中,页面检测工作和报告生成工作的生产者 (主程序) 负责将工作增加到相应的队列中。当巡检器 (inspector) 须要进行页面检测时,生产者将页面检测工作退出 page\_queue;当须要生成报告时,生产者将报告生成工作退出 reporter\_queue。
  3. 生产工作:巡检零碎中的工作消费者 (主程序) 负责从工作队列中获取工作并执行,一次检测工作会有 >= 1 个页面检测工作,交由上文介绍的页面查看器 PageInspector 执行页面查看,而后将检测报告存储到 Redis 中,当该次检测工作的所有页面都实现检测后,reporter_queue 工作被创立并交由巡检器 (inspector) 的 DataReporter 生产。

4 卡口服务

介绍完巡检零碎,接下来咱们看如何将卡口服务集成自巡检零碎中。

卡口服务的次要性能用一句话概括:接入巡检零碎的现有架构,对外裸露一个近程接口,提供给接口调用方被动检测页面的能力,而后将检测报告回传给调用方。

比照现有巡检零碎与卡口服务的差别:

从上文的巡检零碎架构介绍以及剖析下面的表格可知,卡口服务的开发工作就是基于巡检零碎的巡检器架构去定制实现一个巡检器。

4.1 卡口服务运行时序

开始开发卡口服务的巡检器之前,咱们先梳理一下整个卡口服务的运行时序:

其中卡口服务次要开发工作:步骤 2、3、4、7。

4.2 创立工作接口

咱们在上文提到,巡检是一种后置检测伎俩,所以巡检零碎的 DataProviderBase(数据提供基类)次要能力是:“定时轮询接管内部提供的待检测页面列表”。

对于卡口服务来说,检测工作由检测方被动创立,所以咱们不须要过多关注 DataProviderBase 的实现,而是要启动一个 api 服务,负责创立检测工作,示例代码如下:

app.post('/xxx.xxx', async (req, res) => {
  const urls = req.body?.urls // 待检测 url 列表
  const callBack = req.body?.callBack // 调用方接管报告的回调接口地址
  const transData = req.body?.transData // 调用方须要在回调中拿到的透传数据
  // 巡检零碎检测工作创立函数
  newApp.createJob(urls.map(url => ({ url,
      // 在 redis 工作队列中传递的信息
      pos: {callBack, transData},
    })),
    jobId => { // 返回工作 id 给调用方
      res.json({taskId: jobId})
    }
  )
})


4.3 页面检测

PageInspectorBase(页面查看器基类)是卡口服务的革新重点,在这个基类的子类实现方面,咱们须要去做前文提到的具体待实现的检测 case,次要有两类检测 case:

1. 申请资源型检测 case:在子类中覆写 onResponse 办法,针对不同的资源类型执行不同的检测逻辑;

2. 运行时检测 case:在子类中覆写 onPageOpen 办法,通过基类传入的 Page 对象,注入 js 脚本,执行页面运行时检测;

// 
页面检测类
class PageInspector extends PageInspectorBase {
  // ...
  // 针对不同资源类型检测办法配置 Map
  checkResponseMethodsMap = new Map([['image', this.checkImageResponse]])
  
  // 申请资源型检测入口 针对申请资源进行检测
  async onResponse(response: Response, reporter: PageReporter, data: IJobItem) {const resourceType = response.request().resourceType()
    const checkMethod = this.checkResponseMethodsMap.get(resourceType)
    await checkMethod(response, reporter, data)
  }
  
  // 检测图片资源
  async checkImageResponse(response: Response, reporter: PageReporter, data: IJobItem) {
    // ...
    if (imageCdnList.includes(url)) {reporter.add({ errorType: "图片类型谬误. 非 cdn 资源"})}
    // ...
  }
    
  // 运行时检测入口 在页面关上时执行注入的 js 脚本进行运行时检测
  async onPageOpen(page, reporter: PageReporter, data) {
    // ...
    const htmlText = await page.evaluate('window.document.documentElement.innerHTML')
    const phoneRegex = /\b((?:\+?86)?1(?:3\d{3}|5[^4\D]\d{2}|8\d{3}|7(?:[35678]\d{2}|4(?:0\d|1[0-2]|9\d))|9[189]\d{2}|66\d{2})\d{6})\b/g;
    let phoneMatch: RegExpExecArray
    let collectMessage = []
    while ((phoneMatch = phoneRegex.exec(html)) !== null) {const phone = phoneMatch[1];collectMessage.push(` 手机号码:${phone}`);
    }
    collectMessage.forEach(val => {reporter.add({ errorMessage: ` 敏感信息:${val}`})})
    // ...
  }
  // ...
}

RegExp.prototype.exec()

在设置了 global 或 sticky 标记位的状况下(如 /foo/g 或 /foo/y),JavaScript RegExp 对象是有状态的。它们会将上次胜利匹配后的地位记录在 lastIndex 属性中。应用此个性,exec() 可用来对单个字符串中的屡次匹配后果进行逐条的遍历(包含捕捉到的匹配),而相比之下,String.prototype.match() 只会返回匹配到的后果。

4.4 报告与回调

检测工作执行结束后,reporter_queue 中会被创立一个新的“报告生成工作”,主程序调用继承了 DataReporterBase 的子类进行以下操作:

  1. 对检测项逐个整顿,将收集到的谬误进行等级分类,整顿出报告源数据;
  2. 依据报告源数据联合 ejs 模板生成动态 html 并上传,失去在线检测报告地址;
  3. 向调用方回调检测报告。

4.4.1 利用 ChatGPT 生成报告模板

大部分研发的 UI 设计程度引人注目,与其死磕报告模板的款式和交互,不如交给 AI 去搞定,输出事后定义好的数据结构和要求,ChatGPT 能十分疾速地给到一个模板,而后只须要略微做一点点调整就能达到可用的状态。

Q: 我须要你写一个 ejs 模板,这个模板用来展现一个统计报告,为了款式和交互不便,模板会通过内置 bootstrap 的 css 款式和 Jquery,你能够用到他们来实现 html 元素的组合和设置交互,具体的数据结构如下:interface CheckResult {
  name: string;
  status: 0 | 1;
  message: string;
  severityLevel: 0 | 1;
}

interface DetectionResult {
  url: string;
  status: 0 | 1;
  checkResults: CheckResult[];}

interface InspectorReport {
  taskId: string;
  reporterUrl?: string;
  detectionResults: DetectionResult[];
  startTimeStr: string;
  endTimeStr: string;
  duration: number;
  pageTotal: number;
  taskStat: {
    completed: number,
    waiting: number,
    active: number,
    delayed: number,
    paused: number,
    failed: number,
  }
}

export interface InspectorReportBase {
  manifest: IManifest,
  data: InspectorReport;
}

export interface IManifest {
  name: string
  desc?: string
  notifyTo?: string[]}

A: 好的,以下是基于提供的数据结构设计的 ejs 模板:<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <link href="https://xxx.xxx.css" rel="stylesheet">
  <script src="https://xxx.xxx.js"></script>
  <title>
    <%=data.manifest.desc%>
  </title>
</head>

<body>
  <div class="container">
    <h2 class="text-center">
      <%= data.manifest.desc %> 统计报告
    </h2>

    <div class="row">
      <div class="col-md-4">
        <div class="card">
          <div class="card-body">
            <h5 class="card-title"> 工作信息 </h5>
            <p class="card-text"> 工作 ID: <%= data.data.taskId %>
            </p>
            <p class="card-text"> 检测开始工夫:<%= data.data.startTimeStr %>
            </p>
            <p class="card-text"> 检测完结工夫:<%= data.data.endTimeStr %>
            </p>
            <p class="card-text"> 检测时长:<%= data.data.duration %> 秒 </p>
            <p class="card-text"> 检测页面数:<%= data.data.pageTotal %>
            </p>
            <!-- 以下省略 -->
</body>
</html>

AI 生成的在线报告模板:

4.4.2 与调用方解耦

作为服务提供者,卡口服务并不想过多关注调用者是谁,那么要如何实现与调用方的解耦?

——回顾上文“创立工作接口”环节,这个接口设计的入参有 callBack 和 transData 字段:

  1. callBack:检测工作完结时,卡口服务将调用该地址回传检测报告;
  2. transData:检测工作完结时,须要透传给 callBack 的数据;

页面检测工作实现后,在回调测试报告环节,卡口服务将从 redis 队列工作的缓存中中取出这两个值,应用 POST 申请将报告和 transData 发送给 callBack。

卡口服务回调示例代码
axios.post(callBack, {data: { msg: "本次检测检测报告如下:xxxxx", transData: ` 透传的数据如下:${transData}` }
})

在后续的布局中,为了使卡口服务能适应更多场景的不同需要,参考后端微服务注册核心的概念,能够实现一个繁难的注册核心的形象模型,进一步解耦卡口服务与其调用方之间的逻辑,同时能拓展更多功能:自定义检测项、自定义报告模板等。

5 总结

对于卡口服务来说,学习和浏览巡检的源码是一个重要的前置工作。通过深刻了解巡检零碎的实现细节和底层架构设计能够更好地了解巡检零碎是如何工作的,从而更好地进行定制和扩大,这些教训也帮忙晋升了本人的编码能力和设计能力,在后续的技术我的项目中能够失去利用和实际。心愿浏览完本文的开发同学都能从本篇实际总结中有所播种~

援用 / 参考链接

GitHub – OptimalBits/bull

RegExp.prototype.exec() – JavaScript | MDN

文:航飞

本文属得物技术原创,来源于:得物技术官网

未经得物技术许可严禁转载,否则依法追究法律责任!

正文完
 0