koa3-cli 1.0.7 → 1.0.9
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/app/controller/user.js +28 -5
- package/app/lib/validator.js +21 -0
- package/app/router.js +4 -6
- package/app/setup.js +34 -2
- package/package.json +1 -1
- package/app/schema/user.js +0 -29
package/app/controller/user.js
CHANGED
|
@@ -1,4 +1,27 @@
|
|
|
1
1
|
const userService = require('../service/user');
|
|
2
|
+
const { Joi, validateValue } = require('../lib/validator');
|
|
3
|
+
|
|
4
|
+
const idParamSchema = Joi.object({
|
|
5
|
+
id: Joi.string().required().messages({ 'any.required': '用户 id 不能为空' })
|
|
6
|
+
});
|
|
7
|
+
|
|
8
|
+
const createUserBodySchema = Joi.object({
|
|
9
|
+
name: Joi.string().trim().min(1).max(100).required().messages({
|
|
10
|
+
'any.required': '用户名为必填',
|
|
11
|
+
'string.empty': '用户名不能为空',
|
|
12
|
+
'string.max': '用户名不能超过 100 个字符'
|
|
13
|
+
}),
|
|
14
|
+
email: Joi.string().email().allow('').optional(),
|
|
15
|
+
age: Joi.number().integer().min(0).max(150).optional()
|
|
16
|
+
}).options({ stripUnknown: true });
|
|
17
|
+
|
|
18
|
+
const updateUserBodySchema = Joi.object({
|
|
19
|
+
name: Joi.string().trim().min(1).max(100).optional(),
|
|
20
|
+
email: Joi.string().email().allow('').optional(),
|
|
21
|
+
age: Joi.number().integer().min(0).max(150).optional()
|
|
22
|
+
}).min(1).messages({
|
|
23
|
+
'object.min': '至少需要提供一个要更新的字段'
|
|
24
|
+
}).options({ stripUnknown: true });
|
|
2
25
|
|
|
3
26
|
function logMeta(ctx, extra = {}) {
|
|
4
27
|
return {
|
|
@@ -22,7 +45,7 @@ class UserController {
|
|
|
22
45
|
}
|
|
23
46
|
|
|
24
47
|
async detail(ctx) {
|
|
25
|
-
const { id } =
|
|
48
|
+
const { id } = await validateValue(idParamSchema, ctx.params);
|
|
26
49
|
|
|
27
50
|
try {
|
|
28
51
|
const user = await userService.getUserById(id);
|
|
@@ -42,7 +65,7 @@ class UserController {
|
|
|
42
65
|
}
|
|
43
66
|
|
|
44
67
|
async create(ctx) {
|
|
45
|
-
const userData =
|
|
68
|
+
const userData = await validateValue(createUserBodySchema, ctx.request.body);
|
|
46
69
|
|
|
47
70
|
try {
|
|
48
71
|
const user = await userService.createUser(userData);
|
|
@@ -56,8 +79,8 @@ class UserController {
|
|
|
56
79
|
}
|
|
57
80
|
|
|
58
81
|
async update(ctx) {
|
|
59
|
-
const { id } =
|
|
60
|
-
const userData =
|
|
82
|
+
const { id } = await validateValue(idParamSchema, ctx.params);
|
|
83
|
+
const userData = await validateValue(updateUserBodySchema, ctx.request.body);
|
|
61
84
|
|
|
62
85
|
try {
|
|
63
86
|
const user = await userService.updateUser(id, userData);
|
|
@@ -77,7 +100,7 @@ class UserController {
|
|
|
77
100
|
}
|
|
78
101
|
|
|
79
102
|
async delete(ctx) {
|
|
80
|
-
const { id } =
|
|
103
|
+
const { id } = await validateValue(idParamSchema, ctx.params);
|
|
81
104
|
|
|
82
105
|
try {
|
|
83
106
|
const result = await userService.deleteUser(id);
|
package/app/lib/validator.js
CHANGED
|
@@ -43,7 +43,28 @@ function validate(schemas = {}) {
|
|
|
43
43
|
};
|
|
44
44
|
}
|
|
45
45
|
|
|
46
|
+
/**
|
|
47
|
+
* 在控制器内校验单个对象,失败时抛出与 validate() 中间件相同形态的 422 错误
|
|
48
|
+
*/
|
|
49
|
+
async function validateValue(schema, value, validateOptions = { stripUnknown: true }) {
|
|
50
|
+
try {
|
|
51
|
+
return await schema.validateAsync(value ?? {}, validateOptions);
|
|
52
|
+
} catch (err) {
|
|
53
|
+
if (!Joi.isError(err)) {
|
|
54
|
+
throw err;
|
|
55
|
+
}
|
|
56
|
+
const validationError = new Error(err.message || 'Validation Failed');
|
|
57
|
+
validationError.status = 422;
|
|
58
|
+
validationError.details = err.details.map(d => ({
|
|
59
|
+
field: d.path.join('.'),
|
|
60
|
+
message: d.message
|
|
61
|
+
}));
|
|
62
|
+
throw validationError;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
46
66
|
module.exports = {
|
|
47
67
|
validate,
|
|
68
|
+
validateValue,
|
|
48
69
|
Joi
|
|
49
70
|
};
|
package/app/router.js
CHANGED
|
@@ -1,6 +1,4 @@
|
|
|
1
1
|
const { Router } = require('@koa/router');
|
|
2
|
-
const { validate } = require('./lib/validator');
|
|
3
|
-
const userSchema = require('./schema/user');
|
|
4
2
|
|
|
5
3
|
const router = new Router();
|
|
6
4
|
|
|
@@ -14,9 +12,9 @@ router.get('/', homeController.index);
|
|
|
14
12
|
|
|
15
13
|
// 用户相关路由
|
|
16
14
|
router.get('/api/user', userController.list);
|
|
17
|
-
router.get('/api/user/:id',
|
|
18
|
-
router.post('/api/user',
|
|
19
|
-
router.put('/api/user/:id',
|
|
20
|
-
router.delete('/api/user/:id',
|
|
15
|
+
router.get('/api/user/:id', userController.detail);
|
|
16
|
+
router.post('/api/user', userController.create);
|
|
17
|
+
router.put('/api/user/:id', userController.update);
|
|
18
|
+
router.delete('/api/user/:id', userController.delete);
|
|
21
19
|
|
|
22
20
|
module.exports = router;
|
package/app/setup.js
CHANGED
|
@@ -16,12 +16,44 @@ const router = require('./router');
|
|
|
16
16
|
|
|
17
17
|
const rootDir = path.join(__dirname, '..');
|
|
18
18
|
|
|
19
|
+
/**
|
|
20
|
+
* 生成静态资源缓存配置。
|
|
21
|
+
*
|
|
22
|
+
* SPA 的入口 index.html 会引用最新一批带 hash 的 JS/CSS 文件,如果入口文件被浏览器或 CDN
|
|
23
|
+
* 长时间缓存,用户发布后仍会拿到旧入口,进而继续加载旧资源。这里仅禁止 index.html 缓存,
|
|
24
|
+
* 其他静态资源继续使用配置里的 maxAge,保证发布更新及时生效且不影响资源缓存性能。
|
|
25
|
+
*
|
|
26
|
+
* @param {Object} staticOptions koa-static 原始配置项
|
|
27
|
+
* @returns {Object} 注入 index.html 不缓存策略后的 koa-static 配置项
|
|
28
|
+
*/
|
|
29
|
+
function createStaticOptions(staticOptions = {}) {
|
|
30
|
+
const userSetHeaders = staticOptions.setHeaders;
|
|
31
|
+
|
|
32
|
+
return {
|
|
33
|
+
...staticOptions,
|
|
34
|
+
setHeaders(res, filePath, stats) {
|
|
35
|
+
if (typeof userSetHeaders === 'function') {
|
|
36
|
+
userSetHeaders(res, filePath, stats);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// koa-send 在启用 gzip/brotli 时传入的是 index.html.gz / index.html.br,
|
|
40
|
+
// 需要去掉压缩扩展名后再判断,避免预压缩入口文件继续被长缓存。
|
|
41
|
+
const normalizedFileName = path.basename(filePath).replace(/\.(br|gz)$/i, '');
|
|
42
|
+
if (normalizedFileName === 'index.html') {
|
|
43
|
+
res.setHeader('Cache-Control', INDEX_HTML_CACHE_CONTROL);
|
|
44
|
+
res.setHeader('Pragma', 'no-cache');
|
|
45
|
+
res.setHeader('Expires', '0');
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
|
|
19
51
|
function setup(app, config, logger) {
|
|
20
52
|
// 静态资源
|
|
21
53
|
if (config.static && config.static.enable !== false) {
|
|
22
54
|
const staticPath = path.join(rootDir, config.static.dir || 'public');
|
|
23
|
-
if (fs.existsSync(staticPath)) {
|
|
24
|
-
app.use(serveStatic(staticPath, config.static.options || {}));
|
|
55
|
+
if (fs.existsSync(staticPath)) {
|
|
56
|
+
app.use(serveStatic(staticPath, createStaticOptions(config.static.options || {})));
|
|
25
57
|
}
|
|
26
58
|
}
|
|
27
59
|
|
package/package.json
CHANGED
package/app/schema/user.js
DELETED
|
@@ -1,29 +0,0 @@
|
|
|
1
|
-
const { Joi } = require('../lib/validator');
|
|
2
|
-
|
|
3
|
-
const idParam = Joi.object({
|
|
4
|
-
id: Joi.string().required().messages({ 'any.required': '用户 id 不能为空' })
|
|
5
|
-
});
|
|
6
|
-
|
|
7
|
-
const createUserBody = Joi.object({
|
|
8
|
-
name: Joi.string().trim().min(1).max(100).required().messages({
|
|
9
|
-
'any.required': '用户名为必填',
|
|
10
|
-
'string.empty': '用户名不能为空',
|
|
11
|
-
'string.max': '用户名不能超过 100 个字符'
|
|
12
|
-
}),
|
|
13
|
-
email: Joi.string().email().allow('').optional(),
|
|
14
|
-
age: Joi.number().integer().min(0).max(150).optional()
|
|
15
|
-
}).options({ stripUnknown: true });
|
|
16
|
-
|
|
17
|
-
const updateUserBody = Joi.object({
|
|
18
|
-
name: Joi.string().trim().min(1).max(100).optional(),
|
|
19
|
-
email: Joi.string().email().allow('').optional(),
|
|
20
|
-
age: Joi.number().integer().min(0).max(150).optional()
|
|
21
|
-
}).min(1).messages({
|
|
22
|
-
'object.min': '至少需要提供一个要更新的字段'
|
|
23
|
-
}).options({ stripUnknown: true });
|
|
24
|
-
|
|
25
|
-
module.exports = {
|
|
26
|
-
idParam,
|
|
27
|
-
createUserBody,
|
|
28
|
-
updateUserBody
|
|
29
|
-
};
|