XuqmGroup-Server/tenant-service/src/main/java/com/xuqm/tenant/controller/SystemUpdateController.java
XuqmGroup 167d403da6 feat(system): 添加系统更新管理和版本控制功能
- 新增私有化部署系统更新API接口(检查更新、选择性更新、重置等)
- 实现版本管理系统,支持平台版本和服务版本对比检查
- 集成Jenkinsfile自动化构建流程,支持多种版本策略
- 添加Docker镜像版本标签管理和自动注入功能
- 实现选择性更新机制,可指定服务进行增量更新
- 完善版本日志记录和更新历史追踪功能
2026-06-11 13:30:41 +08:00

141 行
5.8 KiB
Java

此文件含有模棱两可的 Unicode 字符

此文件含有可能会与其他字符混淆的 Unicode 字符。 如果您是想特意这样的,可以安全地忽略该警告。 使用 Escape 按钮显示他们。

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;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.servlet.mvc.method.annotation.StreamingResponseBody;
import java.util.List;
import java.util.Map;
import com.xuqm.common.model.ApiResponse;
@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;
}
/** 返回当前正在运行的服务列表。仅 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());
}
}
/** 返回当前部署版本号。仅 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)));
}
/**
* 检查是否有可用更新。
* 对比本地版本与 versions.json 中的最新版本,返回各服务的版本差异。
* 仅 PRIVATE 模式可用。
*/
@GetMapping("/check-update")
public ResponseEntity<?> checkUpdate() {
if (!deployProps.isPrivate()) {
return ResponseEntity.status(403).body(Map.of("message", "此接口仅在私有化部署可用"));
}
return ResponseEntity.ok(updateService.checkForUpdates());
}
/**
* 拉取最新镜像并重建所有容器。耗时较长(需 docker pull
* 仅 PRIVATE 模式可用。
*/
@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()));
}
return stream(emit -> updateService.runUpdate(emit));
}
/**
* 选择性更新:只更新指定的服务。
* 仅 PRIVATE 模式可用。
*/
@PostMapping(value = "/update-selective", produces = MediaType.TEXT_PLAIN_VALUE)
public ResponseEntity<StreamingResponseBody> updateSelective(
@RequestParam(required = false) List<String> services) {
if (!deployProps.isPrivate()) {
return ResponseEntity.status(403)
.contentType(MediaType.TEXT_PLAIN)
.body(out -> out.write("此接口仅在私有化部署可用\n".getBytes()));
}
return stream(emit -> updateService.runSelectiveUpdate(emit, services));
}
/**
* 保留数据,重置容器和数据库表结构。
* 流程:备份核心数据 → 删表 → 重建容器 → 恢复数据 → 执行迁移。
* 仅 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));
}
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) {}
});
return ResponseEntity.ok()
.contentType(MediaType.TEXT_PLAIN)
.header("X-Accel-Buffering", "no")
.header("Cache-Control", "no-cache, no-store")
.body(body);
}
}