利用ast和jscodeshift统计项目文件中接口重复使用率
第一阶段:
由于有的接口最多只有两层 /xx/xx,所以查找出所有接口截取最后两层进行统计
grep -Er 'ur[il]\s*\=|url:|action\=' ./src | awk -F "[\'\`]" '{print $2}'| grep -Eo "(\/[^\/]+){2}$" |sort | uniq -c | sort -r
这样的话有几个问题:
1、yy/xx/xx 和 xx/xx/xx会被统计成一个
2、/xx/xx的GET请求 和 /xx/xx的POST或者其他请求也会统计成一个
第二阶段:
为了获得更准确一点的统计,先通过jscodeshift对项目里各种写法的请求进行规整组成完整url
判断了项目中的各种请求写法
jscodeshift库: https://github.com/facebook/jscodeshift
ast语法树在线解析:https://astexplorer.net/
const glob = require("glob");
const fs = require('fs');
const j = require('jscodeshift');
const util = require('util');
const fetch = require('node-fetch');
const execFile = util.promisify(require('child_process').execFile);
// 解析环境变量url
const getSystemEnvs = (fileSource) => {
const source = j(fileSource);
let envsObj = {};
source.find(j.Property, { key: { name: 'defineInProcessEnv' } }).find(j.Property).forEach(f => {
let key = f.node.key.name;
let value = '';
let valueMatch = f.node.value.arguments[0];
if (valueMatch.type === 'LogicalExpression') {
value = valueMatch.right.value;
} else if (valueMatch.type === 'Literal') {
value = valueMatch.value;
} else {
value = '';
}
envsObj = Object.assign({}, envsObj, { [key]: value });
})
return envsObj;
}
// 规整url接口
const transformApis = (fileSource, envsMap) => {
const source = j(fileSource);
let globalBaseUrl = ''; // 全局的前缀
// 获取页面里定义的全局baseUrl或baseUri前缀变量
const parseGlobalBaseUrl = source.find(j.VariableDeclaration).find(j.VariableDeclarator, {
id: {
type: 'Identifier',
},
init: {
type: 'Literal'
}
}).filter((f) => /baseUrl|baseUri|hostStart/i.test(f.node.id.name));
if (parseGlobalBaseUrl.length) {
globalBaseUrl = parseGlobalBaseUrl.get().node.init.value;
}
// 判断是否有方法封装的
// const baseUrl = '/wms/internal/xxx/';
// const sendPostRequestApi = (argObj) => sendPostRequest(argObj, baseUrl);
const hasFnReWrite = source.find(j.VariableDeclaration).find(j.VariableDeclarator, (f) => f.id.name === 'sendPostRequestApi' && f.init?.body?.callee?.name === 'sendPostRequest' && f.init?.body?.arguments[1]?.name === 'baseUrl').length;
source.find(j.VariableDeclaration).find(j.VariableDeclarator, {
init: {
type: 'ArrowFunctionExpression'
}
})
.forEach((b) => {
let method = ''; // 请求方法
let preUrl = ''; // 原来的url地址
let prefix = ''; // url前缀
// 如果是调用表达式类型
if (b.node?.init?.body?.type === 'CallExpression') {
// 解析请求调用方法类型 排除tms的
const parseMethod = b.node?.init?.body?.callee?.name?.match(/Internal|Post|Get(?=Request)|Put|sendRequest/ig);
if (parseMethod) {
method = parseMethod[0];
// 如果是sendInternalPostRequest internalSendPostRequest 取全局baseUrl|baseUri 前缀是/wms/internal/front
if (parseMethod[0] === 'Internal') {
prefix = globalBaseUrl || '/wms/internal/front';
method = 'POST';
} else if (parseMethod[0] === 'sendRequest') {
// 如果是sendRequest类型 前缀都是wms/qc
prefix = '/wms/qc';
method = 'POST';
}
} else {
return false;
}
// 解析环境变量 有可能process.env.WWS_XXX、''、/wms/xxx/xxx、baseUrl、baseUri、或者没有
// 判断请求方法调用的第二个参数类型 有可能没有传参
// console.info('--', b.node.init.body.arguments[1])
const secondArgumentsType = b.node.init.body.arguments[1] ? b.node.init.body.arguments[1].type : '';
if (secondArgumentsType) {
if (secondArgumentsType === 'MemberExpression' || secondArgumentsType === 'ConditionalExpression') {
// process.env.XXX_XXX或者MOCK_SWITCH ? MOCK_URL + MOCK_HOST +process.env.XXX_XXX : process.env.XXX_XXX类型
const parseEnvs = j(b).find(j.MemberExpression, (d) => d.property.name !== 'env');
prefix = parseEnvs.length ? `${parseEnvs.get().node.property.name}` : '';
prefix = envsMap[prefix];
} else if (secondArgumentsType === 'Literal') {
// 字符串类型 ''、/wms/xxx/xxx
prefix = b.node.init.body.arguments[1].value;
} else if (secondArgumentsType === 'Identifier') {
// 变量类型 param、baseUrl、baseUri hostStart
const varName = b.node.init.body.arguments[1].name;
if (/baseUrl|baseUri|hostStart/i.test(varName)) {
prefix = globalBaseUrl;
} else if (varName === 'param') {
// 这种写法的经查询都是Internal类型的前缀
prefix = '/wms/internal/front';
}
}
} else {
// 没有传路径变量的 默认是/wms/front 如果有方法封装的话则为全局的url前缀
prefix = (hasFnReWrite ? globalBaseUrl : prefix) || '/wms/front';
}
// 获取原url和参数里baseUrl
const firstArgumentsType = b.node.init.body.arguments[0] ? b.node.init.body.arguments[0].type : '';
if (firstArgumentsType === 'ObjectExpression') {
j(b).find(j.Property, { value: { type: 'Literal' } }).forEach((c) => {
if (c.node.key.name === 'url') {
preUrl = c.node.value.value;
} else if (c.node.key.name === 'baseUrl') {
prefix = c.node.value.value;
}
});
j(b).find(j.Property, { key: { name: 'url' } }).forEach((e) => {
e.node.value.value = `${prefix}${preUrl}_${method.toUpperCase()}`;
});
} else if (firstArgumentsType === 'Literal') {
// 这种写法的经查询都是Internal类型的前缀
preUrl = b.node.init.body.arguments[0].value || '';
// 构造成一个{url:'xxx'}对象节点后面好匹配
j(b).find('Literal').replaceWith(j.objectExpression([
j.property(
'init',
j.identifier('url'),
j.literal(`${prefix}${preUrl}_${method.toUpperCase()}`),
),
]));
}
} else if (b.node?.init?.body?.type === 'BlockStatement') {
// 如果是个代码块
const findHasUrlOrUri = j(b).find(j.VariableDeclarator, (j) => /url|uri/i.test(j.id.name));
findHasUrlOrUri.find(j.TemplateLiteral).forEach((f) => {
// 如果url uri是一个模板字符串说明路径可能含有环境变量或者其他路径变量
const parseEnvs = j(f).find(j.MemberExpression, (d) => d.property.name !== 'env')
// 没有环境变量的尾部路径
let urlTail;
const parseUrlTail = j(f).find(j.TemplateElement, (k) => {
urlTail = k.value.cooked;
return urlTail !== '';
});
if (parseEnvs.length) {
if (urlTail) {
findHasUrlOrUri.find(j.TemplateLiteral).replaceWith(j.identifier(`'${envsMap[parseEnvs.get().node.property.name]}${urlTail}_POST'`));
}
}
j(f).find(j.Identifier, ((m) => {
if (/baseUrl|baseUri/i.test(m.name) && urlTail) {
findHasUrlOrUri.find(j.TemplateLiteral).replaceWith(j.identifier(`'${globalBaseUrl}${urlTail}_POST'`));
}
}));
// 如果没有变量只是因为用了反引号
findHasUrlOrUri.find(j.TemplateLiteral).replaceWith(j.identifier(`'${urlTail}_POST'`));
});
// 如果方法块里的url|uri是字符串只是在后面加个请求类型
findHasUrlOrUri.find(j.Literal).forEach((f) => {
f.node.value = `${f.node.value}_POST`;
});
}
});
// 操作记录的url规整
source.find(j.JSXAttribute, {name: {name: 'url'}}).forEach(f => {
if (f.node.value.type === 'Literal') {
f.node.value.value = `${f.node.value.value}_POST`
}
})
// upload请求url规整
source.find(j.JSXAttribute, {name: {name: 'action'}}).forEach(f => {
if (f.node?.value?.type === 'Literal') {
j(f.node).find(j.Literal).replaceWith(j.literal(`${f.node.value.value}_POST`))
}
if (f.node?.value?.type === 'JSXExpressionContainer') {
const jsxExpressionContainer = j(f).find(j.JSXExpressionContainer)
if (f.node?.value?.expression?.name === 'uploadUrl') {
jsxExpressionContainer && jsxExpressionContainer.replaceWith(j.literal(`${globalBaseUrl}_POST`))
}
if (j(f).find(j.TemplateElement).length > 1) {
const parseEnvs = j(f).find(j.MemberExpression, (d) => d.property.name !== 'env')
let prefix = '';
let urlTail = '';
if (parseEnvs.length) {
prefix = parseEnvs.get().node.property.name
prefix = envsMap[prefix];
}
const parseUrlTail = j(f).find(j.TemplateElement, (k) => {
urlTail = k.value.cooked
return urlTail !== '';
})
jsxExpressionContainer && jsxExpressionContainer.replaceWith(j.literal(`${prefix}${urlTail}_POST`))
}
}
})
return source.toSource({
quote: 'single',
});
};
const projectDir = process.argv.slice(2)[0];
const project = projectDir.match(/\/([^\/]+)$/)[1];
const legorcSource = fs.readFileSync(`${projectDir}/.legorc.ts`);
const envsMap = getSystemEnvs(legorcSource.toString()) || {};
// 遍历文件规整url接口
glob(`${projectDir}/src/**/*.js{,x}`, function (er, files) {
for (let i = 0; i < files.length; i += 1) {
const uri = files[i];
const fileSource = fs.readFileSync(uri);
const newData = transformApis(fileSource.toString(), envsMap);
if (newData) {
fs.writeFileSync(uri, newData);
}
}
// 规整完成调用shell进行统计和群消息通知
execFile('bash', ['./api_analyse.sh', `${projectDir}/src`], (error, stdout, stderr) => {
if (error) {
return console.error(error);
}
const content = `# ${project}项目中接口重复使用>=5次的统计
* 时间:${new Date().toLocaleString()}
* ${stdout}`;
// console.info(content)
})
})
通过下面的sh会调用上面的js进行url规整,当上面的js运行完毕会调用下面的sh进行统计
#! /bin/bash
sys_arr=('/tmp/xxx' '/tmp/yyy');
if [ -z $1 ]
then
for s in "${sys_arr[@]}"; do
if [ -d $s ];then
cd $s;
git reset --hard;
git checkout master;
git pull;
cd /www;
node 'api_analyse.js' $s;
fi;
done;
else
grep -Er 'ur[il]\s*\=|url:|action\=' "$1" | awk -F [\'\`\"] '{print $2}' | grep -E '.+_(POST|GET|PUT)' |sort | uniq -c | sort -r | awk '$1>=5{print $0}'
fi;
结果预览:


浙公网安备 33010602011771号