2026-05-21 14:46:40 +08:00
|
|
|
|
package com.xuqm.tenant.controller;
|
|
|
|
|
|
|
|
|
|
|
|
import com.xuqm.tenant.config.PrivateDeploymentProperties;
|
|
|
|
|
|
import com.xuqm.tenant.service.SystemUpdateService;
|
|
|
|
|
|
import org.springframework.http.MediaType;
|
|
|
|
|
|
import org.springframework.http.ResponseEntity;
|
2026-05-22 23:04:36 +08:00
|
|
|
|
import org.springframework.web.bind.annotation.GetMapping;
|
2026-05-22 23:22:46 +08:00
|
|
|
|
import org.springframework.web.bind.annotation.PathVariable;
|
2026-05-21 14:46:40 +08:00
|
|
|
|
import org.springframework.web.bind.annotation.PostMapping;
|
|
|
|
|
|
import org.springframework.web.bind.annotation.RequestMapping;
|
2026-05-22 23:22:46 +08:00
|
|
|
|
import org.springframework.web.bind.annotation.RequestParam;
|
2026-05-21 14:46:40 +08:00
|
|
|
|
import org.springframework.web.bind.annotation.RestController;
|
|
|
|
|
|
import org.springframework.web.servlet.mvc.method.annotation.StreamingResponseBody;
|
|
|
|
|
|
|
2026-05-22 23:22:46 +08:00
|
|
|
|
import java.util.List;
|
2026-05-22 23:04:36 +08:00
|
|
|
|
import java.util.Map;
|
|
|
|
|
|
|
2026-05-21 14:46:40 +08:00
|
|
|
|
@RestController
|
|
|
|
|
|
@RequestMapping("/api/system")
|
|
|
|
|
|
public class SystemUpdateController {
|
|
|
|
|
|
|
|
|
|
|
|
private final PrivateDeploymentProperties deployProps;
|
|
|
|
|
|
private final SystemUpdateService updateService;
|
|
|
|
|
|
|
|
|
|
|
|
public SystemUpdateController(PrivateDeploymentProperties deployProps,
|
|
|
|
|
|
SystemUpdateService updateService) {
|
|
|
|
|
|
this.deployProps = deployProps;
|
|
|
|
|
|
this.updateService = updateService;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-05-22 23:22:46 +08:00
|
|
|
|
/** 返回当前正在运行的服务列表。仅 PRIVATE 模式可用。 */
|
|
|
|
|
|
@GetMapping("/services")
|
|
|
|
|
|
public ResponseEntity<?> services() {
|
|
|
|
|
|
if (!deployProps.isPrivate()) {
|
|
|
|
|
|
return ResponseEntity.status(403).body(Map.of("message", "此接口仅在私有化部署可用"));
|
|
|
|
|
|
}
|
|
|
|
|
|
List<String> svcList = updateService.getRunningServices();
|
|
|
|
|
|
return ResponseEntity.ok(Map.of("data", svcList));
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/** 返回指定服务最近 N 行日志。仅 PRIVATE 模式可用。 */
|
|
|
|
|
|
@GetMapping(value = "/logs/{service}", produces = MediaType.TEXT_PLAIN_VALUE)
|
|
|
|
|
|
public ResponseEntity<String> logs(
|
|
|
|
|
|
@PathVariable String service,
|
|
|
|
|
|
@RequestParam(defaultValue = "200") int lines) {
|
|
|
|
|
|
if (!deployProps.isPrivate()) {
|
|
|
|
|
|
return ResponseEntity.status(403).body("此接口仅在私有化部署可用");
|
|
|
|
|
|
}
|
|
|
|
|
|
try {
|
|
|
|
|
|
return ResponseEntity.ok(updateService.getServiceLogs(service, lines));
|
|
|
|
|
|
} catch (IllegalArgumentException e) {
|
|
|
|
|
|
return ResponseEntity.status(400).body(e.getMessage());
|
|
|
|
|
|
} catch (Exception e) {
|
|
|
|
|
|
return ResponseEntity.status(500).body(e.getMessage());
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-05-22 23:04:36 +08:00
|
|
|
|
/** 返回当前部署版本号。仅 PRIVATE 模式可用。 */
|
|
|
|
|
|
@GetMapping("/version")
|
|
|
|
|
|
public ResponseEntity<?> version() {
|
|
|
|
|
|
if (!deployProps.isPrivate()) {
|
|
|
|
|
|
return ResponseEntity.status(403).body(Map.of("message", "此接口仅在私有化部署可用"));
|
|
|
|
|
|
}
|
|
|
|
|
|
String currentVersion = updateService.readCurrentVersion();
|
|
|
|
|
|
return ResponseEntity.ok(Map.of("data", Map.of("currentVersion", currentVersion)));
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-05-21 14:46:40 +08:00
|
|
|
|
/**
|
2026-05-22 15:33:20 +08:00
|
|
|
|
* 拉取最新镜像并重建所有容器。耗时较长(需 docker pull)。
|
|
|
|
|
|
* 仅 PRIVATE 模式可用。
|
2026-05-21 14:46:40 +08:00
|
|
|
|
*/
|
|
|
|
|
|
@PostMapping(value = "/update", produces = MediaType.TEXT_PLAIN_VALUE)
|
|
|
|
|
|
public ResponseEntity<StreamingResponseBody> update() {
|
|
|
|
|
|
if (!deployProps.isPrivate()) {
|
|
|
|
|
|
return ResponseEntity.status(403)
|
|
|
|
|
|
.contentType(MediaType.TEXT_PLAIN)
|
|
|
|
|
|
.body(out -> out.write("此接口仅在私有化部署可用\n".getBytes()));
|
|
|
|
|
|
}
|
2026-05-22 15:33:20 +08:00
|
|
|
|
return stream(emit -> updateService.runUpdate(emit));
|
|
|
|
|
|
}
|
2026-05-21 14:46:40 +08:00
|
|
|
|
|
2026-05-22 15:33:20 +08:00
|
|
|
|
/**
|
2026-05-23 02:43:35 +08:00
|
|
|
|
* 保留数据,重置容器和数据库表结构。
|
|
|
|
|
|
* 流程:备份核心数据 → 删表 → 重建容器 → 恢复数据 → 执行迁移。
|
2026-05-22 15:33:20 +08:00
|
|
|
|
* 仅 PRIVATE 模式可用。
|
|
|
|
|
|
*/
|
|
|
|
|
|
@PostMapping(value = "/reset", produces = MediaType.TEXT_PLAIN_VALUE)
|
|
|
|
|
|
public ResponseEntity<StreamingResponseBody> reset() {
|
|
|
|
|
|
if (!deployProps.isPrivate()) {
|
|
|
|
|
|
return ResponseEntity.status(403)
|
|
|
|
|
|
.contentType(MediaType.TEXT_PLAIN)
|
|
|
|
|
|
.body(out -> out.write("此接口仅在私有化部署可用\n".getBytes()));
|
|
|
|
|
|
}
|
|
|
|
|
|
return stream(emit -> updateService.runReset(emit));
|
|
|
|
|
|
}
|
2026-05-21 14:46:40 +08:00
|
|
|
|
|
2026-05-22 15:33:20 +08:00
|
|
|
|
private ResponseEntity<StreamingResponseBody> stream(java.util.function.Consumer<java.util.function.Consumer<String>> action) {
|
|
|
|
|
|
StreamingResponseBody body = outputStream -> action.accept(line -> {
|
|
|
|
|
|
try {
|
|
|
|
|
|
outputStream.write((line + "\n").getBytes());
|
|
|
|
|
|
outputStream.flush();
|
|
|
|
|
|
} catch (Exception ignored) {}
|
|
|
|
|
|
});
|
2026-05-21 14:46:40 +08:00
|
|
|
|
return ResponseEntity.ok()
|
|
|
|
|
|
.contentType(MediaType.TEXT_PLAIN)
|
2026-05-27 12:27:42 +08:00
|
|
|
|
.header("X-Accel-Buffering", "no")
|
|
|
|
|
|
.header("Cache-Control", "no-cache, no-store")
|
2026-05-21 14:46:40 +08:00
|
|
|
|
.body(body);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|