jj.js 0.20.0 → 1.0.0-rc.2
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.
- package/README.md +47 -26
- package/jj.js +1 -1
- package/lib/app.js +10 -2
- package/lib/cache.js +1 -1
- package/lib/config.js +34 -21
- package/lib/controller.js +16 -1
- package/lib/ctx.js +80 -57
- package/lib/db/sqlite.js +28 -2
- package/lib/db.js +64 -34
- package/lib/loader.js +67 -37
- package/lib/model.js +2 -2
- package/lib/mvc.js +34 -60
- package/lib/request.js +25 -13
- package/lib/response.js +1 -1
- package/lib/router.js +42 -19
- package/lib/tpl/types.js +5 -5
- package/lib/types.js +175 -263
- package/lib/url.js +28 -27
- package/lib/utils/escape-html.js +15 -0
- package/lib/utils/internal.js +146 -0
- package/lib/utils/is-class.js +12 -0
- package/lib/utils/libs.js +3 -0
- package/lib/view.js +12 -15
- package/logo.png +0 -0
- package/package.json +8 -10
- package/types.js +21 -5
- package/lib/utils/str.js +0 -21
- package/lib/utils/validate.js +0 -13
package/lib/db.js
CHANGED
|
@@ -10,6 +10,7 @@ const Context = require('./context');
|
|
|
10
10
|
* @typedef {import('../types').OkPacket} OkPacket
|
|
11
11
|
* @typedef {import('../types').RowData} RowData
|
|
12
12
|
* @typedef {import('../types').ListData} ListData
|
|
13
|
+
* @typedef {import('../types').PaginateResult} PaginateResult
|
|
13
14
|
* @typedef {import('../types').FieldInfo} FieldInfo
|
|
14
15
|
* @typedef {import('../types').Link} Link
|
|
15
16
|
* @typedef {import('../types').Operator} Operator
|
|
@@ -50,10 +51,6 @@ class Db extends Context
|
|
|
50
51
|
* @type {string}
|
|
51
52
|
*/
|
|
52
53
|
this._queryStr = '';
|
|
53
|
-
/**
|
|
54
|
-
* @type {Map<string, string[]>}
|
|
55
|
-
*/
|
|
56
|
-
this._tableField = new Map();
|
|
57
54
|
/**
|
|
58
55
|
* @type {SqlInstance} sql实例
|
|
59
56
|
*/
|
|
@@ -64,6 +61,8 @@ class Db extends Context
|
|
|
64
61
|
*/
|
|
65
62
|
this.sql = '';
|
|
66
63
|
|
|
64
|
+
/** @type {Promise<*>|null} */
|
|
65
|
+
this._connectPromise = null;
|
|
67
66
|
this.connect(options);
|
|
68
67
|
}
|
|
69
68
|
|
|
@@ -99,17 +98,21 @@ class Db extends Context
|
|
|
99
98
|
* @returns {this}
|
|
100
99
|
*/
|
|
101
100
|
connect(config='default') {
|
|
102
|
-
this._config = typeof config === 'string' ? cfg_db[config] : config;
|
|
101
|
+
this._config = typeof config === 'string' ? (cfg_db[config] || config) : config;
|
|
102
|
+
if(!this._config || typeof this._config !== 'object') {
|
|
103
|
+
throw new Error(`DbError: 数据库配置 "${config}" 不存在`);
|
|
104
|
+
}
|
|
103
105
|
this.reset();
|
|
104
106
|
|
|
105
107
|
if(this._config.connect) {
|
|
106
108
|
this._sql = this._config.connect(this._config);
|
|
109
|
+
this._connectPromise = Promise.resolve(this);
|
|
107
110
|
} else {
|
|
108
111
|
const sqltype = this._config.type;
|
|
109
112
|
/** @class {Sql} */
|
|
110
113
|
const Sql = require(`./db/${sqltype}.js`);
|
|
111
114
|
this._sql = new Sql(this.ctx, this._$logger);
|
|
112
|
-
this._sql.connect(this._config);
|
|
115
|
+
this._connectPromise = this._sql.connect(this._config).then(() => this);
|
|
113
116
|
}
|
|
114
117
|
|
|
115
118
|
return this;
|
|
@@ -165,6 +168,7 @@ class Db extends Context
|
|
|
165
168
|
params !== false && reset !== false && this.reset();
|
|
166
169
|
params || (params = []);
|
|
167
170
|
|
|
171
|
+
await this._connectPromise;
|
|
168
172
|
return await this._sql.query(sql, params);
|
|
169
173
|
}
|
|
170
174
|
|
|
@@ -538,7 +542,7 @@ class Db extends Context
|
|
|
538
542
|
* @param {number} [param0.page] - 页码,可通过page方法配置,默认从默认分页类获取
|
|
539
543
|
* @param {number} [param0.page_size] - 每页行数,可通过page方法配置,默认从config配置获取
|
|
540
544
|
* @param {PaginationInstance} [param0.pagination] - 分页类实例,为空时使用默认分页类
|
|
541
|
-
* @returns {Promise<
|
|
545
|
+
* @returns {Promise<PaginateResult>} - [ListData, PaginationInstance]
|
|
542
546
|
*/
|
|
543
547
|
async paginate({page, page_size, pagination} = {}) {
|
|
544
548
|
!page && (page = this._options.page.page);
|
|
@@ -698,9 +702,10 @@ class Db extends Context
|
|
|
698
702
|
const cache_time = this._options.cache_time;
|
|
699
703
|
const cache_key = md5(this._sql.format(this._queryStr, params));
|
|
700
704
|
if(this._$cache.get(cache_key)) {
|
|
705
|
+
this.reset();
|
|
701
706
|
return this._$cache.get(cache_key);
|
|
702
707
|
}
|
|
703
|
-
const result = await this.query(this._queryStr, params);
|
|
708
|
+
const result = await this.query(this._queryStr, params, reset);
|
|
704
709
|
this._$cache.set(cache_key, result, cache_time);
|
|
705
710
|
return result;
|
|
706
711
|
}
|
|
@@ -714,21 +719,33 @@ class Db extends Context
|
|
|
714
719
|
* @returns {Promise<FieldInfo[]>}
|
|
715
720
|
*/
|
|
716
721
|
async tableInfo(table) {
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
722
|
+
table && this.table(table);
|
|
723
|
+
const tableName = this._parseTable();
|
|
724
|
+
if(this._options.cache_time) {
|
|
725
|
+
const cache_time = this._options.cache_time;
|
|
726
|
+
const cache_key = 'table_info:' + tableName;
|
|
727
|
+
if(this._$cache.get(cache_key)) {
|
|
728
|
+
this.reset();
|
|
729
|
+
return this._$cache.get(cache_key);
|
|
730
|
+
}
|
|
731
|
+
const result = await this._sql.tableInfo(tableName);
|
|
732
|
+
this._$cache.set(cache_key, result, cache_time);
|
|
733
|
+
return result;
|
|
734
|
+
}
|
|
735
|
+
const table_info = await this._sql.tableInfo(tableName)
|
|
736
|
+
this.reset();
|
|
721
737
|
return table_info;
|
|
722
738
|
}
|
|
723
739
|
|
|
724
740
|
/**
|
|
725
741
|
* 获取数据表字段
|
|
726
742
|
* @public
|
|
727
|
-
* @param {string} [table] -
|
|
743
|
+
* @param {string} [table] - 表名字,会替换通过table方法传入的表名字
|
|
744
|
+
* @param {number} [cache_time=60] - 缓存时间,默认60秒
|
|
728
745
|
* @returns {Promise<string[]>}
|
|
729
746
|
*/
|
|
730
|
-
async tableField(table) {
|
|
731
|
-
const table_info = await this.tableInfo(table);
|
|
747
|
+
async tableField(table, cache_time = 60) {
|
|
748
|
+
const table_info = await this.withCache(cache_time).tableInfo(table);
|
|
732
749
|
return table_info.map(item => item.Field);
|
|
733
750
|
}
|
|
734
751
|
|
|
@@ -796,33 +813,46 @@ class Db extends Context
|
|
|
796
813
|
/** @type {any[]} */
|
|
797
814
|
const params = [];
|
|
798
815
|
this._options.where.forEach(item => {
|
|
799
|
-
/** @type {
|
|
816
|
+
/** @type {Where} */
|
|
800
817
|
const whereList = item[0];
|
|
801
818
|
const whereLink = (item[1] && item[1].toLowerCase()) === 'or' ? 'or' : 'and';
|
|
802
819
|
let whereChild = '';
|
|
803
|
-
Object.
|
|
804
|
-
const value = whereList[field];
|
|
820
|
+
Object.entries(whereList).forEach(([field, value]) => {
|
|
805
821
|
if(!~field.indexOf(')')) {
|
|
806
822
|
field = '`' + field.replace(/\./g, '`.`') + '`';
|
|
807
823
|
}
|
|
824
|
+
/** @type {Operator} */
|
|
808
825
|
let fieldLogic = '=';
|
|
809
826
|
let fieldValue = null;
|
|
810
827
|
let fieldLink = 'and';
|
|
811
828
|
if(value instanceof Array) {
|
|
812
|
-
|
|
813
|
-
fieldLogic =
|
|
829
|
+
// @ts-ignore
|
|
830
|
+
fieldLogic = value[0].toLowerCase();
|
|
831
|
+
if(!~logic.indexOf(fieldLogic)) {
|
|
832
|
+
const message = 'DbError: 无效的where操作符 ' + value[0];
|
|
833
|
+
throw new Error(message);
|
|
834
|
+
}
|
|
814
835
|
fieldValue = value[1];
|
|
815
|
-
fieldLink = (value[2] && value[2].toLowerCase()) === 'or' ? 'or' : 'and';
|
|
836
|
+
fieldLink = (value[2] && value[2].toLowerCase()) === 'or' ? 'or' : 'and'; // Fixed: value[2] to fieldLink
|
|
816
837
|
} else {
|
|
817
838
|
fieldValue = value;
|
|
818
839
|
}
|
|
819
840
|
if(fieldLogic === 'exp') {
|
|
820
841
|
whereChild += (whereChild ? ` ${fieldLink} ` : '') + `${fieldValue}`;
|
|
842
|
+
const expData = value[3] || (Array.isArray(value[2]) && value[2]) || (typeof value[2] == 'string' && !['or', 'and'].includes(value[2].toLowerCase()) ? value[2] : undefined);
|
|
843
|
+
if(Array.isArray(expData)) {
|
|
844
|
+
expData.forEach(data => {
|
|
845
|
+
params.push(data);
|
|
846
|
+
});
|
|
847
|
+
} else if(expData !== undefined) {
|
|
848
|
+
params.push(expData);
|
|
849
|
+
}
|
|
821
850
|
} else {
|
|
822
851
|
whereChild += (whereChild ? ` ${fieldLink} ` : '') + `${field} ${fieldLogic} `;
|
|
823
852
|
if(~['in', 'not in'].indexOf(fieldLogic)) {
|
|
824
|
-
|
|
825
|
-
|
|
853
|
+
const inValues = Array.isArray(fieldValue) ? fieldValue : [fieldValue];
|
|
854
|
+
whereChild += `(${inValues.map(() => '?').join(',')})`;
|
|
855
|
+
inValues.forEach(v => params.push(v));
|
|
826
856
|
} else if(~['between', 'not between'].indexOf(fieldLogic)) {
|
|
827
857
|
whereChild += `? and ?`;
|
|
828
858
|
typeof fieldValue === 'string' && (fieldValue = fieldValue.split(','));
|
|
@@ -932,11 +962,9 @@ class Db extends Context
|
|
|
932
962
|
|
|
933
963
|
let allowField = this._options.allowField;
|
|
934
964
|
if(allowField === true) {
|
|
935
|
-
const
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
}
|
|
939
|
-
allowField = this._tableField.get(table_name) || [];
|
|
965
|
+
const _options = this._options;
|
|
966
|
+
allowField = await this.tableField();
|
|
967
|
+
this._options = _options;
|
|
940
968
|
}
|
|
941
969
|
|
|
942
970
|
Object.keys(this._options.data).forEach(key => {
|
|
@@ -997,12 +1025,14 @@ class Db extends Context
|
|
|
997
1025
|
* @returns {PaginationInstance}
|
|
998
1026
|
*/
|
|
999
1027
|
get _$pagination() {
|
|
1000
|
-
if(this
|
|
1001
|
-
this
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1028
|
+
if(this.__pagination === null) {
|
|
1029
|
+
if(this.$pagination && this.$pagination.__ISCLASS__) {
|
|
1030
|
+
this.__pagination = this.$pagination;
|
|
1031
|
+
} else if(this.$ && this.$.pagination) {
|
|
1032
|
+
this.__pagination = this.$.pagination;
|
|
1033
|
+
} else {
|
|
1034
|
+
this.__pagination = new (require('./pagination'))(this.ctx);
|
|
1035
|
+
}
|
|
1006
1036
|
}
|
|
1007
1037
|
return this.__pagination;
|
|
1008
1038
|
}
|
package/lib/loader.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
const pt = require('path');
|
|
2
2
|
/** @type {function} */
|
|
3
3
|
// @ts-ignore
|
|
4
|
-
const isClass = require('is-class');
|
|
4
|
+
const isClass = require('./utils/is-class');
|
|
5
5
|
const {isFileSync, isDirSync} = require('./utils/fs');
|
|
6
6
|
|
|
7
7
|
/**
|
|
@@ -13,10 +13,42 @@ const {isFileSync, isDirSync} = require('./utils/fs');
|
|
|
13
13
|
*/
|
|
14
14
|
|
|
15
15
|
/**
|
|
16
|
+
* 节点类型缓存
|
|
16
17
|
* @type {Map<string, NodeType>}
|
|
17
18
|
*/
|
|
18
19
|
const typeStore = new Map();
|
|
19
20
|
|
|
21
|
+
/**
|
|
22
|
+
* 获取节点类型
|
|
23
|
+
* @param {string} path - 节点路径,最后不带斜杠
|
|
24
|
+
* @returns {NodeType}
|
|
25
|
+
*/
|
|
26
|
+
const getNodeType = path => {
|
|
27
|
+
const _nodePath = path + pt.sep;
|
|
28
|
+
const _nodeFile = path + '.js';
|
|
29
|
+
const _nodeJson = path + '.json';
|
|
30
|
+
let _nodeType = typeStore.get(_nodePath);
|
|
31
|
+
if(_nodeType === undefined) {
|
|
32
|
+
_nodeType = isFileSync(_nodeFile) ? 'file' : isFileSync(_nodeJson) ? 'json' : isDirSync(_nodePath) ? 'dir' : '';
|
|
33
|
+
if(_nodeType == 'file' && isClass(require(_nodeFile))) {
|
|
34
|
+
_nodeType = 'class';
|
|
35
|
+
}
|
|
36
|
+
typeStore.set(_nodePath, _nodeType);
|
|
37
|
+
}
|
|
38
|
+
return _nodeType;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
*
|
|
43
|
+
* @param {NodeType} type - 节点类型
|
|
44
|
+
* @param {string} path - 节点路径,最后不带斜杠
|
|
45
|
+
* @returns {object} - 节点
|
|
46
|
+
*/
|
|
47
|
+
const getNode = (type, path) => {
|
|
48
|
+
if(!type) return undefined;
|
|
49
|
+
return type == 'file' || type == 'class' ? require(path + '.js') : type == 'json' ? require(path + '.json') : {};
|
|
50
|
+
}
|
|
51
|
+
|
|
20
52
|
/**
|
|
21
53
|
* nodejs模块(CommonJS)自动加载器
|
|
22
54
|
* @param {string} path 根目录,需为绝对路径,最后不带斜杠
|
|
@@ -24,19 +56,15 @@ const typeStore = new Map();
|
|
|
24
56
|
* @return {Node}
|
|
25
57
|
*/
|
|
26
58
|
function loader(path, ...args) {
|
|
27
|
-
const
|
|
28
|
-
const
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
if(dirType == 'file' && isClass(node)) {
|
|
34
|
-
dirType = 'class';
|
|
35
|
-
}
|
|
59
|
+
const dirPath = pt.normalize(path).replace(/[\/\\]+$/, '');
|
|
60
|
+
const dirType = getNodeType(dirPath);
|
|
61
|
+
if(dirType == '') return undefined;
|
|
62
|
+
const node = getNode(dirType, dirPath);
|
|
63
|
+
|
|
36
64
|
/** @type {Map<object, NodeInfo>} */
|
|
37
65
|
const infoStore = new Map();
|
|
38
66
|
infoStore.set(node, {
|
|
39
|
-
path: dirPath,
|
|
67
|
+
path: dirPath + pt.sep,
|
|
40
68
|
type: dirType
|
|
41
69
|
});
|
|
42
70
|
return creatLoader(node);
|
|
@@ -54,8 +82,6 @@ function loader(path, ...args) {
|
|
|
54
82
|
return Reflect.get(node, prop, receiver);
|
|
55
83
|
}
|
|
56
84
|
|
|
57
|
-
/** @type {NodeInfo} */
|
|
58
|
-
// @ts-ignore
|
|
59
85
|
const nodeInfo = infoStore.get(node);
|
|
60
86
|
if(nodeInfo.type == 'class') {
|
|
61
87
|
if(!nodeInfo.instance) {
|
|
@@ -69,28 +95,13 @@ function loader(path, ...args) {
|
|
|
69
95
|
return false;
|
|
70
96
|
}
|
|
71
97
|
|
|
72
|
-
const
|
|
73
|
-
const
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
if(_nodeType === undefined) {
|
|
77
|
-
_nodeType = isFileSync(_nodeFile) ? 'file' : isFileSync(_nodeJson) ? 'json' : isDirSync(_nodePath) ? 'dir' : '';
|
|
78
|
-
if(_nodeType == 'file' && isClass(require(_nodeFile))) {
|
|
79
|
-
_nodeType = 'class';
|
|
80
|
-
}
|
|
81
|
-
typeStore.set(_nodePath, _nodeType);
|
|
82
|
-
}
|
|
83
|
-
let _node = {};
|
|
84
|
-
if(_nodeType == '') {
|
|
85
|
-
return undefined; // 直接返回
|
|
86
|
-
} else if(_nodeType == 'file' || _nodeType == 'class') {
|
|
87
|
-
_node = require(_nodeFile);
|
|
88
|
-
} else if(_nodeType == 'json') {
|
|
89
|
-
_node = require(_nodeJson);
|
|
90
|
-
}
|
|
98
|
+
const _childPath = nodeInfo.path + prop;
|
|
99
|
+
const _childType = getNodeType(_childPath);
|
|
100
|
+
if(_childType == '') return undefined; // 直接返回
|
|
101
|
+
const _node = getNode(_childType, _childPath);
|
|
91
102
|
infoStore.set(_node, {
|
|
92
|
-
path:
|
|
93
|
-
type:
|
|
103
|
+
path: _childPath + pt.sep,
|
|
104
|
+
type: _childType
|
|
94
105
|
});
|
|
95
106
|
// @ts-ignore
|
|
96
107
|
return node[prop] = creatLoader(_node); // 代理,并缓存
|
|
@@ -100,8 +111,6 @@ function loader(path, ...args) {
|
|
|
100
111
|
return Reflect.set(node, prop, value, receiver);
|
|
101
112
|
}
|
|
102
113
|
|
|
103
|
-
/** @type {NodeInfo} */
|
|
104
|
-
// @ts-ignore
|
|
105
114
|
const nodeInfo = infoStore.get(node);
|
|
106
115
|
if(nodeInfo.type == 'class') {
|
|
107
116
|
if(!nodeInfo.instance) {
|
|
@@ -112,9 +121,30 @@ function loader(path, ...args) {
|
|
|
112
121
|
}
|
|
113
122
|
|
|
114
123
|
return Reflect.set(node, prop, value, receiver);
|
|
115
|
-
}
|
|
124
|
+
},
|
|
125
|
+
has: (node, prop) => {
|
|
126
|
+
if(prop in node || typeof prop == 'symbol' || prop == 'inspect') {
|
|
127
|
+
return prop in node;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
const nodeInfo = infoStore.get(node);
|
|
131
|
+
if(nodeInfo.type == 'class') {
|
|
132
|
+
if(!nodeInfo.instance) {
|
|
133
|
+
nodeInfo.instance = new node(...args);
|
|
134
|
+
}
|
|
135
|
+
return prop in nodeInfo.instance;
|
|
136
|
+
} else if(nodeInfo.type == 'dir') {
|
|
137
|
+
const _childPath = nodeInfo.path + prop;
|
|
138
|
+
const _childType = getNodeType(_childPath);
|
|
139
|
+
return _childType !== '';
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
return prop in node;
|
|
143
|
+
},
|
|
116
144
|
});
|
|
117
145
|
}
|
|
118
146
|
}
|
|
119
147
|
|
|
148
|
+
loader.clearPathCache = () => typeStore.clear();
|
|
149
|
+
|
|
120
150
|
module.exports = loader;
|
package/lib/model.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
const Context = require('./context');
|
|
2
|
-
const {
|
|
2
|
+
const {toUnderScore} = require('./utils/internal');
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
5
|
* @typedef {import('../types').DbConfigItem} DbConfigItem
|
|
@@ -29,7 +29,7 @@ class Model extends Context
|
|
|
29
29
|
*/
|
|
30
30
|
// @ts-ignore
|
|
31
31
|
this.connection = null;
|
|
32
|
-
this.table =
|
|
32
|
+
this.table = toUnderScore(this.constructor.name);
|
|
33
33
|
this.pk = 'id';
|
|
34
34
|
this._db;
|
|
35
35
|
}
|
package/lib/mvc.js
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
const path = require('path');
|
|
2
2
|
const {readFile} = require('fs').promises;
|
|
3
|
-
const {
|
|
4
|
-
const {validatePath} = require('./utils/validate');
|
|
3
|
+
const {toCamelCase, pathToMiddlewareInfo} = require('./utils/internal');
|
|
5
4
|
const config = require('./config');
|
|
6
5
|
const {app: cfg_app, view: cfg_view} = config;
|
|
7
6
|
const compose = require('koa-compose');
|
|
@@ -15,66 +14,40 @@ const storage = require('./storage');
|
|
|
15
14
|
* @returns {Promise<void>}
|
|
16
15
|
*/
|
|
17
16
|
async function mvc(ctx, next) {
|
|
18
|
-
const controller_folder = ctx.
|
|
17
|
+
const controller_folder = ctx.CONTROLLER_FOLDER || cfg_app.controller_folder;
|
|
19
18
|
|
|
20
19
|
if(cfg_app.app_debug) {
|
|
21
|
-
Logger.http({Router: controller_folder,
|
|
20
|
+
Logger.http({Router: controller_folder, DEEP: ctx.DEEP, CONTROLLER: ctx.CONTROLLER, ACTION: ctx.ACTION});
|
|
22
21
|
Object.keys(ctx.params).length && Logger.http('Params:', ctx.params);
|
|
23
22
|
Object.keys(ctx.query).length && Logger.http('Get:', ctx.query);
|
|
24
23
|
Object.keys(ctx.request.body || {}).length && Logger.http('Post:', ctx.request.body);
|
|
25
24
|
}
|
|
26
|
-
|
|
27
|
-
// 设置store
|
|
28
|
-
const store = storage.getStore();
|
|
29
|
-
store.APP = ctx.APP;
|
|
30
|
-
store.CONTROLLER = ctx.CONTROLLER;
|
|
31
|
-
store.ACTION = ctx.ACTION;
|
|
32
|
-
store.COMMON = cfg_app.common_app;
|
|
33
25
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
// 应用、控制器、方法名字验证
|
|
41
|
-
if(!validatePath(ctx.APP) || !validatePath(ctx.CONTROLLER) || !validatePath(ctx.ACTION)) {
|
|
42
|
-
// throw new Error(`RunError: 应用、控制器或方法名字不合法!`);
|
|
43
|
-
return;
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
if(ctx.APP[0] == '_' || ctx.APP[0] == '$' || !store._[ctx.APP]) {
|
|
47
|
-
if(cfg_app.app_debug) {
|
|
48
|
-
throw new Error(`RunError: 应用:${ctx.APP}不存在!`);
|
|
49
|
-
} else {
|
|
50
|
-
return;
|
|
26
|
+
const {$$} = storage.getStore();
|
|
27
|
+
const controllerPaths = [...ctx.DEEP.split('/').filter(Boolean), controller_folder];
|
|
28
|
+
const $node = controllerPaths.reduce((node, deep) => {
|
|
29
|
+
if(!node[deep]) {
|
|
30
|
+
throw new Error(`RunError: 目录:/app/${controllerPaths.join('/')}不存在!`);
|
|
51
31
|
}
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
if(!store._[ctx.APP][controller_folder]) {
|
|
55
|
-
if(cfg_app.app_debug) {
|
|
56
|
-
throw new Error(`RunError: 目录:${ctx.APP}/${controller_folder}不存在!`);
|
|
57
|
-
} else {
|
|
58
|
-
return;
|
|
59
|
-
}
|
|
60
|
-
}
|
|
32
|
+
return node[deep];
|
|
33
|
+
}, $$);
|
|
61
34
|
|
|
62
35
|
// 模版文件内容直接输出
|
|
63
36
|
if(controller_folder == cfg_view.view_folder) {
|
|
64
|
-
const content = await readFile(path.join(cfg_app.base_dir,
|
|
37
|
+
const content = await readFile(path.join(cfg_app.base_dir, 'app', controllerPaths.join('/'), ctx.CONTROLLER + cfg_view.view_depr + ctx.ACTION + cfg_view.view_ext));
|
|
65
38
|
if(!content) {
|
|
66
|
-
throw new Error(`RunError: 模板文件:${
|
|
39
|
+
throw new Error(`RunError: 模板文件:${controllerPaths.join('/')}/${ctx.CONTROLLER + cfg_view.view_depr + ctx.ACTION + cfg_view.view_ext}不存在!`);
|
|
67
40
|
}
|
|
68
41
|
ctx.body = content;
|
|
69
42
|
return;
|
|
70
43
|
}
|
|
71
44
|
|
|
72
45
|
// 控制器
|
|
73
|
-
const Controller =
|
|
46
|
+
const Controller = $node[ctx.CONTROLLER] || $node['_empty'];
|
|
74
47
|
|
|
75
|
-
if(
|
|
48
|
+
if(!Controller || typeof Controller != 'function') {
|
|
76
49
|
if(cfg_app.app_debug) {
|
|
77
|
-
throw new Error(`RunError:
|
|
50
|
+
throw new Error(`RunError: 控制器文件:${controllerPaths.join('/')}/${ctx.CONTROLLER}不存在!`);
|
|
78
51
|
} else {
|
|
79
52
|
return;
|
|
80
53
|
}
|
|
@@ -82,12 +55,12 @@ async function mvc(ctx, next) {
|
|
|
82
55
|
|
|
83
56
|
// 控制器实例
|
|
84
57
|
const $controller = new Controller(ctx, next);
|
|
85
|
-
const
|
|
86
|
-
const action = typeof $controller[
|
|
58
|
+
const camelCaseAction = toCamelCase(ctx.ACTION);
|
|
59
|
+
const action = typeof $controller[camelCaseAction] == 'function' && !$controller[camelCaseAction].__ISCLASS__ ? camelCaseAction : '_empty';
|
|
87
60
|
|
|
88
|
-
if(
|
|
61
|
+
if(!$controller[action] || typeof $controller[action] != 'function') {
|
|
89
62
|
if(cfg_app.app_debug) {
|
|
90
|
-
throw new Error(`RunError:
|
|
63
|
+
throw new Error(`RunError: 控制器方法:${controllerPaths.join('/')}/${camelCaseAction}不存在!`);
|
|
91
64
|
} else {
|
|
92
65
|
return;
|
|
93
66
|
}
|
|
@@ -98,32 +71,33 @@ async function mvc(ctx, next) {
|
|
|
98
71
|
&& $controller['middleware'].length > 0
|
|
99
72
|
&& $controller['middleware'].reduce((stack, middle) => {
|
|
100
73
|
typeof middle == 'string' && (middle = {middleware: middle});
|
|
101
|
-
if(Array.isArray(middle.except) ? ~middle.except.indexOf(action) : ~
|
|
74
|
+
if(middle.except && (Array.isArray(middle.except) ? ~middle.except.indexOf(action) : ~middle.except.split(',').indexOf(action))) {
|
|
102
75
|
return stack;
|
|
103
76
|
}
|
|
104
|
-
if(middle.accept && (Array.isArray(middle.accept) ? !~middle.accept.indexOf(action) : !~
|
|
77
|
+
if(middle.accept && (Array.isArray(middle.accept) ? !~middle.accept.indexOf(action) : !~middle.accept.split(',').indexOf(action))) {
|
|
105
78
|
return stack;
|
|
106
79
|
}
|
|
107
80
|
// @ts-ignore
|
|
108
81
|
return stack.concat(async (ctx, next) => {
|
|
109
|
-
const
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
82
|
+
const {DEEPS, MIDDLEWARE, ACTION} = pathToMiddlewareInfo(middle.middleware, ctx.DEEP, ctx.CONTROLLER, action);
|
|
83
|
+
const middlewarePaths = [...DEEPS, cfg_app.middleware_folder];
|
|
84
|
+
const $node = middlewarePaths.reduce((node, deep) => {
|
|
85
|
+
if(!node[deep]) {
|
|
86
|
+
throw new Error(`RunError: 中间件目录:/app/${middlewarePaths.join('/')}不存在!`);
|
|
87
|
+
}
|
|
88
|
+
return node[deep];
|
|
89
|
+
}, $$);
|
|
90
|
+
const Middleware = $node[MIDDLEWARE];
|
|
117
91
|
if(!Middleware || typeof Middleware != 'function') {
|
|
118
|
-
throw new Error(`RunError:
|
|
92
|
+
throw new Error(`RunError: 中间件文件:/app/${middlewarePaths.join('/')}/${MIDDLEWARE}不存在!`);
|
|
119
93
|
}
|
|
120
94
|
const $middleware = new Middleware(ctx, next);
|
|
121
|
-
if(!$middleware[
|
|
122
|
-
throw new Error(`RunError: 中间件方法:${
|
|
95
|
+
if(!$middleware[ACTION] || typeof $middleware[ACTION] != 'function') {
|
|
96
|
+
throw new Error(`RunError: 中间件方法:${middlewarePaths.join('/')}/${ACTION}不存在!`);
|
|
123
97
|
}
|
|
124
98
|
|
|
125
99
|
// 执行中间件方法
|
|
126
|
-
await $middleware[
|
|
100
|
+
await $middleware[ACTION]();
|
|
127
101
|
});
|
|
128
102
|
}, [])
|
|
129
103
|
|| [];
|
package/lib/request.js
CHANGED
|
@@ -25,13 +25,14 @@ class Request extends Context
|
|
|
25
25
|
}
|
|
26
26
|
|
|
27
27
|
/**
|
|
28
|
-
*
|
|
28
|
+
* 智能获取请求数据,优先级为:post > get > param
|
|
29
29
|
* @param {string} key
|
|
30
30
|
* @param {*} [default_value='']
|
|
31
31
|
* @returns {*}
|
|
32
32
|
*/
|
|
33
33
|
query(key, default_value='') {
|
|
34
|
-
|
|
34
|
+
const value = this.post(key, undefined) || this.get(key, undefined) || this.param(key, undefined);
|
|
35
|
+
return this._getValue(value, default_value);
|
|
35
36
|
}
|
|
36
37
|
|
|
37
38
|
/**
|
|
@@ -41,7 +42,7 @@ class Request extends Context
|
|
|
41
42
|
* @returns {*}
|
|
42
43
|
*/
|
|
43
44
|
param(key, default_value='') {
|
|
44
|
-
return this._param[key]
|
|
45
|
+
return this._getValue(this._param[key], default_value);
|
|
45
46
|
}
|
|
46
47
|
|
|
47
48
|
/**
|
|
@@ -51,7 +52,7 @@ class Request extends Context
|
|
|
51
52
|
* @returns {*}
|
|
52
53
|
*/
|
|
53
54
|
get(key, default_value='') {
|
|
54
|
-
return Array.isArray(this._get[key]) ? this._get[key]
|
|
55
|
+
return this._getValue(Array.isArray(this._get[key]) ? this._get[key].at(-1) : this._get[key], default_value);
|
|
55
56
|
}
|
|
56
57
|
|
|
57
58
|
/**
|
|
@@ -64,7 +65,7 @@ class Request extends Context
|
|
|
64
65
|
if(!this.$config.app.koa_body && key !== 'is_ajax') {
|
|
65
66
|
throw new Error('请先在app配置中开启koa_body');
|
|
66
67
|
}
|
|
67
|
-
return this._post[key]
|
|
68
|
+
return this._getValue(this._post[key], default_value);
|
|
68
69
|
}
|
|
69
70
|
|
|
70
71
|
|
|
@@ -117,14 +118,6 @@ class Request extends Context
|
|
|
117
118
|
return this.ctx.request.files || {};
|
|
118
119
|
}
|
|
119
120
|
|
|
120
|
-
/**
|
|
121
|
-
* 获取请求应用名
|
|
122
|
-
* @returns {string}
|
|
123
|
-
*/
|
|
124
|
-
app() {
|
|
125
|
-
return this.ctx.APP;
|
|
126
|
-
}
|
|
127
|
-
|
|
128
121
|
/**
|
|
129
122
|
* 获取请求控制器名
|
|
130
123
|
* @returns {string}
|
|
@@ -141,6 +134,15 @@ class Request extends Context
|
|
|
141
134
|
return this.ctx.ACTION;
|
|
142
135
|
}
|
|
143
136
|
|
|
137
|
+
/**
|
|
138
|
+
* 获取客户端IP
|
|
139
|
+
* @returns {string}
|
|
140
|
+
*/
|
|
141
|
+
ip() {
|
|
142
|
+
const forwarded = this.ctx.req.headers['x-forwarded-for'];
|
|
143
|
+
return forwarded ? (Array.isArray(forwarded) ? forwarded[0] : forwarded) : this.ctx.req.socket.remoteAddress || this.ctx.ip;
|
|
144
|
+
}
|
|
145
|
+
|
|
144
146
|
/**
|
|
145
147
|
* 获取请求url
|
|
146
148
|
* @returns {string}
|
|
@@ -202,6 +204,16 @@ class Request extends Context
|
|
|
202
204
|
headerAll() {
|
|
203
205
|
return this.ctx.headers;
|
|
204
206
|
}
|
|
207
|
+
|
|
208
|
+
/**
|
|
209
|
+
* @private
|
|
210
|
+
* @param {*} value
|
|
211
|
+
* @param {*} default_value
|
|
212
|
+
* @returns {*}
|
|
213
|
+
*/
|
|
214
|
+
_getValue(value, default_value) {
|
|
215
|
+
return value === undefined ? default_value : typeof value === 'number' ? Number(value) : value;
|
|
216
|
+
}
|
|
205
217
|
}
|
|
206
218
|
|
|
207
219
|
module.exports = Request;
|
package/lib/response.js
CHANGED
|
@@ -101,7 +101,7 @@ class Response extends Context
|
|
|
101
101
|
* @param {Error} err
|
|
102
102
|
*/
|
|
103
103
|
exception(err) {
|
|
104
|
-
const escapeHtml = require('escape-html');
|
|
104
|
+
const escapeHtml = require('./utils/escape-html');
|
|
105
105
|
const tplData = parseError(err);
|
|
106
106
|
tplData.msg = escapeHtml(tplData.msg);
|
|
107
107
|
// @ts-ignore
|