第一階段:
由於有的接口最多隻有兩層 /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;
結果預覽: