schema-dsl 2.0.0 → 2.0.1
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/CHANGELOG.md +130 -113
- package/LICENSE +21 -21
- package/README.md +628 -628
- package/dist/{DslBuilder-DkLaOo9Q.d.ts → DslBuilder-BIgQOAXp.d.ts} +2 -0
- package/dist/{DslBuilder-DQDN0ZxZ.d.cts → DslBuilder-CjHTucNQ.d.cts} +2 -0
- package/dist/{Validator-hFWKGxir.d.ts → Validator-CllRdrY0.d.ts} +1 -1
- package/dist/{Validator-C7GsVQOH.d.cts → Validator-D6okG9tr.d.cts} +1 -1
- package/dist/index.cjs +75 -29
- package/dist/index.d.cts +10 -4
- package/dist/index.d.ts +10 -4
- package/dist/index.js +75 -29
- package/dist/plugins/custom-format.cjs +33 -17
- package/dist/plugins/custom-format.d.cts +1 -1
- package/dist/plugins/custom-format.d.ts +1 -1
- package/dist/plugins/custom-format.js +33 -17
- package/dist/plugins/custom-type-example.cjs +33 -17
- package/dist/plugins/custom-type-example.d.cts +1 -1
- package/dist/plugins/custom-type-example.d.ts +1 -1
- package/dist/plugins/custom-type-example.js +33 -17
- package/dist/plugins/custom-validator.cjs +0 -2
- package/dist/plugins/custom-validator.d.cts +1 -1
- package/dist/plugins/custom-validator.d.ts +1 -1
- package/dist/plugins/custom-validator.js +0 -2
- package/docs/FEATURE-INDEX.md +553 -553
- package/docs/add-custom-locale.md +496 -496
- package/docs/add-keyword.md +24 -24
- package/docs/api-reference.md +1047 -1047
- package/docs/api.md +13 -13
- package/docs/best-practices-project-structure.md +417 -417
- package/docs/best-practices.md +712 -712
- package/docs/cache-manager.md +344 -344
- package/docs/compile.md +45 -45
- package/docs/conditional-api.md +1307 -1307
- package/docs/custom-extensions-guide.md +339 -339
- package/docs/design-philosophy.md +606 -606
- package/docs/doc-index.md +324 -324
- package/docs/dsl-syntax.md +714 -714
- package/docs/dynamic-locale.md +608 -608
- package/docs/enum.md +482 -482
- package/docs/error-handling.md +1975 -1975
- package/docs/export-guide.md +501 -501
- package/docs/export-limitations.md +567 -567
- package/docs/faq.md +596 -596
- package/docs/frontend-i18n-guide.md +307 -307
- package/docs/i18n-user-guide.md +487 -487
- package/docs/i18n.md +476 -476
- package/docs/index.md +48 -48
- package/docs/json-schema-basics.md +40 -40
- package/docs/label-vs-description.md +271 -271
- package/docs/markdown-exporter.md +406 -406
- package/docs/mongodb-exporter.md +302 -302
- package/docs/multi-language.md +26 -26
- package/docs/multi-type-support.md +322 -322
- package/docs/mysql-exporter.md +280 -280
- package/docs/number-operators.md +449 -449
- package/docs/optional-marker-guide.md +326 -326
- package/docs/performance-guide.md +49 -49
- package/docs/plugin-system.md +381 -381
- package/docs/plugin-type-registration.md +34 -34
- package/docs/postgresql-exporter.md +311 -311
- package/docs/public/favicon.svg +4 -4
- package/docs/quick-start.md +435 -435
- package/docs/runtime-locale-support.md +532 -532
- package/docs/schema-helper.md +345 -345
- package/docs/schema-utils-advanced-issues.md +23 -23
- package/docs/schema-utils-best-practices.md +20 -20
- package/docs/schema-utils-chaining.md +150 -150
- package/docs/schema-utils.md +524 -524
- package/docs/security-checklist.md +20 -20
- package/docs/string-extensions.md +488 -488
- package/docs/troubleshooting.md +486 -486
- package/docs/type-converter.md +310 -310
- package/docs/type-reference.md +242 -242
- package/docs/typescript-guide.md +584 -584
- package/docs/union-type-guide.md +157 -157
- package/docs/union-types.md +284 -284
- package/docs/validate-async.md +491 -491
- package/docs/validate-batch.md +49 -49
- package/docs/validate-dsl-object-support.md +578 -578
- package/docs/validate.md +506 -506
- package/docs/validation-guide.md +502 -502
- package/docs/validator.md +39 -39
- package/package.json +131 -131
- package/plugins/custom-format.cjs +8 -8
- package/plugins/custom-type-example.cjs +8 -8
- package/plugins/custom-validator.cjs +8 -8
- package/src/adapters/DslAdapter.ts +111 -111
- package/src/adapters/index.ts +1 -1
- package/src/config/constants.ts +83 -83
- package/src/config/index.ts +2 -2
- package/src/config/patterns.ts +77 -77
- package/src/core/CacheManager.ts +169 -159
- package/src/core/ConditionalBuilder.ts +382 -382
- package/src/core/ConditionalRuntime.ts +27 -27
- package/src/core/ConditionalValidator.ts +254 -254
- package/src/core/DslBuilder.ts +687 -677
- package/src/core/ErrorCodes.ts +38 -38
- package/src/core/ErrorFormatter.ts +271 -271
- package/src/core/JSONSchemaCore.ts +65 -65
- package/src/core/Locale.ts +187 -187
- package/src/core/MessageTemplate.ts +42 -42
- package/src/core/ObjectDslBuilder.ts +64 -64
- package/src/core/PluginManager.ts +326 -326
- package/src/core/StringExtensions.ts +140 -140
- package/src/core/TemplateEngine.ts +44 -44
- package/src/core/Validator.ts +448 -448
- package/src/errors/I18nError.ts +159 -159
- package/src/errors/ValidationError.ts +105 -105
- package/src/exporters/BaseExporter.ts +60 -60
- package/src/exporters/MarkdownExporter.ts +305 -305
- package/src/exporters/MongoDBExporter.ts +126 -126
- package/src/exporters/MySQLExporter.ts +156 -155
- package/src/exporters/PostgreSQLExporter.ts +222 -222
- package/src/exporters/index.ts +18 -18
- package/src/index.ts +651 -633
- package/src/locales/en-US.ts +160 -160
- package/src/locales/es-ES.ts +160 -160
- package/src/locales/fr-FR.ts +160 -160
- package/src/locales/index.ts +103 -103
- package/src/locales/ja-JP.ts +160 -160
- package/src/locales/types.ts +156 -156
- package/src/locales/zh-CN.ts +160 -160
- package/src/parser/ConstraintParser.ts +101 -101
- package/src/parser/DslParser.ts +470 -470
- package/src/parser/SchemaCompiler.ts +66 -66
- package/src/parser/TypeRegistry.ts +250 -250
- package/src/parser/index.ts +6 -6
- package/src/plugins/custom-format.ts +124 -126
- package/src/plugins/custom-type-example.ts +106 -108
- package/src/plugins/custom-validator.ts +138 -140
- package/src/types/conditional.ts +28 -28
- package/src/types/config.ts +59 -59
- package/src/types/dsl.ts +131 -131
- package/src/types/error.ts +60 -60
- package/src/types/index.ts +17 -17
- package/src/types/infer.ts +127 -127
- package/src/types/plugin.ts +58 -58
- package/src/types/safe-regex.d.ts +9 -9
- package/src/types/schema.ts +66 -66
- package/src/types/validate.ts +71 -71
- package/src/utils/SchemaHelper.ts +196 -196
- package/src/utils/SchemaUtils.ts +365 -346
- package/src/utils/TypeConverter.ts +215 -215
- package/src/utils/index.ts +10 -10
- package/src/validators/CustomKeywords.ts +477 -477
|
@@ -1,417 +1,417 @@
|
|
|
1
|
-
# Schema-DSL 项目最佳实践示例
|
|
2
|
-
|
|
3
|
-
## 推荐的项目结构
|
|
4
|
-
|
|
5
|
-
```text
|
|
6
|
-
your-project/
|
|
7
|
-
├── schemas/ # ✅ 所有 schema 定义(项目启动时加载)
|
|
8
|
-
│ ├── index.js # 统一导出
|
|
9
|
-
│ ├── user.js # 用户相关 schema
|
|
10
|
-
│ ├── order.js # 订单相关 schema
|
|
11
|
-
│ └── product.js # 产品相关 schema
|
|
12
|
-
├── routes/
|
|
13
|
-
│ ├── user.js # 用户路由(使用 schemas/user.js)
|
|
14
|
-
│ ├── order.js # 订单路由(使用 schemas/order.js)
|
|
15
|
-
│ └── product.js # 产品路由(使用 schemas/product.js)
|
|
16
|
-
└── app.js # 主应用入口
|
|
17
|
-
```
|
|
18
|
-
|
|
19
|
-
---
|
|
20
|
-
|
|
21
|
-
## 完整示例代码
|
|
22
|
-
|
|
23
|
-
### 1. 定义 Schema(schemas/user.js)
|
|
24
|
-
|
|
25
|
-
```javascript
|
|
26
|
-
const { dsl } = require('schema-dsl');
|
|
27
|
-
|
|
28
|
-
/**
|
|
29
|
-
* 用户相关的所有 schema
|
|
30
|
-
*
|
|
31
|
-
* ✅ 在项目启动时转换一次,后续直接复用
|
|
32
|
-
* ✅ 避免每次请求都重复转换
|
|
33
|
-
*/
|
|
34
|
-
const userSchemas = {
|
|
35
|
-
// 注册 schema
|
|
36
|
-
register: dsl({
|
|
37
|
-
username: dsl('string:3-32!')
|
|
38
|
-
.pattern(/^[a-zA-Z0-9_]+$/)
|
|
39
|
-
.label('用户名')
|
|
40
|
-
.messages({
|
|
41
|
-
'string.pattern': '用户名只能包含字母、数字和下划线',
|
|
42
|
-
'string.min': '用户名至少需要3个字符',
|
|
43
|
-
'string.max': '用户名最多32个字符'
|
|
44
|
-
}),
|
|
45
|
-
|
|
46
|
-
email: dsl('email!')
|
|
47
|
-
.label('邮箱')
|
|
48
|
-
.messages({
|
|
49
|
-
'string.email': '请输入有效的邮箱地址'
|
|
50
|
-
}),
|
|
51
|
-
|
|
52
|
-
password: dsl('string!').password('strong')
|
|
53
|
-
.label('密码')
|
|
54
|
-
.messages({
|
|
55
|
-
'string.password': '密码必须包含大小写字母、数字和特殊字符'
|
|
56
|
-
}),
|
|
57
|
-
|
|
58
|
-
age: 'number:18-120',
|
|
59
|
-
|
|
60
|
-
phone: dsl('phone')
|
|
61
|
-
.label('手机号')
|
|
62
|
-
.messages({
|
|
63
|
-
'string.phone': '请输入有效的手机号'
|
|
64
|
-
})
|
|
65
|
-
}),
|
|
66
|
-
|
|
67
|
-
// 登录 schema
|
|
68
|
-
login: dsl({
|
|
69
|
-
username: 'string!',
|
|
70
|
-
password: 'string!'
|
|
71
|
-
}),
|
|
72
|
-
|
|
73
|
-
// 更新个人资料 schema
|
|
74
|
-
updateProfile: dsl({
|
|
75
|
-
nickname: 'string:2-20',
|
|
76
|
-
avatar: 'url',
|
|
77
|
-
bio: 'string:0-500',
|
|
78
|
-
birthday: 'date',
|
|
79
|
-
gender: 'male|female|other'
|
|
80
|
-
}),
|
|
81
|
-
|
|
82
|
-
// 修改密码 schema
|
|
83
|
-
changePassword: dsl({
|
|
84
|
-
oldPassword: 'string!',
|
|
85
|
-
newPassword: dsl('string!').password('strong')
|
|
86
|
-
})
|
|
87
|
-
};
|
|
88
|
-
|
|
89
|
-
module.exports = userSchemas;
|
|
90
|
-
```
|
|
91
|
-
|
|
92
|
-
### 2. 定义 Schema(schemas/order.js)
|
|
93
|
-
|
|
94
|
-
```javascript
|
|
95
|
-
const { dsl } = require('schema-dsl');
|
|
96
|
-
|
|
97
|
-
const orderSchemas = {
|
|
98
|
-
// 创建订单
|
|
99
|
-
create: dsl({
|
|
100
|
-
items: 'array:1-100<object>!',
|
|
101
|
-
shippingAddress: dsl({
|
|
102
|
-
name: 'string:2-50!',
|
|
103
|
-
phone: 'phone!',
|
|
104
|
-
address: 'string:10-200!',
|
|
105
|
-
zipCode: 'string:6'
|
|
106
|
-
}),
|
|
107
|
-
paymentMethod: 'alipay|wechat|card!',
|
|
108
|
-
couponCode: 'string:6-20'
|
|
109
|
-
}),
|
|
110
|
-
|
|
111
|
-
// 更新订单状态
|
|
112
|
-
updateStatus: dsl({
|
|
113
|
-
status: 'pending|paid|shipped|completed|cancelled!',
|
|
114
|
-
note: 'string:0-500'
|
|
115
|
-
})
|
|
116
|
-
};
|
|
117
|
-
|
|
118
|
-
module.exports = orderSchemas;
|
|
119
|
-
```
|
|
120
|
-
|
|
121
|
-
### 3. 统一导出(schemas/index.js)
|
|
122
|
-
|
|
123
|
-
```javascript
|
|
124
|
-
/**
|
|
125
|
-
* 统一导出所有 schema
|
|
126
|
-
*
|
|
127
|
-
* 使用方式:
|
|
128
|
-
* const schemas = require('./schemas');
|
|
129
|
-
* const result = validate(schemas.user.register, data);
|
|
130
|
-
*/
|
|
131
|
-
module.exports = {
|
|
132
|
-
user: require('./user'),
|
|
133
|
-
order: require('./order'),
|
|
134
|
-
product: require('./product')
|
|
135
|
-
};
|
|
136
|
-
```
|
|
137
|
-
|
|
138
|
-
### 4. 在路由中使用(routes/user.js)
|
|
139
|
-
|
|
140
|
-
```javascript
|
|
141
|
-
const express = require('express');
|
|
142
|
-
const router = express.Router();
|
|
143
|
-
const { validate } = require('schema-dsl');
|
|
144
|
-
const userSchemas = require('../schemas/user');
|
|
145
|
-
|
|
146
|
-
/**
|
|
147
|
-
* 用户注册
|
|
148
|
-
*
|
|
149
|
-
* ✅ 使用预定义的 schema,不再重复转换
|
|
150
|
-
*/
|
|
151
|
-
router.post('/register', async (req, res) => {
|
|
152
|
-
// ✅ 直接使用,性能最优
|
|
153
|
-
const result = validate(userSchemas.register, req.body);
|
|
154
|
-
|
|
155
|
-
if (!result.valid) {
|
|
156
|
-
return res.status(400).json({
|
|
157
|
-
code: 'VALIDATION_ERROR',
|
|
158
|
-
message: '数据验证失败',
|
|
159
|
-
errors: result.errors
|
|
160
|
-
});
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
// 处理注册逻辑
|
|
164
|
-
try {
|
|
165
|
-
const user = await createUser(result.data);
|
|
166
|
-
res.status(201).json({
|
|
167
|
-
code: 'SUCCESS',
|
|
168
|
-
data: user
|
|
169
|
-
});
|
|
170
|
-
} catch (error) {
|
|
171
|
-
res.status(500).json({
|
|
172
|
-
code: 'SERVER_ERROR',
|
|
173
|
-
message: error.message
|
|
174
|
-
});
|
|
175
|
-
}
|
|
176
|
-
});
|
|
177
|
-
|
|
178
|
-
/**
|
|
179
|
-
* 用户登录
|
|
180
|
-
*/
|
|
181
|
-
router.post('/login', async (req, res) => {
|
|
182
|
-
// ✅ 直接使用
|
|
183
|
-
const result = validate(userSchemas.login, req.body);
|
|
184
|
-
|
|
185
|
-
if (!result.valid) {
|
|
186
|
-
return res.status(400).json({
|
|
187
|
-
code: 'VALIDATION_ERROR',
|
|
188
|
-
errors: result.errors
|
|
189
|
-
});
|
|
190
|
-
}
|
|
191
|
-
|
|
192
|
-
// 处理登录逻辑
|
|
193
|
-
// ...
|
|
194
|
-
});
|
|
195
|
-
|
|
196
|
-
/**
|
|
197
|
-
* 更新个人资料
|
|
198
|
-
*/
|
|
199
|
-
router.put('/profile', authenticate, async (req, res) => {
|
|
200
|
-
// ✅ 直接使用
|
|
201
|
-
const result = validate(userSchemas.updateProfile, req.body);
|
|
202
|
-
|
|
203
|
-
if (!result.valid) {
|
|
204
|
-
return res.status(400).json({
|
|
205
|
-
code: 'VALIDATION_ERROR',
|
|
206
|
-
errors: result.errors
|
|
207
|
-
});
|
|
208
|
-
}
|
|
209
|
-
|
|
210
|
-
// 处理更新逻辑
|
|
211
|
-
// ...
|
|
212
|
-
});
|
|
213
|
-
|
|
214
|
-
module.exports = router;
|
|
215
|
-
```
|
|
216
|
-
|
|
217
|
-
### 5. 主应用入口(app.js)
|
|
218
|
-
|
|
219
|
-
```javascript
|
|
220
|
-
const express = require('express');
|
|
221
|
-
const app = express();
|
|
222
|
-
|
|
223
|
-
// ✅ 在应用启动时加载所有 schema(只转换一次)
|
|
224
|
-
const schemas = require('./schemas');
|
|
225
|
-
console.log('✅ Schemas loaded:', Object.keys(schemas));
|
|
226
|
-
|
|
227
|
-
// 中间件
|
|
228
|
-
app.use(express.json());
|
|
229
|
-
|
|
230
|
-
// 路由
|
|
231
|
-
app.use('/api/user', require('./routes/user'));
|
|
232
|
-
app.use('/api/order', require('./routes/order'));
|
|
233
|
-
app.use('/api/product', require('./routes/product'));
|
|
234
|
-
|
|
235
|
-
// 启动服务
|
|
236
|
-
const PORT = process.env.PORT || 3000;
|
|
237
|
-
app.listen(PORT, () => {
|
|
238
|
-
console.log(`✅ Server started on port ${PORT}`);
|
|
239
|
-
console.log('✅ All schemas are loaded and ready to validate');
|
|
240
|
-
});
|
|
241
|
-
|
|
242
|
-
module.exports = app;
|
|
243
|
-
```
|
|
244
|
-
|
|
245
|
-
---
|
|
246
|
-
|
|
247
|
-
## 性能对比
|
|
248
|
-
|
|
249
|
-
### ❌ 不推荐:每次请求都转换
|
|
250
|
-
|
|
251
|
-
```javascript
|
|
252
|
-
// ❌ 错误示例
|
|
253
|
-
router.post('/register', (req, res) => {
|
|
254
|
-
const result = validate(
|
|
255
|
-
{ // ❌ 每次请求都转换
|
|
256
|
-
username: 'string:3-32!',
|
|
257
|
-
email: 'email!',
|
|
258
|
-
password: dsl('string!').password('strong')
|
|
259
|
-
},
|
|
260
|
-
req.body
|
|
261
|
-
);
|
|
262
|
-
// ...
|
|
263
|
-
});
|
|
264
|
-
```
|
|
265
|
-
|
|
266
|
-
**性能问题**:
|
|
267
|
-
- ❌ 每次请求都执行 DSL → JSON Schema 转换
|
|
268
|
-
- ❌ 1000 次请求 = 1000 次转换
|
|
269
|
-
- ❌ 高并发时性能损失明显
|
|
270
|
-
|
|
271
|
-
### ✅ 推荐:项目启动时转换
|
|
272
|
-
|
|
273
|
-
```javascript
|
|
274
|
-
// ✅ 正确示例
|
|
275
|
-
const userSchemas = require('../schemas/user'); // ✅ 启动时加载
|
|
276
|
-
|
|
277
|
-
router.post('/register', (req, res) => {
|
|
278
|
-
const result = validate(
|
|
279
|
-
userSchemas.register, // ✅ 直接使用
|
|
280
|
-
req.body
|
|
281
|
-
);
|
|
282
|
-
// ...
|
|
283
|
-
});
|
|
284
|
-
```
|
|
285
|
-
|
|
286
|
-
**性能优势**:
|
|
287
|
-
- ✅ 启动时转换 1 次
|
|
288
|
-
- ✅ 1000 次请求 = 0 次转换
|
|
289
|
-
- ✅ 高并发时性能最优
|
|
290
|
-
|
|
291
|
-
---
|
|
292
|
-
|
|
293
|
-
## 使用场景总结
|
|
294
|
-
|
|
295
|
-
| 场景 | 推荐方式 | 代码示例 | 原因 |
|
|
296
|
-
|------|---------|---------|------|
|
|
297
|
-
| **生产环境 API** | ✅ 项目启动时配置 | `const schemas = require('./schemas')` | 避免每次请求都转换 |
|
|
298
|
-
| **高并发服务** | ✅ 项目启动时配置 | 同上 | 3-5% 的性能损失会被放大 |
|
|
299
|
-
| **微服务** | ✅ 项目启动时配置 | 同上 | 保证响应时间稳定 |
|
|
300
|
-
| **单次脚本** | ✅ 直接用 DSL 对象(当前版便捷函数支持) | `validate({ email: 'email!' }, data)` | 只执行一次,性能影响可忽略 |
|
|
301
|
-
| **原型开发** | ✅ 直接用 DSL 对象(当前版便捷函数支持) | 同上 | 快速迭代,无需在意性能 |
|
|
302
|
-
| **测试代码** | ✅ 直接用 DSL 对象(当前版便捷函数支持) | 同上 | 简洁清晰,易于维护 |
|
|
303
|
-
|
|
304
|
-
---
|
|
305
|
-
|
|
306
|
-
## 常见错误
|
|
307
|
-
|
|
308
|
-
### ❌ 错误1:在路由文件中定义 schema
|
|
309
|
-
|
|
310
|
-
```javascript
|
|
311
|
-
// ❌ 不推荐
|
|
312
|
-
router.post('/register', (req, res) => {
|
|
313
|
-
const schema = dsl({ // ❌ 每次请求都创建
|
|
314
|
-
username: 'string:3-32!',
|
|
315
|
-
email: 'email!'
|
|
316
|
-
});
|
|
317
|
-
|
|
318
|
-
const result = validate(schema, req.body);
|
|
319
|
-
// ...
|
|
320
|
-
});
|
|
321
|
-
```
|
|
322
|
-
|
|
323
|
-
**问题**:每次请求都创建新的 schema 对象,浪费性能。
|
|
324
|
-
|
|
325
|
-
### ❌ 错误2:在函数内部定义 schema
|
|
326
|
-
|
|
327
|
-
```javascript
|
|
328
|
-
// ❌ 不推荐
|
|
329
|
-
function validateUser(data) {
|
|
330
|
-
const schema = dsl({ // ❌ 每次调用都创建
|
|
331
|
-
username: 'string:3-32!',
|
|
332
|
-
email: 'email!'
|
|
333
|
-
});
|
|
334
|
-
|
|
335
|
-
return validate(schema, data);
|
|
336
|
-
}
|
|
337
|
-
```
|
|
338
|
-
|
|
339
|
-
**问题**:每次调用函数都创建新的 schema,应该提到函数外部。
|
|
340
|
-
|
|
341
|
-
### ✅ 正确:在模块顶部定义
|
|
342
|
-
|
|
343
|
-
```javascript
|
|
344
|
-
// ✅ 推荐:模块加载时创建一次
|
|
345
|
-
const userSchema = dsl({
|
|
346
|
-
username: 'string:3-32!',
|
|
347
|
-
email: 'email!'
|
|
348
|
-
});
|
|
349
|
-
|
|
350
|
-
router.post('/register', (req, res) => {
|
|
351
|
-
const result = validate(userSchema, req.body); // ✅ 直接使用
|
|
352
|
-
// ...
|
|
353
|
-
});
|
|
354
|
-
```
|
|
355
|
-
|
|
356
|
-
---
|
|
357
|
-
|
|
358
|
-
## TypeScript 支持
|
|
359
|
-
|
|
360
|
-
```typescript
|
|
361
|
-
// schemas/user.ts
|
|
362
|
-
import { dsl } from 'schema-dsl';
|
|
363
|
-
|
|
364
|
-
export const userSchemas = {
|
|
365
|
-
register: dsl({
|
|
366
|
-
username: dsl('string:3-32!')
|
|
367
|
-
.pattern(/^[a-zA-Z0-9_]+$/)
|
|
368
|
-
.error({ pattern: '只能包含字母、数字和下划线' }),
|
|
369
|
-
email: 'email!',
|
|
370
|
-
password: dsl('string:8-64!')
|
|
371
|
-
.pattern(/^(?=.*[A-Za-z])(?=.*\d).{8,}$/)
|
|
372
|
-
.error({ pattern: '密码至少 8 位且必须包含字母和数字' }),
|
|
373
|
-
age: 'number:18-120'
|
|
374
|
-
}),
|
|
375
|
-
|
|
376
|
-
login: dsl({
|
|
377
|
-
username: 'string!',
|
|
378
|
-
password: 'string!'
|
|
379
|
-
})
|
|
380
|
-
};
|
|
381
|
-
|
|
382
|
-
// routes/user.ts
|
|
383
|
-
import { validate } from 'schema-dsl';
|
|
384
|
-
import { userSchemas } from '../schemas/user';
|
|
385
|
-
|
|
386
|
-
router.post('/register', (req, res) => {
|
|
387
|
-
const result = validate(userSchemas.register, req.body);
|
|
388
|
-
// ...
|
|
389
|
-
});
|
|
390
|
-
```
|
|
391
|
-
|
|
392
|
-
---
|
|
393
|
-
|
|
394
|
-
## 总结
|
|
395
|
-
|
|
396
|
-
**✅ 最佳实践**:
|
|
397
|
-
1. 在单独的 `schemas/` 目录定义所有 schema
|
|
398
|
-
2. 项目启动时加载,转换一次
|
|
399
|
-
3. 路由中直接使用,不再转换
|
|
400
|
-
4. 适合生产环境和高并发场景
|
|
401
|
-
|
|
402
|
-
**✅ 性能优势**:
|
|
403
|
-
- 避免每次请求都重复转换
|
|
404
|
-
- schema 复用,内存占用更小
|
|
405
|
-
- 响应时间更稳定
|
|
406
|
-
|
|
407
|
-
**✅ 代码优势**:
|
|
408
|
-
- 集中管理所有验证规则
|
|
409
|
-
- 易于维护和修改
|
|
410
|
-
- 类型安全(TypeScript)
|
|
411
|
-
|
|
412
|
-
---
|
|
413
|
-
|
|
414
|
-
## 对应示例文件
|
|
415
|
-
|
|
416
|
-
**示例入口**: [best-practices-project-structure.ts](https://github.com/vextjs/schema-dsl/blob/main/examples/docs/best-practices-project-structure.ts)
|
|
417
|
-
**说明**: 用一个最小的 `userSchemas` 对象模拟集中定义 / 路由复用结构,直接验证注册与登录两条路径。
|
|
1
|
+
# Schema-DSL 项目最佳实践示例
|
|
2
|
+
|
|
3
|
+
## 推荐的项目结构
|
|
4
|
+
|
|
5
|
+
```text
|
|
6
|
+
your-project/
|
|
7
|
+
├── schemas/ # ✅ 所有 schema 定义(项目启动时加载)
|
|
8
|
+
│ ├── index.js # 统一导出
|
|
9
|
+
│ ├── user.js # 用户相关 schema
|
|
10
|
+
│ ├── order.js # 订单相关 schema
|
|
11
|
+
│ └── product.js # 产品相关 schema
|
|
12
|
+
├── routes/
|
|
13
|
+
│ ├── user.js # 用户路由(使用 schemas/user.js)
|
|
14
|
+
│ ├── order.js # 订单路由(使用 schemas/order.js)
|
|
15
|
+
│ └── product.js # 产品路由(使用 schemas/product.js)
|
|
16
|
+
└── app.js # 主应用入口
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
---
|
|
20
|
+
|
|
21
|
+
## 完整示例代码
|
|
22
|
+
|
|
23
|
+
### 1. 定义 Schema(schemas/user.js)
|
|
24
|
+
|
|
25
|
+
```javascript
|
|
26
|
+
const { dsl } = require('schema-dsl');
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* 用户相关的所有 schema
|
|
30
|
+
*
|
|
31
|
+
* ✅ 在项目启动时转换一次,后续直接复用
|
|
32
|
+
* ✅ 避免每次请求都重复转换
|
|
33
|
+
*/
|
|
34
|
+
const userSchemas = {
|
|
35
|
+
// 注册 schema
|
|
36
|
+
register: dsl({
|
|
37
|
+
username: dsl('string:3-32!')
|
|
38
|
+
.pattern(/^[a-zA-Z0-9_]+$/)
|
|
39
|
+
.label('用户名')
|
|
40
|
+
.messages({
|
|
41
|
+
'string.pattern': '用户名只能包含字母、数字和下划线',
|
|
42
|
+
'string.min': '用户名至少需要3个字符',
|
|
43
|
+
'string.max': '用户名最多32个字符'
|
|
44
|
+
}),
|
|
45
|
+
|
|
46
|
+
email: dsl('email!')
|
|
47
|
+
.label('邮箱')
|
|
48
|
+
.messages({
|
|
49
|
+
'string.email': '请输入有效的邮箱地址'
|
|
50
|
+
}),
|
|
51
|
+
|
|
52
|
+
password: dsl('string!').password('strong')
|
|
53
|
+
.label('密码')
|
|
54
|
+
.messages({
|
|
55
|
+
'string.password': '密码必须包含大小写字母、数字和特殊字符'
|
|
56
|
+
}),
|
|
57
|
+
|
|
58
|
+
age: 'number:18-120',
|
|
59
|
+
|
|
60
|
+
phone: dsl('phone')
|
|
61
|
+
.label('手机号')
|
|
62
|
+
.messages({
|
|
63
|
+
'string.phone': '请输入有效的手机号'
|
|
64
|
+
})
|
|
65
|
+
}),
|
|
66
|
+
|
|
67
|
+
// 登录 schema
|
|
68
|
+
login: dsl({
|
|
69
|
+
username: 'string!',
|
|
70
|
+
password: 'string!'
|
|
71
|
+
}),
|
|
72
|
+
|
|
73
|
+
// 更新个人资料 schema
|
|
74
|
+
updateProfile: dsl({
|
|
75
|
+
nickname: 'string:2-20',
|
|
76
|
+
avatar: 'url',
|
|
77
|
+
bio: 'string:0-500',
|
|
78
|
+
birthday: 'date',
|
|
79
|
+
gender: 'male|female|other'
|
|
80
|
+
}),
|
|
81
|
+
|
|
82
|
+
// 修改密码 schema
|
|
83
|
+
changePassword: dsl({
|
|
84
|
+
oldPassword: 'string!',
|
|
85
|
+
newPassword: dsl('string!').password('strong')
|
|
86
|
+
})
|
|
87
|
+
};
|
|
88
|
+
|
|
89
|
+
module.exports = userSchemas;
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
### 2. 定义 Schema(schemas/order.js)
|
|
93
|
+
|
|
94
|
+
```javascript
|
|
95
|
+
const { dsl } = require('schema-dsl');
|
|
96
|
+
|
|
97
|
+
const orderSchemas = {
|
|
98
|
+
// 创建订单
|
|
99
|
+
create: dsl({
|
|
100
|
+
items: 'array:1-100<object>!',
|
|
101
|
+
shippingAddress: dsl({
|
|
102
|
+
name: 'string:2-50!',
|
|
103
|
+
phone: 'phone!',
|
|
104
|
+
address: 'string:10-200!',
|
|
105
|
+
zipCode: 'string:6'
|
|
106
|
+
}),
|
|
107
|
+
paymentMethod: 'alipay|wechat|card!',
|
|
108
|
+
couponCode: 'string:6-20'
|
|
109
|
+
}),
|
|
110
|
+
|
|
111
|
+
// 更新订单状态
|
|
112
|
+
updateStatus: dsl({
|
|
113
|
+
status: 'pending|paid|shipped|completed|cancelled!',
|
|
114
|
+
note: 'string:0-500'
|
|
115
|
+
})
|
|
116
|
+
};
|
|
117
|
+
|
|
118
|
+
module.exports = orderSchemas;
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
### 3. 统一导出(schemas/index.js)
|
|
122
|
+
|
|
123
|
+
```javascript
|
|
124
|
+
/**
|
|
125
|
+
* 统一导出所有 schema
|
|
126
|
+
*
|
|
127
|
+
* 使用方式:
|
|
128
|
+
* const schemas = require('./schemas');
|
|
129
|
+
* const result = validate(schemas.user.register, data);
|
|
130
|
+
*/
|
|
131
|
+
module.exports = {
|
|
132
|
+
user: require('./user'),
|
|
133
|
+
order: require('./order'),
|
|
134
|
+
product: require('./product')
|
|
135
|
+
};
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
### 4. 在路由中使用(routes/user.js)
|
|
139
|
+
|
|
140
|
+
```javascript
|
|
141
|
+
const express = require('express');
|
|
142
|
+
const router = express.Router();
|
|
143
|
+
const { validate } = require('schema-dsl');
|
|
144
|
+
const userSchemas = require('../schemas/user');
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* 用户注册
|
|
148
|
+
*
|
|
149
|
+
* ✅ 使用预定义的 schema,不再重复转换
|
|
150
|
+
*/
|
|
151
|
+
router.post('/register', async (req, res) => {
|
|
152
|
+
// ✅ 直接使用,性能最优
|
|
153
|
+
const result = validate(userSchemas.register, req.body);
|
|
154
|
+
|
|
155
|
+
if (!result.valid) {
|
|
156
|
+
return res.status(400).json({
|
|
157
|
+
code: 'VALIDATION_ERROR',
|
|
158
|
+
message: '数据验证失败',
|
|
159
|
+
errors: result.errors
|
|
160
|
+
});
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
// 处理注册逻辑
|
|
164
|
+
try {
|
|
165
|
+
const user = await createUser(result.data);
|
|
166
|
+
res.status(201).json({
|
|
167
|
+
code: 'SUCCESS',
|
|
168
|
+
data: user
|
|
169
|
+
});
|
|
170
|
+
} catch (error) {
|
|
171
|
+
res.status(500).json({
|
|
172
|
+
code: 'SERVER_ERROR',
|
|
173
|
+
message: error.message
|
|
174
|
+
});
|
|
175
|
+
}
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
/**
|
|
179
|
+
* 用户登录
|
|
180
|
+
*/
|
|
181
|
+
router.post('/login', async (req, res) => {
|
|
182
|
+
// ✅ 直接使用
|
|
183
|
+
const result = validate(userSchemas.login, req.body);
|
|
184
|
+
|
|
185
|
+
if (!result.valid) {
|
|
186
|
+
return res.status(400).json({
|
|
187
|
+
code: 'VALIDATION_ERROR',
|
|
188
|
+
errors: result.errors
|
|
189
|
+
});
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
// 处理登录逻辑
|
|
193
|
+
// ...
|
|
194
|
+
});
|
|
195
|
+
|
|
196
|
+
/**
|
|
197
|
+
* 更新个人资料
|
|
198
|
+
*/
|
|
199
|
+
router.put('/profile', authenticate, async (req, res) => {
|
|
200
|
+
// ✅ 直接使用
|
|
201
|
+
const result = validate(userSchemas.updateProfile, req.body);
|
|
202
|
+
|
|
203
|
+
if (!result.valid) {
|
|
204
|
+
return res.status(400).json({
|
|
205
|
+
code: 'VALIDATION_ERROR',
|
|
206
|
+
errors: result.errors
|
|
207
|
+
});
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
// 处理更新逻辑
|
|
211
|
+
// ...
|
|
212
|
+
});
|
|
213
|
+
|
|
214
|
+
module.exports = router;
|
|
215
|
+
```
|
|
216
|
+
|
|
217
|
+
### 5. 主应用入口(app.js)
|
|
218
|
+
|
|
219
|
+
```javascript
|
|
220
|
+
const express = require('express');
|
|
221
|
+
const app = express();
|
|
222
|
+
|
|
223
|
+
// ✅ 在应用启动时加载所有 schema(只转换一次)
|
|
224
|
+
const schemas = require('./schemas');
|
|
225
|
+
console.log('✅ Schemas loaded:', Object.keys(schemas));
|
|
226
|
+
|
|
227
|
+
// 中间件
|
|
228
|
+
app.use(express.json());
|
|
229
|
+
|
|
230
|
+
// 路由
|
|
231
|
+
app.use('/api/user', require('./routes/user'));
|
|
232
|
+
app.use('/api/order', require('./routes/order'));
|
|
233
|
+
app.use('/api/product', require('./routes/product'));
|
|
234
|
+
|
|
235
|
+
// 启动服务
|
|
236
|
+
const PORT = process.env.PORT || 3000;
|
|
237
|
+
app.listen(PORT, () => {
|
|
238
|
+
console.log(`✅ Server started on port ${PORT}`);
|
|
239
|
+
console.log('✅ All schemas are loaded and ready to validate');
|
|
240
|
+
});
|
|
241
|
+
|
|
242
|
+
module.exports = app;
|
|
243
|
+
```
|
|
244
|
+
|
|
245
|
+
---
|
|
246
|
+
|
|
247
|
+
## 性能对比
|
|
248
|
+
|
|
249
|
+
### ❌ 不推荐:每次请求都转换
|
|
250
|
+
|
|
251
|
+
```javascript
|
|
252
|
+
// ❌ 错误示例
|
|
253
|
+
router.post('/register', (req, res) => {
|
|
254
|
+
const result = validate(
|
|
255
|
+
{ // ❌ 每次请求都转换
|
|
256
|
+
username: 'string:3-32!',
|
|
257
|
+
email: 'email!',
|
|
258
|
+
password: dsl('string!').password('strong')
|
|
259
|
+
},
|
|
260
|
+
req.body
|
|
261
|
+
);
|
|
262
|
+
// ...
|
|
263
|
+
});
|
|
264
|
+
```
|
|
265
|
+
|
|
266
|
+
**性能问题**:
|
|
267
|
+
- ❌ 每次请求都执行 DSL → JSON Schema 转换
|
|
268
|
+
- ❌ 1000 次请求 = 1000 次转换
|
|
269
|
+
- ❌ 高并发时性能损失明显
|
|
270
|
+
|
|
271
|
+
### ✅ 推荐:项目启动时转换
|
|
272
|
+
|
|
273
|
+
```javascript
|
|
274
|
+
// ✅ 正确示例
|
|
275
|
+
const userSchemas = require('../schemas/user'); // ✅ 启动时加载
|
|
276
|
+
|
|
277
|
+
router.post('/register', (req, res) => {
|
|
278
|
+
const result = validate(
|
|
279
|
+
userSchemas.register, // ✅ 直接使用
|
|
280
|
+
req.body
|
|
281
|
+
);
|
|
282
|
+
// ...
|
|
283
|
+
});
|
|
284
|
+
```
|
|
285
|
+
|
|
286
|
+
**性能优势**:
|
|
287
|
+
- ✅ 启动时转换 1 次
|
|
288
|
+
- ✅ 1000 次请求 = 0 次转换
|
|
289
|
+
- ✅ 高并发时性能最优
|
|
290
|
+
|
|
291
|
+
---
|
|
292
|
+
|
|
293
|
+
## 使用场景总结
|
|
294
|
+
|
|
295
|
+
| 场景 | 推荐方式 | 代码示例 | 原因 |
|
|
296
|
+
|------|---------|---------|------|
|
|
297
|
+
| **生产环境 API** | ✅ 项目启动时配置 | `const schemas = require('./schemas')` | 避免每次请求都转换 |
|
|
298
|
+
| **高并发服务** | ✅ 项目启动时配置 | 同上 | 3-5% 的性能损失会被放大 |
|
|
299
|
+
| **微服务** | ✅ 项目启动时配置 | 同上 | 保证响应时间稳定 |
|
|
300
|
+
| **单次脚本** | ✅ 直接用 DSL 对象(当前版便捷函数支持) | `validate({ email: 'email!' }, data)` | 只执行一次,性能影响可忽略 |
|
|
301
|
+
| **原型开发** | ✅ 直接用 DSL 对象(当前版便捷函数支持) | 同上 | 快速迭代,无需在意性能 |
|
|
302
|
+
| **测试代码** | ✅ 直接用 DSL 对象(当前版便捷函数支持) | 同上 | 简洁清晰,易于维护 |
|
|
303
|
+
|
|
304
|
+
---
|
|
305
|
+
|
|
306
|
+
## 常见错误
|
|
307
|
+
|
|
308
|
+
### ❌ 错误1:在路由文件中定义 schema
|
|
309
|
+
|
|
310
|
+
```javascript
|
|
311
|
+
// ❌ 不推荐
|
|
312
|
+
router.post('/register', (req, res) => {
|
|
313
|
+
const schema = dsl({ // ❌ 每次请求都创建
|
|
314
|
+
username: 'string:3-32!',
|
|
315
|
+
email: 'email!'
|
|
316
|
+
});
|
|
317
|
+
|
|
318
|
+
const result = validate(schema, req.body);
|
|
319
|
+
// ...
|
|
320
|
+
});
|
|
321
|
+
```
|
|
322
|
+
|
|
323
|
+
**问题**:每次请求都创建新的 schema 对象,浪费性能。
|
|
324
|
+
|
|
325
|
+
### ❌ 错误2:在函数内部定义 schema
|
|
326
|
+
|
|
327
|
+
```javascript
|
|
328
|
+
// ❌ 不推荐
|
|
329
|
+
function validateUser(data) {
|
|
330
|
+
const schema = dsl({ // ❌ 每次调用都创建
|
|
331
|
+
username: 'string:3-32!',
|
|
332
|
+
email: 'email!'
|
|
333
|
+
});
|
|
334
|
+
|
|
335
|
+
return validate(schema, data);
|
|
336
|
+
}
|
|
337
|
+
```
|
|
338
|
+
|
|
339
|
+
**问题**:每次调用函数都创建新的 schema,应该提到函数外部。
|
|
340
|
+
|
|
341
|
+
### ✅ 正确:在模块顶部定义
|
|
342
|
+
|
|
343
|
+
```javascript
|
|
344
|
+
// ✅ 推荐:模块加载时创建一次
|
|
345
|
+
const userSchema = dsl({
|
|
346
|
+
username: 'string:3-32!',
|
|
347
|
+
email: 'email!'
|
|
348
|
+
});
|
|
349
|
+
|
|
350
|
+
router.post('/register', (req, res) => {
|
|
351
|
+
const result = validate(userSchema, req.body); // ✅ 直接使用
|
|
352
|
+
// ...
|
|
353
|
+
});
|
|
354
|
+
```
|
|
355
|
+
|
|
356
|
+
---
|
|
357
|
+
|
|
358
|
+
## TypeScript 支持
|
|
359
|
+
|
|
360
|
+
```typescript
|
|
361
|
+
// schemas/user.ts
|
|
362
|
+
import { dsl } from 'schema-dsl';
|
|
363
|
+
|
|
364
|
+
export const userSchemas = {
|
|
365
|
+
register: dsl({
|
|
366
|
+
username: dsl('string:3-32!')
|
|
367
|
+
.pattern(/^[a-zA-Z0-9_]+$/)
|
|
368
|
+
.error({ pattern: '只能包含字母、数字和下划线' }),
|
|
369
|
+
email: 'email!',
|
|
370
|
+
password: dsl('string:8-64!')
|
|
371
|
+
.pattern(/^(?=.*[A-Za-z])(?=.*\d).{8,}$/)
|
|
372
|
+
.error({ pattern: '密码至少 8 位且必须包含字母和数字' }),
|
|
373
|
+
age: 'number:18-120'
|
|
374
|
+
}),
|
|
375
|
+
|
|
376
|
+
login: dsl({
|
|
377
|
+
username: 'string!',
|
|
378
|
+
password: 'string!'
|
|
379
|
+
})
|
|
380
|
+
};
|
|
381
|
+
|
|
382
|
+
// routes/user.ts
|
|
383
|
+
import { validate } from 'schema-dsl';
|
|
384
|
+
import { userSchemas } from '../schemas/user';
|
|
385
|
+
|
|
386
|
+
router.post('/register', (req, res) => {
|
|
387
|
+
const result = validate(userSchemas.register, req.body);
|
|
388
|
+
// ...
|
|
389
|
+
});
|
|
390
|
+
```
|
|
391
|
+
|
|
392
|
+
---
|
|
393
|
+
|
|
394
|
+
## 总结
|
|
395
|
+
|
|
396
|
+
**✅ 最佳实践**:
|
|
397
|
+
1. 在单独的 `schemas/` 目录定义所有 schema
|
|
398
|
+
2. 项目启动时加载,转换一次
|
|
399
|
+
3. 路由中直接使用,不再转换
|
|
400
|
+
4. 适合生产环境和高并发场景
|
|
401
|
+
|
|
402
|
+
**✅ 性能优势**:
|
|
403
|
+
- 避免每次请求都重复转换
|
|
404
|
+
- schema 复用,内存占用更小
|
|
405
|
+
- 响应时间更稳定
|
|
406
|
+
|
|
407
|
+
**✅ 代码优势**:
|
|
408
|
+
- 集中管理所有验证规则
|
|
409
|
+
- 易于维护和修改
|
|
410
|
+
- 类型安全(TypeScript)
|
|
411
|
+
|
|
412
|
+
---
|
|
413
|
+
|
|
414
|
+
## 对应示例文件
|
|
415
|
+
|
|
416
|
+
**示例入口**: [best-practices-project-structure.ts](https://github.com/vextjs/schema-dsl/blob/main/examples/docs/best-practices-project-structure.ts)
|
|
417
|
+
**说明**: 用一个最小的 `userSchemas` 对象模拟集中定义 / 路由复用结构,直接验证注册与登录两条路径。
|