Quellcode durchsuchen

优化SSE多浏览器

tsurumure vor 5 Monaten
Ursprung
Commit
13e777ec30

+ 10 - 0
src/main/java/com/backendsys/modules/common/config/security/utils/HttpRequestUtil.java

@@ -28,6 +28,16 @@ public class HttpRequestUtil {
         return request;
     }
 
+    public String getBrowserWindowUUID() {
+        // 获取当前请求的上下文
+        ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
+        if (attributes != null) {
+            HttpServletRequest request = attributes.getRequest();
+            return request.getHeader("Browser-Window-Uuid");
+        }
+        return null; // 如果没有请求上下文,返回 null
+    }
+
     /**
      * 从 Token 中获得 user_id
      */

+ 2 - 2
src/main/java/com/backendsys/modules/sdk/deepseek/service/impl/DeepSeekClientImpl.java

@@ -41,9 +41,9 @@ import java.util.List;
 @Service
 public class DeepSeekClientImpl implements DeepSeekClient {
 
-    @Value("${deepseek.url}")
+    @Value("${deepseek-api.url}")
     private String API_URL;
-    @Value("${deepseek.api-key}")
+    @Value("${deepseek-api.api-key}")
     private String API_KEY;
     @Autowired
     private SseUtil sseUtil;

+ 1 - 1
src/main/java/com/backendsys/modules/sdk/deepseek/utils/OllamaUtil.java

@@ -42,7 +42,7 @@ public class OllamaUtil {
     @Value("${spring.application.name}")
     private String APPLICATION_NAME;
 
-    @Value("${ai.config.deepseek.domain}")
+    @Value("${deepseek-r1.domain}")
     private String DOMAIN;
 
     /**

+ 13 - 3
src/main/java/com/backendsys/modules/sse/controller/SseController.java

@@ -1,6 +1,7 @@
 package com.backendsys.modules.sse.controller;
 
 import cn.hutool.core.convert.Convert;
+import com.backendsys.modules.common.config.security.utils.HttpRequestUtil;
 import com.backendsys.modules.common.config.security.utils.SecurityUtil;
 import com.backendsys.modules.sse.emitter.SseEmitterManager;
 import com.backendsys.modules.sse.entity.SseResponse;
@@ -13,6 +14,7 @@ import org.springframework.beans.factory.annotation.Value;
 import org.springframework.security.core.Authentication;
 import org.springframework.security.core.context.SecurityContextHolder;
 import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.RequestHeader;
 import org.springframework.web.bind.annotation.RestController;
 import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;
 
@@ -26,6 +28,8 @@ public class SseController {
 
     @Autowired
     private SseUtil sseUtil;
+    @Autowired
+    private HttpRequestUtil httpRequestUtil;
 
     /**
      * [SSE] 消息监听 (拼接键值: 应用名称-用户ID)
@@ -33,11 +37,17 @@ public class SseController {
     @GetMapping(value = "/api/sse/listenStream", produces = "text/event-stream;charset=UTF-8")
     public SseEmitter stream(HttpServletResponse response) {
 
-        // 拼接键值: 应用名称-用户ID
-        String emitterKey = APPLICATION_NAME + "-userid-" + Convert.toStr(SecurityUtil.getUserId());
+        // 获得 浏览器窗口UUID
+        String browserWindowUUID = httpRequestUtil.getBrowserWindowUUID();
+
+        // 拼接键值: 应用名称-用户ID-浏览器窗口UUID
+        String emitterKey = APPLICATION_NAME + "-userid-" + Convert.toStr(SecurityUtil.getUserId() + "-" + browserWindowUUID);
 
         // 如果存在,则关闭
-        sseUtil.closeIfExist(emitterKey);
+         sseUtil.closeIfExist(emitterKey);
+
+//        System.out.println("browserWindowUUID = " + browserWindowUUID);
+//        System.out.println("emitterKey = " + emitterKey);
 
         SseEmitterUTF8 emitter = new SseEmitterUTF8(Long.MAX_VALUE);
         SseEmitterManager manager = SseEmitterManager.getInstance();

+ 3 - 0
src/main/java/com/backendsys/modules/sse/emitter/SseEmitterManager.java

@@ -14,10 +14,13 @@ public class SseEmitterManager {
 
     // 私有构造函数,防止外部直接实例化
     private SseEmitterManager() {}
+
     // 公共静态方法,获取单例实例
     public static SseEmitterManager getInstance() {
         return INSTANCE;
     }
+    public ConcurrentHashMap<String, SseEmitterUTF8> getEmitters() { return emitters; }
+
     // 公共方法,供外部添加SseEmitter
     public void addEmitter(String emitterKey, SseEmitterUTF8 emitter) {
         this.emitters.put(emitterKey, emitter);

+ 33 - 7
src/main/java/com/backendsys/modules/sse/utils/SseUtil.java

@@ -2,10 +2,12 @@ package com.backendsys.modules.sse.utils;
 
 import cn.hutool.core.convert.Convert;
 import cn.hutool.json.JSONUtil;
+import com.backendsys.modules.common.config.security.utils.HttpRequestUtil;
 import com.backendsys.modules.common.config.security.utils.SecurityUtil;
 import com.backendsys.modules.sse.emitter.SseEmitterManager;
 import com.backendsys.modules.sse.entity.SseResponse;
 import com.backendsys.modules.sse.entity.SseResponseEnum;
+import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.beans.factory.annotation.Value;
 import org.springframework.stereotype.Component;
 import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;
@@ -19,6 +21,9 @@ import java.util.concurrent.ConcurrentHashMap;
 @Component
 public class SseUtil {
 
+    @Autowired
+    private HttpRequestUtil httpRequestUtil;
+
     @Value("${spring.application.name}")
     private String APPLICATION_NAME;
 
@@ -41,16 +46,37 @@ public class SseUtil {
 
     // [SSE] 发送消息 (单个) (自己)
     public void send(Long user_id, Object data) {
+
+        // 遍历所有 Emitter 并发送匹配前缀的目标 (拼接键值: 应用名称-用户ID-浏览器窗口UUID)
         SseEmitterManager manager = SseEmitterManager.getInstance();
-        SseEmitterUTF8 emitter = manager.getEmitter(APPLICATION_NAME + "-userid-" + Convert.toStr(user_id));
-        if (emitter != null) {
-            try {
-                emitter.send(SseEmitter.event().data(data));
-            } catch (IOException e) {
-                System.out.println(e.getMessage());
-                manager.removeEmitter(emitter);
+        ConcurrentHashMap<String, SseEmitterUTF8> emitters = manager.getAllEmitters();
+
+        String prefix = APPLICATION_NAME + "-userid-" + Convert.toStr(user_id);
+        for (String key : emitters.keySet()) {
+            if (key.startsWith(prefix)) { // 检查是否满足前缀条件
+                SseEmitterUTF8 emitter = emitters.get(key);
+                if (emitter != null) {
+                    try {
+                        emitter.send(SseEmitter.event().data(data)); // 发送数据
+                    } catch (IOException e) {
+                        System.out.println(e.getMessage());
+                        manager.removeEmitter(emitter); // 发生异常时移除
+                    }
+                }
             }
         }
+
+        // 单个
+//        SseEmitterManager manager = SseEmitterManager.getInstance();
+//        SseEmitterUTF8 emitter = manager.getEmitter(APPLICATION_NAME + "-userid-" + Convert.toStr(user_id));
+//        if (emitter != null) {
+//            try {
+//                emitter.send(SseEmitter.event().data(data));
+//            } catch (IOException e) {
+//                System.out.println(e.getMessage());
+//                manager.removeEmitter(emitter);
+//            }
+//        }
     }
 
     // [SSE] 发送消息 (全部)

+ 9 - 7
src/main/resources/application-local.yml

@@ -155,25 +155,27 @@ douyin:
     access-key-id: AKLTNmFiMjBjMTZjZDYxNDlhZmI2OTc2M2U4MWZlODhhNGY
     secret-access-key: TVRjME9UUmhabUZtWW1JNU5EWXhORGhoWVdJME16Z3pNbUprWkRKa1lqTQ==
 
+# 百度
 baidu:
   ai:
     client-id: VtEo9VpnMzRzuefomiZcLv9W
     client-secret: VzHHaGdBnVM5cTrwG71x9N6ddVqTTPh8
 
-ai:
-  config:
-    deepseek:
-#      domain: http://localhost:11434
-      domain: https://1wd05129tf963.vicp.fun
+# DeepSeek R1
+deepseek-r1:
+#  domain: http://localhost:11434
+  domain: https://1wd05129tf963.vicp.fun
 
-deepseek:
+# DeepSeek API
+deepseek-api:
   url: https://api.deepseek.com
   api-key: sk-6078a4ceee5443128ce74a23538dd54e
 
 # 微信
 wechat:
   miniprogram:
+#     # 未开白名单的APPID
 #     appid: wx159b7ad079a34fa1
 #     app-secret: 7671bcb556dd8a48052f1f669d70b643
      appid: wx3c789d9920a0f98f
-     app-secret: c713a96ac6df5abb9b7edb80fdc41e8b
+     app-secret: c713a96ac6df5abb9b7edb80fdc41e8b

+ 10 - 6
src/main/resources/application-prod.yml

@@ -147,23 +147,27 @@ douyin:
     access-key-id: AKLTNmFiMjBjMTZjZDYxNDlhZmI2OTc2M2U4MWZlODhhNGY
     secret-access-key: TVRjME9UUmhabUZtWW1JNU5EWXhORGhoWVdJME16Z3pNbUprWkRKa1lqTQ==
 
+# 百度
 baidu:
   ai:
     client-id: VtEo9VpnMzRzuefomiZcLv9W
     client-secret: VzHHaGdBnVM5cTrwG71x9N6ddVqTTPh8
 
-ai:
-  config:
-    deepseek:
-      # domain: http://localhost:11434
-      domain: https://1wd05129tf963.vicp.fun
+# DeepSeek R1
+deepseek-r1:
+  #  domain: http://localhost:11434
+  domain: https://1wd05129tf963.vicp.fun
 
-deepseek:
+# DeepSeek API
+deepseek-api:
   url: https://api.deepseek.com
   api-key: sk-6078a4ceee5443128ce74a23538dd54e
 
 # 微信
 wechat:
   miniprogram:
+#     # 未开白名单的APPID
+#     appid: wx159b7ad079a34fa1
+#     app-secret: 7671bcb556dd8a48052f1f669d70b643
     appid: wx3c789d9920a0f98f
     app-secret: c713a96ac6df5abb9b7edb80fdc41e8b