123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229 |
- 'use strict';
- /**
- * 基于meta描述数据生成Controller类
- * 按照描述信息将web请求中的数据提取出来,组织为service的调用参数
- * {Controller名字}.json
- * meta文件属性描述:
- * service 字符串或者对象,字符串表示服务方法名,默认与action同名;
- * 对象可包含name、func两个属性,func与使用字符串是含义相同,
- * name表示服务名
- * parameters 请求参数描述,对象类型,可以包含属性为:'query'、 'params'、 'requestBody'、'options',
- * 分别对应eggjs中的三个请求参数来源和可选参数类型
- * options 可选参数,对象类型,可以指定排序字段和一些常量参数值,具体内容格式随意,在服务中解析
- * params 路由参数,数组类型,从ctx.params中提取数据
- * requestBody 请求数据,数组类型,从ctx.request.body中提取数据
- * query 查询参数,数组类型'options',从ctx.query中提取数据
- * 完整格式:
- "action": {
- "parameters": {
- "params": ["field1", "field2",...], // 可选
- "query": ["field1", "field2",...], // 可选
- "requestBody": ["field1", "field2",...], // 可选
- "options": { "ext1": "value1"}, // 可选
- },
- "service": "query", // 可选
- "options": { // 可选
- "sort": ["field1", "field2",...]
- }
- },
- * 简单格式:
- * 如果meta对象中没有parameters属性,则按简单格式处理,即整个meta的内容都作为parameters来处理
- "action": {
- "params": ["attr1", "attr2",...], // 可选
- "query": ["attr1", "attr2",...], // 可选
- "requestBody": ["attr1", "attr2",...], // 可选
- "options": { "ext1": "value1"}, // 可选
- },
- * meta实例:
- {
- "create": {
- "requestBody": ["code","name","order"]
- },
- "delete": {
- "query": { "id": "_id" }
- },
- "update": {
- "query": ["_id"],
- "requestBody": ["name","order"]
- },
- "list": {
- "parameters": {},
- "service": "query",
- "options": {
- "sort": ["order", "code"]
- }
- },
- "fetch": {
- "query": ["_id"]
- "service": {
- "name": "items",
- "func": "query"
- }
- }
- }
- * 服务接口:
- * someService.someMethod(requestParams, requestBody, options)
- * 服务参数:
- * requestParams 请求参数,必须
- * requestBody 请求数据,可选
- * options 可选参数,可选
- */
- const assert = require('assert');
- const _ = require('lodash');
- const is = require('is-type-of');
- const { trimData } = require('naf-core').Util;
- const key_reg = /^([!#]*)(.*)$/;
- const MapData = (data, names) => {
- if (_.isArray(names)) {
- // return names.reduce((p, c) => {
- // p[c] = data[c];
- // return p;
- // }, {});
- names = names.reduce((p, c) => {
- // p[c] = c.charAt(0) === '!' ? c.substr(1) : c;
- p[c] = key_reg.exec(c)[2];
- return p;
- }, {});
- }
- if (_.isObject(names)) {
- // TODO: 参数映射方式,.eg { "id": "_id", "corp.id": "corpid", "src": "dest" }
- return Object.entries(names).reduce((p, [ key, val ]) => {
- // const required = key.charAt(0) === '!';
- const [ , qualifier, _key ] = key_reg.exec(key);
- key = _key;
- const required = qualifier.includes('!');
- const numeric = qualifier.includes('#');
- let value = _.get(data, key);
- if (required) {
- // key = key.substr(1);
- value = _.get(data, key);
- assert(!_.isUndefined(value), `${key}不能为空`);
- }
- if (!_.isString(val)) {
- val = key;
- }
- if (!_.isUndefined(value)) {
- if (numeric) {
- assert(_.isNumber(value) || (_.isString(value) && /^-?\d+$/.test(value)), `${key}必须为数字`);
- value = Number(value);
- }
- p[val] = value;
- }
- return p;
- }, {});
- }
- return data;
- };
- let MapOptions;
- const MapParameters = (ctx, opts) => {
- const include = [ 'parameters', 'query', 'params', 'requestBody', 'options' ];
- const _opts = trimData({ ...opts }, null, include);
- const keys = Object.keys(_opts);
- return keys.map(key => {
- // 嵌套调用
- if (key === 'parameters') return MapParameters(ctx, opts[key]);
- if (key === 'options') return MapOptions(ctx, opts[key]);
- let data = ctx[key];
- if (key === 'requestBody') data = ctx.request.body;
- const names = opts[key];
- // if (_.isArray(names)) {
- // return names.reduce((p, c) => {
- // p[c] = data[c];
- // return p;
- // }, {});
- // }
- // return data;
- return MapData(data, names);
- }).reduce((p, c) => {
- if (c) {
- p = { ...c, ...p };
- }
- return p;
- }, {});
- };
- const MapRequestBody = (ctx, opts) => {
- // if (_.isArray(opts)) {
- // const data = ctx.request.body;
- // return opts.reduce((p, c) => {
- // p[c] = data[c];
- // return p;
- // }, {});
- // }
- // return MapParameters(ctx, opts) || {};
- if (_.isObject(opts) && [ 'parameters', 'query', 'params', 'requestBody' ].some(p => Object.keys(opts).includes(p))) {
- return MapParameters(ctx, opts) || {};
- }
- return MapData(ctx.request.body, opts);
- };
- MapOptions = (ctx, opts) => {
- const exclude = [ 'parameters', 'query', 'params', 'requestBody' ];
- const _opts = trimData({ ...opts }, exclude) || {};
- const params = MapParameters(ctx, opts) || {};
- if (!_.isUndefined(params.skip) && !_.isNumber(params.skip)) params.skip = Number(params.skip);
- if (!_.isUndefined(params.limit) && !_.isNumber(params.limit)) params.limit = Number(params.limit);
- return { ...params, ..._opts };
- };
- const CrudController = (cls, meta) => {
- Object.keys(meta)
- .forEach(key => {
- // Do not override existing functions
- if (!cls.prototype[key]) {
- const { parameters, requestBody, options } = meta[key];
- cls.prototype[key] = async function() {
- const { ctx } = this;
- const requestParams = MapParameters(ctx, parameters || meta[key]) || {};
- const _requestBody = requestBody && MapRequestBody(ctx, requestBody);
- const _options = options && MapOptions(ctx, options);
- const serviceParams = [ requestParams ];
- if (requestBody) {
- serviceParams.push(_requestBody);
- }
- if (options) {
- serviceParams.push(_options);
- }
- let { service } = meta[key];
- // 修改service元数据的定义方式
- // const funcName = (_.isObject(service) && service.action) || (_.isString(service) && service) || key;
- // const provider = (_.isObject(service) && service.name && ctx.service[service.name]) || this.service;
- if (_.isString(service)) { // TODO: 解析service为对象
- const tokens = service.split('.');
- service = { action: tokens.pop() };
- if (tokens.length > 0) {
- service.name = tokens;
- }
- }
- const funcName = (_.isObject(service) && service.action) || key;
- const provider = (_.isObject(service) && service.name && _.get(ctx.service, service.name)) || this.service;
- const func = provider[funcName];
- if (!is.asyncFunction(func)) {
- throw new Error(`service not support function ${funcName}`);
- }
- const data = await func.call(provider, ...serviceParams);
- let res = { data };
- // 统计数据总数,处理分页
- if (_options && _options.count) {
- const funcName = _.isString(_options.count) ? _options.count : 'count';
- const func = provider[funcName];
- if (!is.asyncFunction(func)) {
- throw new Error('not support count function');
- }
- const total = await func.call(provider, ...serviceParams);
- res = { data, total };
- }
- this.ctx.ok(res);
- };
- }
- });
- return cls;
- };
- module.exports = CrudController;
|