/** * 中介导入功能测试脚本 - 验证ON DUPLICATE KEY UPDATE重构 * * 测试场景: * 1. 更新模式 - 测试importPersonBatch/importEntityBatch的INSERT ON DUPLICATE KEY UPDATE * 2. 仅新增模式 - 测试冲突检测和失败记录 * 3. 边界情况 - 空列表、全部冲突、部分冲突等 */ const axios = require('axios'); const FormData = require('form-data'); const fs = require('fs'); const path = require('path'); // 配置 const BASE_URL = 'http://localhost:8080'; const LOGIN_URL = `${BASE_URL}/login/test`; const PERSON_IMPORT_URL = `${BASE_URL}/ccdi/intermediary/importPersonData`; const ENTITY_IMPORT_URL = `${BASE_URL}/ccdi/intermediary/importEntityData`; const PERSON_STATUS_URL = `${BASE_URL}/ccdi/intermediary/person/import/status`; const ENTITY_STATUS_URL = `${BASE_URL}/ccdi/intermediary/entity/import/status`; const PERSON_FAILURES_URL = `${BASE_URL}/ccdi/intermediary/person/import/failures`; const ENTITY_FAILURES_URL = `${BASE_URL}/ccdi/intermediary/entity/import/failures`; // 测试数据文件路径 const TEST_DATA_DIR = path.join(__dirname, '../test-data/intermediary'); const PERSON_TEST_FILE = path.join(TEST_DATA_DIR, '个人中介黑名单测试数据_1000条_第1批.xlsx'); const ENTITY_TEST_FILE = path.join(TEST_DATA_DIR, '机构中介黑名单测试数据_1000条_第1批.xlsx'); let authToken = ''; // 颜色输出 const colors = { reset: '\x1b[0m', green: '\x1b[32m', red: '\x1b[31m', yellow: '\x1b[33m', blue: '\x1b[36m' }; function log(message, color = 'reset') { console.log(`${colors[color]}${message}${colors.reset}`); } function logSuccess(message) { log(`✓ ${message}`, 'green'); } function logError(message) { log(`✗ ${message}`, 'red'); } function logInfo(message) { log(`ℹ ${message}`, 'blue'); } function logSection(title) { console.log('\n' + '='.repeat(60)); log(title, 'yellow'); console.log('='.repeat(60)); } /** * 登录获取Token */ async function login() { logSection('登录系统'); try { const response = await axios.post(LOGIN_URL, { username: 'admin', password: 'admin123' }); if (response.data.code === 200) { authToken = response.data.data; logSuccess('登录成功'); logInfo(`Token: ${authToken.substring(0, 20)}...`); return true; } else { logError(`登录失败: ${response.data.msg}`); return false; } } catch (error) { logError(`登录请求失败: ${error.message}`); return false; } } /** * 上传文件并开始导入 */ async function importData(file, url, updateSupport, description) { logSection(description); if (!fs.existsSync(file)) { logError(`测试文件不存在: ${file}`); return null; } logInfo(`上传文件: ${path.basename(file)}`); logInfo(`更新模式: ${updateSupport ? '是' : '否'}`); try { const form = new FormData(); form.append('file', fs.createReadStream(file)); form.append('updateSupport', updateSupport.toString()); const response = await axios.post(url, form, { headers: { ...form.getHeaders(), 'Authorization': `Bearer ${authToken}` } }); if (response.data.code === 200) { logSuccess('导入任务已提交'); logInfo(`响应信息: ${response.data.msg}`); // 从响应中提取taskId const match = response.data.msg.match(/任务ID: ([a-zA-Z0-9-]+)/); if (match) { const taskId = match[1]; logInfo(`任务ID: ${taskId}`); return taskId; } } else { logError(`导入失败: ${response.data.msg}`); } } catch (error) { logError(`导入请求失败: ${error.message}`); if (error.response) { logError(`状态码: ${error.response.status}`); logError(`响应数据: ${JSON.stringify(error.response.data)}`); } } return null; } /** * 轮询查询导入状态 */ async function pollImportStatus(taskId, url, description, maxAttempts = 30, interval = 2000) { logInfo(`等待导入完成...`); for (let attempt = 1; attempt <= maxAttempts; attempt++) { try { const response = await axios.get(`${url}?taskId=${taskId}`, { headers: { 'Authorization': `Bearer ${authToken}` } }); if (response.data.code === 200) { const status = response.data.data; logInfo(`[尝试 ${attempt}/${maxAttempts}] 状态: ${status.status}, 进度: ${status.progress}%`); if (status.status === 'SUCCESS' || status.status === 'PARTIAL_SUCCESS') { logSuccess(`${description}完成!`); logInfo(`总数: ${status.totalCount}, 成功: ${status.successCount}, 失败: ${status.failureCount}`); return status; } else if (status.status === 'FAILURE') { logError(`${description}失败`); return status; } } } catch (error) { logError(`查询状态失败: ${error.message}`); } await sleep(interval); } logError('导入超时'); return null; } /** * 获取导入失败记录 */ async function getImportFailures(taskId, url, description) { logSection(`获取${description}失败记录`); try { const response = await axios.get(`${url}?taskId=${taskId}`, { headers: { 'Authorization': `Bearer ${authToken}` } }); if (response.data.code === 200) { const failures = response.data.data; logInfo(`失败记录数: ${failures.length}`); if (failures.length > 0) { logInfo('前3条失败记录:'); failures.slice(0, 3).forEach((failure, index) => { console.log(` ${index + 1}. ${failure.errorMessage || '未知错误'}`); }); // 保存失败记录到文件 const failureFile = path.join(__dirname, `failures_${taskId}.json`); fs.writeFileSync(failureFile, JSON.stringify(failures, null, 2)); logInfo(`失败记录已保存到: ${failureFile}`); } return failures; } } catch (error) { logError(`获取失败记录失败: ${error.message}`); } return []; } /** * 辅助函数: 延迟 */ function sleep(ms) { return new Promise(resolve => setTimeout(resolve, ms)); } /** * 测试场景1: 个人中介 - 更新模式(第一次导入) */ async function testPersonImportUpdateMode() { logSection('测试场景1: 个人中介 - 更新模式(第一次导入)'); const taskId = await importData( PERSON_TEST_FILE, PERSON_IMPORT_URL, true, // 更新模式 '个人中介导入(更新模式)' ); if (!taskId) { logError('导入任务未创建'); return false; } const status = await pollImportStatus(taskId, PERSON_STATUS_URL, '个人中介导入'); if (status && (status.status === 'SUCCESS' || status.status === 'PARTIAL_SUCCESS')) { const failures = await getImportFailures(taskId, PERSON_FAILURES_URL, '个人中介'); logSuccess(`测试场景1完成 - 成功: ${status.successCount}, 失败: ${status.failureCount}`); return true; } return false; } /** * 测试场景2: 个人中介 - 仅新增模式(重复导入应失败) */ async function testPersonImportInsertOnly() { logSection('测试场景2: 个人中介 - 仅新增模式(重复导入)'); const taskId = await importData( PERSON_TEST_FILE, PERSON_IMPORT_URL, false, // 仅新增模式 '个人中介导入(仅新增)' ); if (!taskId) { logError('导入任务未创建'); return false; } const status = await pollImportStatus(taskId, PERSON_STATUS_URL, '个人中介导入'); if (status && (status.status === 'SUCCESS' || status.status === 'PARTIAL_SUCCESS')) { const failures = await getImportFailures(taskId, PERSON_FAILURES_URL, '个人中介'); // 在仅新增模式下,重复导入应该全部失败 if (failures.length > 0) { logSuccess(`测试场景2完成 - 预期有失败记录, 实际失败: ${failures.length}`); return true; } else { logError('测试场景2失败 - 预期有失败记录, 但实际没有'); return false; } } return false; } /** * 测试场景3: 实体中介 - 更新模式(第一次导入) */ async function testEntityImportUpdateMode() { logSection('测试场景3: 实体中介 - 更新模式(第一次导入)'); const taskId = await importData( ENTITY_TEST_FILE, ENTITY_IMPORT_URL, true, // 更新模式 '实体中介导入(更新模式)' ); if (!taskId) { logError('导入任务未创建'); return false; } const status = await pollImportStatus(taskId, ENTITY_STATUS_URL, '实体中介导入'); if (status && (status.status === 'SUCCESS' || status.status === 'PARTIAL_SUCCESS')) { const failures = await getImportFailures(taskId, ENTITY_FAILURES_URL, '实体中介'); logSuccess(`测试场景3完成 - 成功: ${status.successCount}, 失败: ${status.failureCount}`); return true; } return false; } /** * 测试场景4: 实体中介 - 仅新增模式(重复导入应失败) */ async function testEntityImportInsertOnly() { logSection('测试场景4: 实体中介 - 仅新增模式(重复导入)'); const taskId = await importData( ENTITY_TEST_FILE, ENTITY_IMPORT_URL, false, // 仅新增模式 '实体中介导入(仅新增)' ); if (!taskId) { logError('导入任务未创建'); return false; } const status = await pollImportStatus(taskId, ENTITY_STATUS_URL, '实体中介导入'); if (status && (status.status === 'SUCCESS' || status.status === 'PARTIAL_SUCCESS')) { const failures = await getImportFailures(taskId, ENTITY_FAILURES_URL, '实体中介'); // 在仅新增模式下,重复导入应该全部失败 if (failures.length > 0) { logSuccess(`测试场景4完成 - 预期有失败记录, 实际失败: ${failures.length}`); return true; } else { logError('测试场景4失败 - 预期有失败记录, 但实际没有'); return false; } } return false; } /** * 测试场景5: 个人中介 - 再次更新模式(应该更新已有数据) */ async function testPersonImportUpdateAgain() { logSection('测试场景5: 个人中介 - 再次更新模式'); const taskId = await importData( PERSON_TEST_FILE, PERSON_IMPORT_URL, true, // 更新模式 '个人中介导入(再次更新)' ); if (!taskId) { logError('导入任务未创建'); return false; } const status = await pollImportStatus(taskId, PERSON_STATUS_URL, '个人中介导入'); if (status && (status.status === 'SUCCESS' || status.status === 'PARTIAL_SUCCESS')) { const failures = await getImportFailures(taskId, PERSON_FAILURES_URL, '个人中介'); logSuccess(`测试场景5完成 - 成功: ${status.successCount}, 失败: ${status.failureCount}`); return true; } return false; } /** * 主测试流程 */ async function runTests() { console.log('\n╔════════════════════════════════════════════════════════════╗'); console.log('║ 中介导入功能测试 - ON DUPLICATE KEY UPDATE验证 ║'); console.log('╚════════════════════════════════════════════════════════════╝'); const startTime = Date.now(); const results = { passed: 0, failed: 0 }; // 登录 const loginSuccess = await login(); if (!loginSuccess) { logError('无法登录,终止测试'); return; } // 执行测试 const tests = [ { name: '场景1: 个人中介-更新模式(首次)', fn: testPersonImportUpdateMode }, { name: '场景2: 个人中介-仅新增(重复)', fn: testPersonImportInsertOnly }, { name: '场景3: 实体中介-更新模式(首次)', fn: testEntityImportUpdateMode }, { name: '场景4: 实体中介-仅新增(重复)', fn: testEntityImportInsertOnly }, { name: '场景5: 个人中介-再次更新', fn: testPersonImportUpdateAgain } ]; for (const test of tests) { try { const passed = await test.fn(); if (passed) { results.passed++; } else { results.failed++; } await sleep(2000); // 测试之间间隔 } catch (error) { logError(`${test.name} 执行异常: ${error.message}`); results.failed++; } } // 输出测试结果摘要 const duration = ((Date.now() - startTime) / 1000).toFixed(2); console.log('\n' + '='.repeat(60)); log('测试结果摘要', 'yellow'); console.log('='.repeat(60)); logSuccess(`通过: ${results.passed}/${tests.length}`); if (results.failed > 0) { logError(`失败: ${results.failed}/${tests.length}`); } logInfo(`总耗时: ${duration}秒`); console.log('='.repeat(60) + '\n'); } // 运行测试 runTests().catch(error => { logError(`测试运行失败: ${error.message}`); console.error(error); process.exit(1); });