From 94aeb16a61411b7a19d67c7372b12c86cc9d0bd5 Mon Sep 17 00:00:00 2001 From: dengqichen Date: Wed, 27 Nov 2024 15:06:43 +0800 Subject: [PATCH] =?UTF-8?q?=E5=8F=AF=E7=9B=B4=E6=8E=A5=E8=BF=90=E8=A1=8C?= =?UTF-8?q?=EF=BC=8C=E4=BF=AE=E6=94=B9=E4=BA=86=E5=AE=9E=E4=BD=93=E7=B1=BB?= =?UTF-8?q?=E7=9A=84=E5=85=B3=E7=B3=BB=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/README.md | 127 ++++ backend/pom.xml | 39 + backend/readme.md | 15 - .../deploy/backend/BackendApplication.java | 2 + .../deploy/backend/api/UserApiController.java | 8 +- ...{JpaConfig.java => JpaAuditingConfig.java} | 5 +- .../common/controller/BaseController.java | 32 +- .../common/integration/dto/ThirdPartyDTO.java | 5 + .../common/repository/BaseRepository.java | 34 - .../common/repository/IBaseRepository.java | 129 ++++ .../{ => security}/config/SecurityConfig.java | 16 +- .../filter/JwtAuthenticationFilter.java | 59 ++ .../common/security/util/JwtTokenUtil.java | 81 +++ .../{BaseService.java => IBaseService.java} | 4 +- .../common/service/impl/BaseServiceImpl.java | 54 +- .../backend/controller/UserController.java | 7 +- .../deploy/backend/entity/Department.java | 15 + .../qqchen/deploy/backend/entity/Menu.java | 3 + .../qqchen/deploy/backend/entity/Role.java | 10 +- .../qqchen/deploy/backend/entity/User.java | 28 +- .../deploy/backend/entity/UserDepartment.java | 48 ++ .../deploy/backend/entity/UserRole.java | 12 +- .../backend/integration/JenkinsService.java | 79 ++ .../integration/client/IJenkinsClient.java | 24 + .../integration/config/JenkinsProperties.java | 22 + .../backend/integration/dto/BuildInfo.java | 22 + .../integration/dto/JenkinsBuildDTO.java | 20 + .../integration/dto/JenkinsConnectionDTO.java | 14 + .../integration/enums/JenkinsBuildStatus.java | 5 + ...sitory.java => IDepartmentRepository.java} | 5 +- ...tory.java => IJenkinsBuildRepository.java} | 4 +- ...ory.java => IJenkinsConfigRepository.java} | 4 +- ...sitory.java => IJenkinsJobRepository.java} | 4 +- ...ava => IJenkinsSyncHistoryRepository.java} | 4 +- ...itory.java => IJenkinsViewRepository.java} | 4 +- ...nuRepository.java => IMenuRepository.java} | 3 +- ....java => IRepositoryBranchRepository.java} | 5 +- ....java => IRepositoryConfigRepository.java} | 4 +- ...y.java => IRepositoryGroupRepository.java} | 4 +- ...java => IRepositoryProjectRepository.java} | 4 +- ... => IRepositorySyncHistoryRepository.java} | 4 +- ...pository.java => IRoleMenuRepository.java} | 4 +- ...leRepository.java => IRoleRepository.java} | 4 +- ...Repository.java => ITenantRepository.java} | 4 +- ...erRepository.java => IUserRepository.java} | 4 +- ...pository.java => IUserRoleRepository.java} | 4 +- .../backend/service/IDepartmentService.java | 18 + .../backend/service/IJenkinsService.java | 27 + .../deploy/backend/service/IMenuService.java | 20 + .../backend/service/IRepositoryService.java | 46 ++ .../deploy/backend/service/IRoleService.java | 18 + .../backend/service/ITenantService.java | 21 + .../{UserService.java => IUserService.java} | 4 +- .../service/impl/DepartmentServiceImpl.java | 105 +++ .../service/impl/IJenkinsServiceImpl.java | 612 ++++++++++++++++ .../service/impl/IMenuServiceImpl.java | 254 +++++++ .../service/impl/RepositoryServiceImpl.java | 688 ++++++++++++++++++ .../backend/service/impl/RoleServiceImpl.java | 139 ++++ .../service/impl/TenantServiceImpl.java | 113 +++ .../service/impl/UserDetailsServiceImpl.java | 37 + .../backend/service/impl/UserServiceImpl.java | 12 +- backend/src/main/resources/application.yml | 6 +- 62 files changed, 2964 insertions(+), 144 deletions(-) create mode 100644 backend/README.md delete mode 100644 backend/readme.md rename backend/src/main/java/com/qqchen/deploy/backend/common/config/{JpaConfig.java => JpaAuditingConfig.java} (90%) create mode 100644 backend/src/main/java/com/qqchen/deploy/backend/common/integration/dto/ThirdPartyDTO.java delete mode 100644 backend/src/main/java/com/qqchen/deploy/backend/common/repository/BaseRepository.java create mode 100644 backend/src/main/java/com/qqchen/deploy/backend/common/repository/IBaseRepository.java rename backend/src/main/java/com/qqchen/deploy/backend/common/{ => security}/config/SecurityConfig.java (82%) create mode 100644 backend/src/main/java/com/qqchen/deploy/backend/common/security/filter/JwtAuthenticationFilter.java create mode 100644 backend/src/main/java/com/qqchen/deploy/backend/common/security/util/JwtTokenUtil.java rename backend/src/main/java/com/qqchen/deploy/backend/common/service/{BaseService.java => IBaseService.java} (78%) create mode 100644 backend/src/main/java/com/qqchen/deploy/backend/entity/UserDepartment.java create mode 100644 backend/src/main/java/com/qqchen/deploy/backend/integration/JenkinsService.java create mode 100644 backend/src/main/java/com/qqchen/deploy/backend/integration/client/IJenkinsClient.java create mode 100644 backend/src/main/java/com/qqchen/deploy/backend/integration/config/JenkinsProperties.java create mode 100644 backend/src/main/java/com/qqchen/deploy/backend/integration/dto/BuildInfo.java create mode 100644 backend/src/main/java/com/qqchen/deploy/backend/integration/dto/JenkinsBuildDTO.java create mode 100644 backend/src/main/java/com/qqchen/deploy/backend/integration/dto/JenkinsConnectionDTO.java create mode 100644 backend/src/main/java/com/qqchen/deploy/backend/integration/enums/JenkinsBuildStatus.java rename backend/src/main/java/com/qqchen/deploy/backend/repository/{DepartmentRepository.java => IDepartmentRepository.java} (76%) rename backend/src/main/java/com/qqchen/deploy/backend/repository/{JenkinsBuildRepository.java => IJenkinsBuildRepository.java} (75%) rename backend/src/main/java/com/qqchen/deploy/backend/repository/{JenkinsConfigRepository.java => IJenkinsConfigRepository.java} (65%) rename backend/src/main/java/com/qqchen/deploy/backend/repository/{JenkinsJobRepository.java => IJenkinsJobRepository.java} (85%) rename backend/src/main/java/com/qqchen/deploy/backend/repository/{JenkinsSyncHistoryRepository.java => IJenkinsSyncHistoryRepository.java} (60%) rename backend/src/main/java/com/qqchen/deploy/backend/repository/{JenkinsViewRepository.java => IJenkinsViewRepository.java} (72%) rename backend/src/main/java/com/qqchen/deploy/backend/repository/{MenuRepository.java => IMenuRepository.java} (85%) rename backend/src/main/java/com/qqchen/deploy/backend/repository/{RepositoryBranchRepository.java => IRepositoryBranchRepository.java} (69%) rename backend/src/main/java/com/qqchen/deploy/backend/repository/{RepositoryConfigRepository.java => IRepositoryConfigRepository.java} (65%) rename backend/src/main/java/com/qqchen/deploy/backend/repository/{RepositoryGroupRepository.java => IRepositoryGroupRepository.java} (79%) rename backend/src/main/java/com/qqchen/deploy/backend/repository/{RepositoryProjectRepository.java => IRepositoryProjectRepository.java} (79%) rename backend/src/main/java/com/qqchen/deploy/backend/repository/{RepositorySyncHistoryRepository.java => IRepositorySyncHistoryRepository.java} (60%) rename backend/src/main/java/com/qqchen/deploy/backend/repository/{RoleMenuRepository.java => IRoleMenuRepository.java} (69%) rename backend/src/main/java/com/qqchen/deploy/backend/repository/{RoleRepository.java => IRoleRepository.java} (75%) rename backend/src/main/java/com/qqchen/deploy/backend/repository/{TenantRepository.java => ITenantRepository.java} (70%) rename backend/src/main/java/com/qqchen/deploy/backend/repository/{UserRepository.java => IUserRepository.java} (81%) rename backend/src/main/java/com/qqchen/deploy/backend/repository/{UserRoleRepository.java => IUserRoleRepository.java} (85%) create mode 100644 backend/src/main/java/com/qqchen/deploy/backend/service/IDepartmentService.java create mode 100644 backend/src/main/java/com/qqchen/deploy/backend/service/IJenkinsService.java create mode 100644 backend/src/main/java/com/qqchen/deploy/backend/service/IMenuService.java create mode 100644 backend/src/main/java/com/qqchen/deploy/backend/service/IRepositoryService.java create mode 100644 backend/src/main/java/com/qqchen/deploy/backend/service/IRoleService.java create mode 100644 backend/src/main/java/com/qqchen/deploy/backend/service/ITenantService.java rename backend/src/main/java/com/qqchen/deploy/backend/service/{UserService.java => IUserService.java} (66%) create mode 100644 backend/src/main/java/com/qqchen/deploy/backend/service/impl/DepartmentServiceImpl.java create mode 100644 backend/src/main/java/com/qqchen/deploy/backend/service/impl/IJenkinsServiceImpl.java create mode 100644 backend/src/main/java/com/qqchen/deploy/backend/service/impl/IMenuServiceImpl.java create mode 100644 backend/src/main/java/com/qqchen/deploy/backend/service/impl/RepositoryServiceImpl.java create mode 100644 backend/src/main/java/com/qqchen/deploy/backend/service/impl/RoleServiceImpl.java create mode 100644 backend/src/main/java/com/qqchen/deploy/backend/service/impl/TenantServiceImpl.java create mode 100644 backend/src/main/java/com/qqchen/deploy/backend/service/impl/UserDetailsServiceImpl.java diff --git a/backend/README.md b/backend/README.md new file mode 100644 index 00000000..78df908f --- /dev/null +++ b/backend/README.md @@ -0,0 +1,127 @@ +` +分层结构从上到下: +` +``` +Controller (表现层) + ↓ +Service (业务层) + ↓ +Repository (数据访问层) +``` +` +对于外部集成: +` +``` +Controller + ↓ +Service ←→ Integration Service (集成层) + ↓ +Repository +``` + +``` +GET /api/users - 查询所有用户 +GET /api/users/list?enabled=true - 查询所有启用的用户 +GET /api/users/page?pageNum=1&pageSize=10 - 分页查询用户 +``` + +`查询日期示例` +``` +UserQuery query = new UserQuery(); +query.setCreateTimeRange( + LocalDateTime.now().minusDays(7), // 一周内 + LocalDateTime.now() +); +query.setEnabled(true); +query.setCreateBy("admin"); + + +@QueryField(type = QueryType.IN) +private String status; // 可以传入 "ACTIVE,PENDING,CLOSED" + +``` +``` +QueryDSL使用方法 +@Service +@Transactional +public class UserServiceImpl extends BaseServiceImpl implements UserService { + + private final UserRoleRepository userRoleRepository; + private final QUserRole qUserRole = QUserRole.userRole; + + public UserServiceImpl(UserRepository userRepository, UserRoleRepository userRoleRepository) { + super(userRepository); + this.userRoleRepository = userRoleRepository; + } + + public Set getUserRoles(Long userId) { + // 使用QueryDSL构建查询条件 + Predicate predicate = qUserRole.user.id.eq(userId); + return StreamSupport.stream( + userRoleRepository.findAll(predicate).spliterator(), + false + ).collect(Collectors.toSet()); + } + + public void assignRole(Long userId, Long roleId) { + User user = findById(userId); + Role role = roleRepository.findById(roleId) + .orElseThrow(() -> new RuntimeException("Role not found")); + + // 检查是否已存在 + 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); + } + } +} +``` + +``` +-- 插入正常用户数据 +INSERT INTO sys_user ( + 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, '技术部', +'admin', '2024-01-02 10:00:00', null, null, 0), + +('user02', '$2a$10$mW/yJPHjyueQ1g26YxiZNOtr6bKVF4P/w/VHLVHHhxslY.YlXhbcm', '李四', 'lisi@example.com', '13800138002', +true, false, 2, '市场部', +'admin', '2024-01-03 11:00:00', 'admin', '2024-01-04 15:00:00', 1), + +-- 已禁用的用户 +('disabled_user', '$2a$10$mW/yJPHjyueQ1g26YxiZNOtr6bKVF4P/w/VHLVHHhxslY.YlXhbcm', '王五', 'wangwu@example.com', '13800138003', +false, false, 2, '市场部', +'admin', '2024-01-05 14:00:00', 'admin', '2024-01-06 16:00:00', 1), + +-- 已删除的用户 +('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); +``` \ No newline at end of file diff --git a/backend/pom.xml b/backend/pom.xml index a8126d11..8274d359 100644 --- a/backend/pom.xml +++ b/backend/pom.xml @@ -18,12 +18,26 @@ 21 21 21 + 2023.0.0 1.5.5.Final 5.0.0 1.18.30 6.2.0 + 0.12.3 + + + + org.springframework.cloud + spring-cloud-dependencies + ${spring-cloud.version} + pom + import + + + + @@ -101,6 +115,31 @@ spring-security-test test + + + + org.springframework.cloud + spring-cloud-starter-openfeign + + + + + io.jsonwebtoken + jjwt-api + ${jjwt.version} + + + io.jsonwebtoken + jjwt-impl + ${jjwt.version} + runtime + + + io.jsonwebtoken + jjwt-jackson + ${jjwt.version} + runtime + diff --git a/backend/readme.md b/backend/readme.md deleted file mode 100644 index 411a3607..00000000 --- a/backend/readme.md +++ /dev/null @@ -1,15 +0,0 @@ -`查询日期示例` -``` -UserQuery query = new UserQuery(); -query.setCreateTimeRange( - LocalDateTime.now().minusDays(7), // 一周内 - LocalDateTime.now() -); -query.setEnabled(true); -query.setCreateBy("admin"); - - -@QueryField(type = QueryType.IN) -private String status; // 可以传入 "ACTIVE,PENDING,CLOSED" - -``` \ No newline at end of file diff --git a/backend/src/main/java/com/qqchen/deploy/backend/BackendApplication.java b/backend/src/main/java/com/qqchen/deploy/backend/BackendApplication.java index f7c3b77a..8914c48f 100644 --- a/backend/src/main/java/com/qqchen/deploy/backend/BackendApplication.java +++ b/backend/src/main/java/com/qqchen/deploy/backend/BackendApplication.java @@ -2,9 +2,11 @@ package com.qqchen.deploy.backend; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.cloud.openfeign.EnableFeignClients; import org.springframework.data.jpa.repository.config.EnableJpaRepositories; @SpringBootApplication +@EnableFeignClients public class BackendApplication { public static void main(String[] args) { diff --git a/backend/src/main/java/com/qqchen/deploy/backend/api/UserApiController.java b/backend/src/main/java/com/qqchen/deploy/backend/api/UserApiController.java index 7dcf89a0..34a9ca91 100644 --- a/backend/src/main/java/com/qqchen/deploy/backend/api/UserApiController.java +++ b/backend/src/main/java/com/qqchen/deploy/backend/api/UserApiController.java @@ -9,7 +9,7 @@ import com.qqchen.deploy.backend.dto.query.UserQuery; import com.qqchen.deploy.backend.dto.request.UserRequest; import com.qqchen.deploy.backend.dto.response.UserResponse; import com.qqchen.deploy.backend.dto.request.UserRegisterRequest; -import com.qqchen.deploy.backend.service.UserService; +import com.qqchen.deploy.backend.service.IUserService; import org.springframework.data.domain.Page; import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.GetMapping; @@ -22,11 +22,11 @@ import org.springframework.web.bind.annotation.RestController; @RequestMapping("/api/v1/users") public class UserApiController extends BaseController { - protected final UserService userService; + protected final IUserService userService; - public UserApiController(UserService userService, UserConverter converter) { + public UserApiController(IUserService userService, UserConverter converter) { + super(userService, converter); this.userService = userService; - this.converter = converter; } @PostMapping("/register") diff --git a/backend/src/main/java/com/qqchen/deploy/backend/common/config/JpaConfig.java b/backend/src/main/java/com/qqchen/deploy/backend/common/config/JpaAuditingConfig.java similarity index 90% rename from backend/src/main/java/com/qqchen/deploy/backend/common/config/JpaConfig.java rename to backend/src/main/java/com/qqchen/deploy/backend/common/config/JpaAuditingConfig.java index 9eecba5e..0e8172a6 100644 --- a/backend/src/main/java/com/qqchen/deploy/backend/common/config/JpaConfig.java +++ b/backend/src/main/java/com/qqchen/deploy/backend/common/config/JpaAuditingConfig.java @@ -10,9 +10,8 @@ import org.springframework.security.core.context.SecurityContextHolder; import java.util.Optional; @Configuration -@EnableJpaAuditing -public class JpaConfig { - +@EnableJpaAuditing(auditorAwareRef = "auditorProvider") +public class JpaAuditingConfig { @Bean public AuditorAware auditorProvider() { return () -> { diff --git a/backend/src/main/java/com/qqchen/deploy/backend/common/controller/BaseController.java b/backend/src/main/java/com/qqchen/deploy/backend/common/controller/BaseController.java index 4c0b9457..6f5f5439 100644 --- a/backend/src/main/java/com/qqchen/deploy/backend/common/controller/BaseController.java +++ b/backend/src/main/java/com/qqchen/deploy/backend/common/controller/BaseController.java @@ -2,11 +2,11 @@ package com.qqchen.deploy.backend.common.controller; import com.qqchen.deploy.backend.common.domain.Entity; import com.qqchen.deploy.backend.common.converter.BaseConverter; +import com.qqchen.deploy.backend.common.dto.BaseResponse; import com.qqchen.deploy.backend.common.query.BaseQuery; import com.qqchen.deploy.backend.common.dto.BaseRequest; import com.qqchen.deploy.backend.common.api.Response; -import com.qqchen.deploy.backend.common.service.BaseService; -import jakarta.annotation.Resource; +import com.qqchen.deploy.backend.common.service.IBaseService; import org.springframework.data.domain.Page; import org.springframework.web.bind.annotation.*; @@ -16,23 +16,25 @@ import java.util.List; /** * 通用REST控制器 */ -public abstract class BaseController, ID extends Serializable, Q extends BaseQuery, R extends BaseRequest, V> { +public abstract class BaseController, ID extends Serializable, Q extends BaseQuery, REQ extends BaseRequest, RESP extends BaseResponse> { - @Resource - protected BaseService service; + protected final IBaseService service; + protected final BaseConverter converter; - @Resource - protected BaseConverter converter; + protected BaseController(IBaseService service, BaseConverter converter) { + this.service = service; + this.converter = converter; + } @PostMapping - public Response create(@RequestBody R request) { + public Response create(@RequestBody REQ request) { T entity = converter.toEntity(request); T savedEntity = service.create(entity); return Response.success(converter.toResponse(savedEntity)); } @PutMapping("/{id}") - public Response update(@PathVariable ID id, @RequestBody R request) { + public Response update(@PathVariable ID id, @RequestBody REQ request) { T entity = service.findById(id); converter.updateEntity(entity, request); T updatedEntity = service.update(entity); @@ -46,20 +48,26 @@ public abstract class BaseController, ID extends Serializab } @GetMapping("/{id}") - public Response findById(@PathVariable ID id) { + public Response findById(@PathVariable ID id) { T entity = service.findById(id); return Response.success(converter.toResponse(entity)); } @GetMapping - public Response> findAll() { + public Response> findAll() { List entities = service.findAll(); return Response.success(converter.toResponseList(entities)); } @GetMapping("/page") - public Response> page(Q query) { + public Response> page(Q query) { Page page = service.page(query); return Response.success(page.map(entity -> converter.toResponse(entity))); } + + @GetMapping("/list") + public Response> findAll(Q query) { + List entities = service.findAll(query); + return Response.success(converter.toResponseList(entities)); + } } \ No newline at end of file diff --git a/backend/src/main/java/com/qqchen/deploy/backend/common/integration/dto/ThirdPartyDTO.java b/backend/src/main/java/com/qqchen/deploy/backend/common/integration/dto/ThirdPartyDTO.java new file mode 100644 index 00000000..b7110614 --- /dev/null +++ b/backend/src/main/java/com/qqchen/deploy/backend/common/integration/dto/ThirdPartyDTO.java @@ -0,0 +1,5 @@ +package com.qqchen.deploy.backend.common.integration.dto; + +public interface ThirdPartyDTO { + // 标记接口,表明这是第三方系统的数据结构 +} \ No newline at end of file diff --git a/backend/src/main/java/com/qqchen/deploy/backend/common/repository/BaseRepository.java b/backend/src/main/java/com/qqchen/deploy/backend/common/repository/BaseRepository.java deleted file mode 100644 index 90b0440a..00000000 --- a/backend/src/main/java/com/qqchen/deploy/backend/common/repository/BaseRepository.java +++ /dev/null @@ -1,34 +0,0 @@ -package com.qqchen.deploy.backend.common.repository; - -import com.qqchen.deploy.backend.common.domain.Entity; -import org.springframework.data.jpa.repository.JpaRepository; -import org.springframework.data.jpa.repository.Query; -import org.springframework.data.querydsl.QuerydslPredicateExecutor; -import org.springframework.data.repository.NoRepositoryBean; - -import java.io.Serializable; -import java.util.List; -import java.util.Optional; - -@NoRepositoryBean -public interface BaseRepository, ID extends Serializable> extends JpaRepository, QuerydslPredicateExecutor { - - @Override - @Query("select e from #{#entityName} e where e.deleted = false") - List findAll(); - - @Override - @Query("select e from #{#entityName} e where e.id = ?1 and e.deleted = false") - Optional findById(ID id); - - @Override - default void delete(T entity) { - entity.setDeleted(true); - save(entity); - } - - @Override - default void deleteById(ID id) { - findById(id).ifPresent(this::delete); - } -} \ No newline at end of file diff --git a/backend/src/main/java/com/qqchen/deploy/backend/common/repository/IBaseRepository.java b/backend/src/main/java/com/qqchen/deploy/backend/common/repository/IBaseRepository.java new file mode 100644 index 00000000..8946a58c --- /dev/null +++ b/backend/src/main/java/com/qqchen/deploy/backend/common/repository/IBaseRepository.java @@ -0,0 +1,129 @@ +package com.qqchen.deploy.backend.common.repository; + +import com.qqchen.deploy.backend.common.domain.Entity; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.querydsl.QuerydslPredicateExecutor; +import org.springframework.data.repository.NoRepositoryBean; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.jpa.repository.Modifying; +import org.springframework.data.domain.Pageable; +import org.springframework.data.domain.Sort; +import org.springframework.data.jpa.repository.JpaSpecificationExecutor; + +import java.io.Serializable; +import java.util.List; +import java.util.Optional; +import java.util.Collection; +import java.util.stream.Collectors; +import java.util.stream.StreamSupport; +import java.time.LocalDateTime; +import java.util.function.Predicate; +import java.util.Iterator; +import java.util.ArrayList; + +@NoRepositoryBean +public interface IBaseRepository, ID extends Serializable> + extends JpaRepository, QuerydslPredicateExecutor { +// extends JpaRepository, QuerydslPredicateExecutor, JpaSpecificationExecutor { + + + @Override + @Query("select e from #{#entityName} e where e.deleted = false") + List findAll(); + + @Override + @Query("select e from #{#entityName} e where e.id = ?1 and e.deleted = false") + Optional findById(ID id); + + @Override + default void delete(T entity) { + entity.setDeleted(true); + save(entity); + } + + @Override + default void deleteById(ID id) { + findById(id).ifPresent(this::delete); + } + + // 批量操作 + @Modifying + @Query("update #{#entityName} e set e.deleted = true where e.id in ?1") + void logicDeleteByIds(Collection ids); + +// @Modifying +// @Query("update #{#entityName} e set e.enabled = ?2 where e.id in ?1") +// void updateEnabledByIds(Collection ids, boolean enabled); + + // 快速查询方法 + Optional findByIdAndDeletedFalse(ID id); + + List findByIdInAndDeletedFalse(Collection ids); + + boolean existsByIdAndDeletedFalse(ID id); + + // 自定义查询 + @Query("select e from #{#entityName} e where e.deleted = false and " + + "(?1 is null or e.createTime >= ?1) and " + + "(?2 is null or e.createTime <= ?2)") + List findByCreateTimeBetween(LocalDateTime start, LocalDateTime end); + + @Query("select e from #{#entityName} e where e.deleted = false and " + + "(?1 is null or e.createBy = ?1)") + List findByCreateBy(String creator); + + // 统计方法 + @Query("select count(e) from #{#entityName} e where e.deleted = false") + long countNonDeleted(); + +// @Query("select count(e) from #{#entityName} e where e.deleted = false and e.enabled = ?1") +// long countByEnabled(boolean enabled); + + // 高级查询 + @Query("select e from #{#entityName} e where e.deleted = false " + + "order by e.createTime desc") + List findLatest(Pageable pageable); + + @Query("select distinct e.createBy from #{#entityName} e where e.deleted = false") + List findAllCreators(); + + // 批量更新 + @Modifying + @Query("update #{#entityName} e set e.updateBy = ?2, e.updateTime = ?3 where e.id in ?1") + void updateAuditInfo(Collection ids, String updateBy, LocalDateTime updateTime); + + /** + * 根据条件查询并排序 + */ + default List findAllByCondition(com.querydsl.core.types.Predicate predicate, Sort sort) { + Iterable iterable = findAll(predicate, sort); + List result = new ArrayList<>(); + iterable.forEach(result::add); + return result; + } + + // 批量保存并返回保存的实体 + default List saveAllAndReturn(Iterable entities) { + return saveAll(entities); + } + + /** + * 根据条件查询并排序 + * 使用QueryDSL的Predicate进行条件查询 + */ + @Override + Iterable findAll(com.querydsl.core.types.Predicate predicate); + + @Override + Iterable findAll(com.querydsl.core.types.Predicate predicate, Sort sort); + + /** + * 提供一个默认的转换方法 + */ + default List findAllAndConvert(com.querydsl.core.types.Predicate predicate, Sort sort) { + Iterable iterable = findAll(predicate, sort); + List result = new ArrayList<>(); + iterable.forEach(result::add); + return result; + } +} \ No newline at end of file diff --git a/backend/src/main/java/com/qqchen/deploy/backend/common/config/SecurityConfig.java b/backend/src/main/java/com/qqchen/deploy/backend/common/security/config/SecurityConfig.java similarity index 82% rename from backend/src/main/java/com/qqchen/deploy/backend/common/config/SecurityConfig.java rename to backend/src/main/java/com/qqchen/deploy/backend/common/security/config/SecurityConfig.java index fcd16d5a..a615db86 100644 --- a/backend/src/main/java/com/qqchen/deploy/backend/common/config/SecurityConfig.java +++ b/backend/src/main/java/com/qqchen/deploy/backend/common/security/config/SecurityConfig.java @@ -1,13 +1,17 @@ -package com.qqchen.deploy.backend.common.config; +package com.qqchen.deploy.backend.common.security.config; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; +import org.springframework.security.authentication.AuthenticationManager; +import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.config.http.SessionCreationPolicy; import org.springframework.security.core.userdetails.User; import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.core.userdetails.UserDetailsService; +import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; +import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.security.provisioning.InMemoryUserDetailsManager; import org.springframework.security.web.SecurityFilterChain; import org.springframework.web.cors.CorsConfiguration; @@ -69,4 +73,14 @@ public class SecurityConfig { source.registerCorsConfiguration("/**", configuration); return source; } + + @Bean + public PasswordEncoder passwordEncoder() { + return new BCryptPasswordEncoder(); + } + + @Bean + public AuthenticationManager authenticationManager(AuthenticationConfiguration config) throws Exception { + return config.getAuthenticationManager(); + } } \ No newline at end of file diff --git a/backend/src/main/java/com/qqchen/deploy/backend/common/security/filter/JwtAuthenticationFilter.java b/backend/src/main/java/com/qqchen/deploy/backend/common/security/filter/JwtAuthenticationFilter.java new file mode 100644 index 00000000..20e29325 --- /dev/null +++ b/backend/src/main/java/com/qqchen/deploy/backend/common/security/filter/JwtAuthenticationFilter.java @@ -0,0 +1,59 @@ +package com.qqchen.deploy.backend.common.security.filter; + +import com.qqchen.deploy.backend.common.security.util.JwtTokenUtil; +import jakarta.servlet.FilterChain; +import jakarta.servlet.ServletException; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.security.core.userdetails.UserDetails; +import org.springframework.security.core.userdetails.UserDetailsService; +import org.springframework.security.web.authentication.WebAuthenticationDetailsSource; +import org.springframework.stereotype.Component; +import org.springframework.web.filter.OncePerRequestFilter; + +import java.io.IOException; + +@Slf4j +@Component +@RequiredArgsConstructor +public class JwtAuthenticationFilter extends OncePerRequestFilter { + + private final JwtTokenUtil jwtTokenUtil; + + private final UserDetailsService userDetailsService; + + @Override + protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) + throws ServletException, IOException { + + try { + String authHeader = request.getHeader("Authorization"); + String token = null; + String username = null; + + if (authHeader != null && authHeader.startsWith("Bearer ")) { + token = authHeader.substring(7); + username = jwtTokenUtil.getUsernameFromToken(token); + } + + if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) { + UserDetails userDetails = userDetailsService.loadUserByUsername(username); + + if (jwtTokenUtil.validateToken(token, userDetails)) { + UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken( + userDetails, null, userDetails.getAuthorities()); + authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request)); + SecurityContextHolder.getContext().setAuthentication(authentication); + } + } + } catch (Exception e) { + log.error("Cannot set user authentication", e); + } + + chain.doFilter(request, response); + } +} \ No newline at end of file diff --git a/backend/src/main/java/com/qqchen/deploy/backend/common/security/util/JwtTokenUtil.java b/backend/src/main/java/com/qqchen/deploy/backend/common/security/util/JwtTokenUtil.java new file mode 100644 index 00000000..7f89f6a9 --- /dev/null +++ b/backend/src/main/java/com/qqchen/deploy/backend/common/security/util/JwtTokenUtil.java @@ -0,0 +1,81 @@ +package com.qqchen.deploy.backend.common.security.util; + +import io.jsonwebtoken.Claims; +import io.jsonwebtoken.Jwts; +import io.jsonwebtoken.security.Keys; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.security.core.userdetails.UserDetails; +import org.springframework.stereotype.Component; + +import javax.crypto.SecretKey; +import java.nio.charset.StandardCharsets; +import java.util.Date; + +@Slf4j +@Component +public class JwtTokenUtil { + + @Value("${jwt.secret}") + private String secret; + + @Value("${jwt.expiration}") + private Long expiration; + + private SecretKey key; + + private SecretKey getKey() { + if (key == null) { + // 确保密钥长度至少为 256 位 + byte[] keyBytes = new byte[32]; + byte[] secretBytes = secret.getBytes(StandardCharsets.UTF_8); + System.arraycopy(secretBytes, 0, keyBytes, 0, Math.min(secretBytes.length, keyBytes.length)); + key = Keys.hmacShaKeyFor(keyBytes); + } + return key; + } + + public String generateToken(UserDetails userDetails) { + Date now = new Date(); + Date expiryDate = new Date(now.getTime() + expiration * 1000); + + return Jwts.builder() + .subject(userDetails.getUsername()) + .issuedAt(now) + .expiration(expiryDate) + .signWith(getKey()) + .compact(); + } + + public String getUsernameFromToken(String token) { + try { + Claims claims = Jwts.parser() + .verifyWith(getKey()) + .build() + .parseSignedClaims(token) + .getPayload(); + return claims.getSubject(); + } catch (Exception e) { + log.error("Error getting username from token", e); + return null; + } + } + + public boolean validateToken(String token, UserDetails userDetails) { + try { + String username = getUsernameFromToken(token); + Claims claims = Jwts.parser() + .verifyWith(getKey()) + .build() + .parseSignedClaims(token) + .getPayload(); + + boolean isTokenExpired = claims.getExpiration().before(new Date()); + + return (username.equals(userDetails.getUsername()) && !isTokenExpired); + } catch (Exception e) { + log.error("Error validating token", e); + return false; + } + } +} \ No newline at end of file diff --git a/backend/src/main/java/com/qqchen/deploy/backend/common/service/BaseService.java b/backend/src/main/java/com/qqchen/deploy/backend/common/service/IBaseService.java similarity index 78% rename from backend/src/main/java/com/qqchen/deploy/backend/common/service/BaseService.java rename to backend/src/main/java/com/qqchen/deploy/backend/common/service/IBaseService.java index 4420a415..34ad1f9e 100644 --- a/backend/src/main/java/com/qqchen/deploy/backend/common/service/BaseService.java +++ b/backend/src/main/java/com/qqchen/deploy/backend/common/service/IBaseService.java @@ -10,7 +10,7 @@ import java.util.List; /** * 通用服务接口 */ -public interface BaseService, ID extends Serializable> { +public interface IBaseService, ID extends Serializable> { T create(T entity); T update(T entity); @@ -21,5 +21,7 @@ public interface BaseService, ID extends Serializable> { List findAll(); + List findAll(BaseQuery query); + Page page(BaseQuery query); } \ No newline at end of file diff --git a/backend/src/main/java/com/qqchen/deploy/backend/common/service/impl/BaseServiceImpl.java b/backend/src/main/java/com/qqchen/deploy/backend/common/service/impl/BaseServiceImpl.java index 9ae49b1f..3ccf38c8 100644 --- a/backend/src/main/java/com/qqchen/deploy/backend/common/service/impl/BaseServiceImpl.java +++ b/backend/src/main/java/com/qqchen/deploy/backend/common/service/impl/BaseServiceImpl.java @@ -2,6 +2,7 @@ package com.qqchen.deploy.backend.common.service.impl; import java.math.BigDecimal; import java.time.LocalDateTime; +import java.util.ArrayList; import java.util.Arrays; import java.util.List; @@ -11,9 +12,9 @@ import com.qqchen.deploy.backend.common.enums.QueryType; import com.qqchen.deploy.backend.common.query.BaseQuery; import com.qqchen.deploy.backend.common.query.DateRange; import com.qqchen.deploy.backend.common.query.Range; -import com.qqchen.deploy.backend.common.repository.BaseRepository; +import com.qqchen.deploy.backend.common.repository.IBaseRepository; import com.qqchen.deploy.backend.common.annotation.QueryField; -import com.qqchen.deploy.backend.common.service.BaseService; +import com.qqchen.deploy.backend.common.service.IBaseService; import com.qqchen.deploy.backend.common.utils.EntityPathResolver; import com.querydsl.core.BooleanBuilder; import com.querydsl.core.types.EntityPath; @@ -29,17 +30,17 @@ import org.springframework.util.StringUtils; import java.io.Serializable; import java.lang.reflect.Field; -import java.util.Date; +import java.util.stream.Collectors; +import java.util.stream.StreamSupport; @Transactional -public abstract class BaseServiceImpl, ID extends Serializable> - implements BaseService { +public abstract class BaseServiceImpl, ID extends Serializable> implements IBaseService { - protected final BaseRepository repository; + protected final IBaseRepository repository; protected final EntityPath entityPath; - protected BaseServiceImpl(BaseRepository repository) { + protected BaseServiceImpl(IBaseRepository repository) { this.repository = repository; this.entityPath = getEntityPath(); } @@ -65,7 +66,6 @@ public abstract class BaseServiceImpl, ID extends Serializa @Override public Page page(BaseQuery query) { BooleanBuilder builder = new BooleanBuilder().and(Expressions.asBoolean(true).isTrue()); - if (query != null) { buildQueryPredicate(query, builder); } @@ -74,7 +74,6 @@ public abstract class BaseServiceImpl, ID extends Serializa if (genericTypes != null && genericTypes.length > 0) { Class entityClass = (Class) genericTypes[0]; LogicDelete softDelete = entityClass.getAnnotation(LogicDelete.class); - if (softDelete != null && softDelete.value()) { Path deletedPath = EntityPathResolver.getPath(entityPath, "deleted"); if (deletedPath instanceof BooleanPath) { @@ -83,7 +82,6 @@ public abstract class BaseServiceImpl, ID extends Serializa } } } - return repository.findAll(builder, createPageRequest(query)); } @@ -284,4 +282,40 @@ public abstract class BaseServiceImpl, ID extends Serializa public List findAll() { return repository.findAll(); } + + @Override + public List findAll(BaseQuery query) { + BooleanBuilder builder = new BooleanBuilder().and(Expressions.asBoolean(true).isTrue()); + + // 添加查询条件 + if (query != null) { + buildQueryPredicate(query, builder); + } + + // 处理软删除 + if (query == null || query.getDeleted() == null) { + Class[] genericTypes = GenericTypeResolver.resolveTypeArguments(getClass(), BaseServiceImpl.class); + if (genericTypes != null && genericTypes.length > 0) { + Class entityClass = (Class) genericTypes[0]; + LogicDelete softDelete = entityClass.getAnnotation(LogicDelete.class); + if (softDelete != null && softDelete.value()) { + Path deletedPath = EntityPathResolver.getPath(entityPath, "deleted"); + if (deletedPath instanceof BooleanPath) { + builder.and(((BooleanPath) deletedPath).eq(false)); + } + } + } + } + + // 获取排序信息 + Sort sort = query != null && StringUtils.hasText(query.getSortField()) ? + Sort.by(Sort.Direction.fromString(query.getSortOrder()), query.getSortField()) : + Sort.by(Sort.Direction.DESC, "createTime"); + + // 执行查询并返回结果 + Iterable iterable = repository.findAll(builder, sort); + List result = new ArrayList<>(); + iterable.forEach(result::add); + return result; + } } \ No newline at end of file diff --git a/backend/src/main/java/com/qqchen/deploy/backend/controller/UserController.java b/backend/src/main/java/com/qqchen/deploy/backend/controller/UserController.java index bc16c022..bf5dfa12 100644 --- a/backend/src/main/java/com/qqchen/deploy/backend/controller/UserController.java +++ b/backend/src/main/java/com/qqchen/deploy/backend/controller/UserController.java @@ -3,7 +3,7 @@ package com.qqchen.deploy.backend.controller; import com.qqchen.deploy.backend.api.UserApiController; import com.qqchen.deploy.backend.common.api.Response; import com.qqchen.deploy.backend.converter.UserConverter; -import com.qqchen.deploy.backend.service.UserService; +import com.qqchen.deploy.backend.service.IUserService; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; @@ -13,8 +13,11 @@ import org.springframework.web.bind.annotation.RestController; @RequestMapping("/mgmt/users") public class UserController extends UserApiController { - public UserController(UserService userService, UserConverter converter) { + protected final IUserService userService; + + public UserController(IUserService userService, UserConverter converter) { super(userService, converter); + this.userService = userService; } @GetMapping("/check-username") diff --git a/backend/src/main/java/com/qqchen/deploy/backend/entity/Department.java b/backend/src/main/java/com/qqchen/deploy/backend/entity/Department.java index 8d696072..89a62859 100644 --- a/backend/src/main/java/com/qqchen/deploy/backend/entity/Department.java +++ b/backend/src/main/java/com/qqchen/deploy/backend/entity/Department.java @@ -1,16 +1,25 @@ package com.qqchen.deploy.backend.entity; +import com.fasterxml.jackson.annotation.JsonIgnore; import com.qqchen.deploy.backend.common.annotation.LogicDelete; import com.qqchen.deploy.backend.common.domain.Entity; +import jakarta.persistence.CascadeType; import jakarta.persistence.Column; +import jakarta.persistence.FetchType; +import jakarta.persistence.JoinColumn; +import jakarta.persistence.ManyToOne; +import jakarta.persistence.OneToMany; import jakarta.persistence.Table; import jakarta.persistence.Transient; import jakarta.validation.constraints.NotBlank; import lombok.Data; import lombok.EqualsAndHashCode; +import lombok.ToString; import java.util.ArrayList; +import java.util.HashSet; import java.util.List; +import java.util.Set; @Data @EqualsAndHashCode(callSuper = true) @@ -46,4 +55,10 @@ public class Department extends Entity { @Transient // 不映射到数据库 private List children = new ArrayList<>(); + + // 修改关联关系,通过UserDepartment关联 + @OneToMany(mappedBy = "department") + @JsonIgnore + @ToString.Exclude + private Set userDepartments = new HashSet<>(); } \ No newline at end of file diff --git a/backend/src/main/java/com/qqchen/deploy/backend/entity/Menu.java b/backend/src/main/java/com/qqchen/deploy/backend/entity/Menu.java index 27112006..0fd349f7 100644 --- a/backend/src/main/java/com/qqchen/deploy/backend/entity/Menu.java +++ b/backend/src/main/java/com/qqchen/deploy/backend/entity/Menu.java @@ -7,6 +7,7 @@ import jakarta.persistence.Table; import lombok.Data; import lombok.EqualsAndHashCode; +import java.util.List; @Data @@ -40,4 +41,6 @@ public class Menu extends Entity { @Column(nullable = false) private Boolean enabled = true; + +// List roleMenu; } \ No newline at end of file diff --git a/backend/src/main/java/com/qqchen/deploy/backend/entity/Role.java b/backend/src/main/java/com/qqchen/deploy/backend/entity/Role.java index 78406e47..51d58241 100644 --- a/backend/src/main/java/com/qqchen/deploy/backend/entity/Role.java +++ b/backend/src/main/java/com/qqchen/deploy/backend/entity/Role.java @@ -1,5 +1,6 @@ package com.qqchen.deploy.backend.entity; +import com.fasterxml.jackson.annotation.JsonIgnore; import com.qqchen.deploy.backend.common.annotation.LogicDelete; import com.qqchen.deploy.backend.common.domain.Entity; import jakarta.persistence.CascadeType; @@ -9,8 +10,11 @@ import jakarta.persistence.Table; import jakarta.validation.constraints.NotBlank; import lombok.Data; import lombok.EqualsAndHashCode; +import lombok.ToString; +import java.util.HashSet; import java.util.List; +import java.util.Set; @Data @@ -33,6 +37,8 @@ public class Role extends Entity { @Column(nullable = false) private Integer sort = 0; - @OneToMany(mappedBy = "role", cascade = CascadeType.ALL) // 指向 UserRole 的 role 属性 - private List userRoles; + @OneToMany(mappedBy = "role", cascade = CascadeType.ALL) + @JsonIgnore + @ToString.Exclude + private Set userRoles = new HashSet<>(); } \ No newline at end of file diff --git a/backend/src/main/java/com/qqchen/deploy/backend/entity/User.java b/backend/src/main/java/com/qqchen/deploy/backend/entity/User.java index c98f00f1..58bf03c0 100644 --- a/backend/src/main/java/com/qqchen/deploy/backend/entity/User.java +++ b/backend/src/main/java/com/qqchen/deploy/backend/entity/User.java @@ -1,5 +1,6 @@ package com.qqchen.deploy.backend.entity; +import com.fasterxml.jackson.annotation.JsonIgnore; import com.qqchen.deploy.backend.common.annotation.LogicDelete; import com.qqchen.deploy.backend.common.domain.AggregateRoot; import com.qqchen.deploy.backend.common.domain.Entity; @@ -7,9 +8,11 @@ import com.qqchen.deploy.backend.event.UserRoleChangedEvent; import jakarta.persistence.CascadeType; import jakarta.persistence.Column; import jakarta.persistence.OneToMany; +import jakarta.persistence.OneToOne; import jakarta.persistence.Table; import lombok.Data; import lombok.EqualsAndHashCode; +import lombok.ToString; import java.util.HashSet; import java.util.List; @@ -23,32 +26,35 @@ import java.util.stream.Collectors; @Table(name = "sys_user") @LogicDelete public class User extends Entity { - + @Column(unique = true, nullable = false) private String username; - + @Column(nullable = false) private String password; - + @Column(length = 50) private String nickname; - + private String email; - + private String phone; - + @Column(nullable = false) private Boolean enabled = true; @Column(name = "dept_id") private Long deptId; - @Column(name = "dept_name") - private String deptName; + @OneToMany(mappedBy = "user", cascade = CascadeType.ALL, orphanRemoval = true) + @JsonIgnore + @ToString.Exclude + private Set userRoles = new HashSet<>(); - - @OneToMany(mappedBy = "user", cascade = CascadeType.ALL) // 指向 UserRole 的 user 属性 - private Set userRoles; + @OneToMany(mappedBy = "user", cascade = CascadeType.ALL, orphanRemoval = true) + @JsonIgnore + @ToString.Exclude + private Set userDepartments = new HashSet<>(); // public void addRole(Role role) { // UserRole userRole = new UserRole(this, role); diff --git a/backend/src/main/java/com/qqchen/deploy/backend/entity/UserDepartment.java b/backend/src/main/java/com/qqchen/deploy/backend/entity/UserDepartment.java new file mode 100644 index 00000000..9936e541 --- /dev/null +++ b/backend/src/main/java/com/qqchen/deploy/backend/entity/UserDepartment.java @@ -0,0 +1,48 @@ +package com.qqchen.deploy.backend.entity; + +import com.fasterxml.jackson.annotation.JsonIgnore; +import com.qqchen.deploy.backend.common.annotation.LogicDelete; +import com.qqchen.deploy.backend.common.domain.Entity; +import jakarta.persistence.CascadeType; +import jakarta.persistence.Column; +import jakarta.persistence.FetchType; +import jakarta.persistence.JoinColumn; +import jakarta.persistence.ManyToOne; +import jakarta.persistence.OneToMany; +import jakarta.persistence.OneToOne; +import jakarta.persistence.Table; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.ToString; + +import java.util.Set; + +@Data +@EqualsAndHashCode(callSuper = true) +@jakarta.persistence.Entity +@Table(name = "sys_user_department") +@LogicDelete +public class UserDepartment extends Entity { + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "user_id", unique = true) + @JsonIgnore + @ToString.Exclude + private User user; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "department_id") + @JsonIgnore + @ToString.Exclude + private Department department; + + protected UserDepartment() { + // JPA需要无参构造函数 + } + + public UserDepartment(User user, Department department) { + this.user = user; + this.department = department; + } + +} \ No newline at end of file diff --git a/backend/src/main/java/com/qqchen/deploy/backend/entity/UserRole.java b/backend/src/main/java/com/qqchen/deploy/backend/entity/UserRole.java index c2a230c1..96550464 100644 --- a/backend/src/main/java/com/qqchen/deploy/backend/entity/UserRole.java +++ b/backend/src/main/java/com/qqchen/deploy/backend/entity/UserRole.java @@ -1,5 +1,6 @@ package com.qqchen.deploy.backend.entity; +import com.fasterxml.jackson.annotation.JsonIgnore; import com.qqchen.deploy.backend.common.annotation.LogicDelete; import com.qqchen.deploy.backend.common.domain.Entity; import jakarta.persistence.Column; @@ -11,6 +12,7 @@ import jakarta.persistence.OneToOne; import jakarta.persistence.Table; import lombok.Data; import lombok.EqualsAndHashCode; +import lombok.ToString; @Data @EqualsAndHashCode(callSuper = true) @@ -19,12 +21,16 @@ import lombok.EqualsAndHashCode; @LogicDelete public class UserRole extends Entity { - @ManyToOne(fetch = FetchType.LAZY) // 添加懒加载 + @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "user_id") + @JsonIgnore + @ToString.Exclude private User user; - @ManyToOne(fetch = FetchType.LAZY) // 添加懒加载 + @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "role_id") + @JsonIgnore + @ToString.Exclude private Role role; // 添加构造方法 @@ -50,4 +56,6 @@ public class UserRole extends Entity { public int hashCode() { return getClass().hashCode(); } + + } \ No newline at end of file diff --git a/backend/src/main/java/com/qqchen/deploy/backend/integration/JenkinsService.java b/backend/src/main/java/com/qqchen/deploy/backend/integration/JenkinsService.java new file mode 100644 index 00000000..f111d64e --- /dev/null +++ b/backend/src/main/java/com/qqchen/deploy/backend/integration/JenkinsService.java @@ -0,0 +1,79 @@ +package com.qqchen.deploy.backend.integration; + +import com.qqchen.deploy.backend.integration.client.IJenkinsClient; +import com.qqchen.deploy.backend.integration.dto.JenkinsBuildDTO; +import com.qqchen.deploy.backend.integration.dto.JenkinsConnectionDTO; +import feign.FeignException; +import jakarta.annotation.Resource; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; + +@Service +@RequiredArgsConstructor +@Slf4j +public class JenkinsService { + +// @Resource +// private final IJenkinsClient jenkinsClient; +// +// public boolean testConnection(JenkinsConnectionDTO config) { +// try { +// String authHeader = createAuthHeader(config.getUsername(), config.getPassword()); +// jenkinsClient.testConnection(authHeader); +// log.info("Jenkins连接测试成功"); +// return true; +// } catch (FeignException.Unauthorized e) { +// log.error("Jenkins连接测试失败: 认证失败"); +// return false; +// } catch (Exception e) { +// log.error("Jenkins连接测试失败: {}", e.getMessage()); +// return false; +// } +// } +// +// public BuildInfo getBuildInfo(JenkinsConnectionDTO config, String jobName, Integer buildNumber) { +// try { +// String authHeader = createAuthHeader(config.getUsername(), config.getPassword()); +// JenkinsBuildDTO jenkinsBuild = jenkinsClient.getBuildInfo(authHeader, jobName, buildNumber); +// return convertToBuildInfo(jenkinsBuild); +// } catch (Exception e) { +// log.error("获取Jenkins构建信息失败: {}", e.getMessage()); +// throw new RuntimeException("Failed to get build info", e); +// } +// } +// +// private String createAuthHeader(String username, String password) { +// String auth = username + ":" + password; +// return "Basic " + Base64.getEncoder().encodeToString(auth.getBytes()); +// } +// +// private BuildInfo convertToBuildInfo(JenkinsBuildDTO dto) { +// BuildInfo buildInfo = new BuildInfo(); +// buildInfo.setJobName(dto.getJobName()); +// buildInfo.setBuildNumber(dto.getBuildNumber()); +// buildInfo.setBuildUrl(dto.getUrl()); +// buildInfo.setBuildTime(LocalDateTime.ofInstant( +// Instant.ofEpochMilli(dto.getTimestamp()), +// ZoneId.systemDefault() +// )); +// +// // 转换构建状态 +// buildInfo.setStatus(convertBuildStatus(dto.getResult(), dto.getStatus())); +// +// return buildInfo; +// } +// +// private BuildInfo.BuildStatus convertBuildStatus(String result, String status) { +// if ("IN_PROGRESS".equals(status)) { +// return BuildInfo.BuildStatus.IN_PROGRESS; +// } +// +// return switch (result) { +// case "SUCCESS" -> BuildInfo.BuildStatus.SUCCESS; +// case "FAILURE" -> BuildInfo.BuildStatus.FAILURE; +// case "ABORTED" -> BuildInfo.BuildStatus.CANCELLED; +// default -> throw new IllegalStateException("Unknown build status: " + result); +// }; +// } +} \ No newline at end of file diff --git a/backend/src/main/java/com/qqchen/deploy/backend/integration/client/IJenkinsClient.java b/backend/src/main/java/com/qqchen/deploy/backend/integration/client/IJenkinsClient.java new file mode 100644 index 00000000..76a77a0a --- /dev/null +++ b/backend/src/main/java/com/qqchen/deploy/backend/integration/client/IJenkinsClient.java @@ -0,0 +1,24 @@ +package com.qqchen.deploy.backend.integration.client; + +import org.springframework.cloud.openfeign.FeignClient; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestHeader; + +import java.util.Map; + +@FeignClient(name = "jenkins-service", url = "#{null}") // 动态URL +public interface IJenkinsClient { + + @GetMapping("/api/json") + Map testConnection( + @RequestHeader("Authorization") String authorization + ); + + @PostMapping("/job/{jobName}/build") + void triggerBuild( + @RequestHeader("Authorization") String authorization, + @PathVariable("jobName") String jobName + ); +} \ No newline at end of file diff --git a/backend/src/main/java/com/qqchen/deploy/backend/integration/config/JenkinsProperties.java b/backend/src/main/java/com/qqchen/deploy/backend/integration/config/JenkinsProperties.java new file mode 100644 index 00000000..e676ee16 --- /dev/null +++ b/backend/src/main/java/com/qqchen/deploy/backend/integration/config/JenkinsProperties.java @@ -0,0 +1,22 @@ +package com.qqchen.deploy.backend.integration.config; + +import lombok.Data; +import org.springframework.stereotype.Component; + +import java.util.Base64; + +@Data +@Component +public class JenkinsProperties { + + private String url; + + private String username; + + private String password; + + public String getBasicAuth() { + String auth = username + ":" + password; + return "Basic " + Base64.getEncoder().encodeToString(auth.getBytes()); + } +} \ No newline at end of file diff --git a/backend/src/main/java/com/qqchen/deploy/backend/integration/dto/BuildInfo.java b/backend/src/main/java/com/qqchen/deploy/backend/integration/dto/BuildInfo.java new file mode 100644 index 00000000..9aa795ee --- /dev/null +++ b/backend/src/main/java/com/qqchen/deploy/backend/integration/dto/BuildInfo.java @@ -0,0 +1,22 @@ +package com.qqchen.deploy.backend.integration.dto; + +import lombok.Data; + +import java.time.LocalDateTime; + +@Data +public class BuildInfo { + private String jobName; + + private Integer buildNumber; + + private BuildStatus status; + + private LocalDateTime buildTime; + + private String buildUrl; + + public enum BuildStatus { + SUCCESS, FAILURE, IN_PROGRESS, CANCELLED + } +} \ No newline at end of file diff --git a/backend/src/main/java/com/qqchen/deploy/backend/integration/dto/JenkinsBuildDTO.java b/backend/src/main/java/com/qqchen/deploy/backend/integration/dto/JenkinsBuildDTO.java new file mode 100644 index 00000000..61e0360e --- /dev/null +++ b/backend/src/main/java/com/qqchen/deploy/backend/integration/dto/JenkinsBuildDTO.java @@ -0,0 +1,20 @@ +package com.qqchen.deploy.backend.integration.dto; + +import com.qqchen.deploy.backend.common.integration.dto.ThirdPartyDTO; +import lombok.Data; + +@Data +public class JenkinsBuildDTO implements ThirdPartyDTO { + + private String jobName; + + private Integer buildNumber; + + private String status; + + private String result; + + private Long timestamp; + + private String url; +} \ No newline at end of file diff --git a/backend/src/main/java/com/qqchen/deploy/backend/integration/dto/JenkinsConnectionDTO.java b/backend/src/main/java/com/qqchen/deploy/backend/integration/dto/JenkinsConnectionDTO.java new file mode 100644 index 00000000..599398ca --- /dev/null +++ b/backend/src/main/java/com/qqchen/deploy/backend/integration/dto/JenkinsConnectionDTO.java @@ -0,0 +1,14 @@ +package com.qqchen.deploy.backend.integration.dto; + +import com.qqchen.deploy.backend.common.integration.dto.ThirdPartyDTO; +import lombok.Data; + +@Data +public class JenkinsConnectionDTO implements ThirdPartyDTO { + + private String url; + + private String username; + + private String password; +} \ No newline at end of file diff --git a/backend/src/main/java/com/qqchen/deploy/backend/integration/enums/JenkinsBuildStatus.java b/backend/src/main/java/com/qqchen/deploy/backend/integration/enums/JenkinsBuildStatus.java new file mode 100644 index 00000000..2b25be18 --- /dev/null +++ b/backend/src/main/java/com/qqchen/deploy/backend/integration/enums/JenkinsBuildStatus.java @@ -0,0 +1,5 @@ +package com.qqchen.deploy.backend.integration.enums; + +public enum JenkinsBuildStatus { + SUCCESS, FAILURE, IN_PROGRESS, CANCELLED +} \ No newline at end of file diff --git a/backend/src/main/java/com/qqchen/deploy/backend/repository/DepartmentRepository.java b/backend/src/main/java/com/qqchen/deploy/backend/repository/IDepartmentRepository.java similarity index 76% rename from backend/src/main/java/com/qqchen/deploy/backend/repository/DepartmentRepository.java rename to backend/src/main/java/com/qqchen/deploy/backend/repository/IDepartmentRepository.java index 6385bd00..f23dbfee 100644 --- a/backend/src/main/java/com/qqchen/deploy/backend/repository/DepartmentRepository.java +++ b/backend/src/main/java/com/qqchen/deploy/backend/repository/IDepartmentRepository.java @@ -1,15 +1,14 @@ package com.qqchen.deploy.backend.repository; -import com.qqchen.deploy.backend.common.repository.BaseRepository; +import com.qqchen.deploy.backend.common.repository.IBaseRepository; import com.qqchen.deploy.backend.entity.Department; -import org.springframework.data.jpa.repository.JpaSpecificationExecutor; import org.springframework.data.jpa.repository.Query; import org.springframework.data.repository.query.Param; import org.springframework.stereotype.Repository; import java.util.List; @Repository -public interface DepartmentRepository extends BaseRepository { +public interface IDepartmentRepository extends IBaseRepository { List findByParentIdAndDeletedFalseOrderBySort(Long parentId); diff --git a/backend/src/main/java/com/qqchen/deploy/backend/repository/JenkinsBuildRepository.java b/backend/src/main/java/com/qqchen/deploy/backend/repository/IJenkinsBuildRepository.java similarity index 75% rename from backend/src/main/java/com/qqchen/deploy/backend/repository/JenkinsBuildRepository.java rename to backend/src/main/java/com/qqchen/deploy/backend/repository/IJenkinsBuildRepository.java index f043bfb5..c178e5eb 100644 --- a/backend/src/main/java/com/qqchen/deploy/backend/repository/JenkinsBuildRepository.java +++ b/backend/src/main/java/com/qqchen/deploy/backend/repository/IJenkinsBuildRepository.java @@ -1,13 +1,13 @@ package com.qqchen.deploy.backend.repository; -import com.qqchen.deploy.backend.common.repository.BaseRepository; +import com.qqchen.deploy.backend.common.repository.IBaseRepository; import com.qqchen.deploy.backend.entity.JenkinsBuild; import org.springframework.stereotype.Repository; import java.util.List; @Repository -public interface JenkinsBuildRepository extends BaseRepository { +public interface IJenkinsBuildRepository extends IBaseRepository { List findByJobIdAndDeletedFalse(Long jobId); List findByJobIdAndBuildNumberAndDeletedFalse(Long jobId, Integer buildNumber); void deleteByJobIdAndDeletedFalse(Long jobId); diff --git a/backend/src/main/java/com/qqchen/deploy/backend/repository/JenkinsConfigRepository.java b/backend/src/main/java/com/qqchen/deploy/backend/repository/IJenkinsConfigRepository.java similarity index 65% rename from backend/src/main/java/com/qqchen/deploy/backend/repository/JenkinsConfigRepository.java rename to backend/src/main/java/com/qqchen/deploy/backend/repository/IJenkinsConfigRepository.java index 7708cdba..da4874de 100644 --- a/backend/src/main/java/com/qqchen/deploy/backend/repository/JenkinsConfigRepository.java +++ b/backend/src/main/java/com/qqchen/deploy/backend/repository/IJenkinsConfigRepository.java @@ -1,13 +1,13 @@ package com.qqchen.deploy.backend.repository; -import com.qqchen.deploy.backend.common.repository.BaseRepository; +import com.qqchen.deploy.backend.common.repository.IBaseRepository; import com.qqchen.deploy.backend.entity.JenkinsConfig; import org.springframework.stereotype.Repository; import java.util.List; @Repository -public interface JenkinsConfigRepository extends BaseRepository { +public interface IJenkinsConfigRepository extends IBaseRepository { List findByDeletedFalseOrderBySort(); boolean existsByNameAndDeletedFalse(String name); } \ No newline at end of file diff --git a/backend/src/main/java/com/qqchen/deploy/backend/repository/JenkinsJobRepository.java b/backend/src/main/java/com/qqchen/deploy/backend/repository/IJenkinsJobRepository.java similarity index 85% rename from backend/src/main/java/com/qqchen/deploy/backend/repository/JenkinsJobRepository.java rename to backend/src/main/java/com/qqchen/deploy/backend/repository/IJenkinsJobRepository.java index ae46f061..80392b87 100644 --- a/backend/src/main/java/com/qqchen/deploy/backend/repository/JenkinsJobRepository.java +++ b/backend/src/main/java/com/qqchen/deploy/backend/repository/IJenkinsJobRepository.java @@ -1,6 +1,6 @@ package com.qqchen.deploy.backend.repository; -import com.qqchen.deploy.backend.common.repository.BaseRepository; +import com.qqchen.deploy.backend.common.repository.IBaseRepository; import com.qqchen.deploy.backend.entity.JenkinsJob; import org.springframework.data.jpa.repository.Query; import org.springframework.data.repository.query.Param; @@ -8,7 +8,7 @@ import org.springframework.stereotype.Repository; import java.util.List; @Repository -public interface JenkinsJobRepository extends BaseRepository { +public interface IJenkinsJobRepository extends IBaseRepository { List findByJenkinsIdAndDeletedFalse(Long jenkinsId); List findByJenkinsIdAndJobNameAndDeletedFalse(Long jenkinsId, String jobName); void deleteByJenkinsIdAndDeletedFalse(Long jenkinsId); diff --git a/backend/src/main/java/com/qqchen/deploy/backend/repository/JenkinsSyncHistoryRepository.java b/backend/src/main/java/com/qqchen/deploy/backend/repository/IJenkinsSyncHistoryRepository.java similarity index 60% rename from backend/src/main/java/com/qqchen/deploy/backend/repository/JenkinsSyncHistoryRepository.java rename to backend/src/main/java/com/qqchen/deploy/backend/repository/IJenkinsSyncHistoryRepository.java index 2f40933e..973d8fde 100644 --- a/backend/src/main/java/com/qqchen/deploy/backend/repository/JenkinsSyncHistoryRepository.java +++ b/backend/src/main/java/com/qqchen/deploy/backend/repository/IJenkinsSyncHistoryRepository.java @@ -1,12 +1,12 @@ package com.qqchen.deploy.backend.repository; -import com.qqchen.deploy.backend.common.repository.BaseRepository; +import com.qqchen.deploy.backend.common.repository.IBaseRepository; import com.qqchen.deploy.backend.entity.JenkinsSyncHistory; import org.springframework.stereotype.Repository; import java.util.List; @Repository -public interface JenkinsSyncHistoryRepository extends BaseRepository { +public interface IJenkinsSyncHistoryRepository extends IBaseRepository { List findTop50ByOrderByStartTimeDesc(); } \ No newline at end of file diff --git a/backend/src/main/java/com/qqchen/deploy/backend/repository/JenkinsViewRepository.java b/backend/src/main/java/com/qqchen/deploy/backend/repository/IJenkinsViewRepository.java similarity index 72% rename from backend/src/main/java/com/qqchen/deploy/backend/repository/JenkinsViewRepository.java rename to backend/src/main/java/com/qqchen/deploy/backend/repository/IJenkinsViewRepository.java index e08fd058..60813f65 100644 --- a/backend/src/main/java/com/qqchen/deploy/backend/repository/JenkinsViewRepository.java +++ b/backend/src/main/java/com/qqchen/deploy/backend/repository/IJenkinsViewRepository.java @@ -1,14 +1,14 @@ package com.qqchen.deploy.backend.repository; -import com.qqchen.deploy.backend.common.repository.BaseRepository; +import com.qqchen.deploy.backend.common.repository.IBaseRepository; import com.qqchen.deploy.backend.entity.JenkinsView; import org.springframework.stereotype.Repository; import java.util.List; @Repository -public interface JenkinsViewRepository extends BaseRepository { +public interface IJenkinsViewRepository extends IBaseRepository { List findByJenkinsIdAndDeletedFalse(Long jenkinsId); List findByJenkinsIdAndViewNameAndDeletedFalse(Long jenkinsId, String viewName); void deleteByJenkinsIdAndDeletedFalse(Long jenkinsId); diff --git a/backend/src/main/java/com/qqchen/deploy/backend/repository/MenuRepository.java b/backend/src/main/java/com/qqchen/deploy/backend/repository/IMenuRepository.java similarity index 85% rename from backend/src/main/java/com/qqchen/deploy/backend/repository/MenuRepository.java rename to backend/src/main/java/com/qqchen/deploy/backend/repository/IMenuRepository.java index 3e1a8183..95fcb25e 100644 --- a/backend/src/main/java/com/qqchen/deploy/backend/repository/MenuRepository.java +++ b/backend/src/main/java/com/qqchen/deploy/backend/repository/IMenuRepository.java @@ -2,12 +2,11 @@ package com.qqchen.deploy.backend.repository; import com.qqchen.deploy.backend.entity.Menu; import org.springframework.data.jpa.repository.JpaRepository; -import org.springframework.data.jpa.repository.JpaSpecificationExecutor; import org.springframework.stereotype.Repository; import java.util.List; @Repository -public interface MenuRepository extends JpaRepository { +public interface IMenuRepository extends JpaRepository { List findByDeletedFalseOrderBySort(); diff --git a/backend/src/main/java/com/qqchen/deploy/backend/repository/RepositoryBranchRepository.java b/backend/src/main/java/com/qqchen/deploy/backend/repository/IRepositoryBranchRepository.java similarity index 69% rename from backend/src/main/java/com/qqchen/deploy/backend/repository/RepositoryBranchRepository.java rename to backend/src/main/java/com/qqchen/deploy/backend/repository/IRepositoryBranchRepository.java index a3653f5b..b91502d3 100644 --- a/backend/src/main/java/com/qqchen/deploy/backend/repository/RepositoryBranchRepository.java +++ b/backend/src/main/java/com/qqchen/deploy/backend/repository/IRepositoryBranchRepository.java @@ -1,15 +1,14 @@ package com.qqchen.deploy.backend.repository; -import com.qqchen.deploy.backend.common.repository.BaseRepository; +import com.qqchen.deploy.backend.common.repository.IBaseRepository; import com.qqchen.deploy.backend.entity.RepositoryBranch; import org.springframework.data.jpa.repository.Modifying; import org.springframework.data.jpa.repository.Query; -import org.springframework.data.querydsl.QuerydslPredicateExecutor; import org.springframework.stereotype.Repository; import org.springframework.transaction.annotation.Transactional; @Repository -public interface RepositoryBranchRepository extends BaseRepository { +public interface IRepositoryBranchRepository extends IBaseRepository { @Modifying @Transactional diff --git a/backend/src/main/java/com/qqchen/deploy/backend/repository/RepositoryConfigRepository.java b/backend/src/main/java/com/qqchen/deploy/backend/repository/IRepositoryConfigRepository.java similarity index 65% rename from backend/src/main/java/com/qqchen/deploy/backend/repository/RepositoryConfigRepository.java rename to backend/src/main/java/com/qqchen/deploy/backend/repository/IRepositoryConfigRepository.java index 342a1ac1..e1b8985a 100644 --- a/backend/src/main/java/com/qqchen/deploy/backend/repository/RepositoryConfigRepository.java +++ b/backend/src/main/java/com/qqchen/deploy/backend/repository/IRepositoryConfigRepository.java @@ -1,14 +1,14 @@ package com.qqchen.deploy.backend.repository; -import com.qqchen.deploy.backend.common.repository.BaseRepository; +import com.qqchen.deploy.backend.common.repository.IBaseRepository; import com.qqchen.deploy.backend.entity.RepositoryConfig; import org.springframework.stereotype.Repository; import java.util.List; @Repository -public interface RepositoryConfigRepository extends BaseRepository { +public interface IRepositoryConfigRepository extends IBaseRepository { List findByDeletedFalseOrderBySort(); boolean existsByNameAndDeletedFalse(String name); } \ No newline at end of file diff --git a/backend/src/main/java/com/qqchen/deploy/backend/repository/RepositoryGroupRepository.java b/backend/src/main/java/com/qqchen/deploy/backend/repository/IRepositoryGroupRepository.java similarity index 79% rename from backend/src/main/java/com/qqchen/deploy/backend/repository/RepositoryGroupRepository.java rename to backend/src/main/java/com/qqchen/deploy/backend/repository/IRepositoryGroupRepository.java index b1e80658..bbd85f80 100644 --- a/backend/src/main/java/com/qqchen/deploy/backend/repository/RepositoryGroupRepository.java +++ b/backend/src/main/java/com/qqchen/deploy/backend/repository/IRepositoryGroupRepository.java @@ -1,6 +1,6 @@ package com.qqchen.deploy.backend.repository; -import com.qqchen.deploy.backend.common.repository.BaseRepository; +import com.qqchen.deploy.backend.common.repository.IBaseRepository; import com.qqchen.deploy.backend.entity.RepositoryGroup; import org.springframework.data.jpa.repository.Modifying; import org.springframework.data.jpa.repository.Query; @@ -10,7 +10,7 @@ import org.springframework.transaction.annotation.Transactional; import java.util.Optional; @Repository -public interface RepositoryGroupRepository extends BaseRepository { +public interface IRepositoryGroupRepository extends IBaseRepository { @Modifying @Transactional diff --git a/backend/src/main/java/com/qqchen/deploy/backend/repository/RepositoryProjectRepository.java b/backend/src/main/java/com/qqchen/deploy/backend/repository/IRepositoryProjectRepository.java similarity index 79% rename from backend/src/main/java/com/qqchen/deploy/backend/repository/RepositoryProjectRepository.java rename to backend/src/main/java/com/qqchen/deploy/backend/repository/IRepositoryProjectRepository.java index a87b4881..cb7df9f5 100644 --- a/backend/src/main/java/com/qqchen/deploy/backend/repository/RepositoryProjectRepository.java +++ b/backend/src/main/java/com/qqchen/deploy/backend/repository/IRepositoryProjectRepository.java @@ -1,6 +1,6 @@ package com.qqchen.deploy.backend.repository; -import com.qqchen.deploy.backend.common.repository.BaseRepository; +import com.qqchen.deploy.backend.common.repository.IBaseRepository; import com.qqchen.deploy.backend.entity.RepositoryProject; import org.springframework.data.jpa.repository.Modifying; import org.springframework.data.jpa.repository.Query; @@ -10,7 +10,7 @@ import org.springframework.transaction.annotation.Transactional; import java.util.Optional; @Repository -public interface RepositoryProjectRepository extends BaseRepository { +public interface IRepositoryProjectRepository extends IBaseRepository { @Modifying @Transactional diff --git a/backend/src/main/java/com/qqchen/deploy/backend/repository/RepositorySyncHistoryRepository.java b/backend/src/main/java/com/qqchen/deploy/backend/repository/IRepositorySyncHistoryRepository.java similarity index 60% rename from backend/src/main/java/com/qqchen/deploy/backend/repository/RepositorySyncHistoryRepository.java rename to backend/src/main/java/com/qqchen/deploy/backend/repository/IRepositorySyncHistoryRepository.java index 7b22f06a..de597b55 100644 --- a/backend/src/main/java/com/qqchen/deploy/backend/repository/RepositorySyncHistoryRepository.java +++ b/backend/src/main/java/com/qqchen/deploy/backend/repository/IRepositorySyncHistoryRepository.java @@ -1,13 +1,13 @@ package com.qqchen.deploy.backend.repository; -import com.qqchen.deploy.backend.common.repository.BaseRepository; +import com.qqchen.deploy.backend.common.repository.IBaseRepository; import com.qqchen.deploy.backend.entity.RepositorySyncHistory; import org.springframework.stereotype.Repository; import java.util.List; @Repository -public interface RepositorySyncHistoryRepository extends BaseRepository { +public interface IRepositorySyncHistoryRepository extends IBaseRepository { List findTop50ByOrderByStartTimeDesc(); } \ No newline at end of file diff --git a/backend/src/main/java/com/qqchen/deploy/backend/repository/RoleMenuRepository.java b/backend/src/main/java/com/qqchen/deploy/backend/repository/IRoleMenuRepository.java similarity index 69% rename from backend/src/main/java/com/qqchen/deploy/backend/repository/RoleMenuRepository.java rename to backend/src/main/java/com/qqchen/deploy/backend/repository/IRoleMenuRepository.java index bbfcaaee..8389e2ca 100644 --- a/backend/src/main/java/com/qqchen/deploy/backend/repository/RoleMenuRepository.java +++ b/backend/src/main/java/com/qqchen/deploy/backend/repository/IRoleMenuRepository.java @@ -1,14 +1,14 @@ package com.qqchen.deploy.backend.repository; -import com.qqchen.deploy.backend.common.repository.BaseRepository; +import com.qqchen.deploy.backend.common.repository.IBaseRepository; import com.qqchen.deploy.backend.entity.RoleMenu; import org.springframework.stereotype.Repository; import java.util.List; @Repository -public interface RoleMenuRepository extends BaseRepository { +public interface IRoleMenuRepository extends IBaseRepository { List findByRoleId(Long roleId); diff --git a/backend/src/main/java/com/qqchen/deploy/backend/repository/RoleRepository.java b/backend/src/main/java/com/qqchen/deploy/backend/repository/IRoleRepository.java similarity index 75% rename from backend/src/main/java/com/qqchen/deploy/backend/repository/RoleRepository.java rename to backend/src/main/java/com/qqchen/deploy/backend/repository/IRoleRepository.java index 69d39155..a1f23817 100644 --- a/backend/src/main/java/com/qqchen/deploy/backend/repository/RoleRepository.java +++ b/backend/src/main/java/com/qqchen/deploy/backend/repository/IRoleRepository.java @@ -1,7 +1,7 @@ package com.qqchen.deploy.backend.repository; -import com.qqchen.deploy.backend.common.repository.BaseRepository; +import com.qqchen.deploy.backend.common.repository.IBaseRepository; import com.qqchen.deploy.backend.entity.Role; import org.springframework.stereotype.Repository; @@ -9,7 +9,7 @@ import java.util.List; import java.util.Optional; @Repository -public interface RoleRepository extends BaseRepository { +public interface IRoleRepository extends IBaseRepository { List findByDeletedFalseOrderBySort(); diff --git a/backend/src/main/java/com/qqchen/deploy/backend/repository/TenantRepository.java b/backend/src/main/java/com/qqchen/deploy/backend/repository/ITenantRepository.java similarity index 70% rename from backend/src/main/java/com/qqchen/deploy/backend/repository/TenantRepository.java rename to backend/src/main/java/com/qqchen/deploy/backend/repository/ITenantRepository.java index 600d0529..444faba3 100644 --- a/backend/src/main/java/com/qqchen/deploy/backend/repository/TenantRepository.java +++ b/backend/src/main/java/com/qqchen/deploy/backend/repository/ITenantRepository.java @@ -1,13 +1,13 @@ package com.qqchen.deploy.backend.repository; -import com.qqchen.deploy.backend.common.repository.BaseRepository; +import com.qqchen.deploy.backend.common.repository.IBaseRepository; import com.qqchen.deploy.backend.entity.Tenant; import org.springframework.stereotype.Repository; import java.util.List; @Repository -public interface TenantRepository extends BaseRepository { +public interface ITenantRepository extends IBaseRepository { List findByDeletedFalseOrderById(); diff --git a/backend/src/main/java/com/qqchen/deploy/backend/repository/UserRepository.java b/backend/src/main/java/com/qqchen/deploy/backend/repository/IUserRepository.java similarity index 81% rename from backend/src/main/java/com/qqchen/deploy/backend/repository/UserRepository.java rename to backend/src/main/java/com/qqchen/deploy/backend/repository/IUserRepository.java index c89eba19..86729bba 100644 --- a/backend/src/main/java/com/qqchen/deploy/backend/repository/UserRepository.java +++ b/backend/src/main/java/com/qqchen/deploy/backend/repository/IUserRepository.java @@ -1,7 +1,7 @@ package com.qqchen.deploy.backend.repository; -import com.qqchen.deploy.backend.common.repository.BaseRepository; +import com.qqchen.deploy.backend.common.repository.IBaseRepository; import com.qqchen.deploy.backend.entity.User; import org.springframework.stereotype.Repository; @@ -9,7 +9,7 @@ import java.util.List; import java.util.Optional; @Repository -public interface UserRepository extends BaseRepository { +public interface IUserRepository extends IBaseRepository { Optional findByUsernameAndDeletedFalse(String username); diff --git a/backend/src/main/java/com/qqchen/deploy/backend/repository/UserRoleRepository.java b/backend/src/main/java/com/qqchen/deploy/backend/repository/IUserRoleRepository.java similarity index 85% rename from backend/src/main/java/com/qqchen/deploy/backend/repository/UserRoleRepository.java rename to backend/src/main/java/com/qqchen/deploy/backend/repository/IUserRoleRepository.java index b85324a7..b657d3f8 100644 --- a/backend/src/main/java/com/qqchen/deploy/backend/repository/UserRoleRepository.java +++ b/backend/src/main/java/com/qqchen/deploy/backend/repository/IUserRoleRepository.java @@ -1,7 +1,7 @@ package com.qqchen.deploy.backend.repository; -import com.qqchen.deploy.backend.common.repository.BaseRepository; +import com.qqchen.deploy.backend.common.repository.IBaseRepository; import com.qqchen.deploy.backend.entity.UserRole; import org.springframework.data.jpa.repository.Modifying; import org.springframework.data.jpa.repository.Query; @@ -11,7 +11,7 @@ import org.springframework.stereotype.Repository; import java.util.Set; @Repository -public interface UserRoleRepository extends BaseRepository { +public interface IUserRoleRepository extends IBaseRepository { @Query("SELECT ur FROM UserRole ur WHERE ur.user.id = :userId") Set findByUserId(@Param("userId") Long userId); diff --git a/backend/src/main/java/com/qqchen/deploy/backend/service/IDepartmentService.java b/backend/src/main/java/com/qqchen/deploy/backend/service/IDepartmentService.java new file mode 100644 index 00000000..107f7bb5 --- /dev/null +++ b/backend/src/main/java/com/qqchen/deploy/backend/service/IDepartmentService.java @@ -0,0 +1,18 @@ +package com.qqchen.deploy.backend.service; + + +import com.qqchen.deploy.backend.common.service.IBaseService; +import com.qqchen.deploy.backend.entity.Department; + +import java.util.List; + +public interface IDepartmentService extends IBaseService { + + List getTree(); + + void validateCode(String code); + + void validateName(String name); + + Integer getNextSort(Long parentId); +} \ No newline at end of file diff --git a/backend/src/main/java/com/qqchen/deploy/backend/service/IJenkinsService.java b/backend/src/main/java/com/qqchen/deploy/backend/service/IJenkinsService.java new file mode 100644 index 00000000..08f23bd8 --- /dev/null +++ b/backend/src/main/java/com/qqchen/deploy/backend/service/IJenkinsService.java @@ -0,0 +1,27 @@ +package com.qqchen.deploy.backend.service; + + +import com.qqchen.deploy.backend.common.service.IBaseService; +import com.qqchen.deploy.backend.entity.JenkinsConfig; + +public interface IJenkinsService extends IBaseService { +// boolean testConnection(JenkinsTestConnectionDTO dto); + + Long asyncSyncAll(Long jenkinsId); + + Long asyncSyncView(Long jenkinsId); + + Long asyncSyncJob(Long jenkinsId); + + Long asyncSyncBuild(Long jenkinsId); + +// List getSyncHistories(); + +// List getViews(Long jenkinsId); + +// List getJobs(Long jenkinsId); + +// List getBuilds(Long jenkinsId, Long jobId); + +// List getJobsByView(Long jenkinsId, String viewName); +} \ No newline at end of file diff --git a/backend/src/main/java/com/qqchen/deploy/backend/service/IMenuService.java b/backend/src/main/java/com/qqchen/deploy/backend/service/IMenuService.java new file mode 100644 index 00000000..40e2b0fc --- /dev/null +++ b/backend/src/main/java/com/qqchen/deploy/backend/service/IMenuService.java @@ -0,0 +1,20 @@ +package com.qqchen.deploy.backend.service; + + +import com.qqchen.deploy.backend.common.service.IBaseService; +import com.qqchen.deploy.backend.entity.Menu; + +import java.util.List; + +public interface IMenuService extends IBaseService { + +// List getMenuTree(); + +// List getMenuTreeWithoutButtons(); + +// List getUserMenus(Long userId); + + List getMenusByRoleId(Long roleId); + + void saveRoleMenus(Long roleId, List menuIds); +} \ No newline at end of file diff --git a/backend/src/main/java/com/qqchen/deploy/backend/service/IRepositoryService.java b/backend/src/main/java/com/qqchen/deploy/backend/service/IRepositoryService.java new file mode 100644 index 00000000..8c712643 --- /dev/null +++ b/backend/src/main/java/com/qqchen/deploy/backend/service/IRepositoryService.java @@ -0,0 +1,46 @@ +package com.qqchen.deploy.backend.service; + + +import com.qqchen.deploy.backend.common.service.IBaseService; +import com.qqchen.deploy.backend.entity.RepositoryConfig; + +import java.util.List; + +public interface IRepositoryService extends IBaseService { + + // 仓库配置管理 + List listConfigs(); + RepositoryConfig createConfig(RepositoryConfig config); + RepositoryConfig updateConfig(Long id, RepositoryConfig config); + void deleteConfig(Long id); + + // 测试连接 + boolean testConnection(RepositoryConfig config); +// boolean testConnection(TestConnectionDTO dto); + + // 同步操作 + void syncAll(Long repositoryId); + void syncGroups(Long repositoryId); + void syncProjects(Long repositoryId, Long groupId); + void syncBranches(Long repositoryId, Long projectId); + + /** + * 异步同步仓库数据 + * @param repositoryId 仓库ID + * @return 同步历史ID + */ + Long asyncSyncAll(Long repositoryId); + + /** + * 获取同步状态 + * @param historyId 同步历史ID + * @return 仓库同步状态 + */ +// RepositorySyncStatusDTO getSyncStatus(Long historyId); + + /** + * 获取正在同步的仓库列表 + * @return 正在同步的仓库信息列表 + */ +// List getRunningSyncs(); +} \ No newline at end of file diff --git a/backend/src/main/java/com/qqchen/deploy/backend/service/IRoleService.java b/backend/src/main/java/com/qqchen/deploy/backend/service/IRoleService.java new file mode 100644 index 00000000..ff1dd111 --- /dev/null +++ b/backend/src/main/java/com/qqchen/deploy/backend/service/IRoleService.java @@ -0,0 +1,18 @@ +package com.qqchen.deploy.backend.service; + + +import com.qqchen.deploy.backend.common.service.IBaseService; +import com.qqchen.deploy.backend.entity.Role; + +import java.util.List; + +public interface IRoleService extends IBaseService { + + void validateCode(String code); + + void validateName(String name); + + List getRoleMenuIds(Long roleId); + + void updateRoleMenus(Long roleId, List menuIds); +} \ No newline at end of file diff --git a/backend/src/main/java/com/qqchen/deploy/backend/service/ITenantService.java b/backend/src/main/java/com/qqchen/deploy/backend/service/ITenantService.java new file mode 100644 index 00000000..b8a75f12 --- /dev/null +++ b/backend/src/main/java/com/qqchen/deploy/backend/service/ITenantService.java @@ -0,0 +1,21 @@ +package com.qqchen.deploy.backend.service; + +import com.qqchen.deploy.backend.common.service.IBaseService; +import com.qqchen.deploy.backend.entity.Tenant; + +import java.util.List; + +public interface ITenantService extends IBaseService { + + void validateCode(String code); + + void validateName(String name); + + List findAllEnabled(); + + boolean existsByCode(String code); + + boolean existsByName(String name); + +// List findAll(TenantQuery query); +} \ No newline at end of file diff --git a/backend/src/main/java/com/qqchen/deploy/backend/service/UserService.java b/backend/src/main/java/com/qqchen/deploy/backend/service/IUserService.java similarity index 66% rename from backend/src/main/java/com/qqchen/deploy/backend/service/UserService.java rename to backend/src/main/java/com/qqchen/deploy/backend/service/IUserService.java index 8ee2391f..f0b8b85f 100644 --- a/backend/src/main/java/com/qqchen/deploy/backend/service/UserService.java +++ b/backend/src/main/java/com/qqchen/deploy/backend/service/IUserService.java @@ -1,9 +1,9 @@ package com.qqchen.deploy.backend.service; -import com.qqchen.deploy.backend.common.service.BaseService; +import com.qqchen.deploy.backend.common.service.IBaseService; import com.qqchen.deploy.backend.entity.User; -public interface UserService extends BaseService { +public interface IUserService extends IBaseService { User register(User user); User findByUsername(String username); boolean checkUsernameExists(String username); diff --git a/backend/src/main/java/com/qqchen/deploy/backend/service/impl/DepartmentServiceImpl.java b/backend/src/main/java/com/qqchen/deploy/backend/service/impl/DepartmentServiceImpl.java new file mode 100644 index 00000000..14ad6b5e --- /dev/null +++ b/backend/src/main/java/com/qqchen/deploy/backend/service/impl/DepartmentServiceImpl.java @@ -0,0 +1,105 @@ +package com.qqchen.deploy.backend.service.impl; + +import com.qqchen.deploy.backend.common.service.impl.BaseServiceImpl; +import com.qqchen.deploy.backend.entity.Department; +import com.qqchen.deploy.backend.repository.IDepartmentRepository; +import com.qqchen.deploy.backend.service.IDepartmentService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import jakarta.transaction.Transactional; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +@Service +@Transactional +public class DepartmentServiceImpl extends BaseServiceImpl implements IDepartmentService { + + private final IDepartmentRepository repository; + + @Autowired + public DepartmentServiceImpl(IDepartmentRepository repository) { + super(repository); + this.repository = repository; + } + + @Override + public Department create(Department entity) { + validateCode(entity.getCode()); + validateName(entity.getName()); + return repository.save(entity); + } + + public Department update(Long id, Department entity) { + Department existing = repository.findById(id) + .orElseThrow(() -> new RuntimeException("部门不存在")); + + entity.setVersion(existing.getVersion()); + entity.setId(id); + + if (!existing.getCode().equals(entity.getCode())) { + if (repository.existsByCodeAndDeletedFalse(entity.getCode())) { + throw new RuntimeException("部门编码已存在"); + } + } + + if (!existing.getName().equals(entity.getName())) { + if (repository.existsByNameAndDeletedFalse(entity.getName())) { + throw new RuntimeException("部门名称已存在"); + } + } + + entity.setCreateTime(existing.getCreateTime()); + entity.setCreateBy(existing.getCreateBy()); + entity.setDeleted(existing.getDeleted()); + + return repository.save(entity); + } + + @Override + public List getTree() { + List departments = repository.findByDeletedFalseOrderBySort(); + return buildTree(departments); + } + + @Override + public void validateCode(String code) { + if (repository.existsByCodeAndDeletedFalse(code)) { + throw new RuntimeException("部门编码已存在"); + } + } + + @Override + public void validateName(String name) { + if (repository.existsByNameAndDeletedFalse(name)) { + throw new RuntimeException("部门名称已存在"); + } + } + + @Override + public Integer getNextSort(Long parentId) { + return repository.findMaxSortByParentId(parentId) + 1; + } + + private List buildTree(List departments) { + Map> parentIdMap = departments.stream() + .collect(Collectors.groupingBy(d -> d.getParentId() == null ? 0L : d.getParentId())); + + return buildTreeNodes(0L, parentIdMap); + } + + private List buildTreeNodes(Long parentId, Map> parentIdMap) { + List nodes = new ArrayList<>(); + + List children = parentIdMap.get(parentId); + if (children != null) { + for (Department child : children) { + child.setChildren(buildTreeNodes(child.getId(), parentIdMap)); + nodes.add(child); + } + } + + return nodes; + } +} \ No newline at end of file diff --git a/backend/src/main/java/com/qqchen/deploy/backend/service/impl/IJenkinsServiceImpl.java b/backend/src/main/java/com/qqchen/deploy/backend/service/impl/IJenkinsServiceImpl.java new file mode 100644 index 00000000..02777c42 --- /dev/null +++ b/backend/src/main/java/com/qqchen/deploy/backend/service/impl/IJenkinsServiceImpl.java @@ -0,0 +1,612 @@ +//package com.qqchen.deploy.backend.service.impl; +// +//import com.qqchen.deploy.backend.common.service.impl.BaseServiceImpl; +//import com.qqchen.deploy.backend.entity.JenkinsConfig; +//import com.qqchen.deploy.backend.repository.IJenkinsBuildRepository; +//import com.qqchen.deploy.backend.repository.IJenkinsConfigRepository; +//import com.qqchen.deploy.backend.repository.IJenkinsJobRepository; +//import com.qqchen.deploy.backend.repository.IJenkinsSyncHistoryRepository; +//import com.qqchen.deploy.backend.repository.IJenkinsViewRepository; +//import com.qqchen.deploy.backend.service.IJenkinsService; +//import jakarta.persistence.EntityManager; +//import jakarta.persistence.PersistenceContext; +//import lombok.extern.slf4j.Slf4j; +//import org.springframework.beans.factory.annotation.Autowired; +//import org.springframework.boot.web.client.RestTemplateBuilder; +//import org.springframework.core.ParameterizedTypeReference; +//import org.springframework.http.HttpEntity; +//import org.springframework.http.HttpHeaders; +//import org.springframework.http.HttpMethod; +//import org.springframework.http.HttpStatus; +//import org.springframework.http.MediaType; +//import org.springframework.http.ResponseEntity; +//import org.springframework.stereotype.Service; +//import org.springframework.transaction.annotation.Transactional; +//import org.springframework.web.client.RestTemplate; +// +//import java.time.Duration; +//import java.time.Instant; +//import java.time.LocalDateTime; +//import java.time.ZoneId; +//import java.util.ArrayList; +//import java.util.Base64; +//import java.util.Collections; +//import java.util.List; +//import java.util.Map; +//import java.util.concurrent.CompletableFuture; +//import java.util.concurrent.ConcurrentHashMap; +//import java.util.stream.Collectors; +// +//@Slf4j +//@Service +//public class IJenkinsServiceImpl extends BaseServiceImpl implements IJenkinsService { +// +// private final IJenkinsConfigRepository jenkinsConfigRepository; +// private final IJenkinsSyncHistoryRepository jenkinsSyncHistoryRepository; +// private final IJenkinsViewRepository jenkinsViewRepository; +// private final IJenkinsJobRepository jenkinsJobRepository; +// private final IJenkinsBuildRepository jenkinsBuildRepository; +// +// @PersistenceContext +// private EntityManager entityManager; +// +// // 使用 ConcurrentHashMap 记录正在执行的任务 +// private static final Map RUNNING_TASKS = new ConcurrentHashMap<>(); +// +// @Autowired +// public IJenkinsServiceImpl( +// IJenkinsConfigRepository jenkinsConfigRepository, +// IJenkinsSyncHistoryRepository jenkinsSyncHistoryRepository, +// IJenkinsViewRepository jenkinsViewRepository, +// IJenkinsJobRepository jenkinsJobRepository, +// IJenkinsBuildRepository jenkinsBuildRepository +// ){ +// super(jenkinsConfigRepository); +// this.jenkinsConfigRepository = jenkinsConfigRepository; +// this.jenkinsSyncHistoryRepository = jenkinsSyncHistoryRepository; +// this.jenkinsViewRepository = jenkinsViewRepository; +// this.jenkinsJobRepository = jenkinsJobRepository; +// this.jenkinsBuildRepository = jenkinsBuildRepository; +// } +// +// private RestTemplate createRestTemplate() { +// return new RestTemplateBuilder() +// .setConnectTimeout(Duration.ofSeconds(30)) +// .setReadTimeout(Duration.ofSeconds(30)) +// .build(); +// } +// +// private HttpEntity createHttpEntity(String username, String password) { +// HttpHeaders headers = new HttpHeaders(); +// String auth = username + ":" + password; +// byte[] encodedAuth = Base64.getEncoder().encode(auth.getBytes()); +// headers.set("Authorization", "Basic " + new String(encodedAuth)); +// headers.setAccept(Collections.singletonList(MediaType.APPLICATION_JSON)); +// return new HttpEntity<>(headers); +// } +// +// @Transactional +// public JenkinsConfig save(JenkinsConfig config) { +// if (jenkinsConfigRepository.existsByNameAndDeletedFalse(config.getName())) { +// throw new RuntimeException("Jenkins名称已存在"); +// } +// return jenkinsConfigRepository.save(config); +// } +// +// @Transactional +// public JenkinsConfig update(Long id, JenkinsConfig config) { +// JenkinsConfig existing = jenkinsConfigRepository.findById(id) +// .orElseThrow(() -> new RuntimeException("Jenkins配置不存在")); +// +// if (!existing.getName().equals(config.getName()) +// && jenkinsConfigRepository.existsByNameAndDeletedFalse(config.getName())) { +// throw new RuntimeException("Jenkins名称已存在"); +// } +// +// config.setId(id); +// config.setVersion(existing.getVersion()); +// config.setDeleted(existing.getDeleted()); +// return jenkinsConfigRepository.save(config); +// } +// +// @Override +// public boolean testConnection(JenkinsTestConnectionDTO dto) { +// try { +// String url = String.format("%s/api/json", dto.getUrl().trim()); +// ResponseEntity response = createRestTemplate().exchange( +// url, +// HttpMethod.GET, +// createHttpEntity(dto.getUsername(), dto.getPassword()), +// Map.class +// ); +// +// if (response.getStatusCode() == HttpStatus.OK && response.getBody() != null) { +// log.info("Jenkins连接测试成功"); +// return true; +// } +// +// log.error("Jenkins连接测试失败: 响应状态码 {}", response.getStatusCode()); +// return false; +// } catch (Exception e) { +// log.error("Jenkins连接测试失败: {}", e.getMessage()); +// return false; +// } +// } +// +// @Override +// public Long asyncSyncAll(Long jenkinsId) { +// // 检查是否已有同步任务在运行 +// if (RUNNING_TASKS.containsKey(jenkinsId)) { +// log.warn("Jenkins已有同步任务正在运行中, jenkinsId: {}", jenkinsId); +// throw new ApiException("该Jenkins已有同步任务正在运行中"); +// } +// +// log.info("开始同步Jenkins数据, jenkinsId: {}", jenkinsId); +// +// JenkinsConfig config = jenkinsConfigRepository.findById(jenkinsId) +// .orElseThrow(() -> new ApiException("Jenkins配置不存在")); +// +// // 创建全量同步历史记录 +// JenkinsSyncHistory allHistory = createSyncHistory(jenkinsId, JenkinsSyncHistory.SyncType.ALL); +// +// // 标记任务开始运行 +// RUNNING_TASKS.put(jenkinsId, true); +// +// try { +// // 1. 同步视图 +// JenkinsSyncHistory viewHistory = createSyncHistory(jenkinsId, JenkinsSyncHistory.SyncType.VIEW); +// try { +// syncViews(config, viewHistory); +// } catch (Exception e) { +// log.error("视图同步失败", e); +// updateSyncHistory(viewHistory, JenkinsSyncHistory.SyncStatus.FAILED, e.getMessage()); +// throw e; +// } +// +// // 2. 同步作业 +// JenkinsSyncHistory jobHistory = createSyncHistory(jenkinsId, JenkinsSyncHistory.SyncType.JOB); +// try { +// syncJobs(config, jobHistory); +// } catch (Exception e) { +// log.error("作业同步失败", e); +// updateSyncHistory(jobHistory, JenkinsSyncHistory.SyncStatus.FAILED, e.getMessage()); +// throw e; +// } +// +// // 3. 同步构建 +// JenkinsSyncHistory buildHistory = createSyncHistory(jenkinsId, JenkinsSyncHistory.SyncType.BUILD); +// try { +// syncBuilds(config, buildHistory); +// } catch (Exception e) { +// log.error("构建同步失败", e); +// updateSyncHistory(buildHistory, JenkinsSyncHistory.SyncStatus.FAILED, e.getMessage()); +// throw e; +// } +// +// // 更新全量同步状态 +// updateSyncHistory(allHistory, JenkinsSyncHistory.SyncStatus.SUCCESS, null); +// config.setLastAllSyncTime(LocalDateTime.now()); +// jenkinsConfigRepository.save(config); +// log.info("Jenkins数据同步完成, jenkinsId: {}", jenkinsId); +// +// } catch (Exception e) { +// log.error("Jenkins数据同步失败", e); +// updateSyncHistory(allHistory, JenkinsSyncHistory.SyncStatus.FAILED, e.getMessage()); +// } finally { +// RUNNING_TASKS.remove(jenkinsId); +// } +// +// return allHistory.getId(); +// } +// +// /** +// * 同步Jenkins视图 +// * @param config Jenkins配置 +// * @param history 同步历史记录 +// */ +// private void syncViews(JenkinsConfig config, JenkinsSyncHistory history) { +// String url = String.format("%s/api/json?tree=views[name,url,description]", config.getUrl().trim()); +// ResponseEntity> response = createRestTemplate().exchange( +// url, +// HttpMethod.GET, +// createHttpEntity(config.getUsername(), config.getPassword()), +// new ParameterizedTypeReference<>() {} +// ); +// +// if (response.getBody() != null) { +// List> views = (List>) response.getBody().get("views"); +// List viewsToSave = new ArrayList<>(); +// Map existingViews = jenkinsViewRepository +// .findByJenkinsIdAndDeletedFalse(config.getId()) +// .stream() +// .collect(Collectors.toMap(JenkinsView::getViewName, v -> v)); +// +// for (Map view : views) { +// String viewName = (String) view.get("name"); +// // 跳过 "All" 视图 +// if ("All".equalsIgnoreCase(viewName)) { +// log.debug("跳过All视图同步 - 仓库: {}", config.getName()); +// continue; +// } +// +// JenkinsView jenkinsView = existingViews.getOrDefault(viewName, new JenkinsView()); +// jenkinsView.setJenkinsId(config.getId()); +// jenkinsView.setViewName(viewName); +// jenkinsView.setViewUrl((String) view.get("url")); +// jenkinsView.setDescription((String) view.get("description")); +// viewsToSave.add(jenkinsView); +// } +// +// if (!viewsToSave.isEmpty()) { +// jenkinsViewRepository.saveAll(viewsToSave); +// log.info("同步Jenkins视图完成, 总数: {}, 实际同步: {}", views.size(), viewsToSave.size()); +// +// // 更新配置的同步时间 +// config.setLastViewSyncTime(LocalDateTime.now()); +// jenkinsConfigRepository.save(config); +// } +// } +// +// updateSyncHistory(history, JenkinsSyncHistory.SyncStatus.SUCCESS, null); +// } +// +// @Transactional +// public void updateSyncHistory(JenkinsSyncHistory history, +// JenkinsSyncHistory.SyncStatus status, +// String errorMessage) { +// // 重新获取最新的历史记录 +// JenkinsSyncHistory latestHistory = jenkinsSyncHistoryRepository.findById(history.getId()) +// .orElseThrow(() -> new ApiException("同步历史记录不存在")); +// +// latestHistory.setStatus(status); +// latestHistory.setEndTime(LocalDateTime.now()); +// latestHistory.setErrorMessage(errorMessage); +// jenkinsSyncHistoryRepository.save(latestHistory); +// } +// +// @Transactional +// public JenkinsSyncHistory createSyncHistory(Long jenkinsId, JenkinsSyncHistory.SyncType syncType) { +// JenkinsSyncHistory history = new JenkinsSyncHistory(); +// history.setJenkinsId(jenkinsId); +// history.setSyncType(syncType); +// history.setStartTime(LocalDateTime.now()); +// history.setStatus(JenkinsSyncHistory.SyncStatus.RUNNING); +// return jenkinsSyncHistoryRepository.save(history); +// } +// +// /** +// * 同步Jenkins作业 +// * @param config Jenkins配置 +// * @param history 同步历记录 +// */ +// private void syncJobs(JenkinsConfig config, JenkinsSyncHistory history) { +// String url = String.format("%s/api/json?tree=jobs[name,url,description,buildable,lastBuild[number,timestamp,result]]", +// config.getUrl().trim()); +// ResponseEntity> response = createRestTemplate().exchange( +// url, +// HttpMethod.GET, +// createHttpEntity(config.getUsername(), config.getPassword()), +// new ParameterizedTypeReference<>() {} +// ); +// +// if (response.getBody() != null) { +// List> jobs = (List>) response.getBody().get("jobs"); +// List jobsToSave = new ArrayList<>(); +// Map existingJobs = jenkinsJobRepository +// .findByJenkinsIdAndDeletedFalse(config.getId()) +// .stream() +// .collect(Collectors.toMap(JenkinsJob::getJobName, j -> j)); +// +// for (Map job : jobs) { +// String jobName = (String) job.get("name"); +// JenkinsJob jenkinsJob = existingJobs.getOrDefault(jobName, new JenkinsJob()); +// jenkinsJob.setJenkinsId(config.getId()); +// jenkinsJob.setJobName(jobName); +// jenkinsJob.setJobUrl((String) job.get("url")); +// jenkinsJob.setDescription((String) job.get("description")); +// jenkinsJob.setBuildable((Boolean) job.get("buildable")); +// +// Map lastBuild = (Map) job.get("lastBuild"); +// if (lastBuild != null) { +// jenkinsJob.setLastBuildNumber(((Number) lastBuild.get("number")).intValue()); +// long timestamp = ((Number) lastBuild.get("timestamp")).longValue(); +// jenkinsJob.setLastBuildTime(LocalDateTime.ofInstant( +// Instant.ofEpochMilli(timestamp), +// ZoneId.systemDefault() +// )); +// jenkinsJob.setLastBuildStatus((String) lastBuild.get("result")); +// } +// jobsToSave.add(jenkinsJob); +// } +// +// if (!jobsToSave.isEmpty()) { +// jenkinsJobRepository.saveAll(jobsToSave); +// log.info("同步Jenkins作业完成, 总数: {}, 实际同步: {}", jobs.size(), jobsToSave.size()); +// +// // 更新配置的同步时间 +// config.setLastJobSyncTime(LocalDateTime.now()); +// jenkinsConfigRepository.save(config); +// } +// } +// +// updateSyncHistory(history, JenkinsSyncHistory.SyncStatus.SUCCESS, null); +// } +// +// /** +// * 同步Jenkins构建 +// * @param config Jenkins配置 +// * @param history 同步历史记录 +// */ +// private void syncBuilds(JenkinsConfig config, JenkinsSyncHistory history) { +// List jobs = jenkinsJobRepository.findByJenkinsIdAndDeletedFalse(config.getId()); +// List allBuildsToSave = new ArrayList<>(); +// +// for (JenkinsJob job : jobs) { +// String url = String.format("%s/api/json?tree=builds[number,url,timestamp,result,duration,actions[causes[*]]]", +// job.getJobUrl().trim()); +// ResponseEntity> response = createRestTemplate().exchange( +// url, +// HttpMethod.GET, +// createHttpEntity(config.getUsername(), config.getPassword()), +// new ParameterizedTypeReference<>() {} +// ); +// +// if (response.getBody() != null) { +// List> builds = (List>) response.getBody().get("builds"); +// Map existingBuilds = jenkinsBuildRepository +// .findByJobIdAndDeletedFalse(job.getId()) +// .stream() +// .collect(Collectors.toMap(JenkinsBuild::getBuildNumber, b -> b)); +// +// for (Map build : builds) { +// Integer buildNumber = ((Number) build.get("number")).intValue(); +// JenkinsBuild jenkinsBuild = existingBuilds.getOrDefault(buildNumber, new JenkinsBuild()); +// jenkinsBuild.setJenkinsId(config.getId()); +// jenkinsBuild.setJobId(job.getId()); +// jenkinsBuild.setBuildNumber(buildNumber); +// jenkinsBuild.setBuildUrl((String) build.get("url")); +// jenkinsBuild.setBuildStatus((String) build.get("result")); +// +// long timestamp = ((Number) build.get("timestamp")).longValue(); +// jenkinsBuild.setStartTime(LocalDateTime.ofInstant( +// Instant.ofEpochMilli(timestamp), +// ZoneId.systemDefault() +// )); +// +// jenkinsBuild.setDuration(((Number) build.get("duration")).longValue()); +// +// // 处理触发原因 +// List> actions = (List>) build.get("actions"); +// if (actions != null) { +// for (Map action : actions) { +// if (action != null && action.containsKey("causes")) { +// List> causes = (List>) action.get("causes"); +// if (causes != null && !causes.isEmpty()) { +// jenkinsBuild.setTriggerCause((String) causes.get(0).get("shortDescription")); +// break; +// } +// } +// } +// } +// allBuildsToSave.add(jenkinsBuild); +// } +// log.info("处理Jenkins构建历史, 作业: {}, 构建数量: {}", job.getJobName(), builds.size()); +// } +// } +// +// if (!allBuildsToSave.isEmpty()) { +// jenkinsBuildRepository.saveAll(allBuildsToSave); +// log.info("同步Jenkins构建完成, 总数: {}", allBuildsToSave.size()); +// +// // 更新配置的同步时间 +// config.setLastBuildSyncTime(LocalDateTime.now()); +// jenkinsConfigRepository.save(config); +// } +// +// updateSyncHistory(history, JenkinsSyncHistory.SyncStatus.SUCCESS, null); +// } +// +// @Override +// public List getSyncHistories() { +// List histories = jenkinsSyncHistoryRepository.findTop50ByOrderByStartTimeDesc(); +// +// return histories.stream() +// .map(history -> { +// JenkinsConfig config = jenkinsConfigRepository.findById(history.getJenkinsId()) +// .orElse(null); +// +// return JenkinsSyncHistoryDTO.builder() +// .id(history.getId()) +// .jenkinsId(history.getJenkinsId()) +// .jenkinsName(config != null ? config.getName() : "未知Jenkins") +// .syncType(history.getSyncType()) +// .status(history.getStatus()) +// .startTime(history.getStartTime()) +// .endTime(history.getEndTime()) +// .errorMessage(history.getErrorMessage()) +// .build(); +// }) +// .collect(Collectors.toList()); +// } +// +// @Transactional +// public void updateLastSyncTime(Long jenkinsId, String timeField) { +// // 使用 JPQL 更新指定字段 +// String jpql = String.format("UPDATE JenkinsConfig j SET j.%s = :now WHERE j.id = :id", timeField); +// entityManager.createQuery(jpql) +// .setParameter("now", LocalDateTime.now()) +// .setParameter("id", jenkinsId) +// .executeUpdate(); +// } +// +// @Override +// public Long asyncSyncView(Long jenkinsId) { +// // 检查是否已有同步任务在运行 +// if (RUNNING_TASKS.containsKey(jenkinsId)) { +// log.warn("Jenkins已有同步任务正在运行中, jenkinsId: {}", jenkinsId); +// throw new ApiException("该Jenkins已有同步任务正在运行中"); +// } +// +// log.info("开始同步Jenkins视图, jenkinsId: {}", jenkinsId); +// +// JenkinsConfig config = jenkinsConfigRepository.findById(jenkinsId) +// .orElseThrow(() -> new ApiException("Jenkins配置不存在")); +// +// // 创建同步历史记录 +// JenkinsSyncHistory history = createSyncHistory(jenkinsId, JenkinsSyncHistory.SyncType.VIEW); +// +// // 标记任务开始运行 +// RUNNING_TASKS.put(jenkinsId, true); +// +// CompletableFuture.runAsync(() -> { +// try { +// syncViews(config, history); +// } catch (Exception e) { +// log.error("视图同步失败", e); +// updateSyncHistory(history, JenkinsSyncHistory.SyncStatus.FAILED, e.getMessage()); +// } finally { +// RUNNING_TASKS.remove(jenkinsId); +// } +// }); +// +// return history.getId(); +// } +// +// @Override +// public Long asyncSyncJob(Long jenkinsId) { +// // 检查是否已有同步任务在运行 +// if (RUNNING_TASKS.containsKey(jenkinsId)) { +// log.warn("Jenkins已有同步任务正在运行中, jenkinsId: {}", jenkinsId); +// throw new ApiException("该Jenkins已有同步任务正在运行中"); +// } +// +// log.info("开始同步Jenkins作业, jenkinsId: {}", jenkinsId); +// +// JenkinsConfig config = jenkinsConfigRepository.findById(jenkinsId) +// .orElseThrow(() -> new ApiException("Jenkins配置不存在")); +// +// // 创建同步历史记录 +// JenkinsSyncHistory history = createSyncHistory(jenkinsId, JenkinsSyncHistory.SyncType.JOB); +// +// // 标记任务开始运行 +// RUNNING_TASKS.put(jenkinsId, true); +// +// CompletableFuture.runAsync(() -> { +// try { +// syncJobs(config, history); +// } catch (Exception e) { +// log.error("作业同步失败", e); +// updateSyncHistory(history, JenkinsSyncHistory.SyncStatus.FAILED, e.getMessage()); +// } finally { +// RUNNING_TASKS.remove(jenkinsId); +// } +// }); +// +// return history.getId(); +// } +// +// @Override +// public Long asyncSyncBuild(Long jenkinsId) { +// // 检查是否已有同步任务在运行 +// if (RUNNING_TASKS.containsKey(jenkinsId)) { +// log.warn("Jenkins已有同步任务正在运行中, jenkinsId: {}", jenkinsId); +// throw new ApiException("该Jenkins已有同步任务正在运行中"); +// } +// +// log.info("开始同步Jenkins构建, jenkinsId: {}", jenkinsId); +// +// JenkinsConfig config = jenkinsConfigRepository.findById(jenkinsId) +// .orElseThrow(() -> new ApiException("Jenkins配置不存在")); +// +// // 创建同步历史记录 +// JenkinsSyncHistory history = createSyncHistory(jenkinsId, JenkinsSyncHistory.SyncType.BUILD); +// +// // 标记任务开始运行 +// RUNNING_TASKS.put(jenkinsId, true); +// +// CompletableFuture.runAsync(() -> { +// try { +// syncBuilds(config, history); +// } catch (Exception e) { +// log.error("构建同步失败", e); +// updateSyncHistory(history, JenkinsSyncHistory.SyncStatus.FAILED, e.getMessage()); +// } finally { +// RUNNING_TASKS.remove(jenkinsId); +// } +// }); +// +// return history.getId(); +// } +// +// @Override +// public List getViews(Long jenkinsId) { +// List views = jenkinsViewRepository.findByJenkinsIdAndDeletedFalse(jenkinsId); +// return views.stream() +// .map(view -> ViewResponse.builder() +// .id(view.getId()) +// .jenkinsId(view.getJenkinsId()) +// .viewName(view.getViewName()) +// .viewUrl(view.getViewUrl()) +// .description(view.getDescription()) +// .build()) +// .collect(Collectors.toList()); +// } +// +// @Override +// public List getJobs(Long jenkinsId) { +// List jobs = jenkinsJobRepository.findByJenkinsIdAndDeletedFalse(jenkinsId); +// return jobs.stream() +// .map(job -> JobResponse.builder() +// .id(job.getId()) +// .jenkinsId(job.getJenkinsId()) +// .viewId(job.getView() != null ? job.getView().getId() : null) +// .viewName(job.getView() != null ? job.getView().getViewName() : null) +// .jobName(job.getJobName()) +// .jobUrl(job.getJobUrl()) +// .description(job.getDescription()) +// .buildable(job.getBuildable()) +// .lastBuildNumber(job.getLastBuildNumber()) +// .lastBuildTime(job.getLastBuildTime()) +// .lastBuildStatus(job.getLastBuildStatus()) +// .build()) +// .collect(Collectors.toList()); +// } +// +// @Override +// public List getBuilds(Long jenkinsId, Long jobId) { +// List builds = jenkinsBuildRepository.findByJobIdAndDeletedFalseOrderByBuildNumberDesc(jobId); +// return builds.stream() +// .map(build -> BuildResponse.builder() +// .id(build.getId()) +// .jenkinsId(build.getJenkinsId()) +// .jobId(build.getJobId()) +// .buildNumber(build.getBuildNumber()) +// .buildUrl(build.getBuildUrl()) +// .buildStatus(build.getBuildStatus()) +// .startTime(build.getStartTime()) +// .duration(build.getDuration()) +// .triggerCause(build.getTriggerCause()) +// .build()) +// .collect(Collectors.toList()); +// } +// +// @Override +// public List getJobsByView(Long jenkinsId, String viewName) { +// List jobs = jenkinsJobRepository.findJobsByJenkinsIdAndViewName(jenkinsId, viewName); +// return jobs.stream() +// .map(job -> JobResponse.builder() +// .id(job.getId()) +// .jenkinsId(job.getJenkinsId()) +// .viewId(job.getView().getId()) +// .viewName(job.getView().getViewName()) +// .jobName(job.getJobName()) +// .jobUrl(job.getJobUrl()) +// .description(job.getDescription()) +// .buildable(job.getBuildable()) +// .lastBuildNumber(job.getLastBuildNumber()) +// .lastBuildTime(job.getLastBuildTime()) +// .lastBuildStatus(job.getLastBuildStatus()) +// .build()) +// .collect(Collectors.toList()); +// } +//} \ No newline at end of file diff --git a/backend/src/main/java/com/qqchen/deploy/backend/service/impl/IMenuServiceImpl.java b/backend/src/main/java/com/qqchen/deploy/backend/service/impl/IMenuServiceImpl.java new file mode 100644 index 00000000..87d457c4 --- /dev/null +++ b/backend/src/main/java/com/qqchen/deploy/backend/service/impl/IMenuServiceImpl.java @@ -0,0 +1,254 @@ +//package com.qqchen.deploy.backend.service.impl; +// +//import com.qqchen.application.dto.MenuDTO; +//import com.qqchen.application.query.MenuQuery; +//import com.qqchen.application.service.MenuService; +//import com.qqchen.common.exception.ApiException; +//import com.qqchen.common.service.impl.BaseServiceImpl; +//import com.qqchen.domain.entity.Menu; +//import com.qqchen.domain.entity.RoleMenu; +//import com.qqchen.domain.repository.MenuRepository; +//import com.qqchen.domain.repository.RoleMenuRepository; +//import lombok.RequiredArgsConstructor; +//import org.springframework.beans.BeanUtils; +//import org.springframework.data.jpa.repository.JpaRepository; +//import org.springframework.stereotype.Service; +//import org.springframework.transaction.annotation.Transactional; +// +//import java.util.ArrayList; +//import java.util.Arrays; +//import java.util.Comparator; +//import java.util.List; +//import java.util.stream.Collectors; +// +//@Service +//@RequiredArgsConstructor +//public class IMenuServiceImpl extends BaseServiceImpl implements MenuService { +// +// private final MenuRepository menuRepository; +// private final RoleMenuRepository roleMenuRepository; +// +// @Override +// protected JpaRepository getRepository() { +// return menuRepository; +// } +// +// @Override +// public List getMenuTree() { +// List menus = menuRepository.findByDeletedFalseOrderBySort(); +// return buildMenuTree(menus); +// } +// +// @Override +// public List getUserMenus(Long userId) { +// // TODO: 根据用户ID获取角色,然后获取菜单 +// return new ArrayList<>(); +// } +// +// @Override +// public List getMenusByRoleId(Long roleId) { +// List roleMenus = roleMenuRepository.findByRoleId(roleId); +// return roleMenus.stream() +// .map(rm -> menuRepository.findById(rm.getMenuId()) +// .orElseThrow(() -> new ApiException("菜单不存在"))) +// .collect(Collectors.toList()); +// } +// +// @Override +// @Transactional +// public void saveRoleMenus(Long roleId, List menuIds) { +// // 先删除原有的关联 +// roleMenuRepository.deleteByRoleId(roleId); +// +// // 保存新的关联 +// List roleMenus = menuIds.stream() +// .map(menuId -> { +// RoleMenu rm = new RoleMenu(); +// rm.setRoleId(roleId); +// rm.setMenuId(menuId); +// return rm; +// }) +// .collect(Collectors.toList()); +// +// roleMenuRepository.saveAll(roleMenus); +// } +// +// @Override +// @Transactional +// public Menu update(Long id, Menu menu) { +// Menu existing = findById(id) +// .orElseThrow(() -> new ApiException("菜单不存在")); +// +// // 设置不允许修改的字段 +// menu.setId(id); +// menu.setVersion(existing.getVersion()); +// menu.setDeleted(existing.getDeleted()); +// +// // 如果是按钮类型,清空路径和组件 +// if (menu.getType() == 2) { +// menu.setPath(null); +// menu.setComponent(null); +// menu.setIcon(null); +// } +// +// // 如果是目录,设置固定组件 +// if (menu.getType() == 0) { +// menu.setComponent("LAYOUT"); +// } +// +// // 如果没有父级菜单,设置为根节点 +// if (menu.getParentId() == null) { +// menu.setParentId(0L); +// } +// +// return menuRepository.save(menu); +// } +// +// @Override +// @Transactional +// public Menu save(Menu menu) { +// // 如果是按钮类型,清空路径和组件 +// if (menu.getType() == 2) { +// menu.setPath(null); +// menu.setComponent(null); +// menu.setIcon(null); +// } +// +// // 如果是目录,设置固定组件 +// if (menu.getType() == 0) { +// menu.setComponent("LAYOUT"); +// } +// +// // 如果没有父级菜单,设置为根节点 +// if (menu.getParentId() == null) { +// menu.setParentId(0L); +// } +// +// menu.setDeleted(false); +// Menu savedMenu = menuRepository.save(menu); +// +// // 如果是菜单类型,自动创建增删改查按钮 +// if (menu.getType() == 1) { +// createDefaultButtons(savedMenu); +// } +// +// return savedMenu; +// } +// +// /** +// * 创建默认的按钮权限 +// */ +// private void createDefaultButtons(Menu menu) { +// // 获取基础权限标识 +// String basePermission = getBasePermission(menu.getPath()); +// +// // 创建默认按钮列表 +// List buttons = List.of( +// createButton(menu.getName() + "查看", basePermission + ":view", menu.getId(), 1), +// createButton(menu.getName() + "新增", basePermission + ":add", menu.getId(), 2), +// createButton(menu.getName() + "修改", basePermission + ":edit", menu.getId(), 3), +// createButton(menu.getName() + "删除", basePermission + ":delete", menu.getId(), 4) +// ); +// +// menuRepository.saveAll(buttons); +// } +// +// /** +// * 创建按钮 +// */ +// private Menu createButton(String name, String permission, Long parentId, int sort) { +// Menu button = new Menu(); +// button.setName(name); +// button.setPermission(permission); +// button.setType(2); +// button.setParentId(parentId); +// button.setSort(sort); +// button.setEnabled(true); +// button.setDeleted(false); +// return button; +// } +// +// /** +// * 根据路径获取基础权限标识 +// * 例如:/system/user -> system:user +// */ +// private String getBasePermission(String path) { +// if (path == null) { +// return ""; +// } +// // 移除开头的斜杠 +// if (path.startsWith("/")) { +// path = path.substring(1); +// } +// // 将斜杠替换为冒号 +// return path.replace("/", ":"); +// } +// +// @Override +// @Transactional +// public void delete(Long id) { +// Menu menu = findById(id) +// .orElseThrow(() -> new ApiException("菜单不存在")); +// +// // 如果是菜单类型,同时删除其下的按钮 +// if (menu.getType() == 1) { +// List buttons = menuRepository.findByParentIdAndDeletedFalse(id); +// if (!buttons.isEmpty()) { +// for (Menu button : buttons) { +// button.setDeleted(true); +// menuRepository.save(button); +// } +// } +// } +// +// // 检查是否有子菜单 +// List children = menuRepository.findByParentIdAndDeletedFalse(id); +// if (!children.isEmpty()) { +// throw new ApiException("该菜单下还有子菜单,不能删除"); +// } +// +// menu.setDeleted(true); +// menuRepository.save(menu); +// } +// +// private List buildMenuTree(List menus) { +// // 先找出所有的根节点(parentId为null或0的节点) +// List rootMenus = menus.stream() +// .filter(menu -> menu.getParentId() == null || menu.getParentId() == 0) +// .collect(Collectors.toList()); +// +// // 构建子树 +// return rootMenus.stream() +// .map(menu -> buildMenuNode(menu, menus)) +// .collect(Collectors.toList()); +// } +// +// /** +// * 构建菜单节点 +// */ +// private MenuDTO buildMenuNode(Menu menu, List allMenus) { +// MenuDTO dto = new MenuDTO(); +// BeanUtils.copyProperties(menu, dto); +// +// // 查找当前节点的子节点 +// List children = allMenus.stream() +// .filter(m -> menu.getId().equals(m.getParentId())) +// .sorted(Comparator.comparing(Menu::getSort)) +// .collect(Collectors.toList()); +// +// if (!children.isEmpty()) { +// dto.setChildren(children.stream() +// .map(child -> buildMenuNode(child, allMenus)) +// .collect(Collectors.toList())); +// } +// +// return dto; +// } +// +// @Override +// public List getMenuTreeWithoutButtons() { +// // 获取所有非按钮类型的菜单 +// List menus = menuRepository.findByTypeInAndDeletedFalseOrderBySort(Arrays.asList(0, 1)); // 0-目录 1-菜单 +// return buildMenuTree(menus); +// } +//} \ No newline at end of file diff --git a/backend/src/main/java/com/qqchen/deploy/backend/service/impl/RepositoryServiceImpl.java b/backend/src/main/java/com/qqchen/deploy/backend/service/impl/RepositoryServiceImpl.java new file mode 100644 index 00000000..931e890d --- /dev/null +++ b/backend/src/main/java/com/qqchen/deploy/backend/service/impl/RepositoryServiceImpl.java @@ -0,0 +1,688 @@ +//package com.qqchen.deploy.backend.service.impl; +// +//import com.qqchen.application.dto.RepositorySyncStatusDTO; +//import com.qqchen.application.dto.RunningSyncDTO; +//import com.qqchen.application.dto.TestConnectionDTO; +//import com.qqchen.application.service.RepositoryService; +//import com.qqchen.common.exception.ApiException; +//import com.qqchen.domain.entity.RepositoryBranch; +//import com.qqchen.domain.entity.RepositoryConfig; +//import com.qqchen.domain.entity.RepositoryGroup; +//import com.qqchen.domain.entity.RepositoryProject; +//import com.qqchen.domain.entity.RepositorySyncHistory; +//import com.qqchen.domain.repository.RepositoryBranchRepository; +//import com.qqchen.domain.repository.RepositoryConfigRepository; +//import com.qqchen.domain.repository.RepositoryGroupRepository; +//import com.qqchen.domain.repository.RepositoryProjectRepository; +//import com.qqchen.domain.repository.RepositorySyncHistoryRepository; +//import lombok.Getter; +//import lombok.RequiredArgsConstructor; +//import lombok.extern.slf4j.Slf4j; +//import org.springframework.boot.web.client.RestTemplateBuilder; +//import org.springframework.core.ParameterizedTypeReference; +//import org.springframework.core.task.AsyncTaskExecutor; +//import org.springframework.http.HttpEntity; +//import org.springframework.http.HttpHeaders; +//import org.springframework.http.HttpMethod; +//import org.springframework.http.HttpStatus; +//import org.springframework.http.MediaType; +//import org.springframework.http.ResponseEntity; +//import org.springframework.stereotype.Service; +//import org.springframework.transaction.PlatformTransactionManager; +//import org.springframework.transaction.annotation.Transactional; +//import org.springframework.transaction.support.TransactionTemplate; +//import org.springframework.web.client.RestTemplate; +// +//import java.time.Duration; +//import java.time.LocalDateTime; +//import java.util.Collection; +//import java.util.Collections; +//import java.util.List; +//import java.util.Map; +//import java.util.concurrent.CompletableFuture; +//import java.util.concurrent.ConcurrentHashMap; +//import java.util.concurrent.TimeUnit; +//import java.util.stream.Collectors; +// +//@Slf4j +//@Service +//@RequiredArgsConstructor +//public class RepositoryServiceImpl implements RepositoryService { +// +// private final RepositoryConfigRepository configRepository; +// +// private final RepositoryGroupRepository groupRepository; +// +// private final RepositoryProjectRepository projectRepository; +// +// private final RepositoryBranchRepository branchRepository; +// +// private final RepositorySyncHistoryRepository syncHistoryRepository; +// +// private final AsyncTaskExecutor taskExecutor; +// +// private final PlatformTransactionManager transactionManager; +// +// // 使用 ConcurrentHashMap 记录正在执行的任务 +// private static final Map RUNNING_TASKS = new ConcurrentHashMap<>(); +// +// private RestTemplate createRestTemplate() { +// return new RestTemplateBuilder() +// .setConnectTimeout(Duration.ofSeconds(30)) +// .setReadTimeout(Duration.ofSeconds(30)) +// .build(); +// } +// +// private HttpEntity createHttpEntity(String accessToken) { +// HttpHeaders headers = new HttpHeaders(); +// headers.set("PRIVATE-TOKEN", accessToken.trim()); +// headers.setAccept(Collections.singletonList(MediaType.APPLICATION_JSON)); +// return new HttpEntity<>(headers); +// } +// +// @Override +// public List listConfigs() { +// return configRepository.findByDeletedFalseOrderBySort(); +// } +// +// @Override +// @Transactional +// public RepositoryConfig createConfig(RepositoryConfig config) { +// if (configRepository.existsByNameAndDeletedFalse(config.getName())) { +// throw new ApiException("仓库名称已存在"); +// } +// +// if (!testConnection(config)) { +// throw new ApiException("仓库连接测试失败"); +// } +// +// return configRepository.save(config); +// } +// +// @Override +// @Transactional +// public RepositoryConfig updateConfig(Long id, RepositoryConfig config) { +// RepositoryConfig existing = configRepository.findById(id) +// .orElseThrow(() -> new ApiException("仓库配置不存在")); +// +// if (!existing.getName().equals(config.getName()) +// && configRepository.existsByNameAndDeletedFalse(config.getName())) { +// throw new ApiException("仓库名称已存在"); +// } +// +// if (!existing.getUrl().equals(config.getUrl()) +// || !existing.getAccessToken().equals(config.getAccessToken())) { +// if (!testConnection(config)) { +// throw new ApiException("仓库连接测试失败"); +// } +// } +// +// config.setId(id); +// config.setVersion(existing.getVersion()); +// config.setDeleted(existing.getDeleted()); +// return configRepository.save(config); +// } +// +// @Override +// @Transactional +// public void deleteConfig(Long id) { +// RepositoryConfig config = configRepository.findById(id) +// .orElseThrow(() -> new ApiException("仓库配置不存在")); +// +// config.setDeleted(true); +// configRepository.save(config); +// } +// +// @Override +// public boolean testConnection(RepositoryConfig config) { +// try { +// String url = String.format("%s/api/v4/user", config.getUrl().trim()); +// ResponseEntity response = createRestTemplate().exchange( +// url, +// HttpMethod.GET, +// createHttpEntity(config.getAccessToken()), +// Map.class +// ); +// +// if (response.getStatusCode() == HttpStatus.OK && response.getBody() != null) { +// log.info("测试连接成功,用户: {}", response.getBody().get("username")); +// return true; +// } +// +// log.error("测试连接失败: 响应状态码 {}", response.getStatusCode()); +// return false; +// } catch (Exception e) { +// log.error("测试连接失败: {}", e.getMessage()); +// return false; +// } +// } +// +// @Override +// public boolean testConnection(TestConnectionDTO dto) { +// try { +// String url = String.format("%s/api/v4/user", dto.getUrl().trim()); +// ResponseEntity response = createRestTemplate().exchange( +// url, +// HttpMethod.GET, +// createHttpEntity(dto.getAccessToken()), +// Map.class +// ); +// +// if (response.getStatusCode() == HttpStatus.OK && response.getBody() != null) { +// log.info("测试连接成功,用户: {}", response.getBody().get("username")); +// return true; +// } +// +// log.error("测试连接失败: 响应状态码 {}", response.getStatusCode()); +// return false; +// } catch (Exception e) { +// log.error("测试连接失败: {}", e.getMessage()); +// return false; +// } +// } +// +// @Override +// @Transactional +// public Long asyncSyncAll(Long repositoryId) { +// // 检查是否已有同步任务在运行 +// if (RUNNING_TASKS.containsKey(repositoryId)) { +// throw new ApiException("该仓库已有同步任务正在运行中"); +// } +// +// log.info("开始异步同步仓库数据, repositoryId: {}", repositoryId); +// +// RepositoryConfig config = configRepository.findById(repositoryId) +// .orElseThrow(() -> new ApiException("仓库配置不存在")); +// +// // 创建同步历史记录 +// RepositorySyncHistory history = new RepositorySyncHistory(); +// history.setRepositoryId(repositoryId); +// history.setSyncType(RepositorySyncHistory.SyncType.GROUP); +// history.setStartTime(LocalDateTime.now()); +// history.setStatus(RepositorySyncHistory.SyncStatus.RUNNING); +// syncHistoryRepository.save(history); +// +// // 标记任务开始运行 +// RUNNING_TASKS.put(repositoryId, true); +// +// taskExecutor.execute(() -> { +// // 用于存储同步过程中的错误信息 +// StringBuilder errorMessages = new StringBuilder(); +// +// try { +// RestTemplate restTemplate = createRestTemplate(); +// HttpEntity entity = createHttpEntity(config.getAccessToken()); +// +// // 1. 同步所有数据到内存 +// RepositorySyncDataCache dataHolder = new RepositorySyncDataCache(); +// +// // 2. 同步群组数据 +// try { +// CompletableFuture groupsFuture = syncGroupsParallel(config, entity, restTemplate, dataHolder); +// boolean groupsSuccess = groupsFuture.get(10, TimeUnit.MINUTES); +// if (!groupsSuccess) { +// errorMessages.append("群组同步失败; "); +// } +// } catch (Exception e) { +// log.error("群组同步失败", e); +// errorMessages.append("群组同步异常: ").append(e.getMessage()).append("; "); +// } +// +// // 3. 如果同步成功,同步项目数据 +// if (errorMessages.isEmpty() && !dataHolder.getGroups().isEmpty()) { +// try { +// CompletableFuture projectsFuture = syncProjectsParallel( +// config, entity, restTemplate, dataHolder.getGroups().values(), dataHolder); +// boolean projectsSuccess = projectsFuture.get(10, TimeUnit.MINUTES); +// if (!projectsSuccess) { +// errorMessages.append("项目同步失败; "); +// } +// } catch (Exception e) { +// log.error("项目同步失败", e); +// errorMessages.append("项目同步异常: ").append(e.getMessage()).append("; "); +// } +// } +// +// // 4. 如果项目同步成功,同步分支数据 +// if (errorMessages.isEmpty() && !dataHolder.getProjects().isEmpty()) { +// try { +// CompletableFuture branchesFuture = syncBranchesParallel( +// config, entity, restTemplate, dataHolder.getProjects().values(), dataHolder); +// boolean branchesSuccess = branchesFuture.get(10, TimeUnit.MINUTES); +// if (!branchesSuccess) { +// errorMessages.append("分支同步失败; "); +// } +// } catch (Exception e) { +// log.error("分支同步失败", e); +// errorMessages.append("分支同步异常: ").append(e.getMessage()).append("; "); +// } +// } +// +// // 5. 如果所有数据都同步成功,在新的事务中更新数据库 +// if (errorMessages.isEmpty()) { +// try { +// updateDatabaseWithTransaction(repositoryId, dataHolder); +// updateSyncHistory(history, RepositorySyncHistory.SyncStatus.SUCCESS, null); +// log.info("仓库数据同步完成, repositoryId: {}", repositoryId); +// } catch (Exception e) { +// log.error("数据库更新失败", e); +// errorMessages.append("数据库更新失败: ").append(e.getMessage()); +// updateSyncHistory(history, RepositorySyncHistory.SyncStatus.FAILED, errorMessages.toString()); +// } +// } else { +// updateSyncHistory(history, RepositorySyncHistory.SyncStatus.FAILED, errorMessages.toString()); +// } +// } catch (Exception e) { +// log.error("仓库数同步失败", e); +// updateSyncHistory(history, RepositorySyncHistory.SyncStatus.FAILED, e.getMessage()); +// } finally { +// // 无论成功失败,都移除运行标记 +// RUNNING_TASKS.remove(repositoryId); +// } +// }); +// +// return history.getId(); +// } +// +// // 使用编程式事务 +// private void updateDatabaseWithTransaction(Long repositoryId, RepositorySyncDataCache dataHolder) { +// TransactionTemplate transactionTemplate = new TransactionTemplate(transactionManager); +// transactionTemplate.execute(status -> { +// try { +// updateDatabase(repositoryId, dataHolder); +// return null; +// } catch (Exception e) { +// status.setRollbackOnly(); +// throw e; +// } +// }); +// } +// +// // 实际的数据库更新操作 +// private void updateDatabase(Long repositoryId, RepositorySyncDataCache dataHolder) { +// log.info("开始更新数据库..."); +// +// try { +// // 1. 删除旧数据 +// log.info("删除旧数据..."); +// branchRepository.deleteByRepositoryId(repositoryId); +// projectRepository.deleteByRepositoryId(repositoryId); +// groupRepository.deleteByRepositoryId(repositoryId); +// +// // 2. 保存新数据 +// log.info("保存新数据..."); +// if (!dataHolder.getGroups().isEmpty()) { +// groupRepository.saveAll(dataHolder.getGroups().values()); +// } +// if (!dataHolder.getProjects().isEmpty()) { +// projectRepository.saveAll(dataHolder.getProjects().values()); +// } +// if (!dataHolder.getBranches().isEmpty()) { +// branchRepository.saveAll(dataHolder.getBranches().values()); +// } +// +// log.info("数据库更新完成"); +// } catch (Exception e) { +// log.error("数据库更新失败", e); +// throw new ApiException("数据库更新失败: " + e.getMessage()); +// } +// } +// +// private CompletableFuture syncGroupsParallel( +// RepositoryConfig config, +// HttpEntity entity, +// RestTemplate restTemplate, +// RepositorySyncDataCache dataHolder) { +// +// String groupsUrl = String.format("%s/api/v4/groups?per_page=100", config.getUrl()); +// try { +// log.info("开始同步群组 - 仓库: {}", config.getName()); +// +// ResponseEntity>> response = restTemplate.exchange( +// groupsUrl, +// HttpMethod.GET, +// entity, +// new ParameterizedTypeReference<>() {} +// ); +// +// if (response.getBody() == null) { +// log.warn("未获取到群组数据 - 仓库: {}", config.getName()); +// return CompletableFuture.completedFuture(true); +// } +// +// for (Map group : response.getBody()) { +// try { +// RepositoryGroup repositoryGroup = createGroup(config.getId(), group); +// dataHolder.getGroups().put(repositoryGroup.getGroupId(), repositoryGroup); +// } catch (Exception e) { +// log.error("处理群组失败 - 仓库: {}, 群组: {}, 错误: {}", +// config.getName(), group.get("name"), e.getMessage()); +// } +// } +// +// log.info("群组同步完成 - 仓库: {}, 总数: {}", config.getName(), response.getBody().size()); +// return CompletableFuture.completedFuture(true); +// } catch (Exception e) { +// log.error("同步群组失败 - 仓库: {}, 错误: {}", config.getName(), e.getMessage()); +// return CompletableFuture.completedFuture(false); +// } +// } +// +// private CompletableFuture syncProjectsParallel( +// RepositoryConfig config, +// HttpEntity entity, +// RestTemplate restTemplate, +// Collection groups, +// RepositorySyncDataCache dataHolder) { +// +// try { +// log.info("开始同步项目 - 仓库: {}", config.getName()); +// +// for (RepositoryGroup group : groups) { +// try { +// String projectsUrl = String.format("%s/api/v4/groups/%s/projects?per_page=100", +// config.getUrl(), group.getGroupId()); +// +// ResponseEntity>> response = restTemplate.exchange( +// projectsUrl, +// HttpMethod.GET, +// entity, +// new ParameterizedTypeReference<>() {} +// ); +// +// if (response.getBody() == null) { +// continue; +// } +// +// for (Map project : response.getBody()) { +// try { +// RepositoryProject repositoryProject = createProject(config.getId(), project); +// dataHolder.getProjects().put(repositoryProject.getProjectId(), repositoryProject); +// } catch (Exception e) { +// log.error("处理项目失败 - 仓库: {}, 项目: {}, 错误: {}", +// config.getName(), project.get("name"), e.getMessage()); +// } +// } +// } catch (Exception e) { +// log.error("处理群组项目失败 - 仓库: {}, 群组: {}, 错误: {}", +// config.getName(), group.getName(), e.getMessage()); +// } +// } +// +// log.info("项目同步完成 - 仓库: {}", config.getName()); +// return CompletableFuture.completedFuture(true); +// } catch (Exception e) { +// log.error("同步项目失败 - 仓库: {}, 错误: {}", config.getName(), e.getMessage()); +// return CompletableFuture.completedFuture(false); +// } +// } +// +// private CompletableFuture syncBranchesParallel( +// RepositoryConfig config, +// HttpEntity entity, +// RestTemplate restTemplate, +// Collection projects, +// RepositorySyncDataCache dataHolder) { +// +// try { +// log.info("开始同步分支 - 仓库: {}", config.getName()); +// +// for (RepositoryProject project : projects) { +// try { +// String branchesUrl = String.format("%s/api/v4/projects/%s/repository/branches", +// config.getUrl(), project.getProjectId()); +// +// ResponseEntity>> response = restTemplate.exchange( +// branchesUrl, +// HttpMethod.GET, +// entity, +// new ParameterizedTypeReference<>() {} +// ); +// +// if (response.getBody() == null) { +// continue; +// } +// +// for (Map branch : response.getBody()) { +// try { +// RepositoryBranch repositoryBranch = createBranch( +// config.getId(), +// project.getProjectId().toString(), +// branch +// ); +// dataHolder.getBranches().put( +// project.getProjectId() + "-" + repositoryBranch.getName(), +// repositoryBranch +// ); +// } catch (Exception e) { +// log.error("处理分支失败 - 仓库: {}, 项目: {}, 分支: {}, 错误: {}", +// config.getName(), project.getName(), branch.get("name"), e.getMessage()); +// } +// } +// } catch (Exception e) { +// log.error("处理项目分支失败 - 仓库: {}, 项目: {}, 错误: {}", +// config.getName(), project.getName(), e.getMessage()); +// } +// } +// +// log.info("分支同步完成 - 仓库: {}", config.getName()); +// return CompletableFuture.completedFuture(true); +// } catch (Exception e) { +// log.error("同步分支失败 - 仓库: {}, 错误: {}", config.getName(), e.getMessage()); +// return CompletableFuture.completedFuture(false); +// } +// } +// +// @Override +// public RepositorySyncStatusDTO getSyncStatus(Long historyId) { +// RepositorySyncHistory history = syncHistoryRepository.findById(historyId) +// .orElseThrow(() -> new ApiException("同步历史记录不存在")); +// +// return RepositorySyncStatusDTO.builder() +// .status(history.getStatus()) +// .startTime(history.getStartTime()) +// .endTime(history.getEndTime()) +// .errorMessage(history.getErrorMessage()) +// .build(); +// } +// +// private void updateSyncHistory(RepositorySyncHistory history, +// RepositorySyncHistory.SyncStatus status, +// String errorMessage) { +// // 更新同步历史记录 +// history.setStatus(status); +// history.setEndTime(LocalDateTime.now()); +// history.setErrorMessage(errorMessage); +// syncHistoryRepository.save(history); +// +// // 更新仓库配置的最后同步状态 +// RepositoryConfig config = configRepository.findById(history.getRepositoryId()) +// .orElse(null); +// if (config != null) { +// config.setLastSyncTime(history.getEndTime()); +// config.setLastSyncStatus(status); +// configRepository.save(config); +// log.info("更新仓库同步状态 - 仓库: {}, 状态: {}, 时间: {}", +// config.getName(), status, history.getEndTime()); +// } +// } +// +// // 创建群组对象(不保存到数据库) +// private RepositoryGroup createGroup(Long repositoryId, Map group) { +// RepositoryGroup repositoryGroup = new RepositoryGroup(); +// repositoryGroup.setRepositoryId(repositoryId); +// repositoryGroup.setGroupId(Long.valueOf(group.get("id").toString())); +// repositoryGroup.setName((String) group.get("name")); +// repositoryGroup.setPath((String) group.get("path")); +// repositoryGroup.setDescription((String) group.get("description")); +// repositoryGroup.setVisibility((String) group.get("visibility")); +// repositoryGroup.setWebUrl((String) group.get("web_url")); +// return repositoryGroup; +// } +// +// // 创建项目对象(不保存到数据库) +// private RepositoryProject createProject(Long repositoryId, Map project) { +// RepositoryProject repositoryProject = new RepositoryProject(); +// repositoryProject.setRepositoryId(repositoryId); +// repositoryProject.setProjectId(Long.valueOf(project.get("id").toString())); +// repositoryProject.setName((String) project.get("name")); +// repositoryProject.setPath((String) project.get("path")); +// repositoryProject.setDescription((String) project.get("description")); +// repositoryProject.setVisibility((String) project.get("visibility")); +// repositoryProject.setDefaultBranch((String) project.get("default_branch")); +// repositoryProject.setWebUrl((String) project.get("web_url")); +// repositoryProject.setSshUrl((String) project.get("ssh_url_to_repo")); +// repositoryProject.setHttpUrl((String) project.get("http_url_to_repo")); +// return repositoryProject; +// } +// +// // 创建分支对象(不保存到数据库) +// private RepositoryBranch createBranch(Long repositoryId, String projectId, Map branch) { +// RepositoryBranch repositoryBranch = new RepositoryBranch(); +// repositoryBranch.setRepositoryId(repositoryId); +// repositoryBranch.setProjectId(Long.valueOf(projectId)); +// repositoryBranch.setName((String) branch.get("name")); +// +// Map commit = (Map) branch.get("commit"); +// if (commit != null) { +// repositoryBranch.setCommitId((String) commit.get("id")); +// repositoryBranch.setCommitMessage((String) commit.get("message")); +// repositoryBranch.setCommitAuthor((String) commit.get("author_name")); +// String commitDate = (String) commit.get("committed_date"); +// if (commitDate != null) { +// repositoryBranch.setCommitDate(LocalDateTime.parse(commitDate.substring(0, 19))); +// } +// } +// +// repositoryBranch.setProtected_((Boolean) branch.get("protected")); +// repositoryBranch.setDevelopersCanPush((Boolean) branch.get("developers_can_push")); +// repositoryBranch.setDevelopersCanMerge((Boolean) branch.get("developers_can_merge")); +// return repositoryBranch; +// } +// +// @Override +// public void syncAll(Long repositoryId) { +// // 为了保向后兼容,我们调用新的异步方法 +// asyncSyncAll(repositoryId); +// } +// +// @Override +// public void syncGroups(Long repositoryId) { +// // 实现群组同步 +// log.info("开始同步群组数据, repositoryId: {}", repositoryId); +// RepositoryConfig config = configRepository.findById(repositoryId) +// .orElseThrow(() -> new ApiException("仓库配置不存在")); +// +// RestTemplate restTemplate = createRestTemplate(); +// HttpEntity entity = createHttpEntity(config.getAccessToken()); +// RepositorySyncDataCache dataHolder = new RepositorySyncDataCache(); +// +// try { +// syncGroupsParallel(config, entity, restTemplate, dataHolder).get(10, TimeUnit.MINUTES); +// updateDatabase(repositoryId, dataHolder); +// } catch (Exception e) { +// log.error("群组同步失败", e); +// throw new ApiException("群组同步失败: " + e.getMessage()); +// } +// } +// +// @Override +// public void syncProjects(Long repositoryId, Long groupId) { +// // 实现项目同步 +// log.info("开始同步项目数据, repositoryId: {}, groupId: {}", repositoryId, groupId); +// RepositoryConfig config = configRepository.findById(repositoryId) +// .orElseThrow(() -> new ApiException("仓库配置不存在")); +// +// RepositoryGroup group = groupRepository.findByRepositoryIdAndGroupId(repositoryId, groupId) +// .orElseThrow(() -> new ApiException("群组不存在")); +// +// RestTemplate restTemplate = createRestTemplate(); +// HttpEntity entity = createHttpEntity(config.getAccessToken()); +// RepositorySyncDataCache dataHolder = new RepositorySyncDataCache(); +// +// try { +// List groups = Collections.singletonList(group); +// syncProjectsParallel(config, entity, restTemplate, groups, dataHolder).get(10, TimeUnit.MINUTES); +// updateDatabase(repositoryId, dataHolder); +// } catch (Exception e) { +// log.error("项目同步失败", e); +// throw new ApiException("项目同步失败: " + e.getMessage()); +// } +// } +// +// @Override +// public void syncBranches(Long repositoryId, Long projectId) { +// // 实现分支同步 +// log.info("开始同步分支数据, repositoryId: {}, projectId: {}", repositoryId, projectId); +// RepositoryConfig config = configRepository.findById(repositoryId) +// .orElseThrow(() -> new ApiException("仓库配置不存在")); +// +// RepositoryProject project = projectRepository.findByRepositoryIdAndProjectId(repositoryId, projectId) +// .orElseThrow(() -> new ApiException("项目不存在")); +// +// RestTemplate restTemplate = createRestTemplate(); +// HttpEntity entity = createHttpEntity(config.getAccessToken()); +// RepositorySyncDataCache dataHolder = new RepositorySyncDataCache(); +// +// try { +// List projects = Collections.singletonList(project); +// syncBranchesParallel(config, entity, restTemplate, projects, dataHolder).get(10, TimeUnit.MINUTES); +// updateDatabase(repositoryId, dataHolder); +// } catch (Exception e) { +// log.error("分支同步失败", e); +// throw new ApiException("分支同步失败: " + e.getMessage()); +// } +// } +// +// @Override +// public List getRunningSyncs() { +// // 查询最近的50条同步历史记录 +// List histories = syncHistoryRepository +// .findTop50ByOrderByStartTimeDesc(); +// +// return histories.stream() +// .map(history -> { +// RepositoryConfig config = configRepository +// .findById(history.getRepositoryId()) +// .orElse(null); +// +// // 检查任务是否真正在运行 +// boolean isActuallyRunning = RUNNING_TASKS.containsKey(history.getRepositoryId()); +// +// // 如果数据库状态为 RUNNING 但实际未运行,更新状态为 FAILED +// if (history.getStatus() == RepositorySyncHistory.SyncStatus.RUNNING +// && !isActuallyRunning) { +// history.setStatus(RepositorySyncHistory.SyncStatus.FAILED); +// history.setErrorMessage("同步任务意外终止"); +// history.setEndTime(LocalDateTime.now()); +// syncHistoryRepository.save(history); +// } +// +// return RunningSyncDTO.builder() +// .historyId(history.getId()) +// .repositoryId(history.getRepositoryId()) +// .repositoryName(config != null ? config.getName() : "未知仓库") +// .startTime(history.getStartTime()) +// .syncType(history.getSyncType().name()) +// .status(history.getStatus()) +// .endTime(history.getEndTime()) +// .errorMessage(history.getErrorMessage()) +// .isActuallyRunning(isActuallyRunning) // 添加实际运行状态 +// .build(); +// }) +// .collect(Collectors.toList()); +// } +// +// // ... 继续实现其他方法吗? +// +// /** +// * 仓库同步数据缓存,用于在同步过程中临时存储数据 +// */ +// @Getter +// private static class RepositorySyncDataCache { +// // 使用 ConcurrentHashMap 确保线程安全 +// private final Map groups = new ConcurrentHashMap<>(); +// private final Map projects = new ConcurrentHashMap<>(); +// private final Map branches = new ConcurrentHashMap<>(); +// } +//} \ No newline at end of file diff --git a/backend/src/main/java/com/qqchen/deploy/backend/service/impl/RoleServiceImpl.java b/backend/src/main/java/com/qqchen/deploy/backend/service/impl/RoleServiceImpl.java new file mode 100644 index 00000000..3dc9512c --- /dev/null +++ b/backend/src/main/java/com/qqchen/deploy/backend/service/impl/RoleServiceImpl.java @@ -0,0 +1,139 @@ +//package com.qqchen.deploy.backend.service.impl; +// +//import com.qqchen.application.query.RoleQuery; +//import com.qqchen.application.service.RoleService; +//import com.qqchen.common.exception.ApiException; +//import com.qqchen.common.service.impl.BaseServiceImpl; +//import com.qqchen.domain.entity.Role; +//import com.qqchen.domain.entity.RoleMenu; +//import com.qqchen.domain.repository.RoleMenuRepository; +//import com.qqchen.domain.repository.RoleRepository; +//import lombok.RequiredArgsConstructor; +//import lombok.extern.slf4j.Slf4j; +//import org.springframework.data.jpa.repository.JpaRepository; +//import org.springframework.stereotype.Service; +//import org.springframework.transaction.annotation.Transactional; +// +//import java.util.List; +//import java.util.stream.Collectors; +// +//@Slf4j +//@Service +//@RequiredArgsConstructor +//public class RoleServiceImpl extends BaseServiceImpl implements RoleService { +// +// private final RoleRepository roleRepository; +// private final RoleMenuRepository roleMenuRepository; +// +// @Override +// protected JpaRepository getRepository() { +// return roleRepository; +// } +// +// @Override +// public List getRoleMenuIds(Long roleId) { +// return roleMenuRepository.findByRoleId(roleId) +// .stream() +// .map(RoleMenu::getMenuId) +// .collect(Collectors.toList()); +// } +// +// @Override +// @Transactional +// public void updateRoleMenus(Long roleId, List menuIds) { +// // 验证角色是否存在 +// Role role = findById(roleId) +// .orElseThrow(() -> new ApiException("角色不存在")); +// +// // 不允许修改超级管理员的权限 +// if ("ROLE_ADMIN".equals(role.getCode())) { +// throw new ApiException("不能修改超级管理员的权限"); +// } +// +// // 删除原有权限 +// roleMenuRepository.deleteByRoleId(roleId); +// +// // 保存新的权限 +// if (menuIds != null && !menuIds.isEmpty()) { +// List roleMenus = menuIds.stream() +// .map(menuId -> { +// RoleMenu roleMenu = new RoleMenu(); +// roleMenu.setRoleId(roleId); +// roleMenu.setMenuId(menuId); +// return roleMenu; +// }) +// .collect(Collectors.toList()); +// roleMenuRepository.saveAll(roleMenus); +// } +// +// log.info("角色 {} 的权限已更新", role.getName()); +// } +// +// @Override +// @Transactional +// public void delete(Long id) { +// Role role = findById(id) +// .orElseThrow(() -> new ApiException("角色不存在")); +// +// // 不允许删除超级管理员角色 +// if ("ROLE_ADMIN".equals(role.getCode())) { +// throw new ApiException("不能删除超级管理员角色"); +// } +// +// // 执行软删除 +// role.setDeleted(true); +// roleRepository.save(role); +// log.info("角色 {} 已删除", role.getName()); +// } +// +// @Override +// @Transactional +// public Role save(Role entity) { +// validateCode(entity.getCode()); +// validateName(entity.getName()); +// entity.setDeleted(false); +// return super.save(entity); +// } +// +// @Override +// @Transactional +// public Role update(Long id, Role entity) { +// Role existing = findById(id) +// .orElseThrow(() -> new ApiException("角色不存在")); +// +// // 不允许修改超级管理员角色的编码 +// if ("ROLE_ADMIN".equals(existing.getCode()) && !existing.getCode().equals(entity.getCode())) { +// throw new ApiException("不能修改超级管理员角色的编码"); +// } +// +// if (!existing.getCode().equals(entity.getCode())) { +// validateCode(entity.getCode()); +// } +// if (!existing.getName().equals(entity.getName())) { +// validateName(entity.getName()); +// } +// +// entity.setVersion(existing.getVersion()); +// entity.setDeleted(existing.getDeleted()); +// return super.update(id, entity); +// } +// +// @Override +// public void validateCode(String code) { +// if (roleRepository.existsByCodeAndDeletedFalse(code)) { +// throw new ApiException("角色编码已存在"); +// } +// } +// +// @Override +// public void validateName(String name) { +// if (roleRepository.existsByNameAndDeletedFalse(name)) { +// throw new ApiException("角色名称已存在"); +// } +// } +// +// @Override +// public List findAll() { +// return roleRepository.findByDeletedFalseOrderBySort(); +// } +//} \ No newline at end of file diff --git a/backend/src/main/java/com/qqchen/deploy/backend/service/impl/TenantServiceImpl.java b/backend/src/main/java/com/qqchen/deploy/backend/service/impl/TenantServiceImpl.java new file mode 100644 index 00000000..9b213e19 --- /dev/null +++ b/backend/src/main/java/com/qqchen/deploy/backend/service/impl/TenantServiceImpl.java @@ -0,0 +1,113 @@ +//package com.qqchen.deploy.backend.service.impl; +// +//import com.qqchen.deploy.backend.common.service.impl.BaseServiceImpl; +//import com.qqchen.deploy.backend.entity.Tenant; +//import com.qqchen.deploy.backend.repository.ITenantRepository; +//import com.qqchen.deploy.backend.service.ITenantService; +//import com.querydsl.core.BooleanBuilder; +//import jakarta.transaction.Transactional; +//import org.springframework.stereotype.Service; +//import org.springframework.util.StringUtils; +// +//import java.util.List; +// +//@Service +//@Transactional +//public class TenantServiceImpl extends BaseServiceImpl implements ITenantService { +// +// private final ITenantRepository tenantRepository; +// +// public TenantServiceImpl(ITenantRepository tenantRepository) { +// super(tenantRepository); +// this.tenantRepository = tenantRepository; +// } +// +// @Override +// public List findAll(TenantQuery query) { +// QTenant tenant = QTenant.tenant; +// BooleanBuilder builder = new BooleanBuilder(); +// +// // 基础条件:未删除 +// builder.and(tenant.deleted.eq(false)); +// +// // 动态条件 +// if (query != null) { +// // 名称模糊查询 +// if (StringUtils.hasText(query.getName())) { +// builder.and(tenant.name.like("%" + query.getName().trim() + "%")); +// } +// +// // 编码模糊查询 +// if (StringUtils.hasText(query.getCode())) { +// builder.and(tenant.code.like("%" + query.getCode().trim() + "%")); +// } +// +// // 状态精确查询 +// if (query.getEnabled() != null) { +// builder.and(tenant.enabled.eq(query.getEnabled())); +// } +// } +// +// // 按ID排序 +// return (List) tenantRepository.findAll(builder, tenant.id.asc()); +// } +// +// @Override +// public List findAllEnabled() { +// QTenant tenant = QTenant.tenant; +// return (List) tenantRepository.findAll( +// tenant.deleted.eq(false) +// .and(tenant.enabled.eq(true)), +// tenant.id.asc() +// ); +// } +// +// @Override +// public Tenant save(Tenant entity) { +// validateCode(entity.getCode()); +// validateName(entity.getName()); +// entity.setDeleted(false); +// return super.save(entity); +// } +// +// @Override +// public Tenant update(Long id, Tenant entity) { +// Tenant existing = findById(id) +// .orElseThrow(() -> new ApiException("租户不存在")); +// +// if (!existing.getCode().equals(entity.getCode())) { +// validateCode(entity.getCode()); +// } +// if (!existing.getName().equals(entity.getName())) { +// validateName(entity.getName()); +// } +// +// entity.setVersion(existing.getVersion()); +// entity.setDeleted(existing.getDeleted()); +// return super.update(id, entity); +// } +// +// @Override +// public void validateCode(String code) { +// if (tenantRepository.existsByCodeAndDeletedFalse(code)) { +// throw new ApiException("租户编码已存在"); +// } +// } +// +// @Override +// public void validateName(String name) { +// if (tenantRepository.existsByNameAndDeletedFalse(name)) { +// throw new ApiException("租户名称已存在"); +// } +// } +// +// @Override +// public boolean existsByCode(String code) { +// return tenantRepository.existsByCodeAndDeletedFalse(code); +// } +// +// @Override +// public boolean existsByName(String name) { +// return tenantRepository.existsByNameAndDeletedFalse(name); +// } +//} \ No newline at end of file diff --git a/backend/src/main/java/com/qqchen/deploy/backend/service/impl/UserDetailsServiceImpl.java b/backend/src/main/java/com/qqchen/deploy/backend/service/impl/UserDetailsServiceImpl.java new file mode 100644 index 00000000..6bd2927c --- /dev/null +++ b/backend/src/main/java/com/qqchen/deploy/backend/service/impl/UserDetailsServiceImpl.java @@ -0,0 +1,37 @@ +package com.qqchen.deploy.backend.service.impl; + +import com.qqchen.deploy.backend.entity.User; +import com.qqchen.deploy.backend.repository.IUserRepository; +import jakarta.annotation.Resource; +import lombok.RequiredArgsConstructor; +import org.springframework.security.core.authority.SimpleGrantedAuthority; +import org.springframework.security.core.userdetails.UserDetails; +import org.springframework.security.core.userdetails.UserDetailsService; +import org.springframework.security.core.userdetails.UsernameNotFoundException; +import org.springframework.stereotype.Service; + +import java.util.Collections; + +@Service +@RequiredArgsConstructor +public class UserDetailsServiceImpl implements UserDetailsService { + + @Resource + private final IUserRepository userRepository; + + @Override + public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { + User user = userRepository.findByUsernameAndDeletedFalse(username) + .orElseThrow(() -> new UsernameNotFoundException("用户不存在")); + + return new org.springframework.security.core.userdetails.User( + user.getUsername(), + user.getPassword(), + user.getEnabled(), + true, + true, + true, + Collections.singletonList(new SimpleGrantedAuthority("ROLE_USER")) + ); + } +} \ No newline at end of file diff --git a/backend/src/main/java/com/qqchen/deploy/backend/service/impl/UserServiceImpl.java b/backend/src/main/java/com/qqchen/deploy/backend/service/impl/UserServiceImpl.java index 88f54320..3d679878 100644 --- a/backend/src/main/java/com/qqchen/deploy/backend/service/impl/UserServiceImpl.java +++ b/backend/src/main/java/com/qqchen/deploy/backend/service/impl/UserServiceImpl.java @@ -2,20 +2,20 @@ package com.qqchen.deploy.backend.service.impl; import com.qqchen.deploy.backend.common.service.impl.BaseServiceImpl; import com.qqchen.deploy.backend.entity.User; -import com.qqchen.deploy.backend.repository.UserRepository; -import com.qqchen.deploy.backend.service.UserService; +import com.qqchen.deploy.backend.repository.IUserRepository; +import com.qqchen.deploy.backend.service.IUserService; import jakarta.transaction.Transactional; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; @Service @Transactional -public class UserServiceImpl extends BaseServiceImpl implements UserService { - - private final UserRepository userRepository; +public class UserServiceImpl extends BaseServiceImpl implements IUserService { + + private final IUserRepository userRepository; @Autowired - public UserServiceImpl(UserRepository userRepository) { + public UserServiceImpl(IUserRepository userRepository) { super(userRepository); this.userRepository = userRepository; } diff --git a/backend/src/main/resources/application.yml b/backend/src/main/resources/application.yml index 072579c8..7df400bd 100644 --- a/backend/src/main/resources/application.yml +++ b/backend/src/main/resources/application.yml @@ -3,9 +3,9 @@ server: spring: datasource: - url: jdbc:mysql://192.168.1.111:3306/deploy-ease-platform?useSSL=false&serverTimezone=UTC&allowPublicKeyRetrieval=true - username: deploy-ease-platform - password: qichen5210523 + url: jdbc:mysql://127.0.0.1:3306/2024_11_24_platform?useSSL=false&serverTimezone=UTC&allowPublicKeyRetrieval=true + username: root + password: root driver-class-name: com.mysql.cj.jdbc.Driver jpa: hibernate: