XuqmGroup-Server/tenant-service/src/main/java/com/xuqm/tenant/service/SystemUpdateService.java
XuqmGroup f2e126e2d0 feat(tenant-service): 一键更新接口 + Dockerfile 添加 docker-compose
- 新增 SystemUpdateController POST /api/system/update(PRIVATE 模式)
- SystemUpdateService 通过 docker-compose 拉镜像并逐服务重建容器
- Dockerfile 添加 docker-cli + docker-compose(用于容器内调用 Docker API)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-21 14:46:40 +08:00

98 行
3.7 KiB
Java

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<String> 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<String> 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<String> 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);
}
}
}