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:
299
backend/src/modules/dc/tool-c/controllers/SessionController.ts
Normal file
299
backend/src/modules/dc/tool-c/controllers/SessionController.ts
Normal 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();
|
||||
|
||||
Reference in New Issue
Block a user