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.
Files changed (109) hide show
  1. package/.eslintignore +10 -0
  2. package/.eslintrc.json +27 -0
  3. package/.github/CODE_OF_CONDUCT.md +45 -0
  4. package/.github/ISSUE_TEMPLATE/bug_report.md +57 -0
  5. package/.github/ISSUE_TEMPLATE/config.yml +11 -0
  6. package/.github/ISSUE_TEMPLATE/feature_request.md +45 -0
  7. package/.github/ISSUE_TEMPLATE/question.md +31 -0
  8. package/.github/PULL_REQUEST_TEMPLATE.md +70 -0
  9. package/.github/SECURITY.md +184 -0
  10. package/.github/workflows/ci.yml +35 -0
  11. package/CHANGELOG.md +633 -0
  12. package/CONTRIBUTING.md +368 -0
  13. package/LICENSE +21 -0
  14. package/README.md +1122 -0
  15. package/STATUS.md +273 -0
  16. package/docs/FEATURE-INDEX.md +521 -0
  17. package/docs/INDEX.md +224 -0
  18. package/docs/api-reference.md +1098 -0
  19. package/docs/best-practices.md +672 -0
  20. package/docs/cache-manager.md +336 -0
  21. package/docs/design-philosophy.md +602 -0
  22. package/docs/dsl-syntax.md +654 -0
  23. package/docs/dynamic-locale.md +552 -0
  24. package/docs/error-handling.md +703 -0
  25. package/docs/export-guide.md +459 -0
  26. package/docs/faq.md +576 -0
  27. package/docs/frontend-i18n-guide.md +290 -0
  28. package/docs/i18n-user-guide.md +488 -0
  29. package/docs/label-vs-description.md +262 -0
  30. package/docs/markdown-exporter.md +398 -0
  31. package/docs/mongodb-exporter.md +279 -0
  32. package/docs/multi-type-support.md +319 -0
  33. package/docs/mysql-exporter.md +257 -0
  34. package/docs/plugin-system.md +542 -0
  35. package/docs/postgresql-exporter.md +290 -0
  36. package/docs/quick-start.md +761 -0
  37. package/docs/schema-helper.md +340 -0
  38. package/docs/schema-utils.md +492 -0
  39. package/docs/string-extensions.md +480 -0
  40. package/docs/troubleshooting.md +471 -0
  41. package/docs/type-converter.md +319 -0
  42. package/docs/type-reference.md +219 -0
  43. package/docs/validate.md +486 -0
  44. package/docs/validation-guide.md +484 -0
  45. package/examples/array-dsl-example.js +227 -0
  46. package/examples/custom-extension.js +85 -0
  47. package/examples/dsl-match-example.js +74 -0
  48. package/examples/dsl-style.js +118 -0
  49. package/examples/dynamic-locale-configuration.js +348 -0
  50. package/examples/dynamic-locale-example.js +287 -0
  51. package/examples/export-demo.js +130 -0
  52. package/examples/i18n-full-demo.js +310 -0
  53. package/examples/i18n-memory-safety.examples.js +268 -0
  54. package/examples/markdown-export.js +71 -0
  55. package/examples/middleware-usage.js +93 -0
  56. package/examples/password-reset/README.md +153 -0
  57. package/examples/password-reset/schema.js +26 -0
  58. package/examples/password-reset/test.js +101 -0
  59. package/examples/plugin-system.examples.js +205 -0
  60. package/examples/simple-example.js +122 -0
  61. package/examples/string-extensions.js +297 -0
  62. package/examples/user-registration/README.md +156 -0
  63. package/examples/user-registration/routes.js +92 -0
  64. package/examples/user-registration/schema.js +150 -0
  65. package/examples/user-registration/server.js +74 -0
  66. package/index.d.ts +1999 -0
  67. package/index.js +270 -0
  68. package/index.mjs +30 -0
  69. package/lib/adapters/DslAdapter.js +653 -0
  70. package/lib/adapters/index.js +20 -0
  71. package/lib/config/constants.js +286 -0
  72. package/lib/config/patterns/creditCard.js +9 -0
  73. package/lib/config/patterns/idCard.js +9 -0
  74. package/lib/config/patterns/index.js +8 -0
  75. package/lib/config/patterns/licensePlate.js +4 -0
  76. package/lib/config/patterns/passport.js +4 -0
  77. package/lib/config/patterns/phone.js +9 -0
  78. package/lib/config/patterns/postalCode.js +5 -0
  79. package/lib/core/CacheManager.js +376 -0
  80. package/lib/core/DslBuilder.js +740 -0
  81. package/lib/core/ErrorCodes.js +233 -0
  82. package/lib/core/ErrorFormatter.js +342 -0
  83. package/lib/core/JSONSchemaCore.js +347 -0
  84. package/lib/core/Locale.js +119 -0
  85. package/lib/core/MessageTemplate.js +89 -0
  86. package/lib/core/PluginManager.js +448 -0
  87. package/lib/core/StringExtensions.js +209 -0
  88. package/lib/core/Validator.js +316 -0
  89. package/lib/exporters/MarkdownExporter.js +420 -0
  90. package/lib/exporters/MongoDBExporter.js +162 -0
  91. package/lib/exporters/MySQLExporter.js +212 -0
  92. package/lib/exporters/PostgreSQLExporter.js +289 -0
  93. package/lib/exporters/index.js +24 -0
  94. package/lib/locales/en-US.js +65 -0
  95. package/lib/locales/es-ES.js +66 -0
  96. package/lib/locales/fr-FR.js +66 -0
  97. package/lib/locales/index.js +8 -0
  98. package/lib/locales/ja-JP.js +66 -0
  99. package/lib/locales/zh-CN.js +93 -0
  100. package/lib/utils/LRUCache.js +174 -0
  101. package/lib/utils/SchemaHelper.js +240 -0
  102. package/lib/utils/SchemaUtils.js +313 -0
  103. package/lib/utils/TypeConverter.js +245 -0
  104. package/lib/utils/index.js +13 -0
  105. package/lib/validators/CustomKeywords.js +203 -0
  106. package/lib/validators/index.js +11 -0
  107. package/package.json +70 -0
  108. package/plugins/custom-format.js +101 -0
  109. package/plugins/custom-validator.js +200 -0
@@ -0,0 +1,703 @@
1
+ # schema-dsl 错误处理完整指南
2
+
3
+
4
+ > **更新**: 2025-12-25
5
+ > **适用**: 企业级应用开发
6
+
7
+ ---
8
+
9
+ ## 📋 目录
10
+
11
+ 1. [错误对象结构](#错误对象结构)
12
+ 2. [错误消息定制](#错误消息定制)
13
+ 3. [错误码系统](#错误码系统)
14
+ 4. [多层级错误处理](#多层级错误处理)
15
+ 5. [API响应设计](#api响应设计)
16
+ 6. [前端错误展示](#前端错误展示)
17
+ 7. [错误日志记录](#错误日志记录)
18
+ 8. [最佳实践](#最佳实践)
19
+
20
+ ---
21
+
22
+ ## 错误对象结构
23
+
24
+ ### 基础结构
25
+
26
+ SchemaIO 验证返回的错误对象结构:
27
+
28
+ ```javascript
29
+ const { dsl, validate } = require('schema-dsl');
30
+
31
+ const schema = dsl({
32
+ username: 'string:3-32!'.label('用户名')
33
+ });
34
+
35
+ const result = validate(schema, { username: 'ab' });
36
+
37
+ // 返回结构
38
+ {
39
+ valid: false, // 验证是否通过
40
+ errors: [ // 错误数组(基于 ajv)
41
+ {
42
+ instancePath: '/username',
43
+ schemaPath: '#/properties/username/minLength',
44
+ keyword: 'minLength',
45
+ params: { limit: 3 },
46
+ message: 'must NOT have fewer than 3 characters'
47
+ }
48
+ ]
49
+ }
50
+ ```
51
+
52
+ ### 嵌套对象错误
53
+
54
+ ```javascript
55
+ const { dsl, validate } = require('schema-dsl');
56
+
57
+ const schema = dsl({
58
+ user: {
59
+ profile: {
60
+ email: 'email!'
61
+ }
62
+ }
63
+ });
64
+
65
+ const result = validate(schema, {
66
+ user: {
67
+ profile: {
68
+ email: 'invalid'
69
+ }
70
+ }
71
+ });
72
+
73
+ // 错误路径
74
+ console.log(result.errors[0].instancePath); // '/user/profile/email'
75
+ console.log(result.errors[0].message); // 'must match format "email"'
76
+ ```
77
+
78
+ ### 数组项错误
79
+
80
+ ```javascript
81
+ const { dsl, validate } = require('schema-dsl');
82
+
83
+ const schema = dsl({
84
+ items: 'array<string:3->!'
85
+ });
86
+
87
+ const result = validate(schema, {
88
+ items: ['ab', 'valid']
89
+ });
90
+
91
+ // 错误路径
92
+ console.log(result.errors[0].instancePath); // '/items/0'
93
+ ```
94
+
95
+ ---
96
+
97
+ ## 错误消息定制
98
+
99
+ ### 单字段定制
100
+
101
+ ```javascript
102
+ const { dsl } = require('schema-dsl');
103
+
104
+ // 使用 String 扩展定制消息
105
+ const schema = dsl({
106
+ username: 'string:3-32!'
107
+ .label('用户名')
108
+ .messages({
109
+ 'min': '太短了!至少要3个字符'
110
+ })
111
+ });
112
+ ```
113
+
114
+ ### 多规则定制
115
+
116
+ ```javascript
117
+ const { dsl } = require('schema-dsl');
118
+
119
+ const schema = dsl({
120
+ email: 'email!'
121
+ .label('邮箱地址')
122
+ .messages({
123
+ 'format': '邮箱格式不对哦',
124
+ 'required': '邮箱不能为空'
125
+ })
126
+ });
127
+ ```
128
+
129
+ ### 对象级定制
130
+
131
+ ```javascript
132
+ const { dsl } = require('schema-dsl');
133
+
134
+ const schema = dsl({
135
+ username: 'string:3-32!'
136
+ .label('用户名')
137
+ .messages({
138
+ 'min': '{{#label}}至少{{#limit}}个字符',
139
+ 'max': '{{#label}}最多{{#limit}}个字符'
140
+ }),
141
+
142
+ email: 'email!'
143
+ .label('邮箱')
144
+ .messages({
145
+ 'format': '{{#label}}格式无效'
146
+ })
147
+ });
148
+ ```
149
+
150
+ ### 全局定制
151
+
152
+ ```javascript
153
+ const { Locale } = require('schema-dsl');
154
+
155
+ // 设置全局消息
156
+ Locale.setMessages({
157
+ 'min': '输入太短,要{{#limit}}个字符',
158
+ 'format': '格式不正确'
159
+ });
160
+ ```
161
+
162
+ ---
163
+
164
+ ## 错误码系统
165
+
166
+ ### 内置错误码(简化版)
167
+
168
+ SchemaIO 对 ajv 的错误关键字进行了简化映射,使其更易用:
169
+
170
+ #### 字符串错误码
171
+
172
+ | 关键字 | 原始关键字 | 说明 | params |
173
+ |--------|-----------|------|--------|
174
+ | `min` | `minLength` | 长度小于最小值 | { limit: number } |
175
+ | `max` | `maxLength` | 长度大于最大值 | { limit: number } |
176
+ | `format` | `format` | 格式验证失败 | { format: 'email'/'uri'/etc } |
177
+ | `pattern` | `pattern` | 正则不匹配 | { pattern: string } |
178
+ | `enum` | `enum` | 不在枚举值中 | { allowedValues: array } |
179
+
180
+ #### 数字错误码
181
+
182
+ | 关键字 | 原始关键字 | 说明 | params |
183
+ |--------|-----------|------|--------|
184
+ | `min` | `minimum` | 小于最小值 | { limit: number } |
185
+ | `max` | `maximum` | 大于最大值 | { limit: number } |
186
+
187
+ #### 通用错误码
188
+
189
+ | 关键字 | 说明 | params |
190
+ |--------|------|--------|
191
+ | `required` | 必填字段缺失 | { missingProperty: string } |
192
+ | `type` | 类型不匹配 | { type: string } |
193
+
194
+ **💡 提示**: 您可以使用简化关键字(如 `min`)或原始关键字(如 `minLength`)来定制错误消息,系统会自动处理映射。
195
+
196
+ ### 自动 Label 翻译
197
+
198
+ 如果您在语言包中定义了 `label.{fieldName}`,系统会自动将其作为 Label 使用,无需显式调用 `.label()`。
199
+
200
+ ```javascript
201
+ // 语言包
202
+ Locale.addLocale('zh-CN', {
203
+ 'label.username': '用户名',
204
+ 'required': '{{#label}}不能为空'
205
+ });
206
+
207
+ // Schema
208
+ const schema = dsl({
209
+ username: 'string!' // 自动查找 label.username
210
+ });
211
+
212
+ // 错误消息: "用户名不能为空"
213
+ ```
214
+
215
+ ### 自定义验证错误
216
+
217
+ ```javascript
218
+ const { dsl } = require('schema-dsl');
219
+
220
+ const schema = dsl({
221
+ username: 'string:3-32!'
222
+ .custom((value) => {
223
+ if (value.includes('forbidden')) {
224
+ return '内容包含禁止的词语';
225
+ }
226
+ // 验证通过时无需返回
227
+ })
228
+ .label('用户名')
229
+ });
230
+ ```
231
+
232
+ ---
233
+
234
+ ## 多层级错误处理
235
+
236
+ ### 嵌套对象验证
237
+
238
+ ```javascript
239
+ const { dsl, validate } = require('schema-dsl');
240
+
241
+ const schema = dsl({
242
+ user: {
243
+ name: 'string:1-100!',
244
+ address: {
245
+ country: 'string!'.label('国家'),
246
+ city: 'string!'.label('城市'),
247
+ street: 'string!'.label('街道')
248
+ }
249
+ }
250
+ });
251
+
252
+ const result = validate(schema, {
253
+ user: {
254
+ name: 'John',
255
+ address: {
256
+ country: 'CN'
257
+ // 缺少city和street
258
+ }
259
+ }
260
+ });
261
+
262
+ // 错误示例
263
+ // result.errors[0].instancePath: '/user/address/city'
264
+ // result.errors[1].instancePath: '/user/address/street'
265
+ ```
266
+
267
+ ### 数组验证
268
+
269
+ ```javascript
270
+ const { dsl, validate } = require('schema-dsl');
271
+
272
+ const schema = dsl({
273
+ items: 'array:1-<string:3->!'
274
+ .label('商品列表')
275
+ });
276
+
277
+ const result = validate(schema, {
278
+ items: ['ab', 'valid'] // 第一项太短
279
+ });
280
+
281
+ // 错误路径
282
+ console.log(result.errors[0].instancePath); // '/items/0'
283
+ ```
284
+
285
+ ---
286
+
287
+ ## API响应设计
288
+
289
+ ### 标准响应格式
290
+
291
+ ```javascript
292
+ // 成功响应
293
+ {
294
+ success: true,
295
+ code: 'SUCCESS',
296
+ data: { ... }
297
+ }
298
+
299
+ // 验证错误响应
300
+ {
301
+ success: false,
302
+ code: 'VALIDATION_ERROR',
303
+ message: '数据验证失败',
304
+ errors: [
305
+ {
306
+ field: 'username',
307
+ message: 'must NOT have fewer than 3 characters',
308
+ keyword: 'minLength',
309
+ params: { limit: 3 }
310
+ }
311
+ ]
312
+ }
313
+
314
+ // 服务器错误响应
315
+ {
316
+ success: false,
317
+ code: 'SERVER_ERROR',
318
+ message: '服务器内部错误'
319
+ }
320
+ ```
321
+
322
+ ### Express中间件
323
+
324
+ ```javascript
325
+ const { dsl, Validator } = require('schema-dsl');
326
+
327
+ // 验证中间件
328
+ function validateBody(schema) {
329
+ const validator = new Validator();
330
+
331
+ return (req, res, next) => {
332
+ const result = validator.validate(schema, req.body);
333
+
334
+ if (!result.valid) {
335
+ return res.status(400).json({
336
+ success: false,
337
+ code: 'VALIDATION_ERROR',
338
+ message: '请检查输入信息',
339
+ errors: result.errors.map(err => ({
340
+ field: err.instancePath.replace(/^\//, '').replace(/\//g, '.'),
341
+ message: err.message,
342
+ keyword: err.keyword,
343
+ params: err.params
344
+ }))
345
+ });
346
+ }
347
+
348
+ // 验证通过,继续处理
349
+ next();
350
+ };
351
+ }
352
+
353
+ // 使用示例
354
+ const userSchema = dsl({
355
+ username: 'string:3-32!',
356
+ email: 'email!',
357
+ password: 'string:8-64!'
358
+ });
359
+
360
+ app.post('/api/users',
361
+ validateBody(userSchema),
362
+ async (req, res) => {
363
+ const user = await createUser(req.body);
364
+ res.json({ success: true, data: user });
365
+ }
366
+ );
367
+ ```
368
+
369
+ ### Koa中间件
370
+
371
+ ```javascript
372
+ const { dsl, Validator } = require('schema-dsl');
373
+
374
+ function validateBody(schema) {
375
+ const validator = new Validator();
376
+
377
+ return async (ctx, next) => {
378
+ const result = validator.validate(schema, ctx.request.body);
379
+
380
+ if (!result.valid) {
381
+ ctx.status = 400;
382
+ ctx.body = {
383
+ success: false,
384
+ code: 'VALIDATION_ERROR',
385
+ message: '数据验证失败',
386
+ errors: result.errors.map(err => ({
387
+ field: err.instancePath.replace(/^\//, '').replace(/\//g, '.'),
388
+ message: err.message,
389
+ keyword: err.keyword
390
+ }))
391
+ };
392
+ return;
393
+ }
394
+
395
+ await next();
396
+ };
397
+ }
398
+
399
+ // 使用示例
400
+ const registerSchema = dsl({
401
+ username: 'string:3-32!'.username(),
402
+ email: 'email!',
403
+ password: 'string!'.password('strong')
404
+ });
405
+
406
+ router.post('/register', validateBody(registerSchema), async (ctx) => {
407
+ ctx.body = { success: true, data: await register(ctx.request.body) };
408
+ });
409
+ ```
410
+
411
+ ---
412
+
413
+ ## 前端错误展示
414
+
415
+ ### React示例
416
+
417
+ ```javascript
418
+ import React, { useState } from 'react';
419
+
420
+ function RegisterForm() {
421
+ const [errors, setErrors] = useState({});
422
+
423
+ const handleSubmit = async (e) => {
424
+ e.preventDefault();
425
+
426
+ try {
427
+ const response = await fetch('/api/register', {
428
+ method: 'POST',
429
+ headers: { 'Content-Type': 'application/json' },
430
+ body: JSON.stringify(formData)
431
+ });
432
+
433
+ const data = await response.json();
434
+
435
+ if (!data.success && data.code === 'VALIDATION_ERROR') {
436
+ // 将错误数组转为对象
437
+ const errorMap = {};
438
+ data.errors.forEach(err => {
439
+ errorMap[err.field] = err.message;
440
+ });
441
+ setErrors(errorMap);
442
+ }
443
+
444
+ } catch (error) {
445
+ console.error(error);
446
+ }
447
+ };
448
+
449
+ return (
450
+ <form onSubmit={handleSubmit}>
451
+ <div>
452
+ <input name="username" />
453
+ {errors.username && (
454
+ <span className="error">{errors.username}</span>
455
+ )}
456
+ </div>
457
+
458
+ <div>
459
+ <input name="email" type="email" />
460
+ {errors.email && (
461
+ <span className="error">{errors.email}</span>
462
+ )}
463
+ </div>
464
+
465
+ <button type="submit">注册</button>
466
+ </form>
467
+ );
468
+ }
469
+ ```
470
+
471
+ ### Vue示例
472
+
473
+ ```vue
474
+ <template>
475
+ <form @submit.prevent="handleSubmit">
476
+ <div>
477
+ <input v-model="form.username" />
478
+ <span v-if="errors.username" class="error">
479
+ {{ errors.username }}
480
+ </span>
481
+ </div>
482
+
483
+ <div>
484
+ <input v-model="form.email" type="email" />
485
+ <span v-if="errors.email" class="error">
486
+ {{ errors.email }}
487
+ </span>
488
+ </div>
489
+
490
+ <button type="submit">注册</button>
491
+ </form>
492
+ </template>
493
+
494
+ <script>
495
+ export default {
496
+ data() {
497
+ return {
498
+ form: {
499
+ username: '',
500
+ email: ''
501
+ },
502
+ errors: {}
503
+ };
504
+ },
505
+ methods: {
506
+ async handleSubmit() {
507
+ try {
508
+ const response = await fetch('/api/register', {
509
+ method: 'POST',
510
+ headers: { 'Content-Type': 'application/json' },
511
+ body: JSON.stringify(this.form)
512
+ });
513
+
514
+ const data = await response.json();
515
+
516
+ if (!data.success && data.code === 'VALIDATION_ERROR') {
517
+ this.errors = data.errors.reduce((acc, err) => {
518
+ acc[err.field] = err.message;
519
+ return acc;
520
+ }, {});
521
+ }
522
+
523
+ } catch (error) {
524
+ console.error(error);
525
+ }
526
+ }
527
+ }
528
+ };
529
+ </script>
530
+ ```
531
+
532
+ ---
533
+
534
+ ## 错误日志记录
535
+
536
+ ### 基础日志
537
+
538
+ ```javascript
539
+ app.post('/api/register', async (req, res) => {
540
+ const result = await registerSchema.validate(req.body, {
541
+ abortEarly: false
542
+ });
543
+
544
+ if (!result.isValid) {
545
+ // 记录验证错误
546
+ logger.warn('用户注册验证失败', {
547
+ ip: req.ip,
548
+ errors: result.errors,
549
+ data: req.body
550
+ });
551
+
552
+ return res.status(400).json({
553
+ success: false,
554
+ errors: result.errors
555
+ });
556
+ }
557
+
558
+ // 继续处理
559
+ });
560
+ ```
561
+
562
+ ### 结构化日志
563
+
564
+ ```javascript
565
+ const logger = require('winston');
566
+
567
+ function logValidationError(req, result) {
568
+ logger.warn({
569
+ message: '验证失败',
570
+ type: 'VALIDATION_ERROR',
571
+ timestamp: new Date().toISOString(),
572
+ ip: req.ip,
573
+ url: req.url,
574
+ method: req.method,
575
+ errors: result.errors.map(err => ({
576
+ path: err.path.join('.'),
577
+ type: err.type,
578
+ message: err.message
579
+ })),
580
+ // 敏感数据脱敏
581
+ data: maskSensitiveData(req.body)
582
+ });
583
+ }
584
+ ```
585
+
586
+ ---
587
+
588
+ ## 最佳实践
589
+
590
+ ### 1. 使用 label 让错误消息更清晰
591
+
592
+ ```javascript
593
+ const { dsl } = require('schema-dsl');
594
+
595
+ // ✅ 推荐:使用 label
596
+ const schema = dsl({
597
+ username: 'string:3-32!'.label('用户名')
598
+ });
599
+ // 错误消息会包含"用户名"标签
600
+
601
+ // ❌ 不推荐:不使用 label
602
+ const schema = dsl({
603
+ username: 'string:3-32!'
604
+ });
605
+ // 错误消息只显示字段名 "username"
606
+ ```
607
+
608
+ ### 2. 提供友好的中文错误消息
609
+
610
+ ```javascript
611
+ const { dsl } = require('schema-dsl');
612
+
613
+ // ✅ 推荐:自定义中文消息
614
+ const schema = dsl({
615
+ username: 'string:3-32!'
616
+ .label('用户名')
617
+ .messages({
618
+ 'minLength': '{{#label}}至少需要{{#limit}}个字符',
619
+ 'maxLength': '{{#label}}最多{{#limit}}个字符'
620
+ })
621
+ });
622
+
623
+ // ❌ 不推荐:使用默认英文消息
624
+ const schema = dsl({
625
+ username: 'string:3-32!'
626
+ });
627
+ ```
628
+
629
+ ### 3. 使用自定义验证实现业务逻辑
630
+
631
+ ```javascript
632
+ const { dsl } = require('schema-dsl');
633
+
634
+ // ✅ 推荐:返回错误消息字符串
635
+ const schema = dsl({
636
+ username: 'string:3-32!'
637
+ .custom(async (value) => {
638
+ if (await userExists(value)) {
639
+ return '用户名已被占用';
640
+ }
641
+ // 验证通过时无需返回
642
+ })
643
+ .label('用户名')
644
+ });
645
+ ```
646
+
647
+ ### 4. 敏感数据不要出现在错误日志中
648
+
649
+ ```javascript
650
+ function maskSensitiveData(data) {
651
+ return {
652
+ ...data,
653
+ password: '***',
654
+ confirmPassword: '***',
655
+ creditCard: data.creditCard ? '****' + data.creditCard.slice(-4) : undefined
656
+ };
657
+ }
658
+
659
+ // 使用
660
+ logger.warn('验证失败', {
661
+ errors: result.errors,
662
+ data: maskSensitiveData(req.body)
663
+ });
664
+ ```
665
+
666
+ ### 5. 统一错误格式便于前端处理
667
+
668
+ ```javascript
669
+ // 统一的错误格式化函数
670
+ function formatValidationErrors(ajvErrors) {
671
+ return ajvErrors.map(err => ({
672
+ field: err.instancePath.replace(/^\//, '').replace(/\//g, '.'),
673
+ message: err.message,
674
+ keyword: err.keyword,
675
+ params: err.params
676
+ }));
677
+ }
678
+
679
+ // 使用
680
+ if (!result.valid) {
681
+ return res.status(400).json({
682
+ success: false,
683
+ code: 'VALIDATION_ERROR',
684
+ errors: formatValidationErrors(result.errors)
685
+ });
686
+ }
687
+ ```
688
+
689
+ ---
690
+
691
+ ## 相关文档
692
+
693
+ - [API 参考文档](./api-reference.md)
694
+ - [DSL 语法指南](./dsl-syntax.md)
695
+ - [String 扩展文档](./string-extensions.md)
696
+ - [多语言配置](./dynamic-locale.md)
697
+
698
+ ---
699
+
700
+
701
+ **最后更新**: 2025-12-25
702
+
703
+