数据验证 #
一、验证概述 #
1.1 什么是Joi? #
Joi是Hapi生态系统中的数据验证库,用于验证JavaScript对象的结构和数据类型。
1.2 安装Joi #
bash
npm install @hapi/joi
1.3 基本使用 #
javascript
const Joi = require('@hapi/joi');
server.route({
method: 'POST',
path: '/users',
options: {
validate: {
payload: Joi.object({
name: Joi.string().required(),
email: Joi.string().email().required()
})
}
},
handler: (request, h) => {
return request.payload;
}
});
二、验证位置 #
2.1 路径参数验证 #
javascript
server.route({
method: 'GET',
path: '/users/{id}',
options: {
validate: {
params: Joi.object({
id: Joi.number().integer().positive().required()
})
}
},
handler: (request, h) => {
return { id: request.params.id };
}
});
2.2 查询参数验证 #
javascript
server.route({
method: 'GET',
path: '/search',
options: {
validate: {
query: Joi.object({
q: Joi.string().required(),
page: Joi.number().default(1),
limit: Joi.number().default(10)
})
}
},
handler: (request, h) => {
return request.query;
}
});
2.3 请求体验证 #
javascript
server.route({
method: 'POST',
path: '/users',
options: {
validate: {
payload: Joi.object({
name: Joi.string().min(2).max(50).required(),
email: Joi.string().email().required(),
password: Joi.string().min(6).required()
})
}
},
handler: (request, h) => {
return request.payload;
}
});
2.4 请求头验证 #
javascript
server.route({
method: 'GET',
path: '/api/data',
options: {
validate: {
headers: Joi.object({
authorization: Joi.string().required(),
'x-api-version': Joi.string().valid('1.0', '2.0').required()
}).options({ allowUnknown: true })
}
},
handler: (request, h) => {
return { message: 'OK' };
}
});
2.5 响应验证 #
javascript
server.route({
method: 'GET',
path: '/users/{id}',
options: {
response: {
schema: Joi.object({
id: Joi.number().required(),
name: Joi.string().required(),
email: Joi.string().email().required()
})
}
},
handler: async (request, h) => {
return await getUser(request.params.id);
}
});
三、Joi类型 #
3.1 字符串 #
javascript
const schema = Joi.object({
name: Joi.string().min(2).max(50).required(),
email: Joi.string().email().required(),
phone: Joi.string().pattern(/^[0-9]{10,11}$/),
username: Joi.string().alphanum().min(3).max(30),
description: Joi.string().allow(''),
bio: Joi.string().max(500).optional()
});
3.2 数字 #
javascript
const schema = Joi.object({
age: Joi.number().integer().min(0).max(120),
price: Joi.number().positive().precision(2),
quantity: Joi.number().integer().min(0),
rating: Joi.number().min(0).max(5),
discount: Joi.number().min(0).max(100).default(0)
});
3.3 布尔值 #
javascript
const schema = Joi.object({
active: Joi.boolean().required(),
subscribed: Joi.boolean().default(false),
terms: Joi.boolean().truthy('yes', '1').falsy('no', '0')
});
3.4 日期 #
javascript
const schema = Joi.object({
birthDate: Joi.date().required(),
startDate: Joi.date().min('now'),
endDate: Joi.date().greater(Joi.ref('startDate')),
createdAt: Joi.date().iso()
});
3.5 数组 #
javascript
const schema = Joi.object({
tags: Joi.array().items(Joi.string()).min(1).max(10),
ids: Joi.array().items(Joi.number()).unique(),
scores: Joi.array().items(Joi.number().min(0).max(100)),
items: Joi.array().items(
Joi.object({
id: Joi.number().required(),
name: Joi.string().required()
})
)
});
3.6 对象 #
javascript
const schema = Joi.object({
user: Joi.object({
name: Joi.string().required(),
email: Joi.string().email().required(),
address: Joi.object({
street: Joi.string().required(),
city: Joi.string().required(),
zipCode: Joi.string().required()
})
})
});
四、常用验证规则 #
4.1 必填与可选 #
javascript
const schema = Joi.object({
name: Joi.string().required(),
nickname: Joi.string().optional(),
bio: Joi.string().allow(null),
website: Joi.string().uri().allow('')
});
4.2 默认值 #
javascript
const schema = Joi.object({
page: Joi.number().default(1),
limit: Joi.number().default(10),
sort: Joi.string().default('createdAt'),
order: Joi.string().valid('asc', 'desc').default('desc')
});
4.3 枚举值 #
javascript
const schema = Joi.object({
status: Joi.string().valid('active', 'inactive', 'pending'),
role: Joi.string().valid('admin', 'user', 'guest').default('user'),
type: Joi.number().valid(1, 2, 3)
});
4.4 正则表达式 #
javascript
const schema = Joi.object({
phone: Joi.string().pattern(/^[0-9]{10,11}$/),
zipCode: Joi.string().pattern(/^[0-9]{5}(-[0-9]{4})?$/),
username: Joi.string().pattern(/^[a-zA-Z][a-zA-Z0-9_]{2,29}$/)
});
4.5 自定义验证 #
javascript
const schema = Joi.object({
password: Joi.string().custom((value, helpers) => {
if (value.length < 8) {
return helpers.error('string.min', { limit: 8 });
}
if (!/[A-Z]/.test(value)) {
return helpers.error('any.custom', { message: '需要包含大写字母' });
}
if (!/[0-9]/.test(value)) {
return helpers.error('any.custom', { message: '需要包含数字' });
}
return value;
})
});
五、条件验证 #
5.1 when条件 #
javascript
const schema = Joi.object({
type: Joi.string().valid('individual', 'company').required(),
name: Joi.string().when('type', {
is: 'individual',
then: Joi.required()
}),
companyName: Joi.string().when('type', {
is: 'company',
then: Joi.required()
})
});
5.2 引用其他字段 #
javascript
const schema = Joi.object({
password: Joi.string().min(6).required(),
confirmPassword: Joi.string()
.valid(Joi.ref('password'))
.required()
.messages({
'any.only': '密码不匹配'
}),
startDate: Joi.date().required(),
endDate: Joi.date().greater(Joi.ref('startDate')).required()
});
5.3 复杂条件 #
javascript
const schema = Joi.object({
paymentMethod: Joi.string().valid('credit', 'paypal', 'bank').required(),
cardNumber: Joi.string().when('paymentMethod', {
is: 'credit',
then: Joi.string().creditCard().required()
}),
bankAccount: Joi.string().when('paymentMethod', {
is: 'bank',
then: Joi.string().pattern(/^[0-9]{10,20}$/).required()
})
});
六、错误消息 #
6.1 自定义错误消息 #
javascript
const schema = Joi.object({
name: Joi.string()
.min(2)
.max(50)
.required()
.messages({
'string.empty': '姓名不能为空',
'string.min': '姓名至少需要{#limit}个字符',
'string.max': '姓名不能超过{#limit}个字符',
'any.required': '姓名是必填字段'
}),
email: Joi.string()
.email()
.required()
.messages({
'string.email': '请输入有效的邮箱地址',
'any.required': '邮箱是必填字段'
})
});
6.2 全局错误消息 #
javascript
const schema = Joi.object({
name: Joi.string().required()
}).messages({
'any.required': '此字段为必填项',
'string.empty': '此字段不能为空'
});
七、验证失败处理 #
7.1 默认处理 #
javascript
server.route({
method: 'POST',
path: '/users',
options: {
validate: {
payload: Joi.object({
name: Joi.string().required()
})
}
},
handler: (request, h) => {
return request.payload;
}
});
7.2 自定义失败处理 #
javascript
server.route({
method: 'POST',
path: '/users',
options: {
validate: {
payload: Joi.object({
name: Joi.string().required(),
email: Joi.string().email().required()
}),
failAction: (request, h, err) => {
const errors = err.details.map(d => ({
field: d.path.join('.'),
message: d.message
}));
return h.response({
success: false,
errors: errors
}).code(400).takeover();
}
}
},
handler: (request, h) => {
return request.payload;
}
});
7.3 全局失败处理 #
javascript
const server = Hapi.server({
port: 3000,
routes: {
validate: {
failAction: async (request, h, err) => {
console.error('Validation error:', err);
const errors = err.details.map(d => ({
field: d.path.join('.'),
message: d.message
}));
return h.response({
success: false,
errors: errors
}).code(400).takeover();
}
}
}
});
八、Schema复用 #
8.1 定义公共Schema #
javascript
const schemas = {
userId: Joi.object({
id: Joi.number().integer().positive().required()
}),
pagination: Joi.object({
page: Joi.number().integer().min(1).default(1),
limit: Joi.number().integer().min(1).max(100).default(10),
sort: Joi.string().default('createdAt'),
order: Joi.string().valid('asc', 'desc').default('desc')
}),
user: Joi.object({
name: Joi.string().min(2).max(50).required(),
email: Joi.string().email().required(),
password: Joi.string().min(6).required()
})
};
module.exports = schemas;
8.2 使用公共Schema #
javascript
const schemas = require('./schemas');
server.route({
method: 'GET',
path: '/users/{id}',
options: {
validate: {
params: schemas.userId
}
},
handler: getUser
});
server.route({
method: 'GET',
path: '/users',
options: {
validate: {
query: schemas.pagination
}
},
handler: getUsers
});
server.route({
method: 'POST',
path: '/users',
options: {
validate: {
payload: schemas.user
}
},
handler: createUser
});
九、完整示例 #
9.1 用户API验证 #
javascript
const Joi = require('@hapi/joi');
const userSchemas = {
create: Joi.object({
name: Joi.string().min(2).max(50).required()
.messages({
'string.min': '姓名至少需要{#limit}个字符',
'any.required': '姓名是必填字段'
}),
email: Joi.string().email().required()
.messages({
'string.email': '请输入有效的邮箱地址',
'any.required': '邮箱是必填字段'
}),
password: Joi.string().min(6).max(50).required()
.messages({
'string.min': '密码至少需要{#limit}个字符',
'any.required': '密码是必填字段'
}),
age: Joi.number().integer().min(0).max(120)
}),
update: Joi.object({
name: Joi.string().min(2).max(50),
email: Joi.string().email(),
age: Joi.number().integer().min(0).max(120)
}).min(1),
id: Joi.object({
id: Joi.number().integer().positive().required()
}),
list: Joi.object({
page: Joi.number().integer().min(1).default(1),
limit: Joi.number().integer().min(1).max(100).default(10),
name: Joi.string(),
email: Joi.string().email()
})
};
server.route([
{
method: 'GET',
path: '/users',
options: {
validate: {
query: userSchemas.list
}
},
handler: getUsers
},
{
method: 'GET',
path: '/users/{id}',
options: {
validate: {
params: userSchemas.id
}
},
handler: getUser
},
{
method: 'POST',
path: '/users',
options: {
validate: {
payload: userSchemas.create
}
},
handler: createUser
},
{
method: 'PUT',
path: '/users/{id}',
options: {
validate: {
params: userSchemas.id,
payload: userSchemas.update
}
},
handler: updateUser
}
]);
十、总结 #
数据验证要点:
| 验证位置 | 配置项 |
|---|---|
| 路径参数 | validate.params |
| 查询参数 | validate.query |
| 请求体 | validate.payload |
| 请求头 | validate.headers |
| 响应 | response.schema |
下一步,让我们学习Cookie与Session!
最后更新:2026-03-28