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,672 @@
|
|
|
1
|
+
# schema-dsl 最佳实践
|
|
2
|
+
|
|
3
|
+
> **用途**: 帮助你写出高质量、高性能的 Schema 代码
|
|
4
|
+
> **更新**: 2025-12-26
|
|
5
|
+
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
## 📑 目录
|
|
9
|
+
|
|
10
|
+
- [Schema 设计原则](#schema-设计原则)
|
|
11
|
+
- [性能优化](#性能优化)
|
|
12
|
+
- [安全性考虑](#安全性考虑)
|
|
13
|
+
- [错误处理](#错误处理)
|
|
14
|
+
- [代码组织](#代码组织)
|
|
15
|
+
- [生产环境建议](#生产环境建议)
|
|
16
|
+
|
|
17
|
+
---
|
|
18
|
+
|
|
19
|
+
## Schema 设计原则
|
|
20
|
+
|
|
21
|
+
### 1. 简单字段用纯 DSL
|
|
22
|
+
|
|
23
|
+
**推荐**:
|
|
24
|
+
```javascript
|
|
25
|
+
const schema = dsl({
|
|
26
|
+
username: 'string:3-32!',
|
|
27
|
+
age: 'number:18-120',
|
|
28
|
+
email: 'email!',
|
|
29
|
+
role: 'admin|user|guest'
|
|
30
|
+
});
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
**不推荐**(过度复杂):
|
|
34
|
+
```javascript
|
|
35
|
+
const schema = dsl({
|
|
36
|
+
username: dsl('string').minLength(3).maxLength(32).required(),
|
|
37
|
+
// 太冗长了!
|
|
38
|
+
});
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
**原则**: 能用 DSL 字符串表达的,就不要用链式调用。
|
|
42
|
+
|
|
43
|
+
---
|
|
44
|
+
|
|
45
|
+
### 2. 复杂验证用链式调用
|
|
46
|
+
|
|
47
|
+
**适合链式调用的场景**:
|
|
48
|
+
- 需要正则验证
|
|
49
|
+
- 需要自定义错误消息
|
|
50
|
+
- 需要自定义验证器
|
|
51
|
+
- 需要标签(label)
|
|
52
|
+
|
|
53
|
+
**示例**:
|
|
54
|
+
```javascript
|
|
55
|
+
const schema = dsl({
|
|
56
|
+
// 简单字段:纯 DSL
|
|
57
|
+
age: 'number:18-120',
|
|
58
|
+
|
|
59
|
+
// 复杂字段:链式调用
|
|
60
|
+
username: 'string:3-32!'
|
|
61
|
+
.pattern(/^[a-zA-Z0-9_]+$/)
|
|
62
|
+
.label('用户名')
|
|
63
|
+
.messages({
|
|
64
|
+
'pattern': '只能包含字母、数字和下划线',
|
|
65
|
+
'min': '至少3个字符',
|
|
66
|
+
'max': '最多32个字符'
|
|
67
|
+
}),
|
|
68
|
+
|
|
69
|
+
email: 'email!'
|
|
70
|
+
.custom(async (value) => {
|
|
71
|
+
const exists = await checkEmailExists(value);
|
|
72
|
+
if (exists) return '邮箱已被占用';
|
|
73
|
+
})
|
|
74
|
+
.label('邮箱地址')
|
|
75
|
+
});
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
---
|
|
79
|
+
|
|
80
|
+
### 3. 使用预设验证器
|
|
81
|
+
|
|
82
|
+
SchemaIO 提供了常用的预设验证器,开箱即用:
|
|
83
|
+
|
|
84
|
+
```javascript
|
|
85
|
+
const schema = dsl({
|
|
86
|
+
// ✅ 使用预设验证器(推荐)
|
|
87
|
+
username: dsl('string!').username(), // 自动设置 3-32 长度 + 正则
|
|
88
|
+
password: dsl('string!').password('strong'), // 强密码验证
|
|
89
|
+
phone: dsl('string!').phone('cn'), // 中国手机号
|
|
90
|
+
|
|
91
|
+
// ❌ 手动实现(不推荐)
|
|
92
|
+
username: 'string:3-32!'
|
|
93
|
+
.pattern(/^[a-zA-Z][a-zA-Z0-9_]*$/)
|
|
94
|
+
});
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
**可用预设**:
|
|
98
|
+
- `username(preset?)` - 用户名验证
|
|
99
|
+
- `password(strength?)` - 密码强度验证
|
|
100
|
+
- `phone(country?)` - 手机号验证
|
|
101
|
+
- `slug()` - URL slug 验证
|
|
102
|
+
|
|
103
|
+
---
|
|
104
|
+
|
|
105
|
+
### 4. 避免过深的嵌套
|
|
106
|
+
|
|
107
|
+
**不推荐**(嵌套过深):
|
|
108
|
+
```javascript
|
|
109
|
+
const schema = dsl({
|
|
110
|
+
user: {
|
|
111
|
+
profile: {
|
|
112
|
+
personal: {
|
|
113
|
+
address: {
|
|
114
|
+
detail: {
|
|
115
|
+
street: 'string' // 嵌套 5 层
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
});
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
**推荐**(拆分或扁平化):
|
|
125
|
+
```javascript
|
|
126
|
+
// 方案1: 拆分为多个 Schema
|
|
127
|
+
const addressSchema = dsl({
|
|
128
|
+
street: 'string!',
|
|
129
|
+
city: 'string!',
|
|
130
|
+
zipCode: 'string'
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
const userSchema = dsl({
|
|
134
|
+
name: 'string!',
|
|
135
|
+
email: 'email!',
|
|
136
|
+
address: addressSchema
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
// 方案2: 扁平化
|
|
140
|
+
const schema = dsl({
|
|
141
|
+
'user_name': 'string!',
|
|
142
|
+
'user_email': 'email!',
|
|
143
|
+
'address_street': 'string!',
|
|
144
|
+
'address_city': 'string!'
|
|
145
|
+
});
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
**原则**: 嵌套深度建议不超过 3-4 层。
|
|
149
|
+
|
|
150
|
+
---
|
|
151
|
+
|
|
152
|
+
## 性能优化
|
|
153
|
+
|
|
154
|
+
### 1. 预编译 Schema
|
|
155
|
+
|
|
156
|
+
**不推荐**(每次都编译):
|
|
157
|
+
```javascript
|
|
158
|
+
app.post('/api/user', (req, res) => {
|
|
159
|
+
const schema = dsl({ username: 'string!' });
|
|
160
|
+
const result = validate(schema, req.body); // 每次都编译
|
|
161
|
+
});
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
**推荐**(预编译):
|
|
165
|
+
```javascript
|
|
166
|
+
// 在应用启动时编译一次
|
|
167
|
+
const userSchema = dsl({ username: 'string!' });
|
|
168
|
+
const validateUser = validator.compile(userSchema);
|
|
169
|
+
|
|
170
|
+
app.post('/api/user', (req, res) => {
|
|
171
|
+
const result = validateUser(req.body); // 直接使用
|
|
172
|
+
});
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
**性能提升**: 预编译可以提升 **10-100 倍** 的性能!
|
|
176
|
+
|
|
177
|
+
---
|
|
178
|
+
|
|
179
|
+
### 2. 启用缓存
|
|
180
|
+
|
|
181
|
+
```javascript
|
|
182
|
+
const validator = new Validator({
|
|
183
|
+
cache: true // 启用编译缓存
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
// 或者使用全局单例(默认启用缓存)
|
|
187
|
+
const { validate } = require('schema-dsl');
|
|
188
|
+
validate(schema, data); // 自动缓存
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
---
|
|
192
|
+
|
|
193
|
+
### 3. 批量验证
|
|
194
|
+
|
|
195
|
+
**不推荐**(循环验证):
|
|
196
|
+
```javascript
|
|
197
|
+
const errors = [];
|
|
198
|
+
records.forEach(record => {
|
|
199
|
+
const result = validate(schema, record);
|
|
200
|
+
if (!result.valid) {
|
|
201
|
+
errors.push(result.errors);
|
|
202
|
+
}
|
|
203
|
+
});
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
**推荐**(批量验证):
|
|
207
|
+
```javascript
|
|
208
|
+
const result = validator.validateBatch(schema, records);
|
|
209
|
+
// 一次性验证所有记录,性能更好
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
---
|
|
213
|
+
|
|
214
|
+
### 4. 优化正则表达式
|
|
215
|
+
|
|
216
|
+
**不推荐**(可能导致 ReDoS):
|
|
217
|
+
```javascript
|
|
218
|
+
// 危险的正则:灾难性回溯
|
|
219
|
+
.pattern(/^(a+)+$/)
|
|
220
|
+
.pattern(/^(a*)*$/)
|
|
221
|
+
.pattern(/^(a|a)*$/)
|
|
222
|
+
```
|
|
223
|
+
|
|
224
|
+
**推荐**(安全高效):
|
|
225
|
+
```javascript
|
|
226
|
+
// 简单明确的正则
|
|
227
|
+
.pattern(/^[a-zA-Z0-9_]+$/)
|
|
228
|
+
.pattern(/^[a-z]{3,10}$/)
|
|
229
|
+
```
|
|
230
|
+
|
|
231
|
+
**工具**: 使用 [regexploit](https://www.npmjs.com/package/regexploit) 检测危险正则。
|
|
232
|
+
|
|
233
|
+
---
|
|
234
|
+
|
|
235
|
+
### 5. 避免在循环中创建 Schema
|
|
236
|
+
|
|
237
|
+
**不推荐**:
|
|
238
|
+
```javascript
|
|
239
|
+
records.forEach(record => {
|
|
240
|
+
const schema = dsl({ name: 'string!' }); // 每次都创建
|
|
241
|
+
validate(schema, record);
|
|
242
|
+
});
|
|
243
|
+
```
|
|
244
|
+
|
|
245
|
+
**推荐**:
|
|
246
|
+
```javascript
|
|
247
|
+
const schema = dsl({ name: 'string!' }); // 创建一次
|
|
248
|
+
records.forEach(record => {
|
|
249
|
+
validate(schema, record); // 重复使用
|
|
250
|
+
});
|
|
251
|
+
```
|
|
252
|
+
|
|
253
|
+
---
|
|
254
|
+
|
|
255
|
+
## 安全性考虑
|
|
256
|
+
|
|
257
|
+
### 1. 限制用户输入的正则
|
|
258
|
+
|
|
259
|
+
**危险**:
|
|
260
|
+
```javascript
|
|
261
|
+
// ❌ 用户控制的正则表达式
|
|
262
|
+
app.post('/api/validate', (req, res) => {
|
|
263
|
+
const pattern = req.body.pattern; // 用户输入
|
|
264
|
+
const schema = dsl('string').pattern(new RegExp(pattern)); // 危险!
|
|
265
|
+
});
|
|
266
|
+
```
|
|
267
|
+
|
|
268
|
+
**原因**: 用户可能输入恶意正则导致 ReDoS 攻击。
|
|
269
|
+
|
|
270
|
+
**安全做法**:
|
|
271
|
+
```javascript
|
|
272
|
+
// ✅ 使用预定义的正则
|
|
273
|
+
const ALLOWED_PATTERNS = {
|
|
274
|
+
username: /^[a-zA-Z0-9_]+$/,
|
|
275
|
+
email: /^[^\s@]+@[^\s@]+\.[^\s@]+$/
|
|
276
|
+
};
|
|
277
|
+
|
|
278
|
+
app.post('/api/validate', (req, res) => {
|
|
279
|
+
const patternName = req.body.pattern;
|
|
280
|
+
const pattern = ALLOWED_PATTERNS[patternName];
|
|
281
|
+
if (!pattern) {
|
|
282
|
+
return res.status(400).json({ error: 'Invalid pattern' });
|
|
283
|
+
}
|
|
284
|
+
const schema = dsl('string').pattern(pattern);
|
|
285
|
+
});
|
|
286
|
+
```
|
|
287
|
+
|
|
288
|
+
---
|
|
289
|
+
|
|
290
|
+
### 2. 清理错误消息
|
|
291
|
+
|
|
292
|
+
生产环境不要暴露敏感信息:
|
|
293
|
+
|
|
294
|
+
```javascript
|
|
295
|
+
// 开发环境
|
|
296
|
+
if (process.env.NODE_ENV === 'development') {
|
|
297
|
+
return res.status(400).json({
|
|
298
|
+
valid: false,
|
|
299
|
+
errors: result.errors // 详细错误
|
|
300
|
+
});
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
// 生产环境
|
|
304
|
+
return res.status(400).json({
|
|
305
|
+
valid: false,
|
|
306
|
+
message: '输入数据验证失败' // 简化消息
|
|
307
|
+
});
|
|
308
|
+
```
|
|
309
|
+
|
|
310
|
+
---
|
|
311
|
+
|
|
312
|
+
### 3. 限制 Schema 复杂度
|
|
313
|
+
|
|
314
|
+
```javascript
|
|
315
|
+
const validator = new Validator({
|
|
316
|
+
maxNestingDepth: 10, // 限制嵌套深度
|
|
317
|
+
maxSchemaSize: 10000 // 限制 Schema 大小(建议)
|
|
318
|
+
});
|
|
319
|
+
|
|
320
|
+
// 在 validate 前检查
|
|
321
|
+
DslBuilder.validateNestingDepth(schema, 10);
|
|
322
|
+
```
|
|
323
|
+
|
|
324
|
+
---
|
|
325
|
+
|
|
326
|
+
### 4. 防止原型污染
|
|
327
|
+
|
|
328
|
+
```javascript
|
|
329
|
+
// 验证数据时避免原型污染
|
|
330
|
+
const validator = new Validator({
|
|
331
|
+
removeAdditional: true, // 移除额外属性
|
|
332
|
+
useDefaults: false // 不自动填充默认值(如果不需要)
|
|
333
|
+
});
|
|
334
|
+
```
|
|
335
|
+
|
|
336
|
+
---
|
|
337
|
+
|
|
338
|
+
## 错误处理
|
|
339
|
+
|
|
340
|
+
### 1. 统一错误格式
|
|
341
|
+
|
|
342
|
+
**推荐的错误处理中间件**:
|
|
343
|
+
```javascript
|
|
344
|
+
// Express 中间件
|
|
345
|
+
function validateMiddleware(schema) {
|
|
346
|
+
return (req, res, next) => {
|
|
347
|
+
const result = validate(schema, req.body);
|
|
348
|
+
|
|
349
|
+
if (!result.valid) {
|
|
350
|
+
return res.status(400).json({
|
|
351
|
+
code: 'VALIDATION_ERROR',
|
|
352
|
+
message: '请求数据验证失败',
|
|
353
|
+
errors: result.errors.map(err => ({
|
|
354
|
+
field: err.path,
|
|
355
|
+
message: err.message
|
|
356
|
+
}))
|
|
357
|
+
});
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
next();
|
|
361
|
+
};
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
// 使用
|
|
365
|
+
app.post('/api/user',
|
|
366
|
+
validateMiddleware(userSchema),
|
|
367
|
+
userController.create
|
|
368
|
+
);
|
|
369
|
+
```
|
|
370
|
+
|
|
371
|
+
---
|
|
372
|
+
|
|
373
|
+
### 2. 友好的错误消息
|
|
374
|
+
|
|
375
|
+
**使用 label 和自定义消息**:
|
|
376
|
+
```javascript
|
|
377
|
+
const schema = dsl({
|
|
378
|
+
username: 'string:3-32!'
|
|
379
|
+
.label('用户名')
|
|
380
|
+
.messages({
|
|
381
|
+
'required': '{{#label}}不能为空',
|
|
382
|
+
'min': '{{#label}}至少需要{{#limit}}个字符',
|
|
383
|
+
'max': '{{#label}}最多{{#limit}}个字符',
|
|
384
|
+
'pattern': '{{#label}}格式不正确'
|
|
385
|
+
}),
|
|
386
|
+
|
|
387
|
+
email: 'email!'
|
|
388
|
+
.label('邮箱地址')
|
|
389
|
+
.messages({
|
|
390
|
+
'required': '请填写{{#label}}',
|
|
391
|
+
'format': '{{#label}}格式不正确'
|
|
392
|
+
})
|
|
393
|
+
});
|
|
394
|
+
```
|
|
395
|
+
|
|
396
|
+
**效果**:
|
|
397
|
+
```
|
|
398
|
+
❌ 用户名不能为空
|
|
399
|
+
❌ 用户名至少需要3个字符
|
|
400
|
+
✅ 清晰明了,用户友好
|
|
401
|
+
```
|
|
402
|
+
|
|
403
|
+
---
|
|
404
|
+
|
|
405
|
+
### 3. 处理异步验证错误
|
|
406
|
+
|
|
407
|
+
```javascript
|
|
408
|
+
const schema = dsl({
|
|
409
|
+
email: 'email!'.custom(async (value) => {
|
|
410
|
+
try {
|
|
411
|
+
const exists = await checkEmailExists(value);
|
|
412
|
+
if (exists) return '邮箱已被占用';
|
|
413
|
+
} catch (error) {
|
|
414
|
+
// 记录错误但不阻止验证
|
|
415
|
+
console.error('Email check failed:', error);
|
|
416
|
+
// 可以选择跳过此验证或返回提示
|
|
417
|
+
return; // 跳过
|
|
418
|
+
}
|
|
419
|
+
})
|
|
420
|
+
});
|
|
421
|
+
```
|
|
422
|
+
|
|
423
|
+
---
|
|
424
|
+
|
|
425
|
+
## 代码组织
|
|
426
|
+
|
|
427
|
+
### 1. 集中管理 Schema
|
|
428
|
+
|
|
429
|
+
**推荐的项目结构**:
|
|
430
|
+
```
|
|
431
|
+
src/
|
|
432
|
+
├── schemas/
|
|
433
|
+
│ ├── index.js # 导出所有 Schema
|
|
434
|
+
│ ├── user.schema.js # 用户相关 Schema
|
|
435
|
+
│ ├── post.schema.js # 文章相关 Schema
|
|
436
|
+
│ └── common.schema.js # 通用 Schema
|
|
437
|
+
├── routes/
|
|
438
|
+
│ ├── user.routes.js
|
|
439
|
+
│ └── post.routes.js
|
|
440
|
+
└── controllers/
|
|
441
|
+
```
|
|
442
|
+
|
|
443
|
+
**schemas/user.schema.js**:
|
|
444
|
+
```javascript
|
|
445
|
+
const { dsl } = require('schema-dsl');
|
|
446
|
+
|
|
447
|
+
// 可复用的字段
|
|
448
|
+
const commonFields = {
|
|
449
|
+
username: dsl('string!').username().label('用户名'),
|
|
450
|
+
email: 'email!',
|
|
451
|
+
password: dsl('string!').password('strong').label('密码')
|
|
452
|
+
};
|
|
453
|
+
|
|
454
|
+
// 注册 Schema
|
|
455
|
+
exports.registerSchema = dsl({
|
|
456
|
+
...commonFields,
|
|
457
|
+
confirmPassword: 'string!',
|
|
458
|
+
agreeTerms: 'boolean!'
|
|
459
|
+
});
|
|
460
|
+
|
|
461
|
+
// 登录 Schema
|
|
462
|
+
exports.loginSchema = dsl({
|
|
463
|
+
email: commonFields.email,
|
|
464
|
+
password: commonFields.password
|
|
465
|
+
});
|
|
466
|
+
|
|
467
|
+
// 更新 Schema
|
|
468
|
+
exports.updateSchema = dsl({
|
|
469
|
+
username: commonFields.username,
|
|
470
|
+
email: commonFields.email
|
|
471
|
+
// 不包含密码
|
|
472
|
+
});
|
|
473
|
+
```
|
|
474
|
+
|
|
475
|
+
**schemas/index.js**:
|
|
476
|
+
```javascript
|
|
477
|
+
const userSchemas = require('./user.schema');
|
|
478
|
+
const postSchemas = require('./post.schema');
|
|
479
|
+
|
|
480
|
+
module.exports = {
|
|
481
|
+
user: userSchemas,
|
|
482
|
+
post: postSchemas
|
|
483
|
+
};
|
|
484
|
+
```
|
|
485
|
+
|
|
486
|
+
**routes/user.routes.js**:
|
|
487
|
+
```javascript
|
|
488
|
+
const schemas = require('../schemas');
|
|
489
|
+
const { validate } = require('schema-dsl');
|
|
490
|
+
|
|
491
|
+
router.post('/register', (req, res) => {
|
|
492
|
+
const result = validate(schemas.user.registerSchema, req.body);
|
|
493
|
+
// ...
|
|
494
|
+
});
|
|
495
|
+
```
|
|
496
|
+
|
|
497
|
+
---
|
|
498
|
+
|
|
499
|
+
### 2. Schema 复用
|
|
500
|
+
|
|
501
|
+
**使用 SchemaHelper**:
|
|
502
|
+
```javascript
|
|
503
|
+
const { SchemaHelper } = require('schema-dsl');
|
|
504
|
+
|
|
505
|
+
// 创建可复用字段库
|
|
506
|
+
const fields = SchemaHelper.createLibrary({
|
|
507
|
+
email: 'email!',
|
|
508
|
+
phone: dsl('string!').phone('cn'),
|
|
509
|
+
password: dsl('string!').password('strong')
|
|
510
|
+
});
|
|
511
|
+
|
|
512
|
+
// 在多个 Schema 中复用
|
|
513
|
+
const registerSchema = dsl({
|
|
514
|
+
...fields.pick(['email', 'password']),
|
|
515
|
+
username: 'string:3-32!'
|
|
516
|
+
});
|
|
517
|
+
|
|
518
|
+
const profileSchema = dsl({
|
|
519
|
+
...fields.pick(['email', 'phone']),
|
|
520
|
+
bio: 'string:500'
|
|
521
|
+
});
|
|
522
|
+
```
|
|
523
|
+
|
|
524
|
+
---
|
|
525
|
+
|
|
526
|
+
## 生产环境建议
|
|
527
|
+
|
|
528
|
+
### 1. 环境配置
|
|
529
|
+
|
|
530
|
+
```javascript
|
|
531
|
+
// config/validator.js
|
|
532
|
+
const { Validator } = require('schema-dsl');
|
|
533
|
+
|
|
534
|
+
const config = {
|
|
535
|
+
development: {
|
|
536
|
+
verbose: true,
|
|
537
|
+
allErrors: true,
|
|
538
|
+
cache: false // 开发时不缓存,便于调试
|
|
539
|
+
},
|
|
540
|
+
production: {
|
|
541
|
+
verbose: false,
|
|
542
|
+
allErrors: false, // 只返回第一个错误
|
|
543
|
+
cache: true // 生产环境启用缓存
|
|
544
|
+
}
|
|
545
|
+
};
|
|
546
|
+
|
|
547
|
+
module.exports = new Validator(
|
|
548
|
+
config[process.env.NODE_ENV || 'development']
|
|
549
|
+
);
|
|
550
|
+
```
|
|
551
|
+
|
|
552
|
+
---
|
|
553
|
+
|
|
554
|
+
### 2. 监控和日志
|
|
555
|
+
|
|
556
|
+
```javascript
|
|
557
|
+
const validator = new Validator();
|
|
558
|
+
|
|
559
|
+
// 包装 validate 方法,添加监控
|
|
560
|
+
const originalValidate = validator.validate.bind(validator);
|
|
561
|
+
validator.validate = function(schema, data, options) {
|
|
562
|
+
const startTime = Date.now();
|
|
563
|
+
const result = originalValidate(schema, data, options);
|
|
564
|
+
const duration = Date.now() - startTime;
|
|
565
|
+
|
|
566
|
+
// 记录慢查询
|
|
567
|
+
if (duration > 100) {
|
|
568
|
+
console.warn(`Slow validation: ${duration}ms`);
|
|
569
|
+
}
|
|
570
|
+
|
|
571
|
+
// 记录验证失败
|
|
572
|
+
if (!result.valid) {
|
|
573
|
+
logger.info('Validation failed', {
|
|
574
|
+
errors: result.errors.length,
|
|
575
|
+
paths: result.errors.map(e => e.path)
|
|
576
|
+
});
|
|
577
|
+
}
|
|
578
|
+
|
|
579
|
+
return result;
|
|
580
|
+
};
|
|
581
|
+
```
|
|
582
|
+
|
|
583
|
+
---
|
|
584
|
+
|
|
585
|
+
### 3. 健康检查
|
|
586
|
+
|
|
587
|
+
```javascript
|
|
588
|
+
// routes/health.js
|
|
589
|
+
app.get('/health', (req, res) => {
|
|
590
|
+
const { validator } = require('../config/validator');
|
|
591
|
+
|
|
592
|
+
// 检查验证器是否正常
|
|
593
|
+
try {
|
|
594
|
+
const testSchema = dsl({ test: 'string!' });
|
|
595
|
+
const result = validator.validate(testSchema, { test: 'ok' });
|
|
596
|
+
|
|
597
|
+
if (!result.valid) {
|
|
598
|
+
throw new Error('Validator test failed');
|
|
599
|
+
}
|
|
600
|
+
|
|
601
|
+
res.json({
|
|
602
|
+
status: 'ok',
|
|
603
|
+
validator: 'operational',
|
|
604
|
+
cacheSize: validator.getCacheSize()
|
|
605
|
+
});
|
|
606
|
+
} catch (error) {
|
|
607
|
+
res.status(500).json({
|
|
608
|
+
status: 'error',
|
|
609
|
+
message: error.message
|
|
610
|
+
});
|
|
611
|
+
}
|
|
612
|
+
});
|
|
613
|
+
```
|
|
614
|
+
|
|
615
|
+
---
|
|
616
|
+
|
|
617
|
+
### 4. 定期维护
|
|
618
|
+
|
|
619
|
+
```javascript
|
|
620
|
+
// 定期清理缓存
|
|
621
|
+
const cron = require('node-cron');
|
|
622
|
+
|
|
623
|
+
// 每天凌晨清理一次
|
|
624
|
+
cron.schedule('0 0 * * *', () => {
|
|
625
|
+
validator.clearCache();
|
|
626
|
+
console.log('Validator cache cleared');
|
|
627
|
+
});
|
|
628
|
+
|
|
629
|
+
// 或者根据内存使用情况清理
|
|
630
|
+
setInterval(() => {
|
|
631
|
+
const memUsage = process.memoryUsage();
|
|
632
|
+
if (memUsage.heapUsed > 500 * 1024 * 1024) { // 超过 500MB
|
|
633
|
+
validator.clearCache();
|
|
634
|
+
}
|
|
635
|
+
}, 60000); // 每分钟检查一次
|
|
636
|
+
```
|
|
637
|
+
|
|
638
|
+
---
|
|
639
|
+
|
|
640
|
+
## 性能基准参考
|
|
641
|
+
|
|
642
|
+
基于 SchemaIO 的性能测试:
|
|
643
|
+
|
|
644
|
+
| 操作 | 性能指标 |
|
|
645
|
+
|------|---------|
|
|
646
|
+
| 简单验证(未缓存) | ~0.1ms |
|
|
647
|
+
| 简单验证(已缓存) | ~0.01ms |
|
|
648
|
+
| 复杂嵌套(未缓存) | ~1ms |
|
|
649
|
+
| 复杂嵌套(已缓存) | ~0.1ms |
|
|
650
|
+
| 批量验证(1000条) | ~100ms |
|
|
651
|
+
|
|
652
|
+
**结论**: 合理使用缓存可以提升 **10-100倍** 性能。
|
|
653
|
+
|
|
654
|
+
---
|
|
655
|
+
|
|
656
|
+
## 总结
|
|
657
|
+
|
|
658
|
+
遵循这些最佳实践,你的 SchemaIO 代码将具备:
|
|
659
|
+
|
|
660
|
+
✅ **高性能** - 通过预编译和缓存
|
|
661
|
+
✅ **高安全性** - 避免常见安全陷阱
|
|
662
|
+
✅ **高可维护性** - 清晰的代码组织
|
|
663
|
+
✅ **高可用性** - 完善的错误处理
|
|
664
|
+
|
|
665
|
+
---
|
|
666
|
+
|
|
667
|
+
## 延伸阅读
|
|
668
|
+
|
|
669
|
+
- [性能优化指南](performance-guide.md)(待创建)
|
|
670
|
+
- [安全检查清单](security-checklist.md)(待创建)
|
|
671
|
+
- [故障排查指南](troubleshooting.md)
|
|
672
|
+
|