diff --git a/backend/pom.xml b/backend/pom.xml
index bd79e7b1..fe0f3fd9 100644
--- a/backend/pom.xml
+++ b/backend/pom.xml
@@ -281,6 +281,13 @@
HikariCP
5.0.1
+
+
+
+ io.kubernetes
+ client-java
+ 18.0.1
+
diff --git a/backend/src/main/java/com/qqchen/deploy/backend/deploy/api/K8sDeploymentApiController.java b/backend/src/main/java/com/qqchen/deploy/backend/deploy/api/K8sDeploymentApiController.java
new file mode 100644
index 00000000..f4dbef67
--- /dev/null
+++ b/backend/src/main/java/com/qqchen/deploy/backend/deploy/api/K8sDeploymentApiController.java
@@ -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 {
+
+ @Resource
+ private IK8sDeploymentService k8sDeploymentService;
+
+ @Override
+ public Response create(@Validated @RequestBody K8sDeploymentDTO dto) {
+ return super.create(dto);
+ }
+
+ @Override
+ public Response update(@PathVariable Long id, @Validated @RequestBody K8sDeploymentDTO dto) {
+ return super.update(id, dto);
+ }
+
+ @Override
+ public Response delete(@PathVariable Long id) {
+ return super.delete(id);
+ }
+
+ @Override
+ public Response findById(@PathVariable Long id) {
+ return super.findById(id);
+ }
+
+ @Override
+ public Response> findAll() {
+ return super.findAll();
+ }
+
+ @Override
+ public Response> page(K8sDeploymentQuery query) {
+ return super.page(query);
+ }
+
+ @Override
+ public Response> findAll(K8sDeploymentQuery query) {
+ return super.findAll(query);
+ }
+
+ @Override
+ public CompletableFuture> batchProcess(List dtos) {
+ return super.batchProcess(dtos);
+ }
+
+ @Operation(summary = "同步K8S Deployment", description = "异步同步,支持两种模式:1)只传externalSystemId-全量同步 2)传externalSystemId+namespaceId-同步指定命名空间")
+ @PostMapping("/sync")
+ public Response 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> 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> findByNamespaceId(
+ @Parameter(description = "命名空间ID", required = true) @PathVariable Long namespaceId
+ ) {
+ return Response.success(k8sDeploymentService.findByNamespaceId(namespaceId));
+ }
+
+ @Override
+ protected void exportData(HttpServletResponse response, List data) {
+ log.info("导出K8S Deployment数据,数据量:{}", data.size());
+ }
+}
diff --git a/backend/src/main/java/com/qqchen/deploy/backend/deploy/api/K8sNamespaceApiController.java b/backend/src/main/java/com/qqchen/deploy/backend/deploy/api/K8sNamespaceApiController.java
new file mode 100644
index 00000000..ea6332a2
--- /dev/null
+++ b/backend/src/main/java/com/qqchen/deploy/backend/deploy/api/K8sNamespaceApiController.java
@@ -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 {
+
+ @Resource
+ private IK8sNamespaceService k8sNamespaceService;
+
+ @Override
+ public Response create(@Validated @RequestBody K8sNamespaceDTO dto) {
+ return super.create(dto);
+ }
+
+ @Override
+ public Response update(@PathVariable Long id, @Validated @RequestBody K8sNamespaceDTO dto) {
+ return super.update(id, dto);
+ }
+
+ @Override
+ public Response delete(@PathVariable Long id) {
+ return super.delete(id);
+ }
+
+ @Override
+ public Response findById(@PathVariable Long id) {
+ return super.findById(id);
+ }
+
+ @Override
+ public Response> findAll() {
+ return super.findAll();
+ }
+
+ @Override
+ public Response> page(K8sNamespaceQuery query) {
+ return super.page(query);
+ }
+
+ @Override
+ public Response> findAll(K8sNamespaceQuery query) {
+ return super.findAll(query);
+ }
+
+ @Override
+ public CompletableFuture> batchProcess(List dtos) {
+ return super.batchProcess(dtos);
+ }
+
+ @Operation(summary = "同步K8S命名空间", description = "异步同步指定K8S集群的命名空间")
+ @PostMapping("/sync")
+ public Response sync(
+ @Parameter(description = "K8S集群ID(外部系统ID)", required = true) @RequestParam Long externalSystemId
+ ) {
+ k8sNamespaceService.syncNamespaces(externalSystemId);
+ return Response.success();
+ }
+
+ @Override
+ protected void exportData(HttpServletResponse response, List data) {
+ log.info("导出K8S命名空间数据,数据量:{}", data.size());
+ }
+}
diff --git a/backend/src/main/java/com/qqchen/deploy/backend/deploy/api/K8sSyncHistoryApiController.java b/backend/src/main/java/com/qqchen/deploy/backend/deploy/api/K8sSyncHistoryApiController.java
new file mode 100644
index 00000000..ca4a09ef
--- /dev/null
+++ b/backend/src/main/java/com/qqchen/deploy/backend/deploy/api/K8sSyncHistoryApiController.java
@@ -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 {
+
+ @Resource
+ private IK8sSyncHistoryService k8sSyncHistoryService;
+
+ @Override
+ public Response create(@Validated @RequestBody K8sSyncHistoryDTO dto) {
+ return super.create(dto);
+ }
+
+ @Override
+ public Response update(@PathVariable Long id, @Validated @RequestBody K8sSyncHistoryDTO dto) {
+ return super.update(id, dto);
+ }
+
+ @Override
+ public Response delete(@PathVariable Long id) {
+ return super.delete(id);
+ }
+
+ @Override
+ public Response findById(@PathVariable Long id) {
+ return super.findById(id);
+ }
+
+ @Override
+ public Response> findAll() {
+ return super.findAll();
+ }
+
+ @Override
+ public Response> page(K8sSyncHistoryQuery query) {
+ return super.page(query);
+ }
+
+ @Override
+ public Response> findAll(K8sSyncHistoryQuery query) {
+ return super.findAll(query);
+ }
+
+ @Override
+ public CompletableFuture> batchProcess(List dtos) {
+ return super.batchProcess(dtos);
+ }
+
+ @Operation(summary = "根据集群ID查询同步历史", description = "查询指定K8S集群的同步历史记录")
+ @GetMapping("/by-system/{externalSystemId}")
+ public Response> findByExternalSystemId(
+ @Parameter(description = "K8S集群ID", required = true) @PathVariable Long externalSystemId
+ ) {
+ return Response.success(k8sSyncHistoryService.findByExternalSystemId(externalSystemId));
+ }
+
+ @Override
+ protected void exportData(HttpServletResponse response, List data) {
+ log.info("导出K8S同步历史数据,数据量:{}", data.size());
+ }
+}
diff --git a/backend/src/main/java/com/qqchen/deploy/backend/deploy/config/ThreadPoolConfig.java b/backend/src/main/java/com/qqchen/deploy/backend/deploy/config/ThreadPoolConfig.java
index 77b4c56f..6b0c59de 100644
--- a/backend/src/main/java/com/qqchen/deploy/backend/deploy/config/ThreadPoolConfig.java
+++ b/backend/src/main/java/com/qqchen/deploy/backend/deploy/config/ThreadPoolConfig.java
@@ -84,6 +84,29 @@ public class ThreadPoolConfig {
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;
+ }
+
/**
* 通用应用任务线程池 - 保留平台线程(不使用虚拟线程)
*
diff --git a/backend/src/main/java/com/qqchen/deploy/backend/deploy/converter/K8sDeploymentConverter.java b/backend/src/main/java/com/qqchen/deploy/backend/deploy/converter/K8sDeploymentConverter.java
new file mode 100644
index 00000000..dd34c759
--- /dev/null
+++ b/backend/src/main/java/com/qqchen/deploy/backend/deploy/converter/K8sDeploymentConverter.java
@@ -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 {
+}
diff --git a/backend/src/main/java/com/qqchen/deploy/backend/deploy/converter/K8sNamespaceConverter.java b/backend/src/main/java/com/qqchen/deploy/backend/deploy/converter/K8sNamespaceConverter.java
new file mode 100644
index 00000000..bb55c049
--- /dev/null
+++ b/backend/src/main/java/com/qqchen/deploy/backend/deploy/converter/K8sNamespaceConverter.java
@@ -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 {
+}
diff --git a/backend/src/main/java/com/qqchen/deploy/backend/deploy/converter/K8sSyncHistoryConverter.java b/backend/src/main/java/com/qqchen/deploy/backend/deploy/converter/K8sSyncHistoryConverter.java
new file mode 100644
index 00000000..380ac7ed
--- /dev/null
+++ b/backend/src/main/java/com/qqchen/deploy/backend/deploy/converter/K8sSyncHistoryConverter.java
@@ -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 {
+}
diff --git a/backend/src/main/java/com/qqchen/deploy/backend/deploy/dto/K8sDeploymentDTO.java b/backend/src/main/java/com/qqchen/deploy/backend/deploy/dto/K8sDeploymentDTO.java
new file mode 100644
index 00000000..04e5de77
--- /dev/null
+++ b/backend/src/main/java/com/qqchen/deploy/backend/deploy/dto/K8sDeploymentDTO.java
@@ -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 labels;
+
+ @Schema(description = "选择器")
+ private Map selector;
+
+ @Schema(description = "K8S中的创建时间")
+ private LocalDateTime k8sCreateTime;
+
+ @Schema(description = "K8S中的更新时间")
+ private LocalDateTime k8sUpdateTime;
+
+ @Schema(description = "YAML配置")
+ private String yamlConfig;
+}
diff --git a/backend/src/main/java/com/qqchen/deploy/backend/deploy/dto/K8sNamespaceDTO.java b/backend/src/main/java/com/qqchen/deploy/backend/deploy/dto/K8sNamespaceDTO.java
new file mode 100644
index 00000000..ac957a26
--- /dev/null
+++ b/backend/src/main/java/com/qqchen/deploy/backend/deploy/dto/K8sNamespaceDTO.java
@@ -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 labels;
+
+ @Schema(description = "YAML配置")
+ private String yamlConfig;
+
+ @Schema(description = "Deployment数量(仅在列表和分页查询时填充)")
+ private Long deploymentCount;
+}
diff --git a/backend/src/main/java/com/qqchen/deploy/backend/deploy/dto/K8sSyncHistoryDTO.java b/backend/src/main/java/com/qqchen/deploy/backend/deploy/dto/K8sSyncHistoryDTO.java
new file mode 100644
index 00000000..9cc33832
--- /dev/null
+++ b/backend/src/main/java/com/qqchen/deploy/backend/deploy/dto/K8sSyncHistoryDTO.java
@@ -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;
+}
diff --git a/backend/src/main/java/com/qqchen/deploy/backend/deploy/entity/ExternalSystem.java b/backend/src/main/java/com/qqchen/deploy/backend/deploy/entity/ExternalSystem.java
index 9085a52d..ca0cb1f2 100644
--- a/backend/src/main/java/com/qqchen/deploy/backend/deploy/entity/ExternalSystem.java
+++ b/backend/src/main/java/com/qqchen/deploy/backend/deploy/entity/ExternalSystem.java
@@ -82,10 +82,10 @@ public class ExternalSystem extends Entity {
private LocalDateTime lastConnectTime;
/**
- * 系统特有配置,JSON格式
+ * 系统特有配置(如kubeconfig等)
+ * 使用TEXT类型存储,支持任意格式的配置内容
*/
- @Column(columnDefinition = "JSON")
+ @Column(columnDefinition = "TEXT")
private String config;
-
}
\ No newline at end of file
diff --git a/backend/src/main/java/com/qqchen/deploy/backend/deploy/entity/K8sDeployment.java b/backend/src/main/java/com/qqchen/deploy/backend/deploy/entity/K8sDeployment.java
new file mode 100644
index 00000000..f4d75f62
--- /dev/null
+++ b/backend/src/main/java/com/qqchen/deploy/backend/deploy/entity/K8sDeployment.java
@@ -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 {
+
+ @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 labels;
+
+ @Type(JsonType.class)
+ @Column(name = "selector", columnDefinition = "JSON")
+ private Map 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;
+}
diff --git a/backend/src/main/java/com/qqchen/deploy/backend/deploy/entity/K8sNamespace.java b/backend/src/main/java/com/qqchen/deploy/backend/deploy/entity/K8sNamespace.java
new file mode 100644
index 00000000..6c33b461
--- /dev/null
+++ b/backend/src/main/java/com/qqchen/deploy/backend/deploy/entity/K8sNamespace.java
@@ -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 {
+
+ @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 labels;
+
+ @Column(name = "yaml_config", columnDefinition = "TEXT")
+ private String yamlConfig;
+}
diff --git a/backend/src/main/java/com/qqchen/deploy/backend/deploy/entity/K8sSyncHistory.java b/backend/src/main/java/com/qqchen/deploy/backend/deploy/entity/K8sSyncHistory.java
new file mode 100644
index 00000000..ee061818
--- /dev/null
+++ b/backend/src/main/java/com/qqchen/deploy/backend/deploy/entity/K8sSyncHistory.java
@@ -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 {
+
+ @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;
+}
diff --git a/backend/src/main/java/com/qqchen/deploy/backend/deploy/enums/K8sSyncType.java b/backend/src/main/java/com/qqchen/deploy/backend/deploy/enums/K8sSyncType.java
new file mode 100644
index 00000000..b6dba361
--- /dev/null
+++ b/backend/src/main/java/com/qqchen/deploy/backend/deploy/enums/K8sSyncType.java
@@ -0,0 +1,6 @@
+package com.qqchen.deploy.backend.deploy.enums;
+
+public enum K8sSyncType {
+ NAMESPACE, // 同步命名空间
+ DEPLOYMENT // 同步Deployment
+}
diff --git a/backend/src/main/java/com/qqchen/deploy/backend/deploy/integration/IK8sServiceIntegration.java b/backend/src/main/java/com/qqchen/deploy/backend/deploy/integration/IK8sServiceIntegration.java
new file mode 100644
index 00000000..a686cb56
--- /dev/null
+++ b/backend/src/main/java/com/qqchen/deploy/backend/deploy/integration/IK8sServiceIntegration.java
@@ -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 listNamespaces(ExternalSystem externalSystem);
+
+ /**
+ * 查询指定命名空间下的所有Deployment
+ *
+ * @param externalSystem K8S系统配置
+ * @param namespace 命名空间名称
+ * @return Deployment列表
+ */
+ List listDeployments(ExternalSystem externalSystem, String namespace);
+
+ /**
+ * 查询所有Deployment(跨命名空间)
+ *
+ * @param externalSystem K8S系统配置
+ * @return Deployment列表
+ */
+ List listAllDeployments(ExternalSystem externalSystem);
+
+ /**
+ * 获取系统类型
+ *
+ * @return K8S系统类型
+ */
+ @Override
+ default ExternalSystemTypeEnum getSystemType() {
+ return ExternalSystemTypeEnum.K8S;
+ }
+}
diff --git a/backend/src/main/java/com/qqchen/deploy/backend/deploy/integration/impl/K8sServiceIntegrationImpl.java b/backend/src/main/java/com/qqchen/deploy/backend/deploy/integration/impl/K8sServiceIntegrationImpl.java
new file mode 100644
index 00000000..f02b67e3
--- /dev/null
+++ b/backend/src/main/java/com/qqchen/deploy/backend/deploy/integration/impl/K8sServiceIntegrationImpl.java
@@ -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 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 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 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 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 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 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 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;
+ }
+}
diff --git a/backend/src/main/java/com/qqchen/deploy/backend/deploy/integration/response/K8sDeploymentResponse.java b/backend/src/main/java/com/qqchen/deploy/backend/deploy/integration/response/K8sDeploymentResponse.java
new file mode 100644
index 00000000..cf6a437e
--- /dev/null
+++ b/backend/src/main/java/com/qqchen/deploy/backend/deploy/integration/response/K8sDeploymentResponse.java
@@ -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 labels;
+ private Map selector;
+ private LocalDateTime creationTimestamp;
+ private LocalDateTime lastUpdateTime;
+ private String yamlConfig;
+}
diff --git a/backend/src/main/java/com/qqchen/deploy/backend/deploy/integration/response/K8sNamespaceResponse.java b/backend/src/main/java/com/qqchen/deploy/backend/deploy/integration/response/K8sNamespaceResponse.java
new file mode 100644
index 00000000..7ab9b375
--- /dev/null
+++ b/backend/src/main/java/com/qqchen/deploy/backend/deploy/integration/response/K8sNamespaceResponse.java
@@ -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 labels;
+ private LocalDateTime creationTimestamp;
+ private String yamlConfig;
+}
diff --git a/backend/src/main/java/com/qqchen/deploy/backend/deploy/query/K8sDeploymentQuery.java b/backend/src/main/java/com/qqchen/deploy/backend/deploy/query/K8sDeploymentQuery.java
new file mode 100644
index 00000000..3ef8a6c2
--- /dev/null
+++ b/backend/src/main/java/com/qqchen/deploy/backend/deploy/query/K8sDeploymentQuery.java
@@ -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;
+}
diff --git a/backend/src/main/java/com/qqchen/deploy/backend/deploy/query/K8sNamespaceQuery.java b/backend/src/main/java/com/qqchen/deploy/backend/deploy/query/K8sNamespaceQuery.java
new file mode 100644
index 00000000..7516096b
--- /dev/null
+++ b/backend/src/main/java/com/qqchen/deploy/backend/deploy/query/K8sNamespaceQuery.java
@@ -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;
+}
diff --git a/backend/src/main/java/com/qqchen/deploy/backend/deploy/query/K8sSyncHistoryQuery.java b/backend/src/main/java/com/qqchen/deploy/backend/deploy/query/K8sSyncHistoryQuery.java
new file mode 100644
index 00000000..48752e78
--- /dev/null
+++ b/backend/src/main/java/com/qqchen/deploy/backend/deploy/query/K8sSyncHistoryQuery.java
@@ -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;
+}
diff --git a/backend/src/main/java/com/qqchen/deploy/backend/deploy/repository/IJenkinsJobRepository.java b/backend/src/main/java/com/qqchen/deploy/backend/deploy/repository/IJenkinsJobRepository.java
index 8e39969b..ad68d4c5 100644
--- a/backend/src/main/java/com/qqchen/deploy/backend/deploy/repository/IJenkinsJobRepository.java
+++ b/backend/src/main/java/com/qqchen/deploy/backend/deploy/repository/IJenkinsJobRepository.java
@@ -26,6 +26,14 @@ public interface IJenkinsJobRepository extends IBaseRepository
*/
Optional findByExternalSystemIdAndViewIdAndJobName(Long externalSystemId, Long viewId, String jobName);
+ /**
+ * 根据外部系统ID查询所有任务
+ *
+ * @param externalSystemId 外部系统ID
+ * @return 任务列表
+ */
+ List findByExternalSystemId(Long externalSystemId);
+
/**
* 根据外部系统ID和视图ID查询所有任务
*
diff --git a/backend/src/main/java/com/qqchen/deploy/backend/deploy/repository/IK8sDeploymentRepository.java b/backend/src/main/java/com/qqchen/deploy/backend/deploy/repository/IK8sDeploymentRepository.java
new file mode 100644
index 00000000..5d7f6057
--- /dev/null
+++ b/backend/src/main/java/com/qqchen/deploy/backend/deploy/repository/IK8sDeploymentRepository.java
@@ -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 {
+
+ Optional findByNamespaceIdAndDeploymentName(Long namespaceId, String deploymentName);
+
+ List findByExternalSystemId(Long externalSystemId);
+
+ List 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