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