1435 lines
46 KiB
Markdown
1435 lines
46 KiB
Markdown
# 团队管理功能开发指南
|
||
|
||
## 📋 目标
|
||
|
||
实现技术团队管理功能,支持:
|
||
- 产品线和团队的层级管理
|
||
- 团队成员管理(支持跨部门)
|
||
- 团队与应用的分支级别关联(一个团队负责多个应用的特定分支)
|
||
- 团队-应用-环境的差异化配置
|
||
|
||
## 🎯 核心设计
|
||
|
||
### 关系层级
|
||
```
|
||
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);
|
||
}
|
||
```
|
||
|