deploy-ease-platform/backend/docs/团队管理功能开发指南.md
2025-10-25 17:49:22 +08:00

1435 lines
46 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 团队管理功能开发指南
## 📋 目标
实现技术团队管理功能,支持:
- 产品线和团队的层级管理
- 团队成员管理(支持跨部门)
- 团队与应用的分支级别关联(一个团队负责多个应用的特定分支)
- 团队-应用-环境的差异化配置
## 🎯 核心设计
### 关系层级
```
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<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`
```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`
```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`
```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`
```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`
```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`
```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`
```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`
```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`
```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<Team, TeamDTO> {
}
```
#### 步骤 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<TeamMember, TeamMemberDTO> {
}
```
---
### 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<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`
```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`
```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`
```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<ApplicationEnvironmentConfig> teamConfig =
configRepository.findByApplicationIdAndEnvironmentIdAndTeamIdAndDeletedFalse(appId, envId, teamId);
if (teamConfig.isEmpty()) {
teamConfig = configRepository.findByApplicationIdAndEnvironmentIdAndTeamIdIsNullAndDeletedFalse(appId, envId);
}
```