- 新增异步任务线程池配置,支持项目初始化异步执行 - 定义异步任务状态枚举,统一管理任务生命周期状态 - 实现通用SSE通道管理器,支持用户绑定及多业务消息推送 - 创建统一SSE消息结构,支持多业务类型及事件分类 - 提供基础SSE连接管理接口,支持连接建立、状态查询及关闭 - 提供项目初始化异步任务服务接口及实现,支持进度回调和任务取消 - 添加项目初始化异步预览任务接口,支持异步提交、状态查询、结果获取及取消 - 新增项目初始化任务SSE接口,实现任务异步提交与实时进度推送 - 设计前端SSE集成文档,详细说明SSE连接、消息格式和对接步骤 - 添加Spring工具类,方便非Spring管理类获取Bean实例 - 优化项目控制器,整合异步任务相关API接口支持异步项目初始化工作流
9.3 KiB
9.3 KiB
SSE 前端对接文档
概述
本文档描述前端如何与后端 SSE (Server-Sent Events) 服务进行对接,实现异步任务的实时进度推送。
核心特性
- 用户绑定:SSE 通道通过
userId绑定,一个用户只需建立一个连接 - 多业务复用:同一连接可接收多种业务类型的消息(项目初始化、系统通知等)
- 类型区分:通过消息中的
type字段区分不同业务
消息格式
所有 SSE 消息采用统一格式:
{
"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 连接
// 使用用户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. 监听业务消息
// 监听项目初始化进度
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. 提交项目初始化任务
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. 关闭连接
// 页面卸载时关闭连接
window.addEventListener('beforeunload', () => {
// 可选:调用后端关闭接口
fetch(`/api/v1/sse/close/${userId}`, { method: 'POST' });
eventSource.close();
});
完整示例
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
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 | 项目预览数据生成成功 |
错误处理
连接错误
eventSource.onerror = (error) => {
console.error('SSE连接错误:', error);
// 可尝试重连
};
提交任务错误
// 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);
});
注意事项
-
用户ID一致性:SSE 连接和提交任务时必须使用相同的
userId -
连接超时:默认 30 分钟超时,超时后需要重新建立连接
-
单用户单连接:一个
userId同时只能有一个 SSE 连接,新建连接会自动关闭旧连接 -
文件大小限制:建议前端先做文件大小校验,避免上传过大文件
-
重连机制:建议实现自动重连机制,当连接断开时自动重新建立连接
// 简单重连示例
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();
}