import { IncomingMessage, ServerResponse } from 'node:http'; import { createTask, getTask, getAllTasks, runTaskPipeline } from '../services/task-manager.service.ts'; import { generateExcelBuffer } from '../services/export.service.ts'; import { extractTextFromBuffer } from '../utils/file-parser.ts'; import { sendJson, readMultipartFile } from '../utils/http.ts'; import { ingestUploadedCreatorLibrary } from '../services/local-creator-db.service.ts'; /** * POST /api/tasks/upload-brief * 上传 Brief 文件,创建任务 */ export async function handleUploadBrief(request: IncomingMessage, response: ServerResponse): Promise { try { console.log('[Route] 收到上传请求, Content-Type:', request.headers['content-type']); const { fileName, buffer } = await readMultipartFile(request); console.log(`[Route] 解析文件: ${fileName}, 大小: ${buffer.length} bytes`); if (!buffer || buffer.length === 0) { sendJson(response, 400, { ok: false, message: '未收到文件内容' }); return; } // 提取文件文本 const briefText = extractTextFromBuffer(buffer, fileName); // 创建任务 const task = createTask(fileName, briefText); const localCreatorCount = await ingestUploadedCreatorLibrary(buffer, fileName); console.log(`[Route] 任务已创建: ${task.id}, briefText长度: ${briefText.length}`); if (localCreatorCount > 0) { console.log(`[Route] 已从上传文件入库本地达人: ${localCreatorCount} 位`); } sendJson(response, 200, { taskId: task.id, fileName: task.fileName, localCreatorCount, message: `文件 ${fileName} 已上传,任务已创建`, }); } catch (error) { console.error('[Upload] Error:', error); sendJson(response, 500, { ok: false, message: error instanceof Error ? error.message : '上传失败' }); } } /** * POST /api/creator-library/upload * 上传本地达人资料库文件,入库到 PostgreSQL 清洗达人表 */ export async function handleUploadCreatorLibrary(request: IncomingMessage, response: ServerResponse): Promise { try { console.log('[Route] 收到达人资料库上传请求, Content-Type:', request.headers['content-type']); const { fileName, buffer } = await readMultipartFile(request); if (!buffer || buffer.length === 0) { sendJson(response, 400, { ok: false, message: '未收到文件内容' }); return; } const localCreatorCount = await ingestUploadedCreatorLibrary(buffer, fileName); sendJson(response, 200, { ok: true, fileName, localCreatorCount, message: `达人资料库 ${fileName} 已入库 ${localCreatorCount} 位达人`, }); } catch (error) { console.error('[CreatorLibraryUpload] Error:', error); sendJson(response, 500, { ok: false, message: error instanceof Error ? error.message : '达人资料库上传失败' }); } } /** * POST /api/tasks/:taskId/start * 启动任务处理流水线(异步执行) */ export function handleStartTask(request: IncomingMessage, response: ServerResponse, taskId: string): void { const task = getTask(taskId); if (!task) { sendJson(response, 404, { ok: false, message: '任务不存在' }); return; } if (task.status !== 'pending') { sendJson(response, 400, { ok: false, message: `任务状态为 ${task.status},无法启动` }); return; } console.log(`[Route] 启动任务: ${taskId}, 当前状态: ${task.status}`); // 异步执行流水线,不等待完成 runTaskPipeline(taskId).catch((err) => { console.error(`[Task ${taskId}] Pipeline error:`, err); }); sendJson(response, 200, { ok: true, message: '任务已启动,请轮询状态' }); } /** * GET /api/tasks/:taskId * 获取任务状态与进度 */ export function handleGetTask(response: ServerResponse, taskId: string): void { const task = getTask(taskId); if (!task) { sendJson(response, 404, { ok: false, message: '任务不存在' }); return; } sendJson(response, 200, { taskId: task.id, status: task.status, fileName: task.fileName, steps: task.steps, requirements: task.requirements, searchCriteria: task.searchCriteria, totalPool: task.totalPool, candidateCount: task.candidates.length, strongCount: task.candidates.filter((c) => c.recommendStatus === '强推荐').length, backupCount: task.candidates.filter((c) => c.recommendStatus === '备选').length, error: task.error, createdAt: task.createdAt, updatedAt: task.updatedAt, }); } /** * GET /api/tasks * 获取所有任务列表 */ export function handleListTasks(response: ServerResponse): void { const tasks = getAllTasks().map((t) => ({ taskId: t.id, status: t.status, fileName: t.fileName, candidateCount: t.candidates.length, createdAt: t.createdAt, updatedAt: t.updatedAt, })); sendJson(response, 200, { tasks }); } /** * POST /api/tasks/:taskId/export * 导出推荐表 Excel 文件 */ export function handleExportTask(response: ServerResponse, taskId: string): void { const task = getTask(taskId); if (!task) { sendJson(response, 404, { ok: false, message: '任务不存在' }); return; } if (task.candidates.length === 0) { sendJson(response, 400, { ok: false, message: '暂无推荐候选人,无法导出' }); return; } const excelBuffer = generateExcelBuffer(task.candidates, task.fileName); const exportFileName = `${task.fileName.replace(/\.[^.]+$/, '')}-推荐名单.xls`; response.writeHead(200, { 'Access-Control-Allow-Origin': '*', 'Content-Type': 'application/vnd.ms-excel; charset=utf-8', 'Content-Disposition': `attachment; filename="${encodeURIComponent(exportFileName)}"`, 'Content-Length': excelBuffer.byteLength, }); response.end(excelBuffer); }