Browse Source

开发中, 通过 websocket 来管理 docker 镜像和容器

reghao 5 months ago
parent
commit
bb6970f851
22 changed files with 413 additions and 72 deletions
  1. 1 1
      agent/src/main/java/cn/reghao/bnt/agent/AgentApp.java
  2. 4 2
      agent/src/main/java/cn/reghao/bnt/agent/ws/WsClient.java
  3. 7 3
      agent/src/main/java/cn/reghao/bnt/agent/ws/event/EventCenter.java
  4. 103 0
      agent/src/main/java/cn/reghao/bnt/agent/ws/event/handler/EvtDockerHandler.java
  5. 12 10
      common/src/main/java/cn/reghao/bnt/common/docker/DockerService.java
  6. 10 2
      common/src/main/java/cn/reghao/bnt/common/docker/model/ContainerOps.java
  7. 6 2
      common/src/main/java/cn/reghao/bnt/common/docker/model/DockerContainer.java
  8. 7 2
      common/src/main/java/cn/reghao/bnt/common/docker/model/DockerImage.java
  9. 5 2
      common/src/main/java/cn/reghao/bnt/common/docker/model/ImageDelete.java
  10. 24 0
      common/src/main/java/cn/reghao/bnt/common/docker/model/ImageQuery.java
  11. 9 0
      common/src/main/java/cn/reghao/bnt/common/msg/constant/DockerOps.java
  12. 26 0
      common/src/main/java/cn/reghao/bnt/common/msg/event/EvtDockerOps.java
  13. 26 0
      common/src/main/java/cn/reghao/bnt/common/msg/event/EvtDockerOpsResult.java
  14. 9 8
      web/src/main/java/cn/reghao/bnt/web/devops/machine/controller/DockerController.java
  15. 2 1
      web/src/main/java/cn/reghao/bnt/web/devops/machine/controller/MachineController.java
  16. 0 15
      web/src/main/java/cn/reghao/bnt/web/devops/machine/model/dto/ImageQuery.java
  17. 5 9
      web/src/main/java/cn/reghao/bnt/web/ws/EventDispatcherConfig.java
  18. 54 0
      web/src/main/java/cn/reghao/bnt/web/ws/SessionManagerFront.java
  19. 15 2
      web/src/main/java/cn/reghao/bnt/web/ws/WsSender.java
  20. 40 0
      web/src/main/java/cn/reghao/bnt/web/ws/event/DockerOpsService.java
  21. 36 0
      web/src/main/java/cn/reghao/bnt/web/ws/event/EvtDockerOpsResultHandler.java
  22. 12 13
      web/src/main/java/cn/reghao/bnt/web/ws/handler/FrontendHandler.java

+ 1 - 1
agent/src/main/java/cn/reghao/bnt/agent/AgentApp.java

@@ -70,7 +70,7 @@ public class AgentApp {
 		AppStat appStat = new AppStatImpl(dockerApp);
 
 		if (tryConnect(dagentConfig.getHost(), dagentConfig.getPort())) {
-			messageSender = new WsClient(dagentConfig, scheduler, appDeploy, appStat);
+			messageSender = new WsClient(dagentConfig, scheduler, appDeploy, appStat, docker);
 			return messageSender;
 		}
 

+ 4 - 2
agent/src/main/java/cn/reghao/bnt/agent/ws/WsClient.java

@@ -5,6 +5,7 @@ import cn.reghao.bnt.agent.ws.event.EventCenter;
 import cn.reghao.bnt.common.agent.app.iface.AppDeploy;
 import cn.reghao.bnt.common.agent.app.iface.AppStat;
 import cn.reghao.bnt.common.agent.machine.MachineEvent;
+import cn.reghao.bnt.common.docker.Docker;
 import cn.reghao.bnt.common.machine.Machine;
 import cn.reghao.bnt.common.msg.MessageSender;
 import cn.reghao.jutil.jdk.serializer.JdkSerializer;
@@ -31,13 +32,14 @@ public class WsClient implements MessageSender {
     private boolean retry;
     private int retryCount;
 
-    public WsClient(DagentConfig dagentConfig, ScheduledExecutorService scheduler, AppDeploy appDeploy, AppStat appStat) {
+    public WsClient(DagentConfig dagentConfig, ScheduledExecutorService scheduler,
+                    AppDeploy appDeploy, AppStat appStat, Docker docker) {
         String protocol = dagentConfig.getProtocol();
         String host = dagentConfig.getHost();
         int port = dagentConfig.getPort();
         this.url = String.format("%s://%s:%s/bgws/agent?token=%s", protocol, host, port, Machine.ID);
 
-        EventCenter eventCenter = new EventCenter(this, appDeploy, appStat);
+        EventCenter eventCenter = new EventCenter(this, appDeploy, appStat, docker);
         // 每 60s 发送一次心跳
         int heartbeatInterval = 60;
         MachineEvent machineEvent = new MachineEvent(this, new Machine(), appStat, scheduler, heartbeatInterval);

+ 7 - 3
agent/src/main/java/cn/reghao/bnt/agent/ws/event/EventCenter.java

@@ -2,11 +2,14 @@ package cn.reghao.bnt.agent.ws.event;
 
 import cn.reghao.bnt.agent.ws.event.handler.EvtAppDeployHandler;
 import cn.reghao.bnt.agent.ws.event.handler.EvtAppStatHandler;
+import cn.reghao.bnt.agent.ws.event.handler.EvtDockerHandler;
 import cn.reghao.bnt.common.agent.app.iface.AppDeploy;
 import cn.reghao.bnt.common.agent.app.iface.AppStat;
+import cn.reghao.bnt.common.docker.Docker;
 import cn.reghao.bnt.common.msg.MessageSender;
 import cn.reghao.bnt.common.msg.event.EvtAppDeploy;
 import cn.reghao.bnt.common.msg.event.EvtAppStat;
+import cn.reghao.bnt.common.msg.event.EvtDockerOps;
 import cn.reghao.jutil.jdk.event.message.Event;
 import cn.reghao.jutil.jdk.event.message.EventMessage;
 import cn.reghao.jutil.jdk.event.router.EventDispatcher;
@@ -24,15 +27,16 @@ public class EventCenter {
     private final long startTime;
     private final EventDispatcher dispatcher;
 
-    public EventCenter(MessageSender messageSender, AppDeploy appDeploy, AppStat appStat) {
+    public EventCenter(MessageSender messageSender, AppDeploy appDeploy, AppStat appStat, Docker docker) {
         this.startTime = ManagementFactory.getRuntimeMXBean().getStartTime();
         this.dispatcher = new EventDispatcher();
-        initDispatcher(messageSender, appDeploy, appStat);
+        initDispatcher(messageSender, appDeploy, appStat, docker);
     }
 
-    private void initDispatcher(MessageSender messageSender, AppDeploy appDeploy, AppStat appStat) {
+    private void initDispatcher(MessageSender messageSender, AppDeploy appDeploy, AppStat appStat, Docker docker) {
         dispatcher.register(EvtAppDeploy.class, new EvtAppDeployHandler(messageSender, appDeploy));
         dispatcher.register(EvtAppStat.class, new EvtAppStatHandler(messageSender, appStat));
+        dispatcher.register(EvtDockerOps.class, new EvtDockerHandler(messageSender, docker));
     }
 
     public void dispatch(EventMessage eventMessage) {

+ 103 - 0
agent/src/main/java/cn/reghao/bnt/agent/ws/event/handler/EvtDockerHandler.java

@@ -0,0 +1,103 @@
+package cn.reghao.bnt.agent.ws.event.handler;
+
+import cn.reghao.bnt.common.docker.Docker;
+import cn.reghao.bnt.common.docker.DockerService;
+import cn.reghao.bnt.common.docker.model.ContainerOps;
+import cn.reghao.bnt.common.docker.model.ImageDelete;
+import cn.reghao.bnt.common.docker.model.ImageQuery;
+import cn.reghao.bnt.common.machine.Machine;
+import cn.reghao.bnt.common.msg.MessageSender;
+import cn.reghao.bnt.common.msg.constant.DockerOps;
+import cn.reghao.bnt.common.msg.event.EvtDockerOps;
+import cn.reghao.bnt.common.msg.event.EvtDockerOpsResult;
+import cn.reghao.jutil.jdk.event.handler.Handler;
+import cn.reghao.jutil.jdk.event.message.Event;
+import cn.reghao.jutil.jdk.event.message.EventMessage;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.stream.Collectors;
+
+/**
+ * @author reghao
+ * @date 2025-12-16 20:03:03
+ */
+public class EvtDockerHandler extends Handler {
+    private final MessageSender messageSender;
+    private final DockerService dockerService;
+
+    public EvtDockerHandler(MessageSender messageSender, Docker docker) {
+        this.messageSender = messageSender;
+        this.dockerService = new DockerService(docker);
+    }
+
+    @Override
+    public void handle(Event evt) {
+        EvtDockerOps evtDockerOps = (EvtDockerOps) evt;
+
+        String ops = evtDockerOps.getOps();
+        List<String> payload = evtDockerOps.getPayload();
+        EvtDockerOpsResult evtDockerOpsResult = new EvtDockerOpsResult(ops, Machine.ID);
+        List<Object> list = new ArrayList<>();
+        try {
+            switch (DockerOps.valueOf(ops)) {
+                case imageList -> {
+                    ImageQuery imageQuery = new ImageQuery();
+                    list.addAll(dockerService.getDockerImages(imageQuery));
+                }
+                case imageRm -> {
+                    List<String> imageIds =  payload.stream().map(obj -> (String) obj).collect(Collectors.toList());
+                    ImageDelete imageDelete = new ImageDelete();
+                    imageDelete.setImageIds(imageIds);
+                    dockerService.rmDockerImages(imageDelete);
+                }
+                case containerList -> list.addAll(dockerService.getDockerContainers());
+                case containerRm -> {
+                    payload.forEach(obj -> {
+                        String containerId = (String) obj;
+                        ContainerOps containerOps = new ContainerOps(3, containerId);
+                        dockerService.handleDockerContainer(containerOps);
+                    });
+                }
+                case containerStart -> {
+                    payload.forEach(obj -> {
+                        String containerId = (String) obj;
+                        ContainerOps containerOps = new ContainerOps(1, containerId);
+                        dockerService.handleDockerContainer(containerOps);
+                    });
+                }
+                case containerStop -> {
+                    payload.forEach(obj -> {
+                        String containerId = (String) obj;
+                        ContainerOps containerOps = new ContainerOps(2, containerId);
+                        dockerService.handleDockerContainer(containerOps);
+                    });
+                }
+                default -> {
+                }
+            }
+        } catch (Exception e) {
+            e.printStackTrace();
+        }
+
+        int maxSize = 10;
+        List<Object> list0 = new ArrayList<>();
+        if (list.size() > maxSize) {
+            list0.addAll(list.subList(0, maxSize));
+        }
+
+        if (list0.isEmpty()) {
+            evtDockerOpsResult.setResultList(list);
+        } else {
+            evtDockerOpsResult.setResultList(list0);
+        }
+
+        EventMessage evtMsg = EventMessage.evt(evtDockerOpsResult);
+        try {
+            messageSender.send(Machine.ID, evtMsg);
+        } catch (IOException e) {
+            e.printStackTrace();
+        }
+    }
+}

+ 12 - 10
web/src/main/java/cn/reghao/bnt/web/devops/machine/service/DockerService.java → common/src/main/java/cn/reghao/bnt/common/docker/DockerService.java

@@ -1,15 +1,14 @@
-package cn.reghao.bnt.web.devops.machine.service;
+package cn.reghao.bnt.common.docker;
 
-import cn.reghao.bnt.common.docker.DockerImpl;
-import cn.reghao.bnt.web.devops.machine.model.vo.DockerContainer;
-import cn.reghao.bnt.web.devops.machine.model.vo.DockerImage;
-import cn.reghao.bnt.web.devops.machine.model.dto.ContainerOps;
-import cn.reghao.bnt.web.devops.machine.model.dto.ImageDelete;
-import cn.reghao.bnt.web.devops.machine.model.dto.ImageQuery;
+import cn.reghao.bnt.common.docker.Docker;
+import cn.reghao.bnt.common.docker.model.DockerContainer;
+import cn.reghao.bnt.common.docker.model.DockerImage;
+import cn.reghao.bnt.common.docker.model.ContainerOps;
+import cn.reghao.bnt.common.docker.model.ImageDelete;
+import cn.reghao.bnt.common.docker.model.ImageQuery;
 import com.github.dockerjava.api.command.InspectContainerResponse;
 import com.github.dockerjava.api.model.Image;
 import lombok.extern.slf4j.Slf4j;
-import org.springframework.stereotype.Service;
 
 import java.util.List;
 import java.util.Map;
@@ -21,9 +20,12 @@ import java.util.stream.Collectors;
  * @date 2025-12-09 11:01:17
  */
 @Slf4j
-@Service
 public class DockerService {
-    private final DockerImpl docker = new DockerImpl();
+    private final Docker docker;
+
+    public DockerService(Docker docker) {
+        this.docker = docker;
+    }
 
     public List<DockerImage> getDockerImages(ImageQuery imageQuery) {
         String keyword = imageQuery.getKeyword();

+ 10 - 2
web/src/main/java/cn/reghao/bnt/web/devops/machine/model/dto/ContainerOps.java → common/src/main/java/cn/reghao/bnt/common/docker/model/ContainerOps.java

@@ -1,17 +1,25 @@
-package cn.reghao.bnt.web.devops.machine.model.dto;
+package cn.reghao.bnt.common.docker.model;
 
 import jakarta.validation.constraints.NotBlank;
 import jakarta.validation.constraints.NotNull;
+import lombok.AllArgsConstructor;
 import lombok.Getter;
+import lombok.NoArgsConstructor;
 import lombok.Setter;
 
+import java.io.Serializable;
+
 /**
  * @author reghao
  * @date 2025-12-16 16:03:13
  */
+@AllArgsConstructor
+@NoArgsConstructor
 @Setter
 @Getter
-public class ContainerOps {
+public class ContainerOps implements Serializable {
+    private static final long serialVersionUID = 1L;
+
     @NotNull(message = "操作类型不能为空")
     private Integer opsType;
     @NotBlank(message = "容器ID不能为空")

+ 6 - 2
web/src/main/java/cn/reghao/bnt/web/devops/machine/model/vo/DockerContainer.java → common/src/main/java/cn/reghao/bnt/common/docker/model/DockerContainer.java

@@ -1,15 +1,19 @@
-package cn.reghao.bnt.web.devops.machine.model.vo;
+package cn.reghao.bnt.common.docker.model;
 
 import cn.reghao.jutil.jdk.converter.DateTimeConverter;
 import com.github.dockerjava.api.command.InspectContainerResponse;
 import lombok.Getter;
 
+import java.io.Serializable;
+
 /**
  * @author reghao
  * @date 2025-12-09 11:01:46
  */
 @Getter
-public class DockerContainer {
+public class DockerContainer implements Serializable {
+    private static final long serialVersionUID = 1L;
+
     private String containerId;
     private String name;
     private String createdAt;

+ 7 - 2
web/src/main/java/cn/reghao/bnt/web/devops/machine/model/vo/DockerImage.java → common/src/main/java/cn/reghao/bnt/common/docker/model/DockerImage.java

@@ -1,15 +1,20 @@
-package cn.reghao.bnt.web.devops.machine.model.vo;
+package cn.reghao.bnt.common.docker.model;
 
 import cn.reghao.jutil.jdk.converter.DateTimeConverter;
 import com.github.dockerjava.api.model.Image;
+import lombok.Getter;
 
+import java.io.Serializable;
 import java.util.List;
 
 /**
  * @author reghao
  * @date 2025-12-09 11:01:37
  */
-public class DockerImage {
+@Getter
+public class DockerImage implements Serializable {
+    private static final long serialVersionUID = 1L;
+
     private String imageId;
     private String repoTag;
     private String createdAt;

+ 5 - 2
web/src/main/java/cn/reghao/bnt/web/devops/machine/model/dto/ImageDelete.java → common/src/main/java/cn/reghao/bnt/common/docker/model/ImageDelete.java

@@ -1,9 +1,10 @@
-package cn.reghao.bnt.web.devops.machine.model.dto;
+package cn.reghao.bnt.common.docker.model;
 
 import jakarta.validation.constraints.Size;
 import lombok.Getter;
 import lombok.Setter;
 
+import java.io.Serializable;
 import java.util.List;
 
 /**
@@ -12,7 +13,9 @@ import java.util.List;
  */
 @Setter
 @Getter
-public class ImageDelete {
+public class ImageDelete implements Serializable {
+    private static final long serialVersionUID = 1L;
+
     private String machineId;
     @Size(min = 1, max = 10, message = "删除镜像数量范围在 1~10 之间")
     private List<String> imageIds;

+ 24 - 0
common/src/main/java/cn/reghao/bnt/common/docker/model/ImageQuery.java

@@ -0,0 +1,24 @@
+package cn.reghao.bnt.common.docker.model;
+
+import lombok.Getter;
+import lombok.Setter;
+
+import java.io.Serializable;
+
+/**
+ * @author reghao
+ * @date 2025-12-16 14:47:01
+ */
+@Setter
+@Getter
+public class ImageQuery implements Serializable {
+    private static final long serialVersionUID = 1L;
+
+    private int type;
+    private String keyword;
+
+    public ImageQuery() {
+        this.type = 1;
+        this.keyword = "";
+    }
+}

+ 9 - 0
common/src/main/java/cn/reghao/bnt/common/msg/constant/DockerOps.java

@@ -0,0 +1,9 @@
+package cn.reghao.bnt.common.msg.constant;
+
+/**
+ * @author reghao
+ * @date 2025-12-16 20:18:53
+ */
+public enum DockerOps {
+    imageList, imageRm, containerList, containerRm, containerStart, containerStop
+}

+ 26 - 0
common/src/main/java/cn/reghao/bnt/common/msg/event/EvtDockerOps.java

@@ -0,0 +1,26 @@
+package cn.reghao.bnt.common.msg.event;
+
+import cn.reghao.jutil.jdk.event.message.Event;
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+
+import java.util.List;
+
+/**
+ * @author reghao
+ * @date 2025-12-16 20:20:15
+ */
+@NoArgsConstructor
+@AllArgsConstructor
+@Getter
+public class EvtDockerOps extends Event {
+    private String ops;
+    private String machineId;
+    private List<String> payload;
+
+    public EvtDockerOps(String ops, String machineId) {
+        this.ops = ops;
+        this.machineId = machineId;
+    }
+}

+ 26 - 0
common/src/main/java/cn/reghao/bnt/common/msg/event/EvtDockerOpsResult.java

@@ -0,0 +1,26 @@
+package cn.reghao.bnt.common.msg.event;
+
+import cn.reghao.jutil.jdk.event.message.Event;
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+import lombok.Setter;
+
+import java.util.List;
+
+/**
+ * @author reghao
+ * @date 2025-12-16 20:20:29
+ */
+@AllArgsConstructor
+@Setter
+@Getter
+public class EvtDockerOpsResult extends Event {
+    private String ops;
+    private String machineId;
+    private List<Object> resultList;
+
+    public EvtDockerOpsResult(String ops, String machineId) {
+        this.ops = ops;
+        this.machineId = machineId;
+    }
+}

+ 9 - 8
web/src/main/java/cn/reghao/bnt/web/devops/machine/controller/DockerController.java

@@ -1,11 +1,12 @@
 package cn.reghao.bnt.web.devops.machine.controller;
 
-import cn.reghao.bnt.web.devops.machine.model.vo.DockerContainer;
-import cn.reghao.bnt.web.devops.machine.model.vo.DockerImage;
-import cn.reghao.bnt.web.devops.machine.service.DockerService;
-import cn.reghao.bnt.web.devops.machine.model.dto.ContainerOps;
-import cn.reghao.bnt.web.devops.machine.model.dto.ImageDelete;
-import cn.reghao.bnt.web.devops.machine.model.dto.ImageQuery;
+import cn.reghao.bnt.common.docker.DockerImpl;
+import cn.reghao.bnt.common.docker.model.DockerContainer;
+import cn.reghao.bnt.common.docker.model.DockerImage;
+import cn.reghao.bnt.common.docker.DockerService;
+import cn.reghao.bnt.common.docker.model.ContainerOps;
+import cn.reghao.bnt.common.docker.model.ImageDelete;
+import cn.reghao.bnt.common.docker.model.ImageQuery;
 import cn.reghao.jutil.web.WebResult;
 import io.swagger.v3.oas.annotations.Operation;
 import io.swagger.v3.oas.annotations.tags.Tag;
@@ -25,8 +26,8 @@ import java.util.List;
 public class DockerController {
     private final DockerService dockerService;
 
-    public DockerController(DockerService dockerService) {
-        this.dockerService = dockerService;
+    public DockerController() {
+        this.dockerService = new DockerService(new DockerImpl());
     }
 
     @Operation(summary = "获取 docker 镜像列表", description = "N")

+ 2 - 1
web/src/main/java/cn/reghao/bnt/web/devops/machine/controller/MachineController.java

@@ -22,6 +22,7 @@ import org.springframework.http.MediaType;
 import org.springframework.web.bind.annotation.*;
 
 import java.util.List;
+import java.util.stream.Collectors;
 
 /**
  * @author reghao
@@ -111,7 +112,7 @@ public class MachineController {
     @Operation(summary = "获取在线的 agent websocket 会话", description = "N")
     @GetMapping(value = "/ws", produces = MediaType.APPLICATION_JSON_VALUE)
     public String getWebSocketSessions(@RequestParam("env") String env) {
-        List<String> machineList = wsSender.getOnlineMachines(env);
+        List<SelectOption> machineList = wsSender.getOnlineMachines(env);
         return WebResult.success(machineList);
     }
 }

+ 0 - 15
web/src/main/java/cn/reghao/bnt/web/devops/machine/model/dto/ImageQuery.java

@@ -1,15 +0,0 @@
-package cn.reghao.bnt.web.devops.machine.model.dto;
-
-import lombok.Getter;
-import lombok.Setter;
-
-/**
- * @author reghao
- * @date 2025-12-16 14:47:01
- */
-@Setter
-@Getter
-public class ImageQuery {
-    private int type;
-    private String keyword;
-}

+ 5 - 9
web/src/main/java/cn/reghao/bnt/web/ws/EventDispatcherConfig.java

@@ -1,16 +1,10 @@
 package cn.reghao.bnt.web.ws;
 
-import cn.reghao.bnt.common.msg.event.EvtAgentHeartbeat;
-import cn.reghao.bnt.common.msg.event.EvtAgentStart;
-import cn.reghao.bnt.common.msg.event.EvtAppStatResult;
-import cn.reghao.bnt.common.msg.event.EvtTaskResult;
+import cn.reghao.bnt.common.msg.event.*;
 import cn.reghao.bnt.web.devops.app.service.AppDeployService;
 import cn.reghao.bnt.web.devops.machine.service.MachineService;
 import cn.reghao.bnt.web.devops.machine.service.MachineTaskService;
-import cn.reghao.bnt.web.ws.event.EvtAgentHeartbeatHandler;
-import cn.reghao.bnt.web.ws.event.EvtAgentStartHandler;
-import cn.reghao.bnt.web.ws.event.EvtAppStatResultHandler;
-import cn.reghao.bnt.web.ws.event.EvtTaskResultHandler;
+import cn.reghao.bnt.web.ws.event.*;
 import cn.reghao.jutil.jdk.event.router.EventDispatcher;
 import org.springframework.context.annotation.Bean;
 import org.springframework.context.annotation.Configuration;
@@ -23,13 +17,15 @@ import org.springframework.context.annotation.Configuration;
 public class EventDispatcherConfig {
     @Bean
     public EventDispatcher eventDispatcher(MachineService machineService, AppDeployService appDeployService,
-                                           MachineTaskService machineTaskService) {
+                                           MachineTaskService machineTaskService,
+                                           SessionManagerFront sessionManagerFront) {
         EventDispatcher dispatcher = new EventDispatcher();
         dispatcher.register(EvtAgentStart.class, new EvtAgentStartHandler(machineService));
         dispatcher.register(EvtAgentHeartbeat.class, new EvtAgentHeartbeatHandler(machineService));
 
         dispatcher.register(EvtAppStatResult.class, new EvtAppStatResultHandler(appDeployService));
         dispatcher.register(EvtTaskResult.class, new EvtTaskResultHandler(machineTaskService));
+        dispatcher.register(EvtDockerOpsResult.class, new EvtDockerOpsResultHandler(sessionManagerFront));
         return dispatcher;
     }
 }

+ 54 - 0
web/src/main/java/cn/reghao/bnt/web/ws/SessionManagerFront.java

@@ -0,0 +1,54 @@
+package cn.reghao.bnt.web.ws;
+
+import cn.reghao.bnt.web.devops.machine.model.po.MachineHost;
+import cn.reghao.bnt.web.devops.machine.service.MachineQuery;
+import cn.reghao.jutil.jdk.serializer.JdkSerializer;
+import cn.reghao.jutil.jdk.serializer.JsonConverter;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.stereotype.Component;
+import org.springframework.web.socket.BinaryMessage;
+import org.springframework.web.socket.TextMessage;
+import org.springframework.web.socket.WebSocketSession;
+
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.stream.Collectors;
+
+/**
+ * @author reghao
+ * @date 2025-12-16 21:19:26
+ */
+@Slf4j
+@Component
+public class SessionManagerFront {
+    private final static Map<String, WebSocketSession> sessionMap = new HashMap<>();
+    private final MachineQuery machineQuery;
+
+    public SessionManagerFront(MachineQuery machineQuery) {
+        this.machineQuery = machineQuery;
+    }
+
+    public void addSession(String machineId, WebSocketSession session) {
+        sessionMap.put(machineId, session);
+    }
+
+    public void removeSession(String machineId) {
+        sessionMap.remove(machineId);
+    }
+
+    public void send(String dest, Object message) throws IOException {
+        if (dest.isBlank()) {
+            sessionMap.values().forEach(webSocketSession -> {
+                TextMessage textMessage = new TextMessage(JsonConverter.objectToJson(message));
+                try {
+                    webSocketSession.sendMessage(textMessage);
+                } catch (IOException e) {
+                    e.printStackTrace();
+                }
+            });
+        }
+    }
+}

+ 15 - 2
web/src/main/java/cn/reghao/bnt/web/ws/WsSender.java

@@ -2,7 +2,9 @@ package cn.reghao.bnt.web.ws;
 
 import cn.reghao.bnt.web.devops.machine.model.po.MachineHost;
 import cn.reghao.bnt.web.devops.machine.service.MachineQuery;
+import cn.reghao.bnt.web.util.SelectOption;
 import cn.reghao.jutil.jdk.serializer.JdkSerializer;
+import lombok.extern.slf4j.Slf4j;
 import org.springframework.stereotype.Component;
 import org.springframework.web.socket.BinaryMessage;
 import org.springframework.web.socket.WebSocketSession;
@@ -18,6 +20,7 @@ import java.util.stream.Collectors;
  * @author reghao
  * @date 2023-03-01 11:03:31
  */
+@Slf4j
 @Component
 public class WsSender {
     private final static Map<String, WebSocketSession> sessionMap = new HashMap<>();
@@ -37,12 +40,17 @@ public class WsSender {
 
     public void send(String dest, Object message) throws IOException {
         WebSocketSession session = sessionMap.get(dest);
+        if (session == null) {
+            log.error("websocket {} 不在线", dest);
+            return;
+        }
+
         byte[] bytes = JdkSerializer.serialize(message);
         BinaryMessage binaryMessage = new BinaryMessage(bytes);
         session.sendMessage(binaryMessage);
     }
 
-    public List<String> getOnlineMachines(String env) {
+    public List<SelectOption> getOnlineMachines(String env) {
         return sessionMap.keySet().stream()
                 .map(machineId -> {
                     MachineHost machineHost = machineQuery.getMachineHost(machineId);
@@ -51,7 +59,12 @@ public class WsSender {
                     }
 
                     String env1 = machineHost.getEnv();
-                    return env1.equals(env) ? machineHost.getMachineIpv4() : null;
+                    if (!env1.equals(env)) {
+                        return null;
+                    }
+
+                    String machineIpv4 = machineHost.getMachineIpv4();
+                    return new SelectOption(machineIpv4, machineId);
                 })
                 .filter(Objects::nonNull)
                 .collect(Collectors.toList());

+ 40 - 0
web/src/main/java/cn/reghao/bnt/web/ws/event/DockerOpsService.java

@@ -0,0 +1,40 @@
+package cn.reghao.bnt.web.ws.event;
+
+import cn.reghao.bnt.common.msg.constant.DockerOps;
+import cn.reghao.bnt.common.msg.event.EvtDockerOps;
+import cn.reghao.bnt.web.devops.machine.service.MachineQuery;
+import cn.reghao.bnt.web.ws.WsSender;
+import cn.reghao.jutil.jdk.event.message.EventMessage;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.stereotype.Service;
+
+import java.io.IOException;
+
+/**
+ * @author reghao
+ * @date 2025-12-16 20:41:53
+ */
+@Slf4j
+@Service
+public class DockerOpsService {
+    private final WsSender wsSender;
+
+    public DockerOpsService(WsSender wsSender) {
+        this.wsSender = wsSender;
+    }
+
+    public void sendDockerOps(String ops, String machineId) {
+        String ops1 = DockerOps.containerList.name();
+        EvtDockerOps evtDockerOps = new EvtDockerOps(ops, machineId);
+        EventMessage evtMsg = EventMessage.evt(evtDockerOps);
+        publish(machineId, evtMsg);
+    }
+
+    private void publish(String machineId, EventMessage evtMsg) {
+        try {
+            wsSender.send(machineId, evtMsg);
+        } catch (IOException e) {
+            e.printStackTrace();
+        }
+    }
+}

+ 36 - 0
web/src/main/java/cn/reghao/bnt/web/ws/event/EvtDockerOpsResultHandler.java

@@ -0,0 +1,36 @@
+package cn.reghao.bnt.web.ws.event;
+
+import cn.reghao.bnt.common.msg.event.EvtDockerOpsResult;
+import cn.reghao.bnt.web.ws.SessionManagerFront;
+import cn.reghao.bnt.web.ws.WsSender;
+import cn.reghao.jutil.jdk.event.handler.Handler;
+import cn.reghao.jutil.jdk.event.message.Event;
+
+import java.io.IOException;
+import java.util.List;
+
+/**
+ * @author reghao
+ * @date 2025-12-16 20:38:08
+ */
+public class EvtDockerOpsResultHandler extends Handler {
+    private final SessionManagerFront sessionManagerFront;
+
+    public EvtDockerOpsResultHandler(SessionManagerFront sessionManagerFront) {
+        this.sessionManagerFront = sessionManagerFront;
+    }
+
+    @Override
+    public void handle(Event evt) {
+        EvtDockerOpsResult evtDockerOpsResult = (EvtDockerOpsResult) evt;
+        String ops = evtDockerOpsResult.getOps();
+        String machineId = evtDockerOpsResult.getMachineId();
+        List<Object> resultList = evtDockerOpsResult.getResultList();
+
+        try {
+            sessionManagerFront.send("", evtDockerOpsResult);
+        } catch (IOException e) {
+            e.printStackTrace();
+        }
+    }
+}

+ 12 - 13
web/src/main/java/cn/reghao/bnt/web/ws/handler/FrontendHandler.java

@@ -1,6 +1,8 @@
 package cn.reghao.bnt.web.ws.handler;
 
+import cn.reghao.bnt.web.ws.SessionManagerFront;
 import cn.reghao.bnt.web.ws.WsSender;
+import cn.reghao.bnt.web.ws.event.DockerOpsService;
 import cn.reghao.jutil.jdk.serializer.JdkSerializer;
 import cn.reghao.jutil.jdk.serializer.JsonConverter;
 import com.google.gson.JsonObject;
@@ -18,20 +20,25 @@ import java.util.*;
 @Slf4j
 @Component("frontendHander")
 public class FrontendHandler implements WebSocketHandler {
-    private WsSender wsSender;
+    private final SessionManagerFront sessionManagerFront;
+    private final DockerOpsService dockerOpsService;
 
-    public FrontendHandler(WsSender wsSender) {
-        this.wsSender = wsSender;
+    public FrontendHandler(SessionManagerFront sessionManagerFront, DockerOpsService dockerOpsService) {
+        this.sessionManagerFront = sessionManagerFront;
+        this.dockerOpsService = dockerOpsService;
     }
 
     @Override
     public void afterConnectionEstablished(WebSocketSession webSocketSession) throws IOException {
         String sessionId = webSocketSession.getId();
         String query = webSocketSession.getUri().getQuery();
+        sessionManagerFront.addSession(sessionId, webSocketSession);
         log.info("WebSocket 建立连接");
     }
 
     private void removeSession(WebSocketSession webSocketSession) {
+        String sessionId = webSocketSession.getId();
+        sessionManagerFront.removeSession(sessionId);
     }
 
     @Override
@@ -42,17 +49,9 @@ public class FrontendHandler implements WebSocketHandler {
                 log.info("接收到 WebSocket 文本消息");
                 String jsonPayload = (String) webSocketMessage.getPayload();
                 JsonObject jsonObject = JsonConverter.jsonToJsonElement(jsonPayload).getAsJsonObject();
+                String ops = jsonObject.get("ops").getAsString();
                 String machineId = jsonObject.get("machineId").getAsString();
-
-                wsSender.send(machineId, new Object());
-                List<String> list = new ArrayList<>();
-                list.add("aaa");
-                list.add("bbb");
-                Map<String, Object> map = new HashMap<>();
-                map.put("type", "1");
-                map.put("result", list);
-                TextMessage textMessage = new TextMessage(JsonConverter.objectToJson(map));
-                webSocketSession.sendMessage(textMessage);
+                dockerOpsService.sendDockerOps(ops, machineId);
             } else if (webSocketMessage instanceof BinaryMessage) {
                 log.info("接收到 WebSocket 二进制消息");
             } else if (webSocketMessage instanceof PingMessage) {