225 lines
8.5 KiB
JavaScript
225 lines
8.5 KiB
JavaScript
/**
|
|
* 广告数据迁移脚本 - Node.js
|
|
* Feature: database-migration, Property 2: 数据记录数一致性
|
|
* Validates: Requirements 9.4
|
|
*
|
|
* 源表: MySQL advert (16 条记录)
|
|
* 目标表: SQL Server adverts
|
|
*
|
|
* 字段映射:
|
|
* - id -> id
|
|
* - imgurl -> img_url
|
|
* - url -> url
|
|
* - sort -> sort
|
|
* - type -> type
|
|
* - addtime (Unix时间戳) -> created_at (DATETIME2)
|
|
* - update_time (Unix时间戳) -> updated_at (DATETIME2)
|
|
* - ttype -> ttype
|
|
* - coupon_id -> coupon_id
|
|
* - goods_id -> goods_id
|
|
*/
|
|
|
|
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, "''") + "'";
|
|
}
|
|
|
|
// Unix时间戳转换为SQL Server DATETIME2格式
|
|
function unixToDatetime(timestamp) {
|
|
if (!timestamp || timestamp === 0) {
|
|
return 'GETDATE()';
|
|
}
|
|
const date = new Date(timestamp * 1000);
|
|
const year = date.getFullYear();
|
|
const month = String(date.getMonth() + 1).padStart(2, '0');
|
|
const day = String(date.getDate()).padStart(2, '0');
|
|
const hours = String(date.getHours()).padStart(2, '0');
|
|
const minutes = String(date.getMinutes()).padStart(2, '0');
|
|
const seconds = String(date.getSeconds()).padStart(2, '0');
|
|
return `'${year}-${month}-${day} ${hours}:${minutes}:${seconds}'`;
|
|
}
|
|
|
|
// 获取已迁移的广告ID列表
|
|
async function getMigratedIds(pool) {
|
|
const result = await pool.request().query('SELECT id FROM adverts');
|
|
return new Set(result.recordset.map(r => r.id));
|
|
}
|
|
|
|
// 批量插入广告数据
|
|
async function insertAdvertsBatch(pool, adverts) {
|
|
if (adverts.length === 0) return 0;
|
|
|
|
let insertedCount = 0;
|
|
|
|
// 构建批量插入SQL
|
|
let sqlBatch = 'SET IDENTITY_INSERT adverts ON;\n';
|
|
|
|
for (const advert of adverts) {
|
|
const ttypeValue = advert.ttype !== null && advert.ttype !== undefined ? advert.ttype : 'NULL';
|
|
const couponIdValue = advert.coupon_id !== null && advert.coupon_id !== undefined ? advert.coupon_id : 'NULL';
|
|
const goodsIdValue = advert.goods_id !== null && advert.goods_id !== undefined ? advert.goods_id : 'NULL';
|
|
|
|
sqlBatch += `
|
|
INSERT INTO adverts (id, img_url, url, sort, type, created_at, updated_at, ttype, coupon_id, goods_id)
|
|
VALUES (${advert.id}, ${escapeString(advert.imgurl)}, ${escapeString(advert.url)}, ${advert.sort}, ${advert.type}, ${unixToDatetime(advert.addtime)}, ${unixToDatetime(advert.update_time)}, ${ttypeValue}, ${couponIdValue}, ${goodsIdValue});
|
|
`;
|
|
}
|
|
|
|
sqlBatch += 'SET IDENTITY_INSERT adverts OFF;';
|
|
|
|
try {
|
|
await pool.request().batch(sqlBatch);
|
|
insertedCount = adverts.length;
|
|
console.log(` 批量插入成功: ${adverts.length} 条记录`);
|
|
} catch (err) {
|
|
console.error('批量插入失败:', err.message);
|
|
// 如果批量失败,尝试逐条插入
|
|
for (const advert of adverts) {
|
|
try {
|
|
const ttypeValue = advert.ttype !== null && advert.ttype !== undefined ? advert.ttype : 'NULL';
|
|
const couponIdValue = advert.coupon_id !== null && advert.coupon_id !== undefined ? advert.coupon_id : 'NULL';
|
|
const goodsIdValue = advert.goods_id !== null && advert.goods_id !== undefined ? advert.goods_id : 'NULL';
|
|
|
|
const singleSql = `
|
|
SET IDENTITY_INSERT adverts ON;
|
|
INSERT INTO adverts (id, img_url, url, sort, type, created_at, updated_at, ttype, coupon_id, goods_id)
|
|
VALUES (${advert.id}, ${escapeString(advert.imgurl)}, ${escapeString(advert.url)}, ${advert.sort}, ${advert.type}, ${unixToDatetime(advert.addtime)}, ${unixToDatetime(advert.update_time)}, ${ttypeValue}, ${couponIdValue}, ${goodsIdValue});
|
|
SET IDENTITY_INSERT adverts OFF;`;
|
|
|
|
await pool.request().batch(singleSql);
|
|
insertedCount++;
|
|
console.log(` ✓ 插入广告 ${advert.id}`);
|
|
} catch (singleErr) {
|
|
console.error(` ✗ 插入广告 ${advert.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, imgurl, url, sort, type, addtime, update_time, ttype, coupon_id, goods_id
|
|
FROM advert
|
|
ORDER BY id
|
|
`);
|
|
console.log(`MySQL 广告总数: ${rows.length}\n`);
|
|
|
|
// 显示广告列表
|
|
console.log('广告列表:');
|
|
for (const row of rows) {
|
|
const status = migratedIds.has(row.id) ? '已迁移' : '待迁移';
|
|
console.log(` ${row.id}. type=${row.type}, sort=${row.sort} [${status}]`);
|
|
}
|
|
console.log('');
|
|
|
|
// 过滤出未迁移的广告
|
|
const advertsToMigrate = rows.filter(advert => !migratedIds.has(advert.id));
|
|
console.log(`待迁移广告数: ${advertsToMigrate.length}\n`);
|
|
|
|
if (advertsToMigrate.length === 0) {
|
|
console.log('所有广告数据已迁移完成!');
|
|
} else {
|
|
// 批量迁移
|
|
console.log('开始迁移广告数据...');
|
|
const inserted = await insertAdvertsBatch(sqlPool, advertsToMigrate);
|
|
console.log(`\n迁移完成!共插入 ${inserted} 条记录`);
|
|
}
|
|
|
|
// 验证迁移结果
|
|
console.log('\n========================================');
|
|
console.log('迁移结果验证');
|
|
console.log('========================================');
|
|
|
|
const [mysqlCount] = await mysqlConn.execute('SELECT COUNT(*) as count FROM advert');
|
|
const sqlResult = await sqlPool.request().query('SELECT COUNT(*) as count FROM adverts');
|
|
|
|
console.log(`MySQL advert 表记录数: ${mysqlCount[0].count}`);
|
|
console.log(`SQL Server adverts 表记录数: ${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迁移统计:');
|
|
const stats = await sqlPool.request().query(`
|
|
SELECT
|
|
COUNT(*) as total,
|
|
COUNT(DISTINCT type) as type_count,
|
|
COUNT(CASE WHEN goods_id > 0 THEN 1 END) as with_goods,
|
|
COUNT(CASE WHEN coupon_id > 0 THEN 1 END) as with_coupon
|
|
FROM adverts
|
|
`);
|
|
console.log(` 总记录数: ${stats.recordset[0].total}`);
|
|
console.log(` 广告类型数: ${stats.recordset[0].type_count}`);
|
|
console.log(` 关联商品: ${stats.recordset[0].with_goods}`);
|
|
console.log(` 关联优惠券: ${stats.recordset[0].with_coupon}`);
|
|
|
|
} catch (err) {
|
|
console.error('迁移过程中发生错误:', err);
|
|
process.exit(1);
|
|
} finally {
|
|
// 关闭连接
|
|
if (mysqlConn) await mysqlConn.end();
|
|
if (sqlPool) await sqlPool.close();
|
|
}
|
|
}
|
|
|
|
main();
|