schema-dsl 2.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.eslintignore +10 -0
- package/.eslintrc.json +27 -0
- package/.github/CODE_OF_CONDUCT.md +45 -0
- package/.github/ISSUE_TEMPLATE/bug_report.md +57 -0
- package/.github/ISSUE_TEMPLATE/config.yml +11 -0
- package/.github/ISSUE_TEMPLATE/feature_request.md +45 -0
- package/.github/ISSUE_TEMPLATE/question.md +31 -0
- package/.github/PULL_REQUEST_TEMPLATE.md +70 -0
- package/.github/SECURITY.md +184 -0
- package/.github/workflows/ci.yml +35 -0
- package/CHANGELOG.md +633 -0
- package/CONTRIBUTING.md +368 -0
- package/LICENSE +21 -0
- package/README.md +1122 -0
- package/STATUS.md +273 -0
- package/docs/FEATURE-INDEX.md +521 -0
- package/docs/INDEX.md +224 -0
- package/docs/api-reference.md +1098 -0
- package/docs/best-practices.md +672 -0
- package/docs/cache-manager.md +336 -0
- package/docs/design-philosophy.md +602 -0
- package/docs/dsl-syntax.md +654 -0
- package/docs/dynamic-locale.md +552 -0
- package/docs/error-handling.md +703 -0
- package/docs/export-guide.md +459 -0
- package/docs/faq.md +576 -0
- package/docs/frontend-i18n-guide.md +290 -0
- package/docs/i18n-user-guide.md +488 -0
- package/docs/label-vs-description.md +262 -0
- package/docs/markdown-exporter.md +398 -0
- package/docs/mongodb-exporter.md +279 -0
- package/docs/multi-type-support.md +319 -0
- package/docs/mysql-exporter.md +257 -0
- package/docs/plugin-system.md +542 -0
- package/docs/postgresql-exporter.md +290 -0
- package/docs/quick-start.md +761 -0
- package/docs/schema-helper.md +340 -0
- package/docs/schema-utils.md +492 -0
- package/docs/string-extensions.md +480 -0
- package/docs/troubleshooting.md +471 -0
- package/docs/type-converter.md +319 -0
- package/docs/type-reference.md +219 -0
- package/docs/validate.md +486 -0
- package/docs/validation-guide.md +484 -0
- package/examples/array-dsl-example.js +227 -0
- package/examples/custom-extension.js +85 -0
- package/examples/dsl-match-example.js +74 -0
- package/examples/dsl-style.js +118 -0
- package/examples/dynamic-locale-configuration.js +348 -0
- package/examples/dynamic-locale-example.js +287 -0
- package/examples/export-demo.js +130 -0
- package/examples/i18n-full-demo.js +310 -0
- package/examples/i18n-memory-safety.examples.js +268 -0
- package/examples/markdown-export.js +71 -0
- package/examples/middleware-usage.js +93 -0
- package/examples/password-reset/README.md +153 -0
- package/examples/password-reset/schema.js +26 -0
- package/examples/password-reset/test.js +101 -0
- package/examples/plugin-system.examples.js +205 -0
- package/examples/simple-example.js +122 -0
- package/examples/string-extensions.js +297 -0
- package/examples/user-registration/README.md +156 -0
- package/examples/user-registration/routes.js +92 -0
- package/examples/user-registration/schema.js +150 -0
- package/examples/user-registration/server.js +74 -0
- package/index.d.ts +1999 -0
- package/index.js +270 -0
- package/index.mjs +30 -0
- package/lib/adapters/DslAdapter.js +653 -0
- package/lib/adapters/index.js +20 -0
- package/lib/config/constants.js +286 -0
- package/lib/config/patterns/creditCard.js +9 -0
- package/lib/config/patterns/idCard.js +9 -0
- package/lib/config/patterns/index.js +8 -0
- package/lib/config/patterns/licensePlate.js +4 -0
- package/lib/config/patterns/passport.js +4 -0
- package/lib/config/patterns/phone.js +9 -0
- package/lib/config/patterns/postalCode.js +5 -0
- package/lib/core/CacheManager.js +376 -0
- package/lib/core/DslBuilder.js +740 -0
- package/lib/core/ErrorCodes.js +233 -0
- package/lib/core/ErrorFormatter.js +342 -0
- package/lib/core/JSONSchemaCore.js +347 -0
- package/lib/core/Locale.js +119 -0
- package/lib/core/MessageTemplate.js +89 -0
- package/lib/core/PluginManager.js +448 -0
- package/lib/core/StringExtensions.js +209 -0
- package/lib/core/Validator.js +316 -0
- package/lib/exporters/MarkdownExporter.js +420 -0
- package/lib/exporters/MongoDBExporter.js +162 -0
- package/lib/exporters/MySQLExporter.js +212 -0
- package/lib/exporters/PostgreSQLExporter.js +289 -0
- package/lib/exporters/index.js +24 -0
- package/lib/locales/en-US.js +65 -0
- package/lib/locales/es-ES.js +66 -0
- package/lib/locales/fr-FR.js +66 -0
- package/lib/locales/index.js +8 -0
- package/lib/locales/ja-JP.js +66 -0
- package/lib/locales/zh-CN.js +93 -0
- package/lib/utils/LRUCache.js +174 -0
- package/lib/utils/SchemaHelper.js +240 -0
- package/lib/utils/SchemaUtils.js +313 -0
- package/lib/utils/TypeConverter.js +245 -0
- package/lib/utils/index.js +13 -0
- package/lib/validators/CustomKeywords.js +203 -0
- package/lib/validators/index.js +11 -0
- package/package.json +70 -0
- package/plugins/custom-format.js +101 -0
- package/plugins/custom-validator.js +200 -0
|
@@ -0,0 +1,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
|
+
|
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
|
+
|