jj.js 0.8.8 → 0.10.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.
- package/CHANGELOG.md +19 -0
- package/LICENSE +21 -21
- package/README.md +22 -551
- package/jj.js +14 -8
- package/jsconfig.json +9 -0
- package/jsdoc.conf.js +17 -0
- package/lib/app.js +61 -48
- package/lib/cache.js +118 -65
- package/lib/config.js +98 -94
- package/lib/context.js +22 -10
- package/lib/controller.js +34 -8
- package/lib/cookie.js +82 -40
- package/lib/ctx.js +5 -0
- package/lib/db.js +349 -10
- package/lib/loader.js +10 -4
- package/lib/logger.js +126 -47
- package/lib/middleware.js +58 -26
- package/lib/model.js +50 -1
- package/lib/pagination.js +221 -182
- package/lib/response.js +62 -8
- package/lib/router.js +3 -3
- package/lib/run.js +6 -6
- package/lib/tpl/exception.js +64 -64
- package/lib/tpl/jump.js +40 -40
- package/lib/tpl/types.js +22 -0
- package/lib/types.js +236 -0
- package/lib/upload.js +82 -5
- package/lib/url.js +19 -0
- package/lib/utils/date.js +50 -39
- package/lib/utils/error.js +8 -4
- package/lib/utils/fs.js +69 -78
- package/lib/utils/md5.js +9 -7
- package/lib/utils/str.js +20 -10
- package/lib/utils/utils.js +16 -10
- package/lib/view.js +82 -14
- package/package.json +11 -6
- package/types.js +306 -0
package/lib/tpl/exception.js
CHANGED
|
@@ -1,65 +1,65 @@
|
|
|
1
|
-
module.exports = `<!doctype html>
|
|
2
|
-
<html>
|
|
3
|
-
<head>
|
|
4
|
-
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
|
|
5
|
-
<meta name="viewport" content="width=device-width,initial-scale=1.0,minimum-scale=1.0,maximum-scale=1.0,user-scalable=no"/>
|
|
6
|
-
<title>jj.js</title>
|
|
7
|
-
<style type="text/css">
|
|
8
|
-
* { padding: 0; margin: 0; }
|
|
9
|
-
body { background: #fff; font-family: "Microsoft Yahei","Helvetica Neue",Helvetica,Arial,sans-serif; color: #333; font-size: 16px; }
|
|
10
|
-
.message { padding: 24px 48px; }
|
|
11
|
-
.message h1 { font-size: 36px; font-weight: normal; line-height: 50px; margin-bottom: 12px; }
|
|
12
|
-
.message h3 { line-height: 1.8em; font-size: 28px; font-weight: normal; }
|
|
13
|
-
.message .code {
|
|
14
|
-
line-height: 1.5em;
|
|
15
|
-
font-size: 12px;
|
|
16
|
-
white-space: pre-wrap;
|
|
17
|
-
background: #303030;
|
|
18
|
-
color: #f1f1f1;
|
|
19
|
-
padding: 10px .2em 10px 3.2em;
|
|
20
|
-
border-radius: 2px;
|
|
21
|
-
-moz-box-shadow: inset 0 0 10px #000;
|
|
22
|
-
box-shadow: inset 0 0 10px #000;
|
|
23
|
-
counter-reset: line {$begin};
|
|
24
|
-
}
|
|
25
|
-
.message .code .line {
|
|
26
|
-
display: block;
|
|
27
|
-
padding-left: 1em;
|
|
28
|
-
padding-right: 1em;
|
|
29
|
-
min-height: 1.5em;
|
|
30
|
-
position: relative;
|
|
31
|
-
}
|
|
32
|
-
.message .code .line:before {
|
|
33
|
-
counter-increment: line;
|
|
34
|
-
content: counter(line);
|
|
35
|
-
display: inline-block;
|
|
36
|
-
box-sizing: border-box;
|
|
37
|
-
border-right: 1px solid #ddd;
|
|
38
|
-
width: 3.2em;
|
|
39
|
-
padding-right: .5em;
|
|
40
|
-
color: #888;
|
|
41
|
-
text-align: right;
|
|
42
|
-
position: absolute;
|
|
43
|
-
left: -3em;
|
|
44
|
-
}
|
|
45
|
-
.message .code .line:nth-child({$nth}) {
|
|
46
|
-
background-color: #efed0b;
|
|
47
|
-
color: #303030;
|
|
48
|
-
}
|
|
49
|
-
.message .code .line:nth-child({$nth}):before {
|
|
50
|
-
background-color: #efed0b;
|
|
51
|
-
color: #333;
|
|
52
|
-
}
|
|
53
|
-
.message .stack{ line-height: 1.8em; font-size: 14px; }
|
|
54
|
-
</style>
|
|
55
|
-
</head>
|
|
56
|
-
<body>
|
|
57
|
-
<div class="message">
|
|
58
|
-
<h1 class="h0">jj.js</h1>
|
|
59
|
-
<h3>{$msg}</h3>
|
|
60
|
-
<pre class="code">{$code}</pre>
|
|
61
|
-
<h3>Call Stack</h3>
|
|
62
|
-
<p class="stack">{$stack}</p>
|
|
63
|
-
</div>
|
|
64
|
-
</body>
|
|
1
|
+
module.exports = `<!doctype html>
|
|
2
|
+
<html>
|
|
3
|
+
<head>
|
|
4
|
+
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
|
|
5
|
+
<meta name="viewport" content="width=device-width,initial-scale=1.0,minimum-scale=1.0,maximum-scale=1.0,user-scalable=no"/>
|
|
6
|
+
<title>jj.js</title>
|
|
7
|
+
<style type="text/css">
|
|
8
|
+
* { padding: 0; margin: 0; }
|
|
9
|
+
body { background: #fff; font-family: "Microsoft Yahei","Helvetica Neue",Helvetica,Arial,sans-serif; color: #333; font-size: 16px; }
|
|
10
|
+
.message { padding: 24px 48px; }
|
|
11
|
+
.message h1 { font-size: 36px; font-weight: normal; line-height: 50px; margin-bottom: 12px; }
|
|
12
|
+
.message h3 { line-height: 1.8em; font-size: 28px; font-weight: normal; }
|
|
13
|
+
.message .code {
|
|
14
|
+
line-height: 1.5em;
|
|
15
|
+
font-size: 12px;
|
|
16
|
+
white-space: pre-wrap;
|
|
17
|
+
background: #303030;
|
|
18
|
+
color: #f1f1f1;
|
|
19
|
+
padding: 10px .2em 10px 3.2em;
|
|
20
|
+
border-radius: 2px;
|
|
21
|
+
-moz-box-shadow: inset 0 0 10px #000;
|
|
22
|
+
box-shadow: inset 0 0 10px #000;
|
|
23
|
+
counter-reset: line {$begin};
|
|
24
|
+
}
|
|
25
|
+
.message .code .line {
|
|
26
|
+
display: block;
|
|
27
|
+
padding-left: 1em;
|
|
28
|
+
padding-right: 1em;
|
|
29
|
+
min-height: 1.5em;
|
|
30
|
+
position: relative;
|
|
31
|
+
}
|
|
32
|
+
.message .code .line:before {
|
|
33
|
+
counter-increment: line;
|
|
34
|
+
content: counter(line);
|
|
35
|
+
display: inline-block;
|
|
36
|
+
box-sizing: border-box;
|
|
37
|
+
border-right: 1px solid #ddd;
|
|
38
|
+
width: 3.2em;
|
|
39
|
+
padding-right: .5em;
|
|
40
|
+
color: #888;
|
|
41
|
+
text-align: right;
|
|
42
|
+
position: absolute;
|
|
43
|
+
left: -3em;
|
|
44
|
+
}
|
|
45
|
+
.message .code .line:nth-child({$nth}) {
|
|
46
|
+
background-color: #efed0b;
|
|
47
|
+
color: #303030;
|
|
48
|
+
}
|
|
49
|
+
.message .code .line:nth-child({$nth}):before {
|
|
50
|
+
background-color: #efed0b;
|
|
51
|
+
color: #333;
|
|
52
|
+
}
|
|
53
|
+
.message .stack{ line-height: 1.8em; font-size: 14px; }
|
|
54
|
+
</style>
|
|
55
|
+
</head>
|
|
56
|
+
<body>
|
|
57
|
+
<div class="message">
|
|
58
|
+
<h1 class="h0">jj.js</h1>
|
|
59
|
+
<h3>{$msg}</h3>
|
|
60
|
+
<pre class="code">{$code}</pre>
|
|
61
|
+
<h3>Call Stack</h3>
|
|
62
|
+
<p class="stack">{$stack}</p>
|
|
63
|
+
</div>
|
|
64
|
+
</body>
|
|
65
65
|
</html>`;
|
package/lib/tpl/jump.js
CHANGED
|
@@ -1,41 +1,41 @@
|
|
|
1
|
-
module.exports = `<!doctype html>
|
|
2
|
-
<html>
|
|
3
|
-
<head>
|
|
4
|
-
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
|
|
5
|
-
<meta name="viewport" content="width=device-width,initial-scale=1.0,minimum-scale=1.0,maximum-scale=1.0,user-scalable=no"/>
|
|
6
|
-
<title>跳转提示</title>
|
|
7
|
-
<style type="text/css">
|
|
8
|
-
*{ padding: 0; margin: 0; }
|
|
9
|
-
body{ background: #fff; font-family: "Microsoft Yahei","Helvetica Neue",Helvetica,Arial,sans-serif; color: #333; font-size: 16px; }
|
|
10
|
-
.message{ padding: 24px 48px; }
|
|
11
|
-
.message h1{ font-size: 42px; font-weight: normal; line-height: 120px; margin-bottom: 12px; display: none; }
|
|
12
|
-
.message .h{$state}{ display: initial; }
|
|
13
|
-
.message .text{ line-height: 1.8em; font-size: 36px; }
|
|
14
|
-
.message .jump{ padding-top: 10px; }
|
|
15
|
-
.message .jump a{ color: #333; }
|
|
16
|
-
</style>
|
|
17
|
-
</head>
|
|
18
|
-
<body>
|
|
19
|
-
<div class="message">
|
|
20
|
-
<h1 class="h1">◠‿◠</h1>
|
|
21
|
-
<h1 class="h0">>﹏<</h1>
|
|
22
|
-
<p class="text">{$msg}</p>
|
|
23
|
-
<p class="jump">
|
|
24
|
-
页面自动 <a id="href" href="{$url}">跳转</a> 等待时间:<b id="wait">{$wait}</b>
|
|
25
|
-
</p>
|
|
26
|
-
</div>
|
|
27
|
-
<script>
|
|
28
|
-
(function(){
|
|
29
|
-
var wait = document.getElementById('wait'),
|
|
30
|
-
href = document.getElementById('href').href;
|
|
31
|
-
var interval = setInterval(function(){
|
|
32
|
-
var time = --wait.innerHTML;
|
|
33
|
-
if(time <= 0) {
|
|
34
|
-
location.href = href;
|
|
35
|
-
clearInterval(interval);
|
|
36
|
-
};
|
|
37
|
-
}, 1000);
|
|
38
|
-
})();
|
|
39
|
-
</script>
|
|
40
|
-
</body>
|
|
1
|
+
module.exports = `<!doctype html>
|
|
2
|
+
<html>
|
|
3
|
+
<head>
|
|
4
|
+
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
|
|
5
|
+
<meta name="viewport" content="width=device-width,initial-scale=1.0,minimum-scale=1.0,maximum-scale=1.0,user-scalable=no"/>
|
|
6
|
+
<title>跳转提示</title>
|
|
7
|
+
<style type="text/css">
|
|
8
|
+
*{ padding: 0; margin: 0; }
|
|
9
|
+
body{ background: #fff; font-family: "Microsoft Yahei","Helvetica Neue",Helvetica,Arial,sans-serif; color: #333; font-size: 16px; }
|
|
10
|
+
.message{ padding: 24px 48px; }
|
|
11
|
+
.message h1{ font-size: 42px; font-weight: normal; line-height: 120px; margin-bottom: 12px; display: none; }
|
|
12
|
+
.message .h{$state}{ display: initial; }
|
|
13
|
+
.message .text{ line-height: 1.8em; font-size: 36px; }
|
|
14
|
+
.message .jump{ padding-top: 10px; }
|
|
15
|
+
.message .jump a{ color: #333; }
|
|
16
|
+
</style>
|
|
17
|
+
</head>
|
|
18
|
+
<body>
|
|
19
|
+
<div class="message">
|
|
20
|
+
<h1 class="h1">◠‿◠</h1>
|
|
21
|
+
<h1 class="h0">>﹏<</h1>
|
|
22
|
+
<p class="text">{$msg}</p>
|
|
23
|
+
<p class="jump">
|
|
24
|
+
页面自动 <a id="href" href="{$url}">跳转</a> 等待时间:<b id="wait">{$wait}</b>
|
|
25
|
+
</p>
|
|
26
|
+
</div>
|
|
27
|
+
<script>
|
|
28
|
+
(function(){
|
|
29
|
+
var wait = document.getElementById('wait'),
|
|
30
|
+
href = document.getElementById('href').href;
|
|
31
|
+
var interval = setInterval(function(){
|
|
32
|
+
var time = --wait.innerHTML;
|
|
33
|
+
if(time <= 0) {
|
|
34
|
+
location.href = href;
|
|
35
|
+
clearInterval(interval);
|
|
36
|
+
};
|
|
37
|
+
}, 1000);
|
|
38
|
+
})();
|
|
39
|
+
</script>
|
|
40
|
+
</body>
|
|
41
41
|
</html>`;
|
package/lib/tpl/types.js
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
module.exports = `/**
|
|
2
|
+
* @type {typeof import('jj.js/types')}
|
|
3
|
+
*/
|
|
4
|
+
const JJCtx = require('jj.js').Ctx;
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* @typedef {import('jj.js/types').Config} JJConfig
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
__TYPES__
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* @class Ctx
|
|
14
|
+
*/
|
|
15
|
+
class Ctx extends JJCtx {
|
|
16
|
+
__PROPERTY__
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* @module types
|
|
21
|
+
*/
|
|
22
|
+
module.exports = Ctx;`;
|
package/lib/types.js
ADDED
|
@@ -0,0 +1,236 @@
|
|
|
1
|
+
const path = require('path');
|
|
2
|
+
const fsPromises = require('fs').promises;
|
|
3
|
+
const {app: cfg_app} = require('./config');
|
|
4
|
+
const toHump = require('./utils/str').toHump;
|
|
5
|
+
const Logger = require('./logger');
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* watch
|
|
9
|
+
*/
|
|
10
|
+
function start() {
|
|
11
|
+
const watch = require('watch');
|
|
12
|
+
const filter = function(f, stat) {
|
|
13
|
+
const static_dir = cfg_app.static_dir ? path.join(cfg_app.base_dir, cfg_app.static_dir) : '';
|
|
14
|
+
return (!~f.indexOf('.') || /\.js(on)?$/.test(f)) && !~f.indexOf('types.js') && (static_dir && !~f.indexOf(static_dir));
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
let prevFile = {file: null,action: null,stat: null};
|
|
18
|
+
watch.watchTree(cfg_app.base_dir, {ignoreDotFiles: true, ignoreDirectoryPattern: /node_modules/, filter}, (f, curr, prev) => {
|
|
19
|
+
if(typeof f == "object" && prev === null && curr === null) {
|
|
20
|
+
// Finished walking the tree
|
|
21
|
+
Object.keys(f).forEach(file => {
|
|
22
|
+
Logger.system("wacth:", file);
|
|
23
|
+
});
|
|
24
|
+
} else if(prev === null) {
|
|
25
|
+
// f is a new file
|
|
26
|
+
if(prevFile.file != f || prevFile.action != "created") {
|
|
27
|
+
prevFile = {file: f, action: "created", stat: curr};
|
|
28
|
+
createFile(f);
|
|
29
|
+
}
|
|
30
|
+
} else if(curr.nlink === 0) {
|
|
31
|
+
// f was removed
|
|
32
|
+
if(prevFile.file != f || prevFile.action != "removed") {
|
|
33
|
+
prevFile = { file: f, action: "removed", stat: curr };
|
|
34
|
+
createFile(f);
|
|
35
|
+
}
|
|
36
|
+
} else {
|
|
37
|
+
// f was changed
|
|
38
|
+
if(prevFile.file === null) {
|
|
39
|
+
createFile(f);
|
|
40
|
+
} else {
|
|
41
|
+
// stat might return null, so catch errors
|
|
42
|
+
try {
|
|
43
|
+
if (prevFile.stat.mtime.getTime() !== curr.mtime.getTime()) {
|
|
44
|
+
createFile(f);
|
|
45
|
+
}
|
|
46
|
+
} catch(e) {
|
|
47
|
+
createFile(f);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* createTypesFile
|
|
56
|
+
*/
|
|
57
|
+
async function createFile(f) {
|
|
58
|
+
if(this._f == f) {
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
if(this._f) {
|
|
62
|
+
delete require.cache[require.resolve(this._f)];
|
|
63
|
+
}
|
|
64
|
+
this._f = f;
|
|
65
|
+
|
|
66
|
+
const f_info = f.replace(cfg_app.base_dir + path.sep, '').split(path.sep);
|
|
67
|
+
if(f_info[0] && f_info[0] != cfg_app.common_app && !~f_info[0].indexOf('.')) {
|
|
68
|
+
this._APP = f_info[0];
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
const ignore = ['node_modules', 'docker', 'package-lock.json', 'types.js', '.git', '.gitignore'];
|
|
72
|
+
ignore.push(path.basename(module.parent.parent.parent.filename));
|
|
73
|
+
cfg_app.static_dir && ignore.push(path.join(cfg_app.base_dir, cfg_app.static_dir));
|
|
74
|
+
|
|
75
|
+
const node_list = await getNodeList('.', ignore);
|
|
76
|
+
calcNodeList(node_list, this._APP);
|
|
77
|
+
|
|
78
|
+
const types_tpl = require('./tpl/types');
|
|
79
|
+
const types_str = types_tpl.replace('__TYPES__', createTypes(node_list)).replace('__PROPERTY__', createProps(node_list));
|
|
80
|
+
fsPromises.writeFile('types.js', types_str).then(_ => {
|
|
81
|
+
Logger.system('createTypes success:', path.join(cfg_app.base_dir, 'types.js'));
|
|
82
|
+
}).catch(err => {
|
|
83
|
+
Logger.system('createTypes error:', err);
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// 生成类型定义
|
|
88
|
+
function createTypes(node_list, base_type = '') {
|
|
89
|
+
let types = '';
|
|
90
|
+
let props = '';
|
|
91
|
+
Object.entries(node_list).forEach(([path, node]) => {
|
|
92
|
+
const type_name = toHump(base_type + '_' + node.node_name);
|
|
93
|
+
if(node.file_type == 'dir') {
|
|
94
|
+
types += createTypes(node.children, type_name);
|
|
95
|
+
} else {
|
|
96
|
+
if(!node.is_class) {
|
|
97
|
+
types += `/**\n * @typedef {typeof import('${path}')} ${type_name}\n */\n\n`;
|
|
98
|
+
} else {
|
|
99
|
+
types += `/**\n * @typedef {typeof import('${path}')} ${type_name}Class\n`;
|
|
100
|
+
types += ` * @typedef {typeof import('${path}').prototype} ${type_name}Instance\n`;
|
|
101
|
+
types += ` * @typedef {(${type_name}Class & ${type_name}Instance)} ${type_name}\n */\n\n`;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
props += ` * @property {${type_name}} ${node.node_name} - ${path}\n`;
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
if(base_type) {
|
|
109
|
+
types += `/**\n * @typedef {object} ${base_type}\n${props} */\n\n`;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
return types;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// 生成智能属性
|
|
116
|
+
function createProps(node_list) {
|
|
117
|
+
let props = '';
|
|
118
|
+
|
|
119
|
+
Object.entries(node_list).forEach(([path, node]) => {
|
|
120
|
+
const type_name = toHump('_' + node.node_name);
|
|
121
|
+
if(type_name != 'Config') {
|
|
122
|
+
props += `\n /** @type {${type_name}} */\n $${node.node_name}\n`;
|
|
123
|
+
}
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
let type_config = 'JJConfig';
|
|
127
|
+
if(node_list['./config']) {
|
|
128
|
+
type_config = '(JJConfig & Config)';
|
|
129
|
+
}
|
|
130
|
+
props += `\n /** @type {${type_config}} */\n $config\n`;
|
|
131
|
+
|
|
132
|
+
return props;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
// 获取节点对象
|
|
136
|
+
async function getNodeList(dir, ignore=['node_modules', 'docker', 'package-lock.json', '.git', '.gitignore']) {
|
|
137
|
+
const isClass = require('is-class');
|
|
138
|
+
const files = await fsPromises.readdir(path.join(cfg_app.base_dir, dir), {withFileTypes: true});
|
|
139
|
+
const type_list = {};
|
|
140
|
+
for(const dirent of files) {
|
|
141
|
+
const file_name = dirent.name;
|
|
142
|
+
const file_path = dir + '/' + file_name;
|
|
143
|
+
const node_name = path.parse(file_name).name;
|
|
144
|
+
const abs_path = path.join(cfg_app.base_dir, file_path);
|
|
145
|
+
if(ignore.filter(n => abs_path.includes(n)).length) {
|
|
146
|
+
continue;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
const file_type = dirent.isFile() ? 'file' : dirent.isDirectory() ? 'dir' : '';
|
|
150
|
+
const regFile = /.+\.js(on)?$/.test(file_name);
|
|
151
|
+
const regDir = !file_name.includes('.');
|
|
152
|
+
if(file_type == 'file' && !regFile) {
|
|
153
|
+
continue;
|
|
154
|
+
}
|
|
155
|
+
if(file_type == 'dir' && !regDir) {
|
|
156
|
+
continue;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
if(file_type == 'file') {
|
|
160
|
+
try {
|
|
161
|
+
// @ts-ignore
|
|
162
|
+
type_list[file_path] = {node_name, file_name, file_type, is_class: isClass(require(abs_path))};
|
|
163
|
+
} catch(e) {
|
|
164
|
+
// 包含语法错误时,require会出错
|
|
165
|
+
}
|
|
166
|
+
} else if(file_type == 'dir') {
|
|
167
|
+
type_list[file_path] = {node_name, file_name, file_type, children: await getNodeList(file_path, ignore)};;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
};
|
|
171
|
+
return type_list;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
// 复制子节点
|
|
175
|
+
function copyNodeList(node_list) {
|
|
176
|
+
const copy_node_list = {};
|
|
177
|
+
Object.entries(node_list).forEach(([path, node]) => {
|
|
178
|
+
copy_node_list[path] = {...node};
|
|
179
|
+
if(node.children) {
|
|
180
|
+
copy_node_list[path].children = copyNodeList(node.children);
|
|
181
|
+
}
|
|
182
|
+
});
|
|
183
|
+
return copy_node_list;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
// 处理common库
|
|
187
|
+
function calcNodeList(node_list, _APP) {
|
|
188
|
+
// 处理common
|
|
189
|
+
const node_common = './' + cfg_app.common_app;
|
|
190
|
+
if(cfg_app.common_app && node_list[node_common] && node_list[node_common].children) {
|
|
191
|
+
const children = copyNodeList(node_list[node_common].children);
|
|
192
|
+
Object.entries(children).forEach(([path, node]) => {
|
|
193
|
+
node_list[path] = node;
|
|
194
|
+
});
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
// 处理app
|
|
198
|
+
const node_app = './' + _APP;
|
|
199
|
+
if(_APP && node_list[node_app] && node_list[node_app].children) {
|
|
200
|
+
Object.entries(node_list[node_app].children).forEach(([path, node]) => {
|
|
201
|
+
const path_common = path.replace(node_app, node_common);
|
|
202
|
+
if(!node_list[path_common]) {
|
|
203
|
+
node_list[path] = node;
|
|
204
|
+
} else if(node_list[path_common].file_type == 'file') {
|
|
205
|
+
delete node_list[path_common];
|
|
206
|
+
node_list[path] = node;
|
|
207
|
+
} else if(node.children) {
|
|
208
|
+
Object.entries(node.children).forEach(([p, n]) => {
|
|
209
|
+
const p_commmon = p.replace(node_app, node_common);
|
|
210
|
+
node_list[path_common].children[p_commmon] && delete node_list[path_common].children[p_commmon];
|
|
211
|
+
node_list[path_common].children[p] = n;
|
|
212
|
+
});
|
|
213
|
+
}
|
|
214
|
+
});
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
// 特殊处理view和response
|
|
218
|
+
const node_view = './' + _APP + '/view';
|
|
219
|
+
const common_view = './' + cfg_app.common_app + '/view';
|
|
220
|
+
if(node_list[node_view] && !node_list[node_view].is_class) {
|
|
221
|
+
delete node_list[node_view];
|
|
222
|
+
}
|
|
223
|
+
if(node_list[common_view] && !node_list[common_view].is_class) {
|
|
224
|
+
delete node_list[common_view];
|
|
225
|
+
}
|
|
226
|
+
const node_response = './' + _APP + '/response';
|
|
227
|
+
const common_response = './' + cfg_app.common_app + '/response';
|
|
228
|
+
if(node_list[node_response] && !node_list[node_response].is_class) {
|
|
229
|
+
delete node_list[node_response];
|
|
230
|
+
}
|
|
231
|
+
if(node_list[common_response] && !node_list[common_response].is_class) {
|
|
232
|
+
delete node_list[common_response];
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
module.exports = start;
|
package/lib/upload.js
CHANGED
|
@@ -2,41 +2,85 @@ const pt = require('path');
|
|
|
2
2
|
const fs = require('fs');
|
|
3
3
|
const utils = require('./utils/utils');
|
|
4
4
|
const Context = require('./context');
|
|
5
|
-
|
|
5
|
+
const {app: cfg_app} = require('./config');
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* @typedef {import('formidable').Files} Files
|
|
9
|
+
* @typedef {import('formidable').File} File
|
|
10
|
+
* @typedef {import('../types').UploadData} UploadData
|
|
11
|
+
* @typedef {import('../types').ValidateRule} ValidateRule
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* @extends Context
|
|
16
|
+
*/
|
|
6
17
|
class Upload extends Context
|
|
7
18
|
{
|
|
19
|
+
/**
|
|
20
|
+
* 设置上传文件
|
|
21
|
+
* @public
|
|
22
|
+
* @param {(string|File)} file
|
|
23
|
+
* @returns {this}
|
|
24
|
+
*/
|
|
8
25
|
file(file) {
|
|
9
|
-
|
|
26
|
+
/**
|
|
27
|
+
* @private
|
|
28
|
+
*/
|
|
29
|
+
this._file = /** @type {File} */ (typeof file == 'string' ? this.getFile(file) : file);
|
|
10
30
|
return this;
|
|
11
31
|
}
|
|
12
32
|
|
|
33
|
+
/**
|
|
34
|
+
* 设置检查规则
|
|
35
|
+
* @public
|
|
36
|
+
* @param {ValidateRule} [rule]
|
|
37
|
+
* @returns {this}
|
|
38
|
+
*/
|
|
13
39
|
validate(rule={}) {
|
|
40
|
+
/**
|
|
41
|
+
* @private
|
|
42
|
+
*/
|
|
14
43
|
this._rule = rule;
|
|
15
44
|
return this;
|
|
16
45
|
}
|
|
17
46
|
|
|
47
|
+
/**
|
|
48
|
+
* 设置存储文件名或函数
|
|
49
|
+
* @public
|
|
50
|
+
* @param {(string|function)} [name]
|
|
51
|
+
* @returns {this}
|
|
52
|
+
*/
|
|
18
53
|
rule(name) {
|
|
54
|
+
/**
|
|
55
|
+
* @private
|
|
56
|
+
*/
|
|
19
57
|
this._name = name;
|
|
20
58
|
return this;
|
|
21
59
|
}
|
|
22
60
|
|
|
61
|
+
/**
|
|
62
|
+
* 保存文件到目录
|
|
63
|
+
* @public
|
|
64
|
+
* @param {string} [dir]
|
|
65
|
+
* @returns {Promise<(boolean|UploadData)>}
|
|
66
|
+
*/
|
|
23
67
|
async save(dir) {
|
|
24
68
|
if(!this.check()) {
|
|
25
69
|
return false;
|
|
26
70
|
}
|
|
27
71
|
|
|
28
72
|
let savename = '';
|
|
29
|
-
if(this._name
|
|
73
|
+
if(this._name) {
|
|
30
74
|
savename = (typeof this._name == 'function' ? this._name() : this._name) + '';
|
|
31
75
|
} else {
|
|
32
|
-
savename = utils.date.format('YYYY/mmdd/') + utils.md5(
|
|
76
|
+
savename = utils.date.format('YYYY/mmdd/') + utils.md5(Date.now() + Math.random().toString(36).substr(2));
|
|
33
77
|
}
|
|
34
78
|
if(!pt.extname(savename)) {
|
|
35
79
|
savename += pt.extname(this._file.originalFilename);
|
|
36
80
|
}
|
|
37
81
|
|
|
38
82
|
try {
|
|
39
|
-
const filePath = pt.join(
|
|
83
|
+
const filePath = pt.join(cfg_app.base_dir, dir, savename);
|
|
40
84
|
await utils.fs.mkdirs(pt.dirname(filePath));
|
|
41
85
|
const reader = fs.createReadStream(this._file.filepath);
|
|
42
86
|
const writer = fs.createWriteStream(filePath);
|
|
@@ -65,14 +109,30 @@ class Upload extends Context
|
|
|
65
109
|
}
|
|
66
110
|
}
|
|
67
111
|
|
|
112
|
+
/**
|
|
113
|
+
* 获取上传文件
|
|
114
|
+
* @public
|
|
115
|
+
* @param {string} [name] - 为空时,获取所有上传文件
|
|
116
|
+
* @returns {(File|File[]|Files)}
|
|
117
|
+
*/
|
|
68
118
|
getFile(name) {
|
|
69
119
|
return typeof name == 'string' ? this.ctx.request.files[name] : this.ctx.request.files;
|
|
70
120
|
}
|
|
71
121
|
|
|
122
|
+
/**
|
|
123
|
+
* 获取错误信息
|
|
124
|
+
* @public
|
|
125
|
+
* @returns {string}
|
|
126
|
+
*/
|
|
72
127
|
getError() {
|
|
73
128
|
return this._error;
|
|
74
129
|
}
|
|
75
130
|
|
|
131
|
+
/**
|
|
132
|
+
* 检查上传文件
|
|
133
|
+
* @public
|
|
134
|
+
* @returns {boolean}
|
|
135
|
+
*/
|
|
76
136
|
check() {
|
|
77
137
|
if(!this._file) {
|
|
78
138
|
this._error = '上传文件为找到!';
|
|
@@ -104,6 +164,12 @@ class Upload extends Context
|
|
|
104
164
|
return true;
|
|
105
165
|
}
|
|
106
166
|
|
|
167
|
+
/**
|
|
168
|
+
* 检查上传文件后缀
|
|
169
|
+
* @public
|
|
170
|
+
* @param {(string|array)} ext
|
|
171
|
+
* @returns {boolean}
|
|
172
|
+
*/
|
|
107
173
|
checkExt(ext) {
|
|
108
174
|
if(typeof ext == 'string') {
|
|
109
175
|
ext = ext.split(',');
|
|
@@ -114,6 +180,12 @@ class Upload extends Context
|
|
|
114
180
|
return true;
|
|
115
181
|
}
|
|
116
182
|
|
|
183
|
+
/**
|
|
184
|
+
* 检查上传文件mimetype
|
|
185
|
+
* @public
|
|
186
|
+
* @param {(string|array)} type
|
|
187
|
+
* @returns {boolean}
|
|
188
|
+
*/
|
|
117
189
|
checkType(type) {
|
|
118
190
|
if(typeof type == 'string') {
|
|
119
191
|
type = type.split(',');
|
|
@@ -124,6 +196,11 @@ class Upload extends Context
|
|
|
124
196
|
return true;
|
|
125
197
|
}
|
|
126
198
|
|
|
199
|
+
/**
|
|
200
|
+
* 检查上传图片
|
|
201
|
+
* @public
|
|
202
|
+
* @returns {boolean}
|
|
203
|
+
*/
|
|
127
204
|
checkImg() {
|
|
128
205
|
const fileExt = pt.extname(this._file.originalFilename).slice(1).toLowerCase();
|
|
129
206
|
const fileType = this._file.mimetype.toLowerCase().split('/');
|
package/lib/url.js
CHANGED
|
@@ -9,8 +9,20 @@ const querystring = require("querystring");
|
|
|
9
9
|
const Context = require('./context');
|
|
10
10
|
const {toLine} = require('./utils/str');
|
|
11
11
|
|
|
12
|
+
/**
|
|
13
|
+
* @extends Context
|
|
14
|
+
*/
|
|
12
15
|
class Url extends Context
|
|
13
16
|
{
|
|
17
|
+
/**
|
|
18
|
+
* 编译生成url
|
|
19
|
+
* @public
|
|
20
|
+
* @param {string} [url] - 网址,支持智能解析
|
|
21
|
+
* @param {object} [vars] - 网址参数
|
|
22
|
+
* @param {string} [ext] - 网址后缀
|
|
23
|
+
* @param {string} [domain] - 网址域名
|
|
24
|
+
* @returns {string}
|
|
25
|
+
*/
|
|
14
26
|
build(url='', vars, ext='', domain='') {
|
|
15
27
|
if(vars && typeof vars != 'object') {
|
|
16
28
|
[vars, ext, domain] = [{}, vars, ext];
|
|
@@ -47,6 +59,13 @@ class Url extends Context
|
|
|
47
59
|
return domain + urls.join('#');
|
|
48
60
|
}
|
|
49
61
|
|
|
62
|
+
/**
|
|
63
|
+
* 由命名路由反向生成url
|
|
64
|
+
* @public
|
|
65
|
+
* @param {*} url - 路由名字
|
|
66
|
+
* @param {*} query - 网址参数
|
|
67
|
+
* @returns {string}
|
|
68
|
+
*/
|
|
50
69
|
ruleUrl(url, query = {}) {
|
|
51
70
|
if(!routes[url]) {
|
|
52
71
|
return url;
|