可直接运行,修改了实体类的关系。

This commit is contained in:
dengqichen 2024-11-27 15:06:43 +08:00
parent 0537a5d8e4
commit 94aeb16a61
62 changed files with 2964 additions and 144 deletions

127
backend/README.md Normal file
View File

@ -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<User, Long> 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<UserRole> 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);
```

View File

@ -18,12 +18,26 @@
<java.version>21</java.version> <java.version>21</java.version>
<maven.compiler.source>21</maven.compiler.source> <maven.compiler.source>21</maven.compiler.source>
<maven.compiler.target>21</maven.compiler.target> <maven.compiler.target>21</maven.compiler.target>
<spring-cloud.version>2023.0.0</spring-cloud.version>
<mapstruct.version>1.5.5.Final</mapstruct.version> <mapstruct.version>1.5.5.Final</mapstruct.version>
<querydsl.version>5.0.0</querydsl.version> <querydsl.version>5.0.0</querydsl.version>
<lombok.version>1.18.30</lombok.version> <lombok.version>1.18.30</lombok.version>
<spring-security.version>6.2.0</spring-security.version> <spring-security.version>6.2.0</spring-security.version>
<jjwt.version>0.12.3</jjwt.version>
</properties> </properties>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring-cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies> <dependencies>
<!-- Spring Boot --> <!-- Spring Boot -->
<dependency> <dependency>
@ -101,6 +115,31 @@
<artifactId>spring-security-test</artifactId> <artifactId>spring-security-test</artifactId>
<scope>test</scope> <scope>test</scope>
</dependency> </dependency>
<!-- OpenFeign -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
<!-- JWT -->
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-api</artifactId>
<version>${jjwt.version}</version>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-impl</artifactId>
<version>${jjwt.version}</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-jackson</artifactId>
<version>${jjwt.version}</version>
<scope>runtime</scope>
</dependency>
</dependencies> </dependencies>
<build> <build>

View File

@ -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"
```

View File

@ -2,9 +2,11 @@ package com.qqchen.deploy.backend;
import org.springframework.boot.SpringApplication; import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.openfeign.EnableFeignClients;
import org.springframework.data.jpa.repository.config.EnableJpaRepositories; import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
@SpringBootApplication @SpringBootApplication
@EnableFeignClients
public class BackendApplication { public class BackendApplication {
public static void main(String[] args) { public static void main(String[] args) {

View File

@ -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.request.UserRequest;
import com.qqchen.deploy.backend.dto.response.UserResponse; import com.qqchen.deploy.backend.dto.response.UserResponse;
import com.qqchen.deploy.backend.dto.request.UserRegisterRequest; 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.data.domain.Page;
import org.springframework.validation.annotation.Validated; import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.GetMapping;
@ -22,11 +22,11 @@ import org.springframework.web.bind.annotation.RestController;
@RequestMapping("/api/v1/users") @RequestMapping("/api/v1/users")
public class UserApiController extends BaseController<User, Long, UserQuery, UserRequest, UserResponse> { public class UserApiController extends BaseController<User, Long, UserQuery, UserRequest, UserResponse> {
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.userService = userService;
this.converter = converter;
} }
@PostMapping("/register") @PostMapping("/register")

View File

@ -10,9 +10,8 @@ import org.springframework.security.core.context.SecurityContextHolder;
import java.util.Optional; import java.util.Optional;
@Configuration @Configuration
@EnableJpaAuditing @EnableJpaAuditing(auditorAwareRef = "auditorProvider")
public class JpaConfig { public class JpaAuditingConfig {
@Bean @Bean
public AuditorAware<String> auditorProvider() { public AuditorAware<String> auditorProvider() {
return () -> { return () -> {

View File

@ -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.domain.Entity;
import com.qqchen.deploy.backend.common.converter.BaseConverter; 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.query.BaseQuery;
import com.qqchen.deploy.backend.common.dto.BaseRequest; import com.qqchen.deploy.backend.common.dto.BaseRequest;
import com.qqchen.deploy.backend.common.api.Response; import com.qqchen.deploy.backend.common.api.Response;
import com.qqchen.deploy.backend.common.service.BaseService; import com.qqchen.deploy.backend.common.service.IBaseService;
import jakarta.annotation.Resource;
import org.springframework.data.domain.Page; import org.springframework.data.domain.Page;
import org.springframework.web.bind.annotation.*; import org.springframework.web.bind.annotation.*;
@ -16,23 +16,25 @@ import java.util.List;
/** /**
* 通用REST控制器 * 通用REST控制器
*/ */
public abstract class BaseController<T extends Entity<ID>, ID extends Serializable, Q extends BaseQuery, R extends BaseRequest, V> { public abstract class BaseController<T extends Entity<ID>, ID extends Serializable, Q extends BaseQuery, REQ extends BaseRequest, RESP extends BaseResponse> {
@Resource protected final IBaseService<T, ID> service;
protected BaseService<T, ID> service; protected final BaseConverter<T, REQ, RESP> converter;
@Resource protected BaseController(IBaseService<T, ID> service, BaseConverter<T, REQ, RESP> converter) {
protected BaseConverter<T, R, V> converter; this.service = service;
this.converter = converter;
}
@PostMapping @PostMapping
public Response<V> create(@RequestBody R request) { public Response<RESP> create(@RequestBody REQ request) {
T entity = converter.toEntity(request); T entity = converter.toEntity(request);
T savedEntity = service.create(entity); T savedEntity = service.create(entity);
return Response.success(converter.toResponse(savedEntity)); return Response.success(converter.toResponse(savedEntity));
} }
@PutMapping("/{id}") @PutMapping("/{id}")
public Response<V> update(@PathVariable ID id, @RequestBody R request) { public Response<RESP> update(@PathVariable ID id, @RequestBody REQ request) {
T entity = service.findById(id); T entity = service.findById(id);
converter.updateEntity(entity, request); converter.updateEntity(entity, request);
T updatedEntity = service.update(entity); T updatedEntity = service.update(entity);
@ -46,20 +48,26 @@ public abstract class BaseController<T extends Entity<ID>, ID extends Serializab
} }
@GetMapping("/{id}") @GetMapping("/{id}")
public Response<V> findById(@PathVariable ID id) { public Response<RESP> findById(@PathVariable ID id) {
T entity = service.findById(id); T entity = service.findById(id);
return Response.success(converter.toResponse(entity)); return Response.success(converter.toResponse(entity));
} }
@GetMapping @GetMapping
public Response<List<V>> findAll() { public Response<List<RESP>> findAll() {
List<T> entities = service.findAll(); List<T> entities = service.findAll();
return Response.success(converter.toResponseList(entities)); return Response.success(converter.toResponseList(entities));
} }
@GetMapping("/page") @GetMapping("/page")
public Response<Page<V>> page(Q query) { public Response<Page<RESP>> page(Q query) {
Page<T> page = service.page(query); Page<T> page = service.page(query);
return Response.success(page.map(entity -> converter.toResponse(entity))); return Response.success(page.map(entity -> converter.toResponse(entity)));
} }
@GetMapping("/list")
public Response<List<RESP>> findAll(Q query) {
List<T> entities = service.findAll(query);
return Response.success(converter.toResponseList(entities));
}
} }

View File

@ -0,0 +1,5 @@
package com.qqchen.deploy.backend.common.integration.dto;
public interface ThirdPartyDTO {
// 标记接口表明这是第三方系统的数据结构
}

View File

@ -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<T extends Entity<ID>, ID extends Serializable> extends JpaRepository<T, ID>, QuerydslPredicateExecutor<T> {
@Override
@Query("select e from #{#entityName} e where e.deleted = false")
List<T> findAll();
@Override
@Query("select e from #{#entityName} e where e.id = ?1 and e.deleted = false")
Optional<T> 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);
}
}

View File

@ -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<T extends Entity<ID>, ID extends Serializable>
extends JpaRepository<T, ID>, QuerydslPredicateExecutor<T> {
// extends JpaRepository<T, ID>, QuerydslPredicateExecutor<T>, JpaSpecificationExecutor<T> {
@Override
@Query("select e from #{#entityName} e where e.deleted = false")
List<T> findAll();
@Override
@Query("select e from #{#entityName} e where e.id = ?1 and e.deleted = false")
Optional<T> 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<ID> ids);
// @Modifying
// @Query("update #{#entityName} e set e.enabled = ?2 where e.id in ?1")
// void updateEnabledByIds(Collection<ID> ids, boolean enabled);
// 快速查询方法
Optional<T> findByIdAndDeletedFalse(ID id);
List<T> findByIdInAndDeletedFalse(Collection<ID> 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<T> findByCreateTimeBetween(LocalDateTime start, LocalDateTime end);
@Query("select e from #{#entityName} e where e.deleted = false and " +
"(?1 is null or e.createBy = ?1)")
List<T> 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<T> findLatest(Pageable pageable);
@Query("select distinct e.createBy from #{#entityName} e where e.deleted = false")
List<String> findAllCreators();
// 批量更新
@Modifying
@Query("update #{#entityName} e set e.updateBy = ?2, e.updateTime = ?3 where e.id in ?1")
void updateAuditInfo(Collection<ID> ids, String updateBy, LocalDateTime updateTime);
/**
* 根据条件查询并排序
*/
default List<T> findAllByCondition(com.querydsl.core.types.Predicate predicate, Sort sort) {
Iterable<T> iterable = findAll(predicate, sort);
List<T> result = new ArrayList<>();
iterable.forEach(result::add);
return result;
}
// 批量保存并返回保存的实体
default List<T> saveAllAndReturn(Iterable<T> entities) {
return saveAll(entities);
}
/**
* 根据条件查询并排序
* 使用QueryDSL的Predicate进行条件查询
*/
@Override
Iterable<T> findAll(com.querydsl.core.types.Predicate predicate);
@Override
Iterable<T> findAll(com.querydsl.core.types.Predicate predicate, Sort sort);
/**
* 提供一个默认的转换方法
*/
default List<T> findAllAndConvert(com.querydsl.core.types.Predicate predicate, Sort sort) {
Iterable<T> iterable = findAll(predicate, sort);
List<T> result = new ArrayList<>();
iterable.forEach(result::add);
return result;
}
}

View File

@ -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.Bean;
import org.springframework.context.annotation.Configuration; 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.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.http.SessionCreationPolicy; import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.core.userdetails.User; import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService; 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.provisioning.InMemoryUserDetailsManager;
import org.springframework.security.web.SecurityFilterChain; import org.springframework.security.web.SecurityFilterChain;
import org.springframework.web.cors.CorsConfiguration; import org.springframework.web.cors.CorsConfiguration;
@ -69,4 +73,14 @@ public class SecurityConfig {
source.registerCorsConfiguration("/**", configuration); source.registerCorsConfiguration("/**", configuration);
return source; return source;
} }
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
@Bean
public AuthenticationManager authenticationManager(AuthenticationConfiguration config) throws Exception {
return config.getAuthenticationManager();
}
} }

View File

@ -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);
}
}

View File

@ -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;
}
}
}

View File

@ -10,7 +10,7 @@ import java.util.List;
/** /**
* 通用服务接口 * 通用服务接口
*/ */
public interface BaseService<T extends Entity<ID>, ID extends Serializable> { public interface IBaseService<T extends Entity<ID>, ID extends Serializable> {
T create(T entity); T create(T entity);
T update(T entity); T update(T entity);
@ -21,5 +21,7 @@ public interface BaseService<T extends Entity<ID>, ID extends Serializable> {
List<T> findAll(); List<T> findAll();
List<T> findAll(BaseQuery query);
Page<T> page(BaseQuery query); Page<T> page(BaseQuery query);
} }

View File

@ -2,6 +2,7 @@ package com.qqchen.deploy.backend.common.service.impl;
import java.math.BigDecimal; import java.math.BigDecimal;
import java.time.LocalDateTime; import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.List; 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.BaseQuery;
import com.qqchen.deploy.backend.common.query.DateRange; import com.qqchen.deploy.backend.common.query.DateRange;
import com.qqchen.deploy.backend.common.query.Range; 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.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.qqchen.deploy.backend.common.utils.EntityPathResolver;
import com.querydsl.core.BooleanBuilder; import com.querydsl.core.BooleanBuilder;
import com.querydsl.core.types.EntityPath; import com.querydsl.core.types.EntityPath;
@ -29,17 +30,17 @@ import org.springframework.util.StringUtils;
import java.io.Serializable; import java.io.Serializable;
import java.lang.reflect.Field; import java.lang.reflect.Field;
import java.util.Date; import java.util.stream.Collectors;
import java.util.stream.StreamSupport;
@Transactional @Transactional
public abstract class BaseServiceImpl<T extends Entity<ID>, ID extends Serializable> public abstract class BaseServiceImpl<T extends Entity<ID>, ID extends Serializable> implements IBaseService<T, ID> {
implements BaseService<T, ID> {
protected final BaseRepository<T, ID> repository; protected final IBaseRepository<T, ID> repository;
protected final EntityPath<T> entityPath; protected final EntityPath<T> entityPath;
protected BaseServiceImpl(BaseRepository<T, ID> repository) { protected BaseServiceImpl(IBaseRepository<T, ID> repository) {
this.repository = repository; this.repository = repository;
this.entityPath = getEntityPath(); this.entityPath = getEntityPath();
} }
@ -65,7 +66,6 @@ public abstract class BaseServiceImpl<T extends Entity<ID>, ID extends Serializa
@Override @Override
public Page<T> page(BaseQuery query) { public Page<T> page(BaseQuery query) {
BooleanBuilder builder = new BooleanBuilder().and(Expressions.asBoolean(true).isTrue()); BooleanBuilder builder = new BooleanBuilder().and(Expressions.asBoolean(true).isTrue());
if (query != null) { if (query != null) {
buildQueryPredicate(query, builder); buildQueryPredicate(query, builder);
} }
@ -74,7 +74,6 @@ public abstract class BaseServiceImpl<T extends Entity<ID>, ID extends Serializa
if (genericTypes != null && genericTypes.length > 0) { if (genericTypes != null && genericTypes.length > 0) {
Class<T> entityClass = (Class<T>) genericTypes[0]; Class<T> entityClass = (Class<T>) genericTypes[0];
LogicDelete softDelete = entityClass.getAnnotation(LogicDelete.class); LogicDelete softDelete = entityClass.getAnnotation(LogicDelete.class);
if (softDelete != null && softDelete.value()) { if (softDelete != null && softDelete.value()) {
Path<?> deletedPath = EntityPathResolver.getPath(entityPath, "deleted"); Path<?> deletedPath = EntityPathResolver.getPath(entityPath, "deleted");
if (deletedPath instanceof BooleanPath) { if (deletedPath instanceof BooleanPath) {
@ -83,7 +82,6 @@ public abstract class BaseServiceImpl<T extends Entity<ID>, ID extends Serializa
} }
} }
} }
return repository.findAll(builder, createPageRequest(query)); return repository.findAll(builder, createPageRequest(query));
} }
@ -284,4 +282,40 @@ public abstract class BaseServiceImpl<T extends Entity<ID>, ID extends Serializa
public List<T> findAll() { public List<T> findAll() {
return repository.findAll(); return repository.findAll();
} }
@Override
public List<T> 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<T> entityClass = (Class<T>) 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<T> iterable = repository.findAll(builder, sort);
List<T> result = new ArrayList<>();
iterable.forEach(result::add);
return result;
}
} }

View File

@ -3,7 +3,7 @@ package com.qqchen.deploy.backend.controller;
import com.qqchen.deploy.backend.api.UserApiController; import com.qqchen.deploy.backend.api.UserApiController;
import com.qqchen.deploy.backend.common.api.Response; import com.qqchen.deploy.backend.common.api.Response;
import com.qqchen.deploy.backend.converter.UserConverter; 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.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RequestParam;
@ -13,8 +13,11 @@ import org.springframework.web.bind.annotation.RestController;
@RequestMapping("/mgmt/users") @RequestMapping("/mgmt/users")
public class UserController extends UserApiController { public class UserController extends UserApiController {
public UserController(UserService userService, UserConverter converter) { protected final IUserService userService;
public UserController(IUserService userService, UserConverter converter) {
super(userService, converter); super(userService, converter);
this.userService = userService;
} }
@GetMapping("/check-username") @GetMapping("/check-username")

View File

@ -1,16 +1,25 @@
package com.qqchen.deploy.backend.entity; 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.annotation.LogicDelete;
import com.qqchen.deploy.backend.common.domain.Entity; import com.qqchen.deploy.backend.common.domain.Entity;
import jakarta.persistence.CascadeType;
import jakarta.persistence.Column; 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.Table;
import jakarta.persistence.Transient; import jakarta.persistence.Transient;
import jakarta.validation.constraints.NotBlank; import jakarta.validation.constraints.NotBlank;
import lombok.Data; import lombok.Data;
import lombok.EqualsAndHashCode; import lombok.EqualsAndHashCode;
import lombok.ToString;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.HashSet;
import java.util.List; import java.util.List;
import java.util.Set;
@Data @Data
@EqualsAndHashCode(callSuper = true) @EqualsAndHashCode(callSuper = true)
@ -46,4 +55,10 @@ public class Department extends Entity<Long> {
@Transient // 不映射到数据库 @Transient // 不映射到数据库
private List<Department> children = new ArrayList<>(); private List<Department> children = new ArrayList<>();
// 修改关联关系通过UserDepartment关联
@OneToMany(mappedBy = "department")
@JsonIgnore
@ToString.Exclude
private Set<UserDepartment> userDepartments = new HashSet<>();
} }

View File

@ -7,6 +7,7 @@ import jakarta.persistence.Table;
import lombok.Data; import lombok.Data;
import lombok.EqualsAndHashCode; import lombok.EqualsAndHashCode;
import java.util.List;
@Data @Data
@ -40,4 +41,6 @@ public class Menu extends Entity<Long> {
@Column(nullable = false) @Column(nullable = false)
private Boolean enabled = true; private Boolean enabled = true;
// List<RoleMenu> roleMenu;
} }

View File

@ -1,5 +1,6 @@
package com.qqchen.deploy.backend.entity; 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.annotation.LogicDelete;
import com.qqchen.deploy.backend.common.domain.Entity; import com.qqchen.deploy.backend.common.domain.Entity;
import jakarta.persistence.CascadeType; import jakarta.persistence.CascadeType;
@ -9,8 +10,11 @@ import jakarta.persistence.Table;
import jakarta.validation.constraints.NotBlank; import jakarta.validation.constraints.NotBlank;
import lombok.Data; import lombok.Data;
import lombok.EqualsAndHashCode; import lombok.EqualsAndHashCode;
import lombok.ToString;
import java.util.HashSet;
import java.util.List; import java.util.List;
import java.util.Set;
@Data @Data
@ -33,6 +37,8 @@ public class Role extends Entity<Long> {
@Column(nullable = false) @Column(nullable = false)
private Integer sort = 0; private Integer sort = 0;
@OneToMany(mappedBy = "role", cascade = CascadeType.ALL) // 指向 UserRole role 属性 @OneToMany(mappedBy = "role", cascade = CascadeType.ALL)
private List<UserRole> userRoles; @JsonIgnore
@ToString.Exclude
private Set<UserRole> userRoles = new HashSet<>();
} }

View File

@ -1,5 +1,6 @@
package com.qqchen.deploy.backend.entity; 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.annotation.LogicDelete;
import com.qqchen.deploy.backend.common.domain.AggregateRoot; import com.qqchen.deploy.backend.common.domain.AggregateRoot;
import com.qqchen.deploy.backend.common.domain.Entity; 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.CascadeType;
import jakarta.persistence.Column; import jakarta.persistence.Column;
import jakarta.persistence.OneToMany; import jakarta.persistence.OneToMany;
import jakarta.persistence.OneToOne;
import jakarta.persistence.Table; import jakarta.persistence.Table;
import lombok.Data; import lombok.Data;
import lombok.EqualsAndHashCode; import lombok.EqualsAndHashCode;
import lombok.ToString;
import java.util.HashSet; import java.util.HashSet;
import java.util.List; import java.util.List;
@ -23,32 +26,35 @@ import java.util.stream.Collectors;
@Table(name = "sys_user") @Table(name = "sys_user")
@LogicDelete @LogicDelete
public class User extends Entity<Long> { public class User extends Entity<Long> {
@Column(unique = true, nullable = false) @Column(unique = true, nullable = false)
private String username; private String username;
@Column(nullable = false) @Column(nullable = false)
private String password; private String password;
@Column(length = 50) @Column(length = 50)
private String nickname; private String nickname;
private String email; private String email;
private String phone; private String phone;
@Column(nullable = false) @Column(nullable = false)
private Boolean enabled = true; private Boolean enabled = true;
@Column(name = "dept_id") @Column(name = "dept_id")
private Long deptId; private Long deptId;
@Column(name = "dept_name") @OneToMany(mappedBy = "user", cascade = CascadeType.ALL, orphanRemoval = true)
private String deptName; @JsonIgnore
@ToString.Exclude
private Set<UserRole> userRoles = new HashSet<>();
@OneToMany(mappedBy = "user", cascade = CascadeType.ALL, orphanRemoval = true)
@OneToMany(mappedBy = "user", cascade = CascadeType.ALL) // 指向 UserRole user 属性 @JsonIgnore
private Set<UserRole> userRoles; @ToString.Exclude
private Set<UserDepartment> userDepartments = new HashSet<>();
// public void addRole(Role role) { // public void addRole(Role role) {
// UserRole userRole = new UserRole(this, role); // UserRole userRole = new UserRole(this, role);

View File

@ -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<Long> {
@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;
}
}

View File

@ -1,5 +1,6 @@
package com.qqchen.deploy.backend.entity; 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.annotation.LogicDelete;
import com.qqchen.deploy.backend.common.domain.Entity; import com.qqchen.deploy.backend.common.domain.Entity;
import jakarta.persistence.Column; import jakarta.persistence.Column;
@ -11,6 +12,7 @@ import jakarta.persistence.OneToOne;
import jakarta.persistence.Table; import jakarta.persistence.Table;
import lombok.Data; import lombok.Data;
import lombok.EqualsAndHashCode; import lombok.EqualsAndHashCode;
import lombok.ToString;
@Data @Data
@EqualsAndHashCode(callSuper = true) @EqualsAndHashCode(callSuper = true)
@ -19,12 +21,16 @@ import lombok.EqualsAndHashCode;
@LogicDelete @LogicDelete
public class UserRole extends Entity<Long> { public class UserRole extends Entity<Long> {
@ManyToOne(fetch = FetchType.LAZY) // 添加懒加载 @ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "user_id") @JoinColumn(name = "user_id")
@JsonIgnore
@ToString.Exclude
private User user; private User user;
@ManyToOne(fetch = FetchType.LAZY) // 添加懒加载 @ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "role_id") @JoinColumn(name = "role_id")
@JsonIgnore
@ToString.Exclude
private Role role; private Role role;
// 添加构造方法 // 添加构造方法
@ -50,4 +56,6 @@ public class UserRole extends Entity<Long> {
public int hashCode() { public int hashCode() {
return getClass().hashCode(); return getClass().hashCode();
} }
} }

View File

@ -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);
// };
// }
}

View File

@ -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<String, Object> testConnection(
@RequestHeader("Authorization") String authorization
);
@PostMapping("/job/{jobName}/build")
void triggerBuild(
@RequestHeader("Authorization") String authorization,
@PathVariable("jobName") String jobName
);
}

View File

@ -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());
}
}

View File

@ -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
}
}

View File

@ -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;
}

View File

@ -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;
}

View File

@ -0,0 +1,5 @@
package com.qqchen.deploy.backend.integration.enums;
public enum JenkinsBuildStatus {
SUCCESS, FAILURE, IN_PROGRESS, CANCELLED
}

View File

@ -1,15 +1,14 @@
package com.qqchen.deploy.backend.repository; 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 com.qqchen.deploy.backend.entity.Department;
import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
import org.springframework.data.jpa.repository.Query; import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param; import org.springframework.data.repository.query.Param;
import org.springframework.stereotype.Repository; import org.springframework.stereotype.Repository;
import java.util.List; import java.util.List;
@Repository @Repository
public interface DepartmentRepository extends BaseRepository<Department, Long> { public interface IDepartmentRepository extends IBaseRepository<Department, Long> {
List<Department> findByParentIdAndDeletedFalseOrderBySort(Long parentId); List<Department> findByParentIdAndDeletedFalseOrderBySort(Long parentId);

View File

@ -1,13 +1,13 @@
package com.qqchen.deploy.backend.repository; 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 com.qqchen.deploy.backend.entity.JenkinsBuild;
import org.springframework.stereotype.Repository; import org.springframework.stereotype.Repository;
import java.util.List; import java.util.List;
@Repository @Repository
public interface JenkinsBuildRepository extends BaseRepository<JenkinsBuild, Long> { public interface IJenkinsBuildRepository extends IBaseRepository<JenkinsBuild, Long> {
List<JenkinsBuild> findByJobIdAndDeletedFalse(Long jobId); List<JenkinsBuild> findByJobIdAndDeletedFalse(Long jobId);
List<JenkinsBuild> findByJobIdAndBuildNumberAndDeletedFalse(Long jobId, Integer buildNumber); List<JenkinsBuild> findByJobIdAndBuildNumberAndDeletedFalse(Long jobId, Integer buildNumber);
void deleteByJobIdAndDeletedFalse(Long jobId); void deleteByJobIdAndDeletedFalse(Long jobId);

View File

@ -1,13 +1,13 @@
package com.qqchen.deploy.backend.repository; 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 com.qqchen.deploy.backend.entity.JenkinsConfig;
import org.springframework.stereotype.Repository; import org.springframework.stereotype.Repository;
import java.util.List; import java.util.List;
@Repository @Repository
public interface JenkinsConfigRepository extends BaseRepository<JenkinsConfig, Long> { public interface IJenkinsConfigRepository extends IBaseRepository<JenkinsConfig, Long> {
List<JenkinsConfig> findByDeletedFalseOrderBySort(); List<JenkinsConfig> findByDeletedFalseOrderBySort();
boolean existsByNameAndDeletedFalse(String name); boolean existsByNameAndDeletedFalse(String name);
} }

View File

@ -1,6 +1,6 @@
package com.qqchen.deploy.backend.repository; 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 com.qqchen.deploy.backend.entity.JenkinsJob;
import org.springframework.data.jpa.repository.Query; import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param; import org.springframework.data.repository.query.Param;
@ -8,7 +8,7 @@ import org.springframework.stereotype.Repository;
import java.util.List; import java.util.List;
@Repository @Repository
public interface JenkinsJobRepository extends BaseRepository<JenkinsJob, Long> { public interface IJenkinsJobRepository extends IBaseRepository<JenkinsJob, Long> {
List<JenkinsJob> findByJenkinsIdAndDeletedFalse(Long jenkinsId); List<JenkinsJob> findByJenkinsIdAndDeletedFalse(Long jenkinsId);
List<JenkinsJob> findByJenkinsIdAndJobNameAndDeletedFalse(Long jenkinsId, String jobName); List<JenkinsJob> findByJenkinsIdAndJobNameAndDeletedFalse(Long jenkinsId, String jobName);
void deleteByJenkinsIdAndDeletedFalse(Long jenkinsId); void deleteByJenkinsIdAndDeletedFalse(Long jenkinsId);

View File

@ -1,12 +1,12 @@
package com.qqchen.deploy.backend.repository; 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 com.qqchen.deploy.backend.entity.JenkinsSyncHistory;
import org.springframework.stereotype.Repository; import org.springframework.stereotype.Repository;
import java.util.List; import java.util.List;
@Repository @Repository
public interface JenkinsSyncHistoryRepository extends BaseRepository<JenkinsSyncHistory, Long> { public interface IJenkinsSyncHistoryRepository extends IBaseRepository<JenkinsSyncHistory, Long> {
List<JenkinsSyncHistory> findTop50ByOrderByStartTimeDesc(); List<JenkinsSyncHistory> findTop50ByOrderByStartTimeDesc();
} }

View File

@ -1,14 +1,14 @@
package com.qqchen.deploy.backend.repository; 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 com.qqchen.deploy.backend.entity.JenkinsView;
import org.springframework.stereotype.Repository; import org.springframework.stereotype.Repository;
import java.util.List; import java.util.List;
@Repository @Repository
public interface JenkinsViewRepository extends BaseRepository<JenkinsView, Long> { public interface IJenkinsViewRepository extends IBaseRepository<JenkinsView, Long> {
List<JenkinsView> findByJenkinsIdAndDeletedFalse(Long jenkinsId); List<JenkinsView> findByJenkinsIdAndDeletedFalse(Long jenkinsId);
List<JenkinsView> findByJenkinsIdAndViewNameAndDeletedFalse(Long jenkinsId, String viewName); List<JenkinsView> findByJenkinsIdAndViewNameAndDeletedFalse(Long jenkinsId, String viewName);
void deleteByJenkinsIdAndDeletedFalse(Long jenkinsId); void deleteByJenkinsIdAndDeletedFalse(Long jenkinsId);

View File

@ -2,12 +2,11 @@ package com.qqchen.deploy.backend.repository;
import com.qqchen.deploy.backend.entity.Menu; import com.qqchen.deploy.backend.entity.Menu;
import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
import org.springframework.stereotype.Repository; import org.springframework.stereotype.Repository;
import java.util.List; import java.util.List;
@Repository @Repository
public interface MenuRepository extends JpaRepository<Menu, Long> { public interface IMenuRepository extends JpaRepository<Menu, Long> {
List<Menu> findByDeletedFalseOrderBySort(); List<Menu> findByDeletedFalseOrderBySort();

View File

@ -1,15 +1,14 @@
package com.qqchen.deploy.backend.repository; 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 com.qqchen.deploy.backend.entity.RepositoryBranch;
import org.springframework.data.jpa.repository.Modifying; import org.springframework.data.jpa.repository.Modifying;
import org.springframework.data.jpa.repository.Query; import org.springframework.data.jpa.repository.Query;
import org.springframework.data.querydsl.QuerydslPredicateExecutor;
import org.springframework.stereotype.Repository; import org.springframework.stereotype.Repository;
import org.springframework.transaction.annotation.Transactional; import org.springframework.transaction.annotation.Transactional;
@Repository @Repository
public interface RepositoryBranchRepository extends BaseRepository<RepositoryBranch, Long> { public interface IRepositoryBranchRepository extends IBaseRepository<RepositoryBranch, Long> {
@Modifying @Modifying
@Transactional @Transactional

View File

@ -1,14 +1,14 @@
package com.qqchen.deploy.backend.repository; 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 com.qqchen.deploy.backend.entity.RepositoryConfig;
import org.springframework.stereotype.Repository; import org.springframework.stereotype.Repository;
import java.util.List; import java.util.List;
@Repository @Repository
public interface RepositoryConfigRepository extends BaseRepository<RepositoryConfig, Long> { public interface IRepositoryConfigRepository extends IBaseRepository<RepositoryConfig, Long> {
List<RepositoryConfig> findByDeletedFalseOrderBySort(); List<RepositoryConfig> findByDeletedFalseOrderBySort();
boolean existsByNameAndDeletedFalse(String name); boolean existsByNameAndDeletedFalse(String name);
} }

View File

@ -1,6 +1,6 @@
package com.qqchen.deploy.backend.repository; 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 com.qqchen.deploy.backend.entity.RepositoryGroup;
import org.springframework.data.jpa.repository.Modifying; import org.springframework.data.jpa.repository.Modifying;
import org.springframework.data.jpa.repository.Query; import org.springframework.data.jpa.repository.Query;
@ -10,7 +10,7 @@ import org.springframework.transaction.annotation.Transactional;
import java.util.Optional; import java.util.Optional;
@Repository @Repository
public interface RepositoryGroupRepository extends BaseRepository<RepositoryGroup, Long> { public interface IRepositoryGroupRepository extends IBaseRepository<RepositoryGroup, Long> {
@Modifying @Modifying
@Transactional @Transactional

View File

@ -1,6 +1,6 @@
package com.qqchen.deploy.backend.repository; 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 com.qqchen.deploy.backend.entity.RepositoryProject;
import org.springframework.data.jpa.repository.Modifying; import org.springframework.data.jpa.repository.Modifying;
import org.springframework.data.jpa.repository.Query; import org.springframework.data.jpa.repository.Query;
@ -10,7 +10,7 @@ import org.springframework.transaction.annotation.Transactional;
import java.util.Optional; import java.util.Optional;
@Repository @Repository
public interface RepositoryProjectRepository extends BaseRepository<RepositoryProject, Long> { public interface IRepositoryProjectRepository extends IBaseRepository<RepositoryProject, Long> {
@Modifying @Modifying
@Transactional @Transactional

View File

@ -1,13 +1,13 @@
package com.qqchen.deploy.backend.repository; 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 com.qqchen.deploy.backend.entity.RepositorySyncHistory;
import org.springframework.stereotype.Repository; import org.springframework.stereotype.Repository;
import java.util.List; import java.util.List;
@Repository @Repository
public interface RepositorySyncHistoryRepository extends BaseRepository<RepositorySyncHistory, Long> { public interface IRepositorySyncHistoryRepository extends IBaseRepository<RepositorySyncHistory, Long> {
List<RepositorySyncHistory> findTop50ByOrderByStartTimeDesc(); List<RepositorySyncHistory> findTop50ByOrderByStartTimeDesc();
} }

View File

@ -1,14 +1,14 @@
package com.qqchen.deploy.backend.repository; 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 com.qqchen.deploy.backend.entity.RoleMenu;
import org.springframework.stereotype.Repository; import org.springframework.stereotype.Repository;
import java.util.List; import java.util.List;
@Repository @Repository
public interface RoleMenuRepository extends BaseRepository<RoleMenu, Long> { public interface IRoleMenuRepository extends IBaseRepository<RoleMenu, Long> {
List<RoleMenu> findByRoleId(Long roleId); List<RoleMenu> findByRoleId(Long roleId);

View File

@ -1,7 +1,7 @@
package com.qqchen.deploy.backend.repository; 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 com.qqchen.deploy.backend.entity.Role;
import org.springframework.stereotype.Repository; import org.springframework.stereotype.Repository;
@ -9,7 +9,7 @@ import java.util.List;
import java.util.Optional; import java.util.Optional;
@Repository @Repository
public interface RoleRepository extends BaseRepository<Role, Long> { public interface IRoleRepository extends IBaseRepository<Role, Long> {
List<Role> findByDeletedFalseOrderBySort(); List<Role> findByDeletedFalseOrderBySort();

View File

@ -1,13 +1,13 @@
package com.qqchen.deploy.backend.repository; 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 com.qqchen.deploy.backend.entity.Tenant;
import org.springframework.stereotype.Repository; import org.springframework.stereotype.Repository;
import java.util.List; import java.util.List;
@Repository @Repository
public interface TenantRepository extends BaseRepository<Tenant, Long> { public interface ITenantRepository extends IBaseRepository<Tenant, Long> {
List<Tenant> findByDeletedFalseOrderById(); List<Tenant> findByDeletedFalseOrderById();

View File

@ -1,7 +1,7 @@
package com.qqchen.deploy.backend.repository; 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 com.qqchen.deploy.backend.entity.User;
import org.springframework.stereotype.Repository; import org.springframework.stereotype.Repository;
@ -9,7 +9,7 @@ import java.util.List;
import java.util.Optional; import java.util.Optional;
@Repository @Repository
public interface UserRepository extends BaseRepository<User, Long> { public interface IUserRepository extends IBaseRepository<User, Long> {
Optional<User> findByUsernameAndDeletedFalse(String username); Optional<User> findByUsernameAndDeletedFalse(String username);

View File

@ -1,7 +1,7 @@
package com.qqchen.deploy.backend.repository; 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 com.qqchen.deploy.backend.entity.UserRole;
import org.springframework.data.jpa.repository.Modifying; import org.springframework.data.jpa.repository.Modifying;
import org.springframework.data.jpa.repository.Query; import org.springframework.data.jpa.repository.Query;
@ -11,7 +11,7 @@ import org.springframework.stereotype.Repository;
import java.util.Set; import java.util.Set;
@Repository @Repository
public interface UserRoleRepository extends BaseRepository<UserRole, Long> { public interface IUserRoleRepository extends IBaseRepository<UserRole, Long> {
@Query("SELECT ur FROM UserRole ur WHERE ur.user.id = :userId") @Query("SELECT ur FROM UserRole ur WHERE ur.user.id = :userId")
Set<UserRole> findByUserId(@Param("userId") Long userId); Set<UserRole> findByUserId(@Param("userId") Long userId);

View File

@ -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<Department, Long> {
List<Department> getTree();
void validateCode(String code);
void validateName(String name);
Integer getNextSort(Long parentId);
}

View File

@ -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<JenkinsConfig, Long> {
// boolean testConnection(JenkinsTestConnectionDTO dto);
Long asyncSyncAll(Long jenkinsId);
Long asyncSyncView(Long jenkinsId);
Long asyncSyncJob(Long jenkinsId);
Long asyncSyncBuild(Long jenkinsId);
// List<JenkinsSyncHistoryDTO> getSyncHistories();
// List<ViewResponse> getViews(Long jenkinsId);
// List<JobResponse> getJobs(Long jenkinsId);
// List<BuildResponse> getBuilds(Long jenkinsId, Long jobId);
// List<JobResponse> getJobsByView(Long jenkinsId, String viewName);
}

View File

@ -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<Menu, Long> {
// List<MenuDTO> getMenuTree();
// List<MenuDTO> getMenuTreeWithoutButtons();
// List<MenuDTO> getUserMenus(Long userId);
List<Menu> getMenusByRoleId(Long roleId);
void saveRoleMenus(Long roleId, List<Long> menuIds);
}

View File

@ -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<RepositoryConfig, Long> {
// 仓库配置管理
List<RepositoryConfig> 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<RunningSyncDTO> getRunningSyncs();
}

View File

@ -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<Role, Long> {
void validateCode(String code);
void validateName(String name);
List<Long> getRoleMenuIds(Long roleId);
void updateRoleMenus(Long roleId, List<Long> menuIds);
}

View File

@ -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<Tenant, Long> {
void validateCode(String code);
void validateName(String name);
List<Tenant> findAllEnabled();
boolean existsByCode(String code);
boolean existsByName(String name);
// List<Tenant> findAll(TenantQuery query);
}

View File

@ -1,9 +1,9 @@
package com.qqchen.deploy.backend.service; 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; import com.qqchen.deploy.backend.entity.User;
public interface UserService extends BaseService<User, Long> { public interface IUserService extends IBaseService<User, Long> {
User register(User user); User register(User user);
User findByUsername(String username); User findByUsername(String username);
boolean checkUsernameExists(String username); boolean checkUsernameExists(String username);

View File

@ -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<Department, Long> 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<Department> getTree() {
List<Department> 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<Department> buildTree(List<Department> departments) {
Map<Long, List<Department>> parentIdMap = departments.stream()
.collect(Collectors.groupingBy(d -> d.getParentId() == null ? 0L : d.getParentId()));
return buildTreeNodes(0L, parentIdMap);
}
private List<Department> buildTreeNodes(Long parentId, Map<Long, List<Department>> parentIdMap) {
List<Department> nodes = new ArrayList<>();
List<Department> children = parentIdMap.get(parentId);
if (children != null) {
for (Department child : children) {
child.setChildren(buildTreeNodes(child.getId(), parentIdMap));
nodes.add(child);
}
}
return nodes;
}
}

View File

@ -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<JenkinsConfig, Long> 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<Long, Boolean> 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<String> 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<Map> 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<Map<String, Object>> response = createRestTemplate().exchange(
// url,
// HttpMethod.GET,
// createHttpEntity(config.getUsername(), config.getPassword()),
// new ParameterizedTypeReference<>() {}
// );
//
// if (response.getBody() != null) {
// List<Map<String, Object>> views = (List<Map<String, Object>>) response.getBody().get("views");
// List<JenkinsView> viewsToSave = new ArrayList<>();
// Map<String, JenkinsView> existingViews = jenkinsViewRepository
// .findByJenkinsIdAndDeletedFalse(config.getId())
// .stream()
// .collect(Collectors.toMap(JenkinsView::getViewName, v -> v));
//
// for (Map<String, Object> 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<Map<String, Object>> response = createRestTemplate().exchange(
// url,
// HttpMethod.GET,
// createHttpEntity(config.getUsername(), config.getPassword()),
// new ParameterizedTypeReference<>() {}
// );
//
// if (response.getBody() != null) {
// List<Map<String, Object>> jobs = (List<Map<String, Object>>) response.getBody().get("jobs");
// List<JenkinsJob> jobsToSave = new ArrayList<>();
// Map<String, JenkinsJob> existingJobs = jenkinsJobRepository
// .findByJenkinsIdAndDeletedFalse(config.getId())
// .stream()
// .collect(Collectors.toMap(JenkinsJob::getJobName, j -> j));
//
// for (Map<String, Object> 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<String, Object> lastBuild = (Map<String, Object>) 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<JenkinsJob> jobs = jenkinsJobRepository.findByJenkinsIdAndDeletedFalse(config.getId());
// List<JenkinsBuild> 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<Map<String, Object>> response = createRestTemplate().exchange(
// url,
// HttpMethod.GET,
// createHttpEntity(config.getUsername(), config.getPassword()),
// new ParameterizedTypeReference<>() {}
// );
//
// if (response.getBody() != null) {
// List<Map<String, Object>> builds = (List<Map<String, Object>>) response.getBody().get("builds");
// Map<Integer, JenkinsBuild> existingBuilds = jenkinsBuildRepository
// .findByJobIdAndDeletedFalse(job.getId())
// .stream()
// .collect(Collectors.toMap(JenkinsBuild::getBuildNumber, b -> b));
//
// for (Map<String, Object> 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<Map<String, Object>> actions = (List<Map<String, Object>>) build.get("actions");
// if (actions != null) {
// for (Map<String, Object> action : actions) {
// if (action != null && action.containsKey("causes")) {
// List<Map<String, Object>> causes = (List<Map<String, Object>>) 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<JenkinsSyncHistoryDTO> getSyncHistories() {
// List<JenkinsSyncHistory> 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<ViewResponse> getViews(Long jenkinsId) {
// List<JenkinsView> 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<JobResponse> getJobs(Long jenkinsId) {
// List<JenkinsJob> 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<BuildResponse> getBuilds(Long jenkinsId, Long jobId) {
// List<JenkinsBuild> 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<JobResponse> getJobsByView(Long jenkinsId, String viewName) {
// List<JenkinsJob> 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());
// }
//}

View File

@ -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<Menu, MenuQuery> implements MenuService {
//
// private final MenuRepository menuRepository;
// private final RoleMenuRepository roleMenuRepository;
//
// @Override
// protected JpaRepository<Menu, Long> getRepository() {
// return menuRepository;
// }
//
// @Override
// public List<MenuDTO> getMenuTree() {
// List<Menu> menus = menuRepository.findByDeletedFalseOrderBySort();
// return buildMenuTree(menus);
// }
//
// @Override
// public List<MenuDTO> getUserMenus(Long userId) {
// // TODO: 根据用户ID获取角色然后获取菜单
// return new ArrayList<>();
// }
//
// @Override
// public List<Menu> getMenusByRoleId(Long roleId) {
// List<RoleMenu> 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<Long> menuIds) {
// // 先删除原有的关联
// roleMenuRepository.deleteByRoleId(roleId);
//
// // 保存新的关联
// List<RoleMenu> 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<Menu> 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<Menu> buttons = menuRepository.findByParentIdAndDeletedFalse(id);
// if (!buttons.isEmpty()) {
// for (Menu button : buttons) {
// button.setDeleted(true);
// menuRepository.save(button);
// }
// }
// }
//
// // 检查是否有子菜单
// List<Menu> children = menuRepository.findByParentIdAndDeletedFalse(id);
// if (!children.isEmpty()) {
// throw new ApiException("该菜单下还有子菜单,不能删除");
// }
//
// menu.setDeleted(true);
// menuRepository.save(menu);
// }
//
// private List<MenuDTO> buildMenuTree(List<Menu> menus) {
// // 先找出所有的根节点parentId为null或0的节点
// List<Menu> 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<Menu> allMenus) {
// MenuDTO dto = new MenuDTO();
// BeanUtils.copyProperties(menu, dto);
//
// // 查找当前节点的子节点
// List<Menu> 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<MenuDTO> getMenuTreeWithoutButtons() {
// // 获取所有非按钮类型的菜单
// List<Menu> menus = menuRepository.findByTypeInAndDeletedFalseOrderBySort(Arrays.asList(0, 1)); // 0-目录 1-菜单
// return buildMenuTree(menus);
// }
//}

View File

@ -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<Long, Boolean> RUNNING_TASKS = new ConcurrentHashMap<>();
//
// private RestTemplate createRestTemplate() {
// return new RestTemplateBuilder()
// .setConnectTimeout(Duration.ofSeconds(30))
// .setReadTimeout(Duration.ofSeconds(30))
// .build();
// }
//
// private HttpEntity<String> 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<RepositoryConfig> 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<Map> 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<Map> 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<String> entity = createHttpEntity(config.getAccessToken());
//
// // 1. 同步所有数据到内存
// RepositorySyncDataCache dataHolder = new RepositorySyncDataCache();
//
// // 2. 同步群组数据
// try {
// CompletableFuture<Boolean> 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<Boolean> 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<Boolean> 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<Boolean> syncGroupsParallel(
// RepositoryConfig config,
// HttpEntity<String> entity,
// RestTemplate restTemplate,
// RepositorySyncDataCache dataHolder) {
//
// String groupsUrl = String.format("%s/api/v4/groups?per_page=100", config.getUrl());
// try {
// log.info("开始同步群组 - 仓库: {}", config.getName());
//
// ResponseEntity<List<Map<String, Object>>> response = restTemplate.exchange(
// groupsUrl,
// HttpMethod.GET,
// entity,
// new ParameterizedTypeReference<>() {}
// );
//
// if (response.getBody() == null) {
// log.warn("未获取到群组数据 - 仓库: {}", config.getName());
// return CompletableFuture.completedFuture(true);
// }
//
// for (Map<String, Object> 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<Boolean> syncProjectsParallel(
// RepositoryConfig config,
// HttpEntity<String> entity,
// RestTemplate restTemplate,
// Collection<RepositoryGroup> 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<List<Map<String, Object>>> response = restTemplate.exchange(
// projectsUrl,
// HttpMethod.GET,
// entity,
// new ParameterizedTypeReference<>() {}
// );
//
// if (response.getBody() == null) {
// continue;
// }
//
// for (Map<String, Object> 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<Boolean> syncBranchesParallel(
// RepositoryConfig config,
// HttpEntity<String> entity,
// RestTemplate restTemplate,
// Collection<RepositoryProject> 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<List<Map<String, Object>>> response = restTemplate.exchange(
// branchesUrl,
// HttpMethod.GET,
// entity,
// new ParameterizedTypeReference<>() {}
// );
//
// if (response.getBody() == null) {
// continue;
// }
//
// for (Map<String, Object> 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<String, Object> 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<String, Object> 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<String, Object> branch) {
// RepositoryBranch repositoryBranch = new RepositoryBranch();
// repositoryBranch.setRepositoryId(repositoryId);
// repositoryBranch.setProjectId(Long.valueOf(projectId));
// repositoryBranch.setName((String) branch.get("name"));
//
// Map<String, Object> commit = (Map<String, Object>) 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<String> 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<String> entity = createHttpEntity(config.getAccessToken());
// RepositorySyncDataCache dataHolder = new RepositorySyncDataCache();
//
// try {
// List<RepositoryGroup> 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<String> entity = createHttpEntity(config.getAccessToken());
// RepositorySyncDataCache dataHolder = new RepositorySyncDataCache();
//
// try {
// List<RepositoryProject> 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<RunningSyncDTO> getRunningSyncs() {
// // 查询最近的50条同步历史记录
// List<RepositorySyncHistory> 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<Long, RepositoryGroup> groups = new ConcurrentHashMap<>();
// private final Map<Long, RepositoryProject> projects = new ConcurrentHashMap<>();
// private final Map<String, RepositoryBranch> branches = new ConcurrentHashMap<>();
// }
//}

View File

@ -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<Role, RoleQuery> implements RoleService {
//
// private final RoleRepository roleRepository;
// private final RoleMenuRepository roleMenuRepository;
//
// @Override
// protected JpaRepository<Role, Long> getRepository() {
// return roleRepository;
// }
//
// @Override
// public List<Long> getRoleMenuIds(Long roleId) {
// return roleMenuRepository.findByRoleId(roleId)
// .stream()
// .map(RoleMenu::getMenuId)
// .collect(Collectors.toList());
// }
//
// @Override
// @Transactional
// public void updateRoleMenus(Long roleId, List<Long> 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<RoleMenu> 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<Role> findAll() {
// return roleRepository.findByDeletedFalseOrderBySort();
// }
//}

View File

@ -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<Tenant, Long> implements ITenantService {
//
// private final ITenantRepository tenantRepository;
//
// public TenantServiceImpl(ITenantRepository tenantRepository) {
// super(tenantRepository);
// this.tenantRepository = tenantRepository;
// }
//
// @Override
// public List<Tenant> 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<Tenant>) tenantRepository.findAll(builder, tenant.id.asc());
// }
//
// @Override
// public List<Tenant> findAllEnabled() {
// QTenant tenant = QTenant.tenant;
// return (List<Tenant>) 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);
// }
//}

View File

@ -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"))
);
}
}

View File

@ -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.common.service.impl.BaseServiceImpl;
import com.qqchen.deploy.backend.entity.User; import com.qqchen.deploy.backend.entity.User;
import com.qqchen.deploy.backend.repository.UserRepository; import com.qqchen.deploy.backend.repository.IUserRepository;
import com.qqchen.deploy.backend.service.UserService; import com.qqchen.deploy.backend.service.IUserService;
import jakarta.transaction.Transactional; import jakarta.transaction.Transactional;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
@Service @Service
@Transactional @Transactional
public class UserServiceImpl extends BaseServiceImpl<User, Long> implements UserService { public class UserServiceImpl extends BaseServiceImpl<User, Long> implements IUserService {
private final UserRepository userRepository; private final IUserRepository userRepository;
@Autowired @Autowired
public UserServiceImpl(UserRepository userRepository) { public UserServiceImpl(IUserRepository userRepository) {
super(userRepository); super(userRepository);
this.userRepository = userRepository; this.userRepository = userRepository;
} }

View File

@ -3,9 +3,9 @@ server:
spring: spring:
datasource: datasource:
url: jdbc:mysql://192.168.1.111:3306/deploy-ease-platform?useSSL=false&serverTimezone=UTC&allowPublicKeyRetrieval=true url: jdbc:mysql://127.0.0.1:3306/2024_11_24_platform?useSSL=false&serverTimezone=UTC&allowPublicKeyRetrieval=true
username: deploy-ease-platform username: root
password: qichen5210523 password: root
driver-class-name: com.mysql.cj.jdbc.Driver driver-class-name: com.mysql.cj.jdbc.Driver
jpa: jpa:
hibernate: hibernate: