This commit is contained in:
2024-03-01 20:28:14 +08:00
commit 076c21dc36
491 changed files with 84482 additions and 0 deletions

683
server/utils/commons.js Normal file
View File

@@ -0,0 +1,683 @@
const fs = require('fs-extra');
const path = require('path');
const yapi = require('../yapi.js');
const sha1 = require('sha1');
const logModel = require('../models/log.js');
const projectModel = require('../models/project.js');
const interfaceColModel = require('../models/interfaceCol.js');
const interfaceCaseModel = require('../models/interfaceCase.js');
const interfaceModel = require('../models/interface.js');
const userModel = require('../models/user.js');
const json5 = require('json5');
const _ = require('underscore');
const Ajv = require('ajv');
const Mock = require('mockjs');
const sandboxFn = require('./sandbox')
const ejs = require('easy-json-schema');
const jsf = require('json-schema-faker');
const { schemaValidator } = require('../../common/utils');
const http = require('http');
jsf.extend('mock', function () {
return {
mock: function (xx) {
return Mock.mock(xx);
}
};
});
const defaultOptions = {
failOnInvalidTypes: false,
failOnInvalidFormat: false
};
// formats.forEach(item => {
// item = item.name;
// jsf.format(item, () => {
// if (item === 'mobile') {
// return jsf.random.randexp('^[1][34578][0-9]{9}$');
// }
// return Mock.mock('@' + item);
// });
// });
exports.schemaToJson = function (schema, options = {}) {
Object.assign(options, defaultOptions);
jsf.option(options);
let result;
try {
result = jsf(schema);
} catch (err) {
result = err.message;
}
jsf.option(defaultOptions);
return result;
};
exports.resReturn = (data, num, errmsg) => {
num = num || 0;
return {
errcode: num,
errmsg: errmsg || '成功!',
data: data
};
};
exports.log = (msg, type) => {
if (!msg) {
return;
}
type = type || 'log';
let f;
switch (type) {
case 'log':
f = console.log; // eslint-disable-line
break;
case 'warn':
f = console.warn; // eslint-disable-line
break;
case 'error':
f = console.error; // eslint-disable-line
break;
default:
f = console.log; // eslint-disable-line
break;
}
f(type + ':', msg);
let date = new Date();
let year = date.getFullYear();
let month = date.getMonth() + 1;
let logfile = path.join(yapi.WEBROOT_LOG, year + '-' + month + '.log');
if (typeof msg === 'object') {
if (msg instanceof Error) msg = msg.message;
else msg = JSON.stringify(msg);
}
// let data = (new Date).toLocaleString() + '\t|\t' + type + '\t|\t' + msg + '\n';
let data = `[ ${new Date().toLocaleString()} ] [ ${type} ] ${msg}\n`;
fs.writeFileSync(logfile, data, {
flag: 'a'
});
};
exports.fileExist = filePath => {
try {
return fs.statSync(filePath).isFile();
} catch (err) {
return false;
}
};
exports.time = () => {
return Date.parse(new Date()) / 1000;
};
exports.fieldSelect = (data, field) => {
if (!data || !field || !Array.isArray(field)) {
return null;
}
var arr = {};
field.forEach(f => {
typeof data[f] !== 'undefined' && (arr[f] = data[f]);
});
return arr;
};
exports.rand = (min, max) => {
return Math.floor(Math.random() * (max - min) + min);
};
exports.json_parse = json => {
try {
return json5.parse(json);
} catch (e) {
return json;
}
};
exports.randStr = () => {
return Math.random()
.toString(36)
.substr(2);
};
exports.getIp = ctx => {
let ip;
try {
ip = ctx.ip.match(/\d+.\d+.\d+.\d+/) ? ctx.ip.match(/\d+.\d+.\d+.\d+/)[0] : 'localhost';
} catch (e) {
ip = null;
}
return ip;
};
exports.generatePassword = (password, passsalt) => {
return sha1(password + sha1(passsalt));
};
exports.expireDate = day => {
let date = new Date();
date.setTime(date.getTime() + day * 86400000);
return date;
};
exports.sendMail = (options, cb) => {
if (!yapi.mail) return false;
options.subject = options.subject ? options.subject + '-YApi 平台' : 'YApi 平台';
cb =
cb ||
function (err) {
if (err) {
yapi.commons.log('send mail ' + options.to + ' error,' + err.message, 'error');
} else {
yapi.commons.log('send mail ' + options.to + ' success');
}
};
try {
yapi.mail.sendMail(
{
from: yapi.WEBCONFIG.mail.from,
to: options.to,
subject: options.subject,
html: options.contents
},
cb
);
} catch (e) {
yapi.commons.log(e.message, 'error');
console.error(e.message); // eslint-disable-line
}
};
exports.validateSearchKeyword = keyword => {
if (/^\*|\?|\+|\$|\^|\\|\.$/.test(keyword)) {
return false;
}
return true;
};
exports.filterRes = (list, rules) => {
return list.map(item => {
let filteredRes = {};
rules.forEach(rule => {
if (typeof rule == 'string') {
filteredRes[rule] = item[rule];
} else if (typeof rule == 'object') {
filteredRes[rule.alias] = item[rule.key];
}
});
return filteredRes;
});
};
exports.handleVarPath = (pathname, params) => {
function insertParams(name) {
if (!_.find(params, { name: name })) {
params.push({
name: name,
desc: ''
});
}
}
if (!pathname) return;
if (pathname.indexOf(':') !== -1) {
let paths = pathname.split('/'),
name,
i;
for (i = 1; i < paths.length; i++) {
if (paths[i] && paths[i][0] === ':') {
name = paths[i].substr(1);
insertParams(name);
}
}
}
pathname.replace(/\{(.+?)\}/g, function (str, match) {
insertParams(match);
});
};
/**
* 验证一个 path 是否合法
* path第一位必需为 /, path 只允许由 字母数字-/_:.{}= 组成
*/
exports.verifyPath = path => {
// if (/^\/[a-zA-Z0-9\-\/_:!\.\{\}\=]*$/.test(path)) {
// return true;
// } else {
// return false;
// }
return /^\/[a-zA-Z0-9\-\/_:!\.\{\}\=]*$/.test(path);
};
/**
* 沙盒执行 js 代码
* @sandbox Object context
* @script String script
* @return sandbox
*
* @example let a = sandbox({a: 1}, 'a=2')
* a = {a: 2}
*/
exports.sandbox = (sandbox, script) => {
let filter = '^(process|exec|require)$';
let reg = new RegExp(filter, "g");
if (reg.test(script)) {
throw new Error("执行失败,脚本中含敏感操作....");
}
try {
const vm = require('vm');
sandbox = sandbox || {};
script = new vm.Script(script);
const context = new vm.createContext(sandbox);
script.runInContext(context, {
timeout: 3000
});
return sandbox
} catch (err) {
throw err
}
};
function trim(str) {
if (!str) {
return str;
}
str = str + '';
return str.replace(/(^\s*)|(\s*$)/g, '');
}
function ltrim(str) {
if (!str) {
return str;
}
str = str + '';
return str.replace(/(^\s*)/g, '');
}
function rtrim(str) {
if (!str) {
return str;
}
str = str + '';
return str.replace(/(\s*$)/g, '');
}
exports.trim = trim;
exports.ltrim = ltrim;
exports.rtrim = rtrim;
/**
* 处理请求参数类型String 字符串去除两边空格Number 使用parseInt 转换为数字
* @params Object {a: ' ab ', b: ' 123 '}
* @keys Object {a: 'string', b: 'number'}
* @return Object {a: 'ab', b: 123}
*/
exports.handleParams = (params, keys) => {
if (!params || typeof params !== 'object' || !keys || typeof keys !== 'object') {
return false;
}
for (var key in keys) {
var filter = keys[key];
if (params[key]) {
switch (filter) {
case 'string':
params[key] = trim(params[key] + '');
break;
case 'number':
params[key] = !isNaN(params[key]) ? parseInt(params[key], 10) : 0;
break;
default:
params[key] = trim(params + '');
}
}
}
return params;
};
exports.validateParams = (schema2, params) => {
const flag = schema2.closeRemoveAdditional;
const ajv = new Ajv({
allErrors: true,
coerceTypes: true,
useDefaults: true,
removeAdditional: flag ? false : true
});
var localize = require('ajv-i18n');
delete schema2.closeRemoveAdditional;
const schema = ejs(schema2);
schema.additionalProperties = flag ? true : false;
const validate = ajv.compile(schema);
let valid = validate(params);
let message = '请求参数 ';
if (!valid) {
localize.zh(validate.errors);
message += ajv.errorsText(validate.errors, { separator: '\n' });
}
return {
valid: valid,
message: message
};
};
exports.saveLog = logData => {
try {
let logInst = yapi.getInst(logModel);
let data = {
content: logData.content,
type: logData.type,
uid: logData.uid,
username: logData.username,
typeid: logData.typeid,
data: logData.data
};
logInst.save(data).then();
} catch (e) {
yapi.commons.log(e, 'error'); // eslint-disable-line
}
};
/**
*
* @param {*} router router
* @param {*} baseurl base_url_path
* @param {*} routerController controller
* @param {*} path routerPath
* @param {*} method request_method , post get put delete ...
* @param {*} action controller action_name
* @param {*} ws enable ws
*/
exports.createAction = (router, baseurl, routerController, action, path, method, ws) => {
router[method](baseurl + path, async ctx => {
let inst = new routerController(ctx);
try {
await inst.init(ctx);
ctx.params = Object.assign({}, ctx.request.query, ctx.request.body, ctx.params);
if (inst.schemaMap && typeof inst.schemaMap === 'object' && inst.schemaMap[action]) {
let validResult = yapi.commons.validateParams(inst.schemaMap[action], ctx.params);
if (!validResult.valid) {
return (ctx.body = yapi.commons.resReturn(null, 400, validResult.message));
}
}
if (inst.$auth === true) {
await inst[action].call(inst, ctx);
} else {
if (ws === true) {
ctx.ws.send('请登录...');
} else {
ctx.body = yapi.commons.resReturn(null, 40011, '请登录...');
}
}
} catch (err) {
ctx.body = yapi.commons.resReturn(null, 40011, '服务器出错...');
yapi.commons.log(err, 'error');
}
});
};
/**
*
* @param {*} params 接口定义的参数
* @param {*} val 接口case 定义的参数值
*/
function handleParamsValue(params, val) {
let value = {};
try {
params = params.toObject();
} catch (e) { }
if (params.length === 0 || val.length === 0) {
return params;
}
val.forEach(item => {
value[item.name] = item;
});
params.forEach((item, index) => {
if (!value[item.name] || typeof value[item.name] !== 'object') return null;
params[index].value = value[item.name].value;
if (!_.isUndefined(value[item.name].enable)) {
params[index].enable = value[item.name].enable;
}
});
return params;
}
exports.handleParamsValue = handleParamsValue;
exports.getCaseList = async function getCaseList(id) {
const caseInst = yapi.getInst(interfaceCaseModel);
const colInst = yapi.getInst(interfaceColModel);
const projectInst = yapi.getInst(projectModel);
const interfaceInst = yapi.getInst(interfaceModel);
let resultList = await caseInst.list(id, 'all');
let colData = await colInst.get(id);
for (let index = 0; index < resultList.length; index++) {
let result = resultList[index].toObject();
let data = await interfaceInst.get(result.interface_id);
if (!data) {
await caseInst.del(result._id);
continue;
}
let projectData = await projectInst.getBaseInfo(data.project_id);
result.path = projectData.basepath + data.path;
result.method = data.method;
result.title = data.title;
result.req_body_type = data.req_body_type;
result.req_headers = handleParamsValue(data.req_headers, result.req_headers);
result.res_body_type = data.res_body_type;
result.req_body_form = handleParamsValue(data.req_body_form, result.req_body_form);
result.req_query = handleParamsValue(data.req_query, result.req_query);
result.req_params = handleParamsValue(data.req_params, result.req_params);
resultList[index] = result;
}
resultList = resultList.sort((a, b) => {
return a.index - b.index;
});
let ctxBody = yapi.commons.resReturn(resultList);
ctxBody.colData = colData;
return ctxBody;
};
function convertString(variable) {
if (variable instanceof Error) {
return variable.name + ': ' + variable.message;
}
try {
if (variable && typeof variable === 'string') {
return variable;
}
return JSON.stringify(variable, null, ' ');
} catch (err) {
return variable || '';
}
}
exports.runCaseScript = async function runCaseScript(params, colId, interfaceId) {
const colInst = yapi.getInst(interfaceColModel);
let colData = await colInst.get(colId);
const logs = [];
const context = {
assert: require('assert'),
status: params.response.status,
body: params.response.body,
header: params.response.header,
records: params.records,
params: params.params,
log: msg => {
logs.push('log: ' + convertString(msg));
}
};
let result = {};
try {
if (colData.checkHttpCodeIs200) {
let status = +params.response.status;
if (status !== 200) {
throw ('Http status code 不是 200请检查(该规则来源于于 [测试集->通用规则配置] )')
}
}
if (colData.checkResponseField.enable) {
if (params.response.body[colData.checkResponseField.name] != colData.checkResponseField.value) {
throw (`返回json ${colData.checkResponseField.name} 值不是${colData.checkResponseField.value},请检查(该规则来源于于 [测试集->通用规则配置] )`)
}
}
if (colData.checkResponseSchema) {
const interfaceInst = yapi.getInst(interfaceModel);
let interfaceData = await interfaceInst.get(interfaceId);
if (interfaceData.res_body_is_json_schema && interfaceData.res_body) {
let schema = JSON.parse(interfaceData.res_body);
let result = schemaValidator(schema, context.body)
if (!result.valid) {
throw (`返回Json 不符合 response 定义的数据结构,原因: ${result.message}
数据结构如下:
${JSON.stringify(schema, null, 2)}`)
}
}
}
if (colData.checkScript.enable) {
let globalScript = colData.checkScript.content;
// script 是断言
if (globalScript) {
logs.push('执行脚本:' + globalScript)
result = await sandboxFn(context, globalScript, true);
}
}
let script = params.script;
// script 是断言
if (script) {
logs.push('执行脚本:' + script)
result = await sandboxFn(context, script, true);
}
result.logs = logs;
return yapi.commons.resReturn(result);
} catch (err) {
logs.push(convertString(err));
result.logs = logs;
return yapi.commons.resReturn(result, 400, err.name + ': ' + err.message);
}
};
exports.getUserdata = async function getUserdata(uid, role) {
role = role || 'dev';
let userInst = yapi.getInst(userModel);
let userData = await userInst.findById(uid);
if (!userData) {
return null;
}
return {
role: role,
uid: userData._id,
username: userData.username,
email: userData.email
};
};
// 处理mockJs脚本
exports.handleMockScript = async function (script, context) {
let sandbox = {
header: context.ctx.header,
query: context.ctx.query,
body: context.ctx.request.body,
mockJson: context.mockJson,
params: Object.assign({}, context.ctx.query, context.ctx.request.body),
resHeader: context.resHeader,
httpCode: context.httpCode,
delay: context.httpCode,
Random: Mock.Random
};
sandbox.cookie = {};
context.ctx.header.cookie &&
context.ctx.header.cookie.split(';').forEach(function (Cookie) {
var parts = Cookie.split('=');
sandbox.cookie[parts[0].trim()] = (parts[1] || '').trim();
});
sandbox = await sandboxFn(sandbox, script, false);
sandbox.delay = isNaN(sandbox.delay) ? 0 : +sandbox.delay;
context.mockJson = sandbox.mockJson;
context.resHeader = sandbox.resHeader;
context.httpCode = sandbox.httpCode;
context.delay = sandbox.delay;
};
exports.createWebAPIRequest = function (ops) {
return new Promise(function (resolve, reject) {
let req = '';
let http_client = http.request(
{
host: ops.hostname,
method: 'GET',
port: ops.port,
path: ops.path
},
function (res) {
res.on('error', function (err) {
reject(err);
});
res.setEncoding('utf8');
if (res.statusCode != 200) {
reject({ message: 'statusCode != 200' });
} else {
res.on('data', function (chunk) {
req += chunk;
});
res.on('end', function () {
resolve(req);
});
}
}
);
http_client.on('error', (e) => {
reject({ message: `request error: ${e.message}` });
});
http_client.end();
});
}