befly 3.8.29 → 3.8.31
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +91 -6
- package/checks/checkApi.ts +2 -1
- package/checks/checkApp.ts +31 -1
- package/checks/checkTable.ts +3 -2
- package/hooks/cors.ts +3 -3
- package/hooks/parser.ts +8 -6
- package/hooks/permission.ts +12 -5
- package/hooks/validator.ts +1 -1
- package/lib/cacheHelper.ts +73 -65
- package/lib/cipher.ts +2 -1
- package/lib/connect.ts +23 -52
- package/lib/dbHelper.ts +14 -11
- package/lib/jwt.ts +58 -437
- package/lib/logger.ts +76 -197
- package/lib/redisHelper.ts +163 -1
- package/lib/sqlBuilder.ts +2 -1
- package/lib/validator.ts +150 -384
- package/loader/loadApis.ts +4 -7
- package/loader/loadHooks.ts +6 -5
- package/loader/loadPlugins.ts +11 -13
- package/main.ts +26 -53
- package/package.json +10 -8
- package/paths.ts +0 -6
- package/plugins/cipher.ts +1 -1
- package/plugins/config.ts +3 -4
- package/plugins/db.ts +6 -7
- package/plugins/jwt.ts +7 -6
- package/plugins/logger.ts +6 -6
- package/plugins/redis.ts +9 -13
- package/router/api.ts +2 -2
- package/router/static.ts +4 -8
- package/sync/syncAll.ts +8 -13
- package/sync/syncApi.ts +14 -10
- package/sync/syncDb/apply.ts +1 -2
- package/sync/syncDb.ts +12 -15
- package/sync/syncDev.ts +19 -56
- package/sync/syncMenu.ts +182 -137
- package/tests/cacheHelper.test.ts +327 -0
- package/tests/dbHelper-columns.test.ts +5 -20
- package/tests/dbHelper-execute.test.ts +14 -68
- package/tests/fields-redis-cache.test.ts +5 -3
- package/tests/integration.test.ts +17 -32
- package/tests/jwt.test.ts +36 -94
- package/tests/logger.test.ts +32 -34
- package/tests/redisHelper.test.ts +271 -2
- package/tests/redisKeys.test.ts +76 -0
- package/tests/sync-connection.test.ts +0 -6
- package/tests/syncDb-constants.test.ts +12 -12
- package/tests/util.test.ts +5 -1
- package/tests/validator.test.ts +611 -85
- package/types/befly.d.ts +9 -15
- package/types/cache.d.ts +73 -0
- package/types/common.d.ts +10 -128
- package/types/database.d.ts +221 -5
- package/types/index.ts +6 -5
- package/types/plugin.d.ts +1 -4
- package/types/redis.d.ts +37 -2
- package/types/table.d.ts +175 -0
- package/config.ts +0 -70
- package/hooks/_rateLimit.ts +0 -64
- package/lib/regexAliases.ts +0 -59
- package/lib/xml.ts +0 -383
- package/tests/validator-advanced.test.ts +0 -653
- package/tests/xml.test.ts +0 -101
- package/types/addon.d.ts +0 -50
- package/types/crypto.d.ts +0 -23
- package/types/jwt.d.ts +0 -99
- package/types/logger.d.ts +0 -43
- package/types/tool.d.ts +0 -67
- package/types/validator.d.ts +0 -43
package/sync/syncMenu.ts
CHANGED
|
@@ -1,79 +1,129 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* SyncMenu 命令 - 同步菜单数据到数据库
|
|
3
|
-
*
|
|
3
|
+
* 说明:扫描 addon 的 views 目录和项目的 menus.json,同步菜单数据
|
|
4
4
|
*
|
|
5
5
|
* 流程:
|
|
6
|
-
* 1. 扫描所有 addon 的
|
|
7
|
-
* 2.
|
|
8
|
-
* 3.
|
|
9
|
-
* 4.
|
|
10
|
-
* 5.
|
|
11
|
-
* 6.
|
|
12
|
-
* 7.
|
|
13
|
-
* 8. 强制删除配置中不存在的菜单记录
|
|
6
|
+
* 1. 扫描所有 addon 的 views 目录下的 meta.json 文件
|
|
7
|
+
* 2. 根据目录层级构建菜单树(无层级限制)
|
|
8
|
+
* 3. 读取项目的 menus.json 文件(手动配置的菜单)
|
|
9
|
+
* 4. 根据菜单的 path 字段检查是否存在
|
|
10
|
+
* 5. 存在则更新其他字段(name、sort、pid)
|
|
11
|
+
* 6. 不存在则新增菜单记录
|
|
12
|
+
* 7. 强制删除配置中不存在的菜单记录
|
|
14
13
|
* 注:state 字段由框架自动管理(1=正常,2=禁用,0=删除)
|
|
15
14
|
*/
|
|
16
15
|
|
|
16
|
+
import { existsSync } from 'node:fs';
|
|
17
|
+
import { readdir, readFile } from 'node:fs/promises';
|
|
17
18
|
import { join } from 'pathe';
|
|
18
|
-
|
|
19
|
+
|
|
19
20
|
import { Connect } from '../lib/connect.js';
|
|
21
|
+
import { DbHelper } from '../lib/dbHelper.js';
|
|
20
22
|
import { RedisHelper } from '../lib/redisHelper.js';
|
|
21
|
-
import {
|
|
23
|
+
import { RedisKeys } from 'befly-shared/redisKeys';
|
|
24
|
+
import { scanAddons, getAddonDir } from 'befly-shared/addonHelper';
|
|
22
25
|
import { Logger } from '../lib/logger.js';
|
|
23
26
|
import { projectDir } from '../paths.js';
|
|
27
|
+
import { beflyConfig } from '../befly.config.js';
|
|
24
28
|
|
|
25
|
-
import type { SyncMenuOptions, MenuConfig
|
|
29
|
+
import type { SyncMenuOptions, MenuConfig } from '../types/index.js';
|
|
26
30
|
|
|
27
31
|
/**
|
|
28
|
-
*
|
|
29
|
-
*
|
|
30
|
-
* @param transform 路径转换函数
|
|
32
|
+
* 清理目录名中的数字后缀
|
|
33
|
+
* 如:login_1 → login, index_2 → index
|
|
31
34
|
*/
|
|
32
|
-
function
|
|
33
|
-
|
|
34
|
-
menu.path = transform(menu.path);
|
|
35
|
-
}
|
|
36
|
-
menu.children?.forEach((child) => transformMenuPaths(child, transform));
|
|
35
|
+
function cleanDirName(name: string): string {
|
|
36
|
+
return name.replace(/_\d+$/, '');
|
|
37
37
|
}
|
|
38
38
|
|
|
39
39
|
/**
|
|
40
|
-
*
|
|
41
|
-
*
|
|
42
|
-
*
|
|
43
|
-
*
|
|
44
|
-
*
|
|
45
|
-
* 4. 项目菜单不添加前缀
|
|
40
|
+
* 扫描 views 目录,构建菜单树
|
|
41
|
+
* @param viewsDir views 目录路径
|
|
42
|
+
* @param prefix 路径前缀(addon 前缀)
|
|
43
|
+
* @param parentPath 父级路径
|
|
44
|
+
* @returns 菜单数组
|
|
46
45
|
*/
|
|
47
|
-
function
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
46
|
+
async function scanViewsDir(viewsDir: string, prefix: string, parentPath: string = ''): Promise<MenuConfig[]> {
|
|
47
|
+
if (!existsSync(viewsDir)) {
|
|
48
|
+
return [];
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const menus: MenuConfig[] = [];
|
|
52
|
+
const entries = await readdir(viewsDir, { withFileTypes: true });
|
|
53
|
+
|
|
54
|
+
for (const entry of entries) {
|
|
55
|
+
// 只处理目录,忽略 components 目录
|
|
56
|
+
if (!entry.isDirectory() || entry.name === 'components') {
|
|
57
|
+
continue;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
const dirPath = join(viewsDir, entry.name);
|
|
61
|
+
const metaPath = join(dirPath, 'meta.json');
|
|
62
|
+
|
|
63
|
+
// 没有 meta.json 的目录不处理
|
|
64
|
+
if (!existsSync(metaPath)) {
|
|
65
|
+
continue;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// 读取 meta.json
|
|
69
|
+
let meta: { name: string; order?: number };
|
|
70
|
+
try {
|
|
71
|
+
const content = await readFile(metaPath, 'utf-8');
|
|
72
|
+
meta = JSON.parse(content);
|
|
73
|
+
} catch (error: any) {
|
|
74
|
+
Logger.warn({ err: error, path: metaPath }, '读取 meta.json 失败');
|
|
75
|
+
continue;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// 计算路径:清理数字后缀,index 目录特殊处理
|
|
79
|
+
const cleanName = cleanDirName(entry.name);
|
|
80
|
+
let menuPath: string;
|
|
81
|
+
if (cleanName === 'index') {
|
|
82
|
+
// index 目录路径为父级路径,根级别则为 /
|
|
83
|
+
menuPath = parentPath || '/';
|
|
84
|
+
} else {
|
|
85
|
+
menuPath = parentPath ? `${parentPath}/${cleanName}` : `/${cleanName}`;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// 添加 addon 前缀
|
|
89
|
+
const fullPath = prefix ? `${prefix}${menuPath}` : menuPath;
|
|
90
|
+
|
|
91
|
+
const menu: MenuConfig = {
|
|
92
|
+
name: meta.name,
|
|
93
|
+
path: fullPath,
|
|
94
|
+
sort: meta.order || 100
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
// 递归扫描子目录
|
|
98
|
+
const children = await scanViewsDir(dirPath, prefix, menuPath);
|
|
99
|
+
if (children.length > 0) {
|
|
100
|
+
menu.children = children;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
menus.push(menu);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// 按 sort 排序
|
|
107
|
+
menus.sort((a, b) => (a.sort || 100) - (b.sort || 100));
|
|
108
|
+
|
|
109
|
+
return menus;
|
|
53
110
|
}
|
|
54
111
|
|
|
55
112
|
/**
|
|
56
113
|
* 合并菜单配置
|
|
57
|
-
*
|
|
58
|
-
* 支持三级菜单结构:父级、子级、孙级
|
|
114
|
+
* 支持无限层级菜单结构
|
|
59
115
|
*/
|
|
60
|
-
function mergeMenuConfigs(allMenus: Array<{ menus: MenuConfig[];
|
|
61
|
-
|
|
62
|
-
* 递归合并指定层级的菜单(限制最多3层)
|
|
63
|
-
* @param menus 待合并的菜单数组
|
|
64
|
-
* @param depth 当前深度(1=父级, 2=子级, 3=孙级)
|
|
65
|
-
* @returns 合并后的菜单数组
|
|
66
|
-
*/
|
|
67
|
-
function mergeLevel(menus: MenuConfig[], depth: number = 1): MenuConfig[] {
|
|
68
|
-
const menuMap = new Map<string, MenuConfig>();
|
|
116
|
+
function mergeMenuConfigs(allMenus: Array<{ menus: MenuConfig[]; source: string }>): MenuConfig[] {
|
|
117
|
+
const menuMap = new Map<string, MenuConfig>();
|
|
69
118
|
|
|
119
|
+
for (const { menus } of allMenus) {
|
|
70
120
|
for (const menu of menus) {
|
|
71
121
|
if (!menu.path) continue;
|
|
72
122
|
|
|
73
123
|
const existing = menuMap.get(menu.path);
|
|
74
124
|
if (existing) {
|
|
75
125
|
// 合并子菜单
|
|
76
|
-
if (menu.children
|
|
126
|
+
if (menu.children && menu.children.length > 0) {
|
|
77
127
|
existing.children = existing.children || [];
|
|
78
128
|
existing.children.push(...menu.children);
|
|
79
129
|
}
|
|
@@ -81,73 +131,67 @@ function mergeMenuConfigs(allMenus: Array<{ menus: MenuConfig[]; addonName: stri
|
|
|
81
131
|
menuMap.set(menu.path, { ...menu });
|
|
82
132
|
}
|
|
83
133
|
}
|
|
134
|
+
}
|
|
84
135
|
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
136
|
+
return Array.from(menuMap.values());
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* 过滤隐藏的菜单(递归处理子菜单)
|
|
141
|
+
*/
|
|
142
|
+
function filterHiddenMenus(menus: MenuConfig[], hiddenSet: Set<string>): MenuConfig[] {
|
|
143
|
+
const result: MenuConfig[] = [];
|
|
144
|
+
|
|
145
|
+
for (const menu of menus) {
|
|
146
|
+
// 如果菜单在隐藏列表中,跳过
|
|
147
|
+
if (menu.path && hiddenSet.has(menu.path)) {
|
|
148
|
+
continue;
|
|
92
149
|
}
|
|
93
150
|
|
|
94
|
-
|
|
95
|
-
|
|
151
|
+
const filtered = { ...menu };
|
|
152
|
+
|
|
153
|
+
// 递归过滤子菜单
|
|
154
|
+
if (filtered.children && filtered.children.length > 0) {
|
|
155
|
+
filtered.children = filterHiddenMenus(filtered.children, hiddenSet);
|
|
156
|
+
}
|
|
96
157
|
|
|
97
|
-
|
|
98
|
-
|
|
158
|
+
result.push(filtered);
|
|
159
|
+
}
|
|
99
160
|
|
|
100
|
-
return
|
|
161
|
+
return result;
|
|
101
162
|
}
|
|
102
163
|
|
|
103
164
|
/**
|
|
104
|
-
*
|
|
105
|
-
* 子级菜单使用独立路径
|
|
165
|
+
* 收集所有菜单的 path(递归收集所有层级)
|
|
106
166
|
*/
|
|
107
167
|
function collectPaths(menus: MenuConfig[]): Set<string> {
|
|
108
168
|
const paths = new Set<string>();
|
|
109
169
|
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
paths.add(child.path);
|
|
118
|
-
}
|
|
119
|
-
// 第三层菜单
|
|
120
|
-
if (child.children && child.children.length > 0) {
|
|
121
|
-
for (const grandChild of child.children) {
|
|
122
|
-
if (grandChild.path) {
|
|
123
|
-
paths.add(grandChild.path);
|
|
124
|
-
}
|
|
125
|
-
}
|
|
126
|
-
}
|
|
170
|
+
function collect(items: MenuConfig[]): void {
|
|
171
|
+
for (const menu of items) {
|
|
172
|
+
if (menu.path) {
|
|
173
|
+
paths.add(menu.path);
|
|
174
|
+
}
|
|
175
|
+
if (menu.children && menu.children.length > 0) {
|
|
176
|
+
collect(menu.children);
|
|
127
177
|
}
|
|
128
178
|
}
|
|
129
179
|
}
|
|
130
180
|
|
|
181
|
+
collect(menus);
|
|
131
182
|
return paths;
|
|
132
183
|
}
|
|
133
184
|
|
|
134
185
|
/**
|
|
135
|
-
*
|
|
136
|
-
* @param helper 数据库帮助类
|
|
137
|
-
* @param menu 菜单配置
|
|
138
|
-
* @param pid 父级菜单ID
|
|
139
|
-
* @param existingMenuMap 现有菜单映射
|
|
140
|
-
* @param depth 当前深度(1=父级, 2=子级, 3=孙级)
|
|
141
|
-
* @returns 菜单ID
|
|
186
|
+
* 递归同步单个菜单(无层级限制)
|
|
142
187
|
*/
|
|
143
|
-
async function syncMenuRecursive(helper: any, menu: MenuConfig, pid: number, existingMenuMap: Map<string, any
|
|
188
|
+
async function syncMenuRecursive(helper: any, menu: MenuConfig, pid: number, existingMenuMap: Map<string, any>): Promise<number> {
|
|
144
189
|
const existing = existingMenuMap.get(menu.path || '');
|
|
145
190
|
let menuId: number;
|
|
146
191
|
|
|
147
192
|
if (existing) {
|
|
148
193
|
menuId = existing.id;
|
|
149
194
|
|
|
150
|
-
// 检查是否需要更新
|
|
151
195
|
const needUpdate = existing.pid !== pid || existing.name !== menu.name || existing.sort !== (menu.sort || 0);
|
|
152
196
|
|
|
153
197
|
if (needUpdate) {
|
|
@@ -173,10 +217,9 @@ async function syncMenuRecursive(helper: any, menu: MenuConfig, pid: number, exi
|
|
|
173
217
|
});
|
|
174
218
|
}
|
|
175
219
|
|
|
176
|
-
|
|
177
|
-
if (depth < 3 && menu.children?.length > 0) {
|
|
220
|
+
if (menu.children && menu.children.length > 0) {
|
|
178
221
|
for (const child of menu.children) {
|
|
179
|
-
await syncMenuRecursive(helper, child, menuId, existingMenuMap
|
|
222
|
+
await syncMenuRecursive(helper, child, menuId, existingMenuMap);
|
|
180
223
|
}
|
|
181
224
|
}
|
|
182
225
|
|
|
@@ -184,11 +227,9 @@ async function syncMenuRecursive(helper: any, menu: MenuConfig, pid: number, exi
|
|
|
184
227
|
}
|
|
185
228
|
|
|
186
229
|
/**
|
|
187
|
-
*
|
|
188
|
-
* 子级菜单使用独立路径
|
|
230
|
+
* 同步菜单到数据库
|
|
189
231
|
*/
|
|
190
232
|
async function syncMenus(helper: any, menus: MenuConfig[]): Promise<void> {
|
|
191
|
-
// 批量查询所有现有菜单,建立 path -> menu 的映射
|
|
192
233
|
const allExistingMenus = await helper.getAll({
|
|
193
234
|
table: 'addon_admin_menu',
|
|
194
235
|
fields: ['id', 'pid', 'name', 'path', 'sort']
|
|
@@ -204,7 +245,7 @@ async function syncMenus(helper: any, menus: MenuConfig[]): Promise<void> {
|
|
|
204
245
|
try {
|
|
205
246
|
await syncMenuRecursive(helper, menu, 0, existingMenuMap, 1);
|
|
206
247
|
} catch (error: any) {
|
|
207
|
-
Logger.error(
|
|
248
|
+
Logger.error({ err: error, menu: menu.name }, '同步菜单失败');
|
|
208
249
|
throw error;
|
|
209
250
|
}
|
|
210
251
|
}
|
|
@@ -231,52 +272,49 @@ async function deleteObsoleteRecords(helper: any, configPaths: Set<string>): Pro
|
|
|
231
272
|
}
|
|
232
273
|
|
|
233
274
|
/**
|
|
234
|
-
* 加载所有菜单配置(addon +
|
|
235
|
-
* @returns 菜单配置数组
|
|
275
|
+
* 加载所有菜单配置(addon views + 项目 menus.json)
|
|
236
276
|
*/
|
|
237
|
-
async function loadMenuConfigs(): Promise<Array<{ menus: MenuConfig[];
|
|
238
|
-
const allMenus: Array<{ menus: MenuConfig[];
|
|
277
|
+
async function loadMenuConfigs(): Promise<Array<{ menus: MenuConfig[]; source: string }>> {
|
|
278
|
+
const allMenus: Array<{ menus: MenuConfig[]; source: string }> = [];
|
|
239
279
|
|
|
240
|
-
// 1.
|
|
280
|
+
// 1. 扫描所有 addon 的 views 目录
|
|
241
281
|
const addonNames = scanAddons();
|
|
242
282
|
|
|
243
283
|
for (const addonName of addonNames) {
|
|
244
284
|
try {
|
|
245
285
|
const addonDir = getAddonDir(addonName, '');
|
|
246
|
-
const
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
allMenus.push({ menus: menusWithPrefix, addonName: addonName });
|
|
286
|
+
const viewsDir = join(addonDir, 'views');
|
|
287
|
+
|
|
288
|
+
if (existsSync(viewsDir)) {
|
|
289
|
+
const prefix = `/addon/${addonName}`;
|
|
290
|
+
const menus = await scanViewsDir(viewsDir, prefix);
|
|
291
|
+
if (menus.length > 0) {
|
|
292
|
+
allMenus.push({
|
|
293
|
+
menus: menus,
|
|
294
|
+
source: `addon:${addonName}`
|
|
295
|
+
});
|
|
296
|
+
}
|
|
258
297
|
}
|
|
259
298
|
} catch (error: any) {
|
|
260
|
-
Logger.warn(
|
|
299
|
+
Logger.warn({ err: error, addon: addonName }, '扫描 addon views 目录失败');
|
|
261
300
|
}
|
|
262
301
|
}
|
|
263
302
|
|
|
264
|
-
// 2.
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
303
|
+
// 2. 读取项目的 menus.json
|
|
304
|
+
const menusJsonPath = join(projectDir, 'menus.json');
|
|
305
|
+
if (existsSync(menusJsonPath)) {
|
|
306
|
+
try {
|
|
307
|
+
const content = await readFile(menusJsonPath, 'utf-8');
|
|
308
|
+
const projectMenus = JSON.parse(content);
|
|
309
|
+
if (Array.isArray(projectMenus) && projectMenus.length > 0) {
|
|
310
|
+
allMenus.push({
|
|
311
|
+
menus: projectMenus,
|
|
312
|
+
source: 'project'
|
|
313
|
+
});
|
|
314
|
+
}
|
|
315
|
+
} catch (error: any) {
|
|
316
|
+
Logger.warn({ err: error }, '读取项目 menus.json 失败');
|
|
277
317
|
}
|
|
278
|
-
} catch (error: any) {
|
|
279
|
-
Logger.warn(`读取项目配置失败: ${error.message}`);
|
|
280
318
|
}
|
|
281
319
|
|
|
282
320
|
return allMenus;
|
|
@@ -285,39 +323,46 @@ async function loadMenuConfigs(): Promise<Array<{ menus: MenuConfig[]; addonName
|
|
|
285
323
|
/**
|
|
286
324
|
* SyncMenu 命令主函数
|
|
287
325
|
*/
|
|
288
|
-
export async function syncMenuCommand(
|
|
326
|
+
export async function syncMenuCommand(options: SyncMenuOptions = {}): Promise<void> {
|
|
289
327
|
try {
|
|
290
328
|
if (options.plan) {
|
|
291
329
|
Logger.debug('[计划] 同步菜单配置到数据库(plan 模式不执行)');
|
|
292
330
|
return;
|
|
293
331
|
}
|
|
294
332
|
|
|
295
|
-
// 1.
|
|
333
|
+
// 1. 加载所有菜单配置
|
|
296
334
|
const allMenus = await loadMenuConfigs();
|
|
297
335
|
|
|
298
336
|
// 2. 合并菜单配置
|
|
299
|
-
|
|
337
|
+
let mergedMenus = mergeMenuConfigs(allMenus);
|
|
338
|
+
|
|
339
|
+
// 3. 过滤隐藏菜单(根据 hiddenMenus 配置)
|
|
340
|
+
const hiddenMenus = (beflyConfig as any).hiddenMenus || [];
|
|
341
|
+
if (Array.isArray(hiddenMenus) && hiddenMenus.length > 0) {
|
|
342
|
+
const hiddenSet = new Set(hiddenMenus);
|
|
343
|
+
mergedMenus = filterHiddenMenus(mergedMenus, hiddenSet);
|
|
344
|
+
}
|
|
300
345
|
|
|
301
|
-
//
|
|
302
|
-
await Connect.connect(
|
|
346
|
+
// 连接数据库
|
|
347
|
+
await Connect.connect();
|
|
303
348
|
|
|
304
|
-
const helper = Connect.
|
|
349
|
+
const helper = new DbHelper({ redis: new RedisHelper() } as any, Connect.getSql());
|
|
305
350
|
|
|
306
|
-
// 3.
|
|
351
|
+
// 3. 检查表是否存在
|
|
307
352
|
const exists = await helper.tableExists('addon_admin_menu');
|
|
308
353
|
|
|
309
354
|
if (!exists) {
|
|
310
|
-
Logger.debug('表 addon_admin_menu
|
|
355
|
+
Logger.debug('表 addon_admin_menu 不存在,跳过菜单同步');
|
|
311
356
|
return;
|
|
312
357
|
}
|
|
313
358
|
|
|
314
|
-
// 4.
|
|
359
|
+
// 4. 收集所有菜单的 path
|
|
315
360
|
const configPaths = collectPaths(mergedMenus);
|
|
316
361
|
|
|
317
362
|
// 5. 同步菜单
|
|
318
363
|
await syncMenus(helper, mergedMenus);
|
|
319
364
|
|
|
320
|
-
// 6.
|
|
365
|
+
// 6. 删除不存在的菜单
|
|
321
366
|
await deleteObsoleteRecords(helper, configPaths);
|
|
322
367
|
|
|
323
368
|
// 7. 获取最终菜单数据(用于缓存)
|
|
@@ -330,12 +375,12 @@ export async function syncMenuCommand(config: BeflyOptions, options: SyncMenuOpt
|
|
|
330
375
|
// 8. 缓存菜单数据到 Redis
|
|
331
376
|
try {
|
|
332
377
|
const redisHelper = new RedisHelper();
|
|
333
|
-
await redisHelper.setObject(
|
|
378
|
+
await redisHelper.setObject(RedisKeys.menusAll(), allMenusData);
|
|
334
379
|
} catch (error: any) {
|
|
335
|
-
Logger.warn(
|
|
380
|
+
Logger.warn({ err: error }, 'Redis 缓存菜单数据失败');
|
|
336
381
|
}
|
|
337
382
|
} catch (error: any) {
|
|
338
|
-
Logger.error('菜单同步失败'
|
|
383
|
+
Logger.error({ err: error }, '菜单同步失败');
|
|
339
384
|
throw error;
|
|
340
385
|
} finally {
|
|
341
386
|
await Connect.disconnect();
|