befly 3.21.1 → 3.22.0

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.
@@ -8,52 +8,30 @@ import { importDefault } from "../../utils/importDefault.js";
8
8
  import { isNonEmptyString, isPlainObject } from "../../utils/is.js";
9
9
  import { camelCase } from "../../utils/util.js";
10
10
  import { buildSyncDbDiff, groupSyncDbColumns } from "./diff.js";
11
- import { querySyncDbColumns } from "./query.js";
12
11
  import { printSyncDbProcessLog } from "./report.js";
13
12
 
14
- async function loadSyncDbTableDefs(tablesDir) {
15
- const tableMap = new Map();
16
-
17
- const glob = new Bun.Glob("*.json");
18
- const files = await glob.scan({
19
- cwd: tablesDir,
20
- onlyFiles: true,
21
- absolute: true,
22
- followSymlinks: true
23
- });
24
-
25
- for await (const filePath of files) {
26
- const fileName = basename(filePath, ".json");
27
- const tableKey = String(camelCase(fileName)).toLowerCase();
28
- if (fileName.startsWith("_")) {
29
- continue;
30
- }
31
-
32
- const loaded = await importDefault(filePath, {});
33
- if (!isPlainObject(loaded)) {
34
- Logger.warn("tables 定义文件不是对象,已按空对象处理", {
35
- filePath: filePath
36
- });
37
- tableMap.set(tableKey, {
38
- filePath: filePath,
39
- tableDef: {}
40
- });
41
- continue;
42
- }
43
-
44
- tableMap.set(tableKey, {
45
- filePath: filePath,
46
- tableDef: loaded
47
- });
48
- }
49
-
50
- return tableMap;
51
- }
13
+ const SYNC_DB_COLUMNS_SQL = `
14
+ SELECT
15
+ c.TABLE_NAME AS tableName,
16
+ t.TABLE_COMMENT AS tableComment,
17
+ c.COLUMN_NAME AS columnName,
18
+ c.DATA_TYPE AS dataType,
19
+ c.COLUMN_TYPE AS columnType,
20
+ c.CHARACTER_MAXIMUM_LENGTH AS charLength,
21
+ c.COLUMN_COMMENT AS columnComment,
22
+ c.ORDINAL_POSITION AS ordinalPosition
23
+ FROM information_schema.COLUMNS AS c
24
+ LEFT JOIN information_schema.TABLES AS t
25
+ ON t.TABLE_SCHEMA = c.TABLE_SCHEMA
26
+ AND t.TABLE_NAME = c.TABLE_NAME
27
+ WHERE c.TABLE_SCHEMA = DATABASE()
28
+ ORDER BY c.TABLE_NAME ASC, c.ORDINAL_POSITION ASC
29
+ `;
52
30
 
53
31
  export async function prepareSyncDbBaseContext(mysqlConfig) {
54
32
  const tablesDir = resolve(join(process.cwd(), "tables"));
55
33
  await mkdir(tablesDir, { recursive: true });
56
- printSyncDbProcessLog(`tables目录=${tablesDir}`);
34
+ printSyncDbProcessLog(`表定义目录=${tablesDir}`);
57
35
 
58
36
  if (!isPlainObject(mysqlConfig)) {
59
37
  throw new Error("syncDb 缺少 mysql 配置", {
@@ -64,8 +42,8 @@ export async function prepareSyncDbBaseContext(mysqlConfig) {
64
42
  });
65
43
  }
66
44
 
67
- printSyncDbProcessLog(`beflyMode=${mysqlConfig.beflyMode === "manual" ? "manual" : "auto"}`);
68
- printSyncDbProcessLog(`连接 MySQL,database=${mysqlConfig.database}`);
45
+ printSyncDbProcessLog(`同步模式=${mysqlConfig.beflyMode === "manual" ? "手动" : "自动"}`);
46
+ printSyncDbProcessLog(`连接 MySQL,数据库名=${mysqlConfig.database}`);
69
47
  await Connect.connectMysql(mysqlConfig);
70
48
  const mysql = new DbHelper({
71
49
  redis: null,
@@ -75,8 +53,10 @@ export async function prepareSyncDbBaseContext(mysqlConfig) {
75
53
  });
76
54
 
77
55
  printSyncDbProcessLog("开始读取数据库字段信息");
78
- const dbColumns = await querySyncDbColumns(mysql);
79
- printSyncDbProcessLog(`数据库字段读取完成,columnCount=${dbColumns.length}`);
56
+ const queryRes = await mysql.execute(SYNC_DB_COLUMNS_SQL);
57
+ const dbColumns = Array.isArray(queryRes.data) ? queryRes.data : [];
58
+ printSyncDbProcessLog(`数据库字段读取完成,字段数量=${dbColumns.length}`);
59
+
80
60
  const skippedBeflyTables = new Set();
81
61
  for (const row of dbColumns) {
82
62
  const tableName = String(row?.tableName || "");
@@ -93,10 +73,45 @@ export async function prepareSyncDbBaseContext(mysqlConfig) {
93
73
  }
94
74
 
95
75
  const groupedDbColumns = groupSyncDbColumns(dbColumns);
96
- const existingTableMap = await loadSyncDbTableDefs(tablesDir);
76
+ const existingTableMap = new Map();
77
+ const glob = new Bun.Glob("*.json");
78
+ const files = await glob.scan({
79
+ cwd: tablesDir,
80
+ onlyFiles: true,
81
+ absolute: true,
82
+ followSymlinks: true
83
+ });
84
+
85
+ for await (const filePath of files) {
86
+ const tableFileName = basename(filePath, ".json");
87
+ if (tableFileName.startsWith("_")) {
88
+ continue;
89
+ }
90
+
91
+ const tableKey = String(camelCase(tableFileName)).toLowerCase();
92
+ const loaded = await importDefault(filePath, {});
93
+ if (!isPlainObject(loaded)) {
94
+ Logger.warn("tables 定义文件不是对象,已按空对象处理", {
95
+ filePath: filePath
96
+ });
97
+ existingTableMap.set(tableKey, {
98
+ tableFileName: tableFileName,
99
+ filePath: filePath,
100
+ tableDef: {}
101
+ });
102
+ continue;
103
+ }
104
+
105
+ existingTableMap.set(tableKey, {
106
+ tableFileName: tableFileName,
107
+ filePath: filePath,
108
+ tableDef: loaded
109
+ });
110
+ }
111
+
97
112
  const diff = buildSyncDbDiff(groupedDbColumns, existingTableMap, mysqlConfig.beflyMode);
98
- printSyncDbProcessLog(`tables定义文件数量=${existingTableMap.size}`);
99
- printSyncDbProcessLog(`差异扫描完成,missingTableCount=${diff.missingTables.length}missingFieldCount=${Object.values(diff.missingFieldsByTable).reduce((sum, item) => sum + item.fields.length, 0)}`);
113
+ printSyncDbProcessLog(`表定义文件数量=${existingTableMap.size}`);
114
+ printSyncDbProcessLog(`差异扫描完成,缺少表数量=${diff.missingTables.length},缺少字段数量=${diff.missingFieldCount},多余表数量=${diff.extraTables.length},多余字段数量=${diff.extraFieldCount}`);
100
115
 
101
116
  return {
102
117
  diff: diff,
@@ -1,7 +1,7 @@
1
1
  import { join } from "node:path";
2
2
 
3
3
  import { isNonEmptyString, isPlainObject } from "../../utils/is.js";
4
- import { camelCase } from "../../utils/util.js";
4
+ import { camelCase, snakeCase } from "../../utils/util.js";
5
5
  import { printSyncDbProcessLog } from "./report.js";
6
6
  import { toSyncDbFieldDef } from "./transform.js";
7
7
 
@@ -16,6 +16,25 @@ function shouldSkipSyncDbColumn(columnMeta, beflyMode) {
16
16
  return SYNC_DB_MANAGED_FIELD_NAMES.has(columnName);
17
17
  }
18
18
 
19
+ function shouldSkipSyncDbFieldName(fieldName, beflyMode) {
20
+ if (beflyMode !== "manual") {
21
+ return false;
22
+ }
23
+
24
+ return SYNC_DB_MANAGED_FIELD_NAMES.has(snakeCase(String(fieldName || "")));
25
+ }
26
+
27
+ function sortSyncDbObjectKeys(source) {
28
+ const entries = Object.entries(source).sort(([left], [right]) => left.localeCompare(right));
29
+ const output = {};
30
+
31
+ for (const [key, value] of entries) {
32
+ output[key] = value;
33
+ }
34
+
35
+ return output;
36
+ }
37
+
19
38
  export function groupSyncDbColumns(rows) {
20
39
  const grouped = new Map();
21
40
 
@@ -50,11 +69,18 @@ export function groupSyncDbColumns(rows) {
50
69
 
51
70
  export function buildSyncDbDiff(groupedDbColumns, existingTableMap, beflyMode = "auto") {
52
71
  const missingTables = [];
72
+ const extraTables = [];
53
73
  const missingFieldsByTable = {};
74
+ const extraFieldsByTable = {};
75
+ const groupedDbTableKeySet = new Set();
76
+ let missingFieldCount = 0;
77
+ let extraFieldCount = 0;
54
78
 
55
79
  for (const [tableName, columns] of groupedDbColumns.entries()) {
56
80
  const tableFileName = camelCase(tableName);
57
- const existing = existingTableMap.get(String(tableFileName).toLowerCase());
81
+ const tableKey = String(tableFileName).toLowerCase();
82
+ groupedDbTableKeySet.add(tableKey);
83
+ const existing = existingTableMap.get(tableKey);
58
84
  const filteredColumns = columns.filter((columnMeta) => !shouldSkipSyncDbColumn(columnMeta, beflyMode));
59
85
 
60
86
  if (!existing) {
@@ -72,8 +98,10 @@ export function buildSyncDbDiff(groupedDbColumns, existingTableMap, beflyMode =
72
98
 
73
99
  const existingFields = isPlainObject(existing.tableDef) ? existing.tableDef : {};
74
100
  const missingFields = [];
101
+ const dbFieldNameSet = new Set();
75
102
  for (const columnMeta of filteredColumns) {
76
103
  const fieldInfo = toSyncDbFieldDef(columnMeta);
104
+ dbFieldNameSet.add(fieldInfo.fieldName);
77
105
  if (Object.hasOwn(existingFields, fieldInfo.fieldName)) {
78
106
  continue;
79
107
  }
@@ -91,18 +119,61 @@ export function buildSyncDbDiff(groupedDbColumns, existingTableMap, beflyMode =
91
119
  }
92
120
 
93
121
  if (missingFields.length > 0) {
122
+ missingFieldCount = missingFieldCount + missingFields.length;
94
123
  missingFieldsByTable[tableFileName] = {
95
124
  tableName: tableName,
96
- tableFileName: tableFileName,
125
+ tableFileName: existing.tableFileName || tableFileName,
97
126
  filePath: existing.filePath,
98
127
  fields: missingFields
99
128
  };
100
129
  }
130
+
131
+ const extraFields = [];
132
+ for (const fieldName of Object.keys(existingFields)) {
133
+ if (shouldSkipSyncDbFieldName(fieldName, beflyMode)) {
134
+ continue;
135
+ }
136
+ if (dbFieldNameSet.has(fieldName)) {
137
+ continue;
138
+ }
139
+
140
+ extraFields.push({
141
+ tableName: tableName,
142
+ tableFileName: tableFileName,
143
+ fieldName: fieldName,
144
+ fieldDef: existingFields[fieldName]
145
+ });
146
+ }
147
+
148
+ if (extraFields.length > 0) {
149
+ extraFieldCount = extraFieldCount + extraFields.length;
150
+ extraFieldsByTable[tableFileName] = {
151
+ tableName: tableName,
152
+ tableFileName: existing.tableFileName || tableFileName,
153
+ filePath: existing.filePath,
154
+ fields: extraFields
155
+ };
156
+ }
157
+ }
158
+
159
+ for (const [tableKey, existing] of existingTableMap.entries()) {
160
+ if (groupedDbTableKeySet.has(tableKey)) {
161
+ continue;
162
+ }
163
+
164
+ extraTables.push({
165
+ tableFileName: existing.tableFileName || camelCase(tableKey),
166
+ filePath: existing.filePath
167
+ });
101
168
  }
102
169
 
103
170
  return {
104
171
  missingTables: missingTables,
105
- missingFieldsByTable: missingFieldsByTable
172
+ extraTables: extraTables,
173
+ missingFieldsByTable: missingFieldsByTable,
174
+ extraFieldsByTable: extraFieldsByTable,
175
+ missingFieldCount: missingFieldCount,
176
+ extraFieldCount: extraFieldCount
106
177
  };
107
178
  }
108
179
 
@@ -118,7 +189,7 @@ export async function applySyncDbDiff(diff, tablesDir, existingTableMap) {
118
189
  }
119
190
 
120
191
  const filePath = join(tablesDir, `${missingTable.tableFileName}.json`);
121
- await Bun.write(filePath, `${JSON.stringify(tableDef, null, 4)}\n`);
192
+ await Bun.write(filePath, `${JSON.stringify(sortSyncDbObjectKeys(tableDef), null, 4)}\n`);
122
193
  createdTableFileCount = createdTableFileCount + 1;
123
194
  printSyncDbProcessLog(`已生成表映射 ${filePath}`);
124
195
  }
@@ -133,8 +204,8 @@ export async function applySyncDbDiff(diff, tablesDir, existingTableMap) {
133
204
  appendedFieldCount = appendedFieldCount + 1;
134
205
  }
135
206
 
136
- await Bun.write(targetFilePath, `${JSON.stringify(currentTableDef, null, 4)}\n`);
137
- printSyncDbProcessLog(`已补充字段映射 ${targetFilePath},fieldCount=${tableInfo.fields.length}`);
207
+ await Bun.write(targetFilePath, `${JSON.stringify(sortSyncDbObjectKeys(currentTableDef), null, 4)}\n`);
208
+ printSyncDbProcessLog(`已补充字段映射 ${targetFilePath},补充字段数量=${tableInfo.fields.length}`);
138
209
  }
139
210
 
140
211
  return {
@@ -142,11 +213,3 @@ export async function applySyncDbDiff(diff, tablesDir, existingTableMap) {
142
213
  appendedFieldCount: appendedFieldCount
143
214
  };
144
215
  }
145
-
146
- export function countSyncDbMissingFields(diff) {
147
- let missingFieldCount = 0;
148
- for (const item of Object.values(diff.missingFieldsByTable)) {
149
- missingFieldCount = missingFieldCount + item.fields.length;
150
- }
151
- return missingFieldCount;
152
- }
@@ -1,55 +1,23 @@
1
- import { mkdir } from "node:fs/promises";
2
- import { dirname, join, resolve } from "node:path";
1
+ import { join, resolve } from "node:path";
3
2
 
4
3
  import { Connect } from "../../lib/connect.js";
5
4
  import { Logger } from "../../lib/logger.js";
6
- import { applySyncDbDiff, countSyncDbMissingFields } from "./diff.js";
5
+ import { applySyncDbDiff } from "./diff.js";
7
6
  import { prepareSyncDbBaseContext } from "./context.js";
8
- import { printSyncDbDiffSummary, printSyncDbProcessLog, writeSyncDbCheckReport } from "./report.js";
9
-
10
- export async function syncDbCheck(mysqlConfig) {
11
- try {
12
- printSyncDbProcessLog("开始执行 check");
13
- const reportPath = resolve(join(process.cwd(), "db.check.md"));
14
- await mkdir(dirname(reportPath), { recursive: true });
15
- await Bun.write(reportPath, "");
16
-
17
- const { diff, groupedDbColumns, existingTableMap } = await prepareSyncDbBaseContext(mysqlConfig);
18
- const missingFieldCount = countSyncDbMissingFields(diff);
19
- const writtenReportPath = await writeSyncDbCheckReport(reportPath, diff, groupedDbColumns, existingTableMap);
20
- printSyncDbDiffSummary("check", diff);
21
- printSyncDbProcessLog(`check完成,missingTableCount=${diff.missingTables.length},missingFieldCount=${missingFieldCount}`);
22
-
23
- return {
24
- mode: "check",
25
- createdTableFileCount: 0,
26
- appendedFieldCount: 0,
27
- missingTableCount: diff.missingTables.length,
28
- missingFieldCount: missingFieldCount,
29
- hasDiff: diff.missingTables.length > 0 || missingFieldCount > 0,
30
- reportPath: writtenReportPath
31
- };
32
- } catch (error) {
33
- throw new Error("执行表定义同步失败", {
34
- cause: error,
35
- code: "runtime",
36
- subsystem: "scripts",
37
- operation: "syncDb"
38
- });
39
- } finally {
40
- await Connect.disconnect();
41
- await Logger.flush();
42
- }
43
- }
7
+ import { printSyncDbDiffSummary, printSyncDbProcessLog, writeSyncDbReport } from "./report.js";
44
8
 
45
9
  export async function syncDbApply(mysqlConfig) {
10
+ const reportPath = resolve(join(process.cwd(), "db.sync.md"));
46
11
  try {
47
- printSyncDbProcessLog("开始执行 apply");
48
- const { diff, tablesDir, existingTableMap } = await prepareSyncDbBaseContext(mysqlConfig);
49
- printSyncDbDiffSummary("check", diff);
12
+ printSyncDbProcessLog("开始执行应用");
13
+ const { diff, groupedDbColumns, tablesDir, existingTableMap } = await prepareSyncDbBaseContext(mysqlConfig);
14
+ const extraFieldCount = diff.extraFieldCount;
15
+ const writtenReportPath = await writeSyncDbReport(reportPath, diff, groupedDbColumns, existingTableMap);
16
+ printSyncDbDiffSummary(diff);
50
17
  const applyResult = await applySyncDbDiff(diff, tablesDir, existingTableMap);
51
- printSyncDbDiffSummary("apply", diff);
52
- printSyncDbProcessLog(`apply完成,createdTableFileCount=${applyResult.createdTableFileCount},appendedFieldCount=${applyResult.appendedFieldCount}`);
18
+ const hasDiff = diff.missingTables.length > 0 || applyResult.appendedFieldCount > 0 || diff.extraTables.length > 0 || extraFieldCount > 0;
19
+
20
+ printSyncDbProcessLog(`应用完成,缺少表数量=${diff.missingTables.length},缺少字段数量=${applyResult.appendedFieldCount},多余表数量=${diff.extraTables.length},多余字段数量=${extraFieldCount},新建表定义文件数量=${applyResult.createdTableFileCount},补充字段数量=${applyResult.appendedFieldCount}`);
53
21
 
54
22
  return {
55
23
  mode: "apply",
@@ -57,8 +25,10 @@ export async function syncDbApply(mysqlConfig) {
57
25
  appendedFieldCount: applyResult.appendedFieldCount,
58
26
  missingTableCount: diff.missingTables.length,
59
27
  missingFieldCount: applyResult.appendedFieldCount,
60
- hasDiff: diff.missingTables.length > 0 || applyResult.appendedFieldCount > 0,
61
- reportPath: ""
28
+ extraTableCount: diff.extraTables.length,
29
+ extraFieldCount: extraFieldCount,
30
+ hasDiff: hasDiff,
31
+ reportPath: writtenReportPath
62
32
  };
63
33
  } catch (error) {
64
34
  throw new Error("执行表定义同步失败", {
@@ -4,25 +4,44 @@ import { dirname } from "node:path";
4
4
  import { Logger } from "../../lib/logger.js";
5
5
  import { isNonEmptyString } from "../../utils/is.js";
6
6
  import { camelCase } from "../../utils/util.js";
7
- import { countSyncDbMissingFields } from "./diff.js";
8
7
  import { toSyncDbFieldDef } from "./transform.js";
9
8
 
10
9
  export function printSyncDbProcessLog(message) {
11
10
  process.stdout.write(`[syncDb] ${message}\n`);
12
11
  }
13
12
 
14
- export function printSyncDbDiffSummary(mode, diff) {
13
+ export function printSyncDbDiffSummary(diff) {
15
14
  const tableNames = diff.missingTables.map((item) => item.tableName);
15
+ const extraTableFileNames = diff.extraTables.map((item) => item.tableFileName);
16
16
  const missingFieldGroups = Object.values(diff.missingFieldsByTable);
17
- const missingFieldCount = countSyncDbMissingFields(diff);
17
+ const missingFieldCount = resolveSyncDbFieldCount(diff.missingFieldsByTable, diff.missingFieldCount);
18
+ const extraFieldGroups = Object.values(diff.extraFieldsByTable);
19
+ const extraFieldCount = resolveSyncDbFieldCount(diff.extraFieldsByTable, diff.extraFieldCount);
18
20
 
19
- Logger.info("DB 与项目根目录 tables 差异检查完成", {
20
- mode: mode,
21
+ Logger.info("数据库与项目根目录表定义差异检查完成", {
21
22
  missingTableCount: diff.missingTables.length,
22
23
  missingFieldCount: missingFieldCount,
23
- missingTables: tableNames
24
+ extraTableCount: diff.extraTables.length,
25
+ extraFieldCount: extraFieldCount,
26
+ missingTables: tableNames,
27
+ extraTables: extraTableFileNames
24
28
  });
25
29
 
30
+ for (const item of diff.missingTables) {
31
+ Logger.info("发现缺失表", {
32
+ tableName: item.tableName,
33
+ tableFileName: item.tableFileName,
34
+ columnNames: item.columns.map((column) => column.columnName)
35
+ });
36
+ }
37
+
38
+ for (const item of diff.extraTables) {
39
+ Logger.info("发现多余表", {
40
+ tableFileName: item.tableFileName,
41
+ filePath: item.filePath
42
+ });
43
+ }
44
+
26
45
  for (const item of missingFieldGroups) {
27
46
  Logger.info("发现缺失字段", {
28
47
  tableName: item.tableName,
@@ -30,6 +49,14 @@ export function printSyncDbDiffSummary(mode, diff) {
30
49
  fieldNames: item.fields.map((field) => field.fieldName)
31
50
  });
32
51
  }
52
+
53
+ for (const item of extraFieldGroups) {
54
+ Logger.info("发现多余字段", {
55
+ tableName: item.tableName,
56
+ tableFileName: item.tableFileName,
57
+ fieldNames: item.fields.map((field) => field.fieldName)
58
+ });
59
+ }
33
60
  }
34
61
 
35
62
  function escapeSyncDbMdCell(value) {
@@ -47,144 +74,116 @@ function formatSyncDbValue(value) {
47
74
  return String(value);
48
75
  }
49
76
 
50
- function buildSyncDbCheckRows(diff) {
51
- const rows = [];
52
-
53
- for (const missingTable of diff.missingTables) {
54
- for (const columnMeta of missingTable.columns) {
55
- const fieldInfo = toSyncDbFieldDef(columnMeta);
56
- rows.push({
57
- diffType: "缺少表",
58
- tableName: missingTable.tableName,
59
- tableFileName: `${missingTable.tableFileName}.json`,
60
- dbColumnName: columnMeta.columnName,
61
- fieldName: fieldInfo.fieldName,
62
- dbDataType: columnMeta.dataType,
63
- dbColumnType: columnMeta.columnType,
64
- dbCharLength: columnMeta.charLength,
65
- fieldInput: fieldInfo.fieldDef.input,
66
- fieldMax: fieldInfo.fieldDef.max,
67
- fieldDisplayName: fieldInfo.fieldDef.name
68
- });
69
- }
77
+ function resolveSyncDbFieldCount(groups, fieldCount) {
78
+ if (Number.isInteger(fieldCount) && fieldCount >= 0) {
79
+ return fieldCount;
70
80
  }
71
81
 
72
- for (const tableInfo of Object.values(diff.missingFieldsByTable)) {
73
- for (const fieldItem of tableInfo.fields) {
74
- rows.push({
75
- diffType: "缺少字段",
76
- tableName: tableInfo.tableName,
77
- tableFileName: `${tableInfo.tableFileName}.json`,
78
- dbColumnName: fieldItem.dbColumnName,
79
- fieldName: fieldItem.fieldName,
80
- dbDataType: fieldItem.dbDataType,
81
- dbColumnType: fieldItem.dbColumnType,
82
- dbCharLength: fieldItem.dbCharLength,
83
- fieldInput: fieldItem.fieldDef.input,
84
- fieldMax: fieldItem.fieldDef.max,
85
- fieldDisplayName: fieldItem.fieldDef.name
86
- });
87
- }
82
+ let resolvedFieldCount = 0;
83
+ for (const item of Object.values(groups || {})) {
84
+ resolvedFieldCount = resolvedFieldCount + item.fields.length;
88
85
  }
89
86
 
90
- return rows;
87
+ return resolvedFieldCount;
91
88
  }
92
89
 
93
- function groupSyncDbDiffRowsByTable(rows) {
94
- const grouped = new Map();
95
-
96
- for (const row of rows) {
97
- const tableName = String(row.tableName || "-");
98
- const tableFileName = String(row.tableFileName || "-");
99
- const groupKey = `${tableName}@@${tableFileName}`;
100
- if (!grouped.has(groupKey)) {
101
- grouped.set(groupKey, {
102
- tableName: tableName,
103
- tableFileName: tableFileName,
104
- rows: []
105
- });
106
- }
107
-
108
- grouped.get(groupKey).rows.push(row);
109
- }
110
-
111
- return Array.from(grouped.values());
90
+ function appendSyncDbMarkdownRow(lines, values) {
91
+ lines.push(`| ${values.map((item) => escapeSyncDbMdCell(formatSyncDbValue(item))).join(" | ")} |`);
112
92
  }
113
93
 
114
- function buildSyncDbAllTableRows(groupedDbColumns, existingTableMap) {
115
- const rows = [];
116
-
117
- for (const [tableName, columns] of groupedDbColumns.entries()) {
118
- const hasTableDef = existingTableMap.has(String(camelCase(tableName)).toLowerCase()) ? "" : "否";
119
- const tableComment = isNonEmptyString(columns[0]?.tableComment) ? String(columns[0].tableComment).trim() : "-";
120
- rows.push({
121
- tableName: tableName,
122
- tableComment: tableComment,
123
- columnCount: columns.length,
124
- hasTableDef: hasTableDef
125
- });
94
+ function appendSyncDbDiffGroup(lines, tableName, tableFileName, rows) {
95
+ lines.push(`### ${escapeSyncDbMdCell(formatSyncDbValue(tableName))}(${escapeSyncDbMdCell(formatSyncDbValue(tableFileName))})`);
96
+ lines.push("");
97
+ lines.push("| 差异类型 | 数据库字段名 | 字段标识 | 字段名称 | 字段类型 | 列类型 | 字符串长度 | 推断input | 推断max |");
98
+ lines.push("|---|---|---|---|---|---|---:|---|---:|");
99
+ for (const row of rows) {
100
+ appendSyncDbMarkdownRow(lines, row);
126
101
  }
127
-
128
- return rows;
102
+ lines.push("");
129
103
  }
130
104
 
131
- function buildSyncDbCheckMarkdown(diff, groupedDbColumns, existingTableMap) {
132
- const allTableRows = buildSyncDbAllTableRows(groupedDbColumns, existingTableMap);
133
- const rows = buildSyncDbCheckRows(diff);
134
- const diffGroups = groupSyncDbDiffRowsByTable(rows);
135
- const missingFieldCount = countSyncDbMissingFields(diff);
105
+ function buildSyncDbReportMarkdown(diff, groupedDbColumns, existingTableMap) {
106
+ const missingFieldCount = resolveSyncDbFieldCount(diff.missingFieldsByTable, diff.missingFieldCount);
107
+ const extraFieldCount = resolveSyncDbFieldCount(diff.extraFieldsByTable, diff.extraFieldCount);
108
+ const hasDiff = diff.missingTables.length > 0 || missingFieldCount > 0 || diff.extraTables.length > 0 || extraFieldCount > 0;
136
109
 
137
110
  const lines = [];
138
- lines.push("# DB 与项目根目录 tables 差异报告");
111
+ lines.push("# 数据库与项目根目录表定义差异报告");
139
112
  lines.push("");
140
113
  lines.push(`- 生成时间戳: ${String(Date.now())}`);
141
- lines.push(`- 扫描数据库表数量: ${String(allTableRows.length)}`);
114
+ lines.push(`- 扫描数据库表数量: ${String(groupedDbColumns.size)}`);
142
115
  lines.push(`- 缺少表数量: ${String(diff.missingTables.length)}`);
143
116
  lines.push(`- 缺少字段数量: ${String(missingFieldCount)}`);
117
+ lines.push(`- 多余表数量: ${String(diff.extraTables.length)}`);
118
+ lines.push(`- 多余字段数量: ${String(extraFieldCount)}`);
119
+ if (diff.missingTables.length > 0) {
120
+ lines.push(`- 缺少表列表: ${diff.missingTables.map((item) => item.tableName).join(", ")}`);
121
+ }
122
+ if (diff.extraTables.length > 0) {
123
+ lines.push(`- 多余表列表: ${diff.extraTables.map((item) => item.tableFileName).join(", ")}`);
124
+ }
144
125
  lines.push("");
145
126
  lines.push("## 全部数据库表信息");
146
127
  lines.push("");
147
- lines.push("| 数据库表 | 表说明 | 列数量 | tables中是否存在 |\n|---|---|---:|---|");
128
+ lines.push("| 数据库表 | 表说明 | 列数量 | 表定义中是否存在 |\n|---|---|---:|---|");
148
129
 
149
- if (allTableRows.length === 0) {
130
+ if (groupedDbColumns.size === 0) {
150
131
  lines.push("| - | - | - | 未扫描到可对比表(可能全部被过滤) |");
151
132
  } else {
152
- for (const row of allTableRows) {
153
- lines.push(`| ${escapeSyncDbMdCell(formatSyncDbValue(row.tableName))} | ${escapeSyncDbMdCell(formatSyncDbValue(row.tableComment))} | ${escapeSyncDbMdCell(formatSyncDbValue(row.columnCount))} | ${escapeSyncDbMdCell(formatSyncDbValue(row.hasTableDef))} |`);
133
+ for (const [tableName, columns] of groupedDbColumns.entries()) {
134
+ appendSyncDbMarkdownRow(lines, [tableName, isNonEmptyString(columns[0]?.tableComment) ? String(columns[0].tableComment).trim() : "-", columns.length, existingTableMap.has(String(camelCase(tableName)).toLowerCase()) ? "是" : "否"]);
154
135
  }
155
136
  }
156
137
 
157
138
  lines.push("");
158
139
  lines.push("## 全部差异信息");
159
140
  lines.push("");
160
- if (rows.length === 0) {
141
+ if (!hasDiff) {
161
142
  lines.push("- 未发现差异");
162
143
  lines.push("");
163
144
  return lines.join("\n");
164
145
  }
165
146
 
166
- for (const group of diffGroups) {
167
- lines.push(`### ${escapeSyncDbMdCell(formatSyncDbValue(group.tableName))}(${escapeSyncDbMdCell(formatSyncDbValue(group.tableFileName))})`);
168
- lines.push("");
169
- lines.push("| 差异类型 | 数据库字段名 | 字段标识 | 字段名称 | 字段类型 | 列类型 | 字符串长度 | 推断input | 推断max |");
170
- lines.push("|---|---|---|---|---|---|---:|---|---:|");
171
- for (const row of group.rows) {
172
- lines.push(`| ${escapeSyncDbMdCell(formatSyncDbValue(row.diffType))} | ${escapeSyncDbMdCell(formatSyncDbValue(row.dbColumnName))} | ${escapeSyncDbMdCell(formatSyncDbValue(row.fieldName))} | ${escapeSyncDbMdCell(formatSyncDbValue(row.fieldDisplayName))} | ${escapeSyncDbMdCell(formatSyncDbValue(row.dbDataType))} | ${escapeSyncDbMdCell(formatSyncDbValue(row.dbColumnType))} | ${escapeSyncDbMdCell(formatSyncDbValue(row.dbCharLength))} | ${escapeSyncDbMdCell(formatSyncDbValue(row.fieldInput))} | ${escapeSyncDbMdCell(formatSyncDbValue(row.fieldMax))} |`);
147
+ for (const missingTable of diff.missingTables) {
148
+ const groupRows = [];
149
+ for (const columnMeta of missingTable.columns) {
150
+ const fieldInfo = toSyncDbFieldDef(columnMeta);
151
+ groupRows.push(["缺少表", columnMeta.columnName, fieldInfo.fieldName, fieldInfo.fieldDef.name, columnMeta.dataType, columnMeta.columnType, columnMeta.charLength, fieldInfo.fieldDef.input, fieldInfo.fieldDef.max]);
173
152
  }
174
- lines.push("");
153
+ appendSyncDbDiffGroup(lines, missingTable.tableName, `${missingTable.tableFileName}.json`, groupRows);
154
+ }
155
+
156
+ for (const tableInfo of Object.values(diff.missingFieldsByTable)) {
157
+ const groupRows = [];
158
+ for (const fieldItem of tableInfo.fields) {
159
+ groupRows.push(["缺少字段", fieldItem.dbColumnName, fieldItem.fieldName, fieldItem.fieldDef.name, fieldItem.dbDataType, fieldItem.dbColumnType, fieldItem.dbCharLength, fieldItem.fieldDef.input, fieldItem.fieldDef.max]);
160
+ }
161
+ appendSyncDbDiffGroup(lines, tableInfo.tableName, `${tableInfo.tableFileName}.json`, groupRows);
162
+ }
163
+
164
+ for (const extraTable of diff.extraTables) {
165
+ appendSyncDbDiffGroup(lines, "-", `${extraTable.tableFileName}.json`, [["多余表", "-", "-", extraTable.filePath || "-", "-", "-", null, "-", null]]);
166
+ }
167
+
168
+ for (const tableInfo of Object.values(diff.extraFieldsByTable)) {
169
+ const groupRows = [];
170
+ for (const fieldItem of tableInfo.fields) {
171
+ groupRows.push(["多余字段", "-", fieldItem.fieldName, fieldItem.fieldDef?.name, "-", "-", null, fieldItem.fieldDef?.input, fieldItem.fieldDef?.max]);
172
+ }
173
+ appendSyncDbDiffGroup(lines, tableInfo.tableName, `${tableInfo.tableFileName}.json`, groupRows);
175
174
  }
176
175
 
177
176
  lines.push("");
178
177
  return lines.join("\n");
179
178
  }
180
179
 
181
- export async function writeSyncDbCheckReport(reportPath, diff, groupedDbColumns, existingTableMap) {
180
+ export async function writeSyncDbReport(reportPath, diff, groupedDbColumns, existingTableMap) {
182
181
  if (!isNonEmptyString(reportPath)) {
183
182
  return "";
184
183
  }
185
184
 
186
185
  await mkdir(dirname(reportPath), { recursive: true });
187
- const markdown = buildSyncDbCheckMarkdown(diff, groupedDbColumns, existingTableMap);
186
+ const markdown = buildSyncDbReportMarkdown(diff, groupedDbColumns, existingTableMap);
188
187
  await Bun.write(reportPath, markdown);
189
188
  printSyncDbProcessLog(`差异报告已写入 ${reportPath}`);
190
189
  Logger.info("差异报告写入完成", {