diff --git a/backend/docs/团队管理功能开发指南.md b/backend/docs/团队管理功能开发指南.md new file mode 100644 index 00000000..e014d185 --- /dev/null +++ b/backend/docs/团队管理功能开发指南.md @@ -0,0 +1,1434 @@ +# 团队管理功能开发指南 + +## 📋 目标 + +实现技术团队管理功能,支持: +- 产品线和团队的层级管理 +- 团队成员管理(支持跨部门) +- 团队与应用的分支级别关联(一个团队负责多个应用的特定分支) +- 团队-应用-环境的差异化配置 + +## 🎯 核心设计 + +### 关系层级 +``` +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 (环境) +``` + +### 关键特性 +1. **产品线管理**: `deploy_project_group` 重命名为 `deploy_product_line`,更清晰表达业务含义 +2. **分支级别关联**: 团队可以负责应用的特定分支(如 `feature/*`, `release/*`) +3. **差异化配置**: 同一应用在同一环境,不同团队可以有不同的配置(如资源限制、环境变量) + +## 🎯 开发步骤 + +### Phase 1: 数据库设计和迁移 + +#### 步骤 1.1: 创建数据库迁移脚本 + +**文件**: `src/main/resources/db/changelog/changes/v1.0.2-team-management.sql` + +```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` + +```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` + +```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` + +```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` + +```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` + +```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 { + + @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` + +```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 { + + @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` + +```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 { + + @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` + +```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 { + + @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 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` + +```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 { + + /** + * 根据团队编码查询(忽略已删除) + */ + Optional findByTeamCodeAndDeletedFalse(String teamCode); + + /** + * 检查团队编码是否存在 + */ + boolean existsByTeamCodeAndDeletedFalse(String teamCode); +} +``` + +#### 步骤 4.2: 创建团队成员 Repository + +**文件**: `src/main/java/com/qqchen/deploy/backend/deploy/repository/ITeamMemberRepository.java` + +```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 { + + /** + * 根据团队ID查询成员列表 + */ + List findByTeamIdAndDeletedFalse(Long teamId); + + /** + * 根据用户ID查询所属团队 + */ + List findByUserIdAndDeletedFalse(Long userId); + + /** + * 检查用户是否在团队中 + */ + boolean existsByTeamIdAndUserIdAndDeletedFalse(Long teamId, Long userId); + + /** + * 查询用户在团队中的记录 + */ + Optional findByTeamIdAndUserIdAndDeletedFalse(Long teamId, Long userId); +} +``` + +#### 步骤 4.3: 创建团队应用 Repository + +**文件**: `src/main/java/com/qqchen/deploy/backend/deploy/repository/ITeamApplicationRepository.java` + +```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 { + + /** + * 根据团队ID查询关联的应用 + */ + List findByTeamIdAndDeletedFalse(Long teamId); + + /** + * 根据应用ID查询关联的团队 + */ + List findByApplicationIdAndDeletedFalse(Long applicationId); + + /** + * 检查团队和应用的关联是否存在 + */ + boolean existsByTeamIdAndApplicationIdAndDeletedFalse(Long teamId, Long applicationId); +} +``` + +#### 步骤 4.4: 创建应用环境配置 Repository + +**文件**: `src/main/java/com/qqchen/deploy/backend/deploy/repository/IApplicationEnvironmentConfigRepository.java` + +```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 { + + /** + * 根据应用ID查询环境配置 + */ + List findByApplicationIdAndDeletedFalse(Long applicationId); + + /** + * 根据应用ID和环境ID查询默认配置(团队ID为NULL) + */ + Optional findByApplicationIdAndEnvironmentIdAndTeamIdIsNullAndDeletedFalse( + Long applicationId, Long environmentId); + + /** + * 根据应用ID、环境ID和团队ID查询团队专属配置 + */ + Optional 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` + +```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 members; + + @Schema(description = "关联应用列表") + private List applicationIds; +} +``` + +#### 步骤 5.2: 创建团队成员 DTO + +**文件**: `src/main/java/com/qqchen/deploy/backend/deploy/dto/TeamMemberDTO.java` + +```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` + +```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` + +```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 { +} +``` + +#### 步骤 6.2: 创建团队成员 Converter + +**文件**: `src/main/java/com/qqchen/deploy/backend/deploy/converter/TeamMemberConverter.java` + +```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 { +} +``` + +--- + +### Phase 7: Service 层 + +#### 步骤 7.1: 创建团队 Service 接口 + +**文件**: `src/main/java/com/qqchen/deploy/backend/deploy/service/ITeamService.java` + +```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 { + + /** + * 添加团队成员 + */ + void addMember(Long teamId, Long userId, String roleInTeam); + + /** + * 移除团队成员 + */ + void removeMember(Long teamId, Long userId); + + /** + * 获取团队成员列表 + */ + List 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 config); + + /** + * 更新应用环境配置 + */ + void updateEnvironmentConfig(Long configId, Map config); + + /** + * 删除应用环境配置 + */ + void deleteEnvironmentConfig(Long configId); +} +``` + +#### 步骤 7.2: 创建团队 Service 实现 + +**文件**: `src/main/java/com/qqchen/deploy/backend/deploy/service/impl/TeamServiceImpl.java` + +```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 + 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 getTeamMembers(Long teamId) { + List 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 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 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 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 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` + +```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 { + + @Resource + private ITeamService teamService; + + @Operation(summary = "添加团队成员") + @PostMapping("/{teamId}/member") + public Response 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 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> getMembers( + @Parameter(description = "团队ID") @PathVariable Long teamId + ) { + return Response.success(teamService.getTeamMembers(teamId)); + } + + @Operation(summary = "关联应用(支持分支模式)") + @PostMapping("/{teamId}/application") + public Response 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 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 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 config + ) { + teamService.createEnvironmentConfig(applicationId, environmentId, teamId, config); + return Response.success(); + } + + @Operation(summary = "更新应用环境配置") + @PutMapping("/environment-config/{configId}") + public Response updateEnvironmentConfig( + @Parameter(description = "配置ID") @PathVariable Long configId, + @Parameter(description = "配置内容") @RequestBody Map config + ) { + teamService.updateEnvironmentConfig(configId, config); + return Response.success(); + } + + @Operation(summary = "删除应用环境配置") + @DeleteMapping("/environment-config/{configId}") + public Response deleteEnvironmentConfig( + @Parameter(description = "配置ID") @PathVariable Long configId + ) { + teamService.deleteEnvironmentConfig(configId); + return Response.success(); + } +} +``` + +--- + +### Phase 9: 国际化消息 + +**文件**: `src/main/resources/messages.properties` + +```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` + +```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. 运行数据库迁移 +```bash +# 启动应用,Liquibase 会自动执行迁移脚本 +mvn spring-boot:run +``` + +### 2. 编译代码 +```bash +mvn clean compile +``` + +### 3. 运行测试 +```bash +mvn test +``` + +### 4. 验证 API +访问 Swagger UI: `http://localhost:8080/swagger-ui.html` + +--- + +## ✅ 检查清单 + +- [ ] 数据库迁移脚本已创建并注册 +- [ ] 枚举类创建完成 +- [ ] 实体类创建完成 +- [ ] Repository 创建完成 +- [ ] DTO 和 Query 创建完成 +- [ ] Converter 创建完成 +- [ ] Service 接口和实现创建完成 +- [ ] Controller 创建完成 +- [ ] 国际化消息配置完成 +- [ ] 单元测试编写完成 +- [ ] 代码编译通过 +- [ ] API 测试通过 + +--- + +## 📌 注意事项 + +1. **外键约束**: 确保 `sys_user` 和 `sys_department` 表存在 +2. **枚举值**: 前端需要与后端枚举值保持一致 +3. **软删除**: 所有删除操作使用软删除(`deleted=1`) +4. **事务管理**: 涉及多表操作的方法需要加 `@Transactional` +5. **日志记录**: 关键操作需要记录详细日志 + +--- + +## 🎯 后续扩展 + +1. **分支匹配算法优化**: 支持更复杂的分支模式匹配(如正则表达式、优先级规则) +2. **环境配置模板**: 为不同环境类型提供配置模板,简化配置创建 +3. **配置继承机制**: 支持团队配置继承默认配置,只需覆盖差异部分 +4. **部署权限控制**: 基于团队-应用-分支-环境的细粒度权限管理 +5. **团队工作负载统计**: 统计团队负责的应用数量、部署频率等指标 +6. **分支负责人自动通知**: 当某分支有部署需求时,自动通知对应团队 +7. **产品线仪表盘**: 展示产品线下所有团队的工作概况 + +--- + +## 📖 使用场景示例 + +### 场景1:多团队协作开发同一应用 + +**背景**: `user-service` 应用由多个团队协作开发 +- **用户团队**: 负责 `feature/user-*` 分支 +- **支付团队**: 负责 `feature/payment-*` 分支 +- **核心团队**: 负责 `master` 和 `release/*` 分支 + +**配置**: +```sql +-- 用户团队关联 +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` 在测试环境和生产环境,不同团队有不同的资源配置需求 + +**配置**: +```sql +-- 默认测试环境配置(所有团队共享) +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"}'); +``` + +**查询逻辑**: +```java +// 获取团队专属配置,如果不存在则使用默认配置 +Optional teamConfig = + configRepository.findByApplicationIdAndEnvironmentIdAndTeamIdAndDeletedFalse(appId, envId, teamId); + +if (teamConfig.isEmpty()) { + teamConfig = configRepository.findByApplicationIdAndEnvironmentIdAndTeamIdIsNullAndDeletedFalse(appId, envId); +} +``` +