jj.js 0.18.0 → 0.20.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,16 +1,24 @@
1
1
  const path = require('path');
2
- const loader = require('./loader');
3
2
  const {readFile} = require('fs').promises;
4
3
  const {toHump} = require('./utils/str');
4
+ const {validatePath} = require('./utils/validate');
5
5
  const config = require('./config');
6
6
  const {app: cfg_app, view: cfg_view} = config;
7
7
  const compose = require('koa-compose');
8
8
  const Logger = require('./logger');
9
9
  const storage = require('./storage');
10
10
 
11
- async function run(ctx, next, control_type=cfg_app.controller_folder) {
11
+ /**
12
+ * MVC中间件
13
+ * @param {import('../types').KoaCtx} ctx
14
+ * @param {import('../types').KoaMiddleware} next
15
+ * @returns {Promise<void>}
16
+ */
17
+ async function mvc(ctx, next) {
18
+ const controller_folder = ctx.FOLDER || cfg_app.controller_folder;
19
+
12
20
  if(cfg_app.app_debug) {
13
- Logger.http({Router: control_type, APP: ctx.APP, CONTROLLER: ctx.CONTROLLER, ACTION: ctx.ACTION});
21
+ Logger.http({Router: controller_folder, APP: ctx.APP, CONTROLLER: ctx.CONTROLLER, ACTION: ctx.ACTION});
14
22
  Object.keys(ctx.params).length && Logger.http('Params:', ctx.params);
15
23
  Object.keys(ctx.query).length && Logger.http('Get:', ctx.query);
16
24
  Object.keys(ctx.request.body || {}).length && Logger.http('Post:', ctx.request.body);
@@ -22,9 +30,7 @@ async function run(ctx, next, control_type=cfg_app.controller_folder) {
22
30
  store.CONTROLLER = ctx.CONTROLLER;
23
31
  store.ACTION = ctx.ACTION;
24
32
  store.COMMON = cfg_app.common_app;
25
- // 设置根加载器
26
- store.$ = loader('./', ctx);
27
- store._ = loader(cfg_app.base_dir, ctx);
33
+
28
34
  // 配置特殊处理
29
35
  store._.config === undefined && (store._.config = {});
30
36
  Object.entries(config).forEach(([key, value]) => {
@@ -32,58 +38,58 @@ async function run(ctx, next, control_type=cfg_app.controller_folder) {
32
38
  });
33
39
 
34
40
  // 应用、控制器、方法名字验证
35
- if(~ctx.APP.indexOf('.') || ~ctx.CONTROLLER.indexOf('.') || ~ctx.ACTION.indexOf('.')) {
41
+ if(!validatePath(ctx.APP) || !validatePath(ctx.CONTROLLER) || !validatePath(ctx.ACTION)) {
36
42
  // throw new Error(`RunError: 应用、控制器或方法名字不合法!`);
37
- return false;
43
+ return;
38
44
  }
39
45
 
40
46
  if(ctx.APP[0] == '_' || ctx.APP[0] == '$' || !store._[ctx.APP]) {
41
47
  if(cfg_app.app_debug) {
42
48
  throw new Error(`RunError: 应用:${ctx.APP}不存在!`);
43
49
  } else {
44
- return false;
50
+ return;
45
51
  }
46
52
  }
47
53
 
48
- if(!store._[ctx.APP][control_type]) {
54
+ if(!store._[ctx.APP][controller_folder]) {
49
55
  if(cfg_app.app_debug) {
50
- throw new Error(`RunError: 目录:${ctx.APP}/${control_type}不存在!`);
56
+ throw new Error(`RunError: 目录:${ctx.APP}/${controller_folder}不存在!`);
51
57
  } else {
52
- return false;
58
+ return;
53
59
  }
54
60
  }
55
61
 
56
62
  // 模版文件内容直接输出
57
- if(control_type == cfg_view.view_folder) {
58
- const content = await readFile(path.join(cfg_app.base_dir, `${ctx.APP}/${control_type}`, ctx.CONTROLLER + cfg_view.view_depr + ctx.ACTION + cfg_view.view_ext));
63
+ if(controller_folder == cfg_view.view_folder) {
64
+ const content = await readFile(path.join(cfg_app.base_dir, `${ctx.APP}/${controller_folder}`, ctx.CONTROLLER + cfg_view.view_depr + ctx.ACTION + cfg_view.view_ext));
59
65
  if(!content) {
60
- throw new Error(`RunError: 模板文件:${ctx.APP}/${control_type}/${ctx.CONTROLLER + cfg_view.view_depr + ctx.ACTION + cfg_view.view_ext}不存在!`);
66
+ throw new Error(`RunError: 模板文件:${ctx.APP}/${controller_folder}/${ctx.CONTROLLER + cfg_view.view_depr + ctx.ACTION + cfg_view.view_ext}不存在!`);
61
67
  }
62
68
  ctx.body = content;
63
69
  return;
64
70
  }
65
71
 
66
72
  // 控制器
67
- const Controller = store._[ctx.APP][control_type][ctx.CONTROLLER] || store._[ctx.APP][control_type]['_empty'];
73
+ const Controller = store._[ctx.APP][controller_folder][ctx.CONTROLLER] || store._[ctx.APP][controller_folder]['_empty'];
68
74
 
69
75
  if(ctx.CONTROLLER[0] == '_' || ctx.CONTROLLER[0] == '$' || typeof Controller != 'function') {
70
76
  if(cfg_app.app_debug) {
71
- throw new Error(`RunError: class文件:${ctx.APP}/${control_type}/${ctx.CONTROLLER}不存在!`);
77
+ throw new Error(`RunError: class文件:${ctx.APP}/${controller_folder}/${ctx.CONTROLLER}不存在!`);
72
78
  } else {
73
- return false;
79
+ return;
74
80
  }
75
81
  }
76
82
 
77
83
  // 控制器实例
78
84
  const $controller = new Controller(ctx, next);
79
85
  const humpAction = toHump(ctx.ACTION);
80
- const action = typeof $controller[humpAction] == 'function' ? humpAction : '_empty';
86
+ const action = typeof $controller[humpAction] == 'function' && !$controller[humpAction].__ISCLASS__ ? humpAction : '_empty';
81
87
 
82
88
  if(ctx.ACTION[0] == '_' || ctx.ACTION[0] == '$' || typeof $controller[action] != 'function') {
83
89
  if(cfg_app.app_debug) {
84
- throw new Error(`RunError: class方法:${ctx.APP}/${control_type}/${ctx.CONTROLLER}/${humpAction}不存在!`);
90
+ throw new Error(`RunError: class方法:${ctx.APP}/${controller_folder}/${ctx.CONTROLLER}/${humpAction}不存在!`);
85
91
  } else {
86
- return false;
92
+ return;
87
93
  }
88
94
  }
89
95
 
@@ -98,6 +104,7 @@ async function run(ctx, next, control_type=cfg_app.controller_folder) {
98
104
  if(middle.accept && (Array.isArray(middle.accept) ? !~middle.accept.indexOf(action) : !~[middle.accept].indexOf(action))) {
99
105
  return stack;
100
106
  }
107
+ // @ts-ignore
101
108
  return stack.concat(async (ctx, next) => {
102
109
  const [METHOD = action, MIDDLEWARE = ctx.CONTROLLER, APP = ctx.APP] = middle.middleware.split('/').reverse();
103
110
  if(!store._[APP]) {
@@ -130,4 +137,4 @@ async function run(ctx, next, control_type=cfg_app.controller_folder) {
130
137
  });
131
138
  }
132
139
 
133
- module.exports = run;
140
+ module.exports = mvc;
package/lib/pagination.js CHANGED
@@ -1,15 +1,28 @@
1
1
  const {page: cfg_page} = require('./config');
2
2
  const Context = require('./context');
3
3
 
4
+ /**
5
+ * @typedef {import('../types').KoaCtx} KoaCtx
6
+ * @typedef {import('../types').PageConfig} PageConfig
7
+ * @typedef {import('../types').UrlInstance} UrlInstance
8
+ */
9
+
4
10
  /**
5
11
  * @extends Context
6
12
  */
7
13
  class Pagination extends Context
8
14
  {
15
+ /**
16
+ * @type {PageConfig}
17
+ * @private
18
+ */
19
+ // @ts-ignore
20
+ options = null;
21
+
9
22
  /**
10
23
  * Initialize a new `Pagination`
11
24
  * @public
12
- * @param {import('../types').KoaCtx} ctx
25
+ * @param {KoaCtx} ctx
13
26
  */
14
27
  constructor(ctx) {
15
28
  super(ctx);
@@ -23,17 +36,17 @@ class Pagination extends Context
23
36
  /**
24
37
  * 分页配置
25
38
  * @public
26
- * @param {import('../types').PageConfig} [options]
39
+ * @param {PageConfig} [options]
27
40
  * @returns {this}
28
41
  */
29
42
  init(options) {
30
43
  /**
31
- * @type {import('../types').PageConfig}
44
+ * @type {PageConfig}
32
45
  * @private
33
46
  */
34
47
  this.options = {...cfg_page, ...options};
35
- this._pageKey = this.options.page_key;
36
- this._keyOrigin = this.options.key_origin;
48
+ this._pageKey = this.options.page_key || 'page';
49
+ this._keyOrigin = this.options.key_origin || 'query';
37
50
  this._ctxPage = this.ctx
38
51
  && this.ctx[this._keyOrigin]
39
52
  && this.ctx[this._keyOrigin][this._pageKey]
@@ -103,6 +116,7 @@ class Pagination extends Context
103
116
  this._totalPage = Math.ceil(this._total / this._pageSize);
104
117
 
105
118
  return this._initRule().options['template'].replace(/\$\{(\w+)\}/g, (...args) => {
119
+ // @ts-ignore
106
120
  return this['_' + args[1]]();
107
121
  });
108
122
  }
@@ -158,29 +172,42 @@ class Pagination extends Context
158
172
  return list;
159
173
  }
160
174
 
161
- // 解析模块
175
+ /**
176
+ * 解析模块
177
+ * @param {string} tpl_key
178
+ * @param {*} num
179
+ * @returns
180
+ */
162
181
  _parseTpl(tpl_key, num) {
182
+ // @ts-ignore
163
183
  return this.options[tpl_key].replace(/\$\{(\w+)\}/g, (...args) => {
164
184
  return args[1] == 'url' ? this._parseUrl(num) : args[1] == 'page' ? num : args[1] == 'total' ? this._total : this._totalPage;
165
185
  });
166
186
  }
167
187
 
168
- // 解析网址
188
+ /**
189
+ * 解析网址
190
+ * @param {number} page
191
+ * @returns
192
+ */
169
193
  _parseUrl(page) {
170
- return (page == 1 ? this._urlIndex : this._urlPage).replace('${page}', page);
194
+ // @ts-ignore
195
+ return ((page == 1 ? this._urlIndex : this._urlPage) || '').replace('${page}', page);
171
196
  }
172
197
 
173
198
  // 初始网址规则
174
199
  _initRule() {
175
- let url_index = this.options.url_index;
176
- let url_page = this.options.url_page;
200
+ let url_index = this.options.url_index || '';
201
+ let url_page = this.options.url_page || '';
177
202
 
178
203
  if(url_index.slice(0, 1) == ':') {
179
- url_index = this.$url.ruleUrl(url_index.substr(1), {[this._pageKey]: '__page__'}).replace('__page__', '${page}');
204
+ // @ts-ignore
205
+ url_index = this._$url.ruleUrl(url_index.substr(1), {[this._pageKey]: '__page__'}).replace('__page__', '${page}');
180
206
  }
181
207
 
182
208
  if(url_page.slice(0, 1) == ':') {
183
- url_page = this.$url.ruleUrl(url_page.substr(1), {[this._pageKey]: '__page__'}).replace('__page__', '${page}');
209
+ // @ts-ignore
210
+ url_page = this._$url.ruleUrl(url_page.substr(1), {[this._pageKey]: '__page__'}).replace('__page__', '${page}');
184
211
  }
185
212
 
186
213
  if(url_index == '' || url_page == '') {
@@ -212,11 +239,38 @@ class Pagination extends Context
212
239
  }
213
240
  }
214
241
 
215
- this._urlIndex = url_index;
216
- this._urlPage = url_page;
242
+ this._urlIndex = url_index || '';
243
+ this._urlPage = url_page || '';
217
244
 
218
245
  return this;
219
246
  }
247
+
248
+ /**
249
+ * @type {UrlInstance} Url实例
250
+ * @private
251
+ */
252
+ // @ts-ignore
253
+ __url = null;
254
+
255
+ /**
256
+ * @type {UrlInstance} Url实例
257
+ */
258
+ get _$url() {
259
+ if(this.__url === null) {
260
+ if(this.$url && this.$url.__ISCLASS__) {
261
+ this.__url = this.$url;
262
+ } else if(this.$ && this.$.url) {
263
+ this.__url = this.$.url;
264
+ } else {
265
+ this.__url = new (require('./url'))(this.ctx);
266
+ }
267
+ }
268
+ return this.__url;
269
+ }
270
+
271
+ set _$url(url) {
272
+ this.__url = url;
273
+ }
220
274
  }
221
275
 
222
276
  module.exports = Pagination;
package/lib/request.js CHANGED
@@ -61,6 +61,9 @@ class Request extends Context
61
61
  * @returns {*}
62
62
  */
63
63
  post(key, default_value='') {
64
+ if(!this.$config.app.koa_body && key !== 'is_ajax') {
65
+ throw new Error('请先在app配置中开启koa_body');
66
+ }
64
67
  return this._post[key] || default_value;
65
68
  }
66
69
 
@@ -100,10 +103,10 @@ class Request extends Context
100
103
  /**
101
104
  * 获取上传file数据
102
105
  * @param {string} key
103
- * @returns {import('formidable').File | import('formidable').File[]}
106
+ * @returns {import('formidable').File | import('formidable').File[] | undefined}
104
107
  */
105
108
  file(key) {
106
- return this.ctx.request.files[key];
109
+ return this.ctx.request.files && this.ctx.request.files[key];
107
110
  }
108
111
 
109
112
  /**
@@ -111,7 +114,7 @@ class Request extends Context
111
114
  * @returns {import('formidable').Files}
112
115
  */
113
116
  fileAll() {
114
- return this.ctx.request.files;
117
+ return this.ctx.request.files || {};
115
118
  }
116
119
 
117
120
  /**
package/lib/response.js CHANGED
@@ -2,6 +2,12 @@ const {tpl: cfg_tpl} = require('./config');
2
2
  const {parseError} = require('./utils/error');
3
3
  const Context = require('./context');
4
4
 
5
+ /**
6
+ * @typedef {import('../types').KoaCtx} KoaCtx
7
+ * @typedef {import('../types').RequestInstance} RequestInstance
8
+ * @typedef {import('../types').UrlInstance} UrlInstance
9
+ */
10
+
5
11
  /**
6
12
  * @extends Context
7
13
  */
@@ -10,7 +16,7 @@ class Response extends Context
10
16
  /**
11
17
  * Initialize a new `Response`
12
18
  * @public
13
- * @param {import('../types').KoaCtx} ctx
19
+ * @param {KoaCtx} ctx
14
20
  */
15
21
  constructor(ctx) {
16
22
  super(ctx);
@@ -36,7 +42,7 @@ class Response extends Context
36
42
  */
37
43
  redirect(url, status=302) {
38
44
  this.ctx.status = status;
39
- this.ctx.redirect(this.$url.build(url));
45
+ this.ctx.redirect(this._$url.build(url));
40
46
  }
41
47
 
42
48
  /**
@@ -47,7 +53,7 @@ class Response extends Context
47
53
  */
48
54
  success(msg='操作成功!', url) {
49
55
  typeof msg == 'object' && ([msg, url] = ['操作成功!', msg]);
50
- url = typeof url == 'string' ? this.$url.build(url) : (url || this.ctx.header.referer);
56
+ url = typeof url == 'string' ? this._$url.build(url) : (url || this.ctx.header.referer || '');
51
57
  this.jump(msg, url, 1);
52
58
  }
53
59
 
@@ -59,7 +65,7 @@ class Response extends Context
59
65
  */
60
66
  error(msg='操作失败!', url) {
61
67
  typeof msg == 'object' && ([msg, url] = ['操作失败!', msg]);
62
- url = typeof url == 'string' ? this.$url.build(url) : (url || 'javascript:history.back(-1);');
68
+ url = typeof url == 'string' ? this._$url.build(url) : (url || 'javascript:history.back(-1);');
63
69
  this.jump(msg, url, 0);
64
70
  }
65
71
 
@@ -72,7 +78,8 @@ class Response extends Context
72
78
  */
73
79
  jump(msg, url, state=1) {
74
80
  const tplData = {state, msg, url, wait: this._wait};
75
- this.ctx.body = this.$request.isAjax() ? {state, msg, data: url} : this._tpl_jump.replace(/\{\$(\w+)\}/g, (...args) => {
81
+ this.ctx.body = this._$request.isAjax() ? {state, msg, data: url} : this._tpl_jump.replace(/\{\$(\w+)\}/g, (...args) => {
82
+ // @ts-ignore
76
83
  return tplData[args[1]];
77
84
  });
78
85
  }
@@ -97,12 +104,69 @@ class Response extends Context
97
104
  const escapeHtml = require('escape-html');
98
105
  const tplData = parseError(err);
99
106
  tplData.msg = escapeHtml(tplData.msg);
107
+ // @ts-ignore
100
108
  tplData.code = '<span class="line">' + tplData.code.map(str => escapeHtml(str)).join('</span><span class="line">') + '</span>';
109
+ // @ts-ignore
101
110
  tplData.stack = tplData.stack.map(str => escapeHtml(str)).join('<br>');
102
- this.ctx.body = this.$request.isAjax() ? {state: 0, msg: tplData.msg} : this._tpl_exception.replace(/\{\$(\w+)\}/g, (...args) => {
111
+ this.ctx.body = this._$request.isAjax() ? {state: 0, msg: tplData.msg, stack: tplData.stack} : this._tpl_exception.replace(/\{\$(\w+)\}/g, (...args) => {
112
+ // @ts-ignore
103
113
  return tplData[args[1]];
104
114
  });
105
115
  }
116
+
117
+ /**
118
+ * @type {RequestInstance} Request实例
119
+ * @private
120
+ */
121
+ // @ts-ignore
122
+ __request = null;
123
+
124
+ /**
125
+ * @type {RequestInstance} Request实例
126
+ */
127
+ get _$request() {
128
+ if(this.__request === null) {
129
+ if(this.$request && this.$request.__ISCLASS__) {
130
+ this.__request = this.$request;
131
+ } else if(this.$ && this.$.request) {
132
+ this.__request = this.$.request;
133
+ } else {
134
+ this.__request = new (require('./request'))(this.ctx);
135
+ }
136
+ }
137
+ return this.__request;
138
+ }
139
+
140
+ set _$request(request) {
141
+ this.__request = request;
142
+ }
143
+
144
+ /**
145
+ * @type {UrlInstance} Url实例
146
+ * @private
147
+ */
148
+ // @ts-ignore
149
+ __url = null;
150
+
151
+ /**
152
+ * @type {UrlInstance} Url实例
153
+ */
154
+ get _$url() {
155
+ if(this.__url === null) {
156
+ if(this.$url && this.$url.__ISCLASS__) {
157
+ this.__url = this.$url;
158
+ } else if(this.$ && this.$.url) {
159
+ this.__url = this.$.url;
160
+ } else {
161
+ this.__url = new (require('./url'))(this.ctx);
162
+ }
163
+ }
164
+ return this.__url;
165
+ }
166
+
167
+ set _$url(url) {
168
+ this.__url = url;
169
+ }
106
170
  }
107
171
 
108
172
  module.exports = Response;
package/lib/router.js CHANGED
@@ -1,7 +1,6 @@
1
1
  const router = new (require('@koa/router'));
2
2
  const {app: cfg_app, routes: cfg_routes} = require('./config');
3
3
  const {toLine} = require('./utils/str');
4
- const run = require('./run');
5
4
 
6
5
  // 注册用户路由
7
6
  if (cfg_routes) {
@@ -26,7 +25,8 @@ if (cfg_routes) {
26
25
  ctx.APP = paths[2] || cfg_app.default_app;
27
26
  ctx.CONTROLLER = paths[1] || cfg_app.default_controller;
28
27
  ctx.ACTION = paths[0] || cfg_app.default_action;
29
- await run(ctx, next, item.type);
28
+ ctx.FOLDER = item.type || cfg_app.controller_folder;
29
+ await next();
30
30
  });
31
31
  }
32
32
  }
@@ -36,10 +36,11 @@ router.all(cfg_app.app_multi ? '/:APP?/:CONTROLLER?/:ACTION?' : '/:CONTROLLER?/:
36
36
  ctx.APP = ctx.params.APP || cfg_app.default_app;
37
37
  ctx.CONTROLLER = ctx.params.CONTROLLER || cfg_app.default_controller;
38
38
  ctx.ACTION = ctx.params.ACTION || cfg_app.default_action;
39
+ ctx.FOLDER = cfg_app.controller_folder;
39
40
  delete ctx.params.APP;
40
41
  delete ctx.params.CONTROLLER;
41
42
  delete ctx.params.ACTION;
42
- await run(ctx, next);
43
+ await next();
43
44
  });
44
45
 
45
46
  module.exports = router;