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
package/docs/quick-start.md
CHANGED
|
@@ -1,435 +1,435 @@
|
|
|
1
|
-
# schema-dsl 快速上手
|
|
2
|
-
|
|
3
|
-
> **阅读时间**: 5分钟
|
|
4
|
-
> **目标**: 快速掌握 schema-dsl 核心用法
|
|
5
|
-
|
|
6
|
-
---
|
|
7
|
-
|
|
8
|
-
## 📑 目录
|
|
9
|
-
|
|
10
|
-
### 入门指南
|
|
11
|
-
- [🚀 安装](#-安装)
|
|
12
|
-
- [📖 5分钟快速入门](#-5分钟快速入门)
|
|
13
|
-
- [1. Hello World(30秒)](#1-hello-world30秒)
|
|
14
|
-
- [2. DSL 语法速查(1分钟)](#2-dsl-语法速查1分钟)
|
|
15
|
-
- [3. String 扩展(2分钟)](#3-string-扩展2分钟)
|
|
16
|
-
- [4. 完整示例(2分钟)](#4-完整示例2分钟)
|
|
17
|
-
|
|
18
|
-
### 进阶功能
|
|
19
|
-
- [🔧 自定义验证](#-自定义验证)
|
|
20
|
-
- [🗄️ 数据库导出](#️-数据库导出)
|
|
21
|
-
- [📚 下一步](#-下一步)
|
|
22
|
-
|
|
23
|
-
---
|
|
24
|
-
|
|
25
|
-
## 🚀 安装
|
|
26
|
-
|
|
27
|
-
```bash
|
|
28
|
-
npm install schema-dsl
|
|
29
|
-
```
|
|
30
|
-
|
|
31
|
-
> **Node.js 要求**:`>=18.0.0`
|
|
32
|
-
>
|
|
33
|
-
> 当前 TypeScript 重构版以 `Node.js >=18.0.0` 为唯一运行时基线,不再承诺旧 Node 版本兼容。
|
|
34
|
-
|
|
35
|
-
---
|
|
36
|
-
|
|
37
|
-
## 📖 5分钟快速入门
|
|
38
|
-
|
|
39
|
-
### 1. Hello World(30秒)
|
|
40
|
-
|
|
41
|
-
```javascript
|
|
42
|
-
const { dsl, validate } = require('schema-dsl');
|
|
43
|
-
|
|
44
|
-
// 定义Schema
|
|
45
|
-
const schema = dsl({
|
|
46
|
-
name: 'string:1-50!',
|
|
47
|
-
email: 'email!'
|
|
48
|
-
});
|
|
49
|
-
|
|
50
|
-
// 验证数据(使用便捷函数)
|
|
51
|
-
const result = validate(schema, {
|
|
52
|
-
name: '张三',
|
|
53
|
-
email: 'zhangsan@example.com'
|
|
54
|
-
});
|
|
55
|
-
|
|
56
|
-
console.log(result.valid); // true
|
|
57
|
-
```
|
|
58
|
-
|
|
59
|
-
**解释**:
|
|
60
|
-
- `'string:1-50!'` - 必填字符串,长度1-50
|
|
61
|
-
- `'email!'` - 必填邮箱
|
|
62
|
-
- `!` 表示必填
|
|
63
|
-
|
|
64
|
-
---
|
|
65
|
-
|
|
66
|
-
### 2. DSL 语法速查(1分钟)
|
|
67
|
-
|
|
68
|
-
```javascript
|
|
69
|
-
// 基本类型
|
|
70
|
-
'string' // 字符串
|
|
71
|
-
'number' // 数字
|
|
72
|
-
'integer' // 整数
|
|
73
|
-
'boolean' // 布尔值
|
|
74
|
-
'email' // 邮箱
|
|
75
|
-
'url' // URL
|
|
76
|
-
'date' // 日期
|
|
77
|
-
|
|
78
|
-
// 约束
|
|
79
|
-
'string:3-32' // 长度3-32(范围)
|
|
80
|
-
'string:100' // 最大长度100(简写)
|
|
81
|
-
'string:-100' // 最大长度100(明确写法)
|
|
82
|
-
'string:10-' // 最小长度10(无最大限制)
|
|
83
|
-
'number:18-120' // 范围18-120
|
|
84
|
-
|
|
85
|
-
// 必填
|
|
86
|
-
'string!' // 必填字符串
|
|
87
|
-
'email!' // 必填邮箱
|
|
88
|
-
|
|
89
|
-
// 枚举
|
|
90
|
-
'active|inactive|pending' // 三选一
|
|
91
|
-
|
|
92
|
-
// 数组
|
|
93
|
-
'array<string>' // 字符串数组
|
|
94
|
-
'array:1-10<string>' // 1-10个字符串
|
|
95
|
-
'array<string:1-50>' // 带约束的数组元素
|
|
96
|
-
```
|
|
97
|
-
|
|
98
|
-
**语法规则**:
|
|
99
|
-
- `type:max` → 最大值(简写)
|
|
100
|
-
- `type:min-max` → 范围
|
|
101
|
-
- `type:min-` → 只限最小
|
|
102
|
-
- `type:-max` → 只限最大
|
|
103
|
-
|
|
104
|
-
---
|
|
105
|
-
|
|
106
|
-
### 3. String 扩展(2分钟)
|
|
107
|
-
|
|
108
|
-
字符串支持链式调用:
|
|
109
|
-
|
|
110
|
-
```javascript
|
|
111
|
-
const schema = dsl({
|
|
112
|
-
// 字符串直接链式调用,无需 dsl() 包裹
|
|
113
|
-
email: 'email!'
|
|
114
|
-
.pattern(/custom/)
|
|
115
|
-
.label('邮箱地址'),
|
|
116
|
-
|
|
117
|
-
username: 'string:3-32!'
|
|
118
|
-
.pattern(/^[a-zA-Z0-9_]+$/)
|
|
119
|
-
.messages({
|
|
120
|
-
'pattern': '只能包含字母、数字和下划线'
|
|
121
|
-
})
|
|
122
|
-
.label('用户名'),
|
|
123
|
-
|
|
124
|
-
// 简单字段仍然可以用纯DSL
|
|
125
|
-
age: 'number:18-120',
|
|
126
|
-
role: 'user|admin'
|
|
127
|
-
});
|
|
128
|
-
```
|
|
129
|
-
|
|
130
|
-
**可用方法**:
|
|
131
|
-
- `.pattern(regex)` - 正则验证
|
|
132
|
-
- `.label(text)` - 字段标签
|
|
133
|
-
- `.messages(obj)` - 自定义消息
|
|
134
|
-
- `.description(text)` - 描述
|
|
135
|
-
- `.custom(fn)` - 自定义验证器
|
|
136
|
-
|
|
137
|
-
---
|
|
138
|
-
|
|
139
|
-
### 4. 完整示例(2分钟)
|
|
140
|
-
|
|
141
|
-
```javascript
|
|
142
|
-
const { dsl, validate } = require('schema-dsl');
|
|
143
|
-
|
|
144
|
-
// 定义用户注册Schema
|
|
145
|
-
const registerSchema = dsl({
|
|
146
|
-
// 用户名:正则验证
|
|
147
|
-
username: dsl('string:3-32!')
|
|
148
|
-
.pattern(/^[a-zA-Z0-9_]+$/)
|
|
149
|
-
.label('用户名')
|
|
150
|
-
.error({
|
|
151
|
-
pattern: '只能包含字母、数字和下划线'
|
|
152
|
-
}),
|
|
153
|
-
|
|
154
|
-
// 邮箱:标签
|
|
155
|
-
email: dsl('email!').label('邮箱地址'),
|
|
156
|
-
|
|
157
|
-
// 密码:复杂正则
|
|
158
|
-
password: dsl('string:8-64!')
|
|
159
|
-
.pattern(/^(?=.*[a-z])(?=.*[A-Z])(?=.*\d).+$/)
|
|
160
|
-
.label('密码')
|
|
161
|
-
.error({
|
|
162
|
-
pattern: '必须包含大小写字母和数字'
|
|
163
|
-
}),
|
|
164
|
-
|
|
165
|
-
// 简单字段
|
|
166
|
-
age: 'number:18-120',
|
|
167
|
-
role: 'user|admin'
|
|
168
|
-
});
|
|
169
|
-
|
|
170
|
-
// 验证数据
|
|
171
|
-
const testData = {
|
|
172
|
-
username: 'john_doe',
|
|
173
|
-
email: 'john@example.com',
|
|
174
|
-
password: 'Password123',
|
|
175
|
-
age: 25,
|
|
176
|
-
role: 'user'
|
|
177
|
-
};
|
|
178
|
-
|
|
179
|
-
const result = validate(registerSchema, testData);
|
|
180
|
-
|
|
181
|
-
if (result.valid) {
|
|
182
|
-
console.log('✅ 验证通过!');
|
|
183
|
-
} else {
|
|
184
|
-
console.log('❌ 验证失败:', result.errors);
|
|
185
|
-
}
|
|
186
|
-
```
|
|
187
|
-
|
|
188
|
-
---
|
|
189
|
-
|
|
190
|
-
## 💡 最佳实践
|
|
191
|
-
|
|
192
|
-
### 1. 简单字段用纯DSL
|
|
193
|
-
|
|
194
|
-
```javascript
|
|
195
|
-
const schema = dsl({
|
|
196
|
-
name: 'string:1-50!', // ✅ 简洁
|
|
197
|
-
age: 'number:18-120', // ✅ 清晰
|
|
198
|
-
role: 'user|admin' // ✅ 直观
|
|
199
|
-
});
|
|
200
|
-
```
|
|
201
|
-
|
|
202
|
-
### 2. 复杂字段用String扩展
|
|
203
|
-
|
|
204
|
-
```javascript
|
|
205
|
-
const schema = dsl({
|
|
206
|
-
email: 'email!'
|
|
207
|
-
.pattern(/custom/)
|
|
208
|
-
.messages({...})
|
|
209
|
-
.label('邮箱'),
|
|
210
|
-
|
|
211
|
-
username: 'string:3-32!'
|
|
212
|
-
.pattern(/^\w+$/)
|
|
213
|
-
.custom(checkExists)
|
|
214
|
-
});
|
|
215
|
-
```
|
|
216
|
-
|
|
217
|
-
### 3. 80/20 法则
|
|
218
|
-
|
|
219
|
-
**80%字段用纯DSL,20%字段用String扩展**
|
|
220
|
-
|
|
221
|
-
---
|
|
222
|
-
|
|
223
|
-
## 🎯 常见场景
|
|
224
|
-
|
|
225
|
-
### 表单验证
|
|
226
|
-
|
|
227
|
-
```javascript
|
|
228
|
-
const formSchema = dsl({
|
|
229
|
-
email: 'email!'.label('邮箱地址'),
|
|
230
|
-
password: 'string:8-64!'.label('密码'),
|
|
231
|
-
nickname: 'string:2-20'.label('昵称'),
|
|
232
|
-
bio: 'string:500',
|
|
233
|
-
website: 'url',
|
|
234
|
-
age: 'number:18-120',
|
|
235
|
-
gender: 'male|female|other'
|
|
236
|
-
});
|
|
237
|
-
```
|
|
238
|
-
|
|
239
|
-
### 自定义验证
|
|
240
|
-
|
|
241
|
-
> `.custom()` 当前仅支持同步函数;如果需要异步查重,请在 `validate()` / `validateAsync()` 通过后于业务层单独执行。
|
|
242
|
-
|
|
243
|
-
```javascript
|
|
244
|
-
const schema = dsl({
|
|
245
|
-
username: 'string:3-32!'
|
|
246
|
-
.custom((value) => {
|
|
247
|
-
if (value === 'admin') {
|
|
248
|
-
return '用户名已存在';
|
|
249
|
-
}
|
|
250
|
-
})
|
|
251
|
-
});
|
|
252
|
-
```
|
|
253
|
-
|
|
254
|
-
### 嵌套对象
|
|
255
|
-
|
|
256
|
-
```javascript
|
|
257
|
-
const schema = dsl({
|
|
258
|
-
user: {
|
|
259
|
-
profile: {
|
|
260
|
-
name: 'string:1-50!'.label('姓名'),
|
|
261
|
-
avatar: 'url'.label('头像'),
|
|
262
|
-
social: {
|
|
263
|
-
twitter: 'url'.pattern(/twitter\.com/),
|
|
264
|
-
github: 'url'.pattern(/github\.com/)
|
|
265
|
-
}
|
|
266
|
-
}
|
|
267
|
-
}
|
|
268
|
-
});
|
|
269
|
-
```
|
|
270
|
-
|
|
271
|
-
---
|
|
272
|
-
|
|
273
|
-
## 📚 下一步
|
|
274
|
-
|
|
275
|
-
### 深入学习
|
|
276
|
-
|
|
277
|
-
- [DSL 语法完整指南](./dsl-syntax.md)
|
|
278
|
-
- [API 参考文档](./api-reference.md)
|
|
279
|
-
- [String 扩展文档](./string-extensions.md)
|
|
280
|
-
|
|
281
|
-
### 示例代码
|
|
282
|
-
|
|
283
|
-
- [Quick Start 完整示例](https://github.com/vextjs/schema-dsl/blob/main/examples/docs/quick-start.ts)
|
|
284
|
-
|
|
285
|
-
其余主题示例现在都已分别挂到各自文档底部,并统一切到稳定 GitHub 示例链接。
|
|
286
|
-
|
|
287
|
-
### 高级功能
|
|
288
|
-
|
|
289
|
-
- [自定义验证器](./api-reference.md#custom)
|
|
290
|
-
- [条件验证(when)](./api-reference.md#when)
|
|
291
|
-
- [数据库Schema导出](./api-reference.md#导出器)
|
|
292
|
-
|
|
293
|
-
---
|
|
294
|
-
|
|
295
|
-
## 🎯 设计理念与性能
|
|
296
|
-
|
|
297
|
-
### 为什么选择运行时解析?
|
|
298
|
-
|
|
299
|
-
Schema-DSL 使用**运行时解析 DSL**,而非编译时构建(如 Zod),这是有意的设计选择:
|
|
300
|
-
|
|
301
|
-
#### ✅ 运行时解析的优势
|
|
302
|
-
|
|
303
|
-
1. **完全动态** - 验证规则可以从配置文件、数据库动态加载
|
|
304
|
-
```javascript
|
|
305
|
-
// 从配置读取规则
|
|
306
|
-
const rules = await db.findOne({ entity: 'user' });
|
|
307
|
-
const schema = dsl({
|
|
308
|
-
username: `string:${rules.min}-${rules.max}!`
|
|
309
|
-
});
|
|
310
|
-
```
|
|
311
|
-
|
|
312
|
-
2. **多租户支持** - 每个租户可以有不同的验证规则
|
|
313
|
-
```javascript
|
|
314
|
-
// 租户A: 用户名3-32字符
|
|
315
|
-
// 租户B: 用户名5-50字符
|
|
316
|
-
function getTenantSchema(tenantId) {
|
|
317
|
-
const rules = tenantConfig[tenantId];
|
|
318
|
-
return dsl({
|
|
319
|
-
username: `string:${rules.min}-${rules.max}!`
|
|
320
|
-
});
|
|
321
|
-
}
|
|
322
|
-
```
|
|
323
|
-
|
|
324
|
-
3. **可序列化** - DSL 字符串可以存储、传输、共享
|
|
325
|
-
```javascript
|
|
326
|
-
// 存储到数据库
|
|
327
|
-
await db.insert({
|
|
328
|
-
formId: 'register',
|
|
329
|
-
rules: { username: 'string:3-32!', email: 'email!' }
|
|
330
|
-
});
|
|
331
|
-
|
|
332
|
-
// 通过 API 传输
|
|
333
|
-
res.json({ validationRules: rules });
|
|
334
|
-
|
|
335
|
-
// 前后端共享规则
|
|
336
|
-
```
|
|
337
|
-
|
|
338
|
-
4. **低代码基础** - 支持可视化表单构建器
|
|
339
|
-
```javascript
|
|
340
|
-
// 管理员在界面配置验证规则
|
|
341
|
-
const formBuilder = {
|
|
342
|
-
fields: [
|
|
343
|
-
{ name: 'username', validation: 'string:3-32!' }
|
|
344
|
-
]
|
|
345
|
-
};
|
|
346
|
-
```
|
|
347
|
-
|
|
348
|
-
#### ⚠️ 性能权衡
|
|
349
|
-
|
|
350
|
-
S1 有效数据与 Zod 持平,S3 嵌套场景快约 28%,无效数据公平对比快 89x:
|
|
351
|
-
|
|
352
|
-
| 库名 | 性能 | 场景 |
|
|
353
|
-
|------|-----------|------|
|
|
354
|
-
| Ajv (raw) | 4.732M ops/s | 底层引擎,无 DSL 层 |
|
|
355
|
-
| **Schema-DSL** | **1.301M ops/s**(S1有效) | 全功能(DSL + i18n + coerce)|
|
|
356
|
-
| **Schema-DSL** | **1.205M ops/s**(S2 无效,均无 i18n)| 公平对比(均无 i18n)|
|
|
357
|
-
| Zod | 1.305M ops/s(S1有效)/ 13.49K(S2 无效)| 编译时构建,错误路径异常驱动 |
|
|
358
|
-
| Joi | 154K ops/s | 功能丰富 |
|
|
359
|
-
|
|
360
|
-
**结论**:
|
|
361
|
-
- ✅ S3 嵌套有效场景比 Zod 快 **28%**;S1 简单有效场景持平(差 <1%)
|
|
362
|
-
- ✅ 无效数据公平对比(均无 i18n)比 Zod 快 **89x**
|
|
363
|
-
- ✅ 内置缓存,热路径零解析开销
|
|
364
|
-
|
|
365
|
-
### 适用场景
|
|
366
|
-
|
|
367
|
-
**✅ 选择 Schema-DSL**:
|
|
368
|
-
- 需要动态验证规则(配置驱动、多租户)
|
|
369
|
-
- 需要数据库 Schema 导出
|
|
370
|
-
- 快速开发原型
|
|
371
|
-
- 多语言 SaaS 系统
|
|
372
|
-
|
|
373
|
-
**⚠️ 考虑其他库**:
|
|
374
|
-
- TypeScript 项目需要强类型推断 → **Zod**
|
|
375
|
-
- 性能是第一优先级 → **Ajv** 或 **Zod**
|
|
376
|
-
- 静态验证规则 → **Zod**
|
|
377
|
-
|
|
378
|
-
---
|
|
379
|
-
|
|
380
|
-
## 🆘 常见问题
|
|
381
|
-
|
|
382
|
-
### Q: String扩展和纯DSL有什么区别?
|
|
383
|
-
|
|
384
|
-
**A**:
|
|
385
|
-
- **纯DSL**: 适合简单字段,语法简洁
|
|
386
|
-
- **String扩展**: 适合复杂验证,支持链式调用
|
|
387
|
-
|
|
388
|
-
```javascript
|
|
389
|
-
// 纯DSL(简单)
|
|
390
|
-
name: 'string:1-50!'
|
|
391
|
-
|
|
392
|
-
// String扩展(复杂)
|
|
393
|
-
email: 'email!'
|
|
394
|
-
.pattern(/custom/)
|
|
395
|
-
.messages({...})
|
|
396
|
-
```
|
|
397
|
-
|
|
398
|
-
### Q: 如何禁用String扩展?
|
|
399
|
-
|
|
400
|
-
**A**:
|
|
401
|
-
```javascript
|
|
402
|
-
const { uninstallStringExtensions } = require('schema-dsl');
|
|
403
|
-
uninstallStringExtensions();
|
|
404
|
-
```
|
|
405
|
-
|
|
406
|
-
### Q: 支持TypeScript吗?
|
|
407
|
-
|
|
408
|
-
**A**: 支持!schema-dsl 提供完整的 TypeScript 类型定义。
|
|
409
|
-
|
|
410
|
-
---
|
|
411
|
-
|
|
412
|
-
## 🎉 恭喜!
|
|
413
|
-
|
|
414
|
-
你已经掌握了 schema-dsl 的核心用法!
|
|
415
|
-
|
|
416
|
-
**核心要点**:
|
|
417
|
-
1. ✅ DSL语法简洁直观
|
|
418
|
-
2. ✅ String扩展强大灵活
|
|
419
|
-
3. ✅ 80%用DSL,20%用扩展
|
|
420
|
-
4. ✅ 字符串可以直接链式调用
|
|
421
|
-
|
|
422
|
-
**开始使用**: `npm install schema-dsl`
|
|
423
|
-
|
|
424
|
-
---
|
|
425
|
-
|
|
426
|
-
## 对应示例文件
|
|
427
|
-
|
|
428
|
-
**示例入口**: [quick-start.ts](https://github.com/vextjs/schema-dsl/blob/main/examples/docs/quick-start.ts)
|
|
429
|
-
**说明**: 覆盖快速上手中的 Hello World、String 扩展、用户注册示例,以及 `validate()` 与 `Validator.compile()` 的基础复用路径,可直接运行参考。
|
|
430
|
-
|
|
431
|
-
---
|
|
432
|
-
|
|
433
|
-
**最后更新**: 2026-05-08
|
|
434
|
-
|
|
435
|
-
|
|
1
|
+
# schema-dsl 快速上手
|
|
2
|
+
|
|
3
|
+
> **阅读时间**: 5分钟
|
|
4
|
+
> **目标**: 快速掌握 schema-dsl 核心用法
|
|
5
|
+
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
## 📑 目录
|
|
9
|
+
|
|
10
|
+
### 入门指南
|
|
11
|
+
- [🚀 安装](#-安装)
|
|
12
|
+
- [📖 5分钟快速入门](#-5分钟快速入门)
|
|
13
|
+
- [1. Hello World(30秒)](#1-hello-world30秒)
|
|
14
|
+
- [2. DSL 语法速查(1分钟)](#2-dsl-语法速查1分钟)
|
|
15
|
+
- [3. String 扩展(2分钟)](#3-string-扩展2分钟)
|
|
16
|
+
- [4. 完整示例(2分钟)](#4-完整示例2分钟)
|
|
17
|
+
|
|
18
|
+
### 进阶功能
|
|
19
|
+
- [🔧 自定义验证](#-自定义验证)
|
|
20
|
+
- [🗄️ 数据库导出](#️-数据库导出)
|
|
21
|
+
- [📚 下一步](#-下一步)
|
|
22
|
+
|
|
23
|
+
---
|
|
24
|
+
|
|
25
|
+
## 🚀 安装
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
npm install schema-dsl
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
> **Node.js 要求**:`>=18.0.0`
|
|
32
|
+
>
|
|
33
|
+
> 当前 TypeScript 重构版以 `Node.js >=18.0.0` 为唯一运行时基线,不再承诺旧 Node 版本兼容。
|
|
34
|
+
|
|
35
|
+
---
|
|
36
|
+
|
|
37
|
+
## 📖 5分钟快速入门
|
|
38
|
+
|
|
39
|
+
### 1. Hello World(30秒)
|
|
40
|
+
|
|
41
|
+
```javascript
|
|
42
|
+
const { dsl, validate } = require('schema-dsl');
|
|
43
|
+
|
|
44
|
+
// 定义Schema
|
|
45
|
+
const schema = dsl({
|
|
46
|
+
name: 'string:1-50!',
|
|
47
|
+
email: 'email!'
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
// 验证数据(使用便捷函数)
|
|
51
|
+
const result = validate(schema, {
|
|
52
|
+
name: '张三',
|
|
53
|
+
email: 'zhangsan@example.com'
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
console.log(result.valid); // true
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
**解释**:
|
|
60
|
+
- `'string:1-50!'` - 必填字符串,长度1-50
|
|
61
|
+
- `'email!'` - 必填邮箱
|
|
62
|
+
- `!` 表示必填
|
|
63
|
+
|
|
64
|
+
---
|
|
65
|
+
|
|
66
|
+
### 2. DSL 语法速查(1分钟)
|
|
67
|
+
|
|
68
|
+
```javascript
|
|
69
|
+
// 基本类型
|
|
70
|
+
'string' // 字符串
|
|
71
|
+
'number' // 数字
|
|
72
|
+
'integer' // 整数
|
|
73
|
+
'boolean' // 布尔值
|
|
74
|
+
'email' // 邮箱
|
|
75
|
+
'url' // URL
|
|
76
|
+
'date' // 日期
|
|
77
|
+
|
|
78
|
+
// 约束
|
|
79
|
+
'string:3-32' // 长度3-32(范围)
|
|
80
|
+
'string:100' // 最大长度100(简写)
|
|
81
|
+
'string:-100' // 最大长度100(明确写法)
|
|
82
|
+
'string:10-' // 最小长度10(无最大限制)
|
|
83
|
+
'number:18-120' // 范围18-120
|
|
84
|
+
|
|
85
|
+
// 必填
|
|
86
|
+
'string!' // 必填字符串
|
|
87
|
+
'email!' // 必填邮箱
|
|
88
|
+
|
|
89
|
+
// 枚举
|
|
90
|
+
'active|inactive|pending' // 三选一
|
|
91
|
+
|
|
92
|
+
// 数组
|
|
93
|
+
'array<string>' // 字符串数组
|
|
94
|
+
'array:1-10<string>' // 1-10个字符串
|
|
95
|
+
'array<string:1-50>' // 带约束的数组元素
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
**语法规则**:
|
|
99
|
+
- `type:max` → 最大值(简写)
|
|
100
|
+
- `type:min-max` → 范围
|
|
101
|
+
- `type:min-` → 只限最小
|
|
102
|
+
- `type:-max` → 只限最大
|
|
103
|
+
|
|
104
|
+
---
|
|
105
|
+
|
|
106
|
+
### 3. String 扩展(2分钟)
|
|
107
|
+
|
|
108
|
+
字符串支持链式调用:
|
|
109
|
+
|
|
110
|
+
```javascript
|
|
111
|
+
const schema = dsl({
|
|
112
|
+
// 字符串直接链式调用,无需 dsl() 包裹
|
|
113
|
+
email: 'email!'
|
|
114
|
+
.pattern(/custom/)
|
|
115
|
+
.label('邮箱地址'),
|
|
116
|
+
|
|
117
|
+
username: 'string:3-32!'
|
|
118
|
+
.pattern(/^[a-zA-Z0-9_]+$/)
|
|
119
|
+
.messages({
|
|
120
|
+
'pattern': '只能包含字母、数字和下划线'
|
|
121
|
+
})
|
|
122
|
+
.label('用户名'),
|
|
123
|
+
|
|
124
|
+
// 简单字段仍然可以用纯DSL
|
|
125
|
+
age: 'number:18-120',
|
|
126
|
+
role: 'user|admin'
|
|
127
|
+
});
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
**可用方法**:
|
|
131
|
+
- `.pattern(regex)` - 正则验证
|
|
132
|
+
- `.label(text)` - 字段标签
|
|
133
|
+
- `.messages(obj)` - 自定义消息
|
|
134
|
+
- `.description(text)` - 描述
|
|
135
|
+
- `.custom(fn)` - 自定义验证器
|
|
136
|
+
|
|
137
|
+
---
|
|
138
|
+
|
|
139
|
+
### 4. 完整示例(2分钟)
|
|
140
|
+
|
|
141
|
+
```javascript
|
|
142
|
+
const { dsl, validate } = require('schema-dsl');
|
|
143
|
+
|
|
144
|
+
// 定义用户注册Schema
|
|
145
|
+
const registerSchema = dsl({
|
|
146
|
+
// 用户名:正则验证
|
|
147
|
+
username: dsl('string:3-32!')
|
|
148
|
+
.pattern(/^[a-zA-Z0-9_]+$/)
|
|
149
|
+
.label('用户名')
|
|
150
|
+
.error({
|
|
151
|
+
pattern: '只能包含字母、数字和下划线'
|
|
152
|
+
}),
|
|
153
|
+
|
|
154
|
+
// 邮箱:标签
|
|
155
|
+
email: dsl('email!').label('邮箱地址'),
|
|
156
|
+
|
|
157
|
+
// 密码:复杂正则
|
|
158
|
+
password: dsl('string:8-64!')
|
|
159
|
+
.pattern(/^(?=.*[a-z])(?=.*[A-Z])(?=.*\d).+$/)
|
|
160
|
+
.label('密码')
|
|
161
|
+
.error({
|
|
162
|
+
pattern: '必须包含大小写字母和数字'
|
|
163
|
+
}),
|
|
164
|
+
|
|
165
|
+
// 简单字段
|
|
166
|
+
age: 'number:18-120',
|
|
167
|
+
role: 'user|admin'
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
// 验证数据
|
|
171
|
+
const testData = {
|
|
172
|
+
username: 'john_doe',
|
|
173
|
+
email: 'john@example.com',
|
|
174
|
+
password: 'Password123',
|
|
175
|
+
age: 25,
|
|
176
|
+
role: 'user'
|
|
177
|
+
};
|
|
178
|
+
|
|
179
|
+
const result = validate(registerSchema, testData);
|
|
180
|
+
|
|
181
|
+
if (result.valid) {
|
|
182
|
+
console.log('✅ 验证通过!');
|
|
183
|
+
} else {
|
|
184
|
+
console.log('❌ 验证失败:', result.errors);
|
|
185
|
+
}
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
---
|
|
189
|
+
|
|
190
|
+
## 💡 最佳实践
|
|
191
|
+
|
|
192
|
+
### 1. 简单字段用纯DSL
|
|
193
|
+
|
|
194
|
+
```javascript
|
|
195
|
+
const schema = dsl({
|
|
196
|
+
name: 'string:1-50!', // ✅ 简洁
|
|
197
|
+
age: 'number:18-120', // ✅ 清晰
|
|
198
|
+
role: 'user|admin' // ✅ 直观
|
|
199
|
+
});
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
### 2. 复杂字段用String扩展
|
|
203
|
+
|
|
204
|
+
```javascript
|
|
205
|
+
const schema = dsl({
|
|
206
|
+
email: 'email!'
|
|
207
|
+
.pattern(/custom/)
|
|
208
|
+
.messages({...})
|
|
209
|
+
.label('邮箱'),
|
|
210
|
+
|
|
211
|
+
username: 'string:3-32!'
|
|
212
|
+
.pattern(/^\w+$/)
|
|
213
|
+
.custom(checkExists)
|
|
214
|
+
});
|
|
215
|
+
```
|
|
216
|
+
|
|
217
|
+
### 3. 80/20 法则
|
|
218
|
+
|
|
219
|
+
**80%字段用纯DSL,20%字段用String扩展**
|
|
220
|
+
|
|
221
|
+
---
|
|
222
|
+
|
|
223
|
+
## 🎯 常见场景
|
|
224
|
+
|
|
225
|
+
### 表单验证
|
|
226
|
+
|
|
227
|
+
```javascript
|
|
228
|
+
const formSchema = dsl({
|
|
229
|
+
email: 'email!'.label('邮箱地址'),
|
|
230
|
+
password: 'string:8-64!'.label('密码'),
|
|
231
|
+
nickname: 'string:2-20'.label('昵称'),
|
|
232
|
+
bio: 'string:500',
|
|
233
|
+
website: 'url',
|
|
234
|
+
age: 'number:18-120',
|
|
235
|
+
gender: 'male|female|other'
|
|
236
|
+
});
|
|
237
|
+
```
|
|
238
|
+
|
|
239
|
+
### 自定义验证
|
|
240
|
+
|
|
241
|
+
> `.custom()` 当前仅支持同步函数;如果需要异步查重,请在 `validate()` / `validateAsync()` 通过后于业务层单独执行。
|
|
242
|
+
|
|
243
|
+
```javascript
|
|
244
|
+
const schema = dsl({
|
|
245
|
+
username: 'string:3-32!'
|
|
246
|
+
.custom((value) => {
|
|
247
|
+
if (value === 'admin') {
|
|
248
|
+
return '用户名已存在';
|
|
249
|
+
}
|
|
250
|
+
})
|
|
251
|
+
});
|
|
252
|
+
```
|
|
253
|
+
|
|
254
|
+
### 嵌套对象
|
|
255
|
+
|
|
256
|
+
```javascript
|
|
257
|
+
const schema = dsl({
|
|
258
|
+
user: {
|
|
259
|
+
profile: {
|
|
260
|
+
name: 'string:1-50!'.label('姓名'),
|
|
261
|
+
avatar: 'url'.label('头像'),
|
|
262
|
+
social: {
|
|
263
|
+
twitter: 'url'.pattern(/twitter\.com/),
|
|
264
|
+
github: 'url'.pattern(/github\.com/)
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
});
|
|
269
|
+
```
|
|
270
|
+
|
|
271
|
+
---
|
|
272
|
+
|
|
273
|
+
## 📚 下一步
|
|
274
|
+
|
|
275
|
+
### 深入学习
|
|
276
|
+
|
|
277
|
+
- [DSL 语法完整指南](./dsl-syntax.md)
|
|
278
|
+
- [API 参考文档](./api-reference.md)
|
|
279
|
+
- [String 扩展文档](./string-extensions.md)
|
|
280
|
+
|
|
281
|
+
### 示例代码
|
|
282
|
+
|
|
283
|
+
- [Quick Start 完整示例](https://github.com/vextjs/schema-dsl/blob/main/examples/docs/quick-start.ts)
|
|
284
|
+
|
|
285
|
+
其余主题示例现在都已分别挂到各自文档底部,并统一切到稳定 GitHub 示例链接。
|
|
286
|
+
|
|
287
|
+
### 高级功能
|
|
288
|
+
|
|
289
|
+
- [自定义验证器](./api-reference.md#custom)
|
|
290
|
+
- [条件验证(when)](./api-reference.md#when)
|
|
291
|
+
- [数据库Schema导出](./api-reference.md#导出器)
|
|
292
|
+
|
|
293
|
+
---
|
|
294
|
+
|
|
295
|
+
## 🎯 设计理念与性能
|
|
296
|
+
|
|
297
|
+
### 为什么选择运行时解析?
|
|
298
|
+
|
|
299
|
+
Schema-DSL 使用**运行时解析 DSL**,而非编译时构建(如 Zod),这是有意的设计选择:
|
|
300
|
+
|
|
301
|
+
#### ✅ 运行时解析的优势
|
|
302
|
+
|
|
303
|
+
1. **完全动态** - 验证规则可以从配置文件、数据库动态加载
|
|
304
|
+
```javascript
|
|
305
|
+
// 从配置读取规则
|
|
306
|
+
const rules = await db.findOne({ entity: 'user' });
|
|
307
|
+
const schema = dsl({
|
|
308
|
+
username: `string:${rules.min}-${rules.max}!`
|
|
309
|
+
});
|
|
310
|
+
```
|
|
311
|
+
|
|
312
|
+
2. **多租户支持** - 每个租户可以有不同的验证规则
|
|
313
|
+
```javascript
|
|
314
|
+
// 租户A: 用户名3-32字符
|
|
315
|
+
// 租户B: 用户名5-50字符
|
|
316
|
+
function getTenantSchema(tenantId) {
|
|
317
|
+
const rules = tenantConfig[tenantId];
|
|
318
|
+
return dsl({
|
|
319
|
+
username: `string:${rules.min}-${rules.max}!`
|
|
320
|
+
});
|
|
321
|
+
}
|
|
322
|
+
```
|
|
323
|
+
|
|
324
|
+
3. **可序列化** - DSL 字符串可以存储、传输、共享
|
|
325
|
+
```javascript
|
|
326
|
+
// 存储到数据库
|
|
327
|
+
await db.insert({
|
|
328
|
+
formId: 'register',
|
|
329
|
+
rules: { username: 'string:3-32!', email: 'email!' }
|
|
330
|
+
});
|
|
331
|
+
|
|
332
|
+
// 通过 API 传输
|
|
333
|
+
res.json({ validationRules: rules });
|
|
334
|
+
|
|
335
|
+
// 前后端共享规则
|
|
336
|
+
```
|
|
337
|
+
|
|
338
|
+
4. **低代码基础** - 支持可视化表单构建器
|
|
339
|
+
```javascript
|
|
340
|
+
// 管理员在界面配置验证规则
|
|
341
|
+
const formBuilder = {
|
|
342
|
+
fields: [
|
|
343
|
+
{ name: 'username', validation: 'string:3-32!' }
|
|
344
|
+
]
|
|
345
|
+
};
|
|
346
|
+
```
|
|
347
|
+
|
|
348
|
+
#### ⚠️ 性能权衡
|
|
349
|
+
|
|
350
|
+
S1 有效数据与 Zod 持平,S3 嵌套场景快约 28%,无效数据公平对比快 89x:
|
|
351
|
+
|
|
352
|
+
| 库名 | 性能 | 场景 |
|
|
353
|
+
|------|-----------|------|
|
|
354
|
+
| Ajv (raw) | 4.732M ops/s | 底层引擎,无 DSL 层 |
|
|
355
|
+
| **Schema-DSL** | **1.301M ops/s**(S1有效) | 全功能(DSL + i18n + coerce)|
|
|
356
|
+
| **Schema-DSL** | **1.205M ops/s**(S2 无效,均无 i18n)| 公平对比(均无 i18n)|
|
|
357
|
+
| Zod | 1.305M ops/s(S1有效)/ 13.49K(S2 无效)| 编译时构建,错误路径异常驱动 |
|
|
358
|
+
| Joi | 154K ops/s | 功能丰富 |
|
|
359
|
+
|
|
360
|
+
**结论**:
|
|
361
|
+
- ✅ S3 嵌套有效场景比 Zod 快 **28%**;S1 简单有效场景持平(差 <1%)
|
|
362
|
+
- ✅ 无效数据公平对比(均无 i18n)比 Zod 快 **89x**
|
|
363
|
+
- ✅ 内置缓存,热路径零解析开销
|
|
364
|
+
|
|
365
|
+
### 适用场景
|
|
366
|
+
|
|
367
|
+
**✅ 选择 Schema-DSL**:
|
|
368
|
+
- 需要动态验证规则(配置驱动、多租户)
|
|
369
|
+
- 需要数据库 Schema 导出
|
|
370
|
+
- 快速开发原型
|
|
371
|
+
- 多语言 SaaS 系统
|
|
372
|
+
|
|
373
|
+
**⚠️ 考虑其他库**:
|
|
374
|
+
- TypeScript 项目需要强类型推断 → **Zod**
|
|
375
|
+
- 性能是第一优先级 → **Ajv** 或 **Zod**
|
|
376
|
+
- 静态验证规则 → **Zod**
|
|
377
|
+
|
|
378
|
+
---
|
|
379
|
+
|
|
380
|
+
## 🆘 常见问题
|
|
381
|
+
|
|
382
|
+
### Q: String扩展和纯DSL有什么区别?
|
|
383
|
+
|
|
384
|
+
**A**:
|
|
385
|
+
- **纯DSL**: 适合简单字段,语法简洁
|
|
386
|
+
- **String扩展**: 适合复杂验证,支持链式调用
|
|
387
|
+
|
|
388
|
+
```javascript
|
|
389
|
+
// 纯DSL(简单)
|
|
390
|
+
name: 'string:1-50!'
|
|
391
|
+
|
|
392
|
+
// String扩展(复杂)
|
|
393
|
+
email: 'email!'
|
|
394
|
+
.pattern(/custom/)
|
|
395
|
+
.messages({...})
|
|
396
|
+
```
|
|
397
|
+
|
|
398
|
+
### Q: 如何禁用String扩展?
|
|
399
|
+
|
|
400
|
+
**A**:
|
|
401
|
+
```javascript
|
|
402
|
+
const { uninstallStringExtensions } = require('schema-dsl');
|
|
403
|
+
uninstallStringExtensions();
|
|
404
|
+
```
|
|
405
|
+
|
|
406
|
+
### Q: 支持TypeScript吗?
|
|
407
|
+
|
|
408
|
+
**A**: 支持!schema-dsl 提供完整的 TypeScript 类型定义。
|
|
409
|
+
|
|
410
|
+
---
|
|
411
|
+
|
|
412
|
+
## 🎉 恭喜!
|
|
413
|
+
|
|
414
|
+
你已经掌握了 schema-dsl 的核心用法!
|
|
415
|
+
|
|
416
|
+
**核心要点**:
|
|
417
|
+
1. ✅ DSL语法简洁直观
|
|
418
|
+
2. ✅ String扩展强大灵活
|
|
419
|
+
3. ✅ 80%用DSL,20%用扩展
|
|
420
|
+
4. ✅ 字符串可以直接链式调用
|
|
421
|
+
|
|
422
|
+
**开始使用**: `npm install schema-dsl`
|
|
423
|
+
|
|
424
|
+
---
|
|
425
|
+
|
|
426
|
+
## 对应示例文件
|
|
427
|
+
|
|
428
|
+
**示例入口**: [quick-start.ts](https://github.com/vextjs/schema-dsl/blob/main/examples/docs/quick-start.ts)
|
|
429
|
+
**说明**: 覆盖快速上手中的 Hello World、String 扩展、用户注册示例,以及 `validate()` 与 `Validator.compile()` 的基础复用路径,可直接运行参考。
|
|
430
|
+
|
|
431
|
+
---
|
|
432
|
+
|
|
433
|
+
**最后更新**: 2026-05-08
|
|
434
|
+
|
|
435
|
+
|