diff --git a/backend/src/main/java/com/qqchen/deploy/backend/deploy/integration/impl/BaseExternalSystemIntegration.java b/backend/src/main/java/com/qqchen/deploy/backend/deploy/integration/impl/BaseExternalSystemIntegration.java new file mode 100644 index 00000000..8ada2067 --- /dev/null +++ b/backend/src/main/java/com/qqchen/deploy/backend/deploy/integration/impl/BaseExternalSystemIntegration.java @@ -0,0 +1,75 @@ +package com.qqchen.deploy.backend.deploy.integration.impl; + +import com.qqchen.deploy.backend.deploy.entity.ExternalSystem; +import com.qqchen.deploy.backend.framework.util.SensitiveDataEncryptor; +import jakarta.annotation.Resource; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang3.StringUtils; + +/** + * 外部系统集成基类 + * 提供统一的密码解密功能 + * + * @author qqchen + * @since 2025-11-11 + */ +@Slf4j +public abstract class BaseExternalSystemIntegration { + + @Resource + protected SensitiveDataEncryptor encryptor; + + /** + * 解密外部系统的敏感数据(密码和Token) + * 创建一个新对象,不影响原对象 + * + * @param system 原始的外部系统对象(包含加密的密码) + * @return 解密后的外部系统对象 + */ + protected ExternalSystem decryptSystem(ExternalSystem system) { + if (system == null) { + return null; + } + + // 创建新对象,避免修改原对象 + ExternalSystem decrypted = new ExternalSystem(); + + // 复制所有属性 + decrypted.setId(system.getId()); + decrypted.setName(system.getName()); + decrypted.setType(system.getType()); + decrypted.setUrl(system.getUrl()); + decrypted.setAuthType(system.getAuthType()); + decrypted.setUsername(system.getUsername()); + decrypted.setEnabled(system.getEnabled()); + decrypted.setConfig(system.getConfig()); + decrypted.setRemark(system.getRemark()); + decrypted.setSort(system.getSort()); + + // 解密密码 + if (StringUtils.isNotBlank(system.getPassword())) { + try { + String decryptedPassword = encryptor.decrypt(system.getPassword()); + decrypted.setPassword(decryptedPassword); + log.debug("外部系统密码已解密: systemId={}, systemName={}", system.getId(), system.getName()); + } catch (Exception e) { + log.warn("解密密码失败,使用原值: systemId={}, error={}", system.getId(), e.getMessage()); + decrypted.setPassword(system.getPassword()); + } + } + + // 解密Token + if (StringUtils.isNotBlank(system.getToken())) { + try { + String decryptedToken = encryptor.decrypt(system.getToken()); + decrypted.setToken(decryptedToken); + log.debug("外部系统Token已解密: systemId={}, systemName={}", system.getId(), system.getName()); + } catch (Exception e) { + log.warn("解密Token失败,使用原值: systemId={}, error={}", system.getId(), e.getMessage()); + decrypted.setToken(system.getToken()); + } + } + + return decrypted; + } +} diff --git a/backend/src/main/java/com/qqchen/deploy/backend/deploy/integration/impl/GitServiceIntegrationImpl.java b/backend/src/main/java/com/qqchen/deploy/backend/deploy/integration/impl/GitServiceIntegrationImpl.java index e9df702f..639264c4 100644 --- a/backend/src/main/java/com/qqchen/deploy/backend/deploy/integration/impl/GitServiceIntegrationImpl.java +++ b/backend/src/main/java/com/qqchen/deploy/backend/deploy/integration/impl/GitServiceIntegrationImpl.java @@ -27,12 +27,13 @@ import java.util.stream.Collectors; */ @Slf4j @Service -public class GitServiceIntegrationImpl implements IGitServiceIntegration { +public class GitServiceIntegrationImpl extends BaseExternalSystemIntegration implements IGitServiceIntegration { private final RestTemplate restTemplate = new RestTemplate(); @Override public boolean testConnection(ExternalSystem system) { + system = decryptSystem(system); try { String url = system.getUrl() + "/api/v4/version"; HttpHeaders headers = createHeaders(system); @@ -54,6 +55,7 @@ public class GitServiceIntegrationImpl implements IGitServiceIntegration { @Override public List groups(ExternalSystem system) { + system = decryptSystem(system); try { String url = String.format("%s/api/v4/groups?per_page=100", system.getUrl()); HttpHeaders headers = createHeaders(system); @@ -76,6 +78,7 @@ public class GitServiceIntegrationImpl implements IGitServiceIntegration { @Override public List projects(ExternalSystem system) { + system = decryptSystem(system); try { String url = String.format("%s/api/v4/projects", system.getUrl()); HttpHeaders headers = createHeaders(system); @@ -98,6 +101,7 @@ public class GitServiceIntegrationImpl implements IGitServiceIntegration { @Override public List projectsByGroup(ExternalSystem system, Long groupId) { + system = decryptSystem(system); try { String url = String.format("%s/api/v4/groups/%d/projects?per_page=100", system.getUrl(), groupId); HttpHeaders headers = createHeaders(system); @@ -120,6 +124,7 @@ public class GitServiceIntegrationImpl implements IGitServiceIntegration { @Override public List branches(ExternalSystem system, Long projectId) { + system = decryptSystem(system); try { String url = String.format("%s/api/v4/projects/%d/repository/branches?per_page=100", system.getUrl(), projectId); HttpHeaders headers = createHeaders(system); diff --git a/backend/src/main/java/com/qqchen/deploy/backend/deploy/integration/impl/JenkinsServiceIntegrationImpl.java b/backend/src/main/java/com/qqchen/deploy/backend/deploy/integration/impl/JenkinsServiceIntegrationImpl.java index a0d7e2b5..e50c1648 100644 --- a/backend/src/main/java/com/qqchen/deploy/backend/deploy/integration/impl/JenkinsServiceIntegrationImpl.java +++ b/backend/src/main/java/com/qqchen/deploy/backend/deploy/integration/impl/JenkinsServiceIntegrationImpl.java @@ -51,7 +51,7 @@ import java.util.Map; @Slf4j @Service -public class JenkinsServiceIntegrationImpl implements IJenkinsServiceIntegration { +public class JenkinsServiceIntegrationImpl extends BaseExternalSystemIntegration implements IJenkinsServiceIntegration { @Resource private IExternalSystemRepository systemRepository; @@ -60,16 +60,12 @@ public class JenkinsServiceIntegrationImpl implements IJenkinsServiceIntegration @Override public boolean testConnection(ExternalSystem system) { + // 解密系统信息 + system = decryptSystem(system); + try { String url = system.getUrl() + "/api/json"; - // 详细日志:检查认证信息 - log.info("===== Jenkins连接测试调试信息 ====="); - log.info("URL: {}", url); - log.info("认证类型: {}", system.getAuthType()); - log.info("用户名: {}", system.getUsername()); - log.info("密码是否为空: {}", system.getPassword() == null || system.getPassword().isEmpty()); - HttpHeaders headers = createHeaders(system); // 打印实际发送的请求头 @@ -137,6 +133,7 @@ public class JenkinsServiceIntegrationImpl implements IJenkinsServiceIntegration @Override public JenkinsCrumbIssuerResponse getJenkinsCrumbIssue(ExternalSystem externalSystem) { + externalSystem = decryptSystem(externalSystem); String url = externalSystem.getUrl() + "/crumbIssuer/api/json"; HttpHeaders headers = createHeaders(externalSystem); HttpEntity entity = new HttpEntity<>(headers); @@ -145,6 +142,7 @@ public class JenkinsServiceIntegrationImpl implements IJenkinsServiceIntegration @Override public String buildWithParameters(ExternalSystem externalSystem, String jobName, Map parameters) { + externalSystem = decryptSystem(externalSystem); try { // 如果参数中包含PIPELINE_SCRIPT,确保Job配置中有该参数 if (parameters.containsKey("PIPELINE_SCRIPT")) { @@ -375,6 +373,7 @@ public class JenkinsServiceIntegrationImpl implements IJenkinsServiceIntegration @Override public JenkinsQueueBuildInfoResponse getQueuedBuildInfo(ExternalSystem externalSystem, String queueId) { + externalSystem = decryptSystem(externalSystem); String queueUrl = String.format("%s/queue/item/%s/api/json", externalSystem.getUrl().trim(), queueId); ResponseEntity> response = restTemplate.exchange(queueUrl, HttpMethod.GET, createHttpEntity(externalSystem), new ParameterizedTypeReference<>() { }); @@ -405,6 +404,7 @@ public class JenkinsServiceIntegrationImpl implements IJenkinsServiceIntegration @Override public JenkinsBuildStatus getBuildStatus(ExternalSystem externalSystem, String jobName, Integer buildNumber) { + externalSystem = decryptSystem(externalSystem); try { String url = String.format("%s/job/%s/%d/api/json", externalSystem.getUrl().trim(), jobName, buildNumber); ResponseEntity> response = restTemplate.exchange( @@ -435,6 +435,7 @@ public class JenkinsServiceIntegrationImpl implements IJenkinsServiceIntegration @Override public JenkinsBuildResponse getBuildDetails(ExternalSystem externalSystem, String jobName, Integer buildNumber) { + externalSystem = decryptSystem(externalSystem); try { // 构建 tree 参数,只获取我们需要的字段 String treeQuery = "number,url,result,duration,timestamp,building," + @@ -562,9 +563,10 @@ public class JenkinsServiceIntegrationImpl implements IJenkinsServiceIntegration * @return 视图列表 */ public List listViews(ExternalSystem externalSystem) { + final ExternalSystem decryptedSystem = decryptSystem(externalSystem); String treeQuery = "views[name,url,description,_class]"; List views = callJenkinsApi( - externalSystem, + decryptedSystem, "/api/json", treeQuery, "views", @@ -572,6 +574,7 @@ public class JenkinsServiceIntegrationImpl implements IJenkinsServiceIntegration ); // 过滤和清洗数据 + final String baseUrl = StringUtils.removeEnd(decryptedSystem.getUrl(), "/"); return views.stream() .filter(view -> !"all".equalsIgnoreCase(view.getName())) .peek(view -> { @@ -581,7 +584,6 @@ public class JenkinsServiceIntegrationImpl implements IJenkinsServiceIntegration } // 转换URL为相对路径 if (view.getUrl() != null) { - String baseUrl = StringUtils.removeEnd(externalSystem.getUrl(), "/"); view.setUrl(view.getUrl().replace(baseUrl, "")); } }) @@ -596,6 +598,7 @@ public class JenkinsServiceIntegrationImpl implements IJenkinsServiceIntegration * @return 任务列表 */ public List listJobs(ExternalSystem externalSystem, String viewName) { + externalSystem = decryptSystem(externalSystem); // 只查询必要的字段,确保包含lastBuild的timestamp字段 String treeQuery = "jobs[name,url,description,buildable,nextBuildNumber," + "lastBuild[number,timestamp,result],color]"; @@ -629,6 +632,7 @@ public class JenkinsServiceIntegrationImpl implements IJenkinsServiceIntegration @Override public JenkinsJobResponse job(ExternalSystem externalSystem, String jobName) { + externalSystem = decryptSystem(externalSystem); try { // 1. 构建请求URL String url = UriComponentsBuilder.fromHttpUrl(externalSystem.getUrl()) @@ -677,6 +681,7 @@ public class JenkinsServiceIntegrationImpl implements IJenkinsServiceIntegration * @return 构建信息列表 */ public List listBuilds(ExternalSystem externalSystem, String jobName) { + externalSystem = decryptSystem(externalSystem); // Jenkins API 默认只返回最近的若干个构建,通过 {from,to} 可以指定范围 String treeQuery = "builds[number,url,result,timestamp,duration,building,actions[_class,parameters[_class,name,value]]]"; List builds = callJenkinsApi( diff --git a/backend/src/main/java/com/qqchen/deploy/backend/deploy/service/impl/ExternalSystemServiceImpl.java b/backend/src/main/java/com/qqchen/deploy/backend/deploy/service/impl/ExternalSystemServiceImpl.java index 679839c9..3cea59d3 100644 --- a/backend/src/main/java/com/qqchen/deploy/backend/deploy/service/impl/ExternalSystemServiceImpl.java +++ b/backend/src/main/java/com/qqchen/deploy/backend/deploy/service/impl/ExternalSystemServiceImpl.java @@ -197,6 +197,22 @@ public class ExternalSystemServiceImpl extends BaseServiceImpl page(ExternalSystemQuery query) { + org.springframework.data.domain.Page page = super.page(query); + // 查询后处理:用掩码替换敏感数据 + page.getContent().forEach(this::maskSensitiveData); + return page; + } + + @Override + public List findAll(ExternalSystemQuery query) { + List dtos = super.findAll(query); + // 查询后处理:用掩码替换敏感数据 + dtos.forEach(this::maskSensitiveData); + return dtos; + } + @Override @Transactional public boolean testConnection(Long id) {