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

241 lines
8.7 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 5.4
*
* 源表: MySQL coupon_receive (7,846 条记录)
* 目标表: SQL Server coupon_receives
*/
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
}
};
// Unix时间戳转换为 SQL Server DATETIME2 格式
function unixToDatetime(timestamp) {
if (!timestamp || timestamp === 0) {
return null;
}
const date = new Date(timestamp * 1000);
return date.toISOString().slice(0, 23).replace('T', ' ');
}
// 转义SQL字符串
function escapeString(str) {
if (str === null || str === undefined) return 'NULL';
return "N'" + String(str).replace(/'/g, "''") + "'";
}
// 格式化日期时间
function formatDatetime(dt) {
if (!dt) return 'NULL';
return "'" + dt + "'";
}
// 获取已迁移的优惠券领取记录ID列表
async function getMigratedIds(pool) {
const result = await pool.request().query('SELECT id FROM coupon_receives');
return new Set(result.recordset.map(r => r.id));
}
// 批量插入优惠券领取记录数据
async function insertCouponReceivesBatch(pool, records) {
if (records.length === 0) return 0;
let insertedCount = 0;
// 构建批量插入SQL
let sqlBatch = 'SET IDENTITY_INSERT coupon_receives ON;\n';
for (const record of records) {
// 转换时间戳
const createdAt = unixToDatetime(record.addtime) || new Date().toISOString().slice(0, 23).replace('T', ' ');
const endTime = unixToDatetime(record.end_time);
sqlBatch += `
INSERT INTO coupon_receives (
id, title, price, man_price, end_time, user_id, coupon_id, status, state, ttype, created_at
) VALUES (
${record.id},
${escapeString(record.title)},
${parseFloat(record.price) || 0},
${parseFloat(record.man_price) || 0},
${formatDatetime(endTime)},
${record.user_id || 'NULL'},
${record.coupon_id || 'NULL'},
${record.status || 0},
${record.state !== null && record.state !== undefined ? record.state : 0},
${record.ttype || 0},
${formatDatetime(createdAt)}
);
`;
}
sqlBatch += 'SET IDENTITY_INSERT coupon_receives OFF;';
try {
await pool.request().batch(sqlBatch);
insertedCount = records.length;
} catch (err) {
console.error('批量插入失败:', err.message);
// 如果批量失败,尝试逐条插入
for (const record of records) {
try {
const createdAt = unixToDatetime(record.addtime) || new Date().toISOString().slice(0, 23).replace('T', ' ');
const endTime = unixToDatetime(record.end_time);
const singleSql = `
SET IDENTITY_INSERT coupon_receives ON;
INSERT INTO coupon_receives (
id, title, price, man_price, end_time, user_id, coupon_id, status, state, ttype, created_at
) VALUES (
${record.id},
${escapeString(record.title)},
${parseFloat(record.price) || 0},
${parseFloat(record.man_price) || 0},
${formatDatetime(endTime)},
${record.user_id || 'NULL'},
${record.coupon_id || 'NULL'},
${record.status || 0},
${record.state !== null && record.state !== undefined ? record.state : 0},
${record.ttype || 0},
${formatDatetime(createdAt)}
);
SET IDENTITY_INSERT coupon_receives OFF;`;
await pool.request().batch(singleSql);
insertedCount++;
} catch (singleErr) {
console.error(`插入优惠券领取记录 ${record.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, title, price, man_price, end_time, addtime, status, user_id, coupon_id, state, ttype
FROM coupon_receive
ORDER BY id
`);
console.log(`MySQL 优惠券领取记录总数: ${rows.length}\n`);
// 过滤出未迁移的记录
const recordsToMigrate = rows.filter(record => !migratedIds.has(record.id));
console.log(`待迁移优惠券领取记录数: ${recordsToMigrate.length}\n`);
if (recordsToMigrate.length === 0) {
console.log('所有优惠券领取记录数据已迁移完成!');
} else {
// 批量迁移每批100条
const batchSize = 100;
let totalInserted = 0;
for (let i = 0; i < recordsToMigrate.length; i += batchSize) {
const batch = recordsToMigrate.slice(i, i + batchSize);
const inserted = await insertCouponReceivesBatch(sqlPool, batch);
totalInserted += inserted;
console.log(`进度: ${Math.min(i + batchSize, recordsToMigrate.length)}/${recordsToMigrate.length} (本批插入: ${inserted})`);
}
console.log(`\n迁移完成!共插入 ${totalInserted} 条记录`);
}
// 验证迁移结果
console.log('\n========================================');
console.log('迁移结果验证');
console.log('========================================');
const [mysqlCount] = await mysqlConn.execute('SELECT COUNT(*) as count FROM coupon_receive');
const sqlResult = await sqlPool.request().query('SELECT COUNT(*) as count FROM coupon_receives');
console.log(`MySQL coupon_receive 表记录数: ${mysqlCount[0].count}`);
console.log(`SQL Server coupon_receives 表记录数: ${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 [mysqlEndTimeCount] = await mysqlConn.execute('SELECT COUNT(*) as count FROM coupon_receive WHERE end_time IS NOT NULL AND end_time > 0');
const sqlEndTimeResult = await sqlPool.request().query('SELECT COUNT(*) as count FROM coupon_receives WHERE end_time IS NOT NULL');
console.log(`MySQL 有效期记录数: ${mysqlEndTimeCount[0].count}`);
console.log(`SQL Server 有效期记录数: ${sqlEndTimeResult.recordset[0].count}`);
if (mysqlEndTimeCount[0].count === sqlEndTimeResult.recordset[0].count) {
console.log('\n✅ 有效期信息保留完整!');
} else {
console.log(`\n⚠️ 有效期记录数不一致,差异: ${mysqlEndTimeCount[0].count - sqlEndTimeResult.recordset[0].count}`);
}
} catch (err) {
console.error('迁移过程中发生错误:', err);
process.exit(1);
} finally {
// 关闭连接
if (mysqlConn) await mysqlConn.end();
if (sqlPool) await sqlPool.close();
}
}
main();