用途: 完整的数据验证使用指南
阅读时间: 15分钟
const { dsl, validate } = require('schema-dsl');
// 1. 定义 Schema
const schema = dsl({
username: 'string:3-32!',
email: 'email!',
age: 'number:18-120'
});
// 2. 验证数据
const result = validate(schema, {
username: 'john_doe',
email: 'john@example.com',
age: 25
});
// 3. 处理结果
if (result.valid) {
console.log('验证通过', result.data);
} else {
console.log('验证失败', result.errors);
}| DSL | 说明 |
|---|---|
'string' |
字符串 |
'number' |
数字 |
'integer' |
整数 |
'boolean' |
布尔值 |
'object' |
对象 |
'array' |
数组 |
| DSL | 说明 |
|---|---|
'email' |
邮箱格式 |
'url' |
URL 格式 |
'uuid' |
UUID 格式 |
'date' |
日期格式 |
'datetime' |
日期时间格式 |
'time' |
时间格式 |
'ipv4' |
IPv4 地址 |
'ipv6' |
IPv6 地址 |
| DSL | 说明 |
|---|---|
'string:10' |
最大长度 10 |
'string:3-32' |
长度 3-32 |
'string:3-' |
最小长度 3 |
'number:18-120' |
数值范围 18-120 |
'array:1-10' |
数组长度 1-10 |
| DSL | 说明 |
|---|---|
'string!' |
必填字符串 |
'email!' |
必填邮箱 |
'a|b|c' |
枚举值 |
'array<string>' |
字符串数组 |
最简单的验证方式,使用内置单例 Validator:
const { dsl, validate } = require('schema-dsl');
const result = validate(schema, data);需要自定义配置(如类型转换、自定义关键字)时使用:
const { dsl, Validator } = require('schema-dsl');
// 创建自定义配置的 Validator
const validator = new Validator({
allErrors: true, // 返回所有错误
useDefaults: true, // 使用默认值
coerceTypes: true // ✨ 启用类型转换
});
const result = validator.validate(schema, data);注意:
new Validator()会创建一个新的 Ajv 实例,有一定的初始化开销。建议在应用启动时创建并复用,避免在每次请求中创建。
频繁验证同一 Schema 时使用:
const validator = new Validator();
// 预编译 Schema
const validateUser = validator.compile(userSchema);
// 多次验证(无需重复编译)
const result1 = validateUser(data1);
const result2 = validateUser(data2);
const result3 = validateUser(data3);验证多条数据时使用:
const { Validator } = require('schema-dsl');
const validator = new Validator();
const dataList = [
{ username: 'user1', email: 'user1@example.com' },
{ username: 'user2', email: 'invalid' },
{ username: 'u', email: 'user3@example.com' }
];
const results = validator.validateBatch(schema, dataList);
// [
// { valid: true, errors: [] },
// { valid: false, errors: [...] },
// { valid: false, errors: [...] }
// ]{
message: '用户名长度不能少于3个字符',
path: '/username',
keyword: 'minLength',
params: { limit: 3 }
}const schema = dsl({
username: 'string:3-32!'
.label('用户名')
.messages({
'min': '{{#label}}太短了,至少{{#limit}}个字符',
'max': '{{#label}}太长了,最多{{#limit}}个字符',
'required': '请输入{{#label}}'
})
});const { Locale, Validator } = require('schema-dsl');
// 添加语言包
Locale.addLocale('zh-CN', {
'required': '{{#label}}不能为空',
'min': '{{#label}}长度不能少于{{#limit}}',
'email': '请输入有效的{{#label}}'
});
// 验证时指定语言
const validator = new Validator();
const result = validator.validate(schema, data, { locale: 'zh-CN' });function formatErrors(errors) {
return errors.map(err => {
const field = err.path.replace(/^\//, '').replace(/\//g, '.');
return `[${field}] ${err.message}`;
}).join('\n');
}
if (!result.valid) {
console.log(formatErrors(result.errors));
// [username] 用户名长度不能少于3个字符
// [email] 请输入有效的邮箱地址
}// ❌ 每次都编译(慢)
function validateUser(data) {
return validate(userSchema, data);
}
// ✅ 预编译一次,多次使用(快)
const validator = new Validator();
const validateUser = validator.compile(userSchema);// ❌ 每次都创建 Schema
function getSchema() {
return dsl({
username: 'string:3-32!',
email: 'email!'
});
}
// ✅ 缓存 Schema
const userSchema = dsl({
username: 'string:3-32!',
email: 'email!'
});// 只需要第一个错误时
const validator = new Validator({ allErrors: false });
// 需要所有错误时(默认)
const validator = new Validator({ allErrors: true });const result = validate(schema, data);
console.log(`验证耗时: ${result.performance?.duration}ms`);const registerSchema = dsl({
username: 'string:3-32!'
.pattern(/^[a-zA-Z0-9_]+$/)
.label('用户名')
.messages({
'pattern': '{{#label}}只能包含字母、数字和下划线'
}),
email: 'email!'
.label('邮箱地址'),
password: 'string:8-64!'
.password('strong')
.label('密码'),
age: 'number:18-120'
.label('年龄'),
gender: 'male|female|other',
terms: 'boolean!'
.label('服务条款')
.messages({
'required': '请同意{{#label}}'
})
});const createOrderSchema = dsl({
userId: 'string!',
items: 'array!1-100',
shippingAddress: {
street: 'string:5-200!',
city: 'string:2-100!',
zipCode: 'string:5-10!',
country: 'string:2!'
},
paymentMethod: 'credit_card|paypal|bank_transfer',
notes: 'string:500'
});
// Express 中间件
function validateRequest(schema) {
return (req, res, next) => {
const result = validate(schema, req.body);
if (!result.valid) {
return res.status(400).json({ errors: result.errors });
}
req.validatedData = result.data;
next();
};
}
app.post('/orders', validateRequest(createOrderSchema), createOrder);const configSchema = dsl({
server: {
host: 'string!',
port: 'integer:1-65535!',
ssl: 'boolean'
},
database: {
url: 'url!',
poolSize: 'integer:1-100',
timeout: 'integer:1000-60000'
},
logging: {
level: 'debug|info|warn|error',
format: 'json|text'
}
});
function loadConfig(configPath) {
const config = require(configPath);
const result = validate(configSchema, config);
if (!result.valid) {
throw new Error(`配置文件错误:\n${formatErrors(result.errors)}`);
}
return result.data;
}// ❌ 默认错误消息
email: 'email!'
// 错误: "email is required"
// ✅ 使用 label
email: 'email!'.label('邮箱地址')
// 错误: "邮箱地址不能为空"// schemas/index.js
const { dsl } = require('schema-dsl');
exports.userSchema = dsl({
username: 'string:3-32!',
email: 'email!'
});
exports.orderSchema = dsl({
userId: 'string!',
items: 'array!1-100'
});const { SchemaUtils, dsl } = require('schema-dsl');
// 创建可复用字段
const emailField = SchemaUtils.reusable(() =>
dsl('email!').label('邮箱地址')
);
// 在多个 Schema 中复用
const loginSchema = dsl({ email: emailField() });
const registerSchema = dsl({ email: emailField(), name: 'string!' });// 基础验证(快速)
const quickSchema = dsl({
username: 'string!',
email: 'string!'
});
// 完整验证(详细)
const fullSchema = dsl({
username: 'string:3-32!'.pattern(/^[a-z]+$/),
email: 'email!'.custom(async (v) => checkEmailUnique(v))
});
// 先快速验证,再完整验证
function validateWithFallback(data) {
const quick = validate(quickSchema, data);
if (!quick.valid) return quick;
return validate(fullSchema, data);
}describe('User Schema', () => {
it('应该验证有效用户', () => {
const result = validate(userSchema, {
username: 'john_doe',
email: 'john@example.com'
});
expect(result.valid).to.be.true;
});
it('应该拒绝短用户名', () => {
const result = validate(userSchema, {
username: 'ab',
email: 'john@example.com'
});
expect(result.valid).to.be.false;
expect(result.errors[0].keyword).to.equal('minLength');
});
});