Files
nxxmdata/backend/reorder-primary-keys.js
2025-08-25 15:00:46 +08:00

340 lines
10 KiB
JavaScript
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/**
* 重新排序数据库中所有表的主键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 };