最近做了一个项目,其中一个点挺有意思的,拿出来分享一个,算是工作上的一个日记了。
起因
最近有一个大型活动,涉及到大量的前端页面和不同的素材物料,因而采用了大量素材cdn化的处理,但因为cdn资源需要提前预热,所以保障人员要求我来帮他们提供页面中的所有cdn资源地址。
最开始,说是只要活动首页的cdn地址,当时想了下,就一个页面,那就页面抓下包把所有的cdn资源全部弄下来好了。
但是,后来还是发现自己太天真了。
第一次改变
因为作为首页,是给用户优先展示的,因此内容、物料格外多,突然发现每次抓包太费劲了,因此,干脆写个脚本直接检索所有的cdn资源吧。
首先把页面的源代码保存到本地叫index.html,然后直接读取
const fs = require('fs')
fs.readFile('./index.html', (err, data) => {
if (err) {return console.log('数据获取错误')}
let htmlStr = data.toString()
let json = {}
// 为了保护公司隐私,具体cdn的地址就不详细列出了
json.cdnCss = htmlStr.match(/(xxx1\.xxx\.xxx|xxx2\.xxx\.xxx)\/\w+\/[\w|\d]+\/[\w|\d]+\/[\w|\d|\_]+\.css+/g)
json.cdnJs = htmlStr.match(/(xxx1\.xxx\.xxx|xxx2\.xxx\.xxx)\/\w+\/[\w|\d]+\/[\w|\d]+\/[\w|\d|\_]+\.js+/g)
json.cdnCss = htmlStr.match(/(xxx1\.xxx\.xxx|xxx2\.xxx\.xxx)\/\w+\/[\w|\d]+\/[\w|\d]+\/[\w|\d|\_]+\.(jpg|png)+/g)
writeFile(json)
})
相关的cdn数据都已经整理出来了,随后就可以将其整理到对应的数据文档里了。
第二次改变
随后需求又出现了变化,要求把所有页面的cdn资源都整理出来,这如果要把每个页面的代码都保存到本地就太神经病了,干脆做一次改造吧。想了下,可以抓取下页面的代码,这样就不用存到本地了。
const http = require('http')
let cdnHtmlUrl = 'xxx.xxx.xxx/xxx/xxx.html'
let url = ''
// 不想解析https了,全部用http方便
if (/^(http\:\/\/)/g.test(cdnHtmlUrl)) {
url = cdnHtmlUrl
} else if (/^(https\:\/\/)/g.test(cdnHtmlUrl)) {
url = 'http://' + cdnHtmlUrl.split('https://')[1]
} else if (!/^(http\:\/\/)/g.test(cdnHtmlUrl) && !/^(https\:\/\/)/g.test(cdnHtmlUrl)) {
url = 'http://'+ cdnHtmlUrl
}
/**
* 取页面中的代码并检索cdn地址
*/
http.get(url, (res) => {
let html = ''
let json = {}
res.setEncoding('utf-8')
res.on('data', (data) => {
html += data
})
res.on('end', (data) => {
json.cdnCss = htmlStr.match(/(xxx1\.xxx\.xxx|xxx2\.xxx\.xxx)\/\w+\/[\w|\d]+\/[\w|\d]+\/[\w|\d|\_]+\.css+/g)
json.cdnJs = htmlStr.match(/(xxx1\.xxx\.xxx|xxx2\.xxx\.xxx)\/\w+\/[\w|\d]+\/[\w|\d]+\/[\w|\d|\_]+\.js+/g)
json.cdnCss = htmlStr.match(/(xxx1\.xxx\.xxx|xxx2\.xxx\.xxx)\/\w+\/[\w|\d]+\/[\w|\d]+\/[\w|\d|\_]+\.(jpg|png)+/g)
writeFile(json)
})
})
第三次改变
可是上面的方法有个弊端,每次都是将cdn地址全量输出。如果能将本次与上一次的进行比较,只输出有变化的,岂不更好,经过与需求方确认,他们也希望这样,那就再做一点小修改。
那么,我们就需要一个方法compareAndSaveCdn用来比较本次的json同之前的区别,首先要分别读取本次获取的数据和之前保存的数据,我之前已经将之前获取的数据存到对应的json里了,因此直接require就可以了。
let compareAndSaveCdn = function (newJson, fileName) {
let fileNamePointIndex = url.length
if (/\.html$/g.test(url)) {
fileNamePointIndex = url.lastIndexOf('.html')
}else {
fileNamePointIndex = url.lastIndexOf('.jsp')
}
let fileNames = fileName||url.slice(url.lastIndexOf('/')+1, fileNamePointIndex)
let oldFile = {}
let jsonDifferent = {}
file = require('./data/'+fileNames+'.json')
}
获取到对应的数据后,就要进行比较了,这里新增一个方法intercomparison,要求如果数据相同,则返回true,否则返回两者不同的数据。这里有几个思路可以参考,一是对两个数组分别进行遍历进行比较,如下
let intercomparison = (newJson, oldFile) => {
let newArr = []
for (let i = 0; i < newJson.length; i++) {
let flag = true
for (let j = 0; j < oldFile.length; j++) {
if (newJson[i] != oldFile[j]) {
newArr.push(newJson[i])
}
}
if (newArr.length != 0) {
return newArr
} else {
return true
}
}
}
但是上述方法复杂度较高,这里可以考虑使用数组的filter方法
let intercomparison = (newJson, oldFile) => {
let newArr = []
let newArr1 = newJson.filter((value) => {
return oldFile.indexOf(value) === -1
})
let newArr2 = oldFile.filter((value) => {
return newJson.indexOf(value) === -1
})
newArr = newArr1.concat(newArr2)
if (newArr.length != 0) {
return newArr
} else {
return true
}
}
但是,这个方法仍然感觉不是很优雅,是否有更赏心悦目的做法呢,我从网上借鉴到了es6新增的map对象
let intercomparison = function (json, file) {
let newArr = []
map = new Map()
json.forEach(item => map.set(item, (map.get(item)||0) + 1))
file.forEach(item => map.set(item, (map.get(item)||0) - 1))
for (var [item, index] of map) {
if(index !== 0){
newArr.push(item)
}
}
if (newArr.length == 0) {
return true
} else {
return newArr
}
}
接下来就是如何针对数据进行处理了
try {
for (let key in newJson) {
let newJsonFlag = intercomparison(newJson[key], oldFile[key])
if (newJsonFlag == true) {
console.log(fileNames + key + '的资源地址与之前相同')
} else {
fs.writeFile('./data/' + fileNames + '.json', JSON.stringify(newJson, null, '\t'), (err) => {
if (err) {
return 'write error'
}
})
console.warn(fileNames + key + '的资源地址与之前不同,最新版已存储,并输出有差异的different文件')
}
}
fs.writeFile('./data/'+fileNames+'Different.json', JSON.stringify(jsonDifferent, null, '\t'), (err) => {
if (err) {
return 'write error'
}
})
} catch (err) {
console.log('出现错误,新建文件'+fileNames)
}
第四次改变
在页面正式上现网后,突然发现检索出来的代码有问题,全部乱掉了,如图所示
这时候再去花时间去解析动态混淆的代码,估计对方会把我砍死,想了下,干脆直接检索最终要上线的代码好了,那就要对原代码中通过页面地址拉取代码的行为做下处理,并且处理一下将整个仓库的代码全部拉下来进行解析。
const fs = require('fs')
const child_process = require('child_process')
const path = require('path')
// 设置拉取的目录
let dirName = '../xxx/xxx/'
// 每次检索之前先拉取一下代码
child_process.exec('git pull', {cwd: dirName}, (err, stdout) => {
if (err !== null) {
console.log('代码拉取失败,详见:' + err)
} else {
console.log('代码拉取成功,详见:' + stdout)
// 读取目录中的文件,并遍历
fs.readdir(dirName, (err, fileListData) => {
if (err) {return console.log('读取目录失败')}
fileListData.forEach((file) => {
fs.stat(dirName + file, () => {
if (path.extname(dirName + file) === '.html') {
// 执行检索cdn地址的操作
}
})
})
})
}
})
最终输出入下图的文件
小结
当然,整个项目还不是很完美,比如最典型的回调地狱,可以用promise避免的,再比如代码混淆,也没有做处理,还有代码写的不够精简等等,待将来有机会慢慢优化,最好还能输出点什么。