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,299 @@
/**
* Session控制器
*
* API端点
* - POST /sessions/upload 上传Excel文件创建Session
* - GET /sessions/:id 获取Session信息
* - GET /sessions/:id/preview 获取预览数据前100行
* - GET /sessions/:id/full 获取完整数据
* - DELETE /sessions/:id 删除Session
* - POST /sessions/:id/heartbeat 更新心跳
*
* @module SessionController
*/
import { FastifyRequest, FastifyReply } from 'fastify';
import { MultipartFile } from '@fastify/multipart';
import { logger } from '../../../../common/logging/index.js';
import { sessionService } from '../services/SessionService.js';
import { dataProcessService } from '../services/DataProcessService.js';
// ==================== 请求参数类型定义 ====================
interface SessionIdParams {
id: string;
}
// ==================== 控制器 ====================
export class SessionController {
/**
* 上传Excel文件创建Session
*
* POST /api/v1/dc/tool-c/sessions/upload
*/
async upload(request: FastifyRequest, reply: FastifyReply) {
try {
logger.info('[SessionController] 收到文件上传请求');
// 1. 获取multipart数据
const data = await request.file();
if (!data) {
return reply.code(400).send({
success: false,
error: '未找到上传的文件',
});
}
const file = data as MultipartFile;
const fileName = file.filename;
logger.info(`[SessionController] 文件名: ${fileName}`);
// 2. 读取文件到Buffer
const fileBuffer = await file.toBuffer();
// 3. 验证文件
const validation = dataProcessService.validateFile(fileBuffer, fileName);
if (!validation.valid) {
return reply.code(400).send({
success: false,
error: validation.error,
});
}
// 4. 获取用户ID从请求中提取实际部署时从JWT获取
// TODO: 从JWT token中获取userId
const userId = (request as any).userId || 'test-user-001';
// 5. 创建Session
const session = await sessionService.createSession(
userId,
fileName,
fileBuffer
);
logger.info(`[SessionController] Session创建成功: ${session.id}`);
// 6. 返回Session信息
return reply.code(201).send({
success: true,
message: 'Session创建成功',
data: {
sessionId: session.id,
fileName: session.fileName,
fileSize: dataProcessService.formatFileSize(session.fileSize),
totalRows: session.totalRows,
totalCols: session.totalCols,
columns: session.columns,
expiresAt: session.expiresAt,
createdAt: session.createdAt,
},
});
} catch (error: any) {
logger.error(`[SessionController] 文件上传失败: ${error.message}`);
return reply.code(500).send({
success: false,
error: error.message || '文件上传失败,请重试',
});
}
}
/**
* 获取Session信息只含元数据
*
* GET /api/v1/dc/tool-c/sessions/:id
*/
async getSession(
request: FastifyRequest<{ Params: SessionIdParams }>,
reply: FastifyReply
) {
try {
const { id } = request.params;
logger.info(`[SessionController] 获取Session: ${id}`);
const session = await sessionService.getSession(id);
return reply.code(200).send({
success: true,
data: {
sessionId: session.id,
fileName: session.fileName,
fileSize: dataProcessService.formatFileSize(session.fileSize),
totalRows: session.totalRows,
totalCols: session.totalCols,
columns: session.columns,
encoding: session.encoding,
expiresAt: session.expiresAt,
createdAt: session.createdAt,
updatedAt: session.updatedAt,
},
});
} catch (error: any) {
logger.error(`[SessionController] 获取Session失败: ${error.message}`);
const statusCode = error.message.includes('不存在') || error.message.includes('过期')
? 404
: 500;
return reply.code(statusCode).send({
success: false,
error: error.message || '获取Session失败',
});
}
}
/**
* 获取预览数据前100行
*
* GET /api/v1/dc/tool-c/sessions/:id/preview
*/
async getPreviewData(
request: FastifyRequest<{ Params: SessionIdParams }>,
reply: FastifyReply
) {
try {
const { id } = request.params;
logger.info(`[SessionController] 获取预览数据: ${id}`);
const result = await sessionService.getPreviewData(id);
return reply.code(200).send({
success: true,
data: {
sessionId: result.id,
fileName: result.fileName,
totalRows: result.totalRows,
totalCols: result.totalCols,
columns: result.columns,
previewRows: result.previewData.length,
previewData: result.previewData,
},
});
} catch (error: any) {
logger.error(`[SessionController] 获取预览数据失败: ${error.message}`);
const statusCode = error.message.includes('不存在') || error.message.includes('过期')
? 404
: 500;
return reply.code(statusCode).send({
success: false,
error: error.message || '获取预览数据失败',
});
}
}
/**
* 获取完整数据
*
* GET /api/v1/dc/tool-c/sessions/:id/full
*/
async getFullData(
request: FastifyRequest<{ Params: SessionIdParams }>,
reply: FastifyReply
) {
try {
const { id } = request.params;
logger.info(`[SessionController] 获取完整数据: ${id}`);
const data = await sessionService.getFullData(id);
return reply.code(200).send({
success: true,
data: {
sessionId: id,
totalRows: data.length,
data,
},
});
} catch (error: any) {
logger.error(`[SessionController] 获取完整数据失败: ${error.message}`);
const statusCode = error.message.includes('不存在') || error.message.includes('过期')
? 404
: 500;
return reply.code(statusCode).send({
success: false,
error: error.message || '获取完整数据失败',
});
}
}
/**
* 删除Session
*
* DELETE /api/v1/dc/tool-c/sessions/:id
*/
async deleteSession(
request: FastifyRequest<{ Params: SessionIdParams }>,
reply: FastifyReply
) {
try {
const { id } = request.params;
logger.info(`[SessionController] 删除Session: ${id}`);
await sessionService.deleteSession(id);
return reply.code(200).send({
success: true,
message: 'Session删除成功',
});
} catch (error: any) {
logger.error(`[SessionController] 删除Session失败: ${error.message}`);
return reply.code(500).send({
success: false,
error: error.message || '删除Session失败',
});
}
}
/**
* 更新心跳(延长过期时间)
*
* POST /api/v1/dc/tool-c/sessions/:id/heartbeat
*/
async updateHeartbeat(
request: FastifyRequest<{ Params: SessionIdParams }>,
reply: FastifyReply
) {
try {
const { id } = request.params;
logger.info(`[SessionController] 更新心跳: ${id}`);
const newExpiresAt = await sessionService.updateHeartbeat(id);
return reply.code(200).send({
success: true,
message: '心跳更新成功',
data: {
sessionId: id,
expiresAt: newExpiresAt,
},
});
} catch (error: any) {
logger.error(`[SessionController] 更新心跳失败: ${error.message}`);
const statusCode = error.message.includes('不存在')
? 404
: 500;
return reply.code(statusCode).send({
success: false,
error: error.message || '更新心跳失败',
});
}
}
}
// ==================== 导出单例实例 ====================
export const sessionController = new SessionController();