package com.xuqm.tenant.service; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Service; import java.io.BufferedReader; import java.io.InputStreamReader; import java.util.List; import java.util.concurrent.CompletableFuture; import java.util.function.Consumer; @Service public class SystemUpdateService { private static final Logger log = LoggerFactory.getLogger(SystemUpdateService.class); private static final List OTHER_SERVICES = List.of( "file-service", "tenant-web", "im-service", "push-service", "update-service", "license-service" ); @Value("${PRIVATE_DEPLOY_ROOT:/opt/xuqm-private}") private String deployRoot; public void runUpdate(Consumer emit) { String composeFile = deployRoot + "/docker-compose.yml"; emit.accept(">>> 拉取最新镜像..."); for (String svc : OTHER_SERVICES) { if (isRunning(svc)) { emit.accept(" pulling " + svc + " ..."); exec(emit, "docker-compose", "-f", composeFile, "-p", "xuqm", "pull", "--quiet", svc); } } emit.accept(" pulling tenant-service ..."); exec(emit, "docker-compose", "-f", composeFile, "-p", "xuqm", "pull", "--quiet", "tenant-service"); emit.accept(">>> 镜像拉取完成"); emit.accept(">>> 重启各服务..."); for (String svc : OTHER_SERVICES) { if (isRunning(svc)) { emit.accept(" restarting " + svc + " ..."); exec(emit, "docker-compose", "-f", composeFile, "-p", "xuqm", "up", "-d", "--no-deps", "--force-recreate", svc); emit.accept(" " + svc + " ✓"); } } emit.accept(">>> 即将重启 tenant-service,连接将短暂中断..."); emit.accept("RESTART_SELF"); // 延迟 3 秒后重启自身,确保 SSE 事件已发送到客户端 CompletableFuture.runAsync(() -> { try { Thread.sleep(3000); exec(msg -> log.info("[self-restart] {}", msg), "docker-compose", "-f", composeFile, "-p", "xuqm", "up", "-d", "--no-deps", "--force-recreate", "tenant-service"); } catch (Exception e) { log.error("self-restart failed", e); } }); } private boolean isRunning(String service) { String containerName = "xuqm-" + service; try { Process p = new ProcessBuilder("docker", "inspect", "--type=container", containerName) .redirectErrorStream(true) .start(); p.getInputStream().transferTo(java.io.OutputStream.nullOutputStream()); return p.waitFor() == 0; } catch (Exception e) { return false; } } private void exec(Consumer emit, String... cmd) { try { Process p = new ProcessBuilder(cmd) .redirectErrorStream(true) .start(); try (BufferedReader reader = new BufferedReader(new InputStreamReader(p.getInputStream()))) { String line; while ((line = reader.readLine()) != null) { if (!line.isBlank()) emit.accept(" " + line); } } int code = p.waitFor(); if (code != 0) emit.accept(" [warn] exit code " + code); } catch (Exception e) { emit.accept(" [error] " + e.getMessage()); log.error("exec failed: {}", String.join(" ", cmd), e); } } }