大声道撒旦

This commit is contained in:
dengqichen 2025-01-09 14:25:28 +08:00
parent c117e15bcc
commit 0f74f2e646
8 changed files with 235 additions and 72 deletions

View File

@ -20,14 +20,14 @@ import org.springframework.web.bind.annotation.*;
public class RepositoryManagerApiController { public class RepositoryManagerApiController {
@Resource @Resource
private IRepositoryManagerService gitManagerService; private IRepositoryManagerService repositoryManagerService;
@Operation(summary = "同步Git仓库组", description = "同步指定外部系统的所有仓库组") @Operation(summary = "同步Git仓库组", description = "同步指定外部系统的所有仓库组")
@PostMapping("/{externalSystemId}/sync-groups") @PostMapping("/{externalSystemId}/sync-groups")
public Response<Void> syncGroups( public Response<Void> syncGroups(
@Parameter(description = "外部系统ID", required = true) @PathVariable Long externalSystemId @Parameter(description = "外部系统ID", required = true) @PathVariable Long externalSystemId
) { ) {
gitManagerService.syncGroups(externalSystemId); repositoryManagerService.syncGroups(externalSystemId);
return Response.success(); return Response.success();
} }
@ -36,7 +36,7 @@ public class RepositoryManagerApiController {
public Response<Void> syncProjects( public Response<Void> syncProjects(
@Parameter(description = "外部系统ID", required = true) @PathVariable Long externalSystemId @Parameter(description = "外部系统ID", required = true) @PathVariable Long externalSystemId
) { ) {
gitManagerService.syncProjects(externalSystemId); repositoryManagerService.syncProjects(externalSystemId);
return Response.success(); return Response.success();
} }
@ -45,7 +45,7 @@ public class RepositoryManagerApiController {
public Response<Void> syncBranches( public Response<Void> syncBranches(
@Parameter(description = "外部系统ID", required = true) @PathVariable Long externalSystemId @Parameter(description = "外部系统ID", required = true) @PathVariable Long externalSystemId
) { ) {
gitManagerService.syncBranches(externalSystemId); repositoryManagerService.syncBranches(externalSystemId);
return Response.success(); return Response.success();
} }
@ -54,6 +54,6 @@ public class RepositoryManagerApiController {
public Response<GitInstanceDTO> instance( public Response<GitInstanceDTO> instance(
@Parameter(description = "外部系统ID", required = true) @PathVariable Long externalSystemId @Parameter(description = "外部系统ID", required = true) @PathVariable Long externalSystemId
) { ) {
return Response.success(gitManagerService.instance(externalSystemId)); return Response.success(repositoryManagerService.instance(externalSystemId));
} }
} }

View File

@ -16,7 +16,6 @@ import java.time.LocalDateTime;
@LogicDelete @LogicDelete
public class RepositoryProject extends Entity<Long> { public class RepositoryProject extends Entity<Long> {
@Column(nullable = false) @Column(nullable = false)
private String name; private String name;
@ -30,8 +29,8 @@ public class RepositoryProject extends Entity<Long> {
@Column(name = "group_id") @Column(name = "group_id")
private Long groupId; private Long groupId;
@Column(name = "is_default_branch") @Column(name = "default_branch")
private String isDefaultBranch; private String defaultBranch;
@Column(name = "web_url") @Column(name = "web_url")
private String webUrl; private String webUrl;
@ -45,6 +44,15 @@ public class RepositoryProject extends Entity<Long> {
@Column(name = "last_activity_at") @Column(name = "last_activity_at")
private LocalDateTime lastActivityAt; private LocalDateTime lastActivityAt;
@Column(name = "name_with_namespace")
private String nameWithNamespace;
@Column(name = "path_with_namespace")
private String pathWithNamespace;
@Column(name = "created_at")
private LocalDateTime createdAt;
@Column(name = "external_system_id") @Column(name = "external_system_id")
private Long externalSystemId; private Long externalSystemId;

View File

@ -30,6 +30,15 @@ public interface IGitServiceIntegration extends IExternalSystemIntegration {
*/ */
List<GitProjectResponse> projects(ExternalSystem system); List<GitProjectResponse> projects(ExternalSystem system);
/**
* 获取指定组下的所有项目
*
* @param system 外部系统配置
* @param groupId 组ID
* @return 项目列表
*/
List<GitProjectResponse> projectsByGroup(ExternalSystem system, Long groupId);
/** /**
* 获取指定项目的所有分支 * 获取指定项目的所有分支
* *

View File

@ -71,7 +71,7 @@ public class GitServiceIntegrationImpl implements IGitServiceIntegration {
@Override @Override
public List<GitProjectResponse> projects(ExternalSystem system) { public List<GitProjectResponse> projects(ExternalSystem system) {
try { try {
String url = String.format("%s/api/v4/projects?per_page=100", system.getUrl()); String url = String.format("%s/api/v4/projects", system.getUrl());
HttpHeaders headers = createHeaders(system); HttpHeaders headers = createHeaders(system);
HttpEntity<String> entity = new HttpEntity<>(headers); HttpEntity<String> entity = new HttpEntity<>(headers);
@ -89,6 +89,27 @@ public class GitServiceIntegrationImpl implements IGitServiceIntegration {
} }
} }
@Override
public List<GitProjectResponse> projectsByGroup(ExternalSystem system, Long groupId) {
try {
String url = String.format("%s/api/v4/groups/%d/projects?per_page=100", system.getUrl(), groupId);
HttpHeaders headers = createHeaders(system);
HttpEntity<String> entity = new HttpEntity<>(headers);
ResponseEntity<List<GitProjectResponse>> response = restTemplate.exchange(
url,
HttpMethod.GET,
entity,
new ParameterizedTypeReference<>() {}
);
return response.getBody() != null ? response.getBody() : Collections.emptyList();
} catch (Exception e) {
log.error("Failed to fetch git projects for system: {} and group: {}", system.getName(), groupId, e);
return Collections.emptyList();
}
}
@Override @Override
public List<GitBranchResponse> branches(ExternalSystem system, Long projectId) { public List<GitBranchResponse> branches(ExternalSystem system, Long projectId) {
try { try {

View File

@ -1,8 +1,11 @@
package com.qqchen.deploy.backend.deploy.integration.response; package com.qqchen.deploy.backend.deploy.integration.response;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.Data; import lombok.Data;
import java.time.LocalDateTime; import java.time.LocalDateTime;
import java.util.List;
import java.util.Map;
/** /**
* Git项目响应对象 * Git项目响应对象
@ -20,13 +23,81 @@ public class GitProjectResponse {
private String visibility; private String visibility;
@JsonProperty("default_branch")
private String defaultBranch; private String defaultBranch;
@JsonProperty("web_url")
private String webUrl; private String webUrl;
private String sshUrl; @JsonProperty("ssh_url_to_repo")
private String sshUrlToRepo;
private String httpUrl; @JsonProperty("http_url_to_repo")
private String httpUrlToRepo;
@JsonProperty("last_activity_at")
private LocalDateTime lastActivityAt; private LocalDateTime lastActivityAt;
@JsonProperty("name_with_namespace")
private String nameWithNamespace;
@JsonProperty("path_with_namespace")
private String pathWithNamespace;
@JsonProperty("created_at")
private LocalDateTime createdAt;
@JsonProperty("tag_list")
private List<String> tagList;
@JsonProperty("readme_url")
private String readmeUrl;
@JsonProperty("avatar_url")
private String avatarUrl;
@JsonProperty("forks_count")
private Integer forksCount;
@JsonProperty("star_count")
private Integer starCount;
@JsonProperty("packages_enabled")
private Boolean packagesEnabled;
@JsonProperty("empty_repo")
private Boolean emptyRepo;
@JsonProperty("archived")
private Boolean archived;
@JsonProperty("container_registry_enabled")
private Boolean containerRegistryEnabled;
@JsonProperty("container_expiration_policy")
private Map<String, Object> containerExpirationPolicy;
@JsonProperty("resolve_outdated_diff_discussions")
private Boolean resolveOutdatedDiffDiscussions;
private Namespace namespace;
@JsonProperty("_links")
private Map<String, String> links;
@Data
public static class Namespace {
private Long id;
private String name;
private String path;
private String kind;
@JsonProperty("full_path")
private String fullPath;
@JsonProperty("parent_id")
private Long parentId;
@JsonProperty("avatar_url")
private String avatarUrl;
@JsonProperty("web_url")
private String webUrl;
}
} }

View File

@ -60,13 +60,11 @@ public class RepositoryBranchServiceImpl extends BaseServiceImpl<RepositoryBranc
@Transactional @Transactional
public Integer syncBranches(Long externalSystemId) { public Integer syncBranches(Long externalSystemId) {
// 1. 创建同步历史记录 // 1. 创建同步历史记录
RepositorySyncHistoryDTO syncHistory = repositorySyncHistoryService.createSyncHistory( RepositorySyncHistoryDTO syncHistory = repositorySyncHistoryService.createSyncHistory(externalSystemId, RepositorySyncType.BRANCH);
externalSystemId, RepositorySyncType.BRANCH);
try { try {
// 2. 获取外部系统信息 // 2. 获取外部系统信息
ExternalSystem externalSystem = externalSystemRepository.findById(externalSystemId) ExternalSystem externalSystem = externalSystemRepository.findById(externalSystemId).orElseThrow(() -> new BusinessException(ResponseCode.EXTERNAL_SYSTEM_NOT_FOUND));
.orElseThrow(() -> new BusinessException(ResponseCode.EXTERNAL_SYSTEM_NOT_FOUND));
// 3. 获取所有项目 // 3. 获取所有项目
List<RepositoryProject> projects = repositoryProjectRepository.findByExternalSystemIdAndDeletedFalse(externalSystemId); List<RepositoryProject> projects = repositoryProjectRepository.findByExternalSystemIdAndDeletedFalse(externalSystemId);

View File

@ -24,6 +24,8 @@ import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.function.Function; import java.util.function.Function;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import java.util.HashSet;
import java.util.Set;
/** /**
* Git仓库项目服务实现 * Git仓库项目服务实现
@ -54,47 +56,99 @@ public class RepositoryProjectServiceImpl extends BaseServiceImpl<RepositoryProj
// 1. 获取外部系统信息 // 1. 获取外部系统信息
ExternalSystem externalSystem = externalSystemRepository.findById(externalSystemId) ExternalSystem externalSystem = externalSystemRepository.findById(externalSystemId)
.orElseThrow(() -> new BusinessException(ResponseCode.EXTERNAL_SYSTEM_NOT_FOUND)); .orElseThrow(() -> new BusinessException(ResponseCode.EXTERNAL_SYSTEM_NOT_FOUND));
log.info("Start syncing projects for external system: {} (ID: {})", externalSystem.getName(), externalSystemId);
// 2. 获取远程仓库项目信息 // 2. 获取所有组
List<GitProjectResponse> remoteProjects = gitServiceIntegration.projects(externalSystem); List<RepositoryGroup> groups = repositoryGroupRepository.findByExternalSystemIdAndDeletedFalse(externalSystemId);
if (remoteProjects.isEmpty()) { if (groups.isEmpty()) {
log.info("No projects found in remote git system: {}", externalSystem.getName()); log.info("No groups found for external system: {}", externalSystem.getName());
return 0; return 0;
} }
log.info("Found {} groups to sync", groups.size());
// 3. 获取本地已存在的项目 // 3. 获取现有项目映射包括已删除的
List<RepositoryProject> existingProjects = repositoryProjectRepository.findByExternalSystemIdAndDeletedFalse(externalSystemId); Map<Long, RepositoryProject> existingProjects = repositoryProjectRepository
Map<Long, RepositoryProject> existingProjectMap = existingProjects.stream() .findByExternalSystemId(externalSystemId)
.collect(Collectors.toMap(RepositoryProject::getProjectId, Function.identity())); .stream()
.collect(Collectors.toMap(
RepositoryProject::getProjectId,
Function.identity(),
(existing, replacement) -> {
log.warn("Duplicate project found with ID: {}, path: {}",
existing.getProjectId(), existing.getPathWithNamespace());
return existing;
}
));
// 4. 更新或创建项目 // 4. 用于跟踪已处理的项目ID
List<RepositoryProject> projectsToSave = remoteProjects.stream() Set<Long> processedProjectIds = new HashSet<>();
.map(remoteProject -> { int totalCount = 0;
RepositoryProject project = existingProjectMap.getOrDefault(remoteProject.getId(), new RepositoryProject());
project.setExternalSystemId(externalSystemId);
project.setProjectId(remoteProject.getId());
project.setName(remoteProject.getName());
project.setPath(remoteProject.getPath());
project.setDescription(remoteProject.getDescription());
project.setVisibility(remoteProject.getVisibility());
project.setIsDefaultBranch(remoteProject.getDefaultBranch());
project.setWebUrl(remoteProject.getWebUrl());
project.setSshUrl(remoteProject.getSshUrl());
project.setHttpUrl(remoteProject.getHttpUrl());
project.setLastActivityAt(remoteProject.getLastActivityAt());
return project;
})
.collect(Collectors.toList());
List<RepositoryProject> savedProjects = repositoryProjectRepository.saveAll(projectsToSave); // 5. 遍历每个组同步项目
for (RepositoryGroup group : groups) {
List<GitProjectResponse> projectResponses = gitServiceIntegration.projectsByGroup(externalSystem, group.getGroupId());
log.info("Processing group: {} (ID: {}), found {} projects",
group.getName(), group.getGroupId(), projectResponses.size());
log.info("Successfully synchronized {} projects for external system: {}", for (GitProjectResponse projectResponse : projectResponses) {
savedProjects.size(), externalSystem.getName()); if (processedProjectIds.contains(projectResponse.getId())) {
return savedProjects.size(); log.info("Project already processed: {} (ID: {}) in group: {}",
projectResponse.getPathWithNamespace(), projectResponse.getId(), group.getName());
continue;
}
processedProjectIds.add(projectResponse.getId());
RepositoryProject project = existingProjects.get(projectResponse.getId());
if (project == null) {
project = new RepositoryProject();
project.setExternalSystemId(externalSystemId);
project.setProjectId(projectResponse.getId());
log.info("Creating new project: {} (ID: {})",
projectResponse.getPathWithNamespace(), projectResponse.getId());
totalCount++;
} else {
log.debug("Updating existing project: {} (ID: {})",
projectResponse.getPathWithNamespace(), projectResponse.getId());
}
project.setDeleted(false);
project.setName(projectResponse.getName());
project.setPath(projectResponse.getPath());
project.setDescription(projectResponse.getDescription());
project.setVisibility(projectResponse.getVisibility());
project.setGroupId(group.getId());
project.setDefaultBranch(projectResponse.getDefaultBranch());
project.setWebUrl(projectResponse.getWebUrl());
project.setSshUrl(projectResponse.getSshUrlToRepo());
project.setHttpUrl(projectResponse.getHttpUrlToRepo());
project.setLastActivityAt(projectResponse.getLastActivityAt());
project.setNameWithNamespace(projectResponse.getNameWithNamespace());
project.setPathWithNamespace(projectResponse.getPathWithNamespace());
project.setCreatedAt(projectResponse.getCreatedAt());
repositoryProjectRepository.save(project);
existingProjects.remove(projectResponse.getId());
}
}
// 6. 删除不存在的项目
if (!existingProjects.isEmpty()) {
List<Long> toDeleteIds = existingProjects.values().stream()
.filter(p -> !p.getDeleted())
.map(RepositoryProject::getId)
.collect(Collectors.toList());
if (!toDeleteIds.isEmpty()) {
log.info("Deleting {} projects that no longer exist", toDeleteIds.size());
repositoryProjectRepository.deleteAllById(toDeleteIds);
}
}
log.info("Successfully synced projects. Added {} new projects, processed {} total projects",
totalCount, processedProjectIds.size());
return totalCount;
} catch (Exception e) { } catch (Exception e) {
log.error("Failed to sync repository projects for external system: {}", externalSystemId, e); log.error("Failed to sync repository projects for external system: {}", externalSystemId, e);
throw new BusinessException(ResponseCode.REPOSITORY_SYNC_FAILED); throw new BusinessException(ResponseCode.REPOSITORY_PROJECT_SYNC_FAILED);
} }
} }

View File

@ -305,32 +305,34 @@ CREATE TABLE deploy_repo_group
-- 代码仓库项目表 -- 代码仓库项目表
CREATE TABLE deploy_repo_project CREATE TABLE deploy_repo_project
( (
-- 基础字段 id BIGINT AUTO_INCREMENT PRIMARY KEY COMMENT '主键ID',
id BIGINT AUTO_INCREMENT PRIMARY KEY COMMENT '主键ID', create_by VARCHAR(255) NULL COMMENT '创建人',
create_by VARCHAR(100) NULL COMMENT '创建人', create_time DATETIME(6) NULL COMMENT '创建时间',
create_time DATETIME(6) NULL COMMENT '创建时间', deleted BIT NOT NULL DEFAULT 0 COMMENT '是否删除0未删除1已删除',
update_by VARCHAR(100) NULL COMMENT '更新人', update_by VARCHAR(255) NULL COMMENT '更新人',
update_time DATETIME(6) NULL COMMENT '更新时间', update_time DATETIME(6) NULL COMMENT '更新时间',
version INT NOT NULL DEFAULT 1 COMMENT '版本号', version INT NOT NULL DEFAULT 0 COMMENT '乐观锁版本号',
deleted BIT NOT NULL DEFAULT 0 COMMENT '是否删除0-未删除1-已删除',
-- 业务字段 name VARCHAR(255) NOT NULL COMMENT '项目名称',
name VARCHAR(100) NOT NULL COMMENT '项目名称', path VARCHAR(255) NOT NULL COMMENT '项目路径',
description VARCHAR(500) NULL COMMENT '项目描述', description TEXT NULL COMMENT '项目描述',
project_id BIGINT NOT NULL COMMENT '外部系统中的项目ID', visibility VARCHAR(50) NULL COMMENT '可见性',
group_id BIGINT NULL COMMENT '所属仓库组ID', group_id BIGINT NULL COMMENT '所属组ID',
path VARCHAR(200) NOT NULL COMMENT '项目路径', default_branch VARCHAR(100) NULL COMMENT '默认分支',
external_system_id BIGINT NOT NULL COMMENT '外部系统ID', web_url VARCHAR(500) NULL COMMENT 'Web URL',
is_default_branch VARCHAR(100) NULL COMMENT '默认分支', ssh_url VARCHAR(500) NULL COMMENT 'SSH URL',
is_enabled BIT NOT NULL DEFAULT 1 COMMENT '是否启用0-禁用1-启用', http_url VARCHAR(500) NULL COMMENT 'HTTP URL',
visibility ENUM('private', 'internal', 'public') NOT NULL DEFAULT 'private' COMMENT '可见性private-私有internal-内部public-公开', last_activity_at DATETIME(6) NULL COMMENT '最后活动时间',
http_url VARCHAR(255) NULL COMMENT 'HTTP克隆地址', name_with_namespace VARCHAR(500) NULL COMMENT '带命名空间的名称',
ssh_url VARCHAR(255) NULL COMMENT 'SSH克隆地址', path_with_namespace VARCHAR(500) NULL COMMENT '带命名空间的路径',
web_url VARCHAR(255) NULL COMMENT '网页URL', created_at DATETIME(6) NULL COMMENT '创建时间',
last_activity_at DATETIME(6) NULL COMMENT '最后活动时间', external_system_id BIGINT NOT NULL COMMENT '外部系统ID',
sort INT DEFAULT 0 COMMENT '排序号' project_id BIGINT NOT NULL COMMENT '项目ID',
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='代码仓库项目表'; CONSTRAINT UK_repo_project_external_system_id_project_id UNIQUE (external_system_id, project_id),
CONSTRAINT FK_repo_project_group FOREIGN KEY (group_id) REFERENCES deploy_repo_group (id),
CONSTRAINT FK_repo_project_external_system FOREIGN KEY (external_system_id) REFERENCES sys_external_system (id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='Git仓库项目表';
-- 代码仓库分支表 -- 代码仓库分支表
CREATE TABLE deploy_repo_branch CREATE TABLE deploy_repo_branch