task.routes.ts 4.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137
  1. import { IncomingMessage, ServerResponse } from 'node:http';
  2. import { createTask, getTask, getAllTasks, runTaskPipeline } from '../services/task-manager.service.ts';
  3. import { generateExcelBuffer } from '../services/export.service.ts';
  4. import { extractTextFromBuffer } from '../utils/file-parser.ts';
  5. import { sendJson, readMultipartFile } from '../utils/http.ts';
  6. /**
  7. * POST /api/tasks/upload-brief
  8. * 上传 Brief 文件,创建任务
  9. */
  10. export async function handleUploadBrief(request: IncomingMessage, response: ServerResponse): Promise<void> {
  11. try {
  12. console.log('[Route] 收到上传请求, Content-Type:', request.headers['content-type']);
  13. const { fileName, buffer } = await readMultipartFile(request);
  14. console.log(`[Route] 解析文件: ${fileName}, 大小: ${buffer.length} bytes`);
  15. if (!buffer || buffer.length === 0) {
  16. sendJson(response, 400, { ok: false, message: '未收到文件内容' });
  17. return;
  18. }
  19. // 提取文件文本
  20. const briefText = extractTextFromBuffer(buffer, fileName);
  21. // 创建任务
  22. const task = createTask(fileName, briefText);
  23. console.log(`[Route] 任务已创建: ${task.id}, briefText长度: ${briefText.length}`);
  24. sendJson(response, 200, {
  25. taskId: task.id,
  26. fileName: task.fileName,
  27. message: `文件 ${fileName} 已上传,任务已创建`,
  28. });
  29. } catch (error) {
  30. console.error('[Upload] Error:', error);
  31. sendJson(response, 500, { ok: false, message: error instanceof Error ? error.message : '上传失败' });
  32. }
  33. }
  34. /**
  35. * POST /api/tasks/:taskId/start
  36. * 启动任务处理流水线(异步执行)
  37. */
  38. export function handleStartTask(request: IncomingMessage, response: ServerResponse, taskId: string): void {
  39. const task = getTask(taskId);
  40. if (!task) {
  41. sendJson(response, 404, { ok: false, message: '任务不存在' });
  42. return;
  43. }
  44. if (task.status !== 'pending') {
  45. sendJson(response, 400, { ok: false, message: `任务状态为 ${task.status},无法启动` });
  46. return;
  47. }
  48. console.log(`[Route] 启动任务: ${taskId}, 当前状态: ${task.status}`);
  49. // 异步执行流水线,不等待完成
  50. runTaskPipeline(taskId).catch((err) => {
  51. console.error(`[Task ${taskId}] Pipeline error:`, err);
  52. });
  53. sendJson(response, 200, { ok: true, message: '任务已启动,请轮询状态' });
  54. }
  55. /**
  56. * GET /api/tasks/:taskId
  57. * 获取任务状态与进度
  58. */
  59. export function handleGetTask(response: ServerResponse, taskId: string): void {
  60. const task = getTask(taskId);
  61. if (!task) {
  62. sendJson(response, 404, { ok: false, message: '任务不存在' });
  63. return;
  64. }
  65. sendJson(response, 200, {
  66. taskId: task.id,
  67. status: task.status,
  68. fileName: task.fileName,
  69. steps: task.steps,
  70. requirements: task.requirements,
  71. searchCriteria: task.searchCriteria,
  72. totalPool: task.totalPool,
  73. candidateCount: task.candidates.length,
  74. strongCount: task.candidates.filter((c) => c.recommendStatus === '强推荐').length,
  75. backupCount: task.candidates.filter((c) => c.recommendStatus === '备选').length,
  76. error: task.error,
  77. createdAt: task.createdAt,
  78. updatedAt: task.updatedAt,
  79. });
  80. }
  81. /**
  82. * GET /api/tasks
  83. * 获取所有任务列表
  84. */
  85. export function handleListTasks(response: ServerResponse): void {
  86. const tasks = getAllTasks().map((t) => ({
  87. taskId: t.id,
  88. status: t.status,
  89. fileName: t.fileName,
  90. candidateCount: t.candidates.length,
  91. createdAt: t.createdAt,
  92. updatedAt: t.updatedAt,
  93. }));
  94. sendJson(response, 200, { tasks });
  95. }
  96. /**
  97. * POST /api/tasks/:taskId/export
  98. * 导出推荐表 Excel 文件
  99. */
  100. export function handleExportTask(response: ServerResponse, taskId: string): void {
  101. const task = getTask(taskId);
  102. if (!task) {
  103. sendJson(response, 404, { ok: false, message: '任务不存在' });
  104. return;
  105. }
  106. if (task.candidates.length === 0) {
  107. sendJson(response, 400, { ok: false, message: '暂无推荐候选人,无法导出' });
  108. return;
  109. }
  110. const excelBuffer = generateExcelBuffer(task.candidates, task.fileName);
  111. const exportFileName = `${task.fileName.replace(/\.[^.]+$/, '')}-推荐名单.xls`;
  112. response.writeHead(200, {
  113. 'Access-Control-Allow-Origin': '*',
  114. 'Content-Type': 'application/vnd.ms-excel; charset=utf-8',
  115. 'Content-Disposition': `attachment; filename="${encodeURIComponent(exportFileName)}"`,
  116. 'Content-Length': excelBuffer.byteLength,
  117. });
  118. response.end(excelBuffer);
  119. }