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.
- package/README.md +7 -0
- package/apis/admin/list.js +1 -1
- package/apis/admin/upd.js +2 -2
- package/apis/dict/all.js +1 -1
- package/apis/dict/detail.js +1 -1
- package/apis/dict/list.js +1 -1
- package/apis/dict/upd.js +1 -1
- package/apis/dictType/upd.js +1 -1
- package/apis/role/all.js +1 -1
- package/apis/role/list.js +1 -1
- package/apis/role/upd.js +1 -1
- package/checks/config.js +1 -3
- package/checks/table.js +2 -15
- package/configs/beflyConfig.json +1 -3
- package/index.js +5 -10
- package/lib/dbHelper.js +201 -736
- package/lib/dbParse.js +1045 -0
- package/lib/dbUtil.js +83 -438
- package/lib/logger.js +21 -45
- package/lib/sqlBuilder.js +78 -294
- package/package.json +2 -2
- package/plugins/mysql.js +2 -1
- package/scripts/syncDb/context.js +62 -47
- package/scripts/syncDb/diff.js +78 -15
- package/scripts/syncDb/index.js +16 -46
- package/scripts/syncDb/report.js +97 -98
- package/tables/admin.json +24 -0
- package/tables/api.json +24 -0
- package/tables/dict.json +24 -0
- package/tables/dictType.json +24 -0
- package/tables/emailLog.json +24 -0
- package/tables/errorReport.json +140 -0
- package/tables/infoReport.json +123 -0
- package/tables/loginLog.json +24 -0
- package/tables/menu.json +24 -0
- package/tables/operateLog.json +24 -0
- package/tables/role.json +24 -0
- package/tables/sysConfig.json +24 -0
- package/utils/loggerUtils.js +9 -14
- package/utils/scanSources.js +3 -3
- package/scripts/syncDb/query.js +0 -26
|
@@ -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
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
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(
|
|
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(
|
|
68
|
-
printSyncDbProcessLog(`连接 MySQL
|
|
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
|
|
79
|
-
|
|
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 =
|
|
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(
|
|
99
|
-
printSyncDbProcessLog(
|
|
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,
|
package/scripts/syncDb/diff.js
CHANGED
|
@@ -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
|
|
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
|
-
|
|
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}
|
|
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
|
-
}
|
package/scripts/syncDb/index.js
CHANGED
|
@@ -1,55 +1,23 @@
|
|
|
1
|
-
import {
|
|
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
|
|
5
|
+
import { applySyncDbDiff } from "./diff.js";
|
|
7
6
|
import { prepareSyncDbBaseContext } from "./context.js";
|
|
8
|
-
import { printSyncDbDiffSummary, printSyncDbProcessLog,
|
|
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("
|
|
48
|
-
const { diff, tablesDir, existingTableMap } = await prepareSyncDbBaseContext(mysqlConfig);
|
|
49
|
-
|
|
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
|
-
|
|
52
|
-
|
|
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
|
-
|
|
61
|
-
|
|
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("执行表定义同步失败", {
|
package/scripts/syncDb/report.js
CHANGED
|
@@ -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(
|
|
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 =
|
|
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("
|
|
20
|
-
mode: mode,
|
|
21
|
+
Logger.info("数据库与项目根目录表定义差异检查完成", {
|
|
21
22
|
missingTableCount: diff.missingTables.length,
|
|
22
23
|
missingFieldCount: missingFieldCount,
|
|
23
|
-
|
|
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
|
|
51
|
-
|
|
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
|
-
|
|
73
|
-
|
|
74
|
-
|
|
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
|
|
87
|
+
return resolvedFieldCount;
|
|
91
88
|
}
|
|
92
89
|
|
|
93
|
-
function
|
|
94
|
-
|
|
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
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
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
|
|
132
|
-
const
|
|
133
|
-
const
|
|
134
|
-
const
|
|
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("#
|
|
111
|
+
lines.push("# 数据库与项目根目录表定义差异报告");
|
|
139
112
|
lines.push("");
|
|
140
113
|
lines.push(`- 生成时间戳: ${String(Date.now())}`);
|
|
141
|
-
lines.push(`- 扫描数据库表数量: ${String(
|
|
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("| 数据库表 | 表说明 | 列数量 |
|
|
128
|
+
lines.push("| 数据库表 | 表说明 | 列数量 | 表定义中是否存在 |\n|---|---|---:|---|");
|
|
148
129
|
|
|
149
|
-
if (
|
|
130
|
+
if (groupedDbColumns.size === 0) {
|
|
150
131
|
lines.push("| - | - | - | 未扫描到可对比表(可能全部被过滤) |");
|
|
151
132
|
} else {
|
|
152
|
-
for (const
|
|
153
|
-
lines
|
|
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 (
|
|
141
|
+
if (!hasDiff) {
|
|
161
142
|
lines.push("- 未发现差异");
|
|
162
143
|
lines.push("");
|
|
163
144
|
return lines.join("\n");
|
|
164
145
|
}
|
|
165
146
|
|
|
166
|
-
for (const
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
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.
|
|
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
|
|
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 =
|
|
186
|
+
const markdown = buildSyncDbReportMarkdown(diff, groupedDbColumns, existingTableMap);
|
|
188
187
|
await Bun.write(reportPath, markdown);
|
|
189
188
|
printSyncDbProcessLog(`差异报告已写入 ${reportPath}`);
|
|
190
189
|
Logger.info("差异报告写入完成", {
|