feat(dc/tool-c): Day 2 - Session管理与数据处理完成

核心功能:
- 数据库: 创建dc_tool_c_sessions表 (12字段, 3索引)
- 服务层: SessionService (383行), DataProcessService (303行)
- 控制器: SessionController (300行, 6个API端点)
- 路由: 新增6个Session管理路由
- 测试: 7个API测试全部通过 (100%)

技术亮点:
- 零落盘架构: Excel内存解析, OSS存储
- Session管理: 10分钟过期, 心跳延长机制
- 云原生规范: storage/logger/prisma全平台复用
- 完整测试: 上传/预览/完整数据/删除/心跳

文件清单:
- backend/prisma/schema.prisma (新增DcToolCSession模型)
- backend/prisma/migrations/create_tool_c_session.sql
- backend/scripts/create-tool-c-table.mjs
- backend/src/modules/dc/tool-c/services/ (SessionService, DataProcessService)
- backend/src/modules/dc/tool-c/controllers/SessionController.ts
- backend/src/modules/dc/tool-c/routes/index.ts
- backend/test-tool-c-day2.mjs
- docs/03-业务模块/DC-数据清洗整理/00-工具C当前状态与开发指南.md
- docs/03-业务模块/DC-数据清洗整理/06-开发记录/2025-12-06_工具C_Day2开发完成总结.md

代码统计: ~1900行
测试结果: 7/7 通过 (100%)
云原生规范: 完全符合
This commit is contained in:
2025-12-06 22:12:47 +08:00
parent 8be741cd52
commit 2348234013
13 changed files with 3466 additions and 0 deletions

View File

@@ -0,0 +1,382 @@
/**
* Tool C Day 2 API测试脚本
*
* 测试内容:
* 1. 创建测试Excel文件
* 2. 上传文件创建Session
* 3. 获取Session信息
* 4. 获取预览数据
* 5. 获取完整数据
* 6. 更新心跳
* 7. 删除Session
*
* 执行方式node test-tool-c-day2.mjs
*/
import FormData from 'form-data';
import axios from 'axios';
import * as XLSX from 'xlsx';
import { Buffer } from 'buffer';
const BASE_URL = 'http://localhost:3000';
const API_PREFIX = '/api/v1/dc/tool-c';
// ==================== 辅助函数 ====================
function printSection(title) {
console.log('\n' + '='.repeat(70));
console.log(` ${title}`);
console.log('='.repeat(70) + '\n');
}
function printSuccess(message) {
console.log('✅ ' + message);
}
function printError(message) {
console.log('❌ ' + message);
}
function printInfo(message) {
console.log(' ' + message);
}
// ==================== 创建测试Excel文件 ====================
function createTestExcelFile() {
printSection('创建测试Excel文件');
// 创建医疗数据
const testData = [
{ patient_id: 'P001', name: '张三', age: 25, gender: '男', diagnosis: '感冒', sbp: 120, dbp: 80 },
{ patient_id: 'P002', name: '李四', age: 65, gender: '女', diagnosis: '高血压', sbp: 150, dbp: 95 },
{ patient_id: 'P003', name: '王五', age: 45, gender: '男', diagnosis: '糖尿病', sbp: 135, dbp: 85 },
{ patient_id: 'P004', name: '赵六', age: 70, gender: '女', diagnosis: '冠心病', sbp: 160, dbp: 100 },
{ patient_id: 'P005', name: '钱七', age: 35, gender: '男', diagnosis: '胃炎', sbp: 110, dbp: 70 },
{ patient_id: 'P006', name: '孙八', age: 55, gender: '女', diagnosis: '肺炎', sbp: 125, dbp: 82 },
{ patient_id: 'P007', name: '周九', age: 48, gender: '男', diagnosis: '肝炎', sbp: 130, dbp: 88 },
{ patient_id: 'P008', name: '吴十', age: 62, gender: '女', diagnosis: '关节炎', sbp: 145, dbp: 92 },
];
// 创建工作簿
const ws = XLSX.utils.json_to_sheet(testData);
const wb = XLSX.utils.book_new();
XLSX.utils.book_append_sheet(wb, ws, 'Sheet1');
// 生成Buffer
const excelBuffer = XLSX.write(wb, { type: 'buffer', bookType: 'xlsx' });
printSuccess(`测试文件创建成功: ${testData.length}行 x ${Object.keys(testData[0]).length}`);
printInfo(`文件大小: ${(excelBuffer.length / 1024).toFixed(2)} KB`);
return {
buffer: excelBuffer,
fileName: 'test-medical-data.xlsx',
expectedRows: testData.length,
expectedCols: Object.keys(testData[0]).length,
};
}
// ==================== API测试函数 ====================
async function testUploadFile(excelData) {
printSection('测试1: 上传Excel文件创建Session');
try {
const form = new FormData();
form.append('file', excelData.buffer, {
filename: excelData.fileName,
contentType: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
});
printInfo(`上传文件: ${excelData.fileName}`);
const response = await axios.post(
`${BASE_URL}${API_PREFIX}/sessions/upload`,
form,
{
headers: form.getHeaders(),
timeout: 10000,
}
);
if (response.status === 201 && response.data.success) {
printSuccess('文件上传成功');
console.log('响应数据:', JSON.stringify(response.data.data, null, 2));
const { sessionId, totalRows, totalCols, columns } = response.data.data;
// 验证数据
if (totalRows === excelData.expectedRows && totalCols === excelData.expectedCols) {
printSuccess(`数据验证通过: ${totalRows}行 x ${totalCols}`);
} else {
printError(`数据不匹配: 预期${excelData.expectedRows}行x${excelData.expectedCols}列, 实际${totalRows}行x${totalCols}`);
}
printInfo(`Session ID: ${sessionId}`);
printInfo(`列名: ${columns.join(', ')}`);
return sessionId;
} else {
printError('上传失败: ' + JSON.stringify(response.data));
return null;
}
} catch (error) {
printError('上传异常: ' + (error.response?.data?.error || error.message));
if (error.response) {
console.log('错误详情:', JSON.stringify(error.response.data, null, 2));
}
return null;
}
}
async function testGetSession(sessionId) {
printSection('测试2: 获取Session信息');
try {
printInfo(`Session ID: ${sessionId}`);
const response = await axios.get(
`${BASE_URL}${API_PREFIX}/sessions/${sessionId}`,
{ timeout: 5000 }
);
if (response.status === 200 && response.data.success) {
printSuccess('Session信息获取成功');
console.log('Session信息:', JSON.stringify(response.data.data, null, 2));
return true;
} else {
printError('获取失败');
return false;
}
} catch (error) {
printError('获取异常: ' + (error.response?.data?.error || error.message));
return false;
}
}
async function testGetPreviewData(sessionId) {
printSection('测试3: 获取预览数据前100行');
try {
printInfo(`Session ID: ${sessionId}`);
const response = await axios.get(
`${BASE_URL}${API_PREFIX}/sessions/${sessionId}/preview`,
{ timeout: 10000 }
);
if (response.status === 200 && response.data.success) {
printSuccess('预览数据获取成功');
const { totalRows, previewRows, previewData } = response.data.data;
printInfo(`总行数: ${totalRows}, 预览行数: ${previewRows}`);
printInfo(`预览数据前3行:`);
console.log(JSON.stringify(previewData.slice(0, 3), null, 2));
return true;
} else {
printError('获取预览数据失败');
return false;
}
} catch (error) {
printError('获取预览数据异常: ' + (error.response?.data?.error || error.message));
return false;
}
}
async function testGetFullData(sessionId) {
printSection('测试4: 获取完整数据');
try {
printInfo(`Session ID: ${sessionId}`);
const response = await axios.get(
`${BASE_URL}${API_PREFIX}/sessions/${sessionId}/full`,
{ timeout: 10000 }
);
if (response.status === 200 && response.data.success) {
printSuccess('完整数据获取成功');
const { totalRows, data } = response.data.data;
printInfo(`总行数: ${totalRows}`);
printInfo(`完整数据前2行:`);
console.log(JSON.stringify(data.slice(0, 2), null, 2));
return true;
} else {
printError('获取完整数据失败');
return false;
}
} catch (error) {
printError('获取完整数据异常: ' + (error.response?.data?.error || error.message));
return false;
}
}
async function testUpdateHeartbeat(sessionId) {
printSection('测试5: 更新心跳');
try {
printInfo(`Session ID: ${sessionId}`);
const response = await axios.post(
`${BASE_URL}${API_PREFIX}/sessions/${sessionId}/heartbeat`,
{},
{ timeout: 5000 }
);
if (response.status === 200 && response.data.success) {
printSuccess('心跳更新成功');
console.log('新过期时间:', response.data.data.expiresAt);
return true;
} else {
printError('心跳更新失败');
return false;
}
} catch (error) {
printError('心跳更新异常: ' + (error.response?.data?.error || error.message));
return false;
}
}
async function testDeleteSession(sessionId) {
printSection('测试6: 删除Session');
try {
printInfo(`Session ID: ${sessionId}`);
const response = await axios.delete(
`${BASE_URL}${API_PREFIX}/sessions/${sessionId}`,
{ timeout: 5000 }
);
if (response.status === 200 && response.data.success) {
printSuccess('Session删除成功');
return true;
} else {
printError('Session删除失败');
return false;
}
} catch (error) {
printError('Session删除异常: ' + (error.response?.data?.error || error.message));
return false;
}
}
async function testGetDeletedSession(sessionId) {
printSection('测试7: 验证Session已删除');
try {
printInfo(`尝试获取已删除的Session: ${sessionId}`);
const response = await axios.get(
`${BASE_URL}${API_PREFIX}/sessions/${sessionId}`,
{ timeout: 5000 }
);
printError('Session仍然存在不应该');
return false;
} catch (error) {
if (error.response?.status === 404) {
printSuccess('Session已正确删除返回404');
return true;
} else {
printError('未预期的错误: ' + error.message);
return false;
}
}
}
// ==================== 主测试函数 ====================
async function runAllTests() {
console.log('\n' + '🚀'.repeat(35));
console.log(' Tool C Day 2 API测试');
console.log('🚀'.repeat(35));
const results = {};
try {
// 创建测试文件
const excelData = createTestExcelFile();
// 测试1: 上传文件
const sessionId = await testUploadFile(excelData);
results['上传文件'] = !!sessionId;
if (!sessionId) {
printError('上传失败,后续测试无法继续');
return results;
}
// 等待1秒
await new Promise(resolve => setTimeout(resolve, 1000));
// 测试2: 获取Session信息
results['获取Session'] = await testGetSession(sessionId);
await new Promise(resolve => setTimeout(resolve, 500));
// 测试3: 获取预览数据
results['获取预览数据'] = await testGetPreviewData(sessionId);
await new Promise(resolve => setTimeout(resolve, 500));
// 测试4: 获取完整数据
results['获取完整数据'] = await testGetFullData(sessionId);
await new Promise(resolve => setTimeout(resolve, 500));
// 测试5: 更新心跳
results['更新心跳'] = await testUpdateHeartbeat(sessionId);
await new Promise(resolve => setTimeout(resolve, 500));
// 测试6: 删除Session
results['删除Session'] = await testDeleteSession(sessionId);
await new Promise(resolve => setTimeout(resolve, 500));
// 测试7: 验证删除
results['验证删除'] = await testGetDeletedSession(sessionId);
} catch (error) {
printError('测试过程中发生异常: ' + error.message);
console.error(error);
}
// 汇总结果
printSection('测试结果汇总');
let passed = 0;
let total = 0;
for (const [testName, result] of Object.entries(results)) {
total++;
if (result) {
passed++;
console.log(`${testName.padEnd(20)}: ✅ 通过`);
} else {
console.log(`${testName.padEnd(20)}: ❌ 失败`);
}
}
console.log('\n' + '-'.repeat(70));
console.log(`总计: ${passed}/${total} 通过 (${((passed/total)*100).toFixed(1)}%)`);
console.log('-'.repeat(70));
if (passed === total) {
console.log('\n🎉 所有测试通过Day 2 Session管理功能完成\n');
} else {
console.log(`\n⚠️ 有 ${total - passed} 个测试失败,请检查\n`);
}
}
// 执行测试
runAllTests()
.then(() => {
console.log('测试完成');
process.exit(0);
})
.catch((error) => {
console.error('测试失败:', error);
process.exit(1);
});