koa3-cli 1.0.3 → 1.0.5
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 +3 -15
- package/app/controller/home.js +1 -1
- package/app/controller/user.js +99 -88
- package/app/lib/logger.js +141 -0
- package/app/middleware/requestLogger.js +27 -0
- package/app.js +118 -111
- package/config/config.default.js +82 -81
- package/env.example +4 -0
- package/package.json +7 -7
package/README.md
CHANGED
|
@@ -38,7 +38,7 @@ npm run dev
|
|
|
38
38
|
## 项目结构
|
|
39
39
|
|
|
40
40
|
```
|
|
41
|
-
|
|
41
|
+
koa3-cli/
|
|
42
42
|
├── app/ # 应用代码目录
|
|
43
43
|
│ ├── controller/ # 控制器目录
|
|
44
44
|
│ │ ├── home.js # 首页控制器
|
|
@@ -99,18 +99,6 @@ npm start
|
|
|
99
99
|
- API 示例: http://localhost:3000/api/user
|
|
100
100
|
- 文档: http://localhost:3000/index.html
|
|
101
101
|
|
|
102
|
-
### 文档开发
|
|
103
|
-
|
|
104
|
-
启动 VuePress 文档开发服务器:
|
|
105
|
-
```bash
|
|
106
|
-
npm run docs:dev
|
|
107
|
-
```
|
|
108
|
-
|
|
109
|
-
构建文档为静态文件:
|
|
110
|
-
```bash
|
|
111
|
-
npm run docs:build
|
|
112
|
-
```
|
|
113
|
-
|
|
114
102
|
## 环境配置
|
|
115
103
|
|
|
116
104
|
项目支持多环境配置,通过 `NODE_ENV` 环境变量控制:
|
|
@@ -196,8 +184,8 @@ router.get('/api/product', productController.list);
|
|
|
196
184
|
|
|
197
185
|
## 技术栈
|
|
198
186
|
|
|
199
|
-
- **
|
|
200
|
-
-
|
|
187
|
+
- **Koa3**: Web 框架
|
|
188
|
+
- **@koa/router**: 路由
|
|
201
189
|
- **koa-bodyparser**: 请求体解析
|
|
202
190
|
- **koa-static**: 静态资源服务
|
|
203
191
|
- **koa-views**: 模板引擎支持
|
package/app/controller/home.js
CHANGED
package/app/controller/user.js
CHANGED
|
@@ -1,89 +1,100 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
}
|
|
88
|
-
|
|
1
|
+
const userService = require('../service/user');
|
|
2
|
+
|
|
3
|
+
function logMeta(ctx, extra = {}) {
|
|
4
|
+
return {
|
|
5
|
+
requestId: ctx.state && ctx.state.requestId,
|
|
6
|
+
method: ctx.method,
|
|
7
|
+
url: ctx.originalUrl || ctx.url,
|
|
8
|
+
...extra
|
|
9
|
+
};
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
class UserController {
|
|
13
|
+
async list(ctx) {
|
|
14
|
+
try {
|
|
15
|
+
const users = await userService.getUserList();
|
|
16
|
+
ctx.logger.info('User list fetched', logMeta(ctx, { count: Array.isArray(users) ? users.length : undefined }));
|
|
17
|
+
ctx.body = users;
|
|
18
|
+
} catch (error) {
|
|
19
|
+
ctx.logger.error('Failed to fetch user list', logMeta(ctx, { message: error.message, stack: error.stack }));
|
|
20
|
+
ctx.throw(500, error.message);
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
async detail(ctx) {
|
|
25
|
+
const { id } = ctx.params;
|
|
26
|
+
|
|
27
|
+
try {
|
|
28
|
+
const user = await userService.getUserById(id);
|
|
29
|
+
if (!user) {
|
|
30
|
+
ctx.logger.warn('User detail not found', logMeta(ctx, { userId: id }));
|
|
31
|
+
ctx.status = 404;
|
|
32
|
+
ctx.body = { message: 'User not found' };
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
ctx.logger.info('User detail fetched', logMeta(ctx, { userId: id }));
|
|
37
|
+
ctx.body = user;
|
|
38
|
+
} catch (error) {
|
|
39
|
+
ctx.logger.error('Failed to fetch user detail', logMeta(ctx, { userId: id, message: error.message, stack: error.stack }));
|
|
40
|
+
ctx.throw(500, error.message);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
async create(ctx) {
|
|
45
|
+
const userData = ctx.request.body;
|
|
46
|
+
|
|
47
|
+
try {
|
|
48
|
+
const user = await userService.createUser(userData);
|
|
49
|
+
ctx.logger.info('User created', logMeta(ctx, { userId: user && user.id }));
|
|
50
|
+
ctx.status = 201;
|
|
51
|
+
ctx.body = user;
|
|
52
|
+
} catch (error) {
|
|
53
|
+
ctx.logger.error('Failed to create user', logMeta(ctx, { message: error.message, stack: error.stack }));
|
|
54
|
+
ctx.throw(500, error.message);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
async update(ctx) {
|
|
59
|
+
const { id } = ctx.params;
|
|
60
|
+
const userData = ctx.request.body;
|
|
61
|
+
|
|
62
|
+
try {
|
|
63
|
+
const user = await userService.updateUser(id, userData);
|
|
64
|
+
if (!user) {
|
|
65
|
+
ctx.logger.warn('User update target not found', logMeta(ctx, { userId: id }));
|
|
66
|
+
ctx.status = 404;
|
|
67
|
+
ctx.body = { message: 'User not found' };
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
ctx.logger.info('User updated', logMeta(ctx, { userId: id }));
|
|
72
|
+
ctx.body = user;
|
|
73
|
+
} catch (error) {
|
|
74
|
+
ctx.logger.error('Failed to update user', logMeta(ctx, { userId: id, message: error.message, stack: error.stack }));
|
|
75
|
+
ctx.throw(500, error.message);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
async delete(ctx) {
|
|
80
|
+
const { id } = ctx.params;
|
|
81
|
+
|
|
82
|
+
try {
|
|
83
|
+
const result = await userService.deleteUser(id);
|
|
84
|
+
if (!result) {
|
|
85
|
+
ctx.logger.warn('User delete target not found', logMeta(ctx, { userId: id }));
|
|
86
|
+
ctx.status = 404;
|
|
87
|
+
ctx.body = { message: 'User not found' };
|
|
88
|
+
return;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
ctx.logger.info('User deleted', logMeta(ctx, { userId: id }));
|
|
92
|
+
ctx.status = 204;
|
|
93
|
+
} catch (error) {
|
|
94
|
+
ctx.logger.error('Failed to delete user', logMeta(ctx, { userId: id, message: error.message, stack: error.stack }));
|
|
95
|
+
ctx.throw(500, error.message);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
89
100
|
module.exports = new UserController();
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
const fs = require('fs');
|
|
2
|
+
const path = require('path');
|
|
3
|
+
const util = require('util');
|
|
4
|
+
|
|
5
|
+
const LEVEL_WEIGHT = {
|
|
6
|
+
debug: 10,
|
|
7
|
+
info: 20,
|
|
8
|
+
warn: 30,
|
|
9
|
+
error: 40
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
function normalizeLevel(level) {
|
|
13
|
+
const resolved = String(level || 'info').toLowerCase();
|
|
14
|
+
return LEVEL_WEIGHT[resolved] ? resolved : 'info';
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function formatDate(date = new Date()) {
|
|
18
|
+
const pad = (n) => String(n).padStart(2, '0');
|
|
19
|
+
return `${date.getFullYear()}-${pad(date.getMonth() + 1)}-${pad(date.getDate())}`;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function formatTimestamp(date = new Date()) {
|
|
23
|
+
return date.toLocaleString();
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function safeSerialize(meta) {
|
|
27
|
+
if (meta === undefined) {
|
|
28
|
+
return '';
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
if (meta instanceof Error) {
|
|
32
|
+
return JSON.stringify({
|
|
33
|
+
name: meta.name,
|
|
34
|
+
message: meta.message,
|
|
35
|
+
stack: meta.stack
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
if (typeof meta === 'string') {
|
|
40
|
+
return meta;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
try {
|
|
44
|
+
return JSON.stringify(meta);
|
|
45
|
+
} catch (error) {
|
|
46
|
+
return util.inspect(meta, { depth: 4, breakLength: 120 });
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
class Logger {
|
|
51
|
+
constructor(options = {}) {
|
|
52
|
+
this.level = normalizeLevel(options.level);
|
|
53
|
+
this.enableConsole = options.enableConsole !== false;
|
|
54
|
+
this.enableFile = options.enableFile !== false;
|
|
55
|
+
this.dir = options.dir || 'logs';
|
|
56
|
+
this.appName = options.appName || 'koa3-cli';
|
|
57
|
+
this.cwd = options.cwd || process.cwd();
|
|
58
|
+
this.logDir = path.isAbsolute(this.dir) ? this.dir : path.join(this.cwd, this.dir);
|
|
59
|
+
|
|
60
|
+
if (this.enableFile) {
|
|
61
|
+
fs.mkdirSync(this.logDir, { recursive: true });
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
shouldLog(level) {
|
|
66
|
+
return LEVEL_WEIGHT[level] >= LEVEL_WEIGHT[this.level];
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
write(level, message, meta) {
|
|
70
|
+
if (!this.shouldLog(level)) {
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
const timestamp = formatTimestamp();
|
|
75
|
+
const metaText = safeSerialize(meta);
|
|
76
|
+
const line = `[${timestamp}] [${this.appName}] [${level.toUpperCase()}] ${message}${metaText ? ` ${metaText}` : ''}`;
|
|
77
|
+
|
|
78
|
+
if (this.enableConsole) {
|
|
79
|
+
const printer = level === 'error' ? console.error : level === 'warn' ? console.warn : console.log;
|
|
80
|
+
printer(line);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
if (!this.enableFile) {
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
const date = formatDate();
|
|
88
|
+
const commonPath = path.join(this.logDir, `${date}.log`);
|
|
89
|
+
fs.appendFile(commonPath, `${line}\n`, () => {});
|
|
90
|
+
|
|
91
|
+
if (level === 'error') {
|
|
92
|
+
const errorPath = path.join(this.logDir, `${date}.error.log`);
|
|
93
|
+
fs.appendFile(errorPath, `${line}\n`, () => {});
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
access(data) {
|
|
98
|
+
const line = {
|
|
99
|
+
timestamp: formatTimestamp(),
|
|
100
|
+
type: 'access',
|
|
101
|
+
app: this.appName,
|
|
102
|
+
...data
|
|
103
|
+
};
|
|
104
|
+
|
|
105
|
+
if (this.enableConsole && this.shouldLog('info')) {
|
|
106
|
+
console.log(`[${line.timestamp}] [${this.appName}] [ACCESS] ${line.method} ${line.url} ${line.status} ${line.duration}ms ${line.requestId}`);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
if (!this.enableFile) {
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
const date = formatDate();
|
|
114
|
+
const accessPath = path.join(this.logDir, `${date}.access.log`);
|
|
115
|
+
fs.appendFile(accessPath, `${JSON.stringify(line)}\n`, () => {});
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
debug(message, meta) {
|
|
119
|
+
this.write('debug', message, meta);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
info(message, meta) {
|
|
123
|
+
this.write('info', message, meta);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
warn(message, meta) {
|
|
127
|
+
this.write('warn', message, meta);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
error(message, meta) {
|
|
131
|
+
this.write('error', message, meta);
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
function createLogger(options) {
|
|
136
|
+
return new Logger(options);
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
module.exports = {
|
|
140
|
+
createLogger
|
|
141
|
+
};
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
function createRequestId() {
|
|
2
|
+
return `${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 8)}`;
|
|
3
|
+
}
|
|
4
|
+
|
|
5
|
+
module.exports = function requestLogger(logger) {
|
|
6
|
+
return async function requestLoggerMiddleware(ctx, next) {
|
|
7
|
+
const start = Date.now();
|
|
8
|
+
const requestId = ctx.get('x-request-id') || createRequestId();
|
|
9
|
+
|
|
10
|
+
ctx.state.requestId = requestId;
|
|
11
|
+
ctx.set('x-request-id', requestId);
|
|
12
|
+
|
|
13
|
+
try {
|
|
14
|
+
await next();
|
|
15
|
+
} finally {
|
|
16
|
+
const duration = Date.now() - start;
|
|
17
|
+
logger.access({
|
|
18
|
+
requestId,
|
|
19
|
+
method: ctx.method,
|
|
20
|
+
url: ctx.originalUrl || ctx.url,
|
|
21
|
+
status: ctx.status,
|
|
22
|
+
duration,
|
|
23
|
+
ip: ctx.ip
|
|
24
|
+
});
|
|
25
|
+
}
|
|
26
|
+
};
|
|
27
|
+
};
|
package/app.js
CHANGED
|
@@ -1,112 +1,119 @@
|
|
|
1
|
-
const Koa = require('koa');
|
|
2
|
-
const
|
|
3
|
-
const
|
|
4
|
-
const
|
|
5
|
-
const
|
|
6
|
-
const
|
|
7
|
-
const
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
require('dotenv').config();
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
const
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
app.keys = config.keys || ['
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
if (config.static && config.static.enable !== false) {
|
|
40
|
-
const staticPath = path.join(__dirname, config.static.dir || 'public');
|
|
41
|
-
if (fs.existsSync(staticPath)) {
|
|
42
|
-
app.use(
|
|
43
|
-
}
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
app.use(
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
});
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
1
|
+
const Koa = require('koa');
|
|
2
|
+
const bodyParser = require('koa-bodyparser');
|
|
3
|
+
const serveStatic = require('koa-static');
|
|
4
|
+
const views = require('@ladjs/koa-views');
|
|
5
|
+
const path = require('path');
|
|
6
|
+
const fs = require('fs');
|
|
7
|
+
const { createLogger } = require('./app/lib/logger');
|
|
8
|
+
const createRequestLogger = require('./app/middleware/requestLogger');
|
|
9
|
+
|
|
10
|
+
require('dotenv').config();
|
|
11
|
+
|
|
12
|
+
const env = process.env.NODE_ENV || 'development';
|
|
13
|
+
const defaultConfig = require('./config/config.default');
|
|
14
|
+
let envConfig = {};
|
|
15
|
+
try {
|
|
16
|
+
if (env === 'production') {
|
|
17
|
+
envConfig = require('./config/config.prod');
|
|
18
|
+
} else if (env === 'local' || env === 'development') {
|
|
19
|
+
envConfig = require('./config/config.local');
|
|
20
|
+
}
|
|
21
|
+
} catch (e) {
|
|
22
|
+
// Ignore missing env config override.
|
|
23
|
+
}
|
|
24
|
+
const config = Object.assign({}, defaultConfig, envConfig);
|
|
25
|
+
|
|
26
|
+
const middleware = require('./app/middleware');
|
|
27
|
+
const router = require('./app/router');
|
|
28
|
+
|
|
29
|
+
const app = new Koa();
|
|
30
|
+
const logger = createLogger({
|
|
31
|
+
...(config.logger || {}),
|
|
32
|
+
appName: config.name || 'koa3-cli',
|
|
33
|
+
cwd: __dirname
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
app.keys = config.keys || ['koa3-cli-secret-key'];
|
|
37
|
+
app.context.logger = logger;
|
|
38
|
+
|
|
39
|
+
if (config.static && config.static.enable !== false) {
|
|
40
|
+
const staticPath = path.join(__dirname, config.static.dir || 'public');
|
|
41
|
+
if (fs.existsSync(staticPath)) {
|
|
42
|
+
app.use(serveStatic(staticPath, config.static.options || {}));
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
if (config.view && config.view.enable !== false) {
|
|
47
|
+
const viewPath = path.join(__dirname, config.view.root || 'app/view');
|
|
48
|
+
if (fs.existsSync(viewPath)) {
|
|
49
|
+
app.use(views(viewPath, config.view.options || {
|
|
50
|
+
extension: 'ejs'
|
|
51
|
+
}));
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
app.use(bodyParser(config.bodyParser || {}));
|
|
56
|
+
app.use(createRequestLogger(logger));
|
|
57
|
+
|
|
58
|
+
if (middleware && typeof middleware === 'function') {
|
|
59
|
+
app.use(middleware);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
app.use(async (ctx, next) => {
|
|
63
|
+
try {
|
|
64
|
+
await next();
|
|
65
|
+
} catch (err) {
|
|
66
|
+
ctx.status = err.status || 500;
|
|
67
|
+
ctx.body = {
|
|
68
|
+
success: false,
|
|
69
|
+
message: err.message || 'Internal Server Error',
|
|
70
|
+
...(config.env === 'development' && { stack: err.stack })
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
logger.error('Request failed', {
|
|
74
|
+
requestId: ctx.state && ctx.state.requestId,
|
|
75
|
+
method: ctx.method,
|
|
76
|
+
url: ctx.originalUrl || ctx.url,
|
|
77
|
+
status: ctx.status,
|
|
78
|
+
message: err.message,
|
|
79
|
+
stack: err.stack
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
ctx.app.emit('error', err, ctx);
|
|
83
|
+
}
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
app.use(router.routes()).use(router.allowedMethods());
|
|
87
|
+
|
|
88
|
+
app.use(async (ctx) => {
|
|
89
|
+
if (ctx.status === 404) {
|
|
90
|
+
ctx.body = {
|
|
91
|
+
success: false,
|
|
92
|
+
message: 'Not Found'
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
app.on('error', (err, ctx) => {
|
|
98
|
+
logger.error('Server error event', {
|
|
99
|
+
requestId: ctx && ctx.state && ctx.state.requestId,
|
|
100
|
+
message: err.message,
|
|
101
|
+
stack: err.stack
|
|
102
|
+
});
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
process.on('unhandledRejection', (reason) => {
|
|
106
|
+
logger.error('Unhandled promise rejection', reason);
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
process.on('uncaughtException', (error) => {
|
|
110
|
+
logger.error('Uncaught exception', error);
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
const port = config.port || 3000;
|
|
114
|
+
app.listen(port, () => {
|
|
115
|
+
logger.info(`Server is running on http://localhost:${port}`);
|
|
116
|
+
logger.info(`Environment: ${config.env}`);
|
|
117
|
+
});
|
|
118
|
+
|
|
112
119
|
module.exports = app;
|
package/config/config.default.js
CHANGED
|
@@ -1,82 +1,83 @@
|
|
|
1
|
-
/**
|
|
2
|
-
*
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
1
|
+
/**
|
|
2
|
+
* Default config loaded in all environments.
|
|
3
|
+
*/
|
|
4
|
+
module.exports = {
|
|
5
|
+
// Application name
|
|
6
|
+
name: 'koa3-cli',
|
|
7
|
+
|
|
8
|
+
// Runtime env: development, production, test
|
|
9
|
+
env: process.env.NODE_ENV || 'development',
|
|
10
|
+
|
|
11
|
+
// Server port
|
|
12
|
+
port: process.env.PORT || 3000,
|
|
13
|
+
|
|
14
|
+
// Cookie signing keys
|
|
15
|
+
keys: process.env.KEYS ? process.env.KEYS.split(',') : ['koa3-cli-secret-key'],
|
|
16
|
+
|
|
17
|
+
// Static assets
|
|
18
|
+
static: {
|
|
19
|
+
enable: true,
|
|
20
|
+
dir: 'public',
|
|
21
|
+
options: {
|
|
22
|
+
maxAge: 365 * 24 * 60 * 60 * 1000,
|
|
23
|
+
gzip: true
|
|
24
|
+
}
|
|
25
|
+
},
|
|
26
|
+
|
|
27
|
+
// Docs build config
|
|
28
|
+
docs: {
|
|
29
|
+
enable: true,
|
|
30
|
+
buildDir: 'public/docs'
|
|
31
|
+
},
|
|
32
|
+
|
|
33
|
+
// View engine
|
|
34
|
+
view: {
|
|
35
|
+
enable: true,
|
|
36
|
+
root: 'app/view',
|
|
37
|
+
options: {
|
|
38
|
+
extension: 'ejs',
|
|
39
|
+
map: {
|
|
40
|
+
html: 'ejs'
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
},
|
|
44
|
+
|
|
45
|
+
// bodyParser
|
|
46
|
+
bodyParser: {
|
|
47
|
+
enableTypes: ['json', 'form', 'text'],
|
|
48
|
+
jsonLimit: '10mb',
|
|
49
|
+
formLimit: '10mb'
|
|
50
|
+
},
|
|
51
|
+
|
|
52
|
+
// Database (example)
|
|
53
|
+
database: {
|
|
54
|
+
client: 'mysql',
|
|
55
|
+
connection: {
|
|
56
|
+
host: process.env.DB_HOST || 'localhost',
|
|
57
|
+
port: process.env.DB_PORT || 3306,
|
|
58
|
+
user: process.env.DB_USER || 'root',
|
|
59
|
+
password: process.env.DB_PASSWORD || '',
|
|
60
|
+
database: process.env.DB_NAME || 'test'
|
|
61
|
+
},
|
|
62
|
+
pool: {
|
|
63
|
+
min: 2,
|
|
64
|
+
max: 10
|
|
65
|
+
}
|
|
66
|
+
},
|
|
67
|
+
|
|
68
|
+
// Redis (example)
|
|
69
|
+
redis: {
|
|
70
|
+
host: process.env.REDIS_HOST || 'localhost',
|
|
71
|
+
port: process.env.REDIS_PORT || 6379,
|
|
72
|
+
password: process.env.REDIS_PASSWORD || '',
|
|
73
|
+
db: process.env.REDIS_DB || 0
|
|
74
|
+
},
|
|
75
|
+
|
|
76
|
+
// Logger
|
|
77
|
+
logger: {
|
|
78
|
+
level: process.env.LOG_LEVEL || 'info',
|
|
79
|
+
dir: process.env.LOG_DIR || 'logs',
|
|
80
|
+
enableConsole: process.env.LOG_ENABLE_CONSOLE !== 'false',
|
|
81
|
+
enableFile: process.env.LOG_ENABLE_FILE !== 'false'
|
|
82
|
+
}
|
|
82
83
|
};
|
package/env.example
CHANGED
package/package.json
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "koa3-cli",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.5",
|
|
4
4
|
"description": "Koa3脚手架",
|
|
5
5
|
"main": "app.js",
|
|
6
6
|
"bin": {
|
|
7
|
-
"koa3-cli": "
|
|
7
|
+
"koa3-cli": "bin/cli.js"
|
|
8
8
|
},
|
|
9
9
|
"preferGlobal": true,
|
|
10
10
|
"scripts": {
|
|
@@ -25,17 +25,17 @@
|
|
|
25
25
|
"homepage": "https://atwzc.cn/",
|
|
26
26
|
"license": "MIT",
|
|
27
27
|
"dependencies": {
|
|
28
|
-
"@koa/router": "^15.
|
|
28
|
+
"@koa/router": "^15.3.1",
|
|
29
29
|
"@ladjs/koa-views": "^9.0.0",
|
|
30
|
-
"dotenv": "^17.
|
|
31
|
-
"ejs": "^
|
|
32
|
-
"koa": "^3.1.
|
|
30
|
+
"dotenv": "^17.3.1",
|
|
31
|
+
"ejs": "^4.0.1",
|
|
32
|
+
"koa": "^3.1.2",
|
|
33
33
|
"koa-bodyparser": "^4.4.1",
|
|
34
34
|
"koa-cors": "^0.0.16",
|
|
35
35
|
"koa-static": "^5.0.0"
|
|
36
36
|
},
|
|
37
37
|
"devDependencies": {
|
|
38
|
-
"nodemon": "^3.1.
|
|
38
|
+
"nodemon": "^3.1.14"
|
|
39
39
|
},
|
|
40
40
|
"volta": {
|
|
41
41
|
"node": "20.18.1"
|