|
@@ -0,0 +1,170 @@
|
|
|
+package com.ruoyi.web.controller.system;
|
|
|
+
|
|
|
+/**
|
|
|
+ * @ClassName SseServlet
|
|
|
+ * @Description: TODO
|
|
|
+ * @Author LX
|
|
|
+ * @Date 2025/5/15
|
|
|
+ * @Version V1.0
|
|
|
+ **/
|
|
|
+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.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.GetMapping;
|
|
|
+import org.springframework.web.bind.annotation.ResponseBody;
|
|
|
+
|
|
|
+import javax.annotation.PostConstruct;
|
|
|
+import javax.servlet.AsyncContext;
|
|
|
+import javax.servlet.ServletException;
|
|
|
+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.time.LocalDateTime;
|
|
|
+import java.util.Timer;
|
|
|
+import java.util.TimerTask;
|
|
|
+import java.util.concurrent.ConcurrentHashMap;
|
|
|
+
|
|
|
+@Controller
|
|
|
+public class SseServlet extends BaseController {
|
|
|
+// private static final ConcurrentHashMap<String, PrintWriter> connections = new ConcurrentHashMap<>();
|
|
|
+
|
|
|
+ private static final Logger log = LoggerFactory.getLogger(SseServlet.class);
|
|
|
+
|
|
|
+ private static final ConcurrentHashMap<String,ConcurrentHashMap<String, PrintWriter>> countConnections = new ConcurrentHashMap<>();
|
|
|
+
|
|
|
+ private static final ConcurrentHashMap<String,Timer> timerArray = new ConcurrentHashMap<>();
|
|
|
+
|
|
|
+ @GetMapping("/sse/subscribe")
|
|
|
+ protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
|
|
|
+ response.setContentType("text/event-stream");
|
|
|
+ response.setCharacterEncoding("UTF-8");
|
|
|
+ response.setHeader("Cache-Control", "no-cache");
|
|
|
+ response.setHeader("Connection", "keep-alive");
|
|
|
+
|
|
|
+ String clientId = request.getParameter("clientId");
|
|
|
+ if (clientId == null) {
|
|
|
+ response.setStatus(HttpServletResponse.SC_BAD_REQUEST);
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ // Start async processing
|
|
|
+ AsyncContext asyncContext = request.startAsync();
|
|
|
+
|
|
|
+ ConcurrentHashMap<String, PrintWriter> connections = new ConcurrentHashMap<>();
|
|
|
+ if (countConnections.get(ShiroUtils.getSessionId())!=null&&countConnections.get(ShiroUtils.getSessionId()).size()>0){
|
|
|
+ connections = countConnections.get(ShiroUtils.getSessionId());
|
|
|
+ }
|
|
|
+ PrintWriter writer = response.getWriter();
|
|
|
+ connections.put(clientId, writer);
|
|
|
+ countConnections.put(ShiroUtils.getSessionId(),connections);
|
|
|
+
|
|
|
+ // Set timeout for async context
|
|
|
+ asyncContext.setTimeout(0);
|
|
|
+ writer.println("data: Connected\n");
|
|
|
+ writer.flush();
|
|
|
+
|
|
|
+ //TODO 建立心跳定时包
|
|
|
+ final String sessionId = ShiroUtils.getSessionId();
|
|
|
+ Timer timer2 = new Timer();
|
|
|
+ TimerTask timerTask2 = new TimerTask() {
|
|
|
+ @Override
|
|
|
+ public void run() {
|
|
|
+ broadcastHeart(sessionId);
|
|
|
+ }
|
|
|
+ };
|
|
|
+ timer2.schedule(timerTask2, 2000, 2000);
|
|
|
+ timerArray.put(sessionId,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);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public void onTimeout(javax.servlet.AsyncEvent asyncEvent) throws IOException {
|
|
|
+ if(!CollectionUtils.isEmpty(countConnections)) {
|
|
|
+ countConnections.get(ShiroUtils.getSessionId()).remove(clientId);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public void onError(javax.servlet.AsyncEvent asyncEvent) throws IOException {
|
|
|
+ if(!CollectionUtils.isEmpty(countConnections)) {
|
|
|
+ countConnections.get(ShiroUtils.getSessionId()).remove(clientId);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public void onStartAsync(javax.servlet.AsyncEvent asyncEvent) throws IOException {
|
|
|
+ // No action needed here
|
|
|
+ System.out.println("开启链接");
|
|
|
+ }
|
|
|
+ });
|
|
|
+
|
|
|
+ log.info("建立链接成功:sessionId:"+sessionId+";clientId:"+clientId);
|
|
|
+ }
|
|
|
+
|
|
|
+ @GetMapping("/sse/logout")
|
|
|
+ @ResponseBody
|
|
|
+ protected AjaxResult logout(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
|
|
|
+ broadcastLogout();
|
|
|
+ return AjaxResult.success();
|
|
|
+ }
|
|
|
+
|
|
|
+ @Value("${sseUrl}")
|
|
|
+ private String sseUrl;
|
|
|
+
|
|
|
+ @GetMapping("/sse/geturl")
|
|
|
+ @ResponseBody
|
|
|
+ protected AjaxResult geturl(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
|
|
|
+ try {
|
|
|
+ return AjaxResult.success().put("data",sseUrl);
|
|
|
+ }catch(Exception ex){
|
|
|
+ return AjaxResult.error(ex.getLocalizedMessage());
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+ //TODO 广播
|
|
|
+ //TODO 心跳广播
|
|
|
+ public void broadcastHeart(String sessionId) {
|
|
|
+ if(!CollectionUtils.isEmpty(countConnections)) {
|
|
|
+ for (PrintWriter writer : countConnections.get(sessionId).values()) {
|
|
|
+ writer.println("event: heart");
|
|
|
+ writer.println("data: heart is running\n");
|
|
|
+ writer.flush();
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ public void broadcastLogout() {
|
|
|
+ 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();
|
|
|
+ }
|
|
|
+ }
|
|
|
+ countConnections.remove(ShiroUtils.getSessionId());
|
|
|
+ if(!CollectionUtils.isEmpty(timerArray)){
|
|
|
+ Timer timerCurrent = timerArray.get(ShiroUtils.getSessionId());
|
|
|
+ timerCurrent.cancel();
|
|
|
+ timerArray.remove(ShiroUtils.getSessionId());
|
|
|
+ }
|
|
|
+ log.info("退出指令发送:sessionId:"+ShiroUtils.getSessionId());
|
|
|
+ }
|
|
|
+}
|