181 lines
5.5 KiB
JavaScript
181 lines
5.5 KiB
JavaScript
|
|
#!/usr/bin/env node
|
||
|
|
|
||
|
|
/**
|
||
|
|
* 检查数据库表结构脚本
|
||
|
|
* 对比设计文档与实际表结构
|
||
|
|
*/
|
||
|
|
|
||
|
|
const mysql = require('mysql2/promise');
|
||
|
|
const config = require('../config/env');
|
||
|
|
|
||
|
|
async function checkTableStructure() {
|
||
|
|
let connection;
|
||
|
|
|
||
|
|
try {
|
||
|
|
console.log('🔍 开始检查数据库表结构...');
|
||
|
|
|
||
|
|
const dbConfig = {
|
||
|
|
host: config.mysql.host,
|
||
|
|
port: config.mysql.port,
|
||
|
|
user: config.mysql.user,
|
||
|
|
password: config.mysql.password,
|
||
|
|
database: config.mysql.database,
|
||
|
|
charset: config.mysql.charset || 'utf8mb4',
|
||
|
|
timezone: config.mysql.timezone || '+08:00'
|
||
|
|
};
|
||
|
|
|
||
|
|
connection = await mysql.createConnection(dbConfig);
|
||
|
|
console.log('✅ 数据库连接成功');
|
||
|
|
|
||
|
|
// 检查所有表的详细结构
|
||
|
|
const [tables] = await connection.execute(
|
||
|
|
`SELECT table_name FROM information_schema.tables
|
||
|
|
WHERE table_schema = ? ORDER BY table_name`,
|
||
|
|
[dbConfig.database]
|
||
|
|
);
|
||
|
|
|
||
|
|
console.log(`\n📊 数据库 ${dbConfig.database} 中共有 ${tables.length} 个表:`);
|
||
|
|
|
||
|
|
for (const table of tables) {
|
||
|
|
const tableName = table.TABLE_NAME || table.table_name;
|
||
|
|
console.log(`\n🔍 检查表: ${tableName}`);
|
||
|
|
|
||
|
|
// 获取表结构
|
||
|
|
const [columns] = await connection.execute(
|
||
|
|
`SELECT
|
||
|
|
COLUMN_NAME,
|
||
|
|
DATA_TYPE,
|
||
|
|
IS_NULLABLE,
|
||
|
|
COLUMN_DEFAULT,
|
||
|
|
COLUMN_KEY,
|
||
|
|
EXTRA,
|
||
|
|
COLUMN_COMMENT
|
||
|
|
FROM information_schema.COLUMNS
|
||
|
|
WHERE table_schema = ? AND table_name = ?
|
||
|
|
ORDER BY ORDINAL_POSITION`,
|
||
|
|
[dbConfig.database, tableName]
|
||
|
|
);
|
||
|
|
|
||
|
|
// 获取表记录数
|
||
|
|
const [countResult] = await connection.execute(`SELECT COUNT(*) AS count FROM ${tableName}`);
|
||
|
|
const recordCount = countResult[0].count;
|
||
|
|
|
||
|
|
console.log(` 📊 记录数: ${recordCount}`);
|
||
|
|
console.log(` 📋 字段结构 (${columns.length} 个字段):`);
|
||
|
|
|
||
|
|
columns.forEach(col => {
|
||
|
|
const nullable = col.IS_NULLABLE === 'YES' ? 'NULL' : 'NOT NULL';
|
||
|
|
const key = col.COLUMN_KEY ? `[${col.COLUMN_KEY}]` : '';
|
||
|
|
const extra = col.EXTRA ? `[${col.EXTRA}]` : '';
|
||
|
|
const defaultVal = col.COLUMN_DEFAULT !== null ? `DEFAULT: ${col.COLUMN_DEFAULT}` : '';
|
||
|
|
const comment = col.COLUMN_COMMENT ? `// ${col.COLUMN_COMMENT}` : '';
|
||
|
|
|
||
|
|
console.log(` - ${col.COLUMN_NAME}: ${col.DATA_TYPE} ${nullable} ${key} ${extra} ${defaultVal} ${comment}`);
|
||
|
|
});
|
||
|
|
|
||
|
|
// 获取索引信息
|
||
|
|
const [indexes] = await connection.execute(
|
||
|
|
`SELECT
|
||
|
|
INDEX_NAME,
|
||
|
|
COLUMN_NAME,
|
||
|
|
NON_UNIQUE,
|
||
|
|
INDEX_TYPE
|
||
|
|
FROM information_schema.STATISTICS
|
||
|
|
WHERE table_schema = ? AND table_name = ?
|
||
|
|
ORDER BY INDEX_NAME, SEQ_IN_INDEX`,
|
||
|
|
[dbConfig.database, tableName]
|
||
|
|
);
|
||
|
|
|
||
|
|
if (indexes.length > 0) {
|
||
|
|
console.log(` 🔑 索引信息:`);
|
||
|
|
const indexGroups = {};
|
||
|
|
indexes.forEach(idx => {
|
||
|
|
if (!indexGroups[idx.INDEX_NAME]) {
|
||
|
|
indexGroups[idx.INDEX_NAME] = {
|
||
|
|
columns: [],
|
||
|
|
unique: idx.NON_UNIQUE === 0,
|
||
|
|
type: idx.INDEX_TYPE
|
||
|
|
};
|
||
|
|
}
|
||
|
|
indexGroups[idx.INDEX_NAME].columns.push(idx.COLUMN_NAME);
|
||
|
|
});
|
||
|
|
|
||
|
|
Object.entries(indexGroups).forEach(([indexName, info]) => {
|
||
|
|
const uniqueStr = info.unique ? '[UNIQUE]' : '';
|
||
|
|
console.log(` - ${indexName}: (${info.columns.join(', ')}) ${uniqueStr} [${info.type}]`);
|
||
|
|
});
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
// 检查外键约束
|
||
|
|
console.log(`\n🔗 检查外键约束:`);
|
||
|
|
const [foreignKeys] = await connection.execute(
|
||
|
|
`SELECT
|
||
|
|
TABLE_NAME,
|
||
|
|
COLUMN_NAME,
|
||
|
|
REFERENCED_TABLE_NAME,
|
||
|
|
REFERENCED_COLUMN_NAME,
|
||
|
|
CONSTRAINT_NAME
|
||
|
|
FROM information_schema.KEY_COLUMN_USAGE
|
||
|
|
WHERE table_schema = ? AND REFERENCED_TABLE_NAME IS NOT NULL
|
||
|
|
ORDER BY TABLE_NAME, COLUMN_NAME`,
|
||
|
|
[dbConfig.database]
|
||
|
|
);
|
||
|
|
|
||
|
|
if (foreignKeys.length > 0) {
|
||
|
|
foreignKeys.forEach(fk => {
|
||
|
|
console.log(` - ${fk.TABLE_NAME}.${fk.COLUMN_NAME} -> ${fk.REFERENCED_TABLE_NAME}.${fk.REFERENCED_COLUMN_NAME} [${fk.CONSTRAINT_NAME}]`);
|
||
|
|
});
|
||
|
|
} else {
|
||
|
|
console.log(' ⚠️ 未发现外键约束');
|
||
|
|
}
|
||
|
|
|
||
|
|
// 检查表大小
|
||
|
|
console.log(`\n💾 检查表存储大小:`);
|
||
|
|
const [tableSizes] = await connection.execute(
|
||
|
|
`SELECT
|
||
|
|
table_name,
|
||
|
|
ROUND(((data_length + index_length) / 1024 / 1024), 2) AS 'size_mb',
|
||
|
|
table_rows
|
||
|
|
FROM information_schema.tables
|
||
|
|
WHERE table_schema = ?
|
||
|
|
ORDER BY (data_length + index_length) DESC`,
|
||
|
|
[dbConfig.database]
|
||
|
|
);
|
||
|
|
|
||
|
|
tableSizes.forEach(size => {
|
||
|
|
console.log(` - ${size.table_name}: ${size.size_mb} MB (${size.table_rows} 行)`);
|
||
|
|
});
|
||
|
|
|
||
|
|
console.log('\n🎉 表结构检查完成!');
|
||
|
|
|
||
|
|
return {
|
||
|
|
success: true,
|
||
|
|
tableCount: tables.length,
|
||
|
|
foreignKeyCount: foreignKeys.length
|
||
|
|
};
|
||
|
|
|
||
|
|
} catch (error) {
|
||
|
|
console.error('❌ 检查表结构失败:', error.message);
|
||
|
|
return {
|
||
|
|
success: false,
|
||
|
|
error: error.message
|
||
|
|
};
|
||
|
|
} finally {
|
||
|
|
if (connection) {
|
||
|
|
await connection.end();
|
||
|
|
console.log('🔒 数据库连接已关闭');
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
// 如果是直接运行此文件,则执行检查
|
||
|
|
if (require.main === module) {
|
||
|
|
checkTableStructure()
|
||
|
|
.then((result) => {
|
||
|
|
process.exit(result.success ? 0 : 1);
|
||
|
|
})
|
||
|
|
.catch(() => process.exit(1));
|
||
|
|
}
|
||
|
|
|
||
|
|
module.exports = { checkTableStructure };
|