Files
jiebanke/backend/scripts/check-database-structure.js

226 lines
7.8 KiB
JavaScript

#!/usr/bin/env node
/**
* 数据库结构检查脚本
* 检查数据库表结构和数据完整性
*/
const mysql = require('mysql2/promise');
const config = require('../config/env');
async function checkDatabaseStructure() {
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,
timezone: config.mysql.timezone
};
connection = await mysql.createConnection(dbConfig);
console.log('✅ 数据库连接成功');
console.log(`📊 数据库: ${dbConfig.database}`);
console.log('='.repeat(60));
// 测试基本查询
const [testRows] = await connection.execute('SELECT 1 + 1 AS result');
console.log(`✅ 基本查询测试: ${testRows[0].result}`);
// 检查数据库版本和字符集
const [versionRows] = await connection.execute('SELECT VERSION() as version');
console.log(`📊 MySQL版本: ${versionRows[0].version}`);
// 获取所有表
console.log('\n📋 检查数据库表结构:');
const [tables] = await connection.execute(`
SELECT TABLE_NAME, TABLE_ROWS, DATA_LENGTH, INDEX_LENGTH, TABLE_COMMENT
FROM information_schema.TABLES
WHERE TABLE_SCHEMA = ?
ORDER BY TABLE_NAME
`, [dbConfig.database]);
if (tables.length === 0) {
console.log('⚠️ 数据库中没有找到任何表');
console.log('💡 建议运行数据库结构创建脚本');
return { success: false, message: '数据库为空' };
}
console.log(`📊 找到 ${tables.length} 个表:`);
let totalRows = 0;
for (const table of tables) {
const rowCount = table.TABLE_ROWS || 0;
totalRows += rowCount;
const dataSize = (table.DATA_LENGTH / 1024).toFixed(2);
const indexSize = (table.INDEX_LENGTH / 1024).toFixed(2);
console.log(` 📄 ${table.TABLE_NAME.padEnd(25)} | ${String(rowCount).padStart(6)} 行 | ${dataSize.padStart(8)} KB | ${table.TABLE_COMMENT || '无注释'}`);
}
console.log(`\n📊 总记录数: ${totalRows}`);
// 检查核心表的详细结构
console.log('\n🔍 检查核心表结构:');
const coreTables = ['admins', 'users', 'merchants', 'animals', 'orders', 'payments'];
for (const tableName of coreTables) {
const tableExists = tables.find(t => t.TABLE_NAME === tableName);
if (tableExists) {
console.log(`\n📋 表: ${tableName}`);
// 获取表结构
const [columns] = await connection.execute(`
SELECT COLUMN_NAME, DATA_TYPE, IS_NULLABLE, COLUMN_DEFAULT, COLUMN_COMMENT
FROM information_schema.COLUMNS
WHERE TABLE_SCHEMA = ? AND TABLE_NAME = ?
ORDER BY ORDINAL_POSITION
`, [dbConfig.database, tableName]);
console.log(' 字段结构:');
for (const col of columns.slice(0, 10)) { // 只显示前10个字段
const nullable = col.IS_NULLABLE === 'YES' ? '可空' : '非空';
const defaultVal = col.COLUMN_DEFAULT ? `默认:${col.COLUMN_DEFAULT}` : '';
console.log(` ${col.COLUMN_NAME.padEnd(20)} | ${col.DATA_TYPE.padEnd(15)} | ${nullable.padEnd(4)} | ${col.COLUMN_COMMENT || '无注释'}`);
}
if (columns.length > 10) {
console.log(` ... 还有 ${columns.length - 10} 个字段`);
}
// 检查索引
const [indexes] = await connection.execute(`
SELECT INDEX_NAME, COLUMN_NAME, NON_UNIQUE
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] = [];
}
indexGroups[idx.INDEX_NAME].push(idx.COLUMN_NAME);
});
Object.entries(indexGroups).forEach(([indexName, columns]) => {
const type = indexName === 'PRIMARY' ? '主键' : '索引';
console.log(` ${type}: ${indexName} (${columns.join(', ')})`);
});
}
// 检查数据样例
try {
const [sampleData] = await connection.execute(`SELECT * FROM ${tableName} LIMIT 3`);
if (sampleData.length > 0) {
console.log(` 📊 数据样例: ${sampleData.length} 条记录`);
} else {
console.log(' 📊 数据样例: 表为空');
}
} catch (error) {
console.log(` ❌ 无法获取数据样例: ${error.message}`);
}
} else {
console.log(`❌ 核心表不存在: ${tableName}`);
}
}
// 检查外键约束
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
`, [dbConfig.database]);
if (foreignKeys.length > 0) {
console.log(`📊 找到 ${foreignKeys.length} 个外键约束:`);
foreignKeys.forEach(fk => {
console.log(` ${fk.TABLE_NAME}.${fk.COLUMN_NAME} -> ${fk.REFERENCED_TABLE_NAME}.${fk.REFERENCED_COLUMN_NAME}`);
});
} else {
console.log('⚠️ 没有找到外键约束');
}
// 数据完整性检查
console.log('\n🔍 数据完整性检查:');
// 检查管理员数据
if (tables.find(t => t.TABLE_NAME === 'admins')) {
const [adminCount] = await connection.execute('SELECT COUNT(*) as count FROM admins');
console.log(`👨‍💼 管理员数量: ${adminCount[0].count}`);
if (adminCount[0].count > 0) {
const [admins] = await connection.execute('SELECT username, role, status FROM admins LIMIT 5');
admins.forEach(admin => {
console.log(` - ${admin.username} (${admin.role}, 状态: ${admin.status})`);
});
}
}
// 检查用户数据
if (tables.find(t => t.TABLE_NAME === 'users')) {
const [userCount] = await connection.execute('SELECT COUNT(*) as count FROM users');
console.log(`👥 用户数量: ${userCount[0].count}`);
if (userCount[0].count > 0) {
const [userStats] = await connection.execute(`
SELECT user_type, COUNT(*) as count
FROM users
GROUP BY user_type
`);
userStats.forEach(stat => {
console.log(` - ${stat.user_type}: ${stat.count}`);
});
}
}
console.log('\n🎉 数据库结构检查完成!');
return {
success: true,
tableCount: tables.length,
totalRows: totalRows,
tables: tables.map(t => t.TABLE_NAME)
};
} catch (error) {
console.error('❌ 数据库结构检查失败:', error.message);
console.error('🔍 错误代码:', error.code);
return { success: false, error: error.message };
} finally {
if (connection) {
await connection.end();
console.log('🔒 数据库连接已关闭');
}
}
}
// 如果是直接运行此文件,则执行检查
if (require.main === module) {
checkDatabaseStructure()
.then((result) => {
process.exit(result.success ? 0 : 1);
})
.catch(() => process.exit(1));
}
module.exports = { checkDatabaseStructure };