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

215 lines
7.1 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 1.5
*
* 源表: MySQL user_address (51 条记录)
* 目标表: SQL Server user_addresses
*/
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 || str === '') return "N''";
return "N'" + String(str).replace(/'/g, "''") + "'";
}
// 格式化日期时间 (MySQL datetime -> SQL Server datetime2)
function formatDatetime(dt) {
if (!dt) return 'NULL';
// MySQL datetime 对象转换为 SQL Server 格式
if (dt instanceof Date) {
return "'" + dt.toISOString().slice(0, 23).replace('T', ' ') + "'";
}
// 字符串格式直接使用
return "'" + String(dt).slice(0, 23) + "'";
}
// 获取已迁移的地址ID列表
async function getMigratedIds(pool) {
const result = await pool.request().query('SELECT id FROM user_addresses');
return new Set(result.recordset.map(r => r.id));
}
// 批量插入用户地址数据
async function insertAddressesBatch(pool, addresses) {
if (addresses.length === 0) return 0;
let insertedCount = 0;
// 构建批量插入SQL
let sqlBatch = 'SET IDENTITY_INSERT user_addresses ON;\n';
for (const addr of addresses) {
const createdAt = formatDatetime(addr.create_time);
const updatedAt = formatDatetime(addr.update_time);
sqlBatch += `
INSERT INTO user_addresses (
id, user_id, receiver_name, receiver_phone, detailed_address,
is_default, is_deleted, created_at, updated_at
) VALUES (
${addr.id},
${addr.user_id || 0},
${escapeString(addr.receiver_name)},
${escapeString(addr.receiver_phone)},
${escapeString(addr.detailed_address)},
${addr.is_default || 0},
${addr.is_deleted || 0},
${createdAt},
${updatedAt}
);
`;
}
sqlBatch += 'SET IDENTITY_INSERT user_addresses OFF;';
try {
await pool.request().batch(sqlBatch);
insertedCount = addresses.length;
} catch (err) {
console.error('批量插入失败:', err.message);
// 如果批量失败,尝试逐条插入
for (const addr of addresses) {
try {
const createdAt = formatDatetime(addr.create_time);
const updatedAt = formatDatetime(addr.update_time);
const singleSql = `
SET IDENTITY_INSERT user_addresses ON;
INSERT INTO user_addresses (
id, user_id, receiver_name, receiver_phone, detailed_address,
is_default, is_deleted, created_at, updated_at
) VALUES (
${addr.id},
${addr.user_id || 0},
${escapeString(addr.receiver_name)},
${escapeString(addr.receiver_phone)},
${escapeString(addr.detailed_address)},
${addr.is_default || 0},
${addr.is_deleted || 0},
${createdAt},
${updatedAt}
);
SET IDENTITY_INSERT user_addresses OFF;`;
await pool.request().batch(singleSql);
insertedCount++;
} catch (singleErr) {
console.error(`插入地址 ${addr.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, user_id, receiver_name, receiver_phone, detailed_address,
is_default, is_deleted, create_time, update_time
FROM user_address
ORDER BY id
`);
console.log(`MySQL 用户地址总数: ${rows.length}\n`);
// 过滤出未迁移的地址
const addressesToMigrate = rows.filter(addr => !migratedIds.has(addr.id));
console.log(`待迁移地址数: ${addressesToMigrate.length}\n`);
if (addressesToMigrate.length === 0) {
console.log('所有用户地址数据已迁移完成!');
} else {
// 批量迁移每批50条避免SQL太长
const batchSize = 50;
let totalInserted = 0;
for (let i = 0; i < addressesToMigrate.length; i += batchSize) {
const batch = addressesToMigrate.slice(i, i + batchSize);
const inserted = await insertAddressesBatch(sqlPool, batch);
totalInserted += inserted;
console.log(`进度: ${Math.min(i + batchSize, addressesToMigrate.length)}/${addressesToMigrate.length} (本批插入: ${inserted})`);
}
console.log(`\n迁移完成!共插入 ${totalInserted} 条记录`);
}
// 验证迁移结果
console.log('\n========================================');
console.log('迁移结果验证');
console.log('========================================');
const [mysqlCount] = await mysqlConn.execute('SELECT COUNT(*) as count FROM user_address');
const sqlResult = await sqlPool.request().query('SELECT COUNT(*) as count FROM user_addresses');
console.log(`MySQL user_address 表记录数: ${mysqlCount[0].count}`);
console.log(`SQL Server user_addresses 表记录数: ${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}`);
}
} catch (err) {
console.error('迁移过程中发生错误:', err);
process.exit(1);
} finally {
// 关闭连接
if (mysqlConn) await mysqlConn.end();
if (sqlPool) await sqlPool.close();
}
}
main();