Files
nxxmdata/backend/reorder-primary-keys.js

340 lines
10 KiB
JavaScript
Raw Normal View History

/**
* 重新排序数据库中所有表的主键ID
* @file reorder-primary-keys.js
* @description 将所有表的主键ID重新排序从1开始升序同时更新外键引用
*/
const { sequelize } = require('./config/database-simple');
const { QueryTypes } = require('sequelize');
// 根据依赖关系确定的处理顺序(被引用的表优先)
const TABLE_ORDER = [
'roles',
'users',
'farms',
'devices',
'products',
'animals',
'alerts',
'orders',
'sensor_data',
'order_items',
'user_roles',
'seeds'
];
// 外键映射关系
const FOREIGN_KEY_MAPPINGS = {
'animals': [{ column: 'farm_id', referencedTable: 'farms' }],
'alerts': [
{ column: 'device_id', referencedTable: 'devices' },
{ column: 'farm_id', referencedTable: 'farms' }
],
'devices': [{ column: 'farm_id', referencedTable: 'farms' }],
'order_items': [
{ column: 'order_id', referencedTable: 'orders' },
{ column: 'product_id', referencedTable: 'products' }
],
'orders': [{ column: 'user_id', referencedTable: 'users' }],
'sensor_data': [
{ column: 'device_id', referencedTable: 'devices' },
{ column: 'farm_id', referencedTable: 'farms' }
],
'user_roles': [
{ column: 'role_id', referencedTable: 'roles' },
{ column: 'user_id', referencedTable: 'users' }
]
};
class IDReorderManager {
constructor() {
this.idMappings = new Map(); // 存储旧ID到新ID的映射
this.transaction = null;
}
async reorderAllTables() {
this.transaction = await sequelize.transaction();
try {
console.log('=== 开始重新排序所有表的主键ID ===\n');
// 临时禁用外键检查
await sequelize.query('SET FOREIGN_KEY_CHECKS = 0', { transaction: this.transaction });
// 按顺序处理每个表
for (const tableName of TABLE_ORDER) {
await this.reorderTableIDs(tableName);
}
// 重新启用外键检查
await sequelize.query('SET FOREIGN_KEY_CHECKS = 1', { transaction: this.transaction });
// 验证数据完整性
await this.verifyIntegrity();
await this.transaction.commit();
console.log('\n✅ 所有表的ID重新排序完成!');
} catch (error) {
await this.transaction.rollback();
console.error('❌ ID重新排序失败:', error);
throw error;
}
}
async reorderTableIDs(tableName) {
try {
console.log(`\n🔄 处理表: ${tableName}`);
// 检查表是否存在且有数据
const tableExists = await this.checkTableExists(tableName);
if (!tableExists) {
console.log(`⚠️ 表 ${tableName} 不存在,跳过`);
return;
}
const recordCount = await this.getRecordCount(tableName);
if (recordCount === 0) {
console.log(`${tableName} 无数据,跳过`);
return;
}
console.log(` 记录数: ${recordCount}`);
// 获取当前所有记录按ID排序
const records = await sequelize.query(
`SELECT * FROM ${tableName} ORDER BY id`,
{ type: QueryTypes.SELECT, transaction: this.transaction }
);
if (records.length === 0) {
console.log(` 无记录需要处理`);
return;
}
// 创建ID映射
const oldToNewIdMap = new Map();
records.forEach((record, index) => {
const oldId = record.id;
const newId = index + 1;
oldToNewIdMap.set(oldId, newId);
});
// 存储映射供其他表使用
this.idMappings.set(tableName, oldToNewIdMap);
// 检查是否需要重新排序
const needsReorder = records.some((record, index) => record.id !== index + 1);
if (!needsReorder) {
console.log(` ✅ ID已经是连续的无需重新排序`);
return;
}
console.log(` 🔧 重新排序ID: ${records[0].id}-${records[records.length-1].id} -> 1-${records.length}`);
// 更新外键引用(如果有的话)
await this.updateForeignKeyReferences(tableName, records);
// 创建临时表
const tempTableName = `${tableName}_temp_reorder`;
await this.createTempTable(tableName, tempTableName);
// 将数据插入临时表使用新ID
await this.insertDataWithNewIDs(tableName, tempTableName, records, oldToNewIdMap);
// 删除原表数据
await sequelize.query(
`DELETE FROM ${tableName}`,
{ transaction: this.transaction }
);
// 重置自增ID
await sequelize.query(
`ALTER TABLE ${tableName} AUTO_INCREMENT = 1`,
{ transaction: this.transaction }
);
// 将临时表数据复制回原表
await this.copyDataFromTempTable(tableName, tempTableName);
// 删除临时表
await sequelize.query(
`DROP TABLE ${tempTableName}`,
{ transaction: this.transaction }
);
console.log(` ✅ 表 ${tableName} ID重新排序完成`);
} catch (error) {
console.error(`❌ 处理表 ${tableName} 失败:`, error.message);
throw error;
}
}
async checkTableExists(tableName) {
const result = await sequelize.query(
`SELECT COUNT(*) as count FROM information_schema.tables
WHERE table_schema = DATABASE() AND table_name = '${tableName}'`,
{ type: QueryTypes.SELECT, transaction: this.transaction }
);
return result[0].count > 0;
}
async getRecordCount(tableName) {
const result = await sequelize.query(
`SELECT COUNT(*) as count FROM ${tableName}`,
{ type: QueryTypes.SELECT, transaction: this.transaction }
);
return parseInt(result[0].count);
}
async updateForeignKeyReferences(tableName, records) {
const foreignKeys = FOREIGN_KEY_MAPPINGS[tableName];
if (!foreignKeys) return;
console.log(` 🔗 更新外键引用...`);
for (const fk of foreignKeys) {
const referencedTableMapping = this.idMappings.get(fk.referencedTable);
if (!referencedTableMapping) {
console.log(` ⚠️ 引用表 ${fk.referencedTable} 的映射不存在,跳过外键 ${fk.column}`);
continue;
}
// 更新外键值
for (const record of records) {
const oldForeignKeyValue = record[fk.column];
if (oldForeignKeyValue && referencedTableMapping.has(oldForeignKeyValue)) {
const newForeignKeyValue = referencedTableMapping.get(oldForeignKeyValue);
record[fk.column] = newForeignKeyValue;
}
}
}
}
async createTempTable(originalTable, tempTable) {
// 先删除临时表(如果存在)
try {
await sequelize.query(
`DROP TABLE IF EXISTS ${tempTable}`,
{ transaction: this.transaction }
);
} catch (error) {
// 忽略删除错误
}
await sequelize.query(
`CREATE TABLE ${tempTable} LIKE ${originalTable}`,
{ transaction: this.transaction }
);
}
async insertDataWithNewIDs(originalTable, tempTable, records, idMapping) {
if (records.length === 0) return;
// 获取表结构
const columns = await sequelize.query(
`SELECT COLUMN_NAME FROM information_schema.columns
WHERE table_schema = DATABASE() AND table_name = '${originalTable}'
ORDER BY ordinal_position`,
{ type: QueryTypes.SELECT, transaction: this.transaction }
);
const columnNames = columns.map(col => col.COLUMN_NAME);
// 批量插入数据
for (let i = 0; i < records.length; i += 100) {
const batch = records.slice(i, i + 100);
const values = batch.map((record, batchIndex) => {
const newId = i + batchIndex + 1;
const values = columnNames.map(col => {
if (col === 'id') {
return newId;
}
const value = record[col];
if (value === null || value === undefined) {
return 'NULL';
}
if (typeof value === 'string') {
return `'${value.replace(/'/g, "''")}'`;
}
if (value instanceof Date) {
return `'${value.toISOString().slice(0, 19).replace('T', ' ')}'`;
}
if (typeof value === 'object') {
return `'${JSON.stringify(value).replace(/'/g, "''")}'`;
}
return value;
});
return `(${values.join(', ')})`;
});
await sequelize.query(
`INSERT INTO ${tempTable} (${columnNames.join(', ')}) VALUES ${values.join(', ')}`,
{ transaction: this.transaction }
);
}
}
async copyDataFromTempTable(originalTable, tempTable) {
await sequelize.query(
`INSERT INTO ${originalTable} SELECT * FROM ${tempTable}`,
{ transaction: this.transaction }
);
}
async verifyIntegrity() {
console.log('\n🔍 验证数据完整性...');
for (const [tableName, foreignKeys] of Object.entries(FOREIGN_KEY_MAPPINGS)) {
if (!foreignKeys) continue;
for (const fk of foreignKeys) {
try {
const result = await sequelize.query(`
SELECT COUNT(*) as invalid_count
FROM ${tableName} t
WHERE t.${fk.column} IS NOT NULL
AND t.${fk.column} NOT IN (
SELECT id FROM ${fk.referencedTable} WHERE id IS NOT NULL
)
`, { type: QueryTypes.SELECT, transaction: this.transaction });
const invalidCount = parseInt(result[0].invalid_count);
if (invalidCount > 0) {
throw new Error(`${tableName}.${fk.column}${invalidCount} 个无效的外键引用`);
}
} catch (error) {
console.error(`❌ 完整性检查失败: ${tableName}.${fk.column}`, error.message);
throw error;
}
}
}
console.log('✅ 数据完整性验证通过');
}
}
async function reorderPrimaryKeys() {
const manager = new IDReorderManager();
await manager.reorderAllTables();
}
// 如果直接运行此脚本
if (require.main === module) {
reorderPrimaryKeys()
.then(() => {
console.log('\n🎉 主键ID重新排序完成!');
process.exit(0);
})
.catch(error => {
console.error('💥 主键ID重新排序失败:', error);
process.exit(1);
})
.finally(() => {
sequelize.close();
});
}
module.exports = { reorderPrimaryKeys, IDReorderManager };