befly 3.10.17 → 3.10.19
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} +8 -31
- package/dist/checks/checkApi.d.ts +1 -0
- package/{checks/checkApi.ts → dist/checks/checkApi.js} +10 -27
- package/dist/checks/checkHook.d.ts +1 -0
- package/{checks/checkHook.ts → dist/checks/checkHook.js} +10 -19
- package/dist/checks/checkMenu.d.ts +7 -0
- package/{checks/checkMenu.ts → dist/checks/checkMenu.js} +15 -50
- package/dist/checks/checkPlugin.d.ts +1 -0
- package/{checks/checkPlugin.ts → dist/checks/checkPlugin.js} +10 -19
- package/dist/checks/checkTable.d.ts +6 -0
- package/{checks/checkTable.ts → dist/checks/checkTable.js} +16 -40
- 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 +5 -0
- package/{hooks/auth.ts → dist/hooks/auth.js} +5 -9
- package/dist/hooks/cors.d.ts +9 -0
- package/{hooks/cors.ts → dist/hooks/cors.js} +2 -12
- package/dist/hooks/parser.d.ts +12 -0
- package/{hooks/parser.ts → dist/hooks/parser.js} +27 -42
- package/dist/hooks/permission.d.ts +12 -0
- package/{hooks/permission.ts → dist/hooks/permission.js} +11 -22
- package/dist/hooks/validator.d.ts +9 -0
- package/{hooks/validator.ts → dist/hooks/validator.js} +5 -12
- 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} +43 -103
- 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} +46 -87
- 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} +73 -230
- package/dist/lib/dbUtils.d.ts +68 -0
- package/{lib/dbUtils.ts → dist/lib/dbUtils.js} +49 -123
- package/dist/lib/jwt.d.ts +13 -0
- package/{lib/jwt.ts → dist/lib/jwt.js} +11 -32
- package/dist/lib/logger.d.ts +32 -0
- package/{lib/logger.ts → dist/lib/logger.js} +201 -278
- package/dist/lib/redisHelper.d.ts +185 -0
- package/{lib/redisHelper.ts → dist/lib/redisHelper.js} +95 -139
- package/dist/lib/sqlBuilder.d.ts +160 -0
- package/{lib/sqlBuilder.ts → dist/lib/sqlBuilder.js} +131 -277
- 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} +43 -60
- package/dist/loader/loadApis.d.ts +12 -0
- package/{loader/loadApis.ts → dist/loader/loadApis.js} +7 -17
- package/dist/loader/loadHooks.d.ts +8 -0
- package/{loader/loadHooks.ts → dist/loader/loadHooks.js} +5 -19
- package/dist/loader/loadPlugins.d.ts +8 -0
- package/{loader/loadPlugins.ts → dist/loader/loadPlugins.js} +8 -20
- package/dist/main.d.ts +26 -0
- package/{main.ts → dist/main.js} +39 -78
- package/dist/paths.d.ts +93 -0
- package/{paths.ts → dist/paths.js} +6 -19
- package/dist/plugins/cache.d.ts +14 -0
- package/{plugins/cache.ts → dist/plugins/cache.js} +4 -11
- package/dist/plugins/cipher.d.ts +10 -0
- package/{plugins/cipher.ts → dist/plugins/cipher.js} +1 -5
- package/dist/plugins/config.d.ts +10 -0
- package/dist/plugins/config.js +6 -0
- package/dist/plugins/db.d.ts +14 -0
- package/{plugins/db.ts → dist/plugins/db.js} +5 -13
- package/dist/plugins/jwt.d.ts +10 -0
- package/{plugins/jwt.ts → dist/plugins/jwt.js} +2 -7
- package/dist/plugins/logger.d.ts +28 -0
- package/{plugins/logger.ts → dist/plugins/logger.js} +2 -7
- package/dist/plugins/redis.d.ts +14 -0
- package/{plugins/redis.ts → dist/plugins/redis.js} +4 -9
- package/dist/plugins/tool.d.ts +79 -0
- package/{plugins/tool.ts → dist/plugins/tool.js} +7 -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} +17 -31
- package/dist/scripts/ensureDist.d.ts +1 -0
- package/dist/scripts/ensureDist.js +80 -0
- package/dist/sync/syncApi.d.ts +3 -0
- package/{sync/syncApi.ts → dist/sync/syncApi.js} +33 -53
- 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} +27 -60
- package/dist/sync/syncMenu.d.ts +14 -0
- package/{sync/syncMenu.ts → dist/sync/syncMenu.js} +61 -121
- package/dist/sync/syncTable.d.ts +151 -0
- package/{sync/syncTable.ts → dist/sync/syncTable.js} +168 -375
- package/{types → dist/types}/api.d.ts +12 -51
- package/dist/types/api.js +4 -0
- package/{types → dist/types}/befly.d.ts +32 -223
- 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 +15 -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 +47 -0
- package/dist/types/logger.js +6 -0
- package/dist/types/plugin.d.ts +14 -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/arrayKeysToCamel.d.ts +13 -0
- package/{utils/arrayKeysToCamel.ts → dist/utils/arrayKeysToCamel.js} +4 -4
- package/dist/utils/calcPerfTime.d.ts +4 -0
- package/{utils/calcPerfTime.ts → dist/utils/calcPerfTime.js} +3 -3
- package/dist/utils/configTypes.d.ts +1 -0
- package/dist/utils/configTypes.js +1 -0
- 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/genShortId.d.ts +10 -0
- package/{utils/genShortId.ts → dist/utils/genShortId.js} +1 -1
- 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/keysToCamel.d.ts +10 -0
- package/{utils/keysToCamel.ts → dist/utils/keysToCamel.js} +4 -5
- package/dist/utils/keysToSnake.d.ts +10 -0
- package/{utils/keysToSnake.ts → dist/utils/keysToSnake.js} +4 -5
- package/dist/utils/loadMenuConfigs.d.ts +5 -0
- package/{utils/loadMenuConfigs.ts → dist/utils/loadMenuConfigs.js} +22 -49
- package/dist/utils/pickFields.d.ts +4 -0
- package/{utils/pickFields.ts → dist/utils/pickFields.js} +2 -5
- package/dist/utils/process.d.ts +24 -0
- package/{utils/process.ts → dist/utils/process.js} +2 -18
- package/dist/utils/processFields.d.ts +4 -0
- package/{utils/processFields.ts → dist/utils/processFields.js} +4 -8
- package/dist/utils/regex.d.ts +145 -0
- package/{configs/presetRegexp.ts → dist/utils/regex.js} +8 -31
- package/dist/utils/response.d.ts +20 -0
- package/{utils/response.ts → dist/utils/response.js} +27 -48
- package/dist/utils/scanAddons.d.ts +17 -0
- package/{utils/scanAddons.ts → dist/utils/scanAddons.js} +4 -38
- package/dist/utils/scanConfig.d.ts +26 -0
- package/{utils/scanConfig.ts → dist/utils/scanConfig.js} +22 -59
- package/dist/utils/scanFiles.d.ts +30 -0
- package/{utils/scanFiles.ts → dist/utils/scanFiles.js} +25 -65
- package/dist/utils/scanSources.d.ts +10 -0
- package/{utils/scanSources.ts → dist/utils/scanSources.js} +16 -39
- package/dist/utils/sortModules.d.ts +28 -0
- package/{utils/sortModules.ts → dist/utils/sortModules.js} +24 -64
- package/dist/utils/sqlLog.d.ts +14 -0
- package/{utils/sqlLog.ts → dist/utils/sqlLog.js} +2 -14
- package/package.json +15 -32
- package/.gitignore +0 -0
- package/bunfig.toml +0 -3
- 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/plugins/config.ts +0 -13
- package/router/api.ts +0 -130
- package/tsconfig.json +0 -54
- 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/configTypes.ts +0 -3
- package/utils/importDefault.ts +0 -21
|
@@ -1,62 +1,42 @@
|
|
|
1
|
-
import type { BeflyContext } from "../types/befly.js";
|
|
2
|
-
import type { MenuConfig } from "../types/sync.js";
|
|
3
|
-
|
|
4
1
|
import { Logger } from "../lib/logger.js";
|
|
5
2
|
import { compileDisableMenuGlobRules, isMenuPathDisabledByGlobRules } from "../utils/disableMenusGlob.js";
|
|
6
3
|
import { getParentPath } from "../utils/loadMenuConfigs.js";
|
|
7
|
-
|
|
8
|
-
type MenuDef = {
|
|
9
|
-
path: string;
|
|
10
|
-
name: string;
|
|
11
|
-
sort: number;
|
|
12
|
-
parentPath: string;
|
|
13
|
-
};
|
|
14
|
-
|
|
15
|
-
function flattenMenusToDefMap(mergedMenus: MenuConfig[]): Map<string, MenuDef> {
|
|
4
|
+
function flattenMenusToDefMap(mergedMenus) {
|
|
16
5
|
// 读取配置菜单:扁平化为 path => { name, sort, parentPath }
|
|
17
6
|
// - 以 path 为唯一键:后出现的覆盖先出现的(与旧逻辑“同 path 多次同步同一条记录”一致)
|
|
18
7
|
// parentPath 规则:
|
|
19
8
|
// 1) 若 menu 显式携带 parentPath(包括空字符串),以其为准
|
|
20
9
|
// 2) 否则使用“树结构”推导的父级(由 children 嵌套关系决定;根级为 "")
|
|
21
10
|
// 3) 保底:若无法推导(极端情况),回退到 getParentPath(path)
|
|
22
|
-
const menuDefMap = new Map
|
|
23
|
-
|
|
24
|
-
const stack: Array<{ menu: MenuConfig; parentPathFromTree: string }> = [];
|
|
11
|
+
const menuDefMap = new Map();
|
|
12
|
+
const stack = [];
|
|
25
13
|
for (const m of mergedMenus) {
|
|
26
14
|
stack.push({ menu: m, parentPathFromTree: "" });
|
|
27
15
|
}
|
|
28
|
-
|
|
29
16
|
while (stack.length > 0) {
|
|
30
17
|
const item = stack.pop();
|
|
31
18
|
const menu = item ? item.menu : null;
|
|
32
19
|
if (!menu) {
|
|
33
20
|
continue;
|
|
34
21
|
}
|
|
35
|
-
|
|
36
|
-
const
|
|
37
|
-
|
|
38
|
-
const rawChildren = (menu as any).children;
|
|
22
|
+
const path = typeof menu.path === "string" ? menu.path : "";
|
|
23
|
+
const rawChildren = menu.children;
|
|
39
24
|
if (rawChildren && Array.isArray(rawChildren) && rawChildren.length > 0) {
|
|
40
25
|
const nextParentPathFromTree = typeof path === "string" ? path : "";
|
|
41
26
|
for (const child of rawChildren) {
|
|
42
27
|
stack.push({ menu: child, parentPathFromTree: nextParentPathFromTree });
|
|
43
28
|
}
|
|
44
29
|
}
|
|
45
|
-
|
|
46
30
|
if (!path) {
|
|
47
31
|
continue;
|
|
48
32
|
}
|
|
49
|
-
|
|
50
|
-
const name = typeof (menu as any).name === "string" ? (menu as any).name : "";
|
|
33
|
+
const name = typeof menu.name === "string" ? menu.name : "";
|
|
51
34
|
if (!name) {
|
|
52
35
|
continue;
|
|
53
36
|
}
|
|
54
|
-
|
|
55
|
-
const
|
|
56
|
-
|
|
57
|
-
const hasExplicitParentPath = typeof (menu as any).parentPath === "string";
|
|
58
|
-
const parentPath = hasExplicitParentPath ? ((menu as any).parentPath as string) : typeof item?.parentPathFromTree === "string" ? item.parentPathFromTree : getParentPath(path);
|
|
59
|
-
|
|
37
|
+
const sort = typeof menu.sort === "number" ? menu.sort : 999999;
|
|
38
|
+
const hasExplicitParentPath = typeof menu.parentPath === "string";
|
|
39
|
+
const parentPath = hasExplicitParentPath ? menu.parentPath : typeof item?.parentPathFromTree === "string" ? item.parentPathFromTree : getParentPath(path);
|
|
60
40
|
menuDefMap.set(path, {
|
|
61
41
|
path: path,
|
|
62
42
|
name: name,
|
|
@@ -64,86 +44,68 @@ function flattenMenusToDefMap(mergedMenus: MenuConfig[]): Map<string, MenuDef> {
|
|
|
64
44
|
parentPath: parentPath
|
|
65
45
|
});
|
|
66
46
|
}
|
|
67
|
-
|
|
68
47
|
return menuDefMap;
|
|
69
48
|
}
|
|
70
|
-
|
|
71
|
-
export async function syncMenu(ctx: BeflyContext, mergedMenus: MenuConfig[]): Promise<void> {
|
|
49
|
+
export async function syncMenu(ctx, mergedMenus) {
|
|
72
50
|
if (!ctx.db) {
|
|
73
51
|
throw new Error("syncMenu: ctx.db 未初始化(Db 插件未加载或注入失败)");
|
|
74
52
|
}
|
|
75
|
-
|
|
76
53
|
if (!ctx.cache) {
|
|
77
54
|
throw new Error("syncMenu: ctx.cache 未初始化(cache 插件未加载或注入失败)");
|
|
78
55
|
}
|
|
79
|
-
|
|
80
56
|
if (!ctx.config) {
|
|
81
57
|
throw new Error("syncMenu: ctx.config 未初始化(config 插件未加载或注入失败)");
|
|
82
58
|
}
|
|
83
|
-
|
|
84
59
|
if (!(await ctx.db.tableExists("addon_admin_menu")).data) {
|
|
85
60
|
Logger.debug(`addon_admin_menu 表不存在`);
|
|
86
61
|
return;
|
|
87
62
|
}
|
|
88
|
-
|
|
89
63
|
// 防御性过滤:保证禁用菜单不会进入 DB(即使上游遗漏了 checkMenu 的过滤)
|
|
90
64
|
const disableRules = compileDisableMenuGlobRules(ctx.config?.disableMenus);
|
|
91
|
-
const filteredMergedMenus
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
return filtered;
|
|
118
|
-
};
|
|
119
|
-
|
|
120
|
-
return filterMenusByDisableRules(mergedMenus);
|
|
121
|
-
})();
|
|
122
|
-
|
|
65
|
+
const filteredMergedMenus = disableRules.length === 0
|
|
66
|
+
? mergedMenus
|
|
67
|
+
: (() => {
|
|
68
|
+
const filterMenusByDisableRules = (menus) => {
|
|
69
|
+
const filtered = [];
|
|
70
|
+
for (const menu of menus) {
|
|
71
|
+
const menuPath = typeof menu?.path === "string" ? String(menu.path).trim() : "";
|
|
72
|
+
if (menuPath && isMenuPathDisabledByGlobRules(menuPath, disableRules)) {
|
|
73
|
+
continue;
|
|
74
|
+
}
|
|
75
|
+
const children = Array.isArray(menu?.children) ? menu.children : null;
|
|
76
|
+
if (children && children.length > 0) {
|
|
77
|
+
const nextChildren = filterMenusByDisableRules(children);
|
|
78
|
+
if (nextChildren.length > 0) {
|
|
79
|
+
menu.children = nextChildren;
|
|
80
|
+
}
|
|
81
|
+
else {
|
|
82
|
+
delete menu.children;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
filtered.push(menu);
|
|
86
|
+
}
|
|
87
|
+
return filtered;
|
|
88
|
+
};
|
|
89
|
+
return filterMenusByDisableRules(mergedMenus);
|
|
90
|
+
})();
|
|
123
91
|
const menuDefMap = flattenMenusToDefMap(filteredMergedMenus);
|
|
124
|
-
|
|
125
|
-
const configPaths = new Set<string>();
|
|
92
|
+
const configPaths = new Set();
|
|
126
93
|
for (const p of menuDefMap.keys()) {
|
|
127
94
|
configPaths.add(p);
|
|
128
95
|
}
|
|
129
|
-
|
|
130
96
|
const tableName = "addon_admin_menu";
|
|
131
|
-
|
|
132
97
|
// 2) 批量同步(事务内):按 path diff 执行批量 insert/update/delete
|
|
133
|
-
await ctx.db.trans(async (trans
|
|
98
|
+
await ctx.db.trans(async (trans) => {
|
|
134
99
|
// 读取全部菜单(用于清理禁用菜单:不分 state)
|
|
135
100
|
const allExistingMenusAllState = await trans.getAll({
|
|
136
101
|
table: tableName,
|
|
137
102
|
fields: ["id", "name", "path", "parentPath", "sort", "state"]
|
|
138
|
-
}
|
|
139
|
-
|
|
103
|
+
});
|
|
140
104
|
const existingListAllState = allExistingMenusAllState.data.lists || [];
|
|
141
|
-
const existingList = existingListAllState.filter((m
|
|
142
|
-
|
|
143
|
-
const
|
|
144
|
-
const
|
|
145
|
-
const duplicatePathInfoMap = new Map<string, { keptId: number; removedIds: number[] }>();
|
|
146
|
-
|
|
105
|
+
const existingList = existingListAllState.filter((m) => typeof m?.state === "number" && m.state >= 0);
|
|
106
|
+
const existingMenuMap = new Map();
|
|
107
|
+
const duplicateIdSet = new Set();
|
|
108
|
+
const duplicatePathInfoMap = new Map();
|
|
147
109
|
for (const record of existingList) {
|
|
148
110
|
if (typeof record?.path !== "string" || !record.path) {
|
|
149
111
|
continue;
|
|
@@ -151,36 +113,31 @@ export async function syncMenu(ctx: BeflyContext, mergedMenus: MenuConfig[]): Pr
|
|
|
151
113
|
if (typeof record?.id !== "number") {
|
|
152
114
|
continue;
|
|
153
115
|
}
|
|
154
|
-
|
|
155
116
|
const existing = existingMenuMap.get(record.path);
|
|
156
117
|
if (!existing) {
|
|
157
118
|
existingMenuMap.set(record.path, record);
|
|
158
119
|
continue;
|
|
159
120
|
}
|
|
160
|
-
|
|
161
121
|
const existingId = typeof existing?.id === "number" ? existing.id : 0;
|
|
162
122
|
const recordId = record.id;
|
|
163
|
-
|
|
164
123
|
// 保留 id 最大的一条(genTimeID 越大通常越新),其余标记为重复并清理
|
|
165
124
|
if (recordId > existingId) {
|
|
166
125
|
existingMenuMap.set(record.path, record);
|
|
167
|
-
|
|
168
126
|
if (existingId > 0) {
|
|
169
127
|
duplicateIdSet.add(existingId);
|
|
170
128
|
}
|
|
171
|
-
|
|
172
|
-
const info = duplicatePathInfoMap.get(record.path) || { keptId: recordId, removedIds: [] as number[] };
|
|
129
|
+
const info = duplicatePathInfoMap.get(record.path) || { keptId: recordId, removedIds: [] };
|
|
173
130
|
info.keptId = recordId;
|
|
174
131
|
if (existingId > 0) {
|
|
175
132
|
info.removedIds.push(existingId);
|
|
176
133
|
}
|
|
177
134
|
duplicatePathInfoMap.set(record.path, info);
|
|
178
|
-
}
|
|
135
|
+
}
|
|
136
|
+
else {
|
|
179
137
|
if (recordId > 0) {
|
|
180
138
|
duplicateIdSet.add(recordId);
|
|
181
139
|
}
|
|
182
|
-
|
|
183
|
-
const info = duplicatePathInfoMap.get(record.path) || { keptId: existingId, removedIds: [] as number[] };
|
|
140
|
+
const info = duplicatePathInfoMap.get(record.path) || { keptId: existingId, removedIds: [] };
|
|
184
141
|
info.keptId = existingId;
|
|
185
142
|
if (recordId > 0) {
|
|
186
143
|
info.removedIds.push(recordId);
|
|
@@ -188,9 +145,8 @@ export async function syncMenu(ctx: BeflyContext, mergedMenus: MenuConfig[]): Pr
|
|
|
188
145
|
duplicatePathInfoMap.set(record.path, info);
|
|
189
146
|
}
|
|
190
147
|
}
|
|
191
|
-
|
|
192
148
|
if (duplicatePathInfoMap.size > 0) {
|
|
193
|
-
const examples
|
|
149
|
+
const examples = [];
|
|
194
150
|
for (const entry of duplicatePathInfoMap.entries()) {
|
|
195
151
|
const path = entry[0];
|
|
196
152
|
const info = entry[1];
|
|
@@ -199,22 +155,16 @@ export async function syncMenu(ctx: BeflyContext, mergedMenus: MenuConfig[]): Pr
|
|
|
199
155
|
break;
|
|
200
156
|
}
|
|
201
157
|
}
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
examples: examples
|
|
209
|
-
},
|
|
210
|
-
"addon_admin_menu 检测到重复 path 记录:已保留 id 最大的一条并删除其余记录"
|
|
211
|
-
);
|
|
158
|
+
Logger.warn({
|
|
159
|
+
table: tableName,
|
|
160
|
+
duplicatePaths: duplicatePathInfoMap.size,
|
|
161
|
+
duplicateIds: duplicateIdSet.size,
|
|
162
|
+
examples: examples
|
|
163
|
+
}, "addon_admin_menu 检测到重复 path 记录:已保留 id 最大的一条并删除其余记录");
|
|
212
164
|
}
|
|
213
|
-
|
|
214
165
|
// 2) 一次性算出 insert/update(仅依赖 path diff,不使用 pid,不预生成 id)
|
|
215
|
-
const updList
|
|
216
|
-
const insList
|
|
217
|
-
|
|
166
|
+
const updList = [];
|
|
167
|
+
const insList = [];
|
|
218
168
|
for (const def of menuDefMap.values()) {
|
|
219
169
|
const existing = existingMenuMap.get(def.path);
|
|
220
170
|
if (existing) {
|
|
@@ -231,7 +181,8 @@ export async function syncMenu(ctx: BeflyContext, mergedMenus: MenuConfig[]): Pr
|
|
|
231
181
|
}
|
|
232
182
|
});
|
|
233
183
|
}
|
|
234
|
-
}
|
|
184
|
+
}
|
|
185
|
+
else {
|
|
235
186
|
insList.push({
|
|
236
187
|
name: def.name,
|
|
237
188
|
path: def.path,
|
|
@@ -240,18 +191,14 @@ export async function syncMenu(ctx: BeflyContext, mergedMenus: MenuConfig[]): Pr
|
|
|
240
191
|
});
|
|
241
192
|
}
|
|
242
193
|
}
|
|
243
|
-
|
|
244
194
|
if (updList.length > 0) {
|
|
245
195
|
await trans.updBatch(tableName, updList);
|
|
246
196
|
}
|
|
247
|
-
|
|
248
197
|
if (insList.length > 0) {
|
|
249
198
|
await trans.insBatch(tableName, insList);
|
|
250
199
|
}
|
|
251
|
-
|
|
252
200
|
// 3) 删除差集(DB - 配置,仅 state>=0) + 删除重复 path 的多余记录 + 删除禁用菜单(不分 state)
|
|
253
|
-
const delIdSet = new Set
|
|
254
|
-
|
|
201
|
+
const delIdSet = new Set();
|
|
255
202
|
// 3.1) 清理禁用菜单:只要命中 disableMenus,就强制删除(避免 menu/list 之类接口还能查到)
|
|
256
203
|
if (disableRules.length > 0) {
|
|
257
204
|
for (const record of existingListAllState) {
|
|
@@ -259,7 +206,6 @@ export async function syncMenu(ctx: BeflyContext, mergedMenus: MenuConfig[]): Pr
|
|
|
259
206
|
if (!recordPath) {
|
|
260
207
|
continue;
|
|
261
208
|
}
|
|
262
|
-
|
|
263
209
|
if (isMenuPathDisabledByGlobRules(recordPath, disableRules)) {
|
|
264
210
|
if (typeof record?.id === "number" && record.id > 0) {
|
|
265
211
|
delIdSet.add(record.id);
|
|
@@ -267,7 +213,6 @@ export async function syncMenu(ctx: BeflyContext, mergedMenus: MenuConfig[]): Pr
|
|
|
267
213
|
}
|
|
268
214
|
}
|
|
269
215
|
}
|
|
270
|
-
|
|
271
216
|
for (const record of existingList) {
|
|
272
217
|
if (typeof record?.path !== "string" || !record.path) {
|
|
273
218
|
continue;
|
|
@@ -278,30 +223,25 @@ export async function syncMenu(ctx: BeflyContext, mergedMenus: MenuConfig[]): Pr
|
|
|
278
223
|
}
|
|
279
224
|
}
|
|
280
225
|
}
|
|
281
|
-
|
|
282
226
|
for (const id of duplicateIdSet) {
|
|
283
227
|
if (typeof id === "number" && id > 0) {
|
|
284
228
|
delIdSet.add(id);
|
|
285
229
|
}
|
|
286
230
|
}
|
|
287
|
-
|
|
288
231
|
const delIds = Array.from(delIdSet);
|
|
289
|
-
|
|
290
232
|
if (delIds.length > 0) {
|
|
291
233
|
await trans.delForceBatch(tableName, delIds);
|
|
292
234
|
}
|
|
293
235
|
});
|
|
294
|
-
|
|
295
236
|
// 缓存同步职责已收敛到 syncCache(启动流程单点调用),此处只负责 DB 同步。
|
|
296
237
|
}
|
|
297
|
-
|
|
298
238
|
// 仅测试用(避免将内部扫描逻辑变成稳定 API)
|
|
299
239
|
export const __test__ = {
|
|
300
|
-
scanViewsDir: async (viewsDir
|
|
240
|
+
scanViewsDir: async (viewsDir, prefix, parentPath = "") => {
|
|
301
241
|
const mod = await import("../utils/loadMenuConfigs.js");
|
|
302
242
|
return await mod.scanViewsDirToMenuConfigs(viewsDir, prefix, parentPath);
|
|
303
243
|
},
|
|
304
|
-
flattenMenusToDefMap: (mergedMenus
|
|
244
|
+
flattenMenusToDefMap: (mergedMenus) => {
|
|
305
245
|
return flattenMenusToDefMap(mergedMenus);
|
|
306
246
|
}
|
|
307
247
|
};
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* syncTable 命令 - 同步数据库表结构(单文件版)
|
|
3
|
+
*
|
|
4
|
+
* 说明:
|
|
5
|
+
* - 历史上该能力拆分在 packages/core/sync/syncTable/* 多个模块中
|
|
6
|
+
* - 现在按项目要求,将所有实现合并到本文件(目录 packages/core/sync/syncTable/ 已删除)
|
|
7
|
+
*/
|
|
8
|
+
import type { DbDialectName } from "../lib/dbDialect.ts";
|
|
9
|
+
import type { BeflyContext } from "../types/befly.ts";
|
|
10
|
+
import type { DbResult, SqlInfo } from "../types/database.ts";
|
|
11
|
+
import type { ColumnInfo, FieldChange, IndexInfo } from "../types/sync.ts";
|
|
12
|
+
import type { FieldDefinition } from "../types/validate.ts";
|
|
13
|
+
import type { ScanFileResult } from "../utils/scanFiles.ts";
|
|
14
|
+
type SqlExecutor = {
|
|
15
|
+
unsafe<T = any>(sqlStr: string, params?: unknown[]): Promise<DbResult<T, SqlInfo>>;
|
|
16
|
+
};
|
|
17
|
+
type DbDialect = DbDialectName;
|
|
18
|
+
/**
|
|
19
|
+
* 文件导航(推荐阅读顺序)
|
|
20
|
+
* 1) syncTable(ctx, items) 入口(本段下方)
|
|
21
|
+
* 2) 版本/常量/方言判断(DB_VERSION_REQUIREMENTS 等)
|
|
22
|
+
* 3) 通用 DDL 工具(quote/type/default/ddl/index SQL)
|
|
23
|
+
* 4) Runtime I/O(只读元信息:表/列/索引/版本)
|
|
24
|
+
* 5) plan/apply(写变更:建表/改表/SQLite 重建)
|
|
25
|
+
*/
|
|
26
|
+
type SyncTableFn = ((ctx: BeflyContext, items: ScanFileResult[]) => Promise<void>) & {
|
|
27
|
+
TestKit: typeof SYNC_TABLE_TEST_KIT;
|
|
28
|
+
};
|
|
29
|
+
/**
|
|
30
|
+
* 数据库同步命令入口(函数模式)
|
|
31
|
+
*/
|
|
32
|
+
export declare const syncTable: SyncTableFn;
|
|
33
|
+
declare const SYNC_TABLE_TEST_KIT: {
|
|
34
|
+
DB_VERSION_REQUIREMENTS: {
|
|
35
|
+
readonly MYSQL_MIN_MAJOR: 8;
|
|
36
|
+
readonly POSTGRES_MIN_MAJOR: 17;
|
|
37
|
+
readonly SQLITE_MIN_VERSION: "3.50.0";
|
|
38
|
+
readonly SQLITE_MIN_VERSION_NUM: 35000;
|
|
39
|
+
};
|
|
40
|
+
CHANGE_TYPE_LABELS: {
|
|
41
|
+
readonly length: "长度";
|
|
42
|
+
readonly datatype: "类型";
|
|
43
|
+
readonly comment: "注释";
|
|
44
|
+
readonly default: "默认值";
|
|
45
|
+
readonly nullable: "可空约束";
|
|
46
|
+
readonly unique: "唯一约束";
|
|
47
|
+
};
|
|
48
|
+
MYSQL_TABLE_CONFIG: {
|
|
49
|
+
readonly ENGINE: "InnoDB";
|
|
50
|
+
readonly CHARSET: "utf8mb4";
|
|
51
|
+
readonly COLLATE: "utf8mb4_0900_ai_ci";
|
|
52
|
+
};
|
|
53
|
+
SYSTEM_INDEX_FIELDS: readonly string[];
|
|
54
|
+
getTypeMapping: typeof getTypeMapping;
|
|
55
|
+
quoteIdentifier: typeof quoteIdentifier;
|
|
56
|
+
escapeComment: typeof escapeComment;
|
|
57
|
+
applyFieldDefaults: typeof applyFieldDefaults;
|
|
58
|
+
isStringOrArrayType: typeof isStringOrArrayType;
|
|
59
|
+
getSqlType: typeof getSqlType;
|
|
60
|
+
resolveDefaultValue: typeof resolveDefaultValue;
|
|
61
|
+
generateDefaultSql: typeof generateDefaultSql;
|
|
62
|
+
buildIndexSQL: typeof buildIndexSQL;
|
|
63
|
+
buildSystemColumnDefs: typeof buildSystemColumnDefs;
|
|
64
|
+
buildBusinessColumnDefs: typeof buildBusinessColumnDefs;
|
|
65
|
+
generateDDLClause: typeof generateDDLClause;
|
|
66
|
+
isCompatibleTypeChange: typeof isCompatibleTypeChange;
|
|
67
|
+
compareFieldDefinition: typeof compareFieldDefinition;
|
|
68
|
+
tableExistsRuntime: typeof tableExistsRuntime;
|
|
69
|
+
getTableColumnsRuntime: typeof getTableColumnsRuntime;
|
|
70
|
+
getTableIndexesRuntime: typeof getTableIndexesRuntime;
|
|
71
|
+
createRuntime: (dbDialect: DbDialectName, db: SqlExecutor, dbName?: string) => SyncRuntime;
|
|
72
|
+
};
|
|
73
|
+
/**
|
|
74
|
+
* 获取字段类型映射(根据当前数据库类型)
|
|
75
|
+
*/
|
|
76
|
+
declare function getTypeMapping(dbDialect: DbDialect): Record<string, string>;
|
|
77
|
+
/**
|
|
78
|
+
* 根据数据库类型引用标识符
|
|
79
|
+
*/
|
|
80
|
+
declare function quoteIdentifier(dbDialect: DbDialect, identifier: string): string;
|
|
81
|
+
/**
|
|
82
|
+
* 转义 SQL 注释中的双引号
|
|
83
|
+
*/
|
|
84
|
+
declare function escapeComment(str: string): string;
|
|
85
|
+
/**
|
|
86
|
+
* 为字段定义应用默认值
|
|
87
|
+
*/
|
|
88
|
+
declare function applyFieldDefaults(fieldDef: any): void;
|
|
89
|
+
/**
|
|
90
|
+
* 判断是否为字符串或数组类型(需要长度参数)
|
|
91
|
+
*/
|
|
92
|
+
declare function isStringOrArrayType(fieldType: string): boolean;
|
|
93
|
+
/**
|
|
94
|
+
* 获取 SQL 数据类型
|
|
95
|
+
*/
|
|
96
|
+
declare function getSqlType(dbDialect: DbDialect, fieldType: string, fieldMax: number | null, unsigned?: boolean): string;
|
|
97
|
+
/**
|
|
98
|
+
* 处理默认值:将 null 或 'null' 字符串转换为对应类型的默认值
|
|
99
|
+
*/
|
|
100
|
+
declare function resolveDefaultValue(fieldDefault: any, fieldType: string): any;
|
|
101
|
+
/**
|
|
102
|
+
* 生成 SQL DEFAULT 子句
|
|
103
|
+
*/
|
|
104
|
+
declare function generateDefaultSql(actualDefault: any, fieldType: string): string;
|
|
105
|
+
/**
|
|
106
|
+
* 构建索引操作 SQL(统一使用在线策略)
|
|
107
|
+
*/
|
|
108
|
+
declare function buildIndexSQL(dbDialect: DbDialect, tableName: string, indexName: string, fieldName: string, action: "create" | "drop"): string;
|
|
109
|
+
/**
|
|
110
|
+
* 构建系统字段列定义
|
|
111
|
+
*/
|
|
112
|
+
declare function buildSystemColumnDefs(dbDialect: DbDialect): string[];
|
|
113
|
+
/**
|
|
114
|
+
* 构建业务字段列定义
|
|
115
|
+
*/
|
|
116
|
+
declare function buildBusinessColumnDefs(dbDialect: DbDialect, fields: Record<string, FieldDefinition>): string[];
|
|
117
|
+
/**
|
|
118
|
+
* 生成字段 DDL 子句(不含 ALTER TABLE 前缀)
|
|
119
|
+
*/
|
|
120
|
+
declare function generateDDLClause(dbDialect: DbDialect, fieldKey: string, fieldDef: FieldDefinition, isAdd?: boolean): string;
|
|
121
|
+
/**
|
|
122
|
+
* 判断是否为兼容的类型变更(宽化型变更,无数据丢失风险)
|
|
123
|
+
*/
|
|
124
|
+
declare function isCompatibleTypeChange(currentType: string, newType: string): boolean;
|
|
125
|
+
type SyncRuntime = {
|
|
126
|
+
/**
|
|
127
|
+
* 当前数据库方言(mysql/postgresql/sqlite),决定 SQL 片段与元信息查询方式。
|
|
128
|
+
* 约束:必须与 ctx.config.db.type 一致(经归一化)。
|
|
129
|
+
*/
|
|
130
|
+
dbDialect: DbDialect;
|
|
131
|
+
/**
|
|
132
|
+
* SQL 执行器:必须复用 ctx.db。
|
|
133
|
+
* 约束:syncTable 内部禁止新建 DB 连接/事务;runtime 仅保存引用,不拥有生命周期。
|
|
134
|
+
*/
|
|
135
|
+
db: SqlExecutor;
|
|
136
|
+
/**
|
|
137
|
+
* 数据库名:主要用于 MySQL information_schema 查询。
|
|
138
|
+
* 约束:PG/SQLite 可以传空字符串;不要在非 MySQL 方言依赖该值。
|
|
139
|
+
*/
|
|
140
|
+
dbName: string;
|
|
141
|
+
};
|
|
142
|
+
declare function tableExistsRuntime(runtime: SyncRuntime, tableName: string): Promise<boolean>;
|
|
143
|
+
declare function getTableColumnsRuntime(runtime: SyncRuntime, tableName: string): Promise<{
|
|
144
|
+
[key: string]: ColumnInfo;
|
|
145
|
+
}>;
|
|
146
|
+
declare function getTableIndexesRuntime(runtime: SyncRuntime, tableName: string): Promise<IndexInfo>;
|
|
147
|
+
/**
|
|
148
|
+
* 比较字段定义变化
|
|
149
|
+
*/
|
|
150
|
+
declare function compareFieldDefinition(dbDialect: DbDialect, existingColumn: ColumnInfo, fieldDef: FieldDefinition): FieldChange[];
|
|
151
|
+
export {};
|