增加GIT同步实体类跟service

This commit is contained in:
dengqichen 2024-12-03 10:22:44 +08:00
parent cec5b73b3b
commit 7df3b9b36b
20 changed files with 1096 additions and 84 deletions

291
backend/frontend.rules Normal file
View File

@ -0,0 +1,291 @@
# 前端接口对接文档
## 通用说明
### 1. 接口响应格式
所有接口统一返回以下格式:
```typescript
interface Response<T> {
success: boolean; // 请求是否成功
code: number; // 状态码200表示成功其他表示失败
message: string; // 提示信息
data?: T; // 响应数据,可选
}
```
### 2. 分页请求参数
```typescript
interface PageQuery {
pageNum: number; // 页码从1开始
pageSize: number; // 每页大小
sortField?: string; // 排序字段,可选
sortOrder?: 'asc' | 'desc'; // 排序方式,可选
}
```
### 3. 分页响应格式
```typescript
interface PageResponse<T> {
content: T[]; // 数据列表
totalElements: number;// 总记录数
totalPages: number; // 总页数
size: number; // 每页大小
number: number; // 当前页码从0开始
first: boolean; // 是否第一页
last: boolean; // 是否最后一页
empty: boolean; // 是否为空
}
```
## 通用接口
### 1. 基础CRUD接口
所有实体都支持以下基础操作接口:
#### 1.1 分页查询
```typescript
GET /api/v1/{module}/page
请求参数PageQuery & {
// 其他查询条件,根据具体模块定义
}
响应结果Response<PageResponse<T>>
```
#### 1.2 列表查询
```typescript
GET /api/v1/{module}/list
请求参数:{
// 查询条件,根据具体模块定义
}
响应结果Response<T[]>
```
#### 1.3 获取详情
```typescript
GET /api/v1/{module}/{id}
响应结果Response<T>
```
#### 1.4 创建
```typescript
POST /api/v1/{module}
请求参数:{
// 创建参数,根据具体模块定义
}
响应结果Response<T>
```
#### 1.5 更新
```typescript
PUT /api/v1/{module}/{id}
请求参数:{
// 更新参数,根据具体模块定义
}
响应结果Response<T>
```
#### 1.6 删除
```typescript
DELETE /api/v1/{module}/{id}
响应结果Response<void>
```
#### 1.7 批量删除
```typescript
DELETE /api/v1/{module}/batch
请求参数:{
ids: number[]; // ID列表
}
响应结果Response<void>
```
#### 1.8 导出数据
```typescript
GET /api/v1/{module}/export
请求参数:{
// 查询条件,根据具体模块定义
}
响应结果:二进制文件流
```
### 2. 树形结构接口
对于树形结构的数据(如部门、菜单等),还支持以下接口:
#### 2.1 获取树形数据
```typescript
GET /api/v1/{module}/tree
响应结果Response<TreeNode[]>
interface TreeNode {
id: number;
parentId: number | null;
children: TreeNode[];
// 其他字段根据具体模块定义
}
```
### 3. 状态管理接口
对于需要状态管理的数据(如租户、用户等),还支持以下接口:
#### 3.1 获取状态
```typescript
GET /api/v1/{module}/{id}/enabled
响应结果Response<boolean>
```
#### 3.2 更新状态
```typescript
PUT /api/v1/{module}/{id}/enabled
请求参数:
enabled=true // 是否启用
响应结果Response<void>
```
## 错误处理
### 1. 错误码说明
- 200成功
- 400请求参数错误
- 401未认证
- 403无权限
- 404资源不存在
- 500服务器内部错误
### 2. 错误响应示例
```json
{
"success": false,
"code": 400,
"message": "请求参数错误",
"data": {
"field": "name",
"message": "名称不能为空"
}
}
```
## 接口调用示例
### TypeScript 示例
```typescript
// 定义接口返回类型
interface User {
id: number;
username: string;
enabled: boolean;
}
// 分页查询
async function getUserList(query: PageQuery & { username?: string }) {
const response = await axios.get<Response<PageResponse<User>>>('/api/v1/user/page', {
params: query
});
return response.data;
}
// 创建用户
async function createUser(user: Omit<User, 'id'>) {
const response = await axios.post<Response<User>>('/api/v1/user', user);
return response.data;
}
// 更新状态
async function updateUserStatus(id: number, enabled: boolean) {
const response = await axios.put<Response<void>>(`/api/v1/user/${id}/enabled`, null, {
params: { enabled }
});
return response.data;
}
```
### Vue3 + TypeScript 示例
```typescript
// 在组件中使用
import { ref, onMounted } from 'vue';
export default defineComponent({
setup() {
const userList = ref<User[]>([]);
const total = ref(0);
const loading = ref(false);
const queryList = async (query: PageQuery) => {
try {
loading.value = true;
const response = await getUserList(query);
if (response.success) {
userList.value = response.data.content;
total.value = response.data.totalElements;
}
} finally {
loading.value = false;
}
};
onMounted(() => {
queryList({
pageNum: 1,
pageSize: 10
});
});
return {
userList,
total,
loading,
queryList
};
}
});
```
## 注意事项
1. 请求头要求
```typescript
{
'Content-Type': 'application/json',
'Authorization': 'Bearer ${token}' // JWT认证token
}
```
2. 日期时间格式
- 请求参数使用ISO 8601格式YYYY-MM-DDTHH:mm:ss.sssZ
- 响应数据统一返回ISO 8601格式
3. 文件上传
- 使用multipart/form-data格式
- 文件大小限制10MB
4. 接口版本
- 所有接口统一使用v1版本
- URL格式/api/v1/{module}/{resource}
5. 安全性
- 所有接口都需要JWT认证
- Token过期时间2小时
- 需要定期刷新Token
6. 错误处理
- 统一使用axios拦截器处理错误
- 401错误跳转到登录页
- 其他错误统一提示

View File

@ -0,0 +1,51 @@
package com.qqchen.deploy.backend.converter;
import com.qqchen.deploy.backend.entity.RepositoryGroup;
import com.qqchen.deploy.backend.entity.RepositoryProject;
import com.qqchen.deploy.backend.entity.RepositoryBranch;
import com.qqchen.deploy.backend.model.dto.RepositoryGroupDTO;
import com.qqchen.deploy.backend.model.dto.RepositoryProjectDTO;
import com.qqchen.deploy.backend.model.dto.RepositoryBranchDTO;
import com.qqchen.deploy.backend.framework.converter.BaseConverter;
import org.mapstruct.Mapper;
import org.mapstruct.Mapping;
/**
* 仓库相关的对象转换器
*
* @author QQchen
* @version 1.0
*/
@Mapper(config = BaseConverter.class)
public interface RepositoryConverter {
/**
* 仓库组实体转DTO
*/
RepositoryGroupDTO toDto(RepositoryGroup entity);
/**
* 仓库组DTO转实体
*/
RepositoryGroup toEntity(RepositoryGroupDTO dto);
/**
* 仓库项目实体转DTO
*/
RepositoryProjectDTO toDto(RepositoryProject entity);
/**
* 仓库项目DTO转实体
*/
RepositoryProject toEntity(RepositoryProjectDTO dto);
/**
* 仓库分支实体转DTO
*/
RepositoryBranchDTO toDto(RepositoryBranch entity);
/**
* 仓库分支DTO转实体
*/
RepositoryBranch toEntity(RepositoryBranchDTO dto);
}

View File

@ -16,8 +16,8 @@ import java.time.LocalDateTime;
@LogicDelete @LogicDelete
public class RepositoryBranch extends Entity<Long> { public class RepositoryBranch extends Entity<Long> {
@Column(name = "repository_id", nullable = false) @Column(name = "external_system_id")
private Long repositoryId; private Long externalSystemId;
@Column(name = "project_id", nullable = false) @Column(name = "project_id", nullable = false)
private Long projectId; private Long projectId;
@ -37,7 +37,8 @@ public class RepositoryBranch extends Entity<Long> {
@Column(name = "commit_date") @Column(name = "commit_date")
private LocalDateTime commitDate; private LocalDateTime commitDate;
private Boolean protected_ = false; @Column(name = "is_protected")
private Boolean isProtected = false;
@Column(name = "developers_can_push") @Column(name = "developers_can_push")
private Boolean developersCanPush = true; private Boolean developersCanPush = true;
@ -48,8 +49,8 @@ public class RepositoryBranch extends Entity<Long> {
@Column(name = "can_push") @Column(name = "can_push")
private Boolean canPush = true; private Boolean canPush = true;
@Column(name = "default_branch") @Column(name = "is_default_branch")
private Boolean defaultBranch = false; private Boolean isDefaultBranch = false;
@Column(name = "web_url") @Column(name = "web_url")
private String webUrl; private String webUrl;

View File

@ -1,44 +0,0 @@
package com.qqchen.deploy.backend.entity;
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;
@Data
@EqualsAndHashCode(callSuper = true)
@jakarta.persistence.Entity
@Table(name = "deploy_repository_config")
@LogicDelete
public class RepositoryConfig extends Entity<Long> {
@Column(nullable = false)
private String name;
@Column(nullable = false)
private String url;
@Column(name = "access_token", nullable = false)
private String accessToken;
@Column(name = "api_version")
private String apiVersion;
private Integer sort;
private String remark;
@Column(name = "last_sync_time")
private LocalDateTime lastSyncTime;
@Column(name = "last_sync_status")
@Enumerated(EnumType.STRING)
private RepositorySyncHistory.SyncStatus lastSyncStatus;
}

View File

@ -7,16 +7,21 @@ import jakarta.persistence.Table;
import lombok.Data; import lombok.Data;
import lombok.EqualsAndHashCode; import lombok.EqualsAndHashCode;
/**
* 仓库组实体
*
* @author QQchen
* @version 1.0
*/
@Data @Data
@EqualsAndHashCode(callSuper = true) @EqualsAndHashCode(callSuper = true)
@jakarta.persistence.Entity @jakarta.persistence.Entity
@Table(name = "deploy_repository_group") @Table(name = "deploy_repo_group")
@LogicDelete @LogicDelete
public class RepositoryGroup extends Entity<Long> { public class RepositoryGroup extends Entity<Long> {
@Column(name = "repository_id", nullable = false) @Column(name = "external_system_id")
private Long repositoryId; private Long externalSystemId;
@Column(name = "group_id", nullable = false) @Column(name = "group_id", nullable = false)
private Long groupId; private Long groupId;

View File

@ -16,8 +16,8 @@ import java.time.LocalDateTime;
@LogicDelete @LogicDelete
public class RepositoryProject extends Entity<Long> { public class RepositoryProject extends Entity<Long> {
@Column(name = "repository_id", nullable = false) @Column(name = "external_system_id")
private Long repositoryId; private Long externalSystemId;
@Column(name = "project_id", nullable = false) @Column(name = "project_id", nullable = false)
private Long projectId; private Long projectId;
@ -35,8 +35,8 @@ public class RepositoryProject extends Entity<Long> {
@Column(name = "group_id") @Column(name = "group_id")
private Long groupId; private Long groupId;
@Column(name = "default_branch") @Column(name = "is_default_branch")
private String defaultBranch; private String isDefaultBranch;
@Column(name = "web_url") @Column(name = "web_url")
private String webUrl; private String webUrl;

View File

@ -80,7 +80,20 @@ public enum ResponseCode {
/** /**
* Git系统Token必填 * Git系统Token必填
*/ */
EXTERNAL_SYSTEM_GIT_TOKEN_REQUIRED(2502, "Git系统必须提供Token"); EXTERNAL_SYSTEM_GIT_TOKEN_REQUIRED(2502, "Git系统必须提供Token"),
// 仓库相关错误码 (2600-2699)
REPOSITORY_GROUP_NOT_FOUND(2600, "repository.group.not.found"),
REPOSITORY_GROUP_NAME_EXISTS(2601, "repository.group.name.exists"),
REPOSITORY_GROUP_PATH_EXISTS(2602, "repository.group.path.exists"),
REPOSITORY_PROJECT_NOT_FOUND(2610, "repository.project.not.found"),
REPOSITORY_PROJECT_NAME_EXISTS(2611, "repository.project.name.exists"),
REPOSITORY_PROJECT_PATH_EXISTS(2612, "repository.project.path.exists"),
REPOSITORY_BRANCH_NOT_FOUND(2620, "repository.branch.not.found"),
REPOSITORY_BRANCH_NAME_EXISTS(2621, "repository.branch.name.exists"),
REPOSITORY_SYNC_IN_PROGRESS(2630, "repository.sync.in.progress"),
REPOSITORY_SYNC_FAILED(2631, "repository.sync.failed"),
REPOSITORY_SYNC_HISTORY_NOT_FOUND(2632, "repository.sync.history.not.found");
private final int code; private final int code;
private final String messageKey; // 国际化消息key private final String messageKey; // 国际化消息key

View File

@ -0,0 +1,64 @@
package com.qqchen.deploy.backend.model.dto;
import com.qqchen.deploy.backend.framework.dto.BaseDTO;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import lombok.Data;
import lombok.EqualsAndHashCode;
import java.time.LocalDateTime;
/**
* 仓库分支DTO
*
* @author QQchen
* @version 1.0
*/
@Data
@Schema(description = "仓库分支DTO")
@EqualsAndHashCode(callSuper = true)
public class RepositoryBranchDTO extends BaseDTO {
@Schema(description = "外部系统ID")
@NotNull(message = "外部系统ID不能为空")
private Long externalSystemId;
@Schema(description = "所属项目ID")
@NotNull(message = "项目ID不能为空")
private Long projectId;
@Schema(description = "分支名称")
@NotBlank(message = "分支名称不能为空")
private String name;
@Schema(description = "是否为默认分支")
private Boolean isDefaultBranch = false;
@Schema(description = "是否受保护")
private Boolean isProtected = false;
@Schema(description = "是否可推送")
private Boolean canPush = true;
@Schema(description = "开发者是否可推送")
private Boolean developersCanPush = true;
@Schema(description = "开发者是否可合并")
private Boolean developersCanMerge = true;
@Schema(description = "最新提交ID")
private String commitId;
@Schema(description = "最新提交信息")
private String commitMessage;
@Schema(description = "最新提交作者")
private String commitAuthor;
@Schema(description = "最新提交时间")
private LocalDateTime commitDate;
@Schema(description = "网页URL")
private String webUrl;
}

View File

@ -0,0 +1,57 @@
package com.qqchen.deploy.backend.model.dto;
import com.qqchen.deploy.backend.framework.dto.BaseDTO;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import lombok.Data;
import lombok.EqualsAndHashCode;
/**
* 仓库组DTO
*
* @author QQchen
* @version 1.0
*/
@Data
@Schema(description = "仓库组DTO")
@EqualsAndHashCode(callSuper = true)
public class RepositoryGroupDTO extends BaseDTO {
@Schema(description = "外部系统ID")
@NotNull(message = "外部系统ID不能为空")
private Long externalSystemId;
@Schema(description = "外部系统中的组ID")
@NotNull(message = "组ID不能为空")
private Long groupId;
@Schema(description = "仓库组名称")
@NotBlank(message = "仓库组名称不能为空")
private String name;
@Schema(description = "仓库组路径")
@NotBlank(message = "仓库组路径不能为空")
private String path;
@Schema(description = "仓库组描述")
private String description;
@Schema(description = "可见性private-私有internal-内部public-公开")
private String visibility;
@Schema(description = "父级仓库组ID")
private Long parentId;
@Schema(description = "网页URL")
private String webUrl;
@Schema(description = "头像URL")
private String avatarUrl;
@Schema(description = "是否启用")
private Boolean enabled = true;
@Schema(description = "排序号")
private Integer sort = 0;
}

View File

@ -0,0 +1,68 @@
package com.qqchen.deploy.backend.model.dto;
import com.qqchen.deploy.backend.framework.dto.BaseDTO;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import lombok.Data;
import lombok.EqualsAndHashCode;
import java.time.LocalDateTime;
/**
* 仓库项目DTO
*
* @author QQchen
* @version 1.0
*/
@Data
@Schema(description = "仓库项目DTO")
@EqualsAndHashCode(callSuper = true)
public class RepositoryProjectDTO extends BaseDTO {
@Schema(description = "外部系统ID")
@NotNull(message = "外部系统ID不能为空")
private Long externalSystemId;
@Schema(description = "外部系统中的项目ID")
@NotNull(message = "项目ID不能为空")
private Long projectId;
@Schema(description = "项目名称")
@NotBlank(message = "项目名称不能为空")
private String name;
@Schema(description = "项目路径")
@NotBlank(message = "项目路径不能为空")
private String path;
@Schema(description = "项目描述")
private String description;
@Schema(description = "可见性private-私有internal-内部public-公开")
private String visibility;
@Schema(description = "所属仓库组ID")
private Long groupId;
@Schema(description = "默认分支")
private String defaultBranch;
@Schema(description = "网页URL")
private String webUrl;
@Schema(description = "SSH克隆地址")
private String sshUrl;
@Schema(description = "HTTP克隆地址")
private String httpUrl;
@Schema(description = "最后活动时间")
private LocalDateTime lastActivityAt;
@Schema(description = "是否启用")
private Boolean enabled = true;
@Schema(description = "排序号")
private Integer sort = 0;
}

View File

@ -0,0 +1,54 @@
package com.qqchen.deploy.backend.model.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;
/**
* 仓库同步状态DTO
* 用于查询单个同步任务的详细状态
*
* @author QQchen
* @version 1.0
*/
@Data
@Schema(description = "仓库同步状态DTO")
@EqualsAndHashCode(callSuper = true)
public class RepositorySyncStatusDTO extends BaseDTO {
@Schema(description = "外部系统ID")
private Long externalSystemId;
@Schema(description = "外部系统名称")
private String externalSystemName;
@Schema(description = "同步开始时间")
private LocalDateTime startTime;
@Schema(description = "同步结束时间")
private LocalDateTime endTime;
@Schema(description = "同步状态PENDING-等待中RUNNING-运行中SUCCESS-成功FAILED-失败")
private String status;
@Schema(description = "错误信息")
private String errorMessage;
@Schema(description = "同步的组数量")
private Integer groupCount;
@Schema(description = "同步的项目数量")
private Integer projectCount;
@Schema(description = "同步的分支数量")
private Integer branchCount;
@Schema(description = "同步耗时(秒)")
private Long durationSeconds;
@Schema(description = "同步详细日志")
private String syncLog;
}

View File

@ -0,0 +1,51 @@
package com.qqchen.deploy.backend.model.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;
/**
* 仓库同步任务DTO
* 用于展示正在运行的同步任务信息
*
* @author QQchen
* @version 1.0
*/
@Data
@Schema(description = "仓库同步任务DTO")
@EqualsAndHashCode(callSuper = true)
public class RepositorySyncTaskDTO extends BaseDTO {
@Schema(description = "外部系统ID")
private Long externalSystemId;
@Schema(description = "外部系统名称")
private String externalSystemName;
@Schema(description = "同步开始时间")
private LocalDateTime startTime;
@Schema(description = "同步状态PENDING-等待中RUNNING-运行中SUCCESS-成功FAILED-失败")
private String status;
@Schema(description = "错误信息")
private String errorMessage;
@Schema(description = "是否正在运行")
private Boolean isRunning;
@Schema(description = "已同步的组数量")
private Integer syncedGroupCount;
@Schema(description = "已同步的项目数量")
private Integer syncedProjectCount;
@Schema(description = "已同步的分支数量")
private Integer syncedBranchCount;
@Schema(description = "预计剩余时间(秒)")
private Long estimatedRemainingSeconds;
}

View File

@ -7,11 +7,17 @@ import org.springframework.data.jpa.repository.Query;
import org.springframework.stereotype.Repository; import org.springframework.stereotype.Repository;
import org.springframework.transaction.annotation.Transactional; import org.springframework.transaction.annotation.Transactional;
import java.util.List;
import java.util.Optional;
@Repository @Repository
public interface IRepositoryBranchRepository extends IBaseRepository<RepositoryBranch, Long> { public interface IRepositoryBranchRepository extends IBaseRepository<RepositoryBranch, Long> {
@Modifying @Modifying
@Transactional @Transactional
@Query("DELETE FROM RepositoryBranch b WHERE b.repositoryId = :repositoryId") void deleteByExternalSystemId(Long externalSystemId);
void deleteByRepositoryId(Long repositoryId);
Optional<RepositoryBranch> findByExternalSystemIdAndProjectIdAndName(Long externalSystemId, Long projectId, String name);
List<RepositoryBranch> findByExternalSystemIdAndProjectIdAndDeletedFalse(Long externalSystemId, Long projectId);
} }

View File

@ -1,14 +0,0 @@
package com.qqchen.deploy.backend.repository;
import com.qqchen.deploy.backend.framework.repository.IBaseRepository;
import com.qqchen.deploy.backend.entity.RepositoryConfig;
import org.springframework.stereotype.Repository;
import java.util.List;
@Repository
public interface IRepositoryConfigRepository extends IBaseRepository<RepositoryConfig, Long> {
List<RepositoryConfig> findByDeletedFalseOrderBySort();
boolean existsByNameAndDeletedFalse(String name);
}

View File

@ -7,6 +7,7 @@ import org.springframework.data.jpa.repository.Query;
import org.springframework.stereotype.Repository; import org.springframework.stereotype.Repository;
import org.springframework.transaction.annotation.Transactional; import org.springframework.transaction.annotation.Transactional;
import java.util.List;
import java.util.Optional; import java.util.Optional;
@Repository @Repository
@ -14,8 +15,9 @@ public interface IRepositoryGroupRepository extends IBaseRepository<RepositoryGr
@Modifying @Modifying
@Transactional @Transactional
@Query("DELETE FROM RepositoryGroup g WHERE g.repositoryId = :repositoryId") void deleteByExternalSystemId(Long externalSystemId);
void deleteByRepositoryId(Long repositoryId);
Optional<RepositoryGroup> findByRepositoryIdAndGroupId(Long repositoryId, Long groupId); Optional<RepositoryGroup> findByExternalSystemIdAndGroupId(Long externalSystemId, Long groupId);
List<RepositoryGroup> findByExternalSystemIdAndDeletedFalseOrderBySortAsc(Long externalSystemId);
} }

View File

@ -7,6 +7,7 @@ import org.springframework.data.jpa.repository.Query;
import org.springframework.stereotype.Repository; import org.springframework.stereotype.Repository;
import org.springframework.transaction.annotation.Transactional; import org.springframework.transaction.annotation.Transactional;
import java.util.List;
import java.util.Optional; import java.util.Optional;
@Repository @Repository
@ -14,8 +15,9 @@ public interface IRepositoryProjectRepository extends IBaseRepository<Repository
@Modifying @Modifying
@Transactional @Transactional
@Query("DELETE FROM RepositoryProject p WHERE p.repositoryId = :repositoryId") void deleteByExternalSystemId(Long externalSystemId);
void deleteByRepositoryId(Long repositoryId);
Optional<RepositoryProject> findByRepositoryIdAndProjectId(Long repositoryId, Long projectId); Optional<RepositoryProject> findByExternalSystemIdAndProjectId(Long externalSystemId, Long projectId);
List<RepositoryProject> findByExternalSystemIdAndGroupIdAndDeletedFalseOrderBySortAsc(Long externalSystemId, Long groupId);
} }

View File

@ -0,0 +1,132 @@
package com.qqchen.deploy.backend.service;
import com.qqchen.deploy.backend.entity.RepositoryGroup;
import com.qqchen.deploy.backend.entity.RepositoryProject;
import com.qqchen.deploy.backend.entity.RepositoryBranch;
import com.qqchen.deploy.backend.model.dto.RepositoryGroupDTO;
import com.qqchen.deploy.backend.model.dto.RepositoryProjectDTO;
import com.qqchen.deploy.backend.model.dto.RepositoryBranchDTO;
import com.qqchen.deploy.backend.model.dto.RepositorySyncStatusDTO;
import com.qqchen.deploy.backend.model.dto.RepositorySyncTaskDTO;
import java.util.List;
/**
* 版本控制仓库服务接口
* 提供代码仓库的组织结构项目和分支的管理功能以及与外部版本控制系统的数据同步功能
*
* @author QQchen
* @version 1.0
*/
public interface IRepositoryVersionControlService {
/**
* 获取指定外部系统的仓库组列表
*
* @param externalSystemId 外部系统ID
* @return 仓库组列表按排序号升序排列
*/
List<RepositoryGroupDTO> listGroups(Long externalSystemId);
/**
* 获取指定的仓库组信息
*
* @param externalSystemId 外部系统ID
* @param groupId 仓库组ID
* @return 仓库组信息
* @throws com.qqchen.deploy.backend.framework.exception.BusinessException 当仓库组不存在时抛出异常
*/
RepositoryGroupDTO getGroup(Long externalSystemId, Long groupId);
/**
* 同步指定外部系统的仓库组数据
*
* @param externalSystemId 外部系统ID
*/
void syncGroups(Long externalSystemId);
/**
* 获取指定仓库组下的项目列表
*
* @param externalSystemId 外部系统ID
* @param groupId 仓库组ID
* @return 项目列表按排序号升序排列
*/
List<RepositoryProjectDTO> listProjects(Long externalSystemId, Long groupId);
/**
* 获取指定的项目信息
*
* @param externalSystemId 外部系统ID
* @param projectId 项目ID
* @return 项目信息
* @throws com.qqchen.deploy.backend.framework.exception.BusinessException 当项目不存在时抛出异常
*/
RepositoryProjectDTO getProject(Long externalSystemId, Long projectId);
/**
* 同步指定仓库组下的项目数据
*
* @param externalSystemId 外部系统ID
* @param groupId 仓库组ID
*/
void syncProjects(Long externalSystemId, Long groupId);
/**
* 获取指定项目的分支列表
*
* @param externalSystemId 外部系统ID
* @param projectId 项目ID
* @return 分支列表
*/
List<RepositoryBranchDTO> listBranches(Long externalSystemId, Long projectId);
/**
* 获取指定的分支信息
*
* @param externalSystemId 外部系统ID
* @param projectId 项目ID
* @param branchName 分支名称
* @return 分支信息
* @throws com.qqchen.deploy.backend.framework.exception.BusinessException 当分支不存在时抛出异常
*/
RepositoryBranchDTO getBranch(Long externalSystemId, Long projectId, String branchName);
/**
* 同步指定项目的分支数据
*
* @param externalSystemId 外部系统ID
* @param projectId 项目ID
*/
void syncBranches(Long externalSystemId, Long projectId);
/**
* 同步指定外部系统的所有数据同步组项目分支
*
* @param externalSystemId 外部系统ID
*/
void syncAll(Long externalSystemId);
/**
* 异步同步指定外部系统的所有数据
*
* @param externalSystemId 外部系统ID
* @return 同步任务ID
*/
Long asyncSyncAll(Long externalSystemId);
/**
* 获取同步任务状态
*
* @param historyId 同步历史ID
* @return 同步状态信息
*/
RepositorySyncStatusDTO getSyncStatus(Long historyId);
/**
* 获取正在运行的同步任务列表
*
* @return 运行中的同步任务列表
*/
List<RepositorySyncTaskDTO> getRunningSyncs();
}

View File

@ -0,0 +1,145 @@
package com.qqchen.deploy.backend.service.impl;
import com.qqchen.deploy.backend.entity.RepositoryGroup;
import com.qqchen.deploy.backend.entity.RepositoryProject;
import com.qqchen.deploy.backend.entity.RepositoryBranch;
import com.qqchen.deploy.backend.framework.annotation.ServiceType;
import com.qqchen.deploy.backend.framework.enums.ResponseCode;
import com.qqchen.deploy.backend.framework.exception.BusinessException;
import com.qqchen.deploy.backend.model.dto.RepositoryGroupDTO;
import com.qqchen.deploy.backend.model.dto.RepositoryProjectDTO;
import com.qqchen.deploy.backend.model.dto.RepositoryBranchDTO;
import com.qqchen.deploy.backend.model.dto.RepositorySyncStatusDTO;
import com.qqchen.deploy.backend.model.dto.RepositorySyncTaskDTO;
import com.qqchen.deploy.backend.repository.IRepositoryGroupRepository;
import com.qqchen.deploy.backend.repository.IRepositoryProjectRepository;
import com.qqchen.deploy.backend.repository.IRepositoryBranchRepository;
import com.qqchen.deploy.backend.service.IRepositoryVersionControlService;
import com.qqchen.deploy.backend.converter.RepositoryConverter;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.List;
import java.util.stream.Collectors;
/**
* 版本控制仓库服务实现类
* 用于管理代码仓库的组织结构项目和分支支持与外部版本控制系统如GitLab的数据同步
*
* @author QQchen
* @version 1.0
*/
@Slf4j
@Service
@ServiceType(ServiceType.Type.DATABASE)
@RequiredArgsConstructor
public class RepositoryVersionControlServiceImpl implements IRepositoryVersionControlService {
private final IRepositoryGroupRepository groupRepository;
private final IRepositoryProjectRepository projectRepository;
private final IRepositoryBranchRepository branchRepository;
private final RepositoryConverter repositoryConverter;
@Override
public List<RepositoryGroupDTO> listGroups(Long externalSystemId) {
log.debug("获取仓库组列表, externalSystemId: {}", externalSystemId);
return groupRepository.findByExternalSystemIdAndDeletedFalseOrderBySortAsc(externalSystemId)
.stream()
.map(repositoryConverter::toDto)
.collect(Collectors.toList());
}
@Override
public RepositoryGroupDTO getGroup(Long externalSystemId, Long groupId) {
log.debug("获取仓库组信息, externalSystemId: {}, groupId: {}", externalSystemId, groupId);
RepositoryGroup group = groupRepository.findByExternalSystemIdAndGroupId(externalSystemId, groupId)
.orElseThrow(() -> new BusinessException(ResponseCode.REPOSITORY_GROUP_NOT_FOUND));
return repositoryConverter.toDto(group);
}
@Override
@Transactional(rollbackFor = Exception.class)
public void syncGroups(Long externalSystemId) {
log.info("开始同步仓库组数据, externalSystemId: {}", externalSystemId);
// TODO: 实现仓库组同步逻辑
}
@Override
public List<RepositoryProjectDTO> listProjects(Long externalSystemId, Long groupId) {
log.debug("获取项目列表, externalSystemId: {}, groupId: {}", externalSystemId, groupId);
return projectRepository.findByExternalSystemIdAndGroupIdAndDeletedFalseOrderBySortAsc(externalSystemId, groupId)
.stream()
.map(repositoryConverter::toDto)
.collect(Collectors.toList());
}
@Override
public RepositoryProjectDTO getProject(Long externalSystemId, Long projectId) {
log.debug("获取项目信息, externalSystemId: {}, projectId: {}", externalSystemId, projectId);
RepositoryProject project = projectRepository.findByExternalSystemIdAndProjectId(externalSystemId, projectId)
.orElseThrow(() -> new BusinessException(ResponseCode.REPOSITORY_PROJECT_NOT_FOUND));
return repositoryConverter.toDto(project);
}
@Override
@Transactional(rollbackFor = Exception.class)
public void syncProjects(Long externalSystemId, Long groupId) {
log.info("开始同步项目数据, externalSystemId: {}, groupId: {}", externalSystemId, groupId);
// TODO: 实现项目同步逻辑
}
@Override
public List<RepositoryBranchDTO> listBranches(Long externalSystemId, Long projectId) {
log.debug("获取分支列表, externalSystemId: {}, projectId: {}", externalSystemId, projectId);
return branchRepository.findByExternalSystemIdAndProjectIdAndDeletedFalse(externalSystemId, projectId)
.stream()
.map(repositoryConverter::toDto)
.collect(Collectors.toList());
}
@Override
public RepositoryBranchDTO getBranch(Long externalSystemId, Long projectId, String branchName) {
log.debug("获取分支信息, externalSystemId: {}, projectId: {}, branchName: {}",
externalSystemId, projectId, branchName);
RepositoryBranch branch = branchRepository.findByExternalSystemIdAndProjectIdAndName(externalSystemId, projectId, branchName)
.orElseThrow(() -> new BusinessException(ResponseCode.REPOSITORY_BRANCH_NOT_FOUND));
return repositoryConverter.toDto(branch);
}
@Override
@Transactional(rollbackFor = Exception.class)
public void syncBranches(Long externalSystemId, Long projectId) {
log.info("开始同步分支数据, externalSystemId: {}, projectId: {}", externalSystemId, projectId);
// TODO: 实现分支同步逻辑
}
@Override
@Transactional(rollbackFor = Exception.class)
public void syncAll(Long externalSystemId) {
log.info("开始全量同步数据, externalSystemId: {}", externalSystemId);
// TODO: 实现全量同步逻辑
}
@Override
public Long asyncSyncAll(Long externalSystemId) {
log.info("开始异步全量同步数据, externalSystemId: {}", externalSystemId);
// TODO: 实现异步全量同步逻辑
return null;
}
@Override
public RepositorySyncStatusDTO getSyncStatus(Long historyId) {
log.debug("获取同步状态, historyId: {}", historyId);
// TODO: 实现获取同步状态逻辑
return null;
}
@Override
public List<RepositorySyncTaskDTO> getRunningSyncs() {
log.debug("获取运行中的同步任务列表");
// TODO: 实现获取运行中的同步任务列表逻辑
return null;
}
}

View File

@ -145,7 +145,7 @@ CREATE TABLE sys_role_tag (
version INT NOT NULL DEFAULT 0 COMMENT '乐观锁版本号', version INT NOT NULL DEFAULT 0 COMMENT '乐观锁版本号',
name VARCHAR(50) NOT NULL COMMENT '标签名称', name VARCHAR(50) NOT NULL COMMENT '标签名称',
color VARCHAR(20) NULL COMMENT '标签<EFBFBD><EFBFBD>色(十六进制颜色码)' color VARCHAR(20) NULL COMMENT '标签<EFBFBD><EFBFBD><EFBFBD>十六进制颜色码)'
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='角色标签表'; ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='角色标签表';
-- 角色标签关联表 -- 角色标签关联表
@ -262,3 +262,118 @@ CREATE TABLE sys_external_system (
CONSTRAINT UK_external_system_type_url UNIQUE (type, url) CONSTRAINT UK_external_system_type_url UNIQUE (type, url)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='外部系统表'; ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='外部系统表';
-- 代码仓库组表
CREATE TABLE deploy_repo_group (
-- 基础字段
id BIGINT AUTO_INCREMENT PRIMARY KEY COMMENT '主键ID',
create_by VARCHAR(100) NULL COMMENT '创建人',
create_time DATETIME(6) NULL COMMENT '创建时间',
update_by VARCHAR(100) NULL COMMENT '更新人',
update_time DATETIME(6) NULL COMMENT '更新时间',
version INT NOT NULL DEFAULT 1 COMMENT '版本号',
deleted BIT NOT NULL DEFAULT 0 COMMENT '是否删除0-未删除1-已删除',
-- 业务字段
name VARCHAR(100) NOT NULL COMMENT '仓库组名称',
description VARCHAR(500) NULL COMMENT '仓库组描述',
group_id BIGINT NOT NULL COMMENT '外部系统中的组ID',
parent_id BIGINT NULL COMMENT '父级仓库组ID',
path VARCHAR(200) NOT NULL COMMENT '仓库组路径',
external_system_id BIGINT NOT NULL COMMENT '外部系统ID',
avatar_url VARCHAR(255) NULL COMMENT '头像URL',
web_url VARCHAR(255) NULL COMMENT '网页URL',
visibility ENUM('private', 'internal', 'public') NOT NULL DEFAULT 'private' COMMENT '可见性private-私有internal-内部public-公开',
sort INT DEFAULT 0 COMMENT '排序号',
-- 索引
INDEX idx_external_system_group_id (external_system_id, group_id) COMMENT '外部系统组ID索引',
INDEX idx_parent_id (parent_id) COMMENT '父级ID索引',
INDEX idx_path (path) COMMENT '路径索引',
UNIQUE INDEX uk_external_system_group_path (external_system_id, path) COMMENT '外部系统下路径唯一',
-- 外键约束
CONSTRAINT fk_group_parent FOREIGN KEY (parent_id)
REFERENCES deploy_repo_group (id),
CONSTRAINT fk_group_external_system FOREIGN KEY (external_system_id)
REFERENCES sys_external_system (id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='代码仓库组表';
-- 代码仓库项目表
CREATE TABLE deploy_repo_project (
-- 基础字段
id BIGINT AUTO_INCREMENT PRIMARY KEY COMMENT '主键ID',
create_by VARCHAR(100) NULL COMMENT '创建人',
create_time DATETIME(6) NULL COMMENT '创建时间',
update_by VARCHAR(100) NULL COMMENT '更新人',
update_time DATETIME(6) NULL COMMENT '更新时间',
version INT NOT NULL DEFAULT 1 COMMENT '版本号',
deleted BIT NOT NULL DEFAULT 0 COMMENT '是否删除0-未删除1-已删除',
-- 业务字段
name VARCHAR(100) NOT NULL COMMENT '项目名称',
description VARCHAR(500) NULL COMMENT '项目描述',
project_id BIGINT NOT NULL COMMENT '外部系统中的项目ID',
group_id BIGINT NULL COMMENT '所属仓库组ID',
path VARCHAR(200) NOT NULL COMMENT '项目路径',
external_system_id BIGINT NOT NULL COMMENT '外部系统ID',
is_default_branch VARCHAR(100) NULL COMMENT '默认分支',
is_enabled BIT NOT NULL DEFAULT 1 COMMENT '是否启用0-禁用1-启用',
visibility ENUM('private', 'internal', 'public') NOT NULL DEFAULT 'private' COMMENT '可见性private-私有internal-内部public-公开',
http_url VARCHAR(255) NULL COMMENT 'HTTP克隆地址',
ssh_url VARCHAR(255) NULL COMMENT 'SSH克隆地址',
web_url VARCHAR(255) NULL COMMENT '网页URL',
last_activity_at DATETIME(6) NULL COMMENT '最后活动时间',
sort INT DEFAULT 0 COMMENT '排序号',
-- 索引
INDEX idx_external_system_project_id (external_system_id, project_id) COMMENT '外部系统项目ID索引',
INDEX idx_group_id (group_id) COMMENT '组ID索引',
INDEX idx_path (path) COMMENT '路径索引',
UNIQUE INDEX uk_external_system_project_path (external_system_id, path) COMMENT '外部系统下路径唯一',
-- 外键约束
CONSTRAINT fk_project_group FOREIGN KEY (group_id)
REFERENCES deploy_repo_group (id),
CONSTRAINT fk_project_external_system FOREIGN KEY (external_system_id)
REFERENCES sys_external_system (id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='代码仓库项目表';
-- 代码仓库分支表
CREATE TABLE deploy_repo_branch (
-- 基础字段
id BIGINT AUTO_INCREMENT PRIMARY KEY COMMENT '主键ID',
create_by VARCHAR(100) NULL COMMENT '创建人',
create_time DATETIME(6) NULL COMMENT '创建时间',
update_by VARCHAR(100) NULL COMMENT '更新人',
update_time DATETIME(6) NULL COMMENT '更新时间',
version INT NOT NULL DEFAULT 1 COMMENT '版本号',
deleted BIT NOT NULL DEFAULT 0 COMMENT '是否删除0-未删除1-已删除',
-- 业务字段
name VARCHAR(100) NOT NULL COMMENT '分支名称',
project_id BIGINT NOT NULL COMMENT '所属项目ID',
external_system_id BIGINT NOT NULL COMMENT '外部系统ID',
is_default_branch BIT DEFAULT 0 COMMENT '是否为默认分支0-否1-是',
is_protected BIT DEFAULT 0 COMMENT '是否受保护0-否1-是',
can_push BIT DEFAULT 1 COMMENT '是否可推送0-否1-是',
developers_can_push BIT DEFAULT 1 COMMENT '开发者是否可推送0-否1-是',
developers_can_merge BIT DEFAULT 1 COMMENT '开发者是否可合并0-否1-是',
commit_id VARCHAR(64) NULL COMMENT '最新提交ID',
commit_message TEXT NULL COMMENT '最新提交信息',
commit_author VARCHAR(100) NULL COMMENT '最新提交作者',
commit_date DATETIME(6) NULL COMMENT '最新提交时间',
web_url VARCHAR(255) NULL COMMENT '网页URL',
-- 索引
INDEX idx_project_id (project_id) COMMENT '项目ID索引',
INDEX idx_external_system_id (external_system_id) COMMENT '外部系统ID索引',
INDEX idx_name (name) COMMENT '分支名称索引',
UNIQUE INDEX uk_project_branch_name (project_id, name) COMMENT '项目下分支名称唯一',
-- 外键约束
CONSTRAINT fk_branch_project FOREIGN KEY (project_id)
REFERENCES deploy_repo_project (id),
CONSTRAINT fk_branch_external_system FOREIGN KEY (external_system_id)
REFERENCES sys_external_system (id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='代码仓库分支表';

View File

@ -75,3 +75,16 @@ external.system.type.not.supported=不支持的系统类型
# Git系统相关错误 # Git系统相关错误
external.system.git.auth.type.error=Git系统只支持Token认证 external.system.git.auth.type.error=Git系统只支持Token认证
external.system.git.token.required=Git系统必须提供Token external.system.git.token.required=Git系统必须提供Token
# 仓库相关错误消息
repository.group.not.found=仓库组不存在
repository.group.name.exists=仓库组名称"{0}"已存在
repository.group.path.exists=仓库组路径"{0}"已存在
repository.project.not.found=仓库项目不存在
repository.project.name.exists=仓库项目名称"{0}"已存在
repository.project.path.exists=仓库项目路径"{0}"已存在
repository.branch.not.found=分支不存在
repository.branch.name.exists=分支名称"{0}"已存在
repository.sync.in.progress=仓库同步正在进行中
repository.sync.failed=仓库同步失败:{0}
repository.sync.history.not.found=同步历史记录不存在