befly 3.5.1 → 3.5.2
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/bin/index.ts +17 -130
- package/checks/table.ts +5 -7
- package/commands/index.ts +1 -97
- package/commands/sync.ts +60 -28
- package/commands/syncApi.ts +22 -52
- package/commands/syncDb/index.ts +31 -31
- package/commands/syncDb.ts +5 -5
- package/commands/syncDev.ts +35 -33
- package/commands/syncMenu.ts +25 -76
- package/lib/database.ts +0 -9
- package/lib/logger.ts +7 -5
- package/lifecycle/checker.ts +8 -25
- package/lifecycle/lifecycle.ts +5 -34
- package/lifecycle/loader.ts +10 -100
- package/package.json +2 -4
- package/commands/build.ts +0 -62
package/commands/syncDb/index.ts
CHANGED
|
@@ -41,6 +41,21 @@ const globalCount: Record<string, number> = {
|
|
|
41
41
|
indexDrop: 0
|
|
42
42
|
};
|
|
43
43
|
|
|
44
|
+
// 导出统计接口
|
|
45
|
+
export interface SyncDbStats {
|
|
46
|
+
processedTables: number;
|
|
47
|
+
createdTables: number;
|
|
48
|
+
modifiedTables: number;
|
|
49
|
+
addFields: number;
|
|
50
|
+
nameChanges: number;
|
|
51
|
+
typeChanges: number;
|
|
52
|
+
minChanges: number;
|
|
53
|
+
maxChanges: number;
|
|
54
|
+
defaultChanges: number;
|
|
55
|
+
indexCreate: number;
|
|
56
|
+
indexDrop: number;
|
|
57
|
+
}
|
|
58
|
+
|
|
44
59
|
/**
|
|
45
60
|
* 主同步函数
|
|
46
61
|
*
|
|
@@ -49,15 +64,13 @@ const globalCount: Record<string, number> = {
|
|
|
49
64
|
* 2. 建立数据库连接并检查版本
|
|
50
65
|
* 3. 扫描表定义文件(核心表、项目表、addon表)
|
|
51
66
|
* 4. 对比并应用表结构变更
|
|
52
|
-
* 5.
|
|
67
|
+
* 5. 返回统计信息
|
|
53
68
|
*/
|
|
54
|
-
export const SyncDb = async (): Promise<
|
|
69
|
+
export const SyncDb = async (): Promise<SyncDbStats> => {
|
|
55
70
|
const perfTracker = new PerformanceTracker();
|
|
56
71
|
const progressLogger = new ProgressLogger();
|
|
57
72
|
|
|
58
73
|
try {
|
|
59
|
-
Logger.info('开始数据库表结构同步...');
|
|
60
|
-
|
|
61
74
|
// 重置全局统计,避免多次调用累加
|
|
62
75
|
for (const k of Object.keys(globalCount)) {
|
|
63
76
|
if (typeof globalCount[k] === 'number') globalCount[k] = 0;
|
|
@@ -68,13 +81,11 @@ export const SyncDb = async (): Promise<void> => {
|
|
|
68
81
|
if (!(await checkTable())) {
|
|
69
82
|
throw new Error('表定义验证失败');
|
|
70
83
|
}
|
|
71
|
-
Logger.info(`✓ 表定义验证完成,耗时: ${perfTracker.getPhaseTime('表定义验证')}`);
|
|
72
84
|
|
|
73
85
|
// 阶段2:建立数据库连接并检查版本
|
|
74
86
|
perfTracker.markPhase('数据库连接');
|
|
75
87
|
sql = await Database.connectSql({ max: 1 });
|
|
76
88
|
await ensureDbVersion(sql);
|
|
77
|
-
Logger.info(`✓ 数据库连接建立,耗时: ${perfTracker.getPhaseTime('数据库连接')}`);
|
|
78
89
|
|
|
79
90
|
// 阶段3:扫描表定义文件
|
|
80
91
|
perfTracker.markPhase('扫描表文件');
|
|
@@ -113,7 +124,6 @@ export const SyncDb = async (): Promise<void> => {
|
|
|
113
124
|
}
|
|
114
125
|
}
|
|
115
126
|
perfTracker.finishPhase('扫描表文件');
|
|
116
|
-
Logger.info(`✓ 扫描完成,发现 ${totalTables} 个表定义文件,耗时: ${perfTracker.getPhaseTime('扫描表文件')}`);
|
|
117
127
|
|
|
118
128
|
// 阶段4:处理表文件
|
|
119
129
|
perfTracker.markPhase('同步处理');
|
|
@@ -132,7 +142,6 @@ export const SyncDb = async (): Promise<void> => {
|
|
|
132
142
|
|
|
133
143
|
// 跳过以下划线开头的文件(这些是公共字段规则,不是表定义)
|
|
134
144
|
if (fileName.startsWith('_')) {
|
|
135
|
-
Logger.info(`跳过非表定义文件: ${fileName}.json`);
|
|
136
145
|
continue;
|
|
137
146
|
}
|
|
138
147
|
|
|
@@ -155,7 +164,6 @@ export const SyncDb = async (): Promise<void> => {
|
|
|
155
164
|
}
|
|
156
165
|
|
|
157
166
|
processedCount++;
|
|
158
|
-
progressLogger.logTableProgress(processedCount, totalTables, tableName, dirType);
|
|
159
167
|
|
|
160
168
|
const tableDefinition = await Bun.file(file).json();
|
|
161
169
|
const existsTable = await tableExists(sql!, tableName);
|
|
@@ -171,29 +179,21 @@ export const SyncDb = async (): Promise<void> => {
|
|
|
171
179
|
}
|
|
172
180
|
|
|
173
181
|
perfTracker.finishPhase('同步处理');
|
|
174
|
-
Logger.info(`✓ 表处理完成,耗时: ${perfTracker.getPhaseTime('同步处理')}`);
|
|
175
|
-
|
|
176
|
-
// 阶段5:显示统计信息
|
|
177
|
-
Logger.info('\n=== 同步统计信息 ===');
|
|
178
|
-
Logger.info(`总耗时: ${perfTracker.getTotalTime()}`);
|
|
179
|
-
Logger.info(`处理表总数: ${globalCount.processedTables}`);
|
|
180
|
-
Logger.info(`创建表: ${globalCount.createdTables}`);
|
|
181
|
-
Logger.info(`修改表: ${globalCount.modifiedTables}`);
|
|
182
|
-
Logger.info(`字段新增: ${globalCount.addFields}`);
|
|
183
|
-
Logger.info(`字段名称变更: ${globalCount.nameChanges}`);
|
|
184
|
-
Logger.info(`字段类型变更: ${globalCount.typeChanges}`);
|
|
185
|
-
Logger.info(`字段最小值变更: ${globalCount.minChanges}`);
|
|
186
|
-
Logger.info(`字段最大值变更: ${globalCount.maxChanges}`);
|
|
187
|
-
Logger.info(`字段默认值变更: ${globalCount.defaultChanges}`);
|
|
188
|
-
Logger.info(`索引新增: ${globalCount.indexCreate}`);
|
|
189
|
-
Logger.info(`索引删除: ${globalCount.indexDrop}`);
|
|
190
|
-
|
|
191
|
-
if (globalCount.processedTables === 0) {
|
|
192
|
-
Logger.warn('没有找到任何表定义文件');
|
|
193
|
-
}
|
|
194
182
|
|
|
195
|
-
//
|
|
196
|
-
|
|
183
|
+
// 返回统计信息
|
|
184
|
+
return {
|
|
185
|
+
processedTables: globalCount.processedTables,
|
|
186
|
+
createdTables: globalCount.createdTables,
|
|
187
|
+
modifiedTables: globalCount.modifiedTables,
|
|
188
|
+
addFields: globalCount.addFields,
|
|
189
|
+
nameChanges: globalCount.nameChanges,
|
|
190
|
+
typeChanges: globalCount.typeChanges,
|
|
191
|
+
minChanges: globalCount.minChanges,
|
|
192
|
+
maxChanges: globalCount.maxChanges,
|
|
193
|
+
defaultChanges: globalCount.defaultChanges,
|
|
194
|
+
indexCreate: globalCount.indexCreate,
|
|
195
|
+
indexDrop: globalCount.indexDrop
|
|
196
|
+
};
|
|
197
197
|
} catch (error: any) {
|
|
198
198
|
Logger.error(`数据库同步失败`, error);
|
|
199
199
|
process.exit(1);
|
package/commands/syncDb.ts
CHANGED
|
@@ -6,14 +6,14 @@ import { Command } from 'commander';
|
|
|
6
6
|
import { join } from 'pathe';
|
|
7
7
|
import { existsSync } from 'node:fs';
|
|
8
8
|
import { Logger } from '../lib/logger.js';
|
|
9
|
-
import { SyncDb } from './syncDb/index.js';
|
|
9
|
+
import { SyncDb, type SyncDbStats } from './syncDb/index.js';
|
|
10
10
|
|
|
11
11
|
interface SyncDbOptions {
|
|
12
12
|
table?: string;
|
|
13
13
|
dryRun: boolean;
|
|
14
14
|
}
|
|
15
15
|
|
|
16
|
-
export async function syncDbCommand(options: SyncDbOptions) {
|
|
16
|
+
export async function syncDbCommand(options: SyncDbOptions): Promise<SyncDbStats> {
|
|
17
17
|
try {
|
|
18
18
|
// 设置环境变量
|
|
19
19
|
if (options.dryRun) {
|
|
@@ -24,9 +24,9 @@ export async function syncDbCommand(options: SyncDbOptions) {
|
|
|
24
24
|
process.env.SYNC_TABLE = options.table;
|
|
25
25
|
}
|
|
26
26
|
|
|
27
|
-
//
|
|
28
|
-
await SyncDb();
|
|
29
|
-
|
|
27
|
+
// 执行同步并返回统计
|
|
28
|
+
const stats = await SyncDb();
|
|
29
|
+
return stats;
|
|
30
30
|
} catch (error: any) {
|
|
31
31
|
Logger.error('数据库同步失败:', error);
|
|
32
32
|
process.exit(1);
|
package/commands/syncDev.ts
CHANGED
|
@@ -16,23 +16,26 @@ interface SyncDevOptions {
|
|
|
16
16
|
plan?: boolean;
|
|
17
17
|
}
|
|
18
18
|
|
|
19
|
+
export interface SyncDevStats {
|
|
20
|
+
adminCount: number;
|
|
21
|
+
roleCount: number;
|
|
22
|
+
cachedRoles: number;
|
|
23
|
+
}
|
|
24
|
+
|
|
19
25
|
/**
|
|
20
26
|
* SyncDev 命令主函数
|
|
21
27
|
*/
|
|
22
|
-
export async function syncDevCommand(options: SyncDevOptions = {}) {
|
|
28
|
+
export async function syncDevCommand(options: SyncDevOptions = {}): Promise<SyncDevStats> {
|
|
23
29
|
try {
|
|
24
30
|
if (options.plan) {
|
|
25
31
|
Logger.info('[计划] 同步完成后将初始化/更新开发管理员账号(plan 模式不执行)');
|
|
26
|
-
return;
|
|
32
|
+
return { adminCount: 0, roleCount: 0, cachedRoles: 0 };
|
|
27
33
|
}
|
|
28
34
|
|
|
29
35
|
if (!Env.DEV_PASSWORD) {
|
|
30
|
-
|
|
31
|
-
return;
|
|
36
|
+
return { adminCount: 0, roleCount: 0, cachedRoles: 0 };
|
|
32
37
|
}
|
|
33
38
|
|
|
34
|
-
Logger.info('开始同步开发管理员账号...\n');
|
|
35
|
-
|
|
36
39
|
// 连接数据库(SQL + Redis)
|
|
37
40
|
await Database.connect();
|
|
38
41
|
|
|
@@ -41,22 +44,19 @@ export async function syncDevCommand(options: SyncDevOptions = {}) {
|
|
|
41
44
|
// 检查 core_admin 表是否存在
|
|
42
45
|
const existAdmin = await helper.tableExists('core_admin');
|
|
43
46
|
if (!existAdmin) {
|
|
44
|
-
|
|
45
|
-
return;
|
|
47
|
+
return { adminCount: 0, roleCount: 0, cachedRoles: 0 };
|
|
46
48
|
}
|
|
47
49
|
|
|
48
50
|
// 检查 core_role 表是否存在
|
|
49
51
|
const existRole = await helper.tableExists('core_role');
|
|
50
52
|
if (!existRole) {
|
|
51
|
-
|
|
52
|
-
return;
|
|
53
|
+
return { adminCount: 0, roleCount: 0, cachedRoles: 0 };
|
|
53
54
|
}
|
|
54
55
|
|
|
55
56
|
// 检查 core_menu 表是否存在
|
|
56
57
|
const existMenu = await helper.tableExists('core_menu');
|
|
57
58
|
if (!existMenu) {
|
|
58
|
-
|
|
59
|
-
return;
|
|
59
|
+
return { adminCount: 0, roleCount: 0, cachedRoles: 0 };
|
|
60
60
|
}
|
|
61
61
|
|
|
62
62
|
// 查询所有菜单 ID
|
|
@@ -66,12 +66,10 @@ export async function syncDevCommand(options: SyncDevOptions = {}) {
|
|
|
66
66
|
});
|
|
67
67
|
|
|
68
68
|
if (!allMenus || !Array.isArray(allMenus)) {
|
|
69
|
-
|
|
70
|
-
return;
|
|
69
|
+
return { adminCount: 0, roleCount: 0, cachedRoles: 0 };
|
|
71
70
|
}
|
|
72
71
|
|
|
73
72
|
const menuIds = allMenus.length > 0 ? allMenus.map((m: any) => m.id).join(',') : '';
|
|
74
|
-
Logger.debug(`查询到 ${allMenus.length} 个菜单,ID 列表: ${menuIds || '(空)'}`);
|
|
75
73
|
|
|
76
74
|
// 查询所有接口 ID
|
|
77
75
|
const existApi = await helper.tableExists('core_api');
|
|
@@ -84,12 +82,7 @@ export async function syncDevCommand(options: SyncDevOptions = {}) {
|
|
|
84
82
|
|
|
85
83
|
if (allApis && Array.isArray(allApis) && allApis.length > 0) {
|
|
86
84
|
apiIds = allApis.map((a: any) => a.id).join(',');
|
|
87
|
-
Logger.debug(`查询到 ${allApis.length} 个接口,ID 列表: ${apiIds}`);
|
|
88
|
-
} else {
|
|
89
|
-
Logger.info('未查询到接口数据');
|
|
90
85
|
}
|
|
91
|
-
} else {
|
|
92
|
-
Logger.info('接口表不存在,跳过接口权限配置');
|
|
93
86
|
}
|
|
94
87
|
|
|
95
88
|
// 查询或创建 dev 角色
|
|
@@ -110,7 +103,6 @@ export async function syncDevCommand(options: SyncDevOptions = {}) {
|
|
|
110
103
|
apis: apiIds
|
|
111
104
|
}
|
|
112
105
|
});
|
|
113
|
-
Logger.info('dev 角色菜单和接口权限已更新');
|
|
114
106
|
} else {
|
|
115
107
|
// 创建 dev 角色
|
|
116
108
|
const roleId = await helper.insData({
|
|
@@ -149,6 +141,7 @@ export async function syncDevCommand(options: SyncDevOptions = {}) {
|
|
|
149
141
|
where: { email: Env.DEV_EMAIL }
|
|
150
142
|
});
|
|
151
143
|
|
|
144
|
+
let isNew = false;
|
|
152
145
|
if (existing) {
|
|
153
146
|
// 更新现有账号
|
|
154
147
|
await helper.updData({
|
|
@@ -156,26 +149,23 @@ export async function syncDevCommand(options: SyncDevOptions = {}) {
|
|
|
156
149
|
where: { email: Env.DEV_EMAIL },
|
|
157
150
|
data: devData
|
|
158
151
|
});
|
|
159
|
-
Logger.info(`✅ 开发管理员已更新:email=${Env.DEV_EMAIL}, username=dev, roleCode=dev, roleType=admin`);
|
|
160
152
|
} else {
|
|
161
153
|
// 插入新账号
|
|
162
154
|
await helper.insData({
|
|
163
155
|
table: 'core_admin',
|
|
164
156
|
data: devData
|
|
165
157
|
});
|
|
166
|
-
|
|
158
|
+
isNew = true;
|
|
167
159
|
}
|
|
168
160
|
|
|
169
161
|
// 缓存角色权限数据到 Redis
|
|
170
|
-
|
|
162
|
+
let cachedRolesCount = 0;
|
|
171
163
|
try {
|
|
172
164
|
// 检查必要的表是否存在
|
|
173
165
|
const apiTableExists = await helper.tableExists('core_api');
|
|
174
166
|
const roleTableExists = await helper.tableExists('core_role');
|
|
175
167
|
|
|
176
|
-
if (
|
|
177
|
-
Logger.warn('⚠️ 接口或角色表不存在,跳过角色权限缓存');
|
|
178
|
-
} else {
|
|
168
|
+
if (apiTableExists && roleTableExists) {
|
|
179
169
|
// 查询所有角色
|
|
180
170
|
const roles = await helper.getAll({
|
|
181
171
|
table: 'core_role',
|
|
@@ -189,7 +179,6 @@ export async function syncDevCommand(options: SyncDevOptions = {}) {
|
|
|
189
179
|
});
|
|
190
180
|
|
|
191
181
|
const redis = Database.getRedis();
|
|
192
|
-
let cachedRoles = 0;
|
|
193
182
|
|
|
194
183
|
// 为每个角色缓存接口权限
|
|
195
184
|
for (const role of roles) {
|
|
@@ -216,16 +205,29 @@ export async function syncDevCommand(options: SyncDevOptions = {}) {
|
|
|
216
205
|
const result = await redis.sadd(redisKey, ...roleApiPaths);
|
|
217
206
|
|
|
218
207
|
if (result > 0) {
|
|
219
|
-
|
|
220
|
-
Logger.debug(` └ 角色 ${role.code}: ${result} 个接口`);
|
|
208
|
+
cachedRolesCount++;
|
|
221
209
|
}
|
|
222
210
|
}
|
|
223
|
-
|
|
224
|
-
Logger.info(`✅ 已缓存 ${cachedRoles} 个角色的接口权限`);
|
|
225
211
|
}
|
|
226
212
|
} catch (error: any) {
|
|
227
|
-
|
|
213
|
+
// 忽略缓存错误
|
|
228
214
|
}
|
|
215
|
+
|
|
216
|
+
// 获取统计数据
|
|
217
|
+
const allAdmins = await helper.getAll({
|
|
218
|
+
table: 'core_admin',
|
|
219
|
+
fields: ['id']
|
|
220
|
+
});
|
|
221
|
+
const allRoles = await helper.getAll({
|
|
222
|
+
table: 'core_role',
|
|
223
|
+
fields: ['id']
|
|
224
|
+
});
|
|
225
|
+
|
|
226
|
+
return {
|
|
227
|
+
adminCount: allAdmins.length,
|
|
228
|
+
roleCount: allRoles.length,
|
|
229
|
+
cachedRoles: cachedRolesCount
|
|
230
|
+
};
|
|
229
231
|
} catch (error: any) {
|
|
230
232
|
Logger.error('开发管理员同步失败:', error);
|
|
231
233
|
process.exit(1);
|
package/commands/syncMenu.ts
CHANGED
|
@@ -34,6 +34,15 @@ interface MenuConfig {
|
|
|
34
34
|
children?: MenuConfig[];
|
|
35
35
|
}
|
|
36
36
|
|
|
37
|
+
export interface SyncMenuStats {
|
|
38
|
+
totalMenus: number;
|
|
39
|
+
parentMenus: number;
|
|
40
|
+
childMenus: number;
|
|
41
|
+
created: number;
|
|
42
|
+
updated: number;
|
|
43
|
+
deleted: number;
|
|
44
|
+
}
|
|
45
|
+
|
|
37
46
|
/**
|
|
38
47
|
* 读取菜单配置文件
|
|
39
48
|
* 如果文件不存在或不是数组格式,返回空数组
|
|
@@ -41,7 +50,6 @@ interface MenuConfig {
|
|
|
41
50
|
async function readMenuConfig(filePath: string): Promise<MenuConfig[]> {
|
|
42
51
|
try {
|
|
43
52
|
if (!existsSync(filePath)) {
|
|
44
|
-
Logger.warn(`菜单配置文件不存在: ${filePath},使用空数组`);
|
|
45
53
|
return [];
|
|
46
54
|
}
|
|
47
55
|
|
|
@@ -50,13 +58,11 @@ async function readMenuConfig(filePath: string): Promise<MenuConfig[]> {
|
|
|
50
58
|
|
|
51
59
|
// 验证是否为数组
|
|
52
60
|
if (!Array.isArray(content)) {
|
|
53
|
-
Logger.warn(`菜单配置文件格式错误(非数组): ${filePath},使用空数组`);
|
|
54
61
|
return [];
|
|
55
62
|
}
|
|
56
63
|
|
|
57
64
|
return content;
|
|
58
65
|
} catch (error: any) {
|
|
59
|
-
Logger.warn(`读取菜单配置失败: ${filePath},使用空数组`, error.message);
|
|
60
66
|
return [];
|
|
61
67
|
}
|
|
62
68
|
}
|
|
@@ -176,7 +182,6 @@ async function syncMenus(helper: any, menus: MenuConfig[]): Promise<{ created: n
|
|
|
176
182
|
});
|
|
177
183
|
parentId = existingParent.id;
|
|
178
184
|
stats.updated++;
|
|
179
|
-
Logger.debug(` └ 更新父级菜单: ${menu.name} (ID: ${parentId}, Path: ${menu.path})`);
|
|
180
185
|
} else {
|
|
181
186
|
parentId = await helper.insData({
|
|
182
187
|
table: 'core_menu',
|
|
@@ -190,7 +195,6 @@ async function syncMenus(helper: any, menus: MenuConfig[]): Promise<{ created: n
|
|
|
190
195
|
}
|
|
191
196
|
});
|
|
192
197
|
stats.created++;
|
|
193
|
-
Logger.debug(` └ 新增父级菜单: ${menu.name} (ID: ${parentId}, Path: ${menu.path})`);
|
|
194
198
|
}
|
|
195
199
|
|
|
196
200
|
// 2. 同步子级菜单(自动追加父级路径前缀)
|
|
@@ -217,9 +221,8 @@ async function syncMenus(helper: any, menus: MenuConfig[]): Promise<{ created: n
|
|
|
217
221
|
}
|
|
218
222
|
});
|
|
219
223
|
stats.updated++;
|
|
220
|
-
Logger.debug(` └ 更新子级菜单: ${child.name} (ID: ${existingChild.id}, PID: ${parentId}, Path: ${childFullPath})`);
|
|
221
224
|
} else {
|
|
222
|
-
|
|
225
|
+
await helper.insData({
|
|
223
226
|
table: 'core_menu',
|
|
224
227
|
data: {
|
|
225
228
|
pid: parentId,
|
|
@@ -231,7 +234,6 @@ async function syncMenus(helper: any, menus: MenuConfig[]): Promise<{ created: n
|
|
|
231
234
|
}
|
|
232
235
|
});
|
|
233
236
|
stats.created++;
|
|
234
|
-
Logger.debug(` └ 新增子级菜单: ${child.name} (ID: ${childId}, PID: ${parentId}, Path: ${childFullPath})`);
|
|
235
237
|
}
|
|
236
238
|
}
|
|
237
239
|
}
|
|
@@ -248,8 +250,6 @@ async function syncMenus(helper: any, menus: MenuConfig[]): Promise<{ created: n
|
|
|
248
250
|
* 删除配置中不存在的菜单(强制删除)
|
|
249
251
|
*/
|
|
250
252
|
async function deleteObsoleteRecords(helper: any, configPaths: Set<string>): Promise<number> {
|
|
251
|
-
Logger.debug(`\n=== 删除配置中不存在的记录 ===`);
|
|
252
|
-
|
|
253
253
|
const allRecords = await helper.getAll({
|
|
254
254
|
table: 'core_menu',
|
|
255
255
|
fields: ['id', 'path', 'name'],
|
|
@@ -264,60 +264,31 @@ async function deleteObsoleteRecords(helper: any, configPaths: Set<string>): Pro
|
|
|
264
264
|
where: { id: record.id }
|
|
265
265
|
});
|
|
266
266
|
deletedCount++;
|
|
267
|
-
Logger.debug(` └ 删除记录: ${record.name} (ID: ${record.id}, path: ${record.path})`);
|
|
268
267
|
}
|
|
269
268
|
}
|
|
270
269
|
|
|
271
|
-
if (deletedCount === 0) {
|
|
272
|
-
Logger.debug(' ✅ 无需删除的记录');
|
|
273
|
-
}
|
|
274
|
-
|
|
275
270
|
return deletedCount;
|
|
276
271
|
}
|
|
277
272
|
|
|
278
273
|
/**
|
|
279
274
|
* SyncMenu 命令主函数
|
|
280
275
|
*/
|
|
281
|
-
export async function syncMenuCommand(options: SyncMenuOptions = {}) {
|
|
276
|
+
export async function syncMenuCommand(options: SyncMenuOptions = {}): Promise<SyncMenuStats> {
|
|
282
277
|
try {
|
|
283
278
|
if (options.plan) {
|
|
284
279
|
Logger.info('[计划] 同步菜单配置到数据库(plan 模式不执行)');
|
|
285
|
-
|
|
286
|
-
Logger.info('[计划] 2. 合并菜单配置(core 优先覆盖项目)');
|
|
287
|
-
Logger.info('[计划] 3. 子级菜单自动追加父级路径前缀');
|
|
288
|
-
Logger.info('[计划] 4. 根据 path 检查菜单是否存在');
|
|
289
|
-
Logger.info('[计划] 5. 存在则更新,不存在则新增');
|
|
290
|
-
Logger.info('[计划] 6. 强制删除配置中不存在的菜单');
|
|
291
|
-
Logger.info('[计划] 7. 显示菜单结构预览');
|
|
292
|
-
return;
|
|
280
|
+
return { totalMenus: 0, parentMenus: 0, childMenus: 0, created: 0, updated: 0, deleted: 0 };
|
|
293
281
|
}
|
|
294
282
|
|
|
295
|
-
Logger.info('开始同步菜单配置到数据库...\n');
|
|
296
|
-
|
|
297
283
|
// 1. 读取两个配置文件
|
|
298
|
-
Logger.debug('=== 步骤 1: 读取菜单配置文件 ===');
|
|
299
284
|
const projectMenuPath = join(projectDir, 'menu.json');
|
|
300
285
|
const coreMenuPath = join(coreDir, 'menu.json');
|
|
301
286
|
|
|
302
|
-
Logger.info(` 项目路径: ${projectMenuPath}`);
|
|
303
|
-
Logger.info(` core 路径: ${coreMenuPath}`);
|
|
304
|
-
|
|
305
287
|
const projectMenus = await readMenuConfig(projectMenuPath);
|
|
306
288
|
const coreMenus = await readMenuConfig(coreMenuPath);
|
|
307
289
|
|
|
308
|
-
Logger.info(`✅ 项目配置: ${projectMenus.length} 个父级菜单`);
|
|
309
|
-
Logger.info(`✅ core 配置: ${coreMenus.length} 个父级菜单`);
|
|
310
|
-
|
|
311
290
|
// 2. 合并菜单配置
|
|
312
|
-
Logger.info('\n=== 步骤 2: 合并菜单配置(core 优先覆盖项目) ===');
|
|
313
291
|
const mergedMenus = mergeMenuConfigs(projectMenus, coreMenus);
|
|
314
|
-
Logger.info(`✅ 合并后共有 ${mergedMenus.length} 个父级菜单`);
|
|
315
|
-
|
|
316
|
-
// 打印合并后的菜单结构
|
|
317
|
-
for (const menu of mergedMenus) {
|
|
318
|
-
const childCount = menu.children?.length || 0;
|
|
319
|
-
Logger.debug(` └ ${menu.name} (${menu.path}) - ${childCount} 个子菜单`);
|
|
320
|
-
}
|
|
321
292
|
|
|
322
293
|
// 连接数据库(SQL + Redis)
|
|
323
294
|
await Database.connect();
|
|
@@ -325,7 +296,6 @@ export async function syncMenuCommand(options: SyncMenuOptions = {}) {
|
|
|
325
296
|
const helper = Database.getDbHelper();
|
|
326
297
|
|
|
327
298
|
// 3. 检查表是否存在
|
|
328
|
-
Logger.debug('\n=== 步骤 3: 检查数据表 ===');
|
|
329
299
|
const exists = await helper.tableExists('core_menu');
|
|
330
300
|
|
|
331
301
|
if (!exists) {
|
|
@@ -333,47 +303,23 @@ export async function syncMenuCommand(options: SyncMenuOptions = {}) {
|
|
|
333
303
|
process.exit(1);
|
|
334
304
|
}
|
|
335
305
|
|
|
336
|
-
Logger.debug(`✅ 表 core_menu 存在`);
|
|
337
|
-
|
|
338
306
|
// 4. 收集配置文件中所有菜单的 path
|
|
339
|
-
Logger.debug('\n=== 步骤 4: 收集配置菜单路径 ===');
|
|
340
307
|
const configPaths = collectPaths(mergedMenus);
|
|
341
|
-
Logger.debug(`✅ 配置文件中共有 ${configPaths.size} 个菜单路径`);
|
|
342
308
|
|
|
343
309
|
// 5. 同步菜单
|
|
344
|
-
Logger.debug('\n=== 步骤 5: 同步菜单数据(新增/更新) ===');
|
|
345
310
|
const stats = await syncMenus(helper, mergedMenus);
|
|
346
311
|
|
|
347
312
|
// 6. 删除文件中不存在的菜单(强制删除)
|
|
348
313
|
const deletedCount = await deleteObsoleteRecords(helper, configPaths);
|
|
349
314
|
|
|
350
|
-
// 7.
|
|
351
|
-
Logger.debug('\n=== 步骤 7: 菜单结构预览 ===');
|
|
315
|
+
// 7. 获取最终菜单数据
|
|
352
316
|
const allMenus = await helper.getAll({
|
|
353
317
|
table: 'core_menu',
|
|
354
318
|
fields: ['id', 'pid', 'name', 'path', 'type'],
|
|
355
319
|
orderBy: ['pid#ASC', 'sort#ASC', 'id#ASC']
|
|
356
320
|
});
|
|
357
321
|
|
|
358
|
-
|
|
359
|
-
for (const parent of parentMenus) {
|
|
360
|
-
const children = allMenus.filter((m: any) => m.pid === parent.id);
|
|
361
|
-
Logger.debug(` └ ${parent.name} (${parent.path})`);
|
|
362
|
-
for (const child of children) {
|
|
363
|
-
Logger.debug(` └ ${child.name} (${child.path})`);
|
|
364
|
-
}
|
|
365
|
-
}
|
|
366
|
-
|
|
367
|
-
// 8. 输出统计信息
|
|
368
|
-
Logger.info(`\n=== 菜单同步完成 ===`);
|
|
369
|
-
Logger.info(`新增菜单: ${stats.created} 个`);
|
|
370
|
-
Logger.info(`更新菜单: ${stats.updated} 个`);
|
|
371
|
-
Logger.info(`删除菜单: ${deletedCount} 个`);
|
|
372
|
-
Logger.info(`当前父级菜单: ${allMenus.filter((m: any) => m.pid === 0).length} 个`);
|
|
373
|
-
Logger.info(`当前子级菜单: ${allMenus.filter((m: any) => m.pid !== 0).length} 个`);
|
|
374
|
-
|
|
375
|
-
// 9. 缓存菜单数据到 Redis
|
|
376
|
-
Logger.info('\n=== 步骤 8: 缓存菜单数据到 Redis ===');
|
|
322
|
+
// 8. 缓存菜单数据到 Redis
|
|
377
323
|
try {
|
|
378
324
|
const menus = await helper.getAll({
|
|
379
325
|
table: 'core_menu',
|
|
@@ -381,16 +327,19 @@ export async function syncMenuCommand(options: SyncMenuOptions = {}) {
|
|
|
381
327
|
orderBy: ['sort#ASC', 'id#ASC']
|
|
382
328
|
});
|
|
383
329
|
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
if (result === null) {
|
|
387
|
-
Logger.warn('⚠️ 菜单缓存失败');
|
|
388
|
-
} else {
|
|
389
|
-
Logger.info(`✅ 已缓存 ${menus.length} 个菜单到 Redis (Key: menus:all)`);
|
|
390
|
-
}
|
|
330
|
+
await RedisHelper.setObject('menus:all', menus);
|
|
391
331
|
} catch (error: any) {
|
|
392
|
-
|
|
332
|
+
// 忽略缓存错误
|
|
393
333
|
}
|
|
334
|
+
|
|
335
|
+
return {
|
|
336
|
+
totalMenus: allMenus.length,
|
|
337
|
+
parentMenus: allMenus.filter((m: any) => m.pid === 0).length,
|
|
338
|
+
childMenus: allMenus.filter((m: any) => m.pid !== 0).length,
|
|
339
|
+
created: stats.created,
|
|
340
|
+
updated: stats.updated,
|
|
341
|
+
deleted: deletedCount
|
|
342
|
+
};
|
|
394
343
|
} catch (error: any) {
|
|
395
344
|
Logger.error('菜单同步失败:', error);
|
|
396
345
|
process.exit(1);
|
package/lib/database.ts
CHANGED
|
@@ -87,8 +87,6 @@ export class Database {
|
|
|
87
87
|
|
|
88
88
|
const version = await Promise.race([healthCheckPromise, timeoutPromise]);
|
|
89
89
|
|
|
90
|
-
Logger.info(`数据库连接成功,version: ${version}`);
|
|
91
|
-
|
|
92
90
|
this.sqlClient = sql;
|
|
93
91
|
return sql;
|
|
94
92
|
} catch (error: any) {
|
|
@@ -110,7 +108,6 @@ export class Database {
|
|
|
110
108
|
if (this.sqlClient) {
|
|
111
109
|
try {
|
|
112
110
|
await this.sqlClient.close();
|
|
113
|
-
Logger.info('SQL 连接已关闭');
|
|
114
111
|
} catch (error: any) {
|
|
115
112
|
Logger.warn('关闭 SQL 连接时出错:', error.message);
|
|
116
113
|
}
|
|
@@ -186,7 +183,6 @@ export class Database {
|
|
|
186
183
|
});
|
|
187
184
|
|
|
188
185
|
await redis.ping();
|
|
189
|
-
Logger.info('Redis 连接成功');
|
|
190
186
|
|
|
191
187
|
this.redisClient = redis;
|
|
192
188
|
return redis;
|
|
@@ -203,7 +199,6 @@ export class Database {
|
|
|
203
199
|
if (this.redisClient) {
|
|
204
200
|
try {
|
|
205
201
|
this.redisClient.close();
|
|
206
|
-
Logger.info('Redis 连接已关闭');
|
|
207
202
|
} catch (error: any) {
|
|
208
203
|
Logger.warn('关闭 Redis 连接时出错:', error);
|
|
209
204
|
}
|
|
@@ -233,16 +228,12 @@ export class Database {
|
|
|
233
228
|
static async connect(options?: { sql?: SqlClientOptions; redis?: boolean }): Promise<void> {
|
|
234
229
|
try {
|
|
235
230
|
if (options?.sql !== false) {
|
|
236
|
-
Logger.info('正在初始化 SQL 连接...');
|
|
237
231
|
await this.connectSql(options?.sql);
|
|
238
232
|
}
|
|
239
233
|
|
|
240
234
|
if (options?.redis !== false) {
|
|
241
|
-
Logger.info('正在初始化 Redis 连接...');
|
|
242
235
|
await this.connectRedis();
|
|
243
236
|
}
|
|
244
|
-
|
|
245
|
-
Logger.info('数据库连接初始化完成');
|
|
246
237
|
} catch (error: any) {
|
|
247
238
|
Logger.error('数据库初始化失败', error);
|
|
248
239
|
await this.disconnect();
|
package/lib/logger.ts
CHANGED
|
@@ -217,10 +217,12 @@ export class Logger {
|
|
|
217
217
|
* 用于命令开始时提示用户当前环境
|
|
218
218
|
*/
|
|
219
219
|
static printEnv(): void {
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
console.log(
|
|
223
|
-
console.log(
|
|
224
|
-
console.log(
|
|
220
|
+
console.log('========================================');
|
|
221
|
+
console.log('开始执行完整同步流程');
|
|
222
|
+
console.log(`当前环境: ${process.env.NODE_ENV || 'development'}`);
|
|
223
|
+
console.log(`项目名称: ${Env.APP_NAME}`);
|
|
224
|
+
console.log(`数据库地址: ${Env.DB_HOST}`);
|
|
225
|
+
console.log(`数据库名称: ${Env.DB_NAME}`);
|
|
226
|
+
console.log('========================================\n');
|
|
225
227
|
}
|
|
226
228
|
}
|