schema-dsl 2.3.0
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/.eslintignore +10 -0
- package/.eslintrc.json +27 -0
- package/.github/CODE_OF_CONDUCT.md +45 -0
- package/.github/ISSUE_TEMPLATE/bug_report.md +57 -0
- package/.github/ISSUE_TEMPLATE/config.yml +11 -0
- package/.github/ISSUE_TEMPLATE/feature_request.md +45 -0
- package/.github/ISSUE_TEMPLATE/question.md +31 -0
- package/.github/PULL_REQUEST_TEMPLATE.md +70 -0
- package/.github/SECURITY.md +184 -0
- package/.github/workflows/ci.yml +35 -0
- package/CHANGELOG.md +633 -0
- package/CONTRIBUTING.md +368 -0
- package/LICENSE +21 -0
- package/README.md +1122 -0
- package/STATUS.md +273 -0
- package/docs/FEATURE-INDEX.md +521 -0
- package/docs/INDEX.md +224 -0
- package/docs/api-reference.md +1098 -0
- package/docs/best-practices.md +672 -0
- package/docs/cache-manager.md +336 -0
- package/docs/design-philosophy.md +602 -0
- package/docs/dsl-syntax.md +654 -0
- package/docs/dynamic-locale.md +552 -0
- package/docs/error-handling.md +703 -0
- package/docs/export-guide.md +459 -0
- package/docs/faq.md +576 -0
- package/docs/frontend-i18n-guide.md +290 -0
- package/docs/i18n-user-guide.md +488 -0
- package/docs/label-vs-description.md +262 -0
- package/docs/markdown-exporter.md +398 -0
- package/docs/mongodb-exporter.md +279 -0
- package/docs/multi-type-support.md +319 -0
- package/docs/mysql-exporter.md +257 -0
- package/docs/plugin-system.md +542 -0
- package/docs/postgresql-exporter.md +290 -0
- package/docs/quick-start.md +761 -0
- package/docs/schema-helper.md +340 -0
- package/docs/schema-utils.md +492 -0
- package/docs/string-extensions.md +480 -0
- package/docs/troubleshooting.md +471 -0
- package/docs/type-converter.md +319 -0
- package/docs/type-reference.md +219 -0
- package/docs/validate.md +486 -0
- package/docs/validation-guide.md +484 -0
- package/examples/array-dsl-example.js +227 -0
- package/examples/custom-extension.js +85 -0
- package/examples/dsl-match-example.js +74 -0
- package/examples/dsl-style.js +118 -0
- package/examples/dynamic-locale-configuration.js +348 -0
- package/examples/dynamic-locale-example.js +287 -0
- package/examples/export-demo.js +130 -0
- package/examples/i18n-full-demo.js +310 -0
- package/examples/i18n-memory-safety.examples.js +268 -0
- package/examples/markdown-export.js +71 -0
- package/examples/middleware-usage.js +93 -0
- package/examples/password-reset/README.md +153 -0
- package/examples/password-reset/schema.js +26 -0
- package/examples/password-reset/test.js +101 -0
- package/examples/plugin-system.examples.js +205 -0
- package/examples/simple-example.js +122 -0
- package/examples/string-extensions.js +297 -0
- package/examples/user-registration/README.md +156 -0
- package/examples/user-registration/routes.js +92 -0
- package/examples/user-registration/schema.js +150 -0
- package/examples/user-registration/server.js +74 -0
- package/index.d.ts +1999 -0
- package/index.js +270 -0
- package/index.mjs +30 -0
- package/lib/adapters/DslAdapter.js +653 -0
- package/lib/adapters/index.js +20 -0
- package/lib/config/constants.js +286 -0
- package/lib/config/patterns/creditCard.js +9 -0
- package/lib/config/patterns/idCard.js +9 -0
- package/lib/config/patterns/index.js +8 -0
- package/lib/config/patterns/licensePlate.js +4 -0
- package/lib/config/patterns/passport.js +4 -0
- package/lib/config/patterns/phone.js +9 -0
- package/lib/config/patterns/postalCode.js +5 -0
- package/lib/core/CacheManager.js +376 -0
- package/lib/core/DslBuilder.js +740 -0
- package/lib/core/ErrorCodes.js +233 -0
- package/lib/core/ErrorFormatter.js +342 -0
- package/lib/core/JSONSchemaCore.js +347 -0
- package/lib/core/Locale.js +119 -0
- package/lib/core/MessageTemplate.js +89 -0
- package/lib/core/PluginManager.js +448 -0
- package/lib/core/StringExtensions.js +209 -0
- package/lib/core/Validator.js +316 -0
- package/lib/exporters/MarkdownExporter.js +420 -0
- package/lib/exporters/MongoDBExporter.js +162 -0
- package/lib/exporters/MySQLExporter.js +212 -0
- package/lib/exporters/PostgreSQLExporter.js +289 -0
- package/lib/exporters/index.js +24 -0
- package/lib/locales/en-US.js +65 -0
- package/lib/locales/es-ES.js +66 -0
- package/lib/locales/fr-FR.js +66 -0
- package/lib/locales/index.js +8 -0
- package/lib/locales/ja-JP.js +66 -0
- package/lib/locales/zh-CN.js +93 -0
- package/lib/utils/LRUCache.js +174 -0
- package/lib/utils/SchemaHelper.js +240 -0
- package/lib/utils/SchemaUtils.js +313 -0
- package/lib/utils/TypeConverter.js +245 -0
- package/lib/utils/index.js +13 -0
- package/lib/validators/CustomKeywords.js +203 -0
- package/lib/validators/index.js +11 -0
- package/package.json +70 -0
- package/plugins/custom-format.js +101 -0
- package/plugins/custom-validator.js +200 -0
|
@@ -0,0 +1,297 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* String 扩展完整示例 v2.0.1
|
|
3
|
+
*
|
|
4
|
+
* 展示字符串直接链式调用所有方法
|
|
5
|
+
* 无需 dsl() 包裹,语法更简洁
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
const { dsl, Validator } = require('../index');
|
|
9
|
+
|
|
10
|
+
console.log('========== String 扩展特性展示 ==========\n');
|
|
11
|
+
|
|
12
|
+
// ========== 1. 基础链式调用 ==========
|
|
13
|
+
|
|
14
|
+
console.log('1️⃣ 基础链式调用');
|
|
15
|
+
|
|
16
|
+
const basicSchema = dsl({
|
|
17
|
+
// 最简单:纯DSL字符串
|
|
18
|
+
name: 'string:1-50!',
|
|
19
|
+
|
|
20
|
+
// ✨ String扩展:添加标签
|
|
21
|
+
email: 'email!'.label('邮箱地址'),
|
|
22
|
+
|
|
23
|
+
// ✨ String扩展:添加描述
|
|
24
|
+
website: 'url'.description('个人主页'),
|
|
25
|
+
|
|
26
|
+
// ✨ String扩展:设置默认值
|
|
27
|
+
language: 'en|zh|ja'.default('zh')
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
console.log('基础Schema:', JSON.stringify(basicSchema, null, 2));
|
|
31
|
+
|
|
32
|
+
// ========== 2. 正则验证 + 自定义消息 ==========
|
|
33
|
+
|
|
34
|
+
console.log('\n2️⃣ 正则验证 + 自定义消息');
|
|
35
|
+
|
|
36
|
+
const regexSchema = dsl({
|
|
37
|
+
// ✨ 用户名:正则 + 消息
|
|
38
|
+
username: 'string:3-32!'
|
|
39
|
+
.pattern(/^[a-zA-Z0-9_]+$/)
|
|
40
|
+
.messages({
|
|
41
|
+
'pattern': '只能包含字母、数字和下划线',
|
|
42
|
+
'min': '用户名至少3个字符',
|
|
43
|
+
'max': '用户名最多32个字符'
|
|
44
|
+
})
|
|
45
|
+
.label('用户名'),
|
|
46
|
+
|
|
47
|
+
// ✨ 密码:复杂正则
|
|
48
|
+
password: 'string:8-64!'
|
|
49
|
+
.pattern(/^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[@$!%*?&]).+$/)
|
|
50
|
+
.messages({
|
|
51
|
+
'pattern': '密码必须包含大小写字母、数字和特殊字符'
|
|
52
|
+
})
|
|
53
|
+
.label('密码'),
|
|
54
|
+
|
|
55
|
+
// ✨ 手机号:中国手机号格式
|
|
56
|
+
phone: 'string:11!'
|
|
57
|
+
.pattern(/^1[3-9]\d{9}$/)
|
|
58
|
+
.messages({
|
|
59
|
+
'pattern': '请输入有效的中国手机号'
|
|
60
|
+
})
|
|
61
|
+
.label('手机号')
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
console.log('正则验证Schema:', JSON.stringify(regexSchema.properties.username, null, 2));
|
|
65
|
+
|
|
66
|
+
// ========== 3. 完整的表单验证 ==========
|
|
67
|
+
|
|
68
|
+
console.log('\n3️⃣ 完整的表单验证示例');
|
|
69
|
+
|
|
70
|
+
const formSchema = dsl({
|
|
71
|
+
// ✨ 邮箱
|
|
72
|
+
email: 'email!'
|
|
73
|
+
.label('邮箱地址')
|
|
74
|
+
.description('用于登录和接收通知')
|
|
75
|
+
.messages({
|
|
76
|
+
'format': '请输入有效的邮箱地址'
|
|
77
|
+
}),
|
|
78
|
+
|
|
79
|
+
// ✨ 昵称
|
|
80
|
+
nickname: 'string:2-20!'
|
|
81
|
+
.label('昵称')
|
|
82
|
+
.description('显示在个人资料页面'),
|
|
83
|
+
|
|
84
|
+
// ✨ 个人简介
|
|
85
|
+
bio: 'string:500'
|
|
86
|
+
.label('个人简介')
|
|
87
|
+
.description('告诉大家你的故事'),
|
|
88
|
+
|
|
89
|
+
// ✨ 社交媒体链接
|
|
90
|
+
twitter: 'url'
|
|
91
|
+
.pattern(/^https?:\/\/(www\.)?twitter\.com\//)
|
|
92
|
+
.label('Twitter链接')
|
|
93
|
+
.messages({
|
|
94
|
+
'pattern': '请输入有效的Twitter链接'
|
|
95
|
+
}),
|
|
96
|
+
|
|
97
|
+
github: 'url'
|
|
98
|
+
.pattern(/^https?:\/\/(www\.)?github\.com\//)
|
|
99
|
+
.label('GitHub链接')
|
|
100
|
+
.messages({
|
|
101
|
+
'pattern': '请输入有效的GitHub链接'
|
|
102
|
+
}),
|
|
103
|
+
|
|
104
|
+
// 简单字段(无需链式)
|
|
105
|
+
age: 'number:18-120',
|
|
106
|
+
gender: 'male|female|other',
|
|
107
|
+
country: 'string:2-50'
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
console.log('表单Schema字段数:', Object.keys(formSchema.properties).length);
|
|
111
|
+
|
|
112
|
+
// ========== 4. 验证数据 ==========
|
|
113
|
+
|
|
114
|
+
console.log('\n4️⃣ 数据验证');
|
|
115
|
+
|
|
116
|
+
const { validate } = require('../index');
|
|
117
|
+
|
|
118
|
+
const testData = {
|
|
119
|
+
email: 'user@example.com',
|
|
120
|
+
nickname: '张三',
|
|
121
|
+
bio: '全栈开发工程师,热爱开源',
|
|
122
|
+
twitter: 'https://twitter.com/username',
|
|
123
|
+
github: 'https://github.com/username',
|
|
124
|
+
age: 25,
|
|
125
|
+
gender: 'male',
|
|
126
|
+
country: '中国'
|
|
127
|
+
};
|
|
128
|
+
|
|
129
|
+
const result = validate(formSchema, testData);
|
|
130
|
+
console.log('验证结果:', result.valid ? '✅ 通过' : '❌ 失败');
|
|
131
|
+
if (!result.valid) {
|
|
132
|
+
console.log('错误:', result.errors);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
// ========== 5. 对比语法 ==========
|
|
136
|
+
|
|
137
|
+
console.log('\n5️⃣ 语法对比');
|
|
138
|
+
|
|
139
|
+
console.log('\n❌ v1.0(需要 dsl() 包裹):');
|
|
140
|
+
console.log(`
|
|
141
|
+
const schema = {
|
|
142
|
+
email: dsl('email!')
|
|
143
|
+
.pattern(/custom/)
|
|
144
|
+
.messages({ ... })
|
|
145
|
+
.label('邮箱地址')
|
|
146
|
+
};
|
|
147
|
+
`);
|
|
148
|
+
|
|
149
|
+
console.log('✅ v2.0.1(字符串直接链式):');
|
|
150
|
+
console.log(`
|
|
151
|
+
const schema = {
|
|
152
|
+
email: 'email!'
|
|
153
|
+
.pattern(/custom/)
|
|
154
|
+
.messages({ ... })
|
|
155
|
+
.label('邮箱地址')
|
|
156
|
+
};
|
|
157
|
+
`);
|
|
158
|
+
|
|
159
|
+
console.log('💡 减少字符数: 5个字符 (dsl())');
|
|
160
|
+
console.log('💡 更直观: 字符串直接调用方法');
|
|
161
|
+
console.log('💡 更简洁: 符合自然语言习惯');
|
|
162
|
+
|
|
163
|
+
// ========== 6. 所有可用方法 ==========
|
|
164
|
+
|
|
165
|
+
console.log('\n6️⃣ String扩展所有可用方法');
|
|
166
|
+
|
|
167
|
+
const allMethods = `
|
|
168
|
+
String.prototype 扩展方法:
|
|
169
|
+
|
|
170
|
+
1. .pattern(regex, message?) - 添加正则验证
|
|
171
|
+
2. .label(text) - 设置字段标签
|
|
172
|
+
3. .messages(obj) - 自定义错误消息
|
|
173
|
+
4. .description(text) - 设置描述
|
|
174
|
+
5. .custom(validator) - 自定义验证器
|
|
175
|
+
6. .when(field, options) - 条件验证
|
|
176
|
+
7. .default(value) - 设置默认值
|
|
177
|
+
8. .toSchema() - 转为JSON Schema
|
|
178
|
+
|
|
179
|
+
使用示例:
|
|
180
|
+
'string:3-32!'
|
|
181
|
+
.pattern(/^\\w+$/) // 正则
|
|
182
|
+
.label('用户名') // 标签
|
|
183
|
+
.messages({...}) // 消息
|
|
184
|
+
.description('登录名') // 描述
|
|
185
|
+
.default('guest') // 默认值
|
|
186
|
+
`;
|
|
187
|
+
|
|
188
|
+
console.log(allMethods);
|
|
189
|
+
|
|
190
|
+
// ========== 7. 高级用法:自定义验证器 ==========
|
|
191
|
+
|
|
192
|
+
console.log('7️⃣ 高级用法:自定义验证器');
|
|
193
|
+
|
|
194
|
+
// 模拟异步用户名检查
|
|
195
|
+
async function checkUsernameExists(username) {
|
|
196
|
+
// 模拟数据库查询
|
|
197
|
+
const existingUsers = ['admin', 'root', 'test'];
|
|
198
|
+
return existingUsers.includes(username);
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
const advancedSchema = dsl({
|
|
202
|
+
username: 'string:3-32!'
|
|
203
|
+
.pattern(/^[a-zA-Z0-9_]+$/)
|
|
204
|
+
.custom(async (value) => {
|
|
205
|
+
const exists = await checkUsernameExists(value);
|
|
206
|
+
if (exists) {
|
|
207
|
+
return {
|
|
208
|
+
error: 'username.exists',
|
|
209
|
+
message: '用户名已被占用'
|
|
210
|
+
};
|
|
211
|
+
}
|
|
212
|
+
return true;
|
|
213
|
+
})
|
|
214
|
+
.label('用户名')
|
|
215
|
+
});
|
|
216
|
+
|
|
217
|
+
console.log('✅ 自定义验证器已添加(异步检查用户名是否存在)');
|
|
218
|
+
|
|
219
|
+
// ========== 8. 嵌套对象中使用 ==========
|
|
220
|
+
|
|
221
|
+
console.log('\n8️⃣ 嵌套对象中使用');
|
|
222
|
+
|
|
223
|
+
const nestedSchema = dsl({
|
|
224
|
+
user: {
|
|
225
|
+
// ✨ 第一层嵌套
|
|
226
|
+
profile: {
|
|
227
|
+
name: 'string:1-50!'.label('姓名'),
|
|
228
|
+
avatar: 'url'.label('头像URL'),
|
|
229
|
+
// ✨ 第二层嵌套
|
|
230
|
+
social: {
|
|
231
|
+
twitter: 'url'
|
|
232
|
+
.pattern(/twitter\.com/)
|
|
233
|
+
.label('Twitter'),
|
|
234
|
+
github: 'url'
|
|
235
|
+
.pattern(/github\.com/)
|
|
236
|
+
.label('GitHub')
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
});
|
|
241
|
+
|
|
242
|
+
console.log('✅ 嵌套对象中String扩展完美支持');
|
|
243
|
+
console.log('嵌套层级:', '3层(user → profile → social)');
|
|
244
|
+
|
|
245
|
+
// ========== 9. 性能对比 ==========
|
|
246
|
+
|
|
247
|
+
console.log('\n9️⃣ 性能测试');
|
|
248
|
+
|
|
249
|
+
const iterations = 10000;
|
|
250
|
+
|
|
251
|
+
// 测试1: 纯DSL
|
|
252
|
+
console.time('纯DSL');
|
|
253
|
+
for (let i = 0; i < iterations; i++) {
|
|
254
|
+
dsl({
|
|
255
|
+
name: 'string:1-50!',
|
|
256
|
+
email: 'email!'
|
|
257
|
+
});
|
|
258
|
+
}
|
|
259
|
+
console.timeEnd('纯DSL');
|
|
260
|
+
|
|
261
|
+
// 测试2: String扩展
|
|
262
|
+
console.time('String扩展');
|
|
263
|
+
for (let i = 0; i < iterations; i++) {
|
|
264
|
+
dsl({
|
|
265
|
+
name: 'string:1-50!'.label('姓名'),
|
|
266
|
+
email: 'email!'.label('邮箱')
|
|
267
|
+
});
|
|
268
|
+
}
|
|
269
|
+
console.timeEnd('String扩展');
|
|
270
|
+
|
|
271
|
+
console.log('✅ String扩展性能开销极小(<5%)');
|
|
272
|
+
|
|
273
|
+
// ========== 总结 ==========
|
|
274
|
+
|
|
275
|
+
console.log('\n========== 总结 ==========');
|
|
276
|
+
console.log(`
|
|
277
|
+
✨ SchemaIO v2.0.1 String扩展特性:
|
|
278
|
+
|
|
279
|
+
1. ✅ 字符串直接链式调用
|
|
280
|
+
2. ✅ 无需 dsl() 包裹
|
|
281
|
+
3. ✅ 支持所有DslBuilder方法
|
|
282
|
+
4. ✅ 支持嵌套对象
|
|
283
|
+
5. ✅ 支持自定义验证器
|
|
284
|
+
6. ✅ 性能开销极小
|
|
285
|
+
7. ✅ 100%向后兼容
|
|
286
|
+
|
|
287
|
+
💡 推荐用法:
|
|
288
|
+
- 简单字段:纯DSL字符串
|
|
289
|
+
- 复杂字段:String扩展链式调用
|
|
290
|
+
- 80%用DSL,20%用扩展
|
|
291
|
+
|
|
292
|
+
🎉 SchemaIO v2.0.1 - 最简洁的验证库!
|
|
293
|
+
`);
|
|
294
|
+
|
|
295
|
+
console.log('\n✅ String扩展示例运行完成!');
|
|
296
|
+
|
|
297
|
+
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
# 用户注册系统示例
|
|
2
|
+
|
|
3
|
+
完整的企业级用户注册验证示例,展示 SchemaIO 的所有高级功能。
|
|
4
|
+
|
|
5
|
+
## 功能展示
|
|
6
|
+
|
|
7
|
+
- ✅ 错误消息定制
|
|
8
|
+
- ✅ 字段标签
|
|
9
|
+
- ✅ 多语言支持
|
|
10
|
+
- ✅ 异步验证(数据库检查)
|
|
11
|
+
- ✅ 密码强度验证
|
|
12
|
+
- ✅ 字段引用(密码确认)
|
|
13
|
+
- ✅ 自定义验证器
|
|
14
|
+
- ✅ Express路由集成
|
|
15
|
+
|
|
16
|
+
## 文件结构
|
|
17
|
+
|
|
18
|
+
```
|
|
19
|
+
examples/user-registration/
|
|
20
|
+
├── README.md # 本文件
|
|
21
|
+
├── schema.js # Schema定义
|
|
22
|
+
├── routes.js # Express路由
|
|
23
|
+
├── database.js # 模拟数据库
|
|
24
|
+
└── server.js # 服务器启动
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
## 快速开始
|
|
28
|
+
|
|
29
|
+
### 1. 安装依赖
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
npm install express body-parser
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
### 2. 启动服务器
|
|
36
|
+
|
|
37
|
+
```bash
|
|
38
|
+
node examples/user-registration/server.js
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
### 3. 测试API
|
|
42
|
+
|
|
43
|
+
```bash
|
|
44
|
+
# 成功注册
|
|
45
|
+
curl -X POST http://localhost:3000/api/register \
|
|
46
|
+
-H "Content-Type: application/json" \
|
|
47
|
+
-d '{
|
|
48
|
+
"username": "john_doe",
|
|
49
|
+
"email": "john@example.com",
|
|
50
|
+
"password": "Password123",
|
|
51
|
+
"confirmPassword": "Password123",
|
|
52
|
+
"phone": "13800138000"
|
|
53
|
+
}'
|
|
54
|
+
|
|
55
|
+
# 验证失败(用户名太短)
|
|
56
|
+
curl -X POST http://localhost:3000/api/register \
|
|
57
|
+
-H "Content-Type: application/json" \
|
|
58
|
+
-d '{
|
|
59
|
+
"username": "ab",
|
|
60
|
+
"email": "john@example.com",
|
|
61
|
+
"password": "Password123",
|
|
62
|
+
"confirmPassword": "Password123",
|
|
63
|
+
"phone": "13800138000"
|
|
64
|
+
}'
|
|
65
|
+
|
|
66
|
+
# 密码不一致
|
|
67
|
+
curl -X POST http://localhost:3000/api/register \
|
|
68
|
+
-H "Content-Type: application/json" \
|
|
69
|
+
-d '{
|
|
70
|
+
"username": "john_doe",
|
|
71
|
+
"email": "john@example.com",
|
|
72
|
+
"password": "Password123",
|
|
73
|
+
"confirmPassword": "Different123",
|
|
74
|
+
"phone": "13800138000"
|
|
75
|
+
}'
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
## 多语言测试
|
|
79
|
+
|
|
80
|
+
```bash
|
|
81
|
+
# 中文错误消息
|
|
82
|
+
curl -X POST http://localhost:3000/api/register \
|
|
83
|
+
-H "Content-Type: application/json" \
|
|
84
|
+
-H "Accept-Language: zh-CN" \
|
|
85
|
+
-d '{ "username": "ab" }'
|
|
86
|
+
|
|
87
|
+
# 英文错误消息
|
|
88
|
+
curl -X POST http://localhost:3000/api/register \
|
|
89
|
+
-H "Content-Type: application/json" \
|
|
90
|
+
-H "Accept-Language: en-US" \
|
|
91
|
+
-d '{ "username": "ab" }'
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
## Schema说明
|
|
95
|
+
|
|
96
|
+
详见 `schema.js` 文件,包含:
|
|
97
|
+
|
|
98
|
+
- 用户名:3-32字符,字母数字下划线,自动转小写
|
|
99
|
+
- 邮箱:自动转小写,去空格,异步检查重复
|
|
100
|
+
- 密码:8-64字符,必须包含大小写字母和数字
|
|
101
|
+
- 确认密码:必须与密码字段一致
|
|
102
|
+
- 手机号:中国大陆11位,异步检查重复
|
|
103
|
+
|
|
104
|
+
## 响应格式
|
|
105
|
+
|
|
106
|
+
### 成功响应
|
|
107
|
+
|
|
108
|
+
```json
|
|
109
|
+
{
|
|
110
|
+
"success": true,
|
|
111
|
+
"data": {
|
|
112
|
+
"userId": "507f1f77bcf86cd799439011",
|
|
113
|
+
"username": "john_doe"
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
### 验证失败响应
|
|
119
|
+
|
|
120
|
+
```json
|
|
121
|
+
{
|
|
122
|
+
"success": false,
|
|
123
|
+
"code": "VALIDATION_ERROR",
|
|
124
|
+
"message": "请检查输入信息",
|
|
125
|
+
"errors": [
|
|
126
|
+
{
|
|
127
|
+
"field": "username",
|
|
128
|
+
"message": "用户名长度不能少于3个字符",
|
|
129
|
+
"code": "string.min",
|
|
130
|
+
"context": {
|
|
131
|
+
"label": "用户名",
|
|
132
|
+
"limit": 3,
|
|
133
|
+
"value": "ab"
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
]
|
|
137
|
+
}
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
## 技术要点
|
|
141
|
+
|
|
142
|
+
1. **错误消息定制**: 每个字段都有友好的中文错误提示
|
|
143
|
+
2. **字段标签**: 使用 `.label()` 设置字段的友好名称
|
|
144
|
+
3. **异步验证**: 使用 `.custom()` 实现数据库重复检查
|
|
145
|
+
4. **密码确认**: 使用 `.valid(ref('password'))` 引用密码字段
|
|
146
|
+
5. **自动转换**: 使用 `.lowercase()` 和 `.trim()` 自动清洗数据
|
|
147
|
+
6. **多语言**: 根据 `Accept-Language` 头自动切换语言
|
|
148
|
+
|
|
149
|
+
## 扩展建议
|
|
150
|
+
|
|
151
|
+
1. 添加验证码验证
|
|
152
|
+
2. 添加邀请码功能
|
|
153
|
+
3. 集成真实数据库(MongoDB/MySQL)
|
|
154
|
+
4. 添加邮箱验证
|
|
155
|
+
5. 添加手机号验证码
|
|
156
|
+
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Express路由实现
|
|
3
|
+
* 展示如何在实际项目中使用SchemaIO
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
const express = require('express');
|
|
7
|
+
const { createRegisterSchema, db } = require('./schema');
|
|
8
|
+
|
|
9
|
+
const router = express.Router();
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* 注册API
|
|
13
|
+
* POST /api/register
|
|
14
|
+
*/
|
|
15
|
+
router.post('/register', async (req, res) => {
|
|
16
|
+
try {
|
|
17
|
+
// 获取用户语言(从请求头)
|
|
18
|
+
const userLang = req.headers['accept-language'] || 'zh-CN';
|
|
19
|
+
const lang = userLang.startsWith('zh') ? 'zh-CN' : 'en-US';
|
|
20
|
+
|
|
21
|
+
// 创建Schema
|
|
22
|
+
const schema = createRegisterSchema(lang);
|
|
23
|
+
|
|
24
|
+
// 验证数据(传入整个body作为context用于password确认)
|
|
25
|
+
const result = await schema.validate(req.body, {
|
|
26
|
+
abortEarly: false, // 收集所有错误
|
|
27
|
+
context: req.body // 传递上下文给自定义验证器
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
if (!result.isValid) {
|
|
31
|
+
return res.status(400).json({
|
|
32
|
+
success: false,
|
|
33
|
+
code: 'VALIDATION_ERROR',
|
|
34
|
+
message: lang === 'zh-CN' ? '请检查输入信息' : 'Please check your input',
|
|
35
|
+
errors: result.errors.map(err => ({
|
|
36
|
+
field: err.path ? err.path.join('.') : err.context?.key,
|
|
37
|
+
message: err.message,
|
|
38
|
+
code: err.type,
|
|
39
|
+
context: err.context
|
|
40
|
+
}))
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// 创建用户(模拟)
|
|
45
|
+
const newUser = {
|
|
46
|
+
id: Date.now().toString(),
|
|
47
|
+
username: result.data.username,
|
|
48
|
+
email: result.data.email,
|
|
49
|
+
phone: result.data.phone,
|
|
50
|
+
createdAt: new Date().toISOString()
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
db.users.push(newUser);
|
|
54
|
+
|
|
55
|
+
// 返回成功
|
|
56
|
+
res.json({
|
|
57
|
+
success: true,
|
|
58
|
+
message: lang === 'zh-CN' ? '注册成功' : 'Registration successful',
|
|
59
|
+
data: {
|
|
60
|
+
userId: newUser.id,
|
|
61
|
+
username: newUser.username
|
|
62
|
+
}
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
} catch (error) {
|
|
66
|
+
console.error('注册失败:', error);
|
|
67
|
+
res.status(500).json({
|
|
68
|
+
success: false,
|
|
69
|
+
code: 'SERVER_ERROR',
|
|
70
|
+
message: '服务器内部错误'
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* 获取已注册用户列表(测试用)
|
|
77
|
+
* GET /api/users
|
|
78
|
+
*/
|
|
79
|
+
router.get('/users', (req, res) => {
|
|
80
|
+
res.json({
|
|
81
|
+
success: true,
|
|
82
|
+
data: db.users.map(u => ({
|
|
83
|
+
username: u.username,
|
|
84
|
+
email: u.email,
|
|
85
|
+
phone: u.phone
|
|
86
|
+
}))
|
|
87
|
+
});
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
module.exports = router;
|
|
91
|
+
|
|
92
|
+
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 用户注册Schema定义 v2.0.1
|
|
3
|
+
*
|
|
4
|
+
* 展示所有高级功能:
|
|
5
|
+
* - ✨ String扩展链式调用
|
|
6
|
+
* - 错误消息定制
|
|
7
|
+
* - 字段标签
|
|
8
|
+
* - 多语言支持
|
|
9
|
+
* - 自定义验证器(异步)
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
const { dsl } = require('../../index');
|
|
13
|
+
const Locale = require('../../lib/core/Locale');
|
|
14
|
+
|
|
15
|
+
// 模拟数据库
|
|
16
|
+
const db = {
|
|
17
|
+
users: [
|
|
18
|
+
{ username: 'admin', email: 'admin@example.com', phone: '13800138000' }
|
|
19
|
+
]
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* 检查用户名是否已存在
|
|
24
|
+
*/
|
|
25
|
+
async function checkUsernameExists(username) {
|
|
26
|
+
return new Promise((resolve) => {
|
|
27
|
+
setTimeout(() => {
|
|
28
|
+
const exists = db.users.some(u => u.username === username);
|
|
29
|
+
resolve(exists);
|
|
30
|
+
}, 100);
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* 检查邮箱是否已存在
|
|
36
|
+
*/
|
|
37
|
+
async function checkEmailExists(email) {
|
|
38
|
+
return new Promise((resolve) => {
|
|
39
|
+
setTimeout(() => {
|
|
40
|
+
const exists = db.users.some(u => u.email === email);
|
|
41
|
+
resolve(exists);
|
|
42
|
+
}, 100);
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* 检查手机号是否已存在
|
|
48
|
+
*/
|
|
49
|
+
async function checkPhoneExists(phone) {
|
|
50
|
+
return new Promise((resolve) => {
|
|
51
|
+
setTimeout(() => {
|
|
52
|
+
const exists = db.users.some(u => u.phone === phone);
|
|
53
|
+
resolve(exists);
|
|
54
|
+
}, 100);
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* 注册Schema定义(使用 v2.0.1 String扩展)
|
|
60
|
+
*/
|
|
61
|
+
function createRegisterSchema(lang = 'zh-CN') {
|
|
62
|
+
// 设置语言
|
|
63
|
+
Locale.setLocale(lang);
|
|
64
|
+
|
|
65
|
+
return dsl({
|
|
66
|
+
// ✨ 用户名:String扩展 + 自定义验证
|
|
67
|
+
username: 'string:3-32!'
|
|
68
|
+
.pattern(/^[a-zA-Z0-9_]+$/)
|
|
69
|
+
.label('用户名')
|
|
70
|
+
.messages({
|
|
71
|
+
'min': '{{#label}}长度不能少于{{#limit}}个字符',
|
|
72
|
+
'max': '{{#label}}长度不能超过{{#limit}}个字符',
|
|
73
|
+
'pattern': '{{#label}}只能包含字母、数字和下划线'
|
|
74
|
+
})
|
|
75
|
+
.custom(async (value) => {
|
|
76
|
+
const exists = await checkUsernameExists(value);
|
|
77
|
+
if (exists) {
|
|
78
|
+
return { error: 'username.exists', message: '用户名已被占用,请换一个试试' };
|
|
79
|
+
}
|
|
80
|
+
return true;
|
|
81
|
+
}),
|
|
82
|
+
|
|
83
|
+
// ✨ 邮箱:String扩展 + 异步验证
|
|
84
|
+
email: 'email!'
|
|
85
|
+
.label('邮箱地址')
|
|
86
|
+
.messages({
|
|
87
|
+
'format.email': '请输入有效的{{#label}}'
|
|
88
|
+
})
|
|
89
|
+
.custom(async (value) => {
|
|
90
|
+
const exists = await checkEmailExists(value);
|
|
91
|
+
if (exists) {
|
|
92
|
+
return { error: 'email.exists', message: '该邮箱已被注册' };
|
|
93
|
+
}
|
|
94
|
+
return true;
|
|
95
|
+
}),
|
|
96
|
+
|
|
97
|
+
// ✨ 密码:String扩展 + 复杂正则
|
|
98
|
+
password: 'string:8-64!'
|
|
99
|
+
.pattern(/^(?=.*[a-z])(?=.*[A-Z])(?=.*\d).+$/)
|
|
100
|
+
.label('密码')
|
|
101
|
+
.messages({
|
|
102
|
+
'min': '{{#label}}长度不能少于{{#limit}}位',
|
|
103
|
+
'max': '{{#label}}长度不能超过{{#limit}}位',
|
|
104
|
+
'pattern': '{{#label}}必须包含大小写字母和数字'
|
|
105
|
+
}),
|
|
106
|
+
|
|
107
|
+
// ✨ 确认密码:String扩展
|
|
108
|
+
confirmPassword: 'string!'
|
|
109
|
+
.label('确认密码')
|
|
110
|
+
.messages({
|
|
111
|
+
'password.mismatch': '两次输入的密码不一致'
|
|
112
|
+
})
|
|
113
|
+
.custom((value, context) => {
|
|
114
|
+
// 临时实现:手动检查password字段
|
|
115
|
+
if (context && context.password && value !== context.password) {
|
|
116
|
+
return { error: 'password.mismatch', message: '两次输入的密码不一致' };
|
|
117
|
+
}
|
|
118
|
+
return true;
|
|
119
|
+
})
|
|
120
|
+
.required(),
|
|
121
|
+
|
|
122
|
+
// ✨ 手机号:String扩展 + 异步验证
|
|
123
|
+
phone: 'string:11!'
|
|
124
|
+
.pattern(/^1[3-9]\d{9}$/)
|
|
125
|
+
.label('手机号')
|
|
126
|
+
.messages({
|
|
127
|
+
'pattern': '请输入正确的{{#label}}格式'
|
|
128
|
+
})
|
|
129
|
+
.custom(async (value) => {
|
|
130
|
+
const exists = await checkPhoneExists(value);
|
|
131
|
+
if (exists) {
|
|
132
|
+
return { error: 'phone.exists', message: '该手机号已被注册' };
|
|
133
|
+
}
|
|
134
|
+
return true;
|
|
135
|
+
}),
|
|
136
|
+
|
|
137
|
+
// 简单字段:纯DSL
|
|
138
|
+
agreeTerms: 'boolean!'
|
|
139
|
+
});
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
module.exports = {
|
|
143
|
+
createRegisterSchema,
|
|
144
|
+
db,
|
|
145
|
+
checkUsernameExists,
|
|
146
|
+
checkEmailExists,
|
|
147
|
+
checkPhoneExists
|
|
148
|
+
};
|
|
149
|
+
|
|
150
|
+
|