// 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
}
}
Redis
结合Bull
作为巡检系统的异步任务管理工具。定义任务:使用Bull
创建两个任务队列,page_queue
用于存放“页面检测任务”,reporter_queue
用于存放“报告生成任务”。
生产任务:在巡检系统中,页面检测任务和报告生成任务的生产者(主程序)负责将任务添加到相应的队列中。当巡检器(inspector)需要进行页面检测时,生产者将页面检测任务加入page_queue
;当需要生成报告时,生产者将报告生成任务加入reporter_queue
。
消费任务:巡检系统中的任务消费者(主程序)负责从任务队列中获取任务并执行,一次检测任务会有>=1个页面检测任务,交由上文介绍的页面检查器PageInspector
执行页面检查,然后将检测报告存储到Redis中,当该次检测任务的所有页面都完成检测后,reporter_queue
任务被创建并交由巡检器(inspector)的DataReporter
消费。
巡检器架构
去定制实现一个巡检器。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: string
desc?: string
notifyTo?: 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}` }
})
“
扫码添加小助手微信
如有任何疑问,或想要了解更多技术资讯,请添加小助手微信: