Files
ylhp-ai-project-manager/src/main/java/cn/yinlihupo/controller/project/ProjectInitSseController.java
JiaoTianBo 6d91be8af5 feat(project): 实现异步项目初始化及SSE进度推送功能
- 新增异步任务线程池配置,支持项目初始化异步执行
- 定义异步任务状态枚举,统一管理任务生命周期状态
- 实现通用SSE通道管理器,支持用户绑定及多业务消息推送
- 创建统一SSE消息结构,支持多业务类型及事件分类
- 提供基础SSE连接管理接口,支持连接建立、状态查询及关闭
- 提供项目初始化异步任务服务接口及实现,支持进度回调和任务取消
- 添加项目初始化异步预览任务接口,支持异步提交、状态查询、结果获取及取消
- 新增项目初始化任务SSE接口,实现任务异步提交与实时进度推送
- 设计前端SSE集成文档,详细说明SSE连接、消息格式和对接步骤
- 添加Spring工具类,方便非Spring管理类获取Bean实例
- 优化项目控制器,整合异步任务相关API接口支持异步项目初始化工作流
2026-03-28 16:57:55 +08:00

119 lines
4.7 KiB
Java
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package cn.yinlihupo.controller.project;
import cn.yinlihupo.common.core.BaseResponse;
import cn.yinlihupo.common.enums.AsyncTaskStatus;
import cn.yinlihupo.common.sse.SseChannelManager;
import cn.yinlihupo.common.sse.SseMessage;
import cn.yinlihupo.common.util.ResultUtils;
import cn.yinlihupo.service.project.ProjectInitAsyncService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;
import java.util.HashMap;
import java.util.Map;
/**
* 项目初始化 SSE 控制器
* 使用通用 SSE 通道管理器,通过 userId 绑定type 字段区分业务
*/
@Slf4j
@RestController
@RequestMapping("/api/v1/project-init")
@RequiredArgsConstructor
public class ProjectInitSseController {
private final ProjectInitAsyncService projectInitAsyncService;
private final SseChannelManager sseChannelManager;
/**
* 消息类型常量
*/
private static final String MESSAGE_TYPE = "project-init";
/**
* 通过 SSE 提交项目初始化任务
* 使用通用 SSE 通道,通过 userId 推送进度
*
* @param userId 用户ID
* @param file 项目资料文件
* @return 提交结果
*/
@PostMapping("/sse/submit-task")
public BaseResponse<Map<String, Object>> submitTaskWithSse(@RequestParam("userId") String userId,
@RequestParam("file") MultipartFile file) {
log.info("用户通过SSE提交任务, userId: {}, 文件名: {}", userId, file.getOriginalFilename());
if (file.isEmpty()) {
return ResultUtils.error("上传文件不能为空");
}
// 检查用户是否在线
if (!sseChannelManager.isOnline(userId)) {
return ResultUtils.error("用户未建立SSE连接请先调用 /api/v1/sse/connect/" + userId);
}
try {
// 提交异步任务,带进度回调
String taskId = projectInitAsyncService.submitPreviewTask(file, taskVO -> {
// 构建消息并推送
SseMessage message = SseMessage.of(MESSAGE_TYPE, "progress", userId, taskVO);
sseChannelManager.send(userId, message);
// 任务完成或失败,推送完成事件
if (isTaskFinished(taskVO.getStatus())) {
SseMessage completeMessage = SseMessage.of(MESSAGE_TYPE, "complete", userId, taskVO);
sseChannelManager.send(userId, completeMessage);
}
});
// 推送任务提交成功事件
Map<String, Object> submittedData = new HashMap<>();
submittedData.put("taskId", taskId);
submittedData.put("message", "任务已提交");
SseMessage submittedMessage = SseMessage.of(MESSAGE_TYPE, "submitted", userId, submittedData);
sseChannelManager.send(userId, submittedMessage);
log.info("任务提交成功并通过SSE推送, userId: {}, taskId: {}", userId, taskId);
Map<String, Object> result = new HashMap<>();
result.put("taskId", taskId);
result.put("message", "任务已提交进度将通过SSE推送");
return ResultUtils.success("提交成功", result);
} catch (Exception e) {
log.error("提交任务失败, userId: {}, error: {}", userId, e.getMessage(), e);
// 推送错误事件
Map<String, Object> errorData = new HashMap<>();
errorData.put("error", e.getMessage());
SseMessage errorMessage = SseMessage.of(MESSAGE_TYPE, "error", userId, errorData);
sseChannelManager.send(userId, errorMessage);
return ResultUtils.error("提交失败: " + e.getMessage());
}
}
// ==================== 工具方法 ====================
private boolean isTaskFinished(String status) {
return AsyncTaskStatus.COMPLETED.getCode().equals(status) ||
AsyncTaskStatus.FAILED.getCode().equals(status) ||
AsyncTaskStatus.CANCELLED.getCode().equals(status);
}
private void sendErrorAndClose(String userId, String errorMessage) {
Map<String, Object> errorData = new HashMap<>();
errorData.put("error", errorMessage);
SseMessage errorMessage_obj = SseMessage.of(MESSAGE_TYPE, "error", userId, errorData);
sseChannelManager.send(userId, errorMessage_obj);
sseChannelManager.closeChannel(userId);
}
}