来源:得物技术
1
背景
2
用户体验量化标准

3
巡检系统基础架构



// data-provider.tsexport 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.tsexport class PageInspector extends PageInspectorBase {async onPageOpen(page, reporter: PageReporter, data) {const pageTitle = await page.evaluate('window.document.title')console.log('这里可以获取到页面title', pageTitle)}}// data-reporter.tsexport class DataReporter extends DataReporterBase {async beforeFeishuNotify(data: InspectorReportBase) {console.log('在飞书通知前做点什么', data)return data}}
Redis结合Bull作为巡检系统的异步任务管理工具。定义任务:使用Bull创建两个任务队列,page_queue用于存放“页面检测任务”,reporter_queue用于存放“报告生成任务”。
生产任务:在巡检系统中,页面检测任务和报告生成任务的生产者(主程序)负责将任务添加到相应的队列中。当巡检器(inspector)需要进行页面检测时,生产者将页面检测任务加入page_queue;当需要生成报告时,生产者将报告生成任务加入reporter_queue。
消费任务:巡检系统中的任务消费者(主程序)负责从任务队列中获取任务并执行,一次检测任务会有>=1个页面检测任务,交由上文介绍的页面检查器PageInspector执行页面检查,然后将检测报告存储到Redis中,当该次检测任务的所有页面都完成检测后,reporter_queue任务被创建并交由巡检器(inspector)的DataReporter消费。
4
卡口服务
巡检器架构去定制实现一个巡检器。
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 }) } )})PageInspectorBase(页面检查器基类)是卡口服务的改造重点,在这个基类的子类实现方面,我们需要去做前文提到的具体待实现的检测case,主要有两类检测case:onResponse方法,针对不同的资源类型执行不同的检测逻辑;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}`})}) // ... } // ...}reporter_queue中会被创建一个新的“报告生成任务”,主程序调用继承了DataReporterBase的子类进行以下操作:
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: stringdesc?: stringnotifyTo?: string[]}
A: 好的,以下是基于提供的数据结构设计的ejs模板:<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>

callBack和transData字段:callBack:检测任务结束时,卡口服务将调用该地址回传检测报告;
transData:检测任务结束时,需要透传给callBack的数据;
页面检测任务完成后,在回调测试报告环节,卡口服务将从redis队列任务的缓存中中取出这两个值,使用POST请求将报告和transData发送给callBack。
// 卡口服务回调示例代码axios.post(callBack, { data: { msg: "本次检测检测报告如下:xxxxx", transData: `透传的数据如下:${transData}` }})
5
总结
来自 “ ITPUB博客 ” ,链接:https://blog.itpub.net/70027824/viewspace-2958419/ ,如需转载,请注明出处,否则将追究法律责任。