crud-controller.js 7.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229
  1. 'use strict';
  2. /**
  3. * 基于meta描述数据生成Controller类
  4. * 按照描述信息将web请求中的数据提取出来,组织为service的调用参数
  5. * {Controller名字}.json
  6. * meta文件属性描述:
  7. * service 字符串或者对象,字符串表示服务方法名,默认与action同名;
  8. * 对象可包含name、func两个属性,func与使用字符串是含义相同,
  9. * name表示服务名
  10. * parameters 请求参数描述,对象类型,可以包含属性为:'query'、 'params'、 'requestBody'、'options',
  11. * 分别对应eggjs中的三个请求参数来源和可选参数类型
  12. * options 可选参数,对象类型,可以指定排序字段和一些常量参数值,具体内容格式随意,在服务中解析
  13. * params 路由参数,数组类型,从ctx.params中提取数据
  14. * requestBody 请求数据,数组类型,从ctx.request.body中提取数据
  15. * query 查询参数,数组类型'options',从ctx.query中提取数据
  16. * 完整格式:
  17. "action": {
  18. "parameters": {
  19. "params": ["field1", "field2",...], // 可选
  20. "query": ["field1", "field2",...], // 可选
  21. "requestBody": ["field1", "field2",...], // 可选
  22. "options": { "ext1": "value1"}, // 可选
  23. },
  24. "service": "query", // 可选
  25. "options": { // 可选
  26. "sort": ["field1", "field2",...]
  27. }
  28. },
  29. * 简单格式:
  30. * 如果meta对象中没有parameters属性,则按简单格式处理,即整个meta的内容都作为parameters来处理
  31. "action": {
  32. "params": ["attr1", "attr2",...], // 可选
  33. "query": ["attr1", "attr2",...], // 可选
  34. "requestBody": ["attr1", "attr2",...], // 可选
  35. "options": { "ext1": "value1"}, // 可选
  36. },
  37. * meta实例:
  38. {
  39. "create": {
  40. "requestBody": ["code","name","order"]
  41. },
  42. "delete": {
  43. "query": { "id": "_id" }
  44. },
  45. "update": {
  46. "query": ["_id"],
  47. "requestBody": ["name","order"]
  48. },
  49. "list": {
  50. "parameters": {},
  51. "service": "query",
  52. "options": {
  53. "sort": ["order", "code"]
  54. }
  55. },
  56. "fetch": {
  57. "query": ["_id"]
  58. "service": {
  59. "name": "items",
  60. "func": "query"
  61. }
  62. }
  63. }
  64. * 服务接口:
  65. * someService.someMethod(requestParams, requestBody, options)
  66. * 服务参数:
  67. * requestParams 请求参数,必须
  68. * requestBody 请求数据,可选
  69. * options 可选参数,可选
  70. */
  71. const assert = require('assert');
  72. const _ = require('lodash');
  73. const is = require('is-type-of');
  74. const { trimData } = require('naf-core').Util;
  75. const key_reg = /^([!#]*)(.*)$/;
  76. const MapData = (data, names) => {
  77. if (_.isArray(names)) {
  78. // return names.reduce((p, c) => {
  79. // p[c] = data[c];
  80. // return p;
  81. // }, {});
  82. names = names.reduce((p, c) => {
  83. // p[c] = c.charAt(0) === '!' ? c.substr(1) : c;
  84. p[c] = key_reg.exec(c)[2];
  85. return p;
  86. }, {});
  87. }
  88. if (_.isObject(names)) {
  89. // TODO: 参数映射方式,.eg { "id": "_id", "corp.id": "corpid", "src": "dest" }
  90. return Object.entries(names).reduce((p, [ key, val ]) => {
  91. // const required = key.charAt(0) === '!';
  92. const [ , qualifier, _key ] = key_reg.exec(key);
  93. key = _key;
  94. const required = qualifier.includes('!');
  95. const numeric = qualifier.includes('#');
  96. let value = _.get(data, key);
  97. if (required) {
  98. // key = key.substr(1);
  99. value = _.get(data, key);
  100. assert(!_.isUndefined(value), `${key}不能为空`);
  101. }
  102. if (!_.isString(val)) {
  103. val = key;
  104. }
  105. if (!_.isUndefined(value)) {
  106. if (numeric) {
  107. assert(_.isNumber(value) || (_.isString(value) && /^-?\d+$/.test(value)), `${key}必须为数字`);
  108. value = Number(value);
  109. }
  110. p[val] = value;
  111. }
  112. return p;
  113. }, {});
  114. }
  115. return data;
  116. };
  117. let MapOptions;
  118. const MapParameters = (ctx, opts) => {
  119. const include = [ 'parameters', 'query', 'params', 'requestBody', 'options' ];
  120. const _opts = trimData({ ...opts }, null, include);
  121. const keys = Object.keys(_opts);
  122. return keys.map(key => {
  123. // 嵌套调用
  124. if (key === 'parameters') return MapParameters(ctx, opts[key]);
  125. if (key === 'options') return MapOptions(ctx, opts[key]);
  126. let data = ctx[key];
  127. if (key === 'requestBody') data = ctx.request.body;
  128. const names = opts[key];
  129. // if (_.isArray(names)) {
  130. // return names.reduce((p, c) => {
  131. // p[c] = data[c];
  132. // return p;
  133. // }, {});
  134. // }
  135. // return data;
  136. return MapData(data, names);
  137. }).reduce((p, c) => {
  138. if (c) {
  139. p = { ...c, ...p };
  140. }
  141. return p;
  142. }, {});
  143. };
  144. const MapRequestBody = (ctx, opts) => {
  145. // if (_.isArray(opts)) {
  146. // const data = ctx.request.body;
  147. // return opts.reduce((p, c) => {
  148. // p[c] = data[c];
  149. // return p;
  150. // }, {});
  151. // }
  152. // return MapParameters(ctx, opts) || {};
  153. if (_.isObject(opts) && [ 'parameters', 'query', 'params', 'requestBody' ].some(p => Object.keys(opts).includes(p))) {
  154. return MapParameters(ctx, opts) || {};
  155. }
  156. return MapData(ctx.request.body, opts);
  157. };
  158. MapOptions = (ctx, opts) => {
  159. const exclude = [ 'parameters', 'query', 'params', 'requestBody' ];
  160. const _opts = trimData({ ...opts }, exclude) || {};
  161. const params = MapParameters(ctx, opts) || {};
  162. if (!_.isUndefined(params.skip) && !_.isNumber(params.skip)) params.skip = Number(params.skip);
  163. if (!_.isUndefined(params.limit) && !_.isNumber(params.limit)) params.limit = Number(params.limit);
  164. return { ...params, ..._opts };
  165. };
  166. const CrudController = (cls, meta) => {
  167. Object.keys(meta)
  168. .forEach(key => {
  169. // Do not override existing functions
  170. if (!cls.prototype[key]) {
  171. const { parameters, requestBody, options } = meta[key];
  172. cls.prototype[key] = async function() {
  173. const { ctx } = this;
  174. const requestParams = MapParameters(ctx, parameters || meta[key]) || {};
  175. const _requestBody = requestBody && MapRequestBody(ctx, requestBody);
  176. const _options = options && MapOptions(ctx, options);
  177. const serviceParams = [ requestParams ];
  178. if (requestBody) {
  179. serviceParams.push(_requestBody);
  180. }
  181. if (options) {
  182. serviceParams.push(_options);
  183. }
  184. let { service } = meta[key];
  185. // 修改service元数据的定义方式
  186. // const funcName = (_.isObject(service) && service.action) || (_.isString(service) && service) || key;
  187. // const provider = (_.isObject(service) && service.name && ctx.service[service.name]) || this.service;
  188. if (_.isString(service)) { // TODO: 解析service为对象
  189. const tokens = service.split('.');
  190. service = { action: tokens.pop() };
  191. if (tokens.length > 0) {
  192. service.name = tokens;
  193. }
  194. }
  195. const funcName = (_.isObject(service) && service.action) || key;
  196. const provider = (_.isObject(service) && service.name && _.get(ctx.service, service.name)) || this.service;
  197. const func = provider[funcName];
  198. if (!is.asyncFunction(func)) {
  199. throw new Error(`service not support function ${funcName}`);
  200. }
  201. const data = await func.call(provider, ...serviceParams);
  202. let res = { data };
  203. // 统计数据总数,处理分页
  204. if (_options && _options.count) {
  205. const funcName = _.isString(_options.count) ? _options.count : 'count';
  206. const func = provider[funcName];
  207. if (!is.asyncFunction(func)) {
  208. throw new Error('not support count function');
  209. }
  210. const total = await func.call(provider, ...serviceParams);
  211. res = { data, total };
  212. }
  213. this.ctx.ok(res);
  214. };
  215. }
  216. });
  217. return cls;
  218. };
  219. module.exports = CrudController;