HaniBlindBox/server/scripts/migrate_diamond_orders.js
2026-01-02 05:18:05 +08:00

261 lines
9.3 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

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.

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