befly 3.8.30 → 3.8.31
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/README.md +83 -0
- package/checks/checkApp.ts +31 -1
- package/hooks/cors.ts +3 -3
- package/hooks/parser.ts +3 -3
- package/hooks/validator.ts +1 -1
- package/lib/cacheHelper.ts +0 -6
- package/lib/cipher.ts +2 -1
- package/lib/connect.ts +17 -19
- package/lib/jwt.ts +1 -1
- package/lib/logger.ts +1 -1
- package/lib/validator.ts +149 -384
- package/loader/loadHooks.ts +4 -3
- package/loader/loadPlugins.ts +7 -9
- package/main.ts +22 -36
- package/package.json +4 -3
- package/plugins/cipher.ts +1 -1
- package/plugins/config.ts +3 -4
- package/plugins/db.ts +4 -5
- package/plugins/jwt.ts +3 -2
- package/plugins/logger.ts +6 -6
- package/plugins/redis.ts +8 -12
- package/router/static.ts +3 -6
- package/sync/syncAll.ts +7 -12
- package/sync/syncApi.ts +4 -3
- package/sync/syncDb.ts +6 -5
- package/sync/syncDev.ts +9 -8
- package/sync/syncMenu.ts +174 -132
- package/tests/integration.test.ts +2 -6
- package/tests/redisHelper.test.ts +1 -2
- package/tests/validator.test.ts +611 -85
- package/types/befly.d.ts +7 -0
- package/types/cache.d.ts +73 -0
- package/types/common.d.ts +1 -37
- package/types/database.d.ts +5 -0
- package/types/index.ts +5 -5
- package/types/plugin.d.ts +1 -4
- package/types/redis.d.ts +37 -2
- package/types/table.d.ts +6 -44
- package/config.ts +0 -70
- package/tests/validator-advanced.test.ts +0 -653
- package/types/addon.d.ts +0 -50
- package/types/crypto.d.ts +0 -23
- package/types/jwt.d.ts +0 -99
- package/types/logger.d.ts +0 -13
- package/types/tool.d.ts +0 -67
- package/types/validator.d.ts +0 -43
package/lib/validator.ts
CHANGED
|
@@ -1,462 +1,227 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* 数据验证器 - Befly 项目专用
|
|
3
|
-
*
|
|
4
|
-
* 使用正则缓存优化性能
|
|
3
|
+
* 纯静态类设计,简洁易用
|
|
5
4
|
*/
|
|
6
5
|
|
|
7
|
-
import type { ValidationResult, ValidationError } from '../types/validator';
|
|
8
6
|
import { RegexAliases, getCompiledRegex } from 'befly-shared/regex';
|
|
9
|
-
import type { TableDefinition, FieldDefinition } from 'befly-shared/types';
|
|
7
|
+
import type { TableDefinition, FieldDefinition, ValidateResult, SingleResult } from 'befly-shared/types';
|
|
10
8
|
|
|
11
9
|
/**
|
|
12
|
-
*
|
|
13
|
-
*
|
|
10
|
+
* 验证器类
|
|
11
|
+
*
|
|
12
|
+
* @example
|
|
13
|
+
* const result = Validator.validate(data, rules, ['email', 'name']);
|
|
14
|
+
* if (result.failed) {
|
|
15
|
+
* console.log(result.firstError);
|
|
16
|
+
* console.log(result.errors);
|
|
17
|
+
* console.log(result.errorFields);
|
|
18
|
+
* }
|
|
19
|
+
*
|
|
20
|
+
* const single = Validator.single(value, fieldDef);
|
|
21
|
+
* if (!single.error) {
|
|
22
|
+
* console.log(single.value);
|
|
23
|
+
* }
|
|
14
24
|
*/
|
|
15
25
|
export class Validator {
|
|
16
|
-
/** 正则别名映射(内置) */
|
|
17
|
-
private readonly regexAliases: Record<string, string> = RegexAliases;
|
|
18
|
-
|
|
19
26
|
/**
|
|
20
|
-
*
|
|
21
|
-
* @param data - 要验证的数据对象
|
|
22
|
-
* @param rules - 验证规则对象
|
|
23
|
-
* @param required - 必传字段数组
|
|
24
|
-
* @returns 验证结果 { code: 0|1, fields: {} }
|
|
27
|
+
* 验证数据对象
|
|
25
28
|
*/
|
|
26
|
-
validate(data: Record<string, any>, rules: TableDefinition, required: string[] = []):
|
|
27
|
-
const
|
|
28
|
-
code: 0,
|
|
29
|
-
fields: {}
|
|
30
|
-
};
|
|
29
|
+
static validate(data: Record<string, any>, rules: TableDefinition, required: string[] = []): ValidateResult {
|
|
30
|
+
const fieldErrors: Record<string, string> = {};
|
|
31
31
|
|
|
32
32
|
// 参数检查
|
|
33
|
-
if (!this.checkParams(data, rules, required, result)) {
|
|
34
|
-
return result;
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
// 检查必传字段
|
|
38
|
-
this.checkRequiredFields(data, rules, required, result);
|
|
39
|
-
|
|
40
|
-
// 验证所有在规则中定义的字段
|
|
41
|
-
this.validateFields(data, rules, required, result);
|
|
42
|
-
|
|
43
|
-
return result;
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
/**
|
|
47
|
-
* 检查参数有效性
|
|
48
|
-
*/
|
|
49
|
-
private checkParams(data: any, rules: any, required: any, result: ValidationResult): boolean {
|
|
50
33
|
if (!data || typeof data !== 'object' || Array.isArray(data)) {
|
|
51
|
-
|
|
52
|
-
result.fields.error = '数据必须是对象格式';
|
|
53
|
-
return false;
|
|
34
|
+
return this.buildResult({ _error: '数据必须是对象格式' });
|
|
54
35
|
}
|
|
55
|
-
|
|
56
36
|
if (!rules || typeof rules !== 'object') {
|
|
57
|
-
|
|
58
|
-
result.fields.error = '验证规则必须是对象格式';
|
|
59
|
-
return false;
|
|
37
|
+
return this.buildResult({ _error: '验证规则必须是对象格式' });
|
|
60
38
|
}
|
|
61
39
|
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
40
|
+
// 检查必填字段
|
|
41
|
+
for (const field of required) {
|
|
42
|
+
const value = data[field];
|
|
43
|
+
if (value === undefined || value === null || value === '') {
|
|
44
|
+
const label = rules[field]?.name || field;
|
|
45
|
+
fieldErrors[field] = `${label}为必填项`;
|
|
46
|
+
}
|
|
66
47
|
}
|
|
67
48
|
|
|
68
|
-
|
|
69
|
-
|
|
49
|
+
// 验证有值的字段
|
|
50
|
+
for (const [field, rule] of Object.entries(rules)) {
|
|
51
|
+
if (fieldErrors[field]) continue;
|
|
52
|
+
if (!(field in data) && !required.includes(field)) continue;
|
|
70
53
|
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
*/
|
|
74
|
-
private checkRequiredFields(data: Record<string, any>, rules: TableDefinition, required: string[], result: ValidationResult): void {
|
|
75
|
-
for (const fieldName of required) {
|
|
76
|
-
const value = data[fieldName];
|
|
77
|
-
if (!(fieldName in data) || value === undefined || value === null || value === '') {
|
|
78
|
-
result.code = 1;
|
|
79
|
-
const fieldDef = rules[fieldName];
|
|
80
|
-
const fieldLabel = fieldDef?.name || fieldName;
|
|
81
|
-
result.fields[fieldName] = `${fieldLabel}(${fieldName})为必填项`;
|
|
82
|
-
}
|
|
54
|
+
const error = this.checkField(data[field], rule, field);
|
|
55
|
+
if (error) fieldErrors[field] = error;
|
|
83
56
|
}
|
|
57
|
+
|
|
58
|
+
return this.buildResult(fieldErrors);
|
|
84
59
|
}
|
|
85
60
|
|
|
86
61
|
/**
|
|
87
|
-
*
|
|
62
|
+
* 验证单个值(带类型转换)
|
|
88
63
|
*/
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
// 如果字段不存在且不是必传字段,跳过验证
|
|
92
|
-
if (!(fieldName in data) && !required.includes(fieldName)) {
|
|
93
|
-
continue;
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
// 如果必传验证已经失败,跳过后续验证
|
|
97
|
-
if (result.fields[fieldName]) {
|
|
98
|
-
continue;
|
|
99
|
-
}
|
|
64
|
+
static single(value: any, fieldDef: FieldDefinition): SingleResult {
|
|
65
|
+
const { type, default: defaultValue } = fieldDef;
|
|
100
66
|
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
if (error) {
|
|
105
|
-
result.code = 1;
|
|
106
|
-
result.fields[fieldName] = error;
|
|
107
|
-
}
|
|
67
|
+
// 处理空值
|
|
68
|
+
if (value === undefined || value === null || value === '') {
|
|
69
|
+
return { value: this.defaultFor(type, defaultValue), error: null };
|
|
108
70
|
}
|
|
109
|
-
}
|
|
110
71
|
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
if (!regex || typeof regex !== 'string') {
|
|
116
|
-
return regex;
|
|
72
|
+
// 类型转换
|
|
73
|
+
const converted = this.convert(value, type);
|
|
74
|
+
if (converted.error) {
|
|
75
|
+
return { value: null, error: converted.error };
|
|
117
76
|
}
|
|
118
77
|
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
return resolvedRegex;
|
|
124
|
-
}
|
|
125
|
-
return regex;
|
|
78
|
+
// 规则验证
|
|
79
|
+
const error = this.checkRule(converted.value, fieldDef);
|
|
80
|
+
if (error) {
|
|
81
|
+
return { value: null, error: error };
|
|
126
82
|
}
|
|
127
83
|
|
|
128
|
-
return
|
|
84
|
+
return { value: converted.value, error: null };
|
|
129
85
|
}
|
|
130
86
|
|
|
131
|
-
|
|
132
|
-
* 验证单个字段的值
|
|
133
|
-
*/
|
|
134
|
-
private validateFieldValue(value: any, fieldDef: FieldDefinition, fieldName: string): ValidationError {
|
|
135
|
-
let { name, type, min, max, regexp } = fieldDef;
|
|
87
|
+
// ========== 私有方法 ==========
|
|
136
88
|
|
|
137
|
-
|
|
89
|
+
/** 构建结果对象 */
|
|
90
|
+
private static buildResult(fieldErrors: Record<string, string>): ValidateResult {
|
|
91
|
+
const errors = Object.values(fieldErrors);
|
|
92
|
+
const errorFields = Object.keys(fieldErrors);
|
|
93
|
+
const failed = errors.length > 0;
|
|
138
94
|
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
return this.validateArray(value, name, min, max, regexp, fieldName);
|
|
148
|
-
default:
|
|
149
|
-
return `字段 ${fieldName} 的类型 ${type} 不支持`;
|
|
150
|
-
}
|
|
95
|
+
return {
|
|
96
|
+
code: failed ? 1 : 0,
|
|
97
|
+
failed: failed,
|
|
98
|
+
firstError: failed ? errors[0] : null,
|
|
99
|
+
errors: errors,
|
|
100
|
+
errorFields: errorFields,
|
|
101
|
+
fieldErrors: fieldErrors
|
|
102
|
+
};
|
|
151
103
|
}
|
|
152
104
|
|
|
153
|
-
/**
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
private validateNumber(value: any, name: string, min: number | null, max: number | null, spec: string | null, fieldName: string): ValidationError {
|
|
157
|
-
try {
|
|
158
|
-
// 允许数字类型的字符串
|
|
159
|
-
let numValue = value;
|
|
160
|
-
if (typeof value === 'string') {
|
|
161
|
-
numValue = Number(value);
|
|
162
|
-
if (Number.isNaN(numValue) || !isFinite(numValue)) {
|
|
163
|
-
return `${name}(${fieldName})必须是数字`;
|
|
164
|
-
}
|
|
165
|
-
} else if (typeof numValue !== 'number' || Number.isNaN(numValue) || !isFinite(numValue)) {
|
|
166
|
-
return `${name}(${fieldName})必须是数字`;
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
if (min !== null && numValue < min) {
|
|
170
|
-
return `${name}(${fieldName})不能小于${min}`;
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
if (max !== null && max > 0 && numValue > max) {
|
|
174
|
-
return `${name}(${fieldName})不能大于${max}`;
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
if (spec && spec.trim() !== '') {
|
|
178
|
-
try {
|
|
179
|
-
const regExp = getCompiledRegex(spec);
|
|
180
|
-
if (!regExp.test(String(numValue))) {
|
|
181
|
-
return `${name}(${fieldName})格式不正确`;
|
|
182
|
-
}
|
|
183
|
-
} catch (error: any) {
|
|
184
|
-
return `${name}(${fieldName})的正则表达式格式错误`;
|
|
185
|
-
}
|
|
186
|
-
}
|
|
105
|
+
/** 验证单个字段 */
|
|
106
|
+
private static checkField(value: any, fieldDef: FieldDefinition, fieldName: string): string | null {
|
|
107
|
+
const label = fieldDef.name || fieldName;
|
|
187
108
|
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
return `${
|
|
109
|
+
const converted = this.convert(value, fieldDef.type);
|
|
110
|
+
if (converted.error) {
|
|
111
|
+
return `${label}${converted.error}`;
|
|
191
112
|
}
|
|
192
|
-
}
|
|
193
113
|
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
*/
|
|
197
|
-
private validateString(value: any, name: string, min: number | null, max: number | null, spec: string | null, fieldName: string): ValidationError {
|
|
198
|
-
try {
|
|
199
|
-
if (typeof value !== 'string') {
|
|
200
|
-
return `${name}(${fieldName})必须是字符串`;
|
|
201
|
-
}
|
|
202
|
-
|
|
203
|
-
if (min !== null && value.length < min) {
|
|
204
|
-
return `${name}(${fieldName})长度不能少于${min}个字符`;
|
|
205
|
-
}
|
|
206
|
-
|
|
207
|
-
if (max !== null && max > 0 && value.length > max) {
|
|
208
|
-
return `${name}(${fieldName})长度不能超过${max}个字符`;
|
|
209
|
-
}
|
|
210
|
-
|
|
211
|
-
if (spec && spec.trim() !== '') {
|
|
212
|
-
try {
|
|
213
|
-
const regExp = getCompiledRegex(spec);
|
|
214
|
-
if (!regExp.test(value)) {
|
|
215
|
-
return `${name}(${fieldName})格式不正确`;
|
|
216
|
-
}
|
|
217
|
-
} catch (error: any) {
|
|
218
|
-
return `${name}(${fieldName})的正则表达式格式错误`;
|
|
219
|
-
}
|
|
220
|
-
}
|
|
221
|
-
|
|
222
|
-
return null;
|
|
223
|
-
} catch (error: any) {
|
|
224
|
-
return `${name}(${fieldName})验证出错: ${error.message}`;
|
|
225
|
-
}
|
|
114
|
+
const error = this.checkRule(converted.value, fieldDef);
|
|
115
|
+
return error ? `${label}${error}` : null;
|
|
226
116
|
}
|
|
227
117
|
|
|
228
|
-
/**
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
return `${name}(${fieldName})必须是数组`;
|
|
235
|
-
}
|
|
236
|
-
|
|
237
|
-
if (min !== null && value.length < min) {
|
|
238
|
-
return `${name}(${fieldName})至少需要${min}个元素`;
|
|
239
|
-
}
|
|
240
|
-
|
|
241
|
-
if (max !== null && max > 0 && value.length > max) {
|
|
242
|
-
return `${name}(${fieldName})最多只能有${max}个元素`;
|
|
243
|
-
}
|
|
244
|
-
|
|
245
|
-
if (spec && spec.trim() !== '') {
|
|
246
|
-
try {
|
|
247
|
-
const regExp = getCompiledRegex(spec);
|
|
248
|
-
for (const item of value) {
|
|
249
|
-
if (!regExp.test(String(item))) {
|
|
250
|
-
return `${name}(${fieldName})中的元素"${item}"格式不正确`;
|
|
251
|
-
}
|
|
252
|
-
}
|
|
253
|
-
} catch (error: any) {
|
|
254
|
-
return `${name}(${fieldName})的正则表达式格式错误`;
|
|
118
|
+
/** 类型转换 */
|
|
119
|
+
private static convert(value: any, type: string): { value: any; error: string | null } {
|
|
120
|
+
switch (type.toLowerCase()) {
|
|
121
|
+
case 'number':
|
|
122
|
+
if (typeof value === 'number') {
|
|
123
|
+
return Number.isNaN(value) || !isFinite(value) ? { value: null, error: '必须是有效数字' } : { value: value, error: null };
|
|
255
124
|
}
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
}
|
|
262
|
-
}
|
|
125
|
+
if (typeof value === 'string') {
|
|
126
|
+
const num = Number(value);
|
|
127
|
+
return Number.isNaN(num) || !isFinite(num) ? { value: null, error: '必须是数字' } : { value: num, error: null };
|
|
128
|
+
}
|
|
129
|
+
return { value: null, error: '必须是数字' };
|
|
263
130
|
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
validateSingleValue(value: any, fieldDef: FieldDefinition): { valid: boolean; value: any; errors: string[] } {
|
|
268
|
-
let { name, type, min, max, regexp, default: defaultValue } = fieldDef;
|
|
131
|
+
case 'string':
|
|
132
|
+
case 'text':
|
|
133
|
+
return typeof value === 'string' ? { value: value, error: null } : { value: null, error: '必须是字符串' };
|
|
269
134
|
|
|
270
|
-
|
|
135
|
+
case 'array_string':
|
|
136
|
+
case 'array_text':
|
|
137
|
+
return Array.isArray(value) ? { value: value, error: null } : { value: null, error: '必须是数组' };
|
|
271
138
|
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
if (defaultValue !== null) {
|
|
275
|
-
if ((type === 'array_string' || type === 'array_text') && typeof defaultValue === 'string') {
|
|
276
|
-
if (defaultValue === '[]') {
|
|
277
|
-
return { valid: true, value: [], errors: [] };
|
|
278
|
-
}
|
|
279
|
-
try {
|
|
280
|
-
const parsedArray = JSON.parse(defaultValue);
|
|
281
|
-
if (Array.isArray(parsedArray)) {
|
|
282
|
-
return { valid: true, value: parsedArray, errors: [] };
|
|
283
|
-
}
|
|
284
|
-
} catch {
|
|
285
|
-
return { valid: true, value: [], errors: [] };
|
|
286
|
-
}
|
|
287
|
-
}
|
|
288
|
-
// 数字类型默认值转换
|
|
289
|
-
if (type === 'number' && typeof defaultValue === 'string') {
|
|
290
|
-
const numValue = Number(defaultValue);
|
|
291
|
-
if (!isNaN(numValue)) {
|
|
292
|
-
return { valid: true, value: numValue, errors: [] };
|
|
293
|
-
}
|
|
294
|
-
}
|
|
295
|
-
return { valid: true, value: defaultValue, errors: [] };
|
|
296
|
-
}
|
|
297
|
-
if (type === 'number') {
|
|
298
|
-
return { valid: true, value: 0, errors: [] };
|
|
299
|
-
} else if (type === 'array_string' || type === 'array_text') {
|
|
300
|
-
return { valid: true, value: [], errors: [] };
|
|
301
|
-
} else if (type === 'string' || type === 'text') {
|
|
302
|
-
return { valid: true, value: '', errors: [] };
|
|
303
|
-
}
|
|
139
|
+
default:
|
|
140
|
+
return { value: value, error: null };
|
|
304
141
|
}
|
|
142
|
+
}
|
|
305
143
|
|
|
306
|
-
|
|
144
|
+
/** 规则验证 */
|
|
145
|
+
private static checkRule(value: any, fieldDef: FieldDefinition): string | null {
|
|
146
|
+
const { type, min, max, regexp } = fieldDef;
|
|
147
|
+
const regex = this.resolveRegex(regexp);
|
|
307
148
|
|
|
308
|
-
// 类型转换
|
|
309
|
-
let convertedValue = value;
|
|
310
|
-
if (type === 'number' && typeof value === 'string') {
|
|
311
|
-
convertedValue = Number(value);
|
|
312
|
-
if (isNaN(convertedValue)) {
|
|
313
|
-
errors.push(`${name || '值'}必须是有效的数字`);
|
|
314
|
-
return { valid: false, value: null, errors };
|
|
315
|
-
}
|
|
316
|
-
}
|
|
317
|
-
|
|
318
|
-
// 类型验证
|
|
319
149
|
switch (type.toLowerCase()) {
|
|
320
150
|
case 'number':
|
|
321
|
-
if (
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
if (min !== null && convertedValue < min) {
|
|
325
|
-
errors.push(`${name || '值'}不能小于${min}`);
|
|
326
|
-
}
|
|
327
|
-
if (max !== null && max > 0 && convertedValue > max) {
|
|
328
|
-
errors.push(`${name || '值'}不能大于${max}`);
|
|
329
|
-
}
|
|
330
|
-
if (regexp && regexp.trim() !== '') {
|
|
331
|
-
try {
|
|
332
|
-
const regExp = getCompiledRegex(regexp);
|
|
333
|
-
if (!regExp.test(String(convertedValue))) {
|
|
334
|
-
errors.push(`${name || '值'}格式不正确`);
|
|
335
|
-
}
|
|
336
|
-
} catch (e: any) {
|
|
337
|
-
errors.push(`${name || '值'}的正则表达式格式错误: ${e.message}`);
|
|
338
|
-
}
|
|
339
|
-
}
|
|
151
|
+
if (min !== null && value < min) return `不能小于${min}`;
|
|
152
|
+
if (max !== null && max > 0 && value > max) return `不能大于${max}`;
|
|
153
|
+
if (regex && !this.testRegex(regex, String(value))) return '格式不正确';
|
|
340
154
|
break;
|
|
341
155
|
|
|
342
156
|
case 'string':
|
|
343
157
|
case 'text':
|
|
344
|
-
if (
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
if (min !== null && convertedValue.length < min) {
|
|
348
|
-
errors.push(`${name || '值'}长度不能少于${min}个字符`);
|
|
349
|
-
}
|
|
350
|
-
if (max !== null && max > 0 && convertedValue.length > max) {
|
|
351
|
-
errors.push(`${name || '值'}长度不能超过${max}个字符`);
|
|
352
|
-
}
|
|
353
|
-
if (regexp && regexp.trim() !== '') {
|
|
354
|
-
try {
|
|
355
|
-
const regExp = getCompiledRegex(regexp);
|
|
356
|
-
if (!regExp.test(convertedValue)) {
|
|
357
|
-
errors.push(`${name || '值'}格式不正确`);
|
|
358
|
-
}
|
|
359
|
-
} catch {
|
|
360
|
-
errors.push(`${name || '值'}的正则表达式格式错误`);
|
|
361
|
-
}
|
|
362
|
-
}
|
|
158
|
+
if (min !== null && value.length < min) return `长度不能少于${min}个字符`;
|
|
159
|
+
if (max !== null && max > 0 && value.length > max) return `长度不能超过${max}个字符`;
|
|
160
|
+
if (regex && !this.testRegex(regex, value)) return '格式不正确';
|
|
363
161
|
break;
|
|
364
162
|
|
|
365
163
|
case 'array_string':
|
|
366
164
|
case 'array_text':
|
|
367
|
-
if (
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
}
|
|
373
|
-
if (max !== null && max > 0 && convertedValue.length > max) {
|
|
374
|
-
errors.push(`${name || '值'}元素数量不能超过${max}个`);
|
|
375
|
-
}
|
|
376
|
-
if (regexp && regexp.trim() !== '') {
|
|
377
|
-
try {
|
|
378
|
-
const regExp = getCompiledRegex(regexp);
|
|
379
|
-
for (const item of convertedValue) {
|
|
380
|
-
if (!regExp.test(String(item))) {
|
|
381
|
-
errors.push(`${name || '值'}的元素格式不正确`);
|
|
382
|
-
break;
|
|
383
|
-
}
|
|
384
|
-
}
|
|
385
|
-
} catch {
|
|
386
|
-
errors.push(`${name || '值'}的正则表达式格式错误`);
|
|
165
|
+
if (min !== null && value.length < min) return `至少需要${min}个元素`;
|
|
166
|
+
if (max !== null && max > 0 && value.length > max) return `最多只能有${max}个元素`;
|
|
167
|
+
if (regex) {
|
|
168
|
+
for (const item of value) {
|
|
169
|
+
if (!this.testRegex(regex, String(item))) return '元素格式不正确';
|
|
387
170
|
}
|
|
388
171
|
}
|
|
389
172
|
break;
|
|
390
173
|
}
|
|
391
|
-
|
|
392
|
-
return {
|
|
393
|
-
valid: errors.length === 0,
|
|
394
|
-
value: errors.length === 0 ? convertedValue : null,
|
|
395
|
-
errors
|
|
396
|
-
};
|
|
174
|
+
return null;
|
|
397
175
|
}
|
|
398
176
|
|
|
399
|
-
/**
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
static validate(dataOrValue: any, rulesOrFieldDef: any, required?: string[]): any {
|
|
405
|
-
const validator = new Validator();
|
|
406
|
-
|
|
407
|
-
if (rulesOrFieldDef && 'type' in rulesOrFieldDef) {
|
|
408
|
-
return validator.validateSingleValue(dataOrValue, rulesOrFieldDef);
|
|
177
|
+
/** 解析正则别名 */
|
|
178
|
+
private static resolveRegex(regexp: string | null): string | null {
|
|
179
|
+
if (!regexp) return null;
|
|
180
|
+
if (regexp.startsWith('@')) {
|
|
181
|
+
return RegexAliases[regexp.substring(1)] || regexp;
|
|
409
182
|
}
|
|
410
|
-
|
|
411
|
-
return validator.validate(dataOrValue, rulesOrFieldDef, required || []);
|
|
183
|
+
return regexp;
|
|
412
184
|
}
|
|
413
185
|
|
|
414
|
-
/**
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
return
|
|
186
|
+
/** 测试正则 */
|
|
187
|
+
private static testRegex(pattern: string, value: string): boolean {
|
|
188
|
+
try {
|
|
189
|
+
return getCompiledRegex(pattern).test(value);
|
|
190
|
+
} catch {
|
|
191
|
+
return false;
|
|
420
192
|
}
|
|
421
|
-
return result.code === 0;
|
|
422
193
|
}
|
|
423
194
|
|
|
424
|
-
/**
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
195
|
+
/** 获取默认值 */
|
|
196
|
+
private static defaultFor(type: string, defaultValue: any): any {
|
|
197
|
+
if (defaultValue !== null && defaultValue !== undefined) {
|
|
198
|
+
// 数组默认值
|
|
199
|
+
if ((type === 'array_string' || type === 'array_text') && typeof defaultValue === 'string') {
|
|
200
|
+
if (defaultValue === '[]') return [];
|
|
201
|
+
try {
|
|
202
|
+
const parsed = JSON.parse(defaultValue);
|
|
203
|
+
return Array.isArray(parsed) ? parsed : [];
|
|
204
|
+
} catch {
|
|
205
|
+
return [];
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
// 数字默认值
|
|
209
|
+
if (type === 'number' && typeof defaultValue === 'string') {
|
|
210
|
+
const num = Number(defaultValue);
|
|
211
|
+
return isNaN(num) ? 0 : num;
|
|
212
|
+
}
|
|
213
|
+
return defaultValue;
|
|
437
214
|
}
|
|
438
|
-
if (result.code === 0) return null;
|
|
439
|
-
if (!result.fields) return null;
|
|
440
|
-
const errors = Object.values(result.fields);
|
|
441
|
-
return errors.length > 0 ? errors[0] : null;
|
|
442
|
-
}
|
|
443
215
|
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
216
|
+
// 类型默认值
|
|
217
|
+
switch (type.toLowerCase()) {
|
|
218
|
+
case 'number':
|
|
219
|
+
return 0;
|
|
220
|
+
case 'array_string':
|
|
221
|
+
case 'array_text':
|
|
222
|
+
return [];
|
|
223
|
+
default:
|
|
224
|
+
return '';
|
|
450
225
|
}
|
|
451
|
-
if (result.code === 0) return [];
|
|
452
|
-
if (!result.fields) return [];
|
|
453
|
-
return Object.values(result.fields);
|
|
454
|
-
}
|
|
455
|
-
|
|
456
|
-
/**
|
|
457
|
-
* 获取错误字段列表
|
|
458
|
-
*/
|
|
459
|
-
static getErrorFields(result: ValidationResult): string[] {
|
|
460
|
-
return result.code === 0 ? [] : Object.keys(result.fields);
|
|
461
226
|
}
|
|
462
227
|
}
|
package/loader/loadHooks.ts
CHANGED
|
@@ -7,17 +7,18 @@
|
|
|
7
7
|
import { Logger } from '../lib/logger.js';
|
|
8
8
|
import { coreHookDir } from '../paths.js';
|
|
9
9
|
import { scanModules } from '../util.js';
|
|
10
|
+
import { beflyConfig } from '../befly.config.js';
|
|
10
11
|
|
|
11
12
|
// 类型导入
|
|
12
13
|
import type { Hook } from '../types/hook.js';
|
|
13
14
|
|
|
14
|
-
export async function loadHooks(
|
|
15
|
+
export async function loadHooks(hooks: Hook[]): Promise<void> {
|
|
15
16
|
try {
|
|
16
17
|
// 1. 扫描核心钩子
|
|
17
|
-
const coreHooks = await scanModules<Hook>(coreHookDir, 'core', '钩子'
|
|
18
|
+
const coreHooks = await scanModules<Hook>(coreHookDir, 'core', '钩子');
|
|
18
19
|
|
|
19
20
|
// 2. 过滤禁用的钩子
|
|
20
|
-
const disableHooks =
|
|
21
|
+
const disableHooks = beflyConfig.disableHooks || [];
|
|
21
22
|
const enabledHooks = coreHooks.filter((hook) => !disableHooks.includes(hook.name));
|
|
22
23
|
|
|
23
24
|
if (disableHooks.length > 0) {
|