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

206 lines
6.9 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: 数据记录数一致性
* Validates: Requirements 2.6
*
* 源表: MySQL shang (106 条记录)
* 目标表: SQL Server prize_levels
*
* 字段映射:
* - imgurl -> img_url
* - special_imgurl -> special_img_url
* - update_time -> updated_at (Unix时间戳转DATETIME2)
*/
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 || str === 'None') 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 prize_levels');
return new Set(result.recordset.map(r => r.id));
}
// 批量插入奖品等级数据
async function insertPrizeLevelsBatch(pool, items) {
if (items.length === 0) return 0;
let insertedCount = 0;
for (const item of items) {
try {
// 时间戳转换
const updatedAt = unixToDatetime(item.update_time);
const insertSql = `
SET IDENTITY_INSERT prize_levels ON;
INSERT INTO prize_levels (
id, title, pro, img_url, color, goods_id, special_img_url, sort, updated_at
) VALUES (
${item.id},
${escapeString(item.title)},
${parseFloat(item.pro) || 0},
${escapeString(item.imgurl)},
${escapeString(item.color)},
${item.goods_id || 0},
${escapeString(item.special_imgurl)},
${item.sort || 0},
${formatDatetime(updatedAt)}
);
SET IDENTITY_INSERT prize_levels OFF;`;
await pool.request().batch(insertSql);
insertedCount++;
} catch (err) {
console.error(`插入奖品等级 ${item.id} (${item.title}) 失败:`, err.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, pro, imgurl, color, goods_id, special_imgurl, sort, update_time
FROM shang
ORDER BY id
`);
console.log(`MySQL 奖品等级总数: ${rows.length}\n`);
// 过滤出未迁移的奖品等级
const itemsToMigrate = rows.filter(item => !migratedIds.has(item.id));
console.log(`待迁移奖品等级数: ${itemsToMigrate.length}\n`);
if (itemsToMigrate.length === 0) {
console.log('所有奖品等级数据已迁移完成!');
} else {
// 批量迁移每批50条
const batchSize = 50;
let totalInserted = 0;
for (let i = 0; i < itemsToMigrate.length; i += batchSize) {
const batch = itemsToMigrate.slice(i, i + batchSize);
const inserted = await insertPrizeLevelsBatch(sqlPool, batch);
totalInserted += inserted;
console.log(`进度: ${Math.min(i + batchSize, itemsToMigrate.length)}/${itemsToMigrate.length} (本批插入: ${inserted})`);
}
console.log(`\n迁移完成!共插入 ${totalInserted} 条记录`);
}
// 验证迁移结果
console.log('\n========================================');
console.log('迁移结果验证');
console.log('========================================');
const [mysqlCount] = await mysqlConn.execute('SELECT COUNT(*) as count FROM shang');
const sqlResult = await sqlPool.request().query('SELECT COUNT(*) as count FROM prize_levels');
console.log(`MySQL shang 表记录数: ${mysqlCount[0].count}`);
console.log(`SQL Server prize_levels 表记录数: ${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 [mysqlLevels] = await mysqlConn.execute('SELECT title, COUNT(*) as count FROM shang GROUP BY title ORDER BY count DESC LIMIT 10');
const sqlLevelsResult = await sqlPool.request().query('SELECT TOP 10 title, COUNT(*) as count FROM prize_levels GROUP BY title ORDER BY count DESC');
console.log('MySQL 奖品等级分布 (前10):');
for (const row of mysqlLevels) {
console.log(` ${row.title}: ${row.count}`);
}
console.log('SQL Server 奖品等级分布 (前10):');
for (const row of sqlLevelsResult.recordset) {
console.log(` ${row.title}: ${row.count}`);
}
} catch (err) {
console.error('迁移过程中发生错误:', err);
process.exit(1);
} finally {
// 关闭连接
if (mysqlConn) await mysqlConn.end();
if (sqlPool) await sqlPool.close();
}
}
main();