diff --git a/docs/frontend-sse-integration.md b/docs/frontend-sse-integration.md
new file mode 100644
index 0000000..8320d79
--- /dev/null
+++ b/docs/frontend-sse-integration.md
@@ -0,0 +1,357 @@
+# SSE 前端对接文档
+
+## 概述
+
+本文档描述前端如何与后端 SSE (Server-Sent Events) 服务进行对接,实现异步任务的实时进度推送。
+
+### 核心特性
+
+- **用户绑定**:SSE 通道通过 `userId` 绑定,一个用户只需建立一个连接
+- **多业务复用**:同一连接可接收多种业务类型的消息(项目初始化、系统通知等)
+- **类型区分**:通过消息中的 `type` 字段区分不同业务
+
+---
+
+## 消息格式
+
+所有 SSE 消息采用统一格式:
+
+```json
+{
+ "type": "project-init", // 业务类型
+ "event": "progress", // 事件名称
+ "userId": "user_123", // 用户ID
+ "data": { ... }, // 业务数据
+ "timestamp": "2024-01-01T10:00:00"
+}
+```
+
+### 业务类型 (type)
+
+| 类型 | 说明 |
+|------|------|
+| `project-init` | 项目初始化任务进度 |
+| `system-notification` | 系统通知 |
+| `task-notification` | 任务通知 |
+| `system` | 系统事件(连接成功等) |
+
+### 事件名称 (event)
+
+#### 项目初始化 (type=project-init)
+
+| 事件 | 说明 | 数据结构 |
+|------|------|----------|
+| `submitted` | 任务已提交 | `{ taskId, message }` |
+| `progress` | 进度更新 | `ProjectInitTaskVO` |
+| `complete` | 任务完成 | `ProjectInitTaskVO` |
+| `error` | 执行错误 | `{ error }` |
+
+#### 系统事件 (type=system)
+
+| 事件 | 说明 |
+|------|------|
+| `connected` | SSE 连接成功 |
+
+---
+
+## 对接步骤
+
+### 1. 建立 SSE 连接
+
+```javascript
+// 使用用户ID建立连接
+const userId = 'user_123'; // 当前登录用户ID
+const eventSource = new EventSource(`/api/v1/sse/connect/${userId}`);
+
+// 监听连接成功事件
+eventSource.addEventListener('connected', (e) => {
+ const message = JSON.parse(e.data);
+ console.log('SSE连接成功:', message);
+ // { type: "system", event: "connected", userId: "user_123", data: {...} }
+});
+```
+
+### 2. 监听业务消息
+
+```javascript
+// 监听项目初始化进度
+eventSource.addEventListener('progress', (e) => {
+ const message = JSON.parse(e.data);
+
+ // 根据 type 字段处理不同业务
+ switch(message.type) {
+ case 'project-init':
+ handleProjectInitProgress(message.data);
+ break;
+ case 'system-notification':
+ handleSystemNotification(message.data);
+ break;
+ case 'task-notification':
+ handleTaskNotification(message.data);
+ break;
+ }
+});
+
+// 监听任务完成
+eventSource.addEventListener('complete', (e) => {
+ const message = JSON.parse(e.data);
+ if (message.type === 'project-init') {
+ console.log('项目初始化完成:', message.data);
+ // data 包含完整的 ProjectInitTaskVO,包括 result 字段
+ }
+});
+
+// 监听错误
+eventSource.addEventListener('error', (e) => {
+ const message = JSON.parse(e.data);
+ console.error('任务执行错误:', message.data.error);
+});
+```
+
+### 3. 提交项目初始化任务
+
+```javascript
+async function submitProjectInitTask(file) {
+ const formData = new FormData();
+ formData.append('userId', userId); // 必须与 SSE 连接时的 userId 一致
+ formData.append('file', file);
+
+ const response = await fetch('/api/v1/project-init/sse/submit-task', {
+ method: 'POST',
+ body: formData
+ });
+
+ const result = await response.json();
+
+ if (result.code === 200) {
+ console.log('任务提交成功:', result.data.taskId);
+ // 进度将通过已建立的 SSE 连接推送
+ } else {
+ console.error('提交失败:', result.message);
+ }
+}
+```
+
+### 4. 关闭连接
+
+```javascript
+// 页面卸载时关闭连接
+window.addEventListener('beforeunload', () => {
+ // 可选:调用后端关闭接口
+ fetch(`/api/v1/sse/close/${userId}`, { method: 'POST' });
+ eventSource.close();
+});
+```
+
+---
+
+## 完整示例
+
+```javascript
+class SseClient {
+ constructor(userId) {
+ this.userId = userId;
+ this.eventSource = null;
+ this.listeners = new Map();
+ }
+
+ // 建立连接
+ connect() {
+ this.eventSource = new EventSource(`/api/v1/sse/connect/${this.userId}`);
+
+ // 系统事件
+ this.eventSource.addEventListener('connected', (e) => {
+ console.log('SSE连接成功');
+ this.emit('connected', JSON.parse(e.data));
+ });
+
+ // 项目初始化事件
+ this.eventSource.addEventListener('submitted', (e) => {
+ const msg = JSON.parse(e.data);
+ if (msg.type === 'project-init') {
+ this.emit('project-init-submitted', msg.data);
+ }
+ });
+
+ this.eventSource.addEventListener('progress', (e) => {
+ const msg = JSON.parse(e.data);
+ if (msg.type === 'project-init') {
+ this.emit('project-init-progress', msg.data);
+ }
+ });
+
+ this.eventSource.addEventListener('complete', (e) => {
+ const msg = JSON.parse(e.data);
+ if (msg.type === 'project-init') {
+ this.emit('project-init-complete', msg.data);
+ }
+ });
+
+ this.eventSource.addEventListener('error', (e) => {
+ const msg = JSON.parse(e.data);
+ this.emit('error', msg.data);
+ });
+ }
+
+ // 提交项目初始化任务
+ async submitProjectInitTask(file) {
+ const formData = new FormData();
+ formData.append('userId', this.userId);
+ formData.append('file', file);
+
+ const response = await fetch('/api/v1/project-init/sse/submit-task', {
+ method: 'POST',
+ body: formData
+ });
+
+ return response.json();
+ }
+
+ // 事件监听
+ on(event, callback) {
+ if (!this.listeners.has(event)) {
+ this.listeners.set(event, []);
+ }
+ this.listeners.get(event).push(callback);
+ }
+
+ emit(event, data) {
+ const callbacks = this.listeners.get(event);
+ if (callbacks) {
+ callbacks.forEach(cb => cb(data));
+ }
+ }
+
+ // 关闭连接
+ close() {
+ if (this.eventSource) {
+ this.eventSource.close();
+ }
+ }
+}
+
+// 使用示例
+const sseClient = new SseClient('user_123');
+
+// 监听进度
+sseClient.on('project-init-progress', (data) => {
+ console.log(`进度: ${data.progress}%, ${data.progressMessage}`);
+ // 更新进度条
+});
+
+sseClient.on('project-init-complete', (data) => {
+ console.log('完成:', data.result);
+ // 显示结果
+});
+
+// 建立连接
+sseClient.connect();
+
+// 提交任务
+document.getElementById('uploadBtn').addEventListener('click', async () => {
+ const file = document.getElementById('fileInput').files[0];
+ const result = await sseClient.submitProjectInitTask(file);
+ console.log('提交结果:', result);
+});
+```
+
+---
+
+## 数据结构
+
+### ProjectInitTaskVO
+
+```typescript
+interface ProjectInitTaskVO {
+ taskId: string; // 任务ID
+ status: string; // 状态: pending/processing/completed/failed
+ statusDesc: string; // 状态描述
+ progress: number; // 进度百分比 (0-100)
+ progressMessage: string; // 进度描述
+ originalFilename: string; // 原始文件名
+ createTime: string; // 创建时间
+ startTime: string; // 开始时间
+ completeTime: string; // 完成时间
+ result?: ProjectInitResult; // 结果数据(完成时)
+ errorMessage?: string; // 错误信息(失败时)
+}
+```
+
+### 进度阶段说明
+
+| 进度 | 阶段 | 说明 |
+|------|------|------|
+| 0% | pending | 任务已提交,等待处理 |
+| 10% | processing | 开始处理,正在上传文件 |
+| 30% | processing | 文件上传完成,读取内容 |
+| 50% | processing | 文件读取完成,AI分析中 |
+| 60% | processing | AI解析项目结构 |
+| 100% | completed | 项目预览数据生成成功 |
+
+---
+
+## 错误处理
+
+### 连接错误
+
+```javascript
+eventSource.onerror = (error) => {
+ console.error('SSE连接错误:', error);
+ // 可尝试重连
+};
+```
+
+### 提交任务错误
+
+```javascript
+// HTTP 响应错误
+if (response.code !== 200) {
+ console.error('提交失败:', response.message);
+ // 可能的错误:
+ // - "上传文件不能为空"
+ // - "用户未建立SSE连接,请先调用 /api/v1/sse/connect/{userId}"
+}
+
+// 任务执行错误(通过 SSE 推送)
+eventSource.addEventListener('error', (e) => {
+ const msg = JSON.parse(e.data);
+ console.error('任务执行错误:', msg.data.error);
+});
+```
+
+---
+
+## 注意事项
+
+1. **用户ID一致性**:SSE 连接和提交任务时必须使用相同的 `userId`
+
+2. **连接超时**:默认 30 分钟超时,超时后需要重新建立连接
+
+3. **单用户单连接**:一个 `userId` 同时只能有一个 SSE 连接,新建连接会自动关闭旧连接
+
+4. **文件大小限制**:建议前端先做文件大小校验,避免上传过大文件
+
+5. **重连机制**:建议实现自动重连机制,当连接断开时自动重新建立连接
+
+```javascript
+// 简单重连示例
+function connectWithRetry(userId, maxRetries = 3) {
+ let retries = 0;
+
+ const connect = () => {
+ const es = new EventSource(`/api/v1/sse/connect/${userId}`);
+
+ es.onerror = (e) => {
+ es.close();
+ retries++;
+ if (retries < maxRetries) {
+ setTimeout(connect, 3000); // 3秒后重试
+ }
+ };
+
+ return es;
+ };
+
+ return connect();
+}
+```
diff --git a/pom.xml b/pom.xml
index a59c201..f98f389 100644
--- a/pom.xml
+++ b/pom.xml
@@ -145,12 +145,6 @@
1.39.0
-
-
- com.larksuite.oapi
- oapi-sdk
- 2.5.3
-
org.springframework.boot
diff --git a/src/main/java/cn/yinlihupo/common/config/AsyncConfig.java b/src/main/java/cn/yinlihupo/common/config/AsyncConfig.java
new file mode 100644
index 0000000..007c93a
--- /dev/null
+++ b/src/main/java/cn/yinlihupo/common/config/AsyncConfig.java
@@ -0,0 +1,46 @@
+package cn.yinlihupo.common.config;
+
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.scheduling.annotation.EnableAsync;
+import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
+
+import java.util.concurrent.Executor;
+import java.util.concurrent.ThreadPoolExecutor;
+
+/**
+ * 异步任务配置类
+ */
+@Slf4j
+@Configuration
+@EnableAsync
+public class AsyncConfig {
+
+ /**
+ * 项目初始化任务线程池
+ */
+ @Bean("projectInitTaskExecutor")
+ public Executor projectInitTaskExecutor() {
+ ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
+ // 核心线程数
+ executor.setCorePoolSize(2);
+ // 最大线程数
+ executor.setMaxPoolSize(5);
+ // 队列容量
+ executor.setQueueCapacity(50);
+ // 线程名称前缀
+ executor.setThreadNamePrefix("project-init-");
+ // 拒绝策略:由调用线程处理
+ executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
+ // 等待所有任务完成后再关闭线程池
+ executor.setWaitForTasksToCompleteOnShutdown(true);
+ // 等待时间(秒)
+ executor.setAwaitTerminationSeconds(60);
+ // 初始化
+ executor.initialize();
+
+ log.info("项目初始化异步任务线程池初始化完成");
+ return executor;
+ }
+}
diff --git a/src/main/java/cn/yinlihupo/common/enums/AsyncTaskStatus.java b/src/main/java/cn/yinlihupo/common/enums/AsyncTaskStatus.java
new file mode 100644
index 0000000..f2027a1
--- /dev/null
+++ b/src/main/java/cn/yinlihupo/common/enums/AsyncTaskStatus.java
@@ -0,0 +1,55 @@
+package cn.yinlihupo.common.enums;
+
+import lombok.Getter;
+
+/**
+ * 异步任务状态枚举
+ */
+@Getter
+public enum AsyncTaskStatus {
+
+ /**
+ * 待处理
+ */
+ PENDING("pending", "待处理"),
+
+ /**
+ * 处理中
+ */
+ PROCESSING("processing", "处理中"),
+
+ /**
+ * 已完成
+ */
+ COMPLETED("completed", "已完成"),
+
+ /**
+ * 失败
+ */
+ FAILED("failed", "失败"),
+
+ /**
+ * 已取消
+ */
+ CANCELLED("cancelled", "已取消");
+
+ private final String code;
+ private final String description;
+
+ AsyncTaskStatus(String code, String description) {
+ this.code = code;
+ this.description = description;
+ }
+
+ /**
+ * 根据code获取枚举
+ */
+ public static AsyncTaskStatus fromCode(String code) {
+ for (AsyncTaskStatus status : values()) {
+ if (status.code.equals(code)) {
+ return status;
+ }
+ }
+ return null;
+ }
+}
diff --git a/src/main/java/cn/yinlihupo/common/sse/SseChannelManager.java b/src/main/java/cn/yinlihupo/common/sse/SseChannelManager.java
new file mode 100644
index 0000000..0aad48e
--- /dev/null
+++ b/src/main/java/cn/yinlihupo/common/sse/SseChannelManager.java
@@ -0,0 +1,179 @@
+package cn.yinlihupo.common.sse;
+
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.stereotype.Component;
+import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;
+
+import java.io.IOException;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.function.Consumer;
+
+/**
+ * SSE 通道管理器
+ * 统一管理用户与 SSE 通道的关联,支持多业务类型消息推送
+ */
+@Slf4j
+@Component
+public class SseChannelManager {
+
+ /**
+ * 用户通道映射:userId -> SseEmitter
+ */
+ private final Map userChannelMap = new ConcurrentHashMap<>();
+
+ /**
+ * 默认超时时间:30分钟(适合长连接场景)
+ */
+ private static final long DEFAULT_TIMEOUT = 30 * 60 * 1000;
+
+ /**
+ * 创建用户通道
+ *
+ * @param userId 用户ID
+ * @return SseEmitter
+ */
+ public SseEmitter createChannel(String userId) {
+ return createChannel(userId, DEFAULT_TIMEOUT, null);
+ }
+
+ /**
+ * 创建用户通道
+ *
+ * @param userId 用户ID
+ * @param timeout 超时时间(毫秒)
+ * @return SseEmitter
+ */
+ public SseEmitter createChannel(String userId, long timeout) {
+ return createChannel(userId, timeout, null);
+ }
+
+ /**
+ * 创建用户通道
+ *
+ * @param userId 用户ID
+ * @param timeout 超时时间(毫秒)
+ * @param onCloseCallback 通道关闭回调
+ * @return SseEmitter
+ */
+ public SseEmitter createChannel(String userId, long timeout, Consumer onCloseCallback) {
+ // 关闭已存在的通道
+ closeChannel(userId);
+
+ SseEmitter emitter = new SseEmitter(timeout);
+
+ // 存储通道
+ userChannelMap.put(userId, emitter);
+
+ // 设置回调
+ emitter.onCompletion(() -> {
+ log.debug("SSE通道完成关闭, userId: {}", userId);
+ cleanupChannel(userId);
+ if (onCloseCallback != null) {
+ onCloseCallback.accept(userId);
+ }
+ });
+
+ emitter.onError((e) -> {
+ log.error("SSE通道发生错误, userId: {}", userId, e);
+ cleanupChannel(userId);
+ if (onCloseCallback != null) {
+ onCloseCallback.accept(userId);
+ }
+ });
+
+ emitter.onTimeout(() -> {
+ log.warn("SSE通道超时, userId: {}", userId);
+ cleanupChannel(userId);
+ if (onCloseCallback != null) {
+ onCloseCallback.accept(userId);
+ }
+ });
+
+ log.info("SSE通道创建成功, userId: {}", userId);
+ return emitter;
+ }
+
+ /**
+ * 发送消息到指定用户
+ *
+ * @param userId 用户ID
+ * @param message 消息对象
+ * @return 是否发送成功
+ */
+ public boolean send(String userId, SseMessage message) {
+ SseEmitter emitter = userChannelMap.get(userId);
+ if (emitter == null) {
+ log.warn("用户未建立SSE连接,无法推送, userId: {}", userId);
+ return false;
+ }
+
+ try {
+ emitter.send(SseEmitter.event()
+ .name(message.getEvent())
+ .data(message));
+ return true;
+ } catch (IOException e) {
+ log.error("发送消息失败, userId: {}, event: {}", userId, message.getEvent(), e);
+ closeChannel(userId);
+ return false;
+ }
+ }
+
+ /**
+ * 发送消息到指定用户(简化版)
+ *
+ * @param userId 用户ID
+ * @param type 消息类型
+ * @param event 事件名称
+ * @param data 业务数据
+ * @return 是否发送成功
+ */
+ public boolean send(String userId, String type, String event, Object data) {
+ SseMessage message = SseMessage.of(type, event, userId, data);
+ return send(userId, message);
+ }
+
+ /**
+ * 检查用户是否在线
+ *
+ * @param userId 用户ID
+ * @return 是否在线
+ */
+ public boolean isOnline(String userId) {
+ return userChannelMap.containsKey(userId);
+ }
+
+ /**
+ * 获取在线用户数量
+ *
+ * @return 在线用户数
+ */
+ public int getOnlineCount() {
+ return userChannelMap.size();
+ }
+
+ /**
+ * 关闭用户通道
+ *
+ * @param userId 用户ID
+ */
+ public void closeChannel(String userId) {
+ SseEmitter emitter = userChannelMap.remove(userId);
+ if (emitter != null) {
+ try {
+ emitter.complete();
+ } catch (Exception e) {
+ log.warn("关闭通道时发生异常, userId: {}", userId, e);
+ }
+ }
+ log.info("SSE通道已关闭, userId: {}", userId);
+ }
+
+ /**
+ * 清理通道
+ */
+ private void cleanupChannel(String userId) {
+ userChannelMap.remove(userId);
+ }
+}
diff --git a/src/main/java/cn/yinlihupo/common/sse/SseMessage.java b/src/main/java/cn/yinlihupo/common/sse/SseMessage.java
new file mode 100644
index 0000000..c2919f7
--- /dev/null
+++ b/src/main/java/cn/yinlihupo/common/sse/SseMessage.java
@@ -0,0 +1,76 @@
+package cn.yinlihupo.common.sse;
+
+import lombok.Builder;
+import lombok.Data;
+
+import java.time.LocalDateTime;
+
+/**
+ * SSE 消息包装类
+ * 统一的消息格式,支持多业务类型
+ */
+@Data
+@Builder
+public class SseMessage {
+
+ /**
+ * 消息类型
+ * 例如:project-init、system-notification、task-notification 等
+ */
+ private String type;
+
+ /**
+ * 事件名称
+ * 例如:submitted、progress、complete、error 等
+ */
+ private String event;
+
+ /**
+ * 用户ID
+ */
+ private String userId;
+
+ /**
+ * 业务数据
+ */
+ private Object data;
+
+ /**
+ * 消息时间戳
+ */
+ private LocalDateTime timestamp;
+
+ /**
+ * 创建消息
+ */
+ public static SseMessage of(String type, String event, String userId, Object data) {
+ return SseMessage.builder()
+ .type(type)
+ .event(event)
+ .userId(userId)
+ .data(data)
+ .timestamp(LocalDateTime.now())
+ .build();
+ }
+
+ /**
+ * 创建项目初始化消息
+ */
+ public static SseMessage projectInit(String event, String userId, Object data) {
+ return of("project-init", event, userId, data);
+ }
+
+ /**
+ * 创建系统通知消息
+ */
+ public static SseMessage systemNotify(String event, String userId, Object data) {
+ return of("system-notification", event, userId, data);
+ }
+
+ /**
+ * 创建任务通知消息
+ */
+ public static SseMessage taskNotify(String event, String userId, Object data) {
+ return of("task-notification", event, userId, data);
+ }
+}
diff --git a/src/main/java/cn/yinlihupo/common/util/SpringUtils.java b/src/main/java/cn/yinlihupo/common/util/SpringUtils.java
new file mode 100644
index 0000000..5af0f25
--- /dev/null
+++ b/src/main/java/cn/yinlihupo/common/util/SpringUtils.java
@@ -0,0 +1,100 @@
+package cn.yinlihupo.common.util;
+
+import org.springframework.beans.BeansException;
+import org.springframework.context.ApplicationContext;
+import org.springframework.context.ApplicationContextAware;
+import org.springframework.stereotype.Component;
+
+/**
+ * Spring工具类
+ * 用于在非Spring管理的类中获取Spring容器中的Bean
+ *
+ * @author cheems
+ */
+@Component
+public class SpringUtils implements ApplicationContextAware {
+
+ private static ApplicationContext applicationContext;
+
+ @Override
+ public void setApplicationContext(ApplicationContext context) throws BeansException {
+ SpringUtils.applicationContext = context;
+ }
+
+ /**
+ * 获取ApplicationContext
+ *
+ * @return ApplicationContext
+ */
+ public static ApplicationContext getApplicationContext() {
+ return applicationContext;
+ }
+
+ /**
+ * 通过name获取Bean
+ *
+ * @param name Bean名称
+ * @return Bean实例
+ */
+ public static Object getBean(String name) {
+ return applicationContext.getBean(name);
+ }
+
+ /**
+ * 通过class获取Bean
+ *
+ * @param clazz Bean类型
+ * @param 泛型
+ * @return Bean实例
+ */
+ public static T getBean(Class clazz) {
+ return applicationContext.getBean(clazz);
+ }
+
+ /**
+ * 通过name和class获取Bean
+ *
+ * @param name Bean名称
+ * @param clazz Bean类型
+ * @param 泛型
+ * @return Bean实例
+ */
+ public static T getBean(String name, Class clazz) {
+ return applicationContext.getBean(name, clazz);
+ }
+
+ /**
+ * 判断是否包含Bean
+ *
+ * @param name Bean名称
+ * @return 是否包含
+ */
+ public static boolean containsBean(String name) {
+ return applicationContext.containsBean(name);
+ }
+
+ /**
+ * 判断Bean是否为单例
+ *
+ * @param name Bean名称
+ * @return 是否为单例
+ */
+ public static boolean isSingleton(String name) {
+ return applicationContext.isSingleton(name);
+ }
+
+ /**
+ * 获取Bean的类型
+ *
+ * @param name Bean名称
+ * @return Bean类型
+ */
+ public static Class> getType(String name) {
+ return applicationContext.getType(name);
+ }
+}
+
+
+
+
+
diff --git a/src/main/java/cn/yinlihupo/controller/project/ProjectController.java b/src/main/java/cn/yinlihupo/controller/project/ProjectController.java
index f5283e9..11ca764 100644
--- a/src/main/java/cn/yinlihupo/controller/project/ProjectController.java
+++ b/src/main/java/cn/yinlihupo/controller/project/ProjectController.java
@@ -3,13 +3,18 @@ package cn.yinlihupo.controller.project;
import cn.yinlihupo.common.core.BaseResponse;
import cn.yinlihupo.common.util.ResultUtils;
import cn.yinlihupo.domain.vo.ProjectInitResult;
+import cn.yinlihupo.domain.vo.ProjectInitTaskVO;
import cn.yinlihupo.service.oss.OssService;
+import cn.yinlihupo.service.project.ProjectInitAsyncService;
import cn.yinlihupo.service.project.ProjectService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
+import java.util.HashMap;
+import java.util.Map;
+
/**
* AI项目初始化控制器
* 提供项目文档解析和结构化数据生成功能
@@ -21,6 +26,7 @@ import org.springframework.web.multipart.MultipartFile;
public class ProjectController {
private final ProjectService projectService;
+ private final ProjectInitAsyncService projectInitAsyncService;
private final OssService ossService;
/**
@@ -71,6 +77,98 @@ public class ProjectController {
}
}
+ /**
+ * 异步提交项目初始化预览任务
+ *
+ * @param file 项目资料文件
+ * @return 任务ID和初始状态
+ */
+ @PostMapping("/preview-async")
+ public BaseResponse