express-joi、joi
joi是nodej的一个工具模块,主要用于JavaScript对象的校验。它是一种简单易用的javacript对象约束描述语言,可以轻松解决nodejs开发中的各种参数的校验。
1. string() 值必须是字符串
2. alphanum() 值只能包含a-zA-Z的字符串
3. min(n) 最小长度
4. max(n) 最大长度
5. required 值必填项, 不能为 undefined
6. pattern(正则表达式) 值必须符号正则表达式
express-joi 是一个基于 Joi 的中间件,用于在 Express 应用中进行参数验证。它可以帮助我们验证请求中的 body、query 和 params 数据,确保数据的有效性和安全性。
# 安装
npm install @escook/express-joi
npm install joi
# 导入
const express = require('express');
const app = express();
const Joi = require('joi');
const expressJoi = require('@escook/express-joi');
# 定义验证规则
const userSchema = {
body: {
username: Joi.string().alphanum().min(3).max(12).required(),
password: Joi.string().pattern(/^[\S]{6,15}$/).required(),
repassword: Joi.ref('password')
},
query: {
name: Joi.string().alphanum().min(3).required(),
age: Joi.number().integer().min(1).max(100).required()
},
params: {
id: Joi.number().integer().min(0).required()
}
}
# 使用中间件进行验证
// 数据验证通过之后,会把这次请求流转给后面的路由处理函数
// 数据验证失败 , 终止后续代码执行,并抛出一个 全局Error 错误,进入全局错误中间件中
app.post('/adduser/:id', expressJoi(userSchema), function(req, res) {
const body = req.body;
res.send(body);
})
# 错误处理
app.use(function(err, req, res, next) {
if (err instanceof Joi.ValidationError) {
return res.send({ status: 1, message: err.message });
}
res.send({ status: 1, message: err.message });
})
# 启动服务
app.listen(3001, function() {
console.log('Express server running at http://127.0.0.1:3001');
})
案例
》》》创建验证规则文件 (schema/user.js)
// schema/user.js
const joi = require('joi')
// 用户注册和登录表单的验证规则
const username = joi.string().alphanum().min(3).max(12).required()
const password = joi
.string()
.pattern(/^[\S]{6,15}$/)
.required()
const email = joi.string().email().required()
// 注册和登录表单的验证规则对象
exports.reg_login_schema = {
// 表示对 req.body 中的数据进行验证
body: {
username,
password,
email
}
}
// 更新用户信息验证规则
exports.update_userinfo_schema = {
body: {
id: joi.number().integer().min(1).required(),
nickname: joi.string().required(),
email: joi.string().email().required()
}
}
// 更新密码验证规则
exports.update_password_schema = {
body: {
oldPwd: password,
newPwd: joi.not(joi.ref('oldPwd')).concat(password)
}
}
// 更新头像验证规则
exports.update_avatar_schema = {
body: {
avatar: joi.string().dataUri().required()
}
}
》》创建路由文件 (routes/user.js)
// routes/user.js
const express = require('express')
const router = express.Router()
const expressJoi = require('@escook/express-joi')
// 路由处理模块
const userHandler = require('../handler/user')
const {
reg_login_schema,
update_userinfo_schema,
update_password_schema,
update_avatar_schema
} = require('../schema/user')
// 注册新用户
router.post('/register', expressJoi(reg_login_schema), userHandler.register)
// 登录
router.post('/login', expressJoi(reg_login_schema), userHandler.login)
// 更新用户基本信息
router.post('/userinfo', expressJoi(update_userinfo_schema), userHandler.updateUserInfo)
// 重置密码
router.post('/updatepwd', expressJoi(update_password_schema), userHandler.updatePassword)
// 更新头像
router.post('/update/avatar', expressJoi(update_avatar_schema), userHandler.updateAvatar)
module.exports = router
》》创建控制器文件 (handler/user.js)
// handler/user.js
exports.register = (req, res) => {
// 这里的 req.body 数据已经通过验证
res.send({
status: 0,
message: '注册成功!',
data: req.body
})
}
exports.login = (req, res) => {
res.send({
status: 0,
message: '登录成功!',
data: req.body
})
}
exports.updateUserInfo = (req, res) => {
res.send({
status: 0,
message: '更新用户信息成功!',
data: req.body
})
}
exports.updatePassword = (req, res) => {
res.send({
status: 0,
message: '更新密码成功!',
data: req.body
})
}
exports.updateAvatar = (req, res) => {
res.send({
status: 0,
message: '更新头像成功!',
data: req.body
})
}
》》创建主应用文件 (app.js)
// app.js
const express = require('express')
const app = express()
// 配置解析表单数据的中间件
app.use(express.urlencoded({ extended: false }))
app.use(express.json())
// 导入并注册用户路由模块
const userRouter = require('./routes/user')
app.use('/api', userRouter)
// 错误中间件
app.use((err, req, res, next) => {
// 数据验证失败
if (err instanceof joi.ValidationError) {
return res.send({
status: 1,
message: err.message
})
}
// 未知错误
res.send({
status: 1,
message: err.message
})
})
// 启动服务器
app.listen(3000, () => {
console.log('server running at http://127.0.0.1:3000')
})
高级验证案例
》》自定义验证消息
// schema/user.js
exports.reg_login_schema = {
body: {
username: joi.string().alphanum().min(3).max(12).required()
.messages({
'string.base': '用户名必须是字符串',
'string.alphanum': '用户名只能包含字母和数字',
'string.min': '用户名长度不能小于3',
'string.max': '用户名长度不能大于12',
'any.required': '用户名是必填项'
}),
password: joi
.string()
.pattern(/^[\S]{6,15}$/)
.required()
.messages({
'string.base': '密码必须是字符串',
'string.pattern.base': '密码长度6-15位,不能包含空格',
'any.required': '密码是必填项'
})
}
}
》》条件验证
// schema/user.js
exports.update_userinfo_schema = {
body: {
id: joi.number().integer().min(1).required(),
nickname: joi.string().required(),
email: joi.string().email().required(),
// 只有 is_vip 为 true 时才需要验证 vip_expire 字段
is_vip: joi.boolean(),
vip_expire: joi.when('is_vip', {
is: true,
then: joi.date().required(),
otherwise: joi.optional()
})
}
}
》》数组验证
// schema/product.js
const joi = require('joi')
exports.add_product_schema = {
body: {
name: joi.string().required(),
price: joi.number().min(0).required(),
// 验证数组
tags: joi.array().items(
joi.string().valid('新品', '热销', '折扣')
).min(1).required(),
// 验证对象数组
specs: joi.array().items(
joi.object({
name: joi.string().required(),
value: joi.string().required()
})
).min(1).required()
}
}