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,68 @@
|
|
|
1
|
+
import type { WhereConditions } from "../types/common";
|
|
2
|
+
export declare class DbUtils {
|
|
3
|
+
static parseTableRef(tableRef: string): {
|
|
4
|
+
schema: string | null;
|
|
5
|
+
table: string;
|
|
6
|
+
alias: string | null;
|
|
7
|
+
};
|
|
8
|
+
/**
|
|
9
|
+
* 规范化表引用:只 snakeCase schema/table,本身 alias 保持原样。
|
|
10
|
+
* - 支持:table / table alias / schema.table / schema.table alias
|
|
11
|
+
*/
|
|
12
|
+
static normalizeTableRef(tableRef: string): string;
|
|
13
|
+
/**
|
|
14
|
+
* JOIN 场景下主表的限定符:优先使用 alias;没有 alias 时使用 snakeCase(table)。
|
|
15
|
+
* 用于构造类似 "o.state$gt" 的 where key,避免出现 "order o.state$gt" 这种带空格的非法 key。
|
|
16
|
+
*/
|
|
17
|
+
static getJoinMainQualifier(tableRef: string): string;
|
|
18
|
+
/**
|
|
19
|
+
* 字段数组转下划线格式
|
|
20
|
+
* 支持排除字段语法:['!password', '!token']
|
|
21
|
+
*
|
|
22
|
+
* 说明:exclude 模式需要表的所有字段名,因此通过 getTableColumns 回调获取
|
|
23
|
+
*/
|
|
24
|
+
static fieldsToSnake(table: string, fields: string[], getTableColumns: (table: string) => Promise<string[]>): Promise<string[]>;
|
|
25
|
+
static validateAndClassifyFields(fields?: string[]): {
|
|
26
|
+
type: "all" | "include" | "exclude";
|
|
27
|
+
fields: string[];
|
|
28
|
+
};
|
|
29
|
+
static orderByToSnake(orderBy: string[]): string[];
|
|
30
|
+
static processJoinField(field: string): string;
|
|
31
|
+
static processJoinWhereKey(key: string): string;
|
|
32
|
+
static processJoinWhere(where: any): any;
|
|
33
|
+
static processJoinOrderBy(orderBy: string[]): string[];
|
|
34
|
+
static addDefaultStateFilter(where?: WhereConditions, table?: string, hasJoins?: boolean): WhereConditions;
|
|
35
|
+
/**
|
|
36
|
+
* Where 条件键名转下划线格式(递归处理嵌套)
|
|
37
|
+
* 支持操作符字段(如 userId$gt)和逻辑操作符($or, $and)
|
|
38
|
+
*/
|
|
39
|
+
static whereKeysToSnake(where: any): any;
|
|
40
|
+
/**
|
|
41
|
+
* 序列化数组字段(写入数据库前)
|
|
42
|
+
* 将数组类型的字段转换为 JSON 字符串
|
|
43
|
+
*/
|
|
44
|
+
static serializeArrayFields(data: Record<string, any>): Record<string, any>;
|
|
45
|
+
/**
|
|
46
|
+
* 反序列化数组字段(从数据库读取后)
|
|
47
|
+
* 将 JSON 字符串转换回数组
|
|
48
|
+
*/
|
|
49
|
+
static deserializeArrayFields<T = any>(data: Record<string, any> | null): T | null;
|
|
50
|
+
static cleanAndSnakeAndSerializeWriteData(data: Record<string, any>, excludeValues?: any[]): Record<string, any>;
|
|
51
|
+
static stripSystemFieldsForWrite(data: Record<string, any>, options: {
|
|
52
|
+
allowState: boolean;
|
|
53
|
+
}): Record<string, any>;
|
|
54
|
+
static buildInsertRow(options: {
|
|
55
|
+
data: Record<string, any>;
|
|
56
|
+
id: number;
|
|
57
|
+
now: number;
|
|
58
|
+
}): Record<string, any>;
|
|
59
|
+
static buildUpdateRow(options: {
|
|
60
|
+
data: Record<string, any>;
|
|
61
|
+
now: number;
|
|
62
|
+
allowState: boolean;
|
|
63
|
+
}): Record<string, any>;
|
|
64
|
+
static buildPartialUpdateData(options: {
|
|
65
|
+
data: Record<string, any>;
|
|
66
|
+
allowState: boolean;
|
|
67
|
+
}): Record<string, any>;
|
|
68
|
+
}
|
|
@@ -1,88 +1,68 @@
|
|
|
1
|
-
import
|
|
2
|
-
|
|
3
|
-
import { snakeCase } from "es-toolkit/string";
|
|
4
|
-
|
|
5
|
-
import { fieldClear } from "../utils/fieldClear.ts";
|
|
6
|
-
import { keysToSnake } from "../utils/keysToSnake.ts";
|
|
7
|
-
|
|
1
|
+
import { fieldClear } from "../utils/fieldClear";
|
|
2
|
+
import { keysToSnake, snakeCase } from "../utils/util";
|
|
8
3
|
export class DbUtils {
|
|
9
|
-
static parseTableRef(tableRef
|
|
4
|
+
static parseTableRef(tableRef) {
|
|
10
5
|
if (typeof tableRef !== "string") {
|
|
11
6
|
throw new Error(`tableRef 必须是字符串 (tableRef: ${String(tableRef)})`);
|
|
12
7
|
}
|
|
13
|
-
|
|
14
8
|
const trimmed = tableRef.trim();
|
|
15
9
|
if (!trimmed) {
|
|
16
10
|
throw new Error("tableRef 不能为空");
|
|
17
11
|
}
|
|
18
|
-
|
|
19
12
|
const parts = trimmed.split(/\s+/).filter((p) => p.length > 0);
|
|
20
13
|
if (parts.length > 2) {
|
|
21
14
|
throw new Error(`不支持的表引用格式(包含过多片段)。请使用最简形式:table 或 table alias 或 schema.table 或 schema.table alias (tableRef: ${trimmed})`);
|
|
22
15
|
}
|
|
23
|
-
|
|
24
16
|
const namePart = parts[0];
|
|
25
17
|
const aliasPart = parts.length === 2 ? parts[1] : null;
|
|
26
|
-
|
|
27
18
|
const nameSegments = namePart.split(".");
|
|
28
19
|
if (nameSegments.length > 2) {
|
|
29
20
|
throw new Error(`不支持的表引用格式(schema 层级过深) (tableRef: ${trimmed})`);
|
|
30
21
|
}
|
|
31
|
-
|
|
32
22
|
const schema = nameSegments.length === 2 ? nameSegments[0] : null;
|
|
33
23
|
const table = nameSegments.length === 2 ? nameSegments[1] : nameSegments[0];
|
|
34
|
-
|
|
35
24
|
return { schema: schema, table: table, alias: aliasPart };
|
|
36
25
|
}
|
|
37
|
-
|
|
38
26
|
/**
|
|
39
27
|
* 规范化表引用:只 snakeCase schema/table,本身 alias 保持原样。
|
|
40
28
|
* - 支持:table / table alias / schema.table / schema.table alias
|
|
41
29
|
*/
|
|
42
|
-
static normalizeTableRef(tableRef
|
|
30
|
+
static normalizeTableRef(tableRef) {
|
|
43
31
|
const parsed = DbUtils.parseTableRef(tableRef);
|
|
44
|
-
|
|
45
32
|
const schemaPart = parsed.schema ? snakeCase(parsed.schema) : null;
|
|
46
33
|
const tablePart = snakeCase(parsed.table);
|
|
47
|
-
|
|
48
34
|
let result = schemaPart ? `${schemaPart}.${tablePart}` : tablePart;
|
|
49
35
|
if (parsed.alias) {
|
|
50
36
|
result = `${result} ${parsed.alias}`;
|
|
51
37
|
}
|
|
52
|
-
|
|
53
38
|
return result;
|
|
54
39
|
}
|
|
55
|
-
|
|
56
40
|
/**
|
|
57
41
|
* JOIN 场景下主表的限定符:优先使用 alias;没有 alias 时使用 snakeCase(table)。
|
|
58
42
|
* 用于构造类似 "o.state$gt" 的 where key,避免出现 "order o.state$gt" 这种带空格的非法 key。
|
|
59
43
|
*/
|
|
60
|
-
static getJoinMainQualifier(tableRef
|
|
44
|
+
static getJoinMainQualifier(tableRef) {
|
|
61
45
|
const parsed = DbUtils.parseTableRef(tableRef);
|
|
62
46
|
if (parsed.alias) {
|
|
63
47
|
return parsed.alias;
|
|
64
48
|
}
|
|
65
49
|
return snakeCase(parsed.table);
|
|
66
50
|
}
|
|
67
|
-
|
|
68
51
|
/**
|
|
69
52
|
* 字段数组转下划线格式
|
|
70
53
|
* 支持排除字段语法:['!password', '!token']
|
|
71
54
|
*
|
|
72
55
|
* 说明:exclude 模式需要表的所有字段名,因此通过 getTableColumns 回调获取
|
|
73
56
|
*/
|
|
74
|
-
static async fieldsToSnake(table
|
|
57
|
+
static async fieldsToSnake(table, fields, getTableColumns) {
|
|
75
58
|
if (!fields || !Array.isArray(fields)) {
|
|
76
59
|
return ["*"];
|
|
77
60
|
}
|
|
78
|
-
|
|
79
61
|
const classified = DbUtils.validateAndClassifyFields(fields);
|
|
80
|
-
|
|
81
62
|
// 情况1:查询所有字段
|
|
82
63
|
if (classified.type === "all") {
|
|
83
64
|
return ["*"];
|
|
84
65
|
}
|
|
85
|
-
|
|
86
66
|
// 情况2:指定包含字段
|
|
87
67
|
if (classified.type === "include") {
|
|
88
68
|
return classified.fields.map((field) => {
|
|
@@ -93,83 +73,64 @@ export class DbUtils {
|
|
|
93
73
|
return snakeCase(field);
|
|
94
74
|
});
|
|
95
75
|
}
|
|
96
|
-
|
|
97
76
|
// 情况3:排除字段
|
|
98
77
|
if (classified.type === "exclude") {
|
|
99
78
|
const allColumns = await getTableColumns(table);
|
|
100
79
|
const excludeSnakeFields = classified.fields.map((f) => snakeCase(f));
|
|
101
|
-
|
|
102
80
|
const resultFields = allColumns.filter((col) => !excludeSnakeFields.includes(col));
|
|
103
81
|
if (resultFields.length === 0) {
|
|
104
82
|
throw new Error(`排除字段后没有剩余字段可查询。表: ${table}, 排除: ${excludeSnakeFields.join(", ")}`);
|
|
105
83
|
}
|
|
106
|
-
|
|
107
84
|
return resultFields;
|
|
108
85
|
}
|
|
109
|
-
|
|
110
86
|
return ["*"];
|
|
111
87
|
}
|
|
112
|
-
|
|
113
|
-
static validateAndClassifyFields(fields?: string[]): {
|
|
114
|
-
type: "all" | "include" | "exclude";
|
|
115
|
-
fields: string[];
|
|
116
|
-
} {
|
|
88
|
+
static validateAndClassifyFields(fields) {
|
|
117
89
|
// 情况1:空数组或 undefined,表示查询所有
|
|
118
90
|
if (!fields || fields.length === 0) {
|
|
119
91
|
return { type: "all", fields: [] };
|
|
120
92
|
}
|
|
121
|
-
|
|
122
93
|
// 检测是否有星号(禁止)
|
|
123
94
|
if (fields.some((f) => f === "*")) {
|
|
124
95
|
throw new Error("fields 不支持 * 星号,请使用空数组 [] 或不传参数表示查询所有字段");
|
|
125
96
|
}
|
|
126
|
-
|
|
127
97
|
// 检测是否有空字符串或无效值
|
|
128
98
|
if (fields.some((f) => !f || typeof f !== "string" || f.trim() === "")) {
|
|
129
99
|
throw new Error("fields 不能包含空字符串或无效值");
|
|
130
100
|
}
|
|
131
|
-
|
|
132
101
|
// 统计包含字段和排除字段
|
|
133
102
|
const includeFields = fields.filter((f) => !f.startsWith("!"));
|
|
134
103
|
const excludeFields = fields.filter((f) => f.startsWith("!"));
|
|
135
|
-
|
|
136
104
|
// 情况2:全部是包含字段
|
|
137
105
|
if (includeFields.length > 0 && excludeFields.length === 0) {
|
|
138
106
|
return { type: "include", fields: includeFields };
|
|
139
107
|
}
|
|
140
|
-
|
|
141
108
|
// 情况3:全部是排除字段
|
|
142
109
|
if (excludeFields.length > 0 && includeFields.length === 0) {
|
|
143
110
|
// 去掉感叹号前缀
|
|
144
111
|
const cleanExcludeFields = excludeFields.map((f) => f.substring(1));
|
|
145
112
|
return { type: "exclude", fields: cleanExcludeFields };
|
|
146
113
|
}
|
|
147
|
-
|
|
148
114
|
// 混用情况:报错
|
|
149
115
|
throw new Error('fields 不能同时包含普通字段和排除字段(! 开头)。只能使用以下3种方式之一:\n1. 空数组 [] 或不传(查询所有)\n2. 全部指定字段 ["id", "name"]\n3. 全部排除字段 ["!password", "!token"]');
|
|
150
116
|
}
|
|
151
|
-
|
|
152
|
-
static orderByToSnake(orderBy: string[]): string[] {
|
|
117
|
+
static orderByToSnake(orderBy) {
|
|
153
118
|
if (!orderBy || !Array.isArray(orderBy)) {
|
|
154
119
|
return orderBy;
|
|
155
120
|
}
|
|
156
|
-
|
|
157
121
|
return orderBy.map((item) => {
|
|
158
122
|
if (typeof item !== "string" || !item.includes("#")) {
|
|
159
123
|
return item;
|
|
160
124
|
}
|
|
161
|
-
|
|
162
125
|
const [field, direction] = item.split("#");
|
|
163
126
|
return `${snakeCase(field.trim())}#${direction.trim()}`;
|
|
164
127
|
});
|
|
165
128
|
}
|
|
166
|
-
|
|
167
|
-
static processJoinField(field: string): string {
|
|
129
|
+
static processJoinField(field) {
|
|
168
130
|
// 跳过函数、星号、已处理的字段
|
|
169
131
|
if (field.includes("(") || field === "*" || field.startsWith("`")) {
|
|
170
132
|
return field;
|
|
171
133
|
}
|
|
172
|
-
|
|
173
134
|
// 处理别名 AS
|
|
174
135
|
if (field.toUpperCase().includes(" AS ")) {
|
|
175
136
|
const parts = field.split(/\s+AS\s+/i);
|
|
@@ -177,7 +138,6 @@ export class DbUtils {
|
|
|
177
138
|
const aliasPart = parts[1].trim();
|
|
178
139
|
return `${DbUtils.processJoinField(fieldPart)} AS ${aliasPart}`;
|
|
179
140
|
}
|
|
180
|
-
|
|
181
141
|
// 处理表别名.字段名(JOIN 模式下,点号前面通常是别名,不应被 snakeCase 改写)
|
|
182
142
|
if (field.includes(".")) {
|
|
183
143
|
const parts = field.split(".");
|
|
@@ -185,33 +145,27 @@ export class DbUtils {
|
|
|
185
145
|
const fieldName = parts[1];
|
|
186
146
|
return `${tableName.trim()}.${snakeCase(fieldName)}`;
|
|
187
147
|
}
|
|
188
|
-
|
|
189
148
|
// 普通字段
|
|
190
149
|
return snakeCase(field);
|
|
191
150
|
}
|
|
192
|
-
|
|
193
|
-
static processJoinWhereKey(key: string): string {
|
|
151
|
+
static processJoinWhereKey(key) {
|
|
194
152
|
// 保留逻辑操作符
|
|
195
153
|
if (key === "$or" || key === "$and") {
|
|
196
154
|
return key;
|
|
197
155
|
}
|
|
198
|
-
|
|
199
156
|
// 处理带操作符的字段名(如 user.userId$gt)
|
|
200
157
|
if (key.includes("$")) {
|
|
201
158
|
const lastDollarIndex = key.lastIndexOf("$");
|
|
202
159
|
const fieldPart = key.substring(0, lastDollarIndex);
|
|
203
160
|
const operator = key.substring(lastDollarIndex);
|
|
204
|
-
|
|
205
161
|
if (fieldPart.includes(".")) {
|
|
206
162
|
const parts = fieldPart.split(".");
|
|
207
163
|
const tableName = parts[0];
|
|
208
164
|
const fieldName = parts[1];
|
|
209
165
|
return `${tableName.trim()}.${snakeCase(fieldName)}${operator}`;
|
|
210
166
|
}
|
|
211
|
-
|
|
212
167
|
return `${snakeCase(fieldPart)}${operator}`;
|
|
213
168
|
}
|
|
214
|
-
|
|
215
169
|
// 处理表名.字段名
|
|
216
170
|
if (key.includes(".")) {
|
|
217
171
|
const parts = key.split(".");
|
|
@@ -219,101 +173,86 @@ export class DbUtils {
|
|
|
219
173
|
const fieldName = parts[1];
|
|
220
174
|
return `${tableName.trim()}.${snakeCase(fieldName)}`;
|
|
221
175
|
}
|
|
222
|
-
|
|
223
176
|
// 普通字段
|
|
224
177
|
return snakeCase(key);
|
|
225
178
|
}
|
|
226
|
-
|
|
227
|
-
static processJoinWhere(where: any): any {
|
|
179
|
+
static processJoinWhere(where) {
|
|
228
180
|
if (!where || typeof where !== "object") {
|
|
229
181
|
return where;
|
|
230
182
|
}
|
|
231
|
-
|
|
232
183
|
if (Array.isArray(where)) {
|
|
233
184
|
return where.map((item) => DbUtils.processJoinWhere(item));
|
|
234
185
|
}
|
|
235
|
-
|
|
236
|
-
const result: any = {};
|
|
186
|
+
const result = {};
|
|
237
187
|
for (const [key, value] of Object.entries(where)) {
|
|
238
188
|
const newKey = DbUtils.processJoinWhereKey(key);
|
|
239
|
-
|
|
240
189
|
if (key === "$or" || key === "$and") {
|
|
241
|
-
result[newKey] =
|
|
242
|
-
}
|
|
190
|
+
result[newKey] = value.map((item) => DbUtils.processJoinWhere(item));
|
|
191
|
+
}
|
|
192
|
+
else if (typeof value === "object" && value !== null && !Array.isArray(value)) {
|
|
243
193
|
result[newKey] = DbUtils.processJoinWhere(value);
|
|
244
|
-
}
|
|
194
|
+
}
|
|
195
|
+
else {
|
|
245
196
|
result[newKey] = value;
|
|
246
197
|
}
|
|
247
198
|
}
|
|
248
|
-
|
|
249
199
|
return result;
|
|
250
200
|
}
|
|
251
|
-
|
|
252
|
-
static processJoinOrderBy(orderBy: string[]): string[] {
|
|
201
|
+
static processJoinOrderBy(orderBy) {
|
|
253
202
|
if (!orderBy || !Array.isArray(orderBy)) {
|
|
254
203
|
return orderBy;
|
|
255
204
|
}
|
|
256
|
-
|
|
257
205
|
return orderBy.map((item) => {
|
|
258
206
|
if (typeof item !== "string" || !item.includes("#")) {
|
|
259
207
|
return item;
|
|
260
208
|
}
|
|
261
|
-
|
|
262
209
|
const [field, direction] = item.split("#");
|
|
263
210
|
return `${DbUtils.processJoinField(field.trim())}#${direction.trim()}`;
|
|
264
211
|
});
|
|
265
212
|
}
|
|
266
|
-
|
|
267
|
-
static addDefaultStateFilter(where: WhereConditions = {}, table?: string, hasJoins: boolean = false): WhereConditions {
|
|
213
|
+
static addDefaultStateFilter(where = {}, table, hasJoins = false) {
|
|
268
214
|
// 如果用户已经指定了 state 条件,优先使用用户的条件
|
|
269
215
|
const hasStateCondition = Object.keys(where).some((key) => key.startsWith("state") || key.includes(".state"));
|
|
270
|
-
|
|
271
216
|
if (hasStateCondition) {
|
|
272
217
|
return where;
|
|
273
218
|
}
|
|
274
|
-
|
|
275
219
|
// JOIN 查询时需要指定主表名前缀避免歧义
|
|
276
220
|
if (hasJoins && table) {
|
|
277
221
|
// table 可能带别名("order o"),这里只需要别名/主表引用本身,不做 snakeCase 改写
|
|
278
|
-
const result
|
|
222
|
+
const result = {};
|
|
279
223
|
for (const [key, value] of Object.entries(where)) {
|
|
280
224
|
result[key] = value;
|
|
281
225
|
}
|
|
282
226
|
result[`${table}.state$gt`] = 0;
|
|
283
227
|
return result;
|
|
284
228
|
}
|
|
285
|
-
|
|
286
229
|
// 默认查询 state > 0 的数据
|
|
287
|
-
const result
|
|
230
|
+
const result = {};
|
|
288
231
|
for (const [key, value] of Object.entries(where)) {
|
|
289
232
|
result[key] = value;
|
|
290
233
|
}
|
|
291
234
|
result.state$gt = 0;
|
|
292
235
|
return result;
|
|
293
236
|
}
|
|
294
|
-
|
|
295
237
|
/**
|
|
296
238
|
* Where 条件键名转下划线格式(递归处理嵌套)
|
|
297
239
|
* 支持操作符字段(如 userId$gt)和逻辑操作符($or, $and)
|
|
298
240
|
*/
|
|
299
|
-
static whereKeysToSnake(where
|
|
241
|
+
static whereKeysToSnake(where) {
|
|
300
242
|
if (!where || typeof where !== "object") {
|
|
301
243
|
return where;
|
|
302
244
|
}
|
|
303
|
-
|
|
304
245
|
// 处理数组($or, $and 等)
|
|
305
246
|
if (Array.isArray(where)) {
|
|
306
247
|
return where.map((item) => DbUtils.whereKeysToSnake(item));
|
|
307
248
|
}
|
|
308
|
-
|
|
309
|
-
const result: any = {};
|
|
249
|
+
const result = {};
|
|
310
250
|
for (const [key, value] of Object.entries(where)) {
|
|
311
251
|
// 保留 $or, $and 等逻辑操作符
|
|
312
252
|
if (key === "$or" || key === "$and") {
|
|
313
|
-
result[key] =
|
|
253
|
+
result[key] = value.map((item) => DbUtils.whereKeysToSnake(item));
|
|
314
254
|
continue;
|
|
315
255
|
}
|
|
316
|
-
|
|
317
256
|
// 处理带操作符的字段名(如 userId$gt)
|
|
318
257
|
if (key.includes("$")) {
|
|
319
258
|
const lastDollarIndex = key.lastIndexOf("$");
|
|
@@ -323,127 +262,113 @@ export class DbUtils {
|
|
|
323
262
|
result[snakeKey] = value;
|
|
324
263
|
continue;
|
|
325
264
|
}
|
|
326
|
-
|
|
327
265
|
// 普通字段:转换键名,递归处理值(支持嵌套对象)
|
|
328
266
|
const snakeKey = snakeCase(key);
|
|
329
267
|
if (typeof value === "object" && value !== null && !Array.isArray(value)) {
|
|
330
268
|
result[snakeKey] = DbUtils.whereKeysToSnake(value);
|
|
331
|
-
}
|
|
269
|
+
}
|
|
270
|
+
else {
|
|
332
271
|
result[snakeKey] = value;
|
|
333
272
|
}
|
|
334
273
|
}
|
|
335
|
-
|
|
336
274
|
return result;
|
|
337
275
|
}
|
|
338
|
-
|
|
339
276
|
/**
|
|
340
277
|
* 序列化数组字段(写入数据库前)
|
|
341
278
|
* 将数组类型的字段转换为 JSON 字符串
|
|
342
279
|
*/
|
|
343
|
-
static serializeArrayFields(data
|
|
344
|
-
const serialized
|
|
345
|
-
|
|
280
|
+
static serializeArrayFields(data) {
|
|
281
|
+
const serialized = {};
|
|
346
282
|
for (const [key, value] of Object.entries(data)) {
|
|
347
283
|
if (value === null || value === undefined) {
|
|
348
284
|
serialized[key] = value;
|
|
349
285
|
continue;
|
|
350
286
|
}
|
|
351
|
-
|
|
352
287
|
if (Array.isArray(value)) {
|
|
353
288
|
serialized[key] = JSON.stringify(value);
|
|
354
289
|
continue;
|
|
355
290
|
}
|
|
356
|
-
|
|
357
291
|
serialized[key] = value;
|
|
358
292
|
}
|
|
359
|
-
|
|
360
293
|
return serialized;
|
|
361
294
|
}
|
|
362
|
-
|
|
363
295
|
/**
|
|
364
296
|
* 反序列化数组字段(从数据库读取后)
|
|
365
297
|
* 将 JSON 字符串转换回数组
|
|
366
298
|
*/
|
|
367
|
-
static deserializeArrayFields
|
|
299
|
+
static deserializeArrayFields(data) {
|
|
368
300
|
if (!data) {
|
|
369
301
|
return null;
|
|
370
302
|
}
|
|
371
|
-
|
|
372
|
-
const deserialized: Record<string, any> = {};
|
|
303
|
+
const deserialized = {};
|
|
373
304
|
for (const [key, value] of Object.entries(data)) {
|
|
374
305
|
deserialized[key] = value;
|
|
375
306
|
}
|
|
376
|
-
|
|
377
307
|
for (const [key, value] of Object.entries(deserialized)) {
|
|
378
308
|
if (typeof value !== "string") {
|
|
379
309
|
continue;
|
|
380
310
|
}
|
|
381
|
-
|
|
382
311
|
if (value.startsWith("[") && value.endsWith("]")) {
|
|
383
312
|
try {
|
|
384
313
|
const parsed = JSON.parse(value);
|
|
385
314
|
if (Array.isArray(parsed)) {
|
|
386
315
|
deserialized[key] = parsed;
|
|
387
316
|
}
|
|
388
|
-
}
|
|
317
|
+
}
|
|
318
|
+
catch {
|
|
389
319
|
// 解析失败则保持原值
|
|
390
320
|
}
|
|
391
321
|
}
|
|
392
322
|
}
|
|
393
|
-
|
|
394
|
-
return deserialized as T;
|
|
323
|
+
return deserialized;
|
|
395
324
|
}
|
|
396
|
-
|
|
397
|
-
static cleanAndSnakeAndSerializeWriteData(data: Record<string, any>, excludeValues: any[] = [null, undefined]): Record<string, any> {
|
|
325
|
+
static cleanAndSnakeAndSerializeWriteData(data, excludeValues = [null, undefined]) {
|
|
398
326
|
const cleanData = fieldClear(data, { excludeValues: excludeValues });
|
|
399
327
|
const snakeData = keysToSnake(cleanData);
|
|
400
328
|
return DbUtils.serializeArrayFields(snakeData);
|
|
401
329
|
}
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
const result: Record<string, any> = {};
|
|
330
|
+
static stripSystemFieldsForWrite(data, options) {
|
|
331
|
+
const result = {};
|
|
405
332
|
for (const [key, value] of Object.entries(data)) {
|
|
406
333
|
// 系统字段不可由用户覆盖
|
|
407
|
-
if (key === "id")
|
|
408
|
-
|
|
409
|
-
if (key === "
|
|
410
|
-
|
|
411
|
-
if (
|
|
334
|
+
if (key === "id")
|
|
335
|
+
continue;
|
|
336
|
+
if (key === "created_at")
|
|
337
|
+
continue;
|
|
338
|
+
if (key === "updated_at")
|
|
339
|
+
continue;
|
|
340
|
+
if (key === "deleted_at")
|
|
341
|
+
continue;
|
|
342
|
+
if (!options.allowState && key === "state")
|
|
343
|
+
continue;
|
|
412
344
|
result[key] = value;
|
|
413
345
|
}
|
|
414
|
-
|
|
415
346
|
return result;
|
|
416
347
|
}
|
|
417
|
-
|
|
418
|
-
static buildInsertRow(options: { data: Record<string, any>; id: number; now: number }): Record<string, any> {
|
|
348
|
+
static buildInsertRow(options) {
|
|
419
349
|
const serializedData = DbUtils.cleanAndSnakeAndSerializeWriteData(options.data);
|
|
420
350
|
const userData = DbUtils.stripSystemFieldsForWrite(serializedData, { allowState: false });
|
|
421
|
-
|
|
422
|
-
const result: Record<string, any> = {};
|
|
351
|
+
const result = {};
|
|
423
352
|
for (const [key, value] of Object.entries(userData)) {
|
|
424
353
|
result[key] = value;
|
|
425
354
|
}
|
|
426
|
-
|
|
427
355
|
result.id = options.id;
|
|
428
356
|
result.created_at = options.now;
|
|
429
357
|
result.updated_at = options.now;
|
|
430
358
|
result.state = 1;
|
|
431
359
|
return result;
|
|
432
360
|
}
|
|
433
|
-
|
|
434
|
-
static buildUpdateRow(options: { data: Record<string, any>; now: number; allowState: boolean }): Record<string, any> {
|
|
361
|
+
static buildUpdateRow(options) {
|
|
435
362
|
const serializedData = DbUtils.cleanAndSnakeAndSerializeWriteData(options.data);
|
|
436
363
|
const userData = DbUtils.stripSystemFieldsForWrite(serializedData, { allowState: options.allowState });
|
|
437
|
-
|
|
438
|
-
const result: Record<string, any> = {};
|
|
364
|
+
const result = {};
|
|
439
365
|
for (const [key, value] of Object.entries(userData)) {
|
|
440
366
|
result[key] = value;
|
|
441
367
|
}
|
|
442
368
|
result.updated_at = options.now;
|
|
443
369
|
return result;
|
|
444
370
|
}
|
|
445
|
-
|
|
446
|
-
static buildPartialUpdateData(options: { data: Record<string, any>; allowState: boolean }): Record<string, any> {
|
|
371
|
+
static buildPartialUpdateData(options) {
|
|
447
372
|
const serializedData = DbUtils.cleanAndSnakeAndSerializeWriteData(options.data);
|
|
448
373
|
return DbUtils.stripSystemFieldsForWrite(serializedData, { allowState: options.allowState });
|
|
449
374
|
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* JWT 工具类 - 基于 fast-jwt 实现
|
|
3
|
+
*/
|
|
4
|
+
import type { AuthConfig } from "../types/befly";
|
|
5
|
+
import type { JwtPayload, JwtSignOptions, JwtVerifyOptions, JwtDecoded } from "../types/jwt";
|
|
6
|
+
export declare class Jwt {
|
|
7
|
+
private config;
|
|
8
|
+
constructor(config?: AuthConfig);
|
|
9
|
+
sign(payload: JwtPayload, options?: JwtSignOptions): string;
|
|
10
|
+
verify<T = JwtPayload>(token: string, options?: JwtVerifyOptions): T;
|
|
11
|
+
decode(token: string, complete?: false): JwtPayload;
|
|
12
|
+
decode(token: string, complete: true): JwtDecoded;
|
|
13
|
+
}
|
|
@@ -1,35 +1,19 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* JWT 工具类 - 基于 fast-jwt 实现
|
|
3
3
|
*/
|
|
4
|
-
|
|
5
|
-
import type { AuthConfig } from "../types/befly.ts";
|
|
6
|
-
import type { JwtPayload, JwtSignOptions, JwtVerifyOptions, JwtDecoded, JwtHeader } from "../types/jwt.ts";
|
|
7
|
-
import type { Algorithm as FastJwtAlgorithm } from "fast-jwt";
|
|
8
|
-
|
|
9
4
|
import { createSigner, createVerifier, createDecoder } from "fast-jwt";
|
|
10
|
-
|
|
11
|
-
interface FastJwtComplete {
|
|
12
|
-
header: JwtHeader;
|
|
13
|
-
payload: JwtPayload;
|
|
14
|
-
signature: string;
|
|
15
|
-
input: string;
|
|
16
|
-
}
|
|
17
|
-
|
|
18
5
|
export class Jwt {
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
constructor(config: AuthConfig = {}) {
|
|
6
|
+
config;
|
|
7
|
+
constructor(config = {}) {
|
|
22
8
|
this.config = {
|
|
23
9
|
secret: config.secret || "befly-secret",
|
|
24
10
|
expiresIn: config.expiresIn || "7d",
|
|
25
11
|
algorithm: config.algorithm || "HS256"
|
|
26
12
|
};
|
|
27
13
|
}
|
|
28
|
-
|
|
29
|
-
sign(payload: JwtPayload, options: JwtSignOptions = {}): string {
|
|
14
|
+
sign(payload, options = {}) {
|
|
30
15
|
const key = options.secret || this.config.secret || "befly-secret";
|
|
31
|
-
const algorithm = (options.algorithm || this.config.algorithm || "HS256")
|
|
32
|
-
|
|
16
|
+
const algorithm = (options.algorithm || this.config.algorithm || "HS256");
|
|
33
17
|
const signer = createSigner({
|
|
34
18
|
key: key,
|
|
35
19
|
algorithm: algorithm,
|
|
@@ -42,15 +26,13 @@ export class Jwt {
|
|
|
42
26
|
});
|
|
43
27
|
return signer(payload);
|
|
44
28
|
}
|
|
45
|
-
|
|
46
|
-
verify<T = JwtPayload>(token: string, options: JwtVerifyOptions = {}): T {
|
|
29
|
+
verify(token, options = {}) {
|
|
47
30
|
if (!token || typeof token !== "string") {
|
|
48
31
|
throw new Error("Token必须是非空字符串");
|
|
49
32
|
}
|
|
50
33
|
const key = options.secret || this.config.secret || "befly-secret";
|
|
51
|
-
const algorithm = (this.config.algorithm || "HS256")
|
|
52
|
-
const algorithms
|
|
53
|
-
|
|
34
|
+
const algorithm = (this.config.algorithm || "HS256");
|
|
35
|
+
const algorithms = options.algorithms ? options.algorithms : [algorithm];
|
|
54
36
|
const verifier = createVerifier({
|
|
55
37
|
key: key,
|
|
56
38
|
algorithms: algorithms,
|
|
@@ -62,18 +44,15 @@ export class Jwt {
|
|
|
62
44
|
cache: true,
|
|
63
45
|
cacheTTL: 600000
|
|
64
46
|
});
|
|
65
|
-
return verifier(token)
|
|
47
|
+
return verifier(token);
|
|
66
48
|
}
|
|
67
|
-
|
|
68
|
-
decode(token: string, complete?: false): JwtPayload;
|
|
69
|
-
decode(token: string, complete: true): JwtDecoded;
|
|
70
|
-
decode(token: string, complete: boolean = false): JwtPayload | JwtDecoded {
|
|
49
|
+
decode(token, complete = false) {
|
|
71
50
|
if (!token || typeof token !== "string") {
|
|
72
51
|
throw new Error("Token必须是非空字符串");
|
|
73
52
|
}
|
|
74
53
|
if (complete) {
|
|
75
54
|
const decoder = createDecoder({ complete: true });
|
|
76
|
-
const result = decoder(token)
|
|
55
|
+
const result = decoder(token);
|
|
77
56
|
return {
|
|
78
57
|
header: result.header,
|
|
79
58
|
payload: result.payload,
|
|
@@ -81,6 +60,6 @@ export class Jwt {
|
|
|
81
60
|
};
|
|
82
61
|
}
|
|
83
62
|
const decoder = createDecoder();
|
|
84
|
-
return decoder(token)
|
|
63
|
+
return decoder(token);
|
|
85
64
|
}
|
|
86
65
|
}
|