befly 3.10.18 → 3.11.1
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 -307
- package/dist/befly.config.d.ts +7 -0
- package/{befly.config.ts → dist/befly.config.js} +11 -36
- package/dist/befly.js +15621 -0
- package/dist/befly.min.js +21 -0
- package/dist/checks/checkApi.d.ts +1 -0
- package/{checks/checkApi.ts → dist/checks/checkApi.js} +12 -30
- package/dist/checks/checkHook.d.ts +1 -0
- package/dist/checks/checkHook.js +86 -0
- package/dist/checks/checkMenu.d.ts +7 -0
- package/{checks/checkMenu.ts → dist/checks/checkMenu.js} +18 -53
- package/dist/checks/checkPlugin.d.ts +1 -0
- package/dist/checks/checkPlugin.js +86 -0
- package/dist/checks/checkTable.d.ts +6 -0
- package/{checks/checkTable.ts → dist/checks/checkTable.js} +17 -41
- package/dist/configs/presetFields.d.ts +4 -0
- package/{configs/presetFields.ts → dist/configs/presetFields.js} +1 -1
- package/dist/configs/presetRegexp.d.ts +145 -0
- package/{utils/regex.ts → dist/configs/presetRegexp.js} +8 -31
- package/dist/hooks/auth.d.ts +7 -0
- package/{hooks/auth.ts → dist/hooks/auth.js} +8 -10
- package/dist/hooks/cors.d.ts +11 -0
- package/{hooks/cors.ts → dist/hooks/cors.js} +5 -13
- package/dist/hooks/parser.d.ts +14 -0
- package/{hooks/parser.ts → dist/hooks/parser.js} +31 -45
- package/dist/hooks/permission.d.ts +14 -0
- package/{hooks/permission.ts → dist/hooks/permission.js} +16 -25
- package/dist/hooks/validator.d.ts +11 -0
- package/{hooks/validator.ts → dist/hooks/validator.js} +9 -14
- package/dist/index.d.ts +26 -0
- package/{main.ts → dist/index.js} +61 -100
- package/dist/lib/asyncContext.d.ts +21 -0
- package/dist/lib/asyncContext.js +27 -0
- package/dist/lib/cacheHelper.d.ts +95 -0
- package/{lib/cacheHelper.ts → dist/lib/cacheHelper.js} +45 -105
- package/dist/lib/cacheKeys.d.ts +23 -0
- package/{lib/cacheKeys.ts → dist/lib/cacheKeys.js} +5 -10
- package/dist/lib/cipher.d.ts +153 -0
- package/{lib/cipher.ts → dist/lib/cipher.js} +23 -44
- package/dist/lib/connect.d.ts +91 -0
- package/{lib/connect.ts → dist/lib/connect.js} +47 -88
- package/dist/lib/dbDialect.d.ts +87 -0
- package/{lib/dbDialect.ts → dist/lib/dbDialect.js} +32 -112
- package/dist/lib/dbHelper.d.ts +204 -0
- package/{lib/dbHelper.ts → dist/lib/dbHelper.js} +82 -241
- package/dist/lib/dbUtils.d.ts +68 -0
- package/{lib/dbUtils.ts → dist/lib/dbUtils.js} +51 -126
- package/dist/lib/jwt.d.ts +13 -0
- package/{lib/jwt.ts → dist/lib/jwt.js} +11 -32
- package/dist/lib/logger.d.ts +42 -0
- package/dist/lib/logger.js +1144 -0
- package/dist/lib/redisHelper.d.ts +185 -0
- package/{lib/redisHelper.ts → dist/lib/redisHelper.js} +97 -141
- package/dist/lib/sqlBuilder.d.ts +160 -0
- package/{lib/sqlBuilder.ts → dist/lib/sqlBuilder.js} +132 -278
- package/dist/lib/sqlCheck.d.ts +23 -0
- package/{lib/sqlCheck.ts → dist/lib/sqlCheck.js} +24 -41
- package/dist/lib/validator.d.ts +45 -0
- package/{lib/validator.ts → dist/lib/validator.js} +44 -61
- package/dist/loader/loadApis.d.ts +12 -0
- package/{loader/loadApis.ts → dist/loader/loadApis.js} +10 -20
- package/dist/loader/loadHooks.d.ts +7 -0
- package/dist/loader/loadHooks.js +35 -0
- package/dist/loader/loadPlugins.d.ts +8 -0
- package/{loader/loadPlugins.ts → dist/loader/loadPlugins.js} +14 -26
- package/dist/paths.d.ts +93 -0
- package/{paths.ts → dist/paths.js} +6 -19
- package/dist/plugins/cache.d.ts +16 -0
- package/{plugins/cache.ts → dist/plugins/cache.js} +7 -12
- package/dist/plugins/cipher.d.ts +12 -0
- package/{plugins/cipher.ts → dist/plugins/cipher.js} +4 -6
- package/dist/plugins/config.d.ts +12 -0
- package/dist/plugins/config.js +8 -0
- package/dist/plugins/db.d.ts +16 -0
- package/{plugins/db.ts → dist/plugins/db.js} +11 -17
- package/dist/plugins/jwt.d.ts +12 -0
- package/dist/plugins/jwt.js +12 -0
- package/dist/plugins/logger.d.ts +32 -0
- package/{plugins/logger.ts → dist/plugins/logger.js} +5 -8
- package/dist/plugins/redis.d.ts +16 -0
- package/{plugins/redis.ts → dist/plugins/redis.js} +9 -12
- package/dist/plugins/tool.d.ts +81 -0
- package/{plugins/tool.ts → dist/plugins/tool.js} +9 -30
- package/dist/router/api.d.ts +14 -0
- package/dist/router/api.js +107 -0
- package/dist/router/static.d.ts +9 -0
- package/{router/static.ts → dist/router/static.js} +20 -34
- package/dist/scripts/ensureDist.d.ts +1 -0
- package/dist/scripts/ensureDist.js +296 -0
- package/dist/sync/syncApi.d.ts +3 -0
- package/{sync/syncApi.ts → dist/sync/syncApi.js} +35 -55
- package/dist/sync/syncCache.d.ts +2 -0
- package/{sync/syncCache.ts → dist/sync/syncCache.js} +1 -6
- package/dist/sync/syncDev.d.ts +6 -0
- package/{sync/syncDev.ts → dist/sync/syncDev.js} +29 -62
- package/dist/sync/syncMenu.d.ts +14 -0
- package/{sync/syncMenu.ts → dist/sync/syncMenu.js} +65 -125
- package/dist/sync/syncTable.d.ts +151 -0
- package/{sync/syncTable.ts → dist/sync/syncTable.js} +172 -379
- package/{types → dist/types}/api.d.ts +12 -51
- package/dist/types/api.js +4 -0
- package/{types → dist/types}/befly.d.ts +32 -227
- package/dist/types/befly.js +4 -0
- package/{types → dist/types}/cache.d.ts +7 -15
- package/dist/types/cache.js +4 -0
- package/dist/types/cipher.d.ts +27 -0
- package/dist/types/cipher.js +7 -0
- package/{types → dist/types}/common.d.ts +8 -33
- package/dist/types/common.js +5 -0
- package/{types → dist/types}/context.d.ts +3 -5
- package/dist/types/context.js +4 -0
- package/{types → dist/types}/crypto.d.ts +0 -3
- package/dist/types/crypto.js +4 -0
- package/dist/types/database.d.ts +138 -0
- package/dist/types/database.js +4 -0
- package/dist/types/hook.d.ts +17 -0
- package/dist/types/hook.js +6 -0
- package/dist/types/jwt.d.ts +75 -0
- package/dist/types/jwt.js +4 -0
- package/dist/types/logger.d.ts +59 -0
- package/dist/types/logger.js +6 -0
- package/dist/types/plugin.d.ts +16 -0
- package/dist/types/plugin.js +6 -0
- package/dist/types/redis.d.ts +71 -0
- package/dist/types/redis.js +4 -0
- package/{types/roleApisCache.ts → dist/types/roleApisCache.d.ts} +0 -2
- package/dist/types/roleApisCache.js +8 -0
- package/dist/types/sync.d.ts +92 -0
- package/dist/types/sync.js +4 -0
- package/dist/types/table.d.ts +34 -0
- package/dist/types/table.js +4 -0
- package/dist/types/validate.d.ts +67 -0
- package/dist/types/validate.js +4 -0
- package/dist/utils/calcPerfTime.d.ts +4 -0
- package/{utils/calcPerfTime.ts → dist/utils/calcPerfTime.js} +3 -3
- package/dist/utils/convertBigIntFields.d.ts +11 -0
- package/{utils/convertBigIntFields.ts → dist/utils/convertBigIntFields.js} +5 -9
- package/dist/utils/cors.d.ts +8 -0
- package/{utils/cors.ts → dist/utils/cors.js} +1 -3
- package/dist/utils/disableMenusGlob.d.ts +13 -0
- package/{utils/disableMenusGlob.ts → dist/utils/disableMenusGlob.js} +9 -29
- package/dist/utils/fieldClear.d.ts +11 -0
- package/{utils/fieldClear.ts → dist/utils/fieldClear.js} +15 -33
- package/dist/utils/getClientIp.d.ts +6 -0
- package/{utils/getClientIp.ts → dist/utils/getClientIp.js} +1 -7
- package/dist/utils/importDefault.d.ts +1 -0
- package/dist/utils/importDefault.js +29 -0
- package/dist/utils/isDirentDirectory.d.ts +2 -0
- package/{utils/isDirentDirectory.ts → dist/utils/isDirentDirectory.js} +3 -8
- package/dist/utils/loadMenuConfigs.d.ts +29 -0
- package/{utils/loadMenuConfigs.ts → dist/utils/loadMenuConfigs.js} +66 -52
- package/dist/utils/mergeAndConcat.d.ts +7 -0
- package/dist/utils/mergeAndConcat.js +72 -0
- package/dist/utils/processAtSymbol.d.ts +4 -0
- package/{utils/processFields.ts → dist/utils/processAtSymbol.js} +5 -9
- package/dist/utils/processInfo.d.ts +24 -0
- package/{utils/process.ts → dist/utils/processInfo.js} +2 -18
- package/dist/utils/response.d.ts +20 -0
- package/{utils/response.ts → dist/utils/response.js} +28 -49
- package/dist/utils/scanAddons.d.ts +17 -0
- package/{utils/scanAddons.ts → dist/utils/scanAddons.js} +7 -41
- package/dist/utils/scanConfig.d.ts +26 -0
- package/{utils/scanConfig.ts → dist/utils/scanConfig.js} +28 -66
- package/dist/utils/scanCoreBuiltins.d.ts +3 -0
- package/dist/utils/scanCoreBuiltins.js +65 -0
- package/dist/utils/scanFiles.d.ts +30 -0
- package/{utils/scanFiles.ts → dist/utils/scanFiles.js} +44 -71
- package/dist/utils/scanSources.d.ts +10 -0
- package/dist/utils/scanSources.js +46 -0
- package/dist/utils/sortModules.d.ts +28 -0
- package/{utils/sortModules.ts → dist/utils/sortModules.js} +26 -66
- package/dist/utils/util.d.ts +84 -0
- package/dist/utils/util.js +262 -0
- package/package.json +26 -34
- package/.gitignore +0 -0
- package/bunfig.toml +0 -3
- package/checks/checkHook.ts +0 -48
- package/checks/checkPlugin.ts +0 -48
- package/configs/presetRegexp.ts +0 -225
- package/docs/README.md +0 -98
- package/docs/api/api.md +0 -1921
- package/docs/guide/examples.md +0 -926
- package/docs/guide/quickstart.md +0 -354
- package/docs/hooks/auth.md +0 -38
- package/docs/hooks/cors.md +0 -28
- package/docs/hooks/hook.md +0 -838
- package/docs/hooks/parser.md +0 -19
- package/docs/hooks/rateLimit.md +0 -47
- package/docs/infra/redis.md +0 -628
- package/docs/plugins/cipher.md +0 -61
- package/docs/plugins/database.md +0 -189
- package/docs/plugins/plugin.md +0 -986
- package/docs/reference/addon.md +0 -510
- package/docs/reference/config.md +0 -573
- package/docs/reference/logger.md +0 -495
- package/docs/reference/sync.md +0 -478
- package/docs/reference/table.md +0 -763
- package/docs/reference/validator.md +0 -620
- package/lib/asyncContext.ts +0 -43
- package/lib/logger.ts +0 -811
- package/loader/loadHooks.ts +0 -51
- package/plugins/config.ts +0 -13
- package/plugins/jwt.ts +0 -15
- package/router/api.ts +0 -130
- package/tsconfig.json +0 -8
- package/types/database.d.ts +0 -541
- package/types/hook.d.ts +0 -25
- package/types/jwt.d.ts +0 -118
- package/types/logger.d.ts +0 -65
- package/types/plugin.d.ts +0 -19
- package/types/redis.d.ts +0 -83
- package/types/sync.d.ts +0 -398
- package/types/table.d.ts +0 -216
- package/types/validate.d.ts +0 -69
- package/utils/arrayKeysToCamel.ts +0 -18
- package/utils/configTypes.ts +0 -3
- package/utils/genShortId.ts +0 -12
- package/utils/importDefault.ts +0 -21
- package/utils/keysToCamel.ts +0 -22
- package/utils/keysToSnake.ts +0 -22
- package/utils/pickFields.ts +0 -19
- package/utils/scanSources.ts +0 -64
- package/utils/sqlLog.ts +0 -37
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SQL 入参校验工具(静态类)
|
|
3
|
+
*
|
|
4
|
+
* 目标:把“参数合法性/一致性/安全性”判断从 SqlBuilder 等拼接逻辑中拆出来,便于复用与维护。
|
|
5
|
+
*
|
|
6
|
+
* 说明:这里的校验仅关注“字符串/标识符/批量数据结构”层面的正确性;
|
|
7
|
+
* 具体 SQL 语义(如字段是否存在)不在此处校验。
|
|
8
|
+
*/
|
|
9
|
+
export declare class SqlCheck {
|
|
10
|
+
private static readonly SAFE_IDENTIFIER_RE;
|
|
11
|
+
static assertNonEmptyString(value: unknown, label: string): asserts value is string;
|
|
12
|
+
static assertNoUndefinedParam(value: unknown, label: string): void;
|
|
13
|
+
static startsWithQuote(value: string): boolean;
|
|
14
|
+
static isQuotedIdentPaired(value: string): boolean;
|
|
15
|
+
static assertPairedQuotedIdentIfStartsWithQuote(value: string, label: string): void;
|
|
16
|
+
static assertSafeIdentifierPart(part: string, kind: "table" | "schema" | "alias" | "field"): void;
|
|
17
|
+
static assertSafeAlias(aliasPart: string): void;
|
|
18
|
+
static assertNoExprField(field: string): void;
|
|
19
|
+
static assertNoUndefinedInRecord(row: Record<string, unknown>, label: string): void;
|
|
20
|
+
static assertBatchInsertRowsConsistent(rows: Array<Record<string, unknown>>, options: {
|
|
21
|
+
table: string;
|
|
22
|
+
}): string[];
|
|
23
|
+
}
|
|
@@ -6,11 +6,9 @@
|
|
|
6
6
|
* 说明:这里的校验仅关注“字符串/标识符/批量数据结构”层面的正确性;
|
|
7
7
|
* 具体 SQL 语义(如字段是否存在)不在此处校验。
|
|
8
8
|
*/
|
|
9
|
-
|
|
10
9
|
export class SqlCheck {
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
static assertNonEmptyString(value: unknown, label: string): asserts value is string {
|
|
10
|
+
static SAFE_IDENTIFIER_RE = /^[a-zA-Z_][a-zA-Z0-9_]*$/;
|
|
11
|
+
static assertNonEmptyString(value, label) {
|
|
14
12
|
if (typeof value !== "string") {
|
|
15
13
|
throw new Error(`${label} 必须是字符串 (value: ${String(value)})`);
|
|
16
14
|
}
|
|
@@ -18,119 +16,104 @@ export class SqlCheck {
|
|
|
18
16
|
throw new Error(`${label} 不能为空`);
|
|
19
17
|
}
|
|
20
18
|
}
|
|
21
|
-
|
|
22
|
-
static assertNoUndefinedParam(value: unknown, label: string): void {
|
|
19
|
+
static assertNoUndefinedParam(value, label) {
|
|
23
20
|
if (value === undefined) {
|
|
24
21
|
throw new Error(`${label} 不能为 undefined`);
|
|
25
22
|
}
|
|
26
23
|
}
|
|
27
|
-
|
|
28
|
-
static startsWithQuote(value: string): boolean {
|
|
24
|
+
static startsWithQuote(value) {
|
|
29
25
|
const trimmed = value.trim();
|
|
30
26
|
return trimmed.startsWith("`") || trimmed.startsWith('"');
|
|
31
27
|
}
|
|
32
|
-
|
|
33
|
-
static isQuotedIdentPaired(value: string): boolean {
|
|
28
|
+
static isQuotedIdentPaired(value) {
|
|
34
29
|
const trimmed = value.trim();
|
|
35
|
-
if (trimmed.length < 2)
|
|
36
|
-
|
|
30
|
+
if (trimmed.length < 2)
|
|
31
|
+
return false;
|
|
37
32
|
const first = trimmed[0];
|
|
38
33
|
const last = trimmed[trimmed.length - 1];
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
if (first === '"' && last === '"')
|
|
42
|
-
|
|
34
|
+
if (first === "`" && last === "`")
|
|
35
|
+
return true;
|
|
36
|
+
if (first === '"' && last === '"')
|
|
37
|
+
return true;
|
|
43
38
|
return false;
|
|
44
39
|
}
|
|
45
|
-
|
|
46
|
-
static assertPairedQuotedIdentIfStartsWithQuote(value: string, label: string): void {
|
|
40
|
+
static assertPairedQuotedIdentIfStartsWithQuote(value, label) {
|
|
47
41
|
if (SqlCheck.startsWithQuote(value) && !SqlCheck.isQuotedIdentPaired(value)) {
|
|
48
42
|
throw new Error(`${label} 引用不完整,请使用成对的 \`...\` 或 "..." (value: ${value})`);
|
|
49
43
|
}
|
|
50
44
|
}
|
|
51
|
-
|
|
52
|
-
static assertSafeIdentifierPart(part: string, kind: "table" | "schema" | "alias" | "field"): void {
|
|
45
|
+
static assertSafeIdentifierPart(part, kind) {
|
|
53
46
|
// 这里仅允许常规标识符(字母/数字/下划线),避免把复杂表达式混进“自动转义”路径。
|
|
54
47
|
if (!SqlCheck.SAFE_IDENTIFIER_RE.test(part)) {
|
|
55
48
|
throw new Error(`无效的 ${kind} 标识符: ${part}`);
|
|
56
49
|
}
|
|
57
50
|
}
|
|
58
|
-
|
|
59
|
-
static assertSafeAlias(aliasPart: string): void {
|
|
51
|
+
static assertSafeAlias(aliasPart) {
|
|
60
52
|
// alias 允许两种:
|
|
61
53
|
// 1) 已经被引用(`alias` 或 "alias")
|
|
62
54
|
// 2) 普通标识符(不允许带空格/符号),避免注入
|
|
63
|
-
if (SqlCheck.isQuotedIdentPaired(aliasPart))
|
|
55
|
+
if (SqlCheck.isQuotedIdentPaired(aliasPart))
|
|
56
|
+
return;
|
|
64
57
|
if (!SqlCheck.SAFE_IDENTIFIER_RE.test(aliasPart)) {
|
|
65
58
|
throw new Error(`无效的字段别名: ${aliasPart}`);
|
|
66
59
|
}
|
|
67
60
|
}
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
61
|
+
static assertNoExprField(field) {
|
|
62
|
+
if (typeof field !== "string")
|
|
63
|
+
return;
|
|
71
64
|
const trimmed = field.trim();
|
|
72
|
-
if (!trimmed)
|
|
73
|
-
|
|
65
|
+
if (!trimmed)
|
|
66
|
+
return;
|
|
74
67
|
// 收紧:包含函数/表达式(括号)不允许走自动转义路径
|
|
75
68
|
// 这类表达式应显式使用 selectRaw/whereRaw 以避免误拼接和注入风险
|
|
76
69
|
if (trimmed.includes("(") || trimmed.includes(")")) {
|
|
77
70
|
throw new Error(`字段包含函数/表达式,请使用 selectRaw/whereRaw (field: ${trimmed})`);
|
|
78
71
|
}
|
|
79
72
|
}
|
|
80
|
-
|
|
81
|
-
static assertNoUndefinedInRecord(row: Record<string, unknown>, label: string): void {
|
|
73
|
+
static assertNoUndefinedInRecord(row, label) {
|
|
82
74
|
for (const [key, value] of Object.entries(row)) {
|
|
83
75
|
if (value === undefined) {
|
|
84
76
|
throw new Error(`${label} 存在 undefined 字段值 (field: ${key})`);
|
|
85
77
|
}
|
|
86
78
|
}
|
|
87
79
|
}
|
|
88
|
-
|
|
89
|
-
static assertBatchInsertRowsConsistent(rows: Array<Record<string, unknown>>, options: { table: string }): string[] {
|
|
80
|
+
static assertBatchInsertRowsConsistent(rows, options) {
|
|
90
81
|
if (!Array.isArray(rows)) {
|
|
91
82
|
throw new Error("批量插入 rows 必须是数组");
|
|
92
83
|
}
|
|
93
84
|
if (rows.length === 0) {
|
|
94
85
|
throw new Error(`插入数据不能为空 (table: ${options.table})`);
|
|
95
86
|
}
|
|
96
|
-
|
|
97
87
|
const first = rows[0];
|
|
98
88
|
if (!first || typeof first !== "object" || Array.isArray(first)) {
|
|
99
89
|
throw new Error(`批量插入的每一行必须是对象 (table: ${options.table}, rowIndex: 0)`);
|
|
100
90
|
}
|
|
101
|
-
|
|
102
91
|
const fields = Object.keys(first);
|
|
103
92
|
if (fields.length === 0) {
|
|
104
93
|
throw new Error(`插入数据必须至少有一个字段 (table: ${options.table})`);
|
|
105
94
|
}
|
|
106
|
-
|
|
107
95
|
const fieldSet = new Set(fields);
|
|
108
|
-
|
|
109
96
|
for (let i = 0; i < rows.length; i++) {
|
|
110
97
|
const row = rows[i];
|
|
111
98
|
if (!row || typeof row !== "object" || Array.isArray(row)) {
|
|
112
99
|
throw new Error(`批量插入的每一行必须是对象 (table: ${options.table}, rowIndex: ${i})`);
|
|
113
100
|
}
|
|
114
|
-
|
|
115
101
|
const rowKeys = Object.keys(row);
|
|
116
102
|
if (rowKeys.length !== fields.length) {
|
|
117
103
|
throw new Error(`批量插入每行字段必须一致 (table: ${options.table}, rowIndex: ${i})`);
|
|
118
104
|
}
|
|
119
|
-
|
|
120
105
|
for (const key of rowKeys) {
|
|
121
106
|
if (!fieldSet.has(key)) {
|
|
122
107
|
throw new Error(`批量插入每行字段必须一致 (table: ${options.table}, rowIndex: ${i}, extraField: ${key})`);
|
|
123
108
|
}
|
|
124
109
|
}
|
|
125
|
-
|
|
126
110
|
for (const field of fields) {
|
|
127
111
|
if (!(field in row)) {
|
|
128
112
|
throw new Error(`批量插入缺少字段 (table: ${options.table}, rowIndex: ${i}, field: ${field})`);
|
|
129
113
|
}
|
|
130
|
-
SqlCheck.assertNoUndefinedParam(
|
|
114
|
+
SqlCheck.assertNoUndefinedParam(row[field], `批量插入字段值 (table: ${options.table}, rowIndex: ${i}, field: ${field})`);
|
|
131
115
|
}
|
|
132
116
|
}
|
|
133
|
-
|
|
134
117
|
return fields;
|
|
135
118
|
}
|
|
136
119
|
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 数据验证器 - Befly 项目专用
|
|
3
|
+
* 纯静态类设计,简洁易用
|
|
4
|
+
*/
|
|
5
|
+
import type { TableDefinition, FieldDefinition, ValidateResult, SingleResult } from "../types/validate";
|
|
6
|
+
/**
|
|
7
|
+
* 验证器类
|
|
8
|
+
*
|
|
9
|
+
* @example
|
|
10
|
+
* const result = Validator.validate(data, rules, ['email', 'name']);
|
|
11
|
+
* if (result.failed) {
|
|
12
|
+
* console.log(result.firstError);
|
|
13
|
+
* console.log(result.errors);
|
|
14
|
+
* console.log(result.errorFields);
|
|
15
|
+
* }
|
|
16
|
+
*
|
|
17
|
+
* const single = Validator.single(value, fieldDef);
|
|
18
|
+
* if (!single.error) {
|
|
19
|
+
* console.log(single.value);
|
|
20
|
+
* }
|
|
21
|
+
*/
|
|
22
|
+
export declare class Validator {
|
|
23
|
+
/**
|
|
24
|
+
* 验证数据对象
|
|
25
|
+
*/
|
|
26
|
+
static validate(data: Record<string, any>, rules: TableDefinition, required?: string[]): ValidateResult;
|
|
27
|
+
/**
|
|
28
|
+
* 验证单个值(带类型转换)
|
|
29
|
+
*/
|
|
30
|
+
static single(value: any, fieldDef: FieldDefinition): SingleResult;
|
|
31
|
+
/** 构建结果对象 */
|
|
32
|
+
private static buildResult;
|
|
33
|
+
/** 验证单个字段 */
|
|
34
|
+
private static checkField;
|
|
35
|
+
/** 类型转换 */
|
|
36
|
+
private static convert;
|
|
37
|
+
/** 规则验证 */
|
|
38
|
+
private static checkRule;
|
|
39
|
+
/** 解析正则别名 */
|
|
40
|
+
private static resolveRegex;
|
|
41
|
+
/** 测试正则 */
|
|
42
|
+
private static testRegex;
|
|
43
|
+
/** 获取默认值 */
|
|
44
|
+
private static defaultFor;
|
|
45
|
+
}
|
|
@@ -2,11 +2,7 @@
|
|
|
2
2
|
* 数据验证器 - Befly 项目专用
|
|
3
3
|
* 纯静态类设计,简洁易用
|
|
4
4
|
*/
|
|
5
|
-
|
|
6
|
-
import type { TableDefinition, FieldDefinition, ValidateResult, SingleResult } from "../types/validate.ts";
|
|
7
|
-
|
|
8
|
-
import { RegexAliases, getCompiledRegex } from "../configs/presetRegexp.ts";
|
|
9
|
-
|
|
5
|
+
import { RegexAliases, getCompiledRegex } from "../configs/presetRegexp";
|
|
10
6
|
/**
|
|
11
7
|
* 验证器类
|
|
12
8
|
*
|
|
@@ -27,9 +23,8 @@ export class Validator {
|
|
|
27
23
|
/**
|
|
28
24
|
* 验证数据对象
|
|
29
25
|
*/
|
|
30
|
-
static validate(data
|
|
31
|
-
const fieldErrors
|
|
32
|
-
|
|
26
|
+
static validate(data, rules, required = []) {
|
|
27
|
+
const fieldErrors = {};
|
|
33
28
|
// 参数检查
|
|
34
29
|
if (!data || typeof data !== "object" || Array.isArray(data)) {
|
|
35
30
|
return this.buildResult({ _error: "数据必须是对象格式" });
|
|
@@ -37,7 +32,6 @@ export class Validator {
|
|
|
37
32
|
if (!rules || typeof rules !== "object") {
|
|
38
33
|
return this.buildResult({ _error: "验证规则必须是对象格式" });
|
|
39
34
|
}
|
|
40
|
-
|
|
41
35
|
// 检查必填字段
|
|
42
36
|
for (const field of required) {
|
|
43
37
|
const value = data[field];
|
|
@@ -46,54 +40,46 @@ export class Validator {
|
|
|
46
40
|
fieldErrors[field] = `${label}为必填项`;
|
|
47
41
|
}
|
|
48
42
|
}
|
|
49
|
-
|
|
50
43
|
// 验证有值的字段
|
|
51
44
|
for (const [field, rule] of Object.entries(rules)) {
|
|
52
|
-
if (fieldErrors[field])
|
|
45
|
+
if (fieldErrors[field])
|
|
46
|
+
continue;
|
|
53
47
|
// 字段值为 undefined 时跳过验证(除非是必填字段,但必填字段已在上面检查过)
|
|
54
|
-
if (data[field] === undefined && !required.includes(field))
|
|
55
|
-
|
|
48
|
+
if (data[field] === undefined && !required.includes(field))
|
|
49
|
+
continue;
|
|
56
50
|
const error = this.checkField(data[field], rule, field);
|
|
57
|
-
if (error)
|
|
51
|
+
if (error)
|
|
52
|
+
fieldErrors[field] = error;
|
|
58
53
|
}
|
|
59
|
-
|
|
60
54
|
return this.buildResult(fieldErrors);
|
|
61
55
|
}
|
|
62
|
-
|
|
63
56
|
/**
|
|
64
57
|
* 验证单个值(带类型转换)
|
|
65
58
|
*/
|
|
66
|
-
static single(value
|
|
59
|
+
static single(value, fieldDef) {
|
|
67
60
|
const { type, default: defaultValue } = fieldDef;
|
|
68
|
-
|
|
69
61
|
// 处理空值
|
|
70
62
|
if (value === undefined || value === null || value === "") {
|
|
71
63
|
return { value: this.defaultFor(type, defaultValue), error: null };
|
|
72
64
|
}
|
|
73
|
-
|
|
74
65
|
// 类型转换
|
|
75
66
|
const converted = this.convert(value, type);
|
|
76
67
|
if (converted.error) {
|
|
77
68
|
return { value: null, error: converted.error };
|
|
78
69
|
}
|
|
79
|
-
|
|
80
70
|
// 规则验证
|
|
81
71
|
const error = this.checkRule(converted.value, fieldDef);
|
|
82
72
|
if (error) {
|
|
83
73
|
return { value: null, error: error };
|
|
84
74
|
}
|
|
85
|
-
|
|
86
75
|
return { value: converted.value, error: null };
|
|
87
76
|
}
|
|
88
|
-
|
|
89
77
|
// ========== 私有方法 ==========
|
|
90
|
-
|
|
91
78
|
/** 构建结果对象 */
|
|
92
|
-
|
|
79
|
+
static buildResult(fieldErrors) {
|
|
93
80
|
const errors = Object.values(fieldErrors);
|
|
94
81
|
const errorFields = Object.keys(fieldErrors);
|
|
95
82
|
const failed = errors.length > 0;
|
|
96
|
-
|
|
97
83
|
return {
|
|
98
84
|
code: failed ? 1 : 0,
|
|
99
85
|
failed: failed,
|
|
@@ -103,22 +89,18 @@ export class Validator {
|
|
|
103
89
|
fieldErrors: fieldErrors
|
|
104
90
|
};
|
|
105
91
|
}
|
|
106
|
-
|
|
107
92
|
/** 验证单个字段 */
|
|
108
|
-
|
|
93
|
+
static checkField(value, fieldDef, fieldName) {
|
|
109
94
|
const label = fieldDef.name || fieldName;
|
|
110
|
-
|
|
111
95
|
const converted = this.convert(value, fieldDef.type);
|
|
112
96
|
if (converted.error) {
|
|
113
97
|
return `${label}${converted.error}`;
|
|
114
98
|
}
|
|
115
|
-
|
|
116
99
|
const error = this.checkRule(converted.value, fieldDef);
|
|
117
100
|
return error ? `${label}${error}` : null;
|
|
118
101
|
}
|
|
119
|
-
|
|
120
102
|
/** 类型转换 */
|
|
121
|
-
|
|
103
|
+
static convert(value, type) {
|
|
122
104
|
switch (type.toLowerCase()) {
|
|
123
105
|
case "number":
|
|
124
106
|
if (typeof value === "number") {
|
|
@@ -129,15 +111,12 @@ export class Validator {
|
|
|
129
111
|
return Number.isNaN(num) || !isFinite(num) ? { value: null, error: "必须是数字" } : { value: num, error: null };
|
|
130
112
|
}
|
|
131
113
|
return { value: null, error: "必须是数字" };
|
|
132
|
-
|
|
133
114
|
case "string":
|
|
134
115
|
case "text":
|
|
135
116
|
return typeof value === "string" ? { value: value, error: null } : { value: null, error: "必须是字符串" };
|
|
136
|
-
|
|
137
117
|
case "array_string":
|
|
138
118
|
case "array_text":
|
|
139
119
|
return Array.isArray(value) ? { value: value, error: null } : { value: null, error: "必须是数组" };
|
|
140
|
-
|
|
141
120
|
case "array_number_string":
|
|
142
121
|
case "array_number_text":
|
|
143
122
|
if (!Array.isArray(value)) {
|
|
@@ -150,77 +129,82 @@ export class Validator {
|
|
|
150
129
|
}
|
|
151
130
|
}
|
|
152
131
|
return { value: value, error: null };
|
|
153
|
-
|
|
154
132
|
default:
|
|
155
133
|
return { value: value, error: null };
|
|
156
134
|
}
|
|
157
135
|
}
|
|
158
|
-
|
|
159
136
|
/** 规则验证 */
|
|
160
|
-
|
|
137
|
+
static checkRule(value, fieldDef) {
|
|
161
138
|
const { type, min, max, regexp } = fieldDef;
|
|
162
139
|
const regex = this.resolveRegex(regexp);
|
|
163
|
-
|
|
164
140
|
switch (type.toLowerCase()) {
|
|
165
141
|
case "number":
|
|
166
|
-
if (min !== null && value < min)
|
|
167
|
-
|
|
168
|
-
if (
|
|
142
|
+
if (min !== null && value < min)
|
|
143
|
+
return `不能小于${min}`;
|
|
144
|
+
if (max !== null && max > 0 && value > max)
|
|
145
|
+
return `不能大于${max}`;
|
|
146
|
+
if (regex && !this.testRegex(regex, String(value)))
|
|
147
|
+
return "格式不正确";
|
|
169
148
|
break;
|
|
170
|
-
|
|
171
149
|
case "string":
|
|
172
150
|
case "text":
|
|
173
|
-
if (min !== null && value.length < min)
|
|
174
|
-
|
|
175
|
-
if (
|
|
151
|
+
if (min !== null && value.length < min)
|
|
152
|
+
return `长度不能少于${min}个字符`;
|
|
153
|
+
if (max !== null && max > 0 && value.length > max)
|
|
154
|
+
return `长度不能超过${max}个字符`;
|
|
155
|
+
if (regex && !this.testRegex(regex, value))
|
|
156
|
+
return "格式不正确";
|
|
176
157
|
break;
|
|
177
|
-
|
|
178
158
|
case "array_string":
|
|
179
159
|
case "array_text":
|
|
180
160
|
case "array_number_string":
|
|
181
161
|
case "array_number_text":
|
|
182
|
-
if (min !== null && value.length < min)
|
|
183
|
-
|
|
162
|
+
if (min !== null && value.length < min)
|
|
163
|
+
return `至少需要${min}个元素`;
|
|
164
|
+
if (max !== null && max > 0 && value.length > max)
|
|
165
|
+
return `最多只能有${max}个元素`;
|
|
184
166
|
if (regex) {
|
|
185
167
|
for (const item of value) {
|
|
186
|
-
if (!this.testRegex(regex, String(item)))
|
|
168
|
+
if (!this.testRegex(regex, String(item)))
|
|
169
|
+
return "元素格式不正确";
|
|
187
170
|
}
|
|
188
171
|
}
|
|
189
172
|
break;
|
|
190
173
|
}
|
|
191
174
|
return null;
|
|
192
175
|
}
|
|
193
|
-
|
|
194
176
|
/** 解析正则别名 */
|
|
195
|
-
|
|
196
|
-
if (!regexp)
|
|
177
|
+
static resolveRegex(regexp) {
|
|
178
|
+
if (!regexp)
|
|
179
|
+
return null;
|
|
197
180
|
if (regexp.startsWith("@")) {
|
|
198
|
-
const key = regexp.substring(1)
|
|
181
|
+
const key = regexp.substring(1);
|
|
199
182
|
return RegexAliases[key] || regexp;
|
|
200
183
|
}
|
|
201
184
|
return regexp;
|
|
202
185
|
}
|
|
203
|
-
|
|
204
186
|
/** 测试正则 */
|
|
205
|
-
|
|
187
|
+
static testRegex(pattern, value) {
|
|
206
188
|
try {
|
|
207
189
|
return getCompiledRegex(pattern).test(value);
|
|
208
|
-
}
|
|
190
|
+
}
|
|
191
|
+
catch {
|
|
209
192
|
return false;
|
|
210
193
|
}
|
|
211
194
|
}
|
|
212
|
-
|
|
213
195
|
/** 获取默认值 */
|
|
214
|
-
|
|
196
|
+
static defaultFor(type, defaultValue) {
|
|
215
197
|
// 如果字段定义了默认值,则使用字段默认值(优先级最高)
|
|
216
198
|
if (defaultValue !== null && defaultValue !== undefined) {
|
|
217
199
|
// 数组默认值
|
|
218
200
|
if ((type === "array_string" || type === "array_text" || type === "array_number_string" || type === "array_number_text") && typeof defaultValue === "string") {
|
|
219
|
-
if (defaultValue === "[]")
|
|
201
|
+
if (defaultValue === "[]")
|
|
202
|
+
return [];
|
|
220
203
|
try {
|
|
221
204
|
const parsed = JSON.parse(defaultValue);
|
|
222
205
|
return Array.isArray(parsed) ? parsed : [];
|
|
223
|
-
}
|
|
206
|
+
}
|
|
207
|
+
catch {
|
|
224
208
|
return [];
|
|
225
209
|
}
|
|
226
210
|
}
|
|
@@ -231,7 +215,6 @@ export class Validator {
|
|
|
231
215
|
}
|
|
232
216
|
return defaultValue;
|
|
233
217
|
}
|
|
234
|
-
|
|
235
218
|
// 类型默认值(字段未定义 default 时使用)
|
|
236
219
|
switch (type.toLowerCase()) {
|
|
237
220
|
case "number":
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* API 加载器
|
|
3
|
+
* 负责扫描和加载所有 API 路由(组件、项目)
|
|
4
|
+
*/
|
|
5
|
+
import type { ApiRoute } from "../types/api";
|
|
6
|
+
import type { ScanFileResult } from "../utils/scanFiles";
|
|
7
|
+
/**
|
|
8
|
+
* 加载所有 API 路由
|
|
9
|
+
* @param apiItems - scanSources/scanFiles 扫描到的 API 条目数组
|
|
10
|
+
* @returns API 路由映射表
|
|
11
|
+
*/
|
|
12
|
+
export declare function loadApis(apis: ScanFileResult[]): Promise<Map<string, ApiRoute>>;
|
|
@@ -2,42 +2,32 @@
|
|
|
2
2
|
* API 加载器
|
|
3
3
|
* 负责扫描和加载所有 API 路由(组件、项目)
|
|
4
4
|
*/
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
import type { ApiRoute } from "../types/api.ts";
|
|
8
|
-
import type { ScanFileResult } from "../utils/scanFiles.ts";
|
|
9
|
-
|
|
10
|
-
import { Logger } from "../lib/logger.ts";
|
|
11
|
-
import { processFields } from "../utils/processFields.ts";
|
|
12
|
-
|
|
5
|
+
import { Logger } from "../lib/logger";
|
|
6
|
+
import { processAtSymbol } from "../utils/processAtSymbol";
|
|
13
7
|
/**
|
|
14
8
|
* 加载所有 API 路由
|
|
15
9
|
* @param apiItems - scanSources/scanFiles 扫描到的 API 条目数组
|
|
16
10
|
* @returns API 路由映射表
|
|
17
11
|
*/
|
|
18
|
-
export async function loadApis(apis
|
|
19
|
-
const apisMap = new Map
|
|
20
|
-
|
|
12
|
+
export async function loadApis(apis) {
|
|
13
|
+
const apisMap = new Map();
|
|
21
14
|
for (const api of apis) {
|
|
22
|
-
const apiType =
|
|
15
|
+
const apiType = api.type;
|
|
23
16
|
// 兼容:scanFiles 的结果或测试构造数据可能缺少 type 字段;缺少时默认按 API 处理。
|
|
24
17
|
// 仅在 type 显式存在且不等于 "api" 时跳过,避免错误过滤。
|
|
25
18
|
if (apiType && apiType !== "api") {
|
|
26
19
|
continue;
|
|
27
20
|
}
|
|
28
|
-
|
|
29
21
|
try {
|
|
30
|
-
const apiRoute = api
|
|
31
|
-
|
|
22
|
+
const apiRoute = api;
|
|
32
23
|
// 处理字段定义,将 @ 引用替换为实际字段定义
|
|
33
|
-
apiRoute.fields =
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
24
|
+
apiRoute.fields = processAtSymbol(apiRoute.fields || {}, apiRoute.name, apiRoute.routePath);
|
|
25
|
+
apisMap.set(apiRoute.routePath, apiRoute);
|
|
26
|
+
}
|
|
27
|
+
catch (error) {
|
|
37
28
|
Logger.error({ err: error, api: api.relativePath, file: api.filePath }, "接口加载失败");
|
|
38
29
|
throw error;
|
|
39
30
|
}
|
|
40
31
|
}
|
|
41
|
-
|
|
42
32
|
return apisMap;
|
|
43
33
|
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 钩子加载器
|
|
3
|
+
* 默认加载所有来源钩子(core/addon/app)
|
|
4
|
+
*/
|
|
5
|
+
import { sortModules } from "../utils/sortModules";
|
|
6
|
+
export async function loadHooks(hooks) {
|
|
7
|
+
const hooksMap = [];
|
|
8
|
+
const enabledHooks = hooks.filter((item) => {
|
|
9
|
+
const moduleName = item?.moduleName;
|
|
10
|
+
if (typeof moduleName !== "string" || moduleName.trim() === "") {
|
|
11
|
+
return false;
|
|
12
|
+
}
|
|
13
|
+
// enable=false 表示禁用(替代 disableHooks 列表)。
|
|
14
|
+
// enable 仅允许 boolean;缺失 enable 的默认值应在 checkHook 阶段被补全为 true。
|
|
15
|
+
if (item?.enable === false) {
|
|
16
|
+
return false;
|
|
17
|
+
}
|
|
18
|
+
return true;
|
|
19
|
+
});
|
|
20
|
+
const sortedHooks = sortModules(enabledHooks, { moduleLabel: "钩子" });
|
|
21
|
+
if (sortedHooks === false) {
|
|
22
|
+
throw new Error("钩子依赖关系错误");
|
|
23
|
+
}
|
|
24
|
+
for (const item of sortedHooks) {
|
|
25
|
+
const hookName = item.moduleName;
|
|
26
|
+
const hook = item;
|
|
27
|
+
hooksMap.push({
|
|
28
|
+
name: hookName,
|
|
29
|
+
enable: true,
|
|
30
|
+
deps: hook.deps,
|
|
31
|
+
handler: hook.handler
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
return hooksMap;
|
|
35
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 插件加载器
|
|
3
|
+
* 负责扫描和初始化所有插件(核心、组件、项目)
|
|
4
|
+
*/
|
|
5
|
+
import type { BeflyContext } from "../types/befly";
|
|
6
|
+
import type { Plugin } from "../types/plugin";
|
|
7
|
+
import type { ScanFileResult } from "../utils/scanFiles";
|
|
8
|
+
export declare function loadPlugins(plugins: ScanFileResult[], context: BeflyContext): Promise<Plugin[]>;
|
|
@@ -2,55 +2,43 @@
|
|
|
2
2
|
* 插件加载器
|
|
3
3
|
* 负责扫描和初始化所有插件(核心、组件、项目)
|
|
4
4
|
*/
|
|
5
|
-
|
|
6
|
-
import
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
import { Logger } from "../lib/logger.ts";
|
|
11
|
-
import { sortModules } from "../utils/sortModules.ts";
|
|
12
|
-
|
|
13
|
-
export async function loadPlugins(plugins: ScanFileResult[], context: BeflyContext, disablePlugins: string[] = []): Promise<Plugin[]> {
|
|
14
|
-
const pluginsMap: Plugin[] = [];
|
|
15
|
-
|
|
16
|
-
if (disablePlugins.length > 0) {
|
|
17
|
-
Logger.info({ plugins: disablePlugins }, "禁用插件");
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
const enabledPlugins = plugins.filter((item: any) => {
|
|
5
|
+
import { Logger } from "../lib/logger";
|
|
6
|
+
import { sortModules } from "../utils/sortModules";
|
|
7
|
+
export async function loadPlugins(plugins, context) {
|
|
8
|
+
const pluginsMap = [];
|
|
9
|
+
const enabledPlugins = plugins.filter((item) => {
|
|
21
10
|
const moduleName = item?.moduleName;
|
|
22
11
|
if (typeof moduleName !== "string" || moduleName.trim() === "") {
|
|
23
12
|
return false;
|
|
24
13
|
}
|
|
25
|
-
|
|
14
|
+
// enable=false 表示禁用(替代 disablePlugins 列表)。
|
|
15
|
+
// enable 仅允许 boolean;缺失 enable 的默认值应在 checkPlugin 阶段被补全为 true。
|
|
16
|
+
if (item?.enable === false) {
|
|
26
17
|
return false;
|
|
27
18
|
}
|
|
28
19
|
return true;
|
|
29
20
|
});
|
|
30
|
-
|
|
31
21
|
const sortedPlugins = sortModules(enabledPlugins, { moduleLabel: "插件" });
|
|
32
22
|
if (sortedPlugins === false) {
|
|
33
23
|
throw new Error("插件依赖关系错误");
|
|
34
24
|
}
|
|
35
|
-
|
|
36
25
|
for (const item of sortedPlugins) {
|
|
37
|
-
const pluginName =
|
|
38
|
-
const plugin = item
|
|
39
|
-
|
|
26
|
+
const pluginName = item.moduleName;
|
|
27
|
+
const plugin = item;
|
|
40
28
|
try {
|
|
41
29
|
const pluginInstance = typeof plugin.handler === "function" ? await plugin.handler(context) : {};
|
|
42
|
-
|
|
43
|
-
|
|
30
|
+
context[pluginName] = pluginInstance;
|
|
44
31
|
pluginsMap.push({
|
|
45
32
|
name: pluginName,
|
|
33
|
+
enable: true,
|
|
46
34
|
deps: plugin.deps,
|
|
47
35
|
handler: plugin.handler
|
|
48
36
|
});
|
|
49
|
-
}
|
|
37
|
+
}
|
|
38
|
+
catch (error) {
|
|
50
39
|
Logger.error({ err: error, plugin: pluginName }, "插件初始化失败");
|
|
51
40
|
throw error;
|
|
52
41
|
}
|
|
53
42
|
}
|
|
54
|
-
|
|
55
43
|
return pluginsMap;
|
|
56
44
|
}
|