Bladeren bron

伊宁版本更新:
a.新增sse服务器代码逻辑:全局以会话id为key存储各自会话的通讯链接,心跳包以及登出指令都以当前会话id为基础单位发送指令
b.修改伊宁mainView首页,增加跳转参数clientId用以sse通讯
c.新增sse请求地址获取接口

1037015548@qq.com 4 dagen geleden
bovenliggende
commit
774a150772

+ 113 - 34
ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SseServlet.java

@@ -7,28 +7,34 @@ package com.ruoyi.web.controller.system;
  * @Date 2025/5/15
  * @Version V1.0
  **/
+import com.alibaba.fastjson.JSONArray;
+import com.alibaba.fastjson.JSONObject;
 import com.ruoyi.common.core.controller.BaseController;
 import com.ruoyi.common.core.domain.AjaxResult;
 import com.ruoyi.common.utils.ShiroUtils;
 import com.ruoyi.framework.config.ShiroConfig;
 import com.ruoyi.framework.shiro.realm.UserRealm;
+import org.apache.commons.io.IOUtils;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.springframework.beans.factory.annotation.Value;
 import org.springframework.stereotype.Controller;
 import org.springframework.util.CollectionUtils;
+import org.springframework.web.bind.annotation.CrossOrigin;
 import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PostMapping;
 import org.springframework.web.bind.annotation.ResponseBody;
 
 import javax.annotation.PostConstruct;
 import javax.servlet.AsyncContext;
 import javax.servlet.ServletException;
+import javax.servlet.ServletInputStream;
 import javax.servlet.annotation.WebServlet;
 import javax.servlet.http.HttpServlet;
 import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletResponse;
-import java.io.IOException;
-import java.io.PrintWriter;
+import java.io.*;
+import java.nio.charset.Charset;
 import java.time.LocalDateTime;
 import java.util.Timer;
 import java.util.TimerTask;
@@ -40,11 +46,13 @@ public class SseServlet extends BaseController {
 
     private static final Logger log = LoggerFactory.getLogger(SseServlet.class);
 
-    private static final  ConcurrentHashMap<String,ConcurrentHashMap<String, PrintWriter>> countConnections = new ConcurrentHashMap<>();
+    //reqid,连接对象
+    public static final  ConcurrentHashMap<String,ConcurrentHashMap<String, PrintWriter>> countConnections = new ConcurrentHashMap<>();
 
-    private static final ConcurrentHashMap<String,Timer> timerArray = new ConcurrentHashMap<>();
+    public static final ConcurrentHashMap<String,Timer> timerArray = new ConcurrentHashMap<>();
 
     @GetMapping("/sse/subscribe")
+    @CrossOrigin(origins = "*") // 允许所有来源
     protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
         response.setContentType("text/event-stream");
         response.setCharacterEncoding("UTF-8");
@@ -56,17 +64,22 @@ public class SseServlet extends BaseController {
             response.setStatus(HttpServletResponse.SC_BAD_REQUEST);
             return;
         }
+        String reqid = clientId.split("___")[0];
+        if (reqid == null) {
+            response.setStatus(HttpServletResponse.SC_BAD_REQUEST);
+            return;
+        }
 
         // Start async processing
         AsyncContext asyncContext = request.startAsync();
+        PrintWriter writer = response.getWriter();
 
         ConcurrentHashMap<String, PrintWriter> connections = new ConcurrentHashMap<>();
-        if (countConnections.get(ShiroUtils.getSessionId())!=null&&countConnections.get(ShiroUtils.getSessionId()).size()>0){
-            connections = countConnections.get(ShiroUtils.getSessionId());
+        if(countConnections.get(reqid)!=null&&countConnections.get(reqid).size()>0){
+            connections = countConnections.get(reqid);
         }
-        PrintWriter writer = response.getWriter();
-        connections.put(clientId, writer);
-        countConnections.put(ShiroUtils.getSessionId(),connections);
+        connections.put(clientId,writer);
+        countConnections.put(reqid, connections);
 
         // Set timeout for async context
         asyncContext.setTimeout(0);
@@ -74,37 +87,42 @@ public class SseServlet extends BaseController {
         writer.flush();
 
         //TODO 建立心跳定时包
-        final String sessionId = ShiroUtils.getSessionId();
         Timer timer2 = new Timer();
         TimerTask timerTask2 = new TimerTask() {
             @Override
             public void run() {
-                broadcastHeart(sessionId);
+                broadcastHeart(reqid);
             }
         };
         timer2.schedule(timerTask2, 2000, 2000);
-        timerArray.put(sessionId,timer2);
+        timerArray.put(reqid,timer2);
 
         // Handle connection close
         asyncContext.addListener(new javax.servlet.AsyncListener() {
             @Override
             public void onComplete(javax.servlet.AsyncEvent asyncEvent) throws IOException {
                 if(!CollectionUtils.isEmpty(countConnections)) {
-                    countConnections.get(ShiroUtils.getSessionId()).remove(clientId);
+                    if(!CollectionUtils.isEmpty(countConnections.get(reqid))) {
+                        countConnections.get(reqid).remove(clientId);
+                    }
                 }
             }
 
             @Override
             public void onTimeout(javax.servlet.AsyncEvent asyncEvent) throws IOException {
                 if(!CollectionUtils.isEmpty(countConnections)) {
-                    countConnections.get(ShiroUtils.getSessionId()).remove(clientId);
+                    if(!CollectionUtils.isEmpty(countConnections.get(reqid))) {
+                        countConnections.get(reqid).remove(clientId);
+                    }
                 }
             }
 
             @Override
             public void onError(javax.servlet.AsyncEvent asyncEvent) throws IOException {
                 if(!CollectionUtils.isEmpty(countConnections)) {
-                    countConnections.get(ShiroUtils.getSessionId()).remove(clientId);
+                    if(!CollectionUtils.isEmpty(countConnections.get(reqid))) {
+                        countConnections.get(reqid).remove(clientId);
+                    }
                 }
             }
 
@@ -115,14 +133,52 @@ public class SseServlet extends BaseController {
             }
         });
 
-        log.info("建立链接成功:sessionId:"+sessionId+";clientId:"+clientId);
+        log.info("建立链接成功:clientId:"+clientId);
     }
 
-    @GetMapping("/sse/logout")
+    @PostMapping("/sse/logout")
     @ResponseBody
     protected AjaxResult logout(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
-        broadcastLogout();
-        return AjaxResult.success();
+        try {
+            String body = getJsonBodyStr(request);
+            JSONArray jsonArray = JSONArray.parseArray(body);
+            broadcastLogout(jsonArray);
+            return AjaxResult.success();
+        }catch(Exception ex){
+            return AjaxResult.error();
+        }
+    }
+
+    public static String getJsonBodyStr(HttpServletRequest request){
+        BufferedReader reader = null;
+        InputStream inputStream = null;
+        StringBuilder sb = new StringBuilder();
+        try {
+            inputStream = request.getInputStream();
+            reader = new BufferedReader(new InputStreamReader(inputStream, Charset.forName("UTF-8")));
+            String line = "";
+            while ((line = reader.readLine()) != null) {
+                sb.append(line);
+            }
+        } catch (IOException e) {
+            e.printStackTrace();
+        } finally {
+            if (inputStream != null) {
+                try {
+                    inputStream.close();
+                } catch (IOException e) {
+                    e.printStackTrace();
+                }
+            }
+            if (reader != null) {
+                try {
+                    reader.close();
+                } catch (IOException e) {
+                    e.printStackTrace();
+                }
+            }
+        }
+        return sb.toString();
     }
 
     @Value("${sseUrl}")
@@ -141,30 +197,53 @@ public class SseServlet extends BaseController {
 
     //TODO 广播
     //TODO 心跳广播
-    public void broadcastHeart(String sessionId) {
+    public void broadcastHeart(String reqid) {
         if(!CollectionUtils.isEmpty(countConnections)) {
-            for (PrintWriter writer : countConnections.get(sessionId).values()) {
-                writer.println("event: heart");
-                writer.println("data: heart is running\n");
-                writer.flush();
+            for (ConcurrentHashMap<String, PrintWriter> writerMap : countConnections.values()) {
+                try {
+                    //捕捉一下 如果有链接异常 避免影响其他链接循环
+                    for (PrintWriter writer : writerMap.values()) {
+                        writer.println("event: heart");
+                        writer.println("data: heart is running\n");
+                        writer.flush();
+                    }
+                }catch(Exception ex){}
             }
         }
     }
 
-    public void broadcastLogout() {
+    //TODO jsonArray是要退出的clientId集合
+    public void broadcastLogout(JSONArray jsonArray) {
         if(!CollectionUtils.isEmpty(countConnections)) {
-            for (PrintWriter writer : countConnections.get(ShiroUtils.getSessionId()).values()) {
-                writer.println("event: logout");
-                writer.println("data: User is logging out\n");
-                writer.flush();
+            for (String key: countConnections.keySet()) {
+                if(jsonArray!=null) {
+                    for (Object object : jsonArray) {
+                        if (key.equals(object.toString())) {
+                            ConcurrentHashMap<String,PrintWriter> writerMap = countConnections.get(key);
+                            for (PrintWriter writer:writerMap.values()) {
+                                writer.println("event: logout");
+                                writer.println("data: User is logging out\n");
+                                writer.flush();
+                            }
+                        }
+                    }
+                }
+            }
+            for (Object object : jsonArray) {
+                countConnections.remove(object.toString());
             }
         }
-        countConnections.remove(ShiroUtils.getSessionId());
         if(!CollectionUtils.isEmpty(timerArray)){
-            Timer timerCurrent = timerArray.get(ShiroUtils.getSessionId());
-            timerCurrent.cancel();
-            timerArray.remove(ShiroUtils.getSessionId());
+            if(jsonArray!=null) {
+                for (Object object : jsonArray) {
+                    Timer timerCurrent = timerArray.get(object.toString());
+                    if (timerCurrent!=null) {
+                        timerCurrent.cancel();
+                        timerArray.remove(object.toString());
+                    }
+                }
+            }
         }
-        log.info("退出指令发送:sessionId:"+ShiroUtils.getSessionId());
+        log.info("退出指令发送:sessionId:"+jsonArray.toJSONString());
     }
 }

+ 3 - 0
ruoyi-admin/src/main/java/com/ruoyi/web/controller/tool/ActionApi.java

@@ -166,6 +166,9 @@ public class ActionApi {
         AjaxResult ajaxResult = AjaxResult.success();
         ajaxResult.put("loginName", userRelate1.getDanganLoginName());
         ajaxResult.put("rspid", str);
+        ajaxResult.put("clientId",validateMessage.getReqid()+"___"+new Date().getTime()
+                +new Random().nextInt(900000) + 100000);
+
         logger.info("授权成功!",ajaxResult.toString());
         return ajaxResult;
         }catch(Exception ex){

+ 36 - 7
ruoyi-admin/src/main/resources/static/ruoyi/index.js

@@ -27,6 +27,7 @@ function resetTimer() {
 function logoutUser() {
     // 调用后端接口退出会话
     ifExit = true;
+    quitSystem();
     window.open("/logout",'_self');
     alert("登录过期");
     // layer.open({
@@ -55,19 +56,21 @@ document.addEventListener('',resetTimer,false);
 
 // var danganYiningList = [[${danganList}]];
 var quanjuChildWindow = [];
-//TODO 伊宁转属逻辑
-document.getElementById('yiningLogoutLink').addEventListener('click', function(event) {
-    // // 阻止默认的链接跳转行为
-    event.preventDefault();
-
+function quitSystem() {
     //TODO 本平台其他子系统
     //TODO 首先调用登出指令通知所有通讯子系统
+    let reqids = [];
+    if(sessionStorage.getItem("reqids")){
+        reqids = JSON.parse(sessionStorage.getItem("reqids"));
+    }
     $.ajax({
         url: ctx + "sse/logout",
-        type: "GET",
-        data: {},
+        type: "POST",
+        contentType: 'application/json', // 指定内容类型为JSON
+        data: JSON.stringify(reqids),
         success: function (result) {
             console.log("发送登出通讯指令成功");
+            sessionStorage.clear();
         },
         error: function(jqXHR, textStatus, errorThrown) {
             console.error('POST请求失败:', textStatus, errorThrown);
@@ -119,6 +122,25 @@ document.getElementById('yiningLogoutLink').addEventListener('click', function(e
                             } catch (error) {
                                 console.log("水质实验室登出:" + error)
                             }
+                        }else if ("水力模型" === userRelate[i].danganName) {
+                            try {
+                                $.ajax({
+                                    url: "https://hms.xjynwater.com/api/blade-auth/oauth/forceLogout" +
+                                    "?account=" + userRelate[i].danganLoginName, // 替换为实际的API端点
+                                    type: 'GET',
+                                    data: {},
+                                    success: function(response) {
+                                        console.log('水力模型GET请求成功:', response);
+                                    },
+                                    error: function(jqXHR, textStatus, errorThrown) {
+                                        console.error('GET请求失败:', textStatus, errorThrown);
+                                    }
+                                });
+                            } catch (error) {
+                                console.log("水力模型登出:" + error)
+                            }
+                        }else if ("客服中心" === userRelate[i].danganName) {
+                            window.open("https://scs.xjynwater.com/ucmweb/logout.action");
                         } else if (userRelate[i].danganUrl.indexOf("https://office.xjynwater.com")!==-1) {
                             //TODO 方格
                             try {
@@ -155,6 +177,13 @@ document.getElementById('yiningLogoutLink').addEventListener('click', function(e
         $.modal.closeLoading();
         window.open("/logout",'_self');
     },2000);
+}
+//TODO 伊宁转属逻辑
+document.getElementById('yiningLogoutLink').addEventListener('click', function(event) {
+    // // 阻止默认的链接跳转行为
+    event.preventDefault();
+    quitSystem();
+    
 });
 
 $(function() {

+ 13 - 3
ruoyi-admin/src/main/resources/templates/mainYiningView.html

@@ -204,8 +204,18 @@
         }else{
 		    url = url + "?"+"reqid=" + sessionId;
 		}
-		url = url + "&" + "clientId="+ sessionId + new Date().getTime();
-
+		// let clientId = sessionId + new Date().getTime();
+		// // let clientId = "uniqueClientId123";//测试用
+		// url = url + "&" + "clientId="+ clientId;
+		if(sessionStorage.getItem("reqids")) {
+            let arrays = JSON.parse(sessionStorage.getItem("reqids"));
+            arrays.push(sessionId);
+            sessionStorage.setItem("reqids",JSON.stringify(arrays));
+        }else{
+            let arrays = [];
+            arrays.push(sessionId);
+            sessionStorage.setItem("reqids",JSON.stringify(arrays));
+		}
 		console.log(url);
         window.open(url);
 	}
@@ -379,7 +389,7 @@
 
 	//TODO sse测试
     /*const clientId = 'uniqueClientId123'; // This should be unique for each client
-    const eventSource = new EventSource("/sse/subscribe?clientId="+clientId);
+    const eventSource = new EventSource("/sse/subscribe?clientId="+clientId+"&reqid="+sessionId);
 
     eventSource.onmessage = function(event) {
         const newElement = document.createElement("div");