261 lines
9.3 KiB
JavaScript
261 lines
9.3 KiB
JavaScript
/**
|
||
* 钻石订单数据迁移脚本 - 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();
|