46 KiB
团队管理功能开发指南
📋 目标
实现技术团队管理功能,支持:
- 产品线和团队的层级管理
- 团队成员管理(支持跨部门)
- 团队与应用的分支级别关联(一个团队负责多个应用的特定分支)
- 团队-应用-环境的差异化配置
🎯 核心设计
关系层级
deploy_product_line (产品线)
↓ (1:N)
deploy_team (开发团队)
↓ (M:N with branch_pattern)
deploy_team_application (团队-应用关联,支持分支模式)
↓ (关联到)
deploy_application (应用)
↓ (M:N with team-specific config)
deploy_application_environment_config (应用-环境配置,支持团队差异化)
↓
deploy_environment (环境)
关键特性
- 产品线管理:
deploy_project_group重命名为deploy_product_line,更清晰表达业务含义 - 分支级别关联: 团队可以负责应用的特定分支(如
feature/*,release/*) - 差异化配置: 同一应用在同一环境,不同团队可以有不同的配置(如资源限制、环境变量)
🎯 开发步骤
Phase 1: 数据库设计和迁移
步骤 1.1: 创建数据库迁移脚本
文件: src/main/resources/db/changelog/changes/v1.0.2-team-management.sql
-- =====================================================================
-- 团队管理功能表结构 v2.0
-- 关键改动:
-- 1. deploy_project_group -> deploy_product_line(重命名)
-- 2. deploy_team_application 增加 branch_pattern(支持分支级别管理)
-- 3. 新增 deploy_application_environment_config(支持团队差异化配置)
-- =====================================================================
-- =====================================================================
-- 第一步:重命名产品线表
-- =====================================================================
-- 重命名表名
RENAME TABLE deploy_project_group TO deploy_product_line;
-- 重命名字段(保持语义一致)
ALTER TABLE deploy_product_line
CHANGE COLUMN project_group_code product_line_code VARCHAR(50) NOT NULL COMMENT '产品线编码',
CHANGE COLUMN project_group_name product_line_name VARCHAR(100) NOT NULL COMMENT '产品线名称',
CHANGE COLUMN project_group_desc product_line_desc VARCHAR(255) NULL COMMENT '产品线描述';
-- 更新注释
ALTER TABLE deploy_product_line COMMENT='产品线表(原项目组表)';
-- =====================================================================
-- 第二步:创建团队表
-- =====================================================================
-- 1. 技术团队表
CREATE TABLE deploy_team (
id BIGINT AUTO_INCREMENT PRIMARY KEY COMMENT '主键ID',
-- 基本信息
team_code VARCHAR(50) NOT NULL COMMENT '团队编码(如:USER_SERVICE_TEAM)',
team_name VARCHAR(100) NOT NULL COMMENT '团队名称(如:用户服务团队)',
team_desc VARCHAR(500) NULL COMMENT '团队描述',
-- 产品线关联
product_line_id BIGINT NOT NULL COMMENT '所属产品线ID',
-- 团队负责人
leader_id BIGINT NULL COMMENT '团队负责人ID',
enabled BIT NOT NULL DEFAULT 1 COMMENT '是否启用',
sort INT NOT NULL DEFAULT 0 COMMENT '排序号',
-- 基础字段
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 '是否删除',
UNIQUE INDEX uk_team_code (team_code),
INDEX idx_leader_id (leader_id),
INDEX idx_product_line_id (product_line_id),
CONSTRAINT fk_team_leader FOREIGN KEY (leader_id) REFERENCES sys_user (id),
CONSTRAINT fk_team_product_line FOREIGN KEY (product_line_id) REFERENCES deploy_product_line (id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci
COMMENT='技术团队表';
-- 2. 团队成员表
CREATE TABLE deploy_team_member (
id BIGINT AUTO_INCREMENT PRIMARY KEY COMMENT '主键ID',
team_id BIGINT NOT NULL COMMENT '团队ID',
user_id BIGINT NOT NULL COMMENT '用户ID',
join_time DATETIME(6) NULL COMMENT '加入时间',
-- 基础字段
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 '是否删除',
UNIQUE INDEX uk_team_user (team_id, user_id),
INDEX idx_user_id (user_id),
CONSTRAINT fk_team_member_team FOREIGN KEY (team_id) REFERENCES deploy_team (id),
CONSTRAINT fk_team_member_user FOREIGN KEY (user_id) REFERENCES sys_user (id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci
COMMENT='团队成员表';
-- 3. 团队应用关联表(支持分支模式)
CREATE TABLE deploy_team_application (
id BIGINT AUTO_INCREMENT PRIMARY KEY COMMENT '主键ID',
team_id BIGINT NOT NULL COMMENT '团队ID',
application_id BIGINT NOT NULL COMMENT '应用ID',
-- ✅ 新增:分支模式匹配
branch_pattern VARCHAR(255) NULL COMMENT '负责的分支模式(如:feature/*、release/*、master、develop)',
responsibility VARCHAR(50) NOT NULL DEFAULT 'PRIMARY' COMMENT '职责类型:PRIMARY-主要负责,SECONDARY-协作支持',
-- 基础字段
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 '是否删除',
UNIQUE INDEX uk_team_app_branch (team_id, application_id, branch_pattern),
INDEX idx_application_id (application_id),
CONSTRAINT fk_team_app_team FOREIGN KEY (team_id) REFERENCES deploy_team (id),
CONSTRAINT fk_team_app_application FOREIGN KEY (application_id) REFERENCES deploy_application (id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci
COMMENT='团队应用关联表(支持分支级别管理)';
-- 4. 应用环境配置表(支持团队差异化配置)
CREATE TABLE deploy_application_environment_config (
id BIGINT AUTO_INCREMENT PRIMARY KEY COMMENT '主键ID',
application_id BIGINT NOT NULL COMMENT '应用ID',
environment_id BIGINT NOT NULL COMMENT '环境ID',
team_id BIGINT NULL COMMENT '团队ID(为NULL时表示默认配置,非NULL时表示团队专属配置)',
-- 配置内容(JSON格式)
config JSON NULL COMMENT '环境配置(如:资源限制、环境变量、部署参数等)',
enabled BIT NOT NULL DEFAULT 1 COMMENT '是否启用',
-- 基础字段
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 '是否删除',
UNIQUE INDEX uk_app_env_team (application_id, environment_id, team_id),
INDEX idx_environment_id (environment_id),
INDEX idx_team_id (team_id),
CONSTRAINT fk_app_env_config_application FOREIGN KEY (application_id) REFERENCES deploy_application (id),
CONSTRAINT fk_app_env_config_environment FOREIGN KEY (environment_id) REFERENCES deploy_environment (id),
CONSTRAINT fk_app_env_config_team FOREIGN KEY (team_id) REFERENCES deploy_team (id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci
COMMENT='应用环境配置表(支持团队差异化配置)';
-- =====================================================================
-- 第三步:清理旧表
-- =====================================================================
-- 删除旧的产品线环境关联表
DROP TABLE IF EXISTS deploy_project_group_environment;
-- =====================================================================
-- 第四步:调整 deploy_application 表
-- =====================================================================
-- 更新产品线关联(如果存在旧的 project_group_id 字段)
ALTER TABLE deploy_application
DROP FOREIGN KEY IF EXISTS fk_application_project_group;
-- 删除冗余字段
ALTER TABLE deploy_application
DROP COLUMN IF EXISTS project_group_id,
DROP COLUMN IF EXISTS repo_group_id,
DROP COLUMN IF EXISTS external_system_id;
-- =====================================================================
-- 第五步:优化 deploy_log 表
-- =====================================================================
ALTER TABLE deploy_log
DROP COLUMN IF EXISTS form_variables,
DROP COLUMN IF EXISTS deploy_variables,
ADD COLUMN deploy_branch VARCHAR(200) NULL COMMENT '部署分支',
ADD COLUMN team_id BIGINT NULL COMMENT '部署团队ID',
ADD COLUMN deploy_status VARCHAR(50) NULL COMMENT '部署状态:SUCCESS,FAILED,IN_PROGRESS',
ADD COLUMN start_time DATETIME(6) NULL COMMENT '开始时间',
ADD COLUMN end_time DATETIME(6) NULL COMMENT '结束时间',
ADD COLUMN error_message TEXT NULL COMMENT '错误信息',
ADD INDEX idx_team_id (team_id);
步骤 1.2: 注册迁移脚本
文件: src/main/resources/db/changelog/db.changelog-master.yaml
- changeSet:
id: v1.0.2-team-management
author: qqchen
runOnChange: false
failOnError: true
comment: "团队管理功能表结构"
sqlFile:
path: db/changelog/changes/v1.0.2-team-management.sql
stripComments: false
splitStatements: true
endDelimiter: ";"
rollback:
- sql: "DROP TABLE IF EXISTS deploy_team_environment;"
- sql: "DROP TABLE IF EXISTS deploy_team_application;"
- sql: "DROP TABLE IF EXISTS deploy_team_member;"
- sql: "DROP TABLE IF EXISTS deploy_team;"
Phase 2: 枚举类创建
步骤 2.1: 创建团队类型枚举
文件: src/main/java/com/qqchen/deploy/backend/deploy/enums/TeamTypeEnum.java
package com.qqchen.deploy.backend.deploy.enums;
import lombok.Getter;
/**
* 团队类型枚举
*/
@Getter
public enum TeamTypeEnum {
BACKEND("后端团队"),
FRONTEND("前端团队"),
FULLSTACK("全栈团队"),
OPS("运维团队"),
QA("测试团队");
private final String description;
TeamTypeEnum(String description) {
this.description = description;
}
}
步骤 2.2: 创建团队角色枚举
文件: src/main/java/com/qqchen/deploy/backend/deploy/enums/TeamRoleEnum.java
package com.qqchen.deploy.backend.deploy.enums;
import lombok.Getter;
/**
* 团队角色枚举
*/
@Getter
public enum TeamRoleEnum {
LEADER("技术负责人"),
BACKEND_DEVELOPER("后端开发"),
FRONTEND_DEVELOPER("前端开发"),
TESTER("测试工程师"),
OPS("运维工程师"),
ARCHITECT("架构师");
private final String description;
TeamRoleEnum(String description) {
this.description = description;
}
}
步骤 2.3: 创建团队职责枚举
文件: src/main/java/com/qqchen/deploy/backend/deploy/enums/TeamResponsibilityEnum.java
package com.qqchen.deploy.backend.deploy.enums;
import lombok.Getter;
/**
* 团队应用职责类型枚举
*/
@Getter
public enum TeamResponsibilityEnum {
PRIMARY("主要负责"),
SECONDARY("协作支持");
private final String description;
TeamResponsibilityEnum(String description) {
this.description = description;
}
}
Phase 3: 实体类创建
步骤 3.1: 创建团队实体
文件: src/main/java/com/qqchen/deploy/backend/deploy/entity/Team.java
package com.qqchen.deploy.backend.deploy.entity;
import com.qqchen.deploy.backend.deploy.enums.TeamTypeEnum;
import com.qqchen.deploy.backend.framework.domain.Entity;
import jakarta.persistence.*;
import lombok.Data;
import lombok.EqualsAndHashCode;
/**
* 技术团队实体
*/
@Data
@EqualsAndHashCode(callSuper = true)
@jakarta.persistence.Entity
@Table(name = "deploy_team")
public class Team extends Entity<Long> {
@Column(name = "team_code", nullable = false, length = 50)
private String teamCode;
@Column(name = "team_name", nullable = false, length = 100)
private String teamName;
@Column(name = "team_desc", length = 500)
private String teamDesc;
@Column(name = "product_line_id", nullable = false)
private Long productLineId;
@Column(name = "leader_id")
private Long leaderId;
@Column(name = "enabled", nullable = false)
private Boolean enabled = true;
@Column(name = "sort", nullable = false)
private Integer sort = 0;
}
步骤 3.2: 创建团队成员实体
文件: src/main/java/com/qqchen/deploy/backend/deploy/entity/TeamMember.java
package com.qqchen.deploy.backend.deploy.entity;
import com.qqchen.deploy.backend.deploy.enums.TeamRoleEnum;
import com.qqchen.deploy.backend.framework.domain.Entity;
import jakarta.persistence.*;
import lombok.Data;
import lombok.EqualsAndHashCode;
import java.time.LocalDateTime;
/**
* 团队成员实体
*/
@Data
@EqualsAndHashCode(callSuper = true)
@jakarta.persistence.Entity
@Table(name = "deploy_team_member")
public class TeamMember extends Entity<Long> {
@Column(name = "team_id", nullable = false)
private Long teamId;
@Column(name = "user_id", nullable = false)
private Long userId;
@Enumerated(EnumType.STRING)
@Column(name = "role_in_team", nullable = false, length = 50)
private TeamRoleEnum roleInTeam;
@Column(name = "join_time")
private LocalDateTime joinTime;
}
步骤 3.3: 创建团队应用关联实体
文件: src/main/java/com/qqchen/deploy/backend/deploy/entity/TeamApplication.java
package com.qqchen.deploy.backend.deploy.entity;
import com.qqchen.deploy.backend.deploy.enums.TeamResponsibilityEnum;
import com.qqchen.deploy.backend.framework.domain.Entity;
import jakarta.persistence.*;
import lombok.Data;
import lombok.EqualsAndHashCode;
/**
* 团队应用关联实体(支持分支级别管理)
*/
@Data
@EqualsAndHashCode(callSuper = true)
@jakarta.persistence.Entity
@Table(name = "deploy_team_application")
public class TeamApplication extends Entity<Long> {
@Column(name = "team_id", nullable = false)
private Long teamId;
@Column(name = "application_id", nullable = false)
private Long applicationId;
@Column(name = "branch_pattern", length = 255)
private String branchPattern;
@Enumerated(EnumType.STRING)
@Column(name = "responsibility", nullable = false, length = 50)
private TeamResponsibilityEnum responsibility = TeamResponsibilityEnum.PRIMARY;
}
步骤 3.4: 创建应用环境配置实体
文件: src/main/java/com/qqchen/deploy/backend/deploy/entity/ApplicationEnvironmentConfig.java
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.*;
import lombok.Data;
import lombok.EqualsAndHashCode;
import org.hibernate.annotations.Type;
import java.util.Map;
/**
* 应用环境配置实体(支持团队差异化配置)
*/
@Data
@EqualsAndHashCode(callSuper = true)
@jakarta.persistence.Entity
@Table(name = "deploy_application_environment_config")
public class ApplicationEnvironmentConfig extends Entity<Long> {
@Column(name = "application_id", nullable = false)
private Long applicationId;
@Column(name = "environment_id", nullable = false)
private Long environmentId;
@Column(name = "team_id")
private Long teamId;
@Type(JsonType.class)
@Column(name = "config", columnDefinition = "json")
private Map<String, Object> config;
@Column(name = "enabled", nullable = false)
private Boolean enabled = true;
}
Phase 4: Repository 层
步骤 4.1: 创建团队 Repository
文件: src/main/java/com/qqchen/deploy/backend/deploy/repository/ITeamRepository.java
package com.qqchen.deploy.backend.deploy.repository;
import com.qqchen.deploy.backend.deploy.entity.Team;
import com.qqchen.deploy.backend.framework.repository.IBaseRepository;
import org.springframework.stereotype.Repository;
import java.util.Optional;
@Repository
public interface ITeamRepository extends IBaseRepository<Team, Long> {
/**
* 根据团队编码查询(忽略已删除)
*/
Optional<Team> findByTeamCodeAndDeletedFalse(String teamCode);
/**
* 检查团队编码是否存在
*/
boolean existsByTeamCodeAndDeletedFalse(String teamCode);
}
步骤 4.2: 创建团队成员 Repository
文件: src/main/java/com/qqchen/deploy/backend/deploy/repository/ITeamMemberRepository.java
package com.qqchen.deploy.backend.deploy.repository;
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
public interface ITeamMemberRepository extends IBaseRepository<TeamMember, Long> {
/**
* 根据团队ID查询成员列表
*/
List<TeamMember> findByTeamIdAndDeletedFalse(Long teamId);
/**
* 根据用户ID查询所属团队
*/
List<TeamMember> findByUserIdAndDeletedFalse(Long userId);
/**
* 检查用户是否在团队中
*/
boolean existsByTeamIdAndUserIdAndDeletedFalse(Long teamId, Long userId);
/**
* 查询用户在团队中的记录
*/
Optional<TeamMember> findByTeamIdAndUserIdAndDeletedFalse(Long teamId, Long userId);
}
步骤 4.3: 创建团队应用 Repository
文件: src/main/java/com/qqchen/deploy/backend/deploy/repository/ITeamApplicationRepository.java
package com.qqchen.deploy.backend.deploy.repository;
import com.qqchen.deploy.backend.deploy.entity.TeamApplication;
import com.qqchen.deploy.backend.framework.repository.IBaseRepository;
import org.springframework.stereotype.Repository;
import java.util.List;
@Repository
public interface ITeamApplicationRepository extends IBaseRepository<TeamApplication, Long> {
/**
* 根据团队ID查询关联的应用
*/
List<TeamApplication> findByTeamIdAndDeletedFalse(Long teamId);
/**
* 根据应用ID查询关联的团队
*/
List<TeamApplication> findByApplicationIdAndDeletedFalse(Long applicationId);
/**
* 检查团队和应用的关联是否存在
*/
boolean existsByTeamIdAndApplicationIdAndDeletedFalse(Long teamId, Long applicationId);
}
步骤 4.4: 创建应用环境配置 Repository
文件: src/main/java/com/qqchen/deploy/backend/deploy/repository/IApplicationEnvironmentConfigRepository.java
package com.qqchen.deploy.backend.deploy.repository;
import com.qqchen.deploy.backend.deploy.entity.ApplicationEnvironmentConfig;
import com.qqchen.deploy.backend.framework.repository.IBaseRepository;
import org.springframework.stereotype.Repository;
import java.util.List;
import java.util.Optional;
@Repository
public interface IApplicationEnvironmentConfigRepository extends IBaseRepository<ApplicationEnvironmentConfig, Long> {
/**
* 根据应用ID查询环境配置
*/
List<ApplicationEnvironmentConfig> findByApplicationIdAndDeletedFalse(Long applicationId);
/**
* 根据应用ID和环境ID查询默认配置(团队ID为NULL)
*/
Optional<ApplicationEnvironmentConfig> findByApplicationIdAndEnvironmentIdAndTeamIdIsNullAndDeletedFalse(
Long applicationId, Long environmentId);
/**
* 根据应用ID、环境ID和团队ID查询团队专属配置
*/
Optional<ApplicationEnvironmentConfig> findByApplicationIdAndEnvironmentIdAndTeamIdAndDeletedFalse(
Long applicationId, Long environmentId, Long teamId);
/**
* 检查配置是否存在
*/
boolean existsByApplicationIdAndEnvironmentIdAndTeamIdAndDeletedFalse(
Long applicationId, Long environmentId, Long teamId);
}
Phase 5: DTO 和 Query
步骤 5.1: 创建团队 DTO
文件: src/main/java/com/qqchen/deploy/backend/deploy/dto/TeamDTO.java
package com.qqchen.deploy.backend.deploy.dto;
import com.qqchen.deploy.backend.deploy.enums.TeamTypeEnum;
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.List;
@Data
@EqualsAndHashCode(callSuper = true)
@Schema(description = "团队信息")
public class TeamDTO extends BaseDTO {
@Schema(description = "团队编码", example = "USER_SERVICE_TEAM")
private String teamCode;
@Schema(description = "团队名称", example = "用户服务团队")
private String teamName;
@Schema(description = "团队描述")
private String teamDesc;
@Schema(description = "所属产品线ID")
private Long productLineId;
@Schema(description = "负责人ID")
private Long leaderId;
@Schema(description = "是否启用")
private Boolean enabled;
@Schema(description = "排序号")
private Integer sort;
// 扩展字段(用于展示)
@Schema(description = "产品线名称")
private String productLineName;
@Schema(description = "团队成员列表")
private List<TeamMemberDTO> members;
@Schema(description = "关联应用列表")
private List<Long> applicationIds;
}
步骤 5.2: 创建团队成员 DTO
文件: src/main/java/com/qqchen/deploy/backend/deploy/dto/TeamMemberDTO.java
package com.qqchen.deploy.backend.deploy.dto;
import com.qqchen.deploy.backend.deploy.enums.TeamRoleEnum;
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 = "团队成员信息")
public class TeamMemberDTO extends BaseDTO {
@Schema(description = "团队ID")
private Long teamId;
@Schema(description = "用户ID")
private Long userId;
@Schema(description = "团队角色")
private TeamRoleEnum roleInTeam;
@Schema(description = "加入时间")
private LocalDateTime joinTime;
// 扩展字段
@Schema(description = "用户名")
private String username;
@Schema(description = "真实姓名")
private String realName;
@Schema(description = "邮箱")
private String email;
}
步骤 5.3: 创建查询对象
文件: src/main/java/com/qqchen/deploy/backend/deploy/query/TeamQuery.java
package com.qqchen.deploy.backend.deploy.query;
import com.qqchen.deploy.backend.deploy.enums.TeamTypeEnum;
import com.qqchen.deploy.backend.framework.query.BaseQuery;
import com.qqchen.deploy.backend.framework.query.QueryField;
import com.qqchen.deploy.backend.framework.query.QueryType;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import lombok.EqualsAndHashCode;
@Data
@EqualsAndHashCode(callSuper = true)
@Schema(description = "团队查询条件")
public class TeamQuery extends BaseQuery {
@QueryField(field = "teamName", type = QueryType.LIKE)
@Schema(description = "团队名称(模糊查询)")
private String teamName;
@QueryField(field = "teamCode", type = QueryType.LIKE)
@Schema(description = "团队编码(模糊查询)")
private String teamCode;
@QueryField(field = "productLineId")
@Schema(description = "产品线ID")
private Long productLineId;
@QueryField(field = "leaderId")
@Schema(description = "负责人ID")
private Long leaderId;
@QueryField(field = "enabled")
@Schema(description = "是否启用")
private Boolean enabled;
}
Phase 6: Converter
步骤 6.1: 创建团队 Converter
文件: src/main/java/com/qqchen/deploy/backend/deploy/converter/TeamConverter.java
package com.qqchen.deploy.backend.deploy.converter;
import com.qqchen.deploy.backend.deploy.dto.TeamDTO;
import com.qqchen.deploy.backend.deploy.entity.Team;
import com.qqchen.deploy.backend.framework.converter.BaseConverter;
import org.mapstruct.Mapper;
@Mapper(config = BaseConverter.class)
public interface TeamConverter extends BaseConverter<Team, TeamDTO> {
}
步骤 6.2: 创建团队成员 Converter
文件: src/main/java/com/qqchen/deploy/backend/deploy/converter/TeamMemberConverter.java
package com.qqchen.deploy.backend.deploy.converter;
import com.qqchen.deploy.backend.deploy.dto.TeamMemberDTO;
import com.qqchen.deploy.backend.deploy.entity.TeamMember;
import com.qqchen.deploy.backend.framework.converter.BaseConverter;
import org.mapstruct.Mapper;
@Mapper(config = BaseConverter.class)
public interface TeamMemberConverter extends BaseConverter<TeamMember, TeamMemberDTO> {
}
Phase 7: Service 层
步骤 7.1: 创建团队 Service 接口
文件: src/main/java/com/qqchen/deploy/backend/deploy/service/ITeamService.java
package com.qqchen.deploy.backend.deploy.service;
import com.qqchen.deploy.backend.deploy.dto.TeamDTO;
import com.qqchen.deploy.backend.deploy.dto.TeamMemberDTO;
import com.qqchen.deploy.backend.deploy.entity.Team;
import com.qqchen.deploy.backend.deploy.query.TeamQuery;
import com.qqchen.deploy.backend.framework.service.IBaseService;
import java.util.List;
public interface ITeamService extends IBaseService<Team, TeamDTO, TeamQuery, Long> {
/**
* 添加团队成员
*/
void addMember(Long teamId, Long userId, String roleInTeam);
/**
* 移除团队成员
*/
void removeMember(Long teamId, Long userId);
/**
* 获取团队成员列表
*/
List<TeamMemberDTO> getTeamMembers(Long teamId);
/**
* 关联应用(支持分支模式)
* @param teamId 团队ID
* @param applicationId 应用ID
* @param branchPattern 分支模式(如:feature/*、release/*、master)
* @param responsibility 职责类型
*/
void bindApplication(Long teamId, Long applicationId, String branchPattern, String responsibility);
/**
* 解绑应用
*/
void unbindApplication(Long teamId, Long applicationId, String branchPattern);
/**
* 创建应用环境配置
* @param applicationId 应用ID
* @param environmentId 环境ID
* @param teamId 团队ID(可选,NULL表示默认配置)
* @param config 配置内容
*/
void createEnvironmentConfig(Long applicationId, Long environmentId, Long teamId, Map<String, Object> config);
/**
* 更新应用环境配置
*/
void updateEnvironmentConfig(Long configId, Map<String, Object> config);
/**
* 删除应用环境配置
*/
void deleteEnvironmentConfig(Long configId);
}
步骤 7.2: 创建团队 Service 实现
文件: src/main/java/com/qqchen/deploy/backend/deploy/service/impl/TeamServiceImpl.java
package com.qqchen.deploy.backend.deploy.service.impl;
import com.qqchen.deploy.backend.deploy.converter.TeamConverter;
import com.qqchen.deploy.backend.deploy.converter.TeamMemberConverter;
import com.qqchen.deploy.backend.deploy.dto.TeamDTO;
import com.qqchen.deploy.backend.deploy.dto.TeamMemberDTO;
import com.qqchen.deploy.backend.deploy.entity.*;
import com.qqchen.deploy.backend.deploy.enums.TeamResponsibilityEnum;
import com.qqchen.deploy.backend.deploy.enums.TeamRoleEnum;
import com.qqchen.deploy.backend.deploy.query.TeamQuery;
import com.qqchen.deploy.backend.deploy.repository.*;
import com.qqchen.deploy.backend.deploy.service.ITeamService;
import com.qqchen.deploy.backend.framework.enums.ResponseCode;
import com.qqchen.deploy.backend.framework.exception.BusinessException;
import com.qqchen.deploy.backend.framework.service.impl.BaseServiceImpl;
import com.qqchen.deploy.backend.system.repository.IUserRepository;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.time.LocalDateTime;
import java.util.List;
import java.util.stream.Collectors;
@Slf4j
@Service
public class TeamServiceImpl extends BaseServiceImpl<Team, TeamDTO, TeamQuery, Long>
implements ITeamService {
@Resource
private ITeamRepository teamRepository;
@Resource
private ITeamMemberRepository teamMemberRepository;
@Resource
private ITeamApplicationRepository teamApplicationRepository;
@Resource
private IApplicationEnvironmentConfigRepository applicationEnvironmentConfigRepository;
@Resource
private IUserRepository userRepository;
@Resource
private TeamConverter teamConverter;
@Resource
private TeamMemberConverter teamMemberConverter;
@Override
@Transactional
public TeamDTO create(TeamDTO dto) {
// 检查团队编码唯一性
if (teamRepository.existsByTeamCodeAndDeletedFalse(dto.getTeamCode())) {
throw new BusinessException(ResponseCode.SYSTEM_ERROR,
new Object[]{"团队编码已存在: " + dto.getTeamCode()});
}
return super.create(dto);
}
@Override
@Transactional
public void addMember(Long teamId, Long userId, String roleInTeam) {
// 检查团队是否存在
teamRepository.findById(teamId)
.orElseThrow(() -> new BusinessException(ResponseCode.SYSTEM_ERROR,
new Object[]{"团队不存在"}));
// 检查用户是否存在
userRepository.findById(userId)
.orElseThrow(() -> new BusinessException(ResponseCode.SYSTEM_ERROR,
new Object[]{"用户不存在"}));
// 检查是否已经是成员
if (teamMemberRepository.existsByTeamIdAndUserIdAndDeletedFalse(teamId, userId)) {
throw new BusinessException(ResponseCode.SYSTEM_ERROR,
new Object[]{"用户已经是团队成员"});
}
// 创建成员记录
TeamMember member = new TeamMember();
member.setTeamId(teamId);
member.setUserId(userId);
member.setRoleInTeam(TeamRoleEnum.valueOf(roleInTeam));
member.setJoinTime(LocalDateTime.now());
teamMemberRepository.save(member);
log.info("添加团队成员: teamId={}, userId={}, role={}", teamId, userId, roleInTeam);
}
@Override
@Transactional
public void removeMember(Long teamId, Long userId) {
TeamMember member = teamMemberRepository.findByTeamIdAndUserIdAndDeletedFalse(teamId, userId)
.orElseThrow(() -> new BusinessException(ResponseCode.SYSTEM_ERROR,
new Object[]{"用户不是团队成员"}));
member.setDeleted(true);
teamMemberRepository.save(member);
log.info("移除团队成员: teamId={}, userId={}", teamId, userId);
}
@Override
public List<TeamMemberDTO> getTeamMembers(Long teamId) {
List<TeamMember> members = teamMemberRepository.findByTeamIdAndDeletedFalse(teamId);
return members.stream()
.map(teamMemberConverter::toDto)
.collect(Collectors.toList());
}
@Override
@Transactional
public void bindApplication(Long teamId, Long applicationId, String branchPattern, String responsibility) {
// 检查团队-应用-分支的组合是否已存在
List<TeamApplication> existingBindings = teamApplicationRepository
.findByTeamIdAndDeletedFalse(teamId).stream()
.filter(ta -> ta.getApplicationId().equals(applicationId)
&& Objects.equals(ta.getBranchPattern(), branchPattern))
.collect(Collectors.toList());
if (!existingBindings.isEmpty()) {
throw new BusinessException(ResponseCode.SYSTEM_ERROR,
new Object[]{"团队已关联该应用的指定分支模式"});
}
TeamApplication teamApp = new TeamApplication();
teamApp.setTeamId(teamId);
teamApp.setApplicationId(applicationId);
teamApp.setBranchPattern(branchPattern);
teamApp.setResponsibility(TeamResponsibilityEnum.valueOf(responsibility));
teamApplicationRepository.save(teamApp);
log.info("团队关联应用: teamId={}, applicationId={}, branchPattern={}, responsibility={}",
teamId, applicationId, branchPattern, responsibility);
}
@Override
@Transactional
public void unbindApplication(Long teamId, Long applicationId, String branchPattern) {
List<TeamApplication> teamApps = teamApplicationRepository
.findByTeamIdAndDeletedFalse(teamId).stream()
.filter(ta -> ta.getApplicationId().equals(applicationId)
&& Objects.equals(ta.getBranchPattern(), branchPattern))
.collect(Collectors.toList());
teamApps.forEach(ta -> {
ta.setDeleted(true);
teamApplicationRepository.save(ta);
});
log.info("团队解绑应用: teamId={}, applicationId={}, branchPattern={}",
teamId, applicationId, branchPattern);
}
@Override
@Transactional
public void createEnvironmentConfig(Long applicationId, Long environmentId, Long teamId, Map<String, Object> config) {
// 检查配置是否已存在
if (applicationEnvironmentConfigRepository.existsByApplicationIdAndEnvironmentIdAndTeamIdAndDeletedFalse(
applicationId, environmentId, teamId)) {
throw new BusinessException(ResponseCode.SYSTEM_ERROR,
new Object[]{"该应用环境配置已存在"});
}
ApplicationEnvironmentConfig envConfig = new ApplicationEnvironmentConfig();
envConfig.setApplicationId(applicationId);
envConfig.setEnvironmentId(environmentId);
envConfig.setTeamId(teamId);
envConfig.setConfig(config);
envConfig.setEnabled(true);
applicationEnvironmentConfigRepository.save(envConfig);
log.info("创建环境配置: applicationId={}, environmentId={}, teamId={}",
applicationId, environmentId, teamId);
}
@Override
@Transactional
public void updateEnvironmentConfig(Long configId, Map<String, Object> config) {
ApplicationEnvironmentConfig envConfig = applicationEnvironmentConfigRepository.findById(configId)
.orElseThrow(() -> new BusinessException(ResponseCode.SYSTEM_ERROR,
new Object[]{"环境配置不存在"}));
envConfig.setConfig(config);
applicationEnvironmentConfigRepository.save(envConfig);
log.info("更新环境配置: configId={}", configId);
}
@Override
@Transactional
public void deleteEnvironmentConfig(Long configId) {
ApplicationEnvironmentConfig envConfig = applicationEnvironmentConfigRepository.findById(configId)
.orElseThrow(() -> new BusinessException(ResponseCode.SYSTEM_ERROR,
new Object[]{"环境配置不存在"}));
envConfig.setDeleted(true);
applicationEnvironmentConfigRepository.save(envConfig);
log.info("删除环境配置: configId={}", configId);
}
}
Phase 8: Controller 层
步骤 8.1: 创建团队管理 Controller
文件: src/main/java/com/qqchen/deploy/backend/deploy/api/TeamApiController.java
package com.qqchen.deploy.backend.deploy.api;
import com.qqchen.deploy.backend.deploy.dto.TeamDTO;
import com.qqchen.deploy.backend.deploy.dto.TeamMemberDTO;
import com.qqchen.deploy.backend.deploy.entity.Team;
import com.qqchen.deploy.backend.deploy.query.TeamQuery;
import com.qqchen.deploy.backend.deploy.service.ITeamService;
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.web.bind.annotation.*;
import java.util.List;
@Slf4j
@RestController
@RequestMapping("/api/v1/team")
@Tag(name = "团队管理", description = "技术团队管理相关接口")
public class TeamApiController extends BaseController<Team, TeamDTO, Long, TeamQuery> {
@Resource
private ITeamService teamService;
@Operation(summary = "添加团队成员")
@PostMapping("/{teamId}/member")
public Response<Void> addMember(
@Parameter(description = "团队ID") @PathVariable Long teamId,
@Parameter(description = "用户ID") @RequestParam Long userId,
@Parameter(description = "团队角色") @RequestParam String roleInTeam
) {
teamService.addMember(teamId, userId, roleInTeam);
return Response.success();
}
@Operation(summary = "移除团队成员")
@DeleteMapping("/{teamId}/member/{userId}")
public Response<Void> removeMember(
@Parameter(description = "团队ID") @PathVariable Long teamId,
@Parameter(description = "用户ID") @PathVariable Long userId
) {
teamService.removeMember(teamId, userId);
return Response.success();
}
@Operation(summary = "获取团队成员列表")
@GetMapping("/{teamId}/members")
public Response<List<TeamMemberDTO>> getMembers(
@Parameter(description = "团队ID") @PathVariable Long teamId
) {
return Response.success(teamService.getTeamMembers(teamId));
}
@Operation(summary = "关联应用(支持分支模式)")
@PostMapping("/{teamId}/application")
public Response<Void> bindApplication(
@Parameter(description = "团队ID") @PathVariable Long teamId,
@Parameter(description = "应用ID") @RequestParam Long applicationId,
@Parameter(description = "分支模式(如:feature/*、release/*、master)") @RequestParam(required = false) String branchPattern,
@Parameter(description = "职责类型") @RequestParam String responsibility
) {
teamService.bindApplication(teamId, applicationId, branchPattern, responsibility);
return Response.success();
}
@Operation(summary = "解绑应用")
@DeleteMapping("/{teamId}/application/{applicationId}")
public Response<Void> unbindApplication(
@Parameter(description = "团队ID") @PathVariable Long teamId,
@Parameter(description = "应用ID") @PathVariable Long applicationId,
@Parameter(description = "分支模式") @RequestParam(required = false) String branchPattern
) {
teamService.unbindApplication(teamId, applicationId, branchPattern);
return Response.success();
}
@Operation(summary = "创建应用环境配置")
@PostMapping("/environment-config")
public Response<Void> createEnvironmentConfig(
@Parameter(description = "应用ID") @RequestParam Long applicationId,
@Parameter(description = "环境ID") @RequestParam Long environmentId,
@Parameter(description = "团队ID(可选,NULL表示默认配置)") @RequestParam(required = false) Long teamId,
@Parameter(description = "配置内容") @RequestBody Map<String, Object> config
) {
teamService.createEnvironmentConfig(applicationId, environmentId, teamId, config);
return Response.success();
}
@Operation(summary = "更新应用环境配置")
@PutMapping("/environment-config/{configId}")
public Response<Void> updateEnvironmentConfig(
@Parameter(description = "配置ID") @PathVariable Long configId,
@Parameter(description = "配置内容") @RequestBody Map<String, Object> config
) {
teamService.updateEnvironmentConfig(configId, config);
return Response.success();
}
@Operation(summary = "删除应用环境配置")
@DeleteMapping("/environment-config/{configId}")
public Response<Void> deleteEnvironmentConfig(
@Parameter(description = "配置ID") @PathVariable Long configId
) {
teamService.deleteEnvironmentConfig(configId);
return Response.success();
}
}
Phase 9: 国际化消息
文件: src/main/resources/messages.properties
# 团队管理相关错误码
team.code.exists=团队编码{0}已存在
team.not.found=团队不存在
team.member.exists=用户已经是团队成员
team.member.not.found=用户不是团队成员
team.application.bound=团队已关联该应用
team.environment.granted=团队已拥有该环境访问权限
Phase 10: 测试
步骤 10.1: 单元测试
文件: src/test/java/com/qqchen/deploy/backend/deploy/service/TeamServiceTest.java
package com.qqchen.deploy.backend.deploy.service;
import com.qqchen.deploy.backend.deploy.dto.TeamDTO;
import com.qqchen.deploy.backend.deploy.enums.TeamTypeEnum;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.transaction.annotation.Transactional;
import static org.junit.jupiter.api.Assertions.*;
@SpringBootTest
@Transactional
class TeamServiceTest {
@Autowired
private ITeamService teamService;
@Test
void testCreateTeam() {
TeamDTO dto = new TeamDTO();
dto.setTeamCode("TEST_TEAM");
dto.setTeamName("测试团队");
dto.setTeamType(TeamTypeEnum.FULLSTACK);
dto.setBusinessDomain("测试域");
dto.setEnabled(true);
TeamDTO created = teamService.create(dto);
assertNotNull(created.getId());
assertEquals("TEST_TEAM", created.getTeamCode());
}
@Test
void testAddMember() {
// 准备数据
TeamDTO team = createTestTeam();
Long userId = 1L; // 假设存在的用户ID
// 添加成员
teamService.addMember(team.getId(), userId, "DEVELOPER");
// 验证
var members = teamService.getTeamMembers(team.getId());
assertEquals(1, members.size());
}
private TeamDTO createTestTeam() {
TeamDTO dto = new TeamDTO();
dto.setTeamCode("TEST_" + System.currentTimeMillis());
dto.setTeamName("测试团队");
dto.setTeamType(TeamTypeEnum.BACKEND);
dto.setEnabled(true);
return teamService.create(dto);
}
}
🚀 执行步骤
1. 运行数据库迁移
# 启动应用,Liquibase 会自动执行迁移脚本
mvn spring-boot:run
2. 编译代码
mvn clean compile
3. 运行测试
mvn test
4. 验证 API
访问 Swagger UI: http://localhost:8080/swagger-ui.html
✅ 检查清单
- 数据库迁移脚本已创建并注册
- 枚举类创建完成
- 实体类创建完成
- Repository 创建完成
- DTO 和 Query 创建完成
- Converter 创建完成
- Service 接口和实现创建完成
- Controller 创建完成
- 国际化消息配置完成
- 单元测试编写完成
- 代码编译通过
- API 测试通过
📌 注意事项
- 外键约束: 确保
sys_user和sys_department表存在 - 枚举值: 前端需要与后端枚举值保持一致
- 软删除: 所有删除操作使用软删除(
deleted=1) - 事务管理: 涉及多表操作的方法需要加
@Transactional - 日志记录: 关键操作需要记录详细日志
🎯 后续扩展
- 分支匹配算法优化: 支持更复杂的分支模式匹配(如正则表达式、优先级规则)
- 环境配置模板: 为不同环境类型提供配置模板,简化配置创建
- 配置继承机制: 支持团队配置继承默认配置,只需覆盖差异部分
- 部署权限控制: 基于团队-应用-分支-环境的细粒度权限管理
- 团队工作负载统计: 统计团队负责的应用数量、部署频率等指标
- 分支负责人自动通知: 当某分支有部署需求时,自动通知对应团队
- 产品线仪表盘: 展示产品线下所有团队的工作概况
📖 使用场景示例
场景1:多团队协作开发同一应用
背景: user-service 应用由多个团队协作开发
- 用户团队: 负责
feature/user-*分支 - 支付团队: 负责
feature/payment-*分支 - 核心团队: 负责
master和release/*分支
配置:
-- 用户团队关联
INSERT INTO deploy_team_application (team_id, application_id, branch_pattern, responsibility)
VALUES (1, 100, 'feature/user-*', 'PRIMARY');
-- 支付团队关联
INSERT INTO deploy_team_application (team_id, application_id, branch_pattern, responsibility)
VALUES (2, 100, 'feature/payment-*', 'PRIMARY');
-- 核心团队关联
INSERT INTO deploy_team_application (team_id, application_id, branch_pattern, responsibility)
VALUES (3, 100, 'master', 'PRIMARY');
INSERT INTO deploy_team_application (team_id, application_id, branch_pattern, responsibility)
VALUES (3, 100, 'release/*', 'PRIMARY');
场景2:团队在不同环境使用不同配置
背景: order-service 在测试环境和生产环境,不同团队有不同的资源配置需求
配置:
-- 默认测试环境配置(所有团队共享)
INSERT INTO deploy_application_environment_config (application_id, environment_id, team_id, config)
VALUES (200, 1, NULL, '{"replicas": 2, "memory": "512Mi"}');
-- 团队A在测试环境的专属配置(需要更多资源)
INSERT INTO deploy_application_environment_config (application_id, environment_id, team_id, config)
VALUES (200, 1, 10, '{"replicas": 3, "memory": "1Gi", "debug": true}');
-- 生产环境默认配置
INSERT INTO deploy_application_environment_config (application_id, environment_id, team_id, config)
VALUES (200, 2, NULL, '{"replicas": 5, "memory": "2Gi", "cpu": "1000m"}');
查询逻辑:
// 获取团队专属配置,如果不存在则使用默认配置
Optional<ApplicationEnvironmentConfig> teamConfig =
configRepository.findByApplicationIdAndEnvironmentIdAndTeamIdAndDeletedFalse(appId, envId, teamId);
if (teamConfig.isEmpty()) {
teamConfig = configRepository.findByApplicationIdAndEnvironmentIdAndTeamIdIsNullAndDeletedFalse(appId, envId);
}