| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170 |
- 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<void> {
- 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<void> {
- 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);
- }
|