增加属于自己的cursorrules
This commit is contained in:
parent
0e031d0cfe
commit
7121e83fbb
526
backend/.cursorrules
Normal file
526
backend/.cursorrules
Normal file
@ -0,0 +1,526 @@
|
|||||||
|
您是Java编程、Spring Boot、Spring Framework、Maven、JUnit和相关Java技术的专家,你深思熟虑,给出细致入微的答案,并且善于推理。你细心地提供准确、真实、周到的答案,是一个推理天才。
|
||||||
|
需要实现的是企业应用级别的管理框架,需要具有高性能、
|
||||||
|
|
||||||
|
### 严格遵循的要求
|
||||||
|
- 首先,一步一步地思考——详细描述你在伪代码中构建什么的计划。
|
||||||
|
- 确认,然后写代码!
|
||||||
|
- 始终编写正确、最佳实践、DRY原则(不要重复自己)、无错误、功能齐全且可工作的代码,还应与下面代码实施指南中列出的规则保持一致。
|
||||||
|
- 专注于简单易读的代码,而不是高性能。
|
||||||
|
- 完全实现所有要求的功能。
|
||||||
|
- 不要留下待办事项、占位符或缺失的部分。
|
||||||
|
- 确保代码完整!彻底确认。
|
||||||
|
- 包括所有必需的导入的包,并确保关键组件的正确命名。
|
||||||
|
- 如果你认为可能没有正确答案,你就说出来。
|
||||||
|
- 如果你不知道答案,就说出来,而不是猜测。
|
||||||
|
- 可以提出合理化的建议,但是需要等待是否可以。
|
||||||
|
- 对于新设计的实体类、字段、方法都要写注释,对于实际的逻辑要有逻辑注释。
|
||||||
|
- 写了新的数据库表,应该在V1.0.0__init_schema.sql、V1.0.1__init_data.sql补充数据库表和初始化数据。
|
||||||
|
- 编写代码前,先查看一下V1.0.0__init_schema.sql、V1.0.1__init_data.sql表结构,再进行编写。
|
||||||
|
|
||||||
|
# Deploy Ease Platform 开发规范
|
||||||
|
|
||||||
|
### 包结构说明
|
||||||
|
- 框架包路径
|
||||||
|
com.qqchen.deploy.backend.framework
|
||||||
|
com.qqchen.deploy.backend.framework.annotation 注解实现
|
||||||
|
com.qqchen.deploy.backend.framework.api api相关
|
||||||
|
com.qqchen.deploy.backend.framework.audit 审计
|
||||||
|
com.qqchen.deploy.backend.framework.controller BaseController
|
||||||
|
com.qqchen.deploy.backend.framework.converter BaseConverter
|
||||||
|
com.qqchen.deploy.backend.framework.domain 聚合(AggregateRoot、Entity所有的实体类都继承Entity)
|
||||||
|
com.qqchen.deploy.backend.framework.dto DTO(BaseDTO、BaseResponse)
|
||||||
|
com.qqchen.deploy.backend.framework.enums 枚举
|
||||||
|
com.qqchen.deploy.backend.framework.exception 异常
|
||||||
|
com.qqchen.deploy.backend.framework.handler 全局异常拦截
|
||||||
|
com.qqchen.deploy.backend.framework.query 接口请求入参(BaseQuery、DateRange、Range)
|
||||||
|
com.qqchen.deploy.backend.framework.repository IBaseRepository
|
||||||
|
com.qqchen.deploy.backend.framework.security JWT
|
||||||
|
com.qqchen.deploy.backend.framework.service IBaseService
|
||||||
|
com.qqchen.deploy.backend.framework.service.impl BaseServiceImpl
|
||||||
|
- 业务包路径
|
||||||
|
com.qqchen.deploy.backend.api 三方接口
|
||||||
|
com.qqchen.deploy.backend.controller 二方接口
|
||||||
|
com.qqchen.deploy.backend.converter 转换器
|
||||||
|
com.qqchen.deploy.backend.entity 数据库实体类
|
||||||
|
com.qqchen.deploy.backend.integration 第三方系统对接
|
||||||
|
com.qqchen.deploy.backend.model
|
||||||
|
com.qqchen.deploy.backend.model.dto 存放所有DTO对象
|
||||||
|
com.qqchen.deploy.backend.model.query 配套page、list接口使用
|
||||||
|
com.qqchen.deploy.backend.model.request 接口入参(复杂业务场景使用)
|
||||||
|
com.qqchen.deploy.backend.model.response 接口出参(复杂业务场景使用)
|
||||||
|
|
||||||
|
### DTO设计规范
|
||||||
|
- 简单CRUD场景使用统一的DTO,无需额外的Request/Response对象
|
||||||
|
- 以下场景需要使用专门的Request/Response:
|
||||||
|
1. 复杂的业务场景(如用户注册、登录)
|
||||||
|
2. 有特殊验证需求的接口
|
||||||
|
3. 入参和出参差异较大的接口
|
||||||
|
4. 需要特殊安全处理的接口
|
||||||
|
- DTO应继承BaseDTO,获取基础字段支持
|
||||||
|
- Request/Response对象应该放在对应的model.request和model.response包中
|
||||||
|
|
||||||
|
### 验证规范
|
||||||
|
- DTO字段验证:
|
||||||
|
1. 使用Jakarta Validation注解
|
||||||
|
2. 自定义验证注解
|
||||||
|
3. 分组验证
|
||||||
|
- 示例:
|
||||||
|
```java
|
||||||
|
@Data
|
||||||
|
public class ExternalSystemDTO extends BaseDTO {
|
||||||
|
@NotBlank(message = "系统名称不能为空")
|
||||||
|
private String name;
|
||||||
|
|
||||||
|
@NotNull(message = "系统类型不能为空")
|
||||||
|
private SystemType type;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Service层规范
|
||||||
|
- 简单CRUD场景直接继承BaseServiceImpl即可
|
||||||
|
- 复杂业务场景需要:
|
||||||
|
1. 定义业务接口方法
|
||||||
|
2. 实现具体的业务逻辑
|
||||||
|
3. 处理业务异常
|
||||||
|
4. 添加事务控制
|
||||||
|
- 示例:
|
||||||
|
```java
|
||||||
|
@Slf4j
|
||||||
|
@Service
|
||||||
|
@ServiceType(DATABASE)
|
||||||
|
public class ExternalSystemServiceImpl extends BaseServiceImpl<ExternalSystem, ExternalSystemDTO, Long>
|
||||||
|
implements IExternalSystemService {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Transactional
|
||||||
|
public boolean testConnection(Long id) {
|
||||||
|
// 业务实现
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 查询规范
|
||||||
|
- 简单查询使用BaseQuery
|
||||||
|
- 复杂查询需要:
|
||||||
|
1. 继承BaseQuery
|
||||||
|
2. 使用@QueryField注解标注查询字段
|
||||||
|
3. 指定查询类型
|
||||||
|
- 示例:
|
||||||
|
```java
|
||||||
|
@Data
|
||||||
|
@EqualsAndHashCode(callSuper = true)
|
||||||
|
public class ExternalSystemQuery extends BaseQuery {
|
||||||
|
@QueryField(field = "name", type = QueryType.LIKE)
|
||||||
|
private String name;
|
||||||
|
|
||||||
|
@QueryField(field = "type")
|
||||||
|
private SystemType type;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 异常处理规范
|
||||||
|
- 业务异常应承BusinessException
|
||||||
|
- 使用ResponseCode定义错误码
|
||||||
|
- 在messages.properties中定义错误消息
|
||||||
|
- 通过GlobalExceptionHandler统一处理异常
|
||||||
|
- 示例:
|
||||||
|
```java
|
||||||
|
throw new BusinessException(ResponseCode.EXTERNAL_SYSTEM_DISABLED);
|
||||||
|
```
|
||||||
|
|
||||||
|
### Controller层规范
|
||||||
|
- REST FULL接口使用框架BaseController
|
||||||
|
- 新增接口命名规范:
|
||||||
|
- 三方接口:模块名ApiController(如:ExternalSystemApiController)
|
||||||
|
- 二方接口:模块名Controller(如:ExternalSystemController)
|
||||||
|
- 示例:
|
||||||
|
```java
|
||||||
|
@Slf4j
|
||||||
|
@RestController
|
||||||
|
@RequestMapping("/api/v1/external-system")
|
||||||
|
@Tag(name = "外部系统管理", description = "外部系统管理相关接口")
|
||||||
|
public class ExternalSystemApiController extends BaseController<ExternalSystem, ExternalSystemDTO, Long, ExternalSystemQuery> {
|
||||||
|
// 特定业务方法实现
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Repository层规范
|
||||||
|
- 继承IBaseRepository
|
||||||
|
- 定义特定的查询方法
|
||||||
|
- 示例:
|
||||||
|
```java
|
||||||
|
@Repository
|
||||||
|
public interface IExternalSystemRepository extends IBaseRepository<ExternalSystem, Long> {
|
||||||
|
boolean existsByNameAndDeletedFalse(String name);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Converter规范
|
||||||
|
- 继承BaseConverter,遵循以下规则:
|
||||||
|
1. 简单场景(字段完全匹配)示例:
|
||||||
|
```java
|
||||||
|
@Mapper(config = BaseConverter.class)
|
||||||
|
public interface ExternalSystemConverter extends BaseConverter<ExternalSystem, ExternalSystemDTO> {
|
||||||
|
// 字段完全匹配时无需额外配置
|
||||||
|
}
|
||||||
|
```
|
||||||
|
2. 复杂场景(需要特殊映射)示例:
|
||||||
|
```java
|
||||||
|
@Mapper(config = BaseConverter.class)
|
||||||
|
public interface UserConverter extends BaseConverter<User, UserDTO> {
|
||||||
|
@Mapping(target = "departmentId", source = "department.id")
|
||||||
|
@Mapping(target = "departmentName", source = "department.name")
|
||||||
|
UserDTO toDto(User entity);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 测试规范
|
||||||
|
- Service层测试:
|
||||||
|
1. 使用@SpringBootTest
|
||||||
|
2. 使用@MockBean模拟依赖
|
||||||
|
3. 测试所有业务场景
|
||||||
|
- Controller层测试:
|
||||||
|
1. 使用@WebMvcTest或@SpringBootTest + @AutoConfigureMockMvc
|
||||||
|
2. 使用MockMvc测试接口
|
||||||
|
3. 测试所有接口路径
|
||||||
|
- Repository层测试:
|
||||||
|
1. 使用@DataJpaTest
|
||||||
|
2. 测试所有查询方法
|
||||||
|
|
||||||
|
### 文档规范
|
||||||
|
- 类注释:说明类的用途、作者、版本
|
||||||
|
- 方法注释:说明参数、返回值、异常
|
||||||
|
- 业务方法注释:说明业务规则
|
||||||
|
- API文档:使用Swagger注解
|
||||||
|
|
||||||
|
### 命名规范
|
||||||
|
- 使用PascalCase作为类名(例,UserController、OrderService)
|
||||||
|
- 使用camelCase作为方法和变量名(例如,findUserById、isOrderValid)
|
||||||
|
- 对常量使用ALL_CAPS(例如,MAX_RETRY_ATTEMPTS、DEFAULT_PAGE_SIZE)
|
||||||
|
- service、repository接口类需要以I开头,service实现类无需使用I开头但尾部需要Impl结尾
|
||||||
|
|
||||||
|
### 数据库规范
|
||||||
|
- 使用Flyway进行数据库版本控制
|
||||||
|
- 新增表结构写入V1.0.0__init_schema.sql
|
||||||
|
- 新增初始数据写入V1.0.1__init_data.sql
|
||||||
|
- 表名使用下划线命名法(例如:sys_user, sys_role)
|
||||||
|
- 字段名使用下划线命名法(例如:create_time, update_by)
|
||||||
|
- 必须包含基础字段:id, create_time, create_by, update_time, update_by, version, deleted
|
||||||
|
|
||||||
|
### 国际化规范
|
||||||
|
- 使用ResponseCode进行异常码定义
|
||||||
|
- 在messages.properties中定义中文消息
|
||||||
|
- 在messages_en_US.properties中定义英文消息
|
||||||
|
- 在messages_zh_CN.properties中定义中文繁体消息
|
||||||
|
|
||||||
|
### 文档维护规范
|
||||||
|
- README.md文件维护规则:
|
||||||
|
1. 接口变更必须同步更新README.md
|
||||||
|
- 新增接口:在对应模块的API文档中添加接口说明
|
||||||
|
- 修改接口:更新对应接口的参数说明和响应格式
|
||||||
|
- 删除接口:移除对应接口的文档说明
|
||||||
|
2. 接口文档格式要求:
|
||||||
|
```http
|
||||||
|
[HTTP方法] [接口路径]
|
||||||
|
|
||||||
|
请求参数:
|
||||||
|
{
|
||||||
|
"参数1": "值1", // 参数说明(是否必填)
|
||||||
|
"参数2": "值2" // 参数说明(是否必填)
|
||||||
|
}
|
||||||
|
|
||||||
|
响应结果:
|
||||||
|
{
|
||||||
|
"success": true,
|
||||||
|
"code": 200,
|
||||||
|
"message": "操作成功",
|
||||||
|
"data": {
|
||||||
|
// 响应数据结构
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
3. 功能清单维护:
|
||||||
|
- 新增功能:在"已实现功能"章节添加功能说明
|
||||||
|
- 修改功能:更新对应功能的描述
|
||||||
|
- 删除功能:移除对应功能说明
|
||||||
|
4. 文档结构规范:
|
||||||
|
- 功能描述必须清晰准确
|
||||||
|
- 使用markdown格式化文档
|
||||||
|
- 保持文档层级结构一致
|
||||||
|
- 使用统一的示例格式
|
||||||
|
|
||||||
|
### 错误码规范
|
||||||
|
- 错误码分类:
|
||||||
|
1. 系统级错误(1xxx)
|
||||||
|
- 1000-1099:通用系统错误
|
||||||
|
- 1100-1199:依赖注入错误
|
||||||
|
- 1200-1299:数据库错误
|
||||||
|
2. 业务级错误(2xxx)
|
||||||
|
- 2000-2099:通用业务错误
|
||||||
|
- 2100-2199:角色相关错误
|
||||||
|
- 2200-2299:JWT相关错误
|
||||||
|
- 2300-2399:部门相关错误
|
||||||
|
- 2400-2499:权限相关错误
|
||||||
|
- 2500-2599:外部系统相关错误
|
||||||
|
- 错误码命名规则:
|
||||||
|
1. 使用大写字母和下划线
|
||||||
|
2. 采用`模块_操作_错误`的命名方式
|
||||||
|
3. 在ResponseCode枚举类中定义
|
||||||
|
- 错误信息规范:
|
||||||
|
1. 在messages.properties中定义错误信息
|
||||||
|
2. 错误信息要简洁明了
|
||||||
|
3. 包含问题描述和可能的解决方案
|
||||||
|
4. 支持国际化
|
||||||
|
|
||||||
|
### 接口安全规范
|
||||||
|
- JWT Token规范:
|
||||||
|
1. Token结构:
|
||||||
|
- Header:包含算法信息
|
||||||
|
- Payload:包含用户信息和权限信息
|
||||||
|
- Signature:使用密钥签名
|
||||||
|
2. Token有效期:
|
||||||
|
- Access Token:2小时
|
||||||
|
- Refresh Token:7天
|
||||||
|
3. Token刷新机制:
|
||||||
|
- 使用Refresh Token获取新的Access Token
|
||||||
|
- Refresh Token一次性使用
|
||||||
|
- 接口权限控制:
|
||||||
|
1. 使用@PreAuthorize注解控制接口访问权限
|
||||||
|
2. 使用@Secured注解控制方法级别权限
|
||||||
|
3. 实现自定义权限评估器
|
||||||
|
- 敏感数据处理:
|
||||||
|
1. 密码等敏感信息必须加密存储
|
||||||
|
2. 使用AES加密传输敏感数据
|
||||||
|
3. 日志中不得打印敏感信息
|
||||||
|
4. 接口返回时脱敏处理
|
||||||
|
|
||||||
|
### 代码审查规范
|
||||||
|
- 审查重点:
|
||||||
|
1. 代码质量:
|
||||||
|
- 代码是否符合编码规范
|
||||||
|
- 是否存在重复代码
|
||||||
|
- 是否有性能问题
|
||||||
|
2. 业务逻辑:
|
||||||
|
- 业务流程是否正确
|
||||||
|
- 异常处理是否完善
|
||||||
|
- 边界条件是否考虑
|
||||||
|
3. 安全性:
|
||||||
|
- 是否存在安全漏洞
|
||||||
|
- 敏感数据是否安全处理
|
||||||
|
- 权限控制是否正确
|
||||||
|
4. 测试覆盖:
|
||||||
|
- 单元测试是否完整
|
||||||
|
- 是否覆盖关键路径
|
||||||
|
- 是否包含边界测试
|
||||||
|
- 审查流程:
|
||||||
|
1. 提交前自查
|
||||||
|
2. 提交Pull Request
|
||||||
|
3. 代码评审
|
||||||
|
4. 修改完善
|
||||||
|
5. 评审通过
|
||||||
|
- 审查标准:
|
||||||
|
1. 代码实现是否满足需求
|
||||||
|
2. 是否符合开发规范
|
||||||
|
3. 是否有充分的测试覆盖
|
||||||
|
4. 文档是否同步更新
|
||||||
|
|
||||||
|
### 缓存使用规范
|
||||||
|
- 缓存注解:
|
||||||
|
1. @Cacheable:适用于查询操作
|
||||||
|
2. @CachePut:适用于更新操作
|
||||||
|
3. @CacheEvict:适用于删除操作
|
||||||
|
4. @Caching:组合多个缓存操作
|
||||||
|
- 缓存Key:
|
||||||
|
1. 格式:`模块:业务:标识`
|
||||||
|
2. 示例:`user:info:1`
|
||||||
|
3. 避免特殊字符
|
||||||
|
4. 长度控制在200以内
|
||||||
|
- 缓存策略:
|
||||||
|
1. 读多写少用Cache Aside
|
||||||
|
2. 写多读少用Write Through
|
||||||
|
3. 高一致性用Write Back
|
||||||
|
- 示例:
|
||||||
|
```java
|
||||||
|
@Slf4j
|
||||||
|
@Service
|
||||||
|
@ServiceType(DATABASE)
|
||||||
|
public class UserServiceImpl extends BaseServiceImpl<User, UserDTO, Long> implements IUserService {
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
private IUserRepository userRepository;
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
private UserConverter userConverter;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Cacheable(value = "user", key = "#id")
|
||||||
|
public UserDTO findById(Long id) {
|
||||||
|
User user = userRepository.findById(id)
|
||||||
|
.orElseThrow(() -> new BusinessException(ResponseCode.USER_NOT_FOUND));
|
||||||
|
return userConverter.toDto(user);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Transactional
|
||||||
|
@CacheEvict(value = "user", key = "#id")
|
||||||
|
public void delete(Long id) {
|
||||||
|
User user = findEntityById(id);
|
||||||
|
user.setDeleted(true);
|
||||||
|
userRepository.save(user);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 分层设计规范
|
||||||
|
- Controller层:
|
||||||
|
1. REST接口设计
|
||||||
|
2. 参数校验
|
||||||
|
3. 权限控制
|
||||||
|
4. 响应封装
|
||||||
|
- Service层:
|
||||||
|
1. 业务逻辑处理
|
||||||
|
2. 事务控制
|
||||||
|
3. 缓存处理
|
||||||
|
4. 并发控制
|
||||||
|
- Repository层:
|
||||||
|
1. 数据访问
|
||||||
|
2. 查询优化
|
||||||
|
3. 持久化操作
|
||||||
|
|
||||||
|
### 数据对象规范
|
||||||
|
- DTO设计:
|
||||||
|
1. 简单CRUD场景使用统一DTO
|
||||||
|
2. 复杂业务场景使用专门的Request/Response
|
||||||
|
3. 继承BaseDTO获取基础字段
|
||||||
|
- 验证规则:
|
||||||
|
1. 使用Jakarta Validation注解
|
||||||
|
2. 自定义验证注解
|
||||||
|
3. 分组验证
|
||||||
|
- 对象转换:
|
||||||
|
1. 使用MapStruct进行对象转换
|
||||||
|
2. 继承BaseConverter
|
||||||
|
3. 显式声明特殊映射
|
||||||
|
|
||||||
|
### 业务处理规范
|
||||||
|
- 事务管理:
|
||||||
|
1. 声明式事务(@Transactional)
|
||||||
|
2. 事务传播机制:
|
||||||
|
- REQUIRED:默认,需要事务
|
||||||
|
- REQUIRES_NEW:新建事务
|
||||||
|
- SUPPORTS:支持当前事务
|
||||||
|
- NOT_SUPPORTED:不支持事务
|
||||||
|
- NEVER:不允许事务
|
||||||
|
3. 事务隔离级别:
|
||||||
|
- READ_COMMITTED:默认
|
||||||
|
- REPEATABLE_READ:需要时使用
|
||||||
|
- 并发控制:
|
||||||
|
1. 乐观锁:
|
||||||
|
- 使用@Version注解
|
||||||
|
- 实现重试机制
|
||||||
|
2. 悲观锁:
|
||||||
|
- findByIdWithLock方法
|
||||||
|
- 限定锁范围和时间
|
||||||
|
3. 分布式锁:
|
||||||
|
- 使用Redis实现
|
||||||
|
- 设置超时时间
|
||||||
|
- 防止死锁
|
||||||
|
- 缓存策略:
|
||||||
|
1. 缓存注解使用:
|
||||||
|
- @Cacheable:查询
|
||||||
|
- @CachePut:更新
|
||||||
|
- @CacheEvict:删除
|
||||||
|
2. 缓存Key规范:
|
||||||
|
- 格式:模块:业务:标识
|
||||||
|
- 避免特殊字符
|
||||||
|
3. 缓存更新策略:
|
||||||
|
- Cache Aside
|
||||||
|
- Write Through
|
||||||
|
- Write Back
|
||||||
|
|
||||||
|
### 安全规范
|
||||||
|
- 认证授权:
|
||||||
|
1. JWT Token结构和有效期
|
||||||
|
2. 权限注解使用
|
||||||
|
3. 自定义权限评估
|
||||||
|
- 敏感信息:
|
||||||
|
1. 密码加密存储
|
||||||
|
2. 传输数据加密
|
||||||
|
3. 日志脱敏处理
|
||||||
|
4. 接口参数脱敏
|
||||||
|
- 接口防护:
|
||||||
|
1. 参数校验
|
||||||
|
2. SQL注入防护
|
||||||
|
3. XSS防护
|
||||||
|
4. CSRF防护
|
||||||
|
|
||||||
|
### 异常处理规范
|
||||||
|
- 异常分类:
|
||||||
|
1. 业务异常(BusinessException)
|
||||||
|
2. 系统异常(SystemException)
|
||||||
|
3. 参数异常(ValidationException)
|
||||||
|
- 错误码:
|
||||||
|
1. 系统级错误(1xxx)
|
||||||
|
2. 业务级错误(2xxx)
|
||||||
|
3. 第三方错误(3xxx)
|
||||||
|
- 异常处理:
|
||||||
|
1. 统一异常处理器
|
||||||
|
2. 错误信息国际化
|
||||||
|
3. 异常日志记录
|
||||||
|
|
||||||
|
### 日志规范
|
||||||
|
- 日志级别:
|
||||||
|
1. ERROR:系统错误、业务异常
|
||||||
|
2. WARN:潜在问题警告
|
||||||
|
3. INFO:重要业务操作
|
||||||
|
4. DEBUG:调试信息
|
||||||
|
- 日志内容:
|
||||||
|
1. 操作人信息
|
||||||
|
2. 业务模块
|
||||||
|
3. 操作类型
|
||||||
|
4. 关键参数
|
||||||
|
- 日志脱敏:
|
||||||
|
1. 密码信息
|
||||||
|
2. 个人隐私
|
||||||
|
3. 敏感配置
|
||||||
|
|
||||||
|
### 文档规范
|
||||||
|
- 代码注释:
|
||||||
|
1. 类注释:用途、作者、版本
|
||||||
|
2. 方法注释:参数、返回值、异常
|
||||||
|
3. 业务注释:业务规则、处理逻辑
|
||||||
|
- API文档:
|
||||||
|
1. 使用Swagger注解
|
||||||
|
2. 接口说明完整
|
||||||
|
3. 参数说明清晰
|
||||||
|
- README维护:
|
||||||
|
1. 功能清单及时更新
|
||||||
|
2. 接口文档同步修改
|
||||||
|
3. 环境配置说明
|
||||||
|
|
||||||
|
### 测试规范
|
||||||
|
- 单元测试:
|
||||||
|
1. Service层业务测试
|
||||||
|
2. 重要工具类测试
|
||||||
|
3. 边界条件测试
|
||||||
|
- 集成测试:
|
||||||
|
1. Controller层接口测试
|
||||||
|
2. 数据库操作测试
|
||||||
|
3. 缓存操作测试
|
||||||
|
- 测试原则:
|
||||||
|
1. 测试覆盖率要求
|
||||||
|
2. 测试数据隔离
|
||||||
|
3. 测试用例完整性
|
||||||
|
|
||||||
|
### 性能优化规范
|
||||||
|
- 数据库优化:
|
||||||
|
1. 索引设计
|
||||||
|
2. SQL优化
|
||||||
|
3. 分页查询
|
||||||
|
- 代码优化:
|
||||||
|
1. 循环优化
|
||||||
|
2. 集合操作
|
||||||
|
3. 字符串处理
|
||||||
|
- 缓存优化:
|
||||||
|
1. 缓存粒度
|
||||||
|
2. 缓存更新
|
||||||
|
3. 缓存穿透处理
|
||||||
@ -1,136 +1,748 @@
|
|||||||
|
# Deploy Ease Platform
|
||||||
|
|
||||||
# 问题解决方案
|
Deploy Ease Platform 是一个现代化的部署管理平台,旨在简化和自动化应用程序的部署流程。
|
||||||
##### 1、Cannot compare left expression of type 'java.io.Serializable' with right expression of type 'java.lang.Long'
|
|
||||||
```
|
|
||||||
使用@Query查询,因为无法解决序列化问题
|
|
||||||
```
|
|
||||||
|
|
||||||
|
## 已实现功能
|
||||||
|
|
||||||
`
|
### 1. 外部系统管理
|
||||||
分层结构从上到下:
|
- [x] 基础功能
|
||||||
`
|
- 创建外部系统(支持Jenkins、Git等类型)
|
||||||
```
|
- 更新系统信息
|
||||||
Controller (表现层)
|
- 删除系统
|
||||||
↓
|
- 查询系统详情
|
||||||
Service (业务层)
|
- 分页查询系统列表
|
||||||
↓
|
- 条件筛选查询
|
||||||
Repository (数据访问层)
|
- [x] Jenkins集成
|
||||||
```
|
- 连接测试
|
||||||
`
|
- 任务同步
|
||||||
对于外部集成:
|
- 状态检查
|
||||||
`
|
- 支持用户名密码认证
|
||||||
```
|
- 支持Token认证
|
||||||
Controller
|
- [x] Git集成
|
||||||
↓
|
- 仓库连接测试
|
||||||
Service ←→ Integration Service (集成层)
|
- 代码分支同步
|
||||||
↓
|
- 提交历史查询
|
||||||
Repository
|
- 支持用户名密码认证
|
||||||
```
|
- 支持SSH密钥认证
|
||||||
|
- 支持Token认证
|
||||||
|
|
||||||
```
|
### 2. 用户权限管理
|
||||||
GET /api/users - 查询所有用户
|
- [x] 用户管理
|
||||||
GET /api/users/list?enabled=true - 查询所有启用的用户
|
- 用户注册
|
||||||
GET /api/users/page?pageNum=1&pageSize=10 - 分页查询用户
|
- 用户登录
|
||||||
```
|
- 密码重置
|
||||||
|
- 用户信息修改
|
||||||
|
- 用户状态管理
|
||||||
|
- 部门分配
|
||||||
|
- [x] 角色管理
|
||||||
|
- 角色创建与维护
|
||||||
|
- 角色标签管理
|
||||||
|
- 角色权限分配
|
||||||
|
- 用户角色分配
|
||||||
|
- [x] 权限管理
|
||||||
|
- 权限创建与维护
|
||||||
|
- 权限分类管理
|
||||||
|
- 权限与菜单关联
|
||||||
|
- 权限检查与控制
|
||||||
|
|
||||||
`查询日期示例`
|
### 3. 菜单管理
|
||||||
```
|
- [x] 基础功能
|
||||||
UserQuery query = new UserQuery();
|
- 菜单树维护
|
||||||
query.setCreateTimeRange(
|
- 菜单排序
|
||||||
LocalDateTime.now().minusDays(7), // 一周内
|
- 菜单显示控制
|
||||||
LocalDateTime.now()
|
- [x] 权限集成
|
||||||
);
|
- 菜单权限配置
|
||||||
query.setEnabled(true);
|
- 按钮权限配置
|
||||||
query.setCreateBy("admin");
|
- 数据权限配置
|
||||||
|
- [x] 前端集成
|
||||||
|
- 动态路由生成
|
||||||
|
- 菜单组件加载
|
||||||
|
- 权限指令集成
|
||||||
|
|
||||||
|
### 4. 部门管理
|
||||||
|
- [x] 基础功能
|
||||||
|
- 部门树维护
|
||||||
|
- 部门编码管理
|
||||||
|
- 部门描述维护
|
||||||
|
- 部门排序
|
||||||
|
- [x] 人员管理
|
||||||
|
- 部门负责人设置
|
||||||
|
- 部门人员管理
|
||||||
|
- 部门权限控制
|
||||||
|
|
||||||
@QueryField(type = QueryType.IN)
|
### 5. 系统功能
|
||||||
private String status; // 可以传入 "ACTIVE,PENDING,CLOSED"
|
- [x] 认证授权
|
||||||
|
- JWT令牌认证
|
||||||
|
- 权限拦截器
|
||||||
|
- 角色检查器
|
||||||
|
- 权限注解支持
|
||||||
|
- [x] 数据审计
|
||||||
|
- 创建人记录
|
||||||
|
- 创建时间记录
|
||||||
|
- 更新人记录
|
||||||
|
- 更新时间记录
|
||||||
|
- 版本控制
|
||||||
|
- [x] 异常处理
|
||||||
|
- 统一异常处理
|
||||||
|
- 业务异常处理
|
||||||
|
- 系统异常处理
|
||||||
|
- 参数校验异常处理
|
||||||
|
- [x] 数据库支持
|
||||||
|
- Flyway数据库版本控制
|
||||||
|
- 基础表结构
|
||||||
|
- 初始化数据
|
||||||
|
- 软删除支持
|
||||||
|
|
||||||
```
|
### 6. 开发支持
|
||||||
```
|
- [x] 基础框架
|
||||||
QueryDSL使用方法
|
- 统一响应格式
|
||||||
@Service
|
- 统一分页格式
|
||||||
@Transactional
|
- 基础CRUD封装
|
||||||
public class UserServiceImpl extends BaseServiceImpl<User, Long> implements UserService {
|
- DTO自动转换
|
||||||
|
- [x] 代码规范
|
||||||
private final UserRoleRepository userRoleRepository;
|
- 开发规范文档
|
||||||
private final QUserRole qUserRole = QUserRole.userRole;
|
- 接口文档规范
|
||||||
|
- 测试规范
|
||||||
public UserServiceImpl(UserRepository userRepository, UserRoleRepository userRoleRepository) {
|
- 注释规范
|
||||||
super(userRepository);
|
- [x] 测试支持
|
||||||
this.userRoleRepository = userRoleRepository;
|
- 单元测试框架
|
||||||
|
- 集成测试支持
|
||||||
|
- 测试数据构建
|
||||||
|
- Mock支持
|
||||||
|
|
||||||
|
## 技术栈
|
||||||
|
|
||||||
|
### 后端技术栈
|
||||||
|
- Spring Boot 3.x
|
||||||
|
- Spring Security + JWT
|
||||||
|
- Spring Data JPA
|
||||||
|
- Flyway数据库版本控制
|
||||||
|
- MapStruct对象映射
|
||||||
|
- Lombok
|
||||||
|
- JUnit 5
|
||||||
|
|
||||||
|
### 数据库
|
||||||
|
- MySQL 8.0+
|
||||||
|
|
||||||
|
## API文档
|
||||||
|
|
||||||
|
### 1. 外部系统管理API
|
||||||
|
|
||||||
|
#### 1.1 分页查询
|
||||||
|
```http
|
||||||
|
GET /api/v1/external-system/page
|
||||||
|
|
||||||
|
请求参数:
|
||||||
|
{
|
||||||
|
"pageNum": 1, // 页码(从1开始)
|
||||||
|
"pageSize": 10, // 每页大小
|
||||||
|
"sortField": "createTime", // 排序字段
|
||||||
|
"sortOrder": "desc", // 排序方式
|
||||||
|
"name": "string", // 系统名称(可选)
|
||||||
|
"type": "JENKINS", // 系统类型(可选)
|
||||||
|
"enabled": true // 是否启用(可选)
|
||||||
|
}
|
||||||
|
|
||||||
|
响应结果:
|
||||||
|
{
|
||||||
|
"success": true,
|
||||||
|
"code": 200,
|
||||||
|
"message": "操作成功",
|
||||||
|
"data": {
|
||||||
|
"content": [{
|
||||||
|
"id": 1,
|
||||||
|
"createTime": "2024-01-01 12:00:00",
|
||||||
|
"createBy": "admin",
|
||||||
|
"updateTime": "2024-01-01 12:00:00",
|
||||||
|
"updateBy": "admin",
|
||||||
|
"name": "Jenkins测试环境",
|
||||||
|
"type": "JENKINS",
|
||||||
|
"url": "http://jenkins.test.com",
|
||||||
|
"remark": "测试环境Jenkins服务器",
|
||||||
|
"sort": 1,
|
||||||
|
"enabled": true,
|
||||||
|
"authType": "USERNAME_PASSWORD",
|
||||||
|
"username": "admin",
|
||||||
|
"password": "******",
|
||||||
|
"token": null,
|
||||||
|
"syncStatus": "SUCCESS",
|
||||||
|
"lastSyncTime": "2024-01-01 12:00:00",
|
||||||
|
"config": "{}"
|
||||||
|
}],
|
||||||
|
"totalElements": 100,
|
||||||
|
"totalPages": 10,
|
||||||
|
"size": 10,
|
||||||
|
"number": 0,
|
||||||
|
"first": true,
|
||||||
|
"last": false,
|
||||||
|
"empty": false
|
||||||
}
|
}
|
||||||
|
}
|
||||||
public Set<UserRole> getUserRoles(Long userId) {
|
```
|
||||||
// 使用QueryDSL构建查询条件
|
|
||||||
Predicate predicate = qUserRole.user.id.eq(userId);
|
#### 1.2 创建外部系统
|
||||||
return StreamSupport.stream(
|
```http
|
||||||
userRoleRepository.findAll(predicate).spliterator(),
|
POST /api/v1/external-system
|
||||||
false
|
|
||||||
).collect(Collectors.toSet());
|
请求参数:
|
||||||
|
{
|
||||||
|
"name": "Jenkins测试环境", // 系统名称(必填)
|
||||||
|
"type": "JENKINS", // 系统类型(必填)
|
||||||
|
"url": "http://jenkins.test.com", // 系统地址(必填)
|
||||||
|
"remark": "测试环境Jenkins服务器", // 备注(可选)
|
||||||
|
"sort": 1, // 排序(可选)
|
||||||
|
"enabled": true, // 是否启用(可选,默认true)
|
||||||
|
"authType": "USERNAME_PASSWORD", // 认证方式(必填)
|
||||||
|
"username": "admin", // 用户名(可选)
|
||||||
|
"password": "password123", // 密码(可选)
|
||||||
|
"token": null, // 访问令牌(可选)
|
||||||
|
"config": "{}" // 配置信息(可选)
|
||||||
|
}
|
||||||
|
|
||||||
|
响应结果:
|
||||||
|
{
|
||||||
|
"success": true,
|
||||||
|
"code": 200,
|
||||||
|
"message": "操作成功",
|
||||||
|
"data": {
|
||||||
|
"id": 1,
|
||||||
|
// ... 其他字段同上
|
||||||
}
|
}
|
||||||
|
}
|
||||||
public void assignRole(Long userId, Long roleId) {
|
```
|
||||||
User user = findById(userId);
|
|
||||||
Role role = roleRepository.findById(roleId)
|
#### 1.3 更新外部系统
|
||||||
.orElseThrow(() -> new RuntimeException("Role not found"));
|
```http
|
||||||
|
PUT /api/v1/external-system/{id}
|
||||||
// 检查是否已存在
|
|
||||||
Predicate predicate = qUserRole.user.id.eq(userId)
|
请求参数:
|
||||||
.and(qUserRole.role.id.eq(roleId));
|
{
|
||||||
|
// 同创建接口
|
||||||
if (!userRoleRepository.exists(predicate)) {
|
}
|
||||||
UserRole userRole = new UserRole(user, role);
|
|
||||||
userRoleRepository.save(userRole);
|
响应结果:
|
||||||
|
{
|
||||||
|
"success": true,
|
||||||
|
"code": 200,
|
||||||
|
"message": "操作成功",
|
||||||
|
"data": {
|
||||||
|
"id": 1,
|
||||||
|
// ... 其他字段同上
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 1.4 删除外部系统
|
||||||
|
```http
|
||||||
|
DELETE /api/v1/external-system/{id}
|
||||||
|
|
||||||
|
响应结果:
|
||||||
|
{
|
||||||
|
"success": true,
|
||||||
|
"code": 200,
|
||||||
|
"message": "操作成功"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 1.5 测试连接
|
||||||
|
```http
|
||||||
|
POST /api/v1/external-system/{id}/test-connection
|
||||||
|
|
||||||
|
响应结果:
|
||||||
|
{
|
||||||
|
"success": true,
|
||||||
|
"code": 200,
|
||||||
|
"message": "操作成功",
|
||||||
|
"data": true
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 1.6 同步数据
|
||||||
|
```http
|
||||||
|
POST /api/v1/external-system/{id}/sync
|
||||||
|
|
||||||
|
响应结果:
|
||||||
|
{
|
||||||
|
"success": true,
|
||||||
|
"code": 200,
|
||||||
|
"message": "操作成功"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 1.7 检查状态
|
||||||
|
```http
|
||||||
|
GET /api/v1/external-system/{id}/status
|
||||||
|
|
||||||
|
响应结果:
|
||||||
|
{
|
||||||
|
"success": true,
|
||||||
|
"code": 200,
|
||||||
|
"message": "操作成功",
|
||||||
|
"data": {
|
||||||
|
"syncStatus": "SUCCESS",
|
||||||
|
"lastSyncTime": "2024-01-01 12:00:00"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. 用户认证API
|
||||||
|
|
||||||
|
#### 2.1 用户登录
|
||||||
|
```http
|
||||||
|
POST /api/v1/user/login
|
||||||
|
|
||||||
|
请求参数:
|
||||||
|
{
|
||||||
|
"username": "admin", // 用户名(必填)
|
||||||
|
"password": "password" // 密码(必填)
|
||||||
|
}
|
||||||
|
|
||||||
|
响应结果:
|
||||||
|
{
|
||||||
|
"success": true,
|
||||||
|
"code": 200,
|
||||||
|
"message": "登录成功",
|
||||||
|
"data": {
|
||||||
|
"token": "eyJhbGciOiJIUzI1NiJ9...",
|
||||||
|
"user": {
|
||||||
|
"id": 1,
|
||||||
|
"username": "admin",
|
||||||
|
"nickname": "管理员",
|
||||||
|
"email": "admin@example.com",
|
||||||
|
"phone": "13800138000",
|
||||||
|
"departmentId": 1,
|
||||||
|
"departmentName": "技术部"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
```
|
#### 2.2 获取当前用户信息
|
||||||
-- 插入正常用户数据
|
```http
|
||||||
INSERT INTO sys_user (
|
GET /api/v1/user/current
|
||||||
username, password, nickname, email, phone,
|
|
||||||
enabled, deleted, dept_id, dept_name,
|
|
||||||
create_by, create_time, update_by, update_time, version
|
|
||||||
) VALUES
|
|
||||||
-- 管理员用户
|
|
||||||
('admin', '$2a$10$mW/yJPHjyueQ1g26YxiZNOtr6bKVF4P/w/VHLVHHhxslY.YlXhbcm', '系统管理员', 'admin@example.com', '13800138000',
|
|
||||||
true, false, 1, '技术部',
|
|
||||||
'system', '2024-01-01 09:00:00', null, null, 0),
|
|
||||||
|
|
||||||
-- 普通用户
|
响应结果:
|
||||||
('user01', '$2a$10$mW/yJPHjyueQ1g26YxiZNOtr6bKVF4P/w/VHLVHHhxslY.YlXhbcm', '张三', 'zhangsan@example.com', '13800138001',
|
{
|
||||||
true, false, 1, '技术部',
|
"success": true,
|
||||||
'admin', '2024-01-02 10:00:00', null, null, 0),
|
"code": 200,
|
||||||
|
"message": "操作成功",
|
||||||
('user02', '$2a$10$mW/yJPHjyueQ1g26YxiZNOtr6bKVF4P/w/VHLVHHhxslY.YlXhbcm', '李四', 'lisi@example.com', '13800138002',
|
"data": {
|
||||||
true, false, 2, '市场部',
|
"id": 1,
|
||||||
'admin', '2024-01-03 11:00:00', 'admin', '2024-01-04 15:00:00', 1),
|
"username": "admin",
|
||||||
|
"nickname": "管理员",
|
||||||
-- 已禁用的用户
|
"email": "admin@example.com",
|
||||||
('disabled_user', '$2a$10$mW/yJPHjyueQ1g26YxiZNOtr6bKVF4P/w/VHLVHHhxslY.YlXhbcm', '王五', 'wangwu@example.com', '13800138003',
|
"phone": "13800138000",
|
||||||
false, false, 2, '市场部',
|
"departmentId": 1,
|
||||||
'admin', '2024-01-05 14:00:00', 'admin', '2024-01-06 16:00:00', 1),
|
"departmentName": "技术部"
|
||||||
|
}
|
||||||
-- 已删除的用户
|
}
|
||||||
('deleted_user', '$2a$10$mW/yJPHjyueQ1g26YxiZNOtr6bKVF4P/w/VHLVHHhxslY.YlXhbcm', '赵六', 'zhaoliu@example.com', '13800138004',
|
|
||||||
true, true, 3, '财务部',
|
|
||||||
'admin', '2024-01-07 10:00:00', 'admin', '2024-01-08 09:00:00', 2),
|
|
||||||
|
|
||||||
-- 最近创建的用户
|
|
||||||
('new_user01', '$2a$10$mW/yJPHjyueQ1g26YxiZNOtr6bKVF4P/w/VHLVHHhxslY.YlXhbcm', '小明', 'xiaoming@example.com', '13800138005',
|
|
||||||
true, false, 1, '技术部',
|
|
||||||
'admin', '2024-03-01 09:00:00', null, null, 0),
|
|
||||||
|
|
||||||
('new_user02', '$2a$10$mW/yJPHjyueQ1g26YxiZNOtr6bKVF4P/w/VHLVHHhxslY.YlXhbcm', '小红', 'xiaohong@example.com', '13800138006',
|
|
||||||
true, false, 2, '市场部',
|
|
||||||
'admin', '2024-03-02 10:00:00', null, null, 0),
|
|
||||||
|
|
||||||
-- 多次更新的用户
|
|
||||||
('updated_user', '$2a$10$mW/yJPHjyueQ1g26YxiZNOtr6bKVF4P/w/VHLVHHhxslY.YlXhbcm', '小张', 'xiaozhang@example.com', '13800138007',
|
|
||||||
true, false, 3, '财务部',
|
|
||||||
'admin', '2024-02-01 09:00:00', 'admin', '2024-03-01 15:00:00', 5);
|
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### 3. 菜单管理API
|
||||||
|
|
||||||
|
#### 3.1 获取当前用户菜单
|
||||||
|
```http
|
||||||
|
GET /api/v1/menu/current
|
||||||
|
|
||||||
|
响应结果:
|
||||||
|
{
|
||||||
|
"success": true,
|
||||||
|
"code": 200,
|
||||||
|
"message": "操作成功",
|
||||||
|
"data": [{
|
||||||
|
"id": 1,
|
||||||
|
"name": "系统管理",
|
||||||
|
"path": "/system",
|
||||||
|
"component": "Layout",
|
||||||
|
"icon": "setting",
|
||||||
|
"permission": "system:view",
|
||||||
|
"type": 1, // 1:目录 2:菜单 3:按钮
|
||||||
|
"parentId": null,
|
||||||
|
"sort": 1,
|
||||||
|
"hidden": false,
|
||||||
|
"children": [{
|
||||||
|
"id": 2,
|
||||||
|
"name": "用户管理",
|
||||||
|
"path": "/system/user",
|
||||||
|
"component": "system/user/index",
|
||||||
|
"icon": "user",
|
||||||
|
"permission": "system:user:view",
|
||||||
|
"type": 2,
|
||||||
|
"parentId": 1,
|
||||||
|
"sort": 1,
|
||||||
|
"hidden": false,
|
||||||
|
"children": []
|
||||||
|
}]
|
||||||
|
}]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 3.2 获取菜单树
|
||||||
|
```http
|
||||||
|
GET /api/v1/menu/tree
|
||||||
|
|
||||||
|
响应结果:
|
||||||
|
{
|
||||||
|
"success": true,
|
||||||
|
"code": 200,
|
||||||
|
"message": "操作成功",
|
||||||
|
"data": [{
|
||||||
|
// 结构同上
|
||||||
|
}]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 3.3 获取权限树
|
||||||
|
```http
|
||||||
|
GET /api/v1/menu/permission-tree
|
||||||
|
|
||||||
|
响应结果:
|
||||||
|
{
|
||||||
|
"success": true,
|
||||||
|
"code": 200,
|
||||||
|
"message": "操作成功",
|
||||||
|
"data": [{
|
||||||
|
"id": 1,
|
||||||
|
"name": "系统管理",
|
||||||
|
"type": 1,
|
||||||
|
"children": [{
|
||||||
|
"id": 2,
|
||||||
|
"name": "用户管理",
|
||||||
|
"type": 2,
|
||||||
|
"permissions": [{
|
||||||
|
"id": 3,
|
||||||
|
"code": "system:user:add",
|
||||||
|
"name": "新增用户",
|
||||||
|
"type": "BUTTON"
|
||||||
|
}]
|
||||||
|
}]
|
||||||
|
}]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4. 部门管理API
|
||||||
|
|
||||||
|
#### 4.1 获取部门树
|
||||||
|
```http
|
||||||
|
GET /api/v1/department/tree
|
||||||
|
|
||||||
|
响应结果:
|
||||||
|
{
|
||||||
|
"success": true,
|
||||||
|
"code": 200,
|
||||||
|
"message": "操作成功",
|
||||||
|
"data": [{
|
||||||
|
"id": 1,
|
||||||
|
"code": "TECH",
|
||||||
|
"name": "技术部",
|
||||||
|
"description": "技术研发部门",
|
||||||
|
"parentId": null,
|
||||||
|
"sort": 1,
|
||||||
|
"enabled": true,
|
||||||
|
"leaderId": 1,
|
||||||
|
"leaderName": "张三",
|
||||||
|
"children": [{
|
||||||
|
"id": 2,
|
||||||
|
"code": "DEV",
|
||||||
|
"name": "研发组",
|
||||||
|
"description": "研发团队",
|
||||||
|
"parentId": 1,
|
||||||
|
"sort": 1,
|
||||||
|
"enabled": true,
|
||||||
|
"leaderId": 2,
|
||||||
|
"leaderName": "李四",
|
||||||
|
"children": []
|
||||||
|
}]
|
||||||
|
}]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 4.2 创建部门
|
||||||
|
```http
|
||||||
|
POST /api/v1/department
|
||||||
|
|
||||||
|
请求参数:
|
||||||
|
{
|
||||||
|
"code": "TECH", // 部门编码(必填)
|
||||||
|
"name": "技术部", // 部门名称(必填)
|
||||||
|
"description": "技术研发部门", // 描述(可选)
|
||||||
|
"parentId": null, // 上级部门ID(可选)
|
||||||
|
"sort": 1, // 排序(可选)
|
||||||
|
"enabled": true, // 是否启用(可选,默认true)
|
||||||
|
"leaderId": 1, // 负责人ID(可选)
|
||||||
|
"leaderName": "张三" // 负责人姓名(可选)
|
||||||
|
}
|
||||||
|
|
||||||
|
响应结果:
|
||||||
|
{
|
||||||
|
"success": true,
|
||||||
|
"code": 200,
|
||||||
|
"message": "操作成功",
|
||||||
|
"data": {
|
||||||
|
"id": 1,
|
||||||
|
// ... 其他字段同上
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 5. 角色管理API
|
||||||
|
|
||||||
|
#### 5.1 分配角色标签
|
||||||
|
```http
|
||||||
|
POST /api/v1/role/{id}/tags
|
||||||
|
|
||||||
|
请求参数:
|
||||||
|
{
|
||||||
|
"tagIds": [1, 2, 3] // 标签ID列表
|
||||||
|
}
|
||||||
|
|
||||||
|
响应结果:
|
||||||
|
{
|
||||||
|
"success": true,
|
||||||
|
"code": 200,
|
||||||
|
"message": "操作成功"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 5.2 分配用户角色
|
||||||
|
```http
|
||||||
|
POST /api/v1/role/{userId}/assignRoles
|
||||||
|
|
||||||
|
请求参数:
|
||||||
|
{
|
||||||
|
"roleIds": [1, 2, 3] // 角色ID列表
|
||||||
|
}
|
||||||
|
|
||||||
|
响应结果:
|
||||||
|
{
|
||||||
|
"success": true,
|
||||||
|
"code": 200,
|
||||||
|
"message": "操作成功"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 5.3 获取角色权限
|
||||||
|
```http
|
||||||
|
GET /api/v1/role/{id}/permissions
|
||||||
|
|
||||||
|
响应结果:
|
||||||
|
{
|
||||||
|
"success": true,
|
||||||
|
"code": 200,
|
||||||
|
"message": "操作成功",
|
||||||
|
"data": [{
|
||||||
|
"id": 1,
|
||||||
|
"code": "system:user:view",
|
||||||
|
"name": "查看用户",
|
||||||
|
"type": "MENU",
|
||||||
|
"sort": 1,
|
||||||
|
"menuName": "用户管理"
|
||||||
|
}]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 5.4 分配角色权限
|
||||||
|
```http
|
||||||
|
POST /api/v1/role/{id}/permissions
|
||||||
|
|
||||||
|
请求参数:
|
||||||
|
{
|
||||||
|
"permissionIds": [1, 2, 3] // 权限ID列表
|
||||||
|
}
|
||||||
|
|
||||||
|
响应结果:
|
||||||
|
{
|
||||||
|
"success": true,
|
||||||
|
"code": 200,
|
||||||
|
"message": "操作成功"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 6. 权限管理API
|
||||||
|
|
||||||
|
#### 6.1 分页查询权限
|
||||||
|
```http
|
||||||
|
GET /api/v1/permission/page
|
||||||
|
|
||||||
|
请求参数:
|
||||||
|
{
|
||||||
|
"pageNum": 1,
|
||||||
|
"pageSize": 10,
|
||||||
|
"code": "system", // 权限编码(可选)
|
||||||
|
"name": "系统", // 权限名称(可选)
|
||||||
|
"type": "MENU", // 权限类型(可选)
|
||||||
|
"enabled": true, // 是否启用(可选)
|
||||||
|
"menuId": 1 // 菜单ID(可选)
|
||||||
|
}
|
||||||
|
|
||||||
|
响应结果:
|
||||||
|
{
|
||||||
|
"success": true,
|
||||||
|
"code": 200,
|
||||||
|
"message": "操作成功",
|
||||||
|
"data": {
|
||||||
|
"content": [{
|
||||||
|
"id": 1,
|
||||||
|
"code": "system:user:view",
|
||||||
|
"name": "查看用户",
|
||||||
|
"type": "MENU",
|
||||||
|
"sort": 1,
|
||||||
|
"menuId": 2,
|
||||||
|
"menuName": "用户管理"
|
||||||
|
}],
|
||||||
|
"totalElements": 100,
|
||||||
|
"totalPages": 10,
|
||||||
|
"size": 10,
|
||||||
|
"number": 0,
|
||||||
|
"first": true,
|
||||||
|
"last": false,
|
||||||
|
"empty": false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 6.2 创建权限
|
||||||
|
```http
|
||||||
|
POST /api/v1/permission
|
||||||
|
|
||||||
|
请求参数:
|
||||||
|
{
|
||||||
|
"code": "system:user:add", // 权限编码(必填)
|
||||||
|
"name": "新增用户", // 权限名称(必填)
|
||||||
|
"type": "BUTTON", // 权限类型(必填)
|
||||||
|
"menuId": 2, // 菜单ID(必填)
|
||||||
|
"sort": 1 // 排序(可选)
|
||||||
|
}
|
||||||
|
|
||||||
|
响应结果:
|
||||||
|
{
|
||||||
|
"success": true,
|
||||||
|
"code": 200,
|
||||||
|
"message": "操作成功",
|
||||||
|
"data": {
|
||||||
|
"id": 1,
|
||||||
|
// ... 其他字段同上
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 开发规范
|
||||||
|
|
||||||
|
### 后端开发规范
|
||||||
|
- 遵循DDD设计思想
|
||||||
|
- 统一的异常处理
|
||||||
|
- 统一的返回格式
|
||||||
|
- 完整的单元测试
|
||||||
|
- 详细的接口文档
|
||||||
|
- 规范的代码注释
|
||||||
|
|
||||||
|
详细规范请参考:[后端开发规范](.cursorrules)
|
||||||
|
|
||||||
|
### 前端开发规范
|
||||||
|
- 统一的接口调用方式
|
||||||
|
- 统一的错误处理
|
||||||
|
- 标准的时间格式
|
||||||
|
- 规范的国际化实现
|
||||||
|
|
||||||
|
详细规范请参考:[前端开发规范](frontend.rules)
|
||||||
|
|
||||||
|
## 注意事项
|
||||||
|
|
||||||
|
### 1. 数据库
|
||||||
|
- 所有表必须包含基础字段:
|
||||||
|
```sql
|
||||||
|
id BIGINT PRIMARY KEY # 主键
|
||||||
|
create_time DATETIME # 创建时间
|
||||||
|
create_by VARCHAR(50) # 创建人
|
||||||
|
update_time DATETIME # 更新时间
|
||||||
|
update_by VARCHAR(50) # 更新人
|
||||||
|
version INT # 版本号
|
||||||
|
deleted BOOLEAN # 是否删除
|
||||||
|
```
|
||||||
|
- 使用Flyway进行数据库版本控制
|
||||||
|
- 新建表结构写入V1.0.0__init_schema.sql
|
||||||
|
- 初始数据写入V1.0.1__init_data.sql
|
||||||
|
|
||||||
|
### 2. 接口开发
|
||||||
|
- 所有接口都需要JWT认证
|
||||||
|
- 接口版本统一使用v1
|
||||||
|
- 返回数据格式统一为:
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"success": true,
|
||||||
|
"code": 200,
|
||||||
|
"message": "操作成功",
|
||||||
|
"data": {}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
- 分页查询参数统一使用:
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"pageNum": 1,
|
||||||
|
"pageSize": 10,
|
||||||
|
"sortField": "createTime",
|
||||||
|
"sortOrder": "desc"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. 异常处理
|
||||||
|
- 业务异常统一使用BusinessException
|
||||||
|
- 异常码在ResponseCode中定义
|
||||||
|
- 异常信息在messages.properties中定义
|
||||||
|
- 所有异常都通过GlobalExceptionHandler处理
|
||||||
|
|
||||||
|
### 4. 安全性
|
||||||
|
- 敏感信息(密码、Token等)需要加密存储
|
||||||
|
- 外部系统认证信息需要加密传输
|
||||||
|
- 定期清理过期的Token
|
||||||
|
- 限制API调用频率
|
||||||
|
|
||||||
|
### 5. 测试
|
||||||
|
- 所有Service层代码必须有单元测试
|
||||||
|
- 所有Controller必须有集成测试
|
||||||
|
- 测试覆盖率要求80%以上
|
||||||
|
- 测试数据使用@Sql注入,避免影响生产数据
|
||||||
|
|
||||||
|
## 环境要求
|
||||||
|
- JDK 17+
|
||||||
|
- Maven 3.8+
|
||||||
|
- MySQL 8.0+
|
||||||
|
- Node.js 16+
|
||||||
|
|
||||||
|
## 快速开始
|
||||||
|
|
||||||
|
1. 克隆项目
|
||||||
|
```bash
|
||||||
|
git clone [项目地址]
|
||||||
|
```
|
||||||
|
|
||||||
|
2. 配置数据库
|
||||||
|
```bash
|
||||||
|
# 创建数据库
|
||||||
|
CREATE DATABASE deploy_ease DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
|
||||||
|
```
|
||||||
|
|
||||||
|
3. 修改配置
|
||||||
|
```yaml
|
||||||
|
# application.yml
|
||||||
|
spring:
|
||||||
|
datasource:
|
||||||
|
url: jdbc:mysql://localhost:3306/deploy_ease
|
||||||
|
username: your_username
|
||||||
|
password: your_password
|
||||||
|
```
|
||||||
|
|
||||||
|
4. 启动项目
|
||||||
|
```bash
|
||||||
|
mvn spring-boot:run
|
||||||
|
```
|
||||||
|
|
||||||
|
## 贡献指南
|
||||||
|
1. Fork 项目
|
||||||
|
2. 创建功能分支
|
||||||
|
3. 提交代码
|
||||||
|
4. 创建Pull Request
|
||||||
|
|
||||||
|
## 许可证
|
||||||
|
[MIT License](LICENSE)
|
||||||
|
```
|
||||||
|
|||||||
@ -175,6 +175,21 @@
|
|||||||
<groupId>org.flywaydb</groupId>
|
<groupId>org.flywaydb</groupId>
|
||||||
<artifactId>flyway-mysql</artifactId>
|
<artifactId>flyway-mysql</artifactId>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.junit.jupiter</groupId>
|
||||||
|
<artifactId>junit-jupiter</artifactId>
|
||||||
|
<scope>test</scope>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.mockito</groupId>
|
||||||
|
<artifactId>mockito-junit-jupiter</artifactId>
|
||||||
|
<scope>test</scope>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.assertj</groupId>
|
||||||
|
<artifactId>assertj-core</artifactId>
|
||||||
|
<scope>test</scope>
|
||||||
|
</dependency>
|
||||||
</dependencies>
|
</dependencies>
|
||||||
|
|
||||||
<build>
|
<build>
|
||||||
|
|||||||
@ -0,0 +1,70 @@
|
|||||||
|
package com.qqchen.deploy.backend.api;
|
||||||
|
|
||||||
|
import com.qqchen.deploy.backend.entity.ExternalSystem;
|
||||||
|
import com.qqchen.deploy.backend.framework.api.Response;
|
||||||
|
import com.qqchen.deploy.backend.framework.controller.BaseController;
|
||||||
|
import com.qqchen.deploy.backend.model.ExternalSystemDTO;
|
||||||
|
import com.qqchen.deploy.backend.model.query.ExternalSystemQuery;
|
||||||
|
import com.qqchen.deploy.backend.service.IExternalSystemService;
|
||||||
|
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 jakarta.servlet.http.HttpServletResponse;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.web.bind.annotation.*;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 外部系统管理API控制器
|
||||||
|
* 提供外部系统的REST接口,包括:
|
||||||
|
* 1. 基础CRUD操作
|
||||||
|
* 2. 连接测试
|
||||||
|
* 3. 数据同步
|
||||||
|
* 4. 状态管理
|
||||||
|
*
|
||||||
|
* @author QQChen
|
||||||
|
* @version 1.0.0
|
||||||
|
*/
|
||||||
|
@Slf4j
|
||||||
|
@RestController
|
||||||
|
@RequestMapping("/api/v1/external-system")
|
||||||
|
@Tag(name = "第三方系统管理API", description = "对外提供的第三方系统管理接口")
|
||||||
|
public class ExternalSystemApiController extends BaseController<ExternalSystem, ExternalSystemDTO, Long, ExternalSystemQuery> {
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
private IExternalSystemService externalSystemService;
|
||||||
|
|
||||||
|
@Operation(summary = "测试连接")
|
||||||
|
@GetMapping("/{id}/test-connection")
|
||||||
|
public Response<Boolean> testConnection(
|
||||||
|
@Parameter(description = "系统ID", required = true) @PathVariable Long id
|
||||||
|
) {
|
||||||
|
return Response.success(externalSystemService.testConnection(id));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Operation(summary = "同步数据")
|
||||||
|
@PostMapping("/{id}/sync")
|
||||||
|
public Response<Void> syncData(
|
||||||
|
@Parameter(description = "系统ID", required = true) @PathVariable Long id
|
||||||
|
) {
|
||||||
|
externalSystemService.syncData(id);
|
||||||
|
return Response.success();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Operation(summary = "更新状态")
|
||||||
|
@PutMapping("/{id}/status")
|
||||||
|
public Response<Void> updateStatus(
|
||||||
|
@Parameter(description = "系统ID", required = true) @PathVariable Long id,
|
||||||
|
@Parameter(description = "是否启用", required = true) @RequestParam boolean enabled
|
||||||
|
) {
|
||||||
|
externalSystemService.updateStatus(id, enabled);
|
||||||
|
return Response.success();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void exportData(HttpServletResponse response, List<ExternalSystemDTO> data) {
|
||||||
|
// TODO: 实现导出功能
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,15 @@
|
|||||||
|
package com.qqchen.deploy.backend.converter;
|
||||||
|
|
||||||
|
import com.qqchen.deploy.backend.entity.ExternalSystem;
|
||||||
|
import com.qqchen.deploy.backend.framework.converter.BaseConverter;
|
||||||
|
import com.qqchen.deploy.backend.model.ExternalSystemDTO;
|
||||||
|
import org.mapstruct.Mapper;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 外部系统转换器
|
||||||
|
*/
|
||||||
|
@Mapper(config = BaseConverter.class)
|
||||||
|
public interface ExternalSystemConverter extends BaseConverter<ExternalSystem, ExternalSystemDTO> {
|
||||||
|
// MapStruct会自动实现所有方法,包括从BaseConverter继承的方法
|
||||||
|
// 由于ExternalSystem和ExternalSystemDTO的字段名完全匹配,无需额外的映射配置
|
||||||
|
}
|
||||||
@ -1,10 +1,12 @@
|
|||||||
package com.qqchen.deploy.backend.converter;
|
package com.qqchen.deploy.backend.converter;
|
||||||
|
|
||||||
import com.qqchen.deploy.backend.entity.Menu;
|
import com.qqchen.deploy.backend.entity.Menu;
|
||||||
|
import com.qqchen.deploy.backend.entity.Permission;
|
||||||
import com.qqchen.deploy.backend.framework.converter.BaseConverter;
|
import com.qqchen.deploy.backend.framework.converter.BaseConverter;
|
||||||
import com.qqchen.deploy.backend.model.MenuDTO;
|
import com.qqchen.deploy.backend.model.MenuDTO;
|
||||||
import com.qqchen.deploy.backend.model.response.MenuPermissionTreeResponse;
|
import com.qqchen.deploy.backend.model.response.MenuPermissionTreeResponse;
|
||||||
import com.qqchen.deploy.backend.model.response.MenuResponse;
|
import com.qqchen.deploy.backend.model.response.MenuResponse;
|
||||||
|
import com.qqchen.deploy.backend.model.response.PermissionResponse;
|
||||||
import org.mapstruct.Mapper;
|
import org.mapstruct.Mapper;
|
||||||
import org.mapstruct.Mapping;
|
import org.mapstruct.Mapping;
|
||||||
|
|
||||||
@ -25,4 +27,8 @@ public interface MenuConverter extends BaseConverter<Menu, MenuDTO> {
|
|||||||
List<MenuResponse> toResponseList(List<MenuDTO> dtoList);
|
List<MenuResponse> toResponseList(List<MenuDTO> dtoList);
|
||||||
|
|
||||||
MenuPermissionTreeResponse toMenuPermissionResponse(Menu menu);
|
MenuPermissionTreeResponse toMenuPermissionResponse(Menu menu);
|
||||||
|
|
||||||
|
PermissionResponse toPermissionResponse(Permission permission);
|
||||||
|
|
||||||
|
List<PermissionResponse> toPermissionResponseList(List<Permission> permissions);
|
||||||
}
|
}
|
||||||
@ -0,0 +1,120 @@
|
|||||||
|
package com.qqchen.deploy.backend.entity;
|
||||||
|
|
||||||
|
import com.qqchen.deploy.backend.framework.annotation.LogicDelete;
|
||||||
|
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 = "sys_external_system")
|
||||||
|
@LogicDelete
|
||||||
|
public class ExternalSystem extends Entity<Long> {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 系统名称
|
||||||
|
*/
|
||||||
|
@Column(nullable = false)
|
||||||
|
private String name;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 系统类型:JENKINS/GIT/ZENTAO等
|
||||||
|
*/
|
||||||
|
@Column(nullable = false)
|
||||||
|
@Enumerated(EnumType.STRING)
|
||||||
|
private SystemType type;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 系统访问地址
|
||||||
|
*/
|
||||||
|
@Column(nullable = false)
|
||||||
|
private String url;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 备注说明
|
||||||
|
*/
|
||||||
|
@Column(columnDefinition = "TEXT")
|
||||||
|
private String remark;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 排序
|
||||||
|
*/
|
||||||
|
private Integer sort;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 是否启用
|
||||||
|
*/
|
||||||
|
private Boolean enabled = true;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 认证方式:BASIC/TOKEN/OAUTH等
|
||||||
|
*/
|
||||||
|
@Column(name = "auth_type", nullable = false)
|
||||||
|
@Enumerated(EnumType.STRING)
|
||||||
|
private AuthType authType;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 用户名
|
||||||
|
*/
|
||||||
|
private String username;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 密码/密钥
|
||||||
|
*/
|
||||||
|
private String password;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 访问令牌
|
||||||
|
*/
|
||||||
|
private String token;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 最后同步状态
|
||||||
|
*/
|
||||||
|
@Column(name = "sync_status")
|
||||||
|
@Enumerated(EnumType.STRING)
|
||||||
|
private SyncStatus syncStatus;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 最后同步时间
|
||||||
|
*/
|
||||||
|
@Column(name = "last_sync_time")
|
||||||
|
private LocalDateTime lastSyncTime;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 系统特有配置,JSON格式
|
||||||
|
*/
|
||||||
|
@Column(columnDefinition = "JSON")
|
||||||
|
private String config;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 系统类型枚举
|
||||||
|
*/
|
||||||
|
public enum SystemType {
|
||||||
|
JENKINS,
|
||||||
|
GIT,
|
||||||
|
ZENTAO
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 认证类型枚举
|
||||||
|
*/
|
||||||
|
public enum AuthType {
|
||||||
|
BASIC,
|
||||||
|
TOKEN,
|
||||||
|
OAUTH
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 同步状态枚举
|
||||||
|
*/
|
||||||
|
public enum SyncStatus {
|
||||||
|
SUCCESS,
|
||||||
|
FAILED,
|
||||||
|
RUNNING
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -75,10 +75,4 @@ public class Menu extends Entity<Long> {
|
|||||||
@ManyToMany(mappedBy = "menus")
|
@ManyToMany(mappedBy = "menus")
|
||||||
private Set<Role> roles = new HashSet<>();
|
private Set<Role> roles = new HashSet<>();
|
||||||
|
|
||||||
@ManyToMany(fetch = FetchType.LAZY)
|
|
||||||
@JoinTable(
|
|
||||||
name = "sys_permission",
|
|
||||||
joinColumns = @JoinColumn(name = "menu_id")
|
|
||||||
)
|
|
||||||
private Set<Permission> permissions = new HashSet<>();
|
|
||||||
}
|
}
|
||||||
@ -63,7 +63,14 @@ public enum ResponseCode {
|
|||||||
PERMISSION_CODE_EXISTS(2401, "permission.code.exists"),
|
PERMISSION_CODE_EXISTS(2401, "permission.code.exists"),
|
||||||
PERMISSION_NAME_EXISTS(2402, "permission.name.exists"),
|
PERMISSION_NAME_EXISTS(2402, "permission.name.exists"),
|
||||||
PERMISSION_ALREADY_ASSIGNED(2403, "permission.already.assigned"),
|
PERMISSION_ALREADY_ASSIGNED(2403, "permission.already.assigned"),
|
||||||
PERMISSION_ASSIGN_FAILED(2404, "permission.assign.failed");
|
PERMISSION_ASSIGN_FAILED(2404, "permission.assign.failed"),
|
||||||
|
|
||||||
|
// 第三方系统相关错误码 (2500-2599)
|
||||||
|
EXTERNAL_SYSTEM_NAME_EXISTS(2500, "external.system.name.exists"),
|
||||||
|
EXTERNAL_SYSTEM_TYPE_URL_EXISTS(2501, "external.system.type.url.exists"),
|
||||||
|
EXTERNAL_SYSTEM_DISABLED(2502, "external.system.disabled"),
|
||||||
|
EXTERNAL_SYSTEM_SYNC_FAILED(2503, "external.system.sync.failed"),
|
||||||
|
EXTERNAL_SYSTEM_TYPE_NOT_SUPPORTED(2504, "external.system.type.not.supported");
|
||||||
|
|
||||||
private final int code;
|
private final int code;
|
||||||
private final String messageKey; // 国际化消息key
|
private final String messageKey; // 国际化消息key
|
||||||
|
|||||||
@ -0,0 +1,30 @@
|
|||||||
|
package com.qqchen.deploy.backend.integration;
|
||||||
|
|
||||||
|
import com.qqchen.deploy.backend.entity.ExternalSystem;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 第三方系统集成接口
|
||||||
|
* 定义与外部系统交互的标准接口,包括:
|
||||||
|
* 1. 连接测试
|
||||||
|
* 2. 系统类型识别
|
||||||
|
*
|
||||||
|
* @author QQChen
|
||||||
|
* @version 1.0.0
|
||||||
|
*/
|
||||||
|
public interface IExternalSystemIntegration {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 测试连接
|
||||||
|
*
|
||||||
|
* @param system 系统配置
|
||||||
|
* @return 是否连接成功
|
||||||
|
*/
|
||||||
|
boolean testConnection(ExternalSystem system);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取支持的系统类型
|
||||||
|
*
|
||||||
|
* @return 系统类型
|
||||||
|
*/
|
||||||
|
ExternalSystem.SystemType getSystemType();
|
||||||
|
}
|
||||||
@ -0,0 +1,44 @@
|
|||||||
|
package com.qqchen.deploy.backend.integration.impl;
|
||||||
|
|
||||||
|
import com.qqchen.deploy.backend.entity.ExternalSystem;
|
||||||
|
import com.qqchen.deploy.backend.integration.IExternalSystemIntegration;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.http.*;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
import org.springframework.web.client.RestTemplate;
|
||||||
|
|
||||||
|
@Slf4j
|
||||||
|
@Service
|
||||||
|
public class GitIntegration implements IExternalSystemIntegration {
|
||||||
|
|
||||||
|
private final RestTemplate restTemplate = new RestTemplate();
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean testConnection(ExternalSystem system) {
|
||||||
|
try {
|
||||||
|
String url = system.getUrl() + "/api/v4/version"; // GitLab API
|
||||||
|
HttpHeaders headers = new HttpHeaders();
|
||||||
|
if (system.getToken() != null) {
|
||||||
|
headers.set("PRIVATE-TOKEN", system.getToken());
|
||||||
|
}
|
||||||
|
|
||||||
|
HttpEntity<String> entity = new HttpEntity<>(headers);
|
||||||
|
ResponseEntity<String> response = restTemplate.exchange(
|
||||||
|
url,
|
||||||
|
HttpMethod.GET,
|
||||||
|
entity,
|
||||||
|
String.class
|
||||||
|
);
|
||||||
|
|
||||||
|
return response.getStatusCode() == HttpStatus.OK;
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("Failed to connect to Git: {}", system.getUrl(), e);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ExternalSystem.SystemType getSystemType() {
|
||||||
|
return ExternalSystem.SystemType.GIT;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,57 @@
|
|||||||
|
package com.qqchen.deploy.backend.integration.impl;
|
||||||
|
|
||||||
|
import com.qqchen.deploy.backend.entity.ExternalSystem;
|
||||||
|
import com.qqchen.deploy.backend.integration.IExternalSystemIntegration;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.http.*;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
import org.springframework.web.client.RestTemplate;
|
||||||
|
|
||||||
|
import java.util.Base64;
|
||||||
|
|
||||||
|
@Slf4j
|
||||||
|
@Service
|
||||||
|
public class JenkinsIntegration implements IExternalSystemIntegration {
|
||||||
|
|
||||||
|
private final RestTemplate restTemplate = new RestTemplate();
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean testConnection(ExternalSystem system) {
|
||||||
|
try {
|
||||||
|
String url = system.getUrl() + "/api/json";
|
||||||
|
HttpHeaders headers = createHeaders(system);
|
||||||
|
HttpEntity<String> entity = new HttpEntity<>(headers);
|
||||||
|
|
||||||
|
ResponseEntity<String> response = restTemplate.exchange(
|
||||||
|
url,
|
||||||
|
HttpMethod.GET,
|
||||||
|
entity,
|
||||||
|
String.class
|
||||||
|
);
|
||||||
|
|
||||||
|
return response.getStatusCode() == HttpStatus.OK;
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("Failed to connect to Jenkins: {}", system.getUrl(), e);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private HttpHeaders createHeaders(ExternalSystem system) {
|
||||||
|
HttpHeaders headers = new HttpHeaders();
|
||||||
|
switch (system.getAuthType()) {
|
||||||
|
case BASIC -> {
|
||||||
|
String auth = system.getUsername() + ":" + system.getPassword();
|
||||||
|
byte[] encodedAuth = Base64.getEncoder().encode(auth.getBytes());
|
||||||
|
headers.set("Authorization", "Basic " + new String(encodedAuth));
|
||||||
|
}
|
||||||
|
case TOKEN -> headers.set("Authorization", "Bearer " + system.getToken());
|
||||||
|
case OAUTH -> headers.set("Authorization", "Bearer " + system.getToken());
|
||||||
|
}
|
||||||
|
return headers;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ExternalSystem.SystemType getSystemType() {
|
||||||
|
return ExternalSystem.SystemType.JENKINS;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,47 @@
|
|||||||
|
package com.qqchen.deploy.backend.model;
|
||||||
|
|
||||||
|
import com.qqchen.deploy.backend.entity.ExternalSystem.SystemType;
|
||||||
|
import com.qqchen.deploy.backend.entity.ExternalSystem.AuthType;
|
||||||
|
import com.qqchen.deploy.backend.entity.ExternalSystem.SyncStatus;
|
||||||
|
import com.qqchen.deploy.backend.framework.dto.BaseDTO;
|
||||||
|
import jakarta.validation.constraints.NotBlank;
|
||||||
|
import jakarta.validation.constraints.NotNull;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.EqualsAndHashCode;
|
||||||
|
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
@EqualsAndHashCode(callSuper = true)
|
||||||
|
public class ExternalSystemDTO extends BaseDTO {
|
||||||
|
|
||||||
|
@NotBlank(message = "系统名称不能为空")
|
||||||
|
private String name;
|
||||||
|
|
||||||
|
@NotNull(message = "系统类型不能为空")
|
||||||
|
private SystemType type;
|
||||||
|
|
||||||
|
@NotBlank(message = "系统访问地址不能为空")
|
||||||
|
private String url;
|
||||||
|
|
||||||
|
private String remark;
|
||||||
|
|
||||||
|
private Integer sort;
|
||||||
|
|
||||||
|
private Boolean enabled = true;
|
||||||
|
|
||||||
|
@NotNull(message = "认证方式不能为空")
|
||||||
|
private AuthType authType;
|
||||||
|
|
||||||
|
private String username;
|
||||||
|
|
||||||
|
private String password;
|
||||||
|
|
||||||
|
private String token;
|
||||||
|
|
||||||
|
private SyncStatus syncStatus;
|
||||||
|
|
||||||
|
private LocalDateTime lastSyncTime;
|
||||||
|
|
||||||
|
private String config;
|
||||||
|
}
|
||||||
@ -0,0 +1,32 @@
|
|||||||
|
package com.qqchen.deploy.backend.model.query;
|
||||||
|
|
||||||
|
import com.qqchen.deploy.backend.entity.ExternalSystem.SystemType;
|
||||||
|
import com.qqchen.deploy.backend.framework.annotation.QueryField;
|
||||||
|
import com.qqchen.deploy.backend.framework.enums.QueryType;
|
||||||
|
import com.qqchen.deploy.backend.framework.query.BaseQuery;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.EqualsAndHashCode;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 外部系统查询对象
|
||||||
|
* 用于外部系统列表查询,支持以下查询条件:
|
||||||
|
* 1. 系统名称(模糊匹配)
|
||||||
|
* 2. 系统类型(精确匹配)
|
||||||
|
* 3. 启用状态(精确匹配)
|
||||||
|
*
|
||||||
|
* @author QQChen
|
||||||
|
* @version 1.0.0
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
@EqualsAndHashCode(callSuper = true)
|
||||||
|
public class ExternalSystemQuery extends BaseQuery {
|
||||||
|
|
||||||
|
@QueryField(field = "name", type = QueryType.LIKE)
|
||||||
|
private String name;
|
||||||
|
|
||||||
|
@QueryField(field = "type")
|
||||||
|
private SystemType type;
|
||||||
|
|
||||||
|
@QueryField(field = "enabled")
|
||||||
|
private Boolean enabled;
|
||||||
|
}
|
||||||
@ -1,16 +1,30 @@
|
|||||||
package com.qqchen.deploy.backend.model.response;
|
package com.qqchen.deploy.backend.model.response;
|
||||||
|
|
||||||
import com.qqchen.deploy.backend.model.PermissionDTO;
|
import com.fasterxml.jackson.annotation.JsonIgnore;
|
||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
import lombok.EqualsAndHashCode;
|
import lombok.EqualsAndHashCode;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
@Data
|
@Data
|
||||||
@EqualsAndHashCode(callSuper = true)
|
@EqualsAndHashCode(callSuper = true)
|
||||||
public class MenuPermissionTreeResponse extends MenuResponse {
|
public class MenuPermissionTreeResponse extends MenuResponse {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 菜单下的权限列表
|
* 子菜单列表(覆盖父类的children,因为类型不同)
|
||||||
|
*/
|
||||||
|
@JsonIgnore
|
||||||
|
@Override
|
||||||
|
public List<MenuResponse> getChildren() {
|
||||||
|
return super.getChildren();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 子菜单列表
|
||||||
|
*/
|
||||||
|
private List<MenuPermissionTreeResponse> permissionChildren;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 权限列表
|
||||||
*/
|
*/
|
||||||
private List<PermissionResponse> permissions;
|
private List<PermissionResponse> permissions;
|
||||||
}
|
}
|
||||||
@ -1,21 +1,31 @@
|
|||||||
package com.qqchen.deploy.backend.model.response;
|
package com.qqchen.deploy.backend.model.response;
|
||||||
|
|
||||||
import com.qqchen.deploy.backend.framework.dto.BaseResponse;
|
|
||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
|
|
||||||
@Data
|
@Data
|
||||||
public class PermissionResponse extends BaseResponse {
|
public class PermissionResponse {
|
||||||
|
/**
|
||||||
private Long menuId;
|
* 权限ID
|
||||||
|
*/
|
||||||
private String menuName;
|
private Long id;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 权限编码
|
||||||
|
*/
|
||||||
private String code;
|
private String code;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 权限名称
|
||||||
|
*/
|
||||||
private String name;
|
private String name;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 权限类型
|
||||||
|
*/
|
||||||
private String type;
|
private String type;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 排序
|
||||||
|
*/
|
||||||
private Integer sort;
|
private Integer sort;
|
||||||
|
|
||||||
}
|
}
|
||||||
@ -0,0 +1,26 @@
|
|||||||
|
package com.qqchen.deploy.backend.repository;
|
||||||
|
|
||||||
|
import com.qqchen.deploy.backend.entity.ExternalSystem;
|
||||||
|
import com.qqchen.deploy.backend.framework.repository.IBaseRepository;
|
||||||
|
import org.springframework.stereotype.Repository;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
@Repository
|
||||||
|
public interface IExternalSystemRepository extends IBaseRepository<ExternalSystem, Long> {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 检查系统名称是否存在
|
||||||
|
*/
|
||||||
|
boolean existsByNameAndDeletedFalse(String name);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 检查系统类型和URL组合是否存在
|
||||||
|
*/
|
||||||
|
boolean existsByTypeAndUrlAndDeletedFalse(ExternalSystem.SystemType type, String url);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 查询所有未删除的系统,按排序字段排序
|
||||||
|
*/
|
||||||
|
List<ExternalSystem> findByDeletedFalseOrderBySort();
|
||||||
|
}
|
||||||
@ -15,4 +15,27 @@ public interface IPermissionRepository extends IBaseRepository<Permission, Long>
|
|||||||
List<Permission> findAllEnabledOrderByMenuAndSort();
|
List<Permission> findAllEnabledOrderByMenuAndSort();
|
||||||
|
|
||||||
List<Permission> findByIdIn(List<Long> ids);
|
List<Permission> findByIdIn(List<Long> ids);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 查询所有未删除的权限,按排序字段排序
|
||||||
|
*
|
||||||
|
* @return 权限列表
|
||||||
|
*/
|
||||||
|
List<Permission> findAllByDeletedFalseOrderBySort();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据菜单ID查询权限列表
|
||||||
|
*
|
||||||
|
* @param menuId 菜单ID
|
||||||
|
* @return 权限列表
|
||||||
|
*/
|
||||||
|
List<Permission> findByMenuIdAndDeletedFalseOrderBySort(Long menuId);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 检查权限编码是否存在
|
||||||
|
*
|
||||||
|
* @param code 权限编码
|
||||||
|
* @return 是否存在
|
||||||
|
*/
|
||||||
|
boolean existsByCodeAndDeletedFalse(String code);
|
||||||
}
|
}
|
||||||
@ -0,0 +1,31 @@
|
|||||||
|
package com.qqchen.deploy.backend.service;
|
||||||
|
|
||||||
|
import com.qqchen.deploy.backend.entity.ExternalSystem;
|
||||||
|
import com.qqchen.deploy.backend.framework.service.IBaseService;
|
||||||
|
import com.qqchen.deploy.backend.model.ExternalSystemDTO;
|
||||||
|
|
||||||
|
public interface IExternalSystemService extends IBaseService<ExternalSystem, ExternalSystemDTO, Long> {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 测试连接
|
||||||
|
*
|
||||||
|
* @param id 系统ID
|
||||||
|
* @return 是否连接成功
|
||||||
|
*/
|
||||||
|
boolean testConnection(Long id);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 同步系统数据
|
||||||
|
*
|
||||||
|
* @param id 系统ID
|
||||||
|
*/
|
||||||
|
void syncData(Long id);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 启用/禁用系统
|
||||||
|
*
|
||||||
|
* @param id 系统ID
|
||||||
|
* @param enabled 是否启用
|
||||||
|
*/
|
||||||
|
void updateStatus(Long id, boolean enabled);
|
||||||
|
}
|
||||||
@ -22,10 +22,6 @@ public interface IMenuService extends IBaseService<Menu, MenuDTO, Long> {
|
|||||||
*/
|
*/
|
||||||
List<MenuResponse> getUserMenus();
|
List<MenuResponse> getUserMenus();
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取菜单树
|
|
||||||
*/
|
|
||||||
List<MenuDTO> getTree();
|
|
||||||
|
|
||||||
List<MenuPermissionTreeResponse> getPermissionTree();
|
List<MenuPermissionTreeResponse> getPermissionTree();
|
||||||
|
|
||||||
|
|||||||
@ -0,0 +1,153 @@
|
|||||||
|
package com.qqchen.deploy.backend.service.impl;
|
||||||
|
|
||||||
|
import com.qqchen.deploy.backend.entity.ExternalSystem;
|
||||||
|
import com.qqchen.deploy.backend.framework.annotation.ServiceType;
|
||||||
|
import com.qqchen.deploy.backend.framework.enums.ResponseCode;
|
||||||
|
import com.qqchen.deploy.backend.framework.exception.BusinessException;
|
||||||
|
import com.qqchen.deploy.backend.framework.exception.UniqueConstraintException;
|
||||||
|
import com.qqchen.deploy.backend.framework.service.impl.BaseServiceImpl;
|
||||||
|
import com.qqchen.deploy.backend.integration.IExternalSystemIntegration;
|
||||||
|
import com.qqchen.deploy.backend.model.ExternalSystemDTO;
|
||||||
|
import com.qqchen.deploy.backend.repository.IExternalSystemRepository;
|
||||||
|
import com.qqchen.deploy.backend.service.IExternalSystemService;
|
||||||
|
import jakarta.annotation.PostConstruct;
|
||||||
|
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.Map;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
import static com.qqchen.deploy.backend.framework.annotation.ServiceType.Type.DATABASE;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 外部系统服务实现类
|
||||||
|
* 主要功能:
|
||||||
|
* 1. 外部系统的CRUD操作
|
||||||
|
* 2. 系统连接测试
|
||||||
|
* 3. 系统数据同步
|
||||||
|
* 4. 系统状态管理
|
||||||
|
*
|
||||||
|
* @author QQChen
|
||||||
|
* @version 1.0.0
|
||||||
|
*/
|
||||||
|
@Slf4j
|
||||||
|
@Service
|
||||||
|
@ServiceType(DATABASE)
|
||||||
|
public class ExternalSystemServiceImpl extends BaseServiceImpl<ExternalSystem, ExternalSystemDTO, Long>
|
||||||
|
implements IExternalSystemService {
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
private IExternalSystemRepository externalSystemRepository;
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
private List<IExternalSystemIntegration> systemIntegrations;
|
||||||
|
|
||||||
|
private Map<ExternalSystem.SystemType, IExternalSystemIntegration> integrationMap;
|
||||||
|
|
||||||
|
@PostConstruct
|
||||||
|
public void init() {
|
||||||
|
integrationMap = systemIntegrations.stream()
|
||||||
|
.collect(Collectors.toMap(
|
||||||
|
IExternalSystemIntegration::getSystemType,
|
||||||
|
integration -> integration
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void validateUniqueConstraints(ExternalSystemDTO dto) {
|
||||||
|
// 检查名称唯一性
|
||||||
|
if (externalSystemRepository.existsByNameAndDeletedFalse(dto.getName())) {
|
||||||
|
throw new UniqueConstraintException(ResponseCode.EXTERNAL_SYSTEM_NAME_EXISTS, "name", dto.getName());
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查类型+URL组合唯一性
|
||||||
|
if (externalSystemRepository.existsByTypeAndUrlAndDeletedFalse(dto.getType(), dto.getUrl())) {
|
||||||
|
throw new UniqueConstraintException(ResponseCode.EXTERNAL_SYSTEM_TYPE_URL_EXISTS,
|
||||||
|
"type and url", dto.getType() + ":" + dto.getUrl());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Transactional(readOnly = true)
|
||||||
|
public boolean testConnection(Long id) {
|
||||||
|
ExternalSystem system = findEntityById(id);
|
||||||
|
if (!system.getEnabled()) {
|
||||||
|
throw new BusinessException(ResponseCode.EXTERNAL_SYSTEM_DISABLED);
|
||||||
|
}
|
||||||
|
|
||||||
|
IExternalSystemIntegration integration = integrationMap.get(system.getType());
|
||||||
|
if (integration == null) {
|
||||||
|
throw new BusinessException(ResponseCode.EXTERNAL_SYSTEM_TYPE_NOT_SUPPORTED);
|
||||||
|
}
|
||||||
|
|
||||||
|
return integration.testConnection(system);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Transactional
|
||||||
|
public void syncData(Long id) {
|
||||||
|
ExternalSystem system = findEntityById(id);
|
||||||
|
if (!system.getEnabled()) {
|
||||||
|
throw new BusinessException(ResponseCode.EXTERNAL_SYSTEM_DISABLED);
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
system.setSyncStatus(ExternalSystem.SyncStatus.RUNNING);
|
||||||
|
externalSystemRepository.save(system);
|
||||||
|
|
||||||
|
// TODO: 根据不同的系统类型调用不同的同步方法
|
||||||
|
switch (system.getType()) {
|
||||||
|
case JENKINS -> syncJenkinsData(system);
|
||||||
|
case GIT -> syncGitData(system);
|
||||||
|
case ZENTAO -> syncZentaoData(system);
|
||||||
|
}
|
||||||
|
|
||||||
|
system.setSyncStatus(ExternalSystem.SyncStatus.SUCCESS);
|
||||||
|
system.setLastSyncTime(LocalDateTime.now());
|
||||||
|
} catch (Exception e) {
|
||||||
|
system.setSyncStatus(ExternalSystem.SyncStatus.FAILED);
|
||||||
|
log.error("Sync data failed for external system: {}", system.getName(), e);
|
||||||
|
throw new BusinessException(ResponseCode.EXTERNAL_SYSTEM_SYNC_FAILED);
|
||||||
|
} finally {
|
||||||
|
externalSystemRepository.save(system);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Transactional
|
||||||
|
public void updateStatus(Long id, boolean enabled) {
|
||||||
|
ExternalSystem system = findEntityById(id);
|
||||||
|
system.setEnabled(enabled);
|
||||||
|
externalSystemRepository.save(system);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 私有辅助方法,后续实现具体的连接测试逻辑
|
||||||
|
private void testJenkinsConnection(ExternalSystem system) {
|
||||||
|
// TODO: 实现Jenkins连接测试
|
||||||
|
}
|
||||||
|
|
||||||
|
private void testGitConnection(ExternalSystem system) {
|
||||||
|
// TODO: 实现Git连接测试
|
||||||
|
}
|
||||||
|
|
||||||
|
private void testZentaoConnection(ExternalSystem system) {
|
||||||
|
// TODO: 实现禅道连接测试
|
||||||
|
}
|
||||||
|
|
||||||
|
// 私有辅助方法,后续实现具体的数据同步逻辑
|
||||||
|
private void syncJenkinsData(ExternalSystem system) {
|
||||||
|
// TODO: 实现Jenkins数据同步
|
||||||
|
}
|
||||||
|
|
||||||
|
private void syncGitData(ExternalSystem system) {
|
||||||
|
// TODO: 实现Git数据同步
|
||||||
|
}
|
||||||
|
|
||||||
|
private void syncZentaoData(ExternalSystem system) {
|
||||||
|
// TODO: 实现禅道数据同步
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -25,6 +25,7 @@ import java.util.ArrayList;
|
|||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
import java.util.Comparator;
|
import java.util.Comparator;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
|
import java.util.Optional;
|
||||||
|
|
||||||
import static com.qqchen.deploy.backend.framework.annotation.ServiceType.Type.DATABASE;
|
import static com.qqchen.deploy.backend.framework.annotation.ServiceType.Type.DATABASE;
|
||||||
|
|
||||||
@ -42,9 +43,6 @@ public class MenuServiceImpl extends BaseServiceImpl<Menu, MenuDTO, Long> implem
|
|||||||
@Resource
|
@Resource
|
||||||
private MenuConverter menuConverter;
|
private MenuConverter menuConverter;
|
||||||
|
|
||||||
@Resource
|
|
||||||
private PermissionConverter permissionConverter;
|
|
||||||
|
|
||||||
@Resource
|
@Resource
|
||||||
private IUserService userService;
|
private IUserService userService;
|
||||||
|
|
||||||
@ -78,23 +76,56 @@ public class MenuServiceImpl extends BaseServiceImpl<Menu, MenuDTO, Long> implem
|
|||||||
return menuConverter.toResponseList(menuTree);
|
return menuConverter.toResponseList(menuTree);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public List<MenuDTO> getTree() {
|
|
||||||
List<Menu> menus = menuRepository.findByDeletedFalseOrderBySort();
|
|
||||||
return buildTree(menus);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public List<MenuPermissionTreeResponse> getPermissionTree() {
|
public List<MenuPermissionTreeResponse> getPermissionTree() {
|
||||||
|
// 1. 获取所有菜单
|
||||||
List<Menu> menus = menuRepository.findByDeletedFalseOrderBySort();
|
List<Menu> menus = menuRepository.findByDeletedFalseOrderBySort();
|
||||||
List<Permission> permissions = permissionRepository.findAllEnabledOrderByMenuAndSort();
|
|
||||||
List<MenuPermissionTreeResponse> tree = new ArrayList<>();
|
// 2. 获取所有权限
|
||||||
menus.forEach(menu -> {
|
List<Permission> permissions = permissionRepository.findAllByDeletedFalseOrderBySort();
|
||||||
MenuPermissionTreeResponse menuPermissionResponse = menuConverter.toMenuPermissionResponse(menu);
|
|
||||||
List<Permission> list = permissions.stream().filter(permission -> permission.getMenuId().equals(menu.getId())).toList();
|
// 3. 构建权限Map (menuId -> List<Permission>)
|
||||||
menuPermissionResponse.setPermissions(permissionConverter.toResponseList(list));
|
Map<Long, List<Permission>> permissionMap = permissions.stream()
|
||||||
|
.collect(Collectors.groupingBy(Permission::getMenuId));
|
||||||
|
|
||||||
|
// 4. 构建菜单树
|
||||||
|
return buildPermissionTree(menus, permissionMap);
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<MenuPermissionTreeResponse> buildPermissionTree(List<Menu> menus,
|
||||||
|
Map<Long, List<Permission>> permissionMap) {
|
||||||
|
// 1. 转换所有菜单为树形节点
|
||||||
|
List<MenuPermissionTreeResponse> nodes = menus.stream()
|
||||||
|
.map(menu -> convertToTreeNode(menu, permissionMap))
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
|
||||||
|
// 2. 构建父子关系
|
||||||
|
Map<Long, List<MenuPermissionTreeResponse>> childrenMap = nodes.stream()
|
||||||
|
.collect(Collectors.groupingBy(node ->
|
||||||
|
Optional.ofNullable(node.getParentId()).orElse(0L)));
|
||||||
|
|
||||||
|
// 3. 设置子节点并排序
|
||||||
|
nodes.forEach(node -> {
|
||||||
|
List<MenuPermissionTreeResponse> children = childrenMap.getOrDefault(node.getId(), new ArrayList<>());
|
||||||
|
children.sort(Comparator.comparing(MenuPermissionTreeResponse::getSort));
|
||||||
|
node.setPermissionChildren(children);
|
||||||
});
|
});
|
||||||
return null;
|
|
||||||
|
// 4. 返回排序后的顶层节点
|
||||||
|
List<MenuPermissionTreeResponse> rootNodes = childrenMap.getOrDefault(0L, new ArrayList<>());
|
||||||
|
rootNodes.sort(Comparator.comparing(MenuPermissionTreeResponse::getSort));
|
||||||
|
return rootNodes;
|
||||||
|
}
|
||||||
|
|
||||||
|
private MenuPermissionTreeResponse convertToTreeNode(Menu menu,
|
||||||
|
Map<Long, List<Permission>> permissionMap) {
|
||||||
|
MenuPermissionTreeResponse node = menuConverter.toMenuPermissionResponse(menu);
|
||||||
|
|
||||||
|
// 设置权限列表
|
||||||
|
List<Permission> permissions = permissionMap.getOrDefault(menu.getId(), new ArrayList<>());
|
||||||
|
node.setPermissions(menuConverter.toPermissionResponseList(permissions));
|
||||||
|
|
||||||
|
return node;
|
||||||
}
|
}
|
||||||
|
|
||||||
private List<MenuDTO> buildTree(List<Menu> menus) {
|
private List<MenuDTO> buildTree(List<Menu> menus) {
|
||||||
@ -158,10 +189,8 @@ public class MenuServiceImpl extends BaseServiceImpl<Menu, MenuDTO, Long> implem
|
|||||||
if (menus == null || menus.isEmpty()) {
|
if (menus == null || menus.isEmpty()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 根据sort字段排序
|
// 根据sort字段排序
|
||||||
menus.sort(Comparator.comparing(MenuDTO::getSort));
|
menus.sort(Comparator.comparing(MenuDTO::getSort));
|
||||||
|
|
||||||
// 递归排序子菜单
|
// 递归排序子菜单
|
||||||
for (MenuDTO menu : menus) {
|
for (MenuDTO menu : menus) {
|
||||||
if (menu.getChildren() != null && !menu.getChildren().isEmpty()) {
|
if (menu.getChildren() != null && !menu.getChildren().isEmpty()) {
|
||||||
|
|||||||
@ -1,258 +1,275 @@
|
|||||||
-- 创建租户表
|
-- --------------------------------------------------------------------------------------
|
||||||
|
-- 系统基础表
|
||||||
|
-- --------------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
-- 租户表
|
||||||
CREATE TABLE IF NOT EXISTS sys_tenant (
|
CREATE TABLE IF NOT EXISTS sys_tenant (
|
||||||
id BIGINT AUTO_INCREMENT PRIMARY KEY,
|
id BIGINT AUTO_INCREMENT PRIMARY KEY COMMENT '主键ID',
|
||||||
create_by VARCHAR(255) NULL,
|
create_by VARCHAR(255) NULL COMMENT '创建人',
|
||||||
create_time DATETIME(6) NULL,
|
create_time DATETIME(6) NULL COMMENT '创建时间',
|
||||||
deleted BIT NOT NULL DEFAULT 0,
|
deleted BIT NOT NULL DEFAULT 0 COMMENT '是否删除(0:未删除,1:已删除)',
|
||||||
update_by VARCHAR(255) NULL,
|
update_by VARCHAR(255) NULL COMMENT '更新人',
|
||||||
update_time DATETIME(6) NULL,
|
update_time DATETIME(6) NULL COMMENT '更新时间',
|
||||||
version INT NOT NULL DEFAULT 0,
|
version INT NOT NULL DEFAULT 0 COMMENT '乐观锁版本号',
|
||||||
address VARCHAR(255) NULL,
|
|
||||||
code VARCHAR(255) NOT NULL,
|
|
||||||
contact_name VARCHAR(255) NULL,
|
|
||||||
contact_phone VARCHAR(255) NULL,
|
|
||||||
email VARCHAR(255) NULL,
|
|
||||||
enabled BIT NOT NULL DEFAULT 1,
|
|
||||||
name VARCHAR(255) NOT NULL,
|
|
||||||
CONSTRAINT UK_tenant_code UNIQUE (code)
|
|
||||||
);
|
|
||||||
|
|
||||||
-- 创建部门表
|
|
||||||
CREATE TABLE sys_department (
|
|
||||||
id BIGINT AUTO_INCREMENT PRIMARY KEY,
|
|
||||||
create_by VARCHAR(255) NULL,
|
|
||||||
create_time DATETIME(6) NULL,
|
|
||||||
deleted BIT NOT NULL DEFAULT 0,
|
|
||||||
update_by VARCHAR(255) NULL,
|
|
||||||
update_time DATETIME(6) NULL,
|
|
||||||
version INT NOT NULL DEFAULT 0,
|
|
||||||
code VARCHAR(255) NOT NULL,
|
|
||||||
description VARCHAR(255) NULL,
|
|
||||||
enabled BIT NOT NULL DEFAULT 1,
|
|
||||||
leader_id BIGINT NULL,
|
|
||||||
leader_name VARCHAR(255) NULL,
|
|
||||||
name VARCHAR(255) NOT NULL,
|
|
||||||
parent_id BIGINT NULL,
|
|
||||||
sort INT NOT NULL DEFAULT 0,
|
|
||||||
CONSTRAINT UK_department_code UNIQUE (code)
|
|
||||||
);
|
|
||||||
|
|
||||||
-- 创建用户表
|
|
||||||
CREATE TABLE IF NOT EXISTS sys_user (
|
|
||||||
id BIGINT AUTO_INCREMENT PRIMARY KEY,
|
|
||||||
create_by VARCHAR(255) NULL,
|
|
||||||
create_time DATETIME(6) NULL,
|
|
||||||
deleted BIT NOT NULL DEFAULT 0,
|
|
||||||
update_by VARCHAR(255) NULL,
|
|
||||||
update_time DATETIME(6) NULL,
|
|
||||||
version INT NOT NULL DEFAULT 0,
|
|
||||||
email VARCHAR(255) NULL,
|
|
||||||
enabled BIT NOT NULL DEFAULT 1,
|
|
||||||
nickname VARCHAR(50) NULL,
|
|
||||||
password VARCHAR(255) NOT NULL,
|
|
||||||
phone VARCHAR(255) NULL,
|
|
||||||
username VARCHAR(255) NOT NULL,
|
|
||||||
department_id BIGINT NULL,
|
|
||||||
CONSTRAINT UK_user_username UNIQUE (username)
|
|
||||||
-- CONSTRAINT FK_user_department FOREIGN KEY (department_id) REFERENCES sys_department(id)
|
|
||||||
);
|
|
||||||
|
|
||||||
-- 创建系统参数表
|
|
||||||
CREATE TABLE sys_param (
|
|
||||||
id BIGINT AUTO_INCREMENT PRIMARY KEY,
|
|
||||||
create_by VARCHAR(255),
|
|
||||||
create_time DATETIME(6),
|
|
||||||
deleted BIT NOT NULL DEFAULT 0,
|
|
||||||
update_by VARCHAR(255),
|
|
||||||
update_time DATETIME(6),
|
|
||||||
version INT NOT NULL DEFAULT 0,
|
|
||||||
|
|
||||||
code VARCHAR(100) NOT NULL COMMENT '参数编码',
|
code VARCHAR(50) NOT NULL COMMENT '租户编码',
|
||||||
name VARCHAR(100) NOT NULL COMMENT '参数名称',
|
name VARCHAR(100) NOT NULL COMMENT '租户名称',
|
||||||
value TEXT COMMENT '参数值',
|
address VARCHAR(255) NULL COMMENT '租户地址',
|
||||||
type VARCHAR(50) NOT NULL COMMENT '参数类型',
|
contact_name VARCHAR(50) NULL COMMENT '联系人姓名',
|
||||||
description VARCHAR(255) COMMENT '参数描述',
|
contact_phone VARCHAR(20) NULL COMMENT '联系人电话',
|
||||||
enabled BIT NOT NULL DEFAULT 1 COMMENT '是否启用',
|
email VARCHAR(100) NULL COMMENT '联系人邮箱',
|
||||||
|
enabled BIT NOT NULL DEFAULT 1 COMMENT '是否启用(0:禁用,1:启用)',
|
||||||
|
|
||||||
|
CONSTRAINT UK_tenant_code UNIQUE (code)
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='租户表';
|
||||||
|
|
||||||
|
-- 部门表
|
||||||
|
CREATE TABLE sys_department (
|
||||||
|
id BIGINT AUTO_INCREMENT PRIMARY KEY COMMENT '主键ID',
|
||||||
|
create_by VARCHAR(255) NULL COMMENT '创建人',
|
||||||
|
create_time DATETIME(6) NULL COMMENT '创建时间',
|
||||||
|
deleted BIT NOT NULL DEFAULT 0 COMMENT '是否删除(0:未删除,1:已删除)',
|
||||||
|
update_by VARCHAR(255) NULL COMMENT '更新人',
|
||||||
|
update_time DATETIME(6) NULL COMMENT '更新时间',
|
||||||
|
version INT NOT NULL DEFAULT 0 COMMENT '乐观锁版本号',
|
||||||
|
|
||||||
|
code VARCHAR(50) NOT NULL COMMENT '部门编码',
|
||||||
|
name VARCHAR(100) NOT NULL COMMENT '部门名称',
|
||||||
|
description VARCHAR(255) NULL COMMENT '部门描述',
|
||||||
|
leader_id BIGINT NULL COMMENT '部门负责人ID',
|
||||||
|
leader_name VARCHAR(50) NULL COMMENT '部门负责人姓名',
|
||||||
|
parent_id BIGINT NULL COMMENT '父部门ID',
|
||||||
|
sort INT NOT NULL DEFAULT 0 COMMENT '显示顺序',
|
||||||
|
enabled BIT NOT NULL DEFAULT 1 COMMENT '是否启用(0:禁用,1:启用)',
|
||||||
|
|
||||||
|
CONSTRAINT UK_department_code UNIQUE (code)
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='部门表';
|
||||||
|
|
||||||
|
-- 用户表
|
||||||
|
CREATE TABLE IF NOT EXISTS sys_user (
|
||||||
|
id BIGINT AUTO_INCREMENT PRIMARY KEY COMMENT '主键ID',
|
||||||
|
create_by VARCHAR(255) NULL COMMENT '创建人',
|
||||||
|
create_time DATETIME(6) NULL COMMENT '创建时间',
|
||||||
|
deleted BIT NOT NULL DEFAULT 0 COMMENT '是否删除(0:未删除,1:已删除)',
|
||||||
|
update_by VARCHAR(255) NULL COMMENT '更新人',
|
||||||
|
update_time DATETIME(6) NULL COMMENT '更新时间',
|
||||||
|
version INT NOT NULL DEFAULT 0 COMMENT '乐观锁版本号',
|
||||||
|
|
||||||
|
username VARCHAR(50) NOT NULL COMMENT '用户名',
|
||||||
|
password VARCHAR(255) NOT NULL COMMENT '密码',
|
||||||
|
nickname VARCHAR(50) NULL COMMENT '昵称',
|
||||||
|
email VARCHAR(100) NULL COMMENT '邮箱',
|
||||||
|
phone VARCHAR(20) NULL COMMENT '手机号',
|
||||||
|
department_id BIGINT NULL COMMENT '所属部门ID',
|
||||||
|
enabled BIT NOT NULL DEFAULT 1 COMMENT '是否启用(0:禁用,1:启用)',
|
||||||
|
|
||||||
|
CONSTRAINT UK_user_username UNIQUE (username),
|
||||||
|
CONSTRAINT FK_user_department FOREIGN KEY (department_id) REFERENCES sys_department(id)
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='用户表';
|
||||||
|
|
||||||
|
-- 系统参数表
|
||||||
|
CREATE TABLE sys_param (
|
||||||
|
id BIGINT AUTO_INCREMENT PRIMARY KEY COMMENT '主键ID',
|
||||||
|
create_by VARCHAR(255) NULL COMMENT '创建人',
|
||||||
|
create_time DATETIME(6) NULL COMMENT '创建时间',
|
||||||
|
deleted BIT NOT NULL DEFAULT 0 COMMENT '是否删除(0:未删除,1:已删除)',
|
||||||
|
update_by VARCHAR(255) NULL COMMENT '更新人',
|
||||||
|
update_time DATETIME(6) NULL COMMENT '更新时间',
|
||||||
|
version INT NOT NULL DEFAULT 0 COMMENT '乐观锁版本号',
|
||||||
|
|
||||||
|
code VARCHAR(100) NOT NULL COMMENT '参数编码',
|
||||||
|
name VARCHAR(100) NOT NULL COMMENT '参数名称',
|
||||||
|
value TEXT COMMENT '参数值',
|
||||||
|
type VARCHAR(50) NOT NULL COMMENT '参数类型',
|
||||||
|
description VARCHAR(255) NULL COMMENT '参数描述',
|
||||||
|
enabled BIT NOT NULL DEFAULT 1 COMMENT '是否启用(0:禁用,1:启用)',
|
||||||
|
|
||||||
CONSTRAINT UK_sys_param_code UNIQUE (code)
|
CONSTRAINT UK_sys_param_code UNIQUE (code)
|
||||||
) COMMENT '系统参数表';
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='系统参数表';
|
||||||
|
|
||||||
|
-- --------------------------------------------------------------------------------------
|
||||||
|
-- 权限管理相关表
|
||||||
|
-- --------------------------------------------------------------------------------------
|
||||||
|
|
||||||
-- 菜单表
|
-- 菜单表
|
||||||
CREATE TABLE sys_menu (
|
CREATE TABLE sys_menu (
|
||||||
id BIGINT AUTO_INCREMENT PRIMARY KEY,
|
id BIGINT AUTO_INCREMENT PRIMARY KEY COMMENT '主键ID',
|
||||||
create_by VARCHAR(255),
|
create_by VARCHAR(255) NULL COMMENT '创建人',
|
||||||
create_time DATETIME(6),
|
create_time DATETIME(6) NULL COMMENT '创建时间',
|
||||||
deleted BIT NOT NULL DEFAULT 0,
|
deleted BIT NOT NULL DEFAULT 0 COMMENT '是否删除(0:未删除,1:已删除)',
|
||||||
update_by VARCHAR(255),
|
update_by VARCHAR(255) NULL COMMENT '更新人',
|
||||||
update_time DATETIME(6),
|
update_time DATETIME(6) NULL COMMENT '更新时间',
|
||||||
version INT NOT NULL DEFAULT 0,
|
version INT NOT NULL DEFAULT 0 COMMENT '乐观锁版本号',
|
||||||
|
|
||||||
name VARCHAR(100) NOT NULL COMMENT '菜单名称',
|
name VARCHAR(100) NOT NULL COMMENT '菜单名称',
|
||||||
path VARCHAR(200) COMMENT '路由路径',
|
path VARCHAR(200) NULL COMMENT '路由路径',
|
||||||
component VARCHAR(255) COMMENT '组件路径',
|
component VARCHAR(255) NULL COMMENT '组件路径',
|
||||||
icon VARCHAR(100) COMMENT '菜单图标',
|
icon VARCHAR(100) NULL COMMENT '菜单图标',
|
||||||
permission VARCHAR(100) COMMENT '权限标识',
|
permission VARCHAR(100) NULL COMMENT '权限标识',
|
||||||
type INT NOT NULL COMMENT '菜单类型(1:目录 2:菜单 3:按钮)',
|
type TINYINT NOT NULL COMMENT '菜单类型(1:目录 2:菜单 3:按钮)',
|
||||||
parent_id BIGINT COMMENT '父菜单ID',
|
parent_id BIGINT NULL COMMENT '父菜单ID',
|
||||||
sort INT NOT NULL DEFAULT 0 COMMENT '显示顺序',
|
sort INT NOT NULL DEFAULT 0 COMMENT '显示顺序',
|
||||||
hidden BIT NOT NULL DEFAULT 0 COMMENT '是否隐藏(0显示 1隐藏)',
|
hidden BIT NOT NULL DEFAULT 0 COMMENT '是否隐藏(0:显示,1:隐藏)',
|
||||||
enabled BIT NOT NULL DEFAULT 1 COMMENT '是否启用(0禁用 1启用)'
|
enabled BIT NOT NULL DEFAULT 1 COMMENT '是否启用(0:禁用,1:启用)',
|
||||||
) COMMENT '菜单表';
|
|
||||||
|
INDEX IDX_parent_id (parent_id)
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='菜单表';
|
||||||
|
|
||||||
-- 角色表
|
-- 角色表
|
||||||
CREATE TABLE sys_role (
|
CREATE TABLE sys_role (
|
||||||
id BIGINT AUTO_INCREMENT PRIMARY KEY,
|
id BIGINT AUTO_INCREMENT PRIMARY KEY COMMENT '主键ID',
|
||||||
create_by VARCHAR(255),
|
create_by VARCHAR(255) NULL COMMENT '创建人',
|
||||||
create_time DATETIME(6),
|
create_time DATETIME(6) NULL COMMENT '创建时间',
|
||||||
deleted BIT NOT NULL DEFAULT 0,
|
deleted BIT NOT NULL DEFAULT 0 COMMENT '是否删除(0:未删除,1:已删除)',
|
||||||
update_by VARCHAR(255),
|
update_by VARCHAR(255) NULL COMMENT '更新人',
|
||||||
update_time DATETIME(6),
|
update_time DATETIME(6) NULL COMMENT '更新时间',
|
||||||
version INT NOT NULL DEFAULT 0,
|
version INT NOT NULL DEFAULT 0 COMMENT '乐观锁版本号',
|
||||||
|
|
||||||
code VARCHAR(100) NOT NULL COMMENT '角色编码',
|
code VARCHAR(100) NOT NULL COMMENT '角色编码',
|
||||||
name VARCHAR(100) NOT NULL COMMENT '角色名称',
|
name VARCHAR(100) NOT NULL COMMENT '角色名称',
|
||||||
type INT NOT NULL DEFAULT 2 COMMENT '角色类型(1:系统角色 2:自定义角色)',
|
type TINYINT NOT NULL DEFAULT 2 COMMENT '角色类型(1:系统角色,2:自定义角色)',
|
||||||
description VARCHAR(255) COMMENT '角色描述',
|
description VARCHAR(255) NULL COMMENT '角色描述',
|
||||||
sort INT NOT NULL DEFAULT 0 COMMENT '显示顺序',
|
sort INT NOT NULL DEFAULT 0 COMMENT '显示顺序',
|
||||||
|
|
||||||
CONSTRAINT UK_role_code UNIQUE (code)
|
CONSTRAINT UK_role_code UNIQUE (code)
|
||||||
) COMMENT '角色表';
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='角色表';
|
||||||
|
|
||||||
-- 角色标签表
|
-- 角色标签表
|
||||||
CREATE TABLE sys_role_tag (
|
CREATE TABLE sys_role_tag (
|
||||||
id BIGINT AUTO_INCREMENT PRIMARY KEY,
|
id BIGINT AUTO_INCREMENT PRIMARY KEY COMMENT '主键ID',
|
||||||
create_by VARCHAR(255),
|
create_by VARCHAR(255) NULL COMMENT '创建人',
|
||||||
create_time DATETIME(6),
|
create_time DATETIME(6) NULL COMMENT '创建时间',
|
||||||
deleted BIT NOT NULL DEFAULT 0,
|
deleted BIT NOT NULL DEFAULT 0 COMMENT '是否删除(0:未删除,1:已删除)',
|
||||||
update_by VARCHAR(255),
|
update_by VARCHAR(255) NULL COMMENT '更新人',
|
||||||
update_time DATETIME(6),
|
update_time DATETIME(6) NULL COMMENT '更新时间',
|
||||||
version INT NOT NULL DEFAULT 0,
|
version INT NOT NULL DEFAULT 0 COMMENT '乐观锁版本号',
|
||||||
|
|
||||||
name VARCHAR(50) NOT NULL COMMENT '标签名称',
|
name VARCHAR(50) NOT NULL COMMENT '标签名称',
|
||||||
color VARCHAR(20) COMMENT '标签颜色'
|
color VARCHAR(20) NULL COMMENT '标签颜色(十六进制颜色码)'
|
||||||
) COMMENT '角色标签表';
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='角色标签表';
|
||||||
|
|
||||||
-- 角色标签关联表
|
-- 角色标签关联表
|
||||||
CREATE TABLE sys_role_tag_relation (
|
CREATE TABLE sys_role_tag_relation (
|
||||||
role_id BIGINT NOT NULL COMMENT '角色ID',
|
role_id BIGINT NOT NULL COMMENT '角色ID',
|
||||||
tag_id BIGINT NOT NULL COMMENT '标签ID',
|
tag_id BIGINT NOT NULL COMMENT '标签ID',
|
||||||
PRIMARY KEY (role_id, tag_id)
|
|
||||||
-- CONSTRAINT FK_role_tag_role FOREIGN KEY (role_id) REFERENCES sys_role (id),
|
PRIMARY KEY (role_id, tag_id),
|
||||||
-- CONSTRAINT FK_role_tag_tag FOREIGN KEY (tag_id) REFERENCES sys_role_tag (id)
|
CONSTRAINT FK_role_tag_role FOREIGN KEY (role_id) REFERENCES sys_role (id),
|
||||||
) COMMENT '角色标签关联表';
|
CONSTRAINT FK_role_tag_tag FOREIGN KEY (tag_id) REFERENCES sys_role_tag (id)
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='角色标签关联表';
|
||||||
|
|
||||||
-- 用户角色关联表
|
-- 用户角色关联表
|
||||||
CREATE TABLE sys_user_role (
|
CREATE TABLE sys_user_role (
|
||||||
id BIGINT AUTO_INCREMENT PRIMARY KEY,
|
id BIGINT AUTO_INCREMENT PRIMARY KEY COMMENT '主键ID',
|
||||||
create_by VARCHAR(255),
|
create_by VARCHAR(255) NULL COMMENT '创建人',
|
||||||
create_time DATETIME(6),
|
create_time DATETIME(6) NULL COMMENT '创建时间',
|
||||||
deleted BIT NOT NULL DEFAULT 0,
|
deleted BIT NOT NULL DEFAULT 0 COMMENT '是否删除(0:未删除,1:已删除)',
|
||||||
update_by VARCHAR(255),
|
update_by VARCHAR(255) NULL COMMENT '更新人',
|
||||||
update_time DATETIME(6),
|
update_time DATETIME(6) NULL COMMENT '更新时间',
|
||||||
version INT NOT NULL DEFAULT 0,
|
version INT NOT NULL DEFAULT 0 COMMENT '乐观锁版本号',
|
||||||
|
|
||||||
user_id BIGINT NOT NULL COMMENT '用户ID',
|
user_id BIGINT NOT NULL COMMENT '用户ID',
|
||||||
role_id BIGINT NOT NULL COMMENT '角色ID',
|
role_id BIGINT NOT NULL COMMENT '角色ID',
|
||||||
|
|
||||||
CONSTRAINT UK_user_role UNIQUE (user_id, role_id)
|
CONSTRAINT UK_user_role UNIQUE (user_id, role_id),
|
||||||
-- CONSTRAINT FK_user_role_user FOREIGN KEY (user_id) REFERENCES sys_user (id),
|
CONSTRAINT FK_user_role_user FOREIGN KEY (user_id) REFERENCES sys_user (id),
|
||||||
-- CONSTRAINT FK_user_role_role FOREIGN KEY (role_id) REFERENCES sys_role (id)
|
CONSTRAINT FK_user_role_role FOREIGN KEY (role_id) REFERENCES sys_role (id)
|
||||||
) COMMENT '用户角色关联表';
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='用户角色关联表';
|
||||||
|
|
||||||
-- 角色菜单关联表
|
-- 角色菜单关联表
|
||||||
CREATE TABLE sys_role_menu (
|
CREATE TABLE sys_role_menu (
|
||||||
role_id BIGINT NOT NULL COMMENT '角色ID',
|
role_id BIGINT NOT NULL COMMENT '角色ID',
|
||||||
menu_id BIGINT NOT NULL COMMENT '菜单ID',
|
menu_id BIGINT NOT NULL COMMENT '菜单ID',
|
||||||
PRIMARY KEY (role_id, menu_id)
|
|
||||||
-- CONSTRAINT FK_role_menu_role FOREIGN KEY (role_id) REFERENCES sys_role (id),
|
PRIMARY KEY (role_id, menu_id),
|
||||||
-- CONSTRAINT FK_role_menu_menu FOREIGN KEY (menu_id) REFERENCES sys_menu (id)
|
CONSTRAINT FK_role_menu_role FOREIGN KEY (role_id) REFERENCES sys_role (id),
|
||||||
) COMMENT '角色菜单关联表';
|
CONSTRAINT FK_role_menu_menu FOREIGN KEY (menu_id) REFERENCES sys_menu (id)
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='角色菜单关联表';
|
||||||
|
|
||||||
-- 权限模板表
|
-- 权限模板表
|
||||||
CREATE TABLE sys_permission_template (
|
CREATE TABLE sys_permission_template (
|
||||||
id BIGINT AUTO_INCREMENT PRIMARY KEY,
|
id BIGINT AUTO_INCREMENT PRIMARY KEY COMMENT '主键ID',
|
||||||
create_by VARCHAR(255),
|
create_by VARCHAR(255) NULL COMMENT '创建人',
|
||||||
create_time DATETIME(6),
|
create_time DATETIME(6) NULL COMMENT '创建时间',
|
||||||
deleted BIT NOT NULL DEFAULT 0,
|
deleted BIT NOT NULL DEFAULT 0 COMMENT '是否删除(0:未删除,1:已删除)',
|
||||||
update_by VARCHAR(255),
|
update_by VARCHAR(255) NULL COMMENT '更新人',
|
||||||
update_time DATETIME(6),
|
update_time DATETIME(6) NULL COMMENT '更新时间',
|
||||||
version INT NOT NULL DEFAULT 0,
|
version INT NOT NULL DEFAULT 0 COMMENT '乐观锁版本号',
|
||||||
|
|
||||||
code VARCHAR(100) NOT NULL COMMENT '模板编码',
|
code VARCHAR(100) NOT NULL COMMENT '模板编码',
|
||||||
name VARCHAR(100) NOT NULL COMMENT '模板名称',
|
name VARCHAR(100) NOT NULL COMMENT '模板名称',
|
||||||
type INT NOT NULL DEFAULT 1 COMMENT '模板类型(1:系统模板 2:自定义模板)',
|
type TINYINT NOT NULL DEFAULT 1 COMMENT '模板类型(1:系统模板,2:自定义模板)',
|
||||||
description VARCHAR(255) COMMENT '模板描述',
|
description VARCHAR(255) NULL COMMENT '模板描述',
|
||||||
enabled BIT NOT NULL DEFAULT 1 COMMENT '是否启用',
|
enabled BIT NOT NULL DEFAULT 1 COMMENT '是否启用(0:禁用,1:启用)',
|
||||||
|
|
||||||
CONSTRAINT UK_template_code UNIQUE (code)
|
CONSTRAINT UK_template_code UNIQUE (code)
|
||||||
) COMMENT '权限模板表';
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='权限模板表';
|
||||||
|
|
||||||
-- 模板菜单关联表
|
-- 模板菜单关联表
|
||||||
CREATE TABLE sys_template_menu (
|
CREATE TABLE sys_template_menu (
|
||||||
template_id BIGINT NOT NULL COMMENT '模板ID',
|
template_id BIGINT NOT NULL COMMENT '模板ID',
|
||||||
menu_id BIGINT NOT NULL COMMENT '菜单ID',
|
menu_id BIGINT NOT NULL COMMENT '菜单ID',
|
||||||
PRIMARY KEY (template_id, menu_id)
|
|
||||||
-- CONSTRAINT FK_template_menu_template FOREIGN KEY (template_id) REFERENCES sys_permission_template (id),
|
PRIMARY KEY (template_id, menu_id),
|
||||||
-- CONSTRAINT FK_template_menu_menu FOREIGN KEY (menu_id) REFERENCES sys_menu (id)
|
CONSTRAINT FK_template_menu_template FOREIGN KEY (template_id) REFERENCES sys_permission_template (id),
|
||||||
) COMMENT '模板菜单关联表';
|
CONSTRAINT FK_template_menu_menu FOREIGN KEY (menu_id) REFERENCES sys_menu (id)
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='模板菜单关联表';
|
||||||
|
|
||||||
-- 创建权限表
|
-- 权限表
|
||||||
CREATE TABLE sys_permission (
|
CREATE TABLE sys_permission (
|
||||||
id BIGINT AUTO_INCREMENT PRIMARY KEY,
|
id BIGINT AUTO_INCREMENT PRIMARY KEY COMMENT '主键ID',
|
||||||
create_by VARCHAR(255) NULL,
|
create_by VARCHAR(255) NULL COMMENT '创建人',
|
||||||
create_time DATETIME(6) NULL,
|
create_time DATETIME(6) NULL COMMENT '创建时间',
|
||||||
deleted BIT NOT NULL,
|
deleted BIT NOT NULL DEFAULT 0 COMMENT '是否删除(0:未删除,1:已删除)',
|
||||||
update_by VARCHAR(255) NULL,
|
update_by VARCHAR(255) NULL COMMENT '更新人',
|
||||||
update_time DATETIME(6) NULL,
|
update_time DATETIME(6) NULL COMMENT '更新时间',
|
||||||
version INT NOT NULL,
|
version INT NOT NULL DEFAULT 0 COMMENT '乐观锁版本号',
|
||||||
|
|
||||||
menu_id BIGINT NOT NULL COMMENT '关联的菜单ID',
|
menu_id BIGINT NOT NULL COMMENT '关联的菜单ID',
|
||||||
code VARCHAR(100) NOT NULL COMMENT '权限编码',
|
code VARCHAR(100) NOT NULL COMMENT '权限编码',
|
||||||
name VARCHAR(100) NOT NULL COMMENT '权限名称',
|
name VARCHAR(100) NOT NULL COMMENT '权限名称',
|
||||||
type VARCHAR(50) NOT NULL DEFAULT 'FUNCTION' COMMENT '权限类型:MENU/FUNCTION/API',
|
type VARCHAR(50) NOT NULL DEFAULT 'FUNCTION' COMMENT '权限类型(MENU:菜单,FUNCTION:功能,API:接口)',
|
||||||
sort INT NULL COMMENT '排序',
|
sort INT NULL DEFAULT 0 COMMENT '排序',
|
||||||
|
|
||||||
UNIQUE KEY UK_CODE (CODE),
|
CONSTRAINT UK_permission_code UNIQUE (code),
|
||||||
KEY IDX_MENU_ID (MENU_ID)
|
CONSTRAINT FK_permission_menu FOREIGN KEY (menu_id) REFERENCES sys_menu (id),
|
||||||
) COMMENT '系统权限表';
|
INDEX IDX_menu_id (menu_id)
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='系统权限表';
|
||||||
|
|
||||||
-- 创建三方系统表
|
-- --------------------------------------------------------------------------------------
|
||||||
|
-- 外部系统集成相关表
|
||||||
|
-- --------------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
-- 外部系统表
|
||||||
CREATE TABLE sys_external_system (
|
CREATE TABLE sys_external_system (
|
||||||
id BIGINT AUTO_INCREMENT PRIMARY KEY,
|
id BIGINT AUTO_INCREMENT PRIMARY KEY COMMENT '主键ID',
|
||||||
create_by VARCHAR(255) NULL,
|
create_by VARCHAR(255) NULL COMMENT '创建人',
|
||||||
create_time DATETIME(6) NULL,
|
create_time DATETIME(6) NULL COMMENT '创建时间',
|
||||||
deleted BIT NOT NULL,
|
deleted BIT NOT NULL DEFAULT 0 COMMENT '是否删除(0:未删除,1:已删除)',
|
||||||
update_by VARCHAR(255) NULL,
|
update_by VARCHAR(255) NULL COMMENT '更新人',
|
||||||
update_time DATETIME(6) NULL,
|
update_time DATETIME(6) NULL COMMENT '更新时间',
|
||||||
version INT NOT NULL,
|
version INT NOT NULL DEFAULT 0 COMMENT '乐观锁版本号',
|
||||||
|
|
||||||
name VARCHAR(255) NOT NULL COMMENT '系统名称',
|
name VARCHAR(100) NOT NULL COMMENT '系统名称',
|
||||||
type VARCHAR(50) NOT NULL COMMENT '系统类型:JENKINS/GIT/ZENTAO等',
|
type VARCHAR(50) NOT NULL COMMENT '系统类型(JENKINS/GIT/ZENTAO等)',
|
||||||
url VARCHAR(255) NOT NULL COMMENT '系统访问地址',
|
url VARCHAR(255) NOT NULL COMMENT '系统访问地址',
|
||||||
remark TEXT NULL COMMENT '备注说明',
|
remark TEXT NULL COMMENT '备注说明',
|
||||||
sort INT NULL COMMENT '排序',
|
sort INT NULL DEFAULT 0 COMMENT '排序',
|
||||||
enabled BIT NOT NULL DEFAULT TRUE COMMENT '是否启用',
|
enabled BIT NOT NULL DEFAULT 1 COMMENT '是否启用(0:禁用,1:启用)',
|
||||||
|
|
||||||
auth_type VARCHAR(50) NOT NULL COMMENT '认证方式:BASIC/TOKEN/OAUTH等',
|
auth_type VARCHAR(50) NOT NULL COMMENT '认证方式(BASIC:基础认证,TOKEN:令牌认证,OAUTH:OAuth认证)',
|
||||||
username VARCHAR(255) NULL COMMENT '用户名',
|
username VARCHAR(100) NULL COMMENT '用户名',
|
||||||
password VARCHAR(255) NULL COMMENT '密码/密钥',
|
password VARCHAR(255) NULL COMMENT '密码/密钥',
|
||||||
token VARCHAR(255) NULL COMMENT '访问令牌',
|
token VARCHAR(255) NULL COMMENT '访问令牌',
|
||||||
|
|
||||||
sync_status ENUM ('SUCCESS', 'FAILED', 'RUNNING') NULL COMMENT '最后同步状态',
|
sync_status ENUM ('SUCCESS', 'FAILED', 'RUNNING') NULL COMMENT '最后同步状态',
|
||||||
last_sync_time DATETIME(6) NULL COMMENT '最后同步时间',
|
last_sync_time DATETIME(6) NULL COMMENT '最后同步时间',
|
||||||
|
|
||||||
config JSON NULL COMMENT '系统特有配置,JSON格式',
|
config JSON NULL COMMENT '系统特有配置(JSON格式)',
|
||||||
|
|
||||||
UNIQUE KEY UK_NAME (NAME),
|
CONSTRAINT UK_external_system_name UNIQUE (name),
|
||||||
UNIQUE KEY UK_TYPE_URL (TYPE, URL)
|
CONSTRAINT UK_external_system_type_url UNIQUE (type, url),
|
||||||
) COMMENT '第三方系统配置';
|
INDEX IDX_type (type),
|
||||||
|
INDEX IDX_enabled (enabled)
|
||||||
-- 创建角色权限关联表
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='外部系统配置表';
|
||||||
CREATE TABLE sys_role_permission (
|
|
||||||
role_id BIGINT NOT NULL COMMENT '角色ID',
|
|
||||||
permission_id BIGINT NOT NULL COMMENT '权限ID',
|
|
||||||
PRIMARY KEY (role_id, permission_id)
|
|
||||||
-- CONSTRAINT FK_role_permission_role FOREIGN KEY (role_id) REFERENCES sys_role (id),
|
|
||||||
-- CONSTRAINT FK_role_permission_permission FOREIGN KEY (permission_id) REFERENCES sys_permission (id)
|
|
||||||
) COMMENT '角色权限关联表';
|
|
||||||
|
|
||||||
@ -1,26 +1,43 @@
|
|||||||
-- 初始化租户
|
-- --------------------------------------------------------------------------------------
|
||||||
INSERT INTO sys_tenant
|
-- 初始化系统基础数据
|
||||||
(create_by, create_time, deleted, update_by, update_time, version,
|
-- --------------------------------------------------------------------------------------
|
||||||
address, code, contact_name, contact_phone, email, enabled, name)
|
|
||||||
VALUES ('system', '2024-01-01 00:00:00', 0, 'system', '2024-01-01 00:00:00', 0,
|
-- 初始化租户数据
|
||||||
'北京市朝阳区望京SOHO T1 C座', 'default', '张三', '13900000001',
|
INSERT INTO sys_tenant (id, create_time, code, name, address, contact_name, contact_phone, email, enabled)
|
||||||
'admin@deploy-ease.com', 1, '默认租户');
|
VALUES (1, NOW(), 'admin', '系统管理租户', '北京市朝阳区', '管理员', '13800138000', 'admin@system.com', 1);
|
||||||
|
|
||||||
|
-- 初始化部门数据
|
||||||
|
INSERT INTO sys_department (id, create_time, code, name, description, sort, enabled)
|
||||||
|
VALUES (1, NOW(), 'ROOT', '根部门', '系统根部门', 0, 1);
|
||||||
|
|
||||||
|
INSERT INTO sys_department (id, create_time, code, name, description, parent_id, sort, enabled)
|
||||||
|
VALUES
|
||||||
|
(2, NOW(), 'IT', '信息技术部', '负责公司IT系统的规划、建设和运维', 1, 1, 1),
|
||||||
|
(3, NOW(), 'DEV', '研发部', '负责产品研发和技术创新', 1, 2, 1),
|
||||||
|
(4, NOW(), 'OPS', '运维部', '负责系统运维和技术支持', 1, 3, 1);
|
||||||
|
|
||||||
|
-- 初始化用户数据(密码统一为:123456)
|
||||||
|
INSERT INTO sys_user (id, create_time, username, password, nickname, email, phone, department_id, enabled)
|
||||||
|
VALUES
|
||||||
|
(1, NOW(), 'admin', '$2a$10$viWVqfZwQxViLQDk7hhVg.ENYT.3zUFf.aBetlarImKPSS0V2gbSa', '超级管理员', 'admin@system.com', '13800138000', 1, 1),
|
||||||
|
(2, NOW(), 'it_manager', '$2a$10$viWVqfZwQxViLQDk7hhVg.ENYT.3zUFf.aBetlarImKPSS0V2gbSa', 'IT经理', 'it@system.com', '13800138001', 2, 1),
|
||||||
|
(3, NOW(), 'dev_manager', '$2a$10$viWVqfZwQxViLQDk7hhVg.ENYT.3zUFf.aBetlarImKPSS0V2gbSa', '研发经理', 'dev@system.com', '13800138002', 3, 1),
|
||||||
|
(4, NOW(), 'ops_manager', '$2a$10$viWVqfZwQxViLQDk7hhVg.ENYT.3zUFf.aBetlarImKPSS0V2gbSa', '运维经理', 'ops@system.com', '13800138003', 4, 1);
|
||||||
|
|
||||||
-- 初始化系统参数
|
-- 初始化系统参数
|
||||||
INSERT INTO sys_param (id, code, name, value, type, description, enabled, create_by, create_time, version, deleted)
|
INSERT INTO sys_param (id, create_time, code, name, value, type, description, enabled)
|
||||||
VALUES (1, 'USER_STATUS_ENUM', '用户状态枚举', '[{"code":"0","name":"禁用"},{"code":"1","name":"启用"}]', 'ENUM', '用户状态枚举值', TRUE, 'system', '2024-01-01 00:00:00', 0, FALSE);
|
VALUES
|
||||||
|
(1, NOW(), 'SYSTEM_NAME', '系统名称', 'Deploy Ease Platform', 'STRING', '系统显示名称', 1),
|
||||||
|
(2, NOW(), 'SYSTEM_LOGO', '系统Logo', '/static/logo.png', 'STRING', '系统Logo路径', 1),
|
||||||
|
(3, NOW(), 'LOGIN_BACKGROUND', '登录背景', '/static/login-bg.jpg', 'STRING', '登录页面背景图片', 1);
|
||||||
|
|
||||||
-- 初始化角色标签
|
-- --------------------------------------------------------------------------------------
|
||||||
INSERT INTO sys_role_tag (id, name, color, create_by, create_time, deleted, version)
|
-- 初始化权限管理数据
|
||||||
VALUES (1, '研发', '#1890FF', 'system', '2024-01-01 00:00:00', FALSE, 0),
|
-- --------------------------------------------------------------------------------------
|
||||||
(2, '运维', '#52C41A', 'system', '2024-01-01 00:00:00', FALSE, 0),
|
|
||||||
(3, '安全', '#FF4D4F', 'system', '2024-01-01 00:00:00', FALSE, 0),
|
|
||||||
(4, '临时', '#FAAD14', 'system', '2024-01-01 00:00:00', FALSE, 0),
|
|
||||||
(5, '外部', '#722ED1', 'system', '2024-01-01 00:00:00', FALSE, 0);
|
|
||||||
|
|
||||||
-- 初始化菜单数据
|
-- 初始化菜单数据
|
||||||
INSERT INTO sys_menu (id, name, path, component, icon, type, parent_id, sort, hidden, enabled, create_by, create_time, version, deleted)
|
INSERT INTO sys_menu (id, name, path, component, icon, type, parent_id, sort, hidden, enabled, create_by, create_time, version, deleted)
|
||||||
VALUES
|
VALUES
|
||||||
-- 系统管理
|
-- 系统管理
|
||||||
(1, '系统管理', '/system', 'LAYOUT', 'setting', 1, NULL, 1, FALSE, TRUE, 'system', '2024-01-01 00:00:00', 0, FALSE),
|
(1, '系统管理', '/system', 'LAYOUT', 'setting', 1, NULL, 1, FALSE, TRUE, 'system', '2024-01-01 00:00:00', 0, FALSE),
|
||||||
-- 用户管理
|
-- 用户管理
|
||||||
@ -35,128 +52,88 @@ VALUES
|
|||||||
(70, '三方系统', '/system/external', '/System/External/index', 'api', 2, 1, 70, FALSE, TRUE, 'system', '2024-01-01 00:00:00', 0, FALSE);
|
(70, '三方系统', '/system/external', '/System/External/index', 'api', 2, 1, 70, FALSE, TRUE, 'system', '2024-01-01 00:00:00', 0, FALSE);
|
||||||
|
|
||||||
-- 初始化角色数据
|
-- 初始化角色数据
|
||||||
INSERT INTO sys_role (id, code, name, type, description, sort, create_by, create_time, version, deleted)
|
INSERT INTO sys_role (id, create_time, code, name, type, description, sort)
|
||||||
VALUES (1, 'SUPER_ADMIN', '超级管理员', 1, '系统超级管理员', 1, 'system', '2024-01-01 00:00:00', 0, FALSE),
|
VALUES
|
||||||
(2, 'DEV_MANAGER', '开发主管', 2, '开发团队主管', 2, 'system', '2024-01-01 00:00:00', 0, FALSE),
|
(1, NOW(), 'SUPER_ADMIN', '超级管理员', 1, '系统超级管理员,拥有所有权限', 1),
|
||||||
(3, 'OPS_MANAGER', '运维主管', 2, '运维团队主管', 3, 'system', '2024-01-01 00:00:00', 0, FALSE);
|
(2, NOW(), 'SYSTEM_ADMIN', '系统管理员', 1, '系统管理员,拥有大部分系统管理权限', 2),
|
||||||
|
(3, NOW(), 'COMMON_USER', '普通用户', 2, '普通用户,仅拥有基本操作权限', 3);
|
||||||
|
|
||||||
-- 初始化部门数据
|
-- 初始化角色标签
|
||||||
INSERT INTO sys_department (id, code, name, description, enabled, sort, parent_id, create_by, create_time, version, deleted)
|
INSERT INTO sys_role_tag (id, create_time, name, color)
|
||||||
VALUES (1, 'TECH', '技术部', '技术研发部门', TRUE, 1, NULL, 'system', '2024-01-01 00:00:00', 0, FALSE),
|
VALUES
|
||||||
(2, 'DEV', '研发组', '研发团队', TRUE, 1, 1, 'system', '2024-01-01 00:00:00', 0, FALSE),
|
(1, NOW(), '系统内置', '#ff4d4f'),
|
||||||
(3, 'OPS', '运维组', '运维团队', TRUE, 2, 1, 'system', '2024-01-01 00:00:00', 0, FALSE);
|
(2, NOW(), '重要角色', '#ffa940'),
|
||||||
|
(3, NOW(), '普通角色', '#73d13d');
|
||||||
|
|
||||||
-- 初始化用户数据
|
-- 初始化角色标签关联
|
||||||
INSERT INTO sys_user (id, username, password, nickname, email, phone, enabled, department_id, create_by, create_time, version, deleted)
|
|
||||||
VALUES (1, 'admin', '$2a$10$VTbMVv3M.gVaMcLsELtBZuHxGrHyFqf3CYfSFQhcBn0A6pBTvThSy', '系统管理员', 'admin@example.com', '13800138000', TRUE, NULL, 'system', '2024-01-01 00:00:00', 0, FALSE),
|
|
||||||
(2, 'dev_manager', '$2a$10$VTbMVv3M.gVaMcLsELtBZuHxGrHyFqf3CYfSFQhcBn0A6pBTvThSy', '开发主管', 'dev@example.com', '13800138001', TRUE, 2, 'system', '2024-01-01 00:00:00', 0, FALSE),
|
|
||||||
(3, 'ops_manager', '$2a$10$VTbMVv3M.gVaMcLsELtBZuHxGrHyFqf3CYfSFQhcBn0A6pBTvThSy', '运维主管', 'ops@example.com', '13800138002', TRUE, 3, 'system', '2024-01-01 00:00:00', 0, FALSE);
|
|
||||||
|
|
||||||
-- 初始化用户角色关联数据
|
|
||||||
INSERT INTO sys_user_role (user_id, role_id, create_by, create_time, version, deleted)
|
|
||||||
VALUES (1, 1, 'system', '2024-01-01 00:00:00', 0, FALSE), -- 超级管理员 -> 超级管理员角色
|
|
||||||
(2, 2, 'system', '2024-01-01 00:00:00', 0, FALSE), -- 开发主管 -> 开发主管角色
|
|
||||||
(3, 3, 'system', '2024-01-01 00:00:00', 0, FALSE);
|
|
||||||
-- 运维主管 -> 运维主管角色
|
|
||||||
|
|
||||||
-- 初始化角色标签关联数据
|
|
||||||
INSERT INTO sys_role_tag_relation (role_id, tag_id)
|
INSERT INTO sys_role_tag_relation (role_id, tag_id)
|
||||||
VALUES (1, 3), -- 超级管理员 -> 安全标签
|
VALUES
|
||||||
(2, 1), -- 开发主管 -> 研发标签
|
|
||||||
(3, 2);
|
|
||||||
-- 运维主管 -> 运维标签
|
|
||||||
|
|
||||||
-- 初始化角色菜单关联数据
|
|
||||||
INSERT INTO sys_role_menu (role_id, menu_id)
|
|
||||||
VALUES
|
|
||||||
-- 超级管理员拥有所有菜单权限
|
|
||||||
(1, 1),
|
(1, 1),
|
||||||
(1, 2),
|
(2, 1),
|
||||||
(1, 3),
|
(2, 2),
|
||||||
(1, 4),
|
(3, 3);
|
||||||
(1, 5),
|
|
||||||
(1, 70),
|
-- 初始化用户角色关联
|
||||||
-- 开发主管权限
|
INSERT INTO sys_user_role (id, create_time, user_id, role_id)
|
||||||
(2, 70),
|
VALUES
|
||||||
-- 运维主管权限
|
(1, NOW(), 1, 1),
|
||||||
(3, 70);
|
(2, NOW(), 2, 2),
|
||||||
|
(3, NOW(), 3, 2),
|
||||||
|
(4, NOW(), 4, 3);
|
||||||
|
|
||||||
|
-- 初始化角色菜单关联
|
||||||
|
INSERT INTO sys_role_menu (role_id, menu_id)
|
||||||
|
SELECT 1, id FROM sys_menu; -- 超级管理员拥有所有菜单权限
|
||||||
|
|
||||||
|
INSERT INTO sys_role_menu (role_id, menu_id)
|
||||||
|
VALUES
|
||||||
|
(2, 1), (2, 2), (2, 3), (2, 4), (2, 5), -- 系统管理员拥有系统管理相关权限
|
||||||
|
(3, 70); -- 普通用户拥有三方系统权限
|
||||||
|
|
||||||
|
-- 初始化权限模板
|
||||||
|
INSERT INTO sys_permission_template (id, create_time, code, name, type, description, enabled)
|
||||||
|
VALUES
|
||||||
|
(1, NOW(), 'FULL_PERMISSION', '完整权限模板', 1, '包含所有系统权限的模板', 1),
|
||||||
|
(2, NOW(), 'BASIC_PERMISSION', '基础权限模板', 1, '包含基本操作权限的模板', 1);
|
||||||
|
|
||||||
|
-- 初始化模板菜单关联
|
||||||
|
INSERT INTO sys_template_menu (template_id, menu_id)
|
||||||
|
SELECT 1, id FROM sys_menu; -- 完整权限模板关联所有菜单
|
||||||
|
|
||||||
|
INSERT INTO sys_template_menu (template_id, menu_id)
|
||||||
|
VALUES (2, 70); -- 基础权限模板关联三方系统菜单
|
||||||
|
|
||||||
-- 初始化权限数据
|
-- 初始化权限数据
|
||||||
INSERT INTO sys_permission (id, menu_id, code, name, type, sort, create_by, create_time, update_by, update_time, version, deleted)
|
INSERT INTO sys_permission (id, create_time, menu_id, code, name, type, sort)
|
||||||
VALUES
|
VALUES
|
||||||
-- 用户管理权限
|
-- 用户管理权限
|
||||||
(21, 2, 'system:user:add', '用户新增', 'FUNCTION', 1, 'system', '2024-01-01 00:00:00', 'system', '2024-01-01 00:00:00', 0, FALSE),
|
(1, NOW(), 2, 'system:user:list', '用户列表', 'FUNCTION', 1),
|
||||||
(22, 2, 'system:user:edit', '用户编辑', 'FUNCTION', 2, 'system', '2024-01-01 00:00:00', 'system', '2024-01-01 00:00:00', 0, FALSE),
|
(2, NOW(), 2, 'system:user:create', '用户创建', 'FUNCTION', 2),
|
||||||
(23, 2, 'system:user:delete', '用户删除', 'FUNCTION', 3, 'system', '2024-01-01 00:00:00', 'system', '2024-01-01 00:00:00', 0, FALSE),
|
(3, NOW(), 2, 'system:user:update', '用户修改', 'FUNCTION', 3),
|
||||||
(24, 2, 'system:user:reset-password', '重置密码', 'FUNCTION', 4, 'system', '2024-01-01 00:00:00', 'system', '2024-01-01 00:00:00', 0, FALSE),
|
(4, NOW(), 2, 'system:user:delete', '用户删除', 'FUNCTION', 4),
|
||||||
(25, 2, 'system:user:assign-roles', '分配角色', 'FUNCTION', 5, 'system', '2024-01-01 00:00:00', 'system', '2024-01-01 00:00:00', 0, FALSE),
|
|
||||||
|
|
||||||
-- 角色管理权限
|
-- 角色管理权限
|
||||||
(31, 3, 'system:role:add', '角色新增', 'FUNCTION', 1, 'system', '2024-01-01 00:00:00', 'system', '2024-01-01 00:00:00', 0, FALSE),
|
(5, NOW(), 3, 'system:role:list', '角色列表', 'FUNCTION', 1),
|
||||||
(32, 3, 'system:role:edit', '角色编辑', 'FUNCTION', 2, 'system', '2024-01-01 00:00:00', 'system', '2024-01-01 00:00:00', 0, FALSE),
|
(6, NOW(), 3, 'system:role:create', '角色创建', 'FUNCTION', 2),
|
||||||
(33, 3, 'system:role:delete', '角色删除', 'FUNCTION', 3, 'system', '2024-01-01 00:00:00', 'system', '2024-01-01 00:00:00', 0, FALSE),
|
(7, NOW(), 3, 'system:role:update', '角色修改', 'FUNCTION', 3),
|
||||||
(34, 3, 'system:role:assign-tags', '分配标签', 'FUNCTION', 4, 'system', '2024-01-01 00:00:00', 'system', '2024-01-01 00:00:00', 0, FALSE),
|
(8, NOW(), 3, 'system:role:delete', '角色删除', 'FUNCTION', 4),
|
||||||
(35, 3, 'system:role:assign-permissions', '分配权限', 'FUNCTION', 5, 'system', '2024-01-01 00:00:00', 'system', '2024-01-01 00:00:00', 0, FALSE),
|
|
||||||
(36, 3, 'system:role:permission-list', '权限列表', 'FUNCTION', 6, 'system', '2024-01-01 00:00:00', 'system', '2024-01-01 00:00:00', 0, FALSE),
|
|
||||||
|
|
||||||
-- 菜单管理权限
|
-- 三方系统权限
|
||||||
(41, 4, 'system:menu:add', '菜单新增', 'FUNCTION', 1, 'system', '2024-01-01 00:00:00', 'system', '2024-01-01 00:00:00', 0, FALSE),
|
(9, NOW(), 70, 'system:external:list', '三方系统列表', 'FUNCTION', 1),
|
||||||
(42, 4, 'system:menu:edit', '菜单编辑', 'FUNCTION', 2, 'system', '2024-01-01 00:00:00', 'system', '2024-01-01 00:00:00', 0, FALSE),
|
(10, NOW(), 70, 'system:external:create', '三方系统创建', 'FUNCTION', 2),
|
||||||
(43, 4, 'system:menu:delete', '菜单删除', 'FUNCTION', 3, 'system', '2024-01-01 00:00:00', 'system', '2024-01-01 00:00:00', 0, FALSE),
|
(11, NOW(), 70, 'system:external:update', '三方系统修改', 'FUNCTION', 3),
|
||||||
|
(12, NOW(), 70, 'system:external:delete', '三方系统删除', 'FUNCTION', 4),
|
||||||
|
(13, NOW(), 70, 'system:external:test', '连接测试', 'FUNCTION', 5),
|
||||||
|
(14, NOW(), 70, 'system:external:sync', '数据同步', 'FUNCTION', 6);
|
||||||
|
|
||||||
-- 部门管理权限
|
-- --------------------------------------------------------------------------------------
|
||||||
(51, 5, 'system:department:add', '部门新增', 'FUNCTION', 1, 'system', '2024-01-01 00:00:00', 'system', '2024-01-01 00:00:00', 0, FALSE),
|
-- 初始化外部系统数据
|
||||||
(52, 5, 'system:department:edit', '部门编辑', 'FUNCTION', 2, 'system', '2024-01-01 00:00:00', 'system', '2024-01-01 00:00:00', 0, FALSE),
|
-- --------------------------------------------------------------------------------------
|
||||||
(53, 5, 'system:department:delete', '部门删除', 'FUNCTION', 3, 'system', '2024-01-01 00:00:00', 'system', '2024-01-01 00:00:00', 0, FALSE),
|
|
||||||
|
|
||||||
-- 三方系统管理权限
|
-- 初始化外部系统
|
||||||
(71, 70, 'system:external:list', '查看三方系统', 'FUNCTION', 1, 'system', '2024-01-01 00:00:00', 'system', '2024-01-01 00:00:00', 0, FALSE),
|
INSERT INTO sys_external_system (id, create_time, name, type, url, auth_type, username, password, enabled, sort)
|
||||||
(72, 70, 'system:external:create', '新增三方系统', 'FUNCTION', 2, 'system', '2024-01-01 00:00:00', 'system', '2024-01-01 00:00:00', 0, FALSE),
|
VALUES
|
||||||
(73, 70, 'system:external:update', '编辑三方系统', 'FUNCTION', 3, 'system', '2024-01-01 00:00:00', 'system', '2024-01-01 00:00:00', 0, FALSE),
|
(1, NOW(), 'Jenkins测试环境', 'JENKINS', 'http://jenkins-test.example.com', 'BASIC', 'admin', 'jenkins123', 1, 1),
|
||||||
(74, 70, 'system:external:delete', '删除三方系统', 'FUNCTION', 4, 'system', '2024-01-01 00:00:00', 'system', '2024-01-01 00:00:00', 0, FALSE),
|
(2, NOW(), 'Jenkins生产环境', 'JENKINS', 'http://jenkins-prod.example.com', 'BASIC', 'admin', 'jenkins123', 1, 2),
|
||||||
(75, 70, 'system:external:sync', '同步三方系统', 'FUNCTION', 5, 'system', '2024-01-01 00:00:00', 'system', '2024-01-01 00:00:00', 0, FALSE);
|
(3, NOW(), 'GitLab', 'GIT', 'http://gitlab.example.com', 'TOKEN', NULL, NULL, 1, 3),
|
||||||
|
(4, NOW(), '禅道', 'ZENTAO', 'http://zentao.example.com', 'BASIC', 'admin', 'zentao123', 1, 4);
|
||||||
-- 初始化角色权限关联数据
|
|
||||||
INSERT INTO sys_role_permission (role_id, permission_id)
|
|
||||||
VALUES
|
|
||||||
-- 用户管理权限
|
|
||||||
(1, 21),
|
|
||||||
(1, 22),
|
|
||||||
(1, 23),
|
|
||||||
(1, 24),
|
|
||||||
(1, 25),
|
|
||||||
-- 角色管理权限(包含权限管理功能)
|
|
||||||
(1, 31),
|
|
||||||
(1, 32),
|
|
||||||
(1, 33),
|
|
||||||
(1, 34),
|
|
||||||
(1, 35),
|
|
||||||
(1, 36),
|
|
||||||
-- 菜单管理权限
|
|
||||||
(1, 41),
|
|
||||||
(1, 42),
|
|
||||||
(1, 43),
|
|
||||||
-- 部门管理权限
|
|
||||||
(1, 51),
|
|
||||||
(1, 52),
|
|
||||||
(1, 53),
|
|
||||||
-- 三方系统管理权限
|
|
||||||
(1, 71),
|
|
||||||
(1, 72),
|
|
||||||
(1, 73),
|
|
||||||
(1, 74),
|
|
||||||
(1, 75);
|
|
||||||
|
|
||||||
-- 初始化三方系统数据
|
|
||||||
INSERT INTO sys_external_system (id, name, type, url, auth_type, username, password, enabled, sort, create_by, create_time, version, deleted, remark)
|
|
||||||
VALUES
|
|
||||||
-- Jenkins示例
|
|
||||||
(1, 'Jenkins-开发环境', 'JENKINS', 'http://jenkins-dev.example.com', 'BASIC', 'admin', 'password123', TRUE, 1, 'system', '2024-01-01 00:00:00', 0, FALSE, 'Jenkins开发环境'),
|
|
||||||
(2, 'Jenkins-测试环境', 'JENKINS', 'http://jenkins-test.example.com', 'BASIC', 'admin', 'password123', TRUE, 2, 'system', '2024-01-01 00:00:00', 0, FALSE, 'Jenkins测试环境'),
|
|
||||||
|
|
||||||
-- Git仓库示例
|
|
||||||
(3, 'GitLab-主库', 'GIT', 'http://gitlab.example.com', 'TOKEN', NULL, NULL, TRUE, 3, 'system', '2024-01-01 00:00:00', 0, FALSE, '公司GitLab主库'),
|
|
||||||
(4, 'GitHub', 'GIT', 'https://github.com', 'OAUTH', NULL, NULL, TRUE, 4, 'system', '2024-01-01 00:00:00', 0, FALSE, 'GitHub仓库'),
|
|
||||||
|
|
||||||
-- 禅道示例
|
|
||||||
(5, '禅道-项目管理', 'ZENTAO', 'http://zentao.example.com', 'BASIC', 'admin', 'password123', TRUE, 5, 'system', '2024-01-01 00:00:00', 0, FALSE, '禅道项目管理系统');
|
|
||||||
@ -63,4 +63,11 @@ permission.not.found=权限不存在
|
|||||||
permission.code.exists=权限编码{0}已存在
|
permission.code.exists=权限编码{0}已存在
|
||||||
permission.name.exists=权限名称{0}已存在
|
permission.name.exists=权限名称{0}已存在
|
||||||
permission.already.assigned=该权限已分配给角色
|
permission.already.assigned=该权限已分配给角色
|
||||||
permission.assign.failed=权限分配失败
|
permission.assign.failed=权限分配失败
|
||||||
|
|
||||||
|
# 第三方系统相关
|
||||||
|
external.system.name.exists=系统名称"{0}"已存在
|
||||||
|
external.system.type.url.exists=系统类型和URL组合"{0}"已存在
|
||||||
|
external.system.disabled=系统已禁用
|
||||||
|
external.system.sync.failed=系统数据同步失败
|
||||||
|
external.system.type.not.supported=不支持的系统类型
|
||||||
@ -1,52 +0,0 @@
|
|||||||
# Common Response
|
|
||||||
response.success=Success
|
|
||||||
response.error=System Error
|
|
||||||
response.invalid.param=Invalid Parameter
|
|
||||||
response.unauthorized=Unauthorized
|
|
||||||
response.forbidden=Forbidden
|
|
||||||
response.not.found=Resource Not Found
|
|
||||||
response.conflict=Resource Conflict
|
|
||||||
response.unauthorized.full=Full authentication is required to access this resource
|
|
||||||
|
|
||||||
# Business Error
|
|
||||||
tenant.not.found=Tenant not found
|
|
||||||
data.not.found={0} with id {1} not found
|
|
||||||
|
|
||||||
# User Related
|
|
||||||
user.not.found=User not found
|
|
||||||
user.username.exists=Username already exists
|
|
||||||
user.email.exists=Email already exists
|
|
||||||
user.login.error=Invalid username or password
|
|
||||||
|
|
||||||
# System Exception Messages
|
|
||||||
system.optimistic.lock.error=Data has been modified by another user, please refresh and try again
|
|
||||||
system.pessimistic.lock.error=Data is being operated by another user, please try again later
|
|
||||||
system.concurrent.update.error=Concurrent update conflict, please try again
|
|
||||||
system.retry.exceeded.error=Operation retry limit exceeded, please try again later
|
|
||||||
|
|
||||||
# Entity Not Found Messages
|
|
||||||
entity.not.found.id=Entity with id {0} not found
|
|
||||||
entity.not.found.message={0}
|
|
||||||
entity.not.found.name.id={0} with id {1} not found
|
|
||||||
|
|
||||||
# Dependency Injection
|
|
||||||
dependency.injection.service.not.found=No service found for entity {0} (tried bean name: {1})
|
|
||||||
dependency.injection.repository.not.found=No repository found for entity {0}: {1}
|
|
||||||
dependency.injection.converter.not.found=No converter found for entity {0}: {1}
|
|
||||||
dependency.injection.entitypath.failed=Failed to initialize EntityPath for entity {0}: {1}
|
|
||||||
|
|
||||||
# JWT
|
|
||||||
jwt.token.expired=Login expired, please login again
|
|
||||||
jwt.token.invalid=Invalid token
|
|
||||||
jwt.token.missing=No token provided
|
|
||||||
|
|
||||||
# Role related error messages
|
|
||||||
role.not.found=Role not found
|
|
||||||
role.code.exists=Role code already exists
|
|
||||||
role.name.exists=Role name already exists
|
|
||||||
role.in.use=Role is in use and cannot be deleted
|
|
||||||
role.admin.cannot.delete=Cannot delete admin role
|
|
||||||
role.admin.cannot.update=Cannot update admin role
|
|
||||||
role.tag.name.exists=Tag name already exists
|
|
||||||
role.tag.not.found=Tag not found
|
|
||||||
role.tag.in.use=Tag is in use and cannot be deleted
|
|
||||||
@ -0,0 +1,72 @@
|
|||||||
|
package com.qqchen.deploy.backend.api;
|
||||||
|
|
||||||
|
import com.qqchen.deploy.backend.entity.ExternalSystem;
|
||||||
|
import com.qqchen.deploy.backend.model.ExternalSystemDTO;
|
||||||
|
import com.qqchen.deploy.backend.service.IExternalSystemService;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
|
||||||
|
import org.springframework.boot.test.context.SpringBootTest;
|
||||||
|
import org.springframework.boot.test.mock.mockito.MockBean;
|
||||||
|
import org.springframework.http.MediaType;
|
||||||
|
import org.springframework.security.test.context.support.WithMockUser;
|
||||||
|
import org.springframework.test.web.servlet.MockMvc;
|
||||||
|
|
||||||
|
import static org.mockito.ArgumentMatchers.any;
|
||||||
|
import static org.mockito.ArgumentMatchers.eq;
|
||||||
|
import static org.mockito.Mockito.when;
|
||||||
|
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*;
|
||||||
|
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;
|
||||||
|
|
||||||
|
@SpringBootTest
|
||||||
|
@AutoConfigureMockMvc
|
||||||
|
@WithMockUser(username = "admin", roles = {"ADMIN"})
|
||||||
|
class ExternalSystemApiControllerTest {
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private MockMvc mockMvc;
|
||||||
|
|
||||||
|
@MockBean
|
||||||
|
private IExternalSystemService externalSystemService;
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testConnection_WhenSuccess_ShouldReturnTrue() throws Exception {
|
||||||
|
// Mock
|
||||||
|
when(externalSystemService.testConnection(1L)).thenReturn(true);
|
||||||
|
|
||||||
|
// 执行并验证
|
||||||
|
mockMvc.perform(get("/api/v1/external-system/1/test-connection"))
|
||||||
|
.andExpect(status().isOk())
|
||||||
|
.andExpect(jsonPath("$.code").value(200))
|
||||||
|
.andExpect(jsonPath("$.data").value(true));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testConnection_WhenFailed_ShouldReturnFalse() throws Exception {
|
||||||
|
// Mock
|
||||||
|
when(externalSystemService.testConnection(1L)).thenReturn(false);
|
||||||
|
|
||||||
|
// 执行并验证
|
||||||
|
mockMvc.perform(get("/api/v1/external-system/1/test-connection"))
|
||||||
|
.andExpect(status().isOk())
|
||||||
|
.andExpect(jsonPath("$.code").value(200))
|
||||||
|
.andExpect(jsonPath("$.data").value(false));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void syncData_ShouldReturnSuccess() throws Exception {
|
||||||
|
// 执行并验证
|
||||||
|
mockMvc.perform(post("/api/v1/external-system/1/sync"))
|
||||||
|
.andExpect(status().isOk())
|
||||||
|
.andExpect(jsonPath("$.code").value(200));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void updateStatus_ShouldReturnSuccess() throws Exception {
|
||||||
|
// 执行并验证
|
||||||
|
mockMvc.perform(put("/api/v1/external-system/1/status")
|
||||||
|
.param("enabled", "false"))
|
||||||
|
.andExpect(status().isOk())
|
||||||
|
.andExpect(jsonPath("$.code").value(200));
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,148 @@
|
|||||||
|
package com.qqchen.deploy.backend.service.impl;
|
||||||
|
|
||||||
|
import com.qqchen.deploy.backend.entity.ExternalSystem;
|
||||||
|
import com.qqchen.deploy.backend.framework.enums.ResponseCode;
|
||||||
|
import com.qqchen.deploy.backend.framework.exception.BusinessException;
|
||||||
|
import com.qqchen.deploy.backend.framework.exception.UniqueConstraintException;
|
||||||
|
import com.qqchen.deploy.backend.model.ExternalSystemDTO;
|
||||||
|
import com.qqchen.deploy.backend.repository.IExternalSystemRepository;
|
||||||
|
import org.junit.jupiter.api.BeforeEach;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.boot.test.context.SpringBootTest;
|
||||||
|
import org.springframework.boot.test.mock.mockito.MockBean;
|
||||||
|
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
import java.util.Optional;
|
||||||
|
|
||||||
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
|
import static org.assertj.core.api.Assertions.assertThatThrownBy;
|
||||||
|
import static org.mockito.ArgumentMatchers.any;
|
||||||
|
import static org.mockito.Mockito.*;
|
||||||
|
|
||||||
|
@SpringBootTest
|
||||||
|
class ExternalSystemServiceImplTest {
|
||||||
|
|
||||||
|
@MockBean
|
||||||
|
private IExternalSystemRepository externalSystemRepository;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private ExternalSystemServiceImpl externalSystemService;
|
||||||
|
|
||||||
|
private ExternalSystem system;
|
||||||
|
private ExternalSystemDTO systemDTO;
|
||||||
|
|
||||||
|
@BeforeEach
|
||||||
|
void setUp() {
|
||||||
|
// 准备测试数据
|
||||||
|
system = new ExternalSystem();
|
||||||
|
system.setId(1L);
|
||||||
|
system.setName("测试Jenkins");
|
||||||
|
system.setType(ExternalSystem.SystemType.JENKINS);
|
||||||
|
system.setUrl("http://jenkins.test.com");
|
||||||
|
system.setAuthType(ExternalSystem.AuthType.BASIC);
|
||||||
|
system.setUsername("admin");
|
||||||
|
system.setPassword("password");
|
||||||
|
system.setEnabled(true);
|
||||||
|
system.setSort(1);
|
||||||
|
|
||||||
|
systemDTO = new ExternalSystemDTO();
|
||||||
|
systemDTO.setName("测试Jenkins");
|
||||||
|
systemDTO.setType(ExternalSystem.SystemType.JENKINS);
|
||||||
|
systemDTO.setUrl("http://jenkins.test.com");
|
||||||
|
systemDTO.setAuthType(ExternalSystem.AuthType.BASIC);
|
||||||
|
systemDTO.setUsername("admin");
|
||||||
|
systemDTO.setPassword("password");
|
||||||
|
systemDTO.setEnabled(true);
|
||||||
|
systemDTO.setSort(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void validateUniqueConstraints_WhenNameExists_ShouldThrowException() {
|
||||||
|
// Mock
|
||||||
|
when(externalSystemRepository.existsByNameAndDeletedFalse(systemDTO.getName())).thenReturn(true);
|
||||||
|
|
||||||
|
// 验证
|
||||||
|
assertThatThrownBy(() -> externalSystemService.validateUniqueConstraints(systemDTO))
|
||||||
|
.isInstanceOf(UniqueConstraintException.class)
|
||||||
|
.hasFieldOrPropertyWithValue("errorCode", ResponseCode.EXTERNAL_SYSTEM_NAME_EXISTS);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void validateUniqueConstraints_WhenTypeAndUrlExists_ShouldThrowException() {
|
||||||
|
// Mock
|
||||||
|
when(externalSystemRepository.existsByNameAndDeletedFalse(systemDTO.getName())).thenReturn(false);
|
||||||
|
when(externalSystemRepository.existsByTypeAndUrlAndDeletedFalse(systemDTO.getType(), systemDTO.getUrl()))
|
||||||
|
.thenReturn(true);
|
||||||
|
|
||||||
|
// 验证
|
||||||
|
assertThatThrownBy(() -> externalSystemService.validateUniqueConstraints(systemDTO))
|
||||||
|
.isInstanceOf(UniqueConstraintException.class)
|
||||||
|
.hasFieldOrPropertyWithValue("errorCode", ResponseCode.EXTERNAL_SYSTEM_TYPE_URL_EXISTS);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testConnection_WhenSystemDisabled_ShouldThrowException() {
|
||||||
|
// 准备数据
|
||||||
|
system.setEnabled(false);
|
||||||
|
when(externalSystemRepository.findById(1L)).thenReturn(Optional.of(system));
|
||||||
|
|
||||||
|
// 验证
|
||||||
|
assertThatThrownBy(() -> externalSystemService.testConnection(1L))
|
||||||
|
.isInstanceOf(BusinessException.class)
|
||||||
|
.hasFieldOrPropertyWithValue("errorCode", ResponseCode.EXTERNAL_SYSTEM_DISABLED);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testConnection_WhenSystemEnabled_ShouldReturnTrue() {
|
||||||
|
// Mock
|
||||||
|
when(externalSystemRepository.findById(1L)).thenReturn(Optional.of(system));
|
||||||
|
|
||||||
|
// 执行
|
||||||
|
boolean result = externalSystemService.testConnection(1L);
|
||||||
|
|
||||||
|
// 验证
|
||||||
|
assertThat(result).isTrue();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void syncData_WhenSystemDisabled_ShouldThrowException() {
|
||||||
|
// 准备数据
|
||||||
|
system.setEnabled(false);
|
||||||
|
when(externalSystemRepository.findById(1L)).thenReturn(Optional.of(system));
|
||||||
|
|
||||||
|
// 验证
|
||||||
|
assertThatThrownBy(() -> externalSystemService.syncData(1L))
|
||||||
|
.isInstanceOf(BusinessException.class)
|
||||||
|
.hasFieldOrPropertyWithValue("errorCode", ResponseCode.EXTERNAL_SYSTEM_DISABLED);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void syncData_WhenSuccessful_ShouldUpdateStatus() {
|
||||||
|
// Mock
|
||||||
|
when(externalSystemRepository.findById(1L)).thenReturn(Optional.of(system));
|
||||||
|
when(externalSystemRepository.save(any(ExternalSystem.class))).thenReturn(system);
|
||||||
|
|
||||||
|
// 执行
|
||||||
|
externalSystemService.syncData(1L);
|
||||||
|
|
||||||
|
// 验证
|
||||||
|
assertThat(system.getSyncStatus()).isEqualTo(ExternalSystem.SyncStatus.SUCCESS);
|
||||||
|
assertThat(system.getLastSyncTime()).isNotNull();
|
||||||
|
verify(externalSystemRepository, times(2)).save(any(ExternalSystem.class));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void updateStatus_ShouldUpdateSystemStatus() {
|
||||||
|
// Mock
|
||||||
|
when(externalSystemRepository.findById(1L)).thenReturn(Optional.of(system));
|
||||||
|
when(externalSystemRepository.save(any(ExternalSystem.class))).thenReturn(system);
|
||||||
|
|
||||||
|
// 执行
|
||||||
|
externalSystemService.updateStatus(1L, false);
|
||||||
|
|
||||||
|
// 验证
|
||||||
|
assertThat(system.getEnabled()).isFalse();
|
||||||
|
verify(externalSystemRepository).save(system);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,235 @@
|
|||||||
|
package com.qqchen.deploy.backend.service.impl;
|
||||||
|
|
||||||
|
import com.qqchen.deploy.backend.converter.MenuConverter;
|
||||||
|
import com.qqchen.deploy.backend.entity.Menu;
|
||||||
|
import com.qqchen.deploy.backend.entity.Permission;
|
||||||
|
import com.qqchen.deploy.backend.model.MenuDTO;
|
||||||
|
import com.qqchen.deploy.backend.model.response.MenuPermissionTreeResponse;
|
||||||
|
import com.qqchen.deploy.backend.model.response.MenuResponse;
|
||||||
|
import com.qqchen.deploy.backend.model.response.PermissionResponse;
|
||||||
|
import com.qqchen.deploy.backend.repository.IMenuRepository;
|
||||||
|
import com.qqchen.deploy.backend.repository.IPermissionRepository;
|
||||||
|
import org.junit.jupiter.api.BeforeEach;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.boot.test.context.SpringBootTest;
|
||||||
|
import org.springframework.boot.test.mock.mockito.MockBean;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
|
import static org.mockito.ArgumentMatchers.any;
|
||||||
|
import static org.mockito.Mockito.when;
|
||||||
|
|
||||||
|
@SpringBootTest
|
||||||
|
class MenuServiceImplTest {
|
||||||
|
|
||||||
|
@MockBean
|
||||||
|
private IMenuRepository menuRepository;
|
||||||
|
|
||||||
|
@MockBean
|
||||||
|
private IPermissionRepository permissionRepository;
|
||||||
|
|
||||||
|
@MockBean
|
||||||
|
private MenuConverter menuConverter;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private MenuServiceImpl menuService;
|
||||||
|
|
||||||
|
private Menu rootMenu;
|
||||||
|
private Menu childMenu;
|
||||||
|
private Permission permission1;
|
||||||
|
private Permission permission2;
|
||||||
|
|
||||||
|
@BeforeEach
|
||||||
|
void setUp() {
|
||||||
|
// 准备测试数据
|
||||||
|
rootMenu = new Menu();
|
||||||
|
rootMenu.setId(1L);
|
||||||
|
rootMenu.setName("根菜单");
|
||||||
|
rootMenu.setSort(1);
|
||||||
|
rootMenu.setType(1);
|
||||||
|
rootMenu.setParentId(0L);
|
||||||
|
|
||||||
|
childMenu = new Menu();
|
||||||
|
childMenu.setId(2L);
|
||||||
|
childMenu.setName("子菜单");
|
||||||
|
childMenu.setSort(1);
|
||||||
|
childMenu.setType(2);
|
||||||
|
childMenu.setParentId(1L);
|
||||||
|
|
||||||
|
permission1 = new Permission();
|
||||||
|
permission1.setId(1L);
|
||||||
|
permission1.setMenuId(2L);
|
||||||
|
permission1.setName("查看");
|
||||||
|
permission1.setCode("VIEW");
|
||||||
|
permission1.setSort(1);
|
||||||
|
|
||||||
|
permission2 = new Permission();
|
||||||
|
permission2.setId(2L);
|
||||||
|
permission2.setMenuId(2L);
|
||||||
|
permission2.setName("编辑");
|
||||||
|
permission2.setCode("EDIT");
|
||||||
|
permission2.setSort(2);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void getPermissionTree_ShouldReturnCorrectStructure() {
|
||||||
|
// 准备测试数据
|
||||||
|
List<Menu> menus = Arrays.asList(rootMenu, childMenu);
|
||||||
|
List<Permission> permissions = Arrays.asList(permission1, permission2);
|
||||||
|
|
||||||
|
// Mock Repository方法
|
||||||
|
when(menuRepository.findByDeletedFalseOrderBySort()).thenReturn(menus);
|
||||||
|
when(permissionRepository.findAllByDeletedFalseOrderBySort()).thenReturn(permissions);
|
||||||
|
|
||||||
|
// Mock Converter方法
|
||||||
|
MenuPermissionTreeResponse rootResponse = createMenuPermissionResponse(rootMenu);
|
||||||
|
MenuPermissionTreeResponse childResponse = createMenuPermissionResponse(childMenu);
|
||||||
|
when(menuConverter.toMenuPermissionResponse(rootMenu)).thenReturn(rootResponse);
|
||||||
|
when(menuConverter.toMenuPermissionResponse(childMenu)).thenReturn(childResponse);
|
||||||
|
when(menuConverter.toPermissionResponseList(Arrays.asList(permission1, permission2)))
|
||||||
|
.thenReturn(Arrays.asList(
|
||||||
|
createPermissionResponse(permission1),
|
||||||
|
createPermissionResponse(permission2)
|
||||||
|
));
|
||||||
|
|
||||||
|
// 执行测试
|
||||||
|
List<MenuPermissionTreeResponse> result = menuService.getPermissionTree();
|
||||||
|
|
||||||
|
// 验证结果
|
||||||
|
assertThat(result).isNotNull();
|
||||||
|
assertThat(result).hasSize(1); // 只有一个根节点
|
||||||
|
|
||||||
|
MenuPermissionTreeResponse root = result.get(0);
|
||||||
|
assertThat(root.getId()).isEqualTo(1L);
|
||||||
|
assertThat(root.getName()).isEqualTo("根菜单");
|
||||||
|
assertThat(root.getPermissionChildren()).hasSize(1); // 有一个子节点
|
||||||
|
|
||||||
|
MenuPermissionTreeResponse child = root.getPermissionChildren().get(0);
|
||||||
|
assertThat(child.getId()).isEqualTo(2L);
|
||||||
|
assertThat(child.getName()).isEqualTo("子菜单");
|
||||||
|
assertThat(child.getPermissions()).hasSize(2); // 有两个权限
|
||||||
|
|
||||||
|
PermissionResponse firstPermission = child.getPermissions().get(0);
|
||||||
|
assertThat(firstPermission.getCode()).isEqualTo("VIEW");
|
||||||
|
assertThat(firstPermission.getName()).isEqualTo("查看");
|
||||||
|
}
|
||||||
|
|
||||||
|
private MenuPermissionTreeResponse createMenuPermissionResponse(Menu menu) {
|
||||||
|
MenuPermissionTreeResponse response = new MenuPermissionTreeResponse();
|
||||||
|
response.setId(menu.getId());
|
||||||
|
response.setName(menu.getName());
|
||||||
|
response.setParentId(menu.getParentId());
|
||||||
|
response.setType(menu.getType());
|
||||||
|
response.setSort(menu.getSort());
|
||||||
|
return response;
|
||||||
|
}
|
||||||
|
|
||||||
|
private PermissionResponse createPermissionResponse(Permission permission) {
|
||||||
|
PermissionResponse response = new PermissionResponse();
|
||||||
|
response.setId(permission.getId());
|
||||||
|
response.setCode(permission.getCode());
|
||||||
|
response.setName(permission.getName());
|
||||||
|
response.setType(permission.getType());
|
||||||
|
response.setSort(permission.getSort());
|
||||||
|
return response;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void getPermissionTree_WithEmptyData_ShouldReturnEmptyList() {
|
||||||
|
// Mock空数据
|
||||||
|
when(menuRepository.findByDeletedFalseOrderBySort()).thenReturn(List.of());
|
||||||
|
when(permissionRepository.findAllByDeletedFalseOrderBySort()).thenReturn(List.of());
|
||||||
|
|
||||||
|
// 执行测试
|
||||||
|
List<MenuPermissionTreeResponse> result = menuService.getPermissionTree();
|
||||||
|
|
||||||
|
// 验证结果
|
||||||
|
assertThat(result).isNotNull();
|
||||||
|
assertThat(result).isEmpty();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void getMenuTree_ShouldReturnCorrectStructure() {
|
||||||
|
// 准备测试数据
|
||||||
|
List<Menu> menus = Arrays.asList(rootMenu, childMenu);
|
||||||
|
|
||||||
|
// 准备DTO对象
|
||||||
|
MenuDTO rootDto = convertToDto(rootMenu);
|
||||||
|
MenuDTO childDto = convertToDto(childMenu);
|
||||||
|
rootDto.setChildren(new ArrayList<>(Arrays.asList(childDto)));
|
||||||
|
List<MenuDTO> dtoList = new ArrayList<>(Arrays.asList(rootDto, childDto));
|
||||||
|
|
||||||
|
// 准备Response对象
|
||||||
|
MenuResponse rootResponse = createMenuResponse(rootMenu);
|
||||||
|
MenuResponse childResponse = createMenuResponse(childMenu);
|
||||||
|
rootResponse.setChildren(new ArrayList<>(Arrays.asList(childResponse)));
|
||||||
|
List<MenuResponse> responseList = new ArrayList<>(Arrays.asList(rootResponse));
|
||||||
|
|
||||||
|
// Mock Repository方法
|
||||||
|
when(menuRepository.findByDeletedFalseOrderBySort()).thenReturn(menus);
|
||||||
|
|
||||||
|
// Mock Converter方法
|
||||||
|
when(menuConverter.toDtoList(menus)).thenReturn(dtoList);
|
||||||
|
when(menuConverter.toResponseList(List.of(rootDto))).thenReturn(responseList);
|
||||||
|
|
||||||
|
// 执行测试
|
||||||
|
List<MenuResponse> result = menuService.getMenuTree();
|
||||||
|
|
||||||
|
// 验证结果
|
||||||
|
assertThat(result).isNotNull();
|
||||||
|
assertThat(result).hasSize(1); // 只有一个根节点
|
||||||
|
|
||||||
|
MenuResponse root = result.get(0);
|
||||||
|
assertThat(root.getId()).isEqualTo(1L);
|
||||||
|
assertThat(root.getName()).isEqualTo("根菜单");
|
||||||
|
assertThat(root.getType()).isEqualTo(1);
|
||||||
|
assertThat(root.getParentId()).isEqualTo(0L);
|
||||||
|
assertThat(root.getChildren()).hasSize(1); // 有一个子节点
|
||||||
|
|
||||||
|
MenuResponse child = root.getChildren().get(0);
|
||||||
|
assertThat(child.getId()).isEqualTo(2L);
|
||||||
|
assertThat(child.getName()).isEqualTo("子菜单");
|
||||||
|
assertThat(child.getType()).isEqualTo(2);
|
||||||
|
assertThat(child.getParentId()).isEqualTo(1L);
|
||||||
|
assertThat(child.getChildren()).isEmpty(); // 没有子节点
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void getMenuTree_WithEmptyData_ShouldReturnEmptyList() {
|
||||||
|
// Mock空数据
|
||||||
|
when(menuRepository.findByDeletedFalseOrderBySort()).thenReturn(List.of());
|
||||||
|
|
||||||
|
// 执行测试
|
||||||
|
List<MenuResponse> result = menuService.getMenuTree();
|
||||||
|
|
||||||
|
// 验证结果
|
||||||
|
assertThat(result).isNotNull();
|
||||||
|
assertThat(result).isEmpty();
|
||||||
|
}
|
||||||
|
|
||||||
|
private MenuResponse createMenuResponse(Menu menu) {
|
||||||
|
MenuResponse response = new MenuResponse();
|
||||||
|
response.setId(menu.getId());
|
||||||
|
response.setName(menu.getName());
|
||||||
|
response.setParentId(menu.getParentId());
|
||||||
|
response.setType(menu.getType());
|
||||||
|
response.setSort(menu.getSort());
|
||||||
|
response.setChildren(new ArrayList<>());
|
||||||
|
return response;
|
||||||
|
}
|
||||||
|
|
||||||
|
private MenuDTO convertToDto(Menu menu) {
|
||||||
|
MenuDTO dto = new MenuDTO();
|
||||||
|
dto.setId(menu.getId());
|
||||||
|
dto.setName(menu.getName());
|
||||||
|
dto.setParentId(menu.getParentId());
|
||||||
|
dto.setType(menu.getType());
|
||||||
|
dto.setSort(menu.getSort());
|
||||||
|
dto.setChildren(new ArrayList<>());
|
||||||
|
return dto;
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue
Block a user