团队app配置增加绑定工作流

This commit is contained in:
dengqichen 2025-11-02 13:47:09 +08:00
parent 287e820af8
commit 62dec088fd
12 changed files with 674 additions and 7 deletions

View File

@ -0,0 +1,40 @@
package com.qqchen.deploy.backend.deploy.api;
import com.qqchen.deploy.backend.deploy.dto.UserDeployableDTO;
import com.qqchen.deploy.backend.deploy.service.IDeployService;
import com.qqchen.deploy.backend.framework.api.Response;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* 部署管理API控制器
*
* @author qqchen
* @since 2025-11-02
*/
@Slf4j
@RestController
@RequestMapping("/api/v1/deploy")
@Tag(name = "部署管理", description = "部署相关接口")
public class DeployApiController {
@Resource
private IDeployService deployService;
/**
* 获取当前用户可部署的环境和应用
*/
@Operation(summary = "获取可部署的环境", description = "获取当前登录用户在各团队中可部署的环境和应用列表")
@GetMapping("/environments")
@PreAuthorize("isAuthenticated()")
public Response<UserDeployableDTO> getDeployableEnvironments() {
return Response.success(deployService.getDeployableEnvironments());
}
}

View File

@ -11,6 +11,9 @@ import jakarta.annotation.Resource;
import jakarta.servlet.http.HttpServletResponse;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.domain.Page;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@ -30,23 +33,23 @@ public class TeamMemberApiController extends BaseController<TeamMember, TeamMemb
private ITeamMemberService teamMemberService;
@Override
public Response<TeamMemberDTO> create(TeamMemberDTO dto) {
public Response<TeamMemberDTO> create(@Validated @RequestBody TeamMemberDTO dto) {
return super.create(dto);
}
@Override
public Response<TeamMemberDTO> update(Long aLong, TeamMemberDTO dto) {
return super.update(aLong, dto);
public Response<TeamMemberDTO> update(@PathVariable Long id, @Validated @RequestBody TeamMemberDTO dto) {
return super.update(id, dto);
}
@Override
public Response<Void> delete(Long aLong) {
return super.delete(aLong);
public Response<Void> delete(@PathVariable Long id) {
return super.delete(id);
}
@Override
public Response<TeamMemberDTO> findById(Long aLong) {
return super.findById(aLong);
public Response<TeamMemberDTO> findById(@PathVariable Long id) {
return super.findById(id);
}
@Override

View File

@ -0,0 +1,25 @@
package com.qqchen.deploy.backend.deploy.dto;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
/**
* 审批人DTO
*
* @author qqchen
* @since 2025-11-02
*/
@Data
@Schema(description = "审批人信息")
public class ApproverDTO {
@Schema(description = "用户ID")
private Long userId;
@Schema(description = "用户名")
private String username;
@Schema(description = "真实姓名")
private String realName;
}

View File

@ -0,0 +1,52 @@
package com.qqchen.deploy.backend.deploy.dto;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
/**
* 可部署应用DTO
*
* @author qqchen
* @since 2025-11-02
*/
@Data
@Schema(description = "可部署应用信息")
public class DeployableApplicationDTO {
@Schema(description = "团队应用关联ID")
private Long teamApplicationId;
@Schema(description = "应用ID")
private Long applicationId;
@Schema(description = "应用编码")
private String applicationCode;
@Schema(description = "应用名称")
private String applicationName;
@Schema(description = "应用描述")
private String applicationDesc;
@Schema(description = "分支名称")
private String branch;
@Schema(description = "部署系统IDJenkins系统")
private Long deploySystemId;
@Schema(description = "部署系统名称")
private String deploySystemName;
@Schema(description = "部署任务IDJenkins Job")
private String deployJob;
@Schema(description = "工作流定义ID")
private Long workflowDefinitionId;
@Schema(description = "工作流定义名称")
private String workflowDefinitionName;
@Schema(description = "工作流流程标识processKey")
private String workflowDefinitionKey;
}

View File

@ -0,0 +1,45 @@
package com.qqchen.deploy.backend.deploy.dto;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.util.List;
/**
* 可部署环境DTO
*
* @author qqchen
* @since 2025-11-02
*/
@Data
@Schema(description = "可部署环境信息")
public class DeployableEnvironmentDTO {
@Schema(description = "环境ID")
private Long environmentId;
@Schema(description = "环境编码")
private String environmentCode;
@Schema(description = "环境名称")
private String environmentName;
@Schema(description = "环境描述")
private String environmentDesc;
@Schema(description = "是否启用")
private Boolean enabled;
@Schema(description = "排序号")
private Integer sort;
@Schema(description = "是否需要审批")
private Boolean requiresApproval;
@Schema(description = "审批人列表")
private List<ApproverDTO> approvers;
@Schema(description = "可部署应用列表")
private List<DeployableApplicationDTO> applications;
}

View File

@ -0,0 +1,36 @@
package com.qqchen.deploy.backend.deploy.dto;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.util.List;
/**
* 团队可部署环境DTO
*
* @author qqchen
* @since 2025-11-02
*/
@Data
@Schema(description = "团队可部署环境信息")
public class TeamDeployableDTO {
@Schema(description = "团队ID")
private Long teamId;
@Schema(description = "团队编码")
private String teamCode;
@Schema(description = "团队名称")
private String teamName;
@Schema(description = "用户在团队中的角色")
private String teamRole;
@Schema(description = "团队描述")
private String description;
@Schema(description = "可部署环境列表")
private List<DeployableEnvironmentDTO> environments;
}

View File

@ -0,0 +1,30 @@
package com.qqchen.deploy.backend.deploy.dto;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.util.List;
/**
* 用户可部署环境DTO
*
* @author qqchen
* @since 2025-11-02
*/
@Data
@Schema(description = "用户可部署环境信息")
public class UserDeployableDTO {
@Schema(description = "用户ID")
private Long userId;
@Schema(description = "用户名")
private String username;
@Schema(description = "真实姓名")
private String realName;
@Schema(description = "用户所属团队列表")
private List<TeamDeployableDTO> teams;
}

View File

@ -81,5 +81,13 @@ public interface ITeamApplicationRepository extends IBaseRepository<TeamApplicat
"WHERE ta.applicationId IN :applicationIds " +
"GROUP BY ta.applicationId")
List<Object[]> countDistinctTeamIdByApplicationIds(@Param("applicationIds") Collection<Long> applicationIds);
/**
* 批量根据团队ID查询应用配置
*
* @param teamIds 团队ID集合
* @return 团队应用配置列表
*/
List<TeamApplication> findByTeamIdIn(Collection<Long> teamIds);
}

View File

@ -4,6 +4,8 @@ import com.qqchen.deploy.backend.deploy.entity.TeamConfig;
import com.qqchen.deploy.backend.framework.repository.IBaseRepository;
import org.springframework.stereotype.Repository;
import java.util.Collection;
import java.util.List;
import java.util.Optional;
/**
@ -30,5 +32,13 @@ public interface ITeamConfigRepository extends IBaseRepository<TeamConfig, Long>
* @return 是否存在
*/
boolean existsByTeamIdAndDeletedFalse(Long teamId);
/**
* 批量根据团队ID查询配置
*
* @param teamIds 团队ID集合
* @return 团队配置列表
*/
List<TeamConfig> findByTeamIdIn(Collection<Long> teamIds);
}

View File

@ -4,6 +4,7 @@ import com.qqchen.deploy.backend.deploy.entity.TeamMember;
import com.qqchen.deploy.backend.framework.repository.IBaseRepository;
import org.springframework.stereotype.Repository;
import java.util.List;
import java.util.Optional;
@Repository
@ -23,5 +24,10 @@ public interface ITeamMemberRepository extends IBaseRepository<TeamMember, Long>
* 根据团队ID和用户ID查询
*/
Optional<TeamMember> findByTeamIdAndUserIdAndDeletedFalse(Long teamId, Long userId);
/**
* 根据用户ID查询所有团队成员记录
*/
List<TeamMember> findByUserIdAndDeletedFalse(Long userId);
}

View File

@ -0,0 +1,20 @@
package com.qqchen.deploy.backend.deploy.service;
import com.qqchen.deploy.backend.deploy.dto.UserDeployableDTO;
/**
* 部署服务接口
*
* @author qqchen
* @since 2025-11-02
*/
public interface IDeployService {
/**
* 获取当前用户可部署的环境和应用
*
* @return 用户可部署环境信息
*/
UserDeployableDTO getDeployableEnvironments();
}

View File

@ -0,0 +1,392 @@
package com.qqchen.deploy.backend.deploy.service.impl;
import com.qqchen.deploy.backend.deploy.dto.*;
import com.qqchen.deploy.backend.deploy.entity.*;
import com.qqchen.deploy.backend.deploy.repository.*;
import com.qqchen.deploy.backend.deploy.service.IDeployService;
import com.qqchen.deploy.backend.framework.security.SecurityUtils;
import com.qqchen.deploy.backend.system.entity.User;
import com.qqchen.deploy.backend.system.repository.IUserRepository;
import com.qqchen.deploy.backend.workflow.entity.WorkflowDefinition;
import com.qqchen.deploy.backend.workflow.repository.IWorkflowDefinitionRepository;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.*;
import java.util.stream.Collectors;
import static java.util.stream.Collectors.*;
/**
* 部署服务实现
*
* @author qqchen
* @since 2025-11-02
*/
@Slf4j
@Service
public class DeployServiceImpl implements IDeployService {
@Resource
private IUserRepository userRepository;
@Resource
private ITeamMemberRepository teamMemberRepository;
@Resource
private ITeamRepository teamRepository;
@Resource
private ITeamConfigRepository teamConfigRepository;
@Resource
private IEnvironmentRepository environmentRepository;
@Resource
private ITeamApplicationRepository teamApplicationRepository;
@Resource
private IApplicationRepository applicationRepository;
@Resource
private IExternalSystemRepository externalSystemRepository;
@Resource
private IWorkflowDefinitionRepository workflowDefinitionRepository;
@Override
@Transactional(readOnly = true)
public UserDeployableDTO getDeployableEnvironments() {
// 1. 获取当前登录用户
Long currentUserId = SecurityUtils.getCurrentUserId();
User user = userRepository.findById(currentUserId)
.orElseThrow(() -> new RuntimeException("用户不存在"));
// 2. 查询用户所属的团队
List<TeamMember> teamMembers = teamMemberRepository.findByUserIdAndDeletedFalse(currentUserId);
if (teamMembers.isEmpty()) {
log.info("用户 {} 未加入任何团队", user.getUsername());
return buildEmptyResult(user);
}
List<Long> teamIds = teamMembers.stream()
.map(TeamMember::getTeamId)
.collect(toList());
// 3. 批量查询团队信息
Map<Long, Team> teamMap = teamRepository.findAllById(teamIds)
.stream().collect(toMap(Team::getId, t -> t));
// 4. 批量查询团队配置
Map<Long, TeamConfig> configMap = teamConfigRepository.findByTeamIdIn(teamIds)
.stream().collect(toMap(TeamConfig::getTeamId, c -> c));
// 5. 收集所有环境ID
Set<Long> allEnvIds = configMap.values().stream()
.filter(c -> c.getAllowedEnvironmentIds() != null)
.flatMap(c -> c.getAllowedEnvironmentIds().stream())
.collect(Collectors.toSet());
if (allEnvIds.isEmpty()) {
log.info("用户 {} 所属团队未配置任何环境", user.getUsername());
return buildEmptyResult(user);
}
// 6. 批量查询环境信息
Map<Long, Environment> envMap = environmentRepository.findAllById(allEnvIds)
.stream().collect(toMap(Environment::getId, e -> e));
// 7. 批量查询所有团队的应用配置
List<TeamApplication> allTeamApps = teamApplicationRepository.findByTeamIdIn(teamIds);
Map<Long, List<TeamApplication>> teamAppsMap = allTeamApps.stream()
.collect(groupingBy(TeamApplication::getTeamId));
// 8. 批量查询应用信息
Set<Long> appIds = allTeamApps.stream()
.map(TeamApplication::getApplicationId)
.collect(Collectors.toSet());
final Map<Long, Application> appMap;
if (!appIds.isEmpty()) {
appMap = applicationRepository.findAllById(appIds)
.stream().collect(toMap(Application::getId, a -> a));
} else {
appMap = Collections.emptyMap();
}
// 9. 批量查询部署系统
Set<Long> systemIds = allTeamApps.stream()
.map(TeamApplication::getDeploySystemId)
.filter(Objects::nonNull)
.collect(Collectors.toSet());
final Map<Long, ExternalSystem> systemMap;
if (!systemIds.isEmpty()) {
systemMap = externalSystemRepository.findAllById(systemIds)
.stream().collect(toMap(ExternalSystem::getId, s -> s));
} else {
systemMap = Collections.emptyMap();
}
// 10. 批量查询工作流定义
Set<Long> workflowIds = allTeamApps.stream()
.map(TeamApplication::getWorkflowDefinitionId)
.filter(Objects::nonNull)
.collect(Collectors.toSet());
final Map<Long, WorkflowDefinition> workflowMap;
if (!workflowIds.isEmpty()) {
workflowMap = workflowDefinitionRepository.findAllById(workflowIds)
.stream().collect(toMap(WorkflowDefinition::getId, w -> w));
} else {
workflowMap = Collections.emptyMap();
}
// 11. 批量查询审批人信息
Set<Long> approverUserIds = configMap.values().stream()
.filter(c -> c.getApproverUserIds() != null)
.flatMap(c -> c.getApproverUserIds().stream())
.filter(obj -> obj instanceof List)
.flatMap(obj -> ((List<?>) obj).stream())
.filter(id -> id instanceof Number)
.map(id -> ((Number) id).longValue())
.collect(Collectors.toSet());
final Map<Long, User> approverMap;
if (!approverUserIds.isEmpty()) {
approverMap = userRepository.findAllById(approverUserIds)
.stream().collect(toMap(User::getId, u -> u));
} else {
approverMap = Collections.emptyMap();
}
// 12. 组装团队数据
Map<Long, TeamMember> teamMemberMap = teamMembers.stream()
.collect(toMap(TeamMember::getTeamId, tm -> tm));
List<TeamDeployableDTO> teamDTOs = teamIds.stream()
.map(teamId -> buildTeamDTO(
teamId,
teamMap,
teamMemberMap,
configMap,
envMap,
teamAppsMap,
appMap,
systemMap,
workflowMap,
approverMap
))
.filter(Objects::nonNull)
.collect(toList());
// 13. 组装最终结果
UserDeployableDTO result = new UserDeployableDTO();
result.setUserId(user.getId());
result.setUsername(user.getUsername());
result.setRealName(user.getNickname());
result.setTeams(teamDTOs);
log.info("用户 {} 可部署环境数据查询完成,包含 {} 个团队", user.getUsername(), teamDTOs.size());
return result;
}
/**
* 构建空结果
*/
private UserDeployableDTO buildEmptyResult(User user) {
UserDeployableDTO result = new UserDeployableDTO();
result.setUserId(user.getId());
result.setUsername(user.getUsername());
result.setRealName(user.getNickname());
result.setTeams(Collections.emptyList());
return result;
}
/**
* 构建团队DTO
*/
private TeamDeployableDTO buildTeamDTO(
Long teamId,
Map<Long, Team> teamMap,
Map<Long, TeamMember> teamMemberMap,
Map<Long, TeamConfig> configMap,
Map<Long, Environment> envMap,
Map<Long, List<TeamApplication>> teamAppsMap,
Map<Long, Application> appMap,
Map<Long, ExternalSystem> systemMap,
Map<Long, WorkflowDefinition> workflowMap,
Map<Long, User> approverMap
) {
Team team = teamMap.get(teamId);
if (team == null) {
return null;
}
TeamMember member = teamMemberMap.get(teamId);
TeamConfig config = configMap.get(teamId);
TeamDeployableDTO teamDTO = new TeamDeployableDTO();
teamDTO.setTeamId(team.getId());
teamDTO.setTeamCode(team.getTeamCode());
teamDTO.setTeamName(team.getTeamName());
teamDTO.setDescription(team.getDescription());
teamDTO.setTeamRole(member != null ? member.getRoleInTeam() : null);
// 组装环境数据
if (config != null && config.getAllowedEnvironmentIds() != null && !config.getAllowedEnvironmentIds().isEmpty()) {
List<Long> allowedEnvIds = config.getAllowedEnvironmentIds();
List<TeamApplication> teamApps = teamAppsMap.getOrDefault(teamId, Collections.emptyList());
// 按环境分组应用
Map<Long, List<TeamApplication>> appsByEnv = teamApps.stream()
.collect(groupingBy(TeamApplication::getEnvironmentId));
List<DeployableEnvironmentDTO> envDTOs = new ArrayList<>();
for (int i = 0; i < allowedEnvIds.size(); i++) {
Long envId = allowedEnvIds.get(i);
Environment env = envMap.get(envId);
if (env == null) {
continue;
}
DeployableEnvironmentDTO envDTO = buildEnvironmentDTO(
env,
config,
i,
appsByEnv.getOrDefault(envId, Collections.emptyList()),
appMap,
systemMap,
workflowMap,
approverMap
);
envDTOs.add(envDTO);
}
envDTOs.sort(Comparator.comparing(DeployableEnvironmentDTO::getSort));
teamDTO.setEnvironments(envDTOs);
} else {
teamDTO.setEnvironments(Collections.emptyList());
}
return teamDTO;
}
/**
* 构建环境DTO
*/
private DeployableEnvironmentDTO buildEnvironmentDTO(
Environment env,
TeamConfig config,
int envIndex,
List<TeamApplication> teamApps,
Map<Long, Application> appMap,
Map<Long, ExternalSystem> systemMap,
Map<Long, WorkflowDefinition> workflowMap,
Map<Long, User> approverMap
) {
DeployableEnvironmentDTO envDTO = new DeployableEnvironmentDTO();
envDTO.setEnvironmentId(env.getId());
envDTO.setEnvironmentCode(env.getEnvCode());
envDTO.setEnvironmentName(env.getEnvName());
envDTO.setEnvironmentDesc(env.getEnvDesc());
envDTO.setEnabled(env.getEnabled());
envDTO.setSort(env.getSort());
// 审批配置
if (config.getEnvironmentApprovalRequired() != null && envIndex < config.getEnvironmentApprovalRequired().size()) {
envDTO.setRequiresApproval(config.getEnvironmentApprovalRequired().get(envIndex));
} else {
envDTO.setRequiresApproval(false);
}
// 审批人
if (config.getApproverUserIds() != null && envIndex < config.getApproverUserIds().size()) {
Object approverObj = config.getApproverUserIds().get(envIndex);
if (approverObj instanceof List) {
List<?> approverList = (List<?>) approverObj;
List<ApproverDTO> approverDTOs = approverList.stream()
.filter(id -> id instanceof Number)
.map(id -> buildApproverDTO(((Number) id).longValue(), approverMap))
.filter(Objects::nonNull)
.collect(toList());
envDTO.setApprovers(approverDTOs);
} else {
envDTO.setApprovers(Collections.emptyList());
}
} else {
envDTO.setApprovers(Collections.emptyList());
}
// 应用列表
List<DeployableApplicationDTO> appDTOs = teamApps.stream()
.map(ta -> buildApplicationDTO(ta, appMap, systemMap, workflowMap))
.filter(Objects::nonNull)
.collect(toList());
envDTO.setApplications(appDTOs);
return envDTO;
}
/**
* 构建审批人DTO
*/
private ApproverDTO buildApproverDTO(Long userId, Map<Long, User> approverMap) {
User user = approverMap.get(userId);
if (user == null) {
return null;
}
ApproverDTO dto = new ApproverDTO();
dto.setUserId(user.getId());
dto.setUsername(user.getUsername());
dto.setRealName(user.getNickname());
return dto;
}
/**
* 构建应用DTO
*/
private DeployableApplicationDTO buildApplicationDTO(
TeamApplication ta,
Map<Long, Application> appMap,
Map<Long, ExternalSystem> systemMap,
Map<Long, WorkflowDefinition> workflowMap
) {
Application app = appMap.get(ta.getApplicationId());
if (app == null) {
return null;
}
DeployableApplicationDTO dto = new DeployableApplicationDTO();
dto.setTeamApplicationId(ta.getId());
dto.setApplicationId(app.getId());
dto.setApplicationCode(app.getAppCode());
dto.setApplicationName(app.getAppName());
dto.setApplicationDesc(app.getAppDesc());
dto.setBranch(ta.getBranch());
// 部署系统
dto.setDeploySystemId(ta.getDeploySystemId());
if (ta.getDeploySystemId() != null) {
ExternalSystem system = systemMap.get(ta.getDeploySystemId());
dto.setDeploySystemName(system != null ? system.getName() : null);
}
// 部署任务
dto.setDeployJob(ta.getDeployJob());
// 工作流定义
dto.setWorkflowDefinitionId(ta.getWorkflowDefinitionId());
if (ta.getWorkflowDefinitionId() != null) {
WorkflowDefinition workflow = workflowMap.get(ta.getWorkflowDefinitionId());
if (workflow != null) {
dto.setWorkflowDefinitionName(workflow.getName());
dto.setWorkflowDefinitionKey(workflow.getKey());
}
}
return dto;
}
}