nei
Version:
builder for nei platform
536 lines (522 loc) • 17.5 kB
JavaScript
/*
* 格式化 NEI 上的源数据
* @author huntbao
*/
;
let path = require('path');
let neiDbConst = require('../fb-modules/config/db.json');
let logger = require('../util/logger');
module.exports = {
/**
* 获取输出参数的类型, 后端在接口代码中需要该信息
*
* 根据返回值的类型,需返回不同的值,规则如下:
* 1. 如果没有定义返回值,则返回: {typeName: null}。
* 2. 如果返回值为一个导入的 `ResultData`(即只有一个可变类型字段),根据可变类型字段可变类型字段的类型,则返回:
* a. 如果可变类型字段为基本类型(字符、数值、布尔),则返回 {typeName: `基本类型名称`}。
* b. 如果可变类型字段为自定义哈希类型 CustomModel,则返回 {typeName: `CustomModel`}。
* c. 如果可变类型字段是数组,则:
* i. 如果数组元素是基本类型(字符、数值、布尔) 或者自定义类型 CustomModel(或者数组元素的类型是数组),则返回 {typeName: `基本类型名称或者CustomModel`, dim: `数组维数值`}。
* ii. 如果数组元素是数组(二维数组),则根据二维数组的元素类型,递归执行规则 h。例如,如果二维数组的元素是 String, 则返回 {typeName: 'String', dim: 2}。
* d. 如果可变字段没有指定具体的类型, 则返回: {typeName: `Object`}
* 3. 如果返回值为一个导入的自定义类型 CustomModel(但不是 ResultData),则返回:{typeName: `CustomModel`}。
* 4. 如果返回值只有一个字段,则根据它的类型,按规则 2 执行。
* 5. 如果返回值的字段个数大于 1, 如果只有一个自定义类型, 则返回 {typeName: `CustomModel`}, 否则返回 {typeName: `Object`}。
* 6. 其他情况返回:{typeName: null}。
*
* @param {object} params - 参数数组
* @return {Object} 输出参数的类型
* @property {String} typeName - 输出参数的模型名称
* @property {Number} dim - 如果输出参数模型是一个数组, 则它表示数组的维数, 0 表示不是数组, 1 表示一维数组, 依此类推
*/
getOutputModel(params) {
if (!params || params.length == 0) {
return {
typeName: null
}
}
let datatypeName = null;
params.some((item) => {
if (item.datatypeName) {
if (datatypeName === null) {
datatypeName = item.datatypeName;
} else if (datatypeName !== item.datatypeName) {
datatypeName = null;
return true;
}
} else {
datatypeName = null;
return true;
}
});
if (datatypeName) {
// 是一个导入类型
// 先验证是否只有一个可变类型
let variableTypes = params.filter((item) => {
// 只有在被复写过的时候originalType才会是10000, 没有被复写的话 typeName 为 Variable
return item.originalType === 10000 || item.typeName === 'Variable';
});
if (variableTypes.length === 1) {
let varType = variableTypes[0];
// 检测是否为数组, 它是一个哈希的字段, 字段定义为 Array|xxxx
if (varType.isArray) {
return this.getDatatype(varType);
} else {
// 检测类别是否是数组
let dataType = this.neids.datatypes.find((item) => {
return item.id === varType.type;
});
if (dataType.id === 10000) {
// 没有为可变字段设置具体的类型
return {
typeName: `Object`
}
}
if (dataType.format === neiDbConst.MDL_FMT_ARRAY) {
return this.getDatatype(dataType);
}
return {
typeName: varType.typeName
}
}
} else {
return {
typeName: datatypeName
};
}
} else if (params.length === 1) {
let param = params[0];
if (param.isArray) {
return this.getDatatype(param);
} else {
return {
typeName: param.typeName
}
}
} else if (params.length > 1) {
let customDatatypes = params.filter(param => param.type > 10003);
if (customDatatypes.length === 1) {
// 只有一个自定义类型
return {
typeName: customDatatypes[0].typeName
}
}
return {
typeName: 'Object'
}
} else {
return {
typeName: null
}
}
},
/**
* 格式化 nei 数据源
* @param {object} neids - nei 上的数据源
*/
format(neids) {
this.neids = neids;
let ds = {
project: {},
spec: {},
views: [],
templates: [],
interfaces: [],
datatypes: [],
datatypeEnums: [],
constraints: [],
};
let varmaps = this.getVarmaps(neids.specs[0].varmaps);
let getType = (type) => {
return varmaps[type] || type;
};
let getParams = (params) => {
return params.map((item) => {
let arrDim = item.isArray ? 1 : 0;
let datatype = this.neids.datatypes.find((it) => {
return it.id === item.type;
});
let datatypeInfo = this.getDatatype(datatype);
return {
typeId: item.type,
name: item.name,
type: getType(datatypeInfo.typeName),
originalType: datatypeInfo.typeName,
arrDim: datatypeInfo.dim + arrDim,
format: datatype.format,
itemIsArray: datatypeInfo.dim + arrDim > 0,
required: item.required == 1,
ignored: item.ignored == 1,
defaultValue: item.defaultValue,
genExp: item.genExpression,
description: item.description
}
});
};
let getKVs = (params) => {
return params.map((item) => {
return {
key: item.name,
value: item.defaultValue,
description: item.description || ''
}
});
};
let getTemplate = (item) => {
let tpl = {
name: item.name,
path: item.path,
tag: item.tag,
description: item.description,
group: {
name: item.group.name,
description: item.group.description
},
params: getParams(item.params || [])
};
// 处理 path, 如果没有后缀, 加上规范中指定的模板扩展名
let extname = path.extname(tpl.path);
if (!extname && ds.spec.viewExt) {
extname = `.${ds.spec.viewExt}`;
tpl.path += extname;
}
// 对应的 css 和 js 等资源文件会用到这个值
tpl.filename = `${path.dirname(tpl.path)}/${path.basename(tpl.path, extname)}`.replace(/^\/*/, '');
return tpl;
};
let getJSONByStr = (str) => {
try {
return JSON.parse(str);
} catch (e) {
return {};
}
}
// 规范数据
let spec = neids.specs[0].spec;
ds.spec = {
name: spec.name,
description: spec.description,
document: spec.document,
language: this.getSpecLanguage(spec.language)
};
if (spec.attributes) {
Object.assign(ds.spec, {
engine: this.getSpecEngine(spec.attributes.engine),
viewExt: spec.attributes.viewExtension
});
}
if (!neids.project) {
return ds;
}
// 项目数据
ds.project = {
id: neids.project.id,
name: neids.project.name,
description: neids.project.description,
creator: {
email: neids.project.creator.email,
name: neids.project.creator.realname
}
};
// 被页面引用的页面模板id列表
let usedTemplateIds = {};
// 视图
ds.views = neids.pages.map((item) => {
let tpls = [];
item.templates.forEach(function (tpl) {
usedTemplateIds[tpl.id] = 1;
tpls.push(getTemplate(tpl));
});
if (!item.className) {
logger.log('warn', {
data: [item.path, item.name],
message: '没有为页面指定类名: %s, %s'
});
}
return {
name: item.name,
path: item.path,
tag: item.tag,
title: item.title,
description: item.description,
className: item.className,
group: {
name: item.group.name,
description: item.group.description
},
templates: tpls
}
});
// 页面模板, 只处理被页面引用的模板
neids.templates.forEach((item) => {
if (usedTemplateIds[item.id]) {
let tpl = getTemplate(item);
ds.templates.push(tpl);
}
});
// 异步接口
ds.interfaces = neids.interfaces.map((item) => {
let reqClassName = this.getReqClassName(item);
if (!reqClassName.endsWith('Request') && !reqClassName.endsWith('Task')) {
reqClassName += `Request`;
}
let outputModel = this.getOutputModel(item.params.outputs);
let outputModelKey = "";
let temp = item.params.outputs.filter((output) => {
return output.typeName == outputModel.typeName;
});
if (temp.length) {
outputModelKey = temp[0].name;
}
let nItem = {
name: item.name,
description: item.description,
tag: item.tag,
path: item.path,
method: item.method,
className: item.className,
group: {
name: item.group.name,
description: item.group.description
},
status: {
name: item.status.name
},
id: item.id,
inputs: null,
outputs: null,
outputModel: outputModel.typeName,
outputModelArrDim: outputModel.dim,
outputModelKey,
reqHeaders: null,
reqFormat: item.reqFormat,
resFormat: item.resFormat,
reqClassName,
testcases: item.testcases.map((tc) => {
return {
name: tc.name,
host: tc.host,
description: tc.description,
reqHeader: getJSONByStr(tc.reqHeader),
reqData: getJSONByStr(tc.reqData),
resHeader: getJSONByStr(tc.resHeader),
resExpect: getJSONByStr(tc.resExpect)
}
})
};
nItem.inputs = getParams(item.params.inputs);
nItem.outputs = getParams(item.params.outputs);
if (nItem.outputModel) {
nItem.outputModel = getType(nItem.outputModel);
}
nItem.reqHeaders = getKVs(item.params.reqHeaders);
/**
* 提供varHeaders和constHeaders字段, 得以判断出Header是常量还是变量
* @author AbnerZheng
*/
let getSplitReqHeaders = (reqHeaders) => {
let constHeaders = [];
let varHeaders = [];
reqHeaders.forEach((header) => {
// variable header
if (header.defaultValue === '') {
varHeaders.push(header);
} else {
constHeaders.push(header);
}
});
return {
constHeaders: constHeaders,
varHeaders: varHeaders
}
};
let reqSplitHeader = getSplitReqHeaders(item.params.reqHeaders);
nItem.reqConstHeaders = getKVs(reqSplitHeader.constHeaders);
nItem.reqVarHeaders = getKVs(reqSplitHeader.varHeaders);
return nItem;
});
neids.datatypes.forEach((item) => {
// 也要包括匿名数据模型,它肯定是哈希,但 type 定义了其他值
if (item.type === neiDbConst.MDL_FMT_HASH
&& !item.name.startsWith('_')
&& item.format === neiDbConst.MDL_FMT_HASH
|| item.type === neiDbConst.MDL_TYP_HIDDEN) {
let foundVar = item.params.find((item) => {
return item.type === neiDbConst.MDL_SYS_VARIABLE;
});
let fields = getParams(item.params);
let depModels = {};
fields.filter((item) => {
return item.format == neiDbConst.MDL_FMT_HASH || item.format == neiDbConst.MDL_FMT_ARRAY
}).forEach((item) => {
// 根据真正类型查数据模型
let datatype = neids.datatypes.find(dt => dt.name === item.originalType);
if (datatype.format === neiDbConst.MDL_FMT_HASH) {
depModels[item.name] = item.type;
}
});
if (!foundVar) {
// 数据模型, 只处理可见的哈希类型, 并且开始字符不是下划线, 并且没有可变类型的字符
ds.datatypes.push({
id: item.id,
name: item.name,
description: item.description,
tag: item.tag,
group: {
name: item.group.name,
description: item.group.description
},
fields: fields,
depModels: depModels
});
}
} else if (item.format === neiDbConst.MDL_FMT_ENUM) {
let enumDT = {
name: item.name,
description: item.description,
tag: item.tag,
group: {
name: item.group.name,
description: item.group.description
},
// 取第一个成员的类型
type: item.params[0] ? getType(item.params[0].typeName) : 'String',
members: getKVs(item.params)
};
// 所有的枚举类型
ds.datatypeEnums.push(enumDT);
}
});
// 约束函数数据
ds.constraints = neids.constraints;
return ds;
},
/**
* 合并变量映射规则
* @param {array} varmaps - 变量映射规则列表
* @return {object} - 变量映射规则
*/
getVarmaps(varmaps) {
let specVarmaps = {};
let progroupVarmaps = {};
let projectVarmaps = {};
varmaps.forEach((item) => {
switch (item.parentType) {
case neiDbConst.SPC_MAP_SPEC:
specVarmaps[item.orgName] = item.varName;
break;
case neiDbConst.SPC_MAP_PROGROUP:
progroupVarmaps[item.orgName] = item.varName;
break;
case neiDbConst.SPC_MAP_PROJECT:
projectVarmaps[item.orgName] = item.varName;
break;
}
});
// 项目的规则 > 项目组的规则 > 规范的规则
return Object.assign({}, specVarmaps, progroupVarmaps, projectVarmaps);
},
/**
* 返回某个数据模型的类型名称, 以及如果它是一个数组, 返回数组的维度值
* @param {Object} datatype - 数据模型
* @return {Object} 输出参数的类型
* @property {String} typeName - 数据模型的类型名称
* @property {Number} dim - 如果数据模型是一个数组, 则它表示数组的维数, 0 表示不是数组, 1 表示一维数组, 依此类推
*/
getDatatype(datatype) {
let arrDimLen = 0;
let getType = (dt) => {
if (dt.isArray) {
arrDimLen++;
let arrDatatype = this.neids.datatypes.find((item) => {
return item.id === dt.type;
});
return getType(arrDatatype);
} else if (dt.format === neiDbConst.MDL_FMT_ARRAY) {
// 数组类型
arrDimLen++;
let element = dt.params[0];
if (element.isArray) {
return getType(element);
} else {
return element.typeName;
}
} else {
return dt.name;
}
}
let elementTypeName = getType(datatype);
return {
typeName: elementTypeName,
dim: arrDimLen
}
},
/**
* 获取规范所指定的编程语言
* @param {number} language - 语言代码
*/
getSpecLanguage(language) {
let ls = {};
ls[neiDbConst.SPC_LNG_UNKNOWN] = 'unknown';
ls[neiDbConst.SPC_LNG_JAVA] = 'java';
ls[neiDbConst.SPC_LNG_PHP] = 'php';
ls[neiDbConst.SPC_LNG_OC] = 'oc';
ls[neiDbConst.SPC_LNG_SWIFT] = 'swift';
return ls[language];
},
/**
* 获取规范所指定的编程语言
* @param {number} engine - 模板引擎代码
*/
getSpecEngine(engine) {
let es = {};
es[neiDbConst.SPC_ENG_NONE] = '';
es[neiDbConst.SPC_ENG_EJS] = 'ejs';
es[neiDbConst.SPC_ENG_FREEMARK] = 'freemarker';
es[neiDbConst.SPC_ENG_SMARTY] = 'smarty';
es[neiDbConst.SPC_ENG_SWIG] = 'swig';
es[neiDbConst.SPC_ENG_VELOCITY] = 'velocity';
return es[engine];
},
/**
* 根据文件路径获取文件名, 不包括后缀
* @param {string} filePath - 文件路径
*/
getFileNameByPath(filePath) {
let extname = path.extname(filePath);
return path.normalize(`${path.dirname(filePath)}/${path.basename(filePath, extname)}`.replace(/^\/*/, ''));
},
/**
* get request name
* @param {object} itf - interface object
* @return {string} - request class name
*/
getReqClassName(itf) {
let name = itf.className;
if (/^[0-9a-zA-Z_$]+$/gi.test(name)) {
return name;
} else {
let tip;
if (name) {
tip = `接口 "${itf.name}" 的类名 "${name}" 不合法, 尝试使用接口名称`;
} else {
tip = `接口 "${itf.name}" 没有指定类名, 尝试使用接口名称`;
}
logger.log('warn', {
message: tip
});
}
name = itf.name;
if (/^[0-9a-zA-Z_$]+$/gi.test(name)) {
return name;
} else {
logger.log('warn', {
message: `接口 "${itf.name}" 的名称不能作为类名使用, 尝试转换path作为类名`
});
}
// name is invalid for class name, using path's camelCase format
return itf.path.replace(/\/(.)/g, (match, group1) => {
return group1.toUpperCase();
}).replace(/\//g, '');
}
}