1.30增加K8S支持
This commit is contained in:
parent
4c461ea42a
commit
0ed6c1e126
@ -281,6 +281,13 @@
|
|||||||
<artifactId>HikariCP</artifactId>
|
<artifactId>HikariCP</artifactId>
|
||||||
<version>5.0.1</version>
|
<version>5.0.1</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
|
<!-- Kubernetes Java Client (支持K8S 1.23-1.28) -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>io.kubernetes</groupId>
|
||||||
|
<artifactId>client-java</artifactId>
|
||||||
|
<version>18.0.1</version>
|
||||||
|
</dependency>
|
||||||
</dependencies>
|
</dependencies>
|
||||||
|
|
||||||
<build>
|
<build>
|
||||||
|
|||||||
@ -0,0 +1,106 @@
|
|||||||
|
package com.qqchen.deploy.backend.deploy.api;
|
||||||
|
|
||||||
|
import com.qqchen.deploy.backend.deploy.dto.K8sDeploymentDTO;
|
||||||
|
import com.qqchen.deploy.backend.deploy.entity.K8sDeployment;
|
||||||
|
import com.qqchen.deploy.backend.deploy.query.K8sDeploymentQuery;
|
||||||
|
import com.qqchen.deploy.backend.deploy.service.IK8sDeploymentService;
|
||||||
|
import com.qqchen.deploy.backend.framework.api.Response;
|
||||||
|
import com.qqchen.deploy.backend.framework.controller.BaseController;
|
||||||
|
import io.swagger.v3.oas.annotations.Operation;
|
||||||
|
import io.swagger.v3.oas.annotations.Parameter;
|
||||||
|
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||||
|
import jakarta.annotation.Resource;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.data.domain.Page;
|
||||||
|
import org.springframework.validation.annotation.Validated;
|
||||||
|
import org.springframework.web.bind.annotation.*;
|
||||||
|
|
||||||
|
import jakarta.servlet.http.HttpServletResponse;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.concurrent.CompletableFuture;
|
||||||
|
|
||||||
|
@Slf4j
|
||||||
|
@RestController
|
||||||
|
@RequestMapping("/api/v1/k8s-deployment")
|
||||||
|
@Tag(name = "K8S Deployment管理", description = "K8S Deployment管理相关接口")
|
||||||
|
public class K8sDeploymentApiController extends BaseController<K8sDeployment, K8sDeploymentDTO, Long, K8sDeploymentQuery> {
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
private IK8sDeploymentService k8sDeploymentService;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Response<K8sDeploymentDTO> create(@Validated @RequestBody K8sDeploymentDTO dto) {
|
||||||
|
return super.create(dto);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Response<K8sDeploymentDTO> update(@PathVariable Long id, @Validated @RequestBody K8sDeploymentDTO dto) {
|
||||||
|
return super.update(id, dto);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Response<Void> delete(@PathVariable Long id) {
|
||||||
|
return super.delete(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Response<K8sDeploymentDTO> findById(@PathVariable Long id) {
|
||||||
|
return super.findById(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Response<List<K8sDeploymentDTO>> findAll() {
|
||||||
|
return super.findAll();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Response<Page<K8sDeploymentDTO>> page(K8sDeploymentQuery query) {
|
||||||
|
return super.page(query);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Response<List<K8sDeploymentDTO>> findAll(K8sDeploymentQuery query) {
|
||||||
|
return super.findAll(query);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public CompletableFuture<Response<Void>> batchProcess(List<K8sDeploymentDTO> dtos) {
|
||||||
|
return super.batchProcess(dtos);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Operation(summary = "同步K8S Deployment", description = "异步同步,支持两种模式:1)只传externalSystemId-全量同步 2)传externalSystemId+namespaceId-同步指定命名空间")
|
||||||
|
@PostMapping("/sync")
|
||||||
|
public Response<Void> sync(
|
||||||
|
@Parameter(description = "K8S集群ID(外部系统ID)", required = true) @RequestParam Long externalSystemId,
|
||||||
|
@Parameter(description = "命名空间ID(可选)", required = false) @RequestParam(required = false) Long namespaceId
|
||||||
|
) {
|
||||||
|
if (namespaceId != null) {
|
||||||
|
k8sDeploymentService.syncDeployments(externalSystemId, namespaceId);
|
||||||
|
} else {
|
||||||
|
k8sDeploymentService.syncDeployments(externalSystemId);
|
||||||
|
}
|
||||||
|
return Response.success();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Operation(summary = "根据集群ID查询Deployment", description = "查询指定K8S集群的所有Deployment")
|
||||||
|
@GetMapping("/by-system/{externalSystemId}")
|
||||||
|
public Response<List<K8sDeploymentDTO>> findByExternalSystemId(
|
||||||
|
@Parameter(description = "K8S集群ID", required = true) @PathVariable Long externalSystemId
|
||||||
|
) {
|
||||||
|
return Response.success(k8sDeploymentService.findByExternalSystemId(externalSystemId));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Operation(summary = "根据命名空间ID查询Deployment", description = "查询指定命名空间的所有Deployment")
|
||||||
|
@GetMapping("/by-namespace/{namespaceId}")
|
||||||
|
public Response<List<K8sDeploymentDTO>> findByNamespaceId(
|
||||||
|
@Parameter(description = "命名空间ID", required = true) @PathVariable Long namespaceId
|
||||||
|
) {
|
||||||
|
return Response.success(k8sDeploymentService.findByNamespaceId(namespaceId));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void exportData(HttpServletResponse response, List<K8sDeploymentDTO> data) {
|
||||||
|
log.info("导出K8S Deployment数据,数据量:{}", data.size());
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,85 @@
|
|||||||
|
package com.qqchen.deploy.backend.deploy.api;
|
||||||
|
|
||||||
|
import com.qqchen.deploy.backend.deploy.dto.K8sNamespaceDTO;
|
||||||
|
import com.qqchen.deploy.backend.deploy.entity.K8sNamespace;
|
||||||
|
import com.qqchen.deploy.backend.deploy.query.K8sNamespaceQuery;
|
||||||
|
import com.qqchen.deploy.backend.deploy.service.IK8sNamespaceService;
|
||||||
|
import com.qqchen.deploy.backend.framework.api.Response;
|
||||||
|
import com.qqchen.deploy.backend.framework.controller.BaseController;
|
||||||
|
import io.swagger.v3.oas.annotations.Operation;
|
||||||
|
import io.swagger.v3.oas.annotations.Parameter;
|
||||||
|
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||||
|
import jakarta.annotation.Resource;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.data.domain.Page;
|
||||||
|
import org.springframework.validation.annotation.Validated;
|
||||||
|
import org.springframework.web.bind.annotation.*;
|
||||||
|
|
||||||
|
import jakarta.servlet.http.HttpServletResponse;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.concurrent.CompletableFuture;
|
||||||
|
|
||||||
|
@Slf4j
|
||||||
|
@RestController
|
||||||
|
@RequestMapping("/api/v1/k8s-namespace")
|
||||||
|
@Tag(name = "K8S命名空间管理", description = "K8S命名空间管理相关接口")
|
||||||
|
public class K8sNamespaceApiController extends BaseController<K8sNamespace, K8sNamespaceDTO, Long, K8sNamespaceQuery> {
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
private IK8sNamespaceService k8sNamespaceService;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Response<K8sNamespaceDTO> create(@Validated @RequestBody K8sNamespaceDTO dto) {
|
||||||
|
return super.create(dto);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Response<K8sNamespaceDTO> update(@PathVariable Long id, @Validated @RequestBody K8sNamespaceDTO dto) {
|
||||||
|
return super.update(id, dto);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Response<Void> delete(@PathVariable Long id) {
|
||||||
|
return super.delete(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Response<K8sNamespaceDTO> findById(@PathVariable Long id) {
|
||||||
|
return super.findById(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Response<List<K8sNamespaceDTO>> findAll() {
|
||||||
|
return super.findAll();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Response<Page<K8sNamespaceDTO>> page(K8sNamespaceQuery query) {
|
||||||
|
return super.page(query);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Response<List<K8sNamespaceDTO>> findAll(K8sNamespaceQuery query) {
|
||||||
|
return super.findAll(query);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public CompletableFuture<Response<Void>> batchProcess(List<K8sNamespaceDTO> dtos) {
|
||||||
|
return super.batchProcess(dtos);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Operation(summary = "同步K8S命名空间", description = "异步同步指定K8S集群的命名空间")
|
||||||
|
@PostMapping("/sync")
|
||||||
|
public Response<Void> sync(
|
||||||
|
@Parameter(description = "K8S集群ID(外部系统ID)", required = true) @RequestParam Long externalSystemId
|
||||||
|
) {
|
||||||
|
k8sNamespaceService.syncNamespaces(externalSystemId);
|
||||||
|
return Response.success();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void exportData(HttpServletResponse response, List<K8sNamespaceDTO> data) {
|
||||||
|
log.info("导出K8S命名空间数据,数据量:{}", data.size());
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,84 @@
|
|||||||
|
package com.qqchen.deploy.backend.deploy.api;
|
||||||
|
|
||||||
|
import com.qqchen.deploy.backend.deploy.dto.K8sSyncHistoryDTO;
|
||||||
|
import com.qqchen.deploy.backend.deploy.entity.K8sSyncHistory;
|
||||||
|
import com.qqchen.deploy.backend.deploy.query.K8sSyncHistoryQuery;
|
||||||
|
import com.qqchen.deploy.backend.deploy.service.IK8sSyncHistoryService;
|
||||||
|
import com.qqchen.deploy.backend.framework.api.Response;
|
||||||
|
import com.qqchen.deploy.backend.framework.controller.BaseController;
|
||||||
|
import io.swagger.v3.oas.annotations.Operation;
|
||||||
|
import io.swagger.v3.oas.annotations.Parameter;
|
||||||
|
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||||
|
import jakarta.annotation.Resource;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.data.domain.Page;
|
||||||
|
import org.springframework.validation.annotation.Validated;
|
||||||
|
import org.springframework.web.bind.annotation.*;
|
||||||
|
|
||||||
|
import jakarta.servlet.http.HttpServletResponse;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.concurrent.CompletableFuture;
|
||||||
|
|
||||||
|
@Slf4j
|
||||||
|
@RestController
|
||||||
|
@RequestMapping("/api/v1/k8s-sync-history")
|
||||||
|
@Tag(name = "K8S同步历史管理", description = "K8S同步历史管理相关接口")
|
||||||
|
public class K8sSyncHistoryApiController extends BaseController<K8sSyncHistory, K8sSyncHistoryDTO, Long, K8sSyncHistoryQuery> {
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
private IK8sSyncHistoryService k8sSyncHistoryService;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Response<K8sSyncHistoryDTO> create(@Validated @RequestBody K8sSyncHistoryDTO dto) {
|
||||||
|
return super.create(dto);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Response<K8sSyncHistoryDTO> update(@PathVariable Long id, @Validated @RequestBody K8sSyncHistoryDTO dto) {
|
||||||
|
return super.update(id, dto);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Response<Void> delete(@PathVariable Long id) {
|
||||||
|
return super.delete(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Response<K8sSyncHistoryDTO> findById(@PathVariable Long id) {
|
||||||
|
return super.findById(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Response<List<K8sSyncHistoryDTO>> findAll() {
|
||||||
|
return super.findAll();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Response<Page<K8sSyncHistoryDTO>> page(K8sSyncHistoryQuery query) {
|
||||||
|
return super.page(query);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Response<List<K8sSyncHistoryDTO>> findAll(K8sSyncHistoryQuery query) {
|
||||||
|
return super.findAll(query);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public CompletableFuture<Response<Void>> batchProcess(List<K8sSyncHistoryDTO> dtos) {
|
||||||
|
return super.batchProcess(dtos);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Operation(summary = "根据集群ID查询同步历史", description = "查询指定K8S集群的同步历史记录")
|
||||||
|
@GetMapping("/by-system/{externalSystemId}")
|
||||||
|
public Response<List<K8sSyncHistoryDTO>> findByExternalSystemId(
|
||||||
|
@Parameter(description = "K8S集群ID", required = true) @PathVariable Long externalSystemId
|
||||||
|
) {
|
||||||
|
return Response.success(k8sSyncHistoryService.findByExternalSystemId(externalSystemId));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void exportData(HttpServletResponse response, List<K8sSyncHistoryDTO> data) {
|
||||||
|
log.info("导出K8S同步历史数据,数据量:{}", data.size());
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -84,6 +84,29 @@ public class ThreadPoolConfig {
|
|||||||
return executor;
|
return executor;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* K8S资源同步线程池 - 使用虚拟线程(Java 21+)
|
||||||
|
*
|
||||||
|
* ⚠️ 为什么使用虚拟线程?
|
||||||
|
* 1. K8S API调用是典型的**网络I/O密集型**任务
|
||||||
|
* 2. 等待K8S API响应时线程会长时间阻塞
|
||||||
|
* 3. 虚拟线程在阻塞时不占用OS线程,资源消耗极低
|
||||||
|
* 4. 支持数百个并发K8S资源同步(Namespace、Deployment、Pod等)
|
||||||
|
*
|
||||||
|
* 💡 场景:
|
||||||
|
* - 定时同步K8S命名空间
|
||||||
|
* - 定时同步K8S Deployment
|
||||||
|
* - 实时查询Pod状态
|
||||||
|
* - 多集群并发同步
|
||||||
|
*/
|
||||||
|
@Bean("k8sTaskExecutor")
|
||||||
|
public SimpleAsyncTaskExecutor k8sTaskExecutor() {
|
||||||
|
SimpleAsyncTaskExecutor executor = new SimpleAsyncTaskExecutor("k8s-virtual-");
|
||||||
|
executor.setVirtualThreads(true);
|
||||||
|
executor.setConcurrencyLimit(-1); // 无限制,支持多集群并发
|
||||||
|
return executor;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 通用应用任务线程池 - 保留平台线程(不使用虚拟线程)
|
* 通用应用任务线程池 - 保留平台线程(不使用虚拟线程)
|
||||||
*
|
*
|
||||||
|
|||||||
@ -0,0 +1,10 @@
|
|||||||
|
package com.qqchen.deploy.backend.deploy.converter;
|
||||||
|
|
||||||
|
import com.qqchen.deploy.backend.deploy.dto.K8sDeploymentDTO;
|
||||||
|
import com.qqchen.deploy.backend.deploy.entity.K8sDeployment;
|
||||||
|
import com.qqchen.deploy.backend.framework.converter.BaseConverter;
|
||||||
|
import org.mapstruct.Mapper;
|
||||||
|
|
||||||
|
@Mapper(config = BaseConverter.class)
|
||||||
|
public interface K8sDeploymentConverter extends BaseConverter<K8sDeployment, K8sDeploymentDTO> {
|
||||||
|
}
|
||||||
@ -0,0 +1,10 @@
|
|||||||
|
package com.qqchen.deploy.backend.deploy.converter;
|
||||||
|
|
||||||
|
import com.qqchen.deploy.backend.deploy.dto.K8sNamespaceDTO;
|
||||||
|
import com.qqchen.deploy.backend.deploy.entity.K8sNamespace;
|
||||||
|
import com.qqchen.deploy.backend.framework.converter.BaseConverter;
|
||||||
|
import org.mapstruct.Mapper;
|
||||||
|
|
||||||
|
@Mapper(config = BaseConverter.class)
|
||||||
|
public interface K8sNamespaceConverter extends BaseConverter<K8sNamespace, K8sNamespaceDTO> {
|
||||||
|
}
|
||||||
@ -0,0 +1,10 @@
|
|||||||
|
package com.qqchen.deploy.backend.deploy.converter;
|
||||||
|
|
||||||
|
import com.qqchen.deploy.backend.deploy.dto.K8sSyncHistoryDTO;
|
||||||
|
import com.qqchen.deploy.backend.deploy.entity.K8sSyncHistory;
|
||||||
|
import com.qqchen.deploy.backend.framework.converter.BaseConverter;
|
||||||
|
import org.mapstruct.Mapper;
|
||||||
|
|
||||||
|
@Mapper(config = BaseConverter.class)
|
||||||
|
public interface K8sSyncHistoryConverter extends BaseConverter<K8sSyncHistory, K8sSyncHistoryDTO> {
|
||||||
|
}
|
||||||
@ -0,0 +1,51 @@
|
|||||||
|
package com.qqchen.deploy.backend.deploy.dto;
|
||||||
|
|
||||||
|
import com.qqchen.deploy.backend.framework.dto.BaseDTO;
|
||||||
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.EqualsAndHashCode;
|
||||||
|
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
@EqualsAndHashCode(callSuper = true)
|
||||||
|
@Schema(description = "K8S Deployment DTO")
|
||||||
|
public class K8sDeploymentDTO extends BaseDTO {
|
||||||
|
|
||||||
|
@Schema(description = "K8S集群ID")
|
||||||
|
private Long externalSystemId;
|
||||||
|
|
||||||
|
@Schema(description = "命名空间ID")
|
||||||
|
private Long namespaceId;
|
||||||
|
|
||||||
|
@Schema(description = "Deployment名称")
|
||||||
|
private String deploymentName;
|
||||||
|
|
||||||
|
@Schema(description = "期望副本数")
|
||||||
|
private Integer replicas;
|
||||||
|
|
||||||
|
@Schema(description = "可用副本数")
|
||||||
|
private Integer availableReplicas;
|
||||||
|
|
||||||
|
@Schema(description = "就绪副本数")
|
||||||
|
private Integer readyReplicas;
|
||||||
|
|
||||||
|
@Schema(description = "容器镜像")
|
||||||
|
private String image;
|
||||||
|
|
||||||
|
@Schema(description = "标签")
|
||||||
|
private Map<String, String> labels;
|
||||||
|
|
||||||
|
@Schema(description = "选择器")
|
||||||
|
private Map<String, String> selector;
|
||||||
|
|
||||||
|
@Schema(description = "K8S中的创建时间")
|
||||||
|
private LocalDateTime k8sCreateTime;
|
||||||
|
|
||||||
|
@Schema(description = "K8S中的更新时间")
|
||||||
|
private LocalDateTime k8sUpdateTime;
|
||||||
|
|
||||||
|
@Schema(description = "YAML配置")
|
||||||
|
private String yamlConfig;
|
||||||
|
}
|
||||||
@ -0,0 +1,32 @@
|
|||||||
|
package com.qqchen.deploy.backend.deploy.dto;
|
||||||
|
|
||||||
|
import com.qqchen.deploy.backend.framework.dto.BaseDTO;
|
||||||
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.EqualsAndHashCode;
|
||||||
|
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
@EqualsAndHashCode(callSuper = true)
|
||||||
|
@Schema(description = "K8S命名空间DTO")
|
||||||
|
public class K8sNamespaceDTO extends BaseDTO {
|
||||||
|
|
||||||
|
@Schema(description = "K8S集群ID")
|
||||||
|
private Long externalSystemId;
|
||||||
|
|
||||||
|
@Schema(description = "命名空间名称")
|
||||||
|
private String namespaceName;
|
||||||
|
|
||||||
|
@Schema(description = "状态")
|
||||||
|
private String status;
|
||||||
|
|
||||||
|
@Schema(description = "标签")
|
||||||
|
private Map<String, String> labels;
|
||||||
|
|
||||||
|
@Schema(description = "YAML配置")
|
||||||
|
private String yamlConfig;
|
||||||
|
|
||||||
|
@Schema(description = "Deployment数量(仅在列表和分页查询时填充)")
|
||||||
|
private Long deploymentCount;
|
||||||
|
}
|
||||||
@ -0,0 +1,37 @@
|
|||||||
|
package com.qqchen.deploy.backend.deploy.dto;
|
||||||
|
|
||||||
|
import com.qqchen.deploy.backend.deploy.enums.ExternalSystemSyncStatus;
|
||||||
|
import com.qqchen.deploy.backend.deploy.enums.K8sSyncType;
|
||||||
|
import com.qqchen.deploy.backend.framework.dto.BaseDTO;
|
||||||
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.EqualsAndHashCode;
|
||||||
|
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
@EqualsAndHashCode(callSuper = true)
|
||||||
|
@Schema(description = "K8S同步历史DTO")
|
||||||
|
public class K8sSyncHistoryDTO extends BaseDTO {
|
||||||
|
|
||||||
|
@Schema(description = "同步编号")
|
||||||
|
private String number;
|
||||||
|
|
||||||
|
@Schema(description = "同步类型")
|
||||||
|
private K8sSyncType syncType;
|
||||||
|
|
||||||
|
@Schema(description = "同步状态")
|
||||||
|
private ExternalSystemSyncStatus status;
|
||||||
|
|
||||||
|
@Schema(description = "开始时间")
|
||||||
|
private LocalDateTime startTime;
|
||||||
|
|
||||||
|
@Schema(description = "结束时间")
|
||||||
|
private LocalDateTime endTime;
|
||||||
|
|
||||||
|
@Schema(description = "错误信息")
|
||||||
|
private String errorMessage;
|
||||||
|
|
||||||
|
@Schema(description = "K8S集群ID")
|
||||||
|
private Long externalSystemId;
|
||||||
|
}
|
||||||
@ -82,10 +82,10 @@ public class ExternalSystem extends Entity<Long> {
|
|||||||
private LocalDateTime lastConnectTime;
|
private LocalDateTime lastConnectTime;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 系统特有配置,JSON格式
|
* 系统特有配置(如kubeconfig等)
|
||||||
|
* 使用TEXT类型存储,支持任意格式的配置内容
|
||||||
*/
|
*/
|
||||||
@Column(columnDefinition = "JSON")
|
@Column(columnDefinition = "TEXT")
|
||||||
private String config;
|
private String config;
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
@ -0,0 +1,60 @@
|
|||||||
|
package com.qqchen.deploy.backend.deploy.entity;
|
||||||
|
|
||||||
|
import com.qqchen.deploy.backend.framework.domain.Entity;
|
||||||
|
import com.vladmihalcea.hibernate.type.json.JsonType;
|
||||||
|
import jakarta.persistence.Column;
|
||||||
|
import jakarta.persistence.Table;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.EqualsAndHashCode;
|
||||||
|
import org.hibernate.annotations.Type;
|
||||||
|
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* K8S Deployment实体
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
@EqualsAndHashCode(callSuper = true)
|
||||||
|
@jakarta.persistence.Entity
|
||||||
|
@Table(name = "deploy_k8s_deployment")
|
||||||
|
public class K8sDeployment extends Entity<Long> {
|
||||||
|
|
||||||
|
@Column(name = "external_system_id", nullable = false)
|
||||||
|
private Long externalSystemId;
|
||||||
|
|
||||||
|
@Column(name = "namespace_id", nullable = false)
|
||||||
|
private Long namespaceId;
|
||||||
|
|
||||||
|
@Column(name = "deployment_name", nullable = false)
|
||||||
|
private String deploymentName;
|
||||||
|
|
||||||
|
@Column(name = "replicas")
|
||||||
|
private Integer replicas;
|
||||||
|
|
||||||
|
@Column(name = "available_replicas")
|
||||||
|
private Integer availableReplicas;
|
||||||
|
|
||||||
|
@Column(name = "ready_replicas")
|
||||||
|
private Integer readyReplicas;
|
||||||
|
|
||||||
|
@Column(name = "image")
|
||||||
|
private String image;
|
||||||
|
|
||||||
|
@Type(JsonType.class)
|
||||||
|
@Column(name = "labels", columnDefinition = "JSON")
|
||||||
|
private Map<String, String> labels;
|
||||||
|
|
||||||
|
@Type(JsonType.class)
|
||||||
|
@Column(name = "selector", columnDefinition = "JSON")
|
||||||
|
private Map<String, String> selector;
|
||||||
|
|
||||||
|
@Column(name = "k8s_create_time")
|
||||||
|
private LocalDateTime k8sCreateTime;
|
||||||
|
|
||||||
|
@Column(name = "k8s_update_time")
|
||||||
|
private LocalDateTime k8sUpdateTime;
|
||||||
|
|
||||||
|
@Column(name = "yaml_config", columnDefinition = "TEXT")
|
||||||
|
private String yamlConfig;
|
||||||
|
}
|
||||||
@ -0,0 +1,37 @@
|
|||||||
|
package com.qqchen.deploy.backend.deploy.entity;
|
||||||
|
|
||||||
|
import com.qqchen.deploy.backend.framework.domain.Entity;
|
||||||
|
import com.vladmihalcea.hibernate.type.json.JsonType;
|
||||||
|
import jakarta.persistence.Column;
|
||||||
|
import jakarta.persistence.Table;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.EqualsAndHashCode;
|
||||||
|
import org.hibernate.annotations.Type;
|
||||||
|
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* K8S命名空间实体
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
@EqualsAndHashCode(callSuper = true)
|
||||||
|
@jakarta.persistence.Entity
|
||||||
|
@Table(name = "deploy_k8s_namespace")
|
||||||
|
public class K8sNamespace extends Entity<Long> {
|
||||||
|
|
||||||
|
@Column(name = "external_system_id", nullable = false)
|
||||||
|
private Long externalSystemId;
|
||||||
|
|
||||||
|
@Column(name = "namespace_name", nullable = false)
|
||||||
|
private String namespaceName;
|
||||||
|
|
||||||
|
@Column(name = "status")
|
||||||
|
private String status;
|
||||||
|
|
||||||
|
@Type(JsonType.class)
|
||||||
|
@Column(name = "labels", columnDefinition = "JSON")
|
||||||
|
private Map<String, String> labels;
|
||||||
|
|
||||||
|
@Column(name = "yaml_config", columnDefinition = "TEXT")
|
||||||
|
private String yamlConfig;
|
||||||
|
}
|
||||||
@ -0,0 +1,48 @@
|
|||||||
|
package com.qqchen.deploy.backend.deploy.entity;
|
||||||
|
|
||||||
|
import com.qqchen.deploy.backend.deploy.enums.ExternalSystemSyncStatus;
|
||||||
|
import com.qqchen.deploy.backend.deploy.enums.K8sSyncType;
|
||||||
|
import com.qqchen.deploy.backend.framework.annotation.LogicDelete;
|
||||||
|
import com.qqchen.deploy.backend.framework.domain.Entity;
|
||||||
|
import jakarta.persistence.Column;
|
||||||
|
import jakarta.persistence.EnumType;
|
||||||
|
import jakarta.persistence.Enumerated;
|
||||||
|
import jakarta.persistence.Table;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.EqualsAndHashCode;
|
||||||
|
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* K8S同步历史实体
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
@EqualsAndHashCode(callSuper = true)
|
||||||
|
@jakarta.persistence.Entity
|
||||||
|
@Table(name = "deploy_k8s_sync_history")
|
||||||
|
@LogicDelete
|
||||||
|
public class K8sSyncHistory extends Entity<Long> {
|
||||||
|
|
||||||
|
@Column(name = "sync_history_number", nullable = false)
|
||||||
|
private String number;
|
||||||
|
|
||||||
|
@Column(name = "sync_type", nullable = false)
|
||||||
|
@Enumerated(EnumType.STRING)
|
||||||
|
private K8sSyncType syncType;
|
||||||
|
|
||||||
|
@Column(nullable = false)
|
||||||
|
@Enumerated(EnumType.STRING)
|
||||||
|
private ExternalSystemSyncStatus status;
|
||||||
|
|
||||||
|
@Column(name = "start_time", nullable = false)
|
||||||
|
private LocalDateTime startTime;
|
||||||
|
|
||||||
|
@Column(name = "end_time")
|
||||||
|
private LocalDateTime endTime;
|
||||||
|
|
||||||
|
@Column(name = "error_message", columnDefinition = "TEXT")
|
||||||
|
private String errorMessage;
|
||||||
|
|
||||||
|
@Column(name = "external_system_id", nullable = false)
|
||||||
|
private Long externalSystemId;
|
||||||
|
}
|
||||||
@ -0,0 +1,6 @@
|
|||||||
|
package com.qqchen.deploy.backend.deploy.enums;
|
||||||
|
|
||||||
|
public enum K8sSyncType {
|
||||||
|
NAMESPACE, // 同步命名空间
|
||||||
|
DEPLOYMENT // 同步Deployment
|
||||||
|
}
|
||||||
@ -0,0 +1,57 @@
|
|||||||
|
package com.qqchen.deploy.backend.deploy.integration;
|
||||||
|
|
||||||
|
import com.qqchen.deploy.backend.deploy.entity.ExternalSystem;
|
||||||
|
import com.qqchen.deploy.backend.deploy.integration.response.K8sDeploymentResponse;
|
||||||
|
import com.qqchen.deploy.backend.deploy.integration.response.K8sNamespaceResponse;
|
||||||
|
import com.qqchen.deploy.backend.system.enums.ExternalSystemTypeEnum;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* K8S集成服务接口
|
||||||
|
*/
|
||||||
|
public interface IK8sServiceIntegration extends IExternalSystemIntegration {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 测试K8S连接
|
||||||
|
*
|
||||||
|
* @param system K8S系统配置
|
||||||
|
* @return 连接是否成功
|
||||||
|
*/
|
||||||
|
boolean testConnection(ExternalSystem system);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 查询所有命名空间
|
||||||
|
*
|
||||||
|
* @param externalSystem K8S系统配置
|
||||||
|
* @return 命名空间列表
|
||||||
|
*/
|
||||||
|
List<K8sNamespaceResponse> listNamespaces(ExternalSystem externalSystem);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 查询指定命名空间下的所有Deployment
|
||||||
|
*
|
||||||
|
* @param externalSystem K8S系统配置
|
||||||
|
* @param namespace 命名空间名称
|
||||||
|
* @return Deployment列表
|
||||||
|
*/
|
||||||
|
List<K8sDeploymentResponse> listDeployments(ExternalSystem externalSystem, String namespace);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 查询所有Deployment(跨命名空间)
|
||||||
|
*
|
||||||
|
* @param externalSystem K8S系统配置
|
||||||
|
* @return Deployment列表
|
||||||
|
*/
|
||||||
|
List<K8sDeploymentResponse> listAllDeployments(ExternalSystem externalSystem);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取系统类型
|
||||||
|
*
|
||||||
|
* @return K8S系统类型
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
default ExternalSystemTypeEnum getSystemType() {
|
||||||
|
return ExternalSystemTypeEnum.K8S;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,349 @@
|
|||||||
|
package com.qqchen.deploy.backend.deploy.integration.impl;
|
||||||
|
|
||||||
|
import com.qqchen.deploy.backend.deploy.entity.ExternalSystem;
|
||||||
|
import com.qqchen.deploy.backend.framework.utils.JsonUtils;
|
||||||
|
import com.qqchen.deploy.backend.deploy.integration.IK8sServiceIntegration;
|
||||||
|
import com.qqchen.deploy.backend.deploy.integration.response.K8sDeploymentResponse;
|
||||||
|
import com.qqchen.deploy.backend.deploy.integration.response.K8sNamespaceResponse;
|
||||||
|
import com.qqchen.deploy.backend.framework.enums.ResponseCode;
|
||||||
|
import com.qqchen.deploy.backend.framework.exception.BusinessException;
|
||||||
|
import com.qqchen.deploy.backend.system.enums.ExternalSystemTypeEnum;
|
||||||
|
import io.kubernetes.client.openapi.ApiClient;
|
||||||
|
import io.kubernetes.client.openapi.ApiException;
|
||||||
|
import io.kubernetes.client.openapi.apis.AppsV1Api;
|
||||||
|
import io.kubernetes.client.openapi.apis.CoreV1Api;
|
||||||
|
import io.kubernetes.client.openapi.apis.VersionApi;
|
||||||
|
import io.kubernetes.client.openapi.models.*;
|
||||||
|
import io.kubernetes.client.util.Config;
|
||||||
|
import io.kubernetes.client.util.Yaml;
|
||||||
|
import jakarta.annotation.Resource;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
|
import java.io.StringReader;
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
import java.time.ZoneId;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* K8S集成服务实现
|
||||||
|
*/
|
||||||
|
@Slf4j
|
||||||
|
@Service
|
||||||
|
public class K8sServiceIntegrationImpl extends BaseExternalSystemIntegration implements IK8sServiceIntegration {
|
||||||
|
|
||||||
|
// K8S ApiClient缓存 - 线程安全
|
||||||
|
private static final Map<Long, K8sApiClientCache> API_CLIENT_CACHE = new ConcurrentHashMap<>();
|
||||||
|
private static final long CACHE_EXPIRE_TIME = 30 * 60 * 1000; // 30分钟过期
|
||||||
|
|
||||||
|
/**
|
||||||
|
* K8S ApiClient缓存内部类
|
||||||
|
*/
|
||||||
|
private static class K8sApiClientCache {
|
||||||
|
final ApiClient apiClient;
|
||||||
|
final long expireTime;
|
||||||
|
|
||||||
|
K8sApiClientCache(ApiClient apiClient) {
|
||||||
|
this.apiClient = apiClient;
|
||||||
|
this.expireTime = System.currentTimeMillis() + CACHE_EXPIRE_TIME;
|
||||||
|
}
|
||||||
|
|
||||||
|
boolean isExpired() {
|
||||||
|
return System.currentTimeMillis() > expireTime;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 线程安全地获取K8S ApiClient缓存
|
||||||
|
* 如果缓存不存在或已过期,会重新创建
|
||||||
|
*/
|
||||||
|
private synchronized K8sApiClientCache getApiClientCache(ExternalSystem system) {
|
||||||
|
Long systemId = system.getId();
|
||||||
|
K8sApiClientCache cache = API_CLIENT_CACHE.get(systemId);
|
||||||
|
|
||||||
|
if (cache == null || cache.isExpired()) {
|
||||||
|
log.debug("K8S ApiClient缓存失效,重新创建: systemId={}", systemId);
|
||||||
|
|
||||||
|
try {
|
||||||
|
ApiClient apiClient = createApiClientInternal(system);
|
||||||
|
cache = new K8sApiClientCache(apiClient);
|
||||||
|
API_CLIENT_CACHE.put(systemId, cache);
|
||||||
|
log.debug("K8S ApiClient缓存已更新: systemId={}, expireTime={}", systemId, cache.expireTime);
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("创建K8S ApiClient失败: systemId={}", systemId, e);
|
||||||
|
throw new BusinessException(ResponseCode.K8S_CONNECTION_FAILED);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return cache;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ExternalSystemTypeEnum getSystemType() {
|
||||||
|
return ExternalSystemTypeEnum.K8S;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 测试K8S连接
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public boolean testConnection(ExternalSystem system) {
|
||||||
|
log.info("测试K8S连接,集群: {}", system.getName());
|
||||||
|
|
||||||
|
try {
|
||||||
|
String config = system.getConfig();
|
||||||
|
if (config == null || config.trim().isEmpty()) {
|
||||||
|
throw new BusinessException(ResponseCode.K8S_CONFIG_EMPTY);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 创建K8S ApiClient并测试连接(直接使用config作为kubeconfig)
|
||||||
|
ApiClient client = Config.fromConfig(new StringReader(config));
|
||||||
|
client.setConnectTimeout(15000); // 15秒连接超时
|
||||||
|
client.setReadTimeout(30000); // 30秒读取超时
|
||||||
|
|
||||||
|
VersionApi versionApi = new VersionApi(client);
|
||||||
|
VersionInfo version = versionApi.getCode();
|
||||||
|
log.info("K8S集群连接成功,版本: {}", version.getGitVersion());
|
||||||
|
|
||||||
|
return true;
|
||||||
|
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("K8S连接测试失败,集群: {}, 错误: {}", system.getName(), e.getMessage(), e);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 查询所有命名空间
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public List<K8sNamespaceResponse> listNamespaces(ExternalSystem externalSystem) {
|
||||||
|
log.info("查询K8S命名空间,集群: {}", externalSystem.getName());
|
||||||
|
|
||||||
|
try {
|
||||||
|
K8sApiClientCache cache = getApiClientCache(externalSystem);
|
||||||
|
CoreV1Api api = new CoreV1Api(cache.apiClient);
|
||||||
|
|
||||||
|
V1NamespaceList namespaceList = api.listNamespace(
|
||||||
|
null, null, null, null, null, null, null, null, null, null
|
||||||
|
);
|
||||||
|
|
||||||
|
List<K8sNamespaceResponse> namespaces = new ArrayList<>();
|
||||||
|
for (V1Namespace ns : namespaceList.getItems()) {
|
||||||
|
K8sNamespaceResponse response = new K8sNamespaceResponse();
|
||||||
|
response.setName(ns.getMetadata().getName());
|
||||||
|
|
||||||
|
if (ns.getStatus() != null && ns.getStatus().getPhase() != null) {
|
||||||
|
response.setStatus(ns.getStatus().getPhase());
|
||||||
|
}
|
||||||
|
|
||||||
|
response.setLabels(ns.getMetadata().getLabels());
|
||||||
|
|
||||||
|
if (ns.getMetadata().getCreationTimestamp() != null) {
|
||||||
|
response.setCreationTimestamp(
|
||||||
|
LocalDateTime.ofInstant(
|
||||||
|
ns.getMetadata().getCreationTimestamp().toInstant(),
|
||||||
|
ZoneId.systemDefault()
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 序列化为YAML配置
|
||||||
|
try {
|
||||||
|
response.setYamlConfig(Yaml.dump(ns));
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.warn("序列化Namespace为YAML失败: {}", ns.getMetadata().getName(), e);
|
||||||
|
}
|
||||||
|
|
||||||
|
namespaces.add(response);
|
||||||
|
}
|
||||||
|
|
||||||
|
log.info("查询到 {} 个命名空间", namespaces.size());
|
||||||
|
return namespaces;
|
||||||
|
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("查询K8S命名空间失败,集群: {}, 错误: {}", externalSystem.getName(), e.getMessage(), e);
|
||||||
|
throw new BusinessException(ResponseCode.K8S_NAMESPACE_SYNC_FAILED);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 查询指定命名空间下的所有Deployment
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public List<K8sDeploymentResponse> listDeployments(ExternalSystem externalSystem, String namespace) {
|
||||||
|
log.info("查询K8S Deployment,集群: {}, 命名空间: {}", externalSystem.getName(), namespace);
|
||||||
|
|
||||||
|
try {
|
||||||
|
K8sApiClientCache cache = getApiClientCache(externalSystem);
|
||||||
|
AppsV1Api api = new AppsV1Api(cache.apiClient);
|
||||||
|
|
||||||
|
V1DeploymentList deploymentList = api.listNamespacedDeployment(
|
||||||
|
namespace, null, null, null, null, null, null, null, null, null, null
|
||||||
|
);
|
||||||
|
|
||||||
|
List<K8sDeploymentResponse> deployments = new ArrayList<>();
|
||||||
|
for (V1Deployment deployment : deploymentList.getItems()) {
|
||||||
|
K8sDeploymentResponse response = new K8sDeploymentResponse();
|
||||||
|
response.setName(deployment.getMetadata().getName());
|
||||||
|
response.setNamespace(deployment.getMetadata().getNamespace());
|
||||||
|
|
||||||
|
if (deployment.getSpec() != null) {
|
||||||
|
response.setReplicas(deployment.getSpec().getReplicas());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (deployment.getStatus() != null) {
|
||||||
|
response.setAvailableReplicas(deployment.getStatus().getAvailableReplicas());
|
||||||
|
response.setReadyReplicas(deployment.getStatus().getReadyReplicas());
|
||||||
|
}
|
||||||
|
|
||||||
|
response.setLabels(deployment.getMetadata().getLabels());
|
||||||
|
|
||||||
|
if (deployment.getSpec() != null && deployment.getSpec().getSelector() != null) {
|
||||||
|
response.setSelector(deployment.getSpec().getSelector().getMatchLabels());
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取第一个容器的镜像
|
||||||
|
if (deployment.getSpec() != null
|
||||||
|
&& deployment.getSpec().getTemplate() != null
|
||||||
|
&& deployment.getSpec().getTemplate().getSpec() != null
|
||||||
|
&& deployment.getSpec().getTemplate().getSpec().getContainers() != null
|
||||||
|
&& !deployment.getSpec().getTemplate().getSpec().getContainers().isEmpty()) {
|
||||||
|
response.setImage(deployment.getSpec().getTemplate().getSpec().getContainers().get(0).getImage());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (deployment.getMetadata().getCreationTimestamp() != null) {
|
||||||
|
response.setCreationTimestamp(
|
||||||
|
LocalDateTime.ofInstant(
|
||||||
|
deployment.getMetadata().getCreationTimestamp().toInstant(),
|
||||||
|
ZoneId.systemDefault()
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 序列化为YAML配置
|
||||||
|
try {
|
||||||
|
response.setYamlConfig(Yaml.dump(deployment));
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.warn("序列化Deployment为YAML失败: {}", deployment.getMetadata().getName(), e);
|
||||||
|
}
|
||||||
|
|
||||||
|
deployments.add(response);
|
||||||
|
}
|
||||||
|
|
||||||
|
log.info("查询到 {} 个Deployment", deployments.size());
|
||||||
|
return deployments;
|
||||||
|
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("查询K8S Deployment失败,集群: {}, 命名空间: {}, 错误: {}",
|
||||||
|
externalSystem.getName(), namespace, e.getMessage(), e);
|
||||||
|
throw new BusinessException(ResponseCode.K8S_DEPLOYMENT_SYNC_FAILED);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 查询所有Deployment(跨命名空间)
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public List<K8sDeploymentResponse> listAllDeployments(ExternalSystem externalSystem) {
|
||||||
|
log.info("查询所有K8S Deployment,集群: {}", externalSystem.getName());
|
||||||
|
|
||||||
|
try {
|
||||||
|
K8sApiClientCache cache = getApiClientCache(externalSystem);
|
||||||
|
AppsV1Api api = new AppsV1Api(cache.apiClient);
|
||||||
|
|
||||||
|
V1DeploymentList deploymentList = api.listDeploymentForAllNamespaces(
|
||||||
|
null, null, null, null, null, null, null, null, null, null
|
||||||
|
);
|
||||||
|
|
||||||
|
List<K8sDeploymentResponse> deployments = new ArrayList<>();
|
||||||
|
for (V1Deployment deployment : deploymentList.getItems()) {
|
||||||
|
K8sDeploymentResponse response = new K8sDeploymentResponse();
|
||||||
|
response.setName(deployment.getMetadata().getName());
|
||||||
|
response.setNamespace(deployment.getMetadata().getNamespace());
|
||||||
|
|
||||||
|
if (deployment.getSpec() != null) {
|
||||||
|
response.setReplicas(deployment.getSpec().getReplicas());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (deployment.getStatus() != null) {
|
||||||
|
response.setAvailableReplicas(deployment.getStatus().getAvailableReplicas());
|
||||||
|
response.setReadyReplicas(deployment.getStatus().getReadyReplicas());
|
||||||
|
}
|
||||||
|
|
||||||
|
response.setLabels(deployment.getMetadata().getLabels());
|
||||||
|
|
||||||
|
if (deployment.getSpec() != null && deployment.getSpec().getSelector() != null) {
|
||||||
|
response.setSelector(deployment.getSpec().getSelector().getMatchLabels());
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取第一个容器的镜像
|
||||||
|
if (deployment.getSpec() != null
|
||||||
|
&& deployment.getSpec().getTemplate() != null
|
||||||
|
&& deployment.getSpec().getTemplate().getSpec() != null
|
||||||
|
&& deployment.getSpec().getTemplate().getSpec().getContainers() != null
|
||||||
|
&& !deployment.getSpec().getTemplate().getSpec().getContainers().isEmpty()) {
|
||||||
|
response.setImage(deployment.getSpec().getTemplate().getSpec().getContainers().get(0).getImage());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (deployment.getMetadata().getCreationTimestamp() != null) {
|
||||||
|
response.setCreationTimestamp(
|
||||||
|
LocalDateTime.ofInstant(
|
||||||
|
deployment.getMetadata().getCreationTimestamp().toInstant(),
|
||||||
|
ZoneId.systemDefault()
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 序列化为YAML配置
|
||||||
|
try {
|
||||||
|
response.setYamlConfig(Yaml.dump(deployment));
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.warn("序列化Deployment为YAML失败: {}", deployment.getMetadata().getName(), e);
|
||||||
|
}
|
||||||
|
|
||||||
|
deployments.add(response);
|
||||||
|
}
|
||||||
|
|
||||||
|
log.info("查询到 {} 个Deployment", deployments.size());
|
||||||
|
return deployments;
|
||||||
|
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("查询所有K8S Deployment失败,集群: {}, 错误: {}", externalSystem.getName(), e.getMessage(), e);
|
||||||
|
throw new BusinessException(ResponseCode.K8S_DEPLOYMENT_SYNC_FAILED);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建K8S ApiClient(对外接口,使用缓存)
|
||||||
|
*
|
||||||
|
* @param externalSystem K8S系统配置
|
||||||
|
* @return ApiClient
|
||||||
|
*/
|
||||||
|
private ApiClient createApiClient(ExternalSystem externalSystem) {
|
||||||
|
return getApiClientCache(externalSystem).apiClient;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建K8S ApiClient(内部实现,不使用缓存)
|
||||||
|
*
|
||||||
|
* @param externalSystem K8S系统配置
|
||||||
|
* @return ApiClient
|
||||||
|
*/
|
||||||
|
private ApiClient createApiClientInternal(ExternalSystem externalSystem) throws Exception {
|
||||||
|
String config = externalSystem.getConfig();
|
||||||
|
|
||||||
|
if (config == null || config.trim().isEmpty()) {
|
||||||
|
throw new BusinessException(ResponseCode.K8S_CONFIG_EMPTY);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 直接使用config作为kubeconfig内容
|
||||||
|
ApiClient client = Config.fromConfig(new StringReader(config));
|
||||||
|
client.setConnectTimeout(15000); // 15秒连接超时
|
||||||
|
client.setReadTimeout(30000); // 30秒读取超时
|
||||||
|
return client;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,21 @@
|
|||||||
|
package com.qqchen.deploy.backend.deploy.integration.response;
|
||||||
|
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
public class K8sDeploymentResponse {
|
||||||
|
private String name;
|
||||||
|
private String namespace;
|
||||||
|
private Integer replicas;
|
||||||
|
private Integer availableReplicas;
|
||||||
|
private Integer readyReplicas;
|
||||||
|
private String image;
|
||||||
|
private Map<String, String> labels;
|
||||||
|
private Map<String, String> selector;
|
||||||
|
private LocalDateTime creationTimestamp;
|
||||||
|
private LocalDateTime lastUpdateTime;
|
||||||
|
private String yamlConfig;
|
||||||
|
}
|
||||||
@ -0,0 +1,15 @@
|
|||||||
|
package com.qqchen.deploy.backend.deploy.integration.response;
|
||||||
|
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
public class K8sNamespaceResponse {
|
||||||
|
private String name;
|
||||||
|
private String status;
|
||||||
|
private Map<String, String> labels;
|
||||||
|
private LocalDateTime creationTimestamp;
|
||||||
|
private String yamlConfig;
|
||||||
|
}
|
||||||
@ -0,0 +1,24 @@
|
|||||||
|
package com.qqchen.deploy.backend.deploy.query;
|
||||||
|
|
||||||
|
import com.qqchen.deploy.backend.framework.annotation.QueryField;
|
||||||
|
import com.qqchen.deploy.backend.framework.enums.QueryType;
|
||||||
|
import com.qqchen.deploy.backend.framework.query.BaseQuery;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.EqualsAndHashCode;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
@EqualsAndHashCode(callSuper = true)
|
||||||
|
public class K8sDeploymentQuery extends BaseQuery {
|
||||||
|
|
||||||
|
@QueryField(field = "externalSystemId")
|
||||||
|
private Long externalSystemId;
|
||||||
|
|
||||||
|
@QueryField(field = "namespaceId")
|
||||||
|
private Long namespaceId;
|
||||||
|
|
||||||
|
@QueryField(field = "deploymentName", type = QueryType.LIKE)
|
||||||
|
private String deploymentName;
|
||||||
|
|
||||||
|
@QueryField(field = "image", type = QueryType.LIKE)
|
||||||
|
private String image;
|
||||||
|
}
|
||||||
@ -0,0 +1,21 @@
|
|||||||
|
package com.qqchen.deploy.backend.deploy.query;
|
||||||
|
|
||||||
|
import com.qqchen.deploy.backend.framework.annotation.QueryField;
|
||||||
|
import com.qqchen.deploy.backend.framework.enums.QueryType;
|
||||||
|
import com.qqchen.deploy.backend.framework.query.BaseQuery;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.EqualsAndHashCode;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
@EqualsAndHashCode(callSuper = true)
|
||||||
|
public class K8sNamespaceQuery extends BaseQuery {
|
||||||
|
|
||||||
|
@QueryField(field = "externalSystemId")
|
||||||
|
private Long externalSystemId;
|
||||||
|
|
||||||
|
@QueryField(field = "namespaceName", type = QueryType.LIKE)
|
||||||
|
private String namespaceName;
|
||||||
|
|
||||||
|
@QueryField(field = "status")
|
||||||
|
private String status;
|
||||||
|
}
|
||||||
@ -0,0 +1,22 @@
|
|||||||
|
package com.qqchen.deploy.backend.deploy.query;
|
||||||
|
|
||||||
|
import com.qqchen.deploy.backend.deploy.enums.ExternalSystemSyncStatus;
|
||||||
|
import com.qqchen.deploy.backend.deploy.enums.K8sSyncType;
|
||||||
|
import com.qqchen.deploy.backend.framework.annotation.QueryField;
|
||||||
|
import com.qqchen.deploy.backend.framework.query.BaseQuery;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.EqualsAndHashCode;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
@EqualsAndHashCode(callSuper = true)
|
||||||
|
public class K8sSyncHistoryQuery extends BaseQuery {
|
||||||
|
|
||||||
|
@QueryField(field = "externalSystemId")
|
||||||
|
private Long externalSystemId;
|
||||||
|
|
||||||
|
@QueryField(field = "syncType")
|
||||||
|
private K8sSyncType syncType;
|
||||||
|
|
||||||
|
@QueryField(field = "status")
|
||||||
|
private ExternalSystemSyncStatus status;
|
||||||
|
}
|
||||||
@ -26,6 +26,14 @@ public interface IJenkinsJobRepository extends IBaseRepository<JenkinsJob, Long>
|
|||||||
*/
|
*/
|
||||||
Optional<JenkinsJob> findByExternalSystemIdAndViewIdAndJobName(Long externalSystemId, Long viewId, String jobName);
|
Optional<JenkinsJob> findByExternalSystemIdAndViewIdAndJobName(Long externalSystemId, Long viewId, String jobName);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据外部系统ID查询所有任务
|
||||||
|
*
|
||||||
|
* @param externalSystemId 外部系统ID
|
||||||
|
* @return 任务列表
|
||||||
|
*/
|
||||||
|
List<JenkinsJob> findByExternalSystemId(Long externalSystemId);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 根据外部系统ID和视图ID查询所有任务
|
* 根据外部系统ID和视图ID查询所有任务
|
||||||
*
|
*
|
||||||
|
|||||||
@ -0,0 +1,29 @@
|
|||||||
|
package com.qqchen.deploy.backend.deploy.repository;
|
||||||
|
|
||||||
|
import com.qqchen.deploy.backend.deploy.entity.K8sDeployment;
|
||||||
|
import com.qqchen.deploy.backend.framework.repository.IBaseRepository;
|
||||||
|
import org.springframework.data.jpa.repository.Query;
|
||||||
|
import org.springframework.data.repository.query.Param;
|
||||||
|
import org.springframework.stereotype.Repository;
|
||||||
|
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Optional;
|
||||||
|
|
||||||
|
@Repository
|
||||||
|
public interface IK8sDeploymentRepository extends IBaseRepository<K8sDeployment, Long> {
|
||||||
|
|
||||||
|
Optional<K8sDeployment> findByNamespaceIdAndDeploymentName(Long namespaceId, String deploymentName);
|
||||||
|
|
||||||
|
List<K8sDeployment> findByExternalSystemId(Long externalSystemId);
|
||||||
|
|
||||||
|
List<K8sDeployment> findByNamespaceId(Long namespaceId);
|
||||||
|
|
||||||
|
Long countByNamespaceIdAndDeletedFalse(Long namespaceId);
|
||||||
|
|
||||||
|
@Query("SELECT d.namespaceId as namespaceId, COUNT(d.id) as count " +
|
||||||
|
"FROM K8sDeployment d " +
|
||||||
|
"WHERE d.namespaceId IN :namespaceIds AND d.deleted = false " +
|
||||||
|
"GROUP BY d.namespaceId")
|
||||||
|
List<Object[]> countByNamespaceIds(@Param("namespaceIds") Collection<Long> namespaceIds);
|
||||||
|
}
|
||||||
@ -0,0 +1,18 @@
|
|||||||
|
package com.qqchen.deploy.backend.deploy.repository;
|
||||||
|
|
||||||
|
import com.qqchen.deploy.backend.deploy.entity.K8sNamespace;
|
||||||
|
import com.qqchen.deploy.backend.framework.repository.IBaseRepository;
|
||||||
|
import org.springframework.stereotype.Repository;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Optional;
|
||||||
|
|
||||||
|
@Repository
|
||||||
|
public interface IK8sNamespaceRepository extends IBaseRepository<K8sNamespace, Long> {
|
||||||
|
|
||||||
|
Optional<K8sNamespace> findByExternalSystemIdAndNamespaceName(Long externalSystemId, String namespaceName);
|
||||||
|
|
||||||
|
List<K8sNamespace> findByExternalSystemId(Long externalSystemId);
|
||||||
|
|
||||||
|
Long countByExternalSystemIdAndDeletedFalse(Long externalSystemId);
|
||||||
|
}
|
||||||
@ -0,0 +1,13 @@
|
|||||||
|
package com.qqchen.deploy.backend.deploy.repository;
|
||||||
|
|
||||||
|
import com.qqchen.deploy.backend.deploy.entity.K8sSyncHistory;
|
||||||
|
import com.qqchen.deploy.backend.framework.repository.IBaseRepository;
|
||||||
|
import org.springframework.stereotype.Repository;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
@Repository
|
||||||
|
public interface IK8sSyncHistoryRepository extends IBaseRepository<K8sSyncHistory, Long> {
|
||||||
|
|
||||||
|
List<K8sSyncHistory> findByExternalSystemIdOrderByCreateTimeDesc(Long externalSystemId);
|
||||||
|
}
|
||||||
@ -0,0 +1,23 @@
|
|||||||
|
package com.qqchen.deploy.backend.deploy.service;
|
||||||
|
|
||||||
|
import com.qqchen.deploy.backend.deploy.dto.K8sDeploymentDTO;
|
||||||
|
import com.qqchen.deploy.backend.deploy.entity.ExternalSystem;
|
||||||
|
import com.qqchen.deploy.backend.deploy.entity.K8sDeployment;
|
||||||
|
import com.qqchen.deploy.backend.deploy.entity.K8sNamespace;
|
||||||
|
import com.qqchen.deploy.backend.deploy.query.K8sDeploymentQuery;
|
||||||
|
import com.qqchen.deploy.backend.framework.service.IBaseService;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public interface IK8sDeploymentService extends IBaseService<K8sDeployment, K8sDeploymentDTO, K8sDeploymentQuery, Long> {
|
||||||
|
|
||||||
|
Integer syncDeployments(ExternalSystem externalSystem, K8sNamespace namespace);
|
||||||
|
|
||||||
|
void syncDeployments(Long externalSystemId);
|
||||||
|
|
||||||
|
void syncDeployments(Long externalSystemId, Long namespaceId);
|
||||||
|
|
||||||
|
List<K8sDeploymentDTO> findByExternalSystemId(Long externalSystemId);
|
||||||
|
|
||||||
|
List<K8sDeploymentDTO> findByNamespaceId(Long namespaceId);
|
||||||
|
}
|
||||||
@ -0,0 +1,18 @@
|
|||||||
|
package com.qqchen.deploy.backend.deploy.service;
|
||||||
|
|
||||||
|
import com.qqchen.deploy.backend.deploy.dto.K8sNamespaceDTO;
|
||||||
|
import com.qqchen.deploy.backend.deploy.entity.ExternalSystem;
|
||||||
|
import com.qqchen.deploy.backend.deploy.entity.K8sNamespace;
|
||||||
|
import com.qqchen.deploy.backend.deploy.query.K8sNamespaceQuery;
|
||||||
|
import com.qqchen.deploy.backend.framework.service.IBaseService;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public interface IK8sNamespaceService extends IBaseService<K8sNamespace, K8sNamespaceDTO, K8sNamespaceQuery, Long> {
|
||||||
|
|
||||||
|
Integer syncNamespaces(ExternalSystem externalSystem);
|
||||||
|
|
||||||
|
void syncNamespaces(Long externalSystemId);
|
||||||
|
|
||||||
|
List<K8sNamespaceDTO> findByExternalSystemId(Long externalSystemId);
|
||||||
|
}
|
||||||
@ -0,0 +1,13 @@
|
|||||||
|
package com.qqchen.deploy.backend.deploy.service;
|
||||||
|
|
||||||
|
import com.qqchen.deploy.backend.deploy.dto.K8sSyncHistoryDTO;
|
||||||
|
import com.qqchen.deploy.backend.deploy.entity.K8sSyncHistory;
|
||||||
|
import com.qqchen.deploy.backend.deploy.query.K8sSyncHistoryQuery;
|
||||||
|
import com.qqchen.deploy.backend.framework.service.IBaseService;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public interface IK8sSyncHistoryService extends IBaseService<K8sSyncHistory, K8sSyncHistoryDTO, K8sSyncHistoryQuery, Long> {
|
||||||
|
|
||||||
|
List<K8sSyncHistoryDTO> findByExternalSystemId(Long externalSystemId);
|
||||||
|
}
|
||||||
@ -29,11 +29,7 @@ import org.springframework.transaction.annotation.Transactional;
|
|||||||
import java.time.Instant;
|
import java.time.Instant;
|
||||||
import java.time.LocalDateTime;
|
import java.time.LocalDateTime;
|
||||||
import java.time.ZoneId;
|
import java.time.ZoneId;
|
||||||
import java.util.ArrayList;
|
import java.util.*;
|
||||||
import java.util.List;
|
|
||||||
import java.util.OptionalDouble;
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.Set;
|
|
||||||
import java.util.function.Function;
|
import java.util.function.Function;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
@ -150,21 +146,32 @@ public class JenkinsJobServiceImpl extends BaseServiceImpl<JenkinsJob, JenkinsJo
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 4. 遍历视图,同步每个视图下的任务
|
// 4. 两阶段同步:先收集所有Job,再批量更新(避免并发冲突)
|
||||||
int totalSyncedJobs = 0;
|
Map<String, JenkinsJobResponse> allJobResponses = new LinkedHashMap<>();
|
||||||
|
Map<String, Set<Long>> jobViewMapping = new HashMap<>();
|
||||||
StringBuilder errorMessages = new StringBuilder();
|
StringBuilder errorMessages = new StringBuilder();
|
||||||
|
|
||||||
|
// 阶段1:收集所有View的Job信息
|
||||||
for (JenkinsView view : views) {
|
for (JenkinsView view : views) {
|
||||||
try {
|
try {
|
||||||
Integer syncedJobs = syncJobsByView(externalSystem, view);
|
List<JenkinsJobResponse> jobResponses = jenkinsServiceIntegration.listJobs(externalSystem, view.getViewName());
|
||||||
totalSyncedJobs += syncedJobs;
|
for (JenkinsJobResponse jobResponse : jobResponses) {
|
||||||
log.info("Successfully synchronized {} jobs for view: {}", syncedJobs, view.getViewName());
|
String jobName = jobResponse.getName();
|
||||||
|
// 保存Job信息(同一Job只保留一份)
|
||||||
|
allJobResponses.putIfAbsent(jobName, jobResponse);
|
||||||
|
// 记录Job-View关系
|
||||||
|
jobViewMapping.computeIfAbsent(jobName, k -> new HashSet<>()).add(view.getId());
|
||||||
|
}
|
||||||
|
log.debug("Collected {} jobs from view: {}", jobResponses.size(), view.getViewName());
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
// 记录错误但继续同步其他视图
|
String errorMessage = String.format("Failed to fetch jobs for view %s: %s", view.getViewName(), e.getMessage());
|
||||||
String errorMessage = String.format("Failed to sync jobs for view %s: %s", view.getViewName(), e.getMessage());
|
|
||||||
log.error(errorMessage, e);
|
log.error(errorMessage, e);
|
||||||
errorMessages.append(errorMessage).append("\n");
|
errorMessages.append(errorMessage).append("\n");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 阶段2:批量更新Job(每个Job只更新一次,避免并发冲突)
|
||||||
|
int totalSyncedJobs = syncJobsInBatch(externalSystem, allJobResponses, jobViewMapping);
|
||||||
|
|
||||||
// 5. 更新同步历史状态
|
// 5. 更新同步历史状态
|
||||||
if (errorMessages.length() > 0) {
|
if (errorMessages.length() > 0) {
|
||||||
@ -185,6 +192,62 @@ public class JenkinsJobServiceImpl extends BaseServiceImpl<JenkinsJob, JenkinsJo
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 批量同步Job(避免并发冲突)
|
||||||
|
*
|
||||||
|
* @param externalSystem 外部系统
|
||||||
|
* @param allJobResponses 所有Job的响应数据(去重后)
|
||||||
|
* @param jobViewMapping Job与View的映射关系
|
||||||
|
* @return 同步的任务数量
|
||||||
|
*/
|
||||||
|
private int syncJobsInBatch(ExternalSystem externalSystem,
|
||||||
|
Map<String, JenkinsJobResponse> allJobResponses,
|
||||||
|
Map<String, Set<Long>> jobViewMapping) {
|
||||||
|
if (allJobResponses.isEmpty()) {
|
||||||
|
log.debug("No jobs to sync for external system: {}", externalSystem.getId());
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 1. 查询所有现有Job(按externalSystemId,不按viewId)
|
||||||
|
List<JenkinsJob> existingJobs = jenkinsJobRepository.findByExternalSystemId(externalSystem.getId());
|
||||||
|
Map<String, JenkinsJob> existingJobMap = existingJobs.stream()
|
||||||
|
.collect(Collectors.toMap(JenkinsJob::getJobName, Function.identity(), (old, newVal) -> old));
|
||||||
|
|
||||||
|
// 2. 批量更新/新增Job
|
||||||
|
List<JenkinsJob> jobsToSave = new ArrayList<>();
|
||||||
|
for (Map.Entry<String, JenkinsJobResponse> entry : allJobResponses.entrySet()) {
|
||||||
|
String jobName = entry.getKey();
|
||||||
|
JenkinsJobResponse jobResponse = entry.getValue();
|
||||||
|
Set<Long> viewIds = jobViewMapping.get(jobName);
|
||||||
|
|
||||||
|
JenkinsJob jenkinsJob = existingJobMap.get(jobName);
|
||||||
|
if (jenkinsJob == null) {
|
||||||
|
// 新Job:使用第一个View作为主View
|
||||||
|
jenkinsJob = new JenkinsJob();
|
||||||
|
jenkinsJob.setExternalSystemId(externalSystem.getId());
|
||||||
|
jenkinsJob.setJobName(jobName);
|
||||||
|
jenkinsJob.setViewId(viewIds.iterator().next()); // 设置第一个View
|
||||||
|
log.debug("Creating new Jenkins job: {}", jobName);
|
||||||
|
} else {
|
||||||
|
// 已存在的Job:保持原viewId或更新为第一个View
|
||||||
|
if (!viewIds.contains(jenkinsJob.getViewId())) {
|
||||||
|
jenkinsJob.setViewId(viewIds.iterator().next());
|
||||||
|
}
|
||||||
|
log.debug("Updating existing Jenkins job: {}", jobName);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 更新Job信息
|
||||||
|
updateJobFromResponse(jenkinsJob, jobResponse);
|
||||||
|
jobsToSave.add(jenkinsJob);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. 批量保存
|
||||||
|
jenkinsJobRepository.saveAll(jobsToSave);
|
||||||
|
|
||||||
|
log.info("Successfully synchronized {} unique jobs (avoiding duplicates)", jobsToSave.size());
|
||||||
|
return jobsToSave.size();
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 同步指定视图下的Jenkins任务
|
* 同步指定视图下的Jenkins任务
|
||||||
*
|
*
|
||||||
|
|||||||
@ -0,0 +1,226 @@
|
|||||||
|
package com.qqchen.deploy.backend.deploy.service.impl;
|
||||||
|
|
||||||
|
import com.qqchen.deploy.backend.deploy.converter.K8sDeploymentConverter;
|
||||||
|
import com.qqchen.deploy.backend.deploy.dto.K8sDeploymentDTO;
|
||||||
|
import com.qqchen.deploy.backend.deploy.dto.K8sSyncHistoryDTO;
|
||||||
|
import com.qqchen.deploy.backend.deploy.entity.ExternalSystem;
|
||||||
|
import com.qqchen.deploy.backend.deploy.entity.K8sDeployment;
|
||||||
|
import com.qqchen.deploy.backend.deploy.entity.K8sNamespace;
|
||||||
|
import com.qqchen.deploy.backend.deploy.enums.ExternalSystemSyncStatus;
|
||||||
|
import com.qqchen.deploy.backend.deploy.enums.K8sSyncType;
|
||||||
|
import com.qqchen.deploy.backend.deploy.integration.IK8sServiceIntegration;
|
||||||
|
import com.qqchen.deploy.backend.deploy.integration.response.K8sDeploymentResponse;
|
||||||
|
import com.qqchen.deploy.backend.deploy.query.K8sDeploymentQuery;
|
||||||
|
import com.qqchen.deploy.backend.deploy.repository.IExternalSystemRepository;
|
||||||
|
import com.qqchen.deploy.backend.deploy.repository.IK8sDeploymentRepository;
|
||||||
|
import com.qqchen.deploy.backend.deploy.repository.IK8sNamespaceRepository;
|
||||||
|
import com.qqchen.deploy.backend.deploy.service.IK8sDeploymentService;
|
||||||
|
import com.qqchen.deploy.backend.deploy.service.IK8sSyncHistoryService;
|
||||||
|
import com.qqchen.deploy.backend.framework.enums.ResponseCode;
|
||||||
|
import com.qqchen.deploy.backend.framework.exception.BusinessException;
|
||||||
|
import com.qqchen.deploy.backend.framework.service.impl.BaseServiceImpl;
|
||||||
|
import jakarta.annotation.Resource;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.scheduling.annotation.Async;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
|
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Optional;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
@Slf4j
|
||||||
|
@Service
|
||||||
|
public class K8sDeploymentServiceImpl extends BaseServiceImpl<K8sDeployment, K8sDeploymentDTO, K8sDeploymentQuery, Long>
|
||||||
|
implements IK8sDeploymentService {
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
private IK8sDeploymentRepository k8sDeploymentRepository;
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
private IK8sNamespaceRepository k8sNamespaceRepository;
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
private IExternalSystemRepository externalSystemRepository;
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
private K8sDeploymentConverter k8sDeploymentConverter;
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
private IK8sServiceIntegration k8sServiceIntegration;
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
private IK8sSyncHistoryService k8sSyncHistoryService;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Transactional(rollbackFor = Exception.class)
|
||||||
|
public Integer syncDeployments(ExternalSystem externalSystem, K8sNamespace namespace) {
|
||||||
|
log.info("开始同步K8S Deployment,集群: {}, Namespace: {}", externalSystem.getName(), namespace.getNamespaceName());
|
||||||
|
|
||||||
|
try {
|
||||||
|
List<K8sDeploymentResponse> deploymentResponses = k8sServiceIntegration.listDeployments(
|
||||||
|
externalSystem, namespace.getNamespaceName()
|
||||||
|
);
|
||||||
|
|
||||||
|
// 性能优化:批量查询现有数据,避免N+1问题
|
||||||
|
List<K8sDeployment> existingDeployments = k8sDeploymentRepository.findByNamespaceId(namespace.getId());
|
||||||
|
Map<String, K8sDeployment> existingMap = existingDeployments.stream()
|
||||||
|
.collect(java.util.stream.Collectors.toMap(K8sDeployment::getDeploymentName, d -> d));
|
||||||
|
|
||||||
|
// 收集K8S中存在的Deployment名称
|
||||||
|
Set<String> k8sDeploymentNames = deploymentResponses.stream()
|
||||||
|
.map(K8sDeploymentResponse::getName)
|
||||||
|
.collect(java.util.stream.Collectors.toSet());
|
||||||
|
|
||||||
|
List<K8sDeployment> deploymentsToSave = new ArrayList<>();
|
||||||
|
|
||||||
|
// 1. 更新/新增K8S中存在的资源
|
||||||
|
for (K8sDeploymentResponse response : deploymentResponses) {
|
||||||
|
K8sDeployment deployment = existingMap.get(response.getName());
|
||||||
|
if (deployment == null) {
|
||||||
|
deployment = new K8sDeployment();
|
||||||
|
deployment.setExternalSystemId(externalSystem.getId());
|
||||||
|
deployment.setNamespaceId(namespace.getId());
|
||||||
|
deployment.setDeploymentName(response.getName());
|
||||||
|
} else {
|
||||||
|
// 如果之前被软删除,恢复它
|
||||||
|
deployment.setDeleted(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
deployment.setReplicas(response.getReplicas());
|
||||||
|
deployment.setAvailableReplicas(response.getAvailableReplicas());
|
||||||
|
deployment.setReadyReplicas(response.getReadyReplicas());
|
||||||
|
deployment.setImage(response.getImage());
|
||||||
|
deployment.setLabels(response.getLabels());
|
||||||
|
deployment.setSelector(response.getSelector());
|
||||||
|
deployment.setK8sCreateTime(response.getCreationTimestamp());
|
||||||
|
deployment.setK8sUpdateTime(response.getLastUpdateTime());
|
||||||
|
deployment.setYamlConfig(response.getYamlConfig());
|
||||||
|
deploymentsToSave.add(deployment);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. 软删除K8S中不存在但数据库中存在的资源
|
||||||
|
for (K8sDeployment existing : existingDeployments) {
|
||||||
|
if (!k8sDeploymentNames.contains(existing.getDeploymentName()) && !existing.getDeleted()) {
|
||||||
|
existing.setDeleted(true);
|
||||||
|
deploymentsToSave.add(existing);
|
||||||
|
log.info("标记删除Deployment: {}", existing.getDeploymentName());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 性能优化:批量保存
|
||||||
|
k8sDeploymentRepository.saveAll(deploymentsToSave);
|
||||||
|
int syncCount = (int) deploymentResponses.size();
|
||||||
|
|
||||||
|
log.info("K8S Deployment同步完成,集群: {}, Namespace: {}, 同步数量: {}",
|
||||||
|
externalSystem.getName(), namespace.getNamespaceName(), syncCount);
|
||||||
|
return syncCount;
|
||||||
|
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("K8S Deployment同步失败,集群: {}, Namespace: {}, 错误: {}",
|
||||||
|
externalSystem.getName(), namespace.getNamespaceName(), e.getMessage(), e);
|
||||||
|
throw new BusinessException(ResponseCode.K8S_DEPLOYMENT_SYNC_FAILED);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Async("k8sTaskExecutor")
|
||||||
|
@Transactional(rollbackFor = Exception.class)
|
||||||
|
public void syncDeployments(Long externalSystemId) {
|
||||||
|
log.info("异步同步K8S Deployment(所有命名空间),集群ID: {}", externalSystemId);
|
||||||
|
|
||||||
|
K8sSyncHistoryDTO syncHistory = new K8sSyncHistoryDTO();
|
||||||
|
syncHistory.setExternalSystemId(externalSystemId);
|
||||||
|
syncHistory.setSyncType(K8sSyncType.DEPLOYMENT);
|
||||||
|
syncHistory.setStatus(ExternalSystemSyncStatus.RUNNING);
|
||||||
|
syncHistory.setStartTime(LocalDateTime.now());
|
||||||
|
syncHistory.setNumber("K8S-DEPLOY-SYNC-" + System.currentTimeMillis());
|
||||||
|
|
||||||
|
syncHistory = k8sSyncHistoryService.create(syncHistory);
|
||||||
|
|
||||||
|
try {
|
||||||
|
ExternalSystem externalSystem = externalSystemRepository.findById(externalSystemId)
|
||||||
|
.orElseThrow(() -> new BusinessException(ResponseCode.K8S_CLUSTER_NOT_FOUND));
|
||||||
|
|
||||||
|
List<K8sNamespace> namespaces = k8sNamespaceRepository.findByExternalSystemId(externalSystemId);
|
||||||
|
|
||||||
|
int totalSyncCount = 0;
|
||||||
|
for (K8sNamespace namespace : namespaces) {
|
||||||
|
int count = syncDeployments(externalSystem, namespace);
|
||||||
|
totalSyncCount += count;
|
||||||
|
}
|
||||||
|
|
||||||
|
syncHistory.setStatus(ExternalSystemSyncStatus.SUCCESS);
|
||||||
|
syncHistory.setEndTime(LocalDateTime.now());
|
||||||
|
k8sSyncHistoryService.update(syncHistory.getId(), syncHistory);
|
||||||
|
|
||||||
|
log.info("K8S Deployment异步同步成功,集群ID: {}, 总同步数量: {}", externalSystemId, totalSyncCount);
|
||||||
|
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("K8S Deployment异步同步失败,集群ID: {}, 错误: {}", externalSystemId, e.getMessage(), e);
|
||||||
|
|
||||||
|
syncHistory.setStatus(ExternalSystemSyncStatus.FAILED);
|
||||||
|
syncHistory.setEndTime(LocalDateTime.now());
|
||||||
|
syncHistory.setErrorMessage(e.getMessage());
|
||||||
|
k8sSyncHistoryService.update(syncHistory.getId(), syncHistory);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Async("k8sTaskExecutor")
|
||||||
|
@Transactional(rollbackFor = Exception.class)
|
||||||
|
public void syncDeployments(Long externalSystemId, Long namespaceId) {
|
||||||
|
log.info("异步同步K8S Deployment,集群ID: {}, 命名空间ID: {}", externalSystemId, namespaceId);
|
||||||
|
|
||||||
|
K8sSyncHistoryDTO syncHistory = new K8sSyncHistoryDTO();
|
||||||
|
syncHistory.setExternalSystemId(externalSystemId);
|
||||||
|
syncHistory.setSyncType(K8sSyncType.DEPLOYMENT);
|
||||||
|
syncHistory.setStatus(ExternalSystemSyncStatus.RUNNING);
|
||||||
|
syncHistory.setStartTime(LocalDateTime.now());
|
||||||
|
syncHistory.setNumber("K8S-DEPLOY-SYNC-" + System.currentTimeMillis());
|
||||||
|
|
||||||
|
syncHistory = k8sSyncHistoryService.create(syncHistory);
|
||||||
|
|
||||||
|
try {
|
||||||
|
ExternalSystem externalSystem = externalSystemRepository.findById(externalSystemId)
|
||||||
|
.orElseThrow(() -> new BusinessException(ResponseCode.K8S_CLUSTER_NOT_FOUND));
|
||||||
|
|
||||||
|
K8sNamespace namespace = k8sNamespaceRepository.findById(namespaceId)
|
||||||
|
.orElseThrow(() -> new BusinessException(ResponseCode.K8S_NAMESPACE_NOT_FOUND));
|
||||||
|
|
||||||
|
int syncCount = syncDeployments(externalSystem, namespace);
|
||||||
|
|
||||||
|
syncHistory.setStatus(ExternalSystemSyncStatus.SUCCESS);
|
||||||
|
syncHistory.setEndTime(LocalDateTime.now());
|
||||||
|
k8sSyncHistoryService.update(syncHistory.getId(), syncHistory);
|
||||||
|
|
||||||
|
log.info("K8S Deployment异步同步成功,集群ID: {}, 命名空间ID: {}, 同步数量: {}",
|
||||||
|
externalSystemId, namespaceId, syncCount);
|
||||||
|
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("K8S Deployment异步同步失败,集群ID: {}, 命名空间ID: {}, 错误: {}",
|
||||||
|
externalSystemId, namespaceId, e.getMessage(), e);
|
||||||
|
|
||||||
|
syncHistory.setStatus(ExternalSystemSyncStatus.FAILED);
|
||||||
|
syncHistory.setEndTime(LocalDateTime.now());
|
||||||
|
syncHistory.setErrorMessage(e.getMessage());
|
||||||
|
k8sSyncHistoryService.update(syncHistory.getId(), syncHistory);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<K8sDeploymentDTO> findByExternalSystemId(Long externalSystemId) {
|
||||||
|
List<K8sDeployment> deployments = k8sDeploymentRepository.findByExternalSystemId(externalSystemId);
|
||||||
|
return k8sDeploymentConverter.toDtoList(deployments);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<K8sDeploymentDTO> findByNamespaceId(Long namespaceId) {
|
||||||
|
List<K8sDeployment> deployments = k8sDeploymentRepository.findByNamespaceId(namespaceId);
|
||||||
|
return k8sDeploymentConverter.toDtoList(deployments);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@ -0,0 +1,194 @@
|
|||||||
|
package com.qqchen.deploy.backend.deploy.service.impl;
|
||||||
|
|
||||||
|
import com.qqchen.deploy.backend.deploy.converter.K8sNamespaceConverter;
|
||||||
|
import com.qqchen.deploy.backend.deploy.dto.K8sNamespaceDTO;
|
||||||
|
import com.qqchen.deploy.backend.deploy.dto.K8sSyncHistoryDTO;
|
||||||
|
import com.qqchen.deploy.backend.deploy.entity.ExternalSystem;
|
||||||
|
import com.qqchen.deploy.backend.deploy.entity.K8sNamespace;
|
||||||
|
import com.qqchen.deploy.backend.deploy.enums.ExternalSystemSyncStatus;
|
||||||
|
import com.qqchen.deploy.backend.deploy.enums.K8sSyncType;
|
||||||
|
import com.qqchen.deploy.backend.deploy.integration.IK8sServiceIntegration;
|
||||||
|
import com.qqchen.deploy.backend.deploy.integration.response.K8sNamespaceResponse;
|
||||||
|
import com.qqchen.deploy.backend.deploy.query.K8sNamespaceQuery;
|
||||||
|
import com.qqchen.deploy.backend.deploy.repository.IExternalSystemRepository;
|
||||||
|
import com.qqchen.deploy.backend.deploy.repository.IK8sNamespaceRepository;
|
||||||
|
import com.qqchen.deploy.backend.deploy.service.IK8sNamespaceService;
|
||||||
|
import com.qqchen.deploy.backend.deploy.service.IK8sSyncHistoryService;
|
||||||
|
import com.qqchen.deploy.backend.framework.enums.ResponseCode;
|
||||||
|
import com.qqchen.deploy.backend.framework.exception.BusinessException;
|
||||||
|
import com.qqchen.deploy.backend.framework.service.impl.BaseServiceImpl;
|
||||||
|
import jakarta.annotation.Resource;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.scheduling.annotation.Async;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
|
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Optional;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
@Slf4j
|
||||||
|
@Service
|
||||||
|
public class K8sNamespaceServiceImpl extends BaseServiceImpl<K8sNamespace, K8sNamespaceDTO, K8sNamespaceQuery, Long>
|
||||||
|
implements IK8sNamespaceService {
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
private IK8sNamespaceRepository k8sNamespaceRepository;
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
private IExternalSystemRepository externalSystemRepository;
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
private K8sNamespaceConverter k8sNamespaceConverter;
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
private IK8sServiceIntegration k8sServiceIntegration;
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
private IK8sSyncHistoryService k8sSyncHistoryService;
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
private com.qqchen.deploy.backend.deploy.repository.IK8sDeploymentRepository k8sDeploymentRepository;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Transactional(rollbackFor = Exception.class)
|
||||||
|
public Integer syncNamespaces(ExternalSystem externalSystem) {
|
||||||
|
log.info("开始同步K8S命名空间,集群: {}", externalSystem.getName());
|
||||||
|
|
||||||
|
try {
|
||||||
|
List<K8sNamespaceResponse> namespaceResponses = k8sServiceIntegration.listNamespaces(externalSystem);
|
||||||
|
|
||||||
|
// 性能优化:批量查询现有数据,避免N+1问题
|
||||||
|
List<K8sNamespace> existingNamespaces = k8sNamespaceRepository.findByExternalSystemId(externalSystem.getId());
|
||||||
|
Map<String, K8sNamespace> existingMap = existingNamespaces.stream()
|
||||||
|
.collect(java.util.stream.Collectors.toMap(K8sNamespace::getNamespaceName, ns -> ns));
|
||||||
|
|
||||||
|
// 收集K8S中存在的命名空间名称
|
||||||
|
Set<String> k8sNamespaceNames = namespaceResponses.stream()
|
||||||
|
.map(K8sNamespaceResponse::getName)
|
||||||
|
.collect(java.util.stream.Collectors.toSet());
|
||||||
|
|
||||||
|
List<K8sNamespace> namespacesToSave = new ArrayList<>();
|
||||||
|
|
||||||
|
// 1. 更新/新增K8S中存在的资源
|
||||||
|
for (K8sNamespaceResponse response : namespaceResponses) {
|
||||||
|
K8sNamespace namespace = existingMap.get(response.getName());
|
||||||
|
if (namespace == null) {
|
||||||
|
namespace = new K8sNamespace();
|
||||||
|
namespace.setExternalSystemId(externalSystem.getId());
|
||||||
|
namespace.setNamespaceName(response.getName());
|
||||||
|
} else {
|
||||||
|
// 如果之前被软删除,恢复它
|
||||||
|
namespace.setDeleted(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace.setStatus(response.getStatus());
|
||||||
|
namespace.setLabels(response.getLabels());
|
||||||
|
namespace.setYamlConfig(response.getYamlConfig());
|
||||||
|
namespacesToSave.add(namespace);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. 软删除K8S中不存在但数据库中存在的资源
|
||||||
|
for (K8sNamespace existing : existingNamespaces) {
|
||||||
|
if (!k8sNamespaceNames.contains(existing.getNamespaceName()) && !existing.getDeleted()) {
|
||||||
|
existing.setDeleted(true);
|
||||||
|
namespacesToSave.add(existing);
|
||||||
|
log.info("标记删除命名空间: {}", existing.getNamespaceName());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 性能优化:批量保存
|
||||||
|
k8sNamespaceRepository.saveAll(namespacesToSave);
|
||||||
|
int syncCount = (int) namespaceResponses.size();
|
||||||
|
|
||||||
|
log.info("K8S命名空间同步完成,集群: {}, 同步数量: {}", externalSystem.getName(), syncCount);
|
||||||
|
return syncCount;
|
||||||
|
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("K8S命名空间同步失败,集群: {}, 错误: {}", externalSystem.getName(), e.getMessage(), e);
|
||||||
|
throw new BusinessException(ResponseCode.K8S_NAMESPACE_SYNC_FAILED);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Async("k8sTaskExecutor")
|
||||||
|
@Transactional(rollbackFor = Exception.class)
|
||||||
|
public void syncNamespaces(Long externalSystemId) {
|
||||||
|
log.info("异步同步K8S命名空间,集群ID: {}", externalSystemId);
|
||||||
|
|
||||||
|
K8sSyncHistoryDTO syncHistory = new K8sSyncHistoryDTO();
|
||||||
|
syncHistory.setExternalSystemId(externalSystemId);
|
||||||
|
syncHistory.setSyncType(K8sSyncType.NAMESPACE);
|
||||||
|
syncHistory.setStatus(ExternalSystemSyncStatus.RUNNING);
|
||||||
|
syncHistory.setStartTime(LocalDateTime.now());
|
||||||
|
syncHistory.setNumber("K8S-NS-SYNC-" + System.currentTimeMillis());
|
||||||
|
|
||||||
|
syncHistory = k8sSyncHistoryService.create(syncHistory);
|
||||||
|
|
||||||
|
try {
|
||||||
|
ExternalSystem externalSystem = externalSystemRepository.findById(externalSystemId)
|
||||||
|
.orElseThrow(() -> new BusinessException(ResponseCode.K8S_CLUSTER_NOT_FOUND));
|
||||||
|
|
||||||
|
int syncCount = syncNamespaces(externalSystem);
|
||||||
|
|
||||||
|
syncHistory.setStatus(ExternalSystemSyncStatus.SUCCESS);
|
||||||
|
syncHistory.setEndTime(LocalDateTime.now());
|
||||||
|
k8sSyncHistoryService.update(syncHistory.getId(), syncHistory);
|
||||||
|
|
||||||
|
log.info("K8S命名空间异步同步成功,集群ID: {}, 同步数量: {}", externalSystemId, syncCount);
|
||||||
|
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("K8S命名空间异步同步失败,集群ID: {}, 错误: {}", externalSystemId, e.getMessage(), e);
|
||||||
|
|
||||||
|
syncHistory.setStatus(ExternalSystemSyncStatus.FAILED);
|
||||||
|
syncHistory.setEndTime(LocalDateTime.now());
|
||||||
|
syncHistory.setErrorMessage(e.getMessage());
|
||||||
|
k8sSyncHistoryService.update(syncHistory.getId(), syncHistory);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<K8sNamespaceDTO> findByExternalSystemId(Long externalSystemId) {
|
||||||
|
List<K8sNamespace> namespaces = k8sNamespaceRepository.findByExternalSystemId(externalSystemId);
|
||||||
|
return k8sNamespaceConverter.toDtoList(namespaces);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public org.springframework.data.domain.Page<K8sNamespaceDTO> page(K8sNamespaceQuery query) {
|
||||||
|
org.springframework.data.domain.Page<K8sNamespaceDTO> page = super.page(query);
|
||||||
|
fillDeploymentCounts(page.getContent());
|
||||||
|
return page;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<K8sNamespaceDTO> findAll(K8sNamespaceQuery query) {
|
||||||
|
List<K8sNamespaceDTO> list = super.findAll(query);
|
||||||
|
fillDeploymentCounts(list);
|
||||||
|
return list;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void fillDeploymentCounts(List<K8sNamespaceDTO> namespaces) {
|
||||||
|
if (namespaces.isEmpty()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
List<Long> namespaceIds = namespaces.stream()
|
||||||
|
.map(K8sNamespaceDTO::getId)
|
||||||
|
.collect(java.util.stream.Collectors.toList());
|
||||||
|
|
||||||
|
List<Object[]> countResults = k8sDeploymentRepository.countByNamespaceIds(namespaceIds);
|
||||||
|
Map<Long, Long> deploymentCountMap = countResults.stream()
|
||||||
|
.collect(java.util.stream.Collectors.toMap(
|
||||||
|
arr -> (Long) arr[0],
|
||||||
|
arr -> (Long) arr[1]
|
||||||
|
));
|
||||||
|
|
||||||
|
namespaces.forEach(namespace -> {
|
||||||
|
Long deploymentCount = deploymentCountMap.getOrDefault(namespace.getId(), 0L);
|
||||||
|
namespace.setDeploymentCount(deploymentCount);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,32 @@
|
|||||||
|
package com.qqchen.deploy.backend.deploy.service.impl;
|
||||||
|
|
||||||
|
import com.qqchen.deploy.backend.deploy.converter.K8sSyncHistoryConverter;
|
||||||
|
import com.qqchen.deploy.backend.deploy.dto.K8sSyncHistoryDTO;
|
||||||
|
import com.qqchen.deploy.backend.deploy.entity.K8sSyncHistory;
|
||||||
|
import com.qqchen.deploy.backend.deploy.query.K8sSyncHistoryQuery;
|
||||||
|
import com.qqchen.deploy.backend.deploy.repository.IK8sSyncHistoryRepository;
|
||||||
|
import com.qqchen.deploy.backend.deploy.service.IK8sSyncHistoryService;
|
||||||
|
import com.qqchen.deploy.backend.framework.service.impl.BaseServiceImpl;
|
||||||
|
import jakarta.annotation.Resource;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
@Slf4j
|
||||||
|
@Service
|
||||||
|
public class K8sSyncHistoryServiceImpl extends BaseServiceImpl<K8sSyncHistory, K8sSyncHistoryDTO, K8sSyncHistoryQuery, Long>
|
||||||
|
implements IK8sSyncHistoryService {
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
private IK8sSyncHistoryRepository k8sSyncHistoryRepository;
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
private K8sSyncHistoryConverter k8sSyncHistoryConverter;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<K8sSyncHistoryDTO> findByExternalSystemId(Long externalSystemId) {
|
||||||
|
List<K8sSyncHistory> histories = k8sSyncHistoryRepository.findByExternalSystemIdOrderByCreateTimeDesc(externalSystemId);
|
||||||
|
return k8sSyncHistoryConverter.toDtoList(histories);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -270,7 +270,26 @@ public enum ResponseCode {
|
|||||||
JENKINS_QUEUE_TIMEOUT(3205, "jenkins.queue.timeout"),
|
JENKINS_QUEUE_TIMEOUT(3205, "jenkins.queue.timeout"),
|
||||||
JENKINS_BUILD_TIMEOUT(3206, "jenkins.build.timeout"),
|
JENKINS_BUILD_TIMEOUT(3206, "jenkins.build.timeout"),
|
||||||
JENKINS_API_ERROR(3207, "jenkins.api.error"),
|
JENKINS_API_ERROR(3207, "jenkins.api.error"),
|
||||||
JENKINS_RESPONSE_PARSE_ERROR(3208, "jenkins.response.parse.error");
|
JENKINS_RESPONSE_PARSE_ERROR(3208, "jenkins.response.parse.error"),
|
||||||
|
|
||||||
|
// K8S集成错误码 (3220-3239)
|
||||||
|
K8S_CLUSTER_NOT_FOUND(3220, "k8s.cluster.not.found"),
|
||||||
|
K8S_CONNECTION_FAILED(3221, "k8s.connection.failed"),
|
||||||
|
K8S_AUTH_FAILED(3222, "k8s.auth.failed"),
|
||||||
|
K8S_CONFIG_INVALID(3223, "k8s.config.invalid"),
|
||||||
|
K8S_CONFIG_EMPTY(3224, "k8s.config.empty"),
|
||||||
|
K8S_KUBECONFIG_INVALID(3225, "k8s.kubeconfig.invalid"),
|
||||||
|
K8S_KUBECONFIG_EMPTY(3226, "k8s.kubeconfig.empty"),
|
||||||
|
K8S_NAMESPACE_NOT_FOUND(3227, "k8s.namespace.not.found"),
|
||||||
|
K8S_DEPLOYMENT_NOT_FOUND(3228, "k8s.deployment.not.found"),
|
||||||
|
K8S_API_ERROR(3229, "k8s.api.error"),
|
||||||
|
K8S_SERVER_ERROR(3230, "k8s.server.error"),
|
||||||
|
K8S_SYNC_FAILED(3231, "k8s.sync.failed"),
|
||||||
|
K8S_NAMESPACE_SYNC_FAILED(3232, "k8s.namespace.sync.failed"),
|
||||||
|
K8S_DEPLOYMENT_SYNC_FAILED(3233, "k8s.deployment.sync.failed"),
|
||||||
|
K8S_POD_NOT_FOUND(3234, "k8s.pod.not.found"),
|
||||||
|
K8S_RESOURCE_NOT_FOUND(3235, "k8s.resource.not.found"),
|
||||||
|
K8S_OPERATION_FAILED(3236, "k8s.operation.failed");
|
||||||
|
|
||||||
private final int code;
|
private final int code;
|
||||||
private final String messageKey; // 国际化消息key
|
private final String messageKey; // 国际化消息key
|
||||||
|
|||||||
@ -6,5 +6,6 @@ package com.qqchen.deploy.backend.system.enums;
|
|||||||
public enum ExternalSystemAuthTypeEnum {
|
public enum ExternalSystemAuthTypeEnum {
|
||||||
BASIC,
|
BASIC,
|
||||||
TOKEN,
|
TOKEN,
|
||||||
OAUTH
|
OAUTH,
|
||||||
|
KUBECONFIG
|
||||||
}
|
}
|
||||||
@ -6,5 +6,6 @@ package com.qqchen.deploy.backend.system.enums;
|
|||||||
public enum ExternalSystemTypeEnum {
|
public enum ExternalSystemTypeEnum {
|
||||||
JENKINS,
|
JENKINS,
|
||||||
GIT,
|
GIT,
|
||||||
ZENTAO
|
ZENTAO,
|
||||||
|
K8S
|
||||||
}
|
}
|
||||||
File diff suppressed because one or more lines are too long
@ -283,7 +283,7 @@ CREATE TABLE sys_external_system
|
|||||||
sync_status VARCHAR(50) NULL COMMENT '同步状态(SUCCESS/FAILED/RUNNING)',
|
sync_status VARCHAR(50) NULL COMMENT '同步状态(SUCCESS/FAILED/RUNNING)',
|
||||||
last_sync_time DATETIME(6) NULL COMMENT '最后同步时间',
|
last_sync_time DATETIME(6) NULL COMMENT '最后同步时间',
|
||||||
last_connect_time DATETIME(6) NULL COMMENT '最近连接成功时间',
|
last_connect_time DATETIME(6) NULL COMMENT '最近连接成功时间',
|
||||||
config JSON NULL COMMENT '系统特有配置',
|
config TEXT NULL COMMENT '系统特有配置(如kubeconfig等)',
|
||||||
|
|
||||||
CONSTRAINT UK_external_system_name UNIQUE (name),
|
CONSTRAINT UK_external_system_name UNIQUE (name),
|
||||||
CONSTRAINT UK_external_system_type_url UNIQUE (type, url)
|
CONSTRAINT UK_external_system_type_url UNIQUE (type, url)
|
||||||
@ -1449,3 +1449,86 @@ CREATE TABLE deploy_team_bookmark
|
|||||||
CONSTRAINT fk_bookmark_team FOREIGN KEY (team_id) REFERENCES deploy_team (id),
|
CONSTRAINT fk_bookmark_team FOREIGN KEY (team_id) REFERENCES deploy_team (id),
|
||||||
CONSTRAINT fk_bookmark_category FOREIGN KEY (category_id) REFERENCES deploy_team_bookmark_category (id)
|
CONSTRAINT fk_bookmark_category FOREIGN KEY (category_id) REFERENCES deploy_team_bookmark_category (id)
|
||||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='团队导航书签表';
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='团队导航书签表';
|
||||||
|
|
||||||
|
-- --------------------------------------------------------------------------------------
|
||||||
|
-- K8S同步表
|
||||||
|
-- --------------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
-- K8S命名空间表
|
||||||
|
CREATE TABLE deploy_k8s_namespace
|
||||||
|
(
|
||||||
|
id BIGINT PRIMARY KEY AUTO_INCREMENT COMMENT '主键ID',
|
||||||
|
create_by VARCHAR(50) NULL COMMENT '创建人',
|
||||||
|
create_time DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
|
||||||
|
update_by VARCHAR(50) NULL COMMENT '更新人',
|
||||||
|
update_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
|
||||||
|
version INT DEFAULT 1 COMMENT '版本号',
|
||||||
|
deleted BOOLEAN DEFAULT FALSE COMMENT '是否删除',
|
||||||
|
|
||||||
|
external_system_id BIGINT NOT NULL COMMENT 'K8S集群ID(外部系统ID)',
|
||||||
|
namespace_name VARCHAR(255) NOT NULL COMMENT '命名空间名称',
|
||||||
|
status VARCHAR(50) NULL COMMENT '状态',
|
||||||
|
labels JSON NULL COMMENT '标签',
|
||||||
|
yaml_config TEXT NULL COMMENT '完整的YAML配置',
|
||||||
|
|
||||||
|
UNIQUE KEY uk_system_namespace (external_system_id, namespace_name),
|
||||||
|
INDEX idx_external_system (external_system_id),
|
||||||
|
INDEX idx_deleted (deleted)
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='K8S命名空间表';
|
||||||
|
|
||||||
|
-- K8S Deployment表
|
||||||
|
CREATE TABLE deploy_k8s_deployment
|
||||||
|
(
|
||||||
|
id BIGINT PRIMARY KEY AUTO_INCREMENT COMMENT '主键ID',
|
||||||
|
create_by VARCHAR(50) NULL COMMENT '创建人',
|
||||||
|
create_time DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
|
||||||
|
update_by VARCHAR(50) NULL COMMENT '更新人',
|
||||||
|
update_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
|
||||||
|
version INT DEFAULT 1 COMMENT '版本号',
|
||||||
|
deleted BOOLEAN DEFAULT FALSE COMMENT '是否删除',
|
||||||
|
|
||||||
|
external_system_id BIGINT NOT NULL COMMENT 'K8S集群ID(外部系统ID)',
|
||||||
|
namespace_id BIGINT NOT NULL COMMENT '命名空间ID',
|
||||||
|
deployment_name VARCHAR(255) NOT NULL COMMENT 'Deployment名称',
|
||||||
|
|
||||||
|
replicas INT NULL COMMENT '期望副本数',
|
||||||
|
available_replicas INT NULL COMMENT '可用副本数',
|
||||||
|
ready_replicas INT NULL COMMENT '就绪副本数',
|
||||||
|
|
||||||
|
image VARCHAR(500) NULL COMMENT '容器镜像',
|
||||||
|
labels JSON NULL COMMENT '标签',
|
||||||
|
selector JSON NULL COMMENT '选择器',
|
||||||
|
yaml_config TEXT NULL COMMENT '完整的YAML配置',
|
||||||
|
|
||||||
|
k8s_create_time DATETIME NULL COMMENT 'K8S中的创建时间',
|
||||||
|
k8s_update_time DATETIME NULL COMMENT 'K8S中的更新时间',
|
||||||
|
|
||||||
|
UNIQUE KEY uk_namespace_deployment (namespace_id, deployment_name),
|
||||||
|
INDEX idx_external_system (external_system_id),
|
||||||
|
INDEX idx_namespace (namespace_id),
|
||||||
|
INDEX idx_deleted (deleted)
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='K8S Deployment表';
|
||||||
|
|
||||||
|
-- K8S同步历史表
|
||||||
|
CREATE TABLE deploy_k8s_sync_history
|
||||||
|
(
|
||||||
|
id BIGINT PRIMARY KEY AUTO_INCREMENT COMMENT '主键ID',
|
||||||
|
create_by VARCHAR(50) NULL COMMENT '创建人',
|
||||||
|
create_time DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
|
||||||
|
update_by VARCHAR(50) NULL COMMENT '更新人',
|
||||||
|
update_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
|
||||||
|
version INT DEFAULT 1 COMMENT '版本号',
|
||||||
|
deleted BOOLEAN DEFAULT FALSE COMMENT '是否删除',
|
||||||
|
|
||||||
|
sync_history_number VARCHAR(100) NOT NULL COMMENT '同步编号',
|
||||||
|
sync_type VARCHAR(50) NOT NULL COMMENT '同步类型(NAMESPACE/DEPLOYMENT)',
|
||||||
|
status VARCHAR(50) NOT NULL COMMENT '同步状态(SUCCESS/FAILED/RUNNING)',
|
||||||
|
start_time DATETIME NOT NULL COMMENT '开始时间',
|
||||||
|
end_time DATETIME NULL COMMENT '结束时间',
|
||||||
|
error_message TEXT NULL COMMENT '错误信息',
|
||||||
|
external_system_id BIGINT NOT NULL COMMENT 'K8S集群ID(外部系统ID)',
|
||||||
|
|
||||||
|
INDEX idx_external_system (external_system_id),
|
||||||
|
INDEX idx_sync_time (create_time),
|
||||||
|
INDEX idx_deleted (deleted)
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='K8S同步历史表';
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user