befly 2.3.3 → 3.0.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.
Files changed (87) hide show
  1. package/checks/conflict.ts +329 -0
  2. package/checks/table.ts +252 -0
  3. package/config/env.ts +218 -0
  4. package/config/fields.ts +55 -0
  5. package/config/regexAliases.ts +51 -0
  6. package/config/reserved.ts +96 -0
  7. package/main.ts +47 -0
  8. package/package.json +26 -11
  9. package/plugins/db.ts +60 -0
  10. package/plugins/logger.ts +28 -0
  11. package/plugins/redis.ts +47 -0
  12. package/scripts/syncDb/apply.ts +171 -0
  13. package/scripts/syncDb/constants.ts +71 -0
  14. package/scripts/syncDb/ddl.ts +189 -0
  15. package/scripts/syncDb/helpers.ts +173 -0
  16. package/scripts/syncDb/index.ts +203 -0
  17. package/scripts/syncDb/schema.ts +199 -0
  18. package/scripts/syncDb/sqlite.ts +50 -0
  19. package/scripts/syncDb/state.ts +106 -0
  20. package/scripts/syncDb/table.ts +214 -0
  21. package/scripts/syncDb/tableCreate.ts +148 -0
  22. package/scripts/syncDb/tests/constants.test.ts +105 -0
  23. package/scripts/syncDb/tests/ddl.test.ts +134 -0
  24. package/scripts/syncDb/tests/helpers.test.ts +70 -0
  25. package/scripts/syncDb/types.ts +92 -0
  26. package/scripts/syncDb/version.ts +73 -0
  27. package/scripts/syncDb.ts +10 -0
  28. package/tsconfig.json +58 -0
  29. package/types/addon.d.ts +53 -0
  30. package/types/api.d.ts +249 -0
  31. package/types/befly.d.ts +230 -0
  32. package/types/common.d.ts +215 -0
  33. package/types/context.d.ts +7 -0
  34. package/types/crypto.d.ts +23 -0
  35. package/types/database.d.ts +273 -0
  36. package/types/index.d.ts +450 -0
  37. package/types/index.ts +438 -0
  38. package/types/jwt.d.ts +99 -0
  39. package/types/logger.d.ts +43 -0
  40. package/types/plugin.d.ts +109 -0
  41. package/types/redis.d.ts +46 -0
  42. package/types/tool.d.ts +67 -0
  43. package/types/validator.d.ts +43 -0
  44. package/types/validator.ts +43 -0
  45. package/utils/colors.ts +221 -0
  46. package/utils/crypto.ts +308 -0
  47. package/utils/database.ts +348 -0
  48. package/utils/dbHelper.ts +713 -0
  49. package/utils/helper.ts +812 -0
  50. package/utils/index.ts +33 -0
  51. package/utils/jwt.ts +493 -0
  52. package/utils/logger.ts +191 -0
  53. package/utils/redisHelper.ts +321 -0
  54. package/utils/requestContext.ts +167 -0
  55. package/utils/sqlBuilder.ts +611 -0
  56. package/utils/validate.ts +493 -0
  57. package/utils/{xml.js → xml.ts} +100 -74
  58. package/.npmrc +0 -3
  59. package/.prettierignore +0 -2
  60. package/.prettierrc +0 -11
  61. package/apis/health/info.js +0 -49
  62. package/apis/tool/tokenCheck.js +0 -29
  63. package/bin/befly.js +0 -109
  64. package/bunfig.toml +0 -3
  65. package/checks/table.js +0 -206
  66. package/config/env.js +0 -64
  67. package/main.js +0 -579
  68. package/plugins/db.js +0 -46
  69. package/plugins/logger.js +0 -14
  70. package/plugins/redis.js +0 -32
  71. package/plugins/tool.js +0 -8
  72. package/scripts/syncDb.js +0 -752
  73. package/scripts/syncDev.js +0 -96
  74. package/system.js +0 -118
  75. package/tables/common.json +0 -16
  76. package/tables/tool.json +0 -6
  77. package/utils/api.js +0 -27
  78. package/utils/colors.js +0 -83
  79. package/utils/crypto.js +0 -260
  80. package/utils/index.js +0 -334
  81. package/utils/jwt.js +0 -387
  82. package/utils/logger.js +0 -143
  83. package/utils/redisHelper.js +0 -74
  84. package/utils/sqlBuilder.js +0 -498
  85. package/utils/sqlManager.js +0 -471
  86. package/utils/tool.js +0 -31
  87. package/utils/validate.js +0 -226
@@ -0,0 +1,203 @@
1
+ /**
2
+ * syncDb 主入口文件
3
+ *
4
+ * 功能:
5
+ * - 协调所有模块,执行数据库表结构同步
6
+ * - 处理核心表、项目表、addon 表
7
+ * - 提供统计信息和错误处理
8
+ */
9
+
10
+ import path from 'node:path';
11
+ import { Logger } from '../../utils/logger.js';
12
+ import { Env } from '../../config/env.js';
13
+ import { createSqlClient } from '../../utils/database.js';
14
+ import { toSnakeCase } from '../../utils/helper.js';
15
+ import checkTable from '../../checks/table.js';
16
+ import { paths } from '../../paths.js';
17
+ import { scanAddons, getAddonDir, addonDirExists } from '../../utils/helper.js';
18
+
19
+ // 导入模块化的功能
20
+ import { ensureDbVersion } from './version.js';
21
+ import { tableExists } from './schema.js';
22
+ import { createTable, modifyTable } from './table.js';
23
+ import { PerformanceTracker, ProgressLogger } from './state.js';
24
+ import type { SQL } from 'bun';
25
+
26
+ // 全局 SQL 客户端实例
27
+ let sql: SQL | null = null;
28
+
29
+ // 全局统计对象
30
+ const globalCount: Record<string, number> = {
31
+ processedTables: 0,
32
+ createdTables: 0,
33
+ modifiedTables: 0,
34
+ addFields: 0,
35
+ nameChanges: 0,
36
+ typeChanges: 0,
37
+ minChanges: 0,
38
+ maxChanges: 0,
39
+ defaultChanges: 0,
40
+ indexCreate: 0,
41
+ indexDrop: 0
42
+ };
43
+
44
+ /**
45
+ * 主同步函数
46
+ *
47
+ * 流程:
48
+ * 1. 验证表定义文件
49
+ * 2. 建立数据库连接并检查版本
50
+ * 3. 扫描表定义文件(核心表、项目表、addon表)
51
+ * 4. 对比并应用表结构变更
52
+ * 5. 输出统计信息
53
+ */
54
+ export const SyncDb = async (): Promise<void> => {
55
+ const perfTracker = new PerformanceTracker();
56
+ const progressLogger = new ProgressLogger();
57
+
58
+ try {
59
+ Logger.info('开始数据库表结构同步...');
60
+
61
+ // 重置全局统计,避免多次调用累加
62
+ for (const k of Object.keys(globalCount)) {
63
+ if (typeof globalCount[k] === 'number') globalCount[k] = 0;
64
+ }
65
+
66
+ // 阶段1:验证表定义文件
67
+ perfTracker.markPhase('表定义验证');
68
+ if (!(await checkTable())) {
69
+ throw new Error('表定义验证失败');
70
+ }
71
+ Logger.info(`✓ 表定义验证完成,耗时: ${perfTracker.getPhaseTime('表定义验证')}`);
72
+
73
+ // 阶段2:建立数据库连接并检查版本
74
+ perfTracker.markPhase('数据库连接');
75
+ sql = await createSqlClient({ max: 1 });
76
+ await ensureDbVersion(sql);
77
+ Logger.info(`✓ 数据库连接建立,耗时: ${perfTracker.getPhaseTime('数据库连接')}`);
78
+
79
+ // 阶段3:扫描表定义文件
80
+ perfTracker.markPhase('扫描表文件');
81
+ const tablesGlob = new Bun.Glob('*.json');
82
+ const directories: Array<{ path: string; isCore: boolean; addonName?: string }> = [{ path: paths.projectTableDir, isCore: false }];
83
+
84
+ // 添加所有 addon 的 tables 目录
85
+ const addons = scanAddons();
86
+ for (const addon of addons) {
87
+ if (addonDirExists(addon, 'tables')) {
88
+ directories.push({
89
+ path: getAddonDir(addon, 'tables'),
90
+ isCore: false,
91
+ addonName: addon
92
+ });
93
+ }
94
+ }
95
+
96
+ // 统计表文件总数
97
+ let totalTables = 0;
98
+ for (const dirConfig of directories) {
99
+ for await (const file of tablesGlob.scan({
100
+ cwd: dirConfig.path,
101
+ absolute: true,
102
+ onlyFiles: true
103
+ })) {
104
+ const fileName = path.basename(file, '.json');
105
+ if (!fileName.startsWith('_')) {
106
+ totalTables++;
107
+ }
108
+ }
109
+ }
110
+ perfTracker.finishPhase('扫描表文件');
111
+ Logger.info(`✓ 扫描完成,发现 ${totalTables} 个表定义文件,耗时: ${perfTracker.getPhaseTime('扫描表文件')}`);
112
+
113
+ // 阶段4:处理表文件
114
+ perfTracker.markPhase('同步处理');
115
+ let processedCount = 0;
116
+
117
+ for (const dirConfig of directories) {
118
+ const { path: dir, isCore, addonName } = dirConfig;
119
+ const dirType = addonName ? `组件${addonName}` : '项目';
120
+
121
+ for await (const file of tablesGlob.scan({
122
+ cwd: dir,
123
+ absolute: true,
124
+ onlyFiles: true
125
+ })) {
126
+ const fileName = path.basename(file, '.json');
127
+
128
+ // 跳过以下划线开头的文件(这些是公共字段规则,不是表定义)
129
+ if (fileName.startsWith('_')) {
130
+ Logger.info(`跳过非表定义文件: ${fileName}.json`);
131
+ continue;
132
+ }
133
+
134
+ // 确定表名前缀:
135
+ // - addon 表:addon_{addonName}_ 前缀
136
+ // - 项目表:无前缀
137
+ let tableName = toSnakeCase(fileName);
138
+ if (addonName) {
139
+ tableName = `addon_${addonName}_${tableName}`;
140
+ }
141
+
142
+ processedCount++;
143
+ progressLogger.logTableProgress(processedCount, totalTables, tableName);
144
+ Logger.info(` 类型: ${dirType}`);
145
+
146
+ const tableDefinition = await Bun.file(file).json();
147
+ const existsTable = await tableExists(sql!, tableName);
148
+
149
+ if (existsTable) {
150
+ await modifyTable(sql!, tableName, tableDefinition, globalCount);
151
+ } else {
152
+ await createTable(sql!, tableName, tableDefinition);
153
+ globalCount.createdTables++;
154
+ }
155
+ globalCount.processedTables++;
156
+ }
157
+ }
158
+
159
+ perfTracker.finishPhase('同步处理');
160
+ Logger.info(`✓ 表处理完成,耗时: ${perfTracker.getPhaseTime('同步处理')}`);
161
+
162
+ // 阶段5:显示统计信息
163
+ Logger.info('\n=== 同步统计信息 ===');
164
+ Logger.info(`总耗时: ${perfTracker.getTotalTime()}`);
165
+ Logger.info(`处理表总数: ${globalCount.processedTables}`);
166
+ Logger.info(`创建表: ${globalCount.createdTables}`);
167
+ Logger.info(`修改表: ${globalCount.modifiedTables}`);
168
+ Logger.info(`字段新增: ${globalCount.addFields}`);
169
+ Logger.info(`字段名称变更: ${globalCount.nameChanges}`);
170
+ Logger.info(`字段类型变更: ${globalCount.typeChanges}`);
171
+ Logger.info(`字段最小值变更: ${globalCount.minChanges}`);
172
+ Logger.info(`字段最大值变更: ${globalCount.maxChanges}`);
173
+ Logger.info(`字段默认值变更: ${globalCount.defaultChanges}`);
174
+ Logger.info(`索引新增: ${globalCount.indexCreate}`);
175
+ Logger.info(`索引删除: ${globalCount.indexDrop}`);
176
+
177
+ if (globalCount.processedTables === 0) {
178
+ Logger.warn('没有找到任何表定义文件');
179
+ }
180
+
181
+ // 输出性能统计
182
+ perfTracker.logStats();
183
+ } catch (error: any) {
184
+ Logger.error(`数据库同步失败`, error);
185
+ process.exit(1);
186
+ } finally {
187
+ if (sql) {
188
+ try {
189
+ await sql.close();
190
+ } catch (error: any) {
191
+ Logger.warn('关闭数据库连接时出错:', error.message);
192
+ }
193
+ }
194
+ }
195
+ };
196
+
197
+ // 如果直接运行此脚本(Bun 支持 import.meta.main)
198
+ if (import.meta.main) {
199
+ SyncDb().catch((error) => {
200
+ Logger.error('数据库同步失败', error);
201
+ process.exit(1);
202
+ });
203
+ }
@@ -0,0 +1,199 @@
1
+ /**
2
+ * syncDb 表结构查询模块
3
+ *
4
+ * 包含:
5
+ * - 判断表是否存在
6
+ * - 获取表的列信息
7
+ * - 获取表的索引信息
8
+ */
9
+
10
+ import { Env } from '../../config/env.js';
11
+ import { IS_MYSQL, IS_PG, IS_SQLITE } from './constants.js';
12
+ import type { ColumnInfo, IndexInfo } from './types.js';
13
+ import type { SQL } from 'bun';
14
+
15
+ // 重新导出类型供其他模块使用
16
+ export type { ColumnInfo, IndexInfo };
17
+
18
+ /**
19
+ * 判断表是否存在(返回布尔值)
20
+ *
21
+ * @param sql - SQL 客户端实例
22
+ * @param tableName - 表名
23
+ * @returns 表是否存在
24
+ */
25
+ export async function tableExists(sql: SQL, tableName: string): Promise<boolean> {
26
+ if (!sql) throw new Error('SQL 客户端未初始化');
27
+
28
+ try {
29
+ if (IS_MYSQL) {
30
+ const res = await sql`SELECT COUNT(*) AS count FROM information_schema.TABLES WHERE TABLE_SCHEMA = ${Env.DB_NAME} AND TABLE_NAME = ${tableName}`;
31
+ return (res[0]?.count || 0) > 0;
32
+ }
33
+
34
+ if (IS_PG) {
35
+ const res = await sql`SELECT COUNT(*)::int AS count FROM information_schema.tables WHERE table_schema = 'public' AND table_name = ${tableName}`;
36
+ return (res[0]?.count || 0) > 0;
37
+ }
38
+
39
+ if (IS_SQLITE) {
40
+ const res = await sql`SELECT name FROM sqlite_master WHERE type='table' AND name = ${tableName}`;
41
+ return res.length > 0;
42
+ }
43
+
44
+ return false;
45
+ } catch (error: any) {
46
+ throw new Error(`查询表是否存在失败 [${tableName}]: ${error.message}`);
47
+ }
48
+ }
49
+
50
+ /**
51
+ * 获取表的现有列信息(按方言)
52
+ *
53
+ * 查询数据库元数据,获取表的所有列信息,包括:
54
+ * - 列名
55
+ * - 数据类型
56
+ * - 字符最大长度
57
+ * - 是否可为空
58
+ * - 默认值
59
+ * - 列注释(MySQL/PG)
60
+ *
61
+ * @param sql - SQL 客户端实例
62
+ * @param tableName - 表名
63
+ * @returns 列信息对象,键为列名,值为列详情
64
+ */
65
+ export async function getTableColumns(sql: SQL, tableName: string): Promise<{ [key: string]: ColumnInfo }> {
66
+ const columns: { [key: string]: ColumnInfo } = {};
67
+
68
+ try {
69
+ if (IS_MYSQL) {
70
+ const result = await sql`
71
+ SELECT COLUMN_NAME, DATA_TYPE, CHARACTER_MAXIMUM_LENGTH, IS_NULLABLE, COLUMN_DEFAULT, COLUMN_COMMENT, COLUMN_TYPE
72
+ FROM information_schema.COLUMNS
73
+ WHERE TABLE_SCHEMA = ${Env.DB_NAME} AND TABLE_NAME = ${tableName}
74
+ ORDER BY ORDINAL_POSITION
75
+ `;
76
+ for (const row of result) {
77
+ // MySQL 的 COLUMN_DEFAULT 已经是解析后的实际值,无需处理:
78
+ // - 空字符串 DEFAULT '': 返回 '' (空字符串)
79
+ // - 字符串 DEFAULT 'admin': 返回 admin (无引号)
80
+ // - 单引号 DEFAULT '''': 返回 ' (单引号字符)
81
+ // - 数字 DEFAULT 0: 返回 0
82
+ // - NULL: 返回 null
83
+ const defaultValue = row.COLUMN_DEFAULT;
84
+
85
+ columns[row.COLUMN_NAME] = {
86
+ type: row.DATA_TYPE,
87
+ columnType: row.COLUMN_TYPE,
88
+ length: row.CHARACTER_MAXIMUM_LENGTH,
89
+ nullable: row.IS_NULLABLE === 'YES',
90
+ defaultValue: defaultValue,
91
+ comment: row.COLUMN_COMMENT
92
+ };
93
+ }
94
+ } else if (IS_PG) {
95
+ const result = await sql`
96
+ SELECT column_name, data_type, character_maximum_length, is_nullable, column_default
97
+ FROM information_schema.columns
98
+ WHERE table_schema = 'public' AND table_name = ${tableName}
99
+ ORDER BY ordinal_position
100
+ `;
101
+ // 获取列注释
102
+ const comments = await sql`
103
+ SELECT a.attname AS column_name, col_description(c.oid, a.attnum) AS column_comment
104
+ FROM pg_class c
105
+ JOIN pg_attribute a ON a.attrelid = c.oid
106
+ JOIN pg_namespace n ON n.oid = c.relnamespace
107
+ WHERE c.relkind = 'r' AND n.nspname = 'public' AND c.relname = ${tableName} AND a.attnum > 0
108
+ `;
109
+ const commentMap: { [key: string]: string } = {};
110
+ for (const r of comments) commentMap[r.column_name] = r.column_comment;
111
+
112
+ for (const row of result) {
113
+ columns[row.column_name] = {
114
+ type: row.data_type,
115
+ columnType: row.data_type,
116
+ length: row.character_maximum_length,
117
+ nullable: String(row.is_nullable).toUpperCase() === 'YES',
118
+ defaultValue: row.column_default,
119
+ comment: commentMap[row.column_name] ?? null
120
+ };
121
+ }
122
+ } else if (IS_SQLITE) {
123
+ const result = await sql.unsafe(`PRAGMA table_info(${tableName})`);
124
+ for (const row of result) {
125
+ let baseType = String(row.type || '').toUpperCase();
126
+ let length = null;
127
+ const m = /^(\w+)\s*\((\d+)\)/.exec(baseType);
128
+ if (m) {
129
+ baseType = m[1];
130
+ length = Number(m[2]);
131
+ }
132
+ columns[row.name] = {
133
+ type: baseType.toLowerCase(),
134
+ columnType: baseType.toLowerCase(),
135
+ length: length,
136
+ nullable: row.notnull === 0,
137
+ defaultValue: row.dflt_value,
138
+ comment: null
139
+ };
140
+ }
141
+ }
142
+
143
+ return columns;
144
+ } catch (error: any) {
145
+ throw new Error(`获取表列信息失败 [${tableName}]: ${error.message}`);
146
+ }
147
+ }
148
+
149
+ /**
150
+ * 获取表的现有索引信息(单列索引)
151
+ *
152
+ * @param sql - SQL 客户端实例
153
+ * @param tableName - 表名
154
+ * @returns 索引信息对象,键为索引名,值为列名数组
155
+ */
156
+ export async function getTableIndexes(sql: SQL, tableName: string): Promise<IndexInfo> {
157
+ const indexes: IndexInfo = {};
158
+
159
+ try {
160
+ if (IS_MYSQL) {
161
+ const result = await sql`
162
+ SELECT INDEX_NAME, COLUMN_NAME
163
+ FROM information_schema.STATISTICS
164
+ WHERE TABLE_SCHEMA = ${Env.DB_NAME}
165
+ AND TABLE_NAME = ${tableName}
166
+ AND INDEX_NAME != 'PRIMARY'
167
+ ORDER BY INDEX_NAME
168
+ `;
169
+ for (const row of result) {
170
+ if (!indexes[row.INDEX_NAME]) indexes[row.INDEX_NAME] = [];
171
+ indexes[row.INDEX_NAME].push(row.COLUMN_NAME);
172
+ }
173
+ } else if (IS_PG) {
174
+ const result = await sql`
175
+ SELECT indexname, indexdef
176
+ FROM pg_indexes
177
+ WHERE schemaname = 'public' AND tablename = ${tableName}
178
+ `;
179
+ for (const row of result) {
180
+ const m = /\(([^)]+)\)/.exec(row.indexdef);
181
+ if (m) {
182
+ const col = m[1].replace(/\"/g, '').replace(/"/g, '').trim();
183
+ indexes[row.indexname] = [col];
184
+ }
185
+ }
186
+ } else if (IS_SQLITE) {
187
+ const list = await sql.unsafe(`PRAGMA index_list(${tableName})`);
188
+ for (const idx of list) {
189
+ const info = await sql.unsafe(`PRAGMA index_info(${idx.name})`);
190
+ const cols = info.map((r) => r.name);
191
+ if (cols.length === 1) indexes[idx.name] = cols;
192
+ }
193
+ }
194
+
195
+ return indexes;
196
+ } catch (error: any) {
197
+ throw new Error(`获取表索引信息失败 [${tableName}]: ${error.message}`);
198
+ }
199
+ }
@@ -0,0 +1,50 @@
1
+ /**
2
+ * syncDb SQLite 特殊处理模块
3
+ *
4
+ * 包含:
5
+ * - SQLite 重建表迁移(处理列修改等不支持的操作)
6
+ */
7
+
8
+ import { Logger } from '../../utils/logger.js';
9
+ import { createTable } from './tableCreate.js';
10
+ import type { SQL } from 'bun';
11
+
12
+ // 是否为计划模式(从环境变量读取)
13
+ const IS_PLAN = process.argv.includes('--plan');
14
+
15
+ /**
16
+ * SQLite 重建表迁移(简化版)
17
+ *
18
+ * SQLite 不支持修改列类型等操作,需要通过重建表实现:
19
+ * 1. 创建临时表(新结构)
20
+ * 2. 拷贝数据(仅公共列)
21
+ * 3. 删除旧表
22
+ * 4. 重命名临时表
23
+ *
24
+ * 注意:仅处理新增/修改字段,不处理复杂约束与复合索引
25
+ *
26
+ * @param sql - SQL 客户端实例
27
+ * @param tableName - 表名
28
+ * @param fields - 字段定义对象
29
+ */
30
+ export async function rebuildSqliteTable(sql: SQL, tableName: string, fields: Record<string, string>): Promise<void> {
31
+ // 1. 读取现有列顺序
32
+ const info = await sql.unsafe(`PRAGMA table_info(${tableName})`);
33
+ const existingCols = info.map((r) => r.name);
34
+ const targetCols = ['id', 'created_at', 'updated_at', 'deleted_at', 'state', ...Object.keys(fields)];
35
+ const tmpTable = `${tableName}__tmp__${Date.now()}`;
36
+
37
+ // 2. 创建新表(使用当前定义)
38
+ await createTable(sql, tmpTable, fields);
39
+
40
+ // 3. 拷贝数据(按交集列)
41
+ const commonCols = targetCols.filter((c) => existingCols.includes(c));
42
+ if (commonCols.length > 0) {
43
+ const colsSql = commonCols.map((c) => `"${c}"`).join(', ');
44
+ await sql.unsafe(`INSERT INTO "${tmpTable}" (${colsSql}) SELECT ${colsSql} FROM "${tableName}"`);
45
+ }
46
+
47
+ // 4. 删除旧表并重命名
48
+ await sql.unsafe(`DROP TABLE "${tableName}"`);
49
+ await sql.unsafe(`ALTER TABLE "${tmpTable}" RENAME TO "${tableName}"`);
50
+ }
@@ -0,0 +1,106 @@
1
+ /**
2
+ * syncDb 状态管理模块
3
+ *
4
+ * 包含:
5
+ * - 性能统计(阶段耗时、总体计时)
6
+ * - 进度信息记录
7
+ */
8
+
9
+ import { Logger } from '../../utils/logger.js';
10
+
11
+ /**
12
+ * 阶段统计信息
13
+ */
14
+ export interface PhaseStats {
15
+ startTime: number;
16
+ endTime?: number;
17
+ duration?: number;
18
+ }
19
+
20
+ /**
21
+ * 性能统计器
22
+ */
23
+ export class PerformanceTracker {
24
+ private phases = new Map<string, PhaseStats>();
25
+ private globalStart: number;
26
+
27
+ constructor() {
28
+ this.globalStart = Date.now();
29
+ }
30
+
31
+ /**
32
+ * 标记阶段开始
33
+ */
34
+ markPhase(phase: string): void {
35
+ this.phases.set(phase, { startTime: Date.now() });
36
+ }
37
+
38
+ /**
39
+ * 获取阶段耗时
40
+ */
41
+ getPhaseTime(phase: string): string {
42
+ const stats = this.phases.get(phase);
43
+ if (!stats) return '0ms';
44
+
45
+ const duration = stats.duration || Date.now() - stats.startTime;
46
+ return duration > 1000 ? `${(duration / 1000).toFixed(2)}s` : `${duration}ms`;
47
+ }
48
+
49
+ /**
50
+ * 完成阶段并记录耗时
51
+ */
52
+ finishPhase(phase: string): void {
53
+ const stats = this.phases.get(phase);
54
+ if (stats && !stats.endTime) {
55
+ stats.endTime = Date.now();
56
+ stats.duration = stats.endTime - stats.startTime;
57
+ }
58
+ }
59
+
60
+ /**
61
+ * 获取总耗时
62
+ */
63
+ getTotalTime(): string {
64
+ const duration = Date.now() - this.globalStart;
65
+ return duration > 1000 ? `${(duration / 1000).toFixed(2)}s` : `${duration}ms`;
66
+ }
67
+
68
+ /**
69
+ * 输出所有阶段统计
70
+ */
71
+ logStats(): void {
72
+ Logger.info('\n⏱️ 性能统计:');
73
+ for (const [phase, stats] of this.phases) {
74
+ const duration = stats.duration || Date.now() - stats.startTime;
75
+ const timeStr = duration > 1000 ? `${(duration / 1000).toFixed(2)}s` : `${duration}ms`;
76
+ Logger.info(` ${phase}: ${timeStr}`);
77
+ }
78
+ Logger.info(` 总耗时: ${this.getTotalTime()}`);
79
+ }
80
+ }
81
+
82
+ /**
83
+ * 进度记录器
84
+ */
85
+ export class ProgressLogger {
86
+ /**
87
+ * 记录表处理进度
88
+ */
89
+ logTableProgress(current: number, total: number, tableName: string): void {
90
+ Logger.info(`\n[${current}/${total}] 处理表: ${tableName}`);
91
+ }
92
+
93
+ /**
94
+ * 记录字段变更进度
95
+ */
96
+ logFieldChangeProgress(current: number, total: number, fieldName: string, changeType: string): void {
97
+ Logger.info(` [${current}/${total}] 修改字段 ${fieldName} (${changeType})`);
98
+ }
99
+
100
+ /**
101
+ * 记录索引创建进度
102
+ */
103
+ logIndexProgress(current: number, total: number, indexName: string): void {
104
+ Logger.info(` [${current}/${total}] 创建索引: ${indexName}`);
105
+ }
106
+ }