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,203 @@
1
+ /**
2
+ * 自定义JSON Schema关键字
3
+ *
4
+ * 扩展ajv支持自定义验证关键字
5
+ *
6
+ * @module lib/validators/CustomKeywords
7
+ * @version 1.0.0
8
+ */
9
+
10
+ const Locale = require('../core/Locale');
11
+
12
+ /**
13
+ * 自定义关键字集合
14
+ * @class CustomKeywords
15
+ */
16
+ class CustomKeywords {
17
+ /**
18
+ * 注册所有自定义关键字到ajv实例
19
+ * @static
20
+ * @param {Ajv} ajv - ajv实例
21
+ */
22
+ static registerAll(ajv) {
23
+ this.registerRegexKeyword(ajv);
24
+ this.registerFunctionKeyword(ajv);
25
+ this.registerRangeKeyword(ajv);
26
+ this.registerCustomValidatorsKeyword(ajv);
27
+ this.registerMetadataKeywords(ajv);
28
+ }
29
+
30
+ /**
31
+ * 注册元数据关键字(_label, _customMessages等)
32
+ * @static
33
+ * @param {Ajv} ajv - ajv实例
34
+ */
35
+ static registerMetadataKeywords(ajv) {
36
+ // _label: 字段标签
37
+ ajv.addKeyword({
38
+ keyword: '_label',
39
+ metaSchema: { type: 'string' }
40
+ });
41
+
42
+ // _customMessages: 自定义错误消息
43
+ ajv.addKeyword({
44
+ keyword: '_customMessages',
45
+ metaSchema: { type: 'object' }
46
+ });
47
+
48
+ // _description: 描述
49
+ ajv.addKeyword({
50
+ keyword: '_description',
51
+ metaSchema: { type: 'string' }
52
+ });
53
+
54
+ // _whenConditions: 条件验证
55
+ ajv.addKeyword({
56
+ keyword: '_whenConditions',
57
+ metaSchema: { type: 'array' }
58
+ });
59
+
60
+ // _required: 内部使用的必填标记
61
+ ajv.addKeyword({
62
+ keyword: '_required',
63
+ metaSchema: { type: 'boolean' }
64
+ });
65
+ }
66
+
67
+ /**
68
+ * 注册 _customValidators 关键字(SchemaIO 内部使用)
69
+ * @static
70
+ * @param {Ajv} ajv - ajv实例
71
+ */
72
+ static registerCustomValidatorsKeyword(ajv) {
73
+ ajv.addKeyword({
74
+ keyword: '_customValidators',
75
+ validate: function validate(validators, data) {
76
+ if (!Array.isArray(validators)) return true;
77
+
78
+ for (const validator of validators) {
79
+ try {
80
+ const result = validator(data);
81
+
82
+ // 处理 Promise (异步验证)
83
+ if (result instanceof Promise) {
84
+ // ajv 默认不支持同步验证中的异步操作
85
+ // 这里我们只能抛出错误提示
86
+ // 真正的异步支持需要 Validator.validateAsync
87
+ throw new Error(Locale.getMessage('ASYNC_VALIDATION_NOT_SUPPORTED'));
88
+ }
89
+
90
+ // 处理返回值
91
+ if (result === false) {
92
+ validate.errors = [{ message: Locale.getMessage('CUSTOM_VALIDATION_FAILED') }];
93
+ return false;
94
+ }
95
+ if (typeof result === 'string') {
96
+ validate.errors = [{ message: result }];
97
+ return false;
98
+ }
99
+ if (result && typeof result === 'object' && result.error) {
100
+ validate.errors = [{ message: result.message || Locale.getMessage('CUSTOM_VALIDATION_FAILED') }];
101
+ return false;
102
+ }
103
+ } catch (error) {
104
+ validate.errors = [{ message: error.message }];
105
+ return false;
106
+ }
107
+ }
108
+ return true;
109
+ },
110
+ errors: true
111
+ });
112
+ }
113
+
114
+ /**
115
+ * 注册regex关键字(正则验证)
116
+ * @static
117
+ * @param {Ajv} ajv - ajv实例
118
+ */
119
+ static registerRegexKeyword(ajv) {
120
+ ajv.addKeyword({
121
+ keyword: 'regex',
122
+ type: 'string',
123
+ schemaType: 'string',
124
+ validate: function validate(schema, data) {
125
+ try {
126
+ const regex = new RegExp(schema);
127
+ return regex.test(data);
128
+ } catch (error) {
129
+ validate.errors = [{ message: `Invalid regex: ${error.message}` }];
130
+ return false;
131
+ }
132
+ },
133
+ errors: true
134
+ });
135
+ }
136
+
137
+ /**
138
+ * 注册function关键字(自定义函数验证)
139
+ * @static
140
+ * @param {Ajv} ajv - ajv实例
141
+ */
142
+ static registerFunctionKeyword(ajv) {
143
+ ajv.addKeyword({
144
+ keyword: 'validate',
145
+ validate: function validate(schema, data) {
146
+ if (typeof schema !== 'function') {
147
+ validate.errors = [{ message: Locale.getMessage('VALIDATE_MUST_BE_FUNCTION') }];
148
+ return false;
149
+ }
150
+
151
+ try {
152
+ const result = schema(data);
153
+ if (typeof result === 'boolean') {
154
+ return result;
155
+ }
156
+ if (result && typeof result.valid === 'boolean') {
157
+ if (!result.valid && result.message) {
158
+ validate.errors = [{ message: result.message }];
159
+ }
160
+ return result.valid;
161
+ }
162
+ return true;
163
+ } catch (error) {
164
+ validate.errors = [{ message: error.message }];
165
+ return false;
166
+ }
167
+ },
168
+ errors: true
169
+ });
170
+ }
171
+
172
+ /**
173
+ * 注册range关键字(数值范围验证)
174
+ * @static
175
+ * @param {Ajv} ajv - ajv实例
176
+ */
177
+ static registerRangeKeyword(ajv) {
178
+ ajv.addKeyword({
179
+ keyword: 'range',
180
+ type: 'number',
181
+ schemaType: 'object',
182
+ validate: function validate(schema, data) {
183
+ const { min, max } = schema;
184
+
185
+ if (min !== undefined && data < min) {
186
+ validate.errors = [{ message: `must be >= ${min}` }];
187
+ return false;
188
+ }
189
+
190
+ if (max !== undefined && data > max) {
191
+ validate.errors = [{ message: `must be <= ${max}` }];
192
+ return false;
193
+ }
194
+
195
+ return true;
196
+ },
197
+ errors: true
198
+ });
199
+ }
200
+ }
201
+
202
+ module.exports = CustomKeywords;
203
+
@@ -0,0 +1,11 @@
1
+ /**
2
+ * Validators - 验证器统一导出
3
+ * @module lib/validators
4
+ */
5
+
6
+ const CustomKeywords = require('./CustomKeywords');
7
+
8
+ module.exports = {
9
+ CustomKeywords
10
+ };
11
+
package/package.json ADDED
@@ -0,0 +1,70 @@
1
+ {
2
+ "name": "schema-dsl",
3
+ "version": "2.3.0",
4
+ "description": "简洁强大的JSON Schema验证库 - DSL语法 + String扩展 + 便捷validate",
5
+ "main": "index.js",
6
+ "exports": {
7
+ ".": {
8
+ "require": "./index.js",
9
+ "import": "./index.mjs",
10
+ "types": "./index.d.ts"
11
+ }
12
+ },
13
+ "scripts": {
14
+ "test": "mocha test/unit/**/*.test.js",
15
+ "test:integration": "mocha test/integration/**/*.test.js",
16
+ "test:all": "mocha test/**/*.test.js",
17
+ "coverage": "nyc npm test",
18
+ "lint": "eslint lib/**/*.js",
19
+ "example": "node examples/dsl-style.js"
20
+ },
21
+ "keywords": [
22
+ "schema",
23
+ "validation",
24
+ "validator",
25
+ "dsl",
26
+ "json-schema",
27
+ "data-validation",
28
+ "form-validation",
29
+ "mongodb",
30
+ "mysql",
31
+ "postgresql",
32
+ "database-schema",
33
+ "i18n",
34
+ "internationalization",
35
+ "typescript",
36
+ "express",
37
+ "koa",
38
+ "fastify",
39
+ "rest-api",
40
+ "input-validation"
41
+ ],
42
+ "author": "rocky <rockyshi1993@gmail.com>",
43
+ "license": "MIT",
44
+ "repository": {
45
+ "type": "git",
46
+ "url": "https://github.com/vextjs/schema-dsl.git"
47
+ },
48
+ "bugs": {
49
+ "url": "https://github.com/vextjs/schema-dsl/issues"
50
+ },
51
+ "homepage": "https://github.com/vextjs/schema-dsl#readme",
52
+ "engines": {
53
+ "node": ">=12.0.0"
54
+ },
55
+ "dependencies": {
56
+ "ajv": "^8.17.1",
57
+ "ajv-formats": "^2.1.1"
58
+ },
59
+ "devDependencies": {
60
+ "chai": "^4.5.0",
61
+ "eslint": "^8.57.1",
62
+ "joi": "^18.0.2",
63
+ "mocha": "^10.8.2",
64
+ "monsqlize": "^1.0.1",
65
+ "nyc": "^15.1.0",
66
+ "sinon": "^17.0.1",
67
+ "yup": "^1.7.1",
68
+ "zod": "^4.2.1"
69
+ }
70
+ }
@@ -0,0 +1,101 @@
1
+ /**
2
+ * 示例插件:自定义格式验证
3
+ *
4
+ * @description 添加常用的格式验证(手机号、邮编、身份证等)
5
+ * @module plugins/custom-format
6
+ */
7
+
8
+ module.exports = {
9
+ name: 'custom-format',
10
+ version: '1.0.0',
11
+ description: '自定义格式验证插件',
12
+
13
+ install(schemaDsl, options = {}, context) {
14
+ // 获取默认 validator 实例
15
+ const validator = schemaDsl.getDefaultValidator();
16
+ const ajv = validator.getAjv();
17
+
18
+ // 添加自定义格式
19
+ this.addCustomFormats(ajv);
20
+
21
+ console.log('[Plugin] custom-format installed');
22
+ },
23
+
24
+ uninstall(schemaDsl, context) {
25
+ console.log('[Plugin] custom-format uninstalled');
26
+ },
27
+
28
+ addCustomFormats(ajv) {
29
+ // 1. 中国手机号
30
+ ajv.addFormat('phone-cn', {
31
+ validate: /^1[3-9]\d{9}$/
32
+ });
33
+
34
+ // 2. 中国邮政编码
35
+ ajv.addFormat('postal-code-cn', {
36
+ validate: /^\d{6}$/
37
+ });
38
+
39
+ // 3. IPv4 地址
40
+ ajv.addFormat('ipv4', {
41
+ validate: /^((25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$/
42
+ });
43
+
44
+ // 4. 微信号
45
+ ajv.addFormat('wechat', {
46
+ validate: /^[a-zA-Z][-_a-zA-Z0-9]{5,19}$/
47
+ });
48
+
49
+ // 5. QQ号
50
+ ajv.addFormat('qq', {
51
+ validate: /^[1-9][0-9]{4,10}$/
52
+ });
53
+
54
+ // 6. 银行卡号(简单验证)
55
+ ajv.addFormat('bank-card', {
56
+ validate: (value) => {
57
+ if (!/^\d{16,19}$/.test(value)) return false;
58
+
59
+ // Luhn 算法验证
60
+ let sum = 0;
61
+ let shouldDouble = false;
62
+
63
+ for (let i = value.length - 1; i >= 0; i--) {
64
+ let digit = parseInt(value[i]);
65
+
66
+ if (shouldDouble) {
67
+ digit *= 2;
68
+ if (digit > 9) digit -= 9;
69
+ }
70
+
71
+ sum += digit;
72
+ shouldDouble = !shouldDouble;
73
+ }
74
+
75
+ return sum % 10 === 0;
76
+ }
77
+ });
78
+
79
+ // 7. 车牌号(普通+新能源)
80
+ ajv.addFormat('license-plate', {
81
+ validate: /^[京津沪渝冀豫云辽黑湘皖鲁新苏浙赣鄂桂甘晋蒙陕吉闽贵粤青藏川宁琼使领][A-Z][A-HJ-NP-Z0-9]{4,5}[A-HJ-NP-Z0-9挂学警港澳]$/
82
+ });
83
+
84
+ // 8. 统一社会信用代码
85
+ ajv.addFormat('credit-code', {
86
+ validate: /^[0-9A-HJ-NPQRTUWXY]{2}\d{6}[0-9A-HJ-NPQRTUWXY]{10}$/
87
+ });
88
+
89
+ // 9. 护照号(中国)
90
+ ajv.addFormat('passport-cn', {
91
+ validate: /^[EG]\d{8}$/
92
+ });
93
+
94
+ // 10. 港澳通行证
95
+ ajv.addFormat('hk-macao-pass', {
96
+ validate: /^[HM]\d{8,10}$/
97
+ });
98
+ }
99
+ };
100
+
101
+
@@ -0,0 +1,200 @@
1
+ /**
2
+ * 示例插件:自定义验证器
3
+ *
4
+ * @description 展示如何创建一个自定义验证器插件
5
+ * @module plugins/custom-validator
6
+ */
7
+
8
+ module.exports = {
9
+ // 插件元信息
10
+ name: 'custom-validator',
11
+ version: '1.0.0',
12
+ description: '自定义验证器插件,添加业务特定的验证规则',
13
+
14
+ /**
15
+ * 插件安装函数
16
+ *
17
+ * @param {Object} schemaDsl - SchemaIO 实例
18
+ * @param {Object} options - 插件选项
19
+ * @param {Object} context - 插件上下文
20
+ */
21
+ install(schemaDsl, options = {}, context) {
22
+ // 1. 获取默认 validator 实例
23
+ const validator = schemaDsl.getDefaultValidator();
24
+
25
+ // 2. 添加自定义关键字
26
+ this.addCustomKeywords(validator);
27
+
28
+ // 3. 注册到全局
29
+ if (!global.__schemaDsl_plugins) {
30
+ global.__schemaDsl_plugins = {};
31
+ }
32
+ global.__schemaDsl_plugins['custom-validator'] = this;
33
+
34
+ console.log('[Plugin] custom-validator installed');
35
+ },
36
+
37
+ /**
38
+ * 插件卸载函数
39
+ */
40
+ uninstall(schemaDsl, context) {
41
+ // 清理全局注册
42
+ if (global.__schemaDsl_plugins) {
43
+ delete global.__schemaDsl_plugins['custom-validator'];
44
+ }
45
+
46
+ console.log('[Plugin] custom-validator uninstalled');
47
+ },
48
+
49
+ /**
50
+ * 添加自定义关键字
51
+ */
52
+ addCustomKeywords(validator) {
53
+ const ajv = validator.getAjv();
54
+
55
+ // 示例1: 唯一性验证(需要异步检查数据库)
56
+ // 检查关键字是否已存在
57
+ if (!ajv.getKeyword('unique')) {
58
+ validator.addKeyword('unique', {
59
+ async: true,
60
+ type: 'string',
61
+ validate: async function validateUnique(schema, data, parentSchema, dataPath) {
62
+ // schema: { unique: { table: 'users', field: 'email' } }
63
+ // data: 实际要验证的值
64
+
65
+ if (!schema) return true;
66
+
67
+ const { table, field } = schema;
68
+
69
+ // 模拟数据库查询
70
+ // 实际使用时替换为真实的数据库查询
71
+ const exists = false; // await db.query(...)
72
+
73
+ if (exists) {
74
+ validateUnique.errors = [{
75
+ keyword: 'unique',
76
+ message: `${field} already exists in ${table}`,
77
+ params: { table, field }
78
+ }];
79
+ return false;
80
+ }
81
+
82
+ return true;
83
+ }
84
+ });
85
+ }
86
+
87
+ // 示例2: 密码强度验证
88
+ if (!ajv.getKeyword('passwordStrength')) {
89
+ validator.addKeyword('passwordStrength', {
90
+ type: 'string',
91
+ validate: function validatePasswordStrength(schema, data) {
92
+ if (!schema) return true;
93
+
94
+ // schema: { passwordStrength: 'medium' }
95
+ // 强度等级: weak, medium, strong
96
+
97
+ const strength = schema;
98
+ const value = data;
99
+
100
+ const rules = {
101
+ weak: /^.{6,}$/,
102
+ medium: /^(?=.*[a-z])(?=.*[A-Z]).{8,}$/,
103
+ strong: /^(?=.*[a-z])(?=.*[A-Z])(?=.*\d).{10,}$/
104
+ };
105
+
106
+ const pattern = rules[strength];
107
+ if (!pattern) {
108
+ return true;
109
+ }
110
+
111
+ if (!pattern.test(value)) {
112
+ validatePasswordStrength.errors = [{
113
+ keyword: 'passwordStrength',
114
+ message: `Password does not meet ${strength} strength requirements`,
115
+ params: { strength }
116
+ }];
117
+ return false;
118
+ }
119
+
120
+ return true;
121
+ }
122
+ });
123
+ }
124
+
125
+ // 示例3: 中国身份证号验证
126
+ if (!ajv.getKeyword('idCard')) {
127
+ validator.addKeyword('idCard', {
128
+ type: 'string',
129
+ validate: function validateIdCard(schema, data) {
130
+ if (!schema) return true;
131
+
132
+ const value = data;
133
+
134
+ // 身份证号正则
135
+ const pattern = /^[1-9]\d{5}(19|20)\d{2}(0[1-9]|1[0-2])(0[1-9]|[12]\d|3[01])\d{3}[\dXx]$/;
136
+
137
+ if (!pattern.test(value)) {
138
+ validateIdCard.errors = [{
139
+ keyword: 'idCard',
140
+ message: 'Invalid ID card number',
141
+ params: {}
142
+ }];
143
+ return false;
144
+ }
145
+
146
+ // 验证校验码
147
+ if (!this._validateIdCardChecksum(value)) {
148
+ validateIdCard.errors = [{
149
+ keyword: 'idCard',
150
+ message: 'Invalid ID card checksum',
151
+ params: {}
152
+ }];
153
+ return false;
154
+ }
155
+
156
+ return true;
157
+ }
158
+ });
159
+ }
160
+ },
161
+
162
+ /**
163
+ * 验证身份证校验码
164
+ * @private
165
+ */
166
+ _validateIdCardChecksum(idCard) {
167
+ const weights = [7, 9, 10, 5, 8, 4, 2, 1, 6, 3, 7, 9, 10, 5, 8, 4, 2];
168
+ const checksums = ['1', '0', 'X', '9', '8', '7', '6', '5', '4', '3', '2'];
169
+
170
+ let sum = 0;
171
+ for (let i = 0; i < 17; i++) {
172
+ sum += parseInt(idCard[i]) * weights[i];
173
+ }
174
+
175
+ const checksum = checksums[sum % 11];
176
+ return idCard[17].toUpperCase() === checksum;
177
+ },
178
+
179
+ /**
180
+ * 生命周期钩子
181
+ */
182
+ hooks: {
183
+ onBeforeValidate(schema, data) {
184
+ // 验证前钩子
185
+ // 可以在这里修改 schema 或 data
186
+ },
187
+
188
+ onAfterValidate(result) {
189
+ // 验证后钩子
190
+ // 可以在这里修改验证结果
191
+ },
192
+
193
+ onError(error, context) {
194
+ // 错误处理钩子
195
+ console.error('[custom-validator] Error:', error.message);
196
+ }
197
+ }
198
+ };
199
+
200
+