diff --git a/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/chat/controller/api/ChatController.java b/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/chat/controller/api/ChatController.java index 1991b770..e8ed985c 100644 --- a/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/chat/controller/api/ChatController.java +++ b/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/chat/controller/api/ChatController.java @@ -49,9 +49,6 @@ public class ChatController { @PostMapping("/send") @ResponseBody public SseEmitter sseChat(@RequestBody @Valid ChatRequest chatRequest, HttpServletRequest request) { - if (chatRequest.getModel().startsWith("ollama")) { - return sseService.ollamaChat(chatRequest); - } return sseService.sseChat(chatRequest,request); } diff --git a/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/chat/factory/SseServiceFactory.java b/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/chat/factory/SseServiceFactory.java new file mode 100644 index 00000000..679709d8 --- /dev/null +++ b/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/chat/factory/SseServiceFactory.java @@ -0,0 +1,24 @@ +package org.ruoyi.chat.factory; + + +import lombok.extern.slf4j.Slf4j; +import org.ruoyi.chat.service.chat.IChatService; +import org.ruoyi.chat.service.chat.impl.OllamaServiceImpl; +import org.ruoyi.chat.service.chat.impl.OpenAIServiceImpl; + +import org.springframework.stereotype.Component; + +@Component +@Slf4j +public class SseServiceFactory { + + public IChatService getSseService(String type) { + if ("openai".equals(type)) { + return new OpenAIServiceImpl(); + } else if ("ollama".equals(type)) { + return new OllamaServiceImpl(); + } else { + throw new IllegalArgumentException("Unknown type: " + type); + } + } +} diff --git a/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/chat/service/knowledge/vectorstore/VectorStoreFactory.java b/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/chat/factory/VectorStoreFactory.java similarity index 88% rename from ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/chat/service/knowledge/vectorstore/VectorStoreFactory.java rename to ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/chat/factory/VectorStoreFactory.java index 10ce84d4..88239cb9 100644 --- a/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/chat/service/knowledge/vectorstore/VectorStoreFactory.java +++ b/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/chat/factory/VectorStoreFactory.java @@ -1,8 +1,10 @@ -package org.ruoyi.chat.service.knowledge.vectorstore; +package org.ruoyi.chat.factory; import cn.hutool.core.util.StrUtil; import jakarta.annotation.Resource; import lombok.extern.slf4j.Slf4j; +import org.ruoyi.chat.service.knowledge.vectorstore.MilvusVectorStore; +import org.ruoyi.chat.service.knowledge.vectorstore.WeaviateVectorStore; import org.ruoyi.domain.vo.KnowledgeInfoVo; import org.ruoyi.mapper.KnowledgeInfoMapper; import org.ruoyi.service.VectorStoreService; diff --git a/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/chat/service/knowledge/vectorizer/VectorizationFactory.java b/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/chat/factory/VectorizationFactory.java similarity index 89% rename from ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/chat/service/knowledge/vectorizer/VectorizationFactory.java rename to ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/chat/factory/VectorizationFactory.java index 18a0b9a8..425d5943 100644 --- a/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/chat/service/knowledge/vectorizer/VectorizationFactory.java +++ b/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/chat/factory/VectorizationFactory.java @@ -1,9 +1,11 @@ -package org.ruoyi.chat.service.knowledge.vectorizer; +package org.ruoyi.chat.factory; import cn.hutool.core.util.StrUtil; import jakarta.annotation.Resource; import lombok.extern.slf4j.Slf4j; +import org.ruoyi.chat.service.knowledge.vectorizer.BgeLargeVectorization; +import org.ruoyi.chat.service.knowledge.vectorizer.OpenAiVectorization; import org.ruoyi.domain.vo.KnowledgeInfoVo; import org.ruoyi.service.IKnowledgeInfoService; import org.ruoyi.service.VectorizationService; diff --git a/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/chat/service/chat/IChatService.java b/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/chat/service/chat/IChatService.java new file mode 100644 index 00000000..3a6c0aac --- /dev/null +++ b/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/chat/service/chat/IChatService.java @@ -0,0 +1,19 @@ +package org.ruoyi.chat.service.chat; + +import org.ruoyi.common.chat.request.ChatRequest; +import org.springframework.web.servlet.mvc.method.annotation.SseEmitter; + +/** + * 对话Service接口 + * + * @author ageerle + * @date 2025-04-08 + */ +public interface IChatService { + + /** + * 客户端发送消息到服务端 + * @param chatRequest 请求对象 + */ + SseEmitter chat(ChatRequest chatRequest,SseEmitter emitter); +} diff --git a/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/chat/service/chat/ISseService.java b/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/chat/service/chat/ISseService.java index cc3bb4b9..65adeed0 100644 --- a/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/chat/service/chat/ISseService.java +++ b/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/chat/service/chat/ISseService.java @@ -39,9 +39,8 @@ public interface ISseService { */ ResponseEntity textToSpeed(TextToSpeech textToSpeech); - /** - * 上传文件到api服务器 + * 上传文件到服务器 * * @param file 文件信息 * @return 返回文件信息 @@ -49,13 +48,6 @@ public interface ISseService { UploadFileResponse upload(MultipartFile file); - /** - * 使用ollama调用本地模型 - * @param chatRequest 对话信息 - * @return 流式输出返回内容 - */ - SseEmitter ollamaChat(ChatRequest chatRequest); - /** * 企业应用回复 * @param prompt 提示词 @@ -63,7 +55,6 @@ public interface ISseService { */ String wxCpChat(String prompt); - /** * 联网查询 * diff --git a/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/chat/service/chat/impl/OllamaServiceImpl.java b/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/chat/service/chat/impl/OllamaServiceImpl.java new file mode 100644 index 00000000..7537e0b1 --- /dev/null +++ b/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/chat/service/chat/impl/OllamaServiceImpl.java @@ -0,0 +1,80 @@ +package org.ruoyi.chat.service.chat.impl; + +import io.github.ollama4j.OllamaAPI; +import io.github.ollama4j.models.chat.OllamaChatMessage; +import io.github.ollama4j.models.chat.OllamaChatMessageRole; +import io.github.ollama4j.models.chat.OllamaChatRequestBuilder; +import io.github.ollama4j.models.chat.OllamaChatRequestModel; +import io.github.ollama4j.models.generate.OllamaStreamHandler; +import jakarta.servlet.http.HttpServletRequest; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.ruoyi.chat.service.chat.IChatService; +import org.ruoyi.chat.util.SSEUtil; +import org.ruoyi.common.chat.entity.chat.Message; +import org.ruoyi.common.chat.request.ChatRequest; +import org.ruoyi.domain.vo.ChatModelVo; +import org.ruoyi.service.IChatModelService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import org.springframework.web.servlet.mvc.method.annotation.SseEmitter; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.CompletableFuture; + + +@Service +@Slf4j +public class OllamaServiceImpl implements IChatService { + + @Autowired + private IChatModelService chatModelService; + + @Override + public SseEmitter chat(ChatRequest chatRequest,SseEmitter emitter) { + ChatModelVo chatModelVo = chatModelService.selectModelByName(chatRequest.getModel()); + String host = chatModelVo.getApiHost(); + List msgList = chatRequest.getMessages(); + + List messages = new ArrayList<>(); + for (Message message : msgList) { + OllamaChatMessage ollamaChatMessage = new OllamaChatMessage(); + ollamaChatMessage.setRole(OllamaChatMessageRole.USER); + ollamaChatMessage.setContent(message.getContent().toString()); + messages.add(ollamaChatMessage); + } + OllamaAPI api = new OllamaAPI(host); + api.setRequestTimeoutSeconds(100); + OllamaChatRequestBuilder builder = OllamaChatRequestBuilder.getInstance(chatRequest.getModel()); + + OllamaChatRequestModel requestModel = builder + .withMessages(messages) + .build(); + + // 异步执行 OllAma API 调用 + CompletableFuture.runAsync(() -> { + try { + StringBuilder response = new StringBuilder(); + OllamaStreamHandler streamHandler = (s) -> { + String substr = s.substring(response.length()); + response.append(substr); + System.out.println(substr); + try { + emitter.send(substr); + } catch (IOException e) { + SSEUtil.sendErrorEvent(emitter, e.getMessage()); + } + }; + api.chat(requestModel, streamHandler); + emitter.complete(); + } catch (Exception e) { + SSEUtil.sendErrorEvent(emitter, e.getMessage()); + } + }); + + return emitter; + } +} diff --git a/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/chat/service/chat/impl/OpenAIServiceImpl.java b/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/chat/service/chat/impl/OpenAIServiceImpl.java new file mode 100644 index 00000000..b9d2012d --- /dev/null +++ b/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/chat/service/chat/impl/OpenAIServiceImpl.java @@ -0,0 +1,50 @@ +package org.ruoyi.chat.service.chat.impl; + +import lombok.extern.slf4j.Slf4j; +import org.ruoyi.chat.config.ChatConfig; +import org.ruoyi.chat.listener.SSEEventSourceListener; +import org.ruoyi.chat.service.chat.IChatService; +import org.ruoyi.common.chat.entity.chat.ChatCompletion; +import org.ruoyi.common.chat.openai.OpenAiStreamClient; +import org.ruoyi.common.chat.request.ChatRequest; +import org.ruoyi.domain.vo.ChatModelVo; +import org.ruoyi.service.IChatModelService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import org.springframework.web.servlet.mvc.method.annotation.SseEmitter; + +@Service +@Slf4j +public class OpenAIServiceImpl implements IChatService { + + @Autowired + private IChatModelService chatModelService; + @Autowired + private ChatConfig chatConfig; + @Autowired + private OpenAiStreamClient openAiStreamClient; + + @Override + public SseEmitter chat(ChatRequest chatRequest,SseEmitter emitter) { + + SSEEventSourceListener openAIEventSourceListener = new SSEEventSourceListener(emitter); + // 查询模型信息 + ChatModelVo chatModelVo = chatModelService.selectModelByName(chatRequest.getModel()); + + if(chatModelVo!=null){ + // 建请求客户端 + openAiStreamClient = chatConfig.createOpenAiStreamClient(chatModelVo.getApiHost(), chatModelVo.getApiKey()); + // 设置默认提示词 + chatRequest.setSysPrompt(chatModelVo.getSystemPrompt()); + } + ChatCompletion completion = ChatCompletion + .builder() + .messages(chatRequest.getMessages()) + .model(chatRequest.getModel()) + .stream(chatRequest.getStream()) + .build(); + openAiStreamClient.streamChatCompletion(completion, openAIEventSourceListener); + + return emitter; + } +} diff --git a/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/chat/service/chat/impl/SseServiceImpl.java b/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/chat/service/chat/impl/SseServiceImpl.java index 6cf19b40..17a5bd49 100644 --- a/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/chat/service/chat/impl/SseServiceImpl.java +++ b/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/chat/service/chat/impl/SseServiceImpl.java @@ -6,22 +6,17 @@ import com.fasterxml.jackson.databind.ObjectMapper; import com.google.protobuf.ServiceException; import com.zhipu.oapi.ClientV4; import com.zhipu.oapi.service.v4.tools.*; -import io.github.ollama4j.OllamaAPI; -import io.github.ollama4j.models.chat.OllamaChatMessage; -import io.github.ollama4j.models.chat.OllamaChatMessageRole; -import io.github.ollama4j.models.chat.OllamaChatRequestBuilder; -import io.github.ollama4j.models.chat.OllamaChatRequestModel; -import io.github.ollama4j.models.generate.OllamaStreamHandler; import jakarta.servlet.http.HttpServletRequest; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import okhttp3.*; -import org.ruoyi.chat.config.ChatConfig; -import org.ruoyi.chat.listener.SSEEventSourceListener; import org.ruoyi.chat.service.chat.IChatCostService; +import org.ruoyi.chat.service.chat.IChatService; import org.ruoyi.chat.service.chat.ISseService; +import org.ruoyi.chat.factory.SseServiceFactory; import org.ruoyi.chat.util.IpUtil; +import org.ruoyi.chat.util.SSEUtil; import org.ruoyi.common.chat.request.ChatRequest; import org.ruoyi.common.chat.entity.Tts.TextToSpeech; import org.ruoyi.common.chat.entity.chat.ChatCompletion; @@ -32,15 +27,14 @@ import org.ruoyi.common.chat.entity.files.UploadFileResponse; import org.ruoyi.common.chat.entity.whisper.WhisperResponse; import org.ruoyi.common.chat.openai.OpenAiStreamClient; import org.ruoyi.common.core.service.ConfigService; +import org.ruoyi.common.core.utils.DateUtils; import org.ruoyi.common.core.utils.StringUtils; import org.ruoyi.common.core.utils.file.FileUtils; import org.ruoyi.common.core.utils.file.MimeTypeUtils; import org.ruoyi.common.redis.utils.RedisUtils; -import org.ruoyi.domain.vo.ChatModelVo; import org.ruoyi.service.EmbeddingService; -import org.ruoyi.service.IChatModelService; import org.ruoyi.service.VectorStoreService; import org.springframework.core.io.InputStreamResource; import org.springframework.core.io.Resource; @@ -60,7 +54,6 @@ import java.time.Duration; import java.util.ArrayList; import java.util.Collections; import java.util.List; -import java.util.concurrent.CompletableFuture; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicReference; @@ -72,10 +65,6 @@ public class SseServiceImpl implements ISseService { private final OpenAiStreamClient openAiStreamClient; - private final ChatConfig chatConfig; - - private final IChatModelService chatModelService; - private final EmbeddingService embeddingService; private final VectorStoreService vectorStore; @@ -84,6 +73,8 @@ public class SseServiceImpl implements ISseService { private final IChatCostService chatCostService; + private final SseServiceFactory sseServiceFactory; + private static final String requestIdTemplate = "company-%d"; private static final ObjectMapper mapper = new ObjectMapper(); @@ -91,78 +82,64 @@ public class SseServiceImpl implements ISseService { @Override public SseEmitter sseChat(ChatRequest chatRequest, HttpServletRequest request) { SseEmitter sseEmitter = new SseEmitter(0L); - SSEEventSourceListener openAIEventSourceListener = new SSEEventSourceListener(sseEmitter); - // 获取对话消息列表 - List messages = chatRequest.getMessages(); try { - // 查询模型信息 - ChatModelVo chatModelVo = chatModelService.selectModelByName(chatRequest.getModel()); - - OpenAiStreamClient openAiModelStreamClient; - if(chatModelVo!=null){ - // 建请求客户端 - openAiModelStreamClient = chatConfig.createOpenAiStreamClient(chatModelVo.getApiHost(), chatModelVo.getApiKey()); - // 设置默认提示词 - chatRequest.setSysPrompt(chatModelVo.getSystemPrompt()); - }else { - // 使用默认客户端 - openAiModelStreamClient = openAiStreamClient; - } // 构建消息列表增加联网、知识库等内容 buildChatMessageList(chatRequest); - // 根据模型名称前缀调用不同的处理逻辑 - switchModelAndHandle(chatRequest); - + switchModelAndHandle(chatRequest,sseEmitter); // 未登录用户限制对话次数 - if (!StpUtil.isLogin()) { - String clientIp = IpUtil.getClientIp(request); - // 访客每天默认只能对话5次 - int timeWindowInSeconds = 5; - String redisKey = "clientIp:" + clientIp; - int count = 0; - if (RedisUtils.getCacheObject(redisKey) == null) { - // 缓存有效时间1天 - RedisUtils.setCacheObject(redisKey, count, Duration.ofSeconds(86400)); - }else { - count = RedisUtils.getCacheObject(redisKey); - if (count >= timeWindowInSeconds) { - throw new ServiceException("当日免费次数已用完"); - } - count++; - RedisUtils.setCacheObject(redisKey, count); - } - } - - ChatCompletion completion = ChatCompletion - .builder() - .messages(messages) - .model(chatRequest.getModel()) - .stream(chatRequest.getStream()) - .build(); - openAiModelStreamClient.streamChatCompletion(completion, openAIEventSourceListener); - + checkUnauthenticatedUserChatLimit(request); // 保存消息记录 并扣除费用 chatCostService.deductToken(chatRequest); } catch (Exception e) { String message = e.getMessage(); - sendErrorEvent(sseEmitter, message); + SSEUtil.sendErrorEvent(sseEmitter, message); return sseEmitter; } return sseEmitter; } + /** + * 检查未登录用户是否超过当日对话次数限制 + * + * @param request 当前请求 + * @throws ServiceException 如果当日免费次数已用完 + */ + public void checkUnauthenticatedUserChatLimit(HttpServletRequest request) throws ServiceException { + // 未登录用户限制对话次数 + if (!StpUtil.isLogin()) { + String clientIp = IpUtil.getClientIp(request); + // 访客每天默认只能对话5次 + int timeWindowInSeconds = 5; + String redisKey = "clientIp:" + clientIp; + int count = 0; + // 检查Redis中的对话次数 + if (RedisUtils.getCacheObject(redisKey) == null) { + // 缓存有效时间1天 + RedisUtils.setCacheObject(redisKey, count, Duration.ofSeconds(86400)); + } else { + count = RedisUtils.getCacheObject(redisKey); + if (count >= timeWindowInSeconds) { + throw new ServiceException("当日免费次数已用完"); + } + count++; + RedisUtils.setCacheObject(redisKey, count); + } + } + } + /** * 根据模型名称前缀调用不同的处理逻辑 */ - private void switchModelAndHandle(ChatRequest chatRequest) { + private void switchModelAndHandle(ChatRequest chatRequest,SseEmitter emitter) { String model = chatRequest.getModel(); // 如果模型名称以ollama开头,则调用ollama中部署的本地模型 if (model.startsWith("ollama-")) { String[] parts = chatRequest.getModel().split("ollama-", 2); // 限制分割次数为2 if (parts.length > 1) { chatRequest.setModel(parts[1]); - ollamaChat(chatRequest); + IChatService chatService = sseServiceFactory.getSseService("ollama"); + chatService.chat(chatRequest,emitter); } else { throw new IllegalArgumentException("Invalid ollama model name: " + chatRequest.getModel()); } @@ -177,8 +154,13 @@ public class SseServiceImpl implements ISseService { private void buildChatMessageList(ChatRequest chatRequest){ // 获取对话消息列表 List messages = chatRequest.getMessages(); + String sysPrompt = chatRequest.getSysPrompt(); + if(StringUtils.isEmpty(sysPrompt)){ + sysPrompt ="你是一个由RuoYI-AI开发的人工智能助手,名字叫熊猫助手。你擅长中英文对话,能够理解并处理各种问题,提供安全、有帮助、准确的回答。" + + "当前时间:"+ DateUtils.getDate(); + } // 设置系统默认提示词 - Message sysMessage = Message.builder().content(chatRequest.getSysPrompt()).role(Message.Role.SYSTEM).build(); + Message sysMessage = Message.builder().content(sysPrompt).role(Message.Role.SYSTEM).build(); messages.add(0,sysMessage); // 查询向量库相关信息加入到上下文 @@ -216,23 +198,6 @@ public class SseServiceImpl implements ISseService { } } - /** - * 发送SSE错误事件的封装方法 - * - * @param sseEmitter - * @param errorMessage - */ - private void sendErrorEvent(SseEmitter sseEmitter, String errorMessage) { - SseEmitter.SseEventBuilder event = SseEmitter.event() - .name("error") - .data(errorMessage); - try { - sseEmitter.send(event); - } catch (IOException e) { - log.error("SSE发送失败: {}", e.getMessage()); - } - sseEmitter.complete(); - } /** * 文字转语音 @@ -323,51 +288,6 @@ public class SseServiceImpl implements ISseService { return file; } - @Override - public SseEmitter ollamaChat(ChatRequest chatRequest) { - - ChatModelVo chatModelVo = chatModelService.selectModelByName(chatRequest.getModel()); - final SseEmitter emitter = new SseEmitter(); - String host = chatModelVo.getApiHost(); - List msgList = chatRequest.getMessages(); - - List messages = new ArrayList<>(); - for (Message message : msgList) { - OllamaChatMessage ollamaChatMessage = new OllamaChatMessage(); - ollamaChatMessage.setRole(OllamaChatMessageRole.USER); - ollamaChatMessage.setContent(message.getContent().toString()); - messages.add(ollamaChatMessage); - } - OllamaAPI api = new OllamaAPI(host); - api.setRequestTimeoutSeconds(100); - OllamaChatRequestBuilder builder = OllamaChatRequestBuilder.getInstance(chatRequest.getModel()); - - OllamaChatRequestModel requestModel = builder - .withMessages(messages) - .build(); - - // 异步执行 OllAma API 调用 - CompletableFuture.runAsync(() -> { - try { - StringBuilder response = new StringBuilder(); - OllamaStreamHandler streamHandler = (s) -> { - String substr = s.substring(response.length()); - response.append(substr); - System.out.println(substr); - try { - emitter.send(substr); - } catch (IOException e) { - sendErrorEvent(emitter, e.getMessage()); - } - }; - api.chat(requestModel, streamHandler); - emitter.complete(); - } catch (Exception e) { - sendErrorEvent(emitter, e.getMessage()); - } - }); - return emitter; - } @Override public String wxCpChat(String prompt) { diff --git a/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/chat/util/SSEUtil.java b/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/chat/util/SSEUtil.java new file mode 100644 index 00000000..21f183ae --- /dev/null +++ b/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/chat/util/SSEUtil.java @@ -0,0 +1,33 @@ +package org.ruoyi.chat.util; + +import lombok.extern.slf4j.Slf4j; +import org.springframework.web.servlet.mvc.method.annotation.SseEmitter; + +import java.io.IOException; + +/** + * sse工具类 + * + * @author WangLe + */ +@Slf4j +public class SSEUtil { + + /** + * 发送SSE错误事件的封装方法 + * + * @param sseEmitter sse事件对象 + * @param errorMessage 错误信息 + */ + public static void sendErrorEvent(SseEmitter sseEmitter, String errorMessage) { + SseEmitter.SseEventBuilder event = SseEmitter.event() + .name("error") + .data(errorMessage); + try { + sseEmitter.send(event); + } catch (IOException e) { + log.error("SSE发送失败: {}", e.getMessage()); + } + sseEmitter.complete(); + } +}