/** * 钻石订单数据迁移脚本 - Node.js * Feature: database-migration, Property 2: 数据记录数一致性 * Feature: database-migration, Property 7: 数据迁移往返一致性 * Validates: Requirements 6.3 * * 源表: MySQL diamond_orders (398 条记录) * 目标表: SQL Server diamond_orders */ const mysql = require('mysql2/promise'); const sql = require('mssql'); // MySQL 配置 const mysqlConfig = { host: '192.168.195.16', port: 1887, user: 'root', password: 'Dbt@com@123', database: 'youdas', charset: 'utf8mb4' }; // SQL Server 配置 const sqlServerConfig = { server: '192.168.195.15', port: 1433, user: 'sa', password: 'Dbt@com@123', database: 'honey_box', options: { encrypt: false, trustServerCertificate: true } }; // 转义SQL字符串 function escapeString(str) { if (str === null || str === undefined) return 'NULL'; return "N'" + String(str).replace(/'/g, "''") + "'"; } // 格式化日期时间 (MySQL datetime -> SQL Server datetime2) function formatDatetime(dt) { if (!dt) return 'NULL'; if (dt instanceof Date) { return "'" + dt.toISOString().slice(0, 23).replace('T', ' ') + "'"; } return "'" + String(dt).slice(0, 23).replace('T', ' ') + "'"; } // 获取已迁移的钻石订单ID列表 async function getMigratedIds(pool) { const result = await pool.request().query('SELECT id FROM diamond_orders'); return new Set(result.recordset.map(r => Number(r.id))); } // 批量插入钻石订单数据 async function insertDiamondOrdersBatch(pool, orders) { if (orders.length === 0) return 0; let insertedCount = 0; // 构建批量插入SQL let sqlBatch = 'SET IDENTITY_INSERT diamond_orders ON;\n'; for (const order of orders) { sqlBatch += ` INSERT INTO diamond_orders ( id, order_no, user_id, diamond_id, product_id, product_name, amount_paid, pay_method, reward_log, is_first_charge, status, created_at, paid_at ) VALUES ( ${order.id}, ${escapeString(order.order_no)}, ${order.user_id}, ${order.diamond_id}, ${escapeString(order.product_id)}, ${escapeString(order.product_name)}, ${parseFloat(order.amount_paid) || 0}, ${escapeString(order.pay_method)}, ${escapeString(order.reward_log)}, ${order.is_first_charge || 0}, ${escapeString(order.status || 'pending')}, ${formatDatetime(order.created_at)}, ${formatDatetime(order.paid_at)} ); `; } sqlBatch += 'SET IDENTITY_INSERT diamond_orders OFF;'; try { await pool.request().batch(sqlBatch); insertedCount = orders.length; } catch (err) { console.error('批量插入失败:', err.message); // 如果批量失败,尝试逐条插入 for (const order of orders) { try { const singleSql = ` SET IDENTITY_INSERT diamond_orders ON; INSERT INTO diamond_orders ( id, order_no, user_id, diamond_id, product_id, product_name, amount_paid, pay_method, reward_log, is_first_charge, status, created_at, paid_at ) VALUES ( ${order.id}, ${escapeString(order.order_no)}, ${order.user_id}, ${order.diamond_id}, ${escapeString(order.product_id)}, ${escapeString(order.product_name)}, ${parseFloat(order.amount_paid) || 0}, ${escapeString(order.pay_method)}, ${escapeString(order.reward_log)}, ${order.is_first_charge || 0}, ${escapeString(order.status || 'pending')}, ${formatDatetime(order.created_at)}, ${formatDatetime(order.paid_at)} ); SET IDENTITY_INSERT diamond_orders OFF;`; await pool.request().batch(singleSql); insertedCount++; } catch (singleErr) { console.error(`插入钻石订单 ${order.id} 失败:`, singleErr.message); } } } return insertedCount; } async function main() { console.log('========================================'); console.log('钻石订单数据迁移脚本 - Node.js'); console.log('========================================\n'); let mysqlConn = null; let sqlPool = null; try { // 连接 MySQL console.log('正在连接 MySQL...'); mysqlConn = await mysql.createConnection(mysqlConfig); console.log('MySQL 连接成功\n'); // 连接 SQL Server console.log('正在连接 SQL Server...'); sqlPool = await sql.connect(sqlServerConfig); console.log('SQL Server 连接成功\n'); // 获取已迁移的ID console.log('正在获取已迁移的钻石订单ID...'); const migratedIds = await getMigratedIds(sqlPool); console.log(`已迁移钻石订单数: ${migratedIds.size}\n`); // 从 MySQL 获取所有钻石订单数据 console.log('正在从 MySQL 读取钻石订单数据...'); const [rows] = await mysqlConn.execute(` SELECT id, order_no, user_id, diamond_id, product_id, product_name, amount_paid, pay_method, reward_log, is_first_charge, status, created_at, paid_at FROM diamond_orders ORDER BY id `); console.log(`MySQL 钻石订单总数: ${rows.length}\n`); // 过滤出未迁移的钻石订单 const ordersToMigrate = rows.filter(order => !migratedIds.has(Number(order.id))); console.log(`待迁移钻石订单数: ${ordersToMigrate.length}\n`); if (ordersToMigrate.length === 0) { console.log('所有钻石订单数据已迁移完成!'); } else { // 批量迁移(每批50条) const batchSize = 50; let totalInserted = 0; for (let i = 0; i < ordersToMigrate.length; i += batchSize) { const batch = ordersToMigrate.slice(i, i + batchSize); const inserted = await insertDiamondOrdersBatch(sqlPool, batch); totalInserted += inserted; console.log(`进度: ${Math.min(i + batchSize, ordersToMigrate.length)}/${ordersToMigrate.length} (本批插入: ${inserted})`); } console.log(`\n迁移完成!共插入 ${totalInserted} 条记录`); } // 验证迁移结果 console.log('\n========================================'); console.log('迁移结果验证'); console.log('========================================'); const [mysqlCount] = await mysqlConn.execute('SELECT COUNT(*) as count FROM diamond_orders'); const sqlResult = await sqlPool.request().query('SELECT COUNT(*) as count FROM diamond_orders'); console.log(`MySQL diamond_orders 表记录数: ${mysqlCount[0].count}`); console.log(`SQL Server diamond_orders 表记录数: ${sqlResult.recordset[0].count}`); if (mysqlCount[0].count === sqlResult.recordset[0].count) { console.log('\n✅ 数据迁移完成,记录数一致!'); } else { console.log(`\n⚠️ 记录数不一致,差异: ${mysqlCount[0].count - sqlResult.recordset[0].count}`); } // 验证奖励日志保留情况 console.log('\n========================================'); console.log('奖励日志验证'); console.log('========================================'); const [mysqlRewardCount] = await mysqlConn.execute(` SELECT COUNT(*) as count FROM diamond_orders WHERE reward_log IS NOT NULL AND reward_log != '' `); const sqlRewardResult = await sqlPool.request().query(` SELECT COUNT(*) as count FROM diamond_orders WHERE reward_log IS NOT NULL AND reward_log != '' `); console.log(`MySQL 有奖励日志的订单数: ${mysqlRewardCount[0].count}`); console.log(`SQL Server 有奖励日志的订单数: ${sqlRewardResult.recordset[0].count}`); if (mysqlRewardCount[0].count === sqlRewardResult.recordset[0].count) { console.log('\n✅ 奖励日志保留完整!'); } else { console.log(`\n⚠️ 奖励日志数量不一致,差异: ${mysqlRewardCount[0].count - sqlRewardResult.recordset[0].count}`); } // 显示迁移后的数据样本 console.log('\n========================================'); console.log('迁移后数据样本'); console.log('========================================'); const sampleResult = await sqlPool.request().query(` SELECT TOP 5 id, order_no, user_id, amount_paid, status, reward_log, created_at FROM diamond_orders ORDER BY id `); console.log('\nSQL Server diamond_orders 表数据:'); sampleResult.recordset.forEach(row => { console.log(` ID: ${row.id}, 订单号: ${row.order_no}, 用户ID: ${row.user_id}, 金额: ${row.amount_paid}, 状态: ${row.status}`); if (row.reward_log) { console.log(` 奖励日志: ${row.reward_log}`); } }); } catch (err) { console.error('迁移过程中发生错误:', err); process.exit(1); } finally { // 关闭连接 if (mysqlConn) await mysqlConn.end(); if (sqlPool) await sqlPool.close(); } } main();