From a1ffad1c4efda58e8d0bee20b16f8a54f5b98b1c Mon Sep 17 00:00:00 2001 From: teddy Date: Mon, 2 Feb 2026 15:48:23 +0900 Subject: [PATCH] =?UTF-8?q?init=20spotless=20=EC=A0=81=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../cd/training/KamcoTrainingApplication.java | 28 +- .../cd/training/auth/BCryptSaltGenerator.java | 44 +- .../auth/CustomAuthenticationProvider.java | 124 +- .../cd/training/auth/CustomUserDetails.java | 112 +- .../auth/JwtAuthenticationFilter.java | 140 +- .../cd/training/auth/JwtTokenProvider.java | 144 +- .../code/CommonCodeApiController.java | 598 +++---- .../cd/training/code/dto/CommonCodeDto.java | 358 ++--- .../code/service/CommonCodeService.java | 310 ++-- .../common/enums/DeployTargetType.java | 38 +- .../common/enums/DetectionClassification.java | 110 +- .../common/enums/LearnDataRegister.java | 52 +- .../training/common/enums/LearnDataType.java | 52 +- .../common/enums/ModelMngStatusType.java | 54 +- .../common/enums/ProcessStepType.java | 40 +- .../cd/training/common/enums/RoleType.java | 50 +- .../cd/training/common/enums/StatusType.java | 50 +- .../common/enums/TrainStatusType.java | 44 +- .../common/enums/error/AuthErrorCode.java | 52 +- .../common/exception/BadRequestException.java | 20 +- .../common/exception/CustomApiException.java | 56 +- .../exception/DuplicateFileException.java | 14 +- .../common/exception/NotFoundException.java | 20 +- .../common/exception/ValidationException.java | 14 +- .../common/service/BaseCoreService.java | 76 +- .../training/common/utils/CommonCodeUtil.java | 304 ++-- .../common/utils/CommonStringUtils.java | 64 +- .../cd/training/common/utils/ErrorCode.java | 20 +- .../cd/training/common/utils/FIleChecker.java | 1368 ++++++++--------- .../training/common/utils/NameValidator.java | 86 +- .../cd/training/common/utils/UserUtil.java | 82 +- .../training/common/utils/enums/CodeDto.java | 40 +- .../common/utils/enums/CodeExpose.java | 20 +- .../training/common/utils/enums/EnumType.java | 16 +- .../common/utils/enums/EnumValidator.java | 52 +- .../cd/training/common/utils/enums/Enums.java | 152 +- .../utils/geometry/GeometryDeserializer.java | 72 +- .../utils/geometry/GeometrySerializer.java | 62 +- .../utils/html/HtmlEscapeDeserializer.java | 36 +- .../utils/html/HtmlUnescapeSerializer.java | 40 +- .../common/utils/interfaces/EnumValid.java | 46 +- .../utils/interfaces/JsonFormatDttm.java | 30 +- .../kamco/cd/training/config/CacheConfig.java | 22 +- .../kamco/cd/training/config/FileConfig.java | 54 +- .../config/GlobalExceptionHandler.java | 1056 ++++++------- .../cd/training/config/OpenApiConfig.java | 154 +- .../cd/training/config/SecurityConfig.java | 272 ++-- .../cd/training/config/StartupLogger.java | 192 +-- .../cd/training/config/SwaggerConfig.java | 26 +- .../kamco/cd/training/config/WebConfig.java | 64 +- .../cd/training/config/api/ApiLogFilter.java | 56 +- .../training/config/api/ApiLogFunction.java | 264 ++-- .../config/api/ApiResponseAdvice.java | 248 +-- .../training/config/api/ApiResponseDto.java | 398 ++--- .../dataset/DatasetApiController.java | 308 ++-- .../dataset/MapSheetApiController.java | 112 +- .../cd/training/dataset/dto/DatasetDto.java | 424 ++--- .../cd/training/dataset/dto/MapSheetDto.java | 206 +-- .../dataset/service/DatasetService.java | 172 +-- .../dataset/service/MapSheetService.java | 82 +- .../cd/training/log/dto/AuditLogDto.java | 458 +++--- .../cd/training/log/dto/ErrorLogDto.java | 202 +-- .../cd/training/log/dto/EventStatus.java | 48 +- .../kamco/cd/training/log/dto/EventType.java | 58 +- .../training/log/service/AuditLogService.java | 92 +- .../training/log/service/ErrorLogService.java | 38 +- .../cd/training/members/AuthController.java | 482 +++--- .../members/MembersApiController.java | 138 +- .../cd/training/members/dto/MembersDto.java | 366 ++--- .../training/members/dto/SignInRequest.java | 40 +- .../training/members/dto/TokenResponse.java | 32 +- .../members/exception/MemberException.java | 100 +- .../training/members/service/AuthService.java | 154 +- .../members/service/MemberDetailsService.java | 58 +- .../members/service/MembersService.java | 90 +- .../cd/training/menu/MenuApiController.java | 124 +- .../kamco/cd/training/menu/dto/MenuDto.java | 128 +- .../cd/training/menu/service/MenuService.java | 54 +- .../training/model/ModelMngApiController.java | 572 +++---- .../cd/training/model/dto/HyperParamDto.java | 438 +++--- .../cd/training/model/dto/ModelMngDto.java | 1190 +++++++------- .../cd/training/model/dto/ModelVerDto.java | 122 +- .../model/service/ModelMngService.java | 100 +- .../model/service/ModelTrainService.java | 786 +++++----- .../training/postgres/CommonCreateEntity.java | 44 +- .../training/postgres/CommonDateEntity.java | 68 +- .../cd/training/postgres/QueryDslConfig.java | 38 +- .../training/postgres/QuerydslOrderUtil.java | 60 +- .../postgres/core/AnimalCoreService.java | 154 +- .../postgres/core/AuditLogCoreService.java | 124 +- .../postgres/core/CommonCodeCoreService.java | 434 +++--- .../postgres/core/DatasetCoreService.java | 552 +++---- .../postgres/core/ErrorLogCoreService.java | 70 +- .../postgres/core/HyperParamCoreService.java | 674 ++++---- .../postgres/core/MapSheetCoreService.java | 144 +- .../postgres/core/MembersCoreService.java | 335 ++-- .../postgres/core/MenuCoreService.java | 38 +- .../postgres/core/ModelMngCoreService.java | 404 ++--- .../core/SystemMetricsCoreService.java | 132 +- .../postgres/core/TokenCoreService.java | 114 +- .../core/UploadSessionCoreService.java | 110 +- .../postgres/entity/AuditLogEntity.java | 202 +-- .../entity/AuthRefreshTokenEntity.java | 134 +- .../postgres/entity/CommonCodeEntity.java | 270 ++-- .../postgres/entity/DatasetEntity.java | 264 ++-- .../postgres/entity/ErrorLogEntity.java | 122 +- .../postgres/entity/LearnDataEntity.java | 160 +- .../postgres/entity/MapSheetEntity.java | 210 +-- .../postgres/entity/MemberEntity.java | 228 +-- .../training/postgres/entity/MenuEntity.java | 142 +- .../entity/ModelDatasetMappEntity.java | 94 +- .../entity/ModelHyperParamEntity.java | 464 +++--- .../postgres/entity/ModelMngEntity.java | 188 +-- .../entity/ModelTrainMasterEntity.java | 340 ++-- .../postgres/entity/SystemMetricsEntity.java | 110 +- .../postgres/entity/UploadSessionEntity.java | 199 ++- .../repository/SystemMetricsRepository.java | 38 +- .../repository/code/CommonCodeRepository.java | 14 +- .../code/CommonCodeRepositoryCustom.java | 36 +- .../code/CommonCodeRepositoryImpl.java | 178 +-- .../repository/dataset/DatasetRepository.java | 22 +- .../dataset/DatasetRepositoryCustom.java | 26 +- .../dataset/DatasetRepositoryImpl.java | 150 +- .../dataset/MapSheetRepository.java | 26 +- .../dataset/MapSheetRepositoryCustom.java | 18 +- .../dataset/MapSheetRepositoryImpl.java | 108 +- .../repository/log/AuditLogRepository.java | 14 +- .../log/AuditLogRepositoryCustom.java | 50 +- .../log/AuditLogRepositoryImpl.java | 884 +++++------ .../repository/log/ErrorLogRepository.java | 14 +- .../log/ErrorLogRepositoryCustom.java | 16 +- .../log/ErrorLogRepositoryImpl.java | 244 +-- .../members/AuthRefreshTokenImpl.java | 100 +- .../members/AuthRefreshTokenRepository.java | 14 +- .../AuthRefreshTokenRepositoryCustom.java | 24 +- .../repository/members/MembersRepository.java | 14 +- .../members/MembersRepositoryCustom.java | 42 +- .../members/MembersRepositoryImpl.java | 302 ++-- .../repository/menu/MenuRepository.java | 12 +- .../repository/menu/MenuRepositoryCustom.java | 18 +- .../repository/menu/MenuRepositoryImpl.java | 42 +- .../model/ModelDatasetMappRepository.java | 18 +- .../model/ModelHyperParamRepository.java | 28 +- .../repository/model/ModelMngRepository.java | 34 +- .../model/ModelMngRepositoryCustom.java | 32 +- .../model/ModelMngRepositoryImpl.java | 122 +- .../upload/UploadSessionRepository.java | 14 +- .../upload/UploadSessionRepositoryCustom.java | 36 +- .../upload/UploadSessionRepositoryImpl.java | 288 ++-- .../training/upload/UploadApiController.java | 224 ++- .../cd/training/upload/dto/UploadDto.java | 438 +++--- .../upload/service/UploadService.java | 447 +++--- .../KamcoTrainingApplicationTests.java | 22 +- 153 files changed, 12870 insertions(+), 12931 deletions(-) diff --git a/src/main/java/com/kamco/cd/training/KamcoTrainingApplication.java b/src/main/java/com/kamco/cd/training/KamcoTrainingApplication.java index 6351e23..e14139c 100644 --- a/src/main/java/com/kamco/cd/training/KamcoTrainingApplication.java +++ b/src/main/java/com/kamco/cd/training/KamcoTrainingApplication.java @@ -1,14 +1,14 @@ -package com.kamco.cd.training; - -import org.springframework.boot.SpringApplication; -import org.springframework.boot.autoconfigure.SpringBootApplication; -import org.springframework.scheduling.annotation.EnableScheduling; - -@SpringBootApplication -@EnableScheduling -public class KamcoTrainingApplication { - - public static void main(String[] args) { - SpringApplication.run(KamcoTrainingApplication.class, args); - } -} +package com.kamco.cd.training; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.scheduling.annotation.EnableScheduling; + +@SpringBootApplication +@EnableScheduling +public class KamcoTrainingApplication { + + public static void main(String[] args) { + SpringApplication.run(KamcoTrainingApplication.class, args); + } +} diff --git a/src/main/java/com/kamco/cd/training/auth/BCryptSaltGenerator.java b/src/main/java/com/kamco/cd/training/auth/BCryptSaltGenerator.java index b78f6e8..96d05a7 100644 --- a/src/main/java/com/kamco/cd/training/auth/BCryptSaltGenerator.java +++ b/src/main/java/com/kamco/cd/training/auth/BCryptSaltGenerator.java @@ -1,22 +1,22 @@ -package com.kamco.cd.training.auth; - -import java.security.SecureRandom; -import java.util.Base64; - -public class BCryptSaltGenerator { - - public static String generateSaltWithEmployeeNo(String employeeNo) { - - // bcrypt salt는 16바이트(128비트) 필요 - byte[] randomBytes = new byte[16]; - new SecureRandom().nextBytes(randomBytes); - - String base64 = Base64.getEncoder().encodeToString(randomBytes); - - // 사번을 포함 (22자 제한 → 잘라내기) - String mixedSalt = (employeeNo + base64).substring(0, 22); - - // bcrypt 포맷에 맞게 구성 - return "$2a$10$" + mixedSalt; - } -} +package com.kamco.cd.training.auth; + +import java.security.SecureRandom; +import java.util.Base64; + +public class BCryptSaltGenerator { + + public static String generateSaltWithEmployeeNo(String employeeNo) { + + // bcrypt salt는 16바이트(128비트) 필요 + byte[] randomBytes = new byte[16]; + new SecureRandom().nextBytes(randomBytes); + + String base64 = Base64.getEncoder().encodeToString(randomBytes); + + // 사번을 포함 (22자 제한 → 잘라내기) + String mixedSalt = (employeeNo + base64).substring(0, 22); + + // bcrypt 포맷에 맞게 구성 + return "$2a$10$" + mixedSalt; + } +} diff --git a/src/main/java/com/kamco/cd/training/auth/CustomAuthenticationProvider.java b/src/main/java/com/kamco/cd/training/auth/CustomAuthenticationProvider.java index 94ab847..005e96f 100644 --- a/src/main/java/com/kamco/cd/training/auth/CustomAuthenticationProvider.java +++ b/src/main/java/com/kamco/cd/training/auth/CustomAuthenticationProvider.java @@ -1,62 +1,62 @@ -package com.kamco.cd.training.auth; - -import com.kamco.cd.training.common.enums.StatusType; -import com.kamco.cd.training.common.enums.error.AuthErrorCode; -import com.kamco.cd.training.common.exception.CustomApiException; -import com.kamco.cd.training.postgres.entity.MemberEntity; -import com.kamco.cd.training.postgres.repository.members.MembersRepository; -import lombok.RequiredArgsConstructor; -import org.mindrot.jbcrypt.BCrypt; -import org.springframework.security.authentication.AuthenticationProvider; -import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; -import org.springframework.security.core.Authentication; -import org.springframework.security.core.AuthenticationException; -import org.springframework.stereotype.Component; - -@Component -@RequiredArgsConstructor -public class CustomAuthenticationProvider implements AuthenticationProvider { - - private final MembersRepository membersRepository; - - @Override - public Authentication authenticate(Authentication authentication) throws AuthenticationException { - String username = authentication.getName(); - String rawPassword = authentication.getCredentials().toString(); - - // 유저 조회 - MemberEntity member = - membersRepository - .findByEmployeeNo(username) - .orElseThrow(() -> new CustomApiException(AuthErrorCode.LOGIN_ID_NOT_FOUND)); - - // 미사용 상태 - if (member.getStatus().equals(StatusType.INACTIVE.getId())) { - throw new CustomApiException(AuthErrorCode.LOGIN_ID_NOT_FOUND); - } - - // jBCrypt + 커스텀 salt 로 저장된 패스워드 비교 - if (!BCrypt.checkpw(rawPassword, member.getPassword())) { - // 실패 카운트 저장 - int cnt = member.getLoginFailCount() + 1; - member.setLoginFailCount(cnt); - membersRepository.save(member); - throw new CustomApiException(AuthErrorCode.LOGIN_PASSWORD_MISMATCH); - } - - // 로그인 실패 체크 - if (member.getLoginFailCount() >= 5) { - throw new CustomApiException(AuthErrorCode.LOGIN_PASSWORD_EXCEEDED); - } - - // 인증 성공 → UserDetails 생성 - CustomUserDetails userDetails = new CustomUserDetails(member); - - return new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities()); - } - - @Override - public boolean supports(Class authentication) { - return UsernamePasswordAuthenticationToken.class.isAssignableFrom(authentication); - } -} +package com.kamco.cd.training.auth; + +import com.kamco.cd.training.common.enums.StatusType; +import com.kamco.cd.training.common.enums.error.AuthErrorCode; +import com.kamco.cd.training.common.exception.CustomApiException; +import com.kamco.cd.training.postgres.entity.MemberEntity; +import com.kamco.cd.training.postgres.repository.members.MembersRepository; +import lombok.RequiredArgsConstructor; +import org.mindrot.jbcrypt.BCrypt; +import org.springframework.security.authentication.AuthenticationProvider; +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.AuthenticationException; +import org.springframework.stereotype.Component; + +@Component +@RequiredArgsConstructor +public class CustomAuthenticationProvider implements AuthenticationProvider { + + private final MembersRepository membersRepository; + + @Override + public Authentication authenticate(Authentication authentication) throws AuthenticationException { + String username = authentication.getName(); + String rawPassword = authentication.getCredentials().toString(); + + // 유저 조회 + MemberEntity member = + membersRepository + .findByEmployeeNo(username) + .orElseThrow(() -> new CustomApiException(AuthErrorCode.LOGIN_ID_NOT_FOUND)); + + // 미사용 상태 + if (member.getStatus().equals(StatusType.INACTIVE.getId())) { + throw new CustomApiException(AuthErrorCode.LOGIN_ID_NOT_FOUND); + } + + // jBCrypt + 커스텀 salt 로 저장된 패스워드 비교 + if (!BCrypt.checkpw(rawPassword, member.getPassword())) { + // 실패 카운트 저장 + int cnt = member.getLoginFailCount() + 1; + member.setLoginFailCount(cnt); + membersRepository.save(member); + throw new CustomApiException(AuthErrorCode.LOGIN_PASSWORD_MISMATCH); + } + + // 로그인 실패 체크 + if (member.getLoginFailCount() >= 5) { + throw new CustomApiException(AuthErrorCode.LOGIN_PASSWORD_EXCEEDED); + } + + // 인증 성공 → UserDetails 생성 + CustomUserDetails userDetails = new CustomUserDetails(member); + + return new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities()); + } + + @Override + public boolean supports(Class authentication) { + return UsernamePasswordAuthenticationToken.class.isAssignableFrom(authentication); + } +} diff --git a/src/main/java/com/kamco/cd/training/auth/CustomUserDetails.java b/src/main/java/com/kamco/cd/training/auth/CustomUserDetails.java index 465726e..6b98554 100644 --- a/src/main/java/com/kamco/cd/training/auth/CustomUserDetails.java +++ b/src/main/java/com/kamco/cd/training/auth/CustomUserDetails.java @@ -1,56 +1,56 @@ -package com.kamco.cd.training.auth; - -import com.kamco.cd.training.postgres.entity.MemberEntity; -import java.util.Collection; -import java.util.List; -import org.springframework.security.core.GrantedAuthority; -import org.springframework.security.core.authority.SimpleGrantedAuthority; -import org.springframework.security.core.userdetails.UserDetails; - -public class CustomUserDetails implements UserDetails { - - private final MemberEntity member; - - public CustomUserDetails(MemberEntity member) { - this.member = member; - } - - @Override - public Collection getAuthorities() { - return List.of(new SimpleGrantedAuthority("ROLE_" + member.getUserRole())); - } - - @Override - public String getPassword() { - return member.getPassword(); - } - - @Override - public String getUsername() { - return String.valueOf(member.getUuid()); - } - - @Override - public boolean isAccountNonExpired() { - return true; // 추후 상태 필드에 따라 수정 가능 - } - - @Override - public boolean isAccountNonLocked() { - return true; - } - - @Override - public boolean isCredentialsNonExpired() { - return true; - } - - @Override - public boolean isEnabled() { - return "ACTIVE".equals(member.getStatus()); - } - - public MemberEntity getMember() { - return member; - } -} +package com.kamco.cd.training.auth; + +import com.kamco.cd.training.postgres.entity.MemberEntity; +import java.util.Collection; +import java.util.List; +import org.springframework.security.core.GrantedAuthority; +import org.springframework.security.core.authority.SimpleGrantedAuthority; +import org.springframework.security.core.userdetails.UserDetails; + +public class CustomUserDetails implements UserDetails { + + private final MemberEntity member; + + public CustomUserDetails(MemberEntity member) { + this.member = member; + } + + @Override + public Collection getAuthorities() { + return List.of(new SimpleGrantedAuthority("ROLE_" + member.getUserRole())); + } + + @Override + public String getPassword() { + return member.getPassword(); + } + + @Override + public String getUsername() { + return String.valueOf(member.getUuid()); + } + + @Override + public boolean isAccountNonExpired() { + return true; // 추후 상태 필드에 따라 수정 가능 + } + + @Override + public boolean isAccountNonLocked() { + return true; + } + + @Override + public boolean isCredentialsNonExpired() { + return true; + } + + @Override + public boolean isEnabled() { + return "ACTIVE".equals(member.getStatus()); + } + + public MemberEntity getMember() { + return member; + } +} diff --git a/src/main/java/com/kamco/cd/training/auth/JwtAuthenticationFilter.java b/src/main/java/com/kamco/cd/training/auth/JwtAuthenticationFilter.java index eef2029..57c0df6 100644 --- a/src/main/java/com/kamco/cd/training/auth/JwtAuthenticationFilter.java +++ b/src/main/java/com/kamco/cd/training/auth/JwtAuthenticationFilter.java @@ -1,70 +1,70 @@ -package com.kamco.cd.training.auth; - -import jakarta.servlet.FilterChain; -import jakarta.servlet.ServletException; -import jakarta.servlet.http.HttpServletRequest; -import jakarta.servlet.http.HttpServletResponse; -import java.io.IOException; -import lombok.RequiredArgsConstructor; -import org.springframework.lang.NonNull; -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.stereotype.Component; -import org.springframework.util.AntPathMatcher; -import org.springframework.web.filter.OncePerRequestFilter; - -@Component -@RequiredArgsConstructor -public class JwtAuthenticationFilter extends OncePerRequestFilter { - - private final JwtTokenProvider jwtTokenProvider; - private final UserDetailsService userDetailsService; - private static final AntPathMatcher PATH_MATCHER = new AntPathMatcher(); - private static final String[] EXCLUDE_PATHS = { - "/api/auth/signin", "/api/auth/refresh", "/api/auth/logout", "/api/members/*/password" - }; - - @Override - protected void doFilterInternal( - @NonNull HttpServletRequest request, - @NonNull HttpServletResponse response, - @NonNull FilterChain filterChain) - throws ServletException, IOException { - - String token = resolveToken(request); - - if (token != null && jwtTokenProvider.isValidToken(token)) { - String username = jwtTokenProvider.getSubject(token); - - UserDetails userDetails = userDetailsService.loadUserByUsername(username); - UsernamePasswordAuthenticationToken authentication = - new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities()); - SecurityContextHolder.getContext().setAuthentication(authentication); - } - - filterChain.doFilter(request, response); - } - - @Override - protected boolean shouldNotFilter(HttpServletRequest request) { - String path = request.getServletPath(); - - // JWT 필터를 타지 않게 할 URL 패턴들 - for (String pattern : EXCLUDE_PATHS) { - if (PATH_MATCHER.match(pattern, path)) { - return true; - } - } - return false; - } - - private String resolveToken(HttpServletRequest request) { - String bearer = request.getHeader("Authorization"); - if (bearer != null && bearer.startsWith("Bearer ")) { - return bearer.substring(7); - } - return null; - } -} +package com.kamco.cd.training.auth; + +import jakarta.servlet.FilterChain; +import jakarta.servlet.ServletException; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import java.io.IOException; +import lombok.RequiredArgsConstructor; +import org.springframework.lang.NonNull; +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.stereotype.Component; +import org.springframework.util.AntPathMatcher; +import org.springframework.web.filter.OncePerRequestFilter; + +@Component +@RequiredArgsConstructor +public class JwtAuthenticationFilter extends OncePerRequestFilter { + + private final JwtTokenProvider jwtTokenProvider; + private final UserDetailsService userDetailsService; + private static final AntPathMatcher PATH_MATCHER = new AntPathMatcher(); + private static final String[] EXCLUDE_PATHS = { + "/api/auth/signin", "/api/auth/refresh", "/api/auth/logout", "/api/members/*/password" + }; + + @Override + protected void doFilterInternal( + @NonNull HttpServletRequest request, + @NonNull HttpServletResponse response, + @NonNull FilterChain filterChain) + throws ServletException, IOException { + + String token = resolveToken(request); + + if (token != null && jwtTokenProvider.isValidToken(token)) { + String username = jwtTokenProvider.getSubject(token); + + UserDetails userDetails = userDetailsService.loadUserByUsername(username); + UsernamePasswordAuthenticationToken authentication = + new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities()); + SecurityContextHolder.getContext().setAuthentication(authentication); + } + + filterChain.doFilter(request, response); + } + + @Override + protected boolean shouldNotFilter(HttpServletRequest request) { + String path = request.getServletPath(); + + // JWT 필터를 타지 않게 할 URL 패턴들 + for (String pattern : EXCLUDE_PATHS) { + if (PATH_MATCHER.match(pattern, path)) { + return true; + } + } + return false; + } + + private String resolveToken(HttpServletRequest request) { + String bearer = request.getHeader("Authorization"); + if (bearer != null && bearer.startsWith("Bearer ")) { + return bearer.substring(7); + } + return null; + } +} diff --git a/src/main/java/com/kamco/cd/training/auth/JwtTokenProvider.java b/src/main/java/com/kamco/cd/training/auth/JwtTokenProvider.java index a56a431..07d1107 100644 --- a/src/main/java/com/kamco/cd/training/auth/JwtTokenProvider.java +++ b/src/main/java/com/kamco/cd/training/auth/JwtTokenProvider.java @@ -1,72 +1,72 @@ -package com.kamco.cd.training.auth; - -import io.jsonwebtoken.Claims; -import io.jsonwebtoken.Jws; -import io.jsonwebtoken.Jwts; -import io.jsonwebtoken.security.Keys; -import jakarta.annotation.PostConstruct; -import java.nio.charset.StandardCharsets; -import java.util.Date; -import javax.crypto.SecretKey; -import org.springframework.beans.factory.annotation.Value; -import org.springframework.stereotype.Component; - -@Component -public class JwtTokenProvider { - - @Value("${jwt.secret}") - private String secret; - - @Value("${jwt.access-token-validity-in-ms}") - private long accessTokenValidityInMs; - - @Value("${jwt.refresh-token-validity-in-ms}") - private long refreshTokenValidityInMs; - - private SecretKey key; - - @PostConstruct - public void init() { - // HS256용 SecretKey - this.key = Keys.hmacShaKeyFor(secret.getBytes(StandardCharsets.UTF_8)); - } - - public String createAccessToken(String subject) { - return createToken(subject, accessTokenValidityInMs); - } - - public String createRefreshToken(String subject) { - return createToken(subject, refreshTokenValidityInMs); - } - - private String createToken(String subject, long validityInMs) { - Date now = new Date(); - Date expiry = new Date(now.getTime() + validityInMs); - return Jwts.builder().subject(subject).issuedAt(now).expiration(expiry).signWith(key).compact(); - } - - public String getSubject(String token) { - var claims = parseClaims(token).getPayload(); - return claims.getSubject(); - } - - public boolean isValidToken(String token) { - try { - Jws claims = parseClaims(token); - return !claims.getPayload().getExpiration().before(new Date()); - } catch (Exception e) { - return false; - } - } - - private Jws parseClaims(String token) { - return Jwts.parser() - .verifyWith(key) // SecretKey 타입 - .build() - .parseSignedClaims(token); - } - - public long getRefreshTokenValidityInMs() { - return refreshTokenValidityInMs; - } -} +package com.kamco.cd.training.auth; + +import io.jsonwebtoken.Claims; +import io.jsonwebtoken.Jws; +import io.jsonwebtoken.Jwts; +import io.jsonwebtoken.security.Keys; +import jakarta.annotation.PostConstruct; +import java.nio.charset.StandardCharsets; +import java.util.Date; +import javax.crypto.SecretKey; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Component; + +@Component +public class JwtTokenProvider { + + @Value("${jwt.secret}") + private String secret; + + @Value("${jwt.access-token-validity-in-ms}") + private long accessTokenValidityInMs; + + @Value("${jwt.refresh-token-validity-in-ms}") + private long refreshTokenValidityInMs; + + private SecretKey key; + + @PostConstruct + public void init() { + // HS256용 SecretKey + this.key = Keys.hmacShaKeyFor(secret.getBytes(StandardCharsets.UTF_8)); + } + + public String createAccessToken(String subject) { + return createToken(subject, accessTokenValidityInMs); + } + + public String createRefreshToken(String subject) { + return createToken(subject, refreshTokenValidityInMs); + } + + private String createToken(String subject, long validityInMs) { + Date now = new Date(); + Date expiry = new Date(now.getTime() + validityInMs); + return Jwts.builder().subject(subject).issuedAt(now).expiration(expiry).signWith(key).compact(); + } + + public String getSubject(String token) { + var claims = parseClaims(token).getPayload(); + return claims.getSubject(); + } + + public boolean isValidToken(String token) { + try { + Jws claims = parseClaims(token); + return !claims.getPayload().getExpiration().before(new Date()); + } catch (Exception e) { + return false; + } + } + + private Jws parseClaims(String token) { + return Jwts.parser() + .verifyWith(key) // SecretKey 타입 + .build() + .parseSignedClaims(token); + } + + public long getRefreshTokenValidityInMs() { + return refreshTokenValidityInMs; + } +} diff --git a/src/main/java/com/kamco/cd/training/code/CommonCodeApiController.java b/src/main/java/com/kamco/cd/training/code/CommonCodeApiController.java index 83ecf04..484b96e 100644 --- a/src/main/java/com/kamco/cd/training/code/CommonCodeApiController.java +++ b/src/main/java/com/kamco/cd/training/code/CommonCodeApiController.java @@ -1,299 +1,299 @@ -package com.kamco.cd.training.code; - -import com.kamco.cd.training.code.dto.CommonCodeDto; -import com.kamco.cd.training.code.service.CommonCodeService; -import com.kamco.cd.training.common.utils.CommonCodeUtil; -import com.kamco.cd.training.common.utils.enums.CodeDto; -import com.kamco.cd.training.config.api.ApiResponseDto; -import io.swagger.v3.oas.annotations.Operation; -import io.swagger.v3.oas.annotations.media.Content; -import io.swagger.v3.oas.annotations.media.Schema; -import io.swagger.v3.oas.annotations.responses.ApiResponse; -import io.swagger.v3.oas.annotations.responses.ApiResponses; -import io.swagger.v3.oas.annotations.tags.Tag; -import jakarta.validation.Valid; -import java.util.List; -import java.util.Map; -import lombok.RequiredArgsConstructor; -import org.springframework.web.bind.annotation.*; - -@Tag(name = "공통코드 관리", description = "공통코드 관리 API") -@RestController -@RequiredArgsConstructor -@RequestMapping("/api/code") -public class CommonCodeApiController { - - private final CommonCodeService commonCodeService; - private final CommonCodeUtil commonCodeUtil; - - @Operation(summary = "목록 조회", description = "모든 공통코드 조회") - @ApiResponses( - value = { - @ApiResponse( - responseCode = "200", - description = "조회 성공", - content = - @Content( - mediaType = "application/json", - schema = @Schema(implementation = CommonCodeDto.Basic.class))), - @ApiResponse(responseCode = "404", description = "코드를 찾을 수 없음", content = @Content), - @ApiResponse(responseCode = "500", description = "서버 오류", content = @Content) - }) - @GetMapping - public ApiResponseDto> getFindAll() { - return ApiResponseDto.createOK(commonCodeService.getFindAll()); - } - - @Operation(summary = "단건 조회", description = "단건 조회") - @ApiResponses( - value = { - @ApiResponse( - responseCode = "200", - description = "조회 성공", - content = - @Content( - mediaType = "application/json", - schema = @Schema(implementation = CommonCodeDto.Basic.class))), - @ApiResponse(responseCode = "404", description = "코드를 찾을 수 없음", content = @Content), - @ApiResponse(responseCode = "500", description = "서버 오류", content = @Content) - }) - @GetMapping("/{id}") - public ApiResponseDto getOneById( - @io.swagger.v3.oas.annotations.parameters.RequestBody(description = "단건 조회", required = true) - @PathVariable - Long id) { - return ApiResponseDto.ok(commonCodeService.getOneById(id)); - } - - @Operation(summary = "저장", description = "공통코드를 저장 합니다.") - @ApiResponses( - value = { - @ApiResponse( - responseCode = "201", - description = "공통코드 저장 성공", - content = - @Content( - mediaType = "application/json", - schema = @Schema(implementation = Long.class))), - @ApiResponse(responseCode = "400", description = "잘못된 요청 데이터", content = @Content), - @ApiResponse(responseCode = "404", description = "코드를 찾을 수 없음", content = @Content), - @ApiResponse(responseCode = "500", description = "서버 오류", content = @Content) - }) - @PostMapping - public ApiResponseDto save( - @io.swagger.v3.oas.annotations.parameters.RequestBody( - description = "공통코드 생성 요청 정보", - required = true, - content = - @Content( - mediaType = "application/json", - schema = @Schema(implementation = CommonCodeDto.AddReq.class))) - @RequestBody - @Valid - CommonCodeDto.AddReq req) { - return ApiResponseDto.okObject(commonCodeService.save(req)); - } - - @Operation(summary = "수정", description = "공통코드를 수정 합니다.") - @ApiResponses( - value = { - @ApiResponse( - responseCode = "204", - description = "공통코드 수정 성공", - content = - @Content( - mediaType = "application/json", - schema = @Schema(implementation = Long.class))), - @ApiResponse(responseCode = "400", description = "잘못된 요청 데이터", content = @Content), - @ApiResponse(responseCode = "404", description = "코드를 찾을 수 없음", content = @Content), - @ApiResponse(responseCode = "500", description = "서버 오류", content = @Content) - }) - @PutMapping("/{id}") - public ApiResponseDto update( - @io.swagger.v3.oas.annotations.parameters.RequestBody( - description = "공통코드 수정 요청 정보", - required = true, - content = - @Content( - mediaType = "application/json", - schema = @Schema(implementation = CommonCodeDto.ModifyReq.class))) - @PathVariable - Long id, - @RequestBody @Valid CommonCodeDto.ModifyReq req) { - return ApiResponseDto.okObject(commonCodeService.update(id, req)); - } - - @Operation(summary = "삭제", description = "공통코드를 삭제 합니다.") - @ApiResponses( - value = { - @ApiResponse( - responseCode = "204", - description = "공통코드 삭제 성공", - content = - @Content( - mediaType = "application/json", - schema = @Schema(implementation = Long.class))), - @ApiResponse(responseCode = "400", description = "잘못된 요청 데이터", content = @Content), - @ApiResponse(responseCode = "404", description = "코드를 찾을 수 없음", content = @Content), - @ApiResponse(responseCode = "500", description = "서버 오류", content = @Content) - }) - @DeleteMapping("/{id}") - public ApiResponseDto remove( - @io.swagger.v3.oas.annotations.parameters.RequestBody( - description = "공통코드 삭제 요청 정보", - required = true) - @PathVariable - Long id) { - return ApiResponseDto.okObject(commonCodeService.removeCode(id)); - } - - @Operation(summary = "순서 변경", description = "공통코드 순서를 변경 합니다.") - @ApiResponses( - value = { - @ApiResponse( - responseCode = "204", - description = "공통코드 순서 변경 성공", - content = - @Content( - mediaType = "application/json", - schema = @Schema(implementation = Long.class))), - @ApiResponse(responseCode = "400", description = "잘못된 요청 데이터", content = @Content), - @ApiResponse(responseCode = "404", description = "코드를 찾을 수 없음", content = @Content), - @ApiResponse(responseCode = "500", description = "서버 오류", content = @Content) - }) - @PutMapping("/order") - public ApiResponseDto updateOrder( - @io.swagger.v3.oas.annotations.parameters.RequestBody( - description = "공통코드 순서변경 요청 정보", - required = true, - content = - @Content( - mediaType = "application/json", - schema = @Schema(implementation = CommonCodeDto.OrderReq.class))) - @RequestBody - @Valid - CommonCodeDto.OrderReq req) { - - return ApiResponseDto.okObject(commonCodeService.updateOrder(req)); - } - - @Operation(summary = "code 기반 조회", description = "code 기반 조회") - @ApiResponses( - value = { - @ApiResponse( - responseCode = "200", - description = "code 기반 조회 성공", - content = - @Content( - mediaType = "application/json", - schema = @Schema(implementation = Long.class))), - @ApiResponse(responseCode = "400", description = "잘못된 요청 데이터", content = @Content), - @ApiResponse(responseCode = "404", description = "코드를 찾을 수 없음", content = @Content), - @ApiResponse(responseCode = "500", description = "서버 오류", content = @Content) - }) - @GetMapping("/used") - public ApiResponseDto> getByCode( - @io.swagger.v3.oas.annotations.parameters.RequestBody( - description = "공통코드 순서변경 요청 정보", - required = true) - @RequestParam - String code) { - return ApiResponseDto.ok(commonCodeService.findByCode(code)); - } - - @Operation(summary = "변화탐지 분류 코드 목록", description = "변화탐지 분류 코드 목록(공통코드 기반)") - @GetMapping("/clazz") - public ApiResponseDto> getClasses() { - - // List list = - // Arrays.stream(DetectionClassification.values()) - // .sorted(Comparator.comparingInt(DetectionClassification::getOrder)) - // .map(Clazzes::new) - // .toList(); - - // 변화탐지 clazz API : enum -> 공통코드로 변경 - List list = - commonCodeUtil.getChildCodesByParentCode("0000").stream() - .map( - child -> - new CommonCodeDto.Clazzes( - child.getCode(), child.getName(), child.getOrder(), child.getProps2())) - .toList(); - - return ApiResponseDto.ok(list); - } - - @Operation(summary = "공통코드 중복여부 체크", description = "공통코드 중복여부 체크") - @ApiResponses( - value = { - @ApiResponse( - responseCode = "200", - description = "조회 성공", - content = - @Content( - mediaType = "application/json", - schema = @Schema(implementation = CommonCodeDto.Basic.class))), - @ApiResponse(responseCode = "404", description = "코드를 찾을 수 없음", content = @Content), - @ApiResponse(responseCode = "500", description = "서버 오류", content = @Content) - }) - @GetMapping("/check-duplicate") - public ApiResponseDto getCodeCheckDuplicate( - @io.swagger.v3.oas.annotations.parameters.RequestBody(description = "단건 조회", required = true) - @RequestParam(required = false) - Long parentId, - @RequestParam String code) { - return ApiResponseDto.okObject(commonCodeService.getCodeCheckDuplicate(parentId, code)); - } - - @Operation(summary = "코드 조회", description = "코드 리스트 조회") - @ApiResponses( - value = { - @ApiResponse( - responseCode = "200", - description = "코드 조회 성공", - content = - @Content( - mediaType = "application/json", - schema = @Schema(implementation = CodeDto.class))), - @ApiResponse(responseCode = "500", description = "서버 오류", content = @Content) - }) - @GetMapping("/type/codes") - public ApiResponseDto>> getTypeCodes() { - return ApiResponseDto.ok(commonCodeService.getTypeCodes()); - } - - @Operation(summary = "코드 단건 조회", description = "코드 조회") - @ApiResponses( - value = { - @ApiResponse( - responseCode = "200", - description = "코드 조회 성공", - content = - @Content( - mediaType = "application/json", - schema = @Schema(implementation = CodeDto.class))), - @ApiResponse(responseCode = "500", description = "서버 오류", content = @Content) - }) - @GetMapping("/type/{type}") - public ApiResponseDto> getTypeCode(@PathVariable String type) { - return ApiResponseDto.ok(commonCodeService.getTypeCode(type)); - } - - @Operation(summary = "캐시 초기화", description = "공통코드 캐시를 초기화합니다.") - @ApiResponses( - value = { - @ApiResponse( - responseCode = "200", - description = "캐시 초기화 성공", - content = - @Content( - mediaType = "application/json", - schema = @Schema(implementation = String.class))), - @ApiResponse(responseCode = "500", description = "서버 오류", content = @Content) - }) - @PostMapping("/cache/refresh") - public ApiResponseDto refreshCommonCodeCache() { - commonCodeService.refresh(); - return ApiResponseDto.ok("공통코드 캐시가 초기화 되었습니다."); - } -} +package com.kamco.cd.training.code; + +import com.kamco.cd.training.code.dto.CommonCodeDto; +import com.kamco.cd.training.code.service.CommonCodeService; +import com.kamco.cd.training.common.utils.CommonCodeUtil; +import com.kamco.cd.training.common.utils.enums.CodeDto; +import com.kamco.cd.training.config.api.ApiResponseDto; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.media.Content; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import io.swagger.v3.oas.annotations.responses.ApiResponses; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.validation.Valid; +import java.util.List; +import java.util.Map; +import lombok.RequiredArgsConstructor; +import org.springframework.web.bind.annotation.*; + +@Tag(name = "공통코드 관리", description = "공통코드 관리 API") +@RestController +@RequiredArgsConstructor +@RequestMapping("/api/code") +public class CommonCodeApiController { + + private final CommonCodeService commonCodeService; + private final CommonCodeUtil commonCodeUtil; + + @Operation(summary = "목록 조회", description = "모든 공통코드 조회") + @ApiResponses( + value = { + @ApiResponse( + responseCode = "200", + description = "조회 성공", + content = + @Content( + mediaType = "application/json", + schema = @Schema(implementation = CommonCodeDto.Basic.class))), + @ApiResponse(responseCode = "404", description = "코드를 찾을 수 없음", content = @Content), + @ApiResponse(responseCode = "500", description = "서버 오류", content = @Content) + }) + @GetMapping + public ApiResponseDto> getFindAll() { + return ApiResponseDto.createOK(commonCodeService.getFindAll()); + } + + @Operation(summary = "단건 조회", description = "단건 조회") + @ApiResponses( + value = { + @ApiResponse( + responseCode = "200", + description = "조회 성공", + content = + @Content( + mediaType = "application/json", + schema = @Schema(implementation = CommonCodeDto.Basic.class))), + @ApiResponse(responseCode = "404", description = "코드를 찾을 수 없음", content = @Content), + @ApiResponse(responseCode = "500", description = "서버 오류", content = @Content) + }) + @GetMapping("/{id}") + public ApiResponseDto getOneById( + @io.swagger.v3.oas.annotations.parameters.RequestBody(description = "단건 조회", required = true) + @PathVariable + Long id) { + return ApiResponseDto.ok(commonCodeService.getOneById(id)); + } + + @Operation(summary = "저장", description = "공통코드를 저장 합니다.") + @ApiResponses( + value = { + @ApiResponse( + responseCode = "201", + description = "공통코드 저장 성공", + content = + @Content( + mediaType = "application/json", + schema = @Schema(implementation = Long.class))), + @ApiResponse(responseCode = "400", description = "잘못된 요청 데이터", content = @Content), + @ApiResponse(responseCode = "404", description = "코드를 찾을 수 없음", content = @Content), + @ApiResponse(responseCode = "500", description = "서버 오류", content = @Content) + }) + @PostMapping + public ApiResponseDto save( + @io.swagger.v3.oas.annotations.parameters.RequestBody( + description = "공통코드 생성 요청 정보", + required = true, + content = + @Content( + mediaType = "application/json", + schema = @Schema(implementation = CommonCodeDto.AddReq.class))) + @RequestBody + @Valid + CommonCodeDto.AddReq req) { + return ApiResponseDto.okObject(commonCodeService.save(req)); + } + + @Operation(summary = "수정", description = "공통코드를 수정 합니다.") + @ApiResponses( + value = { + @ApiResponse( + responseCode = "204", + description = "공통코드 수정 성공", + content = + @Content( + mediaType = "application/json", + schema = @Schema(implementation = Long.class))), + @ApiResponse(responseCode = "400", description = "잘못된 요청 데이터", content = @Content), + @ApiResponse(responseCode = "404", description = "코드를 찾을 수 없음", content = @Content), + @ApiResponse(responseCode = "500", description = "서버 오류", content = @Content) + }) + @PutMapping("/{id}") + public ApiResponseDto update( + @io.swagger.v3.oas.annotations.parameters.RequestBody( + description = "공통코드 수정 요청 정보", + required = true, + content = + @Content( + mediaType = "application/json", + schema = @Schema(implementation = CommonCodeDto.ModifyReq.class))) + @PathVariable + Long id, + @RequestBody @Valid CommonCodeDto.ModifyReq req) { + return ApiResponseDto.okObject(commonCodeService.update(id, req)); + } + + @Operation(summary = "삭제", description = "공통코드를 삭제 합니다.") + @ApiResponses( + value = { + @ApiResponse( + responseCode = "204", + description = "공통코드 삭제 성공", + content = + @Content( + mediaType = "application/json", + schema = @Schema(implementation = Long.class))), + @ApiResponse(responseCode = "400", description = "잘못된 요청 데이터", content = @Content), + @ApiResponse(responseCode = "404", description = "코드를 찾을 수 없음", content = @Content), + @ApiResponse(responseCode = "500", description = "서버 오류", content = @Content) + }) + @DeleteMapping("/{id}") + public ApiResponseDto remove( + @io.swagger.v3.oas.annotations.parameters.RequestBody( + description = "공통코드 삭제 요청 정보", + required = true) + @PathVariable + Long id) { + return ApiResponseDto.okObject(commonCodeService.removeCode(id)); + } + + @Operation(summary = "순서 변경", description = "공통코드 순서를 변경 합니다.") + @ApiResponses( + value = { + @ApiResponse( + responseCode = "204", + description = "공통코드 순서 변경 성공", + content = + @Content( + mediaType = "application/json", + schema = @Schema(implementation = Long.class))), + @ApiResponse(responseCode = "400", description = "잘못된 요청 데이터", content = @Content), + @ApiResponse(responseCode = "404", description = "코드를 찾을 수 없음", content = @Content), + @ApiResponse(responseCode = "500", description = "서버 오류", content = @Content) + }) + @PutMapping("/order") + public ApiResponseDto updateOrder( + @io.swagger.v3.oas.annotations.parameters.RequestBody( + description = "공통코드 순서변경 요청 정보", + required = true, + content = + @Content( + mediaType = "application/json", + schema = @Schema(implementation = CommonCodeDto.OrderReq.class))) + @RequestBody + @Valid + CommonCodeDto.OrderReq req) { + + return ApiResponseDto.okObject(commonCodeService.updateOrder(req)); + } + + @Operation(summary = "code 기반 조회", description = "code 기반 조회") + @ApiResponses( + value = { + @ApiResponse( + responseCode = "200", + description = "code 기반 조회 성공", + content = + @Content( + mediaType = "application/json", + schema = @Schema(implementation = Long.class))), + @ApiResponse(responseCode = "400", description = "잘못된 요청 데이터", content = @Content), + @ApiResponse(responseCode = "404", description = "코드를 찾을 수 없음", content = @Content), + @ApiResponse(responseCode = "500", description = "서버 오류", content = @Content) + }) + @GetMapping("/used") + public ApiResponseDto> getByCode( + @io.swagger.v3.oas.annotations.parameters.RequestBody( + description = "공통코드 순서변경 요청 정보", + required = true) + @RequestParam + String code) { + return ApiResponseDto.ok(commonCodeService.findByCode(code)); + } + + @Operation(summary = "변화탐지 분류 코드 목록", description = "변화탐지 분류 코드 목록(공통코드 기반)") + @GetMapping("/clazz") + public ApiResponseDto> getClasses() { + + // List list = + // Arrays.stream(DetectionClassification.values()) + // .sorted(Comparator.comparingInt(DetectionClassification::getOrder)) + // .map(Clazzes::new) + // .toList(); + + // 변화탐지 clazz API : enum -> 공통코드로 변경 + List list = + commonCodeUtil.getChildCodesByParentCode("0000").stream() + .map( + child -> + new CommonCodeDto.Clazzes( + child.getCode(), child.getName(), child.getOrder(), child.getProps2())) + .toList(); + + return ApiResponseDto.ok(list); + } + + @Operation(summary = "공통코드 중복여부 체크", description = "공통코드 중복여부 체크") + @ApiResponses( + value = { + @ApiResponse( + responseCode = "200", + description = "조회 성공", + content = + @Content( + mediaType = "application/json", + schema = @Schema(implementation = CommonCodeDto.Basic.class))), + @ApiResponse(responseCode = "404", description = "코드를 찾을 수 없음", content = @Content), + @ApiResponse(responseCode = "500", description = "서버 오류", content = @Content) + }) + @GetMapping("/check-duplicate") + public ApiResponseDto getCodeCheckDuplicate( + @io.swagger.v3.oas.annotations.parameters.RequestBody(description = "단건 조회", required = true) + @RequestParam(required = false) + Long parentId, + @RequestParam String code) { + return ApiResponseDto.okObject(commonCodeService.getCodeCheckDuplicate(parentId, code)); + } + + @Operation(summary = "코드 조회", description = "코드 리스트 조회") + @ApiResponses( + value = { + @ApiResponse( + responseCode = "200", + description = "코드 조회 성공", + content = + @Content( + mediaType = "application/json", + schema = @Schema(implementation = CodeDto.class))), + @ApiResponse(responseCode = "500", description = "서버 오류", content = @Content) + }) + @GetMapping("/type/codes") + public ApiResponseDto>> getTypeCodes() { + return ApiResponseDto.ok(commonCodeService.getTypeCodes()); + } + + @Operation(summary = "코드 단건 조회", description = "코드 조회") + @ApiResponses( + value = { + @ApiResponse( + responseCode = "200", + description = "코드 조회 성공", + content = + @Content( + mediaType = "application/json", + schema = @Schema(implementation = CodeDto.class))), + @ApiResponse(responseCode = "500", description = "서버 오류", content = @Content) + }) + @GetMapping("/type/{type}") + public ApiResponseDto> getTypeCode(@PathVariable String type) { + return ApiResponseDto.ok(commonCodeService.getTypeCode(type)); + } + + @Operation(summary = "캐시 초기화", description = "공통코드 캐시를 초기화합니다.") + @ApiResponses( + value = { + @ApiResponse( + responseCode = "200", + description = "캐시 초기화 성공", + content = + @Content( + mediaType = "application/json", + schema = @Schema(implementation = String.class))), + @ApiResponse(responseCode = "500", description = "서버 오류", content = @Content) + }) + @PostMapping("/cache/refresh") + public ApiResponseDto refreshCommonCodeCache() { + commonCodeService.refresh(); + return ApiResponseDto.ok("공통코드 캐시가 초기화 되었습니다."); + } +} diff --git a/src/main/java/com/kamco/cd/training/code/dto/CommonCodeDto.java b/src/main/java/com/kamco/cd/training/code/dto/CommonCodeDto.java index 5e836ac..1eddfbe 100644 --- a/src/main/java/com/kamco/cd/training/code/dto/CommonCodeDto.java +++ b/src/main/java/com/kamco/cd/training/code/dto/CommonCodeDto.java @@ -1,179 +1,179 @@ -package com.kamco.cd.training.code.dto; - -import com.fasterxml.jackson.databind.annotation.JsonDeserialize; -import com.fasterxml.jackson.databind.annotation.JsonSerialize; -import com.kamco.cd.training.common.utils.html.HtmlEscapeDeserializer; -import com.kamco.cd.training.common.utils.html.HtmlUnescapeSerializer; -import com.kamco.cd.training.common.utils.interfaces.JsonFormatDttm; -import io.swagger.v3.oas.annotations.media.Schema; -import jakarta.validation.constraints.NotEmpty; -import jakarta.validation.constraints.NotNull; -import java.time.ZonedDateTime; -import java.util.List; -import lombok.AllArgsConstructor; -import lombok.Getter; -import lombok.NoArgsConstructor; -import lombok.Setter; -import org.springframework.data.domain.PageRequest; -import org.springframework.data.domain.Pageable; -import org.springframework.data.domain.Sort; - -public class CommonCodeDto { - - @Schema(name = "CodeAddReq", description = "공통코드 저장 정보") - @Getter - @Setter - @NoArgsConstructor - @AllArgsConstructor - public static class AddReq { - - @NotEmpty private String code; - @NotEmpty private String name; - private String description; - private int order; - private boolean used; - private Long parentId; - - @JsonDeserialize(using = HtmlEscapeDeserializer.class) - private String props1; - - @JsonDeserialize(using = HtmlEscapeDeserializer.class) - private String props2; - - @JsonDeserialize(using = HtmlEscapeDeserializer.class) - private String props3; - } - - @Schema(name = "CodeModifyReq", description = "공통코드 수정 정보") - @Getter - @Setter - @NoArgsConstructor - @AllArgsConstructor - public static class ModifyReq { - @NotEmpty private String name; - private String description; - private boolean used; - - @JsonDeserialize(using = HtmlEscapeDeserializer.class) - private String props1; - - @JsonDeserialize(using = HtmlEscapeDeserializer.class) - private String props2; - - @JsonDeserialize(using = HtmlEscapeDeserializer.class) - private String props3; - } - - @Schema(name = "CodeOrderReq", description = "공통코드 순서 변경 정보") - @Getter - @Setter - @NoArgsConstructor - @AllArgsConstructor - public static class OrderReq { - @NotNull private Long id; - @NotNull private Integer order; - } - - @Schema(name = "CommonCode Basic", description = "공통코드 기본 정보") - @Getter - public static class Basic { - - private Long id; - private String code; - private String description; - private String name; - private Integer order; - private Boolean used; - private Boolean deleted; - private List children; - - @JsonFormatDttm private ZonedDateTime createdDttm; - - @JsonFormatDttm private ZonedDateTime updatedDttm; - - @JsonSerialize(using = HtmlUnescapeSerializer.class) - private String props1; - - @JsonSerialize(using = HtmlUnescapeSerializer.class) - private String props2; - - @JsonSerialize(using = HtmlUnescapeSerializer.class) - private String props3; - - @JsonFormatDttm private ZonedDateTime deletedDttm; - - public Basic( - Long id, - String code, - String description, - String name, - Integer order, - Boolean used, - Boolean deleted, - List children, - ZonedDateTime createdDttm, - ZonedDateTime updatedDttm, - String props1, - String props2, - String props3, - ZonedDateTime deletedDttm) { - this.id = id; - this.code = code; - this.description = description; - this.name = name; - this.order = order; - this.used = used; - this.deleted = deleted; - this.children = children; - this.createdDttm = createdDttm; - this.updatedDttm = updatedDttm; - this.props1 = props1; - this.props2 = props2; - this.props3 = props3; - this.deletedDttm = deletedDttm; - } - } - - @Schema(name = "SearchReq", description = "검색 요청") - @Getter - @Setter - @NoArgsConstructor - @AllArgsConstructor - public static class SearchReq { - - // 검색 조건 - private String name; - - // 페이징 파라미터 - private int page = 0; - private int size = 20; - private String sort; - - public Pageable toPageable() { - if (sort != null && !sort.isEmpty()) { - String[] sortParams = sort.split(","); - String property = sortParams[0]; - Sort.Direction direction = - sortParams.length > 1 ? Sort.Direction.fromString(sortParams[1]) : Sort.Direction.ASC; - return PageRequest.of(page, size, Sort.by(direction, property)); - } - return PageRequest.of(page, size); - } - } - - @Getter - public static class Clazzes { - - private String code; - private String name; - private Integer order; - private String color; - - public Clazzes(String code, String name, Integer order, String color) { - this.code = code; - this.name = name; - this.order = order; - this.color = color; - } - } -} +package com.kamco.cd.training.code.dto; + +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.fasterxml.jackson.databind.annotation.JsonSerialize; +import com.kamco.cd.training.common.utils.html.HtmlEscapeDeserializer; +import com.kamco.cd.training.common.utils.html.HtmlUnescapeSerializer; +import com.kamco.cd.training.common.utils.interfaces.JsonFormatDttm; +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotEmpty; +import jakarta.validation.constraints.NotNull; +import java.time.ZonedDateTime; +import java.util.List; +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Pageable; +import org.springframework.data.domain.Sort; + +public class CommonCodeDto { + + @Schema(name = "CodeAddReq", description = "공통코드 저장 정보") + @Getter + @Setter + @NoArgsConstructor + @AllArgsConstructor + public static class AddReq { + + @NotEmpty private String code; + @NotEmpty private String name; + private String description; + private int order; + private boolean used; + private Long parentId; + + @JsonDeserialize(using = HtmlEscapeDeserializer.class) + private String props1; + + @JsonDeserialize(using = HtmlEscapeDeserializer.class) + private String props2; + + @JsonDeserialize(using = HtmlEscapeDeserializer.class) + private String props3; + } + + @Schema(name = "CodeModifyReq", description = "공통코드 수정 정보") + @Getter + @Setter + @NoArgsConstructor + @AllArgsConstructor + public static class ModifyReq { + @NotEmpty private String name; + private String description; + private boolean used; + + @JsonDeserialize(using = HtmlEscapeDeserializer.class) + private String props1; + + @JsonDeserialize(using = HtmlEscapeDeserializer.class) + private String props2; + + @JsonDeserialize(using = HtmlEscapeDeserializer.class) + private String props3; + } + + @Schema(name = "CodeOrderReq", description = "공통코드 순서 변경 정보") + @Getter + @Setter + @NoArgsConstructor + @AllArgsConstructor + public static class OrderReq { + @NotNull private Long id; + @NotNull private Integer order; + } + + @Schema(name = "CommonCode Basic", description = "공통코드 기본 정보") + @Getter + public static class Basic { + + private Long id; + private String code; + private String description; + private String name; + private Integer order; + private Boolean used; + private Boolean deleted; + private List children; + + @JsonFormatDttm private ZonedDateTime createdDttm; + + @JsonFormatDttm private ZonedDateTime updatedDttm; + + @JsonSerialize(using = HtmlUnescapeSerializer.class) + private String props1; + + @JsonSerialize(using = HtmlUnescapeSerializer.class) + private String props2; + + @JsonSerialize(using = HtmlUnescapeSerializer.class) + private String props3; + + @JsonFormatDttm private ZonedDateTime deletedDttm; + + public Basic( + Long id, + String code, + String description, + String name, + Integer order, + Boolean used, + Boolean deleted, + List children, + ZonedDateTime createdDttm, + ZonedDateTime updatedDttm, + String props1, + String props2, + String props3, + ZonedDateTime deletedDttm) { + this.id = id; + this.code = code; + this.description = description; + this.name = name; + this.order = order; + this.used = used; + this.deleted = deleted; + this.children = children; + this.createdDttm = createdDttm; + this.updatedDttm = updatedDttm; + this.props1 = props1; + this.props2 = props2; + this.props3 = props3; + this.deletedDttm = deletedDttm; + } + } + + @Schema(name = "SearchReq", description = "검색 요청") + @Getter + @Setter + @NoArgsConstructor + @AllArgsConstructor + public static class SearchReq { + + // 검색 조건 + private String name; + + // 페이징 파라미터 + private int page = 0; + private int size = 20; + private String sort; + + public Pageable toPageable() { + if (sort != null && !sort.isEmpty()) { + String[] sortParams = sort.split(","); + String property = sortParams[0]; + Sort.Direction direction = + sortParams.length > 1 ? Sort.Direction.fromString(sortParams[1]) : Sort.Direction.ASC; + return PageRequest.of(page, size, Sort.by(direction, property)); + } + return PageRequest.of(page, size); + } + } + + @Getter + public static class Clazzes { + + private String code; + private String name; + private Integer order; + private String color; + + public Clazzes(String code, String name, Integer order, String color) { + this.code = code; + this.name = name; + this.order = order; + this.color = color; + } + } +} diff --git a/src/main/java/com/kamco/cd/training/code/service/CommonCodeService.java b/src/main/java/com/kamco/cd/training/code/service/CommonCodeService.java index 0041e9a..4787c2e 100644 --- a/src/main/java/com/kamco/cd/training/code/service/CommonCodeService.java +++ b/src/main/java/com/kamco/cd/training/code/service/CommonCodeService.java @@ -1,155 +1,155 @@ -package com.kamco.cd.training.code.service; - -import com.kamco.cd.training.code.dto.CommonCodeDto.AddReq; -import com.kamco.cd.training.code.dto.CommonCodeDto.Basic; -import com.kamco.cd.training.code.dto.CommonCodeDto.ModifyReq; -import com.kamco.cd.training.code.dto.CommonCodeDto.OrderReq; -import com.kamco.cd.training.common.utils.enums.CodeDto; -import com.kamco.cd.training.common.utils.enums.Enums; -import com.kamco.cd.training.config.api.ApiResponseDto; -import com.kamco.cd.training.postgres.core.CommonCodeCoreService; -import java.util.List; -import java.util.Map; -import java.util.Optional; -import lombok.RequiredArgsConstructor; -import org.springframework.cache.annotation.CacheEvict; -import org.springframework.cache.annotation.Cacheable; -import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Transactional; - -// training 서버는 Redis 사용하지 않고 Spring Boot 메모리 캐시를 사용함 -// => org.springframework.cache.annotation.Cacheable - -@Service -@RequiredArgsConstructor -@Transactional(readOnly = true) -public class CommonCodeService { - - private final CommonCodeCoreService commonCodeCoreService; - - /** - * 공통코드 목록 조회 - * - * @return 모튼 코드 정보 - */ - @Cacheable("trainCommonCodes") - public List getFindAll() { - return commonCodeCoreService.findAll(); - } - - /** - * 공통코드 단건 조회 - * - * @param id - * @return 코드 아이디로 조회한 코드 정보 - */ - public Basic getOneById(Long id) { - return commonCodeCoreService.getOneById(id); - } - - /** - * 공통코드 생성 요청 - * - * @param req 생성요청 정보 - * @return 생성된 코드 id - */ - @Transactional - @CacheEvict(value = "trainCommonCodes", allEntries = true) - public ApiResponseDto.ResponseObj save(AddReq req) { - return commonCodeCoreService.save(req); - } - - /** - * 공통코드 수정 요청 - * - * @param id 코드 아이디 - * @param req 수정요청 정보 - */ - @Transactional - @CacheEvict(value = "trainCommonCodes", allEntries = true) - public ApiResponseDto.ResponseObj update(Long id, ModifyReq req) { - return commonCodeCoreService.update(id, req); - } - - /** - * 공통코드 삭제 처리 - * - * @param id 코드 아이디 - */ - @Transactional - @CacheEvict(value = "trainCommonCodes", allEntries = true) - public ApiResponseDto.ResponseObj removeCode(Long id) { - return commonCodeCoreService.removeCode(id); - } - - /** - * 공통코드 순서 변경 - * - * @param req id, order 정보를 가진 List - */ - @Transactional - @CacheEvict(value = "trainCommonCodes", allEntries = true) - public ApiResponseDto.ResponseObj updateOrder(OrderReq req) { - return commonCodeCoreService.updateOrder(req); - } - - /** - * 코드기반 조회 - * - * @param code 코드 - * @return 코드로 조회한 공통코드 정보 - */ - public List findByCode(String code) { - return commonCodeCoreService.findByCode(code); - } - - /** - * 중복 체크 - * - * @param parentId - * @param code - * @return - */ - public ApiResponseDto.ResponseObj getCodeCheckDuplicate(Long parentId, String code) { - return commonCodeCoreService.getCodeCheckDuplicate(parentId, code); - } - - /** - * 공통코드 이름 조회 - * - * @param parentCodeCd 상위 코드 - * @param childCodeCd 하위 코드 - * @return 공통코드명 - */ - public Optional getCode(String parentCodeCd, String childCodeCd) { - return commonCodeCoreService.getCode(parentCodeCd, childCodeCd); - } - - /** - * 공통코드 이름 조회 - * - * @param parentCodeCd 상위 코드 - * @param childCodeCd 하위 코드 - * @return 공통코드명 - */ - public Optional getTypeCode(String parentCodeCd, String childCodeCd) { - return commonCodeCoreService.getCode(parentCodeCd, childCodeCd); - } - - public List getTypeCode(String type) { - return Enums.getCodes(type); - } - - /** - * 공통코드 리스트 조회 - * - * @return - */ - public Map> getTypeCodes() { - return Enums.getAllCodes(); - } - - /** 메모리 캐시 초기화 */ - @CacheEvict(value = "trainCommonCodes", allEntries = true) - public void refresh() {} -} +package com.kamco.cd.training.code.service; + +import com.kamco.cd.training.code.dto.CommonCodeDto.AddReq; +import com.kamco.cd.training.code.dto.CommonCodeDto.Basic; +import com.kamco.cd.training.code.dto.CommonCodeDto.ModifyReq; +import com.kamco.cd.training.code.dto.CommonCodeDto.OrderReq; +import com.kamco.cd.training.common.utils.enums.CodeDto; +import com.kamco.cd.training.common.utils.enums.Enums; +import com.kamco.cd.training.config.api.ApiResponseDto; +import com.kamco.cd.training.postgres.core.CommonCodeCoreService; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import lombok.RequiredArgsConstructor; +import org.springframework.cache.annotation.CacheEvict; +import org.springframework.cache.annotation.Cacheable; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +// training 서버는 Redis 사용하지 않고 Spring Boot 메모리 캐시를 사용함 +// => org.springframework.cache.annotation.Cacheable + +@Service +@RequiredArgsConstructor +@Transactional(readOnly = true) +public class CommonCodeService { + + private final CommonCodeCoreService commonCodeCoreService; + + /** + * 공통코드 목록 조회 + * + * @return 모튼 코드 정보 + */ + @Cacheable("trainCommonCodes") + public List getFindAll() { + return commonCodeCoreService.findAll(); + } + + /** + * 공통코드 단건 조회 + * + * @param id + * @return 코드 아이디로 조회한 코드 정보 + */ + public Basic getOneById(Long id) { + return commonCodeCoreService.getOneById(id); + } + + /** + * 공통코드 생성 요청 + * + * @param req 생성요청 정보 + * @return 생성된 코드 id + */ + @Transactional + @CacheEvict(value = "trainCommonCodes", allEntries = true) + public ApiResponseDto.ResponseObj save(AddReq req) { + return commonCodeCoreService.save(req); + } + + /** + * 공통코드 수정 요청 + * + * @param id 코드 아이디 + * @param req 수정요청 정보 + */ + @Transactional + @CacheEvict(value = "trainCommonCodes", allEntries = true) + public ApiResponseDto.ResponseObj update(Long id, ModifyReq req) { + return commonCodeCoreService.update(id, req); + } + + /** + * 공통코드 삭제 처리 + * + * @param id 코드 아이디 + */ + @Transactional + @CacheEvict(value = "trainCommonCodes", allEntries = true) + public ApiResponseDto.ResponseObj removeCode(Long id) { + return commonCodeCoreService.removeCode(id); + } + + /** + * 공통코드 순서 변경 + * + * @param req id, order 정보를 가진 List + */ + @Transactional + @CacheEvict(value = "trainCommonCodes", allEntries = true) + public ApiResponseDto.ResponseObj updateOrder(OrderReq req) { + return commonCodeCoreService.updateOrder(req); + } + + /** + * 코드기반 조회 + * + * @param code 코드 + * @return 코드로 조회한 공통코드 정보 + */ + public List findByCode(String code) { + return commonCodeCoreService.findByCode(code); + } + + /** + * 중복 체크 + * + * @param parentId + * @param code + * @return + */ + public ApiResponseDto.ResponseObj getCodeCheckDuplicate(Long parentId, String code) { + return commonCodeCoreService.getCodeCheckDuplicate(parentId, code); + } + + /** + * 공통코드 이름 조회 + * + * @param parentCodeCd 상위 코드 + * @param childCodeCd 하위 코드 + * @return 공통코드명 + */ + public Optional getCode(String parentCodeCd, String childCodeCd) { + return commonCodeCoreService.getCode(parentCodeCd, childCodeCd); + } + + /** + * 공통코드 이름 조회 + * + * @param parentCodeCd 상위 코드 + * @param childCodeCd 하위 코드 + * @return 공통코드명 + */ + public Optional getTypeCode(String parentCodeCd, String childCodeCd) { + return commonCodeCoreService.getCode(parentCodeCd, childCodeCd); + } + + public List getTypeCode(String type) { + return Enums.getCodes(type); + } + + /** + * 공통코드 리스트 조회 + * + * @return + */ + public Map> getTypeCodes() { + return Enums.getAllCodes(); + } + + /** 메모리 캐시 초기화 */ + @CacheEvict(value = "trainCommonCodes", allEntries = true) + public void refresh() {} +} diff --git a/src/main/java/com/kamco/cd/training/common/enums/DeployTargetType.java b/src/main/java/com/kamco/cd/training/common/enums/DeployTargetType.java index 6839385..2975f05 100644 --- a/src/main/java/com/kamco/cd/training/common/enums/DeployTargetType.java +++ b/src/main/java/com/kamco/cd/training/common/enums/DeployTargetType.java @@ -1,19 +1,19 @@ -package com.kamco.cd.training.common.enums; - -import com.kamco.cd.training.common.utils.enums.CodeExpose; -import com.kamco.cd.training.common.utils.enums.EnumType; -import lombok.AllArgsConstructor; -import lombok.Getter; - -@CodeExpose -@Getter -@AllArgsConstructor -public enum DeployTargetType implements EnumType { - // @formatter:off - GUKU("GUKU", "국토교통부"), - PROD("PROD", "운영계"); - // @formatter:on - - private final String id; - private final String text; -} +package com.kamco.cd.training.common.enums; + +import com.kamco.cd.training.common.utils.enums.CodeExpose; +import com.kamco.cd.training.common.utils.enums.EnumType; +import lombok.AllArgsConstructor; +import lombok.Getter; + +@CodeExpose +@Getter +@AllArgsConstructor +public enum DeployTargetType implements EnumType { + // @formatter:off + GUKU("GUKU", "국토교통부"), + PROD("PROD", "운영계"); + // @formatter:on + + private final String id; + private final String text; +} diff --git a/src/main/java/com/kamco/cd/training/common/enums/DetectionClassification.java b/src/main/java/com/kamco/cd/training/common/enums/DetectionClassification.java index ca4ec49..9551feb 100644 --- a/src/main/java/com/kamco/cd/training/common/enums/DetectionClassification.java +++ b/src/main/java/com/kamco/cd/training/common/enums/DetectionClassification.java @@ -1,55 +1,55 @@ -package com.kamco.cd.training.common.enums; - -import lombok.AllArgsConstructor; -import lombok.Getter; - -@Getter -@AllArgsConstructor -public enum DetectionClassification { - BUILDING("building", "건물", 10), - CONTAINER("container", "컨테이너", 20), - FIELD("field", "경작지", 30), - FOREST("forest", "숲", 40), - GRASS("grass", "초지", 50), - GREENHOUSE("greenhouse", "비닐하우스", 60), - LAND("land", "일반토지", 70), - ORCHARD("orchard", "과수원", 80), - ROAD("road", "도로", 90), - STONE("stone", "모래/자갈", 100), - TANK("tank", "물탱크", 110), - TUMULUS("tumulus", "토분(무덤)", 120), - WASTE("waste", "폐기물", 130), - WATER("water", "물", 140), - ETC("ETC", "기타", 200); // For 'etc' (miscellaneous/other) - - private final String id; - private final String desc; - private final int order; - - /** - * Optional: Helper method to get the enum from a String, case-insensitive, or return ETC if not - * found. - */ - public static DetectionClassification fromString(String text) { - if (text == null || text.trim().isEmpty()) { - return ETC; - } - - try { - return DetectionClassification.valueOf(text.toUpperCase()); - } catch (IllegalArgumentException e) { - // If the string doesn't match any enum constant name, return ETC - return ETC; - } - } - - /** - * Desc 한글명 get 하기 - * - * @return - */ - public static String fromStrDesc(String text) { - DetectionClassification dtf = fromString(text); - return dtf.getDesc(); - } -} +package com.kamco.cd.training.common.enums; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +@Getter +@AllArgsConstructor +public enum DetectionClassification { + BUILDING("building", "건물", 10), + CONTAINER("container", "컨테이너", 20), + FIELD("field", "경작지", 30), + FOREST("forest", "숲", 40), + GRASS("grass", "초지", 50), + GREENHOUSE("greenhouse", "비닐하우스", 60), + LAND("land", "일반토지", 70), + ORCHARD("orchard", "과수원", 80), + ROAD("road", "도로", 90), + STONE("stone", "모래/자갈", 100), + TANK("tank", "물탱크", 110), + TUMULUS("tumulus", "토분(무덤)", 120), + WASTE("waste", "폐기물", 130), + WATER("water", "물", 140), + ETC("ETC", "기타", 200); // For 'etc' (miscellaneous/other) + + private final String id; + private final String desc; + private final int order; + + /** + * Optional: Helper method to get the enum from a String, case-insensitive, or return ETC if not + * found. + */ + public static DetectionClassification fromString(String text) { + if (text == null || text.trim().isEmpty()) { + return ETC; + } + + try { + return DetectionClassification.valueOf(text.toUpperCase()); + } catch (IllegalArgumentException e) { + // If the string doesn't match any enum constant name, return ETC + return ETC; + } + } + + /** + * Desc 한글명 get 하기 + * + * @return + */ + public static String fromStrDesc(String text) { + DetectionClassification dtf = fromString(text); + return dtf.getDesc(); + } +} diff --git a/src/main/java/com/kamco/cd/training/common/enums/LearnDataRegister.java b/src/main/java/com/kamco/cd/training/common/enums/LearnDataRegister.java index ddfa72b..79b9b4a 100644 --- a/src/main/java/com/kamco/cd/training/common/enums/LearnDataRegister.java +++ b/src/main/java/com/kamco/cd/training/common/enums/LearnDataRegister.java @@ -1,26 +1,26 @@ -package com.kamco.cd.training.common.enums; - -import com.kamco.cd.training.common.utils.enums.EnumType; -import lombok.AllArgsConstructor; -import lombok.Getter; - -@Getter -@AllArgsConstructor -public enum LearnDataRegister implements EnumType { - READY("준비"), - UPLOADING("업로드중"), - UPLOAD_FAILED("업로드 실패"), - COMPLETED("완료"); - - private final String desc; - - @Override - public String getId() { - return name(); - } - - @Override - public String getText() { - return desc; - } -} +package com.kamco.cd.training.common.enums; + +import com.kamco.cd.training.common.utils.enums.EnumType; +import lombok.AllArgsConstructor; +import lombok.Getter; + +@Getter +@AllArgsConstructor +public enum LearnDataRegister implements EnumType { + READY("준비"), + UPLOADING("업로드중"), + UPLOAD_FAILED("업로드 실패"), + COMPLETED("완료"); + + private final String desc; + + @Override + public String getId() { + return name(); + } + + @Override + public String getText() { + return desc; + } +} diff --git a/src/main/java/com/kamco/cd/training/common/enums/LearnDataType.java b/src/main/java/com/kamco/cd/training/common/enums/LearnDataType.java index 65dd1a2..cd24fe6 100644 --- a/src/main/java/com/kamco/cd/training/common/enums/LearnDataType.java +++ b/src/main/java/com/kamco/cd/training/common/enums/LearnDataType.java @@ -1,26 +1,26 @@ -package com.kamco.cd.training.common.enums; - -import com.kamco.cd.training.common.utils.enums.CodeExpose; -import com.kamco.cd.training.common.utils.enums.EnumType; -import lombok.AllArgsConstructor; -import lombok.Getter; - -@CodeExpose -@Getter -@AllArgsConstructor -public enum LearnDataType implements EnumType { - DELIVER("납품"), - PRODUCTION("제작"); - - private final String desc; - - @Override - public String getId() { - return name(); - } - - @Override - public String getText() { - return desc; - } -} +package com.kamco.cd.training.common.enums; + +import com.kamco.cd.training.common.utils.enums.CodeExpose; +import com.kamco.cd.training.common.utils.enums.EnumType; +import lombok.AllArgsConstructor; +import lombok.Getter; + +@CodeExpose +@Getter +@AllArgsConstructor +public enum LearnDataType implements EnumType { + DELIVER("납품"), + PRODUCTION("제작"); + + private final String desc; + + @Override + public String getId() { + return name(); + } + + @Override + public String getText() { + return desc; + } +} diff --git a/src/main/java/com/kamco/cd/training/common/enums/ModelMngStatusType.java b/src/main/java/com/kamco/cd/training/common/enums/ModelMngStatusType.java index abd2139..438d876 100644 --- a/src/main/java/com/kamco/cd/training/common/enums/ModelMngStatusType.java +++ b/src/main/java/com/kamco/cd/training/common/enums/ModelMngStatusType.java @@ -1,27 +1,27 @@ -package com.kamco.cd.training.common.enums; - -import com.kamco.cd.training.common.utils.enums.CodeExpose; -import com.kamco.cd.training.common.utils.enums.EnumType; -import lombok.AllArgsConstructor; -import lombok.Getter; - -@CodeExpose -@Getter -@AllArgsConstructor -public enum ModelMngStatusType implements EnumType { - READY("준비"), - IN_PROGRESS("진행중"), - COMPLETED("완료"); - - private String desc; - - @Override - public String getId() { - return name(); - } - - @Override - public String getText() { - return desc; - } -} +package com.kamco.cd.training.common.enums; + +import com.kamco.cd.training.common.utils.enums.CodeExpose; +import com.kamco.cd.training.common.utils.enums.EnumType; +import lombok.AllArgsConstructor; +import lombok.Getter; + +@CodeExpose +@Getter +@AllArgsConstructor +public enum ModelMngStatusType implements EnumType { + READY("준비"), + IN_PROGRESS("진행중"), + COMPLETED("완료"); + + private String desc; + + @Override + public String getId() { + return name(); + } + + @Override + public String getText() { + return desc; + } +} diff --git a/src/main/java/com/kamco/cd/training/common/enums/ProcessStepType.java b/src/main/java/com/kamco/cd/training/common/enums/ProcessStepType.java index 17ecf51..b2c55d5 100644 --- a/src/main/java/com/kamco/cd/training/common/enums/ProcessStepType.java +++ b/src/main/java/com/kamco/cd/training/common/enums/ProcessStepType.java @@ -1,20 +1,20 @@ -package com.kamco.cd.training.common.enums; - -import com.kamco.cd.training.common.utils.enums.CodeExpose; -import com.kamco.cd.training.common.utils.enums.EnumType; -import lombok.AllArgsConstructor; -import lombok.Getter; - -@CodeExpose -@Getter -@AllArgsConstructor -public enum ProcessStepType implements EnumType { - // @formatter:off - STEP1("STEP1", "학습 중"), - STEP2("STEP2", "테스트 중"), - STEP3("STEP3", "완료"); - // @formatter:on - - private final String id; - private final String text; -} +package com.kamco.cd.training.common.enums; + +import com.kamco.cd.training.common.utils.enums.CodeExpose; +import com.kamco.cd.training.common.utils.enums.EnumType; +import lombok.AllArgsConstructor; +import lombok.Getter; + +@CodeExpose +@Getter +@AllArgsConstructor +public enum ProcessStepType implements EnumType { + // @formatter:off + STEP1("STEP1", "학습 중"), + STEP2("STEP2", "테스트 중"), + STEP3("STEP3", "완료"); + // @formatter:on + + private final String id; + private final String text; +} diff --git a/src/main/java/com/kamco/cd/training/common/enums/RoleType.java b/src/main/java/com/kamco/cd/training/common/enums/RoleType.java index bcc2f0c..cb04df7 100644 --- a/src/main/java/com/kamco/cd/training/common/enums/RoleType.java +++ b/src/main/java/com/kamco/cd/training/common/enums/RoleType.java @@ -1,25 +1,25 @@ -package com.kamco.cd.training.common.enums; - -import com.kamco.cd.training.common.utils.enums.EnumType; -import lombok.AllArgsConstructor; -import lombok.Getter; - -@Getter -@AllArgsConstructor -public enum RoleType implements EnumType { - ROLE_ADMIN("시스템 관리자"), - ROLE_LABELER("라벨러"), - ROLE_REVIEWER("검수자"); - - private final String desc; - - @Override - public String getId() { - return name(); - } - - @Override - public String getText() { - return desc; - } -} +package com.kamco.cd.training.common.enums; + +import com.kamco.cd.training.common.utils.enums.EnumType; +import lombok.AllArgsConstructor; +import lombok.Getter; + +@Getter +@AllArgsConstructor +public enum RoleType implements EnumType { + ROLE_ADMIN("시스템 관리자"), + ROLE_LABELER("라벨러"), + ROLE_REVIEWER("검수자"); + + private final String desc; + + @Override + public String getId() { + return name(); + } + + @Override + public String getText() { + return desc; + } +} diff --git a/src/main/java/com/kamco/cd/training/common/enums/StatusType.java b/src/main/java/com/kamco/cd/training/common/enums/StatusType.java index af86ac5..b0a1e6d 100644 --- a/src/main/java/com/kamco/cd/training/common/enums/StatusType.java +++ b/src/main/java/com/kamco/cd/training/common/enums/StatusType.java @@ -1,25 +1,25 @@ -package com.kamco.cd.training.common.enums; - -import com.kamco.cd.training.common.utils.enums.EnumType; -import lombok.AllArgsConstructor; -import lombok.Getter; - -@Getter -@AllArgsConstructor -public enum StatusType implements EnumType { - ACTIVE("사용"), - INACTIVE("미사용"), - PENDING("계정등록"); - - private final String desc; - - @Override - public String getId() { - return name(); - } - - @Override - public String getText() { - return desc; - } -} +package com.kamco.cd.training.common.enums; + +import com.kamco.cd.training.common.utils.enums.EnumType; +import lombok.AllArgsConstructor; +import lombok.Getter; + +@Getter +@AllArgsConstructor +public enum StatusType implements EnumType { + ACTIVE("사용"), + INACTIVE("미사용"), + PENDING("계정등록"); + + private final String desc; + + @Override + public String getId() { + return name(); + } + + @Override + public String getText() { + return desc; + } +} diff --git a/src/main/java/com/kamco/cd/training/common/enums/TrainStatusType.java b/src/main/java/com/kamco/cd/training/common/enums/TrainStatusType.java index e5da42f..8a3db27 100644 --- a/src/main/java/com/kamco/cd/training/common/enums/TrainStatusType.java +++ b/src/main/java/com/kamco/cd/training/common/enums/TrainStatusType.java @@ -1,22 +1,22 @@ -package com.kamco.cd.training.common.enums; - -import com.kamco.cd.training.common.utils.enums.CodeExpose; -import com.kamco.cd.training.common.utils.enums.EnumType; -import lombok.AllArgsConstructor; -import lombok.Getter; - -@CodeExpose -@Getter -@AllArgsConstructor -public enum TrainStatusType implements EnumType { - // @formatter:off - READY("READY", "대기"), - ING("ING", "진행중"), - COMPLETED("COMPLETED", "완료"), - STOPPED("STOPPED", "중단됨"), - ERROR("ERROR", "오류"); - // @formatter:on - - private final String id; - private final String text; -} +package com.kamco.cd.training.common.enums; + +import com.kamco.cd.training.common.utils.enums.CodeExpose; +import com.kamco.cd.training.common.utils.enums.EnumType; +import lombok.AllArgsConstructor; +import lombok.Getter; + +@CodeExpose +@Getter +@AllArgsConstructor +public enum TrainStatusType implements EnumType { + // @formatter:off + READY("READY", "대기"), + ING("ING", "진행중"), + COMPLETED("COMPLETED", "완료"), + STOPPED("STOPPED", "중단됨"), + ERROR("ERROR", "오류"); + // @formatter:on + + private final String id; + private final String text; +} diff --git a/src/main/java/com/kamco/cd/training/common/enums/error/AuthErrorCode.java b/src/main/java/com/kamco/cd/training/common/enums/error/AuthErrorCode.java index 8763298..d6b414b 100644 --- a/src/main/java/com/kamco/cd/training/common/enums/error/AuthErrorCode.java +++ b/src/main/java/com/kamco/cd/training/common/enums/error/AuthErrorCode.java @@ -1,26 +1,26 @@ -package com.kamco.cd.training.common.enums.error; - -import com.kamco.cd.training.common.utils.ErrorCode; -import lombok.Getter; -import org.springframework.http.HttpStatus; - -@Getter -public enum AuthErrorCode implements ErrorCode { - LOGIN_ID_NOT_FOUND("LOGIN_ID_NOT_FOUND", HttpStatus.UNAUTHORIZED), - - LOGIN_PASSWORD_MISMATCH("LOGIN_PASSWORD_MISMATCH", HttpStatus.UNAUTHORIZED), - - LOGIN_PASSWORD_EXCEEDED("LOGIN_PASSWORD_EXCEEDED", HttpStatus.UNAUTHORIZED), - - REFRESH_TOKEN_EXPIRED_OR_REVOKED("REFRESH_TOKEN_EXPIRED_OR_REVOKED", HttpStatus.UNAUTHORIZED), - - REFRESH_TOKEN_MISMATCH("REFRESH_TOKEN_MISMATCH", HttpStatus.UNAUTHORIZED); - - private final String code; - private final HttpStatus status; - - AuthErrorCode(String code, HttpStatus status) { - this.code = code; - this.status = status; - } -} +package com.kamco.cd.training.common.enums.error; + +import com.kamco.cd.training.common.utils.ErrorCode; +import lombok.Getter; +import org.springframework.http.HttpStatus; + +@Getter +public enum AuthErrorCode implements ErrorCode { + LOGIN_ID_NOT_FOUND("LOGIN_ID_NOT_FOUND", HttpStatus.UNAUTHORIZED), + + LOGIN_PASSWORD_MISMATCH("LOGIN_PASSWORD_MISMATCH", HttpStatus.UNAUTHORIZED), + + LOGIN_PASSWORD_EXCEEDED("LOGIN_PASSWORD_EXCEEDED", HttpStatus.UNAUTHORIZED), + + REFRESH_TOKEN_EXPIRED_OR_REVOKED("REFRESH_TOKEN_EXPIRED_OR_REVOKED", HttpStatus.UNAUTHORIZED), + + REFRESH_TOKEN_MISMATCH("REFRESH_TOKEN_MISMATCH", HttpStatus.UNAUTHORIZED); + + private final String code; + private final HttpStatus status; + + AuthErrorCode(String code, HttpStatus status) { + this.code = code; + this.status = status; + } +} diff --git a/src/main/java/com/kamco/cd/training/common/exception/BadRequestException.java b/src/main/java/com/kamco/cd/training/common/exception/BadRequestException.java index a017fd3..e197672 100644 --- a/src/main/java/com/kamco/cd/training/common/exception/BadRequestException.java +++ b/src/main/java/com/kamco/cd/training/common/exception/BadRequestException.java @@ -1,10 +1,10 @@ -package com.kamco.cd.training.common.exception; - -import org.springframework.http.HttpStatus; - -public class BadRequestException extends CustomApiException { - - public BadRequestException(String message) { - super("BAD_REQUEST", HttpStatus.BAD_REQUEST, message); - } -} +package com.kamco.cd.training.common.exception; + +import org.springframework.http.HttpStatus; + +public class BadRequestException extends CustomApiException { + + public BadRequestException(String message) { + super("BAD_REQUEST", HttpStatus.BAD_REQUEST, message); + } +} diff --git a/src/main/java/com/kamco/cd/training/common/exception/CustomApiException.java b/src/main/java/com/kamco/cd/training/common/exception/CustomApiException.java index e2d8fec..085e422 100644 --- a/src/main/java/com/kamco/cd/training/common/exception/CustomApiException.java +++ b/src/main/java/com/kamco/cd/training/common/exception/CustomApiException.java @@ -1,28 +1,28 @@ -package com.kamco.cd.training.common.exception; - -import com.kamco.cd.training.common.utils.ErrorCode; -import lombok.Getter; -import org.springframework.http.HttpStatus; - -@Getter -public class CustomApiException extends RuntimeException { - - private final String codeName; // ApiResponseCode enum name과 맞추는 용도 (예: "UNPROCESSABLE_ENTITY") - private final HttpStatus status; // 응답으로 내려줄 HttpStatus - - public CustomApiException(String codeName, HttpStatus status, String message) { - super(message); - this.codeName = codeName; - this.status = status; - } - - public CustomApiException(String codeName, HttpStatus status) { - this.codeName = codeName; - this.status = status; - } - - public CustomApiException(ErrorCode errorCode) { - this.codeName = errorCode.getCode(); - this.status = errorCode.getStatus(); - } -} +package com.kamco.cd.training.common.exception; + +import com.kamco.cd.training.common.utils.ErrorCode; +import lombok.Getter; +import org.springframework.http.HttpStatus; + +@Getter +public class CustomApiException extends RuntimeException { + + private final String codeName; // ApiResponseCode enum name과 맞추는 용도 (예: "UNPROCESSABLE_ENTITY") + private final HttpStatus status; // 응답으로 내려줄 HttpStatus + + public CustomApiException(String codeName, HttpStatus status, String message) { + super(message); + this.codeName = codeName; + this.status = status; + } + + public CustomApiException(String codeName, HttpStatus status) { + this.codeName = codeName; + this.status = status; + } + + public CustomApiException(ErrorCode errorCode) { + this.codeName = errorCode.getCode(); + this.status = errorCode.getStatus(); + } +} diff --git a/src/main/java/com/kamco/cd/training/common/exception/DuplicateFileException.java b/src/main/java/com/kamco/cd/training/common/exception/DuplicateFileException.java index 881cfc1..ec45af4 100644 --- a/src/main/java/com/kamco/cd/training/common/exception/DuplicateFileException.java +++ b/src/main/java/com/kamco/cd/training/common/exception/DuplicateFileException.java @@ -1,7 +1,7 @@ -package com.kamco.cd.training.common.exception; - -public class DuplicateFileException extends RuntimeException { - public DuplicateFileException(String message) { - super(message); - } -} +package com.kamco.cd.training.common.exception; + +public class DuplicateFileException extends RuntimeException { + public DuplicateFileException(String message) { + super(message); + } +} diff --git a/src/main/java/com/kamco/cd/training/common/exception/NotFoundException.java b/src/main/java/com/kamco/cd/training/common/exception/NotFoundException.java index 0c6b94f..17b4216 100644 --- a/src/main/java/com/kamco/cd/training/common/exception/NotFoundException.java +++ b/src/main/java/com/kamco/cd/training/common/exception/NotFoundException.java @@ -1,10 +1,10 @@ -package com.kamco.cd.training.common.exception; - -import org.springframework.http.HttpStatus; - -public class NotFoundException extends CustomApiException { - - public NotFoundException(String message) { - super("NOT_FOUND", HttpStatus.NOT_FOUND, message); - } -} +package com.kamco.cd.training.common.exception; + +import org.springframework.http.HttpStatus; + +public class NotFoundException extends CustomApiException { + + public NotFoundException(String message) { + super("NOT_FOUND", HttpStatus.NOT_FOUND, message); + } +} diff --git a/src/main/java/com/kamco/cd/training/common/exception/ValidationException.java b/src/main/java/com/kamco/cd/training/common/exception/ValidationException.java index bc37f97..8baae8b 100644 --- a/src/main/java/com/kamco/cd/training/common/exception/ValidationException.java +++ b/src/main/java/com/kamco/cd/training/common/exception/ValidationException.java @@ -1,7 +1,7 @@ -package com.kamco.cd.training.common.exception; - -public class ValidationException extends RuntimeException { - public ValidationException(String message) { - super(message); - } -} +package com.kamco.cd.training.common.exception; + +public class ValidationException extends RuntimeException { + public ValidationException(String message) { + super(message); + } +} diff --git a/src/main/java/com/kamco/cd/training/common/service/BaseCoreService.java b/src/main/java/com/kamco/cd/training/common/service/BaseCoreService.java index d3ad069..077c23e 100644 --- a/src/main/java/com/kamco/cd/training/common/service/BaseCoreService.java +++ b/src/main/java/com/kamco/cd/training/common/service/BaseCoreService.java @@ -1,38 +1,38 @@ -package com.kamco.cd.training.common.service; - -import org.springframework.data.domain.Page; - -/** - * Base Core Service Interface - * - *

CRUD operations를 정의하는 기본 서비스 인터페이스 - * - * @param Entity 타입 - * @param Entity의 ID 타입 - * @param Search Request 타입 - */ -public interface BaseCoreService { - - /** - * ID로 엔티티를 삭제합니다. - * - * @param id 삭제할 엔티티의 ID - */ - void remove(ID id); - - /** - * ID로 단건 조회합니다. - * - * @param id 조회할 엔티티의 ID - * @return 조회된 엔티티 - */ - T getOneById(ID id); - - /** - * 검색 조건과 페이징으로 조회합니다. - * - * @param searchReq 검색 조건 - * @return 페이징 처리된 검색 결과 - */ - Page search(S searchReq); -} +package com.kamco.cd.training.common.service; + +import org.springframework.data.domain.Page; + +/** + * Base Core Service Interface + * + *

CRUD operations를 정의하는 기본 서비스 인터페이스 + * + * @param Entity 타입 + * @param Entity의 ID 타입 + * @param Search Request 타입 + */ +public interface BaseCoreService { + + /** + * ID로 엔티티를 삭제합니다. + * + * @param id 삭제할 엔티티의 ID + */ + void remove(ID id); + + /** + * ID로 단건 조회합니다. + * + * @param id 조회할 엔티티의 ID + * @return 조회된 엔티티 + */ + T getOneById(ID id); + + /** + * 검색 조건과 페이징으로 조회합니다. + * + * @param searchReq 검색 조건 + * @return 페이징 처리된 검색 결과 + */ + Page search(S searchReq); +} diff --git a/src/main/java/com/kamco/cd/training/common/utils/CommonCodeUtil.java b/src/main/java/com/kamco/cd/training/common/utils/CommonCodeUtil.java index 25cfef9..2415e1c 100644 --- a/src/main/java/com/kamco/cd/training/common/utils/CommonCodeUtil.java +++ b/src/main/java/com/kamco/cd/training/common/utils/CommonCodeUtil.java @@ -1,152 +1,152 @@ -package com.kamco.cd.training.common.utils; - -import com.fasterxml.jackson.databind.ObjectMapper; -import com.kamco.cd.training.code.dto.CommonCodeDto.Basic; -import com.kamco.cd.training.code.service.CommonCodeService; -import com.kamco.cd.training.config.api.ApiResponseDto; -import java.util.List; -import java.util.Optional; -import lombok.extern.slf4j.Slf4j; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.stereotype.Component; - -/** - * 공통코드 조회 유틸리티 클래스 애플리케이션 전역에서 공통코드를 조회하기 위한 유틸리티입니다. training 서버는 Redis 사용하고 Spring 내장 메모리 캐시 - * 사용합니다. - */ -// -@Slf4j -@Component -public class CommonCodeUtil { - - private final CommonCodeService commonCodeService; - - @Autowired private ObjectMapper objectMapper; - - public CommonCodeUtil(CommonCodeService commonCodeService) { - this.commonCodeService = commonCodeService; - } - - /** - * 모든 공통코드 조회 - * - * @return 캐시된 모든 공통코드 목록 - */ - public List getAllCommonCodes() { - try { - return commonCodeService.getFindAll(); - } catch (Exception e) { - log.error("공통코드 전체 조회 중 오류 발생", e); - return List.of(); - } - } - - /** - * 특정 코드로 공통코드 조회 - * - * @param code 코드값 - * @return 해당 코드의 공통코드 목록 - */ - public List getCommonCodesByCode(String code) { - if (code == null || code.isEmpty()) { - log.warn("유효하지 않은 코드: {}", code); - return List.of(); - } - - try { - return commonCodeService.findByCode(code); - } catch (Exception e) { - log.error("코드 기반 공통코드 조회 중 오류 발생: {}", code, e); - return List.of(); - } - } - - /** - * 특정 ID로 공통코드 단건 조회 - * - * @param id 공통코드 ID - * @return 조회된 공통코드 - */ - public Optional getCommonCodeById(Long id) { - if (id == null || id <= 0) { - log.warn("유효하지 않은 ID: {}", id); - return Optional.empty(); - } - - try { - return Optional.of(commonCodeService.getOneById(id)); - } catch (Exception e) { - log.error("ID 기반 공통코드 조회 중 오류 발생: {}", id, e); - return Optional.empty(); - } - } - - /** - * 상위 코드와 하위 코드로 공통코드명 조회 - * - * @param parentCode 상위 코드 - * @param childCode 하위 코드 - * @return 공통코드명 - */ - public Optional getCodeName(String parentCode, String childCode) { - if (parentCode == null || parentCode.isEmpty() || childCode == null || childCode.isEmpty()) { - log.warn("유효하지 않은 코드: parentCode={}, childCode={}", parentCode, childCode); - return Optional.empty(); - } - - try { - return commonCodeService.getCode(parentCode, childCode); - } catch (Exception e) { - log.error("코드명 조회 중 오류 발생: parentCode={}, childCode={}", parentCode, childCode, e); - return Optional.empty(); - } - } - - /** - * 상위 코드를 기반으로 하위 코드 조회 - * - * @param parentCode 상위 코드 - * @return 해당 상위 코드의 하위 공통코드 목록 - */ - public List getChildCodesByParentCode(String parentCode) { - if (parentCode == null || parentCode.isEmpty()) { - log.warn("유효하지 않은 상위 코드: {}", parentCode); - return List.of(); - } - - try { - return commonCodeService.getFindAll().stream() - .filter(code -> parentCode.equals(code.getCode())) - .findFirst() - .map(Basic::getChildren) - .orElse(List.of()); - } catch (Exception e) { - log.error("상위 코드 기반 하위 코드 조회 중 오류 발생: {}", parentCode, e); - return List.of(); - } - } - - /** - * 코드 사용 가능 여부 확인 - * - * @param parentId 상위 코드 ID -> 1depth 는 null 허용 (파라미터 미필수로 변경) - * @param code 확인할 코드값 - * @return 사용 가능 여부 (true: 사용 가능, false: 중복 또는 오류) - */ - public boolean isCodeAvailable(Long parentId, String code) { - if (parentId <= 0 || code == null || code.isEmpty()) { - log.warn("유효하지 않은 입력: parentId={}, code={}", parentId, code); - return false; - } - - try { - ApiResponseDto.ResponseObj response = commonCodeService.getCodeCheckDuplicate(parentId, code); - // ResponseObj의 code 필드 : OK이면 성공, 아니면 실패 - return response.getCode() != null - && response.getCode().equals(ApiResponseDto.ApiResponseCode.OK); - } catch (Exception e) { - log.error("코드 중복 확인 중 오류 발생: parentId={}, code={}", parentId, code, e); - return false; - } - } -} +package com.kamco.cd.training.common.utils; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.kamco.cd.training.code.dto.CommonCodeDto.Basic; +import com.kamco.cd.training.code.service.CommonCodeService; +import com.kamco.cd.training.config.api.ApiResponseDto; +import java.util.List; +import java.util.Optional; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +/** + * 공통코드 조회 유틸리티 클래스 애플리케이션 전역에서 공통코드를 조회하기 위한 유틸리티입니다. training 서버는 Redis 사용하고 Spring 내장 메모리 캐시 + * 사용합니다. + */ +// +@Slf4j +@Component +public class CommonCodeUtil { + + private final CommonCodeService commonCodeService; + + @Autowired private ObjectMapper objectMapper; + + public CommonCodeUtil(CommonCodeService commonCodeService) { + this.commonCodeService = commonCodeService; + } + + /** + * 모든 공통코드 조회 + * + * @return 캐시된 모든 공통코드 목록 + */ + public List getAllCommonCodes() { + try { + return commonCodeService.getFindAll(); + } catch (Exception e) { + log.error("공통코드 전체 조회 중 오류 발생", e); + return List.of(); + } + } + + /** + * 특정 코드로 공통코드 조회 + * + * @param code 코드값 + * @return 해당 코드의 공통코드 목록 + */ + public List getCommonCodesByCode(String code) { + if (code == null || code.isEmpty()) { + log.warn("유효하지 않은 코드: {}", code); + return List.of(); + } + + try { + return commonCodeService.findByCode(code); + } catch (Exception e) { + log.error("코드 기반 공통코드 조회 중 오류 발생: {}", code, e); + return List.of(); + } + } + + /** + * 특정 ID로 공통코드 단건 조회 + * + * @param id 공통코드 ID + * @return 조회된 공통코드 + */ + public Optional getCommonCodeById(Long id) { + if (id == null || id <= 0) { + log.warn("유효하지 않은 ID: {}", id); + return Optional.empty(); + } + + try { + return Optional.of(commonCodeService.getOneById(id)); + } catch (Exception e) { + log.error("ID 기반 공통코드 조회 중 오류 발생: {}", id, e); + return Optional.empty(); + } + } + + /** + * 상위 코드와 하위 코드로 공통코드명 조회 + * + * @param parentCode 상위 코드 + * @param childCode 하위 코드 + * @return 공통코드명 + */ + public Optional getCodeName(String parentCode, String childCode) { + if (parentCode == null || parentCode.isEmpty() || childCode == null || childCode.isEmpty()) { + log.warn("유효하지 않은 코드: parentCode={}, childCode={}", parentCode, childCode); + return Optional.empty(); + } + + try { + return commonCodeService.getCode(parentCode, childCode); + } catch (Exception e) { + log.error("코드명 조회 중 오류 발생: parentCode={}, childCode={}", parentCode, childCode, e); + return Optional.empty(); + } + } + + /** + * 상위 코드를 기반으로 하위 코드 조회 + * + * @param parentCode 상위 코드 + * @return 해당 상위 코드의 하위 공통코드 목록 + */ + public List getChildCodesByParentCode(String parentCode) { + if (parentCode == null || parentCode.isEmpty()) { + log.warn("유효하지 않은 상위 코드: {}", parentCode); + return List.of(); + } + + try { + return commonCodeService.getFindAll().stream() + .filter(code -> parentCode.equals(code.getCode())) + .findFirst() + .map(Basic::getChildren) + .orElse(List.of()); + } catch (Exception e) { + log.error("상위 코드 기반 하위 코드 조회 중 오류 발생: {}", parentCode, e); + return List.of(); + } + } + + /** + * 코드 사용 가능 여부 확인 + * + * @param parentId 상위 코드 ID -> 1depth 는 null 허용 (파라미터 미필수로 변경) + * @param code 확인할 코드값 + * @return 사용 가능 여부 (true: 사용 가능, false: 중복 또는 오류) + */ + public boolean isCodeAvailable(Long parentId, String code) { + if (parentId <= 0 || code == null || code.isEmpty()) { + log.warn("유효하지 않은 입력: parentId={}, code={}", parentId, code); + return false; + } + + try { + ApiResponseDto.ResponseObj response = commonCodeService.getCodeCheckDuplicate(parentId, code); + // ResponseObj의 code 필드 : OK이면 성공, 아니면 실패 + return response.getCode() != null + && response.getCode().equals(ApiResponseDto.ApiResponseCode.OK); + } catch (Exception e) { + log.error("코드 중복 확인 중 오류 발생: parentId={}, code={}", parentId, code, e); + return false; + } + } +} diff --git a/src/main/java/com/kamco/cd/training/common/utils/CommonStringUtils.java b/src/main/java/com/kamco/cd/training/common/utils/CommonStringUtils.java index adc7193..a42b9f4 100644 --- a/src/main/java/com/kamco/cd/training/common/utils/CommonStringUtils.java +++ b/src/main/java/com/kamco/cd/training/common/utils/CommonStringUtils.java @@ -1,32 +1,32 @@ -package com.kamco.cd.training.common.utils; - -import com.kamco.cd.training.auth.BCryptSaltGenerator; -import java.util.regex.Pattern; -import org.mindrot.jbcrypt.BCrypt; - -public class CommonStringUtils { - - /** - * 영문, 숫자, 특수문자를 모두 포함하여 8~20자 이내의 비밀번호 - * - * @param password 벨리데이션 필요한 패스워드 - * @return - */ - public static boolean isValidPassword(String password) { - String passwordPattern = - "^(?=.*[A-Za-z])(?=.*\\d)(?=.*[!@#$%^&*()_+\\-\\[\\]{};':\"\\\\|,.<>/?]).{8,20}$"; - return Pattern.matches(passwordPattern, password); - } - - /** - * 패스워드 암호화 - * - * @param password 암호화 필요한 패스워드 - * @param employeeNo salt 생성에 필요한 사원번호 - * @return - */ - public static String hashPassword(String password, String employeeNo) { - String salt = BCryptSaltGenerator.generateSaltWithEmployeeNo(employeeNo.trim()); - return BCrypt.hashpw(password.trim(), salt); - } -} +package com.kamco.cd.training.common.utils; + +import com.kamco.cd.training.auth.BCryptSaltGenerator; +import java.util.regex.Pattern; +import org.mindrot.jbcrypt.BCrypt; + +public class CommonStringUtils { + + /** + * 영문, 숫자, 특수문자를 모두 포함하여 8~20자 이내의 비밀번호 + * + * @param password 벨리데이션 필요한 패스워드 + * @return + */ + public static boolean isValidPassword(String password) { + String passwordPattern = + "^(?=.*[A-Za-z])(?=.*\\d)(?=.*[!@#$%^&*()_+\\-\\[\\]{};':\"\\\\|,.<>/?]).{8,20}$"; + return Pattern.matches(passwordPattern, password); + } + + /** + * 패스워드 암호화 + * + * @param password 암호화 필요한 패스워드 + * @param employeeNo salt 생성에 필요한 사원번호 + * @return + */ + public static String hashPassword(String password, String employeeNo) { + String salt = BCryptSaltGenerator.generateSaltWithEmployeeNo(employeeNo.trim()); + return BCrypt.hashpw(password.trim(), salt); + } +} diff --git a/src/main/java/com/kamco/cd/training/common/utils/ErrorCode.java b/src/main/java/com/kamco/cd/training/common/utils/ErrorCode.java index 8e99391..9b8dfdb 100644 --- a/src/main/java/com/kamco/cd/training/common/utils/ErrorCode.java +++ b/src/main/java/com/kamco/cd/training/common/utils/ErrorCode.java @@ -1,10 +1,10 @@ -package com.kamco.cd.training.common.utils; - -import org.springframework.http.HttpStatus; - -public interface ErrorCode { - - String getCode(); - - HttpStatus getStatus(); -} +package com.kamco.cd.training.common.utils; + +import org.springframework.http.HttpStatus; + +public interface ErrorCode { + + String getCode(); + + HttpStatus getStatus(); +} diff --git a/src/main/java/com/kamco/cd/training/common/utils/FIleChecker.java b/src/main/java/com/kamco/cd/training/common/utils/FIleChecker.java index 20f78b9..e80b3b2 100644 --- a/src/main/java/com/kamco/cd/training/common/utils/FIleChecker.java +++ b/src/main/java/com/kamco/cd/training/common/utils/FIleChecker.java @@ -1,685 +1,683 @@ -package com.kamco.cd.training.common.utils; - -import static java.lang.String.CASE_INSENSITIVE_ORDER; - -import io.swagger.v3.oas.annotations.media.Schema; -import java.io.BufferedReader; -import java.io.File; -import java.io.FileReader; -import java.io.IOException; -import java.io.InputStream; -import java.io.InputStreamReader; -import java.nio.file.Files; -import java.nio.file.Path; -import java.nio.file.Paths; -import java.nio.file.attribute.FileTime; -import java.security.MessageDigest; -import java.security.NoSuchAlgorithmException; -import java.text.SimpleDateFormat; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Comparator; -import java.util.Date; -import java.util.List; -import java.util.Set; -import java.util.function.Predicate; -import java.util.stream.Collectors; -import java.util.stream.Stream; -import lombok.Getter; -import org.apache.commons.io.FilenameUtils; -import org.geotools.coverage.grid.GridCoverage2D; -import org.geotools.gce.geotiff.GeoTiffReader; -import org.springframework.util.FileSystemUtils; -import org.springframework.web.multipart.MultipartFile; - -public class FIleChecker { - - static SimpleDateFormat dttmFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); - - public static boolean isValidFile(String pathStr) { - - Path path = Paths.get(pathStr); - - if (!Files.exists(path)) { - return false; - } - - if (!Files.isRegularFile(path)) { - return false; - } - - if (!Files.isReadable(path)) { - return false; - } - - try { - if (Files.size(path) <= 0) { - return false; - } - } catch (IOException e) { - return false; - } - - return true; - } - - public static boolean verifyFileIntegrity(Path path, String expectedHash) - throws IOException, NoSuchAlgorithmException { - - // 1. 알고리즘 선택 (SHA-256 권장, MD5는 보안상 비추천) - MessageDigest digest = MessageDigest.getInstance("SHA-256"); - - try (InputStream fis = Files.newInputStream(path)) { - byte[] buffer = new byte[8192]; // 8KB 버퍼 - int bytesRead; - while ((bytesRead = fis.read(buffer)) != -1) { - digest.update(buffer, 0, bytesRead); - } - } - - // 3. 계산된 바이트 배열을 16진수 문자열로 변환 - StringBuilder sb = new StringBuilder(); - for (byte b : digest.digest()) { - sb.append(String.format("%02x", b)); - } - String actualHash = sb.toString(); - - return actualHash.equalsIgnoreCase(expectedHash); - } - - public static boolean checkTfw(String filePath) { - - File file = new File(filePath); - - if (!file.exists()) { - return false; - } - - // 1. 파일의 모든 라인을 읽어옴 - List lines = new ArrayList<>(); - try (BufferedReader br = new BufferedReader(new FileReader(file))) { - String line; - while ((line = br.readLine()) != null) { - if (!line.trim().isEmpty()) { // 빈 줄 제외 - lines.add(Double.parseDouble(line.trim())); - } - } - } catch (IOException ignored) { - return false; - } - - // 2. 6줄이 맞는지 확인 - if (lines.size() < 6) { - // System.out.println("유효하지 않은 TFW 파일입니다. (데이터 부족)"); - return false; - } - - return true; - } - - public static boolean checkGeoTiff(String filePath) { - - File file = new File(filePath); - - if (!file.exists()) { - return false; - } - - GeoTiffReader reader = null; - try { - // 1. 파일 포맷 및 헤더 확인 - reader = new GeoTiffReader(file); - - // 2. 실제 데이터 로딩 (여기서 파일 깨짐 여부 확인됨) - // null을 넣으면 전체 영역을 읽지 않고 메타데이터 위주로 체크하여 빠름 - GridCoverage2D coverage = reader.read(null); - - if (coverage == null) return false; - - // 3. GIS 필수 정보(좌표계)가 있는지 확인 - // if (coverage.getCoordinateReferenceSystem() == null) { - // GeoTIFF가 아니라 일반 TIFF일 수도 있음(이미지는 정상이지만, 좌표계(CRS) 정보가 없습니다.) - // } - - return true; - - } catch (Exception e) { - System.err.println("손상된 TIF 파일입니다: " + e.getMessage()); - return false; - } finally { - // 리소스 해제 (필수) - if (reader != null) reader.dispose(); - } - } - - public static Boolean cmmndGdalInfo(String filePath) { - - File file = new File(filePath); - - if (!file.exists()) { - System.err.println("파일이 존재하지 않습니다: " + filePath); - return false; - } - - boolean hasDriver = false; - - // 운영체제 감지 - String osName = System.getProperty("os.name").toLowerCase(); - boolean isWindows = osName.contains("win"); - boolean isMac = osName.contains("mac"); - boolean isUnix = osName.contains("nix") || osName.contains("nux") || osName.contains("aix"); - - // gdalinfo 경로 찾기 (일반적인 설치 경로 우선 확인) - String gdalinfoPath = findGdalinfoPath(); - if (gdalinfoPath == null) { - System.err.println("gdalinfo 명령어를 찾을 수 없습니다. GDAL이 설치되어 있는지 확인하세요."); - System.err.println("macOS: brew install gdal"); - System.err.println("Ubuntu/Debian: sudo apt-get install gdal-bin"); - System.err.println("CentOS/RHEL: sudo yum install gdal"); - return false; - } - - List command = new ArrayList<>(); - - if (isWindows) { - // 윈도우용 - command.add("cmd.exe"); // 윈도우 명령 프롬프트 실행 - command.add("/c"); // 명령어를 수행하고 종료한다는 옵션 - command.add("gdalinfo"); - command.add(filePath); - command.add("|"); - command.add("findstr"); - command.add("/i"); - command.add("Geo"); - } else if (isMac || isUnix) { - // 리눅스, 맥용 - command.add("sh"); - command.add("-c"); - command.add(gdalinfoPath + " \"" + filePath + "\" | grep -i Geo"); - } else { - System.err.println("지원하지 않는 운영체제: " + osName); - return false; - } - - ProcessBuilder processBuilder = new ProcessBuilder(command); - processBuilder.redirectErrorStream(true); - - Process process = null; - BufferedReader reader = null; - try { - System.out.println("gdalinfo 명령어 실행 시작: " + filePath); - process = processBuilder.start(); - - reader = new BufferedReader(new InputStreamReader(process.getInputStream())); - - String line; - while ((line = reader.readLine()) != null) { - // System.out.println("gdalinfo 출력: " + line); - if (line.contains("Driver: GTiff/GeoTIFF")) { - hasDriver = true; - break; - } - } - - int exitCode = process.waitFor(); - System.out.println("gdalinfo 종료 코드: " + exitCode); - - // 프로세스가 정상 종료되지 않았고 Driver를 찾지 못한 경우 - if (exitCode != 0 && !hasDriver) { - System.err.println("gdalinfo 명령 실행 실패. Exit code: " + exitCode); - } - - } catch (IOException e) { - System.err.println("gdalinfo 실행 중 I/O 오류 발생: " + e.getMessage()); - e.printStackTrace(); - return false; - } catch (InterruptedException e) { - System.err.println("gdalinfo 실행 중 인터럽트 발생: " + e.getMessage()); - Thread.currentThread().interrupt(); - return false; - } catch (Exception e) { - System.err.println("gdalinfo 실행 중 예상치 못한 오류 발생: " + e.getMessage()); - e.printStackTrace(); - return false; - } finally { - // 리소스 정리 - if (reader != null) { - try { - reader.close(); - } catch (IOException e) { - System.err.println("BufferedReader 종료 중 오류: " + e.getMessage()); - } - } - if (process != null) { - process.destroy(); - } - } - - return hasDriver; - } - - public static boolean mkDir(String dirPath) { - Path uploadTargetPath = Paths.get(dirPath); - try { - Files.createDirectories(uploadTargetPath); - } catch (IOException e) { - return false; - } - - return true; - } - - public static List getFolderAll(String dirPath, String sortType, int maxDepth) { - - Path startPath = Paths.get(dirPath); - - List folderList = List.of(); - - try (Stream stream = Files.walk(startPath, maxDepth)) { - - folderList = - stream - .filter(Files::isDirectory) - .filter(p -> !p.toString().equals(dirPath)) - .map( - path -> { - int depth = path.getNameCount(); - - String folderNm = path.getFileName().toString(); - String parentFolderNm = path.getParent().getFileName().toString(); - String parentPath = path.getParent().toString(); - String fullPath = path.toAbsolutePath().toString(); - - boolean isValid = - !NameValidator.containsKorean(folderNm) - && !NameValidator.containsWhitespaceRegex(folderNm); - - File file = new File(fullPath); - int childCnt = getChildFolderCount(file); - String lastModified = getLastModified(file); - - return new Folder( - folderNm, - parentFolderNm, - parentPath, - fullPath, - depth, - childCnt, - lastModified, - isValid); - }) - .collect(Collectors.toList()); - - if (sortType.equals("name") || sortType.equals("name asc")) { - folderList.sort( - Comparator.comparing( - Folder::getFolderNm, CASE_INSENSITIVE_ORDER // 대소문자 구분 없이 - )); - } else if (sortType.equals("name desc")) { - folderList.sort( - Comparator.comparing( - Folder::getFolderNm, CASE_INSENSITIVE_ORDER // 대소문자 구분 없이 - ) - .reversed()); - } else if (sortType.equals("dttm desc")) { - folderList.sort( - Comparator.comparing( - Folder::getLastModified, CASE_INSENSITIVE_ORDER // 대소문자 구분 없이 - ) - .reversed()); - } else { - folderList.sort( - Comparator.comparing( - Folder::getLastModified, CASE_INSENSITIVE_ORDER // 대소문자 구분 없이 - )); - } - - } catch (IOException e) { - throw new RuntimeException(e); - } - - return folderList; - } - - public static List getFolderAll(String dirPath) { - return getFolderAll(dirPath, "name", 1); - } - - public static List getFolderAll(String dirPath, String sortType) { - return getFolderAll(dirPath, sortType, 1); - } - - public static int getChildFolderCount(String dirPath) { - File directory = new File(dirPath); - File[] childFolders = directory.listFiles(File::isDirectory); - - int childCnt = 0; - if (childFolders != null) { - childCnt = childFolders.length; - } - - return childCnt; - } - - public static int getChildFolderCount(File directory) { - File[] childFolders = directory.listFiles(File::isDirectory); - - int childCnt = 0; - if (childFolders != null) { - childCnt = childFolders.length; - } - - return childCnt; - } - - public static String getLastModified(String dirPath) { - File file = new File(dirPath); - return dttmFormat.format(new Date(file.lastModified())); - } - - public static String getLastModified(File file) { - return dttmFormat.format(new Date(file.lastModified())); - } - - public static List getFilesFromAllDepth( - String dir, - String targetFileNm, - String extension, - int maxDepth, - String sortType, - int startPos, - int endPos) { - - Path startPath = Paths.get(dir); - String dirPath = dir; - - int limit = endPos - startPos + 1; - - Set targetExtensions = createExtensionSet(extension); - - List fileList = new ArrayList<>(); - SimpleDateFormat dttmFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); - - Predicate isTargetName = - p -> { - if (targetFileNm == null - || targetFileNm.trim().isEmpty() - || targetFileNm.trim().equals("*")) { - return true; // 전체 파일 허용 - } - return p.getFileName().toString().contains(targetFileNm); - }; - - try (Stream stream = Files.walk(startPath, maxDepth)) { - - fileList = - stream - .filter(Files::isRegularFile) - .filter( - p -> - extension == null - || extension.equals("") - || extension.equals("*") - || targetExtensions.contains(extractExtension(p))) - .sorted(getFileComparator(sortType)) - .filter(isTargetName) - .skip(startPos) - .limit(limit) - .map( - path -> { - // int depth = path.getNameCount(); - - String fileNm = path.getFileName().toString(); - String ext = FilenameUtils.getExtension(fileNm); - String parentFolderNm = path.getParent().getFileName().toString(); - String parentPath = path.getParent().toString(); - String fullPath = path.toAbsolutePath().toString(); - - File file = new File(fullPath); - long fileSize = file.length(); - String lastModified = dttmFormat.format(new Date(file.lastModified())); - - return new Basic( - fileNm, parentFolderNm, parentPath, fullPath, ext, fileSize, lastModified); - }) - .collect(Collectors.toList()); - - } catch (IOException e) { - System.err.println("파일 I/O 오류 발생: " + e.getMessage()); - } - - return fileList; - } - - public static List getFilesFromAllDepth( - String dir, String targetFileNm, String extension) { - - return FIleChecker.getFilesFromAllDepth(dir, targetFileNm, extension, 100, "name", 0, 100); - } - - public static int getFileCountFromAllDepth(String dir, String targetFileNm, String extension) { - - List basicList = - FIleChecker.getFilesFromAllDepth(dir, targetFileNm, extension); - - return (int) - basicList.stream().filter(dto -> dto.getExtension().toString().equals(extension)).count(); - } - - public static Long getFileTotSize(List files) { - - Long fileTotSize = 0L; - if (files != null || files.size() > 0) { - fileTotSize = files.stream().mapToLong(FIleChecker.Basic::getFileSize).sum(); - } - - return fileTotSize; - } - - public static boolean multipartSaveTo(MultipartFile mfile, String targetPath) { - Path tmpSavePath = Paths.get(targetPath); - - boolean fileUpload = true; - try { - mfile.transferTo(tmpSavePath); - } catch (IOException e) { - // throw new RuntimeException(e); - return false; - } - - return true; - } - - - public static boolean multipartChunkSaveTo(MultipartFile mfile, String targetPath, int chunkIndex) { - File dest = new File(targetPath, String.valueOf(chunkIndex)); - - boolean fileUpload = true; - try { - mfile.transferTo(dest); - } catch (IOException e) { - return false; - } - - return true; - } - - public static boolean deleteFolder(String path) { - return FileSystemUtils.deleteRecursively(new File(path)); - } - - - - public static boolean validationMultipart(MultipartFile mfile) { - // 파일 유효성 검증 - if (mfile == null || mfile.isEmpty() || mfile.getSize() == 0) { - return false; - } - - return true; - } - - public static boolean checkExtensions(String fileName, String ext) { - if (fileName == null) return false; - - if (!fileName.substring(fileName.lastIndexOf('.') + 1).toLowerCase().equals(ext)) { - return false; - } - - return true; - } - - public static Set createExtensionSet(String extensionString) { - if (extensionString == null || extensionString.isBlank()) { - return Set.of(); - } - - // "java, class" -> ["java", " class"] -> [".java", ".class"] - return Arrays.stream(extensionString.split(",")) - .map(ext -> ext.trim()) - .filter(ext -> !ext.isEmpty()) - .map(ext -> "." + ext.toLowerCase()) - .collect(Collectors.toSet()); - } - - public static String extractExtension(Path path) { - String filename = path.getFileName().toString(); - int lastDotIndex = filename.lastIndexOf('.'); - - // 확장자가 없거나 파일명이 .으로 끝나는 경우 - if (lastDotIndex == -1 || lastDotIndex == filename.length() - 1) { - return ""; // 빈 문자열 반환 - } - - // 확장자 추출 및 소문자 변환 - return filename.substring(lastDotIndex).toLowerCase(); - } - - public static Comparator getFileComparator(String sortType) { - - // 파일 이름 비교 기본 Comparator (대소문자 무시) - Comparator nameComparator = - Comparator.comparing(path -> path.getFileName().toString(), CASE_INSENSITIVE_ORDER); - - Comparator dateComparator = - Comparator.comparing( - path -> { - try { - return Files.getLastModifiedTime(path); - } catch (IOException e) { - return FileTime.fromMillis(0); - } - }); - - if ("name desc".equalsIgnoreCase(sortType)) { - return nameComparator.reversed(); - } else if ("date".equalsIgnoreCase(sortType)) { - return dateComparator; - } else if ("date desc".equalsIgnoreCase(sortType)) { - return dateComparator.reversed(); - } else { - return nameComparator; - } - } - - private static String findGdalinfoPath() { - // 일반적인 설치 경로 확인 - String[] possiblePaths = { - "/usr/local/bin/gdalinfo", // Homebrew (macOS) - "/opt/homebrew/bin/gdalinfo", // Homebrew (Apple Silicon macOS) - "/usr/bin/gdalinfo", // Linux - "gdalinfo" // PATH에 있는 경우 - }; - - for (String path : possiblePaths) { - if (isCommandAvailable(path)) { - return path; - } - } - - return null; - } - - private static boolean isCommandAvailable(String command) { - try { - ProcessBuilder pb = new ProcessBuilder(command, "--version"); - pb.redirectErrorStream(true); - Process process = pb.start(); - - // 프로세스 완료 대기 (최대 5초) - boolean finished = process.waitFor(5, java.util.concurrent.TimeUnit.SECONDS); - - if (!finished) { - process.destroy(); - return false; - } - - // 종료 코드가 0이면 정상 (일부 명령어는 --version에서 다른 코드 반환할 수 있음) - return process.exitValue() == 0 || process.exitValue() == 1; - } catch (Exception e) { - return false; - } - } - - @Schema(name = "Folder", description = "폴더 정보") - @Getter - public static class Folder { - private final String folderNm; - private final String parentFolderNm; - private final String parentPath; - private final String fullPath; - private final int depth; - private final long childCnt; - private final String lastModified; - private final Boolean isValid; - - public Folder( - String folderNm, - String parentFolderNm, - String parentPath, - String fullPath, - int depth, - long childCnt, - String lastModified, - Boolean isValid) { - this.folderNm = folderNm; - this.parentFolderNm = parentFolderNm; - this.parentPath = parentPath; - this.fullPath = fullPath; - this.depth = depth; - this.childCnt = childCnt; - this.lastModified = lastModified; - this.isValid = isValid; - } - } - - @Schema(name = "File Basic", description = "파일 기본 정보") - @Getter - public static class Basic { - - private final String fileNm; - private final String parentFolderNm; - private final String parentPath; - private final String fullPath; - private final String extension; - private final long fileSize; - private final String lastModified; - - public Basic( - String fileNm, - String parentFolderNm, - String parentPath, - String fullPath, - String extension, - long fileSize, - String lastModified) { - this.fileNm = fileNm; - this.parentFolderNm = parentFolderNm; - this.parentPath = parentPath; - this.fullPath = fullPath; - this.extension = extension; - this.fileSize = fileSize; - this.lastModified = lastModified; - } - } -} +package com.kamco.cd.training.common.utils; + +import static java.lang.String.CASE_INSENSITIVE_ORDER; + +import io.swagger.v3.oas.annotations.media.Schema; +import java.io.BufferedReader; +import java.io.File; +import java.io.FileReader; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.nio.file.attribute.FileTime; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.text.SimpleDateFormat; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Comparator; +import java.util.Date; +import java.util.List; +import java.util.Set; +import java.util.function.Predicate; +import java.util.stream.Collectors; +import java.util.stream.Stream; +import lombok.Getter; +import org.apache.commons.io.FilenameUtils; +import org.geotools.coverage.grid.GridCoverage2D; +import org.geotools.gce.geotiff.GeoTiffReader; +import org.springframework.util.FileSystemUtils; +import org.springframework.web.multipart.MultipartFile; + +public class FIleChecker { + + static SimpleDateFormat dttmFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); + + public static boolean isValidFile(String pathStr) { + + Path path = Paths.get(pathStr); + + if (!Files.exists(path)) { + return false; + } + + if (!Files.isRegularFile(path)) { + return false; + } + + if (!Files.isReadable(path)) { + return false; + } + + try { + if (Files.size(path) <= 0) { + return false; + } + } catch (IOException e) { + return false; + } + + return true; + } + + public static boolean verifyFileIntegrity(Path path, String expectedHash) + throws IOException, NoSuchAlgorithmException { + + // 1. 알고리즘 선택 (SHA-256 권장, MD5는 보안상 비추천) + MessageDigest digest = MessageDigest.getInstance("SHA-256"); + + try (InputStream fis = Files.newInputStream(path)) { + byte[] buffer = new byte[8192]; // 8KB 버퍼 + int bytesRead; + while ((bytesRead = fis.read(buffer)) != -1) { + digest.update(buffer, 0, bytesRead); + } + } + + // 3. 계산된 바이트 배열을 16진수 문자열로 변환 + StringBuilder sb = new StringBuilder(); + for (byte b : digest.digest()) { + sb.append(String.format("%02x", b)); + } + String actualHash = sb.toString(); + + return actualHash.equalsIgnoreCase(expectedHash); + } + + public static boolean checkTfw(String filePath) { + + File file = new File(filePath); + + if (!file.exists()) { + return false; + } + + // 1. 파일의 모든 라인을 읽어옴 + List lines = new ArrayList<>(); + try (BufferedReader br = new BufferedReader(new FileReader(file))) { + String line; + while ((line = br.readLine()) != null) { + if (!line.trim().isEmpty()) { // 빈 줄 제외 + lines.add(Double.parseDouble(line.trim())); + } + } + } catch (IOException ignored) { + return false; + } + + // 2. 6줄이 맞는지 확인 + if (lines.size() < 6) { + // System.out.println("유효하지 않은 TFW 파일입니다. (데이터 부족)"); + return false; + } + + return true; + } + + public static boolean checkGeoTiff(String filePath) { + + File file = new File(filePath); + + if (!file.exists()) { + return false; + } + + GeoTiffReader reader = null; + try { + // 1. 파일 포맷 및 헤더 확인 + reader = new GeoTiffReader(file); + + // 2. 실제 데이터 로딩 (여기서 파일 깨짐 여부 확인됨) + // null을 넣으면 전체 영역을 읽지 않고 메타데이터 위주로 체크하여 빠름 + GridCoverage2D coverage = reader.read(null); + + if (coverage == null) return false; + + // 3. GIS 필수 정보(좌표계)가 있는지 확인 + // if (coverage.getCoordinateReferenceSystem() == null) { + // GeoTIFF가 아니라 일반 TIFF일 수도 있음(이미지는 정상이지만, 좌표계(CRS) 정보가 없습니다.) + // } + + return true; + + } catch (Exception e) { + System.err.println("손상된 TIF 파일입니다: " + e.getMessage()); + return false; + } finally { + // 리소스 해제 (필수) + if (reader != null) reader.dispose(); + } + } + + public static Boolean cmmndGdalInfo(String filePath) { + + File file = new File(filePath); + + if (!file.exists()) { + System.err.println("파일이 존재하지 않습니다: " + filePath); + return false; + } + + boolean hasDriver = false; + + // 운영체제 감지 + String osName = System.getProperty("os.name").toLowerCase(); + boolean isWindows = osName.contains("win"); + boolean isMac = osName.contains("mac"); + boolean isUnix = osName.contains("nix") || osName.contains("nux") || osName.contains("aix"); + + // gdalinfo 경로 찾기 (일반적인 설치 경로 우선 확인) + String gdalinfoPath = findGdalinfoPath(); + if (gdalinfoPath == null) { + System.err.println("gdalinfo 명령어를 찾을 수 없습니다. GDAL이 설치되어 있는지 확인하세요."); + System.err.println("macOS: brew install gdal"); + System.err.println("Ubuntu/Debian: sudo apt-get install gdal-bin"); + System.err.println("CentOS/RHEL: sudo yum install gdal"); + return false; + } + + List command = new ArrayList<>(); + + if (isWindows) { + // 윈도우용 + command.add("cmd.exe"); // 윈도우 명령 프롬프트 실행 + command.add("/c"); // 명령어를 수행하고 종료한다는 옵션 + command.add("gdalinfo"); + command.add(filePath); + command.add("|"); + command.add("findstr"); + command.add("/i"); + command.add("Geo"); + } else if (isMac || isUnix) { + // 리눅스, 맥용 + command.add("sh"); + command.add("-c"); + command.add(gdalinfoPath + " \"" + filePath + "\" | grep -i Geo"); + } else { + System.err.println("지원하지 않는 운영체제: " + osName); + return false; + } + + ProcessBuilder processBuilder = new ProcessBuilder(command); + processBuilder.redirectErrorStream(true); + + Process process = null; + BufferedReader reader = null; + try { + System.out.println("gdalinfo 명령어 실행 시작: " + filePath); + process = processBuilder.start(); + + reader = new BufferedReader(new InputStreamReader(process.getInputStream())); + + String line; + while ((line = reader.readLine()) != null) { + // System.out.println("gdalinfo 출력: " + line); + if (line.contains("Driver: GTiff/GeoTIFF")) { + hasDriver = true; + break; + } + } + + int exitCode = process.waitFor(); + System.out.println("gdalinfo 종료 코드: " + exitCode); + + // 프로세스가 정상 종료되지 않았고 Driver를 찾지 못한 경우 + if (exitCode != 0 && !hasDriver) { + System.err.println("gdalinfo 명령 실행 실패. Exit code: " + exitCode); + } + + } catch (IOException e) { + System.err.println("gdalinfo 실행 중 I/O 오류 발생: " + e.getMessage()); + e.printStackTrace(); + return false; + } catch (InterruptedException e) { + System.err.println("gdalinfo 실행 중 인터럽트 발생: " + e.getMessage()); + Thread.currentThread().interrupt(); + return false; + } catch (Exception e) { + System.err.println("gdalinfo 실행 중 예상치 못한 오류 발생: " + e.getMessage()); + e.printStackTrace(); + return false; + } finally { + // 리소스 정리 + if (reader != null) { + try { + reader.close(); + } catch (IOException e) { + System.err.println("BufferedReader 종료 중 오류: " + e.getMessage()); + } + } + if (process != null) { + process.destroy(); + } + } + + return hasDriver; + } + + public static boolean mkDir(String dirPath) { + Path uploadTargetPath = Paths.get(dirPath); + try { + Files.createDirectories(uploadTargetPath); + } catch (IOException e) { + return false; + } + + return true; + } + + public static List getFolderAll(String dirPath, String sortType, int maxDepth) { + + Path startPath = Paths.get(dirPath); + + List folderList = List.of(); + + try (Stream stream = Files.walk(startPath, maxDepth)) { + + folderList = + stream + .filter(Files::isDirectory) + .filter(p -> !p.toString().equals(dirPath)) + .map( + path -> { + int depth = path.getNameCount(); + + String folderNm = path.getFileName().toString(); + String parentFolderNm = path.getParent().getFileName().toString(); + String parentPath = path.getParent().toString(); + String fullPath = path.toAbsolutePath().toString(); + + boolean isValid = + !NameValidator.containsKorean(folderNm) + && !NameValidator.containsWhitespaceRegex(folderNm); + + File file = new File(fullPath); + int childCnt = getChildFolderCount(file); + String lastModified = getLastModified(file); + + return new Folder( + folderNm, + parentFolderNm, + parentPath, + fullPath, + depth, + childCnt, + lastModified, + isValid); + }) + .collect(Collectors.toList()); + + if (sortType.equals("name") || sortType.equals("name asc")) { + folderList.sort( + Comparator.comparing( + Folder::getFolderNm, CASE_INSENSITIVE_ORDER // 대소문자 구분 없이 + )); + } else if (sortType.equals("name desc")) { + folderList.sort( + Comparator.comparing( + Folder::getFolderNm, CASE_INSENSITIVE_ORDER // 대소문자 구분 없이 + ) + .reversed()); + } else if (sortType.equals("dttm desc")) { + folderList.sort( + Comparator.comparing( + Folder::getLastModified, CASE_INSENSITIVE_ORDER // 대소문자 구분 없이 + ) + .reversed()); + } else { + folderList.sort( + Comparator.comparing( + Folder::getLastModified, CASE_INSENSITIVE_ORDER // 대소문자 구분 없이 + )); + } + + } catch (IOException e) { + throw new RuntimeException(e); + } + + return folderList; + } + + public static List getFolderAll(String dirPath) { + return getFolderAll(dirPath, "name", 1); + } + + public static List getFolderAll(String dirPath, String sortType) { + return getFolderAll(dirPath, sortType, 1); + } + + public static int getChildFolderCount(String dirPath) { + File directory = new File(dirPath); + File[] childFolders = directory.listFiles(File::isDirectory); + + int childCnt = 0; + if (childFolders != null) { + childCnt = childFolders.length; + } + + return childCnt; + } + + public static int getChildFolderCount(File directory) { + File[] childFolders = directory.listFiles(File::isDirectory); + + int childCnt = 0; + if (childFolders != null) { + childCnt = childFolders.length; + } + + return childCnt; + } + + public static String getLastModified(String dirPath) { + File file = new File(dirPath); + return dttmFormat.format(new Date(file.lastModified())); + } + + public static String getLastModified(File file) { + return dttmFormat.format(new Date(file.lastModified())); + } + + public static List getFilesFromAllDepth( + String dir, + String targetFileNm, + String extension, + int maxDepth, + String sortType, + int startPos, + int endPos) { + + Path startPath = Paths.get(dir); + String dirPath = dir; + + int limit = endPos - startPos + 1; + + Set targetExtensions = createExtensionSet(extension); + + List fileList = new ArrayList<>(); + SimpleDateFormat dttmFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); + + Predicate isTargetName = + p -> { + if (targetFileNm == null + || targetFileNm.trim().isEmpty() + || targetFileNm.trim().equals("*")) { + return true; // 전체 파일 허용 + } + return p.getFileName().toString().contains(targetFileNm); + }; + + try (Stream stream = Files.walk(startPath, maxDepth)) { + + fileList = + stream + .filter(Files::isRegularFile) + .filter( + p -> + extension == null + || extension.equals("") + || extension.equals("*") + || targetExtensions.contains(extractExtension(p))) + .sorted(getFileComparator(sortType)) + .filter(isTargetName) + .skip(startPos) + .limit(limit) + .map( + path -> { + // int depth = path.getNameCount(); + + String fileNm = path.getFileName().toString(); + String ext = FilenameUtils.getExtension(fileNm); + String parentFolderNm = path.getParent().getFileName().toString(); + String parentPath = path.getParent().toString(); + String fullPath = path.toAbsolutePath().toString(); + + File file = new File(fullPath); + long fileSize = file.length(); + String lastModified = dttmFormat.format(new Date(file.lastModified())); + + return new Basic( + fileNm, parentFolderNm, parentPath, fullPath, ext, fileSize, lastModified); + }) + .collect(Collectors.toList()); + + } catch (IOException e) { + System.err.println("파일 I/O 오류 발생: " + e.getMessage()); + } + + return fileList; + } + + public static List getFilesFromAllDepth( + String dir, String targetFileNm, String extension) { + + return FIleChecker.getFilesFromAllDepth(dir, targetFileNm, extension, 100, "name", 0, 100); + } + + public static int getFileCountFromAllDepth(String dir, String targetFileNm, String extension) { + + List basicList = + FIleChecker.getFilesFromAllDepth(dir, targetFileNm, extension); + + return (int) + basicList.stream().filter(dto -> dto.getExtension().toString().equals(extension)).count(); + } + + public static Long getFileTotSize(List files) { + + Long fileTotSize = 0L; + if (files != null || files.size() > 0) { + fileTotSize = files.stream().mapToLong(FIleChecker.Basic::getFileSize).sum(); + } + + return fileTotSize; + } + + public static boolean multipartSaveTo(MultipartFile mfile, String targetPath) { + Path tmpSavePath = Paths.get(targetPath); + + boolean fileUpload = true; + try { + mfile.transferTo(tmpSavePath); + } catch (IOException e) { + // throw new RuntimeException(e); + return false; + } + + return true; + } + + public static boolean multipartChunkSaveTo( + MultipartFile mfile, String targetPath, int chunkIndex) { + File dest = new File(targetPath, String.valueOf(chunkIndex)); + + boolean fileUpload = true; + try { + mfile.transferTo(dest); + } catch (IOException e) { + return false; + } + + return true; + } + + public static boolean deleteFolder(String path) { + return FileSystemUtils.deleteRecursively(new File(path)); + } + + public static boolean validationMultipart(MultipartFile mfile) { + // 파일 유효성 검증 + if (mfile == null || mfile.isEmpty() || mfile.getSize() == 0) { + return false; + } + + return true; + } + + public static boolean checkExtensions(String fileName, String ext) { + if (fileName == null) return false; + + if (!fileName.substring(fileName.lastIndexOf('.') + 1).toLowerCase().equals(ext)) { + return false; + } + + return true; + } + + public static Set createExtensionSet(String extensionString) { + if (extensionString == null || extensionString.isBlank()) { + return Set.of(); + } + + // "java, class" -> ["java", " class"] -> [".java", ".class"] + return Arrays.stream(extensionString.split(",")) + .map(ext -> ext.trim()) + .filter(ext -> !ext.isEmpty()) + .map(ext -> "." + ext.toLowerCase()) + .collect(Collectors.toSet()); + } + + public static String extractExtension(Path path) { + String filename = path.getFileName().toString(); + int lastDotIndex = filename.lastIndexOf('.'); + + // 확장자가 없거나 파일명이 .으로 끝나는 경우 + if (lastDotIndex == -1 || lastDotIndex == filename.length() - 1) { + return ""; // 빈 문자열 반환 + } + + // 확장자 추출 및 소문자 변환 + return filename.substring(lastDotIndex).toLowerCase(); + } + + public static Comparator getFileComparator(String sortType) { + + // 파일 이름 비교 기본 Comparator (대소문자 무시) + Comparator nameComparator = + Comparator.comparing(path -> path.getFileName().toString(), CASE_INSENSITIVE_ORDER); + + Comparator dateComparator = + Comparator.comparing( + path -> { + try { + return Files.getLastModifiedTime(path); + } catch (IOException e) { + return FileTime.fromMillis(0); + } + }); + + if ("name desc".equalsIgnoreCase(sortType)) { + return nameComparator.reversed(); + } else if ("date".equalsIgnoreCase(sortType)) { + return dateComparator; + } else if ("date desc".equalsIgnoreCase(sortType)) { + return dateComparator.reversed(); + } else { + return nameComparator; + } + } + + private static String findGdalinfoPath() { + // 일반적인 설치 경로 확인 + String[] possiblePaths = { + "/usr/local/bin/gdalinfo", // Homebrew (macOS) + "/opt/homebrew/bin/gdalinfo", // Homebrew (Apple Silicon macOS) + "/usr/bin/gdalinfo", // Linux + "gdalinfo" // PATH에 있는 경우 + }; + + for (String path : possiblePaths) { + if (isCommandAvailable(path)) { + return path; + } + } + + return null; + } + + private static boolean isCommandAvailable(String command) { + try { + ProcessBuilder pb = new ProcessBuilder(command, "--version"); + pb.redirectErrorStream(true); + Process process = pb.start(); + + // 프로세스 완료 대기 (최대 5초) + boolean finished = process.waitFor(5, java.util.concurrent.TimeUnit.SECONDS); + + if (!finished) { + process.destroy(); + return false; + } + + // 종료 코드가 0이면 정상 (일부 명령어는 --version에서 다른 코드 반환할 수 있음) + return process.exitValue() == 0 || process.exitValue() == 1; + } catch (Exception e) { + return false; + } + } + + @Schema(name = "Folder", description = "폴더 정보") + @Getter + public static class Folder { + private final String folderNm; + private final String parentFolderNm; + private final String parentPath; + private final String fullPath; + private final int depth; + private final long childCnt; + private final String lastModified; + private final Boolean isValid; + + public Folder( + String folderNm, + String parentFolderNm, + String parentPath, + String fullPath, + int depth, + long childCnt, + String lastModified, + Boolean isValid) { + this.folderNm = folderNm; + this.parentFolderNm = parentFolderNm; + this.parentPath = parentPath; + this.fullPath = fullPath; + this.depth = depth; + this.childCnt = childCnt; + this.lastModified = lastModified; + this.isValid = isValid; + } + } + + @Schema(name = "File Basic", description = "파일 기본 정보") + @Getter + public static class Basic { + + private final String fileNm; + private final String parentFolderNm; + private final String parentPath; + private final String fullPath; + private final String extension; + private final long fileSize; + private final String lastModified; + + public Basic( + String fileNm, + String parentFolderNm, + String parentPath, + String fullPath, + String extension, + long fileSize, + String lastModified) { + this.fileNm = fileNm; + this.parentFolderNm = parentFolderNm; + this.parentPath = parentPath; + this.fullPath = fullPath; + this.extension = extension; + this.fileSize = fileSize; + this.lastModified = lastModified; + } + } +} diff --git a/src/main/java/com/kamco/cd/training/common/utils/NameValidator.java b/src/main/java/com/kamco/cd/training/common/utils/NameValidator.java index fff58fa..ad6188e 100644 --- a/src/main/java/com/kamco/cd/training/common/utils/NameValidator.java +++ b/src/main/java/com/kamco/cd/training/common/utils/NameValidator.java @@ -1,43 +1,43 @@ -package com.kamco.cd.training.common.utils; - -import java.util.regex.Matcher; -import java.util.regex.Pattern; - -public class NameValidator { - - private static final String HANGUL_REGEX = ".*\\p{IsHangul}.*"; - private static final Pattern HANGUL_PATTERN = Pattern.compile(HANGUL_REGEX); - - private static final String WHITESPACE_REGEX = ".*\\s.*"; - private static final Pattern WHITESPACE_PATTERN = Pattern.compile(WHITESPACE_REGEX); - - public static boolean containsKorean(String str) { - if (str == null || str.isEmpty()) { - return false; - } - Matcher matcher = HANGUL_PATTERN.matcher(str); - return matcher.matches(); - } - - public static boolean containsWhitespaceRegex(String str) { - if (str == null || str.isEmpty()) { - return false; - } - - Matcher matcher = WHITESPACE_PATTERN.matcher(str); - // find()를 사용하여 문자열 내에서 패턴이 일치하는 부분이 있는지 확인 - return matcher.find(); - } - - public static boolean isNullOrEmpty(String str) { - if (str == null) { - return true; - } - - if (str.isEmpty()) { - return true; - } - - return false; - } -} +package com.kamco.cd.training.common.utils; + +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +public class NameValidator { + + private static final String HANGUL_REGEX = ".*\\p{IsHangul}.*"; + private static final Pattern HANGUL_PATTERN = Pattern.compile(HANGUL_REGEX); + + private static final String WHITESPACE_REGEX = ".*\\s.*"; + private static final Pattern WHITESPACE_PATTERN = Pattern.compile(WHITESPACE_REGEX); + + public static boolean containsKorean(String str) { + if (str == null || str.isEmpty()) { + return false; + } + Matcher matcher = HANGUL_PATTERN.matcher(str); + return matcher.matches(); + } + + public static boolean containsWhitespaceRegex(String str) { + if (str == null || str.isEmpty()) { + return false; + } + + Matcher matcher = WHITESPACE_PATTERN.matcher(str); + // find()를 사용하여 문자열 내에서 패턴이 일치하는 부분이 있는지 확인 + return matcher.find(); + } + + public static boolean isNullOrEmpty(String str) { + if (str == null) { + return true; + } + + if (str.isEmpty()) { + return true; + } + + return false; + } +} diff --git a/src/main/java/com/kamco/cd/training/common/utils/UserUtil.java b/src/main/java/com/kamco/cd/training/common/utils/UserUtil.java index 4d1dd57..6dd3d3c 100644 --- a/src/main/java/com/kamco/cd/training/common/utils/UserUtil.java +++ b/src/main/java/com/kamco/cd/training/common/utils/UserUtil.java @@ -1,41 +1,41 @@ -package com.kamco.cd.training.common.utils; - -import com.kamco.cd.training.auth.CustomUserDetails; -import com.kamco.cd.training.members.dto.MembersDto; -import com.kamco.cd.training.postgres.entity.MemberEntity; -import java.util.Optional; -import lombok.RequiredArgsConstructor; -import org.springframework.security.core.context.SecurityContextHolder; -import org.springframework.stereotype.Component; - -@Component -@RequiredArgsConstructor -public class UserUtil { - - public MembersDto.Member getCurrentUser() { - return Optional.ofNullable(SecurityContextHolder.getContext().getAuthentication()) - .filter(auth -> auth.getPrincipal() instanceof CustomUserDetails) - .map( - auth -> { - CustomUserDetails user = (CustomUserDetails) auth.getPrincipal(); - MemberEntity m = user.getMember(); - return new MembersDto.Member(m.getId(), m.getName(), m.getEmployeeNo()); - }) - .orElse(null); - } - - public Long getId() { - MembersDto.Member user = getCurrentUser(); - return user != null ? user.getId() : null; - } - - public String getName() { - MembersDto.Member user = getCurrentUser(); - return user != null ? user.getName() : null; - } - - public String getEmployeeNo() { - MembersDto.Member user = getCurrentUser(); - return user != null ? user.getEmployeeNo() : null; - } -} +package com.kamco.cd.training.common.utils; + +import com.kamco.cd.training.auth.CustomUserDetails; +import com.kamco.cd.training.members.dto.MembersDto; +import com.kamco.cd.training.postgres.entity.MemberEntity; +import java.util.Optional; +import lombok.RequiredArgsConstructor; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.stereotype.Component; + +@Component +@RequiredArgsConstructor +public class UserUtil { + + public MembersDto.Member getCurrentUser() { + return Optional.ofNullable(SecurityContextHolder.getContext().getAuthentication()) + .filter(auth -> auth.getPrincipal() instanceof CustomUserDetails) + .map( + auth -> { + CustomUserDetails user = (CustomUserDetails) auth.getPrincipal(); + MemberEntity m = user.getMember(); + return new MembersDto.Member(m.getId(), m.getName(), m.getEmployeeNo()); + }) + .orElse(null); + } + + public Long getId() { + MembersDto.Member user = getCurrentUser(); + return user != null ? user.getId() : null; + } + + public String getName() { + MembersDto.Member user = getCurrentUser(); + return user != null ? user.getName() : null; + } + + public String getEmployeeNo() { + MembersDto.Member user = getCurrentUser(); + return user != null ? user.getEmployeeNo() : null; + } +} diff --git a/src/main/java/com/kamco/cd/training/common/utils/enums/CodeDto.java b/src/main/java/com/kamco/cd/training/common/utils/enums/CodeDto.java index edad49b..936bebf 100644 --- a/src/main/java/com/kamco/cd/training/common/utils/enums/CodeDto.java +++ b/src/main/java/com/kamco/cd/training/common/utils/enums/CodeDto.java @@ -1,20 +1,20 @@ -package com.kamco.cd.training.common.utils.enums; - -public class CodeDto { - - private String code; - private String name; - - public CodeDto(String code, String name) { - this.code = code; - this.name = name; - } - - public String getCode() { - return code; - } - - public String getName() { - return name; - } -} +package com.kamco.cd.training.common.utils.enums; + +public class CodeDto { + + private String code; + private String name; + + public CodeDto(String code, String name) { + this.code = code; + this.name = name; + } + + public String getCode() { + return code; + } + + public String getName() { + return name; + } +} diff --git a/src/main/java/com/kamco/cd/training/common/utils/enums/CodeExpose.java b/src/main/java/com/kamco/cd/training/common/utils/enums/CodeExpose.java index cd431a8..d5cb518 100644 --- a/src/main/java/com/kamco/cd/training/common/utils/enums/CodeExpose.java +++ b/src/main/java/com/kamco/cd/training/common/utils/enums/CodeExpose.java @@ -1,10 +1,10 @@ -package com.kamco.cd.training.common.utils.enums; - -import java.lang.annotation.ElementType; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; - -@Target(ElementType.TYPE) -@Retention(RetentionPolicy.RUNTIME) -public @interface CodeExpose {} +package com.kamco.cd.training.common.utils.enums; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Target(ElementType.TYPE) +@Retention(RetentionPolicy.RUNTIME) +public @interface CodeExpose {} diff --git a/src/main/java/com/kamco/cd/training/common/utils/enums/EnumType.java b/src/main/java/com/kamco/cd/training/common/utils/enums/EnumType.java index 5d2ea0f..373dcbe 100644 --- a/src/main/java/com/kamco/cd/training/common/utils/enums/EnumType.java +++ b/src/main/java/com/kamco/cd/training/common/utils/enums/EnumType.java @@ -1,8 +1,8 @@ -package com.kamco.cd.training.common.utils.enums; - -public interface EnumType { - - String getId(); - - String getText(); -} +package com.kamco.cd.training.common.utils.enums; + +public interface EnumType { + + String getId(); + + String getText(); +} diff --git a/src/main/java/com/kamco/cd/training/common/utils/enums/EnumValidator.java b/src/main/java/com/kamco/cd/training/common/utils/enums/EnumValidator.java index 7904316..4c396af 100644 --- a/src/main/java/com/kamco/cd/training/common/utils/enums/EnumValidator.java +++ b/src/main/java/com/kamco/cd/training/common/utils/enums/EnumValidator.java @@ -1,26 +1,26 @@ -package com.kamco.cd.training.common.utils.enums; - -import com.kamco.cd.training.common.utils.interfaces.EnumValid; -import jakarta.validation.ConstraintValidator; -import jakarta.validation.ConstraintValidatorContext; -import java.util.Arrays; -import java.util.Set; -import java.util.stream.Collectors; - -public class EnumValidator implements ConstraintValidator { - - private Set acceptedValues; - - @Override - public void initialize(EnumValid constraintAnnotation) { - acceptedValues = - Arrays.stream(constraintAnnotation.enumClass().getEnumConstants()) - .map(Enum::name) - .collect(Collectors.toSet()); - } - - @Override - public boolean isValid(String value, ConstraintValidatorContext context) { - return value != null && acceptedValues.contains(value); - } -} +package com.kamco.cd.training.common.utils.enums; + +import com.kamco.cd.training.common.utils.interfaces.EnumValid; +import jakarta.validation.ConstraintValidator; +import jakarta.validation.ConstraintValidatorContext; +import java.util.Arrays; +import java.util.Set; +import java.util.stream.Collectors; + +public class EnumValidator implements ConstraintValidator { + + private Set acceptedValues; + + @Override + public void initialize(EnumValid constraintAnnotation) { + acceptedValues = + Arrays.stream(constraintAnnotation.enumClass().getEnumConstants()) + .map(Enum::name) + .collect(Collectors.toSet()); + } + + @Override + public boolean isValid(String value, ConstraintValidatorContext context) { + return value != null && acceptedValues.contains(value); + } +} diff --git a/src/main/java/com/kamco/cd/training/common/utils/enums/Enums.java b/src/main/java/com/kamco/cd/training/common/utils/enums/Enums.java index 12ecf89..01804b2 100644 --- a/src/main/java/com/kamco/cd/training/common/utils/enums/Enums.java +++ b/src/main/java/com/kamco/cd/training/common/utils/enums/Enums.java @@ -1,76 +1,76 @@ -package com.kamco.cd.training.common.utils.enums; - -import java.util.Arrays; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Set; -import org.reflections.Reflections; - -public class Enums { - - private static final String BASE_PACKAGE = "com.kamco.cd.training"; - - /** 노출 가능한 enum만 모아둔 맵 key: enum simpleName (예: RoleType) value: enum Class */ - private static final Map>> exposedEnumMap = scanExposedEnumMap(); - - // code로 enum 찾기 - public static & EnumType> E fromId(Class enumClass, String id) { - if (id == null) { - return null; - } - - for (E e : enumClass.getEnumConstants()) { - if (id.equalsIgnoreCase(e.getId())) { - return e; - } - } - return null; - } - - // enum -> CodeDto list - public static List toList(Class> enumClass) { - Object[] enums = enumClass.getEnumConstants(); - - return Arrays.stream(enums) - .map(e -> (EnumType) e) - .map(e -> new CodeDto(e.getId(), e.getText())) - .toList(); - } - - /** 특정 타입(enum)만 조회 /codes/{type} -> type = RoleType 같은 값 */ - public static List getCodes(String type) { - Class> enumClass = exposedEnumMap.get(type); - if (enumClass == null) { - throw new IllegalArgumentException("지원하지 않는 코드 타입: " + type); - } - return toList(enumClass); - } - - /** 전체 enum 코드 조회 */ - public static Map> getAllCodes() { - Map> result = new HashMap<>(); - for (Map.Entry>> e : exposedEnumMap.entrySet()) { - result.put(e.getKey(), toList(e.getValue())); - } - return result; - } - - /** - * @CodeExpose + EnumType 인 enum만 스캔해서 Map 구성 - */ - private static Map>> scanExposedEnumMap() { - Reflections reflections = new Reflections(BASE_PACKAGE); - - Set> types = reflections.getTypesAnnotatedWith(CodeExpose.class); - - Map>> result = new HashMap<>(); - - for (Class clazz : types) { - if (clazz.isEnum() && EnumType.class.isAssignableFrom(clazz)) { - result.put(clazz.getSimpleName(), (Class>) clazz); - } - } - return result; - } -} +package com.kamco.cd.training.common.utils.enums; + +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; +import org.reflections.Reflections; + +public class Enums { + + private static final String BASE_PACKAGE = "com.kamco.cd.training"; + + /** 노출 가능한 enum만 모아둔 맵 key: enum simpleName (예: RoleType) value: enum Class */ + private static final Map>> exposedEnumMap = scanExposedEnumMap(); + + // code로 enum 찾기 + public static & EnumType> E fromId(Class enumClass, String id) { + if (id == null) { + return null; + } + + for (E e : enumClass.getEnumConstants()) { + if (id.equalsIgnoreCase(e.getId())) { + return e; + } + } + return null; + } + + // enum -> CodeDto list + public static List toList(Class> enumClass) { + Object[] enums = enumClass.getEnumConstants(); + + return Arrays.stream(enums) + .map(e -> (EnumType) e) + .map(e -> new CodeDto(e.getId(), e.getText())) + .toList(); + } + + /** 특정 타입(enum)만 조회 /codes/{type} -> type = RoleType 같은 값 */ + public static List getCodes(String type) { + Class> enumClass = exposedEnumMap.get(type); + if (enumClass == null) { + throw new IllegalArgumentException("지원하지 않는 코드 타입: " + type); + } + return toList(enumClass); + } + + /** 전체 enum 코드 조회 */ + public static Map> getAllCodes() { + Map> result = new HashMap<>(); + for (Map.Entry>> e : exposedEnumMap.entrySet()) { + result.put(e.getKey(), toList(e.getValue())); + } + return result; + } + + /** + * @CodeExpose + EnumType 인 enum만 스캔해서 Map 구성 + */ + private static Map>> scanExposedEnumMap() { + Reflections reflections = new Reflections(BASE_PACKAGE); + + Set> types = reflections.getTypesAnnotatedWith(CodeExpose.class); + + Map>> result = new HashMap<>(); + + for (Class clazz : types) { + if (clazz.isEnum() && EnumType.class.isAssignableFrom(clazz)) { + result.put(clazz.getSimpleName(), (Class>) clazz); + } + } + return result; + } +} diff --git a/src/main/java/com/kamco/cd/training/common/utils/geometry/GeometryDeserializer.java b/src/main/java/com/kamco/cd/training/common/utils/geometry/GeometryDeserializer.java index 9ae6fcd..8c69df4 100644 --- a/src/main/java/com/kamco/cd/training/common/utils/geometry/GeometryDeserializer.java +++ b/src/main/java/com/kamco/cd/training/common/utils/geometry/GeometryDeserializer.java @@ -1,36 +1,36 @@ -package com.kamco.cd.training.common.utils.geometry; - -import com.fasterxml.jackson.core.JacksonException; -import com.fasterxml.jackson.core.JsonParser; -import com.fasterxml.jackson.databind.DeserializationContext; -import com.fasterxml.jackson.databind.deser.std.StdDeserializer; -import java.io.IOException; -import org.locationtech.jts.geom.Geometry; -import org.locationtech.jts.io.geojson.GeoJsonReader; -import org.springframework.util.StringUtils; - -public class GeometryDeserializer extends StdDeserializer { - - public GeometryDeserializer(Class targetType) { - super(targetType); - } - - // TODO: test code - @SuppressWarnings("unchecked") - @Override - public T deserialize(JsonParser jsonParser, DeserializationContext deserializationContext) - throws IOException, JacksonException { - String json = jsonParser.readValueAsTree().toString(); - - if (!StringUtils.hasText(json)) { - return null; - } - - try { - GeoJsonReader reader = new GeoJsonReader(); - return (T) reader.read(json); - } catch (Exception e) { - throw new IllegalArgumentException("Failed to deserialize GeoJSON into Geometry", e); - } - } -} +package com.kamco.cd.training.common.utils.geometry; + +import com.fasterxml.jackson.core.JacksonException; +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.databind.DeserializationContext; +import com.fasterxml.jackson.databind.deser.std.StdDeserializer; +import java.io.IOException; +import org.locationtech.jts.geom.Geometry; +import org.locationtech.jts.io.geojson.GeoJsonReader; +import org.springframework.util.StringUtils; + +public class GeometryDeserializer extends StdDeserializer { + + public GeometryDeserializer(Class targetType) { + super(targetType); + } + + // TODO: test code + @SuppressWarnings("unchecked") + @Override + public T deserialize(JsonParser jsonParser, DeserializationContext deserializationContext) + throws IOException, JacksonException { + String json = jsonParser.readValueAsTree().toString(); + + if (!StringUtils.hasText(json)) { + return null; + } + + try { + GeoJsonReader reader = new GeoJsonReader(); + return (T) reader.read(json); + } catch (Exception e) { + throw new IllegalArgumentException("Failed to deserialize GeoJSON into Geometry", e); + } + } +} diff --git a/src/main/java/com/kamco/cd/training/common/utils/geometry/GeometrySerializer.java b/src/main/java/com/kamco/cd/training/common/utils/geometry/GeometrySerializer.java index 4d5e066..24d1e7a 100644 --- a/src/main/java/com/kamco/cd/training/common/utils/geometry/GeometrySerializer.java +++ b/src/main/java/com/kamco/cd/training/common/utils/geometry/GeometrySerializer.java @@ -1,31 +1,31 @@ -package com.kamco.cd.training.common.utils.geometry; - -import com.fasterxml.jackson.core.JsonGenerator; -import com.fasterxml.jackson.databind.SerializerProvider; -import com.fasterxml.jackson.databind.ser.std.StdSerializer; -import java.io.IOException; -import java.util.Objects; -import org.locationtech.jts.geom.Geometry; -import org.locationtech.jts.io.geojson.GeoJsonWriter; - -public class GeometrySerializer extends StdSerializer { - - // TODO: test code - public GeometrySerializer(Class targetType) { - super(targetType); - } - - @Override - public void serialize( - T geometry, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) - throws IOException { - if (Objects.nonNull(geometry)) { - // default: 8자리 강제로 반올림시킴. 16자리로 늘려줌 - GeoJsonWriter writer = new GeoJsonWriter(16); - String json = writer.write(geometry); - jsonGenerator.writeRawValue(json); - } else { - jsonGenerator.writeNull(); - } - } -} +package com.kamco.cd.training.common.utils.geometry; + +import com.fasterxml.jackson.core.JsonGenerator; +import com.fasterxml.jackson.databind.SerializerProvider; +import com.fasterxml.jackson.databind.ser.std.StdSerializer; +import java.io.IOException; +import java.util.Objects; +import org.locationtech.jts.geom.Geometry; +import org.locationtech.jts.io.geojson.GeoJsonWriter; + +public class GeometrySerializer extends StdSerializer { + + // TODO: test code + public GeometrySerializer(Class targetType) { + super(targetType); + } + + @Override + public void serialize( + T geometry, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) + throws IOException { + if (Objects.nonNull(geometry)) { + // default: 8자리 강제로 반올림시킴. 16자리로 늘려줌 + GeoJsonWriter writer = new GeoJsonWriter(16); + String json = writer.write(geometry); + jsonGenerator.writeRawValue(json); + } else { + jsonGenerator.writeNull(); + } + } +} diff --git a/src/main/java/com/kamco/cd/training/common/utils/html/HtmlEscapeDeserializer.java b/src/main/java/com/kamco/cd/training/common/utils/html/HtmlEscapeDeserializer.java index 81487a4..e969535 100644 --- a/src/main/java/com/kamco/cd/training/common/utils/html/HtmlEscapeDeserializer.java +++ b/src/main/java/com/kamco/cd/training/common/utils/html/HtmlEscapeDeserializer.java @@ -1,18 +1,18 @@ -package com.kamco.cd.training.common.utils.html; - -import com.fasterxml.jackson.core.JacksonException; -import com.fasterxml.jackson.core.JsonParser; -import com.fasterxml.jackson.databind.DeserializationContext; -import com.fasterxml.jackson.databind.JsonDeserializer; -import java.io.IOException; -import org.springframework.web.util.HtmlUtils; - -public class HtmlEscapeDeserializer extends JsonDeserializer { - - @Override - public Object deserialize(JsonParser jsonParser, DeserializationContext deserializationContext) - throws IOException, JacksonException { - String value = jsonParser.getValueAsString(); - return value == null ? null : HtmlUtils.htmlEscape(value); - } -} +package com.kamco.cd.training.common.utils.html; + +import com.fasterxml.jackson.core.JacksonException; +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.databind.DeserializationContext; +import com.fasterxml.jackson.databind.JsonDeserializer; +import java.io.IOException; +import org.springframework.web.util.HtmlUtils; + +public class HtmlEscapeDeserializer extends JsonDeserializer { + + @Override + public Object deserialize(JsonParser jsonParser, DeserializationContext deserializationContext) + throws IOException, JacksonException { + String value = jsonParser.getValueAsString(); + return value == null ? null : HtmlUtils.htmlEscape(value); + } +} diff --git a/src/main/java/com/kamco/cd/training/common/utils/html/HtmlUnescapeSerializer.java b/src/main/java/com/kamco/cd/training/common/utils/html/HtmlUnescapeSerializer.java index c632d34..3f29c03 100644 --- a/src/main/java/com/kamco/cd/training/common/utils/html/HtmlUnescapeSerializer.java +++ b/src/main/java/com/kamco/cd/training/common/utils/html/HtmlUnescapeSerializer.java @@ -1,20 +1,20 @@ -package com.kamco.cd.training.common.utils.html; - -import com.fasterxml.jackson.core.JsonGenerator; -import com.fasterxml.jackson.databind.JsonSerializer; -import com.fasterxml.jackson.databind.SerializerProvider; -import java.io.IOException; -import org.springframework.web.util.HtmlUtils; - -public class HtmlUnescapeSerializer extends JsonSerializer { - @Override - public void serialize( - String value, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) - throws IOException { - if (value == null) { - jsonGenerator.writeNull(); - } else { - jsonGenerator.writeString(HtmlUtils.htmlUnescape(value)); - } - } -} +package com.kamco.cd.training.common.utils.html; + +import com.fasterxml.jackson.core.JsonGenerator; +import com.fasterxml.jackson.databind.JsonSerializer; +import com.fasterxml.jackson.databind.SerializerProvider; +import java.io.IOException; +import org.springframework.web.util.HtmlUtils; + +public class HtmlUnescapeSerializer extends JsonSerializer { + @Override + public void serialize( + String value, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) + throws IOException { + if (value == null) { + jsonGenerator.writeNull(); + } else { + jsonGenerator.writeString(HtmlUtils.htmlUnescape(value)); + } + } +} diff --git a/src/main/java/com/kamco/cd/training/common/utils/interfaces/EnumValid.java b/src/main/java/com/kamco/cd/training/common/utils/interfaces/EnumValid.java index ab5dc87..1afb61d 100644 --- a/src/main/java/com/kamco/cd/training/common/utils/interfaces/EnumValid.java +++ b/src/main/java/com/kamco/cd/training/common/utils/interfaces/EnumValid.java @@ -1,23 +1,23 @@ -package com.kamco.cd.training.common.utils.interfaces; - -import com.kamco.cd.training.common.utils.enums.EnumValidator; -import jakarta.validation.Constraint; -import jakarta.validation.Payload; -import java.lang.annotation.ElementType; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; - -@Target(ElementType.FIELD) -@Retention(RetentionPolicy.RUNTIME) -@Constraint(validatedBy = EnumValidator.class) -public @interface EnumValid { - - String message() default "올바르지 않은 값입니다."; - - Class[] groups() default {}; - - Class[] payload() default {}; - - Class> enumClass(); -} +package com.kamco.cd.training.common.utils.interfaces; + +import com.kamco.cd.training.common.utils.enums.EnumValidator; +import jakarta.validation.Constraint; +import jakarta.validation.Payload; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Target(ElementType.FIELD) +@Retention(RetentionPolicy.RUNTIME) +@Constraint(validatedBy = EnumValidator.class) +public @interface EnumValid { + + String message() default "올바르지 않은 값입니다."; + + Class[] groups() default {}; + + Class[] payload() default {}; + + Class> enumClass(); +} diff --git a/src/main/java/com/kamco/cd/training/common/utils/interfaces/JsonFormatDttm.java b/src/main/java/com/kamco/cd/training/common/utils/interfaces/JsonFormatDttm.java index 1d4daff..9af3db4 100644 --- a/src/main/java/com/kamco/cd/training/common/utils/interfaces/JsonFormatDttm.java +++ b/src/main/java/com/kamco/cd/training/common/utils/interfaces/JsonFormatDttm.java @@ -1,15 +1,15 @@ -package com.kamco.cd.training.common.utils.interfaces; - -import com.fasterxml.jackson.annotation.JacksonAnnotationsInside; -import com.fasterxml.jackson.annotation.JsonFormat; -import java.lang.annotation.*; - -@Target({ElementType.FIELD, ElementType.METHOD}) -@Retention(RetentionPolicy.RUNTIME) -@Documented -@JacksonAnnotationsInside -@JsonFormat( - shape = JsonFormat.Shape.STRING, - pattern = "yyyy-MM-dd'T'HH:mm:ssXXX", - timezone = "Asia/Seoul") -public @interface JsonFormatDttm {} +package com.kamco.cd.training.common.utils.interfaces; + +import com.fasterxml.jackson.annotation.JacksonAnnotationsInside; +import com.fasterxml.jackson.annotation.JsonFormat; +import java.lang.annotation.*; + +@Target({ElementType.FIELD, ElementType.METHOD}) +@Retention(RetentionPolicy.RUNTIME) +@Documented +@JacksonAnnotationsInside +@JsonFormat( + shape = JsonFormat.Shape.STRING, + pattern = "yyyy-MM-dd'T'HH:mm:ssXXX", + timezone = "Asia/Seoul") +public @interface JsonFormatDttm {} diff --git a/src/main/java/com/kamco/cd/training/config/CacheConfig.java b/src/main/java/com/kamco/cd/training/config/CacheConfig.java index e9b5112..15da3ad 100644 --- a/src/main/java/com/kamco/cd/training/config/CacheConfig.java +++ b/src/main/java/com/kamco/cd/training/config/CacheConfig.java @@ -1,11 +1,11 @@ -package com.kamco.cd.training.config; - -import org.springframework.cache.annotation.EnableCaching; -import org.springframework.context.annotation.Configuration; - -@EnableCaching -@Configuration -public class CacheConfig { - // training 서버는 Redis 사용하지 않고 Spring Boot 메모리 캐시를 사용함 - // => org.springframework.cache.annotation.Cacheable -} +package com.kamco.cd.training.config; + +import org.springframework.cache.annotation.EnableCaching; +import org.springframework.context.annotation.Configuration; + +@EnableCaching +@Configuration +public class CacheConfig { + // training 서버는 Redis 사용하지 않고 Spring Boot 메모리 캐시를 사용함 + // => org.springframework.cache.annotation.Cacheable +} diff --git a/src/main/java/com/kamco/cd/training/config/FileConfig.java b/src/main/java/com/kamco/cd/training/config/FileConfig.java index d4bcfc1..45fd6c7 100644 --- a/src/main/java/com/kamco/cd/training/config/FileConfig.java +++ b/src/main/java/com/kamco/cd/training/config/FileConfig.java @@ -1,27 +1,27 @@ -package com.kamco.cd.training.config; - -import lombok.Getter; -import lombok.Setter; -import org.springframework.boot.context.properties.ConfigurationProperties; -import org.springframework.stereotype.Component; - -/** GeoJSON 파일 모니터링 설정 */ -@Component -@ConfigurationProperties(prefix = "file.config") -@Getter -@Setter -public class FileConfig { - - // private String rootDir = "D:\\app/"; - private String rootDir = "/app/"; - private String rootSyncDir = rootDir + "original-images/"; - private String tmpSyncDir = rootSyncDir + "tmp/"; - - private String dataSetDir = rootDir + "dataset/"; - private String tmpDataSetDir = dataSetDir + "tmp/"; - - // private String rootSyncDir = "/app/original-images/"; - // private String tmpSyncDir = rootSyncDir + "tmp/"; - - private String syncFileExt = "tfw,tif"; -} +package com.kamco.cd.training.config; + +import lombok.Getter; +import lombok.Setter; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.stereotype.Component; + +/** GeoJSON 파일 모니터링 설정 */ +@Component +@ConfigurationProperties(prefix = "file.config") +@Getter +@Setter +public class FileConfig { + + // private String rootDir = "D:\\app/"; + private String rootDir = "/app/"; + private String rootSyncDir = rootDir + "original-images/"; + private String tmpSyncDir = rootSyncDir + "tmp/"; + + private String dataSetDir = rootDir + "dataset/"; + private String tmpDataSetDir = dataSetDir + "tmp/"; + + // private String rootSyncDir = "/app/original-images/"; + // private String tmpSyncDir = rootSyncDir + "tmp/"; + + private String syncFileExt = "tfw,tif"; +} diff --git a/src/main/java/com/kamco/cd/training/config/GlobalExceptionHandler.java b/src/main/java/com/kamco/cd/training/config/GlobalExceptionHandler.java index 5b48c46..6148346 100644 --- a/src/main/java/com/kamco/cd/training/config/GlobalExceptionHandler.java +++ b/src/main/java/com/kamco/cd/training/config/GlobalExceptionHandler.java @@ -1,528 +1,528 @@ -package com.kamco.cd.training.config; - -import com.kamco.cd.training.auth.CustomUserDetails; -import com.kamco.cd.training.common.exception.CustomApiException; -import com.kamco.cd.training.common.exception.DuplicateFileException; -import com.kamco.cd.training.config.api.ApiLogFunction; -import com.kamco.cd.training.config.api.ApiResponseDto; -import com.kamco.cd.training.config.api.ApiResponseDto.ApiResponseCode; -import com.kamco.cd.training.log.dto.ErrorLogDto; -import com.kamco.cd.training.members.exception.MemberException; -import com.kamco.cd.training.postgres.entity.ErrorLogEntity; -import com.kamco.cd.training.postgres.repository.log.ErrorLogRepository; -import io.micrometer.core.instrument.config.validate.ValidationException; -import jakarta.persistence.EntityNotFoundException; -import jakarta.servlet.http.HttpServletRequest; -import java.nio.file.AccessDeniedException; -import java.time.ZonedDateTime; -import java.util.Arrays; -import java.util.NoSuchElementException; -import java.util.stream.Collectors; -import lombok.extern.slf4j.Slf4j; -import org.springframework.core.annotation.Order; -import org.springframework.dao.DataIntegrityViolationException; -import org.springframework.dao.DuplicateKeyException; -import org.springframework.http.HttpStatus; -import org.springframework.http.ResponseEntity; -import org.springframework.http.converter.HttpMessageNotReadableException; -import org.springframework.security.authentication.BadCredentialsException; -import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; -import org.springframework.web.bind.MethodArgumentNotValidException; -import org.springframework.web.bind.annotation.ExceptionHandler; -import org.springframework.web.bind.annotation.ResponseStatus; -import org.springframework.web.bind.annotation.RestControllerAdvice; -import org.springframework.web.client.HttpServerErrorException; - -@Slf4j -@Order(value = 1) -@RestControllerAdvice -public class GlobalExceptionHandler { - - private final ErrorLogRepository errorLogRepository; - - public GlobalExceptionHandler(ErrorLogRepository errorLogRepository) { - this.errorLogRepository = errorLogRepository; - } - - @ResponseStatus(HttpStatus.CONFLICT) - @ExceptionHandler(DuplicateFileException.class) - public ApiResponseDto handleDuplicateFileException( - DuplicateFileException e, HttpServletRequest request) { - log.warn("[DuplicateFileException] resource :{} ", e.getMessage()); - this.errorLog(request, e); - ApiResponseCode code = ApiResponseCode.CONFLICT; - ErrorLogEntity errorLog = - saveErrorLogData( - request, - code, - HttpStatus.CONFLICT, - ErrorLogDto.LogErrorLevel.WARNING, - e.getStackTrace()); - - return ApiResponseDto.createException( - code, e.getMessage(), HttpStatus.CONFLICT, errorLog.getId()); - } - - @ResponseStatus(HttpStatus.UNPROCESSABLE_ENTITY) - @ExceptionHandler(ValidationException.class) - public ApiResponseDto handleValidationException( - ValidationException e, HttpServletRequest request) { - log.warn("[ValidationException] resource :{} ", e.getMessage()); - this.errorLog(request, e); - ApiResponseCode code = ApiResponseCode.UNPROCESSABLE_ENTITY; - ErrorLogEntity errorLog = - saveErrorLogData( - request, - code, - HttpStatus.UNPROCESSABLE_ENTITY, - ErrorLogDto.LogErrorLevel.WARNING, - e.getStackTrace()); - - return ApiResponseDto.createException( - code, e.getMessage(), HttpStatus.UNPROCESSABLE_ENTITY, errorLog.getId()); - } - - @ResponseStatus(HttpStatus.BAD_REQUEST) - @ExceptionHandler(IllegalArgumentException.class) - public ApiResponseDto handleIllegalArgumentException( - IllegalArgumentException e, HttpServletRequest request) { - log.warn("[IllegalArgumentException] resource :{} ", e.getMessage()); - this.errorLog(request, e); - ApiResponseCode code = ApiResponseCode.BAD_REQUEST; - ErrorLogEntity errorLog = - saveErrorLogData( - request, - code, - HttpStatus.BAD_REQUEST, - ErrorLogDto.LogErrorLevel.WARNING, - e.getStackTrace()); - - return ApiResponseDto.createException( - code, e.getMessage(), HttpStatus.BAD_REQUEST, errorLog.getId()); - } - - @ResponseStatus(HttpStatus.UNPROCESSABLE_ENTITY) - @ExceptionHandler(EntityNotFoundException.class) - public ApiResponseDto handlerEntityNotFoundException( - EntityNotFoundException e, HttpServletRequest request) { - log.warn("[EntityNotFoundException] resource :{} ", e.getMessage()); - this.errorLog(request, e); - String codeName = "NOT_FOUND_DATA"; - ErrorLogEntity errorLog = - saveErrorLogData( - request, - ApiResponseCode.getCode(codeName), - HttpStatus.valueOf("UNPROCESSABLE_ENTITY"), - ErrorLogDto.LogErrorLevel.WARNING, - e.getStackTrace()); - - return ApiResponseDto.createException( - ApiResponseCode.getCode(codeName), - ApiResponseCode.getMessage(codeName), - HttpStatus.valueOf("UNPROCESSABLE_ENTITY"), - errorLog.getId()); - } - - @ResponseStatus(HttpStatus.BAD_REQUEST) - @ExceptionHandler(HttpMessageNotReadableException.class) - public ApiResponseDto handleUnreadable( - HttpMessageNotReadableException e, HttpServletRequest request) { - log.warn("[HttpMessageNotReadableException] resource :{} ", e.getMessage()); - this.errorLog(request, e); - String codeName = "BAD_REQUEST"; - ErrorLogEntity errorLog = - saveErrorLogData( - request, - ApiResponseCode.getCode(codeName), - HttpStatus.valueOf(codeName), - ErrorLogDto.LogErrorLevel.WARNING, - e.getStackTrace()); - - return ApiResponseDto.createException( - ApiResponseCode.getCode(codeName), - ApiResponseCode.getMessage(codeName), - HttpStatus.valueOf(codeName), - errorLog.getId()); - } - - @ResponseStatus(HttpStatus.NOT_FOUND) - @ExceptionHandler(NoSuchElementException.class) - public ApiResponseDto handlerNoSuchElementException( - NoSuchElementException e, HttpServletRequest request) { - log.warn("[NoSuchElementException] resource :{} ", e.getMessage()); - this.errorLog(request, e); - String codeName = "NOT_FOUND_DATA"; - ErrorLogEntity errorLog = - saveErrorLogData( - request, - ApiResponseCode.getCode(codeName), - HttpStatus.valueOf(codeName), - ErrorLogDto.LogErrorLevel.WARNING, - e.getStackTrace()); - - return ApiResponseDto.createException( - ApiResponseCode.getCode(codeName), - ApiResponseCode.getMessage(codeName), - HttpStatus.valueOf("NOT_FOUND"), - errorLog.getId()); - } - - @ResponseStatus(HttpStatus.UNPROCESSABLE_ENTITY) - @ExceptionHandler(DataIntegrityViolationException.class) - public ApiResponseDto handlerDataIntegrityViolationException( - DataIntegrityViolationException e, HttpServletRequest request) { - log.warn("[DataIntegrityViolationException] resource :{} ", e.getMessage()); - this.errorLog(request, e); - String codeName = "DATA_INTEGRITY_ERROR"; - ErrorLogEntity errorLog = - saveErrorLogData( - request, - ApiResponseCode.getCode(codeName), - HttpStatus.valueOf("UNPROCESSABLE_ENTITY"), - ErrorLogDto.LogErrorLevel.CRITICAL, - e.getStackTrace()); - - return ApiResponseDto.createException( - ApiResponseCode.getCode(codeName), - ApiResponseCode.getMessage(codeName), - HttpStatus.valueOf("UNPROCESSABLE_ENTITY"), - errorLog.getId()); - } - - @ResponseStatus(HttpStatus.BAD_REQUEST) - @ExceptionHandler(MethodArgumentNotValidException.class) - public ApiResponseDto handlerMethodArgumentNotValidException( - MethodArgumentNotValidException e, HttpServletRequest request) { - log.warn("[MethodArgumentNotValidException] resource :{} ", e.getMessage()); - this.errorLog(request, e); - String codeName = "BAD_REQUEST"; - ErrorLogEntity errorLog = - saveErrorLogData( - request, - ApiResponseCode.getCode(codeName), - HttpStatus.valueOf(codeName), - ErrorLogDto.LogErrorLevel.WARNING, - e.getStackTrace()); - - return ApiResponseDto.createException( - ApiResponseCode.getCode(codeName), - ApiResponseCode.getMessage(codeName), - HttpStatus.valueOf(codeName), - errorLog.getId()); - } - - @ResponseStatus(HttpStatus.FORBIDDEN) - @ExceptionHandler(AccessDeniedException.class) - public ApiResponseDto handlerAccessDeniedException( - AccessDeniedException e, HttpServletRequest request) { - log.warn("[AccessDeniedException] resource :{} ", e.getMessage()); - this.errorLog(request, e); - String codeName = "FORBIDDEN"; - ErrorLogEntity errorLog = - saveErrorLogData( - request, - ApiResponseCode.getCode(codeName), - HttpStatus.valueOf(codeName), - ErrorLogDto.LogErrorLevel.ERROR, - e.getStackTrace()); - - return ApiResponseDto.createException( - ApiResponseCode.getCode(codeName), - ApiResponseCode.getMessage(codeName), - HttpStatus.valueOf(codeName), - errorLog.getId()); - } - - @ResponseStatus(HttpStatus.BAD_GATEWAY) - @ExceptionHandler(HttpServerErrorException.BadGateway.class) - public ApiResponseDto handlerHttpServerErrorException( - HttpServerErrorException e, HttpServletRequest request) { - log.warn("[HttpServerErrorException] resource :{} ", e.getMessage()); - this.errorLog(request, e); - String codeName = "BAD_GATEWAY"; - ErrorLogEntity errorLog = - saveErrorLogData( - request, - ApiResponseCode.getCode(codeName), - HttpStatus.valueOf(codeName), - ErrorLogDto.LogErrorLevel.CRITICAL, - e.getStackTrace()); - - return ApiResponseDto.createException( - ApiResponseCode.getCode(codeName), - ApiResponseCode.getMessage(codeName), - HttpStatus.valueOf(codeName), - errorLog.getId()); - } - - @ResponseStatus(HttpStatus.UNPROCESSABLE_ENTITY) - @ExceptionHandler(IllegalStateException.class) - public ApiResponseDto handlerIllegalStateException( - IllegalStateException e, HttpServletRequest request) { - log.warn("[IllegalStateException] resource :{} ", e.getMessage()); - this.errorLog(request, e); - String codeName = "UNPROCESSABLE_ENTITY"; - ErrorLogEntity errorLog = - saveErrorLogData( - request, - ApiResponseCode.getCode(codeName), - HttpStatus.valueOf(codeName), - ErrorLogDto.LogErrorLevel.WARNING, - e.getStackTrace()); - - return ApiResponseDto.createException( - ApiResponseCode.getCode(codeName), - ApiResponseCode.getMessage(codeName), - HttpStatus.valueOf(codeName), - errorLog.getId()); - } - - @ResponseStatus(HttpStatus.BAD_REQUEST) - @ExceptionHandler(MemberException.DuplicateMemberException.class) - public ApiResponseDto handlerDuplicateMemberException( - MemberException.DuplicateMemberException e, HttpServletRequest request) { - log.warn("[DuplicateMemberException] resource :{} ", e.getMessage()); - this.errorLog(request, e); - String codeName = ""; - - switch (e.getField()) { - case EMPLOYEE_NO -> { - codeName = "DUPLICATE_EMPLOYEEID"; - } - default -> { - codeName = "DUPLICATE_DATA"; - } - } - - ErrorLogEntity errorLog = - saveErrorLogData( - request, - ApiResponseCode.getCode(codeName), - HttpStatus.valueOf("BAD_REQUEST"), - ErrorLogDto.LogErrorLevel.WARNING, - e.getStackTrace()); - - return ApiResponseDto.createException( - ApiResponseCode.getCode(codeName), - ApiResponseCode.getMessage(codeName), - HttpStatus.valueOf("BAD_REQUEST"), - errorLog.getId()); - } - - @ResponseStatus(HttpStatus.BAD_REQUEST) - @ExceptionHandler(MemberException.MemberNotFoundException.class) - public ApiResponseDto handlerMemberNotFoundException( - MemberException.MemberNotFoundException e, HttpServletRequest request) { - log.warn("[MemberNotFoundException] resource :{} ", e.getMessage()); - this.errorLog(request, e); - String codeName = "NOT_FOUND_USER"; - - ErrorLogEntity errorLog = - saveErrorLogData( - request, - ApiResponseCode.getCode(codeName), - HttpStatus.valueOf("BAD_REQUEST"), - ErrorLogDto.LogErrorLevel.WARNING, - e.getStackTrace()); - - return ApiResponseDto.createException( - ApiResponseCode.getCode(codeName), - ApiResponseCode.getMessage(codeName), - HttpStatus.valueOf("BAD_REQUEST"), - errorLog.getId()); - } - - @ResponseStatus(HttpStatus.CONFLICT) - @ExceptionHandler(DuplicateKeyException.class) - public ApiResponseDto handlerDuplicateKeyException( - DuplicateKeyException e, HttpServletRequest request) { - log.warn("[DuplicateKeyException] resource :{} ", e.getMessage()); - this.errorLog(request, e); - String codeName = "DUPLICATE_DATA"; - - ErrorLogEntity errorLog = - saveErrorLogData( - request, - ApiResponseCode.getCode(codeName), - HttpStatus.valueOf("CONFLICT"), - ErrorLogDto.LogErrorLevel.WARNING, - e.getStackTrace()); - - return ApiResponseDto.createException( - ApiResponseCode.getCode(codeName), - ApiResponseCode.getMessage(codeName), - HttpStatus.valueOf("CONFLICT"), - errorLog.getId()); - } - - @ExceptionHandler(BadCredentialsException.class) - public ResponseEntity> handleBadCredentials( - BadCredentialsException e, HttpServletRequest request) { - log.warn("[BadCredentialsException] resource : {} ", e.getMessage()); - this.errorLog(request, e); - String codeName = "UNAUTHORIZED"; - - ErrorLogEntity errorLog = - saveErrorLogData( - request, - ApiResponseCode.getCode(codeName), - HttpStatus.valueOf(codeName), - ErrorLogDto.LogErrorLevel.WARNING, - e.getStackTrace()); - - ApiResponseDto body = - ApiResponseDto.createException( - ApiResponseCode.getCode(codeName), - ApiResponseCode.getMessage(codeName), - HttpStatus.valueOf(codeName), - errorLog.getId()); - - return ResponseEntity.status(HttpStatus.UNAUTHORIZED) // 🔥 여기서 401 지정 - .body(body); - } - - @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR) - @ExceptionHandler(RuntimeException.class) - public ApiResponseDto handlerRuntimeException( - RuntimeException e, HttpServletRequest request) { - log.warn("[RuntimeException] resource :{} ", e.getMessage()); - this.errorLog(request, e); - String codeName = "INTERNAL_SERVER_ERROR"; - ErrorLogEntity errorLog = - saveErrorLogData( - request, - ApiResponseCode.getCode(codeName), - HttpStatus.valueOf(codeName), - ErrorLogDto.LogErrorLevel.CRITICAL, - e.getStackTrace()); - - return ApiResponseDto.createException( - ApiResponseCode.getCode(codeName), - ApiResponseCode.getMessage(codeName), - HttpStatus.valueOf(codeName), - errorLog.getId()); - } - - @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR) - @ExceptionHandler(Exception.class) - public ApiResponseDto handlerException(Exception e, HttpServletRequest request) { - log.warn("[Exception] resource :{} ", e.getMessage()); - this.errorLog(request, e); - String codeName = "INTERNAL_SERVER_ERROR"; - ErrorLogEntity errorLog = - saveErrorLogData( - request, - ApiResponseCode.getCode(codeName), - HttpStatus.valueOf(codeName), - ErrorLogDto.LogErrorLevel.CRITICAL, - e.getStackTrace()); - - return ApiResponseDto.createException( - ApiResponseCode.getCode(codeName), - ApiResponseCode.getMessage(codeName), - HttpStatus.valueOf(codeName), - errorLog.getId()); - } - - /** - * 에러 로그 테이블 저장 로직 - * - * @param request : request - * @param errorCode : 정의된 enum errorCode - * @param httpStatus : HttpStatus 값 - * @param logErrorLevel : WARNING, ERROR, CRITICAL - * @param stackTrace : 에러 내용 - * @return : insert하고 결과로 받은 Entity - */ - private ErrorLogEntity saveErrorLogData( - HttpServletRequest request, - ApiResponseCode errorCode, - HttpStatus httpStatus, - ErrorLogDto.LogErrorLevel logErrorLevel, - StackTraceElement[] stackTrace) { - - Long userid = null; - - /** - * servletRequest.getUserPrincipal() instanceof UsernamePasswordAuthenticationToken auth 이 요청이 - * JWT 인증을 통과한 요청인가? 그리고 Spring Security Authentication 객체가 UsernamePasswordAuthenticationToken - * 타입인가? 체크 - */ - /** - * auth.getPrincipal() instanceof CustomUserDetails customUserDetails principal 안에 들어있는 객체가 내가 - * 만든 CustomUserDetails 타입인가? 체크 - */ - if (request.getUserPrincipal() instanceof UsernamePasswordAuthenticationToken auth - && auth.getPrincipal() instanceof CustomUserDetails customUserDetails) { - - // audit 에는 long 타입 user_id가 들어가지만 토큰 sub은 uuid여서 user_id 가져오기 - userid = customUserDetails.getMember().getId(); - } - - String stackTraceStr = - Arrays.stream(stackTrace) - .map(StackTraceElement::toString) - .collect(Collectors.joining("\n")) - .substring(0, Math.min(stackTrace.length, 255)); - - ErrorLogEntity errorLogEntity = - new ErrorLogEntity( - request.getRequestURI(), - ApiLogFunction.getEventType(request), - logErrorLevel, - String.valueOf(httpStatus.value()), - errorCode.getText(), - stackTraceStr, - userid, - ZonedDateTime.now()); - - return errorLogRepository.save(errorLogEntity); - } - - @ExceptionHandler(CustomApiException.class) - public ApiResponseDto handleCustomApiException( - CustomApiException e, HttpServletRequest request) { - log.warn("[CustomApiException] resource : {}", e.getMessage()); - this.errorLog(request, e); - String codeName = e.getCodeName(); - HttpStatus status = e.getStatus(); - // String message = e.getMessage() == null ? ApiResponseCode.getMessage(codeName) : - // e.getMessage(); - // - // ApiResponseCode apiCode = ApiResponseCode.getCode(codeName); - // - // ErrorLogEntity errorLog = - // saveErrorLogData( - // request, apiCode, status, ErrorLogDto.LogErrorLevel.WARNING, e.getStackTrace()); - // - // ApiResponseDto body = - // ApiResponseDto.createException(apiCode, message, status, errorLog.getId()); - - ErrorLogEntity errorLog = - saveErrorLogData( - request, - ApiResponseCode.getCode(codeName), - HttpStatus.valueOf(status.value()), - ErrorLogDto.LogErrorLevel.WARNING, - e.getStackTrace()); - - return ApiResponseDto.createException( - ApiResponseCode.getCode(codeName), - ApiResponseCode.getMessage(codeName), - HttpStatus.valueOf(status.value()), - errorLog.getId()); - - // return new ResponseEntity<>(body, status); - } - - private void errorLog(HttpServletRequest request, Throwable e) { - String uri = request != null ? request.getRequestURI() : "N/A"; - - // 예외 타입명 (IllegalArgumentException, BadCredentialsException 등) - String exceptionName = e.getClass().getSimpleName(); - - log.error("[{}] uri={}, message={}", exceptionName, uri, e.getMessage()); - - log.error("[{}] stacktrace", exceptionName, e); - } -} +package com.kamco.cd.training.config; + +import com.kamco.cd.training.auth.CustomUserDetails; +import com.kamco.cd.training.common.exception.CustomApiException; +import com.kamco.cd.training.common.exception.DuplicateFileException; +import com.kamco.cd.training.config.api.ApiLogFunction; +import com.kamco.cd.training.config.api.ApiResponseDto; +import com.kamco.cd.training.config.api.ApiResponseDto.ApiResponseCode; +import com.kamco.cd.training.log.dto.ErrorLogDto; +import com.kamco.cd.training.members.exception.MemberException; +import com.kamco.cd.training.postgres.entity.ErrorLogEntity; +import com.kamco.cd.training.postgres.repository.log.ErrorLogRepository; +import io.micrometer.core.instrument.config.validate.ValidationException; +import jakarta.persistence.EntityNotFoundException; +import jakarta.servlet.http.HttpServletRequest; +import java.nio.file.AccessDeniedException; +import java.time.ZonedDateTime; +import java.util.Arrays; +import java.util.NoSuchElementException; +import java.util.stream.Collectors; +import lombok.extern.slf4j.Slf4j; +import org.springframework.core.annotation.Order; +import org.springframework.dao.DataIntegrityViolationException; +import org.springframework.dao.DuplicateKeyException; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.http.converter.HttpMessageNotReadableException; +import org.springframework.security.authentication.BadCredentialsException; +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; +import org.springframework.web.bind.MethodArgumentNotValidException; +import org.springframework.web.bind.annotation.ExceptionHandler; +import org.springframework.web.bind.annotation.ResponseStatus; +import org.springframework.web.bind.annotation.RestControllerAdvice; +import org.springframework.web.client.HttpServerErrorException; + +@Slf4j +@Order(value = 1) +@RestControllerAdvice +public class GlobalExceptionHandler { + + private final ErrorLogRepository errorLogRepository; + + public GlobalExceptionHandler(ErrorLogRepository errorLogRepository) { + this.errorLogRepository = errorLogRepository; + } + + @ResponseStatus(HttpStatus.CONFLICT) + @ExceptionHandler(DuplicateFileException.class) + public ApiResponseDto handleDuplicateFileException( + DuplicateFileException e, HttpServletRequest request) { + log.warn("[DuplicateFileException] resource :{} ", e.getMessage()); + this.errorLog(request, e); + ApiResponseCode code = ApiResponseCode.CONFLICT; + ErrorLogEntity errorLog = + saveErrorLogData( + request, + code, + HttpStatus.CONFLICT, + ErrorLogDto.LogErrorLevel.WARNING, + e.getStackTrace()); + + return ApiResponseDto.createException( + code, e.getMessage(), HttpStatus.CONFLICT, errorLog.getId()); + } + + @ResponseStatus(HttpStatus.UNPROCESSABLE_ENTITY) + @ExceptionHandler(ValidationException.class) + public ApiResponseDto handleValidationException( + ValidationException e, HttpServletRequest request) { + log.warn("[ValidationException] resource :{} ", e.getMessage()); + this.errorLog(request, e); + ApiResponseCode code = ApiResponseCode.UNPROCESSABLE_ENTITY; + ErrorLogEntity errorLog = + saveErrorLogData( + request, + code, + HttpStatus.UNPROCESSABLE_ENTITY, + ErrorLogDto.LogErrorLevel.WARNING, + e.getStackTrace()); + + return ApiResponseDto.createException( + code, e.getMessage(), HttpStatus.UNPROCESSABLE_ENTITY, errorLog.getId()); + } + + @ResponseStatus(HttpStatus.BAD_REQUEST) + @ExceptionHandler(IllegalArgumentException.class) + public ApiResponseDto handleIllegalArgumentException( + IllegalArgumentException e, HttpServletRequest request) { + log.warn("[IllegalArgumentException] resource :{} ", e.getMessage()); + this.errorLog(request, e); + ApiResponseCode code = ApiResponseCode.BAD_REQUEST; + ErrorLogEntity errorLog = + saveErrorLogData( + request, + code, + HttpStatus.BAD_REQUEST, + ErrorLogDto.LogErrorLevel.WARNING, + e.getStackTrace()); + + return ApiResponseDto.createException( + code, e.getMessage(), HttpStatus.BAD_REQUEST, errorLog.getId()); + } + + @ResponseStatus(HttpStatus.UNPROCESSABLE_ENTITY) + @ExceptionHandler(EntityNotFoundException.class) + public ApiResponseDto handlerEntityNotFoundException( + EntityNotFoundException e, HttpServletRequest request) { + log.warn("[EntityNotFoundException] resource :{} ", e.getMessage()); + this.errorLog(request, e); + String codeName = "NOT_FOUND_DATA"; + ErrorLogEntity errorLog = + saveErrorLogData( + request, + ApiResponseCode.getCode(codeName), + HttpStatus.valueOf("UNPROCESSABLE_ENTITY"), + ErrorLogDto.LogErrorLevel.WARNING, + e.getStackTrace()); + + return ApiResponseDto.createException( + ApiResponseCode.getCode(codeName), + ApiResponseCode.getMessage(codeName), + HttpStatus.valueOf("UNPROCESSABLE_ENTITY"), + errorLog.getId()); + } + + @ResponseStatus(HttpStatus.BAD_REQUEST) + @ExceptionHandler(HttpMessageNotReadableException.class) + public ApiResponseDto handleUnreadable( + HttpMessageNotReadableException e, HttpServletRequest request) { + log.warn("[HttpMessageNotReadableException] resource :{} ", e.getMessage()); + this.errorLog(request, e); + String codeName = "BAD_REQUEST"; + ErrorLogEntity errorLog = + saveErrorLogData( + request, + ApiResponseCode.getCode(codeName), + HttpStatus.valueOf(codeName), + ErrorLogDto.LogErrorLevel.WARNING, + e.getStackTrace()); + + return ApiResponseDto.createException( + ApiResponseCode.getCode(codeName), + ApiResponseCode.getMessage(codeName), + HttpStatus.valueOf(codeName), + errorLog.getId()); + } + + @ResponseStatus(HttpStatus.NOT_FOUND) + @ExceptionHandler(NoSuchElementException.class) + public ApiResponseDto handlerNoSuchElementException( + NoSuchElementException e, HttpServletRequest request) { + log.warn("[NoSuchElementException] resource :{} ", e.getMessage()); + this.errorLog(request, e); + String codeName = "NOT_FOUND_DATA"; + ErrorLogEntity errorLog = + saveErrorLogData( + request, + ApiResponseCode.getCode(codeName), + HttpStatus.valueOf(codeName), + ErrorLogDto.LogErrorLevel.WARNING, + e.getStackTrace()); + + return ApiResponseDto.createException( + ApiResponseCode.getCode(codeName), + ApiResponseCode.getMessage(codeName), + HttpStatus.valueOf("NOT_FOUND"), + errorLog.getId()); + } + + @ResponseStatus(HttpStatus.UNPROCESSABLE_ENTITY) + @ExceptionHandler(DataIntegrityViolationException.class) + public ApiResponseDto handlerDataIntegrityViolationException( + DataIntegrityViolationException e, HttpServletRequest request) { + log.warn("[DataIntegrityViolationException] resource :{} ", e.getMessage()); + this.errorLog(request, e); + String codeName = "DATA_INTEGRITY_ERROR"; + ErrorLogEntity errorLog = + saveErrorLogData( + request, + ApiResponseCode.getCode(codeName), + HttpStatus.valueOf("UNPROCESSABLE_ENTITY"), + ErrorLogDto.LogErrorLevel.CRITICAL, + e.getStackTrace()); + + return ApiResponseDto.createException( + ApiResponseCode.getCode(codeName), + ApiResponseCode.getMessage(codeName), + HttpStatus.valueOf("UNPROCESSABLE_ENTITY"), + errorLog.getId()); + } + + @ResponseStatus(HttpStatus.BAD_REQUEST) + @ExceptionHandler(MethodArgumentNotValidException.class) + public ApiResponseDto handlerMethodArgumentNotValidException( + MethodArgumentNotValidException e, HttpServletRequest request) { + log.warn("[MethodArgumentNotValidException] resource :{} ", e.getMessage()); + this.errorLog(request, e); + String codeName = "BAD_REQUEST"; + ErrorLogEntity errorLog = + saveErrorLogData( + request, + ApiResponseCode.getCode(codeName), + HttpStatus.valueOf(codeName), + ErrorLogDto.LogErrorLevel.WARNING, + e.getStackTrace()); + + return ApiResponseDto.createException( + ApiResponseCode.getCode(codeName), + ApiResponseCode.getMessage(codeName), + HttpStatus.valueOf(codeName), + errorLog.getId()); + } + + @ResponseStatus(HttpStatus.FORBIDDEN) + @ExceptionHandler(AccessDeniedException.class) + public ApiResponseDto handlerAccessDeniedException( + AccessDeniedException e, HttpServletRequest request) { + log.warn("[AccessDeniedException] resource :{} ", e.getMessage()); + this.errorLog(request, e); + String codeName = "FORBIDDEN"; + ErrorLogEntity errorLog = + saveErrorLogData( + request, + ApiResponseCode.getCode(codeName), + HttpStatus.valueOf(codeName), + ErrorLogDto.LogErrorLevel.ERROR, + e.getStackTrace()); + + return ApiResponseDto.createException( + ApiResponseCode.getCode(codeName), + ApiResponseCode.getMessage(codeName), + HttpStatus.valueOf(codeName), + errorLog.getId()); + } + + @ResponseStatus(HttpStatus.BAD_GATEWAY) + @ExceptionHandler(HttpServerErrorException.BadGateway.class) + public ApiResponseDto handlerHttpServerErrorException( + HttpServerErrorException e, HttpServletRequest request) { + log.warn("[HttpServerErrorException] resource :{} ", e.getMessage()); + this.errorLog(request, e); + String codeName = "BAD_GATEWAY"; + ErrorLogEntity errorLog = + saveErrorLogData( + request, + ApiResponseCode.getCode(codeName), + HttpStatus.valueOf(codeName), + ErrorLogDto.LogErrorLevel.CRITICAL, + e.getStackTrace()); + + return ApiResponseDto.createException( + ApiResponseCode.getCode(codeName), + ApiResponseCode.getMessage(codeName), + HttpStatus.valueOf(codeName), + errorLog.getId()); + } + + @ResponseStatus(HttpStatus.UNPROCESSABLE_ENTITY) + @ExceptionHandler(IllegalStateException.class) + public ApiResponseDto handlerIllegalStateException( + IllegalStateException e, HttpServletRequest request) { + log.warn("[IllegalStateException] resource :{} ", e.getMessage()); + this.errorLog(request, e); + String codeName = "UNPROCESSABLE_ENTITY"; + ErrorLogEntity errorLog = + saveErrorLogData( + request, + ApiResponseCode.getCode(codeName), + HttpStatus.valueOf(codeName), + ErrorLogDto.LogErrorLevel.WARNING, + e.getStackTrace()); + + return ApiResponseDto.createException( + ApiResponseCode.getCode(codeName), + ApiResponseCode.getMessage(codeName), + HttpStatus.valueOf(codeName), + errorLog.getId()); + } + + @ResponseStatus(HttpStatus.BAD_REQUEST) + @ExceptionHandler(MemberException.DuplicateMemberException.class) + public ApiResponseDto handlerDuplicateMemberException( + MemberException.DuplicateMemberException e, HttpServletRequest request) { + log.warn("[DuplicateMemberException] resource :{} ", e.getMessage()); + this.errorLog(request, e); + String codeName = ""; + + switch (e.getField()) { + case EMPLOYEE_NO -> { + codeName = "DUPLICATE_EMPLOYEEID"; + } + default -> { + codeName = "DUPLICATE_DATA"; + } + } + + ErrorLogEntity errorLog = + saveErrorLogData( + request, + ApiResponseCode.getCode(codeName), + HttpStatus.valueOf("BAD_REQUEST"), + ErrorLogDto.LogErrorLevel.WARNING, + e.getStackTrace()); + + return ApiResponseDto.createException( + ApiResponseCode.getCode(codeName), + ApiResponseCode.getMessage(codeName), + HttpStatus.valueOf("BAD_REQUEST"), + errorLog.getId()); + } + + @ResponseStatus(HttpStatus.BAD_REQUEST) + @ExceptionHandler(MemberException.MemberNotFoundException.class) + public ApiResponseDto handlerMemberNotFoundException( + MemberException.MemberNotFoundException e, HttpServletRequest request) { + log.warn("[MemberNotFoundException] resource :{} ", e.getMessage()); + this.errorLog(request, e); + String codeName = "NOT_FOUND_USER"; + + ErrorLogEntity errorLog = + saveErrorLogData( + request, + ApiResponseCode.getCode(codeName), + HttpStatus.valueOf("BAD_REQUEST"), + ErrorLogDto.LogErrorLevel.WARNING, + e.getStackTrace()); + + return ApiResponseDto.createException( + ApiResponseCode.getCode(codeName), + ApiResponseCode.getMessage(codeName), + HttpStatus.valueOf("BAD_REQUEST"), + errorLog.getId()); + } + + @ResponseStatus(HttpStatus.CONFLICT) + @ExceptionHandler(DuplicateKeyException.class) + public ApiResponseDto handlerDuplicateKeyException( + DuplicateKeyException e, HttpServletRequest request) { + log.warn("[DuplicateKeyException] resource :{} ", e.getMessage()); + this.errorLog(request, e); + String codeName = "DUPLICATE_DATA"; + + ErrorLogEntity errorLog = + saveErrorLogData( + request, + ApiResponseCode.getCode(codeName), + HttpStatus.valueOf("CONFLICT"), + ErrorLogDto.LogErrorLevel.WARNING, + e.getStackTrace()); + + return ApiResponseDto.createException( + ApiResponseCode.getCode(codeName), + ApiResponseCode.getMessage(codeName), + HttpStatus.valueOf("CONFLICT"), + errorLog.getId()); + } + + @ExceptionHandler(BadCredentialsException.class) + public ResponseEntity> handleBadCredentials( + BadCredentialsException e, HttpServletRequest request) { + log.warn("[BadCredentialsException] resource : {} ", e.getMessage()); + this.errorLog(request, e); + String codeName = "UNAUTHORIZED"; + + ErrorLogEntity errorLog = + saveErrorLogData( + request, + ApiResponseCode.getCode(codeName), + HttpStatus.valueOf(codeName), + ErrorLogDto.LogErrorLevel.WARNING, + e.getStackTrace()); + + ApiResponseDto body = + ApiResponseDto.createException( + ApiResponseCode.getCode(codeName), + ApiResponseCode.getMessage(codeName), + HttpStatus.valueOf(codeName), + errorLog.getId()); + + return ResponseEntity.status(HttpStatus.UNAUTHORIZED) // 🔥 여기서 401 지정 + .body(body); + } + + @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR) + @ExceptionHandler(RuntimeException.class) + public ApiResponseDto handlerRuntimeException( + RuntimeException e, HttpServletRequest request) { + log.warn("[RuntimeException] resource :{} ", e.getMessage()); + this.errorLog(request, e); + String codeName = "INTERNAL_SERVER_ERROR"; + ErrorLogEntity errorLog = + saveErrorLogData( + request, + ApiResponseCode.getCode(codeName), + HttpStatus.valueOf(codeName), + ErrorLogDto.LogErrorLevel.CRITICAL, + e.getStackTrace()); + + return ApiResponseDto.createException( + ApiResponseCode.getCode(codeName), + ApiResponseCode.getMessage(codeName), + HttpStatus.valueOf(codeName), + errorLog.getId()); + } + + @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR) + @ExceptionHandler(Exception.class) + public ApiResponseDto handlerException(Exception e, HttpServletRequest request) { + log.warn("[Exception] resource :{} ", e.getMessage()); + this.errorLog(request, e); + String codeName = "INTERNAL_SERVER_ERROR"; + ErrorLogEntity errorLog = + saveErrorLogData( + request, + ApiResponseCode.getCode(codeName), + HttpStatus.valueOf(codeName), + ErrorLogDto.LogErrorLevel.CRITICAL, + e.getStackTrace()); + + return ApiResponseDto.createException( + ApiResponseCode.getCode(codeName), + ApiResponseCode.getMessage(codeName), + HttpStatus.valueOf(codeName), + errorLog.getId()); + } + + /** + * 에러 로그 테이블 저장 로직 + * + * @param request : request + * @param errorCode : 정의된 enum errorCode + * @param httpStatus : HttpStatus 값 + * @param logErrorLevel : WARNING, ERROR, CRITICAL + * @param stackTrace : 에러 내용 + * @return : insert하고 결과로 받은 Entity + */ + private ErrorLogEntity saveErrorLogData( + HttpServletRequest request, + ApiResponseCode errorCode, + HttpStatus httpStatus, + ErrorLogDto.LogErrorLevel logErrorLevel, + StackTraceElement[] stackTrace) { + + Long userid = null; + + /** + * servletRequest.getUserPrincipal() instanceof UsernamePasswordAuthenticationToken auth 이 요청이 + * JWT 인증을 통과한 요청인가? 그리고 Spring Security Authentication 객체가 UsernamePasswordAuthenticationToken + * 타입인가? 체크 + */ + /** + * auth.getPrincipal() instanceof CustomUserDetails customUserDetails principal 안에 들어있는 객체가 내가 + * 만든 CustomUserDetails 타입인가? 체크 + */ + if (request.getUserPrincipal() instanceof UsernamePasswordAuthenticationToken auth + && auth.getPrincipal() instanceof CustomUserDetails customUserDetails) { + + // audit 에는 long 타입 user_id가 들어가지만 토큰 sub은 uuid여서 user_id 가져오기 + userid = customUserDetails.getMember().getId(); + } + + String stackTraceStr = + Arrays.stream(stackTrace) + .map(StackTraceElement::toString) + .collect(Collectors.joining("\n")) + .substring(0, Math.min(stackTrace.length, 255)); + + ErrorLogEntity errorLogEntity = + new ErrorLogEntity( + request.getRequestURI(), + ApiLogFunction.getEventType(request), + logErrorLevel, + String.valueOf(httpStatus.value()), + errorCode.getText(), + stackTraceStr, + userid, + ZonedDateTime.now()); + + return errorLogRepository.save(errorLogEntity); + } + + @ExceptionHandler(CustomApiException.class) + public ApiResponseDto handleCustomApiException( + CustomApiException e, HttpServletRequest request) { + log.warn("[CustomApiException] resource : {}", e.getMessage()); + this.errorLog(request, e); + String codeName = e.getCodeName(); + HttpStatus status = e.getStatus(); + // String message = e.getMessage() == null ? ApiResponseCode.getMessage(codeName) : + // e.getMessage(); + // + // ApiResponseCode apiCode = ApiResponseCode.getCode(codeName); + // + // ErrorLogEntity errorLog = + // saveErrorLogData( + // request, apiCode, status, ErrorLogDto.LogErrorLevel.WARNING, e.getStackTrace()); + // + // ApiResponseDto body = + // ApiResponseDto.createException(apiCode, message, status, errorLog.getId()); + + ErrorLogEntity errorLog = + saveErrorLogData( + request, + ApiResponseCode.getCode(codeName), + HttpStatus.valueOf(status.value()), + ErrorLogDto.LogErrorLevel.WARNING, + e.getStackTrace()); + + return ApiResponseDto.createException( + ApiResponseCode.getCode(codeName), + ApiResponseCode.getMessage(codeName), + HttpStatus.valueOf(status.value()), + errorLog.getId()); + + // return new ResponseEntity<>(body, status); + } + + private void errorLog(HttpServletRequest request, Throwable e) { + String uri = request != null ? request.getRequestURI() : "N/A"; + + // 예외 타입명 (IllegalArgumentException, BadCredentialsException 등) + String exceptionName = e.getClass().getSimpleName(); + + log.error("[{}] uri={}, message={}", exceptionName, uri, e.getMessage()); + + log.error("[{}] stacktrace", exceptionName, e); + } +} diff --git a/src/main/java/com/kamco/cd/training/config/OpenApiConfig.java b/src/main/java/com/kamco/cd/training/config/OpenApiConfig.java index 27707a9..76f9ddc 100644 --- a/src/main/java/com/kamco/cd/training/config/OpenApiConfig.java +++ b/src/main/java/com/kamco/cd/training/config/OpenApiConfig.java @@ -1,77 +1,77 @@ -package com.kamco.cd.training.config; - -import io.swagger.v3.oas.models.Components; -import io.swagger.v3.oas.models.OpenAPI; -import io.swagger.v3.oas.models.info.Info; -import io.swagger.v3.oas.models.security.SecurityRequirement; -import io.swagger.v3.oas.models.security.SecurityScheme; -import io.swagger.v3.oas.models.servers.Server; -import java.util.ArrayList; -import java.util.List; -import org.springframework.beans.factory.annotation.Value; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; - -@Configuration -public class OpenApiConfig { - - @Value("${swagger.local-port}") - private String localPort; - - @Value("${spring.profiles.active:local}") - private String profile; - - @Value("${swagger.dev-url:https://kamco.training-dev-api.gs.dabeeo.com}") - private String devUrl; - - @Value("${swagger.prod-url:https://api.training-kamco.com}") - private String prodUrl; - - @Bean - public OpenAPI kamcoOpenAPI() { - // 1) SecurityScheme 정의 (Bearer JWT) - SecurityScheme bearerAuth = - new SecurityScheme() - .type(SecurityScheme.Type.HTTP) - .scheme("bearer") - .bearerFormat("JWT") - .in(SecurityScheme.In.HEADER) - .name("Authorization"); - - // 2) SecurityRequirement (기본으로 BearerAuth 사용) - SecurityRequirement securityRequirement = new SecurityRequirement().addList("BearerAuth"); - - // 3) Components 에 SecurityScheme 등록 - Components components = new Components().addSecuritySchemes("BearerAuth", bearerAuth); - - // profile 별 server url 분기 - List servers = new ArrayList<>(); - if ("dev".equals(profile)) { - servers.add(new Server().url(devUrl).description("개발 서버")); - servers.add(new Server().url("http://localhost:" + localPort).description("로컬 서버")); - // servers.add(new Server().url(prodUrl).description("운영 서버")); - } else if ("prod".equals(profile)) { - // servers.add(new Server().url(prodUrl).description("운영 서버")); - servers.add(new Server().url("http://localhost:" + localPort).description("로컬 서버")); - servers.add(new Server().url(devUrl).description("개발 서버")); - } else { - servers.add(new Server().url("http://localhost:" + localPort).description("로컬 서버")); - servers.add(new Server().url(devUrl).description("개발 서버")); - // servers.add(new Server().url(prodUrl).description("운영 서버")); - } - - return new OpenAPI() - .info( - new Info() - .title("KAMCO Change Detection API") - .description( - "KAMCO 변화 탐지 시스템 API 문서\n\n" - + "이 API는 지리공간 데이터를 활용한 변화 탐지 시스템을 제공합니다.\n" - + "GeoJSON 형식의 공간 데이터를 처리하며, PostgreSQL/PostGIS 기반으로 동작합니다.") - .version("v1.0.0")) - .servers(servers) - // 만들어둔 components를 넣어야 함 - .components(components) - .addSecurityItem(securityRequirement); - } -} +package com.kamco.cd.training.config; + +import io.swagger.v3.oas.models.Components; +import io.swagger.v3.oas.models.OpenAPI; +import io.swagger.v3.oas.models.info.Info; +import io.swagger.v3.oas.models.security.SecurityRequirement; +import io.swagger.v3.oas.models.security.SecurityScheme; +import io.swagger.v3.oas.models.servers.Server; +import java.util.ArrayList; +import java.util.List; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +@Configuration +public class OpenApiConfig { + + @Value("${swagger.local-port}") + private String localPort; + + @Value("${spring.profiles.active:local}") + private String profile; + + @Value("${swagger.dev-url:https://kamco.training-dev-api.gs.dabeeo.com}") + private String devUrl; + + @Value("${swagger.prod-url:https://api.training-kamco.com}") + private String prodUrl; + + @Bean + public OpenAPI kamcoOpenAPI() { + // 1) SecurityScheme 정의 (Bearer JWT) + SecurityScheme bearerAuth = + new SecurityScheme() + .type(SecurityScheme.Type.HTTP) + .scheme("bearer") + .bearerFormat("JWT") + .in(SecurityScheme.In.HEADER) + .name("Authorization"); + + // 2) SecurityRequirement (기본으로 BearerAuth 사용) + SecurityRequirement securityRequirement = new SecurityRequirement().addList("BearerAuth"); + + // 3) Components 에 SecurityScheme 등록 + Components components = new Components().addSecuritySchemes("BearerAuth", bearerAuth); + + // profile 별 server url 분기 + List servers = new ArrayList<>(); + if ("dev".equals(profile)) { + servers.add(new Server().url(devUrl).description("개발 서버")); + servers.add(new Server().url("http://localhost:" + localPort).description("로컬 서버")); + // servers.add(new Server().url(prodUrl).description("운영 서버")); + } else if ("prod".equals(profile)) { + // servers.add(new Server().url(prodUrl).description("운영 서버")); + servers.add(new Server().url("http://localhost:" + localPort).description("로컬 서버")); + servers.add(new Server().url(devUrl).description("개발 서버")); + } else { + servers.add(new Server().url("http://localhost:" + localPort).description("로컬 서버")); + servers.add(new Server().url(devUrl).description("개발 서버")); + // servers.add(new Server().url(prodUrl).description("운영 서버")); + } + + return new OpenAPI() + .info( + new Info() + .title("KAMCO Change Detection API") + .description( + "KAMCO 변화 탐지 시스템 API 문서\n\n" + + "이 API는 지리공간 데이터를 활용한 변화 탐지 시스템을 제공합니다.\n" + + "GeoJSON 형식의 공간 데이터를 처리하며, PostgreSQL/PostGIS 기반으로 동작합니다.") + .version("v1.0.0")) + .servers(servers) + // 만들어둔 components를 넣어야 함 + .components(components) + .addSecurityItem(securityRequirement); + } +} diff --git a/src/main/java/com/kamco/cd/training/config/SecurityConfig.java b/src/main/java/com/kamco/cd/training/config/SecurityConfig.java index 8df45ca..e27cbaa 100644 --- a/src/main/java/com/kamco/cd/training/config/SecurityConfig.java +++ b/src/main/java/com/kamco/cd/training/config/SecurityConfig.java @@ -1,136 +1,136 @@ -package com.kamco.cd.training.config; - -import com.kamco.cd.training.auth.CustomAuthenticationProvider; -import com.kamco.cd.training.auth.JwtAuthenticationFilter; -import java.util.List; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.http.HttpMethod; -import org.springframework.security.authentication.AuthenticationManager; -import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration; -import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; -import org.springframework.security.config.annotation.web.configuration.WebSecurityCustomizer; -import org.springframework.security.config.http.SessionCreationPolicy; -import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; -import org.springframework.security.crypto.password.PasswordEncoder; -import org.springframework.security.web.SecurityFilterChain; -import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; -import org.springframework.security.web.firewall.HttpFirewall; -import org.springframework.security.web.firewall.StrictHttpFirewall; -import org.springframework.web.cors.CorsConfiguration; -import org.springframework.web.cors.CorsConfigurationSource; -import org.springframework.web.cors.UrlBasedCorsConfigurationSource; - -@Configuration -@EnableWebSecurity -public class SecurityConfig { - - @Bean - public SecurityFilterChain securityFilterChain( - org.springframework.security.config.annotation.web.builders.HttpSecurity http, - JwtAuthenticationFilter jwtAuthenticationFilter, - CustomAuthenticationProvider customAuthenticationProvider) - throws Exception { - - http.cors(cors -> cors.configurationSource(corsConfigurationSource())) - .csrf(csrf -> csrf.disable()) - .sessionManagement(sm -> sm.sessionCreationPolicy(SessionCreationPolicy.STATELESS)) - .formLogin(form -> form.disable()) - - // /monitor 에서 Basic 인증을 쓰려면 disable 하면 안됨 - .httpBasic(basic -> {}) - .logout(logout -> logout.disable()) - .authenticationProvider(customAuthenticationProvider) - .authorizeHttpRequests( - auth -> - auth - - // monitor - .requestMatchers("/monitor/health", "/monitor/health/**") - .permitAll() - .requestMatchers("/monitor/**") - .authenticated() // Basic으로 인증되게끔 - - // mapsheet - .requestMatchers("/api/mapsheet/**") - .permitAll() - .requestMatchers(HttpMethod.POST, "/api/mapsheet/upload") - .permitAll() - - // test role - .requestMatchers("/api/test/admin") - .hasRole("ADMIN") - .requestMatchers("/api/test/label") - .hasAnyRole("ADMIN", "LABELER") - .requestMatchers("/api/test/review") - .hasAnyRole("ADMIN", "REVIEWER") - - // common permit - .requestMatchers("/error") - .permitAll() - .requestMatchers(HttpMethod.OPTIONS, "/**") - .permitAll() - .requestMatchers( - "/api/auth/signin", - "/api/auth/refresh", - "/api/auth/logout", - "/swagger-ui/**", - "/v3/api-docs/**", - "/api/members/*/password", - "/api/upload/chunk-upload-dataset", - "/api/upload/chunk-upload-complete") - .permitAll() - - // default - .anyRequest() - .authenticated()) - - // JWT 필터는 앞단에 - .addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class); - - return http.build(); - } - - @Bean - public AuthenticationManager authenticationManager(AuthenticationConfiguration configuration) - throws Exception { - return configuration.getAuthenticationManager(); - } - - @Bean - public PasswordEncoder passwordEncoder() { - return new BCryptPasswordEncoder(); - } - - /** CORS 설정 */ - @Bean - public CorsConfigurationSource corsConfigurationSource() { - CorsConfiguration config = new CorsConfiguration(); // CORS 객체 생성 - config.setAllowedOriginPatterns(List.of("*")); // 도메인 허용 - config.setAllowedMethods(List.of("GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS")); - config.setAllowedHeaders(List.of("*")); // 헤더요청 Authorization, Content-Type, X-Custom-Header - config.setAllowCredentials(true); // 쿠키, Authorization 헤더, Bearer Token 등 자격증명 포함 요청을 허용할지 설정 - config.setExposedHeaders(List.of("Content-Disposition")); - - UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(); - /** "/**" → 모든 API 경로에 대해 이 CORS 규칙을 적용 /api/** 같이 특정 경로만 지정 가능. */ - source.registerCorsConfiguration("/**", config); // CORS 정책을 등록 - return source; - } - - @Bean - public HttpFirewall httpFirewall() { - StrictHttpFirewall firewall = new StrictHttpFirewall(); - firewall.setAllowUrlEncodedSlash(true); - firewall.setAllowUrlEncodedDoubleSlash(true); - firewall.setAllowUrlEncodedPercent(true); - firewall.setAllowSemicolon(true); - return firewall; - } - - /** 완전 제외(필터 자체를 안 탐) */ - @Bean - public WebSecurityCustomizer webSecurityCustomizer() { - return (web) -> web.ignoring().requestMatchers("/api/mapsheet/**"); - } -} +package com.kamco.cd.training.config; + +import com.kamco.cd.training.auth.CustomAuthenticationProvider; +import com.kamco.cd.training.auth.JwtAuthenticationFilter; +import java.util.List; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.http.HttpMethod; +import org.springframework.security.authentication.AuthenticationManager; +import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration; +import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; +import org.springframework.security.config.annotation.web.configuration.WebSecurityCustomizer; +import org.springframework.security.config.http.SessionCreationPolicy; +import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; +import org.springframework.security.crypto.password.PasswordEncoder; +import org.springframework.security.web.SecurityFilterChain; +import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; +import org.springframework.security.web.firewall.HttpFirewall; +import org.springframework.security.web.firewall.StrictHttpFirewall; +import org.springframework.web.cors.CorsConfiguration; +import org.springframework.web.cors.CorsConfigurationSource; +import org.springframework.web.cors.UrlBasedCorsConfigurationSource; + +@Configuration +@EnableWebSecurity +public class SecurityConfig { + + @Bean + public SecurityFilterChain securityFilterChain( + org.springframework.security.config.annotation.web.builders.HttpSecurity http, + JwtAuthenticationFilter jwtAuthenticationFilter, + CustomAuthenticationProvider customAuthenticationProvider) + throws Exception { + + http.cors(cors -> cors.configurationSource(corsConfigurationSource())) + .csrf(csrf -> csrf.disable()) + .sessionManagement(sm -> sm.sessionCreationPolicy(SessionCreationPolicy.STATELESS)) + .formLogin(form -> form.disable()) + + // /monitor 에서 Basic 인증을 쓰려면 disable 하면 안됨 + .httpBasic(basic -> {}) + .logout(logout -> logout.disable()) + .authenticationProvider(customAuthenticationProvider) + .authorizeHttpRequests( + auth -> + auth + + // monitor + .requestMatchers("/monitor/health", "/monitor/health/**") + .permitAll() + .requestMatchers("/monitor/**") + .authenticated() // Basic으로 인증되게끔 + + // mapsheet + .requestMatchers("/api/mapsheet/**") + .permitAll() + .requestMatchers(HttpMethod.POST, "/api/mapsheet/upload") + .permitAll() + + // test role + .requestMatchers("/api/test/admin") + .hasRole("ADMIN") + .requestMatchers("/api/test/label") + .hasAnyRole("ADMIN", "LABELER") + .requestMatchers("/api/test/review") + .hasAnyRole("ADMIN", "REVIEWER") + + // common permit + .requestMatchers("/error") + .permitAll() + .requestMatchers(HttpMethod.OPTIONS, "/**") + .permitAll() + .requestMatchers( + "/api/auth/signin", + "/api/auth/refresh", + "/api/auth/logout", + "/swagger-ui/**", + "/v3/api-docs/**", + "/api/members/*/password", + "/api/upload/chunk-upload-dataset", + "/api/upload/chunk-upload-complete") + .permitAll() + + // default + .anyRequest() + .authenticated()) + + // JWT 필터는 앞단에 + .addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class); + + return http.build(); + } + + @Bean + public AuthenticationManager authenticationManager(AuthenticationConfiguration configuration) + throws Exception { + return configuration.getAuthenticationManager(); + } + + @Bean + public PasswordEncoder passwordEncoder() { + return new BCryptPasswordEncoder(); + } + + /** CORS 설정 */ + @Bean + public CorsConfigurationSource corsConfigurationSource() { + CorsConfiguration config = new CorsConfiguration(); // CORS 객체 생성 + config.setAllowedOriginPatterns(List.of("*")); // 도메인 허용 + config.setAllowedMethods(List.of("GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS")); + config.setAllowedHeaders(List.of("*")); // 헤더요청 Authorization, Content-Type, X-Custom-Header + config.setAllowCredentials(true); // 쿠키, Authorization 헤더, Bearer Token 등 자격증명 포함 요청을 허용할지 설정 + config.setExposedHeaders(List.of("Content-Disposition")); + + UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(); + /** "/**" → 모든 API 경로에 대해 이 CORS 규칙을 적용 /api/** 같이 특정 경로만 지정 가능. */ + source.registerCorsConfiguration("/**", config); // CORS 정책을 등록 + return source; + } + + @Bean + public HttpFirewall httpFirewall() { + StrictHttpFirewall firewall = new StrictHttpFirewall(); + firewall.setAllowUrlEncodedSlash(true); + firewall.setAllowUrlEncodedDoubleSlash(true); + firewall.setAllowUrlEncodedPercent(true); + firewall.setAllowSemicolon(true); + return firewall; + } + + /** 완전 제외(필터 자체를 안 탐) */ + @Bean + public WebSecurityCustomizer webSecurityCustomizer() { + return (web) -> web.ignoring().requestMatchers("/api/mapsheet/**"); + } +} diff --git a/src/main/java/com/kamco/cd/training/config/StartupLogger.java b/src/main/java/com/kamco/cd/training/config/StartupLogger.java index 22851de..177e004 100644 --- a/src/main/java/com/kamco/cd/training/config/StartupLogger.java +++ b/src/main/java/com/kamco/cd/training/config/StartupLogger.java @@ -1,96 +1,96 @@ -package com.kamco.cd.training.config; - -import com.zaxxer.hikari.HikariDataSource; -import javax.sql.DataSource; -import lombok.RequiredArgsConstructor; -import lombok.extern.slf4j.Slf4j; -import org.springframework.boot.context.event.ApplicationReadyEvent; -import org.springframework.context.event.EventListener; -import org.springframework.core.env.Environment; -import org.springframework.stereotype.Component; - -@Slf4j -@Component -@RequiredArgsConstructor -public class StartupLogger { - - private final Environment environment; - private final DataSource dataSource; - - @EventListener(ApplicationReadyEvent.class) - public void logStartupInfo() { - String[] activeProfiles = environment.getActiveProfiles(); - String profileInfo = activeProfiles.length > 0 ? String.join(", ", activeProfiles) : "default"; - - // Database connection information - String dbUrl = environment.getProperty("spring.datasource.url"); - String dbUsername = environment.getProperty("spring.datasource.username"); - String dbDriver = environment.getProperty("spring.datasource.driver-class-name"); - - // HikariCP pool settings - String poolInfo = ""; - if (dataSource instanceof HikariDataSource hikariDs) { - poolInfo = - String.format( - """ - │ Pool Size : min=%d, max=%d - │ Connection Timeout: %dms - │ Idle Timeout : %dms - │ Max Lifetime : %dms""", - hikariDs.getMinimumIdle(), - hikariDs.getMaximumPoolSize(), - hikariDs.getConnectionTimeout(), - hikariDs.getIdleTimeout(), - hikariDs.getMaxLifetime()); - } - - // JPA/Hibernate settings - String showSql = environment.getProperty("spring.jpa.show-sql", "false"); - String ddlAuto = environment.getProperty("spring.jpa.hibernate.ddl-auto", "none"); - String batchSize = - environment.getProperty("spring.jpa.properties.hibernate.jdbc.batch_size", "N/A"); - String batchFetchSize = - environment.getProperty("spring.jpa.properties.hibernate.default_batch_fetch_size", "N/A"); - - String startupMessage = - String.format( - """ - - ╔════════════════════════════════════════════════════════════════════════════════╗ - ║ 🚀 APPLICATION STARTUP INFORMATION ║ - ╠════════════════════════════════════════════════════════════════════════════════╣ - ║ PROFILE CONFIGURATION ║ - ╠────────────────────────────────────────────────────────────────────────────────╣ - │ Active Profile(s): %s - ╠════════════════════════════════════════════════════════════════════════════════╣ - ║ DATABASE CONFIGURATION ║ - ╠────────────────────────────────────────────────────────────────────────────────╣ - │ Database URL : %s - │ Username : %s - │ Driver : %s - ╠════════════════════════════════════════════════════════════════════════════════╣ - ║ HIKARICP CONNECTION POOL ║ - ╠────────────────────────────────────────────────────────────────────────────────╣ - %s - ╠════════════════════════════════════════════════════════════════════════════════╣ - ║ JPA/HIBERNATE CONFIGURATION ║ - ╠────────────────────────────────────────────────────────────────────────────────╣ - │ Show SQL : %s - │ DDL Auto : %s - │ JDBC Batch Size : %s - │ Fetch Batch Size : %s - ╚════════════════════════════════════════════════════════════════════════════════╝ - """, - profileInfo, - dbUrl != null ? dbUrl : "N/A", - dbUsername != null ? dbUsername : "N/A", - dbDriver != null ? dbDriver : "PostgreSQL JDBC Driver (auto-detected)", - poolInfo, - showSql, - ddlAuto, - batchSize, - batchFetchSize); - - log.info(startupMessage); - } -} +package com.kamco.cd.training.config; + +import com.zaxxer.hikari.HikariDataSource; +import javax.sql.DataSource; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.boot.context.event.ApplicationReadyEvent; +import org.springframework.context.event.EventListener; +import org.springframework.core.env.Environment; +import org.springframework.stereotype.Component; + +@Slf4j +@Component +@RequiredArgsConstructor +public class StartupLogger { + + private final Environment environment; + private final DataSource dataSource; + + @EventListener(ApplicationReadyEvent.class) + public void logStartupInfo() { + String[] activeProfiles = environment.getActiveProfiles(); + String profileInfo = activeProfiles.length > 0 ? String.join(", ", activeProfiles) : "default"; + + // Database connection information + String dbUrl = environment.getProperty("spring.datasource.url"); + String dbUsername = environment.getProperty("spring.datasource.username"); + String dbDriver = environment.getProperty("spring.datasource.driver-class-name"); + + // HikariCP pool settings + String poolInfo = ""; + if (dataSource instanceof HikariDataSource hikariDs) { + poolInfo = + String.format( + """ + │ Pool Size : min=%d, max=%d + │ Connection Timeout: %dms + │ Idle Timeout : %dms + │ Max Lifetime : %dms""", + hikariDs.getMinimumIdle(), + hikariDs.getMaximumPoolSize(), + hikariDs.getConnectionTimeout(), + hikariDs.getIdleTimeout(), + hikariDs.getMaxLifetime()); + } + + // JPA/Hibernate settings + String showSql = environment.getProperty("spring.jpa.show-sql", "false"); + String ddlAuto = environment.getProperty("spring.jpa.hibernate.ddl-auto", "none"); + String batchSize = + environment.getProperty("spring.jpa.properties.hibernate.jdbc.batch_size", "N/A"); + String batchFetchSize = + environment.getProperty("spring.jpa.properties.hibernate.default_batch_fetch_size", "N/A"); + + String startupMessage = + String.format( + """ + + ╔════════════════════════════════════════════════════════════════════════════════╗ + ║ 🚀 APPLICATION STARTUP INFORMATION ║ + ╠════════════════════════════════════════════════════════════════════════════════╣ + ║ PROFILE CONFIGURATION ║ + ╠────────────────────────────────────────────────────────────────────────────────╣ + │ Active Profile(s): %s + ╠════════════════════════════════════════════════════════════════════════════════╣ + ║ DATABASE CONFIGURATION ║ + ╠────────────────────────────────────────────────────────────────────────────────╣ + │ Database URL : %s + │ Username : %s + │ Driver : %s + ╠════════════════════════════════════════════════════════════════════════════════╣ + ║ HIKARICP CONNECTION POOL ║ + ╠────────────────────────────────────────────────────────────────────────────────╣ + %s + ╠════════════════════════════════════════════════════════════════════════════════╣ + ║ JPA/HIBERNATE CONFIGURATION ║ + ╠────────────────────────────────────────────────────────────────────────────────╣ + │ Show SQL : %s + │ DDL Auto : %s + │ JDBC Batch Size : %s + │ Fetch Batch Size : %s + ╚════════════════════════════════════════════════════════════════════════════════╝ + """, + profileInfo, + dbUrl != null ? dbUrl : "N/A", + dbUsername != null ? dbUsername : "N/A", + dbDriver != null ? dbDriver : "PostgreSQL JDBC Driver (auto-detected)", + poolInfo, + showSql, + ddlAuto, + batchSize, + batchFetchSize); + + log.info(startupMessage); + } +} diff --git a/src/main/java/com/kamco/cd/training/config/SwaggerConfig.java b/src/main/java/com/kamco/cd/training/config/SwaggerConfig.java index 88620aa..8fc87dc 100644 --- a/src/main/java/com/kamco/cd/training/config/SwaggerConfig.java +++ b/src/main/java/com/kamco/cd/training/config/SwaggerConfig.java @@ -1,13 +1,13 @@ -package com.kamco.cd.training.config; - -import io.swagger.v3.oas.annotations.enums.SecuritySchemeType; -import io.swagger.v3.oas.annotations.security.SecurityScheme; -import org.springframework.context.annotation.Configuration; - -@Configuration -@SecurityScheme( - name = "BearerAuth", - type = SecuritySchemeType.HTTP, - scheme = "bearer", - bearerFormat = "JWT") -public class SwaggerConfig {} +package com.kamco.cd.training.config; + +import io.swagger.v3.oas.annotations.enums.SecuritySchemeType; +import io.swagger.v3.oas.annotations.security.SecurityScheme; +import org.springframework.context.annotation.Configuration; + +@Configuration +@SecurityScheme( + name = "BearerAuth", + type = SecuritySchemeType.HTTP, + scheme = "bearer", + bearerFormat = "JWT") +public class SwaggerConfig {} diff --git a/src/main/java/com/kamco/cd/training/config/WebConfig.java b/src/main/java/com/kamco/cd/training/config/WebConfig.java index 59ba930..a896de4 100644 --- a/src/main/java/com/kamco/cd/training/config/WebConfig.java +++ b/src/main/java/com/kamco/cd/training/config/WebConfig.java @@ -1,32 +1,32 @@ -package com.kamco.cd.training.config; - -import com.fasterxml.jackson.databind.ObjectMapper; -import com.fasterxml.jackson.databind.module.SimpleModule; -import com.kamco.cd.training.common.utils.geometry.GeometryDeserializer; -import com.kamco.cd.training.common.utils.geometry.GeometrySerializer; -import org.locationtech.jts.geom.Geometry; -import org.locationtech.jts.geom.Point; -import org.locationtech.jts.geom.Polygon; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.http.converter.json.Jackson2ObjectMapperBuilder; -import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; - -@Configuration -public class WebConfig implements WebMvcConfigurer { - - @Bean - public ObjectMapper objectMapper() { - SimpleModule module = new SimpleModule(); - module.addSerializer(Geometry.class, new GeometrySerializer<>(Geometry.class)); - module.addDeserializer(Geometry.class, new GeometryDeserializer<>(Geometry.class)); - - module.addSerializer(Polygon.class, new GeometrySerializer<>(Polygon.class)); - module.addDeserializer(Polygon.class, new GeometryDeserializer<>(Polygon.class)); - - module.addSerializer(Point.class, new GeometrySerializer<>(Point.class)); - module.addDeserializer(Point.class, new GeometryDeserializer<>(Point.class)); - - return Jackson2ObjectMapperBuilder.json().modulesToInstall(module).build(); - } -} +package com.kamco.cd.training.config; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.module.SimpleModule; +import com.kamco.cd.training.common.utils.geometry.GeometryDeserializer; +import com.kamco.cd.training.common.utils.geometry.GeometrySerializer; +import org.locationtech.jts.geom.Geometry; +import org.locationtech.jts.geom.Point; +import org.locationtech.jts.geom.Polygon; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.http.converter.json.Jackson2ObjectMapperBuilder; +import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; + +@Configuration +public class WebConfig implements WebMvcConfigurer { + + @Bean + public ObjectMapper objectMapper() { + SimpleModule module = new SimpleModule(); + module.addSerializer(Geometry.class, new GeometrySerializer<>(Geometry.class)); + module.addDeserializer(Geometry.class, new GeometryDeserializer<>(Geometry.class)); + + module.addSerializer(Polygon.class, new GeometrySerializer<>(Polygon.class)); + module.addDeserializer(Polygon.class, new GeometryDeserializer<>(Polygon.class)); + + module.addSerializer(Point.class, new GeometrySerializer<>(Point.class)); + module.addDeserializer(Point.class, new GeometryDeserializer<>(Point.class)); + + return Jackson2ObjectMapperBuilder.json().modulesToInstall(module).build(); + } +} diff --git a/src/main/java/com/kamco/cd/training/config/api/ApiLogFilter.java b/src/main/java/com/kamco/cd/training/config/api/ApiLogFilter.java index 669a36d..e988d57 100644 --- a/src/main/java/com/kamco/cd/training/config/api/ApiLogFilter.java +++ b/src/main/java/com/kamco/cd/training/config/api/ApiLogFilter.java @@ -1,28 +1,28 @@ -package com.kamco.cd.training.config.api; - -import jakarta.servlet.FilterChain; -import jakarta.servlet.ServletException; -import jakarta.servlet.http.HttpServletRequest; -import jakarta.servlet.http.HttpServletResponse; -import java.io.IOException; -import org.springframework.stereotype.Component; -import org.springframework.web.filter.OncePerRequestFilter; -import org.springframework.web.util.ContentCachingRequestWrapper; -import org.springframework.web.util.ContentCachingResponseWrapper; - -@Component -public class ApiLogFilter extends OncePerRequestFilter { - @Override - protected void doFilterInternal( - HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) - throws ServletException, IOException { - ContentCachingRequestWrapper wrappedRequest = new ContentCachingRequestWrapper(request); - - ContentCachingResponseWrapper wrappedResponse = new ContentCachingResponseWrapper(response); - - filterChain.doFilter(wrappedRequest, wrappedResponse); - - // 반드시 response body copy - wrappedResponse.copyBodyToResponse(); - } -} +package com.kamco.cd.training.config.api; + +import jakarta.servlet.FilterChain; +import jakarta.servlet.ServletException; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import java.io.IOException; +import org.springframework.stereotype.Component; +import org.springframework.web.filter.OncePerRequestFilter; +import org.springframework.web.util.ContentCachingRequestWrapper; +import org.springframework.web.util.ContentCachingResponseWrapper; + +@Component +public class ApiLogFilter extends OncePerRequestFilter { + @Override + protected void doFilterInternal( + HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) + throws ServletException, IOException { + ContentCachingRequestWrapper wrappedRequest = new ContentCachingRequestWrapper(request); + + ContentCachingResponseWrapper wrappedResponse = new ContentCachingResponseWrapper(response); + + filterChain.doFilter(wrappedRequest, wrappedResponse); + + // 반드시 response body copy + wrappedResponse.copyBodyToResponse(); + } +} diff --git a/src/main/java/com/kamco/cd/training/config/api/ApiLogFunction.java b/src/main/java/com/kamco/cd/training/config/api/ApiLogFunction.java index 3aed8d1..ee69df6 100644 --- a/src/main/java/com/kamco/cd/training/config/api/ApiLogFunction.java +++ b/src/main/java/com/kamco/cd/training/config/api/ApiLogFunction.java @@ -1,132 +1,132 @@ -package com.kamco.cd.training.config.api; - -import com.kamco.cd.training.log.dto.EventStatus; -import com.kamco.cd.training.log.dto.EventType; -import com.kamco.cd.training.menu.dto.MenuDto; -import jakarta.servlet.http.HttpServletRequest; -import java.io.UnsupportedEncodingException; -import java.util.List; -import java.util.Map; -import java.util.stream.Collectors; -import org.springframework.web.util.ContentCachingRequestWrapper; - -public class ApiLogFunction { - - // 클라이언트 IP 추출 - public static String getClientIp(HttpServletRequest request) { - String[] headers = { - "X-Forwarded-For", - "Proxy-Client-IP", - "WL-Proxy-Client-IP", - "HTTP_CLIENT_IP", - "HTTP_X_FORWARDED_FOR" - }; - for (String header : headers) { - String ip = request.getHeader(header); - if (ip != null && ip.length() != 0 && !"unknown".equalsIgnoreCase(ip)) { - return ip.split(",")[0]; - } - } - String ip = request.getRemoteAddr(); - if ("0:0:0:0:0:0:0:1".equals(ip)) { // local 일 때 - ip = "127.0.0.1"; - } - return ip; - } - - // 사용자 ID 추출 예시 (Spring Security 기준) - public static String getUserId(HttpServletRequest request) { - try { - return request.getUserPrincipal() != null ? request.getUserPrincipal().getName() : null; - } catch (Exception e) { - return null; - } - } - - public static EventType getEventType(HttpServletRequest request) { - String method = request.getMethod().toUpperCase(); - String uri = request.getRequestURI().toLowerCase(); - - // URL 기반 DOWNLOAD/PRINT 분류 - if (uri.contains("/download") || uri.contains("/export")) { - return EventType.DOWNLOAD; - } - if (uri.contains("/print")) { - return EventType.PRINT; - } - - // 일반 CRUD - return switch (method) { - case "POST" -> EventType.CREATE; - case "GET" -> EventType.READ; - case "DELETE" -> EventType.DELETE; - case "PUT", "PATCH" -> EventType.UPDATE; - default -> EventType.OTHER; - }; - } - - public static String getRequestBody( - HttpServletRequest servletRequest, ContentCachingRequestWrapper contentWrapper) { - StringBuilder resultBody = new StringBuilder(); - // GET, form-urlencoded POST 파라미터 - Map paramMap = servletRequest.getParameterMap(); - - String queryParams = - paramMap.entrySet().stream() - .map(e -> e.getKey() + "=" + String.join(",", e.getValue())) - .collect(Collectors.joining("&")); - - resultBody.append(queryParams.isEmpty() ? "" : queryParams); - - // JSON Body - if ("POST".equalsIgnoreCase(servletRequest.getMethod()) - && servletRequest.getContentType() != null - && servletRequest.getContentType().contains("application/json")) { - try { - // json인 경우는 Wrapper를 통해 가져오기 - resultBody.append(getBodyData(contentWrapper)); - - } catch (Exception e) { - resultBody.append("cannot read JSON body ").append(e.toString()); - } - } - - // Multipart form-data - if ("POST".equalsIgnoreCase(servletRequest.getMethod()) - && servletRequest.getContentType() != null - && servletRequest.getContentType().startsWith("multipart/form-data")) { - resultBody.append("multipart/form-data request"); - } - - return resultBody.toString(); - } - - // JSON Body 읽기 - public static String getBodyData(ContentCachingRequestWrapper request) { - byte[] buf = request.getContentAsByteArray(); - if (buf.length == 0) { - return null; - } - try { - return new String(buf, request.getCharacterEncoding()); - } catch (UnsupportedEncodingException e) { - return new String(buf); - } - } - - // ApiResponse 의 Status가 2xx 범위이면 SUCCESS, 아니면 FAILED - public static EventStatus isSuccessFail(ApiResponseDto apiResponse) { - return apiResponse.getHttpStatus().is2xxSuccessful() ? EventStatus.SUCCESS : EventStatus.FAILED; - } - - public static String getUriMenuInfo(List menuList, String uri) { - - MenuDto.Basic m = - menuList.stream() - .filter(menu -> menu.getMenuApiUrl() != null && uri.contains(menu.getMenuApiUrl())) - .findFirst() - .orElse(null); - - return m != null ? m.getMenuUid() : "SYSTEM"; - } -} +package com.kamco.cd.training.config.api; + +import com.kamco.cd.training.log.dto.EventStatus; +import com.kamco.cd.training.log.dto.EventType; +import com.kamco.cd.training.menu.dto.MenuDto; +import jakarta.servlet.http.HttpServletRequest; +import java.io.UnsupportedEncodingException; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; +import org.springframework.web.util.ContentCachingRequestWrapper; + +public class ApiLogFunction { + + // 클라이언트 IP 추출 + public static String getClientIp(HttpServletRequest request) { + String[] headers = { + "X-Forwarded-For", + "Proxy-Client-IP", + "WL-Proxy-Client-IP", + "HTTP_CLIENT_IP", + "HTTP_X_FORWARDED_FOR" + }; + for (String header : headers) { + String ip = request.getHeader(header); + if (ip != null && ip.length() != 0 && !"unknown".equalsIgnoreCase(ip)) { + return ip.split(",")[0]; + } + } + String ip = request.getRemoteAddr(); + if ("0:0:0:0:0:0:0:1".equals(ip)) { // local 일 때 + ip = "127.0.0.1"; + } + return ip; + } + + // 사용자 ID 추출 예시 (Spring Security 기준) + public static String getUserId(HttpServletRequest request) { + try { + return request.getUserPrincipal() != null ? request.getUserPrincipal().getName() : null; + } catch (Exception e) { + return null; + } + } + + public static EventType getEventType(HttpServletRequest request) { + String method = request.getMethod().toUpperCase(); + String uri = request.getRequestURI().toLowerCase(); + + // URL 기반 DOWNLOAD/PRINT 분류 + if (uri.contains("/download") || uri.contains("/export")) { + return EventType.DOWNLOAD; + } + if (uri.contains("/print")) { + return EventType.PRINT; + } + + // 일반 CRUD + return switch (method) { + case "POST" -> EventType.CREATE; + case "GET" -> EventType.READ; + case "DELETE" -> EventType.DELETE; + case "PUT", "PATCH" -> EventType.UPDATE; + default -> EventType.OTHER; + }; + } + + public static String getRequestBody( + HttpServletRequest servletRequest, ContentCachingRequestWrapper contentWrapper) { + StringBuilder resultBody = new StringBuilder(); + // GET, form-urlencoded POST 파라미터 + Map paramMap = servletRequest.getParameterMap(); + + String queryParams = + paramMap.entrySet().stream() + .map(e -> e.getKey() + "=" + String.join(",", e.getValue())) + .collect(Collectors.joining("&")); + + resultBody.append(queryParams.isEmpty() ? "" : queryParams); + + // JSON Body + if ("POST".equalsIgnoreCase(servletRequest.getMethod()) + && servletRequest.getContentType() != null + && servletRequest.getContentType().contains("application/json")) { + try { + // json인 경우는 Wrapper를 통해 가져오기 + resultBody.append(getBodyData(contentWrapper)); + + } catch (Exception e) { + resultBody.append("cannot read JSON body ").append(e.toString()); + } + } + + // Multipart form-data + if ("POST".equalsIgnoreCase(servletRequest.getMethod()) + && servletRequest.getContentType() != null + && servletRequest.getContentType().startsWith("multipart/form-data")) { + resultBody.append("multipart/form-data request"); + } + + return resultBody.toString(); + } + + // JSON Body 읽기 + public static String getBodyData(ContentCachingRequestWrapper request) { + byte[] buf = request.getContentAsByteArray(); + if (buf.length == 0) { + return null; + } + try { + return new String(buf, request.getCharacterEncoding()); + } catch (UnsupportedEncodingException e) { + return new String(buf); + } + } + + // ApiResponse 의 Status가 2xx 범위이면 SUCCESS, 아니면 FAILED + public static EventStatus isSuccessFail(ApiResponseDto apiResponse) { + return apiResponse.getHttpStatus().is2xxSuccessful() ? EventStatus.SUCCESS : EventStatus.FAILED; + } + + public static String getUriMenuInfo(List menuList, String uri) { + + MenuDto.Basic m = + menuList.stream() + .filter(menu -> menu.getMenuApiUrl() != null && uri.contains(menu.getMenuApiUrl())) + .findFirst() + .orElse(null); + + return m != null ? m.getMenuUid() : "SYSTEM"; + } +} diff --git a/src/main/java/com/kamco/cd/training/config/api/ApiResponseAdvice.java b/src/main/java/com/kamco/cd/training/config/api/ApiResponseAdvice.java index 822c01c..80721a9 100644 --- a/src/main/java/com/kamco/cd/training/config/api/ApiResponseAdvice.java +++ b/src/main/java/com/kamco/cd/training/config/api/ApiResponseAdvice.java @@ -1,124 +1,124 @@ -package com.kamco.cd.training.config.api; - -import com.fasterxml.jackson.databind.ObjectMapper; -import com.kamco.cd.training.auth.CustomUserDetails; -import com.kamco.cd.training.menu.service.MenuService; -import com.kamco.cd.training.postgres.entity.AuditLogEntity; -import com.kamco.cd.training.postgres.repository.log.AuditLogRepository; -import jakarta.servlet.http.HttpServletRequest; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.core.MethodParameter; -import org.springframework.http.MediaType; -import org.springframework.http.converter.HttpMessageConverter; -import org.springframework.http.server.ServerHttpRequest; -import org.springframework.http.server.ServerHttpResponse; -import org.springframework.http.server.ServletServerHttpRequest; -import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; -import org.springframework.web.bind.annotation.RestControllerAdvice; -import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice; -import org.springframework.web.util.ContentCachingRequestWrapper; - -/** - * ApiResponseDto의 내장된 HTTP 상태 코드를 실제 HTTP 응답에 적용하는 Advice - * - *

createOK() → 201 CREATED ok() → 200 OK deleteOk() → 204 NO_CONTENT - */ -@RestControllerAdvice -public class ApiResponseAdvice implements ResponseBodyAdvice { - - private final AuditLogRepository auditLogRepository; - private final MenuService menuService; - - @Autowired private ObjectMapper objectMapper; - - public ApiResponseAdvice(AuditLogRepository auditLogRepository, MenuService menuService) { - this.auditLogRepository = auditLogRepository; - this.menuService = menuService; - } - - @Override - public boolean supports( - MethodParameter returnType, Class> converterType) { - // ApiResponseDto를 반환하는 경우에만 적용 - return returnType.getParameterType().equals(ApiResponseDto.class); - } - - @Override - public Object beforeBodyWrite( - Object body, - MethodParameter returnType, - MediaType selectedContentType, - Class> selectedConverterType, - ServerHttpRequest request, - ServerHttpResponse response) { - - HttpServletRequest servletRequest = ((ServletServerHttpRequest) request).getServletRequest(); - ContentCachingRequestWrapper contentWrapper = null; - if (servletRequest instanceof ContentCachingRequestWrapper wrapper) { - contentWrapper = wrapper; - } - - if (body instanceof ApiResponseDto apiResponse) { - response.setStatusCode(apiResponse.getHttpStatus()); - - String ip = ApiLogFunction.getClientIp(servletRequest); - Long userid = null; - - if (servletRequest.getUserPrincipal() instanceof UsernamePasswordAuthenticationToken auth - && auth.getPrincipal() instanceof CustomUserDetails customUserDetails) { - userid = customUserDetails.getMember().getId(); - } - - String requestBody; - // 멀티파트 요청은 바디 로깅을 생략 (파일 바이너리로 인한 문제 예방) - MediaType reqContentType = null; - try { - String ct = servletRequest.getContentType(); - reqContentType = ct != null ? MediaType.valueOf(ct) : null; - } catch (Exception ignored) { - } - if (reqContentType != null && MediaType.MULTIPART_FORM_DATA.includes(reqContentType)) { - requestBody = "(multipart omitted)"; - } else { - requestBody = ApiLogFunction.getRequestBody(servletRequest, contentWrapper); - requestBody = maskSensitiveFields(requestBody); - } - - AuditLogEntity log = - new AuditLogEntity( - userid, - ApiLogFunction.getEventType(servletRequest), - ApiLogFunction.isSuccessFail(apiResponse), - ApiLogFunction.getUriMenuInfo( - menuService.getFindAll(), servletRequest.getRequestURI()), - ip, - servletRequest.getRequestURI(), - requestBody, - apiResponse.getErrorLogUid()); - auditLogRepository.save(log); - } - - return body; - } - - /** - * 마스킹 - * - * @param json - * @return - */ - private String maskSensitiveFields(String json) { - if (json == null) { - return null; - } - - // password 마스킹 - json = json.replaceAll("\"password\"\\s*:\\s*\"[^\"]*\"", "\"password\":\"****\""); - - // 토큰 마스킹 - json = json.replaceAll("\"accessToken\"\\s*:\\s*\"[^\"]*\"", "\"accessToken\":\"****\""); - json = json.replaceAll("\"refreshToken\"\\s*:\\s*\"[^\"]*\"", "\"refreshToken\":\"****\""); - - return json; - } -} +package com.kamco.cd.training.config.api; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.kamco.cd.training.auth.CustomUserDetails; +import com.kamco.cd.training.menu.service.MenuService; +import com.kamco.cd.training.postgres.entity.AuditLogEntity; +import com.kamco.cd.training.postgres.repository.log.AuditLogRepository; +import jakarta.servlet.http.HttpServletRequest; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.core.MethodParameter; +import org.springframework.http.MediaType; +import org.springframework.http.converter.HttpMessageConverter; +import org.springframework.http.server.ServerHttpRequest; +import org.springframework.http.server.ServerHttpResponse; +import org.springframework.http.server.ServletServerHttpRequest; +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; +import org.springframework.web.bind.annotation.RestControllerAdvice; +import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice; +import org.springframework.web.util.ContentCachingRequestWrapper; + +/** + * ApiResponseDto의 내장된 HTTP 상태 코드를 실제 HTTP 응답에 적용하는 Advice + * + *

createOK() → 201 CREATED ok() → 200 OK deleteOk() → 204 NO_CONTENT + */ +@RestControllerAdvice +public class ApiResponseAdvice implements ResponseBodyAdvice { + + private final AuditLogRepository auditLogRepository; + private final MenuService menuService; + + @Autowired private ObjectMapper objectMapper; + + public ApiResponseAdvice(AuditLogRepository auditLogRepository, MenuService menuService) { + this.auditLogRepository = auditLogRepository; + this.menuService = menuService; + } + + @Override + public boolean supports( + MethodParameter returnType, Class> converterType) { + // ApiResponseDto를 반환하는 경우에만 적용 + return returnType.getParameterType().equals(ApiResponseDto.class); + } + + @Override + public Object beforeBodyWrite( + Object body, + MethodParameter returnType, + MediaType selectedContentType, + Class> selectedConverterType, + ServerHttpRequest request, + ServerHttpResponse response) { + + HttpServletRequest servletRequest = ((ServletServerHttpRequest) request).getServletRequest(); + ContentCachingRequestWrapper contentWrapper = null; + if (servletRequest instanceof ContentCachingRequestWrapper wrapper) { + contentWrapper = wrapper; + } + + if (body instanceof ApiResponseDto apiResponse) { + response.setStatusCode(apiResponse.getHttpStatus()); + + String ip = ApiLogFunction.getClientIp(servletRequest); + Long userid = null; + + if (servletRequest.getUserPrincipal() instanceof UsernamePasswordAuthenticationToken auth + && auth.getPrincipal() instanceof CustomUserDetails customUserDetails) { + userid = customUserDetails.getMember().getId(); + } + + String requestBody; + // 멀티파트 요청은 바디 로깅을 생략 (파일 바이너리로 인한 문제 예방) + MediaType reqContentType = null; + try { + String ct = servletRequest.getContentType(); + reqContentType = ct != null ? MediaType.valueOf(ct) : null; + } catch (Exception ignored) { + } + if (reqContentType != null && MediaType.MULTIPART_FORM_DATA.includes(reqContentType)) { + requestBody = "(multipart omitted)"; + } else { + requestBody = ApiLogFunction.getRequestBody(servletRequest, contentWrapper); + requestBody = maskSensitiveFields(requestBody); + } + + AuditLogEntity log = + new AuditLogEntity( + userid, + ApiLogFunction.getEventType(servletRequest), + ApiLogFunction.isSuccessFail(apiResponse), + ApiLogFunction.getUriMenuInfo( + menuService.getFindAll(), servletRequest.getRequestURI()), + ip, + servletRequest.getRequestURI(), + requestBody, + apiResponse.getErrorLogUid()); + auditLogRepository.save(log); + } + + return body; + } + + /** + * 마스킹 + * + * @param json + * @return + */ + private String maskSensitiveFields(String json) { + if (json == null) { + return null; + } + + // password 마스킹 + json = json.replaceAll("\"password\"\\s*:\\s*\"[^\"]*\"", "\"password\":\"****\""); + + // 토큰 마스킹 + json = json.replaceAll("\"accessToken\"\\s*:\\s*\"[^\"]*\"", "\"accessToken\":\"****\""); + json = json.replaceAll("\"refreshToken\"\\s*:\\s*\"[^\"]*\"", "\"refreshToken\":\"****\""); + + return json; + } +} diff --git a/src/main/java/com/kamco/cd/training/config/api/ApiResponseDto.java b/src/main/java/com/kamco/cd/training/config/api/ApiResponseDto.java index 8650f24..b9dc41b 100644 --- a/src/main/java/com/kamco/cd/training/config/api/ApiResponseDto.java +++ b/src/main/java/com/kamco/cd/training/config/api/ApiResponseDto.java @@ -1,199 +1,199 @@ -package com.kamco.cd.training.config.api; - -import com.fasterxml.jackson.annotation.JsonIgnore; -import com.fasterxml.jackson.annotation.JsonInclude; -import com.kamco.cd.training.common.utils.enums.EnumType; -import lombok.Getter; -import lombok.RequiredArgsConstructor; -import lombok.ToString; -import org.springframework.http.HttpStatus; - -@Getter -@ToString -public class ApiResponseDto { - - private T data; - - @JsonInclude(JsonInclude.Include.NON_NULL) - private Error error; - - @JsonInclude(JsonInclude.Include.NON_NULL) - private T errorData; - - @JsonIgnore private HttpStatus httpStatus; - - @JsonIgnore private Long errorLogUid; - - public ApiResponseDto(T data) { - this.data = data; - } - - private ApiResponseDto(T data, HttpStatus httpStatus) { - this.data = data; - this.httpStatus = httpStatus; - } - - public ApiResponseDto(ApiResponseCode code) { - this.error = new Error(code.getId(), code.getMessage()); - } - - public ApiResponseDto(ApiResponseCode code, String message) { - this.error = new Error(code.getId(), message); - } - - public ApiResponseDto(ApiResponseCode code, String message, HttpStatus httpStatus) { - this.error = new Error(code.getId(), message); - this.httpStatus = httpStatus; - } - - public ApiResponseDto( - ApiResponseCode code, String message, HttpStatus httpStatus, Long errorLogUid) { - this.error = new Error(code.getId(), message); - this.httpStatus = httpStatus; - this.errorLogUid = errorLogUid; - } - - public ApiResponseDto(ApiResponseCode code, String message, T errorData) { - this.error = new Error(code.getId(), message); - this.errorData = errorData; - } - - // HTTP 상태 코드가 내장된 ApiResponseDto 반환 메서드들 - public static ApiResponseDto createOK(T data) { - return new ApiResponseDto<>(data, HttpStatus.CREATED); - } - - public static ApiResponseDto ok(T data) { - return new ApiResponseDto<>(data, HttpStatus.OK); - } - - public static ApiResponseDto okObject(ResponseObj data) { - if (data.getCode().equals(ApiResponseCode.OK)) { - return new ApiResponseDto<>(data, HttpStatus.NO_CONTENT); - } else { - return new ApiResponseDto<>(data.getCode(), data.getMessage(), HttpStatus.CONFLICT); - } - } - - public static ApiResponseDto deleteOk(T data) { - return new ApiResponseDto<>(data, HttpStatus.NO_CONTENT); - } - - public static ApiResponseDto createException(ApiResponseCode code) { - return new ApiResponseDto<>(code); - } - - public static ApiResponseDto createException(ApiResponseCode code, String message) { - return new ApiResponseDto<>(code, message); - } - - public static ApiResponseDto createException( - ApiResponseCode code, String message, HttpStatus httpStatus) { - return new ApiResponseDto<>(code, message, httpStatus); - } - - public static ApiResponseDto createException( - ApiResponseCode code, String message, HttpStatus httpStatus, Long errorLogUid) { - return new ApiResponseDto<>(code, message, httpStatus, errorLogUid); - } - - public static ApiResponseDto createException( - ApiResponseCode code, String message, T data) { - return new ApiResponseDto<>(code, message, data); - } - - @Getter - public static class Error { - - private final String code; - private final String message; - - public Error(String code, String message) { - this.code = code; - this.message = message; - } - } - - /** Error가 아닌 Business상 성공이거나 실패인 경우, 메세지 함께 전달하기 위한 object */ - @Getter - public static class ResponseObj { - - private final ApiResponseCode code; - private final String message; - - public ResponseObj(ApiResponseCode code, String message) { - this.code = code; - this.message = message; - } - } - - @Getter - @RequiredArgsConstructor - public enum ApiResponseCode implements EnumType { - - // @formatter:off - OK("요청이 성공하였습니다."), - BAD_REQUEST("요청 파라미터가 잘못되었습니다."), - BAD_GATEWAY("네트워크 상태가 불안정합니다."), - ALREADY_EXIST_MALL("이미 등록된 쇼핑센터입니다."), - NOT_FOUND_MAP("지도를 찾을 수 없습니다."), - UNAUTHORIZED("권한이 없습니다."), - CONFLICT("이미 등록된 컨텐츠입니다."), - NOT_FOUND("Resource를 찾을 수 없습니다."), - NOT_FOUND_DATA("데이터를 찾을 수 없습니다."), - NOT_FOUND_WEATHER_DATA("날씨 데이터를 찾을 수 없습니다."), - FAIL_SEND_MESSAGE("메시지를 전송하지 못했습니다."), - TOO_MANY_CONNECTED_MACHINES("연결된 기기가 너무 많습니다."), - UNAUTHENTICATED("인증에 실패하였습니다."), - INVALID_TOKEN("잘못된 토큰입니다."), - EXPIRED_TOKEN("만료된 토큰입니다."), - INTERNAL_SERVER_ERROR("서버에 문제가 발생 하였습니다."), - FORBIDDEN("권한을 확인해주세요."), - INVALID_PASSWORD("잘못된 비밀번호 입니다."), - NOT_FOUND_CAR_IN("입차정보가 없습니다."), - WRONG_STATUS("잘못된 상태입니다."), - FAIL_VERIFICATION("인증에 실패하였습니다."), - INVALID_EMAIL("잘못된 형식의 이메일입니다."), - REQUIRED_EMAIL("이메일은 필수 항목입니다."), - WRONG_PASSWORD("잘못된 패스워드입니다."), - DUPLICATE_EMAIL("이미 가입된 이메일입니다."), - DUPLICATE_DATA("이미 등록되어 있습니다."), - DATA_INTEGRITY_ERROR("데이터 무결성이 위반되어 요청을 처리할수 없습니다."), - FOREIGN_KEY_ERROR("참조 중인 데이터가 있어 삭제할 수 없습니다."), - DUPLICATE_EMPLOYEEID("이미 가입된 사번입니다."), - NOT_FOUND_USER_FOR_EMAIL("이메일로 유저를 찾을 수 없습니다."), - NOT_FOUND_USER("사용자를 찾을 수 없습니다."), - UNPROCESSABLE_ENTITY("이 데이터는 삭제할 수 없습니다."), - LOGIN_ID_NOT_FOUND("아이디를 잘못 입력하셨습니다."), - LOGIN_PASSWORD_MISMATCH("비밀번호를 잘못 입력하셨습니다."), - LOGIN_PASSWORD_EXCEEDED("비밀번호 오류 횟수를 초과하여 이용하실 수 없습니다.\n로그인 오류에 대해 관리자에게 문의하시기 바랍니다."), - REFRESH_TOKEN_EXPIRED_OR_REVOKED("토큰 정보가 만료 되었습니다."), - REFRESH_TOKEN_MISMATCH("토큰 정보가 일치하지 않습니다."), - INACTIVE_ID("사용할 수 없는 계정입니다."), - INVALID_EMAIL_TOKEN( - "You can only reset your password within 24 hours from when the email was sent.\n" - + "To reset your password again, please submit a new request through \"Forgot" - + " Password.\""), - ; - // @formatter:on - private final String message; - - @Override - public String getId() { - return name(); - } - - @Override - public String getText() { - return message; - } - - public static ApiResponseCode getCode(String name) { - return ApiResponseCode.valueOf(name.toUpperCase()); - } - - public static String getMessage(String name) { - return ApiResponseCode.valueOf(name.toUpperCase()).getText(); - } - } -} +package com.kamco.cd.training.config.api; + +import com.fasterxml.jackson.annotation.JsonIgnore; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.kamco.cd.training.common.utils.enums.EnumType; +import lombok.Getter; +import lombok.RequiredArgsConstructor; +import lombok.ToString; +import org.springframework.http.HttpStatus; + +@Getter +@ToString +public class ApiResponseDto { + + private T data; + + @JsonInclude(JsonInclude.Include.NON_NULL) + private Error error; + + @JsonInclude(JsonInclude.Include.NON_NULL) + private T errorData; + + @JsonIgnore private HttpStatus httpStatus; + + @JsonIgnore private Long errorLogUid; + + public ApiResponseDto(T data) { + this.data = data; + } + + private ApiResponseDto(T data, HttpStatus httpStatus) { + this.data = data; + this.httpStatus = httpStatus; + } + + public ApiResponseDto(ApiResponseCode code) { + this.error = new Error(code.getId(), code.getMessage()); + } + + public ApiResponseDto(ApiResponseCode code, String message) { + this.error = new Error(code.getId(), message); + } + + public ApiResponseDto(ApiResponseCode code, String message, HttpStatus httpStatus) { + this.error = new Error(code.getId(), message); + this.httpStatus = httpStatus; + } + + public ApiResponseDto( + ApiResponseCode code, String message, HttpStatus httpStatus, Long errorLogUid) { + this.error = new Error(code.getId(), message); + this.httpStatus = httpStatus; + this.errorLogUid = errorLogUid; + } + + public ApiResponseDto(ApiResponseCode code, String message, T errorData) { + this.error = new Error(code.getId(), message); + this.errorData = errorData; + } + + // HTTP 상태 코드가 내장된 ApiResponseDto 반환 메서드들 + public static ApiResponseDto createOK(T data) { + return new ApiResponseDto<>(data, HttpStatus.CREATED); + } + + public static ApiResponseDto ok(T data) { + return new ApiResponseDto<>(data, HttpStatus.OK); + } + + public static ApiResponseDto okObject(ResponseObj data) { + if (data.getCode().equals(ApiResponseCode.OK)) { + return new ApiResponseDto<>(data, HttpStatus.NO_CONTENT); + } else { + return new ApiResponseDto<>(data.getCode(), data.getMessage(), HttpStatus.CONFLICT); + } + } + + public static ApiResponseDto deleteOk(T data) { + return new ApiResponseDto<>(data, HttpStatus.NO_CONTENT); + } + + public static ApiResponseDto createException(ApiResponseCode code) { + return new ApiResponseDto<>(code); + } + + public static ApiResponseDto createException(ApiResponseCode code, String message) { + return new ApiResponseDto<>(code, message); + } + + public static ApiResponseDto createException( + ApiResponseCode code, String message, HttpStatus httpStatus) { + return new ApiResponseDto<>(code, message, httpStatus); + } + + public static ApiResponseDto createException( + ApiResponseCode code, String message, HttpStatus httpStatus, Long errorLogUid) { + return new ApiResponseDto<>(code, message, httpStatus, errorLogUid); + } + + public static ApiResponseDto createException( + ApiResponseCode code, String message, T data) { + return new ApiResponseDto<>(code, message, data); + } + + @Getter + public static class Error { + + private final String code; + private final String message; + + public Error(String code, String message) { + this.code = code; + this.message = message; + } + } + + /** Error가 아닌 Business상 성공이거나 실패인 경우, 메세지 함께 전달하기 위한 object */ + @Getter + public static class ResponseObj { + + private final ApiResponseCode code; + private final String message; + + public ResponseObj(ApiResponseCode code, String message) { + this.code = code; + this.message = message; + } + } + + @Getter + @RequiredArgsConstructor + public enum ApiResponseCode implements EnumType { + + // @formatter:off + OK("요청이 성공하였습니다."), + BAD_REQUEST("요청 파라미터가 잘못되었습니다."), + BAD_GATEWAY("네트워크 상태가 불안정합니다."), + ALREADY_EXIST_MALL("이미 등록된 쇼핑센터입니다."), + NOT_FOUND_MAP("지도를 찾을 수 없습니다."), + UNAUTHORIZED("권한이 없습니다."), + CONFLICT("이미 등록된 컨텐츠입니다."), + NOT_FOUND("Resource를 찾을 수 없습니다."), + NOT_FOUND_DATA("데이터를 찾을 수 없습니다."), + NOT_FOUND_WEATHER_DATA("날씨 데이터를 찾을 수 없습니다."), + FAIL_SEND_MESSAGE("메시지를 전송하지 못했습니다."), + TOO_MANY_CONNECTED_MACHINES("연결된 기기가 너무 많습니다."), + UNAUTHENTICATED("인증에 실패하였습니다."), + INVALID_TOKEN("잘못된 토큰입니다."), + EXPIRED_TOKEN("만료된 토큰입니다."), + INTERNAL_SERVER_ERROR("서버에 문제가 발생 하였습니다."), + FORBIDDEN("권한을 확인해주세요."), + INVALID_PASSWORD("잘못된 비밀번호 입니다."), + NOT_FOUND_CAR_IN("입차정보가 없습니다."), + WRONG_STATUS("잘못된 상태입니다."), + FAIL_VERIFICATION("인증에 실패하였습니다."), + INVALID_EMAIL("잘못된 형식의 이메일입니다."), + REQUIRED_EMAIL("이메일은 필수 항목입니다."), + WRONG_PASSWORD("잘못된 패스워드입니다."), + DUPLICATE_EMAIL("이미 가입된 이메일입니다."), + DUPLICATE_DATA("이미 등록되어 있습니다."), + DATA_INTEGRITY_ERROR("데이터 무결성이 위반되어 요청을 처리할수 없습니다."), + FOREIGN_KEY_ERROR("참조 중인 데이터가 있어 삭제할 수 없습니다."), + DUPLICATE_EMPLOYEEID("이미 가입된 사번입니다."), + NOT_FOUND_USER_FOR_EMAIL("이메일로 유저를 찾을 수 없습니다."), + NOT_FOUND_USER("사용자를 찾을 수 없습니다."), + UNPROCESSABLE_ENTITY("이 데이터는 삭제할 수 없습니다."), + LOGIN_ID_NOT_FOUND("아이디를 잘못 입력하셨습니다."), + LOGIN_PASSWORD_MISMATCH("비밀번호를 잘못 입력하셨습니다."), + LOGIN_PASSWORD_EXCEEDED("비밀번호 오류 횟수를 초과하여 이용하실 수 없습니다.\n로그인 오류에 대해 관리자에게 문의하시기 바랍니다."), + REFRESH_TOKEN_EXPIRED_OR_REVOKED("토큰 정보가 만료 되었습니다."), + REFRESH_TOKEN_MISMATCH("토큰 정보가 일치하지 않습니다."), + INACTIVE_ID("사용할 수 없는 계정입니다."), + INVALID_EMAIL_TOKEN( + "You can only reset your password within 24 hours from when the email was sent.\n" + + "To reset your password again, please submit a new request through \"Forgot" + + " Password.\""), + ; + // @formatter:on + private final String message; + + @Override + public String getId() { + return name(); + } + + @Override + public String getText() { + return message; + } + + public static ApiResponseCode getCode(String name) { + return ApiResponseCode.valueOf(name.toUpperCase()); + } + + public static String getMessage(String name) { + return ApiResponseCode.valueOf(name.toUpperCase()).getText(); + } + } +} diff --git a/src/main/java/com/kamco/cd/training/dataset/DatasetApiController.java b/src/main/java/com/kamco/cd/training/dataset/DatasetApiController.java index afb2692..c0a2142 100644 --- a/src/main/java/com/kamco/cd/training/dataset/DatasetApiController.java +++ b/src/main/java/com/kamco/cd/training/dataset/DatasetApiController.java @@ -1,154 +1,154 @@ -package com.kamco.cd.training.dataset; - -import com.kamco.cd.training.config.api.ApiResponseDto; -import com.kamco.cd.training.dataset.dto.DatasetDto; -import com.kamco.cd.training.dataset.service.DatasetService; -import io.swagger.v3.oas.annotations.Operation; -import io.swagger.v3.oas.annotations.Parameter; -import io.swagger.v3.oas.annotations.media.Content; -import io.swagger.v3.oas.annotations.media.Schema; -import io.swagger.v3.oas.annotations.responses.ApiResponse; -import io.swagger.v3.oas.annotations.responses.ApiResponses; -import io.swagger.v3.oas.annotations.tags.Tag; -import jakarta.validation.Valid; -import java.util.UUID; -import lombok.RequiredArgsConstructor; -import org.springframework.data.domain.Page; -import org.springframework.web.bind.annotation.*; - -@Tag(name = "데이터셋 관리", description = "어드민 홈 > 학습데이터관리 > 전체데이터 API") -@RestController -@RequestMapping("/api/datasets") -@RequiredArgsConstructor -public class DatasetApiController { - - private final DatasetService datasetService; - - @Operation(summary = "데이터셋 목록 조회", description = "데이터셋(회차) 목록을 조회합니다.") - @ApiResponses( - value = { - @ApiResponse( - responseCode = "200", - description = "조회 성공", - content = - @Content( - mediaType = "application/json", - schema = @Schema(implementation = Page.class))), - @ApiResponse(responseCode = "400", description = "잘못된 검색 조건", content = @Content), - @ApiResponse(responseCode = "500", description = "서버 오류", content = @Content) - }) - @GetMapping - public ApiResponseDto> searchDatasets( - @Parameter(description = "구분", example = "DELIVER(납품), PRODUCTION(제작)") - @RequestParam(required = false) - String groupTitle, - @Parameter(description = "제목", example = "") @RequestParam(required = false) String title, - @Parameter(description = "페이지 번호 (0부터 시작)", example = "0") @RequestParam(defaultValue = "0") - int page, - @Parameter(description = "페이지 크기", example = "20") @RequestParam(defaultValue = "20") - int size) { - DatasetDto.SearchReq searchReq = new DatasetDto.SearchReq(); - searchReq.setTitle(title); - searchReq.setGroupTitle(groupTitle); - searchReq.setPage(page); - searchReq.setSize(size); - return ApiResponseDto.ok(datasetService.searchDatasets(searchReq)); - } - - @Operation(summary = "데이터셋 상세 조회", description = "데이터셋 상세 정보를 조회합니다.") - @ApiResponses( - value = { - @ApiResponse( - responseCode = "200", - description = "조회 성공", - content = - @Content( - mediaType = "application/json", - schema = @Schema(implementation = DatasetDto.Basic.class))), - @ApiResponse(responseCode = "404", description = "데이터셋을 찾을 수 없음", content = @Content), - @ApiResponse(responseCode = "500", description = "서버 오류", content = @Content) - }) - @GetMapping("/{uuid}") - public ApiResponseDto getDatasetDetail(@PathVariable UUID uuid) { - return ApiResponseDto.ok(datasetService.getDatasetDetail(uuid)); - } - - @Operation(summary = "데이터셋 등록", description = "신규 데이터셋(회차)을 생성합니다.") - @ApiResponses( - value = { - @ApiResponse( - responseCode = "201", - description = "등록 성공", - content = - @Content( - mediaType = "application/json", - schema = @Schema(implementation = Long.class))), - @ApiResponse(responseCode = "400", description = "잘못된 요청 데이터", content = @Content), - @ApiResponse(responseCode = "500", description = "서버 오류", content = @Content) - }) - @PostMapping("/register") - public ApiResponseDto registerDataset( - @RequestBody @Valid DatasetDto.RegisterReq registerReq) { - Long id = datasetService.registerDataset(registerReq); - return ApiResponseDto.createOK(id); - } - - @Operation(summary = "데이터셋 수정", description = "데이터셋 정보를 수정합니다.") - @ApiResponses( - value = { - @ApiResponse( - responseCode = "200", - description = "수정 성공", - content = - @Content( - mediaType = "application/json", - schema = @Schema(implementation = Long.class))), - @ApiResponse(responseCode = "400", description = "잘못된 요청 데이터", content = @Content), - @ApiResponse(responseCode = "404", description = "데이터셋을 찾을 수 없음", content = @Content), - @ApiResponse(responseCode = "500", description = "서버 오류", content = @Content) - }) - @PutMapping("/{uuid}") - public ApiResponseDto updateDataset( - @PathVariable UUID uuid, @RequestBody DatasetDto.UpdateReq updateReq) { - datasetService.updateDataset(uuid, updateReq); - return ApiResponseDto.ok(uuid); - } - - @Operation(summary = "데이터셋 삭제", description = "데이터셋을 삭제합니다.") - @ApiResponses( - value = { - @ApiResponse(responseCode = "201", description = "삭제 성공", content = @Content), - @ApiResponse(responseCode = "400", description = "잘못된 요청", content = @Content), - @ApiResponse(responseCode = "404", description = "데이터셋을 찾을 수 없음", content = @Content), - @ApiResponse(responseCode = "500", description = "서버 오류", content = @Content) - }) - @DeleteMapping("/{uuid}") - public ApiResponseDto deleteDatasets(@PathVariable UUID uuid) { - - datasetService.deleteDatasets(uuid); - return ApiResponseDto.ok(uuid); - } - - /* - @Operation(summary = "데이터셋 통계 요약", description = "선택 데이터셋의 통계를 요약합니다.") - @ApiResponses( - value = { - @ApiResponse( - responseCode = "200", - description = "조회 성공", - content = - @Content( - mediaType = "application/json", - schema = @Schema(implementation = DatasetDto.Summary.class))), - @ApiResponse(responseCode = "400", description = "잘못된 요청", content = @Content), - @ApiResponse(responseCode = "500", description = "서버 오류", content = @Content) - }) - @PostMapping("/summary") - public ApiResponseDto getDatasetSummary( - @RequestBody @Valid DatasetDto.SummaryReq summaryReq) { - return ApiResponseDto.ok(datasetService.getDatasetSummary(summaryReq)); - } - - */ - -} +package com.kamco.cd.training.dataset; + +import com.kamco.cd.training.config.api.ApiResponseDto; +import com.kamco.cd.training.dataset.dto.DatasetDto; +import com.kamco.cd.training.dataset.service.DatasetService; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.media.Content; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import io.swagger.v3.oas.annotations.responses.ApiResponses; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.validation.Valid; +import java.util.UUID; +import lombok.RequiredArgsConstructor; +import org.springframework.data.domain.Page; +import org.springframework.web.bind.annotation.*; + +@Tag(name = "데이터셋 관리", description = "어드민 홈 > 학습데이터관리 > 전체데이터 API") +@RestController +@RequestMapping("/api/datasets") +@RequiredArgsConstructor +public class DatasetApiController { + + private final DatasetService datasetService; + + @Operation(summary = "데이터셋 목록 조회", description = "데이터셋(회차) 목록을 조회합니다.") + @ApiResponses( + value = { + @ApiResponse( + responseCode = "200", + description = "조회 성공", + content = + @Content( + mediaType = "application/json", + schema = @Schema(implementation = Page.class))), + @ApiResponse(responseCode = "400", description = "잘못된 검색 조건", content = @Content), + @ApiResponse(responseCode = "500", description = "서버 오류", content = @Content) + }) + @GetMapping + public ApiResponseDto> searchDatasets( + @Parameter(description = "구분", example = "DELIVER(납품), PRODUCTION(제작)") + @RequestParam(required = false) + String groupTitle, + @Parameter(description = "제목", example = "") @RequestParam(required = false) String title, + @Parameter(description = "페이지 번호 (0부터 시작)", example = "0") @RequestParam(defaultValue = "0") + int page, + @Parameter(description = "페이지 크기", example = "20") @RequestParam(defaultValue = "20") + int size) { + DatasetDto.SearchReq searchReq = new DatasetDto.SearchReq(); + searchReq.setTitle(title); + searchReq.setGroupTitle(groupTitle); + searchReq.setPage(page); + searchReq.setSize(size); + return ApiResponseDto.ok(datasetService.searchDatasets(searchReq)); + } + + @Operation(summary = "데이터셋 상세 조회", description = "데이터셋 상세 정보를 조회합니다.") + @ApiResponses( + value = { + @ApiResponse( + responseCode = "200", + description = "조회 성공", + content = + @Content( + mediaType = "application/json", + schema = @Schema(implementation = DatasetDto.Basic.class))), + @ApiResponse(responseCode = "404", description = "데이터셋을 찾을 수 없음", content = @Content), + @ApiResponse(responseCode = "500", description = "서버 오류", content = @Content) + }) + @GetMapping("/{uuid}") + public ApiResponseDto getDatasetDetail(@PathVariable UUID uuid) { + return ApiResponseDto.ok(datasetService.getDatasetDetail(uuid)); + } + + @Operation(summary = "데이터셋 등록", description = "신규 데이터셋(회차)을 생성합니다.") + @ApiResponses( + value = { + @ApiResponse( + responseCode = "201", + description = "등록 성공", + content = + @Content( + mediaType = "application/json", + schema = @Schema(implementation = Long.class))), + @ApiResponse(responseCode = "400", description = "잘못된 요청 데이터", content = @Content), + @ApiResponse(responseCode = "500", description = "서버 오류", content = @Content) + }) + @PostMapping("/register") + public ApiResponseDto registerDataset( + @RequestBody @Valid DatasetDto.RegisterReq registerReq) { + Long id = datasetService.registerDataset(registerReq); + return ApiResponseDto.createOK(id); + } + + @Operation(summary = "데이터셋 수정", description = "데이터셋 정보를 수정합니다.") + @ApiResponses( + value = { + @ApiResponse( + responseCode = "200", + description = "수정 성공", + content = + @Content( + mediaType = "application/json", + schema = @Schema(implementation = Long.class))), + @ApiResponse(responseCode = "400", description = "잘못된 요청 데이터", content = @Content), + @ApiResponse(responseCode = "404", description = "데이터셋을 찾을 수 없음", content = @Content), + @ApiResponse(responseCode = "500", description = "서버 오류", content = @Content) + }) + @PutMapping("/{uuid}") + public ApiResponseDto updateDataset( + @PathVariable UUID uuid, @RequestBody DatasetDto.UpdateReq updateReq) { + datasetService.updateDataset(uuid, updateReq); + return ApiResponseDto.ok(uuid); + } + + @Operation(summary = "데이터셋 삭제", description = "데이터셋을 삭제합니다.") + @ApiResponses( + value = { + @ApiResponse(responseCode = "201", description = "삭제 성공", content = @Content), + @ApiResponse(responseCode = "400", description = "잘못된 요청", content = @Content), + @ApiResponse(responseCode = "404", description = "데이터셋을 찾을 수 없음", content = @Content), + @ApiResponse(responseCode = "500", description = "서버 오류", content = @Content) + }) + @DeleteMapping("/{uuid}") + public ApiResponseDto deleteDatasets(@PathVariable UUID uuid) { + + datasetService.deleteDatasets(uuid); + return ApiResponseDto.ok(uuid); + } + + /* + @Operation(summary = "데이터셋 통계 요약", description = "선택 데이터셋의 통계를 요약합니다.") + @ApiResponses( + value = { + @ApiResponse( + responseCode = "200", + description = "조회 성공", + content = + @Content( + mediaType = "application/json", + schema = @Schema(implementation = DatasetDto.Summary.class))), + @ApiResponse(responseCode = "400", description = "잘못된 요청", content = @Content), + @ApiResponse(responseCode = "500", description = "서버 오류", content = @Content) + }) + @PostMapping("/summary") + public ApiResponseDto getDatasetSummary( + @RequestBody @Valid DatasetDto.SummaryReq summaryReq) { + return ApiResponseDto.ok(datasetService.getDatasetSummary(summaryReq)); + } + + */ + +} diff --git a/src/main/java/com/kamco/cd/training/dataset/MapSheetApiController.java b/src/main/java/com/kamco/cd/training/dataset/MapSheetApiController.java index 8a26943..ef5112a 100644 --- a/src/main/java/com/kamco/cd/training/dataset/MapSheetApiController.java +++ b/src/main/java/com/kamco/cd/training/dataset/MapSheetApiController.java @@ -1,56 +1,56 @@ -package com.kamco.cd.training.dataset; - -import com.kamco.cd.training.config.api.ApiResponseDto; -import com.kamco.cd.training.dataset.dto.MapSheetDto; -import com.kamco.cd.training.dataset.service.MapSheetService; -import io.swagger.v3.oas.annotations.Operation; -import io.swagger.v3.oas.annotations.media.Content; -import io.swagger.v3.oas.annotations.media.Schema; -import io.swagger.v3.oas.annotations.responses.ApiResponse; -import io.swagger.v3.oas.annotations.responses.ApiResponses; -import io.swagger.v3.oas.annotations.tags.Tag; -import jakarta.validation.Valid; -import lombok.RequiredArgsConstructor; -import org.springframework.data.domain.Page; -import org.springframework.web.bind.annotation.*; - -@Tag(name = "도엽 관리", description = "도엽(MapSheet) 관리 API") -@RestController -@RequiredArgsConstructor -public class MapSheetApiController { - - private final MapSheetService mapSheetService; - - @Operation(summary = "도엽 목록 조회", description = "데이터셋의 도엽 목록을 조회합니다.") - @ApiResponses( - value = { - @ApiResponse( - responseCode = "200", - description = "조회 성공", - content = - @Content( - mediaType = "application/json", - schema = @Schema(implementation = Page.class))), - @ApiResponse(responseCode = "400", description = "잘못된 검색 조건", content = @Content), - @ApiResponse(responseCode = "500", description = "서버 오류", content = @Content) - }) - @PostMapping("/api/datasets/items/search") - public ApiResponseDto> searchMapSheets( - @RequestBody @Valid MapSheetDto.SearchReq searchReq) { - return ApiResponseDto.ok(mapSheetService.searchMapSheets(searchReq)); - } - - @Operation(summary = "도엽 삭제", description = "도엽을 삭제합니다 (다건 지원).") - @ApiResponses( - value = { - @ApiResponse(responseCode = "200", description = "삭제 성공", content = @Content), - @ApiResponse(responseCode = "400", description = "잘못된 요청", content = @Content), - @ApiResponse(responseCode = "404", description = "도엽을 찾을 수 없음", content = @Content), - @ApiResponse(responseCode = "500", description = "서버 오류", content = @Content) - }) - @PostMapping("/api/datasets/items/delete") - public ApiResponseDto deleteMapSheets(@RequestBody @Valid MapSheetDto.DeleteReq deleteReq) { - mapSheetService.deleteMapSheets(deleteReq); - return ApiResponseDto.ok(null); - } -} +package com.kamco.cd.training.dataset; + +import com.kamco.cd.training.config.api.ApiResponseDto; +import com.kamco.cd.training.dataset.dto.MapSheetDto; +import com.kamco.cd.training.dataset.service.MapSheetService; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.media.Content; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import io.swagger.v3.oas.annotations.responses.ApiResponses; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.validation.Valid; +import lombok.RequiredArgsConstructor; +import org.springframework.data.domain.Page; +import org.springframework.web.bind.annotation.*; + +@Tag(name = "도엽 관리", description = "도엽(MapSheet) 관리 API") +@RestController +@RequiredArgsConstructor +public class MapSheetApiController { + + private final MapSheetService mapSheetService; + + @Operation(summary = "도엽 목록 조회", description = "데이터셋의 도엽 목록을 조회합니다.") + @ApiResponses( + value = { + @ApiResponse( + responseCode = "200", + description = "조회 성공", + content = + @Content( + mediaType = "application/json", + schema = @Schema(implementation = Page.class))), + @ApiResponse(responseCode = "400", description = "잘못된 검색 조건", content = @Content), + @ApiResponse(responseCode = "500", description = "서버 오류", content = @Content) + }) + @PostMapping("/api/datasets/items/search") + public ApiResponseDto> searchMapSheets( + @RequestBody @Valid MapSheetDto.SearchReq searchReq) { + return ApiResponseDto.ok(mapSheetService.searchMapSheets(searchReq)); + } + + @Operation(summary = "도엽 삭제", description = "도엽을 삭제합니다 (다건 지원).") + @ApiResponses( + value = { + @ApiResponse(responseCode = "200", description = "삭제 성공", content = @Content), + @ApiResponse(responseCode = "400", description = "잘못된 요청", content = @Content), + @ApiResponse(responseCode = "404", description = "도엽을 찾을 수 없음", content = @Content), + @ApiResponse(responseCode = "500", description = "서버 오류", content = @Content) + }) + @PostMapping("/api/datasets/items/delete") + public ApiResponseDto deleteMapSheets(@RequestBody @Valid MapSheetDto.DeleteReq deleteReq) { + mapSheetService.deleteMapSheets(deleteReq); + return ApiResponseDto.ok(null); + } +} diff --git a/src/main/java/com/kamco/cd/training/dataset/dto/DatasetDto.java b/src/main/java/com/kamco/cd/training/dataset/dto/DatasetDto.java index 7e902e1..d1bfaa9 100644 --- a/src/main/java/com/kamco/cd/training/dataset/dto/DatasetDto.java +++ b/src/main/java/com/kamco/cd/training/dataset/dto/DatasetDto.java @@ -1,212 +1,212 @@ -package com.kamco.cd.training.dataset.dto; - -import com.kamco.cd.training.common.enums.LearnDataRegister; -import com.kamco.cd.training.common.enums.LearnDataType; -import com.kamco.cd.training.common.utils.enums.Enums; -import com.kamco.cd.training.common.utils.interfaces.JsonFormatDttm; -import io.swagger.v3.oas.annotations.media.Schema; -import jakarta.validation.constraints.NotBlank; -import jakarta.validation.constraints.NotNull; -import jakarta.validation.constraints.Size; -import java.time.ZonedDateTime; -import java.util.List; -import java.util.UUID; -import lombok.AllArgsConstructor; -import lombok.Getter; -import lombok.NoArgsConstructor; -import lombok.Setter; -import org.springframework.data.domain.PageRequest; -import org.springframework.data.domain.Pageable; -import org.springframework.data.domain.Sort; - -public class DatasetDto { - - @Schema(name = "Dataset Basic", description = "데이터셋 기본 정보") - @Getter - @Setter - @NoArgsConstructor - @AllArgsConstructor - public static class Basic { - - private Long id; - private UUID uuid; - private String groupTitle; - private String groupTitleCd; - private String title; - private Long roundNo; - private String totalSize; - private String memo; - @JsonFormatDttm private ZonedDateTime createdDttm; - private String status; - private String statusCd; - private Boolean deleted; - - public Basic( - Long id, - UUID uuid, - String groupTitle, - String title, - Long roundNo, - Long totalSize, - String memo, - ZonedDateTime createdDttm, - String status, - Boolean deleted) { - this.id = id; - this.uuid = uuid; - this.groupTitle = getGroupTitle(groupTitle); - this.groupTitleCd = groupTitle; - this.title = title; - this.roundNo = roundNo; - this.totalSize = getTotalSize(totalSize); - this.memo = memo; - this.createdDttm = createdDttm; - this.status = getStatus(status); - this.statusCd = status; - this.deleted = deleted; - } - - public String getTotalSize(Long totalSize) { - if (totalSize == null) return "0G"; - double giga = totalSize / (1024.0 * 1024 * 1024); - return String.format("%.2fG", giga); - } - - public String getGroupTitle(String groupTitleCd) { - LearnDataType type = Enums.fromId(LearnDataType.class, groupTitleCd); - return type == null ? null : type.getText(); - } - - public String getStatus(String status) { - LearnDataRegister type = Enums.fromId(LearnDataRegister.class, status); - return type == null ? null : type.getText(); - } - } - - @Schema(name = "Dataset Detail", description = "데이터셋 상세 정보") - @Getter - @Setter - @NoArgsConstructor - @AllArgsConstructor - public static class Detail { - - private Long id; - private String groupTitle; - private String title; - private Long roundNo; - private String totalSize; - private String memo; - @JsonFormatDttm private ZonedDateTime createdDttm; - private String status; - private Boolean deleted; - } - - @Schema(name = "DatasetSearchReq", description = "데이터셋 목록 조회 요청") - @Getter - @Setter - @NoArgsConstructor - @AllArgsConstructor - public static class SearchReq { - - @Schema(description = "구분", example = "DELIVER(납품), PRODUCTION(제작)") - private String groupTitle; - - @Schema(description = "제목 (부분 검색)", example = "1차") - private String title; - - @Schema(description = "페이지 번호 (1부터 시작)", example = "1") - private int page = 1; - - @Schema(description = "페이지 크기", example = "20") - private int size = 20; - - public Pageable toPageable() { - // API에서는 1부터 시작하지만 내부적으로는 0부터 시작 - int pageIndex = Math.max(0, page - 1); - return PageRequest.of(pageIndex, size, Sort.by(Sort.Direction.DESC, "createdDttm")); - } - } - - @Schema(name = "DatasetDetailReq", description = "데이터셋 상세 조회 요청") - @Getter - @Setter - @NoArgsConstructor - @AllArgsConstructor - public static class DetailReq { - - @NotNull(message = "데이터셋 ID는 필수입니다") - @Schema(description = "데이터셋 ID", example = "101") - private Long datasetId; - } - - @Schema(name = "DatasetRegisterReq", description = "데이터셋 등록 요청") - @Getter - @Setter - @NoArgsConstructor - @AllArgsConstructor - public static class RegisterReq { - - @NotBlank(message = "제목은 필수입니다") - @Size(max = 200, message = "제목은 최대 200자까지 입력 가능합니다") - @Schema(description = "제목", example = "1차 제작") - private String title; - - @NotBlank(message = "연도는 필수입니다") - @Size(max = 4, message = "연도는 4자리입니다") - @Schema(description = "연도 (YYYY)", example = "2024") - private String year; - - @Schema(description = "회차", example = "1") - private Long roundNo; - - @Schema(description = "메모", example = "데이터셋 설명") - private String memo; - } - - @Schema(name = "DatasetUpdateReq", description = "데이터셋 수정 요청") - @Getter - @Setter - @NoArgsConstructor - @AllArgsConstructor - public static class UpdateReq { - - @Size(max = 200, message = "제목은 최대 200자까지 입력 가능합니다") - @Schema(description = "제목", example = "1차 제작") - private String title; - - @Schema(description = "메모", example = "데이터셋 설명") - private String memo; - } - - @Schema(name = "DatasetSummaryReq", description = "데이터셋 통계 요약 요청") - @Getter - @Setter - @NoArgsConstructor - @AllArgsConstructor - public static class SummaryReq { - - @NotNull(message = "데이터셋 ID 목록은 필수입니다") - @Schema(description = "데이터셋 ID 목록", example = "[101, 105]") - private List datasetIds; - } - - @Schema(name = "DatasetSummary", description = "데이터셋 통계 요약") - @Getter - @Setter - @NoArgsConstructor - @AllArgsConstructor - public static class Summary { - - @Schema(description = "총 데이터셋 수", example = "2") - private int totalDatasets; - - @Schema(description = "총 도엽 수", example = "1500") - private long totalMapSheets; - - @Schema(description = "총 파일 크기 (bytes)", example = "10737418240") - private long totalFileSize; - - @Schema(description = "평균 도엽 수", example = "750") - private double averageMapSheets; - } -} +package com.kamco.cd.training.dataset.dto; + +import com.kamco.cd.training.common.enums.LearnDataRegister; +import com.kamco.cd.training.common.enums.LearnDataType; +import com.kamco.cd.training.common.utils.enums.Enums; +import com.kamco.cd.training.common.utils.interfaces.JsonFormatDttm; +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import jakarta.validation.constraints.Size; +import java.time.ZonedDateTime; +import java.util.List; +import java.util.UUID; +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Pageable; +import org.springframework.data.domain.Sort; + +public class DatasetDto { + + @Schema(name = "Dataset Basic", description = "데이터셋 기본 정보") + @Getter + @Setter + @NoArgsConstructor + @AllArgsConstructor + public static class Basic { + + private Long id; + private UUID uuid; + private String groupTitle; + private String groupTitleCd; + private String title; + private Long roundNo; + private String totalSize; + private String memo; + @JsonFormatDttm private ZonedDateTime createdDttm; + private String status; + private String statusCd; + private Boolean deleted; + + public Basic( + Long id, + UUID uuid, + String groupTitle, + String title, + Long roundNo, + Long totalSize, + String memo, + ZonedDateTime createdDttm, + String status, + Boolean deleted) { + this.id = id; + this.uuid = uuid; + this.groupTitle = getGroupTitle(groupTitle); + this.groupTitleCd = groupTitle; + this.title = title; + this.roundNo = roundNo; + this.totalSize = getTotalSize(totalSize); + this.memo = memo; + this.createdDttm = createdDttm; + this.status = getStatus(status); + this.statusCd = status; + this.deleted = deleted; + } + + public String getTotalSize(Long totalSize) { + if (totalSize == null) return "0G"; + double giga = totalSize / (1024.0 * 1024 * 1024); + return String.format("%.2fG", giga); + } + + public String getGroupTitle(String groupTitleCd) { + LearnDataType type = Enums.fromId(LearnDataType.class, groupTitleCd); + return type == null ? null : type.getText(); + } + + public String getStatus(String status) { + LearnDataRegister type = Enums.fromId(LearnDataRegister.class, status); + return type == null ? null : type.getText(); + } + } + + @Schema(name = "Dataset Detail", description = "데이터셋 상세 정보") + @Getter + @Setter + @NoArgsConstructor + @AllArgsConstructor + public static class Detail { + + private Long id; + private String groupTitle; + private String title; + private Long roundNo; + private String totalSize; + private String memo; + @JsonFormatDttm private ZonedDateTime createdDttm; + private String status; + private Boolean deleted; + } + + @Schema(name = "DatasetSearchReq", description = "데이터셋 목록 조회 요청") + @Getter + @Setter + @NoArgsConstructor + @AllArgsConstructor + public static class SearchReq { + + @Schema(description = "구분", example = "DELIVER(납품), PRODUCTION(제작)") + private String groupTitle; + + @Schema(description = "제목 (부분 검색)", example = "1차") + private String title; + + @Schema(description = "페이지 번호 (1부터 시작)", example = "1") + private int page = 1; + + @Schema(description = "페이지 크기", example = "20") + private int size = 20; + + public Pageable toPageable() { + // API에서는 1부터 시작하지만 내부적으로는 0부터 시작 + int pageIndex = Math.max(0, page - 1); + return PageRequest.of(pageIndex, size, Sort.by(Sort.Direction.DESC, "createdDttm")); + } + } + + @Schema(name = "DatasetDetailReq", description = "데이터셋 상세 조회 요청") + @Getter + @Setter + @NoArgsConstructor + @AllArgsConstructor + public static class DetailReq { + + @NotNull(message = "데이터셋 ID는 필수입니다") + @Schema(description = "데이터셋 ID", example = "101") + private Long datasetId; + } + + @Schema(name = "DatasetRegisterReq", description = "데이터셋 등록 요청") + @Getter + @Setter + @NoArgsConstructor + @AllArgsConstructor + public static class RegisterReq { + + @NotBlank(message = "제목은 필수입니다") + @Size(max = 200, message = "제목은 최대 200자까지 입력 가능합니다") + @Schema(description = "제목", example = "1차 제작") + private String title; + + @NotBlank(message = "연도는 필수입니다") + @Size(max = 4, message = "연도는 4자리입니다") + @Schema(description = "연도 (YYYY)", example = "2024") + private String year; + + @Schema(description = "회차", example = "1") + private Long roundNo; + + @Schema(description = "메모", example = "데이터셋 설명") + private String memo; + } + + @Schema(name = "DatasetUpdateReq", description = "데이터셋 수정 요청") + @Getter + @Setter + @NoArgsConstructor + @AllArgsConstructor + public static class UpdateReq { + + @Size(max = 200, message = "제목은 최대 200자까지 입력 가능합니다") + @Schema(description = "제목", example = "1차 제작") + private String title; + + @Schema(description = "메모", example = "데이터셋 설명") + private String memo; + } + + @Schema(name = "DatasetSummaryReq", description = "데이터셋 통계 요약 요청") + @Getter + @Setter + @NoArgsConstructor + @AllArgsConstructor + public static class SummaryReq { + + @NotNull(message = "데이터셋 ID 목록은 필수입니다") + @Schema(description = "데이터셋 ID 목록", example = "[101, 105]") + private List datasetIds; + } + + @Schema(name = "DatasetSummary", description = "데이터셋 통계 요약") + @Getter + @Setter + @NoArgsConstructor + @AllArgsConstructor + public static class Summary { + + @Schema(description = "총 데이터셋 수", example = "2") + private int totalDatasets; + + @Schema(description = "총 도엽 수", example = "1500") + private long totalMapSheets; + + @Schema(description = "총 파일 크기 (bytes)", example = "10737418240") + private long totalFileSize; + + @Schema(description = "평균 도엽 수", example = "750") + private double averageMapSheets; + } +} diff --git a/src/main/java/com/kamco/cd/training/dataset/dto/MapSheetDto.java b/src/main/java/com/kamco/cd/training/dataset/dto/MapSheetDto.java index a55eb4d..8f46478 100644 --- a/src/main/java/com/kamco/cd/training/dataset/dto/MapSheetDto.java +++ b/src/main/java/com/kamco/cd/training/dataset/dto/MapSheetDto.java @@ -1,103 +1,103 @@ -package com.kamco.cd.training.dataset.dto; - -import com.kamco.cd.training.common.utils.interfaces.JsonFormatDttm; -import io.swagger.v3.oas.annotations.media.Schema; -import jakarta.validation.constraints.NotNull; -import java.time.ZonedDateTime; -import java.util.List; -import lombok.AllArgsConstructor; -import lombok.Getter; -import lombok.NoArgsConstructor; -import lombok.Setter; -import org.springframework.data.domain.PageRequest; -import org.springframework.data.domain.Pageable; -import org.springframework.data.domain.Sort; - -public class MapSheetDto { - - @Schema(name = "MapSheet Basic", description = "도엽 기본 정보") - @Getter - @Setter - @NoArgsConstructor - @AllArgsConstructor - public static class Basic { - - private Long id; - private Long datasetId; - private String sheetNum; - private String fileName; - private Long fileSize; - private String filePath; - private String status; - private String memo; - private Boolean deleted; - - @JsonFormatDttm private ZonedDateTime createdDttm; - - @JsonFormatDttm private ZonedDateTime updatedDttm; - } - - @Schema(name = "MapSheetSearchReq", description = "도엽 목록 조회 요청") - @Getter - @Setter - @NoArgsConstructor - @AllArgsConstructor - public static class SearchReq { - - @NotNull(message = "데이터셋 ID는 필수입니다") - @Schema(description = "데이터셋 ID", example = "101") - private Long datasetId; - - @Schema(description = "페이지 번호 (1부터 시작)", example = "1") - private int page = 1; - - @Schema(description = "페이지 크기", example = "20") - private int size = 20; - - public Pageable toPageable() { - int pageIndex = Math.max(0, page - 1); - return PageRequest.of(pageIndex, size, Sort.by(Sort.Direction.DESC, "createdDttm")); - } - } - - @Schema(name = "MapSheetDeleteReq", description = "도엽 삭제 요청 (다건)") - @Getter - @Setter - @NoArgsConstructor - @AllArgsConstructor - public static class DeleteReq { - - @NotNull(message = "삭제할 도엽 ID 목록은 필수입니다") - @Schema(description = "삭제할 도엽 ID 목록", example = "[9991, 9992]") - private List itemIds; - } - - @Schema(name = "MapSheetCheckReq", description = "도엽 번호 유효성 검증 요청") - @Getter - @Setter - @NoArgsConstructor - @AllArgsConstructor - public static class CheckReq { - - @NotNull(message = "도엽 번호는 필수입니다") - @Schema(description = "도엽 번호", example = "377055") - private String sheetNum; - } - - @Schema(name = "MapSheetCheckRes", description = "도엽 번호 유효성 검증 응답") - @Getter - @Setter - @NoArgsConstructor - @AllArgsConstructor - public static class CheckRes { - - @Schema(description = "유효 여부", example = "true") - private boolean valid; - - @Schema(description = "메시지", example = "유효한 도엽 번호입니다") - private String message; - - @Schema(description = "중복 여부", example = "false") - private boolean duplicate; - } -} +package com.kamco.cd.training.dataset.dto; + +import com.kamco.cd.training.common.utils.interfaces.JsonFormatDttm; +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotNull; +import java.time.ZonedDateTime; +import java.util.List; +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Pageable; +import org.springframework.data.domain.Sort; + +public class MapSheetDto { + + @Schema(name = "MapSheet Basic", description = "도엽 기본 정보") + @Getter + @Setter + @NoArgsConstructor + @AllArgsConstructor + public static class Basic { + + private Long id; + private Long datasetId; + private String sheetNum; + private String fileName; + private Long fileSize; + private String filePath; + private String status; + private String memo; + private Boolean deleted; + + @JsonFormatDttm private ZonedDateTime createdDttm; + + @JsonFormatDttm private ZonedDateTime updatedDttm; + } + + @Schema(name = "MapSheetSearchReq", description = "도엽 목록 조회 요청") + @Getter + @Setter + @NoArgsConstructor + @AllArgsConstructor + public static class SearchReq { + + @NotNull(message = "데이터셋 ID는 필수입니다") + @Schema(description = "데이터셋 ID", example = "101") + private Long datasetId; + + @Schema(description = "페이지 번호 (1부터 시작)", example = "1") + private int page = 1; + + @Schema(description = "페이지 크기", example = "20") + private int size = 20; + + public Pageable toPageable() { + int pageIndex = Math.max(0, page - 1); + return PageRequest.of(pageIndex, size, Sort.by(Sort.Direction.DESC, "createdDttm")); + } + } + + @Schema(name = "MapSheetDeleteReq", description = "도엽 삭제 요청 (다건)") + @Getter + @Setter + @NoArgsConstructor + @AllArgsConstructor + public static class DeleteReq { + + @NotNull(message = "삭제할 도엽 ID 목록은 필수입니다") + @Schema(description = "삭제할 도엽 ID 목록", example = "[9991, 9992]") + private List itemIds; + } + + @Schema(name = "MapSheetCheckReq", description = "도엽 번호 유효성 검증 요청") + @Getter + @Setter + @NoArgsConstructor + @AllArgsConstructor + public static class CheckReq { + + @NotNull(message = "도엽 번호는 필수입니다") + @Schema(description = "도엽 번호", example = "377055") + private String sheetNum; + } + + @Schema(name = "MapSheetCheckRes", description = "도엽 번호 유효성 검증 응답") + @Getter + @Setter + @NoArgsConstructor + @AllArgsConstructor + public static class CheckRes { + + @Schema(description = "유효 여부", example = "true") + private boolean valid; + + @Schema(description = "메시지", example = "유효한 도엽 번호입니다") + private String message; + + @Schema(description = "중복 여부", example = "false") + private boolean duplicate; + } +} diff --git a/src/main/java/com/kamco/cd/training/dataset/service/DatasetService.java b/src/main/java/com/kamco/cd/training/dataset/service/DatasetService.java index 3b5515d..7a80166 100644 --- a/src/main/java/com/kamco/cd/training/dataset/service/DatasetService.java +++ b/src/main/java/com/kamco/cd/training/dataset/service/DatasetService.java @@ -1,86 +1,86 @@ -package com.kamco.cd.training.dataset.service; - -import com.kamco.cd.training.dataset.dto.DatasetDto; -import com.kamco.cd.training.postgres.core.DatasetCoreService; -import java.util.UUID; -import lombok.RequiredArgsConstructor; -import lombok.extern.slf4j.Slf4j; -import org.springframework.data.domain.Page; -import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Transactional; - -@Slf4j -@Service -@RequiredArgsConstructor -@Transactional(readOnly = true) -public class DatasetService { - - private final DatasetCoreService datasetCoreService; - - /** - * 데이터셋 목록 조회 - * - * @param searchReq 검색 조건 - * @return 데이터셋 목록 - */ - public Page searchDatasets(DatasetDto.SearchReq searchReq) { - log.info("데이터셋 목록 조회 - 조건: {}", searchReq); - return datasetCoreService.findDatasetList(searchReq); - } - - /** - * 데이터셋 상세 조회 - * - * @param id 상세 조회할 목록 Id - * @return 데이터셋 상세 정보 - */ - public DatasetDto.Basic getDatasetDetail(UUID id) { - return datasetCoreService.getOneByUuid(id); - } - - /** - * 데이터셋 등록 - * - * @param registerReq 등록 요청 - * @return 등록된 데이터셋 ID - */ - @Transactional - public Long registerDataset(DatasetDto.RegisterReq registerReq) { - log.info("데이터셋 등록 - 요청: {}", registerReq); - DatasetDto.Basic saved = datasetCoreService.save(registerReq); - log.info("데이터셋 등록 완료 - ID: {}", saved.getId()); - return saved.getId(); - } - - /** - * 데이터셋 수정 - * - * @param updateReq 수정 요청 - * @return 수정된 데이터셋 ID - */ - @Transactional - public void updateDataset(UUID uuid, DatasetDto.UpdateReq updateReq) { - datasetCoreService.update(uuid, updateReq); - } - - /** - * 데이터셋 삭제 (다건) - * - * @param uuid 삭제 요청 - */ - @Transactional - public void deleteDatasets(UUID uuid) { - datasetCoreService.deleteDatasets(uuid); - } - - /** - * 데이터셋 통계 요약 - * - * @param summaryReq 요약 요청 - * @return 통계 요약 - */ - public DatasetDto.Summary getDatasetSummary(DatasetDto.SummaryReq summaryReq) { - log.info("데이터셋 통계 요약 - 요청: {}", summaryReq); - return datasetCoreService.getDatasetSummary(summaryReq); - } -} +package com.kamco.cd.training.dataset.service; + +import com.kamco.cd.training.dataset.dto.DatasetDto; +import com.kamco.cd.training.postgres.core.DatasetCoreService; +import java.util.UUID; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.data.domain.Page; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +@Slf4j +@Service +@RequiredArgsConstructor +@Transactional(readOnly = true) +public class DatasetService { + + private final DatasetCoreService datasetCoreService; + + /** + * 데이터셋 목록 조회 + * + * @param searchReq 검색 조건 + * @return 데이터셋 목록 + */ + public Page searchDatasets(DatasetDto.SearchReq searchReq) { + log.info("데이터셋 목록 조회 - 조건: {}", searchReq); + return datasetCoreService.findDatasetList(searchReq); + } + + /** + * 데이터셋 상세 조회 + * + * @param id 상세 조회할 목록 Id + * @return 데이터셋 상세 정보 + */ + public DatasetDto.Basic getDatasetDetail(UUID id) { + return datasetCoreService.getOneByUuid(id); + } + + /** + * 데이터셋 등록 + * + * @param registerReq 등록 요청 + * @return 등록된 데이터셋 ID + */ + @Transactional + public Long registerDataset(DatasetDto.RegisterReq registerReq) { + log.info("데이터셋 등록 - 요청: {}", registerReq); + DatasetDto.Basic saved = datasetCoreService.save(registerReq); + log.info("데이터셋 등록 완료 - ID: {}", saved.getId()); + return saved.getId(); + } + + /** + * 데이터셋 수정 + * + * @param updateReq 수정 요청 + * @return 수정된 데이터셋 ID + */ + @Transactional + public void updateDataset(UUID uuid, DatasetDto.UpdateReq updateReq) { + datasetCoreService.update(uuid, updateReq); + } + + /** + * 데이터셋 삭제 (다건) + * + * @param uuid 삭제 요청 + */ + @Transactional + public void deleteDatasets(UUID uuid) { + datasetCoreService.deleteDatasets(uuid); + } + + /** + * 데이터셋 통계 요약 + * + * @param summaryReq 요약 요청 + * @return 통계 요약 + */ + public DatasetDto.Summary getDatasetSummary(DatasetDto.SummaryReq summaryReq) { + log.info("데이터셋 통계 요약 - 요청: {}", summaryReq); + return datasetCoreService.getDatasetSummary(summaryReq); + } +} diff --git a/src/main/java/com/kamco/cd/training/dataset/service/MapSheetService.java b/src/main/java/com/kamco/cd/training/dataset/service/MapSheetService.java index 97c5d39..0eb385c 100644 --- a/src/main/java/com/kamco/cd/training/dataset/service/MapSheetService.java +++ b/src/main/java/com/kamco/cd/training/dataset/service/MapSheetService.java @@ -1,41 +1,41 @@ -package com.kamco.cd.training.dataset.service; - -import com.kamco.cd.training.dataset.dto.MapSheetDto; -import com.kamco.cd.training.postgres.core.MapSheetCoreService; -import lombok.RequiredArgsConstructor; -import lombok.extern.slf4j.Slf4j; -import org.springframework.data.domain.Page; -import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Transactional; - -@Slf4j -@Service -@RequiredArgsConstructor -@Transactional(readOnly = true) -public class MapSheetService { - - private final MapSheetCoreService mapSheetCoreService; - - /** - * 도엽 목록 조회 - * - * @param searchReq 검색 조건 - * @return 도엽 목록 - */ - public Page searchMapSheets(MapSheetDto.SearchReq searchReq) { - log.info("도엽 목록 조회 - 조건: {}", searchReq); - return mapSheetCoreService.findMapSheetList(searchReq); - } - - /** - * 도엽 삭제 (다건) - * - * @param deleteReq 삭제 요청 - */ - @Transactional - public void deleteMapSheets(MapSheetDto.DeleteReq deleteReq) { - log.info("도엽 삭제 - 요청: {}", deleteReq); - mapSheetCoreService.deleteMapSheets(deleteReq); - log.info("도엽 삭제 완료 - 개수: {}", deleteReq.getItemIds().size()); - } -} +package com.kamco.cd.training.dataset.service; + +import com.kamco.cd.training.dataset.dto.MapSheetDto; +import com.kamco.cd.training.postgres.core.MapSheetCoreService; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.data.domain.Page; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +@Slf4j +@Service +@RequiredArgsConstructor +@Transactional(readOnly = true) +public class MapSheetService { + + private final MapSheetCoreService mapSheetCoreService; + + /** + * 도엽 목록 조회 + * + * @param searchReq 검색 조건 + * @return 도엽 목록 + */ + public Page searchMapSheets(MapSheetDto.SearchReq searchReq) { + log.info("도엽 목록 조회 - 조건: {}", searchReq); + return mapSheetCoreService.findMapSheetList(searchReq); + } + + /** + * 도엽 삭제 (다건) + * + * @param deleteReq 삭제 요청 + */ + @Transactional + public void deleteMapSheets(MapSheetDto.DeleteReq deleteReq) { + log.info("도엽 삭제 - 요청: {}", deleteReq); + mapSheetCoreService.deleteMapSheets(deleteReq); + log.info("도엽 삭제 완료 - 개수: {}", deleteReq.getItemIds().size()); + } +} diff --git a/src/main/java/com/kamco/cd/training/log/dto/AuditLogDto.java b/src/main/java/com/kamco/cd/training/log/dto/AuditLogDto.java index 98da9c9..2fc78b8 100644 --- a/src/main/java/com/kamco/cd/training/log/dto/AuditLogDto.java +++ b/src/main/java/com/kamco/cd/training/log/dto/AuditLogDto.java @@ -1,229 +1,229 @@ -package com.kamco.cd.training.log.dto; - -import com.fasterxml.jackson.annotation.JsonIgnore; -import com.kamco.cd.training.common.utils.interfaces.JsonFormatDttm; -import io.swagger.v3.oas.annotations.media.Schema; -import java.time.ZonedDateTime; -import lombok.AllArgsConstructor; -import lombok.Getter; -import lombok.NoArgsConstructor; -import lombok.Setter; -import org.springframework.data.domain.PageRequest; -import org.springframework.data.domain.Pageable; -import org.springframework.data.domain.Sort; - -public class AuditLogDto { - - @Schema(name = "AuditLogBasic", description = "감사로그 기본 정보") - @Getter - public static class Basic { - - @JsonIgnore private final Long id; - private final Long userUid; - private final EventType eventType; - private final EventStatus eventStatus; - private final String menuUid; - private final String ipAddress; - private final String requestUri; - private final String requestBody; - private final Long errorLogUid; - - @JsonFormatDttm private final ZonedDateTime createdDttm; - - public Basic( - Long id, - Long userUid, - EventType eventType, - EventStatus eventStatus, - String menuUid, - String ipAddress, - String requestUri, - String requestBody, - Long errorLogUid, - ZonedDateTime createdDttm) { - this.id = id; - this.userUid = userUid; - this.eventType = eventType; - this.eventStatus = eventStatus; - this.menuUid = menuUid; - this.ipAddress = ipAddress; - this.requestUri = requestUri; - this.requestBody = requestBody; - this.errorLogUid = errorLogUid; - this.createdDttm = createdDttm; - } - } - - @Schema(name = "AuditCommon", description = "목록 공통") - @Getter - @AllArgsConstructor - public static class AuditCommon { - private int readCount; - private int cudCount; - private int printCount; - private int downloadCount; - private Long totalCount; - } - - @Schema(name = "DailyAuditList", description = "일자별 목록") - @Getter - public static class DailyAuditList extends AuditCommon { - private final String baseDate; - - public DailyAuditList( - int readCount, - int cudCount, - int printCount, - int downloadCount, - Long totalCount, - String baseDate) { - super(readCount, cudCount, printCount, downloadCount, totalCount); - this.baseDate = baseDate; - } - } - - @Schema(name = "MenuAuditList", description = "메뉴별 목록") - @Getter - public static class MenuAuditList extends AuditCommon { - private final String menuId; - private final String menuName; - - public MenuAuditList( - String menuId, - String menuName, - int readCount, - int cudCount, - int printCount, - int downloadCount, - Long totalCount) { - super(readCount, cudCount, printCount, downloadCount, totalCount); - this.menuId = menuId; - this.menuName = menuName; - } - } - - @Schema(name = "UserAuditList", description = "사용자별 목록") - @Getter - public static class UserAuditList extends AuditCommon { - private final Long accountId; - private final String loginId; - private final String username; - - public UserAuditList( - Long accountId, - String loginId, - String username, - int readCount, - int cudCount, - int printCount, - int downloadCount, - Long totalCount) { - super(readCount, cudCount, printCount, downloadCount, totalCount); - this.accountId = accountId; - this.loginId = loginId; - this.username = username; - } - } - - @Schema(name = "AuditDetail", description = "감사 로그 상세 공통") - @Getter - @AllArgsConstructor - public static class AuditDetail { - private Long logId; - private EventType eventType; - private LogDetail detail; - } - - @Schema(name = "DailyDetail", description = "일자별 로그 상세") - @Getter - public static class DailyDetail extends AuditDetail { - private final String userName; - private final String loginId; - private final String menuName; - - public DailyDetail( - Long logId, - String userName, - String loginId, - String menuName, - EventType eventType, - LogDetail detail) { - super(logId, eventType, detail); - this.userName = userName; - this.loginId = loginId; - this.menuName = menuName; - } - } - - @Schema(name = "MenuDetail", description = "메뉴별 로그 상세") - @Getter - public static class MenuDetail extends AuditDetail { - private final String logDateTime; - private final String userName; - private final String loginId; - - public MenuDetail( - Long logId, - String logDateTime, - String userName, - String loginId, - EventType eventType, - LogDetail detail) { - super(logId, eventType, detail); - this.logDateTime = logDateTime; - this.userName = userName; - this.loginId = loginId; - } - } - - @Schema(name = "UserDetail", description = "사용자별 로그 상세") - @Getter - public static class UserDetail extends AuditDetail { - private final String logDateTime; - private final String menuNm; - - public UserDetail( - Long logId, String logDateTime, String menuNm, EventType eventType, LogDetail detail) { - super(logId, eventType, detail); - this.logDateTime = logDateTime; - this.menuNm = menuNm; - } - } - - @Getter - @Setter - @AllArgsConstructor - public static class LogDetail { - String serviceName; - String parentMenuName; - String menuName; - String menuUrl; - String menuDescription; - Long sortOrder; - boolean used; - } - - @Schema(name = "searchReq", description = "일자별 로그 검색 요청") - @Getter - @Setter - @NoArgsConstructor - @AllArgsConstructor - public static class searchReq { - - // 페이징 파라미터 - private int page = 0; - private int size = 20; - private String sort; - - public Pageable toPageable() { - if (sort != null && !sort.isEmpty()) { - String[] sortParams = sort.split(","); - String property = sortParams[0]; - Sort.Direction direction = - sortParams.length > 1 ? Sort.Direction.fromString(sortParams[1]) : Sort.Direction.ASC; - return PageRequest.of(page, size, Sort.by(direction, property)); - } - return PageRequest.of(page, size); - } - } -} +package com.kamco.cd.training.log.dto; + +import com.fasterxml.jackson.annotation.JsonIgnore; +import com.kamco.cd.training.common.utils.interfaces.JsonFormatDttm; +import io.swagger.v3.oas.annotations.media.Schema; +import java.time.ZonedDateTime; +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Pageable; +import org.springframework.data.domain.Sort; + +public class AuditLogDto { + + @Schema(name = "AuditLogBasic", description = "감사로그 기본 정보") + @Getter + public static class Basic { + + @JsonIgnore private final Long id; + private final Long userUid; + private final EventType eventType; + private final EventStatus eventStatus; + private final String menuUid; + private final String ipAddress; + private final String requestUri; + private final String requestBody; + private final Long errorLogUid; + + @JsonFormatDttm private final ZonedDateTime createdDttm; + + public Basic( + Long id, + Long userUid, + EventType eventType, + EventStatus eventStatus, + String menuUid, + String ipAddress, + String requestUri, + String requestBody, + Long errorLogUid, + ZonedDateTime createdDttm) { + this.id = id; + this.userUid = userUid; + this.eventType = eventType; + this.eventStatus = eventStatus; + this.menuUid = menuUid; + this.ipAddress = ipAddress; + this.requestUri = requestUri; + this.requestBody = requestBody; + this.errorLogUid = errorLogUid; + this.createdDttm = createdDttm; + } + } + + @Schema(name = "AuditCommon", description = "목록 공통") + @Getter + @AllArgsConstructor + public static class AuditCommon { + private int readCount; + private int cudCount; + private int printCount; + private int downloadCount; + private Long totalCount; + } + + @Schema(name = "DailyAuditList", description = "일자별 목록") + @Getter + public static class DailyAuditList extends AuditCommon { + private final String baseDate; + + public DailyAuditList( + int readCount, + int cudCount, + int printCount, + int downloadCount, + Long totalCount, + String baseDate) { + super(readCount, cudCount, printCount, downloadCount, totalCount); + this.baseDate = baseDate; + } + } + + @Schema(name = "MenuAuditList", description = "메뉴별 목록") + @Getter + public static class MenuAuditList extends AuditCommon { + private final String menuId; + private final String menuName; + + public MenuAuditList( + String menuId, + String menuName, + int readCount, + int cudCount, + int printCount, + int downloadCount, + Long totalCount) { + super(readCount, cudCount, printCount, downloadCount, totalCount); + this.menuId = menuId; + this.menuName = menuName; + } + } + + @Schema(name = "UserAuditList", description = "사용자별 목록") + @Getter + public static class UserAuditList extends AuditCommon { + private final Long accountId; + private final String loginId; + private final String username; + + public UserAuditList( + Long accountId, + String loginId, + String username, + int readCount, + int cudCount, + int printCount, + int downloadCount, + Long totalCount) { + super(readCount, cudCount, printCount, downloadCount, totalCount); + this.accountId = accountId; + this.loginId = loginId; + this.username = username; + } + } + + @Schema(name = "AuditDetail", description = "감사 로그 상세 공통") + @Getter + @AllArgsConstructor + public static class AuditDetail { + private Long logId; + private EventType eventType; + private LogDetail detail; + } + + @Schema(name = "DailyDetail", description = "일자별 로그 상세") + @Getter + public static class DailyDetail extends AuditDetail { + private final String userName; + private final String loginId; + private final String menuName; + + public DailyDetail( + Long logId, + String userName, + String loginId, + String menuName, + EventType eventType, + LogDetail detail) { + super(logId, eventType, detail); + this.userName = userName; + this.loginId = loginId; + this.menuName = menuName; + } + } + + @Schema(name = "MenuDetail", description = "메뉴별 로그 상세") + @Getter + public static class MenuDetail extends AuditDetail { + private final String logDateTime; + private final String userName; + private final String loginId; + + public MenuDetail( + Long logId, + String logDateTime, + String userName, + String loginId, + EventType eventType, + LogDetail detail) { + super(logId, eventType, detail); + this.logDateTime = logDateTime; + this.userName = userName; + this.loginId = loginId; + } + } + + @Schema(name = "UserDetail", description = "사용자별 로그 상세") + @Getter + public static class UserDetail extends AuditDetail { + private final String logDateTime; + private final String menuNm; + + public UserDetail( + Long logId, String logDateTime, String menuNm, EventType eventType, LogDetail detail) { + super(logId, eventType, detail); + this.logDateTime = logDateTime; + this.menuNm = menuNm; + } + } + + @Getter + @Setter + @AllArgsConstructor + public static class LogDetail { + String serviceName; + String parentMenuName; + String menuName; + String menuUrl; + String menuDescription; + Long sortOrder; + boolean used; + } + + @Schema(name = "searchReq", description = "일자별 로그 검색 요청") + @Getter + @Setter + @NoArgsConstructor + @AllArgsConstructor + public static class searchReq { + + // 페이징 파라미터 + private int page = 0; + private int size = 20; + private String sort; + + public Pageable toPageable() { + if (sort != null && !sort.isEmpty()) { + String[] sortParams = sort.split(","); + String property = sortParams[0]; + Sort.Direction direction = + sortParams.length > 1 ? Sort.Direction.fromString(sortParams[1]) : Sort.Direction.ASC; + return PageRequest.of(page, size, Sort.by(direction, property)); + } + return PageRequest.of(page, size); + } + } +} diff --git a/src/main/java/com/kamco/cd/training/log/dto/ErrorLogDto.java b/src/main/java/com/kamco/cd/training/log/dto/ErrorLogDto.java index 3373a13..2b139ae 100644 --- a/src/main/java/com/kamco/cd/training/log/dto/ErrorLogDto.java +++ b/src/main/java/com/kamco/cd/training/log/dto/ErrorLogDto.java @@ -1,101 +1,101 @@ -package com.kamco.cd.training.log.dto; - -import com.kamco.cd.training.common.utils.enums.EnumType; -import io.swagger.v3.oas.annotations.media.Schema; -import java.time.LocalDate; -import lombok.AllArgsConstructor; -import lombok.Getter; -import lombok.NoArgsConstructor; -import lombok.Setter; -import org.springframework.data.domain.PageRequest; -import org.springframework.data.domain.Pageable; -import org.springframework.data.domain.Sort; - -public class ErrorLogDto { - - @Schema(name = "ErrorLogBasic", description = "에러로그 기본 정보") - @Getter - @Setter - @AllArgsConstructor - public static class Basic { - - private final Long id; - private final String serviceName; - private final String menuNm; - private final String loginId; - private final String userName; - private final EventType errorType; - private final String errorName; - private final LogErrorLevel errorLevel; - private final String errorCode; - private final String errorMessage; - private final String errorDetail; - private final String createDate; // to_char해서 가져옴 - } - - @Schema(name = "ErrorSearchReq", description = "에러로그 검색 요청") - @Getter - @Setter - @NoArgsConstructor - @AllArgsConstructor - public static class ErrorSearchReq { - - LogErrorLevel errorLevel; - EventType eventType; - LocalDate startDate; - LocalDate endDate; - - // 페이징 파라미터 - private int page = 0; - private int size = 20; - private String sort; - - public ErrorSearchReq( - LogErrorLevel errorLevel, - EventType eventType, - LocalDate startDate, - LocalDate endDate, - int page, - int size) { - this.errorLevel = errorLevel; - this.eventType = eventType; - this.startDate = startDate; - this.endDate = endDate; - this.page = page; - this.size = size; - } - - public Pageable toPageable() { - if (sort != null && !sort.isEmpty()) { - String[] sortParams = sort.split(","); - String property = sortParams[0]; - Sort.Direction direction = - sortParams.length > 1 ? Sort.Direction.fromString(sortParams[1]) : Sort.Direction.ASC; - return PageRequest.of(page, size, Sort.by(direction, property)); - } - return PageRequest.of(page, size); - } - } - - public enum LogErrorLevel implements EnumType { - WARNING("Warning"), - ERROR("Error"), - CRITICAL("Critical"); - - private final String desc; - - LogErrorLevel(String desc) { - this.desc = desc; - } - - @Override - public String getId() { - return name(); - } - - @Override - public String getText() { - return desc; - } - } -} +package com.kamco.cd.training.log.dto; + +import com.kamco.cd.training.common.utils.enums.EnumType; +import io.swagger.v3.oas.annotations.media.Schema; +import java.time.LocalDate; +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Pageable; +import org.springframework.data.domain.Sort; + +public class ErrorLogDto { + + @Schema(name = "ErrorLogBasic", description = "에러로그 기본 정보") + @Getter + @Setter + @AllArgsConstructor + public static class Basic { + + private final Long id; + private final String serviceName; + private final String menuNm; + private final String loginId; + private final String userName; + private final EventType errorType; + private final String errorName; + private final LogErrorLevel errorLevel; + private final String errorCode; + private final String errorMessage; + private final String errorDetail; + private final String createDate; // to_char해서 가져옴 + } + + @Schema(name = "ErrorSearchReq", description = "에러로그 검색 요청") + @Getter + @Setter + @NoArgsConstructor + @AllArgsConstructor + public static class ErrorSearchReq { + + LogErrorLevel errorLevel; + EventType eventType; + LocalDate startDate; + LocalDate endDate; + + // 페이징 파라미터 + private int page = 0; + private int size = 20; + private String sort; + + public ErrorSearchReq( + LogErrorLevel errorLevel, + EventType eventType, + LocalDate startDate, + LocalDate endDate, + int page, + int size) { + this.errorLevel = errorLevel; + this.eventType = eventType; + this.startDate = startDate; + this.endDate = endDate; + this.page = page; + this.size = size; + } + + public Pageable toPageable() { + if (sort != null && !sort.isEmpty()) { + String[] sortParams = sort.split(","); + String property = sortParams[0]; + Sort.Direction direction = + sortParams.length > 1 ? Sort.Direction.fromString(sortParams[1]) : Sort.Direction.ASC; + return PageRequest.of(page, size, Sort.by(direction, property)); + } + return PageRequest.of(page, size); + } + } + + public enum LogErrorLevel implements EnumType { + WARNING("Warning"), + ERROR("Error"), + CRITICAL("Critical"); + + private final String desc; + + LogErrorLevel(String desc) { + this.desc = desc; + } + + @Override + public String getId() { + return name(); + } + + @Override + public String getText() { + return desc; + } + } +} diff --git a/src/main/java/com/kamco/cd/training/log/dto/EventStatus.java b/src/main/java/com/kamco/cd/training/log/dto/EventStatus.java index b0aa71b..1b7dc73 100644 --- a/src/main/java/com/kamco/cd/training/log/dto/EventStatus.java +++ b/src/main/java/com/kamco/cd/training/log/dto/EventStatus.java @@ -1,24 +1,24 @@ -package com.kamco.cd.training.log.dto; - -import com.kamco.cd.training.common.utils.enums.EnumType; -import lombok.AllArgsConstructor; -import lombok.Getter; - -@Getter -@AllArgsConstructor -public enum EventStatus implements EnumType { - SUCCESS("이벤트 결과 성공"), - FAILED("이벤트 결과 실패"); - - private final String desc; - - @Override - public String getId() { - return name(); - } - - @Override - public String getText() { - return desc; - } -} +package com.kamco.cd.training.log.dto; + +import com.kamco.cd.training.common.utils.enums.EnumType; +import lombok.AllArgsConstructor; +import lombok.Getter; + +@Getter +@AllArgsConstructor +public enum EventStatus implements EnumType { + SUCCESS("이벤트 결과 성공"), + FAILED("이벤트 결과 실패"); + + private final String desc; + + @Override + public String getId() { + return name(); + } + + @Override + public String getText() { + return desc; + } +} diff --git a/src/main/java/com/kamco/cd/training/log/dto/EventType.java b/src/main/java/com/kamco/cd/training/log/dto/EventType.java index 0f6c066..bf5707a 100644 --- a/src/main/java/com/kamco/cd/training/log/dto/EventType.java +++ b/src/main/java/com/kamco/cd/training/log/dto/EventType.java @@ -1,29 +1,29 @@ -package com.kamco.cd.training.log.dto; - -import com.kamco.cd.training.common.utils.enums.EnumType; -import lombok.AllArgsConstructor; -import lombok.Getter; - -@Getter -@AllArgsConstructor -public enum EventType implements EnumType { - CREATE("생성"), - READ("조회"), - UPDATE("수정"), - DELETE("삭제"), - DOWNLOAD("다운로드"), - PRINT("출력"), - OTHER("기타"); - - private final String desc; - - @Override - public String getId() { - return name(); - } - - @Override - public String getText() { - return desc; - } -} +package com.kamco.cd.training.log.dto; + +import com.kamco.cd.training.common.utils.enums.EnumType; +import lombok.AllArgsConstructor; +import lombok.Getter; + +@Getter +@AllArgsConstructor +public enum EventType implements EnumType { + CREATE("생성"), + READ("조회"), + UPDATE("수정"), + DELETE("삭제"), + DOWNLOAD("다운로드"), + PRINT("출력"), + OTHER("기타"); + + private final String desc; + + @Override + public String getId() { + return name(); + } + + @Override + public String getText() { + return desc; + } +} diff --git a/src/main/java/com/kamco/cd/training/log/service/AuditLogService.java b/src/main/java/com/kamco/cd/training/log/service/AuditLogService.java index 0ee5403..ef0b1a3 100644 --- a/src/main/java/com/kamco/cd/training/log/service/AuditLogService.java +++ b/src/main/java/com/kamco/cd/training/log/service/AuditLogService.java @@ -1,46 +1,46 @@ -package com.kamco.cd.training.log.service; - -import com.kamco.cd.training.log.dto.AuditLogDto; -import com.kamco.cd.training.postgres.core.AuditLogCoreService; -import java.time.LocalDate; -import lombok.RequiredArgsConstructor; -import org.springframework.data.domain.Page; -import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Transactional; - -@Service -@RequiredArgsConstructor -@Transactional(readOnly = true) -public class AuditLogService { - private final AuditLogCoreService auditLogCoreService; - - public Page getLogByDaily( - AuditLogDto.searchReq searchRange, LocalDate startDate, LocalDate endDate) { - return auditLogCoreService.getLogByDaily(searchRange, startDate, endDate); - } - - public Page getLogByMenu( - AuditLogDto.searchReq searchRange, String searchValue) { - return auditLogCoreService.getLogByMenu(searchRange, searchValue); - } - - public Page getLogByAccount( - AuditLogDto.searchReq searchRange, String searchValue) { - return auditLogCoreService.getLogByAccount(searchRange, searchValue); - } - - public Page getLogByDailyResult( - AuditLogDto.searchReq searchRange, LocalDate logDate) { - return auditLogCoreService.getLogByDailyResult(searchRange, logDate); - } - - public Page getLogByMenuResult( - AuditLogDto.searchReq searchRange, String menuId) { - return auditLogCoreService.getLogByMenuResult(searchRange, menuId); - } - - public Page getLogByAccountResult( - AuditLogDto.searchReq searchRange, Long accountId) { - return auditLogCoreService.getLogByAccountResult(searchRange, accountId); - } -} +package com.kamco.cd.training.log.service; + +import com.kamco.cd.training.log.dto.AuditLogDto; +import com.kamco.cd.training.postgres.core.AuditLogCoreService; +import java.time.LocalDate; +import lombok.RequiredArgsConstructor; +import org.springframework.data.domain.Page; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +@Service +@RequiredArgsConstructor +@Transactional(readOnly = true) +public class AuditLogService { + private final AuditLogCoreService auditLogCoreService; + + public Page getLogByDaily( + AuditLogDto.searchReq searchRange, LocalDate startDate, LocalDate endDate) { + return auditLogCoreService.getLogByDaily(searchRange, startDate, endDate); + } + + public Page getLogByMenu( + AuditLogDto.searchReq searchRange, String searchValue) { + return auditLogCoreService.getLogByMenu(searchRange, searchValue); + } + + public Page getLogByAccount( + AuditLogDto.searchReq searchRange, String searchValue) { + return auditLogCoreService.getLogByAccount(searchRange, searchValue); + } + + public Page getLogByDailyResult( + AuditLogDto.searchReq searchRange, LocalDate logDate) { + return auditLogCoreService.getLogByDailyResult(searchRange, logDate); + } + + public Page getLogByMenuResult( + AuditLogDto.searchReq searchRange, String menuId) { + return auditLogCoreService.getLogByMenuResult(searchRange, menuId); + } + + public Page getLogByAccountResult( + AuditLogDto.searchReq searchRange, Long accountId) { + return auditLogCoreService.getLogByAccountResult(searchRange, accountId); + } +} diff --git a/src/main/java/com/kamco/cd/training/log/service/ErrorLogService.java b/src/main/java/com/kamco/cd/training/log/service/ErrorLogService.java index 5c3e10e..0e1c0ef 100644 --- a/src/main/java/com/kamco/cd/training/log/service/ErrorLogService.java +++ b/src/main/java/com/kamco/cd/training/log/service/ErrorLogService.java @@ -1,19 +1,19 @@ -package com.kamco.cd.training.log.service; - -import com.kamco.cd.training.log.dto.ErrorLogDto; -import com.kamco.cd.training.postgres.core.ErrorLogCoreService; -import lombok.RequiredArgsConstructor; -import org.springframework.data.domain.Page; -import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Transactional; - -@Service -@RequiredArgsConstructor -@Transactional(readOnly = true) -public class ErrorLogService { - private final ErrorLogCoreService errorLogCoreService; - - public Page findLogByError(ErrorLogDto.ErrorSearchReq searchReq) { - return errorLogCoreService.findLogByError(searchReq); - } -} +package com.kamco.cd.training.log.service; + +import com.kamco.cd.training.log.dto.ErrorLogDto; +import com.kamco.cd.training.postgres.core.ErrorLogCoreService; +import lombok.RequiredArgsConstructor; +import org.springframework.data.domain.Page; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +@Service +@RequiredArgsConstructor +@Transactional(readOnly = true) +public class ErrorLogService { + private final ErrorLogCoreService errorLogCoreService; + + public Page findLogByError(ErrorLogDto.ErrorSearchReq searchReq) { + return errorLogCoreService.findLogByError(searchReq); + } +} diff --git a/src/main/java/com/kamco/cd/training/members/AuthController.java b/src/main/java/com/kamco/cd/training/members/AuthController.java index 31a8db6..55b9242 100644 --- a/src/main/java/com/kamco/cd/training/members/AuthController.java +++ b/src/main/java/com/kamco/cd/training/members/AuthController.java @@ -1,241 +1,241 @@ -package com.kamco.cd.training.members; - -import com.kamco.cd.training.auth.CustomUserDetails; -import com.kamco.cd.training.auth.JwtTokenProvider; -import com.kamco.cd.training.common.enums.StatusType; -import com.kamco.cd.training.common.exception.CustomApiException; -import com.kamco.cd.training.config.api.ApiResponseDto; -import com.kamco.cd.training.members.dto.MembersDto; -import com.kamco.cd.training.members.dto.SignInRequest; -import com.kamco.cd.training.members.dto.TokenResponse; -import com.kamco.cd.training.members.service.AuthService; -import io.swagger.v3.oas.annotations.Operation; -import io.swagger.v3.oas.annotations.media.Content; -import io.swagger.v3.oas.annotations.media.ExampleObject; -import io.swagger.v3.oas.annotations.media.Schema; -import io.swagger.v3.oas.annotations.responses.ApiResponse; -import io.swagger.v3.oas.annotations.responses.ApiResponses; -import io.swagger.v3.oas.annotations.tags.Tag; -import jakarta.servlet.http.HttpServletResponse; -import java.nio.file.AccessDeniedException; -import java.time.Duration; -import java.util.UUID; -import lombok.RequiredArgsConstructor; -import org.springframework.beans.factory.annotation.Value; -import org.springframework.http.HttpHeaders; -import org.springframework.http.HttpStatus; -import org.springframework.http.ResponseCookie; -import org.springframework.http.ResponseEntity; -import org.springframework.security.authentication.AuthenticationManager; -import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; -import org.springframework.security.core.Authentication; -import org.springframework.web.ErrorResponse; -import org.springframework.web.bind.annotation.PostMapping; -import org.springframework.web.bind.annotation.RequestBody; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RestController; - -@Tag(name = "인증(Auth)", description = "로그인, 토큰 재발급, 로그아웃 API") -@RestController -@RequestMapping("/api/auth") -@RequiredArgsConstructor -public class AuthController { - - private final AuthenticationManager authenticationManager; - private final JwtTokenProvider jwtTokenProvider; - private final AuthService authService; - - @Value("${token.refresh-cookie-name}") - private String refreshCookieName; - - @Value("${token.refresh-cookie-secure:true}") - private boolean refreshCookieSecure; - - @PostMapping("/signin") - @Operation(summary = "로그인", description = "사번으로 로그인하여 액세스/리프레시 토큰을 발급.") - @ApiResponses({ - @ApiResponse( - responseCode = "200", - description = "로그인 성공", - content = @Content(schema = @Schema(implementation = TokenResponse.class))), - @ApiResponse( - responseCode = "401", - description = "로그인 실패 (아이디/비밀번호 오류, 계정잠금 등)", - content = - @Content( - schema = @Schema(implementation = ErrorResponse.class), - examples = { - @ExampleObject( - name = "사번 입력 오류", - description = "존재하지 않는 아이디", - value = - """ - { - "code": "LOGIN_ID_NOT_FOUND", - "message": "사번을 잘못 입력하셨습니다." - } - """), - @ExampleObject( - name = "비밀번호 입력 오류 (4회 이하)", - description = "아이디는 정상, 비밀번호를 여러 번 틀린 경우", - value = - """ - { - "code": "LOGIN_PASSWORD_MISMATCH", - "message": "비밀번호를 잘못 입력하셨습니다." - } - """), - @ExampleObject( - name = "비밀번호 오류 횟수 초과", - description = "비밀번호 5회 이상 오류로 계정 잠김", - value = - """ - { - "code": "LOGIN_PASSWORD_EXCEEDED", - "message": "비밀번호 오류 횟수를 초과하여 이용하실 수 없습니다. 로그인 오류에 대해 관리자에게 문의하시기 바랍니다." - } - """), - @ExampleObject( - name = "사용 중지 된 계정의 로그인 시도", - description = "사용 중지 된 계정의 로그인 시도", - value = - """ - { - "code": "INACTIVE_ID", - "message": "사용할 수 없는 계정입니다." - } - """) - })) - }) - public ApiResponseDto signin( - @io.swagger.v3.oas.annotations.parameters.RequestBody( - description = "로그인 요청 정보", - required = true) - @RequestBody - SignInRequest request, - HttpServletResponse response) { - - // 사용자 상태 조회 - String status = authService.getUserStatus(request); - - if(StatusType.INACTIVE.getId().equals(status)) { - throw new CustomApiException("INACTIVE_ID", HttpStatus.UNAUTHORIZED); - } - - Authentication authentication = null; - MembersDto.Member member = new MembersDto.Member(); - - authentication = - authenticationManager.authenticate( - new UsernamePasswordAuthenticationToken(request.getUsername(), request.getPassword())); - - String username = authentication.getName(); // UserDetailsService 에서 사용한 username - - String accessToken = jwtTokenProvider.createAccessToken(username); - String refreshToken = jwtTokenProvider.createRefreshToken(username); - - // 토큰 저장 - authService.tokenSave(username, refreshToken, jwtTokenProvider.getRefreshTokenValidityInMs()); - - // HttpOnly + Secure 쿠키에 RefreshToken 저장 - ResponseCookie cookie = - ResponseCookie.from(refreshCookieName, refreshToken) - .httpOnly(true) - .secure(refreshCookieSecure) - .path("/") - .maxAge(Duration.ofMillis(jwtTokenProvider.getRefreshTokenValidityInMs())) - .sameSite("Strict") - .build(); - - response.addHeader(HttpHeaders.SET_COOKIE, cookie.toString()); - - CustomUserDetails user = (CustomUserDetails) authentication.getPrincipal(); - member.setId(user.getMember().getId()); - member.setName(user.getMember().getName()); - member.setEmployeeNo(user.getMember().getEmployeeNo()); - - // PENDING 비활성 상태(새로운 패스워드 입력 해야함) - if (StatusType.PENDING.getId().equals(status)) { - member.setEmployeeNo(request.getUsername()); - return ApiResponseDto.ok(new TokenResponse(status, accessToken, refreshToken, member)); - } - - // 인증 성공 로그인 시간 저장 - authService.saveLogin(UUID.fromString(username)); - - return ApiResponseDto.ok(new TokenResponse(status, accessToken, refreshToken, member)); - } - - @PostMapping("/refresh") - @Operation(summary = "토큰 재발급", description = "리프레시 토큰으로 새로운 액세스/리프레시 토큰을 재발급합니다.") - @ApiResponses({ - @ApiResponse( - responseCode = "200", - description = "재발급 성공", - content = @Content(schema = @Schema(implementation = TokenResponse.class))), - @ApiResponse( - responseCode = "403", - description = "만료되었거나 유효하지 않은 리프레시 토큰", - content = @Content(schema = @Schema(implementation = ErrorResponse.class))) - }) - public ResponseEntity refresh(String refreshToken, HttpServletResponse response) - throws AccessDeniedException { - if (refreshToken == null || !jwtTokenProvider.isValidToken(refreshToken)) { - throw new AccessDeniedException("만료되었거나 유효하지 않은 리프레시 토큰 입니다."); - } - String username = jwtTokenProvider.getSubject(refreshToken); - - // 저장된 RefreshToken과 일치하는지 확인 - authService.validateRefreshToken(username, refreshToken); - - // 새 토큰 발급 - String newAccessToken = jwtTokenProvider.createAccessToken(username); - String newRefreshToken = jwtTokenProvider.createRefreshToken(username); - - // 토큰 저장 - authService.tokenSave(username, refreshToken, jwtTokenProvider.getRefreshTokenValidityInMs()); - - // 쿠키 갱신 - ResponseCookie cookie = - ResponseCookie.from(refreshCookieName, newRefreshToken) - .httpOnly(true) - .secure(refreshCookieSecure) - .path("/") - .maxAge(Duration.ofMillis(jwtTokenProvider.getRefreshTokenValidityInMs())) - .sameSite("Strict") - .build(); - response.addHeader(HttpHeaders.SET_COOKIE, cookie.toString()); - - MembersDto.Member member = new MembersDto.Member(); - return ResponseEntity.ok(new TokenResponse("ACTIVE", newAccessToken, newRefreshToken, member)); - } - - @PostMapping("/logout") - @Operation(summary = "로그아웃", description = "현재 사용자의 토큰을 무효화(리프레시 토큰 삭제)합니다.") - @ApiResponses({ - @ApiResponse( - responseCode = "200", - description = "로그아웃 성공", - content = @Content(schema = @Schema(implementation = Void.class))) - }) - public ApiResponseDto> logout( - Authentication authentication, HttpServletResponse response) { - if (authentication != null) { - String username = authentication.getName(); - authService.logout(username); - } - - // 쿠키 삭제 (Max-Age=0) - ResponseCookie cookie = - ResponseCookie.from(refreshCookieName, "") - .httpOnly(true) - .secure(refreshCookieSecure) - .path("/") - .maxAge(0) - .sameSite("Strict") - .build(); - response.addHeader(HttpHeaders.SET_COOKIE, cookie.toString()); - - return ApiResponseDto.createOK(ResponseEntity.noContent().build()); - } -} +package com.kamco.cd.training.members; + +import com.kamco.cd.training.auth.CustomUserDetails; +import com.kamco.cd.training.auth.JwtTokenProvider; +import com.kamco.cd.training.common.enums.StatusType; +import com.kamco.cd.training.common.exception.CustomApiException; +import com.kamco.cd.training.config.api.ApiResponseDto; +import com.kamco.cd.training.members.dto.MembersDto; +import com.kamco.cd.training.members.dto.SignInRequest; +import com.kamco.cd.training.members.dto.TokenResponse; +import com.kamco.cd.training.members.service.AuthService; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.media.Content; +import io.swagger.v3.oas.annotations.media.ExampleObject; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import io.swagger.v3.oas.annotations.responses.ApiResponses; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.servlet.http.HttpServletResponse; +import java.nio.file.AccessDeniedException; +import java.time.Duration; +import java.util.UUID; +import lombok.RequiredArgsConstructor; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseCookie; +import org.springframework.http.ResponseEntity; +import org.springframework.security.authentication.AuthenticationManager; +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; +import org.springframework.security.core.Authentication; +import org.springframework.web.ErrorResponse; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +@Tag(name = "인증(Auth)", description = "로그인, 토큰 재발급, 로그아웃 API") +@RestController +@RequestMapping("/api/auth") +@RequiredArgsConstructor +public class AuthController { + + private final AuthenticationManager authenticationManager; + private final JwtTokenProvider jwtTokenProvider; + private final AuthService authService; + + @Value("${token.refresh-cookie-name}") + private String refreshCookieName; + + @Value("${token.refresh-cookie-secure:true}") + private boolean refreshCookieSecure; + + @PostMapping("/signin") + @Operation(summary = "로그인", description = "사번으로 로그인하여 액세스/리프레시 토큰을 발급.") + @ApiResponses({ + @ApiResponse( + responseCode = "200", + description = "로그인 성공1", + content = @Content(schema = @Schema(implementation = TokenResponse.class))), + @ApiResponse( + responseCode = "401", + description = "로그인 실패 (아이디/비밀번호 오류, 계정잠금 등)", + content = + @Content( + schema = @Schema(implementation = ErrorResponse.class), + examples = { + @ExampleObject( + name = "사번 입력 오류", + description = "존재하지 않는 아이디", + value = + """ + { + "code": "LOGIN_ID_NOT_FOUND", + "message": "사번을 잘못 입력하셨습니다." + } + """), + @ExampleObject( + name = "비밀번호 입력 오류 (4회 이하)", + description = "아이디는 정상, 비밀번호를 여러 번 틀린 경우", + value = + """ + { + "code": "LOGIN_PASSWORD_MISMATCH", + "message": "비밀번호를 잘못 입력하셨습니다." + } + """), + @ExampleObject( + name = "비밀번호 오류 횟수 초과", + description = "비밀번호 5회 이상 오류로 계정 잠김", + value = + """ + { + "code": "LOGIN_PASSWORD_EXCEEDED", + "message": "비밀번호 오류 횟수를 초과하여 이용하실 수 없습니다. 로그인 오류에 대해 관리자에게 문의하시기 바랍니다." + } + """), + @ExampleObject( + name = "사용 중지 된 계정의 로그인 시도", + description = "사용 중지 된 계정의 로그인 시도", + value = + """ + { + "code": "INACTIVE_ID", + "message": "사용할 수 없는 계정입니다." + } + """) + })) + }) + public ApiResponseDto signin( + @io.swagger.v3.oas.annotations.parameters.RequestBody( + description = "로그인 요청 정보", + required = true) + @RequestBody + SignInRequest request, + HttpServletResponse response) { + + // 사용자 상태 조회 + String status = authService.getUserStatus(request); + + if (StatusType.INACTIVE.getId().equals(status)) { + throw new CustomApiException("INACTIVE_ID", HttpStatus.UNAUTHORIZED); + } + + Authentication authentication = null; + MembersDto.Member member = new MembersDto.Member(); + + authentication = + authenticationManager.authenticate( + new UsernamePasswordAuthenticationToken(request.getUsername(), request.getPassword())); + + String username = authentication.getName(); // UserDetailsService 에서 사용한 username + + String accessToken = jwtTokenProvider.createAccessToken(username); + String refreshToken = jwtTokenProvider.createRefreshToken(username); + + // 토큰 저장 + authService.tokenSave(username, refreshToken, jwtTokenProvider.getRefreshTokenValidityInMs()); + + // HttpOnly + Secure 쿠키에 RefreshToken 저장 + ResponseCookie cookie = + ResponseCookie.from(refreshCookieName, refreshToken) + .httpOnly(true) + .secure(refreshCookieSecure) + .path("/") + .maxAge(Duration.ofMillis(jwtTokenProvider.getRefreshTokenValidityInMs())) + .sameSite("Strict") + .build(); + + response.addHeader(HttpHeaders.SET_COOKIE, cookie.toString()); + + CustomUserDetails user = (CustomUserDetails) authentication.getPrincipal(); + member.setId(user.getMember().getId()); + member.setName(user.getMember().getName()); + member.setEmployeeNo(user.getMember().getEmployeeNo()); + + // PENDING 비활성 상태(새로운 패스워드 입력 해야함) + if (StatusType.PENDING.getId().equals(status)) { + member.setEmployeeNo(request.getUsername()); + return ApiResponseDto.ok(new TokenResponse(status, accessToken, refreshToken, member)); + } + + // 인증 성공 로그인 시간 저장 + authService.saveLogin(UUID.fromString(username)); + + return ApiResponseDto.ok(new TokenResponse(status, accessToken, refreshToken, member)); + } + + @PostMapping("/refresh") + @Operation(summary = "토큰 재발급", description = "리프레시 토큰으로 새로운 액세스/리프레시 토큰을 재발급합니다.") + @ApiResponses({ + @ApiResponse( + responseCode = "200", + description = "재발급 성공", + content = @Content(schema = @Schema(implementation = TokenResponse.class))), + @ApiResponse( + responseCode = "403", + description = "만료되었거나 유효하지 않은 리프레시 토큰", + content = @Content(schema = @Schema(implementation = ErrorResponse.class))) + }) + public ResponseEntity refresh(String refreshToken, HttpServletResponse response) + throws AccessDeniedException { + if (refreshToken == null || !jwtTokenProvider.isValidToken(refreshToken)) { + throw new AccessDeniedException("만료되었거나 유효하지 않은 리프레시 토큰 입니다."); + } + String username = jwtTokenProvider.getSubject(refreshToken); + + // 저장된 RefreshToken과 일치하는지 확인 + authService.validateRefreshToken(username, refreshToken); + + // 새 토큰 발급 + String newAccessToken = jwtTokenProvider.createAccessToken(username); + String newRefreshToken = jwtTokenProvider.createRefreshToken(username); + + // 토큰 저장 + authService.tokenSave(username, refreshToken, jwtTokenProvider.getRefreshTokenValidityInMs()); + + // 쿠키 갱신 + ResponseCookie cookie = + ResponseCookie.from(refreshCookieName, newRefreshToken) + .httpOnly(true) + .secure(refreshCookieSecure) + .path("/") + .maxAge(Duration.ofMillis(jwtTokenProvider.getRefreshTokenValidityInMs())) + .sameSite("Strict") + .build(); + response.addHeader(HttpHeaders.SET_COOKIE, cookie.toString()); + + MembersDto.Member member = new MembersDto.Member(); + return ResponseEntity.ok(new TokenResponse("ACTIVE", newAccessToken, newRefreshToken, member)); + } + + @PostMapping("/logout") + @Operation(summary = "로그아웃", description = "현재 사용자의 토큰을 무효화(리프레시 토큰 삭제)합니다.") + @ApiResponses({ + @ApiResponse( + responseCode = "200", + description = "로그아웃 성공", + content = @Content(schema = @Schema(implementation = Void.class))) + }) + public ApiResponseDto> logout( + Authentication authentication, HttpServletResponse response) { + if (authentication != null) { + String username = authentication.getName(); + authService.logout(username); + } + + // 쿠키 삭제 (Max-Age=0) + ResponseCookie cookie = + ResponseCookie.from(refreshCookieName, "") + .httpOnly(true) + .secure(refreshCookieSecure) + .path("/") + .maxAge(0) + .sameSite("Strict") + .build(); + response.addHeader(HttpHeaders.SET_COOKIE, cookie.toString()); + + return ApiResponseDto.createOK(ResponseEntity.noContent().build()); + } +} diff --git a/src/main/java/com/kamco/cd/training/members/MembersApiController.java b/src/main/java/com/kamco/cd/training/members/MembersApiController.java index bcb8b9c..3fd821a 100644 --- a/src/main/java/com/kamco/cd/training/members/MembersApiController.java +++ b/src/main/java/com/kamco/cd/training/members/MembersApiController.java @@ -1,69 +1,69 @@ -package com.kamco.cd.training.members; - -import com.kamco.cd.training.config.api.ApiResponseDto; -import com.kamco.cd.training.members.dto.MembersDto; -import com.kamco.cd.training.members.dto.MembersDto.Basic; -import com.kamco.cd.training.members.service.MembersService; -import io.swagger.v3.oas.annotations.Operation; -import io.swagger.v3.oas.annotations.media.Content; -import io.swagger.v3.oas.annotations.media.Schema; -import io.swagger.v3.oas.annotations.responses.ApiResponse; -import io.swagger.v3.oas.annotations.responses.ApiResponses; -import io.swagger.v3.oas.annotations.tags.Tag; -import jakarta.validation.Valid; -import lombok.RequiredArgsConstructor; -import org.springframework.data.domain.Page; -import org.springframework.security.authentication.AuthenticationManager; -import org.springframework.web.bind.annotation.*; - -@Tag(name = "회원정보 관리", description = "회원정보 관리 API") -@RestController -@RequestMapping("/api/members") -@RequiredArgsConstructor -public class MembersApiController { - - private final AuthenticationManager authenticationManager; - private final MembersService membersService; - - @Operation(summary = "회원정보 목록", description = "회원정보 조회") - @ApiResponses( - value = { - @ApiResponse( - responseCode = "200", - description = "검색 성공", - content = - @Content( - mediaType = "application/json", - schema = @Schema(implementation = Page.class))), - @ApiResponse(responseCode = "400", description = "잘못된 검색 조건", content = @Content), - @ApiResponse(responseCode = "500", description = "서버 오류", content = @Content) - }) - @PostMapping("/search") - public ApiResponseDto> getMemberList( - @RequestBody @Valid MembersDto.SearchReq searchReq) { - return ApiResponseDto.ok(membersService.findByMembers(searchReq)); - } - - @Operation( - summary = "사용자 비밀번호 변경", - description = "로그인 성공후 status가 INACTIVE일때 로그인 id를 memberId로 path 생성필요") - @ApiResponses( - value = { - @ApiResponse( - responseCode = "201", - description = "사용자 비밀번호 변경", - content = - @Content( - mediaType = "application/json", - schema = @Schema(implementation = Long.class))), - @ApiResponse(responseCode = "400", description = "잘못된 요청 데이터", content = @Content), - @ApiResponse(responseCode = "404", description = "코드를 찾을 수 없음", content = @Content), - @ApiResponse(responseCode = "500", description = "서버 오류", content = @Content) - }) - @PatchMapping("/{memberId}/password") - public ApiResponseDto resetPassword( - @PathVariable String memberId, @RequestBody @Valid MembersDto.InitReq initReq) { - membersService.resetPassword(memberId, initReq); - return ApiResponseDto.createOK(memberId); - } -} +package com.kamco.cd.training.members; + +import com.kamco.cd.training.config.api.ApiResponseDto; +import com.kamco.cd.training.members.dto.MembersDto; +import com.kamco.cd.training.members.dto.MembersDto.Basic; +import com.kamco.cd.training.members.service.MembersService; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.media.Content; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import io.swagger.v3.oas.annotations.responses.ApiResponses; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.validation.Valid; +import lombok.RequiredArgsConstructor; +import org.springframework.data.domain.Page; +import org.springframework.security.authentication.AuthenticationManager; +import org.springframework.web.bind.annotation.*; + +@Tag(name = "회원정보 관리", description = "회원정보 관리 API") +@RestController +@RequestMapping("/api/members") +@RequiredArgsConstructor +public class MembersApiController { + + private final AuthenticationManager authenticationManager; + private final MembersService membersService; + + @Operation(summary = "회원정보 목록", description = "회원정보 조회") + @ApiResponses( + value = { + @ApiResponse( + responseCode = "200", + description = "검색 성공", + content = + @Content( + mediaType = "application/json", + schema = @Schema(implementation = Page.class))), + @ApiResponse(responseCode = "400", description = "잘못된 검색 조건", content = @Content), + @ApiResponse(responseCode = "500", description = "서버 오류", content = @Content) + }) + @PostMapping("/search") + public ApiResponseDto> getMemberList( + @RequestBody @Valid MembersDto.SearchReq searchReq) { + return ApiResponseDto.ok(membersService.findByMembers(searchReq)); + } + + @Operation( + summary = "사용자 비밀번호 변경", + description = "로그인 성공후 status가 INACTIVE일때 로그인 id를 memberId로 path 생성필요") + @ApiResponses( + value = { + @ApiResponse( + responseCode = "201", + description = "사용자 비밀번호 변경", + content = + @Content( + mediaType = "application/json", + schema = @Schema(implementation = Long.class))), + @ApiResponse(responseCode = "400", description = "잘못된 요청 데이터", content = @Content), + @ApiResponse(responseCode = "404", description = "코드를 찾을 수 없음", content = @Content), + @ApiResponse(responseCode = "500", description = "서버 오류", content = @Content) + }) + @PatchMapping("/{memberId}/password") + public ApiResponseDto resetPassword( + @PathVariable String memberId, @RequestBody @Valid MembersDto.InitReq initReq) { + membersService.resetPassword(memberId, initReq); + return ApiResponseDto.createOK(memberId); + } +} diff --git a/src/main/java/com/kamco/cd/training/members/dto/MembersDto.java b/src/main/java/com/kamco/cd/training/members/dto/MembersDto.java index f84bf60..959bce9 100644 --- a/src/main/java/com/kamco/cd/training/members/dto/MembersDto.java +++ b/src/main/java/com/kamco/cd/training/members/dto/MembersDto.java @@ -1,183 +1,183 @@ -package com.kamco.cd.training.members.dto; - -import com.kamco.cd.training.common.enums.RoleType; -import com.kamco.cd.training.common.enums.StatusType; -import com.kamco.cd.training.common.utils.enums.Enums; -import com.kamco.cd.training.common.utils.interfaces.EnumValid; -import com.kamco.cd.training.common.utils.interfaces.JsonFormatDttm; -import io.swagger.v3.oas.annotations.media.Schema; -import jakarta.validation.constraints.NotBlank; -import jakarta.validation.constraints.Size; -import java.time.ZonedDateTime; -import java.util.UUID; -import lombok.AllArgsConstructor; -import lombok.Getter; -import lombok.NoArgsConstructor; -import lombok.Setter; -import org.springframework.data.domain.PageRequest; -import org.springframework.data.domain.Pageable; - -public class MembersDto { - - @Getter - @Setter - public static class Basic { - - private Long id; - private UUID uuid; - private String userRole; - private String userRoleName; - private String name; - private String employeeNo; - private String status; - private String statusName; - @JsonFormatDttm private ZonedDateTime createdDttm; - @JsonFormatDttm private ZonedDateTime firstLoginDttm; - @JsonFormatDttm private ZonedDateTime lastLoginDttm; - @JsonFormatDttm private ZonedDateTime statusChgDttm; - - public Basic( - Long id, - UUID uuid, - String userRole, - String name, - String employeeNo, - String status, - ZonedDateTime createdDttm, - ZonedDateTime firstLoginDttm, - ZonedDateTime lastLoginDttm, - ZonedDateTime statusChgDttm, - Boolean pwdResetYn) { - this.id = id; - this.uuid = uuid; - this.userRole = userRole; - this.userRoleName = getUserRoleName(userRole); - this.name = name; - this.employeeNo = employeeNo; - this.status = status; - this.statusName = getStatusName(status, pwdResetYn); - this.createdDttm = createdDttm; - this.firstLoginDttm = firstLoginDttm; - this.lastLoginDttm = lastLoginDttm; - this.statusChgDttm = statusChgDttm; - } - - private String getUserRoleName(String roleId) { - RoleType type = Enums.fromId(RoleType.class, roleId); - return type.getText(); - } - - private String getStatusName(String status, Boolean pwdResetYn) { - StatusType type = Enums.fromId(StatusType.class, status); - pwdResetYn = pwdResetYn != null && pwdResetYn; - if (type.equals(StatusType.PENDING) && pwdResetYn) { - type = StatusType.ACTIVE; - } - return type.getText(); - } - } - - @Getter - @Setter - @NoArgsConstructor - @AllArgsConstructor - public static class SearchReq { - - @Schema(description = "전체, 관리자(ADMIN), 라벨러(LABELER), 검수자(REVIEWER)", example = "") - private String userRole; - - @Schema(description = "키워드", example = "홍길동") - private String keyword; - - // 페이징 파라미터 - @Schema(description = "페이지 번호 (0부터 시작) ", example = "0") - private int page = 0; - - @Schema(description = "페이지 크기", example = "20") - private int size = 20; - - public Pageable toPageable() { - return PageRequest.of(page, size); - } - } - - @Getter - @Setter - public static class AddReq { - - @Schema(description = "관리자 유형", example = "ADMIN") - @NotBlank - @EnumValid(enumClass = RoleType.class, message = "userRole은 ADMIN, LABELER, REVIEWER 만 가능합니다.") - private String userRole; - - @Schema(description = "사번", example = "K20251212001") - @Size(max = 50) - private String employeeNo; - - @Schema(description = "이름", example = "홍길동") - @NotBlank - @Size(min = 2, max = 100) - private String name; - - @NotBlank - @Schema(description = "패스워드", example = "") - @Size(max = 255) - private String password; - - public AddReq(String userRole, String employeeNo, String name, String password) { - this.userRole = userRole; - this.employeeNo = employeeNo; - this.name = name; - this.password = password; - } - } - - @Getter - @Setter - public static class UpdateReq { - - @Schema(description = "이름", example = "홍길동") - @Size(min = 2, max = 100) - private String name; - - @Schema(description = "상태", example = "ACTIVE") - @EnumValid(enumClass = StatusType.class, message = "status는 ACTIVE, INACTIVE, DELETED 만 가능합니다.") - private String status; - - @Schema(description = "패스워드", example = "") - @Size(max = 255) - private String password; - - public UpdateReq(String name, String status, String password) { - this.name = name; - this.status = status; - this.password = password; - } - } - - @Getter - @Setter - public static class InitReq { - - @Schema(description = "기존 패스워드", example = "") - @Size(max = 255) - @NotBlank - private String oldPassword; - - @Schema(description = "신규 패스워드", example = "") - @Size(max = 255) - @NotBlank - private String newPassword; - } - - @Getter - @Setter - @AllArgsConstructor - @NoArgsConstructor - public static class Member { - - private Long id; - private String name; - private String employeeNo; - } -} +package com.kamco.cd.training.members.dto; + +import com.kamco.cd.training.common.enums.RoleType; +import com.kamco.cd.training.common.enums.StatusType; +import com.kamco.cd.training.common.utils.enums.Enums; +import com.kamco.cd.training.common.utils.interfaces.EnumValid; +import com.kamco.cd.training.common.utils.interfaces.JsonFormatDttm; +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.Size; +import java.time.ZonedDateTime; +import java.util.UUID; +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Pageable; + +public class MembersDto { + + @Getter + @Setter + public static class Basic { + + private Long id; + private UUID uuid; + private String userRole; + private String userRoleName; + private String name; + private String employeeNo; + private String status; + private String statusName; + @JsonFormatDttm private ZonedDateTime createdDttm; + @JsonFormatDttm private ZonedDateTime firstLoginDttm; + @JsonFormatDttm private ZonedDateTime lastLoginDttm; + @JsonFormatDttm private ZonedDateTime statusChgDttm; + + public Basic( + Long id, + UUID uuid, + String userRole, + String name, + String employeeNo, + String status, + ZonedDateTime createdDttm, + ZonedDateTime firstLoginDttm, + ZonedDateTime lastLoginDttm, + ZonedDateTime statusChgDttm, + Boolean pwdResetYn) { + this.id = id; + this.uuid = uuid; + this.userRole = userRole; + this.userRoleName = getUserRoleName(userRole); + this.name = name; + this.employeeNo = employeeNo; + this.status = status; + this.statusName = getStatusName(status, pwdResetYn); + this.createdDttm = createdDttm; + this.firstLoginDttm = firstLoginDttm; + this.lastLoginDttm = lastLoginDttm; + this.statusChgDttm = statusChgDttm; + } + + private String getUserRoleName(String roleId) { + RoleType type = Enums.fromId(RoleType.class, roleId); + return type.getText(); + } + + private String getStatusName(String status, Boolean pwdResetYn) { + StatusType type = Enums.fromId(StatusType.class, status); + pwdResetYn = pwdResetYn != null && pwdResetYn; + if (type.equals(StatusType.PENDING) && pwdResetYn) { + type = StatusType.ACTIVE; + } + return type.getText(); + } + } + + @Getter + @Setter + @NoArgsConstructor + @AllArgsConstructor + public static class SearchReq { + + @Schema(description = "전체, 관리자(ADMIN), 라벨러(LABELER), 검수자(REVIEWER)", example = "") + private String userRole; + + @Schema(description = "키워드", example = "홍길동") + private String keyword; + + // 페이징 파라미터 + @Schema(description = "페이지 번호 (0부터 시작) ", example = "0") + private int page = 0; + + @Schema(description = "페이지 크기", example = "20") + private int size = 20; + + public Pageable toPageable() { + return PageRequest.of(page, size); + } + } + + @Getter + @Setter + public static class AddReq { + + @Schema(description = "관리자 유형", example = "ADMIN") + @NotBlank + @EnumValid(enumClass = RoleType.class, message = "userRole은 ADMIN, LABELER, REVIEWER 만 가능합니다.") + private String userRole; + + @Schema(description = "사번", example = "K20251212001") + @Size(max = 50) + private String employeeNo; + + @Schema(description = "이름", example = "홍길동") + @NotBlank + @Size(min = 2, max = 100) + private String name; + + @NotBlank + @Schema(description = "패스워드", example = "") + @Size(max = 255) + private String password; + + public AddReq(String userRole, String employeeNo, String name, String password) { + this.userRole = userRole; + this.employeeNo = employeeNo; + this.name = name; + this.password = password; + } + } + + @Getter + @Setter + public static class UpdateReq { + + @Schema(description = "이름", example = "홍길동") + @Size(min = 2, max = 100) + private String name; + + @Schema(description = "상태", example = "ACTIVE") + @EnumValid(enumClass = StatusType.class, message = "status는 ACTIVE, INACTIVE, DELETED 만 가능합니다.") + private String status; + + @Schema(description = "패스워드", example = "") + @Size(max = 255) + private String password; + + public UpdateReq(String name, String status, String password) { + this.name = name; + this.status = status; + this.password = password; + } + } + + @Getter + @Setter + public static class InitReq { + + @Schema(description = "기존 패스워드", example = "") + @Size(max = 255) + @NotBlank + private String oldPassword; + + @Schema(description = "신규 패스워드", example = "") + @Size(max = 255) + @NotBlank + private String newPassword; + } + + @Getter + @Setter + @AllArgsConstructor + @NoArgsConstructor + public static class Member { + + private Long id; + private String name; + private String employeeNo; + } +} diff --git a/src/main/java/com/kamco/cd/training/members/dto/SignInRequest.java b/src/main/java/com/kamco/cd/training/members/dto/SignInRequest.java index a0332bc..f4b6571 100644 --- a/src/main/java/com/kamco/cd/training/members/dto/SignInRequest.java +++ b/src/main/java/com/kamco/cd/training/members/dto/SignInRequest.java @@ -1,20 +1,20 @@ -package com.kamco.cd.training.members.dto; - -import com.fasterxml.jackson.annotation.JsonProperty; -import io.swagger.v3.oas.annotations.media.Schema; -import lombok.Getter; -import lombok.Setter; -import lombok.ToString; - -@Getter -@Setter -@ToString(exclude = "password") -public class SignInRequest { - - @Schema(description = "사용자 ID", example = "1234567") - private String username; - - @Schema(description = "비밀번호", example = "Admin2!@#") - @JsonProperty(access = JsonProperty.Access.WRITE_ONLY) - private String password; -} +package com.kamco.cd.training.members.dto; + +import com.fasterxml.jackson.annotation.JsonProperty; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Getter; +import lombok.Setter; +import lombok.ToString; + +@Getter +@Setter +@ToString(exclude = "password") +public class SignInRequest { + + @Schema(description = "사용자 ID", example = "1234567") + private String username; + + @Schema(description = "비밀번호", example = "Admin2!@#") + @JsonProperty(access = JsonProperty.Access.WRITE_ONLY) + private String password; +} diff --git a/src/main/java/com/kamco/cd/training/members/dto/TokenResponse.java b/src/main/java/com/kamco/cd/training/members/dto/TokenResponse.java index 1cfc232..3124d60 100644 --- a/src/main/java/com/kamco/cd/training/members/dto/TokenResponse.java +++ b/src/main/java/com/kamco/cd/training/members/dto/TokenResponse.java @@ -1,16 +1,16 @@ -package com.kamco.cd.training.members.dto; - -import lombok.AllArgsConstructor; -import lombok.Getter; -import lombok.Setter; - -@Getter -@Setter -@AllArgsConstructor -public class TokenResponse { - - private String status; - private String accessToken; - private String refreshToken; - private MembersDto.Member member; -} +package com.kamco.cd.training.members.dto; + +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.Setter; + +@Getter +@Setter +@AllArgsConstructor +public class TokenResponse { + + private String status; + private String accessToken; + private String refreshToken; + private MembersDto.Member member; +} diff --git a/src/main/java/com/kamco/cd/training/members/exception/MemberException.java b/src/main/java/com/kamco/cd/training/members/exception/MemberException.java index 5578607..bc375d5 100644 --- a/src/main/java/com/kamco/cd/training/members/exception/MemberException.java +++ b/src/main/java/com/kamco/cd/training/members/exception/MemberException.java @@ -1,50 +1,50 @@ -package com.kamco.cd.training.members.exception; - -import lombok.Getter; - -@Getter -public class MemberException { - - // *** Duplicate Member Exception *** - @Getter - public static class DuplicateMemberException extends RuntimeException { - - public enum Field { - USER_ID, - EMPLOYEE_NO, - DEFAULT - } - - private final Field field; - private final String value; - - public DuplicateMemberException(Field field, String value) { - super(field.name() + " duplicate: " + value); - this.field = field; - this.value = value; - } - } - - // *** Member Not Found Exception *** - public static class MemberNotFoundException extends RuntimeException { - - public MemberNotFoundException() { - super("Member not found"); - } - - public MemberNotFoundException(String message) { - super(message); - } - } - - public static class PasswordNotFoundException extends RuntimeException { - - public PasswordNotFoundException() { - super("Password not found"); - } - - public PasswordNotFoundException(String message) { - super(message); - } - } -} +package com.kamco.cd.training.members.exception; + +import lombok.Getter; + +@Getter +public class MemberException { + + // *** Duplicate Member Exception *** + @Getter + public static class DuplicateMemberException extends RuntimeException { + + public enum Field { + USER_ID, + EMPLOYEE_NO, + DEFAULT + } + + private final Field field; + private final String value; + + public DuplicateMemberException(Field field, String value) { + super(field.name() + " duplicate: " + value); + this.field = field; + this.value = value; + } + } + + // *** Member Not Found Exception *** + public static class MemberNotFoundException extends RuntimeException { + + public MemberNotFoundException() { + super("Member not found"); + } + + public MemberNotFoundException(String message) { + super(message); + } + } + + public static class PasswordNotFoundException extends RuntimeException { + + public PasswordNotFoundException() { + super("Password not found"); + } + + public PasswordNotFoundException(String message) { + super(message); + } + } +} diff --git a/src/main/java/com/kamco/cd/training/members/service/AuthService.java b/src/main/java/com/kamco/cd/training/members/service/AuthService.java index c0e875c..882fa4a 100644 --- a/src/main/java/com/kamco/cd/training/members/service/AuthService.java +++ b/src/main/java/com/kamco/cd/training/members/service/AuthService.java @@ -1,77 +1,77 @@ -package com.kamco.cd.training.members.service; - -import com.kamco.cd.training.common.enums.error.AuthErrorCode; -import com.kamco.cd.training.common.exception.CustomApiException; -import com.kamco.cd.training.members.dto.SignInRequest; -import com.kamco.cd.training.postgres.core.MembersCoreService; -import com.kamco.cd.training.postgres.core.TokenCoreService; -import java.util.UUID; -import lombok.RequiredArgsConstructor; -import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Transactional; - -@Service -@RequiredArgsConstructor -@Transactional(readOnly = true) -public class AuthService { - - private final MembersCoreService membersCoreService; - private final TokenCoreService tokenCoreService; - - /** - * 토큰 저장 - * - * @param subject - * @param refreshToken - * @param validityMs - */ - @Transactional - public void tokenSave(String subject, String refreshToken, long validityMs) { - tokenCoreService.save(subject, refreshToken, validityMs); - } - - /** - * refreshToken을 DB와 비교 검증 - * - * @param subject 사용자 식별(UUID) - * @param requestRefreshToken refresh token - */ - public void validateRefreshToken(String subject, String requestRefreshToken) { - String savedToken = tokenCoreService.getValidTokenOrThrow(subject); - - if (!savedToken.equals(requestRefreshToken)) { - throw new CustomApiException(AuthErrorCode.REFRESH_TOKEN_MISMATCH); - } - } - - /** - * 로그아웃(토큰폐기) - * - * @param subject 사용자 식별(UUID) - */ - @Transactional - public void logout(String subject) { - // RefreshToken 폐기 - tokenCoreService.revokeBySubject(subject); - } - - /** - * 로그인 일시 저장 - * - * @param uuid - */ - @Transactional - public void saveLogin(UUID uuid) { - membersCoreService.saveLogin(uuid); - } - - /** - * 사용자 상태 조회 - * - * @param request - * @return - */ - public String getUserStatus(SignInRequest request) { - return membersCoreService.getUserStatus(request); - } -} +package com.kamco.cd.training.members.service; + +import com.kamco.cd.training.common.enums.error.AuthErrorCode; +import com.kamco.cd.training.common.exception.CustomApiException; +import com.kamco.cd.training.members.dto.SignInRequest; +import com.kamco.cd.training.postgres.core.MembersCoreService; +import com.kamco.cd.training.postgres.core.TokenCoreService; +import java.util.UUID; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +@Service +@RequiredArgsConstructor +@Transactional(readOnly = true) +public class AuthService { + + private final MembersCoreService membersCoreService; + private final TokenCoreService tokenCoreService; + + /** + * 토큰 저장 + * + * @param subject + * @param refreshToken + * @param validityMs + */ + @Transactional + public void tokenSave(String subject, String refreshToken, long validityMs) { + tokenCoreService.save(subject, refreshToken, validityMs); + } + + /** + * refreshToken을 DB와 비교 검증 + * + * @param subject 사용자 식별(UUID) + * @param requestRefreshToken refresh token + */ + public void validateRefreshToken(String subject, String requestRefreshToken) { + String savedToken = tokenCoreService.getValidTokenOrThrow(subject); + + if (!savedToken.equals(requestRefreshToken)) { + throw new CustomApiException(AuthErrorCode.REFRESH_TOKEN_MISMATCH); + } + } + + /** + * 로그아웃(토큰폐기) + * + * @param subject 사용자 식별(UUID) + */ + @Transactional + public void logout(String subject) { + // RefreshToken 폐기 + tokenCoreService.revokeBySubject(subject); + } + + /** + * 로그인 일시 저장 + * + * @param uuid + */ + @Transactional + public void saveLogin(UUID uuid) { + membersCoreService.saveLogin(uuid); + } + + /** + * 사용자 상태 조회 + * + * @param request + * @return + */ + public String getUserStatus(SignInRequest request) { + return membersCoreService.getUserStatus(request); + } +} diff --git a/src/main/java/com/kamco/cd/training/members/service/MemberDetailsService.java b/src/main/java/com/kamco/cd/training/members/service/MemberDetailsService.java index 33a4355..4d7c81d 100644 --- a/src/main/java/com/kamco/cd/training/members/service/MemberDetailsService.java +++ b/src/main/java/com/kamco/cd/training/members/service/MemberDetailsService.java @@ -1,29 +1,29 @@ -package com.kamco.cd.training.members.service; - -import com.kamco.cd.training.auth.CustomUserDetails; -import com.kamco.cd.training.postgres.entity.MemberEntity; -import com.kamco.cd.training.postgres.repository.members.MembersRepository; -import java.util.UUID; -import lombok.RequiredArgsConstructor; -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; - -@Service -@RequiredArgsConstructor -public class MemberDetailsService implements UserDetailsService { - - private final MembersRepository membersRepository; - - @Override - public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { - UUID uuid = UUID.fromString(username); - MemberEntity member = - membersRepository - .findByUUID(uuid) - .orElseThrow(() -> new UsernameNotFoundException("USER NOT FOUND")); - - return new CustomUserDetails(member); - } -} +package com.kamco.cd.training.members.service; + +import com.kamco.cd.training.auth.CustomUserDetails; +import com.kamco.cd.training.postgres.entity.MemberEntity; +import com.kamco.cd.training.postgres.repository.members.MembersRepository; +import java.util.UUID; +import lombok.RequiredArgsConstructor; +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; + +@Service +@RequiredArgsConstructor +public class MemberDetailsService implements UserDetailsService { + + private final MembersRepository membersRepository; + + @Override + public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { + UUID uuid = UUID.fromString(username); + MemberEntity member = + membersRepository + .findByUUID(uuid) + .orElseThrow(() -> new UsernameNotFoundException("USER NOT FOUND")); + + return new CustomUserDetails(member); + } +} diff --git a/src/main/java/com/kamco/cd/training/members/service/MembersService.java b/src/main/java/com/kamco/cd/training/members/service/MembersService.java index 0f38bea..a9122ab 100644 --- a/src/main/java/com/kamco/cd/training/members/service/MembersService.java +++ b/src/main/java/com/kamco/cd/training/members/service/MembersService.java @@ -1,45 +1,45 @@ -package com.kamco.cd.training.members.service; - -import com.kamco.cd.training.common.exception.CustomApiException; -import com.kamco.cd.training.common.utils.CommonStringUtils; -import com.kamco.cd.training.members.dto.MembersDto; -import com.kamco.cd.training.members.dto.MembersDto.Basic; -import com.kamco.cd.training.postgres.core.MembersCoreService; -import lombok.RequiredArgsConstructor; -import org.springframework.data.domain.Page; -import org.springframework.http.HttpStatus; -import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Transactional; - -@Service -@Transactional(readOnly = true) -@RequiredArgsConstructor -public class MembersService { - - private final MembersCoreService membersCoreService; - - /** - * 회원목록 조회 - * - * @param searchReq - * @return - */ - public Page findByMembers(MembersDto.SearchReq searchReq) { - return membersCoreService.findByMembers(searchReq); - } - - /** - * 패스워드 사용자 변경 - * - * @param id - * @param initReq - */ - @Transactional - public void resetPassword(String id, MembersDto.InitReq initReq) { - - if (!CommonStringUtils.isValidPassword(initReq.getNewPassword())) { - throw new CustomApiException("WRONG_PASSWORD", HttpStatus.BAD_REQUEST); - } - membersCoreService.resetPassword(id, initReq); - } -} +package com.kamco.cd.training.members.service; + +import com.kamco.cd.training.common.exception.CustomApiException; +import com.kamco.cd.training.common.utils.CommonStringUtils; +import com.kamco.cd.training.members.dto.MembersDto; +import com.kamco.cd.training.members.dto.MembersDto.Basic; +import com.kamco.cd.training.postgres.core.MembersCoreService; +import lombok.RequiredArgsConstructor; +import org.springframework.data.domain.Page; +import org.springframework.http.HttpStatus; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +@Service +@Transactional(readOnly = true) +@RequiredArgsConstructor +public class MembersService { + + private final MembersCoreService membersCoreService; + + /** + * 회원목록 조회 + * + * @param searchReq + * @return + */ + public Page findByMembers(MembersDto.SearchReq searchReq) { + return membersCoreService.findByMembers(searchReq); + } + + /** + * 패스워드 사용자 변경 + * + * @param id + * @param initReq + */ + @Transactional + public void resetPassword(String id, MembersDto.InitReq initReq) { + + if (!CommonStringUtils.isValidPassword(initReq.getNewPassword())) { + throw new CustomApiException("WRONG_PASSWORD", HttpStatus.BAD_REQUEST); + } + membersCoreService.resetPassword(id, initReq); + } +} diff --git a/src/main/java/com/kamco/cd/training/menu/MenuApiController.java b/src/main/java/com/kamco/cd/training/menu/MenuApiController.java index eb58ab3..279d118 100644 --- a/src/main/java/com/kamco/cd/training/menu/MenuApiController.java +++ b/src/main/java/com/kamco/cd/training/menu/MenuApiController.java @@ -1,62 +1,62 @@ -package com.kamco.cd.training.menu; - -import com.kamco.cd.training.config.api.ApiResponseDto; -import com.kamco.cd.training.menu.dto.MenuDto; -import com.kamco.cd.training.menu.service.MenuService; -import io.swagger.v3.oas.annotations.Operation; -import io.swagger.v3.oas.annotations.media.Content; -import io.swagger.v3.oas.annotations.media.Schema; -import io.swagger.v3.oas.annotations.responses.ApiResponse; -import io.swagger.v3.oas.annotations.responses.ApiResponses; -import io.swagger.v3.oas.annotations.tags.Tag; -import java.util.List; -import lombok.RequiredArgsConstructor; -import org.springframework.data.domain.Page; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RestController; - -@Tag(name = "메뉴 관리", description = "메뉴 관리 API") -@RestController -@RequestMapping("/api/menu") -@RequiredArgsConstructor -public class MenuApiController { - - private final MenuService menuService; - - @Operation(summary = "메뉴 목록", description = "메뉴 목록 조회") - @ApiResponses( - value = { - @ApiResponse( - responseCode = "200", - description = "검색 성공", - content = - @Content( - mediaType = "application/json", - schema = @Schema(implementation = Page.class))), - @ApiResponse(responseCode = "400", description = "잘못된 검색 조건", content = @Content), - @ApiResponse(responseCode = "500", description = "서버 오류", content = @Content) - }) - @GetMapping - public ApiResponseDto> getFindAll() { - return ApiResponseDto.ok(menuService.getFindAll()); - } - - @Operation(summary = "캐시 초기화", description = "메뉴관리 캐시를 초기화합니다.") - @ApiResponses( - value = { - @ApiResponse( - responseCode = "200", - description = "캐시 초기화 성공", - content = - @Content( - mediaType = "application/json", - schema = @Schema(implementation = String.class))), - @ApiResponse(responseCode = "500", description = "서버 오류", content = @Content) - }) - @GetMapping("/cache/refresh") - public ApiResponseDto refreshCommonCodeCache() { - menuService.refresh(); - return ApiResponseDto.ok("메뉴관리 캐시가 초기화되었습니다."); - } -} +package com.kamco.cd.training.menu; + +import com.kamco.cd.training.config.api.ApiResponseDto; +import com.kamco.cd.training.menu.dto.MenuDto; +import com.kamco.cd.training.menu.service.MenuService; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.media.Content; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import io.swagger.v3.oas.annotations.responses.ApiResponses; +import io.swagger.v3.oas.annotations.tags.Tag; +import java.util.List; +import lombok.RequiredArgsConstructor; +import org.springframework.data.domain.Page; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +@Tag(name = "메뉴 관리", description = "메뉴 관리 API") +@RestController +@RequestMapping("/api/menu") +@RequiredArgsConstructor +public class MenuApiController { + + private final MenuService menuService; + + @Operation(summary = "메뉴 목록", description = "메뉴 목록 조회") + @ApiResponses( + value = { + @ApiResponse( + responseCode = "200", + description = "검색 성공", + content = + @Content( + mediaType = "application/json", + schema = @Schema(implementation = Page.class))), + @ApiResponse(responseCode = "400", description = "잘못된 검색 조건", content = @Content), + @ApiResponse(responseCode = "500", description = "서버 오류", content = @Content) + }) + @GetMapping + public ApiResponseDto> getFindAll() { + return ApiResponseDto.ok(menuService.getFindAll()); + } + + @Operation(summary = "캐시 초기화", description = "메뉴관리 캐시를 초기화합니다.") + @ApiResponses( + value = { + @ApiResponse( + responseCode = "200", + description = "캐시 초기화 성공", + content = + @Content( + mediaType = "application/json", + schema = @Schema(implementation = String.class))), + @ApiResponse(responseCode = "500", description = "서버 오류", content = @Content) + }) + @GetMapping("/cache/refresh") + public ApiResponseDto refreshCommonCodeCache() { + menuService.refresh(); + return ApiResponseDto.ok("메뉴관리 캐시가 초기화되었습니다."); + } +} diff --git a/src/main/java/com/kamco/cd/training/menu/dto/MenuDto.java b/src/main/java/com/kamco/cd/training/menu/dto/MenuDto.java index fc53213..2df93c2 100644 --- a/src/main/java/com/kamco/cd/training/menu/dto/MenuDto.java +++ b/src/main/java/com/kamco/cd/training/menu/dto/MenuDto.java @@ -1,64 +1,64 @@ -package com.kamco.cd.training.menu.dto; - -import com.kamco.cd.training.common.utils.interfaces.JsonFormatDttm; -import io.swagger.v3.oas.annotations.media.Schema; -import java.time.ZonedDateTime; -import java.util.List; -import lombok.Getter; -import lombok.NoArgsConstructor; - -public class MenuDto { - - @Schema(name = "Menu Basic", description = "메뉴 기본 정보") - @Getter - @NoArgsConstructor - public static class Basic { - - private String menuUid; - private String menuNm; - private String menuUrl; - private String description; - private Long menuOrder; - private Boolean isUse; - private Boolean deleted; - private Long createdUid; - private Long updatedUid; - - private List children; - - @JsonFormatDttm private ZonedDateTime createdDttm; - - @JsonFormatDttm private ZonedDateTime updatedDttm; - - private String menuApiUrl; - - public Basic( - String menuUid, - String menuNm, - String menuUrl, - String description, - Long menuOrder, - Boolean isUse, - Boolean deleted, - Long createdUid, - Long updatedUid, - List children, - ZonedDateTime createdDttm, - ZonedDateTime updatedDttm, - String menuApiUrl) { - this.menuUid = menuUid; - this.menuNm = menuNm; - this.menuUrl = menuUrl; - this.description = description; - this.menuOrder = menuOrder; - this.isUse = isUse; - this.deleted = deleted; - this.createdUid = createdUid; - this.updatedUid = updatedUid; - this.children = children; - this.createdDttm = createdDttm; - this.updatedDttm = updatedDttm; - this.menuApiUrl = menuApiUrl; - } - } -} +package com.kamco.cd.training.menu.dto; + +import com.kamco.cd.training.common.utils.interfaces.JsonFormatDttm; +import io.swagger.v3.oas.annotations.media.Schema; +import java.time.ZonedDateTime; +import java.util.List; +import lombok.Getter; +import lombok.NoArgsConstructor; + +public class MenuDto { + + @Schema(name = "Menu Basic", description = "메뉴 기본 정보") + @Getter + @NoArgsConstructor + public static class Basic { + + private String menuUid; + private String menuNm; + private String menuUrl; + private String description; + private Long menuOrder; + private Boolean isUse; + private Boolean deleted; + private Long createdUid; + private Long updatedUid; + + private List children; + + @JsonFormatDttm private ZonedDateTime createdDttm; + + @JsonFormatDttm private ZonedDateTime updatedDttm; + + private String menuApiUrl; + + public Basic( + String menuUid, + String menuNm, + String menuUrl, + String description, + Long menuOrder, + Boolean isUse, + Boolean deleted, + Long createdUid, + Long updatedUid, + List children, + ZonedDateTime createdDttm, + ZonedDateTime updatedDttm, + String menuApiUrl) { + this.menuUid = menuUid; + this.menuNm = menuNm; + this.menuUrl = menuUrl; + this.description = description; + this.menuOrder = menuOrder; + this.isUse = isUse; + this.deleted = deleted; + this.createdUid = createdUid; + this.updatedUid = updatedUid; + this.children = children; + this.createdDttm = createdDttm; + this.updatedDttm = updatedDttm; + this.menuApiUrl = menuApiUrl; + } + } +} diff --git a/src/main/java/com/kamco/cd/training/menu/service/MenuService.java b/src/main/java/com/kamco/cd/training/menu/service/MenuService.java index 27f1985..f25f3f9 100644 --- a/src/main/java/com/kamco/cd/training/menu/service/MenuService.java +++ b/src/main/java/com/kamco/cd/training/menu/service/MenuService.java @@ -1,27 +1,27 @@ -package com.kamco.cd.training.menu.service; - -import com.kamco.cd.training.menu.dto.MenuDto; -import com.kamco.cd.training.postgres.core.MenuCoreService; -import java.util.List; -import lombok.RequiredArgsConstructor; -import org.springframework.cache.annotation.CacheEvict; -import org.springframework.cache.annotation.Cacheable; -import org.springframework.stereotype.Service; - -// training 서버는 Redis 사용하지 않고 Spring Boot 메모리 캐시를 사용함 -// => org.springframework.cache.annotation.Cacheable - -@Service -@RequiredArgsConstructor -public class MenuService { - private final MenuCoreService menuCoreService; - - @Cacheable("trainMenuFindAll") - public List getFindAll() { - return menuCoreService.getFindAll(); - } - - /** 메모리 캐시 초기화 */ - @CacheEvict(value = "trainMenuFindAll", allEntries = true) - public void refresh() {} -} +package com.kamco.cd.training.menu.service; + +import com.kamco.cd.training.menu.dto.MenuDto; +import com.kamco.cd.training.postgres.core.MenuCoreService; +import java.util.List; +import lombok.RequiredArgsConstructor; +import org.springframework.cache.annotation.CacheEvict; +import org.springframework.cache.annotation.Cacheable; +import org.springframework.stereotype.Service; + +// training 서버는 Redis 사용하지 않고 Spring Boot 메모리 캐시를 사용함 +// => org.springframework.cache.annotation.Cacheable + +@Service +@RequiredArgsConstructor +public class MenuService { + private final MenuCoreService menuCoreService; + + @Cacheable("trainMenuFindAll") + public List getFindAll() { + return menuCoreService.getFindAll(); + } + + /** 메모리 캐시 초기화 */ + @CacheEvict(value = "trainMenuFindAll", allEntries = true) + public void refresh() {} +} diff --git a/src/main/java/com/kamco/cd/training/model/ModelMngApiController.java b/src/main/java/com/kamco/cd/training/model/ModelMngApiController.java index 4be1e81..2d71708 100644 --- a/src/main/java/com/kamco/cd/training/model/ModelMngApiController.java +++ b/src/main/java/com/kamco/cd/training/model/ModelMngApiController.java @@ -1,286 +1,286 @@ -package com.kamco.cd.training.model; - -import com.kamco.cd.training.config.api.ApiResponseDto; -import com.kamco.cd.training.model.dto.ModelMngDto; -import com.kamco.cd.training.model.dto.ModelMngDto.Basic; -import com.kamco.cd.training.model.service.ModelMngService; -import com.kamco.cd.training.model.service.ModelTrainService; -import io.swagger.v3.oas.annotations.Operation; -import io.swagger.v3.oas.annotations.Parameter; -import io.swagger.v3.oas.annotations.media.Content; -import io.swagger.v3.oas.annotations.media.Schema; -import io.swagger.v3.oas.annotations.responses.ApiResponse; -import io.swagger.v3.oas.annotations.responses.ApiResponses; -import io.swagger.v3.oas.annotations.tags.Tag; -import jakarta.validation.Valid; -import java.util.List; -import lombok.RequiredArgsConstructor; -import org.springframework.data.domain.Page; -import org.springframework.web.bind.annotation.DeleteMapping; -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.RequestBody; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RequestParam; -import org.springframework.web.bind.annotation.RestController; - -@RestController -@RequiredArgsConstructor -@Tag(name = "모델관리", description = "모델관리 (학습 모델, 하이퍼파라미터, 메모)") -@RequestMapping("/api/models") -public class ModelMngApiController { - private final ModelMngService modelMngService; - private final ModelTrainService modelTrainService; - - @Operation(summary = "학습 모델 목록 조회", description = "학습 모델 목록을 조회합니다") - @ApiResponses( - value = { - @ApiResponse( - responseCode = "200", - description = "검색 성공", - content = - @Content( - mediaType = "application/json", - schema = @Schema(implementation = Page.class))), - @ApiResponse(responseCode = "400", description = "잘못된 검색 조건", content = @Content), - @ApiResponse(responseCode = "500", description = "서버 오류", content = @Content) - }) - @GetMapping - public ApiResponseDto> findByModels( - @Parameter(description = "상태 코드") @RequestParam(required = false) String status, - @Parameter(description = "페이지 번호") @RequestParam(defaultValue = "0") int page, - @Parameter(description = "페이지 크기") @RequestParam(defaultValue = "20") int size) { - ModelMngDto.SearchReq searchReq = new ModelMngDto.SearchReq(status, page, size); - return ApiResponseDto.ok(modelMngService.findByModels(searchReq)); - } - - @Operation(summary = "학습 모델 상세 조회", description = "학습 모델의 상세 정보를 UUID로 조회합니다") - @ApiResponses( - value = { - @ApiResponse( - responseCode = "200", - description = "조회 성공", - content = - @Content( - mediaType = "application/json", - schema = @Schema(implementation = ModelMngDto.Detail.class))), - @ApiResponse(responseCode = "404", description = "모델을 찾을 수 없음", content = @Content), - @ApiResponse(responseCode = "500", description = "서버 오류", content = @Content) - }) - @GetMapping("/{uuid}") - public ApiResponseDto getModelDetail( - @Parameter(description = "모델 UUID", example = "b7e99739-6736-45f9-a224-8161ecddf287") - @PathVariable - String uuid) { - return ApiResponseDto.ok(modelMngService.getModelDetailByUuid(uuid)); - } - - // ==================== 학습 모델학습관리 API (5종) ==================== - - @Operation(summary = "학습 모델 통합 조회", description = "학습 관리 화면에서 학습 이력 리스트와 현재 상태를 조회합니다") - @ApiResponses( - value = { - @ApiResponse( - responseCode = "200", - description = "조회 성공", - content = - @Content( - mediaType = "application/json", - schema = @Schema(implementation = List.class))), - @ApiResponse(responseCode = "500", description = "서버 오류", content = @Content) - }) - @GetMapping("/train") - public ApiResponseDto> getTrainModelList() { - return ApiResponseDto.ok(modelTrainService.getTrainModelList()); - } - - @Operation(summary = "학습 설정 통합 조회", description = "학습 실행 팝업 구성에 필요한 모든 데이터를 한 번에 반환합니다") - @ApiResponses( - value = { - @ApiResponse( - responseCode = "200", - description = "조회 성공", - content = - @Content( - mediaType = "application/json", - schema = @Schema(implementation = ModelMngDto.FormConfigRes.class))), - @ApiResponse(responseCode = "500", description = "서버 오류", content = @Content) - }) - @GetMapping("/train/form-config") - public ApiResponseDto getFormConfig() { - return ApiResponseDto.ok(modelTrainService.getFormConfig()); - } - - @Operation(summary = "하이퍼파라미터 등록", description = "Step 1 에서 파라미터를 수정하여 신규 버전으로 저장합니다") - @ApiResponses( - value = { - @ApiResponse( - responseCode = "200", - description = "등록 성공", - content = - @Content( - mediaType = "application/json", - schema = @Schema(implementation = String.class))), - @ApiResponse(responseCode = "400", description = "잘못된 요청", content = @Content), - @ApiResponse(responseCode = "500", description = "서버 오류", content = @Content) - }) - @PostMapping("/hyper-params") - public ApiResponseDto createHyperParam( - @Valid @RequestBody ModelMngDto.HyperParamCreateReq createReq) { - String newVersion = modelTrainService.createHyperParam(createReq); - return ApiResponseDto.ok(newVersion); - } - - @Operation(summary = "하이퍼파라미터 단건 조회", description = "특정 버전의 하이퍼파라미터 상세 정보를 조회합니다") - @ApiResponses( - value = { - @ApiResponse( - responseCode = "200", - description = "조회 성공", - content = - @Content( - mediaType = "application/json", - schema = @Schema(implementation = ModelMngDto.HyperParamInfo.class))), - @ApiResponse(responseCode = "404", description = "하이퍼파라미터를 찾을 수 없음", content = @Content), - @ApiResponse(responseCode = "500", description = "서버 오류", content = @Content) - }) - @GetMapping("/hyper-params/{hyperVer}") - public ApiResponseDto getHyperParam( - @Parameter(description = "하이퍼파라미터 버전", example = "H1") @PathVariable String hyperVer) { - return ApiResponseDto.ok(modelTrainService.getHyperParam(hyperVer)); - } - - @Operation(summary = "하이퍼파라미터 삭제", description = "특정 버전의 하이퍼파라미터를 삭제합니다 (H1은 삭제 불가)") - @ApiResponses( - value = { - @ApiResponse(responseCode = "200", description = "삭제 성공", content = @Content), - @ApiResponse(responseCode = "400", description = "H1은 삭제 불가", content = @Content), - @ApiResponse(responseCode = "404", description = "하이퍼파라미터를 찾을 수 없음", content = @Content), - @ApiResponse(responseCode = "500", description = "서버 오류", content = @Content) - }) - @DeleteMapping("/hyper-params/{hyperVer}") - public ApiResponseDto deleteHyperParam( - @Parameter(description = "하이퍼파라미터 버전", example = "V3.99.251221.120518") @PathVariable - String hyperVer) { - modelTrainService.deleteHyperParam(hyperVer); - return ApiResponseDto.ok(null); - } - - @Operation(summary = "학습 시작", description = "모든 설정(Step 1~3)을 마치고 최종적으로 학습 프로세스를 시작합니다") - @ApiResponses( - value = { - @ApiResponse( - responseCode = "200", - description = "학습 시작 성공", - content = - @Content( - mediaType = "application/json", - schema = @Schema(implementation = ModelMngDto.TrainStartRes.class))), - @ApiResponse(responseCode = "400", description = "잘못된 요청", content = @Content), - @ApiResponse(responseCode = "500", description = "서버 오류", content = @Content) - }) - @PostMapping("/train") - public ApiResponseDto startTraining( - @Valid @RequestBody ModelMngDto.TrainStartReq trainReq) { - return ApiResponseDto.ok(modelTrainService.startTraining(trainReq)); - } - - @Operation(summary = "학습 모델 삭제", description = "목록에서 특정 학습 모델을 삭제합니다") - @ApiResponses( - value = { - @ApiResponse(responseCode = "200", description = "삭제 성공", content = @Content), - @ApiResponse(responseCode = "400", description = "진행 중인 모델은 삭제 불가", content = @Content), - @ApiResponse(responseCode = "404", description = "모델을 찾을 수 없음", content = @Content), - @ApiResponse(responseCode = "500", description = "서버 오류", content = @Content) - }) - @DeleteMapping("/train/{uuid}") - public ApiResponseDto deleteTrainModel( - @Parameter(description = "모델 UUID") @PathVariable String uuid) { - modelTrainService.deleteTrainModel(uuid); - return ApiResponseDto.ok(null); - } - - // ==================== Resume Training (학습 재시작) ==================== - - @Operation(summary = "학습 재시작 정보 조회", description = "중단된 학습의 재시작 가능 여부와 Checkpoint 정보를 조회합니다") - @ApiResponses( - value = { - @ApiResponse( - responseCode = "200", - description = "조회 성공", - content = - @Content( - mediaType = "application/json", - schema = @Schema(implementation = ModelMngDto.ResumeInfo.class))), - @ApiResponse(responseCode = "404", description = "모델을 찾을 수 없음", content = @Content), - @ApiResponse(responseCode = "500", description = "서버 오류", content = @Content) - }) - @GetMapping("/train/{uuid}/resume-info") - public ApiResponseDto getResumeInfo( - @Parameter(description = "모델 UUID") @PathVariable String uuid) { - return ApiResponseDto.ok(modelTrainService.getResumeInfo(uuid)); - } - - @Operation(summary = "학습 재시작", description = "중단된 지점(Checkpoint)부터 학습을 재개합니다") - @ApiResponses( - value = { - @ApiResponse( - responseCode = "200", - description = "재시작 성공", - content = - @Content( - mediaType = "application/json", - schema = @Schema(implementation = ModelMngDto.ResumeResponse.class))), - @ApiResponse(responseCode = "400", description = "재시작 불가능한 상태", content = @Content), - @ApiResponse(responseCode = "404", description = "모델을 찾을 수 없음", content = @Content), - @ApiResponse(responseCode = "500", description = "서버 오류", content = @Content) - }) - @PostMapping("/train/{uuid}/resume") - public ApiResponseDto resumeTraining( - @Parameter(description = "모델 UUID") @PathVariable String uuid, - @Valid @RequestBody ModelMngDto.ResumeRequest resumeReq) { - return ApiResponseDto.ok(modelTrainService.resumeTraining(uuid, resumeReq)); - } - - // ==================== Best Epoch Setting (Best Epoch 설정) ==================== - - @Operation(summary = "Best Epoch 설정", description = "사용자가 직접 Best Epoch를 선택하여 설정합니다") - @ApiResponses( - value = { - @ApiResponse( - responseCode = "200", - description = "설정 성공", - content = - @Content( - mediaType = "application/json", - schema = @Schema(implementation = ModelMngDto.BestEpochResponse.class))), - @ApiResponse(responseCode = "404", description = "모델을 찾을 수 없음", content = @Content), - @ApiResponse(responseCode = "500", description = "서버 오류", content = @Content) - }) - @PostMapping("/train/{uuid}/best-epoch") - public ApiResponseDto setBestEpoch( - @Parameter(description = "모델 UUID") @PathVariable String uuid, - @Valid @RequestBody ModelMngDto.BestEpochRequest bestEpochReq) { - return ApiResponseDto.ok(modelTrainService.setBestEpoch(uuid, bestEpochReq)); - } - - @Operation(summary = "Epoch별 성능 지표 조회", description = "학습된 모델의 Epoch별 성능 지표를 조회합니다") - @ApiResponses( - value = { - @ApiResponse( - responseCode = "200", - description = "조회 성공", - content = - @Content( - mediaType = "application/json", - schema = @Schema(implementation = List.class))), - @ApiResponse(responseCode = "404", description = "모델을 찾을 수 없음", content = @Content), - @ApiResponse(responseCode = "500", description = "서버 오류", content = @Content) - }) - @GetMapping("/train/{uuid}/epoch-metrics") - public ApiResponseDto> getEpochMetrics( - @Parameter(description = "모델 UUID") @PathVariable String uuid) { - return ApiResponseDto.ok(modelTrainService.getEpochMetrics(uuid)); - } -} +package com.kamco.cd.training.model; + +import com.kamco.cd.training.config.api.ApiResponseDto; +import com.kamco.cd.training.model.dto.ModelMngDto; +import com.kamco.cd.training.model.dto.ModelMngDto.Basic; +import com.kamco.cd.training.model.service.ModelMngService; +import com.kamco.cd.training.model.service.ModelTrainService; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.media.Content; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import io.swagger.v3.oas.annotations.responses.ApiResponses; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.validation.Valid; +import java.util.List; +import lombok.RequiredArgsConstructor; +import org.springframework.data.domain.Page; +import org.springframework.web.bind.annotation.DeleteMapping; +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.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; + +@RestController +@RequiredArgsConstructor +@Tag(name = "모델관리", description = "모델관리 (학습 모델, 하이퍼파라미터, 메모)") +@RequestMapping("/api/models") +public class ModelMngApiController { + private final ModelMngService modelMngService; + private final ModelTrainService modelTrainService; + + @Operation(summary = "학습 모델 목록 조회", description = "학습 모델 목록을 조회합니다") + @ApiResponses( + value = { + @ApiResponse( + responseCode = "200", + description = "검색 성공", + content = + @Content( + mediaType = "application/json", + schema = @Schema(implementation = Page.class))), + @ApiResponse(responseCode = "400", description = "잘못된 검색 조건", content = @Content), + @ApiResponse(responseCode = "500", description = "서버 오류", content = @Content) + }) + @GetMapping + public ApiResponseDto> findByModels( + @Parameter(description = "상태 코드") @RequestParam(required = false) String status, + @Parameter(description = "페이지 번호") @RequestParam(defaultValue = "0") int page, + @Parameter(description = "페이지 크기") @RequestParam(defaultValue = "20") int size) { + ModelMngDto.SearchReq searchReq = new ModelMngDto.SearchReq(status, page, size); + return ApiResponseDto.ok(modelMngService.findByModels(searchReq)); + } + + @Operation(summary = "학습 모델 상세 조회", description = "학습 모델의 상세 정보를 UUID로 조회합니다") + @ApiResponses( + value = { + @ApiResponse( + responseCode = "200", + description = "조회 성공", + content = + @Content( + mediaType = "application/json", + schema = @Schema(implementation = ModelMngDto.Detail.class))), + @ApiResponse(responseCode = "404", description = "모델을 찾을 수 없음", content = @Content), + @ApiResponse(responseCode = "500", description = "서버 오류", content = @Content) + }) + @GetMapping("/{uuid}") + public ApiResponseDto getModelDetail( + @Parameter(description = "모델 UUID", example = "b7e99739-6736-45f9-a224-8161ecddf287") + @PathVariable + String uuid) { + return ApiResponseDto.ok(modelMngService.getModelDetailByUuid(uuid)); + } + + // ==================== 학습 모델학습관리 API (5종) ==================== + + @Operation(summary = "학습 모델 통합 조회", description = "학습 관리 화면에서 학습 이력 리스트와 현재 상태를 조회합니다") + @ApiResponses( + value = { + @ApiResponse( + responseCode = "200", + description = "조회 성공", + content = + @Content( + mediaType = "application/json", + schema = @Schema(implementation = List.class))), + @ApiResponse(responseCode = "500", description = "서버 오류", content = @Content) + }) + @GetMapping("/train") + public ApiResponseDto> getTrainModelList() { + return ApiResponseDto.ok(modelTrainService.getTrainModelList()); + } + + @Operation(summary = "학습 설정 통합 조회", description = "학습 실행 팝업 구성에 필요한 모든 데이터를 한 번에 반환합니다") + @ApiResponses( + value = { + @ApiResponse( + responseCode = "200", + description = "조회 성공", + content = + @Content( + mediaType = "application/json", + schema = @Schema(implementation = ModelMngDto.FormConfigRes.class))), + @ApiResponse(responseCode = "500", description = "서버 오류", content = @Content) + }) + @GetMapping("/train/form-config") + public ApiResponseDto getFormConfig() { + return ApiResponseDto.ok(modelTrainService.getFormConfig()); + } + + @Operation(summary = "하이퍼파라미터 등록", description = "Step 1 에서 파라미터를 수정하여 신규 버전으로 저장합니다") + @ApiResponses( + value = { + @ApiResponse( + responseCode = "200", + description = "등록 성공", + content = + @Content( + mediaType = "application/json", + schema = @Schema(implementation = String.class))), + @ApiResponse(responseCode = "400", description = "잘못된 요청", content = @Content), + @ApiResponse(responseCode = "500", description = "서버 오류", content = @Content) + }) + @PostMapping("/hyper-params") + public ApiResponseDto createHyperParam( + @Valid @RequestBody ModelMngDto.HyperParamCreateReq createReq) { + String newVersion = modelTrainService.createHyperParam(createReq); + return ApiResponseDto.ok(newVersion); + } + + @Operation(summary = "하이퍼파라미터 단건 조회", description = "특정 버전의 하이퍼파라미터 상세 정보를 조회합니다") + @ApiResponses( + value = { + @ApiResponse( + responseCode = "200", + description = "조회 성공", + content = + @Content( + mediaType = "application/json", + schema = @Schema(implementation = ModelMngDto.HyperParamInfo.class))), + @ApiResponse(responseCode = "404", description = "하이퍼파라미터를 찾을 수 없음", content = @Content), + @ApiResponse(responseCode = "500", description = "서버 오류", content = @Content) + }) + @GetMapping("/hyper-params/{hyperVer}") + public ApiResponseDto getHyperParam( + @Parameter(description = "하이퍼파라미터 버전", example = "H1") @PathVariable String hyperVer) { + return ApiResponseDto.ok(modelTrainService.getHyperParam(hyperVer)); + } + + @Operation(summary = "하이퍼파라미터 삭제", description = "특정 버전의 하이퍼파라미터를 삭제합니다 (H1은 삭제 불가)") + @ApiResponses( + value = { + @ApiResponse(responseCode = "200", description = "삭제 성공", content = @Content), + @ApiResponse(responseCode = "400", description = "H1은 삭제 불가", content = @Content), + @ApiResponse(responseCode = "404", description = "하이퍼파라미터를 찾을 수 없음", content = @Content), + @ApiResponse(responseCode = "500", description = "서버 오류", content = @Content) + }) + @DeleteMapping("/hyper-params/{hyperVer}") + public ApiResponseDto deleteHyperParam( + @Parameter(description = "하이퍼파라미터 버전", example = "V3.99.251221.120518") @PathVariable + String hyperVer) { + modelTrainService.deleteHyperParam(hyperVer); + return ApiResponseDto.ok(null); + } + + @Operation(summary = "학습 시작", description = "모든 설정(Step 1~3)을 마치고 최종적으로 학습 프로세스를 시작합니다") + @ApiResponses( + value = { + @ApiResponse( + responseCode = "200", + description = "학습 시작 성공", + content = + @Content( + mediaType = "application/json", + schema = @Schema(implementation = ModelMngDto.TrainStartRes.class))), + @ApiResponse(responseCode = "400", description = "잘못된 요청", content = @Content), + @ApiResponse(responseCode = "500", description = "서버 오류", content = @Content) + }) + @PostMapping("/train") + public ApiResponseDto startTraining( + @Valid @RequestBody ModelMngDto.TrainStartReq trainReq) { + return ApiResponseDto.ok(modelTrainService.startTraining(trainReq)); + } + + @Operation(summary = "학습 모델 삭제", description = "목록에서 특정 학습 모델을 삭제합니다") + @ApiResponses( + value = { + @ApiResponse(responseCode = "200", description = "삭제 성공", content = @Content), + @ApiResponse(responseCode = "400", description = "진행 중인 모델은 삭제 불가", content = @Content), + @ApiResponse(responseCode = "404", description = "모델을 찾을 수 없음", content = @Content), + @ApiResponse(responseCode = "500", description = "서버 오류", content = @Content) + }) + @DeleteMapping("/train/{uuid}") + public ApiResponseDto deleteTrainModel( + @Parameter(description = "모델 UUID") @PathVariable String uuid) { + modelTrainService.deleteTrainModel(uuid); + return ApiResponseDto.ok(null); + } + + // ==================== Resume Training (학습 재시작) ==================== + + @Operation(summary = "학습 재시작 정보 조회", description = "중단된 학습의 재시작 가능 여부와 Checkpoint 정보를 조회합니다") + @ApiResponses( + value = { + @ApiResponse( + responseCode = "200", + description = "조회 성공", + content = + @Content( + mediaType = "application/json", + schema = @Schema(implementation = ModelMngDto.ResumeInfo.class))), + @ApiResponse(responseCode = "404", description = "모델을 찾을 수 없음", content = @Content), + @ApiResponse(responseCode = "500", description = "서버 오류", content = @Content) + }) + @GetMapping("/train/{uuid}/resume-info") + public ApiResponseDto getResumeInfo( + @Parameter(description = "모델 UUID") @PathVariable String uuid) { + return ApiResponseDto.ok(modelTrainService.getResumeInfo(uuid)); + } + + @Operation(summary = "학습 재시작", description = "중단된 지점(Checkpoint)부터 학습을 재개합니다") + @ApiResponses( + value = { + @ApiResponse( + responseCode = "200", + description = "재시작 성공", + content = + @Content( + mediaType = "application/json", + schema = @Schema(implementation = ModelMngDto.ResumeResponse.class))), + @ApiResponse(responseCode = "400", description = "재시작 불가능한 상태", content = @Content), + @ApiResponse(responseCode = "404", description = "모델을 찾을 수 없음", content = @Content), + @ApiResponse(responseCode = "500", description = "서버 오류", content = @Content) + }) + @PostMapping("/train/{uuid}/resume") + public ApiResponseDto resumeTraining( + @Parameter(description = "모델 UUID") @PathVariable String uuid, + @Valid @RequestBody ModelMngDto.ResumeRequest resumeReq) { + return ApiResponseDto.ok(modelTrainService.resumeTraining(uuid, resumeReq)); + } + + // ==================== Best Epoch Setting (Best Epoch 설정) ==================== + + @Operation(summary = "Best Epoch 설정", description = "사용자가 직접 Best Epoch를 선택하여 설정합니다") + @ApiResponses( + value = { + @ApiResponse( + responseCode = "200", + description = "설정 성공", + content = + @Content( + mediaType = "application/json", + schema = @Schema(implementation = ModelMngDto.BestEpochResponse.class))), + @ApiResponse(responseCode = "404", description = "모델을 찾을 수 없음", content = @Content), + @ApiResponse(responseCode = "500", description = "서버 오류", content = @Content) + }) + @PostMapping("/train/{uuid}/best-epoch") + public ApiResponseDto setBestEpoch( + @Parameter(description = "모델 UUID") @PathVariable String uuid, + @Valid @RequestBody ModelMngDto.BestEpochRequest bestEpochReq) { + return ApiResponseDto.ok(modelTrainService.setBestEpoch(uuid, bestEpochReq)); + } + + @Operation(summary = "Epoch별 성능 지표 조회", description = "학습된 모델의 Epoch별 성능 지표를 조회합니다") + @ApiResponses( + value = { + @ApiResponse( + responseCode = "200", + description = "조회 성공", + content = + @Content( + mediaType = "application/json", + schema = @Schema(implementation = List.class))), + @ApiResponse(responseCode = "404", description = "모델을 찾을 수 없음", content = @Content), + @ApiResponse(responseCode = "500", description = "서버 오류", content = @Content) + }) + @GetMapping("/train/{uuid}/epoch-metrics") + public ApiResponseDto> getEpochMetrics( + @Parameter(description = "모델 UUID") @PathVariable String uuid) { + return ApiResponseDto.ok(modelTrainService.getEpochMetrics(uuid)); + } +} diff --git a/src/main/java/com/kamco/cd/training/model/dto/HyperParamDto.java b/src/main/java/com/kamco/cd/training/model/dto/HyperParamDto.java index 49b571a..76cd4ad 100644 --- a/src/main/java/com/kamco/cd/training/model/dto/HyperParamDto.java +++ b/src/main/java/com/kamco/cd/training/model/dto/HyperParamDto.java @@ -1,219 +1,219 @@ -package com.kamco.cd.training.model.dto; - -import com.kamco.cd.training.common.utils.interfaces.JsonFormatDttm; -import com.kamco.cd.training.postgres.entity.ModelHyperParamEntity; -import io.swagger.v3.oas.annotations.media.Schema; -import jakarta.validation.constraints.NotBlank; -import java.time.ZonedDateTime; -import lombok.AllArgsConstructor; -import lombok.Getter; -import lombok.NoArgsConstructor; -import lombok.Setter; - -public class HyperParamDto { - - @Schema(name = "HyperParam Basic", description = "하이퍼파라미터 기본 정보") - @Getter - @Setter - @NoArgsConstructor - @AllArgsConstructor - public static class Basic { - private String hyperVer; - - // Important - private String backbone; - private String inputSize; - private String cropSize; - private Integer epochCnt; - private Integer batchSize; - - // Architecture - private Double dropPathRate; - private Integer frozenStages; - private String neckPolicy; - private String decoderChannels; - private String classWeight; - private Integer numLayers; - - // Optimization - private Double learningRate; - private Double weightDecay; - private Double layerDecayRate; - private Boolean ddpFindUnusedParams; - private Integer ignoreIndex; - - // Data - private Integer trainNumWorkers; - private Integer valNumWorkers; - private Integer testNumWorkers; - private Boolean trainShuffle; - private Boolean trainPersistent; - private Boolean valPersistent; - - // Evaluation - private String metrics; - private String saveBest; - private String saveBestRule; - private Integer valInterval; - private Integer logInterval; - private Integer visInterval; - - // Hardware - private Integer gpuCnt; - private String gpuIds; - private Integer masterPort; - - // Augmentation - private Double rotProb; - private Double flipProb; - private String rotDegree; - private Double exchangeProb; - private Integer brightnessDelta; - private String contrastRange; - private String saturationRange; - private Integer hueDelta; - - // Legacy (deprecated) - private Double dropoutRatio; - private Integer cnnFilterCnt; - - // Common - private String memo; - @JsonFormatDttm private ZonedDateTime createdDttm; - - public Basic(ModelHyperParamEntity entity) { - this.hyperVer = entity.getHyperVer(); - - // Important - this.backbone = entity.getBackbone(); - this.inputSize = entity.getInputSize(); - this.cropSize = entity.getCropSize(); - this.epochCnt = entity.getEpochCnt(); - this.batchSize = entity.getBatchSize(); - - // Architecture - this.dropPathRate = entity.getDropPathRate(); - this.frozenStages = entity.getFrozenStages(); - this.neckPolicy = entity.getNeckPolicy(); - this.decoderChannels = entity.getDecoderChannels(); - this.classWeight = entity.getClassWeight(); - this.numLayers = entity.getNumLayers(); - - // Optimization - this.learningRate = entity.getLearningRate(); - this.weightDecay = entity.getWeightDecay(); - this.layerDecayRate = entity.getLayerDecayRate(); - this.ddpFindUnusedParams = entity.getDdpFindUnusedParams(); - this.ignoreIndex = entity.getIgnoreIndex(); - - // Data - this.trainNumWorkers = entity.getTrainNumWorkers(); - this.valNumWorkers = entity.getValNumWorkers(); - this.testNumWorkers = entity.getTestNumWorkers(); - this.trainShuffle = entity.getTrainShuffle(); - this.trainPersistent = entity.getTrainPersistent(); - this.valPersistent = entity.getValPersistent(); - - // Evaluation - this.metrics = entity.getMetrics(); - this.saveBest = entity.getSaveBest(); - this.saveBestRule = entity.getSaveBestRule(); - this.valInterval = entity.getValInterval(); - this.logInterval = entity.getLogInterval(); - this.visInterval = entity.getVisInterval(); - - // Hardware - this.gpuCnt = entity.getGpuCnt(); - this.gpuIds = entity.getGpuIds(); - this.masterPort = entity.getMasterPort(); - - // Augmentation - this.rotProb = entity.getRotProb(); - this.flipProb = entity.getFlipProb(); - this.rotDegree = entity.getRotDegree(); - this.exchangeProb = entity.getExchangeProb(); - this.brightnessDelta = entity.getBrightnessDelta(); - this.contrastRange = entity.getContrastRange(); - this.saturationRange = entity.getSaturationRange(); - this.hueDelta = entity.getHueDelta(); - - // Legacy - this.dropoutRatio = entity.getDropoutRatio(); - this.cnnFilterCnt = entity.getCnnFilterCnt(); - - // Common - this.memo = entity.getMemo(); - this.createdDttm = entity.getCreatedDttm(); - } - } - - @Schema(name = "HyperParam AddReq", description = "하이퍼파라미터 등록 요청") - @Getter - @Setter - @NoArgsConstructor - @AllArgsConstructor - public static class AddReq { - @NotBlank(message = "버전명은 필수입니다") - private String hyperVer; - - // Important - private String backbone; - private String inputSize; - private String cropSize; - private Integer epochCnt; - private Integer batchSize; - - // Architecture - private Double dropPathRate; - private Integer frozenStages; - private String neckPolicy; - private String decoderChannels; - private String classWeight; - private Integer numLayers; - - // Optimization - private Double learningRate; - private Double weightDecay; - private Double layerDecayRate; - private Boolean ddpFindUnusedParams; - private Integer ignoreIndex; - - // Data - private Integer trainNumWorkers; - private Integer valNumWorkers; - private Integer testNumWorkers; - private Boolean trainShuffle; - private Boolean trainPersistent; - private Boolean valPersistent; - - // Evaluation - private String metrics; - private String saveBest; - private String saveBestRule; - private Integer valInterval; - private Integer logInterval; - private Integer visInterval; - - // Hardware - private Integer gpuCnt; - private String gpuIds; - private Integer masterPort; - - // Augmentation - private Double rotProb; - private Double flipProb; - private String rotDegree; - private Double exchangeProb; - private Integer brightnessDelta; - private String contrastRange; - private String saturationRange; - private Integer hueDelta; - - // Legacy (deprecated) - private Double dropoutRatio; - private Integer cnnFilterCnt; - - // Common - private String memo; - } -} +package com.kamco.cd.training.model.dto; + +import com.kamco.cd.training.common.utils.interfaces.JsonFormatDttm; +import com.kamco.cd.training.postgres.entity.ModelHyperParamEntity; +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotBlank; +import java.time.ZonedDateTime; +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +public class HyperParamDto { + + @Schema(name = "HyperParam Basic", description = "하이퍼파라미터 기본 정보") + @Getter + @Setter + @NoArgsConstructor + @AllArgsConstructor + public static class Basic { + private String hyperVer; + + // Important + private String backbone; + private String inputSize; + private String cropSize; + private Integer epochCnt; + private Integer batchSize; + + // Architecture + private Double dropPathRate; + private Integer frozenStages; + private String neckPolicy; + private String decoderChannels; + private String classWeight; + private Integer numLayers; + + // Optimization + private Double learningRate; + private Double weightDecay; + private Double layerDecayRate; + private Boolean ddpFindUnusedParams; + private Integer ignoreIndex; + + // Data + private Integer trainNumWorkers; + private Integer valNumWorkers; + private Integer testNumWorkers; + private Boolean trainShuffle; + private Boolean trainPersistent; + private Boolean valPersistent; + + // Evaluation + private String metrics; + private String saveBest; + private String saveBestRule; + private Integer valInterval; + private Integer logInterval; + private Integer visInterval; + + // Hardware + private Integer gpuCnt; + private String gpuIds; + private Integer masterPort; + + // Augmentation + private Double rotProb; + private Double flipProb; + private String rotDegree; + private Double exchangeProb; + private Integer brightnessDelta; + private String contrastRange; + private String saturationRange; + private Integer hueDelta; + + // Legacy (deprecated) + private Double dropoutRatio; + private Integer cnnFilterCnt; + + // Common + private String memo; + @JsonFormatDttm private ZonedDateTime createdDttm; + + public Basic(ModelHyperParamEntity entity) { + this.hyperVer = entity.getHyperVer(); + + // Important + this.backbone = entity.getBackbone(); + this.inputSize = entity.getInputSize(); + this.cropSize = entity.getCropSize(); + this.epochCnt = entity.getEpochCnt(); + this.batchSize = entity.getBatchSize(); + + // Architecture + this.dropPathRate = entity.getDropPathRate(); + this.frozenStages = entity.getFrozenStages(); + this.neckPolicy = entity.getNeckPolicy(); + this.decoderChannels = entity.getDecoderChannels(); + this.classWeight = entity.getClassWeight(); + this.numLayers = entity.getNumLayers(); + + // Optimization + this.learningRate = entity.getLearningRate(); + this.weightDecay = entity.getWeightDecay(); + this.layerDecayRate = entity.getLayerDecayRate(); + this.ddpFindUnusedParams = entity.getDdpFindUnusedParams(); + this.ignoreIndex = entity.getIgnoreIndex(); + + // Data + this.trainNumWorkers = entity.getTrainNumWorkers(); + this.valNumWorkers = entity.getValNumWorkers(); + this.testNumWorkers = entity.getTestNumWorkers(); + this.trainShuffle = entity.getTrainShuffle(); + this.trainPersistent = entity.getTrainPersistent(); + this.valPersistent = entity.getValPersistent(); + + // Evaluation + this.metrics = entity.getMetrics(); + this.saveBest = entity.getSaveBest(); + this.saveBestRule = entity.getSaveBestRule(); + this.valInterval = entity.getValInterval(); + this.logInterval = entity.getLogInterval(); + this.visInterval = entity.getVisInterval(); + + // Hardware + this.gpuCnt = entity.getGpuCnt(); + this.gpuIds = entity.getGpuIds(); + this.masterPort = entity.getMasterPort(); + + // Augmentation + this.rotProb = entity.getRotProb(); + this.flipProb = entity.getFlipProb(); + this.rotDegree = entity.getRotDegree(); + this.exchangeProb = entity.getExchangeProb(); + this.brightnessDelta = entity.getBrightnessDelta(); + this.contrastRange = entity.getContrastRange(); + this.saturationRange = entity.getSaturationRange(); + this.hueDelta = entity.getHueDelta(); + + // Legacy + this.dropoutRatio = entity.getDropoutRatio(); + this.cnnFilterCnt = entity.getCnnFilterCnt(); + + // Common + this.memo = entity.getMemo(); + this.createdDttm = entity.getCreatedDttm(); + } + } + + @Schema(name = "HyperParam AddReq", description = "하이퍼파라미터 등록 요청") + @Getter + @Setter + @NoArgsConstructor + @AllArgsConstructor + public static class AddReq { + @NotBlank(message = "버전명은 필수입니다") + private String hyperVer; + + // Important + private String backbone; + private String inputSize; + private String cropSize; + private Integer epochCnt; + private Integer batchSize; + + // Architecture + private Double dropPathRate; + private Integer frozenStages; + private String neckPolicy; + private String decoderChannels; + private String classWeight; + private Integer numLayers; + + // Optimization + private Double learningRate; + private Double weightDecay; + private Double layerDecayRate; + private Boolean ddpFindUnusedParams; + private Integer ignoreIndex; + + // Data + private Integer trainNumWorkers; + private Integer valNumWorkers; + private Integer testNumWorkers; + private Boolean trainShuffle; + private Boolean trainPersistent; + private Boolean valPersistent; + + // Evaluation + private String metrics; + private String saveBest; + private String saveBestRule; + private Integer valInterval; + private Integer logInterval; + private Integer visInterval; + + // Hardware + private Integer gpuCnt; + private String gpuIds; + private Integer masterPort; + + // Augmentation + private Double rotProb; + private Double flipProb; + private String rotDegree; + private Double exchangeProb; + private Integer brightnessDelta; + private String contrastRange; + private String saturationRange; + private Integer hueDelta; + + // Legacy (deprecated) + private Double dropoutRatio; + private Integer cnnFilterCnt; + + // Common + private String memo; + } +} diff --git a/src/main/java/com/kamco/cd/training/model/dto/ModelMngDto.java b/src/main/java/com/kamco/cd/training/model/dto/ModelMngDto.java index 7c36235..e4f6d24 100644 --- a/src/main/java/com/kamco/cd/training/model/dto/ModelMngDto.java +++ b/src/main/java/com/kamco/cd/training/model/dto/ModelMngDto.java @@ -1,595 +1,595 @@ -package com.kamco.cd.training.model.dto; - -import com.kamco.cd.training.common.utils.interfaces.JsonFormatDttm; -import io.swagger.v3.oas.annotations.media.Schema; -import jakarta.validation.constraints.NotBlank; -import jakarta.validation.constraints.NotEmpty; -import jakarta.validation.constraints.NotNull; -import java.time.ZonedDateTime; -import java.util.List; -import java.util.Map; -import lombok.AllArgsConstructor; -import lombok.Builder; -import lombok.Getter; -import lombok.NoArgsConstructor; -import lombok.Setter; -import org.springframework.data.domain.PageRequest; -import org.springframework.data.domain.Pageable; - -public class ModelMngDto { - - @Schema(name = "모델관리 목록 조회", description = "모델관리 목록 조회") - @Getter - @Setter - @NoArgsConstructor - @AllArgsConstructor - @Builder - public static class Basic { - - private Long id; - private String modelNm; - @JsonFormatDttm private ZonedDateTime startDttm; - @JsonFormatDttm private ZonedDateTime trainingEndDttm; - @JsonFormatDttm private ZonedDateTime testEndDttm; - private String durationDttm; - private String processStage; - private String statusCd; - private String status; - } - - @Schema(name = "searchReq", description = "모델 관리 목록조회 파라미터") - @Getter - @Setter - @NoArgsConstructor - @AllArgsConstructor - public static class SearchReq { - - private String status; - // 페이징 파라미터 - private int page = 0; - private int size = 20; - - public Pageable toPageable() { - return PageRequest.of(page, size); - } - } - - @Schema(name = "Detail", description = "모델 상세 정보") - @Getter - @Builder - public static class Detail { - private String uuid; - private String modelVer; - private String hyperVer; - private String epochVer; - private String processStep; - private String statusCd; - private String statusText; - - @JsonFormatDttm private ZonedDateTime trainStartDttm; - - private Integer epochCnt; - private String datasetRatio; - private Integer bestEpoch; - private Integer confirmedBestEpoch; - - @JsonFormatDttm private ZonedDateTime step1EndDttm; - - private String step1Duration; - - @JsonFormatDttm private ZonedDateTime step2EndDttm; - - private String step2Duration; - private Integer progressRate; - - @JsonFormatDttm private ZonedDateTime createdDttm; - - @JsonFormatDttm private ZonedDateTime updatedDttm; - - private String modelPath; - private String errorMsg; - } - - @Schema(name = "TrainListRes", description = "학습 모델 목록 응답") - @Getter - @Setter - @NoArgsConstructor - @AllArgsConstructor - @Builder - public static class TrainListRes { - private String uuid; - private String modelVer; - private String status; - private String processStep; - - @JsonFormatDttm private ZonedDateTime trainStartDttm; - - private Integer progressRate; - private Integer epochCnt; - - @JsonFormatDttm private ZonedDateTime step1EndDttm; - - private String step1Duration; - - @JsonFormatDttm private ZonedDateTime step2EndDttm; - - private String step2Duration; - - @JsonFormatDttm private ZonedDateTime createdDttm; - - private String errorMsg; - - private Boolean canResume; - private Integer lastCheckpointEpoch; - } - - @Schema(name = "FormConfigRes", description = "학습 설정 통합 조회 응답") - @Getter - @Setter - @NoArgsConstructor - @AllArgsConstructor - @Builder - public static class FormConfigRes { - private Boolean isTrainAvailable; - private List hyperParams; - private List datasets; - private String runningModelUuid; - } - - @Schema(name = "HyperParamInfo", description = "하이퍼파라미터 정보") - @Getter - @Setter - @NoArgsConstructor - @AllArgsConstructor - @Builder - public static class HyperParamInfo { - @Schema(description = "하이퍼파라미터 버전", example = "V3.99.251221.120518") - private String hyperVer; - - // Important - @Schema(description = "백본", example = "large") - private String backbone; - - @Schema(description = "입력 사이즈", example = "256,256") - private String inputSize; - - @Schema(description = "크롭 사이즈", example = "256,256") - private String cropSize; - - @Schema(description = "에폭 수", example = "200") - private Integer epochCnt; - - @Schema(description = "배치 사이즈", example = "16") - private Integer batchSize; - - // Architecture - @Schema(description = "Drop Path Rate", example = "0.3") - private Double dropPathRate; - - @Schema(description = "Frozen Stages", example = "-1") - private Integer frozenStages; - - @Schema(description = "Neck Policy", example = "abs_diff") - private String neckPolicy; - - @Schema(description = "Decoder Channels", example = "512,256,128,64") - private String decoderChannels; - - @Schema(description = "Class Weight", example = "1,1") - private String classWeight; - - @Schema(description = "레이어 수", example = "24") - private Integer numLayers; - - // Optimization - @Schema(description = "Learning Rate", example = "0.00006") - private Double learningRate; - - @Schema(description = "Weight Decay", example = "0.05") - private Double weightDecay; - - @Schema(description = "Layer Decay Rate", example = "0.9") - private Double layerDecayRate; - - @Schema(description = "DDP Unused Params 찾기", example = "true") - private Boolean ddpFindUnusedParams; - - @Schema(description = "Ignore Index", example = "255") - private Integer ignoreIndex; - - // Data - @Schema(description = "Train Workers", example = "16") - private Integer trainNumWorkers; - - @Schema(description = "Val Workers", example = "8") - private Integer valNumWorkers; - - @Schema(description = "Test Workers", example = "8") - private Integer testNumWorkers; - - @Schema(description = "Train Shuffle", example = "true") - private Boolean trainShuffle; - - @Schema(description = "Train Persistent", example = "true") - private Boolean trainPersistent; - - @Schema(description = "Val Persistent", example = "true") - private Boolean valPersistent; - - // Evaluation - @Schema(description = "Metrics", example = "mFscore,mIoU") - private String metrics; - - @Schema(description = "Save Best", example = "changed_fscore") - private String saveBest; - - @Schema(description = "Save Best Rule", example = "greater") - private String saveBestRule; - - @Schema(description = "Val Interval", example = "10") - private Integer valInterval; - - @Schema(description = "Log Interval", example = "400") - private Integer logInterval; - - @Schema(description = "Vis Interval", example = "1") - private Integer visInterval; - - // Hardware - @Schema(description = "GPU 수", example = "4") - private Integer gpuCnt; - - @Schema(description = "GPU IDs", example = "0,1,2,3") - private String gpuIds; - - @Schema(description = "Master Port", example = "1122") - private Integer masterPort; - - // Augmentation - @Schema(description = "Rotation 확률", example = "0.5") - private Double rotProb; - - @Schema(description = "Flip 확률", example = "0.5") - private Double flipProb; - - @Schema(description = "Rotation 각도", example = "-20,20") - private String rotDegree; - - @Schema(description = "Exchange 확률", example = "0.5") - private Double exchangeProb; - - @Schema(description = "Brightness Delta", example = "10") - private Integer brightnessDelta; - - @Schema(description = "Contrast Range", example = "0.8,1.2") - private String contrastRange; - - @Schema(description = "Saturation Range", example = "0.8,1.2") - private String saturationRange; - - @Schema(description = "Hue Delta", example = "10") - private Integer hueDelta; - - // Legacy - private Double dropoutRatio; - private Integer cnnFilterCnt; - - // Common - @Schema(description = "메모", example = "안녕하세요 캠코담당자 입니다. 하이퍼파라미터 신규등록합니다") - private String memo; - - @JsonFormatDttm private ZonedDateTime createdDttm; - } - - @Schema(name = "DatasetInfo", description = "데이터셋 정보") - @Getter - @Setter - @NoArgsConstructor - @AllArgsConstructor - @Builder - public static class DatasetInfo { - private Long id; - private String title; - private String groupTitle; - private Long totalItems; - private String totalSize; - private Map classCounts; - private String memo; - - @JsonFormatDttm private ZonedDateTime createdDttm; - } - - @Schema(name = "HyperParamCreateReq", description = "하이퍼파라미터 등록 요청") - @Getter - @Setter - @NoArgsConstructor - @AllArgsConstructor - public static class HyperParamCreateReq { - // baseHyperVer는 필수 아님 (신규 생성 시 H1으로 자동 설정) - @Schema(description = "기준이 되는 하이퍼파라미터 버전", example = "H3") - private String baseHyperVer; - - @NotBlank(message = "신규 버전명은 필수입니다") - @Schema(description = "새로 생성할 하이퍼파라미터 버전명", example = "V3.99.251221.120518") - private String newHyperVer; - - // Important - 필수 필드 - @NotBlank(message = "Backbone은 필수입니다") - @Schema(example = "large") - private String backbone; - - @NotBlank(message = "Input Size는 필수입니다") - @Schema(example = "256,256") - private String inputSize; - - @NotBlank(message = "Crop Size는 필수입니다") - @Schema(example = "256,256") - private String cropSize; - - @NotNull(message = "Epoch Count는 필수입니다") - @Schema(example = "200") - private Integer epochCnt; - - @NotNull(message = "Batch Size는 필수입니다") - @Schema(example = "16") - private Integer batchSize; - - // Architecture - 필수 필드 - @NotNull(message = "Drop Path Rate는 필수입니다") - @Schema(example = "0.3") - private Double dropPathRate; - - @NotNull(message = "Frozen Stages는 필수입니다") - @Schema(example = "-1") - private Integer frozenStages; - - @NotBlank(message = "Neck Policy는 필수입니다") - @Schema(example = "abs_diff") - private String neckPolicy; - - @NotBlank(message = "Decoder Channels는 필수입니다") - @Schema(example = "512,256,128,64") - private String decoderChannels; - - @NotBlank(message = "Class Weight는 필수입니다") - @Schema(example = "1,1") - private String classWeight; - - // numLayers는 필수 아님 - @Schema(example = "24") - private Integer numLayers; - - // Optimization - 필수 필드 - @NotNull(message = "Learning Rate는 필수입니다") - @Schema(example = "0.00006") - private Double learningRate; - - @NotNull(message = "Weight Decay는 필수입니다") - @Schema(example = "0.05") - private Double weightDecay; - - @NotNull(message = "Layer Decay Rate는 필수입니다") - @Schema(example = "0.9") - private Double layerDecayRate; - - @NotNull(message = "DDP Find Unused Params는 필수입니다") - @Schema(example = "true") - private Boolean ddpFindUnusedParams; - - @NotNull(message = "Ignore Index는 필수입니다") - @Schema(example = "255") - private Integer ignoreIndex; - - // Data - 필수 필드 - @NotNull(message = "Train Num Workers는 필수입니다") - @Schema(example = "16") - private Integer trainNumWorkers; - - @NotNull(message = "Val Num Workers는 필수입니다") - @Schema(example = "8") - private Integer valNumWorkers; - - @NotNull(message = "Test Num Workers는 필수입니다") - @Schema(example = "8") - private Integer testNumWorkers; - - @NotNull(message = "Train Shuffle는 필수입니다") - @Schema(example = "true") - private Boolean trainShuffle; - - @NotNull(message = "Train Persistent는 필수입니다") - @Schema(example = "true") - private Boolean trainPersistent; - - @NotNull(message = "Val Persistent는 필수입니다") - @Schema(example = "true") - private Boolean valPersistent; - - // Evaluation - 필수 필드 - @NotBlank(message = "Metrics는 필수입니다") - @Schema(example = "mFscore,mIoU") - private String metrics; - - @NotBlank(message = "Save Best는 필수입니다") - @Schema(example = "changed_fscore") - private String saveBest; - - @NotBlank(message = "Save Best Rule은 필수입니다") - @Schema(example = "greater") - private String saveBestRule; - - @NotNull(message = "Val Interval은 필수입니다") - @Schema(example = "10") - private Integer valInterval; - - @NotNull(message = "Log Interval은 필수입니다") - @Schema(example = "400") - private Integer logInterval; - - @NotNull(message = "Vis Interval은 필수입니다") - @Schema(example = "1") - private Integer visInterval; - - // Hardware - 필수 아님 (예외 항목) - @Schema(example = "4") - private Integer gpuCnt; - - @Schema(example = "0,1,2,3") - private String gpuIds; - - @Schema(example = "1122") - private Integer masterPort; - - // Augmentation - 필수 필드 - @NotNull(message = "Rotation Probability는 필수입니다") - @Schema(example = "0.5") - private Double rotProb; - - @NotNull(message = "Flip Probability는 필수입니다") - @Schema(example = "0.5") - private Double flipProb; - - @NotBlank(message = "Rotation Degree는 필수입니다") - @Schema(example = "-20,20") - private String rotDegree; - - @NotNull(message = "Exchange Probability는 필수입니다") - @Schema(example = "0.5") - private Double exchangeProb; - - @NotNull(message = "Brightness Delta는 필수입니다") - @Schema(example = "10") - private Integer brightnessDelta; - - @NotBlank(message = "Contrast Range는 필수입니다") - @Schema(example = "0.8,1.2") - private String contrastRange; - - @NotBlank(message = "Saturation Range는 필수입니다") - @Schema(example = "0.8,1.2") - private String saturationRange; - - @NotNull(message = "Hue Delta는 필수입니다") - @Schema(example = "10") - private Integer hueDelta; - - // Legacy - 필수 아님 (예외 항목) - private Double dropoutRatio; - private Integer cnnFilterCnt; - - // Common - 필수 아님 (예외 항목) - @Schema(example = "안녕하세요 캠코담당자 입니다. 하이퍼파라미터 신규등록합니다") - private String memo; - } - - @Schema(name = "TrainStartReq", description = "학습 시작 요청") - @Getter - @Setter - @NoArgsConstructor - @AllArgsConstructor - public static class TrainStartReq { - @NotBlank(message = "하이퍼파라미터 버전은 필수입니다") - @Schema(example = "V3.99.251221.120518") - private String hyperVer; - - @NotEmpty(message = "데이터셋은 최소 1개 이상 선택해야 합니다") - private List datasetIds; - - @NotNull(message = "에폭 수는 필수입니다") - @jakarta.validation.constraints.Min(value = 1, message = "에폭 수는 최소 1 이상이어야 합니다") - @jakarta.validation.constraints.Max(value = 200, message = "에폭 수는 최대 200까지 설정 가능합니다") - @Schema(example = "200") - private Integer epoch; - - @Schema(example = "7:2:1", description = "데이터 분할 비율 (Training:Validation:Test)") - private String datasetRatio; - } - - @Schema(name = "TrainStartRes", description = "학습 시작 응답") - @Getter - @Setter - @NoArgsConstructor - @AllArgsConstructor - @Builder - public static class TrainStartRes { - private String uuid; - private String status; - } - - @Schema(name = "ResumeInfo", description = "학습 재시작 정보") - @Getter - @Setter - @NoArgsConstructor - @AllArgsConstructor - @Builder - public static class ResumeInfo { - private Boolean canResume; - private Integer lastEpoch; - private Integer totalEpoch; - private String checkpointPath; - @JsonFormatDttm private ZonedDateTime failedAt; - } - - @Schema(name = "ResumeRequest", description = "학습 재시작 요청") - @Getter - @Setter - @NoArgsConstructor - @AllArgsConstructor - public static class ResumeRequest { - @NotNull(message = "재시작 Epoch는 필수입니다") - private Integer resumeFromEpoch; - - private Integer newTotalEpoch; - } - - @Schema(name = "ResumeResponse", description = "학습 재시작 응답") - @Getter - @Setter - @NoArgsConstructor - @AllArgsConstructor - @Builder - public static class ResumeResponse { - private String uuid; - private String status; - private Integer resumedFromEpoch; - } - - @Schema(name = "BestEpochRequest", description = "Best Epoch 설정 요청") - @Getter - @Setter - @NoArgsConstructor - @AllArgsConstructor - public static class BestEpochRequest { - @NotNull(message = "Best Epoch는 필수입니다") - private Integer bestEpoch; - - private String reason; - } - - @Schema(name = "BestEpochResponse", description = "Best Epoch 설정 응답") - @Getter - @Setter - @NoArgsConstructor - @AllArgsConstructor - @Builder - public static class BestEpochResponse { - private String uuid; - private Integer bestEpoch; - private Integer confirmedBestEpoch; - private Integer previousBestEpoch; - } - - @Schema(name = "EpochMetric", description = "Epoch별 성능 지표") - @Getter - @Setter - @NoArgsConstructor - @AllArgsConstructor - @Builder - public static class EpochMetric { - private Integer epoch; - private Double mIoU; - private Double mFscore; - private Double loss; - private Boolean isBest; - } -} +package com.kamco.cd.training.model.dto; + +import com.kamco.cd.training.common.utils.interfaces.JsonFormatDttm; +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotEmpty; +import jakarta.validation.constraints.NotNull; +import java.time.ZonedDateTime; +import java.util.List; +import java.util.Map; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Pageable; + +public class ModelMngDto { + + @Schema(name = "모델관리 목록 조회", description = "모델관리 목록 조회") + @Getter + @Setter + @NoArgsConstructor + @AllArgsConstructor + @Builder + public static class Basic { + + private Long id; + private String modelNm; + @JsonFormatDttm private ZonedDateTime startDttm; + @JsonFormatDttm private ZonedDateTime trainingEndDttm; + @JsonFormatDttm private ZonedDateTime testEndDttm; + private String durationDttm; + private String processStage; + private String statusCd; + private String status; + } + + @Schema(name = "searchReq", description = "모델 관리 목록조회 파라미터") + @Getter + @Setter + @NoArgsConstructor + @AllArgsConstructor + public static class SearchReq { + + private String status; + // 페이징 파라미터 + private int page = 0; + private int size = 20; + + public Pageable toPageable() { + return PageRequest.of(page, size); + } + } + + @Schema(name = "Detail", description = "모델 상세 정보") + @Getter + @Builder + public static class Detail { + private String uuid; + private String modelVer; + private String hyperVer; + private String epochVer; + private String processStep; + private String statusCd; + private String statusText; + + @JsonFormatDttm private ZonedDateTime trainStartDttm; + + private Integer epochCnt; + private String datasetRatio; + private Integer bestEpoch; + private Integer confirmedBestEpoch; + + @JsonFormatDttm private ZonedDateTime step1EndDttm; + + private String step1Duration; + + @JsonFormatDttm private ZonedDateTime step2EndDttm; + + private String step2Duration; + private Integer progressRate; + + @JsonFormatDttm private ZonedDateTime createdDttm; + + @JsonFormatDttm private ZonedDateTime updatedDttm; + + private String modelPath; + private String errorMsg; + } + + @Schema(name = "TrainListRes", description = "학습 모델 목록 응답") + @Getter + @Setter + @NoArgsConstructor + @AllArgsConstructor + @Builder + public static class TrainListRes { + private String uuid; + private String modelVer; + private String status; + private String processStep; + + @JsonFormatDttm private ZonedDateTime trainStartDttm; + + private Integer progressRate; + private Integer epochCnt; + + @JsonFormatDttm private ZonedDateTime step1EndDttm; + + private String step1Duration; + + @JsonFormatDttm private ZonedDateTime step2EndDttm; + + private String step2Duration; + + @JsonFormatDttm private ZonedDateTime createdDttm; + + private String errorMsg; + + private Boolean canResume; + private Integer lastCheckpointEpoch; + } + + @Schema(name = "FormConfigRes", description = "학습 설정 통합 조회 응답") + @Getter + @Setter + @NoArgsConstructor + @AllArgsConstructor + @Builder + public static class FormConfigRes { + private Boolean isTrainAvailable; + private List hyperParams; + private List datasets; + private String runningModelUuid; + } + + @Schema(name = "HyperParamInfo", description = "하이퍼파라미터 정보") + @Getter + @Setter + @NoArgsConstructor + @AllArgsConstructor + @Builder + public static class HyperParamInfo { + @Schema(description = "하이퍼파라미터 버전", example = "V3.99.251221.120518") + private String hyperVer; + + // Important + @Schema(description = "백본", example = "large") + private String backbone; + + @Schema(description = "입력 사이즈", example = "256,256") + private String inputSize; + + @Schema(description = "크롭 사이즈", example = "256,256") + private String cropSize; + + @Schema(description = "에폭 수", example = "200") + private Integer epochCnt; + + @Schema(description = "배치 사이즈", example = "16") + private Integer batchSize; + + // Architecture + @Schema(description = "Drop Path Rate", example = "0.3") + private Double dropPathRate; + + @Schema(description = "Frozen Stages", example = "-1") + private Integer frozenStages; + + @Schema(description = "Neck Policy", example = "abs_diff") + private String neckPolicy; + + @Schema(description = "Decoder Channels", example = "512,256,128,64") + private String decoderChannels; + + @Schema(description = "Class Weight", example = "1,1") + private String classWeight; + + @Schema(description = "레이어 수", example = "24") + private Integer numLayers; + + // Optimization + @Schema(description = "Learning Rate", example = "0.00006") + private Double learningRate; + + @Schema(description = "Weight Decay", example = "0.05") + private Double weightDecay; + + @Schema(description = "Layer Decay Rate", example = "0.9") + private Double layerDecayRate; + + @Schema(description = "DDP Unused Params 찾기", example = "true") + private Boolean ddpFindUnusedParams; + + @Schema(description = "Ignore Index", example = "255") + private Integer ignoreIndex; + + // Data + @Schema(description = "Train Workers", example = "16") + private Integer trainNumWorkers; + + @Schema(description = "Val Workers", example = "8") + private Integer valNumWorkers; + + @Schema(description = "Test Workers", example = "8") + private Integer testNumWorkers; + + @Schema(description = "Train Shuffle", example = "true") + private Boolean trainShuffle; + + @Schema(description = "Train Persistent", example = "true") + private Boolean trainPersistent; + + @Schema(description = "Val Persistent", example = "true") + private Boolean valPersistent; + + // Evaluation + @Schema(description = "Metrics", example = "mFscore,mIoU") + private String metrics; + + @Schema(description = "Save Best", example = "changed_fscore") + private String saveBest; + + @Schema(description = "Save Best Rule", example = "greater") + private String saveBestRule; + + @Schema(description = "Val Interval", example = "10") + private Integer valInterval; + + @Schema(description = "Log Interval", example = "400") + private Integer logInterval; + + @Schema(description = "Vis Interval", example = "1") + private Integer visInterval; + + // Hardware + @Schema(description = "GPU 수", example = "4") + private Integer gpuCnt; + + @Schema(description = "GPU IDs", example = "0,1,2,3") + private String gpuIds; + + @Schema(description = "Master Port", example = "1122") + private Integer masterPort; + + // Augmentation + @Schema(description = "Rotation 확률", example = "0.5") + private Double rotProb; + + @Schema(description = "Flip 확률", example = "0.5") + private Double flipProb; + + @Schema(description = "Rotation 각도", example = "-20,20") + private String rotDegree; + + @Schema(description = "Exchange 확률", example = "0.5") + private Double exchangeProb; + + @Schema(description = "Brightness Delta", example = "10") + private Integer brightnessDelta; + + @Schema(description = "Contrast Range", example = "0.8,1.2") + private String contrastRange; + + @Schema(description = "Saturation Range", example = "0.8,1.2") + private String saturationRange; + + @Schema(description = "Hue Delta", example = "10") + private Integer hueDelta; + + // Legacy + private Double dropoutRatio; + private Integer cnnFilterCnt; + + // Common + @Schema(description = "메모", example = "안녕하세요 캠코담당자 입니다. 하이퍼파라미터 신규등록합니다") + private String memo; + + @JsonFormatDttm private ZonedDateTime createdDttm; + } + + @Schema(name = "DatasetInfo", description = "데이터셋 정보") + @Getter + @Setter + @NoArgsConstructor + @AllArgsConstructor + @Builder + public static class DatasetInfo { + private Long id; + private String title; + private String groupTitle; + private Long totalItems; + private String totalSize; + private Map classCounts; + private String memo; + + @JsonFormatDttm private ZonedDateTime createdDttm; + } + + @Schema(name = "HyperParamCreateReq", description = "하이퍼파라미터 등록 요청") + @Getter + @Setter + @NoArgsConstructor + @AllArgsConstructor + public static class HyperParamCreateReq { + // baseHyperVer는 필수 아님 (신규 생성 시 H1으로 자동 설정) + @Schema(description = "기준이 되는 하이퍼파라미터 버전", example = "H3") + private String baseHyperVer; + + @NotBlank(message = "신규 버전명은 필수입니다") + @Schema(description = "새로 생성할 하이퍼파라미터 버전명", example = "V3.99.251221.120518") + private String newHyperVer; + + // Important - 필수 필드 + @NotBlank(message = "Backbone은 필수입니다") + @Schema(example = "large") + private String backbone; + + @NotBlank(message = "Input Size는 필수입니다") + @Schema(example = "256,256") + private String inputSize; + + @NotBlank(message = "Crop Size는 필수입니다") + @Schema(example = "256,256") + private String cropSize; + + @NotNull(message = "Epoch Count는 필수입니다") + @Schema(example = "200") + private Integer epochCnt; + + @NotNull(message = "Batch Size는 필수입니다") + @Schema(example = "16") + private Integer batchSize; + + // Architecture - 필수 필드 + @NotNull(message = "Drop Path Rate는 필수입니다") + @Schema(example = "0.3") + private Double dropPathRate; + + @NotNull(message = "Frozen Stages는 필수입니다") + @Schema(example = "-1") + private Integer frozenStages; + + @NotBlank(message = "Neck Policy는 필수입니다") + @Schema(example = "abs_diff") + private String neckPolicy; + + @NotBlank(message = "Decoder Channels는 필수입니다") + @Schema(example = "512,256,128,64") + private String decoderChannels; + + @NotBlank(message = "Class Weight는 필수입니다") + @Schema(example = "1,1") + private String classWeight; + + // numLayers는 필수 아님 + @Schema(example = "24") + private Integer numLayers; + + // Optimization - 필수 필드 + @NotNull(message = "Learning Rate는 필수입니다") + @Schema(example = "0.00006") + private Double learningRate; + + @NotNull(message = "Weight Decay는 필수입니다") + @Schema(example = "0.05") + private Double weightDecay; + + @NotNull(message = "Layer Decay Rate는 필수입니다") + @Schema(example = "0.9") + private Double layerDecayRate; + + @NotNull(message = "DDP Find Unused Params는 필수입니다") + @Schema(example = "true") + private Boolean ddpFindUnusedParams; + + @NotNull(message = "Ignore Index는 필수입니다") + @Schema(example = "255") + private Integer ignoreIndex; + + // Data - 필수 필드 + @NotNull(message = "Train Num Workers는 필수입니다") + @Schema(example = "16") + private Integer trainNumWorkers; + + @NotNull(message = "Val Num Workers는 필수입니다") + @Schema(example = "8") + private Integer valNumWorkers; + + @NotNull(message = "Test Num Workers는 필수입니다") + @Schema(example = "8") + private Integer testNumWorkers; + + @NotNull(message = "Train Shuffle는 필수입니다") + @Schema(example = "true") + private Boolean trainShuffle; + + @NotNull(message = "Train Persistent는 필수입니다") + @Schema(example = "true") + private Boolean trainPersistent; + + @NotNull(message = "Val Persistent는 필수입니다") + @Schema(example = "true") + private Boolean valPersistent; + + // Evaluation - 필수 필드 + @NotBlank(message = "Metrics는 필수입니다") + @Schema(example = "mFscore,mIoU") + private String metrics; + + @NotBlank(message = "Save Best는 필수입니다") + @Schema(example = "changed_fscore") + private String saveBest; + + @NotBlank(message = "Save Best Rule은 필수입니다") + @Schema(example = "greater") + private String saveBestRule; + + @NotNull(message = "Val Interval은 필수입니다") + @Schema(example = "10") + private Integer valInterval; + + @NotNull(message = "Log Interval은 필수입니다") + @Schema(example = "400") + private Integer logInterval; + + @NotNull(message = "Vis Interval은 필수입니다") + @Schema(example = "1") + private Integer visInterval; + + // Hardware - 필수 아님 (예외 항목) + @Schema(example = "4") + private Integer gpuCnt; + + @Schema(example = "0,1,2,3") + private String gpuIds; + + @Schema(example = "1122") + private Integer masterPort; + + // Augmentation - 필수 필드 + @NotNull(message = "Rotation Probability는 필수입니다") + @Schema(example = "0.5") + private Double rotProb; + + @NotNull(message = "Flip Probability는 필수입니다") + @Schema(example = "0.5") + private Double flipProb; + + @NotBlank(message = "Rotation Degree는 필수입니다") + @Schema(example = "-20,20") + private String rotDegree; + + @NotNull(message = "Exchange Probability는 필수입니다") + @Schema(example = "0.5") + private Double exchangeProb; + + @NotNull(message = "Brightness Delta는 필수입니다") + @Schema(example = "10") + private Integer brightnessDelta; + + @NotBlank(message = "Contrast Range는 필수입니다") + @Schema(example = "0.8,1.2") + private String contrastRange; + + @NotBlank(message = "Saturation Range는 필수입니다") + @Schema(example = "0.8,1.2") + private String saturationRange; + + @NotNull(message = "Hue Delta는 필수입니다") + @Schema(example = "10") + private Integer hueDelta; + + // Legacy - 필수 아님 (예외 항목) + private Double dropoutRatio; + private Integer cnnFilterCnt; + + // Common - 필수 아님 (예외 항목) + @Schema(example = "안녕하세요 캠코담당자 입니다. 하이퍼파라미터 신규등록합니다") + private String memo; + } + + @Schema(name = "TrainStartReq", description = "학습 시작 요청") + @Getter + @Setter + @NoArgsConstructor + @AllArgsConstructor + public static class TrainStartReq { + @NotBlank(message = "하이퍼파라미터 버전은 필수입니다") + @Schema(example = "V3.99.251221.120518") + private String hyperVer; + + @NotEmpty(message = "데이터셋은 최소 1개 이상 선택해야 합니다") + private List datasetIds; + + @NotNull(message = "에폭 수는 필수입니다") + @jakarta.validation.constraints.Min(value = 1, message = "에폭 수는 최소 1 이상이어야 합니다") + @jakarta.validation.constraints.Max(value = 200, message = "에폭 수는 최대 200까지 설정 가능합니다") + @Schema(example = "200") + private Integer epoch; + + @Schema(example = "7:2:1", description = "데이터 분할 비율 (Training:Validation:Test)") + private String datasetRatio; + } + + @Schema(name = "TrainStartRes", description = "학습 시작 응답") + @Getter + @Setter + @NoArgsConstructor + @AllArgsConstructor + @Builder + public static class TrainStartRes { + private String uuid; + private String status; + } + + @Schema(name = "ResumeInfo", description = "학습 재시작 정보") + @Getter + @Setter + @NoArgsConstructor + @AllArgsConstructor + @Builder + public static class ResumeInfo { + private Boolean canResume; + private Integer lastEpoch; + private Integer totalEpoch; + private String checkpointPath; + @JsonFormatDttm private ZonedDateTime failedAt; + } + + @Schema(name = "ResumeRequest", description = "학습 재시작 요청") + @Getter + @Setter + @NoArgsConstructor + @AllArgsConstructor + public static class ResumeRequest { + @NotNull(message = "재시작 Epoch는 필수입니다") + private Integer resumeFromEpoch; + + private Integer newTotalEpoch; + } + + @Schema(name = "ResumeResponse", description = "학습 재시작 응답") + @Getter + @Setter + @NoArgsConstructor + @AllArgsConstructor + @Builder + public static class ResumeResponse { + private String uuid; + private String status; + private Integer resumedFromEpoch; + } + + @Schema(name = "BestEpochRequest", description = "Best Epoch 설정 요청") + @Getter + @Setter + @NoArgsConstructor + @AllArgsConstructor + public static class BestEpochRequest { + @NotNull(message = "Best Epoch는 필수입니다") + private Integer bestEpoch; + + private String reason; + } + + @Schema(name = "BestEpochResponse", description = "Best Epoch 설정 응답") + @Getter + @Setter + @NoArgsConstructor + @AllArgsConstructor + @Builder + public static class BestEpochResponse { + private String uuid; + private Integer bestEpoch; + private Integer confirmedBestEpoch; + private Integer previousBestEpoch; + } + + @Schema(name = "EpochMetric", description = "Epoch별 성능 지표") + @Getter + @Setter + @NoArgsConstructor + @AllArgsConstructor + @Builder + public static class EpochMetric { + private Integer epoch; + private Double mIoU; + private Double mFscore; + private Double loss; + private Boolean isBest; + } +} diff --git a/src/main/java/com/kamco/cd/training/model/dto/ModelVerDto.java b/src/main/java/com/kamco/cd/training/model/dto/ModelVerDto.java index dedc818..b589925 100644 --- a/src/main/java/com/kamco/cd/training/model/dto/ModelVerDto.java +++ b/src/main/java/com/kamco/cd/training/model/dto/ModelVerDto.java @@ -1,61 +1,61 @@ -package com.kamco.cd.training.model.dto; - -import com.kamco.cd.training.common.utils.interfaces.JsonFormatDttm; -import io.swagger.v3.oas.annotations.media.Schema; -import java.time.ZonedDateTime; -import lombok.Getter; - -public class ModelVerDto { - - @Schema(name = "modelVer Basic", description = "모델버전 엔티티 기본 정보") - @Getter - public static class Basic { - - private final Long id; - private final Long modelUid; - - private final String modelCate; - private final String modelVer; - - private final String usedState; - private final String modelState; - private final Double qualityProb; - private final String deployState; - private final String modelPath; - - @JsonFormatDttm private final ZonedDateTime createdDttm; - private final Long createdUid; - - @JsonFormatDttm private final ZonedDateTime updatedDttm; - private final Long updatedUid; - - public Basic( - Long id, - Long modelUid, - String modelCate, - String modelVer, - String usedState, - String modelState, - Double qualityProb, - String deployState, - String modelPath, - ZonedDateTime createdDttm, - Long createdUid, - ZonedDateTime updatedDttm, - Long updatedUid) { - this.id = id; - this.modelUid = modelUid; - this.modelCate = modelCate; - this.modelVer = modelVer; - this.usedState = usedState; - this.modelState = modelState; - this.qualityProb = qualityProb; - this.deployState = deployState; - this.modelPath = modelPath; - this.createdDttm = createdDttm; - this.createdUid = createdUid; - this.updatedDttm = updatedDttm; - this.updatedUid = updatedUid; - } - } -} +package com.kamco.cd.training.model.dto; + +import com.kamco.cd.training.common.utils.interfaces.JsonFormatDttm; +import io.swagger.v3.oas.annotations.media.Schema; +import java.time.ZonedDateTime; +import lombok.Getter; + +public class ModelVerDto { + + @Schema(name = "modelVer Basic", description = "모델버전 엔티티 기본 정보") + @Getter + public static class Basic { + + private final Long id; + private final Long modelUid; + + private final String modelCate; + private final String modelVer; + + private final String usedState; + private final String modelState; + private final Double qualityProb; + private final String deployState; + private final String modelPath; + + @JsonFormatDttm private final ZonedDateTime createdDttm; + private final Long createdUid; + + @JsonFormatDttm private final ZonedDateTime updatedDttm; + private final Long updatedUid; + + public Basic( + Long id, + Long modelUid, + String modelCate, + String modelVer, + String usedState, + String modelState, + Double qualityProb, + String deployState, + String modelPath, + ZonedDateTime createdDttm, + Long createdUid, + ZonedDateTime updatedDttm, + Long updatedUid) { + this.id = id; + this.modelUid = modelUid; + this.modelCate = modelCate; + this.modelVer = modelVer; + this.usedState = usedState; + this.modelState = modelState; + this.qualityProb = qualityProb; + this.deployState = deployState; + this.modelPath = modelPath; + this.createdDttm = createdDttm; + this.createdUid = createdUid; + this.updatedDttm = updatedDttm; + this.updatedUid = updatedUid; + } + } +} diff --git a/src/main/java/com/kamco/cd/training/model/service/ModelMngService.java b/src/main/java/com/kamco/cd/training/model/service/ModelMngService.java index 070352b..1b1be3a 100644 --- a/src/main/java/com/kamco/cd/training/model/service/ModelMngService.java +++ b/src/main/java/com/kamco/cd/training/model/service/ModelMngService.java @@ -1,50 +1,50 @@ -package com.kamco.cd.training.model.service; - -import com.kamco.cd.training.model.dto.ModelMngDto; -import com.kamco.cd.training.model.dto.ModelMngDto.Basic; -import com.kamco.cd.training.model.dto.ModelMngDto.SearchReq; -import com.kamco.cd.training.postgres.core.ModelMngCoreService; -import lombok.RequiredArgsConstructor; -import lombok.extern.slf4j.Slf4j; -import org.springframework.data.domain.Page; -import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Transactional; - -@Service -@RequiredArgsConstructor -@Transactional(readOnly = true) -@Slf4j -public class ModelMngService { - - private final ModelMngCoreService modelMngCoreService; - - /** - * 모델 목록 조회 - * - * @param searchReq 검색 조건 - * @return 페이징 처리된 모델 목록 - */ - public Page findByModels(SearchReq searchReq) { - return modelMngCoreService.findByModels(searchReq); - } - - /** - * 모델 상세 조회 - * - * @param modelUid 모델 UID - * @return 모델 상세 정보 - */ - public ModelMngDto.Detail getModelDetail(Long modelUid) { - return modelMngCoreService.getModelDetail(modelUid); - } - - /** - * 모델 상세 조회 (UUID 기반) - * - * @param uuid 모델 UUID - * @return 모델 상세 정보 - */ - public ModelMngDto.Detail getModelDetailByUuid(String uuid) { - return modelMngCoreService.getModelDetailByUuid(uuid); - } -} +package com.kamco.cd.training.model.service; + +import com.kamco.cd.training.model.dto.ModelMngDto; +import com.kamco.cd.training.model.dto.ModelMngDto.Basic; +import com.kamco.cd.training.model.dto.ModelMngDto.SearchReq; +import com.kamco.cd.training.postgres.core.ModelMngCoreService; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.data.domain.Page; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +@Service +@RequiredArgsConstructor +@Transactional(readOnly = true) +@Slf4j +public class ModelMngService { + + private final ModelMngCoreService modelMngCoreService; + + /** + * 모델 목록 조회 + * + * @param searchReq 검색 조건 + * @return 페이징 처리된 모델 목록 + */ + public Page findByModels(SearchReq searchReq) { + return modelMngCoreService.findByModels(searchReq); + } + + /** + * 모델 상세 조회 + * + * @param modelUid 모델 UID + * @return 모델 상세 정보 + */ + public ModelMngDto.Detail getModelDetail(Long modelUid) { + return modelMngCoreService.getModelDetail(modelUid); + } + + /** + * 모델 상세 조회 (UUID 기반) + * + * @param uuid 모델 UUID + * @return 모델 상세 정보 + */ + public ModelMngDto.Detail getModelDetailByUuid(String uuid) { + return modelMngCoreService.getModelDetailByUuid(uuid); + } +} diff --git a/src/main/java/com/kamco/cd/training/model/service/ModelTrainService.java b/src/main/java/com/kamco/cd/training/model/service/ModelTrainService.java index eb17111..b12c9fb 100644 --- a/src/main/java/com/kamco/cd/training/model/service/ModelTrainService.java +++ b/src/main/java/com/kamco/cd/training/model/service/ModelTrainService.java @@ -1,393 +1,393 @@ -package com.kamco.cd.training.model.service; - -import com.kamco.cd.training.common.exception.BadRequestException; -import com.kamco.cd.training.common.exception.NotFoundException; -import com.kamco.cd.training.model.dto.ModelMngDto; -import com.kamco.cd.training.postgres.core.DatasetCoreService; -import com.kamco.cd.training.postgres.core.HyperParamCoreService; -import com.kamco.cd.training.postgres.core.ModelMngCoreService; -import com.kamco.cd.training.postgres.core.SystemMetricsCoreService; -import com.kamco.cd.training.postgres.entity.ModelTrainMasterEntity; -import java.util.List; -import lombok.RequiredArgsConstructor; -import lombok.extern.slf4j.Slf4j; -import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Transactional; - -@Service -@RequiredArgsConstructor -@Transactional(readOnly = true) -@Slf4j -public class ModelTrainService { - - private final ModelMngCoreService modelMngCoreService; - private final HyperParamCoreService hyperParamCoreService; - private final DatasetCoreService datasetCoreService; - private final SystemMetricsCoreService systemMetricsCoreService; - - /** - * 학습 모델 목록 조회 - * - * @return 학습 모델 목록 - */ - public List getTrainModelList() { - return modelMngCoreService.findAllTrainModels(); - } - - /** - * 학습 설정 통합 조회 - * - * @return 학습 설정 폼 데이터 - */ - public ModelMngDto.FormConfigRes getFormConfig() { - // 1. 현재 실행 중인 모델 확인 - String runningModelUuid = modelMngCoreService.findRunningModelUuid(); - boolean isTrainAvailable = (runningModelUuid == null); - - // 2. 저장공간 체크 (10GB 미만 시 학습 불가) - if (isTrainAvailable) { - isTrainAvailable = systemMetricsCoreService.isStorageAvailableForTraining(); - long availableMB = systemMetricsCoreService.getAvailableStorageMB(); - log.info("저장공간 체크 완료: {}MB 사용 가능, 학습 가능 여부: {}", availableMB, isTrainAvailable); - } - - // 3. 하이퍼파라미터 목록 - List hyperParams = hyperParamCoreService.findAllActiveHyperParams(); - - // 4. 데이터셋 목록 - List datasets = datasetCoreService.findAllActiveDatasetsForTraining(); - - return ModelMngDto.FormConfigRes.builder() - .isTrainAvailable(isTrainAvailable) - .hyperParams(hyperParams) - .datasets(datasets) - .runningModelUuid(runningModelUuid) - .build(); - } - - /** - * 하이퍼파라미터 등록 - * - * @param createReq 등록 요청 - * @return 생성된 버전명 - */ - @Transactional - public String createHyperParam(ModelMngDto.HyperParamCreateReq createReq) { - // 신규 버전 추가 시 baseHyperVer가 없으면 H1으로 설정 - if (createReq.getBaseHyperVer() == null || createReq.getBaseHyperVer().isEmpty()) { - String firstVersion = hyperParamCoreService.getFirstHyperParamVersion(); - createReq.setBaseHyperVer(firstVersion); - log.info("baseHyperVer가 없어 첫 번째 버전으로 설정: {}", firstVersion); - } - - String newVersion = hyperParamCoreService.createHyperParam(createReq); - log.info("하이퍼파라미터 등록 완료: {}", newVersion); - return newVersion; - } - - /** - * 하이퍼파라미터 단건 조회 - * - * @param hyperVer 하이퍼파라미터 버전 - * @return 하이퍼파라미터 정보 - */ - public ModelMngDto.HyperParamInfo getHyperParam(String hyperVer) { - return hyperParamCoreService.findByHyperVer(hyperVer); - } - - /** - * 하이퍼파라미터 삭제 - * - * @param hyperVer 하이퍼파라미터 버전 - */ - @Transactional - public void deleteHyperParam(String hyperVer) { - hyperParamCoreService.deleteHyperParam(hyperVer); - log.info("하이퍼파라미터 삭제 완료: {}", hyperVer); - } - - /** - * 학습 시작 - * - * @param trainReq 학습 시작 요청 - * @return 학습 시작 응답 - */ - @Transactional - public ModelMngDto.TrainStartRes startTraining(ModelMngDto.TrainStartReq trainReq) { - // 1. 동시 실행 방지 검증 - String runningModelUuid = modelMngCoreService.findRunningModelUuid(); - if (runningModelUuid != null) { - throw new BadRequestException( - "이미 실행 중인 학습이 있습니다. 학습은 한 번에 한 개만 실행할 수 있습니다. (실행 중인 모델: " + runningModelUuid + ")"); - } - - // 2. 저장공간 체크 (10GB 미만 시 학습 불가) - if (!systemMetricsCoreService.isStorageAvailableForTraining()) { - long availableMB = systemMetricsCoreService.getAvailableStorageMB(); - long requiredMB = 10 * 1024; // 10GB - throw new BadRequestException( - String.format( - "저장공간이 부족하여 학습을 시작할 수 없습니다. (필요: %dMB, 사용 가능: %dMB)", requiredMB, availableMB)); - } - - // 3. 데이터셋 상태 검증 (COMPLETED 상태만 학습 가능) - validateDatasetStatus(trainReq.getDatasetIds()); - - // 4. 데이터 분할 비율 검증 (예: "7:2:1" 형식) - if (trainReq.getDatasetRatio() != null && !trainReq.getDatasetRatio().isEmpty()) { - validateDatasetRatio(trainReq.getDatasetRatio()); - } - - // 5. 학습 마스터 생성 - ModelTrainMasterEntity entity = modelMngCoreService.createTrainMaster(trainReq); - - // 5. 데이터셋 매핑 생성 - modelMngCoreService.createDatasetMappings(entity.getId(), trainReq.getDatasetIds()); - - // 6. 실제 UUID 사용 - String uuid = entity.getUuid().toString(); - - log.info( - "학습 시작: uuid={}, hyperVer={}, epoch={}, datasets={}", - uuid, - trainReq.getHyperVer(), - trainReq.getEpoch(), - trainReq.getDatasetIds()); - - // TODO: 비동기 GPU 학습 프로세스 트리거 로직 추가 - - return ModelMngDto.TrainStartRes.builder().uuid(uuid).status(entity.getStatusCd()).build(); - } - - /** - * 데이터셋 상태 검증 - * - * @param datasetIds 데이터셋 ID 목록 - */ - private void validateDatasetStatus(List datasetIds) { - for (Long datasetId : datasetIds) { - try { - var dataset = datasetCoreService.getOneById(datasetId); - - // COMPLETED 상태가 아닌 데이터셋이 포함되어 있으면 예외 발생 - if (dataset.getStatus() == null || !"COMPLETED".equals(dataset.getStatus())) { - throw new BadRequestException( - String.format( - "학습에 사용할 수 없는 데이터셋입니다. (ID: %d, 상태: %s). COMPLETED 상태의 데이터셋만 선택 가능합니다.", - datasetId, dataset.getStatus() != null ? dataset.getStatus() : "NULL")); - } - - log.debug("데이터셋 상태 검증 통과: ID={}, Status={}", datasetId, dataset.getStatus()); - } catch (NotFoundException e) { - throw new BadRequestException("존재하지 않는 데이터셋입니다. ID: " + datasetId); - } - } - log.info("모든 데이터셋 상태 검증 완료: {} 개", datasetIds.size()); - } - - /** - * 데이터 분할 비율 검증 - * - * @param datasetRatio 데이터셋 비율 (예: "7:2:1") - */ - private void validateDatasetRatio(String datasetRatio) { - try { - String[] parts = datasetRatio.split(":"); - if (parts.length != 3) { - throw new BadRequestException("데이터 분할 비율은 'Training:Validation:Test' 형식이어야 합니다 (예: 7:2:1)"); - } - - int train = Integer.parseInt(parts[0].trim()); - int validation = Integer.parseInt(parts[1].trim()); - int test = Integer.parseInt(parts[2].trim()); - - int sum = train + validation + test; - if (sum != 10) { - throw new BadRequestException( - String.format("데이터 분할 비율의 합계는 10이어야 합니다. (현재 합계: %d, 입력값: %s)", sum, datasetRatio)); - } - - if (train <= 0 || validation < 0 || test < 0) { - throw new BadRequestException("데이터 분할 비율은 모두 0 이상이어야 합니다 (Training은 1 이상)"); - } - - log.info( - "데이터 분할 비율 검증 완료: Training={}0%, Validation={}0%, Test={}0%", train, validation, test); - } catch (NumberFormatException e) { - throw new BadRequestException("데이터 분할 비율은 숫자로만 구성되어야 합니다: " + datasetRatio); - } - } - - /** - * 학습 모델 삭제 - * - * @param uuid 모델 UUID - */ - @Transactional - public void deleteTrainModel(String uuid) { - modelMngCoreService.deleteByUuid(uuid); - log.info("학습 모델 삭제 완료: uuid={}", uuid); - } - - // ==================== Resume Training (학습 재시작) ==================== - - /** - * 학습 재시작 정보 조회 - * - * @param uuid 모델 UUID - * @return 재시작 정보 - */ - public ModelMngDto.ResumeInfo getResumeInfo(String uuid) { - ModelTrainMasterEntity entity = modelMngCoreService.findByUuid(uuid); - - return ModelMngDto.ResumeInfo.builder() - .canResume(entity.getCanResume() != null && entity.getCanResume()) - .lastEpoch(entity.getLastCheckpointEpoch()) - .totalEpoch(entity.getEpochCnt()) - .checkpointPath(entity.getCheckpointPath()) - .failedAt( - entity.getStopDttm() != null - ? entity.getStopDttm().atZone(java.time.ZoneId.systemDefault()) - : null) - .build(); - } - - /** - * 학습 재시작 - * - * @param uuid 모델 UUID - * @param resumeReq 재시작 요청 - * @return 재시작 응답 - */ - @Transactional - public ModelMngDto.ResumeResponse resumeTraining( - String uuid, ModelMngDto.ResumeRequest resumeReq) { - ModelTrainMasterEntity entity = modelMngCoreService.findByUuid(uuid); - - // 재시작 가능 여부 검증 - if (entity.getCanResume() == null || !entity.getCanResume()) { - throw new IllegalStateException("학습 재시작이 불가능한 모델입니다: " + uuid); - } - - if (entity.getLastCheckpointEpoch() == null) { - throw new IllegalStateException("Checkpoint가 존재하지 않습니다: " + uuid); - } - - // 상태 업데이트 - entity.setStatusCd("RUNNING"); - entity.setProgressRate(0); - - // 총 Epoch 수 변경 (선택사항) - if (resumeReq.getNewTotalEpoch() != null) { - entity.setEpochCnt(resumeReq.getNewTotalEpoch()); - } - - log.info( - "학습 재시작: uuid={}, resumeFromEpoch={}, totalEpoch={}", - uuid, - resumeReq.getResumeFromEpoch(), - entity.getEpochCnt()); - - // TODO: 비동기 GPU 학습 재시작 프로세스 트리거 로직 추가 - // - Checkpoint 파일 로드 - // - 지정된 Epoch부터 학습 재개 - - return ModelMngDto.ResumeResponse.builder() - .uuid(uuid) - .status(entity.getStatusCd()) - .resumedFromEpoch(resumeReq.getResumeFromEpoch()) - .build(); - } - - // ==================== Best Epoch Setting (Best Epoch 설정) ==================== - - /** - * Best Epoch 설정 - * - * @param uuid 모델 UUID - * @param bestEpochReq Best Epoch 요청 - * @return Best Epoch 응답 - */ - @Transactional - public ModelMngDto.BestEpochResponse setBestEpoch( - String uuid, ModelMngDto.BestEpochRequest bestEpochReq) { - ModelTrainMasterEntity entity = modelMngCoreService.findByUuid(uuid); - - // 1차 학습 완료 상태 검증 - if (!"STEP1_COMPLETED".equals(entity.getStatusCd()) - && !"STEP1".equals(entity.getProcessStep())) { - log.warn( - "Best Epoch 설정 시도: 현재 상태={}, processStep={}", - entity.getStatusCd(), - entity.getProcessStep()); - } - - Integer previousBestEpoch = entity.getConfirmedBestEpoch(); - - // 사용자가 확정한 Best Epoch 설정 - entity.setConfirmedBestEpoch(bestEpochReq.getBestEpoch()); - - // 2차 학습(Test) 단계로 상태 전이 - entity.setProcessStep("STEP2"); - entity.setStatusCd("STEP2_RUNNING"); - entity.setProgressRate(0); - entity.setUpdatedDttm(java.time.ZonedDateTime.now()); - - log.info( - "Best Epoch 설정 및 2차 학습 시작: uuid={}, newBestEpoch={}, previousBestEpoch={}, reason={}, newStatus={}", - uuid, - bestEpochReq.getBestEpoch(), - previousBestEpoch, - bestEpochReq.getReason(), - entity.getStatusCd()); - - // TODO: 비동기 GPU 2차 학습(Test) 프로세스 트리거 로직 추가 - // - Best Epoch 모델 로드 - // - Test 데이터셋으로 성능 평가 실행 - // - 완료 시 STEP2_COMPLETED 상태로 전환 - - return ModelMngDto.BestEpochResponse.builder() - .uuid(uuid) - .bestEpoch(entity.getBestEpoch()) // 자동 선택된 값 - .confirmedBestEpoch(entity.getConfirmedBestEpoch()) // 사용자 확정 값 - .previousBestEpoch(previousBestEpoch) - .build(); - } - - /** - * Epoch별 성능 지표 조회 - * - * @param uuid 모델 UUID - * @return Epoch별 성능 지표 목록 - */ - public List getEpochMetrics(String uuid) { - ModelTrainMasterEntity entity = modelMngCoreService.findByUuid(uuid); - - // TODO: 실제 학습 로그 파일이나 DB에서 Epoch별 성능 지표 조회 - // 현재는 샘플 데이터 반환 - List metrics = new java.util.ArrayList<>(); - - if (entity.getEpochCnt() != null && entity.getBestEpoch() != null) { - // 샘플 데이터 생성 (실제로는 학습 로그 파일 파싱 또는 별도 테이블 조회) - for (int i = 1; i <= Math.min(entity.getEpochCnt(), 10); i++) { - int epoch = entity.getBestEpoch() - 5 + i; - if (epoch <= 0 || epoch > entity.getEpochCnt()) { - continue; - } - - metrics.add( - ModelMngDto.EpochMetric.builder() - .epoch(epoch) - .mIoU(0.80 + (Math.random() * 0.15)) // 샘플 데이터 - .mFscore(0.85 + (Math.random() * 0.10)) // 샘플 데이터 - .loss(0.3 - (Math.random() * 0.15)) // 샘플 데이터 - .isBest(entity.getBestEpoch() != null && epoch == entity.getBestEpoch()) - .build()); - } - } - - log.info("Epoch별 성능 지표 조회: uuid={}, metricsCount={}", uuid, metrics.size()); - - return metrics; - } -} +package com.kamco.cd.training.model.service; + +import com.kamco.cd.training.common.exception.BadRequestException; +import com.kamco.cd.training.common.exception.NotFoundException; +import com.kamco.cd.training.model.dto.ModelMngDto; +import com.kamco.cd.training.postgres.core.DatasetCoreService; +import com.kamco.cd.training.postgres.core.HyperParamCoreService; +import com.kamco.cd.training.postgres.core.ModelMngCoreService; +import com.kamco.cd.training.postgres.core.SystemMetricsCoreService; +import com.kamco.cd.training.postgres.entity.ModelTrainMasterEntity; +import java.util.List; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +@Service +@RequiredArgsConstructor +@Transactional(readOnly = true) +@Slf4j +public class ModelTrainService { + + private final ModelMngCoreService modelMngCoreService; + private final HyperParamCoreService hyperParamCoreService; + private final DatasetCoreService datasetCoreService; + private final SystemMetricsCoreService systemMetricsCoreService; + + /** + * 학습 모델 목록 조회 + * + * @return 학습 모델 목록 + */ + public List getTrainModelList() { + return modelMngCoreService.findAllTrainModels(); + } + + /** + * 학습 설정 통합 조회 + * + * @return 학습 설정 폼 데이터 + */ + public ModelMngDto.FormConfigRes getFormConfig() { + // 1. 현재 실행 중인 모델 확인 + String runningModelUuid = modelMngCoreService.findRunningModelUuid(); + boolean isTrainAvailable = (runningModelUuid == null); + + // 2. 저장공간 체크 (10GB 미만 시 학습 불가) + if (isTrainAvailable) { + isTrainAvailable = systemMetricsCoreService.isStorageAvailableForTraining(); + long availableMB = systemMetricsCoreService.getAvailableStorageMB(); + log.info("저장공간 체크 완료: {}MB 사용 가능, 학습 가능 여부: {}", availableMB, isTrainAvailable); + } + + // 3. 하이퍼파라미터 목록 + List hyperParams = hyperParamCoreService.findAllActiveHyperParams(); + + // 4. 데이터셋 목록 + List datasets = datasetCoreService.findAllActiveDatasetsForTraining(); + + return ModelMngDto.FormConfigRes.builder() + .isTrainAvailable(isTrainAvailable) + .hyperParams(hyperParams) + .datasets(datasets) + .runningModelUuid(runningModelUuid) + .build(); + } + + /** + * 하이퍼파라미터 등록 + * + * @param createReq 등록 요청 + * @return 생성된 버전명 + */ + @Transactional + public String createHyperParam(ModelMngDto.HyperParamCreateReq createReq) { + // 신규 버전 추가 시 baseHyperVer가 없으면 H1으로 설정 + if (createReq.getBaseHyperVer() == null || createReq.getBaseHyperVer().isEmpty()) { + String firstVersion = hyperParamCoreService.getFirstHyperParamVersion(); + createReq.setBaseHyperVer(firstVersion); + log.info("baseHyperVer가 없어 첫 번째 버전으로 설정: {}", firstVersion); + } + + String newVersion = hyperParamCoreService.createHyperParam(createReq); + log.info("하이퍼파라미터 등록 완료: {}", newVersion); + return newVersion; + } + + /** + * 하이퍼파라미터 단건 조회 + * + * @param hyperVer 하이퍼파라미터 버전 + * @return 하이퍼파라미터 정보 + */ + public ModelMngDto.HyperParamInfo getHyperParam(String hyperVer) { + return hyperParamCoreService.findByHyperVer(hyperVer); + } + + /** + * 하이퍼파라미터 삭제 + * + * @param hyperVer 하이퍼파라미터 버전 + */ + @Transactional + public void deleteHyperParam(String hyperVer) { + hyperParamCoreService.deleteHyperParam(hyperVer); + log.info("하이퍼파라미터 삭제 완료: {}", hyperVer); + } + + /** + * 학습 시작 + * + * @param trainReq 학습 시작 요청 + * @return 학습 시작 응답 + */ + @Transactional + public ModelMngDto.TrainStartRes startTraining(ModelMngDto.TrainStartReq trainReq) { + // 1. 동시 실행 방지 검증 + String runningModelUuid = modelMngCoreService.findRunningModelUuid(); + if (runningModelUuid != null) { + throw new BadRequestException( + "이미 실행 중인 학습이 있습니다. 학습은 한 번에 한 개만 실행할 수 있습니다. (실행 중인 모델: " + runningModelUuid + ")"); + } + + // 2. 저장공간 체크 (10GB 미만 시 학습 불가) + if (!systemMetricsCoreService.isStorageAvailableForTraining()) { + long availableMB = systemMetricsCoreService.getAvailableStorageMB(); + long requiredMB = 10 * 1024; // 10GB + throw new BadRequestException( + String.format( + "저장공간이 부족하여 학습을 시작할 수 없습니다. (필요: %dMB, 사용 가능: %dMB)", requiredMB, availableMB)); + } + + // 3. 데이터셋 상태 검증 (COMPLETED 상태만 학습 가능) + validateDatasetStatus(trainReq.getDatasetIds()); + + // 4. 데이터 분할 비율 검증 (예: "7:2:1" 형식) + if (trainReq.getDatasetRatio() != null && !trainReq.getDatasetRatio().isEmpty()) { + validateDatasetRatio(trainReq.getDatasetRatio()); + } + + // 5. 학습 마스터 생성 + ModelTrainMasterEntity entity = modelMngCoreService.createTrainMaster(trainReq); + + // 5. 데이터셋 매핑 생성 + modelMngCoreService.createDatasetMappings(entity.getId(), trainReq.getDatasetIds()); + + // 6. 실제 UUID 사용 + String uuid = entity.getUuid().toString(); + + log.info( + "학습 시작: uuid={}, hyperVer={}, epoch={}, datasets={}", + uuid, + trainReq.getHyperVer(), + trainReq.getEpoch(), + trainReq.getDatasetIds()); + + // TODO: 비동기 GPU 학습 프로세스 트리거 로직 추가 + + return ModelMngDto.TrainStartRes.builder().uuid(uuid).status(entity.getStatusCd()).build(); + } + + /** + * 데이터셋 상태 검증 + * + * @param datasetIds 데이터셋 ID 목록 + */ + private void validateDatasetStatus(List datasetIds) { + for (Long datasetId : datasetIds) { + try { + var dataset = datasetCoreService.getOneById(datasetId); + + // COMPLETED 상태가 아닌 데이터셋이 포함되어 있으면 예외 발생 + if (dataset.getStatus() == null || !"COMPLETED".equals(dataset.getStatus())) { + throw new BadRequestException( + String.format( + "학습에 사용할 수 없는 데이터셋입니다. (ID: %d, 상태: %s). COMPLETED 상태의 데이터셋만 선택 가능합니다.", + datasetId, dataset.getStatus() != null ? dataset.getStatus() : "NULL")); + } + + log.debug("데이터셋 상태 검증 통과: ID={}, Status={}", datasetId, dataset.getStatus()); + } catch (NotFoundException e) { + throw new BadRequestException("존재하지 않는 데이터셋입니다. ID: " + datasetId); + } + } + log.info("모든 데이터셋 상태 검증 완료: {} 개", datasetIds.size()); + } + + /** + * 데이터 분할 비율 검증 + * + * @param datasetRatio 데이터셋 비율 (예: "7:2:1") + */ + private void validateDatasetRatio(String datasetRatio) { + try { + String[] parts = datasetRatio.split(":"); + if (parts.length != 3) { + throw new BadRequestException("데이터 분할 비율은 'Training:Validation:Test' 형식이어야 합니다 (예: 7:2:1)"); + } + + int train = Integer.parseInt(parts[0].trim()); + int validation = Integer.parseInt(parts[1].trim()); + int test = Integer.parseInt(parts[2].trim()); + + int sum = train + validation + test; + if (sum != 10) { + throw new BadRequestException( + String.format("데이터 분할 비율의 합계는 10이어야 합니다. (현재 합계: %d, 입력값: %s)", sum, datasetRatio)); + } + + if (train <= 0 || validation < 0 || test < 0) { + throw new BadRequestException("데이터 분할 비율은 모두 0 이상이어야 합니다 (Training은 1 이상)"); + } + + log.info( + "데이터 분할 비율 검증 완료: Training={}0%, Validation={}0%, Test={}0%", train, validation, test); + } catch (NumberFormatException e) { + throw new BadRequestException("데이터 분할 비율은 숫자로만 구성되어야 합니다: " + datasetRatio); + } + } + + /** + * 학습 모델 삭제 + * + * @param uuid 모델 UUID + */ + @Transactional + public void deleteTrainModel(String uuid) { + modelMngCoreService.deleteByUuid(uuid); + log.info("학습 모델 삭제 완료: uuid={}", uuid); + } + + // ==================== Resume Training (학습 재시작) ==================== + + /** + * 학습 재시작 정보 조회 + * + * @param uuid 모델 UUID + * @return 재시작 정보 + */ + public ModelMngDto.ResumeInfo getResumeInfo(String uuid) { + ModelTrainMasterEntity entity = modelMngCoreService.findByUuid(uuid); + + return ModelMngDto.ResumeInfo.builder() + .canResume(entity.getCanResume() != null && entity.getCanResume()) + .lastEpoch(entity.getLastCheckpointEpoch()) + .totalEpoch(entity.getEpochCnt()) + .checkpointPath(entity.getCheckpointPath()) + .failedAt( + entity.getStopDttm() != null + ? entity.getStopDttm().atZone(java.time.ZoneId.systemDefault()) + : null) + .build(); + } + + /** + * 학습 재시작 + * + * @param uuid 모델 UUID + * @param resumeReq 재시작 요청 + * @return 재시작 응답 + */ + @Transactional + public ModelMngDto.ResumeResponse resumeTraining( + String uuid, ModelMngDto.ResumeRequest resumeReq) { + ModelTrainMasterEntity entity = modelMngCoreService.findByUuid(uuid); + + // 재시작 가능 여부 검증 + if (entity.getCanResume() == null || !entity.getCanResume()) { + throw new IllegalStateException("학습 재시작이 불가능한 모델입니다: " + uuid); + } + + if (entity.getLastCheckpointEpoch() == null) { + throw new IllegalStateException("Checkpoint가 존재하지 않습니다: " + uuid); + } + + // 상태 업데이트 + entity.setStatusCd("RUNNING"); + entity.setProgressRate(0); + + // 총 Epoch 수 변경 (선택사항) + if (resumeReq.getNewTotalEpoch() != null) { + entity.setEpochCnt(resumeReq.getNewTotalEpoch()); + } + + log.info( + "학습 재시작: uuid={}, resumeFromEpoch={}, totalEpoch={}", + uuid, + resumeReq.getResumeFromEpoch(), + entity.getEpochCnt()); + + // TODO: 비동기 GPU 학습 재시작 프로세스 트리거 로직 추가 + // - Checkpoint 파일 로드 + // - 지정된 Epoch부터 학습 재개 + + return ModelMngDto.ResumeResponse.builder() + .uuid(uuid) + .status(entity.getStatusCd()) + .resumedFromEpoch(resumeReq.getResumeFromEpoch()) + .build(); + } + + // ==================== Best Epoch Setting (Best Epoch 설정) ==================== + + /** + * Best Epoch 설정 + * + * @param uuid 모델 UUID + * @param bestEpochReq Best Epoch 요청 + * @return Best Epoch 응답 + */ + @Transactional + public ModelMngDto.BestEpochResponse setBestEpoch( + String uuid, ModelMngDto.BestEpochRequest bestEpochReq) { + ModelTrainMasterEntity entity = modelMngCoreService.findByUuid(uuid); + + // 1차 학습 완료 상태 검증 + if (!"STEP1_COMPLETED".equals(entity.getStatusCd()) + && !"STEP1".equals(entity.getProcessStep())) { + log.warn( + "Best Epoch 설정 시도: 현재 상태={}, processStep={}", + entity.getStatusCd(), + entity.getProcessStep()); + } + + Integer previousBestEpoch = entity.getConfirmedBestEpoch(); + + // 사용자가 확정한 Best Epoch 설정 + entity.setConfirmedBestEpoch(bestEpochReq.getBestEpoch()); + + // 2차 학습(Test) 단계로 상태 전이 + entity.setProcessStep("STEP2"); + entity.setStatusCd("STEP2_RUNNING"); + entity.setProgressRate(0); + entity.setUpdatedDttm(java.time.ZonedDateTime.now()); + + log.info( + "Best Epoch 설정 및 2차 학습 시작: uuid={}, newBestEpoch={}, previousBestEpoch={}, reason={}, newStatus={}", + uuid, + bestEpochReq.getBestEpoch(), + previousBestEpoch, + bestEpochReq.getReason(), + entity.getStatusCd()); + + // TODO: 비동기 GPU 2차 학습(Test) 프로세스 트리거 로직 추가 + // - Best Epoch 모델 로드 + // - Test 데이터셋으로 성능 평가 실행 + // - 완료 시 STEP2_COMPLETED 상태로 전환 + + return ModelMngDto.BestEpochResponse.builder() + .uuid(uuid) + .bestEpoch(entity.getBestEpoch()) // 자동 선택된 값 + .confirmedBestEpoch(entity.getConfirmedBestEpoch()) // 사용자 확정 값 + .previousBestEpoch(previousBestEpoch) + .build(); + } + + /** + * Epoch별 성능 지표 조회 + * + * @param uuid 모델 UUID + * @return Epoch별 성능 지표 목록 + */ + public List getEpochMetrics(String uuid) { + ModelTrainMasterEntity entity = modelMngCoreService.findByUuid(uuid); + + // TODO: 실제 학습 로그 파일이나 DB에서 Epoch별 성능 지표 조회 + // 현재는 샘플 데이터 반환 + List metrics = new java.util.ArrayList<>(); + + if (entity.getEpochCnt() != null && entity.getBestEpoch() != null) { + // 샘플 데이터 생성 (실제로는 학습 로그 파일 파싱 또는 별도 테이블 조회) + for (int i = 1; i <= Math.min(entity.getEpochCnt(), 10); i++) { + int epoch = entity.getBestEpoch() - 5 + i; + if (epoch <= 0 || epoch > entity.getEpochCnt()) { + continue; + } + + metrics.add( + ModelMngDto.EpochMetric.builder() + .epoch(epoch) + .mIoU(0.80 + (Math.random() * 0.15)) // 샘플 데이터 + .mFscore(0.85 + (Math.random() * 0.10)) // 샘플 데이터 + .loss(0.3 - (Math.random() * 0.15)) // 샘플 데이터 + .isBest(entity.getBestEpoch() != null && epoch == entity.getBestEpoch()) + .build()); + } + } + + log.info("Epoch별 성능 지표 조회: uuid={}, metricsCount={}", uuid, metrics.size()); + + return metrics; + } +} diff --git a/src/main/java/com/kamco/cd/training/postgres/CommonCreateEntity.java b/src/main/java/com/kamco/cd/training/postgres/CommonCreateEntity.java index 27eed0d..fd7ab98 100644 --- a/src/main/java/com/kamco/cd/training/postgres/CommonCreateEntity.java +++ b/src/main/java/com/kamco/cd/training/postgres/CommonCreateEntity.java @@ -1,22 +1,22 @@ -package com.kamco.cd.training.postgres; - -import jakarta.persistence.Column; -import jakarta.persistence.MappedSuperclass; -import jakarta.persistence.PrePersist; -import java.time.ZonedDateTime; -import lombok.Getter; -import org.springframework.data.annotation.CreatedDate; - -@Getter -@MappedSuperclass -public class CommonCreateEntity { - - @CreatedDate - @Column(name = "created_dttm", updatable = false, nullable = false) - private ZonedDateTime createdDate; - - @PrePersist - protected void onPersist() { - this.createdDate = ZonedDateTime.now(); - } -} +package com.kamco.cd.training.postgres; + +import jakarta.persistence.Column; +import jakarta.persistence.MappedSuperclass; +import jakarta.persistence.PrePersist; +import java.time.ZonedDateTime; +import lombok.Getter; +import org.springframework.data.annotation.CreatedDate; + +@Getter +@MappedSuperclass +public class CommonCreateEntity { + + @CreatedDate + @Column(name = "created_dttm", updatable = false, nullable = false) + private ZonedDateTime createdDate; + + @PrePersist + protected void onPersist() { + this.createdDate = ZonedDateTime.now(); + } +} diff --git a/src/main/java/com/kamco/cd/training/postgres/CommonDateEntity.java b/src/main/java/com/kamco/cd/training/postgres/CommonDateEntity.java index bd7f03b..eb80ea1 100644 --- a/src/main/java/com/kamco/cd/training/postgres/CommonDateEntity.java +++ b/src/main/java/com/kamco/cd/training/postgres/CommonDateEntity.java @@ -1,34 +1,34 @@ -package com.kamco.cd.training.postgres; - -import jakarta.persistence.Column; -import jakarta.persistence.MappedSuperclass; -import jakarta.persistence.PrePersist; -import jakarta.persistence.PreUpdate; -import java.time.ZonedDateTime; -import lombok.Getter; -import org.springframework.data.annotation.CreatedDate; -import org.springframework.data.annotation.LastModifiedDate; - -@Getter -@MappedSuperclass -public class CommonDateEntity { - - @CreatedDate - @Column(name = "created_dttm", updatable = false, nullable = false) - private ZonedDateTime createdDate; - - @LastModifiedDate - @Column(name = "updated_dttm", nullable = false) - private ZonedDateTime modifiedDate; - - @PrePersist - protected void onPersist() { - this.createdDate = ZonedDateTime.now(); - this.modifiedDate = ZonedDateTime.now(); - } - - @PreUpdate - protected void onUpdate() { - this.modifiedDate = ZonedDateTime.now(); - } -} +package com.kamco.cd.training.postgres; + +import jakarta.persistence.Column; +import jakarta.persistence.MappedSuperclass; +import jakarta.persistence.PrePersist; +import jakarta.persistence.PreUpdate; +import java.time.ZonedDateTime; +import lombok.Getter; +import org.springframework.data.annotation.CreatedDate; +import org.springframework.data.annotation.LastModifiedDate; + +@Getter +@MappedSuperclass +public class CommonDateEntity { + + @CreatedDate + @Column(name = "created_dttm", updatable = false, nullable = false) + private ZonedDateTime createdDate; + + @LastModifiedDate + @Column(name = "updated_dttm", nullable = false) + private ZonedDateTime modifiedDate; + + @PrePersist + protected void onPersist() { + this.createdDate = ZonedDateTime.now(); + this.modifiedDate = ZonedDateTime.now(); + } + + @PreUpdate + protected void onUpdate() { + this.modifiedDate = ZonedDateTime.now(); + } +} diff --git a/src/main/java/com/kamco/cd/training/postgres/QueryDslConfig.java b/src/main/java/com/kamco/cd/training/postgres/QueryDslConfig.java index 9c53b9a..18fdefe 100644 --- a/src/main/java/com/kamco/cd/training/postgres/QueryDslConfig.java +++ b/src/main/java/com/kamco/cd/training/postgres/QueryDslConfig.java @@ -1,19 +1,19 @@ -package com.kamco.cd.training.postgres; - -import com.querydsl.jpa.impl.JPAQueryFactory; -import jakarta.persistence.EntityManager; -import lombok.RequiredArgsConstructor; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; - -@RequiredArgsConstructor -@Configuration -public class QueryDslConfig { - - private final EntityManager entityManager; - - @Bean - public JPAQueryFactory jpaQueryFactory() { - return new JPAQueryFactory(entityManager); - } -} +package com.kamco.cd.training.postgres; + +import com.querydsl.jpa.impl.JPAQueryFactory; +import jakarta.persistence.EntityManager; +import lombok.RequiredArgsConstructor; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +@RequiredArgsConstructor +@Configuration +public class QueryDslConfig { + + private final EntityManager entityManager; + + @Bean + public JPAQueryFactory jpaQueryFactory() { + return new JPAQueryFactory(entityManager); + } +} diff --git a/src/main/java/com/kamco/cd/training/postgres/QuerydslOrderUtil.java b/src/main/java/com/kamco/cd/training/postgres/QuerydslOrderUtil.java index 7513b55..75aded8 100644 --- a/src/main/java/com/kamco/cd/training/postgres/QuerydslOrderUtil.java +++ b/src/main/java/com/kamco/cd/training/postgres/QuerydslOrderUtil.java @@ -1,30 +1,30 @@ -package com.kamco.cd.training.postgres; - -import com.querydsl.core.types.Order; -import com.querydsl.core.types.OrderSpecifier; -import com.querydsl.core.types.dsl.PathBuilder; -import org.springframework.data.domain.Pageable; - -public class QuerydslOrderUtil { - /** - * Pageable의 Sort 정보를 QueryDSL OrderSpecifier 배열로 변환 - * - * @param pageable Spring Pageable - * @param entityClass 엔티티 클래스 (예: User.class) - * @param alias Q 엔티티 alias (예: "user") - */ - public static OrderSpecifier[] getOrderSpecifiers( - Pageable pageable, Class entityClass, String alias) { - PathBuilder entityPath = new PathBuilder<>(entityClass, alias); - - return pageable.getSort().stream() - .map( - sort -> { - Order order = sort.isAscending() ? Order.ASC : Order.DESC; - // PathBuilder.get()는 컬럼명(String)을 동적 Path로 반환 - return new OrderSpecifier<>( - order, entityPath.get(sort.getProperty(), Comparable.class)); - }) - .toArray(OrderSpecifier[]::new); - } -} +package com.kamco.cd.training.postgres; + +import com.querydsl.core.types.Order; +import com.querydsl.core.types.OrderSpecifier; +import com.querydsl.core.types.dsl.PathBuilder; +import org.springframework.data.domain.Pageable; + +public class QuerydslOrderUtil { + /** + * Pageable의 Sort 정보를 QueryDSL OrderSpecifier 배열로 변환 + * + * @param pageable Spring Pageable + * @param entityClass 엔티티 클래스 (예: User.class) + * @param alias Q 엔티티 alias (예: "user") + */ + public static OrderSpecifier[] getOrderSpecifiers( + Pageable pageable, Class entityClass, String alias) { + PathBuilder entityPath = new PathBuilder<>(entityClass, alias); + + return pageable.getSort().stream() + .map( + sort -> { + Order order = sort.isAscending() ? Order.ASC : Order.DESC; + // PathBuilder.get()는 컬럼명(String)을 동적 Path로 반환 + return new OrderSpecifier<>( + order, entityPath.get(sort.getProperty(), Comparable.class)); + }) + .toArray(OrderSpecifier[]::new); + } +} diff --git a/src/main/java/com/kamco/cd/training/postgres/core/AnimalCoreService.java b/src/main/java/com/kamco/cd/training/postgres/core/AnimalCoreService.java index fee4602..12c8782 100644 --- a/src/main/java/com/kamco/cd/training/postgres/core/AnimalCoreService.java +++ b/src/main/java/com/kamco/cd/training/postgres/core/AnimalCoreService.java @@ -1,77 +1,77 @@ -// package com.kamco.cd.kamcoback.postgres.core; -// -// import com.kamco.cd.kamcoback.common.service.BaseCoreService; -// import com.kamco.cd.kamcoback.postgres.entity.AnimalEntity; -// import com.kamco.cd.kamcoback.postgres.entity.ZooEntity; -// import com.kamco.cd.kamcoback.postgres.repository.AnimalRepository; -// import com.kamco.cd.kamcoback.postgres.repository.ZooRepository; -// import com.kamco.cd.kamcoback.zoo.dto.AnimalDto; -// import jakarta.persistence.EntityNotFoundException; -// import lombok.RequiredArgsConstructor; -// import org.springframework.data.domain.Page; -// import org.springframework.stereotype.Service; -// import org.springframework.transaction.annotation.Transactional; -// -// @Service -// @RequiredArgsConstructor -// @Transactional(readOnly = true) -// public class AnimalCoreService -// implements BaseCoreService { -// -// private final AnimalRepository animalRepository; -// private final ZooRepository zooRepository; -// -// @Transactional(readOnly = true) -// public AnimalDto.Basic getDataByUuid(String uuid) { -// AnimalEntity getZoo = -// animalRepository -// .getAnimalByUuid(uuid) -// .orElseThrow(() -> new EntityNotFoundException("Zoo not found with uuid: " + uuid)); -// return getZoo.toDto(); -// } -// -// // AddReq를 받는 추가 메서드 -// @Transactional -// public AnimalDto.Basic create(AnimalDto.AddReq req) { -// ZooEntity zoo = null; -// if (req.getZooUuid() != null) { -// zoo = -// zooRepository -// .getZooByUuid(req.getZooUuid()) -// .orElseThrow( -// () -> -// new EntityNotFoundException("Zoo not found with uuid: " + -// req.getZooUuid())); -// } -// AnimalEntity entity = new AnimalEntity(req.getCategory(), req.getSpecies(), req.getName(), -// zoo); -// AnimalEntity saved = animalRepository.save(entity); -// return saved.toDto(); -// } -// -// @Override -// @Transactional -// public void remove(Long id) { -// AnimalEntity getAnimal = -// animalRepository -// .getAnimalByUid(id) -// .orElseThrow(() -> new EntityNotFoundException("getAnimal not found with id: " + id)); -// getAnimal.deleted(); -// } -// -// @Override -// public AnimalDto.Basic getOneById(Long id) { -// AnimalEntity getAnimal = -// animalRepository -// .getAnimalByUid(id) -// .orElseThrow(() -> new EntityNotFoundException("Zoo not found with id: " + id)); -// return getAnimal.toDto(); -// } -// -// @Override -// public Page search(AnimalDto.SearchReq searchReq) { -// -// Page animalEntities = animalRepository.listAnimal(searchReq); -// return animalEntities.map(AnimalEntity::toDto); -// } -// } +// package com.kamco.cd.kamcoback.postgres.core; +// +// import com.kamco.cd.kamcoback.common.service.BaseCoreService; +// import com.kamco.cd.kamcoback.postgres.entity.AnimalEntity; +// import com.kamco.cd.kamcoback.postgres.entity.ZooEntity; +// import com.kamco.cd.kamcoback.postgres.repository.AnimalRepository; +// import com.kamco.cd.kamcoback.postgres.repository.ZooRepository; +// import com.kamco.cd.kamcoback.zoo.dto.AnimalDto; +// import jakarta.persistence.EntityNotFoundException; +// import lombok.RequiredArgsConstructor; +// import org.springframework.data.domain.Page; +// import org.springframework.stereotype.Service; +// import org.springframework.transaction.annotation.Transactional; +// +// @Service +// @RequiredArgsConstructor +// @Transactional(readOnly = true) +// public class AnimalCoreService +// implements BaseCoreService { +// +// private final AnimalRepository animalRepository; +// private final ZooRepository zooRepository; +// +// @Transactional(readOnly = true) +// public AnimalDto.Basic getDataByUuid(String uuid) { +// AnimalEntity getZoo = +// animalRepository +// .getAnimalByUuid(uuid) +// .orElseThrow(() -> new EntityNotFoundException("Zoo not found with uuid: " + uuid)); +// return getZoo.toDto(); +// } +// +// // AddReq를 받는 추가 메서드 +// @Transactional +// public AnimalDto.Basic create(AnimalDto.AddReq req) { +// ZooEntity zoo = null; +// if (req.getZooUuid() != null) { +// zoo = +// zooRepository +// .getZooByUuid(req.getZooUuid()) +// .orElseThrow( +// () -> +// new EntityNotFoundException("Zoo not found with uuid: " + +// req.getZooUuid())); +// } +// AnimalEntity entity = new AnimalEntity(req.getCategory(), req.getSpecies(), req.getName(), +// zoo); +// AnimalEntity saved = animalRepository.save(entity); +// return saved.toDto(); +// } +// +// @Override +// @Transactional +// public void remove(Long id) { +// AnimalEntity getAnimal = +// animalRepository +// .getAnimalByUid(id) +// .orElseThrow(() -> new EntityNotFoundException("getAnimal not found with id: " + id)); +// getAnimal.deleted(); +// } +// +// @Override +// public AnimalDto.Basic getOneById(Long id) { +// AnimalEntity getAnimal = +// animalRepository +// .getAnimalByUid(id) +// .orElseThrow(() -> new EntityNotFoundException("Zoo not found with id: " + id)); +// return getAnimal.toDto(); +// } +// +// @Override +// public Page search(AnimalDto.SearchReq searchReq) { +// +// Page animalEntities = animalRepository.listAnimal(searchReq); +// return animalEntities.map(AnimalEntity::toDto); +// } +// } diff --git a/src/main/java/com/kamco/cd/training/postgres/core/AuditLogCoreService.java b/src/main/java/com/kamco/cd/training/postgres/core/AuditLogCoreService.java index 013e267..a2a4473 100644 --- a/src/main/java/com/kamco/cd/training/postgres/core/AuditLogCoreService.java +++ b/src/main/java/com/kamco/cd/training/postgres/core/AuditLogCoreService.java @@ -1,62 +1,62 @@ -package com.kamco.cd.training.postgres.core; - -import com.kamco.cd.training.common.service.BaseCoreService; -import com.kamco.cd.training.log.dto.AuditLogDto; -import com.kamco.cd.training.postgres.repository.log.AuditLogRepository; -import java.time.LocalDate; -import lombok.RequiredArgsConstructor; -import org.springframework.data.domain.Page; -import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Transactional; - -@Service -@RequiredArgsConstructor -@Transactional(readOnly = true) -public class AuditLogCoreService - implements BaseCoreService { - - private final AuditLogRepository auditLogRepository; - - @Override - public void remove(Long aLong) {} - - @Override - public AuditLogDto.DailyAuditList getOneById(Long aLong) { - return null; - } - - @Override - public Page search(AuditLogDto.searchReq searchReq) { - return null; - } - - public Page getLogByDaily( - AuditLogDto.searchReq searchRange, LocalDate startDate, LocalDate endDate) { - return auditLogRepository.findLogByDaily(searchRange, startDate, endDate); - } - - public Page getLogByMenu( - AuditLogDto.searchReq searchRange, String searchValue) { - return auditLogRepository.findLogByMenu(searchRange, searchValue); - } - - public Page getLogByAccount( - AuditLogDto.searchReq searchRange, String searchValue) { - return auditLogRepository.findLogByAccount(searchRange, searchValue); - } - - public Page getLogByDailyResult( - AuditLogDto.searchReq searchRange, LocalDate logDate) { - return auditLogRepository.findLogByDailyResult(searchRange, logDate); - } - - public Page getLogByMenuResult( - AuditLogDto.searchReq searchRange, String menuId) { - return auditLogRepository.findLogByMenuResult(searchRange, menuId); - } - - public Page getLogByAccountResult( - AuditLogDto.searchReq searchRange, Long accountId) { - return auditLogRepository.findLogByAccountResult(searchRange, accountId); - } -} +package com.kamco.cd.training.postgres.core; + +import com.kamco.cd.training.common.service.BaseCoreService; +import com.kamco.cd.training.log.dto.AuditLogDto; +import com.kamco.cd.training.postgres.repository.log.AuditLogRepository; +import java.time.LocalDate; +import lombok.RequiredArgsConstructor; +import org.springframework.data.domain.Page; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +@Service +@RequiredArgsConstructor +@Transactional(readOnly = true) +public class AuditLogCoreService + implements BaseCoreService { + + private final AuditLogRepository auditLogRepository; + + @Override + public void remove(Long aLong) {} + + @Override + public AuditLogDto.DailyAuditList getOneById(Long aLong) { + return null; + } + + @Override + public Page search(AuditLogDto.searchReq searchReq) { + return null; + } + + public Page getLogByDaily( + AuditLogDto.searchReq searchRange, LocalDate startDate, LocalDate endDate) { + return auditLogRepository.findLogByDaily(searchRange, startDate, endDate); + } + + public Page getLogByMenu( + AuditLogDto.searchReq searchRange, String searchValue) { + return auditLogRepository.findLogByMenu(searchRange, searchValue); + } + + public Page getLogByAccount( + AuditLogDto.searchReq searchRange, String searchValue) { + return auditLogRepository.findLogByAccount(searchRange, searchValue); + } + + public Page getLogByDailyResult( + AuditLogDto.searchReq searchRange, LocalDate logDate) { + return auditLogRepository.findLogByDailyResult(searchRange, logDate); + } + + public Page getLogByMenuResult( + AuditLogDto.searchReq searchRange, String menuId) { + return auditLogRepository.findLogByMenuResult(searchRange, menuId); + } + + public Page getLogByAccountResult( + AuditLogDto.searchReq searchRange, Long accountId) { + return auditLogRepository.findLogByAccountResult(searchRange, accountId); + } +} diff --git a/src/main/java/com/kamco/cd/training/postgres/core/CommonCodeCoreService.java b/src/main/java/com/kamco/cd/training/postgres/core/CommonCodeCoreService.java index 9709e64..e71d893 100644 --- a/src/main/java/com/kamco/cd/training/postgres/core/CommonCodeCoreService.java +++ b/src/main/java/com/kamco/cd/training/postgres/core/CommonCodeCoreService.java @@ -1,217 +1,217 @@ -package com.kamco.cd.training.postgres.core; - -import com.kamco.cd.training.code.dto.CommonCodeDto; -import com.kamco.cd.training.code.dto.CommonCodeDto.Basic; -import com.kamco.cd.training.code.dto.CommonCodeDto.SearchReq; -import com.kamco.cd.training.common.service.BaseCoreService; -import com.kamco.cd.training.config.api.ApiResponseDto.ApiResponseCode; -import com.kamco.cd.training.config.api.ApiResponseDto.ResponseObj; -import com.kamco.cd.training.postgres.entity.CommonCodeEntity; -import com.kamco.cd.training.postgres.repository.code.CommonCodeRepository; -import jakarta.persistence.EntityNotFoundException; -import java.util.List; -import java.util.Optional; -import lombok.RequiredArgsConstructor; -import org.springframework.data.domain.Page; -import org.springframework.stereotype.Service; - -@Service -@RequiredArgsConstructor -public class CommonCodeCoreService - implements BaseCoreService { - - private final CommonCodeRepository commonCodeRepository; - - /** - * 모두 조회 - * - * @return - */ - // @Cacheable(value = "commonCodes") - public List findAll() { - return commonCodeRepository.findByAll().stream().map(CommonCodeEntity::toDto).toList(); - } - - /** - * 등록 - * - * @param req - * @return - */ - // @CacheEvict(value = "commonCodes", allEntries = true) - public ResponseObj save(CommonCodeDto.AddReq req) { - - String regex = "^[A-Z0-9]+(_[A-Z0-9]+)*$"; - boolean isValid = req.getCode().matches(regex); - if (!isValid) { - return new ResponseObj(ApiResponseCode.CONFLICT, "공통코드에 영문 대문자, 숫자, 언더바(_)만 입력 가능합니다."); - } - - Long existsCount = - commonCodeRepository.findByParentIdCodeExists(req.getParentId(), req.getCode()); - if (existsCount > 0) { - return new ResponseObj(ApiResponseCode.DUPLICATE_DATA, "이미 사용중인 공통코드ID 입니다."); - } - - CommonCodeEntity entity = - new CommonCodeEntity( - req.getCode(), - req.getName(), - req.getDescription(), - req.getOrder(), - req.isUsed(), - req.getProps1(), - req.getProps2(), - req.getProps3()); - - if (req.getParentId() != null) { - CommonCodeEntity parentCommonCodeEntity = - commonCodeRepository - .findById(req.getParentId()) - .orElseThrow( - () -> - new EntityNotFoundException( - "parent id 를 찾을 수 없습니다. id : " + req.getParentId())); - - entity.addParent(parentCommonCodeEntity); - } else { - entity.addParent(null); - } - commonCodeRepository.save(entity).toDto(); - - return new ResponseObj(ApiResponseCode.OK, ""); - } - - /** - * 수정 - * - * @param id - * @param req - * @return - */ - // @CacheEvict(value = "commonCodes", allEntries = true) - public ResponseObj update(Long id, CommonCodeDto.ModifyReq req) { - CommonCodeEntity found = - commonCodeRepository - .findByCodeId(id) - .orElseThrow(() -> new EntityNotFoundException("common code 를 찾을 수 없습니다. id : " + id)); - - found.update( - req.getName(), - req.getDescription(), - req.isUsed(), - req.getProps1(), - req.getProps2(), - req.getProps3()); - - return new ResponseObj(ApiResponseCode.OK, ""); - } - - /** - * 순서 변경 - * - * @param req - * @return - */ - // @CacheEvict(value = "commonCodes", allEntries = true) - public ResponseObj updateOrder(CommonCodeDto.OrderReq req) { - - CommonCodeEntity found = - commonCodeRepository - .findByCodeId(req.getId()) - .orElseThrow( - () -> new EntityNotFoundException("common code 를 찾을 수 없습니다. id : " + req.getId())); - - found.updateOrder(req.getOrder()); - - return new ResponseObj(ApiResponseCode.OK, ""); - } - - public List findByCode(String code) { - return commonCodeRepository.findByCode(code).stream().map(CommonCodeEntity::toDto).toList(); - } - - /** - * 공통코드 이름 조회 - * - * @param parentCodeCd - * @param childCodeCd - * @return - */ - public Optional getCode(String parentCodeCd, String childCodeCd) { - return commonCodeRepository.getCode(parentCodeCd, childCodeCd); - } - - /** - * 공통코드 삭제 - * - * @param id - * @return - */ - // @CacheEvict(value = "commonCodes", allEntries = true) - public ResponseObj removeCode(Long id) { - CommonCodeEntity entity = - commonCodeRepository - .findByCodeId(id) - .orElseThrow(() -> new EntityNotFoundException("code를 찾을 수 없습니다. id " + id)); - - // 하위코드가 있으면 삭제 불가 - if (!entity.getChildren().isEmpty()) { - return new ResponseObj( - ApiResponseCode.UNPROCESSABLE_ENTITY, - "하위에 다른 공통코드를 가지고 있습니다.
하위공통 코드를 이동한 후 삭제할 수 있습니다."); - } - - // id 코드 deleted = false 업데이트 - entity.deleted(); - return new ResponseObj(ApiResponseCode.OK, ""); - } - - @Override - public void remove(Long aLong) { - // 미사용 - } - - /** - * id 로 단건 조회 - * - * @param id - * @return - */ - @Override - public Basic getOneById(Long id) { - CommonCodeEntity entity = - commonCodeRepository - .findByCodeId(id) - .orElseThrow(() -> new EntityNotFoundException("code를 찾을 수 없습니다. id " + id)); - return entity.toDto(); - } - - @Override - public Page search(SearchReq searchReq) { - return null; - } - - /** - * 중복 체크 - * - * @param parentId - * @param code - * @return - */ - public ResponseObj getCodeCheckDuplicate(Long parentId, String code) { - Long existsCount = commonCodeRepository.findByParentIdCodeExists(parentId, code); - - String regex = "^[A-Z0-9]+(_[A-Z0-9]+)*$"; - boolean isValid = code.matches(regex); - if (!isValid) { - return new ResponseObj(ApiResponseCode.CONFLICT, "공통코드에 영문 대문자, 숫자, 언더바(_)만 입력 가능합니다."); - } - - if (existsCount > 0) { - return new ResponseObj(ApiResponseCode.DUPLICATE_DATA, "이미 사용중인 공통코드ID 입니다."); - } - - return new ResponseObj(ApiResponseCode.OK, ""); - } -} +package com.kamco.cd.training.postgres.core; + +import com.kamco.cd.training.code.dto.CommonCodeDto; +import com.kamco.cd.training.code.dto.CommonCodeDto.Basic; +import com.kamco.cd.training.code.dto.CommonCodeDto.SearchReq; +import com.kamco.cd.training.common.service.BaseCoreService; +import com.kamco.cd.training.config.api.ApiResponseDto.ApiResponseCode; +import com.kamco.cd.training.config.api.ApiResponseDto.ResponseObj; +import com.kamco.cd.training.postgres.entity.CommonCodeEntity; +import com.kamco.cd.training.postgres.repository.code.CommonCodeRepository; +import jakarta.persistence.EntityNotFoundException; +import java.util.List; +import java.util.Optional; +import lombok.RequiredArgsConstructor; +import org.springframework.data.domain.Page; +import org.springframework.stereotype.Service; + +@Service +@RequiredArgsConstructor +public class CommonCodeCoreService + implements BaseCoreService { + + private final CommonCodeRepository commonCodeRepository; + + /** + * 모두 조회 + * + * @return + */ + // @Cacheable(value = "commonCodes") + public List findAll() { + return commonCodeRepository.findByAll().stream().map(CommonCodeEntity::toDto).toList(); + } + + /** + * 등록 + * + * @param req + * @return + */ + // @CacheEvict(value = "commonCodes", allEntries = true) + public ResponseObj save(CommonCodeDto.AddReq req) { + + String regex = "^[A-Z0-9]+(_[A-Z0-9]+)*$"; + boolean isValid = req.getCode().matches(regex); + if (!isValid) { + return new ResponseObj(ApiResponseCode.CONFLICT, "공통코드에 영문 대문자, 숫자, 언더바(_)만 입력 가능합니다."); + } + + Long existsCount = + commonCodeRepository.findByParentIdCodeExists(req.getParentId(), req.getCode()); + if (existsCount > 0) { + return new ResponseObj(ApiResponseCode.DUPLICATE_DATA, "이미 사용중인 공통코드ID 입니다."); + } + + CommonCodeEntity entity = + new CommonCodeEntity( + req.getCode(), + req.getName(), + req.getDescription(), + req.getOrder(), + req.isUsed(), + req.getProps1(), + req.getProps2(), + req.getProps3()); + + if (req.getParentId() != null) { + CommonCodeEntity parentCommonCodeEntity = + commonCodeRepository + .findById(req.getParentId()) + .orElseThrow( + () -> + new EntityNotFoundException( + "parent id 를 찾을 수 없습니다. id : " + req.getParentId())); + + entity.addParent(parentCommonCodeEntity); + } else { + entity.addParent(null); + } + commonCodeRepository.save(entity).toDto(); + + return new ResponseObj(ApiResponseCode.OK, ""); + } + + /** + * 수정 + * + * @param id + * @param req + * @return + */ + // @CacheEvict(value = "commonCodes", allEntries = true) + public ResponseObj update(Long id, CommonCodeDto.ModifyReq req) { + CommonCodeEntity found = + commonCodeRepository + .findByCodeId(id) + .orElseThrow(() -> new EntityNotFoundException("common code 를 찾을 수 없습니다. id : " + id)); + + found.update( + req.getName(), + req.getDescription(), + req.isUsed(), + req.getProps1(), + req.getProps2(), + req.getProps3()); + + return new ResponseObj(ApiResponseCode.OK, ""); + } + + /** + * 순서 변경 + * + * @param req + * @return + */ + // @CacheEvict(value = "commonCodes", allEntries = true) + public ResponseObj updateOrder(CommonCodeDto.OrderReq req) { + + CommonCodeEntity found = + commonCodeRepository + .findByCodeId(req.getId()) + .orElseThrow( + () -> new EntityNotFoundException("common code 를 찾을 수 없습니다. id : " + req.getId())); + + found.updateOrder(req.getOrder()); + + return new ResponseObj(ApiResponseCode.OK, ""); + } + + public List findByCode(String code) { + return commonCodeRepository.findByCode(code).stream().map(CommonCodeEntity::toDto).toList(); + } + + /** + * 공통코드 이름 조회 + * + * @param parentCodeCd + * @param childCodeCd + * @return + */ + public Optional getCode(String parentCodeCd, String childCodeCd) { + return commonCodeRepository.getCode(parentCodeCd, childCodeCd); + } + + /** + * 공통코드 삭제 + * + * @param id + * @return + */ + // @CacheEvict(value = "commonCodes", allEntries = true) + public ResponseObj removeCode(Long id) { + CommonCodeEntity entity = + commonCodeRepository + .findByCodeId(id) + .orElseThrow(() -> new EntityNotFoundException("code를 찾을 수 없습니다. id " + id)); + + // 하위코드가 있으면 삭제 불가 + if (!entity.getChildren().isEmpty()) { + return new ResponseObj( + ApiResponseCode.UNPROCESSABLE_ENTITY, + "하위에 다른 공통코드를 가지고 있습니다.
하위공통 코드를 이동한 후 삭제할 수 있습니다."); + } + + // id 코드 deleted = false 업데이트 + entity.deleted(); + return new ResponseObj(ApiResponseCode.OK, ""); + } + + @Override + public void remove(Long aLong) { + // 미사용 + } + + /** + * id 로 단건 조회 + * + * @param id + * @return + */ + @Override + public Basic getOneById(Long id) { + CommonCodeEntity entity = + commonCodeRepository + .findByCodeId(id) + .orElseThrow(() -> new EntityNotFoundException("code를 찾을 수 없습니다. id " + id)); + return entity.toDto(); + } + + @Override + public Page search(SearchReq searchReq) { + return null; + } + + /** + * 중복 체크 + * + * @param parentId + * @param code + * @return + */ + public ResponseObj getCodeCheckDuplicate(Long parentId, String code) { + Long existsCount = commonCodeRepository.findByParentIdCodeExists(parentId, code); + + String regex = "^[A-Z0-9]+(_[A-Z0-9]+)*$"; + boolean isValid = code.matches(regex); + if (!isValid) { + return new ResponseObj(ApiResponseCode.CONFLICT, "공통코드에 영문 대문자, 숫자, 언더바(_)만 입력 가능합니다."); + } + + if (existsCount > 0) { + return new ResponseObj(ApiResponseCode.DUPLICATE_DATA, "이미 사용중인 공통코드ID 입니다."); + } + + return new ResponseObj(ApiResponseCode.OK, ""); + } +} diff --git a/src/main/java/com/kamco/cd/training/postgres/core/DatasetCoreService.java b/src/main/java/com/kamco/cd/training/postgres/core/DatasetCoreService.java index ae3329e..19be62a 100644 --- a/src/main/java/com/kamco/cd/training/postgres/core/DatasetCoreService.java +++ b/src/main/java/com/kamco/cd/training/postgres/core/DatasetCoreService.java @@ -1,276 +1,276 @@ -package com.kamco.cd.training.postgres.core; - -import com.fasterxml.jackson.core.type.TypeReference; -import com.fasterxml.jackson.databind.ObjectMapper; -import com.kamco.cd.training.common.exception.NotFoundException; -import com.kamco.cd.training.common.service.BaseCoreService; -import com.kamco.cd.training.dataset.dto.DatasetDto; -import com.kamco.cd.training.model.dto.ModelMngDto; -import com.kamco.cd.training.postgres.entity.DatasetEntity; -import com.kamco.cd.training.postgres.repository.dataset.DatasetRepository; -import java.time.ZonedDateTime; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.UUID; -import lombok.RequiredArgsConstructor; -import lombok.extern.slf4j.Slf4j; -import org.apache.commons.lang3.StringUtils; -import org.springframework.data.domain.Page; -import org.springframework.stereotype.Service; - -@Service -@RequiredArgsConstructor -@Slf4j -public class DatasetCoreService - implements BaseCoreService { - private final DatasetRepository datasetRepository; - private final ObjectMapper objectMapper; - - /** - * 학습 데이터 삭제 - * - * @param id 데이터셋 ID - */ - @Override - public void remove(Long id) { - DatasetEntity entity = - datasetRepository - .findById(id) - .orElseThrow(() -> new NotFoundException("데이터셋을 찾을 수 없습니다. ID: " + id)); - entity.setDeleted(true); - datasetRepository.save(entity); - } - - public void remove(UUID id) { - DatasetEntity entity = - datasetRepository - .findByUuid(id) - .orElseThrow(() -> new NotFoundException("데이터셋을 찾을 수 없습니다. ID: " + id)); - entity.setDeleted(true); - datasetRepository.save(entity); - } - - @Override - public DatasetDto.Basic getOneById(Long id) { - DatasetEntity entity = - datasetRepository - .findById(id) - .orElseThrow(() -> new NotFoundException("데이터셋을 찾을 수 없습니다. ID: " + id)); - - if (entity.getDeleted()) { - throw new NotFoundException("삭제된 데이터셋입니다. ID: " + id); - } - - return entity.toDto(); - } - - public DatasetDto.Basic getOneByUuid(UUID id) { - DatasetEntity entity = - datasetRepository - .findByUuid(id) - .orElseThrow(() -> new NotFoundException("데이터셋을 찾을 수 없습니다. uuid: " + id)); - return entity.toDto(); - } - - @Override - public Page search(DatasetDto.SearchReq searchReq) { - Page entityPage = datasetRepository.findDatasetList(searchReq); - return entityPage.map(DatasetEntity::toDto); - } - - /** - * 학습데이터 조회 - * - * @param searchReq 검색 조건 - * @return 페이징 처리된 데이터셋 목록 - */ - public Page findDatasetList(DatasetDto.SearchReq searchReq) { - return search(searchReq); - } - - /** - * 학습데이터 등록 - * - * @param registerReq 등록 요청 데이터 - * @return 등록된 데이터셋 정보 - */ - public DatasetDto.Basic save(DatasetDto.RegisterReq registerReq) { - // 먼저 id1 필드를 임시값(0)으로 설정하여 저장 - DatasetEntity entity = new DatasetEntity(); - entity.setTitle(registerReq.getTitle()); - entity.setYear(registerReq.getYear()); - entity.setGroupTitle("PRODUCTION"); - entity.setDataYear(registerReq.getYear()); - entity.setRoundNo(registerReq.getRoundNo() != null ? registerReq.getRoundNo() : 1L); - entity.setMemo(registerReq.getMemo()); - entity.setStatus("READY"); - entity.setDataType("CREATE"); - entity.setTotalItems(0L); - entity.setTotalSize(0L); - entity.setItemCount(0L); - entity.setDeleted(false); - entity.setCreatedDttm(ZonedDateTime.now()); - entity.setId1(0L); // 임시값 - - DatasetEntity savedEntity = datasetRepository.save(entity); - - // 저장 후 id1을 dataset_uid와 동일하게 업데이트 - savedEntity.setId1(savedEntity.getId()); - savedEntity = datasetRepository.save(savedEntity); - - return savedEntity.toDto(); - } - - /** - * 학습 데이터 수정 - * - * @param updateReq 수정 요청 데이터 - * @return 수정된 데이터셋 정보 - */ - public void update(UUID uuid, DatasetDto.UpdateReq updateReq) { - DatasetEntity entity = - datasetRepository - .findByUuid(uuid) - .orElseThrow(() -> new NotFoundException("데이터셋을 찾을 수 없습니다. uuid: " + uuid)); - - if (StringUtils.isNotBlank(updateReq.getTitle())) { - entity.setTitle(updateReq.getTitle()); - } - if (StringUtils.isNotBlank(updateReq.getMemo())) { - entity.setMemo(updateReq.getMemo()); - } - - datasetRepository.save(entity); - } - - /** - * 학습데이터 삭제 - * - * @param uuid 삭제 요청 (데이터셋 ID 목록) - */ - public void deleteDatasets(UUID uuid) { - remove(uuid); - } - - public DatasetDto.Summary getDatasetSummary(DatasetDto.SummaryReq summaryReq) { - long totalMapSheets = 0; - long totalFileSize = 0; - - for (Long datasetId : summaryReq.getDatasetIds()) { - DatasetEntity entity = - datasetRepository - .findById(datasetId) - .orElseThrow(() -> new NotFoundException("데이터셋을 찾을 수 없습니다. ID: " + datasetId)); - - if (!entity.getDeleted()) { - totalMapSheets += entity.getTotalItems() != null ? entity.getTotalItems() : 0; - totalFileSize += entity.getTotalSize() != null ? entity.getTotalSize() : 0; - } - } - - double averageMapSheets = - !summaryReq.getDatasetIds().isEmpty() - ? (double) totalMapSheets / summaryReq.getDatasetIds().size() - : 0; - - return new DatasetDto.Summary( - summaryReq.getDatasetIds().size(), totalMapSheets, totalFileSize, averageMapSheets); - } - - /** - * 활성 데이터셋 전체 조회 (학습 관리용) - * - * @return 데이터셋 정보 목록 - */ - public List findAllActiveDatasetsForTraining() { - List entities = datasetRepository.findByDeletedOrderByCreatedDttmDesc(false); - - return entities.stream() - .map( - entity -> { - // totalSize를 읽기 쉬운 형식으로 변환 - String totalSizeStr = formatFileSize(entity.getTotalSize()); - - // classCounts JSON 파싱 - Map classCounts = entity.getClassCounts(); - - return ModelMngDto.DatasetInfo.builder() - .id(entity.getId()) - .title(entity.getTitle()) - .groupTitle(entity.getGroupTitle()) - .totalItems(entity.getTotalItems()) - .totalSize(totalSizeStr) - .classCounts(classCounts) - .memo(entity.getMemo()) - .createdDttm(entity.getCreatedDttm()) - .build(); - }) - .toList(); - } - - /** - * JSON 문자열을 Map으로 파싱 - * - * @param jsonStr JSON 문자열 - * @return 클래스별 카운트 맵 - */ - private Map parseClassCounts(String jsonStr) { - if (jsonStr == null || jsonStr.trim().isEmpty()) { - return new HashMap<>(); - } - - try { - return objectMapper.readValue(jsonStr, new TypeReference>() {}); - } catch (Exception e) { - log.warn("클래스 통계 JSON 파싱 실패: {}", jsonStr, e); - return new HashMap<>(); - } - } - - /** - * 데이터셋의 클래스 통계 계산 및 저장 - * - * @param datasetId 데이터셋 ID - * @param classCounts 클래스별 카운트 - */ - public void updateClassCounts(Long datasetId, Map classCounts) { - DatasetEntity entity = - datasetRepository - .findById(datasetId) - .orElseThrow(() -> new NotFoundException("데이터셋을 찾을 수 없습니다. ID: " + datasetId)); - - try { - entity.setClassCounts(classCounts); - datasetRepository.save(entity); - log.info("데이터셋 클래스 통계 업데이트 완료: datasetId={}, classes={}", datasetId, classCounts.keySet()); - } catch (Exception e) { - log.error("클래스 통계 JSON 변환 실패: datasetId={}", datasetId, e); - } - } - - /** - * 파일 크기를 읽기 쉬운 형식으로 변환 - * - * @param size 바이트 단위 크기 - * @return 형식화된 문자열 (예: "1.5GB") - */ - private String formatFileSize(Long size) { - if (size == null || size == 0) { - return "0 GB"; - } - - double gb = size / (1024.0 * 1024.0 * 1024.0); - if (gb >= 1.0) { - return String.format("%.2f GB", gb); - } - - double mb = size / (1024.0 * 1024.0); - if (mb >= 1.0) { - return String.format("%.2f MB", mb); - } - - double kb = size / 1024.0; - return String.format("%.2f KB", kb); - } -} +package com.kamco.cd.training.postgres.core; + +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.kamco.cd.training.common.exception.NotFoundException; +import com.kamco.cd.training.common.service.BaseCoreService; +import com.kamco.cd.training.dataset.dto.DatasetDto; +import com.kamco.cd.training.model.dto.ModelMngDto; +import com.kamco.cd.training.postgres.entity.DatasetEntity; +import com.kamco.cd.training.postgres.repository.dataset.DatasetRepository; +import java.time.ZonedDateTime; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.UUID; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang3.StringUtils; +import org.springframework.data.domain.Page; +import org.springframework.stereotype.Service; + +@Service +@RequiredArgsConstructor +@Slf4j +public class DatasetCoreService + implements BaseCoreService { + private final DatasetRepository datasetRepository; + private final ObjectMapper objectMapper; + + /** + * 학습 데이터 삭제 + * + * @param id 데이터셋 ID + */ + @Override + public void remove(Long id) { + DatasetEntity entity = + datasetRepository + .findById(id) + .orElseThrow(() -> new NotFoundException("데이터셋을 찾을 수 없습니다. ID: " + id)); + entity.setDeleted(true); + datasetRepository.save(entity); + } + + public void remove(UUID id) { + DatasetEntity entity = + datasetRepository + .findByUuid(id) + .orElseThrow(() -> new NotFoundException("데이터셋을 찾을 수 없습니다. ID: " + id)); + entity.setDeleted(true); + datasetRepository.save(entity); + } + + @Override + public DatasetDto.Basic getOneById(Long id) { + DatasetEntity entity = + datasetRepository + .findById(id) + .orElseThrow(() -> new NotFoundException("데이터셋을 찾을 수 없습니다. ID: " + id)); + + if (entity.getDeleted()) { + throw new NotFoundException("삭제된 데이터셋입니다. ID: " + id); + } + + return entity.toDto(); + } + + public DatasetDto.Basic getOneByUuid(UUID id) { + DatasetEntity entity = + datasetRepository + .findByUuid(id) + .orElseThrow(() -> new NotFoundException("데이터셋을 찾을 수 없습니다. uuid: " + id)); + return entity.toDto(); + } + + @Override + public Page search(DatasetDto.SearchReq searchReq) { + Page entityPage = datasetRepository.findDatasetList(searchReq); + return entityPage.map(DatasetEntity::toDto); + } + + /** + * 학습데이터 조회 + * + * @param searchReq 검색 조건 + * @return 페이징 처리된 데이터셋 목록 + */ + public Page findDatasetList(DatasetDto.SearchReq searchReq) { + return search(searchReq); + } + + /** + * 학습데이터 등록 + * + * @param registerReq 등록 요청 데이터 + * @return 등록된 데이터셋 정보 + */ + public DatasetDto.Basic save(DatasetDto.RegisterReq registerReq) { + // 먼저 id1 필드를 임시값(0)으로 설정하여 저장 + DatasetEntity entity = new DatasetEntity(); + entity.setTitle(registerReq.getTitle()); + entity.setYear(registerReq.getYear()); + entity.setGroupTitle("PRODUCTION"); + entity.setDataYear(registerReq.getYear()); + entity.setRoundNo(registerReq.getRoundNo() != null ? registerReq.getRoundNo() : 1L); + entity.setMemo(registerReq.getMemo()); + entity.setStatus("READY"); + entity.setDataType("CREATE"); + entity.setTotalItems(0L); + entity.setTotalSize(0L); + entity.setItemCount(0L); + entity.setDeleted(false); + entity.setCreatedDttm(ZonedDateTime.now()); + entity.setId1(0L); // 임시값 + + DatasetEntity savedEntity = datasetRepository.save(entity); + + // 저장 후 id1을 dataset_uid와 동일하게 업데이트 + savedEntity.setId1(savedEntity.getId()); + savedEntity = datasetRepository.save(savedEntity); + + return savedEntity.toDto(); + } + + /** + * 학습 데이터 수정 + * + * @param updateReq 수정 요청 데이터 + * @return 수정된 데이터셋 정보 + */ + public void update(UUID uuid, DatasetDto.UpdateReq updateReq) { + DatasetEntity entity = + datasetRepository + .findByUuid(uuid) + .orElseThrow(() -> new NotFoundException("데이터셋을 찾을 수 없습니다. uuid: " + uuid)); + + if (StringUtils.isNotBlank(updateReq.getTitle())) { + entity.setTitle(updateReq.getTitle()); + } + if (StringUtils.isNotBlank(updateReq.getMemo())) { + entity.setMemo(updateReq.getMemo()); + } + + datasetRepository.save(entity); + } + + /** + * 학습데이터 삭제 + * + * @param uuid 삭제 요청 (데이터셋 ID 목록) + */ + public void deleteDatasets(UUID uuid) { + remove(uuid); + } + + public DatasetDto.Summary getDatasetSummary(DatasetDto.SummaryReq summaryReq) { + long totalMapSheets = 0; + long totalFileSize = 0; + + for (Long datasetId : summaryReq.getDatasetIds()) { + DatasetEntity entity = + datasetRepository + .findById(datasetId) + .orElseThrow(() -> new NotFoundException("데이터셋을 찾을 수 없습니다. ID: " + datasetId)); + + if (!entity.getDeleted()) { + totalMapSheets += entity.getTotalItems() != null ? entity.getTotalItems() : 0; + totalFileSize += entity.getTotalSize() != null ? entity.getTotalSize() : 0; + } + } + + double averageMapSheets = + !summaryReq.getDatasetIds().isEmpty() + ? (double) totalMapSheets / summaryReq.getDatasetIds().size() + : 0; + + return new DatasetDto.Summary( + summaryReq.getDatasetIds().size(), totalMapSheets, totalFileSize, averageMapSheets); + } + + /** + * 활성 데이터셋 전체 조회 (학습 관리용) + * + * @return 데이터셋 정보 목록 + */ + public List findAllActiveDatasetsForTraining() { + List entities = datasetRepository.findByDeletedOrderByCreatedDttmDesc(false); + + return entities.stream() + .map( + entity -> { + // totalSize를 읽기 쉬운 형식으로 변환 + String totalSizeStr = formatFileSize(entity.getTotalSize()); + + // classCounts JSON 파싱 + Map classCounts = entity.getClassCounts(); + + return ModelMngDto.DatasetInfo.builder() + .id(entity.getId()) + .title(entity.getTitle()) + .groupTitle(entity.getGroupTitle()) + .totalItems(entity.getTotalItems()) + .totalSize(totalSizeStr) + .classCounts(classCounts) + .memo(entity.getMemo()) + .createdDttm(entity.getCreatedDttm()) + .build(); + }) + .toList(); + } + + /** + * JSON 문자열을 Map으로 파싱 + * + * @param jsonStr JSON 문자열 + * @return 클래스별 카운트 맵 + */ + private Map parseClassCounts(String jsonStr) { + if (jsonStr == null || jsonStr.trim().isEmpty()) { + return new HashMap<>(); + } + + try { + return objectMapper.readValue(jsonStr, new TypeReference>() {}); + } catch (Exception e) { + log.warn("클래스 통계 JSON 파싱 실패: {}", jsonStr, e); + return new HashMap<>(); + } + } + + /** + * 데이터셋의 클래스 통계 계산 및 저장 + * + * @param datasetId 데이터셋 ID + * @param classCounts 클래스별 카운트 + */ + public void updateClassCounts(Long datasetId, Map classCounts) { + DatasetEntity entity = + datasetRepository + .findById(datasetId) + .orElseThrow(() -> new NotFoundException("데이터셋을 찾을 수 없습니다. ID: " + datasetId)); + + try { + entity.setClassCounts(classCounts); + datasetRepository.save(entity); + log.info("데이터셋 클래스 통계 업데이트 완료: datasetId={}, classes={}", datasetId, classCounts.keySet()); + } catch (Exception e) { + log.error("클래스 통계 JSON 변환 실패: datasetId={}", datasetId, e); + } + } + + /** + * 파일 크기를 읽기 쉬운 형식으로 변환 + * + * @param size 바이트 단위 크기 + * @return 형식화된 문자열 (예: "1.5GB") + */ + private String formatFileSize(Long size) { + if (size == null || size == 0) { + return "0 GB"; + } + + double gb = size / (1024.0 * 1024.0 * 1024.0); + if (gb >= 1.0) { + return String.format("%.2f GB", gb); + } + + double mb = size / (1024.0 * 1024.0); + if (mb >= 1.0) { + return String.format("%.2f MB", mb); + } + + double kb = size / 1024.0; + return String.format("%.2f KB", kb); + } +} diff --git a/src/main/java/com/kamco/cd/training/postgres/core/ErrorLogCoreService.java b/src/main/java/com/kamco/cd/training/postgres/core/ErrorLogCoreService.java index 968f2ec..ac95744 100644 --- a/src/main/java/com/kamco/cd/training/postgres/core/ErrorLogCoreService.java +++ b/src/main/java/com/kamco/cd/training/postgres/core/ErrorLogCoreService.java @@ -1,35 +1,35 @@ -package com.kamco.cd.training.postgres.core; - -import com.kamco.cd.training.common.service.BaseCoreService; -import com.kamco.cd.training.log.dto.ErrorLogDto; -import com.kamco.cd.training.postgres.repository.log.ErrorLogRepository; -import lombok.RequiredArgsConstructor; -import org.springframework.data.domain.Page; -import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Transactional; - -@Service -@RequiredArgsConstructor -@Transactional(readOnly = true) -public class ErrorLogCoreService - implements BaseCoreService { - - private final ErrorLogRepository errorLogRepository; - - public Page findLogByError(ErrorLogDto.ErrorSearchReq searchReq) { - return errorLogRepository.findLogByError(searchReq); - } - - @Override - public void remove(Long aLong) {} - - @Override - public ErrorLogDto.Basic getOneById(Long aLong) { - return null; - } - - @Override - public Page search(ErrorLogDto.ErrorSearchReq searchReq) { - return null; - } -} +package com.kamco.cd.training.postgres.core; + +import com.kamco.cd.training.common.service.BaseCoreService; +import com.kamco.cd.training.log.dto.ErrorLogDto; +import com.kamco.cd.training.postgres.repository.log.ErrorLogRepository; +import lombok.RequiredArgsConstructor; +import org.springframework.data.domain.Page; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +@Service +@RequiredArgsConstructor +@Transactional(readOnly = true) +public class ErrorLogCoreService + implements BaseCoreService { + + private final ErrorLogRepository errorLogRepository; + + public Page findLogByError(ErrorLogDto.ErrorSearchReq searchReq) { + return errorLogRepository.findLogByError(searchReq); + } + + @Override + public void remove(Long aLong) {} + + @Override + public ErrorLogDto.Basic getOneById(Long aLong) { + return null; + } + + @Override + public Page search(ErrorLogDto.ErrorSearchReq searchReq) { + return null; + } +} diff --git a/src/main/java/com/kamco/cd/training/postgres/core/HyperParamCoreService.java b/src/main/java/com/kamco/cd/training/postgres/core/HyperParamCoreService.java index ebc72c8..eecd67b 100644 --- a/src/main/java/com/kamco/cd/training/postgres/core/HyperParamCoreService.java +++ b/src/main/java/com/kamco/cd/training/postgres/core/HyperParamCoreService.java @@ -1,337 +1,337 @@ -package com.kamco.cd.training.postgres.core; - -import com.kamco.cd.training.common.exception.BadRequestException; -import com.kamco.cd.training.common.exception.NotFoundException; -import com.kamco.cd.training.model.dto.ModelMngDto; -import com.kamco.cd.training.postgres.entity.ModelHyperParamEntity; -import com.kamco.cd.training.postgres.repository.model.ModelHyperParamRepository; -import java.time.ZonedDateTime; -import java.util.List; -import lombok.RequiredArgsConstructor; -import org.springframework.stereotype.Service; - -@Service -@RequiredArgsConstructor -public class HyperParamCoreService { - private final ModelHyperParamRepository hyperParamRepository; - - /** - * 하이퍼파라미터 전체 조회 (삭제되지 않은 것만) - * - * @return 하이퍼파라미터 목록 - */ - public List findAllActiveHyperParams() { - List entities = - hyperParamRepository.findByDelYnOrderByCreatedDttmDesc("N"); - - return entities.stream().map(this::mapToHyperParamInfo).toList(); - } - - private ModelMngDto.HyperParamInfo mapToHyperParamInfo(ModelHyperParamEntity entity) { - return ModelMngDto.HyperParamInfo.builder() - .hyperVer(entity.getHyperVer()) - // Important - .backbone(entity.getBackbone()) - .inputSize(entity.getInputSize()) - .cropSize(entity.getCropSize()) - .epochCnt(entity.getEpochCnt()) - .batchSize(entity.getBatchSize()) - // Architecture - .dropPathRate(entity.getDropPathRate()) - .frozenStages(entity.getFrozenStages()) - .neckPolicy(entity.getNeckPolicy()) - .decoderChannels(entity.getDecoderChannels()) - .classWeight(entity.getClassWeight()) - .numLayers(entity.getNumLayers()) - // Optimization - .learningRate(entity.getLearningRate()) - .weightDecay(entity.getWeightDecay()) - .layerDecayRate(entity.getLayerDecayRate()) - .ddpFindUnusedParams(entity.getDdpFindUnusedParams()) - .ignoreIndex(entity.getIgnoreIndex()) - // Data - .trainNumWorkers(entity.getTrainNumWorkers()) - .valNumWorkers(entity.getValNumWorkers()) - .testNumWorkers(entity.getTestNumWorkers()) - .trainShuffle(entity.getTrainShuffle()) - .trainPersistent(entity.getTrainPersistent()) - .valPersistent(entity.getValPersistent()) - // Evaluation - .metrics(entity.getMetrics()) - .saveBest(entity.getSaveBest()) - .saveBestRule(entity.getSaveBestRule()) - .valInterval(entity.getValInterval()) - .logInterval(entity.getLogInterval()) - .visInterval(entity.getVisInterval()) - // Hardware - .gpuCnt(entity.getGpuCnt()) - .gpuIds(entity.getGpuIds()) - .masterPort(entity.getMasterPort()) - // Augmentation - .rotProb(entity.getRotProb()) - .flipProb(entity.getFlipProb()) - .rotDegree(entity.getRotDegree()) - .exchangeProb(entity.getExchangeProb()) - .brightnessDelta(entity.getBrightnessDelta()) - .contrastRange(entity.getContrastRange()) - .saturationRange(entity.getSaturationRange()) - .hueDelta(entity.getHueDelta()) - // Legacy - .dropoutRatio(entity.getDropoutRatio()) - .cnnFilterCnt(entity.getCnnFilterCnt()) - // Common - .memo(entity.getMemo()) - .createdDttm(entity.getCreatedDttm()) - .build(); - } - - /** - * 하이퍼파라미터 등록 - * - * @param createReq 등록 요청 - * @return 등록된 버전명 - */ - public String createHyperParam(ModelMngDto.HyperParamCreateReq createReq) { - // 중복 체크 - if (hyperParamRepository.existsById(createReq.getNewHyperVer())) { - throw new BadRequestException("이미 존재하는 버전입니다: " + createReq.getNewHyperVer()); - } - - // 기준 버전 조회 - ModelHyperParamEntity baseEntity = - hyperParamRepository - .findById(createReq.getBaseHyperVer()) - .orElseThrow( - () -> new NotFoundException("기준 버전을 찾을 수 없습니다: " + createReq.getBaseHyperVer())); - - // 신규 엔티티 생성 (기준 값 복사 후 변경된 값만 적용) - ModelHyperParamEntity entity = new ModelHyperParamEntity(); - entity.setHyperVer(createReq.getNewHyperVer()); - - // Important - entity.setBackbone( - createReq.getBackbone() != null ? createReq.getBackbone() : baseEntity.getBackbone()); - entity.setInputSize( - createReq.getInputSize() != null ? createReq.getInputSize() : baseEntity.getInputSize()); - entity.setCropSize( - createReq.getCropSize() != null ? createReq.getCropSize() : baseEntity.getCropSize()); - entity.setEpochCnt( - createReq.getEpochCnt() != null ? createReq.getEpochCnt() : baseEntity.getEpochCnt()); - entity.setBatchSize( - createReq.getBatchSize() != null ? createReq.getBatchSize() : baseEntity.getBatchSize()); - - // Architecture - entity.setDropPathRate( - createReq.getDropPathRate() != null - ? createReq.getDropPathRate() - : baseEntity.getDropPathRate()); - entity.setFrozenStages( - createReq.getFrozenStages() != null - ? createReq.getFrozenStages() - : baseEntity.getFrozenStages()); - entity.setNeckPolicy( - createReq.getNeckPolicy() != null ? createReq.getNeckPolicy() : baseEntity.getNeckPolicy()); - entity.setDecoderChannels( - createReq.getDecoderChannels() != null - ? createReq.getDecoderChannels() - : baseEntity.getDecoderChannels()); - entity.setClassWeight( - createReq.getClassWeight() != null - ? createReq.getClassWeight() - : baseEntity.getClassWeight()); - entity.setNumLayers( - createReq.getNumLayers() != null ? createReq.getNumLayers() : baseEntity.getNumLayers()); - - // Optimization - entity.setLearningRate( - createReq.getLearningRate() != null - ? createReq.getLearningRate() - : baseEntity.getLearningRate()); - entity.setWeightDecay( - createReq.getWeightDecay() != null - ? createReq.getWeightDecay() - : baseEntity.getWeightDecay()); - entity.setLayerDecayRate( - createReq.getLayerDecayRate() != null - ? createReq.getLayerDecayRate() - : baseEntity.getLayerDecayRate()); - entity.setDdpFindUnusedParams( - createReq.getDdpFindUnusedParams() != null - ? createReq.getDdpFindUnusedParams() - : baseEntity.getDdpFindUnusedParams()); - entity.setIgnoreIndex( - createReq.getIgnoreIndex() != null - ? createReq.getIgnoreIndex() - : baseEntity.getIgnoreIndex()); - - // Data - entity.setTrainNumWorkers( - createReq.getTrainNumWorkers() != null - ? createReq.getTrainNumWorkers() - : baseEntity.getTrainNumWorkers()); - entity.setValNumWorkers( - createReq.getValNumWorkers() != null - ? createReq.getValNumWorkers() - : baseEntity.getValNumWorkers()); - entity.setTestNumWorkers( - createReq.getTestNumWorkers() != null - ? createReq.getTestNumWorkers() - : baseEntity.getTestNumWorkers()); - entity.setTrainShuffle( - createReq.getTrainShuffle() != null - ? createReq.getTrainShuffle() - : baseEntity.getTrainShuffle()); - entity.setTrainPersistent( - createReq.getTrainPersistent() != null - ? createReq.getTrainPersistent() - : baseEntity.getTrainPersistent()); - entity.setValPersistent( - createReq.getValPersistent() != null - ? createReq.getValPersistent() - : baseEntity.getValPersistent()); - - // Evaluation - entity.setMetrics( - createReq.getMetrics() != null ? createReq.getMetrics() : baseEntity.getMetrics()); - entity.setSaveBest( - createReq.getSaveBest() != null ? createReq.getSaveBest() : baseEntity.getSaveBest()); - entity.setSaveBestRule( - createReq.getSaveBestRule() != null - ? createReq.getSaveBestRule() - : baseEntity.getSaveBestRule()); - entity.setValInterval( - createReq.getValInterval() != null - ? createReq.getValInterval() - : baseEntity.getValInterval()); - entity.setLogInterval( - createReq.getLogInterval() != null - ? createReq.getLogInterval() - : baseEntity.getLogInterval()); - entity.setVisInterval( - createReq.getVisInterval() != null - ? createReq.getVisInterval() - : baseEntity.getVisInterval()); - - // Hardware - entity.setGpuCnt( - createReq.getGpuCnt() != null ? createReq.getGpuCnt() : baseEntity.getGpuCnt()); - entity.setGpuIds( - createReq.getGpuIds() != null ? createReq.getGpuIds() : baseEntity.getGpuIds()); - entity.setMasterPort( - createReq.getMasterPort() != null ? createReq.getMasterPort() : baseEntity.getMasterPort()); - - // Augmentation - entity.setRotProb( - createReq.getRotProb() != null ? createReq.getRotProb() : baseEntity.getRotProb()); - entity.setFlipProb( - createReq.getFlipProb() != null ? createReq.getFlipProb() : baseEntity.getFlipProb()); - entity.setRotDegree( - createReq.getRotDegree() != null ? createReq.getRotDegree() : baseEntity.getRotDegree()); - entity.setExchangeProb( - createReq.getExchangeProb() != null - ? createReq.getExchangeProb() - : baseEntity.getExchangeProb()); - entity.setBrightnessDelta( - createReq.getBrightnessDelta() != null - ? createReq.getBrightnessDelta() - : baseEntity.getBrightnessDelta()); - entity.setContrastRange( - createReq.getContrastRange() != null - ? createReq.getContrastRange() - : baseEntity.getContrastRange()); - entity.setSaturationRange( - createReq.getSaturationRange() != null - ? createReq.getSaturationRange() - : baseEntity.getSaturationRange()); - entity.setHueDelta( - createReq.getHueDelta() != null ? createReq.getHueDelta() : baseEntity.getHueDelta()); - - // Legacy - entity.setDropoutRatio( - createReq.getDropoutRatio() != null - ? createReq.getDropoutRatio() - : baseEntity.getDropoutRatio()); - entity.setCnnFilterCnt( - createReq.getCnnFilterCnt() != null - ? createReq.getCnnFilterCnt() - : baseEntity.getCnnFilterCnt()); - - // Common - entity.setMemo(createReq.getMemo()); - entity.setDelYn("N"); - entity.setCreatedDttm(ZonedDateTime.now()); - - ModelHyperParamEntity saved = hyperParamRepository.save(entity); - return saved.getHyperVer(); - } - - /** - * 하이퍼파라미터 단건 조회 - * - * @param hyperVer 하이퍼파라미터 버전 - * @return 하이퍼파라미터 정보 - */ - public ModelMngDto.HyperParamInfo findByHyperVer(String hyperVer) { - ModelHyperParamEntity entity = - hyperParamRepository - .findById(hyperVer) - .orElseThrow(() -> new NotFoundException("하이퍼파라미터를 찾을 수 없습니다: " + hyperVer)); - - if ("Y".equals(entity.getDelYn())) { - throw new NotFoundException("삭제된 하이퍼파라미터입니다: " + hyperVer); - } - - return mapToHyperParamInfo(entity); - } - - /** - * 하이퍼파라미터 수정 (기존 버전은 수정 불가) - * - * @param hyperVer 하이퍼파라미터 버전 - * @param updateReq 수정 요청 - */ - public void updateHyperParam(String hyperVer, ModelMngDto.HyperParamCreateReq updateReq) { - // 기존 버전은 수정 불가 - throw new BadRequestException("기존 버전은 수정할 수 없습니다. 신규 버전을 생성해주세요."); - } - - /** - * 하이퍼파라미터 삭제 (논리 삭제) - * - * @param hyperVer 하이퍼파라미터 버전 - */ - public void deleteHyperParam(String hyperVer) { - // H1은 디폴트 버전이므로 삭제 불가 - if ("H1".equals(hyperVer)) { - throw new BadRequestException("H1은 디폴트 하이퍼파라미터 버전이므로 삭제할 수 없습니다."); - } - - ModelHyperParamEntity entity = - hyperParamRepository - .findById(hyperVer) - .orElseThrow(() -> new NotFoundException("하이퍼파라미터를 찾을 수 없습니다: " + hyperVer)); - - if ("Y".equals(entity.getDelYn())) { - throw new BadRequestException("이미 삭제된 하이퍼파라미터입니다: " + hyperVer); - } - - // 논리 삭제 처리 - entity.setDelYn("Y"); - hyperParamRepository.save(entity); - } - - /** - * 첫 번째 하이퍼파라미터 버전 조회 (H1 확인용) - * - * @return 첫 번째 하이퍼파라미터 버전 - */ - public String getFirstHyperParamVersion() { - List entities = - hyperParamRepository.findByDelYnOrderByCreatedDttmDesc("N"); - if (entities.isEmpty()) { - throw new NotFoundException("하이퍼파라미터가 존재하지 않습니다."); - } - // 가장 오래된 것이 H1이므로 리스트의 마지막 요소 반환 - return entities.get(entities.size() - 1).getHyperVer(); - } -} +package com.kamco.cd.training.postgres.core; + +import com.kamco.cd.training.common.exception.BadRequestException; +import com.kamco.cd.training.common.exception.NotFoundException; +import com.kamco.cd.training.model.dto.ModelMngDto; +import com.kamco.cd.training.postgres.entity.ModelHyperParamEntity; +import com.kamco.cd.training.postgres.repository.model.ModelHyperParamRepository; +import java.time.ZonedDateTime; +import java.util.List; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; + +@Service +@RequiredArgsConstructor +public class HyperParamCoreService { + private final ModelHyperParamRepository hyperParamRepository; + + /** + * 하이퍼파라미터 전체 조회 (삭제되지 않은 것만) + * + * @return 하이퍼파라미터 목록 + */ + public List findAllActiveHyperParams() { + List entities = + hyperParamRepository.findByDelYnOrderByCreatedDttmDesc("N"); + + return entities.stream().map(this::mapToHyperParamInfo).toList(); + } + + private ModelMngDto.HyperParamInfo mapToHyperParamInfo(ModelHyperParamEntity entity) { + return ModelMngDto.HyperParamInfo.builder() + .hyperVer(entity.getHyperVer()) + // Important + .backbone(entity.getBackbone()) + .inputSize(entity.getInputSize()) + .cropSize(entity.getCropSize()) + .epochCnt(entity.getEpochCnt()) + .batchSize(entity.getBatchSize()) + // Architecture + .dropPathRate(entity.getDropPathRate()) + .frozenStages(entity.getFrozenStages()) + .neckPolicy(entity.getNeckPolicy()) + .decoderChannels(entity.getDecoderChannels()) + .classWeight(entity.getClassWeight()) + .numLayers(entity.getNumLayers()) + // Optimization + .learningRate(entity.getLearningRate()) + .weightDecay(entity.getWeightDecay()) + .layerDecayRate(entity.getLayerDecayRate()) + .ddpFindUnusedParams(entity.getDdpFindUnusedParams()) + .ignoreIndex(entity.getIgnoreIndex()) + // Data + .trainNumWorkers(entity.getTrainNumWorkers()) + .valNumWorkers(entity.getValNumWorkers()) + .testNumWorkers(entity.getTestNumWorkers()) + .trainShuffle(entity.getTrainShuffle()) + .trainPersistent(entity.getTrainPersistent()) + .valPersistent(entity.getValPersistent()) + // Evaluation + .metrics(entity.getMetrics()) + .saveBest(entity.getSaveBest()) + .saveBestRule(entity.getSaveBestRule()) + .valInterval(entity.getValInterval()) + .logInterval(entity.getLogInterval()) + .visInterval(entity.getVisInterval()) + // Hardware + .gpuCnt(entity.getGpuCnt()) + .gpuIds(entity.getGpuIds()) + .masterPort(entity.getMasterPort()) + // Augmentation + .rotProb(entity.getRotProb()) + .flipProb(entity.getFlipProb()) + .rotDegree(entity.getRotDegree()) + .exchangeProb(entity.getExchangeProb()) + .brightnessDelta(entity.getBrightnessDelta()) + .contrastRange(entity.getContrastRange()) + .saturationRange(entity.getSaturationRange()) + .hueDelta(entity.getHueDelta()) + // Legacy + .dropoutRatio(entity.getDropoutRatio()) + .cnnFilterCnt(entity.getCnnFilterCnt()) + // Common + .memo(entity.getMemo()) + .createdDttm(entity.getCreatedDttm()) + .build(); + } + + /** + * 하이퍼파라미터 등록 + * + * @param createReq 등록 요청 + * @return 등록된 버전명 + */ + public String createHyperParam(ModelMngDto.HyperParamCreateReq createReq) { + // 중복 체크 + if (hyperParamRepository.existsById(createReq.getNewHyperVer())) { + throw new BadRequestException("이미 존재하는 버전입니다: " + createReq.getNewHyperVer()); + } + + // 기준 버전 조회 + ModelHyperParamEntity baseEntity = + hyperParamRepository + .findById(createReq.getBaseHyperVer()) + .orElseThrow( + () -> new NotFoundException("기준 버전을 찾을 수 없습니다: " + createReq.getBaseHyperVer())); + + // 신규 엔티티 생성 (기준 값 복사 후 변경된 값만 적용) + ModelHyperParamEntity entity = new ModelHyperParamEntity(); + entity.setHyperVer(createReq.getNewHyperVer()); + + // Important + entity.setBackbone( + createReq.getBackbone() != null ? createReq.getBackbone() : baseEntity.getBackbone()); + entity.setInputSize( + createReq.getInputSize() != null ? createReq.getInputSize() : baseEntity.getInputSize()); + entity.setCropSize( + createReq.getCropSize() != null ? createReq.getCropSize() : baseEntity.getCropSize()); + entity.setEpochCnt( + createReq.getEpochCnt() != null ? createReq.getEpochCnt() : baseEntity.getEpochCnt()); + entity.setBatchSize( + createReq.getBatchSize() != null ? createReq.getBatchSize() : baseEntity.getBatchSize()); + + // Architecture + entity.setDropPathRate( + createReq.getDropPathRate() != null + ? createReq.getDropPathRate() + : baseEntity.getDropPathRate()); + entity.setFrozenStages( + createReq.getFrozenStages() != null + ? createReq.getFrozenStages() + : baseEntity.getFrozenStages()); + entity.setNeckPolicy( + createReq.getNeckPolicy() != null ? createReq.getNeckPolicy() : baseEntity.getNeckPolicy()); + entity.setDecoderChannels( + createReq.getDecoderChannels() != null + ? createReq.getDecoderChannels() + : baseEntity.getDecoderChannels()); + entity.setClassWeight( + createReq.getClassWeight() != null + ? createReq.getClassWeight() + : baseEntity.getClassWeight()); + entity.setNumLayers( + createReq.getNumLayers() != null ? createReq.getNumLayers() : baseEntity.getNumLayers()); + + // Optimization + entity.setLearningRate( + createReq.getLearningRate() != null + ? createReq.getLearningRate() + : baseEntity.getLearningRate()); + entity.setWeightDecay( + createReq.getWeightDecay() != null + ? createReq.getWeightDecay() + : baseEntity.getWeightDecay()); + entity.setLayerDecayRate( + createReq.getLayerDecayRate() != null + ? createReq.getLayerDecayRate() + : baseEntity.getLayerDecayRate()); + entity.setDdpFindUnusedParams( + createReq.getDdpFindUnusedParams() != null + ? createReq.getDdpFindUnusedParams() + : baseEntity.getDdpFindUnusedParams()); + entity.setIgnoreIndex( + createReq.getIgnoreIndex() != null + ? createReq.getIgnoreIndex() + : baseEntity.getIgnoreIndex()); + + // Data + entity.setTrainNumWorkers( + createReq.getTrainNumWorkers() != null + ? createReq.getTrainNumWorkers() + : baseEntity.getTrainNumWorkers()); + entity.setValNumWorkers( + createReq.getValNumWorkers() != null + ? createReq.getValNumWorkers() + : baseEntity.getValNumWorkers()); + entity.setTestNumWorkers( + createReq.getTestNumWorkers() != null + ? createReq.getTestNumWorkers() + : baseEntity.getTestNumWorkers()); + entity.setTrainShuffle( + createReq.getTrainShuffle() != null + ? createReq.getTrainShuffle() + : baseEntity.getTrainShuffle()); + entity.setTrainPersistent( + createReq.getTrainPersistent() != null + ? createReq.getTrainPersistent() + : baseEntity.getTrainPersistent()); + entity.setValPersistent( + createReq.getValPersistent() != null + ? createReq.getValPersistent() + : baseEntity.getValPersistent()); + + // Evaluation + entity.setMetrics( + createReq.getMetrics() != null ? createReq.getMetrics() : baseEntity.getMetrics()); + entity.setSaveBest( + createReq.getSaveBest() != null ? createReq.getSaveBest() : baseEntity.getSaveBest()); + entity.setSaveBestRule( + createReq.getSaveBestRule() != null + ? createReq.getSaveBestRule() + : baseEntity.getSaveBestRule()); + entity.setValInterval( + createReq.getValInterval() != null + ? createReq.getValInterval() + : baseEntity.getValInterval()); + entity.setLogInterval( + createReq.getLogInterval() != null + ? createReq.getLogInterval() + : baseEntity.getLogInterval()); + entity.setVisInterval( + createReq.getVisInterval() != null + ? createReq.getVisInterval() + : baseEntity.getVisInterval()); + + // Hardware + entity.setGpuCnt( + createReq.getGpuCnt() != null ? createReq.getGpuCnt() : baseEntity.getGpuCnt()); + entity.setGpuIds( + createReq.getGpuIds() != null ? createReq.getGpuIds() : baseEntity.getGpuIds()); + entity.setMasterPort( + createReq.getMasterPort() != null ? createReq.getMasterPort() : baseEntity.getMasterPort()); + + // Augmentation + entity.setRotProb( + createReq.getRotProb() != null ? createReq.getRotProb() : baseEntity.getRotProb()); + entity.setFlipProb( + createReq.getFlipProb() != null ? createReq.getFlipProb() : baseEntity.getFlipProb()); + entity.setRotDegree( + createReq.getRotDegree() != null ? createReq.getRotDegree() : baseEntity.getRotDegree()); + entity.setExchangeProb( + createReq.getExchangeProb() != null + ? createReq.getExchangeProb() + : baseEntity.getExchangeProb()); + entity.setBrightnessDelta( + createReq.getBrightnessDelta() != null + ? createReq.getBrightnessDelta() + : baseEntity.getBrightnessDelta()); + entity.setContrastRange( + createReq.getContrastRange() != null + ? createReq.getContrastRange() + : baseEntity.getContrastRange()); + entity.setSaturationRange( + createReq.getSaturationRange() != null + ? createReq.getSaturationRange() + : baseEntity.getSaturationRange()); + entity.setHueDelta( + createReq.getHueDelta() != null ? createReq.getHueDelta() : baseEntity.getHueDelta()); + + // Legacy + entity.setDropoutRatio( + createReq.getDropoutRatio() != null + ? createReq.getDropoutRatio() + : baseEntity.getDropoutRatio()); + entity.setCnnFilterCnt( + createReq.getCnnFilterCnt() != null + ? createReq.getCnnFilterCnt() + : baseEntity.getCnnFilterCnt()); + + // Common + entity.setMemo(createReq.getMemo()); + entity.setDelYn("N"); + entity.setCreatedDttm(ZonedDateTime.now()); + + ModelHyperParamEntity saved = hyperParamRepository.save(entity); + return saved.getHyperVer(); + } + + /** + * 하이퍼파라미터 단건 조회 + * + * @param hyperVer 하이퍼파라미터 버전 + * @return 하이퍼파라미터 정보 + */ + public ModelMngDto.HyperParamInfo findByHyperVer(String hyperVer) { + ModelHyperParamEntity entity = + hyperParamRepository + .findById(hyperVer) + .orElseThrow(() -> new NotFoundException("하이퍼파라미터를 찾을 수 없습니다: " + hyperVer)); + + if ("Y".equals(entity.getDelYn())) { + throw new NotFoundException("삭제된 하이퍼파라미터입니다: " + hyperVer); + } + + return mapToHyperParamInfo(entity); + } + + /** + * 하이퍼파라미터 수정 (기존 버전은 수정 불가) + * + * @param hyperVer 하이퍼파라미터 버전 + * @param updateReq 수정 요청 + */ + public void updateHyperParam(String hyperVer, ModelMngDto.HyperParamCreateReq updateReq) { + // 기존 버전은 수정 불가 + throw new BadRequestException("기존 버전은 수정할 수 없습니다. 신규 버전을 생성해주세요."); + } + + /** + * 하이퍼파라미터 삭제 (논리 삭제) + * + * @param hyperVer 하이퍼파라미터 버전 + */ + public void deleteHyperParam(String hyperVer) { + // H1은 디폴트 버전이므로 삭제 불가 + if ("H1".equals(hyperVer)) { + throw new BadRequestException("H1은 디폴트 하이퍼파라미터 버전이므로 삭제할 수 없습니다."); + } + + ModelHyperParamEntity entity = + hyperParamRepository + .findById(hyperVer) + .orElseThrow(() -> new NotFoundException("하이퍼파라미터를 찾을 수 없습니다: " + hyperVer)); + + if ("Y".equals(entity.getDelYn())) { + throw new BadRequestException("이미 삭제된 하이퍼파라미터입니다: " + hyperVer); + } + + // 논리 삭제 처리 + entity.setDelYn("Y"); + hyperParamRepository.save(entity); + } + + /** + * 첫 번째 하이퍼파라미터 버전 조회 (H1 확인용) + * + * @return 첫 번째 하이퍼파라미터 버전 + */ + public String getFirstHyperParamVersion() { + List entities = + hyperParamRepository.findByDelYnOrderByCreatedDttmDesc("N"); + if (entities.isEmpty()) { + throw new NotFoundException("하이퍼파라미터가 존재하지 않습니다."); + } + // 가장 오래된 것이 H1이므로 리스트의 마지막 요소 반환 + return entities.get(entities.size() - 1).getHyperVer(); + } +} diff --git a/src/main/java/com/kamco/cd/training/postgres/core/MapSheetCoreService.java b/src/main/java/com/kamco/cd/training/postgres/core/MapSheetCoreService.java index 41f8007..db0164e 100644 --- a/src/main/java/com/kamco/cd/training/postgres/core/MapSheetCoreService.java +++ b/src/main/java/com/kamco/cd/training/postgres/core/MapSheetCoreService.java @@ -1,72 +1,72 @@ -package com.kamco.cd.training.postgres.core; - -import com.kamco.cd.training.common.exception.NotFoundException; -import com.kamco.cd.training.common.service.BaseCoreService; -import com.kamco.cd.training.dataset.dto.MapSheetDto; -import com.kamco.cd.training.postgres.entity.MapSheetEntity; -import com.kamco.cd.training.postgres.repository.dataset.MapSheetRepository; -import java.util.Optional; -import lombok.RequiredArgsConstructor; -import org.springframework.data.domain.Page; -import org.springframework.stereotype.Service; - -@Service -@RequiredArgsConstructor -public class MapSheetCoreService - implements BaseCoreService { - - private final MapSheetRepository mapSheetRepository; - - @Override - public void remove(Long id) { - Optional mapSheet = mapSheetRepository.findById(id); - if (mapSheet.isEmpty()) { - return; - } - - MapSheetEntity entity = mapSheet.get(); - entity.setDeleted(true); - mapSheetRepository.save(entity); - } - - @Override - public MapSheetDto.Basic getOneById(Long id) { - MapSheetEntity entity = - mapSheetRepository - .findById(id) - .orElseThrow(() -> new NotFoundException("도엽을 찾을 수 없습니다. ID: " + id)); - - if (entity.getDeleted()) { - throw new NotFoundException("삭제된 도엽입니다. ID: " + id); - } - - return entity.toDto(); - } - - @Override - public Page search(MapSheetDto.SearchReq searchReq) { - Page entityPage = mapSheetRepository.findMapSheetList(searchReq); - return entityPage.map(MapSheetEntity::toDto); - } - - /** - * 도엽 목록 조회 - * - * @param searchReq 검색 조건 - * @return 페이징 처리된 도엽 목록 - */ - public Page findMapSheetList(MapSheetDto.SearchReq searchReq) { - return search(searchReq); - } - - /** - * 도엽 삭제 (다건) - * - * @param deleteReq 삭제 요청 - */ - public void deleteMapSheets(MapSheetDto.DeleteReq deleteReq) { - for (Long id : deleteReq.getItemIds()) { - remove(id); - } - } -} +package com.kamco.cd.training.postgres.core; + +import com.kamco.cd.training.common.exception.NotFoundException; +import com.kamco.cd.training.common.service.BaseCoreService; +import com.kamco.cd.training.dataset.dto.MapSheetDto; +import com.kamco.cd.training.postgres.entity.MapSheetEntity; +import com.kamco.cd.training.postgres.repository.dataset.MapSheetRepository; +import java.util.Optional; +import lombok.RequiredArgsConstructor; +import org.springframework.data.domain.Page; +import org.springframework.stereotype.Service; + +@Service +@RequiredArgsConstructor +public class MapSheetCoreService + implements BaseCoreService { + + private final MapSheetRepository mapSheetRepository; + + @Override + public void remove(Long id) { + Optional mapSheet = mapSheetRepository.findById(id); + if (mapSheet.isEmpty()) { + return; + } + + MapSheetEntity entity = mapSheet.get(); + entity.setDeleted(true); + mapSheetRepository.save(entity); + } + + @Override + public MapSheetDto.Basic getOneById(Long id) { + MapSheetEntity entity = + mapSheetRepository + .findById(id) + .orElseThrow(() -> new NotFoundException("도엽을 찾을 수 없습니다. ID: " + id)); + + if (entity.getDeleted()) { + throw new NotFoundException("삭제된 도엽입니다. ID: " + id); + } + + return entity.toDto(); + } + + @Override + public Page search(MapSheetDto.SearchReq searchReq) { + Page entityPage = mapSheetRepository.findMapSheetList(searchReq); + return entityPage.map(MapSheetEntity::toDto); + } + + /** + * 도엽 목록 조회 + * + * @param searchReq 검색 조건 + * @return 페이징 처리된 도엽 목록 + */ + public Page findMapSheetList(MapSheetDto.SearchReq searchReq) { + return search(searchReq); + } + + /** + * 도엽 삭제 (다건) + * + * @param deleteReq 삭제 요청 + */ + public void deleteMapSheets(MapSheetDto.DeleteReq deleteReq) { + for (Long id : deleteReq.getItemIds()) { + remove(id); + } + } +} diff --git a/src/main/java/com/kamco/cd/training/postgres/core/MembersCoreService.java b/src/main/java/com/kamco/cd/training/postgres/core/MembersCoreService.java index 274dada..3ad7ea7 100644 --- a/src/main/java/com/kamco/cd/training/postgres/core/MembersCoreService.java +++ b/src/main/java/com/kamco/cd/training/postgres/core/MembersCoreService.java @@ -1,167 +1,168 @@ -package com.kamco.cd.training.postgres.core; - -import com.kamco.cd.training.auth.BCryptSaltGenerator; -import com.kamco.cd.training.common.enums.StatusType; -import com.kamco.cd.training.common.enums.error.AuthErrorCode; -import com.kamco.cd.training.common.exception.CustomApiException; -import com.kamco.cd.training.common.utils.CommonStringUtils; -import com.kamco.cd.training.common.utils.UserUtil; -import com.kamco.cd.training.members.dto.MembersDto; -import com.kamco.cd.training.members.dto.MembersDto.AddReq; -import com.kamco.cd.training.members.dto.MembersDto.Basic; -import com.kamco.cd.training.members.dto.SignInRequest; -import com.kamco.cd.training.members.exception.MemberException.DuplicateMemberException; -import com.kamco.cd.training.members.exception.MemberException.DuplicateMemberException.Field; -import com.kamco.cd.training.members.exception.MemberException.MemberNotFoundException; -import com.kamco.cd.training.postgres.entity.MemberEntity; -import com.kamco.cd.training.postgres.repository.members.MembersRepository; -import java.time.ZonedDateTime; -import java.util.UUID; -import lombok.RequiredArgsConstructor; -import org.apache.commons.lang3.StringUtils; -import org.mindrot.jbcrypt.BCrypt; -import org.springframework.data.domain.Page; -import org.springframework.http.HttpStatus; -import org.springframework.stereotype.Service; - -@Service -@RequiredArgsConstructor -public class MembersCoreService { - - private final MembersRepository membersRepository; - private final UserUtil userUtil; - - /** - * 관리자 계정 등록 - * - * @param addReq - * @return - */ - public Long saveMembers(AddReq addReq) { - if (membersRepository.existsByEmployeeNo(addReq.getEmployeeNo())) { - throw new DuplicateMemberException(Field.EMPLOYEE_NO, addReq.getEmployeeNo()); - } - - // salt 생성, 사번이 salt - String salt = BCryptSaltGenerator.generateSaltWithEmployeeNo(addReq.getEmployeeNo().trim()); - // 패스워드 암호화, 초기 패스워드 고정 - String hashedPassword = BCrypt.hashpw(addReq.getPassword(), salt); - - MemberEntity memberEntity = new MemberEntity(); - memberEntity.setUserId(addReq.getEmployeeNo()); - memberEntity.setUserRole(addReq.getUserRole()); - memberEntity.setPassword(hashedPassword); - memberEntity.setName(addReq.getName()); - memberEntity.setEmployeeNo(addReq.getEmployeeNo()); - memberEntity.setRgstrUidl(userUtil.getId()); - memberEntity.setStatus(StatusType.PENDING.getId()); - - return membersRepository.save(memberEntity).getId(); - } - - /** - * 관리자 계정 수정 - * - * @param uuid - * @param updateReq - */ - public void updateMembers(UUID uuid, MembersDto.UpdateReq updateReq) { - MemberEntity memberEntity = - membersRepository.findByUUID(uuid).orElseThrow(MemberNotFoundException::new); - - if (StringUtils.isNotBlank(updateReq.getName())) { - memberEntity.setName(updateReq.getName()); - } - - if (StringUtils.isNotBlank(updateReq.getStatus())) { - memberEntity.changeStatus(updateReq.getStatus()); - } - - if (StringUtils.isNotBlank(updateReq.getPassword())) { - - // 패스워드 유효성 검사 - if (!CommonStringUtils.isValidPassword(updateReq.getPassword())) { - throw new CustomApiException("WRONG_PASSWORD", HttpStatus.BAD_REQUEST); - } - - String password = - CommonStringUtils.hashPassword(updateReq.getPassword(), memberEntity.getEmployeeNo()); - - memberEntity.setStatus(StatusType.PENDING.getId()); - memberEntity.setLoginFailCount(0); - memberEntity.setPassword(password); - memberEntity.setPwdResetYn(true); - } - memberEntity.setUpdtrUid(userUtil.getId()); - membersRepository.save(memberEntity); - } - - /** - * 패스워드 변경 - * - * @param id - */ - public void resetPassword(String id, MembersDto.InitReq initReq) { - MemberEntity memberEntity = - membersRepository.findByEmployeeNo(id).orElseThrow(() -> new MemberNotFoundException()); - - // 기존 패스워드 확인 - if (!BCrypt.checkpw(initReq.getOldPassword(), memberEntity.getPassword())) { - throw new CustomApiException(AuthErrorCode.LOGIN_PASSWORD_MISMATCH); - } - - String password = - CommonStringUtils.hashPassword(initReq.getOldPassword(), memberEntity.getEmployeeNo()); - - memberEntity.setPassword(password); - memberEntity.setStatus(StatusType.ACTIVE.getId()); - memberEntity.setUpdatedDttm(ZonedDateTime.now()); - memberEntity.setUpdtrUid(memberEntity.getId()); - memberEntity.setPwdResetYn(false); - membersRepository.save(memberEntity); - } - - // - - /** - * 회원목록 조회 - * - * @param searchReq - * @return - */ - public Page findByMembers(MembersDto.SearchReq searchReq) { - Page entityPage = membersRepository.findByMembers(searchReq); - return entityPage.map(MemberEntity::toDto); - } - - /** - * 사용자 상태 조회 - * - * @param request - * @return - */ - public String getUserStatus(SignInRequest request) { - MemberEntity memberEntity = - membersRepository - .findByEmployeeNo(request.getUsername()) - .orElseThrow(() -> new CustomApiException("LOGIN_ID_NOT_FOUND", HttpStatus.UNAUTHORIZED)); - return memberEntity.getStatus(); - } - - /** - * 최초 로그인 저장 마지막 로그인 저장 - * - * @param uuid - */ - public void saveLogin(UUID uuid) { - MemberEntity memberEntity = - membersRepository.findByUUID(uuid).orElseThrow(() -> new MemberNotFoundException()); - - if (memberEntity.getFirstLoginDttm() == null) { - memberEntity.setFirstLoginDttm(ZonedDateTime.now()); - } - memberEntity.setLastLoginDttm(ZonedDateTime.now()); - memberEntity.setLoginFailCount(0); - membersRepository.save(memberEntity); - } -} +package com.kamco.cd.training.postgres.core; + +import com.kamco.cd.training.auth.BCryptSaltGenerator; +import com.kamco.cd.training.common.enums.StatusType; +import com.kamco.cd.training.common.enums.error.AuthErrorCode; +import com.kamco.cd.training.common.exception.CustomApiException; +import com.kamco.cd.training.common.utils.CommonStringUtils; +import com.kamco.cd.training.common.utils.UserUtil; +import com.kamco.cd.training.members.dto.MembersDto; +import com.kamco.cd.training.members.dto.MembersDto.AddReq; +import com.kamco.cd.training.members.dto.MembersDto.Basic; +import com.kamco.cd.training.members.dto.SignInRequest; +import com.kamco.cd.training.members.exception.MemberException.DuplicateMemberException; +import com.kamco.cd.training.members.exception.MemberException.DuplicateMemberException.Field; +import com.kamco.cd.training.members.exception.MemberException.MemberNotFoundException; +import com.kamco.cd.training.postgres.entity.MemberEntity; +import com.kamco.cd.training.postgres.repository.members.MembersRepository; +import java.time.ZonedDateTime; +import java.util.UUID; +import lombok.RequiredArgsConstructor; +import org.apache.commons.lang3.StringUtils; +import org.mindrot.jbcrypt.BCrypt; +import org.springframework.data.domain.Page; +import org.springframework.http.HttpStatus; +import org.springframework.stereotype.Service; + +@Service +@RequiredArgsConstructor +public class MembersCoreService { + + private final MembersRepository membersRepository; + private final UserUtil userUtil; + + /** + * 관리자 계정 등록 + * + * @param addReq + * @return + */ + public Long saveMembers(AddReq addReq) { + if (membersRepository.existsByEmployeeNo(addReq.getEmployeeNo())) { + throw new DuplicateMemberException(Field.EMPLOYEE_NO, addReq.getEmployeeNo()); + } + + // salt 생성, 사번이 salt + String salt = BCryptSaltGenerator.generateSaltWithEmployeeNo(addReq.getEmployeeNo().trim()); + // 패스워드 암호화, 초기 패스워드 고정 + String hashedPassword = BCrypt.hashpw(addReq.getPassword(), salt); + + MemberEntity memberEntity = new MemberEntity(); + memberEntity.setUserId(addReq.getEmployeeNo()); + memberEntity.setUserRole(addReq.getUserRole()); + memberEntity.setPassword(hashedPassword); + memberEntity.setName(addReq.getName()); + memberEntity.setEmployeeNo(addReq.getEmployeeNo()); + memberEntity.setRgstrUidl(userUtil.getId()); + memberEntity.setStatus(StatusType.PENDING.getId()); + + return membersRepository.save(memberEntity).getId(); + } + + /** + * 관리자 계정 수정 + * + * @param uuid + * @param updateReq + */ + public void updateMembers(UUID uuid, MembersDto.UpdateReq updateReq) { + MemberEntity memberEntity = + membersRepository.findByUUID(uuid).orElseThrow(MemberNotFoundException::new); + + if (StringUtils.isNotBlank(updateReq.getName())) { + memberEntity.setName(updateReq.getName()); + } + + if (StringUtils.isNotBlank(updateReq.getStatus())) { + memberEntity.changeStatus(updateReq.getStatus()); + } + + if (StringUtils.isNotBlank(updateReq.getPassword())) { + + // 패스워드 유효성 검사 + if (!CommonStringUtils.isValidPassword(updateReq.getPassword())) { + throw new CustomApiException("WRONG_PASSWORD", HttpStatus.BAD_REQUEST); + } + + String password = + CommonStringUtils.hashPassword(updateReq.getPassword(), memberEntity.getEmployeeNo()); + + memberEntity.setStatus(StatusType.PENDING.getId()); + memberEntity.setLoginFailCount(0); + memberEntity.setPassword(password); + memberEntity.setPwdResetYn(true); + } + memberEntity.setUpdtrUid(userUtil.getId()); + membersRepository.save(memberEntity); + } + + /** + * 패스워드 변경 + * + * @param id + */ + public void resetPassword(String id, MembersDto.InitReq initReq) { + MemberEntity memberEntity = + membersRepository.findByEmployeeNo(id).orElseThrow(() -> new MemberNotFoundException()); + + // 기존 패스워드 확인 + if (!BCrypt.checkpw(initReq.getOldPassword(), memberEntity.getPassword())) { + throw new CustomApiException(AuthErrorCode.LOGIN_PASSWORD_MISMATCH); + } + + String password = + CommonStringUtils.hashPassword(initReq.getOldPassword(), memberEntity.getEmployeeNo()); + + memberEntity.setPassword(password); + memberEntity.setStatus(StatusType.ACTIVE.getId()); + memberEntity.setUpdatedDttm(ZonedDateTime.now()); + memberEntity.setUpdtrUid(memberEntity.getId()); + memberEntity.setPwdResetYn(false); + membersRepository.save(memberEntity); + } + + // + + /** + * 회원목록 조회 + * + * @param searchReq + * @return + */ + public Page findByMembers(MembersDto.SearchReq searchReq) { + Page entityPage = membersRepository.findByMembers(searchReq); + return entityPage.map(MemberEntity::toDto); + } + + /** + * 사용자 상태 조회 + * + * @param request + * @return + */ + public String getUserStatus(SignInRequest request) { + MemberEntity memberEntity = + membersRepository + .findByEmployeeNo(request.getUsername()) + .orElseThrow( + () -> new CustomApiException("LOGIN_ID_NOT_FOUND", HttpStatus.UNAUTHORIZED)); + return memberEntity.getStatus(); + } + + /** + * 최초 로그인 저장 마지막 로그인 저장 + * + * @param uuid + */ + public void saveLogin(UUID uuid) { + MemberEntity memberEntity = + membersRepository.findByUUID(uuid).orElseThrow(() -> new MemberNotFoundException()); + + if (memberEntity.getFirstLoginDttm() == null) { + memberEntity.setFirstLoginDttm(ZonedDateTime.now()); + } + memberEntity.setLastLoginDttm(ZonedDateTime.now()); + memberEntity.setLoginFailCount(0); + membersRepository.save(memberEntity); + } +} diff --git a/src/main/java/com/kamco/cd/training/postgres/core/MenuCoreService.java b/src/main/java/com/kamco/cd/training/postgres/core/MenuCoreService.java index bf8ad5d..cc552cf 100644 --- a/src/main/java/com/kamco/cd/training/postgres/core/MenuCoreService.java +++ b/src/main/java/com/kamco/cd/training/postgres/core/MenuCoreService.java @@ -1,19 +1,19 @@ -package com.kamco.cd.training.postgres.core; - -import com.kamco.cd.training.menu.dto.MenuDto; -import com.kamco.cd.training.postgres.entity.MenuEntity; -import com.kamco.cd.training.postgres.repository.menu.MenuRepository; -import java.util.List; -import lombok.RequiredArgsConstructor; -import org.springframework.stereotype.Service; - -@Service -@RequiredArgsConstructor -public class MenuCoreService { - - private final MenuRepository menuRepository; - - public List getFindAll() { - return menuRepository.getFindAll().stream().map(MenuEntity::toDto).toList(); - } -} +package com.kamco.cd.training.postgres.core; + +import com.kamco.cd.training.menu.dto.MenuDto; +import com.kamco.cd.training.postgres.entity.MenuEntity; +import com.kamco.cd.training.postgres.repository.menu.MenuRepository; +import java.util.List; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; + +@Service +@RequiredArgsConstructor +public class MenuCoreService { + + private final MenuRepository menuRepository; + + public List getFindAll() { + return menuRepository.getFindAll().stream().map(MenuEntity::toDto).toList(); + } +} diff --git a/src/main/java/com/kamco/cd/training/postgres/core/ModelMngCoreService.java b/src/main/java/com/kamco/cd/training/postgres/core/ModelMngCoreService.java index fc379fd..0e5bd82 100644 --- a/src/main/java/com/kamco/cd/training/postgres/core/ModelMngCoreService.java +++ b/src/main/java/com/kamco/cd/training/postgres/core/ModelMngCoreService.java @@ -1,202 +1,202 @@ -package com.kamco.cd.training.postgres.core; - -import com.kamco.cd.training.common.exception.BadRequestException; -import com.kamco.cd.training.common.exception.NotFoundException; -import com.kamco.cd.training.model.dto.ModelMngDto; -import com.kamco.cd.training.model.dto.ModelMngDto.Basic; -import com.kamco.cd.training.postgres.entity.ModelDatasetMappEntity; -import com.kamco.cd.training.postgres.entity.ModelTrainMasterEntity; -import com.kamco.cd.training.postgres.repository.model.ModelDatasetMappRepository; -import com.kamco.cd.training.postgres.repository.model.ModelMngRepository; -import java.time.ZonedDateTime; -import java.util.List; -import lombok.RequiredArgsConstructor; -import org.springframework.data.domain.Page; -import org.springframework.stereotype.Service; - -@Service -@RequiredArgsConstructor -public class ModelMngCoreService { - private final ModelMngRepository modelMngRepository; - private final ModelDatasetMappRepository modelDatasetMappRepository; - - /** - * 모델 목록 조회 - * - * @param searchReq 검색 조건 - * @return 페이징 처리된 모델 목록 - */ - public Page findByModels(ModelMngDto.SearchReq searchReq) { - Page entityPage = modelMngRepository.findByModels(searchReq); - return entityPage.map(ModelTrainMasterEntity::toDto); - } - - /** - * 모델 상세 조회 - * - * @param modelUid 모델 UID - * @return 모델 상세 정보 - */ - public ModelMngDto.Detail getModelDetail(Long modelUid) { - ModelTrainMasterEntity entity = - modelMngRepository - .findById(modelUid) - .orElseThrow(() -> new NotFoundException("모델을 찾을 수 없습니다. ID: " + modelUid)); - - if (Boolean.TRUE.equals(entity.getDelYn())) { - throw new NotFoundException("삭제된 모델입니다. ID: " + modelUid); - } - - return ModelMngDto.Detail.builder() - .uuid(entity.getUuid().toString()) - .modelVer(entity.getModelVer()) - .hyperVer(entity.getHyperVer()) - .epochVer(entity.getEpochVer()) - .processStep(entity.getProcessStep()) - .statusCd(entity.getStatusCd()) - .trainStartDttm(entity.getTrainStartDttm()) - .epochCnt(entity.getEpochCnt()) - .datasetRatio(entity.getDatasetRatio()) - .bestEpoch(entity.getBestEpoch()) - .confirmedBestEpoch(entity.getConfirmedBestEpoch()) - .step1EndDttm(entity.getStep1EndDttm()) - .step1Duration(entity.getStep1Duration()) - .step2EndDttm(entity.getStep2EndDttm()) - .step2Duration(entity.getStep2Duration()) - .progressRate(entity.getProgressRate()) - .createdDttm(entity.getCreatedDttm()) - .updatedDttm(entity.getUpdatedDttm()) - .modelPath(entity.getModelPath()) - .errorMsg(entity.getErrorMsg()) - .build(); - } - - /** - * 모델 상세 조회 (UUID 기반) - * - * @param uuid 모델 UUID - * @return 모델 상세 정보 - */ - public ModelMngDto.Detail getModelDetailByUuid(String uuid) { - ModelTrainMasterEntity entity = findByUuid(uuid); - return getModelDetail(entity.getId()); - } - - /** - * 학습 모델 전체 목록 조회 (삭제되지 않은 것만) - * - * @return 학습 모델 목록 - */ - public List findAllTrainModels() { - List entities = - modelMngRepository.findByDelYnOrderByCreatedDttmDesc(false); - - return entities.stream() - .map( - entity -> - ModelMngDto.TrainListRes.builder() - .uuid(entity.getUuid().toString()) - .modelVer(entity.getModelVer()) - .status(entity.getStatusCd()) - .processStep(entity.getProcessStep()) - .trainStartDttm(entity.getTrainStartDttm()) - .progressRate(entity.getProgressRate()) - .epochCnt(entity.getEpochCnt()) - .step1EndDttm(entity.getStep1EndDttm()) - .step1Duration(entity.getStep1Duration()) - .step2EndDttm(entity.getStep2EndDttm()) - .step2Duration(entity.getStep2Duration()) - .createdDttm(entity.getCreatedDttm()) - .errorMsg(entity.getErrorMsg()) - .canResume(entity.getCanResume()) - .lastCheckpointEpoch(entity.getLastCheckpointEpoch()) - .build()) - .toList(); - } - - /** - * 현재 실행 중인 모델 확인 - * - * @return 실행 중인 모델 UUID (없으면 null) - */ - public String findRunningModelUuid() { - return modelMngRepository - .findFirstByStatusCdAndDelYn("RUNNING", false) - .map(entity -> entity.getUuid().toString()) - .orElse(null); - } - - /** - * 학습 마스터 생성 - * - * @param trainReq 학습 시작 요청 - * @return 생성된 모델 Entity - */ - public ModelTrainMasterEntity createTrainMaster(ModelMngDto.TrainStartReq trainReq) { - ModelTrainMasterEntity entity = new ModelTrainMasterEntity(); - entity.setModelVer(trainReq.getHyperVer()); - entity.setHyperVer(trainReq.getHyperVer()); - entity.setEpochVer(String.valueOf(trainReq.getEpoch())); - entity.setProcessStep("STEP1"); - entity.setStatusCd("READY"); - entity.setTrainStartDttm(ZonedDateTime.now()); - entity.setEpochCnt(trainReq.getEpoch()); - entity.setDatasetRatio(trainReq.getDatasetRatio()); - entity.setDelYn(false); - entity.setCreatedDttm(ZonedDateTime.now()); - entity.setProgressRate(0); - - return modelMngRepository.save(entity); - } - - /** - * 데이터셋 매핑 생성 - * - * @param modelUid 모델 UID - * @param datasetIds 데이터셋 ID 목록 - */ - public void createDatasetMappings(Long modelUid, List datasetIds) { - for (Long datasetId : datasetIds) { - ModelDatasetMappEntity mapping = new ModelDatasetMappEntity(); - mapping.setModelUid(modelUid); - mapping.setDatasetUid(datasetId); - mapping.setDatasetType("TRAIN"); - modelDatasetMappRepository.save(mapping); - } - } - - /** - * UUID로 모델 조회 - * - * @param uuid UUID - * @return 모델 Entity - */ - public ModelTrainMasterEntity findByUuid(String uuid) { - try { - java.util.UUID uuidObj = java.util.UUID.fromString(uuid); - return modelMngRepository - .findByUuid(uuidObj) - .orElseThrow(() -> new NotFoundException("모델을 찾을 수 없습니다. UUID: " + uuid)); - } catch (IllegalArgumentException e) { - throw new BadRequestException("잘못된 UUID 형식입니다: " + uuid); - } - } - - /** - * 모델 삭제 (논리 삭제) - * - * @param uuid UUID - */ - public void deleteByUuid(String uuid) { - ModelTrainMasterEntity entity = findByUuid(uuid); - - // 진행 중인 모델은 삭제 불가 - if ("RUNNING".equals(entity.getStatusCd())) { - throw new BadRequestException("진행 중인 모델은 삭제할 수 없습니다."); - } - - entity.setDelYn(true); - entity.setUpdatedDttm(ZonedDateTime.now()); - modelMngRepository.save(entity); - } -} +package com.kamco.cd.training.postgres.core; + +import com.kamco.cd.training.common.exception.BadRequestException; +import com.kamco.cd.training.common.exception.NotFoundException; +import com.kamco.cd.training.model.dto.ModelMngDto; +import com.kamco.cd.training.model.dto.ModelMngDto.Basic; +import com.kamco.cd.training.postgres.entity.ModelDatasetMappEntity; +import com.kamco.cd.training.postgres.entity.ModelTrainMasterEntity; +import com.kamco.cd.training.postgres.repository.model.ModelDatasetMappRepository; +import com.kamco.cd.training.postgres.repository.model.ModelMngRepository; +import java.time.ZonedDateTime; +import java.util.List; +import lombok.RequiredArgsConstructor; +import org.springframework.data.domain.Page; +import org.springframework.stereotype.Service; + +@Service +@RequiredArgsConstructor +public class ModelMngCoreService { + private final ModelMngRepository modelMngRepository; + private final ModelDatasetMappRepository modelDatasetMappRepository; + + /** + * 모델 목록 조회 + * + * @param searchReq 검색 조건 + * @return 페이징 처리된 모델 목록 + */ + public Page findByModels(ModelMngDto.SearchReq searchReq) { + Page entityPage = modelMngRepository.findByModels(searchReq); + return entityPage.map(ModelTrainMasterEntity::toDto); + } + + /** + * 모델 상세 조회 + * + * @param modelUid 모델 UID + * @return 모델 상세 정보 + */ + public ModelMngDto.Detail getModelDetail(Long modelUid) { + ModelTrainMasterEntity entity = + modelMngRepository + .findById(modelUid) + .orElseThrow(() -> new NotFoundException("모델을 찾을 수 없습니다. ID: " + modelUid)); + + if (Boolean.TRUE.equals(entity.getDelYn())) { + throw new NotFoundException("삭제된 모델입니다. ID: " + modelUid); + } + + return ModelMngDto.Detail.builder() + .uuid(entity.getUuid().toString()) + .modelVer(entity.getModelVer()) + .hyperVer(entity.getHyperVer()) + .epochVer(entity.getEpochVer()) + .processStep(entity.getProcessStep()) + .statusCd(entity.getStatusCd()) + .trainStartDttm(entity.getTrainStartDttm()) + .epochCnt(entity.getEpochCnt()) + .datasetRatio(entity.getDatasetRatio()) + .bestEpoch(entity.getBestEpoch()) + .confirmedBestEpoch(entity.getConfirmedBestEpoch()) + .step1EndDttm(entity.getStep1EndDttm()) + .step1Duration(entity.getStep1Duration()) + .step2EndDttm(entity.getStep2EndDttm()) + .step2Duration(entity.getStep2Duration()) + .progressRate(entity.getProgressRate()) + .createdDttm(entity.getCreatedDttm()) + .updatedDttm(entity.getUpdatedDttm()) + .modelPath(entity.getModelPath()) + .errorMsg(entity.getErrorMsg()) + .build(); + } + + /** + * 모델 상세 조회 (UUID 기반) + * + * @param uuid 모델 UUID + * @return 모델 상세 정보 + */ + public ModelMngDto.Detail getModelDetailByUuid(String uuid) { + ModelTrainMasterEntity entity = findByUuid(uuid); + return getModelDetail(entity.getId()); + } + + /** + * 학습 모델 전체 목록 조회 (삭제되지 않은 것만) + * + * @return 학습 모델 목록 + */ + public List findAllTrainModels() { + List entities = + modelMngRepository.findByDelYnOrderByCreatedDttmDesc(false); + + return entities.stream() + .map( + entity -> + ModelMngDto.TrainListRes.builder() + .uuid(entity.getUuid().toString()) + .modelVer(entity.getModelVer()) + .status(entity.getStatusCd()) + .processStep(entity.getProcessStep()) + .trainStartDttm(entity.getTrainStartDttm()) + .progressRate(entity.getProgressRate()) + .epochCnt(entity.getEpochCnt()) + .step1EndDttm(entity.getStep1EndDttm()) + .step1Duration(entity.getStep1Duration()) + .step2EndDttm(entity.getStep2EndDttm()) + .step2Duration(entity.getStep2Duration()) + .createdDttm(entity.getCreatedDttm()) + .errorMsg(entity.getErrorMsg()) + .canResume(entity.getCanResume()) + .lastCheckpointEpoch(entity.getLastCheckpointEpoch()) + .build()) + .toList(); + } + + /** + * 현재 실행 중인 모델 확인 + * + * @return 실행 중인 모델 UUID (없으면 null) + */ + public String findRunningModelUuid() { + return modelMngRepository + .findFirstByStatusCdAndDelYn("RUNNING", false) + .map(entity -> entity.getUuid().toString()) + .orElse(null); + } + + /** + * 학습 마스터 생성 + * + * @param trainReq 학습 시작 요청 + * @return 생성된 모델 Entity + */ + public ModelTrainMasterEntity createTrainMaster(ModelMngDto.TrainStartReq trainReq) { + ModelTrainMasterEntity entity = new ModelTrainMasterEntity(); + entity.setModelVer(trainReq.getHyperVer()); + entity.setHyperVer(trainReq.getHyperVer()); + entity.setEpochVer(String.valueOf(trainReq.getEpoch())); + entity.setProcessStep("STEP1"); + entity.setStatusCd("READY"); + entity.setTrainStartDttm(ZonedDateTime.now()); + entity.setEpochCnt(trainReq.getEpoch()); + entity.setDatasetRatio(trainReq.getDatasetRatio()); + entity.setDelYn(false); + entity.setCreatedDttm(ZonedDateTime.now()); + entity.setProgressRate(0); + + return modelMngRepository.save(entity); + } + + /** + * 데이터셋 매핑 생성 + * + * @param modelUid 모델 UID + * @param datasetIds 데이터셋 ID 목록 + */ + public void createDatasetMappings(Long modelUid, List datasetIds) { + for (Long datasetId : datasetIds) { + ModelDatasetMappEntity mapping = new ModelDatasetMappEntity(); + mapping.setModelUid(modelUid); + mapping.setDatasetUid(datasetId); + mapping.setDatasetType("TRAIN"); + modelDatasetMappRepository.save(mapping); + } + } + + /** + * UUID로 모델 조회 + * + * @param uuid UUID + * @return 모델 Entity + */ + public ModelTrainMasterEntity findByUuid(String uuid) { + try { + java.util.UUID uuidObj = java.util.UUID.fromString(uuid); + return modelMngRepository + .findByUuid(uuidObj) + .orElseThrow(() -> new NotFoundException("모델을 찾을 수 없습니다. UUID: " + uuid)); + } catch (IllegalArgumentException e) { + throw new BadRequestException("잘못된 UUID 형식입니다: " + uuid); + } + } + + /** + * 모델 삭제 (논리 삭제) + * + * @param uuid UUID + */ + public void deleteByUuid(String uuid) { + ModelTrainMasterEntity entity = findByUuid(uuid); + + // 진행 중인 모델은 삭제 불가 + if ("RUNNING".equals(entity.getStatusCd())) { + throw new BadRequestException("진행 중인 모델은 삭제할 수 없습니다."); + } + + entity.setDelYn(true); + entity.setUpdatedDttm(ZonedDateTime.now()); + modelMngRepository.save(entity); + } +} diff --git a/src/main/java/com/kamco/cd/training/postgres/core/SystemMetricsCoreService.java b/src/main/java/com/kamco/cd/training/postgres/core/SystemMetricsCoreService.java index 1213562..dfca947 100644 --- a/src/main/java/com/kamco/cd/training/postgres/core/SystemMetricsCoreService.java +++ b/src/main/java/com/kamco/cd/training/postgres/core/SystemMetricsCoreService.java @@ -1,66 +1,66 @@ -package com.kamco.cd.training.postgres.core; - -import com.kamco.cd.training.postgres.entity.SystemMetricsEntity; -import com.kamco.cd.training.postgres.repository.SystemMetricsRepository; -import lombok.RequiredArgsConstructor; -import lombok.extern.slf4j.Slf4j; -import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Transactional; - -@Service -@RequiredArgsConstructor -@Transactional(readOnly = true) -@Slf4j -public class SystemMetricsCoreService { - - private final SystemMetricsRepository systemMetricsRepository; - - /** - * 현재 사용 가능한 저장공간 조회 (MB 단위) - * - * @return 사용 가능한 저장공간 (MB) - */ - public long getAvailableStorageMB() { - SystemMetricsEntity latestMetrics = - systemMetricsRepository - .findLatestMetrics() - .orElseThrow(() -> new IllegalStateException("시스템 메트릭 정보를 조회할 수 없습니다")); - - Long kbmemfree = latestMetrics.getKbmemfree(); - Long kbmemused = latestMetrics.getKbmemused(); - - if (kbmemfree == null || kbmemused == null) { - log.warn("시스템 메트릭에 메모리 정보가 없습니다"); - return 0L; - } - - // 남은 용량 = kbmemfree - kbmemused (KB 단위) - long availableKB = kbmemfree - kbmemused; - - // KB를 MB로 변환 - long availableMB = availableKB / 1024; - - log.info( - "사용 가능한 저장공간: {}MB (kbmemfree: {}KB, kbmemused: {}KB)", availableMB, kbmemfree, kbmemused); - - return Math.max(0L, availableMB); - } - - /** - * 학습 실행 가능 여부 확인 (10GB 이상 필요) - * - * @return 학습 실행 가능 여부 - */ - public boolean isStorageAvailableForTraining() { - long availableMB = getAvailableStorageMB(); - long requiredMB = 10 * 1024; // 10GB = 10,240MB - - boolean isAvailable = availableMB >= requiredMB; - - if (!isAvailable) { - log.warn("저장공간 부족: 현재 {}MB, 필요 {}MB", availableMB, requiredMB); - } - - return isAvailable; - } -} +package com.kamco.cd.training.postgres.core; + +import com.kamco.cd.training.postgres.entity.SystemMetricsEntity; +import com.kamco.cd.training.postgres.repository.SystemMetricsRepository; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +@Service +@RequiredArgsConstructor +@Transactional(readOnly = true) +@Slf4j +public class SystemMetricsCoreService { + + private final SystemMetricsRepository systemMetricsRepository; + + /** + * 현재 사용 가능한 저장공간 조회 (MB 단위) + * + * @return 사용 가능한 저장공간 (MB) + */ + public long getAvailableStorageMB() { + SystemMetricsEntity latestMetrics = + systemMetricsRepository + .findLatestMetrics() + .orElseThrow(() -> new IllegalStateException("시스템 메트릭 정보를 조회할 수 없습니다")); + + Long kbmemfree = latestMetrics.getKbmemfree(); + Long kbmemused = latestMetrics.getKbmemused(); + + if (kbmemfree == null || kbmemused == null) { + log.warn("시스템 메트릭에 메모리 정보가 없습니다"); + return 0L; + } + + // 남은 용량 = kbmemfree - kbmemused (KB 단위) + long availableKB = kbmemfree - kbmemused; + + // KB를 MB로 변환 + long availableMB = availableKB / 1024; + + log.info( + "사용 가능한 저장공간: {}MB (kbmemfree: {}KB, kbmemused: {}KB)", availableMB, kbmemfree, kbmemused); + + return Math.max(0L, availableMB); + } + + /** + * 학습 실행 가능 여부 확인 (10GB 이상 필요) + * + * @return 학습 실행 가능 여부 + */ + public boolean isStorageAvailableForTraining() { + long availableMB = getAvailableStorageMB(); + long requiredMB = 10 * 1024; // 10GB = 10,240MB + + boolean isAvailable = availableMB >= requiredMB; + + if (!isAvailable) { + log.warn("저장공간 부족: 현재 {}MB, 필요 {}MB", availableMB, requiredMB); + } + + return isAvailable; + } +} diff --git a/src/main/java/com/kamco/cd/training/postgres/core/TokenCoreService.java b/src/main/java/com/kamco/cd/training/postgres/core/TokenCoreService.java index 6305ebc..4ef6c69 100644 --- a/src/main/java/com/kamco/cd/training/postgres/core/TokenCoreService.java +++ b/src/main/java/com/kamco/cd/training/postgres/core/TokenCoreService.java @@ -1,57 +1,57 @@ -package com.kamco.cd.training.postgres.core; - -import com.kamco.cd.training.common.enums.error.AuthErrorCode; -import com.kamco.cd.training.common.exception.CustomApiException; -import com.kamco.cd.training.postgres.entity.AuthRefreshTokenEntity; -import com.kamco.cd.training.postgres.repository.members.AuthRefreshTokenRepository; -import java.time.ZonedDateTime; -import lombok.RequiredArgsConstructor; -import org.springframework.stereotype.Service; - -@Service -@RequiredArgsConstructor -public class TokenCoreService { - - private final AuthRefreshTokenRepository tokenRepository; - - public void save(String subject, String refreshToken, long validityMs) { - ZonedDateTime expiresAt = ZonedDateTime.now().plusSeconds(validityMs / 1000); - - tokenRepository - .findBySubject(subject) - .ifPresentOrElse( - entity -> entity.rotate(refreshToken, expiresAt), - () -> - tokenRepository.save(new AuthRefreshTokenEntity(subject, refreshToken, expiresAt))); - } - - /** - * refreshToken을 DB와 비교 검증 - * - * @param subject 사용자 식별(UUID) - * @return - */ - public String getValidTokenOrThrow(String subject) { - // DB 기준 유효한 RefreshToken 문자열 반환 (없으면 401 예외) - return tokenRepository - .findBySubjectAndRevokedDttmIsNullAndExpiresDttmAfter(subject, ZonedDateTime.now()) - .orElseThrow(() -> new CustomApiException(AuthErrorCode.REFRESH_TOKEN_EXPIRED_OR_REVOKED)) - .getToken(); - } - - /** - * 로그아웃 - * - * @param subject - */ - public void revokeBySubject(String subject) { - - AuthRefreshTokenEntity token = - tokenRepository - .findBySubjectAndRevokedDttmIsNullAndExpiresDttmAfter(subject, ZonedDateTime.now()) - .orElseThrow(() -> new CustomApiException(AuthErrorCode.REFRESH_TOKEN_MISMATCH)); - - // save() 호출 안 해도 됨 (영속 상태 + 트랜잭션) - token.revoke(); - } -} +package com.kamco.cd.training.postgres.core; + +import com.kamco.cd.training.common.enums.error.AuthErrorCode; +import com.kamco.cd.training.common.exception.CustomApiException; +import com.kamco.cd.training.postgres.entity.AuthRefreshTokenEntity; +import com.kamco.cd.training.postgres.repository.members.AuthRefreshTokenRepository; +import java.time.ZonedDateTime; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; + +@Service +@RequiredArgsConstructor +public class TokenCoreService { + + private final AuthRefreshTokenRepository tokenRepository; + + public void save(String subject, String refreshToken, long validityMs) { + ZonedDateTime expiresAt = ZonedDateTime.now().plusSeconds(validityMs / 1000); + + tokenRepository + .findBySubject(subject) + .ifPresentOrElse( + entity -> entity.rotate(refreshToken, expiresAt), + () -> + tokenRepository.save(new AuthRefreshTokenEntity(subject, refreshToken, expiresAt))); + } + + /** + * refreshToken을 DB와 비교 검증 + * + * @param subject 사용자 식별(UUID) + * @return + */ + public String getValidTokenOrThrow(String subject) { + // DB 기준 유효한 RefreshToken 문자열 반환 (없으면 401 예외) + return tokenRepository + .findBySubjectAndRevokedDttmIsNullAndExpiresDttmAfter(subject, ZonedDateTime.now()) + .orElseThrow(() -> new CustomApiException(AuthErrorCode.REFRESH_TOKEN_EXPIRED_OR_REVOKED)) + .getToken(); + } + + /** + * 로그아웃 + * + * @param subject + */ + public void revokeBySubject(String subject) { + + AuthRefreshTokenEntity token = + tokenRepository + .findBySubjectAndRevokedDttmIsNullAndExpiresDttmAfter(subject, ZonedDateTime.now()) + .orElseThrow(() -> new CustomApiException(AuthErrorCode.REFRESH_TOKEN_MISMATCH)); + + // save() 호출 안 해도 됨 (영속 상태 + 트랜잭션) + token.revoke(); + } +} diff --git a/src/main/java/com/kamco/cd/training/postgres/core/UploadSessionCoreService.java b/src/main/java/com/kamco/cd/training/postgres/core/UploadSessionCoreService.java index cf35d6d..1ad0432 100644 --- a/src/main/java/com/kamco/cd/training/postgres/core/UploadSessionCoreService.java +++ b/src/main/java/com/kamco/cd/training/postgres/core/UploadSessionCoreService.java @@ -1,57 +1,53 @@ -package com.kamco.cd.training.postgres.core; - -import com.kamco.cd.training.postgres.entity.UploadSessionEntity; -import com.kamco.cd.training.postgres.repository.upload.UploadSessionRepository; -import com.kamco.cd.training.upload.dto.UploadDto; -import java.util.UUID; -import lombok.RequiredArgsConstructor; -import org.springframework.stereotype.Service; - -@Service -@RequiredArgsConstructor -public class UploadSessionCoreService { - - private final UploadSessionRepository uploadSessionRepository; - - public void createUploadSession(UploadDto.UploadAddReq addReq) - { - /* - UUID newUuid = UUID.randomUUID(); - - UploadSessionEntity entity = new UploadSessionEntity(); - entity.setUploadId(addReq.getUploadId()); - entity.setDatasetId(addReq.getDatasetId()); - entity.setFileName(addReq.getFileName()); - entity.setFileSize(addReq.getFileSize()); - entity.setFinalPath(addReq.getFinalPath()); - entity.setStatus(addReq.getStatus()); - entity.setTempPath(addReq.getTempPath()); - entity.setChunkIndex(addReq.getChunkIndex()); - entity.setChunkTotalIndex(addReq.getChunkTotalIndex()); - entity.setUploadDivi(addReq.getUploadDivi()); - entity.setFileHash(addReq.getFileHash()); - entity.setUuid(newUuid); - - //System.out.println("======================"); - - UploadSessionEntity saved = uploadSessionRepository.save(entity); - - return String.valueOf(saved.getUuid()); - */ - - uploadSessionRepository.insertUploadSession(addReq); - - } - - public UploadDto.uploadDto findByDatasetUid(Long datasetId, String uploadDivi){ - return uploadSessionRepository.findByDatasetUid(datasetId, uploadDivi); - } - - public UploadDto.uploadDto findByUuid(String uuid){ - return uploadSessionRepository.findByUuid(uuid); - } - - public void updateUploadSessionStatus(UploadDto.UploadAddReq addReq){ - uploadSessionRepository.updateUploadSessionStatus(addReq); - } -} +package com.kamco.cd.training.postgres.core; + +import com.kamco.cd.training.postgres.repository.upload.UploadSessionRepository; +import com.kamco.cd.training.upload.dto.UploadDto; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; + +@Service +@RequiredArgsConstructor +public class UploadSessionCoreService { + + private final UploadSessionRepository uploadSessionRepository; + + public void createUploadSession(UploadDto.UploadAddReq addReq) { + /* + UUID newUuid = UUID.randomUUID(); + + UploadSessionEntity entity = new UploadSessionEntity(); + entity.setUploadId(addReq.getUploadId()); + entity.setDatasetId(addReq.getDatasetId()); + entity.setFileName(addReq.getFileName()); + entity.setFileSize(addReq.getFileSize()); + entity.setFinalPath(addReq.getFinalPath()); + entity.setStatus(addReq.getStatus()); + entity.setTempPath(addReq.getTempPath()); + entity.setChunkIndex(addReq.getChunkIndex()); + entity.setChunkTotalIndex(addReq.getChunkTotalIndex()); + entity.setUploadDivi(addReq.getUploadDivi()); + entity.setFileHash(addReq.getFileHash()); + entity.setUuid(newUuid); + + //System.out.println("======================"); + + UploadSessionEntity saved = uploadSessionRepository.save(entity); + + return String.valueOf(saved.getUuid()); + */ + + uploadSessionRepository.insertUploadSession(addReq); + } + + public UploadDto.uploadDto findByDatasetUid(Long datasetId, String uploadDivi) { + return uploadSessionRepository.findByDatasetUid(datasetId, uploadDivi); + } + + public UploadDto.uploadDto findByUuid(String uuid) { + return uploadSessionRepository.findByUuid(uuid); + } + + public void updateUploadSessionStatus(UploadDto.UploadAddReq addReq) { + uploadSessionRepository.updateUploadSessionStatus(addReq); + } +} diff --git a/src/main/java/com/kamco/cd/training/postgres/entity/AuditLogEntity.java b/src/main/java/com/kamco/cd/training/postgres/entity/AuditLogEntity.java index ad4407e..d8cf554 100644 --- a/src/main/java/com/kamco/cd/training/postgres/entity/AuditLogEntity.java +++ b/src/main/java/com/kamco/cd/training/postgres/entity/AuditLogEntity.java @@ -1,101 +1,101 @@ -package com.kamco.cd.training.postgres.entity; - -import com.kamco.cd.training.log.dto.AuditLogDto; -import com.kamco.cd.training.log.dto.EventStatus; -import com.kamco.cd.training.log.dto.EventType; -import com.kamco.cd.training.postgres.CommonCreateEntity; -import jakarta.persistence.*; -import lombok.AccessLevel; -import lombok.Getter; -import lombok.NoArgsConstructor; - -@Entity -@Getter -@NoArgsConstructor(access = AccessLevel.PROTECTED) -@Table(name = "tb_audit_log") -public class AuditLogEntity extends CommonCreateEntity { - @Id - @GeneratedValue(strategy = GenerationType.IDENTITY) - @Column(name = "audit_log_uid", nullable = false) - private Long id; - - @Column(name = "user_uid") - private Long userUid; - - @Enumerated(EnumType.STRING) - private EventType eventType; - - @Enumerated(EnumType.STRING) - private EventStatus eventStatus; - - @Column(name = "menu_uid") - private String menuUid; - - @Column(name = "ip_address") - private String ipAddress; - - @Column(name = "request_uri") - private String requestUri; - - @Column(name = "request_body", columnDefinition = "TEXT") - private String requestBody; - - @Column(name = "error_log_uid") - private Long errorLogUid; - - public AuditLogEntity( - Long userUid, - EventType eventType, - EventStatus eventStatus, - String menuUid, - String ipAddress, - String requestUri, - String requestBody, - Long errorLogUid) { - this.userUid = userUid; - this.eventType = eventType; - this.eventStatus = eventStatus; - this.menuUid = menuUid; - this.ipAddress = ipAddress; - this.requestUri = requestUri; - this.requestBody = requestBody; - this.errorLogUid = errorLogUid; - } - - public AuditLogDto.Basic toDto() { - return new AuditLogDto.Basic( - this.id, - this.userUid, - this.eventType, - this.eventStatus, - this.menuUid, - this.ipAddress, - this.requestUri, - this.requestBody, - this.errorLogUid, - super.getCreatedDate()); - } - - @Override - public String toString() { - StringBuilder sb = new StringBuilder(); - sb.append(this.id) - .append("\n") - .append(this.userUid) - .append("\n") - .append(this.eventType) - .append("\n") - .append(this.eventStatus) - .append("\n") - .append(this.menuUid) - .append("\n") - .append(this.ipAddress) - .append("\n") - .append(this.requestUri) - .append("\n") - .append(this.requestBody) - .append("\n") - .append(this.errorLogUid); - return sb.toString(); - } -} +package com.kamco.cd.training.postgres.entity; + +import com.kamco.cd.training.log.dto.AuditLogDto; +import com.kamco.cd.training.log.dto.EventStatus; +import com.kamco.cd.training.log.dto.EventType; +import com.kamco.cd.training.postgres.CommonCreateEntity; +import jakarta.persistence.*; +import lombok.AccessLevel; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Entity +@Getter +@NoArgsConstructor(access = AccessLevel.PROTECTED) +@Table(name = "tb_audit_log") +public class AuditLogEntity extends CommonCreateEntity { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "audit_log_uid", nullable = false) + private Long id; + + @Column(name = "user_uid") + private Long userUid; + + @Enumerated(EnumType.STRING) + private EventType eventType; + + @Enumerated(EnumType.STRING) + private EventStatus eventStatus; + + @Column(name = "menu_uid") + private String menuUid; + + @Column(name = "ip_address") + private String ipAddress; + + @Column(name = "request_uri") + private String requestUri; + + @Column(name = "request_body", columnDefinition = "TEXT") + private String requestBody; + + @Column(name = "error_log_uid") + private Long errorLogUid; + + public AuditLogEntity( + Long userUid, + EventType eventType, + EventStatus eventStatus, + String menuUid, + String ipAddress, + String requestUri, + String requestBody, + Long errorLogUid) { + this.userUid = userUid; + this.eventType = eventType; + this.eventStatus = eventStatus; + this.menuUid = menuUid; + this.ipAddress = ipAddress; + this.requestUri = requestUri; + this.requestBody = requestBody; + this.errorLogUid = errorLogUid; + } + + public AuditLogDto.Basic toDto() { + return new AuditLogDto.Basic( + this.id, + this.userUid, + this.eventType, + this.eventStatus, + this.menuUid, + this.ipAddress, + this.requestUri, + this.requestBody, + this.errorLogUid, + super.getCreatedDate()); + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + sb.append(this.id) + .append("\n") + .append(this.userUid) + .append("\n") + .append(this.eventType) + .append("\n") + .append(this.eventStatus) + .append("\n") + .append(this.menuUid) + .append("\n") + .append(this.ipAddress) + .append("\n") + .append(this.requestUri) + .append("\n") + .append(this.requestBody) + .append("\n") + .append(this.errorLogUid); + return sb.toString(); + } +} diff --git a/src/main/java/com/kamco/cd/training/postgres/entity/AuthRefreshTokenEntity.java b/src/main/java/com/kamco/cd/training/postgres/entity/AuthRefreshTokenEntity.java index c548605..b46275d 100644 --- a/src/main/java/com/kamco/cd/training/postgres/entity/AuthRefreshTokenEntity.java +++ b/src/main/java/com/kamco/cd/training/postgres/entity/AuthRefreshTokenEntity.java @@ -1,67 +1,67 @@ -package com.kamco.cd.training.postgres.entity; - -import jakarta.persistence.Column; -import jakarta.persistence.Entity; -import jakarta.persistence.GeneratedValue; -import jakarta.persistence.GenerationType; -import jakarta.persistence.Id; -import jakarta.persistence.Table; -import jakarta.validation.constraints.NotNull; -import jakarta.validation.constraints.Size; -import java.time.ZonedDateTime; -import lombok.Getter; -import lombok.NoArgsConstructor; -import lombok.Setter; -import org.hibernate.annotations.CreationTimestamp; -import org.hibernate.annotations.UpdateTimestamp; - -@Getter -@Setter -@Entity -@Table(name = "auth_refresh_token") -@NoArgsConstructor -public class AuthRefreshTokenEntity { - - @Id - @GeneratedValue(strategy = GenerationType.IDENTITY) - @Column(name = "id", nullable = false) - private Long id; - - @Size(max = 255) - @NotNull - @Column(name = "subject", nullable = false) - private String subject; - - @NotNull - @Column(name = "token", nullable = false, length = Integer.MAX_VALUE) - private String token; - - @NotNull - @Column(name = "expires_dttm", nullable = false) - private ZonedDateTime expiresDttm; - - @Column(name = "revoked_dttm") - private ZonedDateTime revokedDttm; - - @CreationTimestamp - @Column(updatable = false) - private ZonedDateTime createdDttm; - - @UpdateTimestamp private ZonedDateTime updatedDttm; - - public AuthRefreshTokenEntity(String subject, String token, ZonedDateTime expiresDttm) { - this.subject = subject; - this.token = token; - this.expiresDttm = expiresDttm; - } - - public void rotate(String token, ZonedDateTime expiresDttm) { - this.token = token; - this.expiresDttm = expiresDttm; - this.revokedDttm = null; - } - - public void revoke() { - this.revokedDttm = ZonedDateTime.now(); - } -} +package com.kamco.cd.training.postgres.entity; + +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.Table; +import jakarta.validation.constraints.NotNull; +import jakarta.validation.constraints.Size; +import java.time.ZonedDateTime; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; +import org.hibernate.annotations.CreationTimestamp; +import org.hibernate.annotations.UpdateTimestamp; + +@Getter +@Setter +@Entity +@Table(name = "auth_refresh_token") +@NoArgsConstructor +public class AuthRefreshTokenEntity { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "id", nullable = false) + private Long id; + + @Size(max = 255) + @NotNull + @Column(name = "subject", nullable = false) + private String subject; + + @NotNull + @Column(name = "token", nullable = false, length = Integer.MAX_VALUE) + private String token; + + @NotNull + @Column(name = "expires_dttm", nullable = false) + private ZonedDateTime expiresDttm; + + @Column(name = "revoked_dttm") + private ZonedDateTime revokedDttm; + + @CreationTimestamp + @Column(updatable = false) + private ZonedDateTime createdDttm; + + @UpdateTimestamp private ZonedDateTime updatedDttm; + + public AuthRefreshTokenEntity(String subject, String token, ZonedDateTime expiresDttm) { + this.subject = subject; + this.token = token; + this.expiresDttm = expiresDttm; + } + + public void rotate(String token, ZonedDateTime expiresDttm) { + this.token = token; + this.expiresDttm = expiresDttm; + this.revokedDttm = null; + } + + public void revoke() { + this.revokedDttm = ZonedDateTime.now(); + } +} diff --git a/src/main/java/com/kamco/cd/training/postgres/entity/CommonCodeEntity.java b/src/main/java/com/kamco/cd/training/postgres/entity/CommonCodeEntity.java index 8710d61..dcfbe00 100644 --- a/src/main/java/com/kamco/cd/training/postgres/entity/CommonCodeEntity.java +++ b/src/main/java/com/kamco/cd/training/postgres/entity/CommonCodeEntity.java @@ -1,135 +1,135 @@ -package com.kamco.cd.training.postgres.entity; - -import com.kamco.cd.training.code.dto.CommonCodeDto; -import com.kamco.cd.training.postgres.CommonDateEntity; -import jakarta.persistence.*; -import jakarta.validation.constraints.NotNull; -import jakarta.validation.constraints.Size; -import java.time.ZonedDateTime; -import java.util.ArrayList; -import java.util.List; -import lombok.AccessLevel; -import lombok.Getter; -import lombok.NoArgsConstructor; -import org.hibernate.annotations.Where; - -@Entity -@Getter -@NoArgsConstructor(access = AccessLevel.PROTECTED) -@Table(name = "tb_cm_cd") -public class CommonCodeEntity extends CommonDateEntity { - - @Id - @GeneratedValue(strategy = GenerationType.IDENTITY) - @Column(name = "code_id", nullable = false, updatable = false) - private Long id; - - @Size(max = 255) - @Column(name = "code_cd", updatable = false) - private String code; - - @Size(max = 255) - @Column(name = "cd_ct") - private String description; - - @Size(max = 255) - @Column(name = "cd_nm") - private String name; - - @Column(name = "cd_odr") - private Integer order; - - @Column(name = "used") - private Boolean used; - - @NotNull - @Column(name = "deleted", nullable = false) - private Boolean deleted = false; - - @ManyToOne(fetch = FetchType.LAZY) - @JoinColumn(name = "parent_id", updatable = false) - private CommonCodeEntity parent; - - @OneToMany(mappedBy = "parent", fetch = FetchType.LAZY, cascade = CascadeType.ALL) - @Where(clause = "deleted = false or deleted is null") - private List children = new ArrayList<>(); - - @Size(max = 255) - @Column(name = "props1") - private String props1; - - @Size(max = 255) - @Column(name = "props2") - private String props2; - - @Size(max = 255) - @Column(name = "props3") - private String props3; - - @Column(name = "deleted_dttm") - private ZonedDateTime deletedDttm; - - public CommonCodeEntity( - String code, - String name, - String description, - Integer order, - Boolean used, - String props1, - String props2, - String props3) { - this.code = code; - this.name = name; - this.description = description; - this.order = order; - this.used = used; - this.props1 = props1; - this.props2 = props2; - this.props3 = props3; - } - - public CommonCodeDto.Basic toDto() { - return new CommonCodeDto.Basic( - this.id, - this.code, - this.description, - this.name, - this.order, - this.used, - this.deleted, - this.children.stream().map(CommonCodeEntity::toDto).toList(), - super.getCreatedDate(), - super.getModifiedDate(), - this.props1, - this.props2, - this.props3, - this.deletedDttm); - } - - public void addParent(CommonCodeEntity parent) { - this.parent = parent; - } - - public boolean isDeleted() { - return deleted; - } - - public void deleted() { - this.deleted = true; - this.deletedDttm = ZonedDateTime.now(); - } - - public void updateOrder(int order) { - this.order = order; - } - - public void update( - String name, String description, boolean used, String props1, String props2, String props3) { - this.name = name; - this.description = description; - this.used = used; - this.props1 = props1; - this.props2 = props2; - this.props3 = props3; - } -} +package com.kamco.cd.training.postgres.entity; + +import com.kamco.cd.training.code.dto.CommonCodeDto; +import com.kamco.cd.training.postgres.CommonDateEntity; +import jakarta.persistence.*; +import jakarta.validation.constraints.NotNull; +import jakarta.validation.constraints.Size; +import java.time.ZonedDateTime; +import java.util.ArrayList; +import java.util.List; +import lombok.AccessLevel; +import lombok.Getter; +import lombok.NoArgsConstructor; +import org.hibernate.annotations.Where; + +@Entity +@Getter +@NoArgsConstructor(access = AccessLevel.PROTECTED) +@Table(name = "tb_cm_cd") +public class CommonCodeEntity extends CommonDateEntity { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "code_id", nullable = false, updatable = false) + private Long id; + + @Size(max = 255) + @Column(name = "code_cd", updatable = false) + private String code; + + @Size(max = 255) + @Column(name = "cd_ct") + private String description; + + @Size(max = 255) + @Column(name = "cd_nm") + private String name; + + @Column(name = "cd_odr") + private Integer order; + + @Column(name = "used") + private Boolean used; + + @NotNull + @Column(name = "deleted", nullable = false) + private Boolean deleted = false; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "parent_id", updatable = false) + private CommonCodeEntity parent; + + @OneToMany(mappedBy = "parent", fetch = FetchType.LAZY, cascade = CascadeType.ALL) + @Where(clause = "deleted = false or deleted is null") + private List children = new ArrayList<>(); + + @Size(max = 255) + @Column(name = "props1") + private String props1; + + @Size(max = 255) + @Column(name = "props2") + private String props2; + + @Size(max = 255) + @Column(name = "props3") + private String props3; + + @Column(name = "deleted_dttm") + private ZonedDateTime deletedDttm; + + public CommonCodeEntity( + String code, + String name, + String description, + Integer order, + Boolean used, + String props1, + String props2, + String props3) { + this.code = code; + this.name = name; + this.description = description; + this.order = order; + this.used = used; + this.props1 = props1; + this.props2 = props2; + this.props3 = props3; + } + + public CommonCodeDto.Basic toDto() { + return new CommonCodeDto.Basic( + this.id, + this.code, + this.description, + this.name, + this.order, + this.used, + this.deleted, + this.children.stream().map(CommonCodeEntity::toDto).toList(), + super.getCreatedDate(), + super.getModifiedDate(), + this.props1, + this.props2, + this.props3, + this.deletedDttm); + } + + public void addParent(CommonCodeEntity parent) { + this.parent = parent; + } + + public boolean isDeleted() { + return deleted; + } + + public void deleted() { + this.deleted = true; + this.deletedDttm = ZonedDateTime.now(); + } + + public void updateOrder(int order) { + this.order = order; + } + + public void update( + String name, String description, boolean used, String props1, String props2, String props3) { + this.name = name; + this.description = description; + this.used = used; + this.props1 = props1; + this.props2 = props2; + this.props3 = props3; + } +} diff --git a/src/main/java/com/kamco/cd/training/postgres/entity/DatasetEntity.java b/src/main/java/com/kamco/cd/training/postgres/entity/DatasetEntity.java index b6e73a1..026becc 100644 --- a/src/main/java/com/kamco/cd/training/postgres/entity/DatasetEntity.java +++ b/src/main/java/com/kamco/cd/training/postgres/entity/DatasetEntity.java @@ -1,132 +1,132 @@ -package com.kamco.cd.training.postgres.entity; - -import com.kamco.cd.training.dataset.dto.DatasetDto; -import jakarta.persistence.Column; -import jakarta.persistence.Entity; -import jakarta.persistence.GeneratedValue; -import jakarta.persistence.GenerationType; -import jakarta.persistence.Id; -import jakarta.persistence.Table; -import jakarta.validation.constraints.NotNull; -import jakarta.validation.constraints.Size; -import java.time.Instant; -import java.time.ZonedDateTime; -import java.util.Map; -import java.util.UUID; -import lombok.Getter; -import lombok.Setter; -import org.hibernate.annotations.ColumnDefault; -import org.hibernate.annotations.JdbcTypeCode; -import org.hibernate.type.SqlTypes; - -@Getter -@Setter -@Entity -@Table(name = "tb_dataset") -public class DatasetEntity { - - @Id - @GeneratedValue(strategy = GenerationType.IDENTITY) - @Column(name = "dataset_uid", nullable = false) - private Long id; - - @Column(name = "uuid", nullable = false) - private UUID uuid; - - @Size(max = 200) - @Column(name = "group_title", length = 200) - private String groupTitle; - - @Size(max = 10) - @Column(name = "data_year", length = 10) - private String dataYear; - - @Size(max = 50) - @ColumnDefault("'CREATE'") - @Column(name = "data_type", length = 50) - private String dataType; - - @ColumnDefault("1") - @Column(name = "round_no") - private Long roundNo; - - @ColumnDefault("0") - @Column(name = "total_size") - private Long totalSize; - - @ColumnDefault("0") - @Column(name = "item_count") - private Long itemCount; - - @Column(name = "memo", length = Integer.MAX_VALUE) - private String memo; - - @Size(max = 20) - @ColumnDefault("'READY'") - @Column(name = "status", length = 20) - private String status; - - @Size(max = 50) - @Column(name = "reg_user_id", length = 50) - private String regUserId; - - @ColumnDefault("now()") - @Column(name = "reg_dttm") - private Instant regDttm; - - @Column(name = "mod_dttm") - private Instant modDttm; - - @Column(name = "id") - private Long id1; - - @Size(max = 50) - @Column(name = "created_by", length = 50) - private String createdBy; - - @NotNull - @ColumnDefault("now()") - @Column(name = "created_dttm", nullable = false) - private ZonedDateTime createdDttm; - - @NotNull - @ColumnDefault("false") - @Column(name = "deleted", nullable = false) - private Boolean deleted = false; - - @Size(max = 200) - @Column(name = "title", length = 200) - private String title; - - @Column(name = "total_items") - private Long totalItems; - - @Size(max = 50) - @Column(name = "updated_by", length = 50) - private String updatedBy; - - @Column(name = "updated_dttm") - private ZonedDateTime updatedDttm; - - @Size(max = 4) - @Column(name = "year", length = 4) - private String year; - - @Column(name = "class_counts", columnDefinition = "jsonb") - @JdbcTypeCode(SqlTypes.JSON) - private Map classCounts; - - public DatasetDto.Basic toDto() { - return new DatasetDto.Basic( - this.id, - this.uuid, - this.groupTitle, - this.title, - this.roundNo, - this.totalSize, - this.memo, - this.createdDttm, - this.status, - this.deleted); - } -} +package com.kamco.cd.training.postgres.entity; + +import com.kamco.cd.training.dataset.dto.DatasetDto; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.Table; +import jakarta.validation.constraints.NotNull; +import jakarta.validation.constraints.Size; +import java.time.Instant; +import java.time.ZonedDateTime; +import java.util.Map; +import java.util.UUID; +import lombok.Getter; +import lombok.Setter; +import org.hibernate.annotations.ColumnDefault; +import org.hibernate.annotations.JdbcTypeCode; +import org.hibernate.type.SqlTypes; + +@Getter +@Setter +@Entity +@Table(name = "tb_dataset") +public class DatasetEntity { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "dataset_uid", nullable = false) + private Long id; + + @Column(name = "uuid", nullable = false) + private UUID uuid; + + @Size(max = 200) + @Column(name = "group_title", length = 200) + private String groupTitle; + + @Size(max = 10) + @Column(name = "data_year", length = 10) + private String dataYear; + + @Size(max = 50) + @ColumnDefault("'CREATE'") + @Column(name = "data_type", length = 50) + private String dataType; + + @ColumnDefault("1") + @Column(name = "round_no") + private Long roundNo; + + @ColumnDefault("0") + @Column(name = "total_size") + private Long totalSize; + + @ColumnDefault("0") + @Column(name = "item_count") + private Long itemCount; + + @Column(name = "memo", length = Integer.MAX_VALUE) + private String memo; + + @Size(max = 20) + @ColumnDefault("'READY'") + @Column(name = "status", length = 20) + private String status; + + @Size(max = 50) + @Column(name = "reg_user_id", length = 50) + private String regUserId; + + @ColumnDefault("now()") + @Column(name = "reg_dttm") + private Instant regDttm; + + @Column(name = "mod_dttm") + private Instant modDttm; + + @Column(name = "id") + private Long id1; + + @Size(max = 50) + @Column(name = "created_by", length = 50) + private String createdBy; + + @NotNull + @ColumnDefault("now()") + @Column(name = "created_dttm", nullable = false) + private ZonedDateTime createdDttm; + + @NotNull + @ColumnDefault("false") + @Column(name = "deleted", nullable = false) + private Boolean deleted = false; + + @Size(max = 200) + @Column(name = "title", length = 200) + private String title; + + @Column(name = "total_items") + private Long totalItems; + + @Size(max = 50) + @Column(name = "updated_by", length = 50) + private String updatedBy; + + @Column(name = "updated_dttm") + private ZonedDateTime updatedDttm; + + @Size(max = 4) + @Column(name = "year", length = 4) + private String year; + + @Column(name = "class_counts", columnDefinition = "jsonb") + @JdbcTypeCode(SqlTypes.JSON) + private Map classCounts; + + public DatasetDto.Basic toDto() { + return new DatasetDto.Basic( + this.id, + this.uuid, + this.groupTitle, + this.title, + this.roundNo, + this.totalSize, + this.memo, + this.createdDttm, + this.status, + this.deleted); + } +} diff --git a/src/main/java/com/kamco/cd/training/postgres/entity/ErrorLogEntity.java b/src/main/java/com/kamco/cd/training/postgres/entity/ErrorLogEntity.java index 5553f9c..c1d558a 100644 --- a/src/main/java/com/kamco/cd/training/postgres/entity/ErrorLogEntity.java +++ b/src/main/java/com/kamco/cd/training/postgres/entity/ErrorLogEntity.java @@ -1,61 +1,61 @@ -package com.kamco.cd.training.postgres.entity; - -import com.kamco.cd.training.log.dto.ErrorLogDto; -import com.kamco.cd.training.log.dto.EventType; -import com.kamco.cd.training.postgres.CommonCreateEntity; -import jakarta.persistence.*; -import jakarta.validation.constraints.Size; -import java.time.ZonedDateTime; -import lombok.AccessLevel; -import lombok.Getter; -import lombok.NoArgsConstructor; - -@Entity -@Getter -@NoArgsConstructor(access = AccessLevel.PROTECTED) -@Table(name = "tb_error_log") -public class ErrorLogEntity extends CommonCreateEntity { - @Id - @GeneratedValue(strategy = GenerationType.IDENTITY) - @Column(name = "error_log_uid", nullable = false) - private Long id; - - @Column(name = "request_id") - private String requestId; - - @Column(name = "error_type") - @Enumerated(EnumType.STRING) - private EventType errorType; - - @Enumerated(EnumType.STRING) - private ErrorLogDto.LogErrorLevel errorLevel; - - private String errorCode; - private String errorMessage; - - @Size(max = 255) - @Column(name = "stack_trace") - private String stackTrace; - - private Long handlerUid; - private ZonedDateTime handledDttm; - - public ErrorLogEntity( - String requestId, - EventType errorType, - ErrorLogDto.LogErrorLevel errorLevel, - String errorCode, - String errorMessage, - String stackTrace, - Long handlerUid, - ZonedDateTime handledDttm) { - this.requestId = requestId; - this.errorType = errorType; - this.errorLevel = errorLevel; - this.errorCode = errorCode; - this.errorMessage = errorMessage; - this.stackTrace = stackTrace; - this.handlerUid = handlerUid; - this.handledDttm = handledDttm; - } -} +package com.kamco.cd.training.postgres.entity; + +import com.kamco.cd.training.log.dto.ErrorLogDto; +import com.kamco.cd.training.log.dto.EventType; +import com.kamco.cd.training.postgres.CommonCreateEntity; +import jakarta.persistence.*; +import jakarta.validation.constraints.Size; +import java.time.ZonedDateTime; +import lombok.AccessLevel; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Entity +@Getter +@NoArgsConstructor(access = AccessLevel.PROTECTED) +@Table(name = "tb_error_log") +public class ErrorLogEntity extends CommonCreateEntity { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "error_log_uid", nullable = false) + private Long id; + + @Column(name = "request_id") + private String requestId; + + @Column(name = "error_type") + @Enumerated(EnumType.STRING) + private EventType errorType; + + @Enumerated(EnumType.STRING) + private ErrorLogDto.LogErrorLevel errorLevel; + + private String errorCode; + private String errorMessage; + + @Size(max = 255) + @Column(name = "stack_trace") + private String stackTrace; + + private Long handlerUid; + private ZonedDateTime handledDttm; + + public ErrorLogEntity( + String requestId, + EventType errorType, + ErrorLogDto.LogErrorLevel errorLevel, + String errorCode, + String errorMessage, + String stackTrace, + Long handlerUid, + ZonedDateTime handledDttm) { + this.requestId = requestId; + this.errorType = errorType; + this.errorLevel = errorLevel; + this.errorCode = errorCode; + this.errorMessage = errorMessage; + this.stackTrace = stackTrace; + this.handlerUid = handlerUid; + this.handledDttm = handledDttm; + } +} diff --git a/src/main/java/com/kamco/cd/training/postgres/entity/LearnDataEntity.java b/src/main/java/com/kamco/cd/training/postgres/entity/LearnDataEntity.java index a339417..4118ba5 100644 --- a/src/main/java/com/kamco/cd/training/postgres/entity/LearnDataEntity.java +++ b/src/main/java/com/kamco/cd/training/postgres/entity/LearnDataEntity.java @@ -1,80 +1,80 @@ -package com.kamco.cd.training.postgres.entity; - -import jakarta.persistence.Column; -import jakarta.persistence.Entity; -import jakarta.persistence.GeneratedValue; -import jakarta.persistence.GenerationType; -import jakarta.persistence.Id; -import jakarta.persistence.PrePersist; -import jakarta.persistence.PreUpdate; -import jakarta.persistence.Table; -import jakarta.validation.constraints.NotNull; -import jakarta.validation.constraints.Size; -import java.time.ZonedDateTime; -import lombok.Getter; -import lombok.Setter; -import org.hibernate.annotations.ColumnDefault; - -@Getter -@Setter -@Entity -@Table(name = "tb_learn_data") -public class LearnDataEntity { - - @Id - @GeneratedValue(strategy = GenerationType.IDENTITY) - @Column(name = "id", nullable = false) - private Long id; - - @Size(max = 50) - @NotNull - @Column(name = "category", nullable = false, length = 50) - private String category; - - @Size(max = 200) - @Column(name = "title", length = 200) - private String title; - - @Column(name = "episode") - private Long episode; - - @Size(max = 20) - @Column(name = "capacity", length = 20) - private String capacity; - - @Column(name = "memo", columnDefinition = "text") - private String memo; - - @Size(max = 20) - @NotNull - @Column(name = "status", nullable = false, length = 20) - private String status; - - @NotNull - @ColumnDefault("false") - @Column(name = "deleted", nullable = false) - private Boolean deleted = false; - - @NotNull - @ColumnDefault("now()") - @Column(name = "created_dttm", nullable = false) - private ZonedDateTime createdDttm = ZonedDateTime.now(); - - @Column(name = "updated_dttm") - private ZonedDateTime updatedDttm; - - @PrePersist - protected void onPersist() { - if (this.createdDttm == null) { - this.createdDttm = ZonedDateTime.now(); - } - if (this.deleted == null) { - this.deleted = false; - } - } - - @PreUpdate - protected void onUpdate() { - this.updatedDttm = ZonedDateTime.now(); - } -} +package com.kamco.cd.training.postgres.entity; + +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.PrePersist; +import jakarta.persistence.PreUpdate; +import jakarta.persistence.Table; +import jakarta.validation.constraints.NotNull; +import jakarta.validation.constraints.Size; +import java.time.ZonedDateTime; +import lombok.Getter; +import lombok.Setter; +import org.hibernate.annotations.ColumnDefault; + +@Getter +@Setter +@Entity +@Table(name = "tb_learn_data") +public class LearnDataEntity { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "id", nullable = false) + private Long id; + + @Size(max = 50) + @NotNull + @Column(name = "category", nullable = false, length = 50) + private String category; + + @Size(max = 200) + @Column(name = "title", length = 200) + private String title; + + @Column(name = "episode") + private Long episode; + + @Size(max = 20) + @Column(name = "capacity", length = 20) + private String capacity; + + @Column(name = "memo", columnDefinition = "text") + private String memo; + + @Size(max = 20) + @NotNull + @Column(name = "status", nullable = false, length = 20) + private String status; + + @NotNull + @ColumnDefault("false") + @Column(name = "deleted", nullable = false) + private Boolean deleted = false; + + @NotNull + @ColumnDefault("now()") + @Column(name = "created_dttm", nullable = false) + private ZonedDateTime createdDttm = ZonedDateTime.now(); + + @Column(name = "updated_dttm") + private ZonedDateTime updatedDttm; + + @PrePersist + protected void onPersist() { + if (this.createdDttm == null) { + this.createdDttm = ZonedDateTime.now(); + } + if (this.deleted == null) { + this.deleted = false; + } + } + + @PreUpdate + protected void onUpdate() { + this.updatedDttm = ZonedDateTime.now(); + } +} diff --git a/src/main/java/com/kamco/cd/training/postgres/entity/MapSheetEntity.java b/src/main/java/com/kamco/cd/training/postgres/entity/MapSheetEntity.java index 71898f5..b73861f 100644 --- a/src/main/java/com/kamco/cd/training/postgres/entity/MapSheetEntity.java +++ b/src/main/java/com/kamco/cd/training/postgres/entity/MapSheetEntity.java @@ -1,105 +1,105 @@ -package com.kamco.cd.training.postgres.entity; - -import com.kamco.cd.training.dataset.dto.MapSheetDto; -import jakarta.persistence.*; -import jakarta.validation.constraints.NotNull; -import jakarta.validation.constraints.Size; -import java.time.ZonedDateTime; -import lombok.Getter; -import lombok.Setter; -import org.hibernate.annotations.ColumnDefault; - -@Getter -@Setter -@Entity -@Table(name = "tb_map_sheet") -public class MapSheetEntity { - - @Id - @GeneratedValue(strategy = GenerationType.IDENTITY) - @Column(name = "id", nullable = false) - private Long id; - - @NotNull - @Column(name = "object_id", nullable = false) - private Long objectId; - - @NotNull - @Column(name = "dataset_id", nullable = false) - private Long datasetId; - - @NotNull - @Size(max = 20) - @Column(name = "sheet_num", nullable = false, length = 20) - private String sheetNum; - - @Size(max = 255) - @Column(name = "file_name", length = 255) - private String fileName; - - @Column(name = "file_size") - private Long fileSize; - - @Size(max = 500) - @Column(name = "file_path", length = 500) - private String filePath; - - @Size(max = 20) - @Column(name = "status", length = 20) - private String status; - - @Column(name = "memo", columnDefinition = "TEXT") - private String memo; - - @NotNull - @ColumnDefault("false") - @Column(name = "deleted", nullable = false) - private Boolean deleted = false; - - @NotNull - @ColumnDefault("now()") - @Column(name = "created_dttm", nullable = false) - private ZonedDateTime createdDttm; - - @Column(name = "updated_dttm") - private ZonedDateTime updatedDttm; - - @Column(name = "map_id_cd_no") - private Long mapIdCdNo; - - @Size(max = 128) - @Column(name = "map_id_nm", length = 128) - private String mapIdNm; - - @Size(max = 128) - @Column(name = "shape_leng", length = 128) - private String shapeLeng; - - @Column(name = "shape_area") - private Double shapeArea; - - @Column(name = "scale_ratio") - private Long scaleRatio; - - @Column(name = "wkt_geom", columnDefinition = "TEXT") - private String wktGeom; - - @Column(name = "ref_map_id_cd_no") - private Long refMapIdCdNo; - - public MapSheetDto.Basic toDto() { - MapSheetDto.Basic dto = new MapSheetDto.Basic(); - dto.setId(this.id); - dto.setDatasetId(this.datasetId); - dto.setSheetNum(this.sheetNum); - dto.setFileName(this.fileName); - dto.setFileSize(this.fileSize); - dto.setFilePath(this.filePath); - dto.setStatus(this.status); - dto.setMemo(this.memo); - dto.setDeleted(this.deleted); - dto.setCreatedDttm(this.createdDttm); - dto.setUpdatedDttm(this.updatedDttm); - return dto; - } -} +package com.kamco.cd.training.postgres.entity; + +import com.kamco.cd.training.dataset.dto.MapSheetDto; +import jakarta.persistence.*; +import jakarta.validation.constraints.NotNull; +import jakarta.validation.constraints.Size; +import java.time.ZonedDateTime; +import lombok.Getter; +import lombok.Setter; +import org.hibernate.annotations.ColumnDefault; + +@Getter +@Setter +@Entity +@Table(name = "tb_map_sheet") +public class MapSheetEntity { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "id", nullable = false) + private Long id; + + @NotNull + @Column(name = "object_id", nullable = false) + private Long objectId; + + @NotNull + @Column(name = "dataset_id", nullable = false) + private Long datasetId; + + @NotNull + @Size(max = 20) + @Column(name = "sheet_num", nullable = false, length = 20) + private String sheetNum; + + @Size(max = 255) + @Column(name = "file_name", length = 255) + private String fileName; + + @Column(name = "file_size") + private Long fileSize; + + @Size(max = 500) + @Column(name = "file_path", length = 500) + private String filePath; + + @Size(max = 20) + @Column(name = "status", length = 20) + private String status; + + @Column(name = "memo", columnDefinition = "TEXT") + private String memo; + + @NotNull + @ColumnDefault("false") + @Column(name = "deleted", nullable = false) + private Boolean deleted = false; + + @NotNull + @ColumnDefault("now()") + @Column(name = "created_dttm", nullable = false) + private ZonedDateTime createdDttm; + + @Column(name = "updated_dttm") + private ZonedDateTime updatedDttm; + + @Column(name = "map_id_cd_no") + private Long mapIdCdNo; + + @Size(max = 128) + @Column(name = "map_id_nm", length = 128) + private String mapIdNm; + + @Size(max = 128) + @Column(name = "shape_leng", length = 128) + private String shapeLeng; + + @Column(name = "shape_area") + private Double shapeArea; + + @Column(name = "scale_ratio") + private Long scaleRatio; + + @Column(name = "wkt_geom", columnDefinition = "TEXT") + private String wktGeom; + + @Column(name = "ref_map_id_cd_no") + private Long refMapIdCdNo; + + public MapSheetDto.Basic toDto() { + MapSheetDto.Basic dto = new MapSheetDto.Basic(); + dto.setId(this.id); + dto.setDatasetId(this.datasetId); + dto.setSheetNum(this.sheetNum); + dto.setFileName(this.fileName); + dto.setFileSize(this.fileSize); + dto.setFilePath(this.filePath); + dto.setStatus(this.status); + dto.setMemo(this.memo); + dto.setDeleted(this.deleted); + dto.setCreatedDttm(this.createdDttm); + dto.setUpdatedDttm(this.updatedDttm); + return dto; + } +} diff --git a/src/main/java/com/kamco/cd/training/postgres/entity/MemberEntity.java b/src/main/java/com/kamco/cd/training/postgres/entity/MemberEntity.java index 4dfee72..dedc498 100644 --- a/src/main/java/com/kamco/cd/training/postgres/entity/MemberEntity.java +++ b/src/main/java/com/kamco/cd/training/postgres/entity/MemberEntity.java @@ -1,114 +1,114 @@ -package com.kamco.cd.training.postgres.entity; - -import com.kamco.cd.training.common.enums.StatusType; -import com.kamco.cd.training.members.dto.MembersDto; -import jakarta.persistence.*; -import jakarta.validation.constraints.NotNull; -import jakarta.validation.constraints.Size; -import java.time.ZonedDateTime; -import java.util.Objects; -import java.util.UUID; -import lombok.Getter; -import lombok.Setter; -import org.hibernate.annotations.ColumnDefault; - -@Getter -@Setter -@Entity -@Table(name = "tb_member") -public class MemberEntity { - - @Id - @GeneratedValue(strategy = GenerationType.IDENTITY) - @Column(name = "id", nullable = false) - private Long id; - - @NotNull - @ColumnDefault("gen_random_uuid()") - @Column(name = "uuid", nullable = false) - private UUID uuid = UUID.randomUUID(); - - @Size(max = 50) - @NotNull - @Column(name = "user_role", nullable = false, length = 50) - private String userRole; - - @Size(max = 50) - @NotNull - @Column(name = "user_id", nullable = false, length = 50) - private String userId; - - @Size(max = 50) - @NotNull - @Column(name = "employee_no", nullable = false, length = 50) - private String employeeNo; - - @Size(max = 100) - @NotNull - @Column(name = "name", nullable = false, length = 100) - private String name; - - @Size(max = 255) - @NotNull - @Column(name = "password", nullable = false) - private String password; - - @Size(max = 20) - @Column(name = "status", length = 20) - private String status = StatusType.PENDING.getId(); - - @NotNull - @ColumnDefault("now()") - @Column(name = "created_dttm", nullable = false) - private ZonedDateTime createdDttm = ZonedDateTime.now(); - - @ColumnDefault("now()") - @Column(name = "updated_dttm") - private ZonedDateTime updatedDttm = ZonedDateTime.now(); - - @Column(name = "first_login_dttm") - private ZonedDateTime firstLoginDttm; - - @Column(name = "last_login_dttm") - private ZonedDateTime lastLoginDttm; - - @Column(name = "login_fail_count") - @ColumnDefault("0") - private Integer loginFailCount = 0; - - @Column(name = "rgstr_uid") - private Long rgstrUidl; - - @Column(name = "updtr_uid") - private Long updtrUid; - - @Column(name = "status_chg_dttm") - private ZonedDateTime statusChgDttm; - - @Column(name = "pwd_reset_yn") - private Boolean pwdResetYn; - - public void changeStatus(String newStatus) { - // 같은 값 보내도 무시 - if (Objects.equals(this.status, newStatus)) { - return; - } - this.status = newStatus; - this.statusChgDttm = ZonedDateTime.now(); - } - - public MembersDto.Basic toDto() { - return new MembersDto.Basic( - this.id, - this.uuid, - this.userRole, - this.name, - this.employeeNo, - this.status, - this.createdDttm, - this.firstLoginDttm, - this.lastLoginDttm, - this.statusChgDttm, - this.pwdResetYn); - } -} +package com.kamco.cd.training.postgres.entity; + +import com.kamco.cd.training.common.enums.StatusType; +import com.kamco.cd.training.members.dto.MembersDto; +import jakarta.persistence.*; +import jakarta.validation.constraints.NotNull; +import jakarta.validation.constraints.Size; +import java.time.ZonedDateTime; +import java.util.Objects; +import java.util.UUID; +import lombok.Getter; +import lombok.Setter; +import org.hibernate.annotations.ColumnDefault; + +@Getter +@Setter +@Entity +@Table(name = "tb_member") +public class MemberEntity { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "id", nullable = false) + private Long id; + + @NotNull + @ColumnDefault("gen_random_uuid()") + @Column(name = "uuid", nullable = false) + private UUID uuid = UUID.randomUUID(); + + @Size(max = 50) + @NotNull + @Column(name = "user_role", nullable = false, length = 50) + private String userRole; + + @Size(max = 50) + @NotNull + @Column(name = "user_id", nullable = false, length = 50) + private String userId; + + @Size(max = 50) + @NotNull + @Column(name = "employee_no", nullable = false, length = 50) + private String employeeNo; + + @Size(max = 100) + @NotNull + @Column(name = "name", nullable = false, length = 100) + private String name; + + @Size(max = 255) + @NotNull + @Column(name = "password", nullable = false) + private String password; + + @Size(max = 20) + @Column(name = "status", length = 20) + private String status = StatusType.PENDING.getId(); + + @NotNull + @ColumnDefault("now()") + @Column(name = "created_dttm", nullable = false) + private ZonedDateTime createdDttm = ZonedDateTime.now(); + + @ColumnDefault("now()") + @Column(name = "updated_dttm") + private ZonedDateTime updatedDttm = ZonedDateTime.now(); + + @Column(name = "first_login_dttm") + private ZonedDateTime firstLoginDttm; + + @Column(name = "last_login_dttm") + private ZonedDateTime lastLoginDttm; + + @Column(name = "login_fail_count") + @ColumnDefault("0") + private Integer loginFailCount = 0; + + @Column(name = "rgstr_uid") + private Long rgstrUidl; + + @Column(name = "updtr_uid") + private Long updtrUid; + + @Column(name = "status_chg_dttm") + private ZonedDateTime statusChgDttm; + + @Column(name = "pwd_reset_yn") + private Boolean pwdResetYn; + + public void changeStatus(String newStatus) { + // 같은 값 보내도 무시 + if (Objects.equals(this.status, newStatus)) { + return; + } + this.status = newStatus; + this.statusChgDttm = ZonedDateTime.now(); + } + + public MembersDto.Basic toDto() { + return new MembersDto.Basic( + this.id, + this.uuid, + this.userRole, + this.name, + this.employeeNo, + this.status, + this.createdDttm, + this.firstLoginDttm, + this.lastLoginDttm, + this.statusChgDttm, + this.pwdResetYn); + } +} diff --git a/src/main/java/com/kamco/cd/training/postgres/entity/MenuEntity.java b/src/main/java/com/kamco/cd/training/postgres/entity/MenuEntity.java index 3247c4f..a853b92 100644 --- a/src/main/java/com/kamco/cd/training/postgres/entity/MenuEntity.java +++ b/src/main/java/com/kamco/cd/training/postgres/entity/MenuEntity.java @@ -1,71 +1,71 @@ -package com.kamco.cd.training.postgres.entity; - -import com.kamco.cd.training.menu.dto.MenuDto; -import com.kamco.cd.training.postgres.CommonDateEntity; -import jakarta.persistence.*; -import jakarta.validation.constraints.NotNull; -import java.util.ArrayList; -import java.util.List; -import lombok.AccessLevel; -import lombok.Getter; -import lombok.NoArgsConstructor; - -@Entity -@Getter -@NoArgsConstructor(access = AccessLevel.PROTECTED) -@Table(name = "tb_menu") -public class MenuEntity extends CommonDateEntity { - @Id - @Column(name = "menu_uid") - private String menuUid; - - @Column(name = "menu_nm") - private String menuNm; - - @Column(name = "menu_url") - private String menuUrl; - - @Column(name = "description") - private String description; - - @Column(name = "menu_order") - private Long menuOrder; - - @NotNull - @Column(name = "is_use", nullable = true) - private Boolean isUse = true; - - @NotNull - @Column(name = "deleted", nullable = false) - private Boolean deleted = false; - - private Long createdUid; - private Long updatedUid; - - @ManyToOne(fetch = FetchType.LAZY) - @JoinColumn(name = "parent_menu_uid") - private MenuEntity parent; - - @OneToMany(mappedBy = "parent", fetch = FetchType.LAZY, cascade = CascadeType.ALL) - private List children = new ArrayList<>(); - - @Column(name = "menu_api_uri") - private String menuApiUri; - - public MenuDto.Basic toDto() { - return new MenuDto.Basic( - this.menuUid, - this.menuNm, - this.menuUrl, - this.description, - this.menuOrder, - this.isUse, - this.deleted, - this.createdUid, - this.updatedUid, - this.children.stream().map(MenuEntity::toDto).toList(), - this.getCreatedDate(), - this.getModifiedDate(), - this.menuApiUri); - } -} +package com.kamco.cd.training.postgres.entity; + +import com.kamco.cd.training.menu.dto.MenuDto; +import com.kamco.cd.training.postgres.CommonDateEntity; +import jakarta.persistence.*; +import jakarta.validation.constraints.NotNull; +import java.util.ArrayList; +import java.util.List; +import lombok.AccessLevel; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Entity +@Getter +@NoArgsConstructor(access = AccessLevel.PROTECTED) +@Table(name = "tb_menu") +public class MenuEntity extends CommonDateEntity { + @Id + @Column(name = "menu_uid") + private String menuUid; + + @Column(name = "menu_nm") + private String menuNm; + + @Column(name = "menu_url") + private String menuUrl; + + @Column(name = "description") + private String description; + + @Column(name = "menu_order") + private Long menuOrder; + + @NotNull + @Column(name = "is_use", nullable = true) + private Boolean isUse = true; + + @NotNull + @Column(name = "deleted", nullable = false) + private Boolean deleted = false; + + private Long createdUid; + private Long updatedUid; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "parent_menu_uid") + private MenuEntity parent; + + @OneToMany(mappedBy = "parent", fetch = FetchType.LAZY, cascade = CascadeType.ALL) + private List children = new ArrayList<>(); + + @Column(name = "menu_api_uri") + private String menuApiUri; + + public MenuDto.Basic toDto() { + return new MenuDto.Basic( + this.menuUid, + this.menuNm, + this.menuUrl, + this.description, + this.menuOrder, + this.isUse, + this.deleted, + this.createdUid, + this.updatedUid, + this.children.stream().map(MenuEntity::toDto).toList(), + this.getCreatedDate(), + this.getModifiedDate(), + this.menuApiUri); + } +} diff --git a/src/main/java/com/kamco/cd/training/postgres/entity/ModelDatasetMappEntity.java b/src/main/java/com/kamco/cd/training/postgres/entity/ModelDatasetMappEntity.java index 1583842..3f5f54e 100644 --- a/src/main/java/com/kamco/cd/training/postgres/entity/ModelDatasetMappEntity.java +++ b/src/main/java/com/kamco/cd/training/postgres/entity/ModelDatasetMappEntity.java @@ -1,47 +1,47 @@ -package com.kamco.cd.training.postgres.entity; - -import jakarta.persistence.Column; -import jakarta.persistence.Entity; -import jakarta.persistence.Id; -import jakarta.persistence.IdClass; -import jakarta.persistence.Table; -import jakarta.validation.constraints.NotNull; -import jakarta.validation.constraints.Size; -import java.io.Serializable; -import lombok.AllArgsConstructor; -import lombok.Getter; -import lombok.NoArgsConstructor; -import lombok.Setter; - -@Getter -@Setter -@Entity -@NoArgsConstructor -@AllArgsConstructor -@Table(name = "tb_model_dataset_mapp") -@IdClass(ModelDatasetMappEntity.ModelDatasetMappId.class) -public class ModelDatasetMappEntity { - - @Id - @NotNull - @Column(name = "model_uid", nullable = false) - private Long modelUid; - - @Id - @NotNull - @Column(name = "dataset_uid", nullable = false) - private Long datasetUid; - - @Size(max = 20) - @Column(name = "dataset_type", length = 20) - private String datasetType; - - @Getter - @Setter - @NoArgsConstructor - @AllArgsConstructor - public static class ModelDatasetMappId implements Serializable { - private Long modelUid; - private Long datasetUid; - } -} +package com.kamco.cd.training.postgres.entity; + +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.Id; +import jakarta.persistence.IdClass; +import jakarta.persistence.Table; +import jakarta.validation.constraints.NotNull; +import jakarta.validation.constraints.Size; +import java.io.Serializable; +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +@Getter +@Setter +@Entity +@NoArgsConstructor +@AllArgsConstructor +@Table(name = "tb_model_dataset_mapp") +@IdClass(ModelDatasetMappEntity.ModelDatasetMappId.class) +public class ModelDatasetMappEntity { + + @Id + @NotNull + @Column(name = "model_uid", nullable = false) + private Long modelUid; + + @Id + @NotNull + @Column(name = "dataset_uid", nullable = false) + private Long datasetUid; + + @Size(max = 20) + @Column(name = "dataset_type", length = 20) + private String datasetType; + + @Getter + @Setter + @NoArgsConstructor + @AllArgsConstructor + public static class ModelDatasetMappId implements Serializable { + private Long modelUid; + private Long datasetUid; + } +} diff --git a/src/main/java/com/kamco/cd/training/postgres/entity/ModelHyperParamEntity.java b/src/main/java/com/kamco/cd/training/postgres/entity/ModelHyperParamEntity.java index 77339ca..d4cf6c2 100644 --- a/src/main/java/com/kamco/cd/training/postgres/entity/ModelHyperParamEntity.java +++ b/src/main/java/com/kamco/cd/training/postgres/entity/ModelHyperParamEntity.java @@ -1,232 +1,232 @@ -package com.kamco.cd.training.postgres.entity; - -import jakarta.persistence.Column; -import jakarta.persistence.Entity; -import jakarta.persistence.Id; -import jakarta.persistence.Table; -import jakarta.validation.constraints.NotNull; -import jakarta.validation.constraints.Size; -import java.time.ZonedDateTime; -import lombok.AllArgsConstructor; -import lombok.Getter; -import lombok.NoArgsConstructor; -import lombok.Setter; -import org.hibernate.annotations.ColumnDefault; - -@Getter -@Setter -@Entity -@NoArgsConstructor -@AllArgsConstructor -@Table(name = "tb_model_hyper_param") -public class ModelHyperParamEntity { - - @Id - @NotNull - @Size(max = 50) - @Column(name = "hyper_ver", nullable = false, length = 50) - private String hyperVer; - - // ==================== Important Parameters ==================== - - @Size(max = 20) - @ColumnDefault("'large'") - @Column(name = "backbone", length = 20) - private String backbone = "large"; - - @Size(max = 20) - @ColumnDefault("'256,256'") - @Column(name = "input_size", length = 20) - private String inputSize = "256,256"; - - @Size(max = 20) - @ColumnDefault("'256,256'") - @Column(name = "crop_size", length = 20) - private String cropSize = "256,256"; - - @ColumnDefault("200") - @Column(name = "epoch_cnt") - private Integer epochCnt = 200; - - @ColumnDefault("16") - @Column(name = "batch_size") - private Integer batchSize = 16; - - // ==================== Model Architecture ==================== - - @ColumnDefault("0.3") - @Column(name = "drop_path_rate") - private Double dropPathRate = 0.3; - - @ColumnDefault("-1") - @Column(name = "frozen_stages") - private Integer frozenStages = -1; - - @Size(max = 20) - @ColumnDefault("'abs_diff'") - @Column(name = "neck_policy", length = 20) - private String neckPolicy = "abs_diff"; - - @Size(max = 255) - @ColumnDefault("'512,256,128,64'") - @Column(name = "decoder_channels", length = 255) - private String decoderChannels = "512,256,128,64"; - - @Size(max = 500) - @Column(name = "class_weight", length = 500) - private String classWeight; - - @Column(name = "num_layers") - private Integer numLayers; - - // ==================== Loss & Optimization ==================== - - @ColumnDefault("0.00006") - @Column(name = "learning_rate") - private Double learningRate = 0.00006; - - @ColumnDefault("0.05") - @Column(name = "weight_decay") - private Double weightDecay = 0.05; - - @ColumnDefault("0.9") - @Column(name = "layer_decay_rate") - private Double layerDecayRate = 0.9; - - @ColumnDefault("true") - @Column(name = "ddp_find_unused_params") - private Boolean ddpFindUnusedParams = true; - - @ColumnDefault("255") - @Column(name = "ignore_index") - private Integer ignoreIndex = 255; - - // ==================== Data ==================== - - @ColumnDefault("16") - @Column(name = "train_num_workers") - private Integer trainNumWorkers = 16; - - @ColumnDefault("8") - @Column(name = "val_num_workers") - private Integer valNumWorkers = 8; - - @ColumnDefault("8") - @Column(name = "test_num_workers") - private Integer testNumWorkers = 8; - - @ColumnDefault("true") - @Column(name = "train_shuffle") - private Boolean trainShuffle = true; - - @ColumnDefault("true") - @Column(name = "train_persistent") - private Boolean trainPersistent = true; - - @ColumnDefault("true") - @Column(name = "val_persistent") - private Boolean valPersistent = true; - - // ==================== Evaluation ==================== - - @Size(max = 255) - @ColumnDefault("'mFscore,mIoU'") - @Column(name = "metrics", length = 255) - private String metrics = "mFscore,mIoU"; - - @Size(max = 50) - @ColumnDefault("'changed_fscore'") - @Column(name = "save_best", length = 50) - private String saveBest = "changed_fscore"; - - @Size(max = 20) - @ColumnDefault("'greater'") - @Column(name = "save_best_rule", length = 20) - private String saveBestRule = "greater"; - - @ColumnDefault("10") - @Column(name = "val_interval") - private Integer valInterval = 10; - - @ColumnDefault("400") - @Column(name = "log_interval") - private Integer logInterval = 400; - - @ColumnDefault("1") - @Column(name = "vis_interval") - private Integer visInterval = 1; - - // ==================== Hardware ==================== - - @ColumnDefault("4") - @Column(name = "gpu_cnt") - private Integer gpuCnt = 4; - - @Size(max = 100) - @ColumnDefault("'0,1,2,3'") - @Column(name = "gpu_ids", length = 100) - private String gpuIds = "0,1,2,3"; - - @ColumnDefault("1122") - @Column(name = "master_port") - private Integer masterPort = 1122; - - // ==================== Augmentation ==================== - - @ColumnDefault("0.5") - @Column(name = "rot_prob") - private Double rotProb = 0.5; - - @ColumnDefault("0.5") - @Column(name = "flip_prob") - private Double flipProb = 0.5; - - @Size(max = 20) - @ColumnDefault("'-20,20'") - @Column(name = "rot_degree", length = 20) - private String rotDegree = "-20,20"; - - @ColumnDefault("0.5") - @Column(name = "exchange_prob") - private Double exchangeProb = 0.5; - - @ColumnDefault("10") - @Column(name = "brightness_delta") - private Integer brightnessDelta = 10; - - @Size(max = 20) - @ColumnDefault("'0.8,1.2'") - @Column(name = "contrast_range", length = 20) - private String contrastRange = "0.8,1.2"; - - @Size(max = 20) - @ColumnDefault("'0.8,1.2'") - @Column(name = "saturation_range", length = 20) - private String saturationRange = "0.8,1.2"; - - @ColumnDefault("10") - @Column(name = "hue_delta") - private Integer hueDelta = 10; - - // ==================== Legacy (deprecated) ==================== - - @Column(name = "cnn_filter_cnt") - private Integer cnnFilterCnt; - - @Column(name = "dropout_ratio") - private Double dropoutRatio; - - // ==================== Common ==================== - - @Column(name = "memo", length = Integer.MAX_VALUE) - private String memo; - - @Size(max = 255) - @ColumnDefault("'N'") - @Column(name = "del_yn", length = 255) - private String delYn = "N"; - - @ColumnDefault("now()") - @Column(name = "created_dttm") - private ZonedDateTime createdDttm; -} +package com.kamco.cd.training.postgres.entity; + +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.Id; +import jakarta.persistence.Table; +import jakarta.validation.constraints.NotNull; +import jakarta.validation.constraints.Size; +import java.time.ZonedDateTime; +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; +import org.hibernate.annotations.ColumnDefault; + +@Getter +@Setter +@Entity +@NoArgsConstructor +@AllArgsConstructor +@Table(name = "tb_model_hyper_param") +public class ModelHyperParamEntity { + + @Id + @NotNull + @Size(max = 50) + @Column(name = "hyper_ver", nullable = false, length = 50) + private String hyperVer; + + // ==================== Important Parameters ==================== + + @Size(max = 20) + @ColumnDefault("'large'") + @Column(name = "backbone", length = 20) + private String backbone = "large"; + + @Size(max = 20) + @ColumnDefault("'256,256'") + @Column(name = "input_size", length = 20) + private String inputSize = "256,256"; + + @Size(max = 20) + @ColumnDefault("'256,256'") + @Column(name = "crop_size", length = 20) + private String cropSize = "256,256"; + + @ColumnDefault("200") + @Column(name = "epoch_cnt") + private Integer epochCnt = 200; + + @ColumnDefault("16") + @Column(name = "batch_size") + private Integer batchSize = 16; + + // ==================== Model Architecture ==================== + + @ColumnDefault("0.3") + @Column(name = "drop_path_rate") + private Double dropPathRate = 0.3; + + @ColumnDefault("-1") + @Column(name = "frozen_stages") + private Integer frozenStages = -1; + + @Size(max = 20) + @ColumnDefault("'abs_diff'") + @Column(name = "neck_policy", length = 20) + private String neckPolicy = "abs_diff"; + + @Size(max = 255) + @ColumnDefault("'512,256,128,64'") + @Column(name = "decoder_channels", length = 255) + private String decoderChannels = "512,256,128,64"; + + @Size(max = 500) + @Column(name = "class_weight", length = 500) + private String classWeight; + + @Column(name = "num_layers") + private Integer numLayers; + + // ==================== Loss & Optimization ==================== + + @ColumnDefault("0.00006") + @Column(name = "learning_rate") + private Double learningRate = 0.00006; + + @ColumnDefault("0.05") + @Column(name = "weight_decay") + private Double weightDecay = 0.05; + + @ColumnDefault("0.9") + @Column(name = "layer_decay_rate") + private Double layerDecayRate = 0.9; + + @ColumnDefault("true") + @Column(name = "ddp_find_unused_params") + private Boolean ddpFindUnusedParams = true; + + @ColumnDefault("255") + @Column(name = "ignore_index") + private Integer ignoreIndex = 255; + + // ==================== Data ==================== + + @ColumnDefault("16") + @Column(name = "train_num_workers") + private Integer trainNumWorkers = 16; + + @ColumnDefault("8") + @Column(name = "val_num_workers") + private Integer valNumWorkers = 8; + + @ColumnDefault("8") + @Column(name = "test_num_workers") + private Integer testNumWorkers = 8; + + @ColumnDefault("true") + @Column(name = "train_shuffle") + private Boolean trainShuffle = true; + + @ColumnDefault("true") + @Column(name = "train_persistent") + private Boolean trainPersistent = true; + + @ColumnDefault("true") + @Column(name = "val_persistent") + private Boolean valPersistent = true; + + // ==================== Evaluation ==================== + + @Size(max = 255) + @ColumnDefault("'mFscore,mIoU'") + @Column(name = "metrics", length = 255) + private String metrics = "mFscore,mIoU"; + + @Size(max = 50) + @ColumnDefault("'changed_fscore'") + @Column(name = "save_best", length = 50) + private String saveBest = "changed_fscore"; + + @Size(max = 20) + @ColumnDefault("'greater'") + @Column(name = "save_best_rule", length = 20) + private String saveBestRule = "greater"; + + @ColumnDefault("10") + @Column(name = "val_interval") + private Integer valInterval = 10; + + @ColumnDefault("400") + @Column(name = "log_interval") + private Integer logInterval = 400; + + @ColumnDefault("1") + @Column(name = "vis_interval") + private Integer visInterval = 1; + + // ==================== Hardware ==================== + + @ColumnDefault("4") + @Column(name = "gpu_cnt") + private Integer gpuCnt = 4; + + @Size(max = 100) + @ColumnDefault("'0,1,2,3'") + @Column(name = "gpu_ids", length = 100) + private String gpuIds = "0,1,2,3"; + + @ColumnDefault("1122") + @Column(name = "master_port") + private Integer masterPort = 1122; + + // ==================== Augmentation ==================== + + @ColumnDefault("0.5") + @Column(name = "rot_prob") + private Double rotProb = 0.5; + + @ColumnDefault("0.5") + @Column(name = "flip_prob") + private Double flipProb = 0.5; + + @Size(max = 20) + @ColumnDefault("'-20,20'") + @Column(name = "rot_degree", length = 20) + private String rotDegree = "-20,20"; + + @ColumnDefault("0.5") + @Column(name = "exchange_prob") + private Double exchangeProb = 0.5; + + @ColumnDefault("10") + @Column(name = "brightness_delta") + private Integer brightnessDelta = 10; + + @Size(max = 20) + @ColumnDefault("'0.8,1.2'") + @Column(name = "contrast_range", length = 20) + private String contrastRange = "0.8,1.2"; + + @Size(max = 20) + @ColumnDefault("'0.8,1.2'") + @Column(name = "saturation_range", length = 20) + private String saturationRange = "0.8,1.2"; + + @ColumnDefault("10") + @Column(name = "hue_delta") + private Integer hueDelta = 10; + + // ==================== Legacy (deprecated) ==================== + + @Column(name = "cnn_filter_cnt") + private Integer cnnFilterCnt; + + @Column(name = "dropout_ratio") + private Double dropoutRatio; + + // ==================== Common ==================== + + @Column(name = "memo", length = Integer.MAX_VALUE) + private String memo; + + @Size(max = 255) + @ColumnDefault("'N'") + @Column(name = "del_yn", length = 255) + private String delYn = "N"; + + @ColumnDefault("now()") + @Column(name = "created_dttm") + private ZonedDateTime createdDttm; +} diff --git a/src/main/java/com/kamco/cd/training/postgres/entity/ModelMngEntity.java b/src/main/java/com/kamco/cd/training/postgres/entity/ModelMngEntity.java index 1761431..a951242 100644 --- a/src/main/java/com/kamco/cd/training/postgres/entity/ModelMngEntity.java +++ b/src/main/java/com/kamco/cd/training/postgres/entity/ModelMngEntity.java @@ -1,94 +1,94 @@ -package com.kamco.cd.training.postgres.entity; - -import jakarta.persistence.Column; -import jakarta.persistence.Entity; -import jakarta.persistence.GeneratedValue; -import jakarta.persistence.GenerationType; -import jakarta.persistence.Id; -import jakarta.persistence.PrePersist; -import jakarta.persistence.Table; -import jakarta.validation.constraints.Size; -import java.time.Instant; -import java.time.ZonedDateTime; -import java.util.UUID; -import lombok.Getter; -import lombok.Setter; -import org.hibernate.annotations.ColumnDefault; - -@Getter -@Setter -@Entity -@Table(name = "tb_model_mng") -public class ModelMngEntity { - - @Id - @GeneratedValue(strategy = GenerationType.IDENTITY) - @Column(name = "model_uid", nullable = false) - private Long id; - - @Column(name = "uuid", columnDefinition = "uuid", nullable = false, updatable = false) - private UUID uuid; - - @Size(max = 100) - @ColumnDefault("'NULL::character varying'") - @Column(name = "model_nm", length = 100) - private String modelNm; - - @Size(max = 64) - @ColumnDefault("'NULL::character varying'") - @Column(name = "model_cate", length = 64) - private String modelCate; - - @Size(max = 255) - @ColumnDefault("'NULL::character varying'") - @Column(name = "model_path") - private String modelPath; - - @Column(name = "created_dttm") - private Instant createdDttm; - - @Column(name = "created_uid") - private Long createdUid; - - @Column(name = "updated_dttm") - private Instant updatedDttm; - - @Column(name = "updated_uid") - private Long updatedUid; - - @Column(name = "start_dttm") - private ZonedDateTime startDttm; - - @Column(name = "training_end_dttm") - private ZonedDateTime trainingEndDttm; - - @Column(name = "test_end_dttm") - private ZonedDateTime testEndDttm; - - @Column(name = "duration_dttm") - private ZonedDateTime durationDttm; - - @Size(max = 50) - @Column(name = "process_stage", length = 50) - private String processStage; - - @Size(max = 50) - @Column(name = "status", length = 50) - private String status; - - @ColumnDefault("false") - @Column(name = "deleted") - private Boolean deleted; - - @PrePersist - public void prePersist() { - if (this.uuid == null) { - this.uuid = UUID.randomUUID(); - } - } - - /** UUID 필드가 제대로 설정되었는지 확인 (디버그용) */ - public String getUuidString() { - return this.uuid != null ? this.uuid.toString() : null; - } -} +package com.kamco.cd.training.postgres.entity; + +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.PrePersist; +import jakarta.persistence.Table; +import jakarta.validation.constraints.Size; +import java.time.Instant; +import java.time.ZonedDateTime; +import java.util.UUID; +import lombok.Getter; +import lombok.Setter; +import org.hibernate.annotations.ColumnDefault; + +@Getter +@Setter +@Entity +@Table(name = "tb_model_mng") +public class ModelMngEntity { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "model_uid", nullable = false) + private Long id; + + @Column(name = "uuid", columnDefinition = "uuid", nullable = false, updatable = false) + private UUID uuid; + + @Size(max = 100) + @ColumnDefault("'NULL::character varying'") + @Column(name = "model_nm", length = 100) + private String modelNm; + + @Size(max = 64) + @ColumnDefault("'NULL::character varying'") + @Column(name = "model_cate", length = 64) + private String modelCate; + + @Size(max = 255) + @ColumnDefault("'NULL::character varying'") + @Column(name = "model_path") + private String modelPath; + + @Column(name = "created_dttm") + private Instant createdDttm; + + @Column(name = "created_uid") + private Long createdUid; + + @Column(name = "updated_dttm") + private Instant updatedDttm; + + @Column(name = "updated_uid") + private Long updatedUid; + + @Column(name = "start_dttm") + private ZonedDateTime startDttm; + + @Column(name = "training_end_dttm") + private ZonedDateTime trainingEndDttm; + + @Column(name = "test_end_dttm") + private ZonedDateTime testEndDttm; + + @Column(name = "duration_dttm") + private ZonedDateTime durationDttm; + + @Size(max = 50) + @Column(name = "process_stage", length = 50) + private String processStage; + + @Size(max = 50) + @Column(name = "status", length = 50) + private String status; + + @ColumnDefault("false") + @Column(name = "deleted") + private Boolean deleted; + + @PrePersist + public void prePersist() { + if (this.uuid == null) { + this.uuid = UUID.randomUUID(); + } + } + + /** UUID 필드가 제대로 설정되었는지 확인 (디버그용) */ + public String getUuidString() { + return this.uuid != null ? this.uuid.toString() : null; + } +} diff --git a/src/main/java/com/kamco/cd/training/postgres/entity/ModelTrainMasterEntity.java b/src/main/java/com/kamco/cd/training/postgres/entity/ModelTrainMasterEntity.java index 99f3275..a7a0019 100644 --- a/src/main/java/com/kamco/cd/training/postgres/entity/ModelTrainMasterEntity.java +++ b/src/main/java/com/kamco/cd/training/postgres/entity/ModelTrainMasterEntity.java @@ -1,170 +1,170 @@ -package com.kamco.cd.training.postgres.entity; - -import com.kamco.cd.training.model.dto.ModelMngDto; -import jakarta.persistence.Column; -import jakarta.persistence.Entity; -import jakarta.persistence.GeneratedValue; -import jakarta.persistence.GenerationType; -import jakarta.persistence.Id; -import jakarta.persistence.PrePersist; -import jakarta.persistence.Table; -import jakarta.validation.constraints.NotNull; -import jakarta.validation.constraints.Size; -import java.time.Instant; -import java.time.ZonedDateTime; -import java.util.UUID; -import lombok.Getter; -import lombok.Setter; -import org.hibernate.annotations.ColumnDefault; - -@Getter -@Setter -@Entity -@Table(name = "tb_model_train_master") -public class ModelTrainMasterEntity { - - @Id - @GeneratedValue(strategy = GenerationType.IDENTITY) - @Column(name = "model_uid", nullable = false) - private Long id; - - @Column(name = "uuid", columnDefinition = "uuid", nullable = false, updatable = false) - private UUID uuid; - - @Size(max = 50) - @NotNull - @Column(name = "model_ver", nullable = false, length = 50) - private String modelVer; - - @Size(max = 50) - @Column(name = "hyper_ver", length = 50) - private String hyperVer; - - @Size(max = 50) - @Column(name = "epoch_ver", length = 50) - private String epochVer; - - @Size(max = 50) - @Column(name = "process_step", length = 50) - private String processStep; - - @Size(max = 20) - @Column(name = "status_cd", length = 20) - private String statusCd; - - @Column(name = "train_start_dttm") - private ZonedDateTime trainStartDttm; - - @Column(name = "epoch_cnt") - private Integer epochCnt; - - @Size(max = 50) - @Column(name = "dataset_ratio", length = 50) - private String datasetRatio; - - @Column(name = "best_epoch") - private Integer bestEpoch; - - @Column(name = "step1_end_dttm") - private ZonedDateTime step1EndDttm; - - @Size(max = 50) - @Column(name = "step1_duration", length = 50) - private String step1Duration; - - @Column(name = "step2_end_dttm") - private ZonedDateTime step2EndDttm; - - @Size(max = 50) - @Column(name = "step2_duration", length = 50) - private String step2Duration; - - @ColumnDefault("false") - @Column(name = "del_yn") - private Boolean delYn = false; - - @Column(name = "created_uid") - private Long createdUid; - - @Column(name = "updated_uid") - private Long updatedUid; - - @ColumnDefault("now()") - @Column(name = "created_dttm") - private ZonedDateTime createdDttm; - - @Column(name = "updated_dttm") - private ZonedDateTime updatedDttm; - - @ColumnDefault("0") - @Column(name = "progress_rate") - private Integer progressRate; - - @Column(name = "stop_dttm") - private Instant stopDttm; - - @Column(name = "confirmed_best_epoch") - private Integer confirmedBestEpoch; - - @Size(max = 255) - @Column(name = "model_path") - private String modelPath; - - @Column(name = "error_msg", length = Integer.MAX_VALUE) - private String errorMsg; - - // ==================== Resume Training (학습 재시작) ==================== - - @Column(name = "last_checkpoint_epoch") - private Integer lastCheckpointEpoch; - - @Size(max = 500) - @Column(name = "checkpoint_path", length = 500) - private String checkpointPath; - - @ColumnDefault("false") - @Column(name = "can_resume") - private Boolean canResume = false; - - @Column(name = "step2_start_dttm") - private Instant step2StartDttm; - - @Size(max = 1000) - @Column(name = "train_log_path", length = 1000) - private String trainLogPath; - - @Column(name = "memo", length = Integer.MAX_VALUE) - private String memo; - - @Column(name = "base_model_uid") - private Integer baseModelUid; - - @Size(max = 1000) - @Column(name = "pretrained_model_path", length = 1000) - private String pretrainedModelPath; - - @PrePersist - public void prePersist() { - if (this.uuid == null) { - this.uuid = UUID.randomUUID(); - } - } - - public ModelMngDto.Basic toDto() { - ModelMngDto.Basic dto = new ModelMngDto.Basic(); - dto.setId(this.id); - dto.setModelNm(this.modelVer); - dto.setStartDttm(this.trainStartDttm); - dto.setTrainingEndDttm(this.step1EndDttm); - dto.setTestEndDttm(this.step2EndDttm); - dto.setDurationDttm(this.step2Duration); - dto.setProcessStage(this.processStep); - dto.setStatusCd(this.statusCd); - return dto; - } - - /** UUID 필드가 제대로 설정되었는지 확인 (디버그용) */ - public String getUuidString() { - return this.uuid != null ? this.uuid.toString() : null; - } -} +package com.kamco.cd.training.postgres.entity; + +import com.kamco.cd.training.model.dto.ModelMngDto; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.PrePersist; +import jakarta.persistence.Table; +import jakarta.validation.constraints.NotNull; +import jakarta.validation.constraints.Size; +import java.time.Instant; +import java.time.ZonedDateTime; +import java.util.UUID; +import lombok.Getter; +import lombok.Setter; +import org.hibernate.annotations.ColumnDefault; + +@Getter +@Setter +@Entity +@Table(name = "tb_model_train_master") +public class ModelTrainMasterEntity { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "model_uid", nullable = false) + private Long id; + + @Column(name = "uuid", columnDefinition = "uuid", nullable = false, updatable = false) + private UUID uuid; + + @Size(max = 50) + @NotNull + @Column(name = "model_ver", nullable = false, length = 50) + private String modelVer; + + @Size(max = 50) + @Column(name = "hyper_ver", length = 50) + private String hyperVer; + + @Size(max = 50) + @Column(name = "epoch_ver", length = 50) + private String epochVer; + + @Size(max = 50) + @Column(name = "process_step", length = 50) + private String processStep; + + @Size(max = 20) + @Column(name = "status_cd", length = 20) + private String statusCd; + + @Column(name = "train_start_dttm") + private ZonedDateTime trainStartDttm; + + @Column(name = "epoch_cnt") + private Integer epochCnt; + + @Size(max = 50) + @Column(name = "dataset_ratio", length = 50) + private String datasetRatio; + + @Column(name = "best_epoch") + private Integer bestEpoch; + + @Column(name = "step1_end_dttm") + private ZonedDateTime step1EndDttm; + + @Size(max = 50) + @Column(name = "step1_duration", length = 50) + private String step1Duration; + + @Column(name = "step2_end_dttm") + private ZonedDateTime step2EndDttm; + + @Size(max = 50) + @Column(name = "step2_duration", length = 50) + private String step2Duration; + + @ColumnDefault("false") + @Column(name = "del_yn") + private Boolean delYn = false; + + @Column(name = "created_uid") + private Long createdUid; + + @Column(name = "updated_uid") + private Long updatedUid; + + @ColumnDefault("now()") + @Column(name = "created_dttm") + private ZonedDateTime createdDttm; + + @Column(name = "updated_dttm") + private ZonedDateTime updatedDttm; + + @ColumnDefault("0") + @Column(name = "progress_rate") + private Integer progressRate; + + @Column(name = "stop_dttm") + private Instant stopDttm; + + @Column(name = "confirmed_best_epoch") + private Integer confirmedBestEpoch; + + @Size(max = 255) + @Column(name = "model_path") + private String modelPath; + + @Column(name = "error_msg", length = Integer.MAX_VALUE) + private String errorMsg; + + // ==================== Resume Training (학습 재시작) ==================== + + @Column(name = "last_checkpoint_epoch") + private Integer lastCheckpointEpoch; + + @Size(max = 500) + @Column(name = "checkpoint_path", length = 500) + private String checkpointPath; + + @ColumnDefault("false") + @Column(name = "can_resume") + private Boolean canResume = false; + + @Column(name = "step2_start_dttm") + private Instant step2StartDttm; + + @Size(max = 1000) + @Column(name = "train_log_path", length = 1000) + private String trainLogPath; + + @Column(name = "memo", length = Integer.MAX_VALUE) + private String memo; + + @Column(name = "base_model_uid") + private Integer baseModelUid; + + @Size(max = 1000) + @Column(name = "pretrained_model_path", length = 1000) + private String pretrainedModelPath; + + @PrePersist + public void prePersist() { + if (this.uuid == null) { + this.uuid = UUID.randomUUID(); + } + } + + public ModelMngDto.Basic toDto() { + ModelMngDto.Basic dto = new ModelMngDto.Basic(); + dto.setId(this.id); + dto.setModelNm(this.modelVer); + dto.setStartDttm(this.trainStartDttm); + dto.setTrainingEndDttm(this.step1EndDttm); + dto.setTestEndDttm(this.step2EndDttm); + dto.setDurationDttm(this.step2Duration); + dto.setProcessStage(this.processStep); + dto.setStatusCd(this.statusCd); + return dto; + } + + /** UUID 필드가 제대로 설정되었는지 확인 (디버그용) */ + public String getUuidString() { + return this.uuid != null ? this.uuid.toString() : null; + } +} diff --git a/src/main/java/com/kamco/cd/training/postgres/entity/SystemMetricsEntity.java b/src/main/java/com/kamco/cd/training/postgres/entity/SystemMetricsEntity.java index 99a7471..21a6e73 100644 --- a/src/main/java/com/kamco/cd/training/postgres/entity/SystemMetricsEntity.java +++ b/src/main/java/com/kamco/cd/training/postgres/entity/SystemMetricsEntity.java @@ -1,55 +1,55 @@ -package com.kamco.cd.training.postgres.entity; - -import jakarta.persistence.Column; -import jakarta.persistence.Entity; -import jakarta.persistence.GeneratedValue; -import jakarta.persistence.GenerationType; -import jakarta.persistence.Id; -import jakarta.persistence.Table; -import java.time.ZonedDateTime; -import lombok.AllArgsConstructor; -import lombok.Builder; -import lombok.Getter; -import lombok.NoArgsConstructor; -import lombok.Setter; - -@Entity -@Table(name = "system_metrics") -@Getter -@Setter -@NoArgsConstructor -@AllArgsConstructor -@Builder -public class SystemMetricsEntity { - - @Id - @GeneratedValue(strategy = GenerationType.IDENTITY) - private Integer id; - - @Column(nullable = false) - private ZonedDateTime timestamp; - - @Column(name = "server_name", nullable = false) - private String serverName; - - @Column(name = "cpu_user") - private Float cpuUser; - - @Column(name = "cpu_system") - private Float cpuSystem; - - @Column(name = "cpu_iowait") - private Float cpuIowait; - - @Column(name = "cpu_idle") - private Float cpuIdle; - - @Column(name = "kbmemfree") - private Long kbmemfree; - - @Column(name = "kbmemused") - private Long kbmemused; - - @Column(name = "memused") - private Float memused; -} +package com.kamco.cd.training.postgres.entity; + +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.Table; +import java.time.ZonedDateTime; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +@Entity +@Table(name = "system_metrics") +@Getter +@Setter +@NoArgsConstructor +@AllArgsConstructor +@Builder +public class SystemMetricsEntity { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Integer id; + + @Column(nullable = false) + private ZonedDateTime timestamp; + + @Column(name = "server_name", nullable = false) + private String serverName; + + @Column(name = "cpu_user") + private Float cpuUser; + + @Column(name = "cpu_system") + private Float cpuSystem; + + @Column(name = "cpu_iowait") + private Float cpuIowait; + + @Column(name = "cpu_idle") + private Float cpuIdle; + + @Column(name = "kbmemfree") + private Long kbmemfree; + + @Column(name = "kbmemused") + private Long kbmemused; + + @Column(name = "memused") + private Float memused; +} diff --git a/src/main/java/com/kamco/cd/training/postgres/entity/UploadSessionEntity.java b/src/main/java/com/kamco/cd/training/postgres/entity/UploadSessionEntity.java index 1c1634a..e8e987a 100644 --- a/src/main/java/com/kamco/cd/training/postgres/entity/UploadSessionEntity.java +++ b/src/main/java/com/kamco/cd/training/postgres/entity/UploadSessionEntity.java @@ -1,102 +1,97 @@ -package com.kamco.cd.training.postgres.entity; - -import com.kamco.cd.training.upload.dto.UploadDto; -import jakarta.persistence.*; -import jakarta.validation.constraints.NotNull; -import jakarta.validation.constraints.Size; -import java.time.ZonedDateTime; -import java.util.UUID; -import lombok.Getter; -import lombok.Setter; -import org.hibernate.annotations.ColumnDefault; - -@Getter -@Setter -@Entity -@Table(name = "tb_upload_session") -public class UploadSessionEntity { - - @Id - @Column(name = "upload_id", nullable = false, length = 100) - private String uploadId; - - @Size(max = 255) - @NotNull - @Column(name = "file_name", nullable = false, length = 255) - private String fileName; - - @NotNull - @Column(name = "file_size", nullable = false) - private Long fileSize; - - @Column(name = "dataset_id") - private Long datasetId; - - @Column(name = "total_chunks") - private Integer totalChunks; - - @Column(name = "uploaded_chunks") - private Integer uploadedChunks = 0; - - @Size(max = 20) - @NotNull - @Column(name = "status", nullable = false, length = 20) - private String status; // INIT, UPLOADING, PROCESSING, COMPLETED, FAILED - - @Size(max = 500) - @Column(name = "temp_path", length = 500) - private String tempPath; - - @Size(max = 500) - @Column(name = "final_path", length = 500) - private String finalPath; - - @Column(name = "error_message", columnDefinition = "text") - private String errorMessage; - - @NotNull - @ColumnDefault("now()") - @Column(name = "created_dttm", nullable = false) - private ZonedDateTime createdDttm = ZonedDateTime.now(); - - @NotNull - @ColumnDefault("now()") - @Column(name = "updated_dttm", nullable = false) - private ZonedDateTime updatedDttm = ZonedDateTime.now(); - - @Column(name = "completed_dttm") - private ZonedDateTime completedDttm; - - @Size(max = 50) - @Column(name = "upload_divi", length = 50) - private String uploadDivi; - - @Size(max = 300) - @Column(name = "file_hash", length = 300) - private String fileHash; - - @Column(name = "chunk_total_index") - private Integer chunkTotalIndex; - - @Column(name = "chunk_index") - private Integer chunkIndex = 0; - - @NotNull - @ColumnDefault("uuid_generate_v4()") - @Column(name = "uuid", nullable = false) - private UUID uuid; - - @PrePersist - protected void onPersist() { - if (this.createdDttm == null) { - this.createdDttm = ZonedDateTime.now(); - } - if (this.chunkIndex == null) { - this.chunkIndex = 0; - } - } - - - - -} +package com.kamco.cd.training.postgres.entity; + +import jakarta.persistence.*; +import jakarta.validation.constraints.NotNull; +import jakarta.validation.constraints.Size; +import java.time.ZonedDateTime; +import java.util.UUID; +import lombok.Getter; +import lombok.Setter; +import org.hibernate.annotations.ColumnDefault; + +@Getter +@Setter +@Entity +@Table(name = "tb_upload_session") +public class UploadSessionEntity { + + @Id + @Column(name = "upload_id", nullable = false, length = 100) + private String uploadId; + + @Size(max = 255) + @NotNull + @Column(name = "file_name", nullable = false, length = 255) + private String fileName; + + @NotNull + @Column(name = "file_size", nullable = false) + private Long fileSize; + + @Column(name = "dataset_id") + private Long datasetId; + + @Column(name = "total_chunks") + private Integer totalChunks; + + @Column(name = "uploaded_chunks") + private Integer uploadedChunks = 0; + + @Size(max = 20) + @NotNull + @Column(name = "status", nullable = false, length = 20) + private String status; // INIT, UPLOADING, PROCESSING, COMPLETED, FAILED + + @Size(max = 500) + @Column(name = "temp_path", length = 500) + private String tempPath; + + @Size(max = 500) + @Column(name = "final_path", length = 500) + private String finalPath; + + @Column(name = "error_message", columnDefinition = "text") + private String errorMessage; + + @NotNull + @ColumnDefault("now()") + @Column(name = "created_dttm", nullable = false) + private ZonedDateTime createdDttm = ZonedDateTime.now(); + + @NotNull + @ColumnDefault("now()") + @Column(name = "updated_dttm", nullable = false) + private ZonedDateTime updatedDttm = ZonedDateTime.now(); + + @Column(name = "completed_dttm") + private ZonedDateTime completedDttm; + + @Size(max = 50) + @Column(name = "upload_divi", length = 50) + private String uploadDivi; + + @Size(max = 300) + @Column(name = "file_hash", length = 300) + private String fileHash; + + @Column(name = "chunk_total_index") + private Integer chunkTotalIndex; + + @Column(name = "chunk_index") + private Integer chunkIndex = 0; + + @NotNull + @ColumnDefault("uuid_generate_v4()") + @Column(name = "uuid", nullable = false) + private UUID uuid; + + @PrePersist + protected void onPersist() { + if (this.createdDttm == null) { + this.createdDttm = ZonedDateTime.now(); + } + if (this.chunkIndex == null) { + this.chunkIndex = 0; + } + } +} diff --git a/src/main/java/com/kamco/cd/training/postgres/repository/SystemMetricsRepository.java b/src/main/java/com/kamco/cd/training/postgres/repository/SystemMetricsRepository.java index 3bf49c7..97c158a 100644 --- a/src/main/java/com/kamco/cd/training/postgres/repository/SystemMetricsRepository.java +++ b/src/main/java/com/kamco/cd/training/postgres/repository/SystemMetricsRepository.java @@ -1,19 +1,19 @@ -package com.kamco.cd.training.postgres.repository; - -import com.kamco.cd.training.postgres.entity.SystemMetricsEntity; -import java.util.Optional; -import org.springframework.data.jpa.repository.JpaRepository; -import org.springframework.data.jpa.repository.Query; -import org.springframework.stereotype.Repository; - -@Repository -public interface SystemMetricsRepository extends JpaRepository { - - /** - * 가장 최근의 시스템 메트릭 조회 - * - * @return 최근 시스템 메트릭 - */ - @Query("SELECT s FROM SystemMetricsEntity s ORDER BY s.timestamp DESC LIMIT 1") - Optional findLatestMetrics(); -} +package com.kamco.cd.training.postgres.repository; + +import com.kamco.cd.training.postgres.entity.SystemMetricsEntity; +import java.util.Optional; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; +import org.springframework.stereotype.Repository; + +@Repository +public interface SystemMetricsRepository extends JpaRepository { + + /** + * 가장 최근의 시스템 메트릭 조회 + * + * @return 최근 시스템 메트릭 + */ + @Query("SELECT s FROM SystemMetricsEntity s ORDER BY s.timestamp DESC LIMIT 1") + Optional findLatestMetrics(); +} diff --git a/src/main/java/com/kamco/cd/training/postgres/repository/code/CommonCodeRepository.java b/src/main/java/com/kamco/cd/training/postgres/repository/code/CommonCodeRepository.java index 51a417d..097f468 100644 --- a/src/main/java/com/kamco/cd/training/postgres/repository/code/CommonCodeRepository.java +++ b/src/main/java/com/kamco/cd/training/postgres/repository/code/CommonCodeRepository.java @@ -1,7 +1,7 @@ -package com.kamco.cd.training.postgres.repository.code; - -import com.kamco.cd.training.postgres.entity.CommonCodeEntity; -import org.springframework.data.jpa.repository.JpaRepository; - -public interface CommonCodeRepository - extends JpaRepository, CommonCodeRepositoryCustom {} +package com.kamco.cd.training.postgres.repository.code; + +import com.kamco.cd.training.postgres.entity.CommonCodeEntity; +import org.springframework.data.jpa.repository.JpaRepository; + +public interface CommonCodeRepository + extends JpaRepository, CommonCodeRepositoryCustom {} diff --git a/src/main/java/com/kamco/cd/training/postgres/repository/code/CommonCodeRepositoryCustom.java b/src/main/java/com/kamco/cd/training/postgres/repository/code/CommonCodeRepositoryCustom.java index 74964f6..4e16987 100644 --- a/src/main/java/com/kamco/cd/training/postgres/repository/code/CommonCodeRepositoryCustom.java +++ b/src/main/java/com/kamco/cd/training/postgres/repository/code/CommonCodeRepositoryCustom.java @@ -1,18 +1,18 @@ -package com.kamco.cd.training.postgres.repository.code; - -import com.kamco.cd.training.postgres.entity.CommonCodeEntity; -import jakarta.validation.constraints.NotEmpty; -import java.util.List; -import java.util.Optional; - -public interface CommonCodeRepositoryCustom { - Optional findByCodeId(Long id); - - Optional findByCode(String code); - - List findByAll(); - - Optional getCode(String parentCodeCd, String childCodeCd); - - Long findByParentIdCodeExists(Long parentId, @NotEmpty String code); -} +package com.kamco.cd.training.postgres.repository.code; + +import com.kamco.cd.training.postgres.entity.CommonCodeEntity; +import jakarta.validation.constraints.NotEmpty; +import java.util.List; +import java.util.Optional; + +public interface CommonCodeRepositoryCustom { + Optional findByCodeId(Long id); + + Optional findByCode(String code); + + List findByAll(); + + Optional getCode(String parentCodeCd, String childCodeCd); + + Long findByParentIdCodeExists(Long parentId, @NotEmpty String code); +} diff --git a/src/main/java/com/kamco/cd/training/postgres/repository/code/CommonCodeRepositoryImpl.java b/src/main/java/com/kamco/cd/training/postgres/repository/code/CommonCodeRepositoryImpl.java index 0c1b613..2d45fa3 100644 --- a/src/main/java/com/kamco/cd/training/postgres/repository/code/CommonCodeRepositoryImpl.java +++ b/src/main/java/com/kamco/cd/training/postgres/repository/code/CommonCodeRepositoryImpl.java @@ -1,89 +1,89 @@ -package com.kamco.cd.training.postgres.repository.code; - -import static com.kamco.cd.training.postgres.entity.QCommonCodeEntity.commonCodeEntity; - -import com.kamco.cd.training.postgres.entity.CommonCodeEntity; -import com.kamco.cd.training.postgres.entity.QCommonCodeEntity; -import com.querydsl.core.types.dsl.BooleanExpression; -import com.querydsl.jpa.impl.JPAQueryFactory; -import java.util.List; -import java.util.Optional; -import lombok.RequiredArgsConstructor; -import org.springframework.stereotype.Repository; - -@Repository -@RequiredArgsConstructor -public class CommonCodeRepositoryImpl implements CommonCodeRepositoryCustom { - - private final JPAQueryFactory queryFactory; - - @Override - public Optional findByCodeId(Long id) { - return Optional.ofNullable( - queryFactory.selectFrom(commonCodeEntity).where(commonCodeEntity.id.eq(id)).fetchOne()); - } - - @Override - public Optional findByCode(String code) { - QCommonCodeEntity child = new QCommonCodeEntity("child"); - return Optional.ofNullable( - queryFactory - .selectFrom(commonCodeEntity) - .leftJoin(commonCodeEntity.children, child) - .on(child.deleted.isFalse().or(child.deleted.isNull())) - .where( - commonCodeEntity.parent.isNull(), - commonCodeEntity.code.eq(code), - commonCodeEntity.used.isTrue(), - commonCodeEntity.deleted.isFalse().or(commonCodeEntity.deleted.isNull())) - .orderBy(child.order.asc()) - .fetchOne()); - } - - @Override - public List findByAll() { - QCommonCodeEntity child = new QCommonCodeEntity("child"); - return queryFactory - .selectFrom(commonCodeEntity) - .leftJoin(commonCodeEntity.children, child) - .on(child.deleted.isFalse().or(child.deleted.isNull())) - .where( - commonCodeEntity.parent.isNull(), - commonCodeEntity.deleted.isFalse().or(commonCodeEntity.deleted.isNull())) - .orderBy(commonCodeEntity.order.asc(), child.order.asc()) - .fetch(); - } - - @Override - public Optional getCode(String parentCodeCd, String childCodeCd) { - QCommonCodeEntity parent = QCommonCodeEntity.commonCodeEntity; - QCommonCodeEntity child = new QCommonCodeEntity("child"); - - String result = - queryFactory - .select(child.name) - .from(child) - .join(child.parent, parent) - .where( - parent.code.eq(parentCodeCd).and(child.code.eq(childCodeCd)), - child.deleted.isFalse().or(child.deleted.isNull())) - .fetchFirst(); // 단일 결과만 - - return Optional.ofNullable(result); - } - - @Override - public Long findByParentIdCodeExists(Long parentId, String code) { - return queryFactory - .select(commonCodeEntity.code.count()) - .from(commonCodeEntity) - .where(conditionParentId(parentId), commonCodeEntity.code.eq(code)) - .fetchOne(); - } - - private BooleanExpression conditionParentId(Long parentId) { - return parentId == null - ? commonCodeEntity.parent.id.isNull() - : commonCodeEntity.parent.id.eq(parentId); - } -} +package com.kamco.cd.training.postgres.repository.code; + +import static com.kamco.cd.training.postgres.entity.QCommonCodeEntity.commonCodeEntity; + +import com.kamco.cd.training.postgres.entity.CommonCodeEntity; +import com.kamco.cd.training.postgres.entity.QCommonCodeEntity; +import com.querydsl.core.types.dsl.BooleanExpression; +import com.querydsl.jpa.impl.JPAQueryFactory; +import java.util.List; +import java.util.Optional; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Repository; + +@Repository +@RequiredArgsConstructor +public class CommonCodeRepositoryImpl implements CommonCodeRepositoryCustom { + + private final JPAQueryFactory queryFactory; + + @Override + public Optional findByCodeId(Long id) { + return Optional.ofNullable( + queryFactory.selectFrom(commonCodeEntity).where(commonCodeEntity.id.eq(id)).fetchOne()); + } + + @Override + public Optional findByCode(String code) { + QCommonCodeEntity child = new QCommonCodeEntity("child"); + return Optional.ofNullable( + queryFactory + .selectFrom(commonCodeEntity) + .leftJoin(commonCodeEntity.children, child) + .on(child.deleted.isFalse().or(child.deleted.isNull())) + .where( + commonCodeEntity.parent.isNull(), + commonCodeEntity.code.eq(code), + commonCodeEntity.used.isTrue(), + commonCodeEntity.deleted.isFalse().or(commonCodeEntity.deleted.isNull())) + .orderBy(child.order.asc()) + .fetchOne()); + } + + @Override + public List findByAll() { + QCommonCodeEntity child = new QCommonCodeEntity("child"); + return queryFactory + .selectFrom(commonCodeEntity) + .leftJoin(commonCodeEntity.children, child) + .on(child.deleted.isFalse().or(child.deleted.isNull())) + .where( + commonCodeEntity.parent.isNull(), + commonCodeEntity.deleted.isFalse().or(commonCodeEntity.deleted.isNull())) + .orderBy(commonCodeEntity.order.asc(), child.order.asc()) + .fetch(); + } + + @Override + public Optional getCode(String parentCodeCd, String childCodeCd) { + QCommonCodeEntity parent = QCommonCodeEntity.commonCodeEntity; + QCommonCodeEntity child = new QCommonCodeEntity("child"); + + String result = + queryFactory + .select(child.name) + .from(child) + .join(child.parent, parent) + .where( + parent.code.eq(parentCodeCd).and(child.code.eq(childCodeCd)), + child.deleted.isFalse().or(child.deleted.isNull())) + .fetchFirst(); // 단일 결과만 + + return Optional.ofNullable(result); + } + + @Override + public Long findByParentIdCodeExists(Long parentId, String code) { + return queryFactory + .select(commonCodeEntity.code.count()) + .from(commonCodeEntity) + .where(conditionParentId(parentId), commonCodeEntity.code.eq(code)) + .fetchOne(); + } + + private BooleanExpression conditionParentId(Long parentId) { + return parentId == null + ? commonCodeEntity.parent.id.isNull() + : commonCodeEntity.parent.id.eq(parentId); + } +} diff --git a/src/main/java/com/kamco/cd/training/postgres/repository/dataset/DatasetRepository.java b/src/main/java/com/kamco/cd/training/postgres/repository/dataset/DatasetRepository.java index f32c9d0..b8ea142 100644 --- a/src/main/java/com/kamco/cd/training/postgres/repository/dataset/DatasetRepository.java +++ b/src/main/java/com/kamco/cd/training/postgres/repository/dataset/DatasetRepository.java @@ -1,11 +1,11 @@ -package com.kamco.cd.training.postgres.repository.dataset; - -import com.kamco.cd.training.postgres.entity.DatasetEntity; -import java.util.List; -import org.springframework.data.jpa.repository.JpaRepository; - -public interface DatasetRepository - extends JpaRepository, DatasetRepositoryCustom { - - List findByDeletedOrderByCreatedDttmDesc(Boolean deleted); -} +package com.kamco.cd.training.postgres.repository.dataset; + +import com.kamco.cd.training.postgres.entity.DatasetEntity; +import java.util.List; +import org.springframework.data.jpa.repository.JpaRepository; + +public interface DatasetRepository + extends JpaRepository, DatasetRepositoryCustom { + + List findByDeletedOrderByCreatedDttmDesc(Boolean deleted); +} diff --git a/src/main/java/com/kamco/cd/training/postgres/repository/dataset/DatasetRepositoryCustom.java b/src/main/java/com/kamco/cd/training/postgres/repository/dataset/DatasetRepositoryCustom.java index 7235186..80831bc 100644 --- a/src/main/java/com/kamco/cd/training/postgres/repository/dataset/DatasetRepositoryCustom.java +++ b/src/main/java/com/kamco/cd/training/postgres/repository/dataset/DatasetRepositoryCustom.java @@ -1,13 +1,13 @@ -package com.kamco.cd.training.postgres.repository.dataset; - -import com.kamco.cd.training.dataset.dto.DatasetDto; -import com.kamco.cd.training.postgres.entity.DatasetEntity; -import java.util.Optional; -import java.util.UUID; -import org.springframework.data.domain.Page; - -public interface DatasetRepositoryCustom { - Page findDatasetList(DatasetDto.SearchReq searchReq); - - Optional findByUuid(UUID id); -} +package com.kamco.cd.training.postgres.repository.dataset; + +import com.kamco.cd.training.dataset.dto.DatasetDto; +import com.kamco.cd.training.postgres.entity.DatasetEntity; +import java.util.Optional; +import java.util.UUID; +import org.springframework.data.domain.Page; + +public interface DatasetRepositoryCustom { + Page findDatasetList(DatasetDto.SearchReq searchReq); + + Optional findByUuid(UUID id); +} diff --git a/src/main/java/com/kamco/cd/training/postgres/repository/dataset/DatasetRepositoryImpl.java b/src/main/java/com/kamco/cd/training/postgres/repository/dataset/DatasetRepositoryImpl.java index 3c9a3e0..daca00d 100644 --- a/src/main/java/com/kamco/cd/training/postgres/repository/dataset/DatasetRepositoryImpl.java +++ b/src/main/java/com/kamco/cd/training/postgres/repository/dataset/DatasetRepositoryImpl.java @@ -1,75 +1,75 @@ -package com.kamco.cd.training.postgres.repository.dataset; - -import com.kamco.cd.training.dataset.dto.DatasetDto; -import com.kamco.cd.training.postgres.entity.DatasetEntity; -import com.kamco.cd.training.postgres.entity.QDatasetEntity; -import com.querydsl.core.BooleanBuilder; -import com.querydsl.jpa.impl.JPAQueryFactory; -import java.util.List; -import java.util.Optional; -import java.util.UUID; -import lombok.RequiredArgsConstructor; -import org.apache.commons.lang3.StringUtils; -import org.springframework.data.domain.Page; -import org.springframework.data.domain.PageImpl; -import org.springframework.data.domain.Pageable; -import org.springframework.stereotype.Repository; - -@Repository -@RequiredArgsConstructor -public class DatasetRepositoryImpl implements DatasetRepositoryCustom { - - private final JPAQueryFactory queryFactory; - private final QDatasetEntity dataset = QDatasetEntity.datasetEntity; - - /** - * 데이터셋 목록 조회 - * - * @param searchReq 검색 조건 - * @return 페이징 처리된 데이터셋 Entity 목록 - */ - @Override - public Page findDatasetList(DatasetDto.SearchReq searchReq) { - Pageable pageable = searchReq.toPageable(); - BooleanBuilder builder = new BooleanBuilder(); - - // 제목 - if (StringUtils.isNotBlank(searchReq.getTitle())) { - String contains = "%" + searchReq.getTitle() + "%"; - builder.and(dataset.title.likeIgnoreCase(contains)); - } - - // 구분 - if (StringUtils.isNotBlank(searchReq.getGroupTitle())) { - builder.and(dataset.groupTitle.eq(searchReq.getGroupTitle())); - } - - // Entity 직접 조회 (Projections 사용 지양) - List content = - queryFactory - .selectFrom(dataset) - .where(builder.and(dataset.deleted.isFalse())) - .offset(pageable.getOffset()) - .limit(pageable.getPageSize()) - .orderBy(dataset.createdDttm.desc()) - .fetch(); - - // Count 쿼리 별도 실행 (null safe handling) - long total = - Optional.ofNullable( - queryFactory.select(dataset.count()).from(dataset).where(builder).fetchOne()) - .orElse(0L); - - return new PageImpl<>(content, pageable, total); - } - - @Override - public Optional findByUuid(UUID id) { - return Optional.ofNullable( - queryFactory - .select(dataset) - .from(dataset) - .where(dataset.uuid.eq(id), dataset.deleted.isFalse()) - .fetchOne()); - } -} +package com.kamco.cd.training.postgres.repository.dataset; + +import com.kamco.cd.training.dataset.dto.DatasetDto; +import com.kamco.cd.training.postgres.entity.DatasetEntity; +import com.kamco.cd.training.postgres.entity.QDatasetEntity; +import com.querydsl.core.BooleanBuilder; +import com.querydsl.jpa.impl.JPAQueryFactory; +import java.util.List; +import java.util.Optional; +import java.util.UUID; +import lombok.RequiredArgsConstructor; +import org.apache.commons.lang3.StringUtils; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageImpl; +import org.springframework.data.domain.Pageable; +import org.springframework.stereotype.Repository; + +@Repository +@RequiredArgsConstructor +public class DatasetRepositoryImpl implements DatasetRepositoryCustom { + + private final JPAQueryFactory queryFactory; + private final QDatasetEntity dataset = QDatasetEntity.datasetEntity; + + /** + * 데이터셋 목록 조회 + * + * @param searchReq 검색 조건 + * @return 페이징 처리된 데이터셋 Entity 목록 + */ + @Override + public Page findDatasetList(DatasetDto.SearchReq searchReq) { + Pageable pageable = searchReq.toPageable(); + BooleanBuilder builder = new BooleanBuilder(); + + // 제목 + if (StringUtils.isNotBlank(searchReq.getTitle())) { + String contains = "%" + searchReq.getTitle() + "%"; + builder.and(dataset.title.likeIgnoreCase(contains)); + } + + // 구분 + if (StringUtils.isNotBlank(searchReq.getGroupTitle())) { + builder.and(dataset.groupTitle.eq(searchReq.getGroupTitle())); + } + + // Entity 직접 조회 (Projections 사용 지양) + List content = + queryFactory + .selectFrom(dataset) + .where(builder.and(dataset.deleted.isFalse())) + .offset(pageable.getOffset()) + .limit(pageable.getPageSize()) + .orderBy(dataset.createdDttm.desc()) + .fetch(); + + // Count 쿼리 별도 실행 (null safe handling) + long total = + Optional.ofNullable( + queryFactory.select(dataset.count()).from(dataset).where(builder).fetchOne()) + .orElse(0L); + + return new PageImpl<>(content, pageable, total); + } + + @Override + public Optional findByUuid(UUID id) { + return Optional.ofNullable( + queryFactory + .select(dataset) + .from(dataset) + .where(dataset.uuid.eq(id), dataset.deleted.isFalse()) + .fetchOne()); + } +} diff --git a/src/main/java/com/kamco/cd/training/postgres/repository/dataset/MapSheetRepository.java b/src/main/java/com/kamco/cd/training/postgres/repository/dataset/MapSheetRepository.java index b2485fe..a940baf 100644 --- a/src/main/java/com/kamco/cd/training/postgres/repository/dataset/MapSheetRepository.java +++ b/src/main/java/com/kamco/cd/training/postgres/repository/dataset/MapSheetRepository.java @@ -1,13 +1,13 @@ -package com.kamco.cd.training.postgres.repository.dataset; - -import com.kamco.cd.training.postgres.entity.MapSheetEntity; -import java.util.List; -import org.springframework.data.jpa.repository.JpaRepository; - -public interface MapSheetRepository - extends JpaRepository, MapSheetRepositoryCustom { - - List findByDatasetIdAndDeletedFalse(Long datasetId); - - long countByDatasetIdAndDeletedFalse(Long datasetId); -} +package com.kamco.cd.training.postgres.repository.dataset; + +import com.kamco.cd.training.postgres.entity.MapSheetEntity; +import java.util.List; +import org.springframework.data.jpa.repository.JpaRepository; + +public interface MapSheetRepository + extends JpaRepository, MapSheetRepositoryCustom { + + List findByDatasetIdAndDeletedFalse(Long datasetId); + + long countByDatasetIdAndDeletedFalse(Long datasetId); +} diff --git a/src/main/java/com/kamco/cd/training/postgres/repository/dataset/MapSheetRepositoryCustom.java b/src/main/java/com/kamco/cd/training/postgres/repository/dataset/MapSheetRepositoryCustom.java index 3e0224d..a2e0f4c 100644 --- a/src/main/java/com/kamco/cd/training/postgres/repository/dataset/MapSheetRepositoryCustom.java +++ b/src/main/java/com/kamco/cd/training/postgres/repository/dataset/MapSheetRepositoryCustom.java @@ -1,9 +1,9 @@ -package com.kamco.cd.training.postgres.repository.dataset; - -import com.kamco.cd.training.dataset.dto.MapSheetDto; -import com.kamco.cd.training.postgres.entity.MapSheetEntity; -import org.springframework.data.domain.Page; - -public interface MapSheetRepositoryCustom { - Page findMapSheetList(MapSheetDto.SearchReq searchReq); -} +package com.kamco.cd.training.postgres.repository.dataset; + +import com.kamco.cd.training.dataset.dto.MapSheetDto; +import com.kamco.cd.training.postgres.entity.MapSheetEntity; +import org.springframework.data.domain.Page; + +public interface MapSheetRepositoryCustom { + Page findMapSheetList(MapSheetDto.SearchReq searchReq); +} diff --git a/src/main/java/com/kamco/cd/training/postgres/repository/dataset/MapSheetRepositoryImpl.java b/src/main/java/com/kamco/cd/training/postgres/repository/dataset/MapSheetRepositoryImpl.java index 658ef90..8607fca 100644 --- a/src/main/java/com/kamco/cd/training/postgres/repository/dataset/MapSheetRepositoryImpl.java +++ b/src/main/java/com/kamco/cd/training/postgres/repository/dataset/MapSheetRepositoryImpl.java @@ -1,54 +1,54 @@ -package com.kamco.cd.training.postgres.repository.dataset; - -import com.kamco.cd.training.dataset.dto.MapSheetDto; -import com.kamco.cd.training.postgres.entity.MapSheetEntity; -import com.kamco.cd.training.postgres.entity.QMapSheetEntity; -import com.querydsl.core.BooleanBuilder; -import com.querydsl.jpa.impl.JPAQueryFactory; -import java.util.List; -import java.util.Optional; -import lombok.RequiredArgsConstructor; -import org.springframework.data.domain.Page; -import org.springframework.data.domain.PageImpl; -import org.springframework.data.domain.Pageable; -import org.springframework.stereotype.Repository; - -@Repository -@RequiredArgsConstructor -public class MapSheetRepositoryImpl implements MapSheetRepositoryCustom { - - private final JPAQueryFactory queryFactory; - private final QMapSheetEntity mapSheet = QMapSheetEntity.mapSheetEntity; - - @Override - public Page findMapSheetList(MapSheetDto.SearchReq searchReq) { - Pageable pageable = searchReq.toPageable(); - BooleanBuilder builder = new BooleanBuilder(); - - // 데이터셋 ID 필터 - if (searchReq.getDatasetId() != null) { - builder.and(mapSheet.datasetId.eq(searchReq.getDatasetId())); - } - - // 삭제되지 않은 것만 - builder.and(mapSheet.deleted.isFalse()); - - // Entity 직접 조회 (Projections 사용 지양) - List content = - queryFactory - .selectFrom(mapSheet) - .where(builder) - .offset(pageable.getOffset()) - .limit(pageable.getPageSize()) - .orderBy(mapSheet.createdDttm.desc()) - .fetch(); - - // Count 쿼리 별도 실행 (null safe handling) - long total = - Optional.ofNullable( - queryFactory.select(mapSheet.count()).from(mapSheet).where(builder).fetchOne()) - .orElse(0L); - - return new PageImpl<>(content, pageable, total); - } -} +package com.kamco.cd.training.postgres.repository.dataset; + +import com.kamco.cd.training.dataset.dto.MapSheetDto; +import com.kamco.cd.training.postgres.entity.MapSheetEntity; +import com.kamco.cd.training.postgres.entity.QMapSheetEntity; +import com.querydsl.core.BooleanBuilder; +import com.querydsl.jpa.impl.JPAQueryFactory; +import java.util.List; +import java.util.Optional; +import lombok.RequiredArgsConstructor; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageImpl; +import org.springframework.data.domain.Pageable; +import org.springframework.stereotype.Repository; + +@Repository +@RequiredArgsConstructor +public class MapSheetRepositoryImpl implements MapSheetRepositoryCustom { + + private final JPAQueryFactory queryFactory; + private final QMapSheetEntity mapSheet = QMapSheetEntity.mapSheetEntity; + + @Override + public Page findMapSheetList(MapSheetDto.SearchReq searchReq) { + Pageable pageable = searchReq.toPageable(); + BooleanBuilder builder = new BooleanBuilder(); + + // 데이터셋 ID 필터 + if (searchReq.getDatasetId() != null) { + builder.and(mapSheet.datasetId.eq(searchReq.getDatasetId())); + } + + // 삭제되지 않은 것만 + builder.and(mapSheet.deleted.isFalse()); + + // Entity 직접 조회 (Projections 사용 지양) + List content = + queryFactory + .selectFrom(mapSheet) + .where(builder) + .offset(pageable.getOffset()) + .limit(pageable.getPageSize()) + .orderBy(mapSheet.createdDttm.desc()) + .fetch(); + + // Count 쿼리 별도 실행 (null safe handling) + long total = + Optional.ofNullable( + queryFactory.select(mapSheet.count()).from(mapSheet).where(builder).fetchOne()) + .orElse(0L); + + return new PageImpl<>(content, pageable, total); + } +} diff --git a/src/main/java/com/kamco/cd/training/postgres/repository/log/AuditLogRepository.java b/src/main/java/com/kamco/cd/training/postgres/repository/log/AuditLogRepository.java index d1d88fb..4ead529 100644 --- a/src/main/java/com/kamco/cd/training/postgres/repository/log/AuditLogRepository.java +++ b/src/main/java/com/kamco/cd/training/postgres/repository/log/AuditLogRepository.java @@ -1,7 +1,7 @@ -package com.kamco.cd.training.postgres.repository.log; - -import com.kamco.cd.training.postgres.entity.AuditLogEntity; -import org.springframework.data.jpa.repository.JpaRepository; - -public interface AuditLogRepository - extends JpaRepository, AuditLogRepositoryCustom {} +package com.kamco.cd.training.postgres.repository.log; + +import com.kamco.cd.training.postgres.entity.AuditLogEntity; +import org.springframework.data.jpa.repository.JpaRepository; + +public interface AuditLogRepository + extends JpaRepository, AuditLogRepositoryCustom {} diff --git a/src/main/java/com/kamco/cd/training/postgres/repository/log/AuditLogRepositoryCustom.java b/src/main/java/com/kamco/cd/training/postgres/repository/log/AuditLogRepositoryCustom.java index 3c568a9..9c64c7a 100644 --- a/src/main/java/com/kamco/cd/training/postgres/repository/log/AuditLogRepositoryCustom.java +++ b/src/main/java/com/kamco/cd/training/postgres/repository/log/AuditLogRepositoryCustom.java @@ -1,25 +1,25 @@ -package com.kamco.cd.training.postgres.repository.log; - -import com.kamco.cd.training.log.dto.AuditLogDto; -import java.time.LocalDate; -import org.springframework.data.domain.Page; - -public interface AuditLogRepositoryCustom { - - Page findLogByDaily( - AuditLogDto.searchReq searchReq, LocalDate startDate, LocalDate endDate); - - Page findLogByMenu( - AuditLogDto.searchReq searchReq, String searchValue); - - Page findLogByAccount( - AuditLogDto.searchReq searchReq, String searchValue); - - Page findLogByDailyResult( - AuditLogDto.searchReq searchReq, LocalDate logDate); - - Page findLogByMenuResult(AuditLogDto.searchReq searchReq, String menuId); - - Page findLogByAccountResult( - AuditLogDto.searchReq searchReq, Long accountId); -} +package com.kamco.cd.training.postgres.repository.log; + +import com.kamco.cd.training.log.dto.AuditLogDto; +import java.time.LocalDate; +import org.springframework.data.domain.Page; + +public interface AuditLogRepositoryCustom { + + Page findLogByDaily( + AuditLogDto.searchReq searchReq, LocalDate startDate, LocalDate endDate); + + Page findLogByMenu( + AuditLogDto.searchReq searchReq, String searchValue); + + Page findLogByAccount( + AuditLogDto.searchReq searchReq, String searchValue); + + Page findLogByDailyResult( + AuditLogDto.searchReq searchReq, LocalDate logDate); + + Page findLogByMenuResult(AuditLogDto.searchReq searchReq, String menuId); + + Page findLogByAccountResult( + AuditLogDto.searchReq searchReq, Long accountId); +} diff --git a/src/main/java/com/kamco/cd/training/postgres/repository/log/AuditLogRepositoryImpl.java b/src/main/java/com/kamco/cd/training/postgres/repository/log/AuditLogRepositoryImpl.java index 8c0c739..f7353ac 100644 --- a/src/main/java/com/kamco/cd/training/postgres/repository/log/AuditLogRepositoryImpl.java +++ b/src/main/java/com/kamco/cd/training/postgres/repository/log/AuditLogRepositoryImpl.java @@ -1,442 +1,442 @@ -package com.kamco.cd.training.postgres.repository.log; - -import static com.kamco.cd.training.postgres.entity.QAuditLogEntity.auditLogEntity; -import static com.kamco.cd.training.postgres.entity.QErrorLogEntity.errorLogEntity; -import static com.kamco.cd.training.postgres.entity.QMemberEntity.memberEntity; -import static com.kamco.cd.training.postgres.entity.QMenuEntity.menuEntity; - -import com.kamco.cd.training.log.dto.AuditLogDto; -import com.kamco.cd.training.log.dto.ErrorLogDto; -import com.kamco.cd.training.log.dto.EventStatus; -import com.kamco.cd.training.log.dto.EventType; -import com.kamco.cd.training.postgres.entity.QMenuEntity; -import com.querydsl.core.types.Projections; -import com.querydsl.core.types.dsl.*; -import com.querydsl.jpa.impl.JPAQueryFactory; -import io.micrometer.common.util.StringUtils; -import java.time.LocalDate; -import java.time.LocalDateTime; -import java.time.ZonedDateTime; -import java.util.List; -import java.util.Objects; -import lombok.RequiredArgsConstructor; -import org.springframework.data.domain.Page; -import org.springframework.data.domain.PageImpl; -import org.springframework.data.domain.Pageable; -import org.springframework.stereotype.Repository; - -@Repository -@RequiredArgsConstructor -public class AuditLogRepositoryImpl implements AuditLogRepositoryCustom { - - private final JPAQueryFactory queryFactory; - private final StringExpression NULL_STRING = Expressions.stringTemplate("cast(null as text)"); - - @Override - public Page findLogByDaily( - AuditLogDto.searchReq searchReq, LocalDate startDate, LocalDate endDate) { - StringExpression groupDateTime = - Expressions.stringTemplate("to_char({0}, 'YYYY-MM-DD')", auditLogEntity.createdDate); - - Pageable pageable = searchReq.toPageable(); - List foundContent = - queryFactory - .select( - Projections.constructor( - AuditLogDto.DailyAuditList.class, - readCount().as("readCount"), - cudCount().as("cudCount"), - printCount().as("printCount"), - downloadCount().as("downloadCount"), - auditLogEntity.count().as("totalCount"), - groupDateTime.as("baseDate"))) - .from(auditLogEntity) - .where(eventEndedAtBetween(startDate, endDate)) - .groupBy(groupDateTime) - .offset(pageable.getOffset()) - .limit(pageable.getPageSize()) - .orderBy(groupDateTime.desc()) - .fetch(); - - Long countQuery = - queryFactory - .select(groupDateTime.countDistinct()) - .from(auditLogEntity) - .where(eventEndedAtBetween(startDate, endDate)) - .fetchOne(); - - return new PageImpl<>(foundContent, pageable, countQuery); - } - - @Override - public Page findLogByMenu( - AuditLogDto.searchReq searchReq, String searchValue) { - Pageable pageable = searchReq.toPageable(); - List foundContent = - queryFactory - .select( - Projections.constructor( - AuditLogDto.MenuAuditList.class, - auditLogEntity.menuUid.as("menuId"), - menuEntity.menuNm.max().as("menuName"), - readCount().as("readCount"), - cudCount().as("cudCount"), - printCount().as("printCount"), - downloadCount().as("downloadCount"), - auditLogEntity.count().as("totalCount"))) - .from(auditLogEntity) - .leftJoin(menuEntity) - .on(auditLogEntity.menuUid.eq(menuEntity.menuUid)) - .where(menuNameEquals(searchValue)) - .groupBy(auditLogEntity.menuUid) - .offset(pageable.getOffset()) - .limit(pageable.getPageSize()) - .orderBy(auditLogEntity.createdDate.max().desc()) - .fetch(); - - // count query group by 를 지정하면 하나의 row 가 아니라 그룹핑된 여러 row 가 나올 수 있다. - // select query 의 group by 대상의 컬럼을 count query 에선 select distinct 로 처리 한다. - Long countQuery = - queryFactory - .select(auditLogEntity.menuUid.countDistinct()) - .from(auditLogEntity) - .leftJoin(menuEntity) - .on(auditLogEntity.menuUid.eq(menuEntity.menuUid)) - .where(menuNameEquals(searchValue)) - .fetchOne(); - - return new PageImpl<>(foundContent, pageable, countQuery); - } - - @Override - public Page findLogByAccount( - AuditLogDto.searchReq searchReq, String searchValue) { - Pageable pageable = searchReq.toPageable(); - List foundContent = - queryFactory - .select( - Projections.constructor( - AuditLogDto.UserAuditList.class, - auditLogEntity.userUid.as("accountId"), - memberEntity.employeeNo.as("loginId"), - memberEntity.name.as("username"), - readCount().as("readCount"), - cudCount().as("cudCount"), - printCount().as("printCount"), - downloadCount().as("downloadCount"), - auditLogEntity.count().as("totalCount"))) - .from(auditLogEntity) - .leftJoin(memberEntity) - .on(auditLogEntity.userUid.eq(memberEntity.id)) - .where(loginIdOrUsernameContains(searchValue)) - .groupBy(auditLogEntity.userUid, memberEntity.employeeNo, memberEntity.name) - .offset(pageable.getOffset()) - .limit(pageable.getPageSize()) - // .orderBy(auditLogEntity.eventEndedAt.max().desc()) - .fetch(); - - Long countQuery = - queryFactory - .select(auditLogEntity.userUid.countDistinct()) - .from(auditLogEntity) - .leftJoin(memberEntity) - .on(auditLogEntity.userUid.eq(memberEntity.id)) - .where(loginIdOrUsernameContains(searchValue)) - .fetchOne(); - - return new PageImpl<>(foundContent, pageable, countQuery); - } - - @Override - public Page findLogByDailyResult( - AuditLogDto.searchReq searchReq, LocalDate logDate) { - Pageable pageable = searchReq.toPageable(); - QMenuEntity parent = new QMenuEntity("parent"); - // 1depth menu name - StringExpression parentMenuName = - new CaseBuilder() - .when(parent.menuUid.isNull()) - .then(menuEntity.menuNm) - .otherwise(parent.menuNm); - - // 2depth menu name - StringExpression menuName = - new CaseBuilder() - .when(parent.menuUid.isNull()) - .then(NULL_STRING) - .otherwise(menuEntity.menuNm); - - List foundContent = - queryFactory - .select( - Projections.constructor( - AuditLogDto.DailyDetail.class, - auditLogEntity.id.as("logId"), - memberEntity.name.as("userName"), - memberEntity.employeeNo.as("loginId"), - menuEntity.menuNm.as("menuName"), - auditLogEntity.eventType.as("eventType"), - Projections.constructor( - AuditLogDto.LogDetail.class, - Expressions.constant("한국자산관리공사"), // serviceName - parentMenuName.as("parentMenuName"), - menuName, - menuEntity.menuUrl.as("menuUrl"), - menuEntity.description.as("menuDescription"), - menuEntity.menuOrder.as("sortOrder"), - menuEntity.isUse.as("used")))) - .from(auditLogEntity) - .leftJoin(menuEntity) - .on(auditLogEntity.menuUid.eq(menuEntity.menuUid)) - .leftJoin(menuEntity.parent, parent) - .leftJoin(memberEntity) - .on(auditLogEntity.userUid.eq(memberEntity.id)) - .where(eventEndedAtEqDate(logDate)) - .offset(pageable.getOffset()) - .limit(pageable.getPageSize()) - .orderBy(auditLogEntity.createdDate.desc()) - .fetch(); - - Long countQuery = - queryFactory - .select(auditLogEntity.id.countDistinct()) - .from(auditLogEntity) - .leftJoin(menuEntity) - .on(auditLogEntity.menuUid.eq(menuEntity.menuUid)) - .leftJoin(menuEntity.parent, parent) - .leftJoin(memberEntity) - .on(auditLogEntity.userUid.eq(memberEntity.id)) - .where(eventEndedAtEqDate(logDate)) - .fetchOne(); - - return new PageImpl<>(foundContent, pageable, countQuery); - } - - @Override - public Page findLogByMenuResult( - AuditLogDto.searchReq searchReq, String menuUid) { - Pageable pageable = searchReq.toPageable(); - QMenuEntity parent = new QMenuEntity("parent"); - // 1depth menu name - StringExpression parentMenuName = - new CaseBuilder() - .when(parent.menuUid.isNull()) - .then(menuEntity.menuNm) - .otherwise(parent.menuNm); - - // 2depth menu name - StringExpression menuName = - new CaseBuilder() - .when(parent.menuUid.isNull()) - .then(NULL_STRING) - .otherwise(menuEntity.menuNm); - - List foundContent = - queryFactory - .select( - Projections.constructor( - AuditLogDto.MenuDetail.class, - auditLogEntity.id.as("logId"), - Expressions.stringTemplate( - "to_char({0}, 'YYYY-MM-DD')", auditLogEntity.createdDate) - .as("logDateTime"), // ?? - memberEntity.name.as("userName"), - memberEntity.employeeNo.as("loginId"), - auditLogEntity.eventType.as("eventType"), - Projections.constructor( - AuditLogDto.LogDetail.class, - Expressions.constant("한국자산관리공사"), // serviceName - parentMenuName.as("parentMenuName"), - menuName, - menuEntity.menuUrl.as("menuUrl"), - menuEntity.description.as("menuDescription"), - menuEntity.menuOrder.as("sortOrder"), - menuEntity.isUse.as("used")))) - .from(auditLogEntity) - .leftJoin(menuEntity) - .on(auditLogEntity.menuUid.eq(menuEntity.menuUid)) - .leftJoin(menuEntity.parent, parent) - .leftJoin(memberEntity) - .on(auditLogEntity.userUid.eq(memberEntity.id)) - .where(menuUidEq(menuUid)) - .offset(pageable.getOffset()) - .limit(pageable.getPageSize()) - .orderBy(auditLogEntity.createdDate.desc()) - .fetch(); - - Long countQuery = - queryFactory - .select(auditLogEntity.id.countDistinct()) - .from(auditLogEntity) - .leftJoin(menuEntity) - .on(auditLogEntity.menuUid.eq(menuEntity.menuUid)) - .leftJoin(menuEntity.parent, parent) - .leftJoin(memberEntity) - .on(auditLogEntity.userUid.eq(memberEntity.id)) - .where(menuUidEq(menuUid)) - .fetchOne(); - - return new PageImpl<>(foundContent, pageable, countQuery); - } - - @Override - public Page findLogByAccountResult( - AuditLogDto.searchReq searchReq, Long userUid) { - Pageable pageable = searchReq.toPageable(); - QMenuEntity parent = new QMenuEntity("parent"); - // 1depth menu name - StringExpression parentMenuName = - new CaseBuilder() - .when(parent.menuUid.isNull()) - .then(menuEntity.menuNm) - .otherwise(parent.menuNm); - - // 2depth menu name - StringExpression menuName = - new CaseBuilder() - .when(parent.menuUid.isNull()) - .then(NULL_STRING) - .otherwise(menuEntity.menuNm); - - List foundContent = - queryFactory - .select( - Projections.constructor( - AuditLogDto.UserDetail.class, - auditLogEntity.id.as("logId"), - Expressions.stringTemplate( - "to_char({0}, 'YYYY-MM-DD')", auditLogEntity.createdDate) - .as("logDateTime"), - menuEntity.menuNm.as("menuName"), - auditLogEntity.eventType.as("eventType"), - Projections.constructor( - AuditLogDto.LogDetail.class, - Expressions.constant("한국자산관리공사"), // serviceName - parentMenuName.as("parentMenuName"), - menuName, - menuEntity.menuUrl.as("menuUrl"), - menuEntity.description.as("menuDescription"), - menuEntity.menuOrder.as("sortOrder"), - menuEntity.isUse.as("used")))) - .from(auditLogEntity) - .leftJoin(menuEntity) - .on(auditLogEntity.menuUid.eq(menuEntity.menuUid)) - .leftJoin(menuEntity.parent, parent) - .leftJoin(memberEntity) - .on(auditLogEntity.userUid.eq(memberEntity.id)) - .where(userUidEq(userUid)) - .offset(pageable.getOffset()) - .limit(pageable.getPageSize()) - .orderBy(auditLogEntity.createdDate.desc()) - .fetch(); - - Long countQuery = - queryFactory - .select(auditLogEntity.id.countDistinct()) - .from(auditLogEntity) - .leftJoin(menuEntity) - .on(auditLogEntity.menuUid.eq(menuEntity.menuUid)) - .leftJoin(menuEntity.parent, parent) - .leftJoin(memberEntity) - .on(auditLogEntity.userUid.eq(memberEntity.id)) - .where(userUidEq(userUid)) - .fetchOne(); - - return new PageImpl<>(foundContent, pageable, countQuery); - } - - private BooleanExpression eventEndedAtBetween(LocalDate startDate, LocalDate endDate) { - if (Objects.isNull(startDate) || Objects.isNull(endDate)) { - return null; - } - LocalDateTime startDateTime = startDate.atStartOfDay(); - LocalDateTime endDateTime = endDate.plusDays(1).atStartOfDay(); - return auditLogEntity - .createdDate - .goe(ZonedDateTime.from(startDateTime)) - .and(auditLogEntity.createdDate.lt(ZonedDateTime.from(endDateTime))); - } - - private BooleanExpression menuNameEquals(String searchValue) { - if (StringUtils.isBlank(searchValue)) { - return null; - } - return menuEntity.menuNm.contains(searchValue); - } - - private BooleanExpression loginIdOrUsernameContains(String searchValue) { - if (StringUtils.isBlank(searchValue)) { - return null; - } - return memberEntity - .employeeNo - .contains(searchValue) - .or(memberEntity.name.contains(searchValue)); - } - - private BooleanExpression eventStatusEqFailed() { - return auditLogEntity.eventStatus.eq(EventStatus.FAILED); - } - - private BooleanExpression eventTypeEq(EventType eventType) { - if (Objects.isNull(eventType)) { - return null; - } - return auditLogEntity.eventType.eq(eventType); - } - - private BooleanExpression errorLevelEq(ErrorLogDto.LogErrorLevel level) { - if (Objects.isNull(level)) { - return null; - } - return errorLogEntity.errorLevel.eq(ErrorLogDto.LogErrorLevel.valueOf(level.name())); - } - - private BooleanExpression eventEndedAtEqDate(LocalDate logDate) { - StringExpression eventEndedDate = - Expressions.stringTemplate("to_char({0}, 'YYYY-MM-DD')", auditLogEntity.createdDate); - LocalDateTime comparisonDate = logDate.atStartOfDay(); - - return eventEndedDate.eq(comparisonDate.toString()); - } - - private BooleanExpression menuUidEq(String menuUid) { - return auditLogEntity.menuUid.eq(menuUid); - } - - private BooleanExpression userUidEq(Long userUid) { - return auditLogEntity.userUid.eq(userUid); - } - - private NumberExpression readCount() { - return new CaseBuilder() - .when(auditLogEntity.eventType.eq(EventType.READ)) - .then(1) - .otherwise(0) - .sum(); - } - - private NumberExpression cudCount() { - return new CaseBuilder() - .when(auditLogEntity.eventType.in(EventType.CREATE, EventType.UPDATE, EventType.DELETE)) - .then(1) - .otherwise(0) - .sum(); - } - - private NumberExpression printCount() { - return new CaseBuilder() - .when(auditLogEntity.eventType.eq(EventType.PRINT)) - .then(1) - .otherwise(0) - .sum(); - } - - private NumberExpression downloadCount() { - return new CaseBuilder() - .when(auditLogEntity.eventType.eq(EventType.DOWNLOAD)) - .then(1) - .otherwise(0) - .sum(); - } -} +package com.kamco.cd.training.postgres.repository.log; + +import static com.kamco.cd.training.postgres.entity.QAuditLogEntity.auditLogEntity; +import static com.kamco.cd.training.postgres.entity.QErrorLogEntity.errorLogEntity; +import static com.kamco.cd.training.postgres.entity.QMemberEntity.memberEntity; +import static com.kamco.cd.training.postgres.entity.QMenuEntity.menuEntity; + +import com.kamco.cd.training.log.dto.AuditLogDto; +import com.kamco.cd.training.log.dto.ErrorLogDto; +import com.kamco.cd.training.log.dto.EventStatus; +import com.kamco.cd.training.log.dto.EventType; +import com.kamco.cd.training.postgres.entity.QMenuEntity; +import com.querydsl.core.types.Projections; +import com.querydsl.core.types.dsl.*; +import com.querydsl.jpa.impl.JPAQueryFactory; +import io.micrometer.common.util.StringUtils; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.ZonedDateTime; +import java.util.List; +import java.util.Objects; +import lombok.RequiredArgsConstructor; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageImpl; +import org.springframework.data.domain.Pageable; +import org.springframework.stereotype.Repository; + +@Repository +@RequiredArgsConstructor +public class AuditLogRepositoryImpl implements AuditLogRepositoryCustom { + + private final JPAQueryFactory queryFactory; + private final StringExpression NULL_STRING = Expressions.stringTemplate("cast(null as text)"); + + @Override + public Page findLogByDaily( + AuditLogDto.searchReq searchReq, LocalDate startDate, LocalDate endDate) { + StringExpression groupDateTime = + Expressions.stringTemplate("to_char({0}, 'YYYY-MM-DD')", auditLogEntity.createdDate); + + Pageable pageable = searchReq.toPageable(); + List foundContent = + queryFactory + .select( + Projections.constructor( + AuditLogDto.DailyAuditList.class, + readCount().as("readCount"), + cudCount().as("cudCount"), + printCount().as("printCount"), + downloadCount().as("downloadCount"), + auditLogEntity.count().as("totalCount"), + groupDateTime.as("baseDate"))) + .from(auditLogEntity) + .where(eventEndedAtBetween(startDate, endDate)) + .groupBy(groupDateTime) + .offset(pageable.getOffset()) + .limit(pageable.getPageSize()) + .orderBy(groupDateTime.desc()) + .fetch(); + + Long countQuery = + queryFactory + .select(groupDateTime.countDistinct()) + .from(auditLogEntity) + .where(eventEndedAtBetween(startDate, endDate)) + .fetchOne(); + + return new PageImpl<>(foundContent, pageable, countQuery); + } + + @Override + public Page findLogByMenu( + AuditLogDto.searchReq searchReq, String searchValue) { + Pageable pageable = searchReq.toPageable(); + List foundContent = + queryFactory + .select( + Projections.constructor( + AuditLogDto.MenuAuditList.class, + auditLogEntity.menuUid.as("menuId"), + menuEntity.menuNm.max().as("menuName"), + readCount().as("readCount"), + cudCount().as("cudCount"), + printCount().as("printCount"), + downloadCount().as("downloadCount"), + auditLogEntity.count().as("totalCount"))) + .from(auditLogEntity) + .leftJoin(menuEntity) + .on(auditLogEntity.menuUid.eq(menuEntity.menuUid)) + .where(menuNameEquals(searchValue)) + .groupBy(auditLogEntity.menuUid) + .offset(pageable.getOffset()) + .limit(pageable.getPageSize()) + .orderBy(auditLogEntity.createdDate.max().desc()) + .fetch(); + + // count query group by 를 지정하면 하나의 row 가 아니라 그룹핑된 여러 row 가 나올 수 있다. + // select query 의 group by 대상의 컬럼을 count query 에선 select distinct 로 처리 한다. + Long countQuery = + queryFactory + .select(auditLogEntity.menuUid.countDistinct()) + .from(auditLogEntity) + .leftJoin(menuEntity) + .on(auditLogEntity.menuUid.eq(menuEntity.menuUid)) + .where(menuNameEquals(searchValue)) + .fetchOne(); + + return new PageImpl<>(foundContent, pageable, countQuery); + } + + @Override + public Page findLogByAccount( + AuditLogDto.searchReq searchReq, String searchValue) { + Pageable pageable = searchReq.toPageable(); + List foundContent = + queryFactory + .select( + Projections.constructor( + AuditLogDto.UserAuditList.class, + auditLogEntity.userUid.as("accountId"), + memberEntity.employeeNo.as("loginId"), + memberEntity.name.as("username"), + readCount().as("readCount"), + cudCount().as("cudCount"), + printCount().as("printCount"), + downloadCount().as("downloadCount"), + auditLogEntity.count().as("totalCount"))) + .from(auditLogEntity) + .leftJoin(memberEntity) + .on(auditLogEntity.userUid.eq(memberEntity.id)) + .where(loginIdOrUsernameContains(searchValue)) + .groupBy(auditLogEntity.userUid, memberEntity.employeeNo, memberEntity.name) + .offset(pageable.getOffset()) + .limit(pageable.getPageSize()) + // .orderBy(auditLogEntity.eventEndedAt.max().desc()) + .fetch(); + + Long countQuery = + queryFactory + .select(auditLogEntity.userUid.countDistinct()) + .from(auditLogEntity) + .leftJoin(memberEntity) + .on(auditLogEntity.userUid.eq(memberEntity.id)) + .where(loginIdOrUsernameContains(searchValue)) + .fetchOne(); + + return new PageImpl<>(foundContent, pageable, countQuery); + } + + @Override + public Page findLogByDailyResult( + AuditLogDto.searchReq searchReq, LocalDate logDate) { + Pageable pageable = searchReq.toPageable(); + QMenuEntity parent = new QMenuEntity("parent"); + // 1depth menu name + StringExpression parentMenuName = + new CaseBuilder() + .when(parent.menuUid.isNull()) + .then(menuEntity.menuNm) + .otherwise(parent.menuNm); + + // 2depth menu name + StringExpression menuName = + new CaseBuilder() + .when(parent.menuUid.isNull()) + .then(NULL_STRING) + .otherwise(menuEntity.menuNm); + + List foundContent = + queryFactory + .select( + Projections.constructor( + AuditLogDto.DailyDetail.class, + auditLogEntity.id.as("logId"), + memberEntity.name.as("userName"), + memberEntity.employeeNo.as("loginId"), + menuEntity.menuNm.as("menuName"), + auditLogEntity.eventType.as("eventType"), + Projections.constructor( + AuditLogDto.LogDetail.class, + Expressions.constant("한국자산관리공사"), // serviceName + parentMenuName.as("parentMenuName"), + menuName, + menuEntity.menuUrl.as("menuUrl"), + menuEntity.description.as("menuDescription"), + menuEntity.menuOrder.as("sortOrder"), + menuEntity.isUse.as("used")))) + .from(auditLogEntity) + .leftJoin(menuEntity) + .on(auditLogEntity.menuUid.eq(menuEntity.menuUid)) + .leftJoin(menuEntity.parent, parent) + .leftJoin(memberEntity) + .on(auditLogEntity.userUid.eq(memberEntity.id)) + .where(eventEndedAtEqDate(logDate)) + .offset(pageable.getOffset()) + .limit(pageable.getPageSize()) + .orderBy(auditLogEntity.createdDate.desc()) + .fetch(); + + Long countQuery = + queryFactory + .select(auditLogEntity.id.countDistinct()) + .from(auditLogEntity) + .leftJoin(menuEntity) + .on(auditLogEntity.menuUid.eq(menuEntity.menuUid)) + .leftJoin(menuEntity.parent, parent) + .leftJoin(memberEntity) + .on(auditLogEntity.userUid.eq(memberEntity.id)) + .where(eventEndedAtEqDate(logDate)) + .fetchOne(); + + return new PageImpl<>(foundContent, pageable, countQuery); + } + + @Override + public Page findLogByMenuResult( + AuditLogDto.searchReq searchReq, String menuUid) { + Pageable pageable = searchReq.toPageable(); + QMenuEntity parent = new QMenuEntity("parent"); + // 1depth menu name + StringExpression parentMenuName = + new CaseBuilder() + .when(parent.menuUid.isNull()) + .then(menuEntity.menuNm) + .otherwise(parent.menuNm); + + // 2depth menu name + StringExpression menuName = + new CaseBuilder() + .when(parent.menuUid.isNull()) + .then(NULL_STRING) + .otherwise(menuEntity.menuNm); + + List foundContent = + queryFactory + .select( + Projections.constructor( + AuditLogDto.MenuDetail.class, + auditLogEntity.id.as("logId"), + Expressions.stringTemplate( + "to_char({0}, 'YYYY-MM-DD')", auditLogEntity.createdDate) + .as("logDateTime"), // ?? + memberEntity.name.as("userName"), + memberEntity.employeeNo.as("loginId"), + auditLogEntity.eventType.as("eventType"), + Projections.constructor( + AuditLogDto.LogDetail.class, + Expressions.constant("한국자산관리공사"), // serviceName + parentMenuName.as("parentMenuName"), + menuName, + menuEntity.menuUrl.as("menuUrl"), + menuEntity.description.as("menuDescription"), + menuEntity.menuOrder.as("sortOrder"), + menuEntity.isUse.as("used")))) + .from(auditLogEntity) + .leftJoin(menuEntity) + .on(auditLogEntity.menuUid.eq(menuEntity.menuUid)) + .leftJoin(menuEntity.parent, parent) + .leftJoin(memberEntity) + .on(auditLogEntity.userUid.eq(memberEntity.id)) + .where(menuUidEq(menuUid)) + .offset(pageable.getOffset()) + .limit(pageable.getPageSize()) + .orderBy(auditLogEntity.createdDate.desc()) + .fetch(); + + Long countQuery = + queryFactory + .select(auditLogEntity.id.countDistinct()) + .from(auditLogEntity) + .leftJoin(menuEntity) + .on(auditLogEntity.menuUid.eq(menuEntity.menuUid)) + .leftJoin(menuEntity.parent, parent) + .leftJoin(memberEntity) + .on(auditLogEntity.userUid.eq(memberEntity.id)) + .where(menuUidEq(menuUid)) + .fetchOne(); + + return new PageImpl<>(foundContent, pageable, countQuery); + } + + @Override + public Page findLogByAccountResult( + AuditLogDto.searchReq searchReq, Long userUid) { + Pageable pageable = searchReq.toPageable(); + QMenuEntity parent = new QMenuEntity("parent"); + // 1depth menu name + StringExpression parentMenuName = + new CaseBuilder() + .when(parent.menuUid.isNull()) + .then(menuEntity.menuNm) + .otherwise(parent.menuNm); + + // 2depth menu name + StringExpression menuName = + new CaseBuilder() + .when(parent.menuUid.isNull()) + .then(NULL_STRING) + .otherwise(menuEntity.menuNm); + + List foundContent = + queryFactory + .select( + Projections.constructor( + AuditLogDto.UserDetail.class, + auditLogEntity.id.as("logId"), + Expressions.stringTemplate( + "to_char({0}, 'YYYY-MM-DD')", auditLogEntity.createdDate) + .as("logDateTime"), + menuEntity.menuNm.as("menuName"), + auditLogEntity.eventType.as("eventType"), + Projections.constructor( + AuditLogDto.LogDetail.class, + Expressions.constant("한국자산관리공사"), // serviceName + parentMenuName.as("parentMenuName"), + menuName, + menuEntity.menuUrl.as("menuUrl"), + menuEntity.description.as("menuDescription"), + menuEntity.menuOrder.as("sortOrder"), + menuEntity.isUse.as("used")))) + .from(auditLogEntity) + .leftJoin(menuEntity) + .on(auditLogEntity.menuUid.eq(menuEntity.menuUid)) + .leftJoin(menuEntity.parent, parent) + .leftJoin(memberEntity) + .on(auditLogEntity.userUid.eq(memberEntity.id)) + .where(userUidEq(userUid)) + .offset(pageable.getOffset()) + .limit(pageable.getPageSize()) + .orderBy(auditLogEntity.createdDate.desc()) + .fetch(); + + Long countQuery = + queryFactory + .select(auditLogEntity.id.countDistinct()) + .from(auditLogEntity) + .leftJoin(menuEntity) + .on(auditLogEntity.menuUid.eq(menuEntity.menuUid)) + .leftJoin(menuEntity.parent, parent) + .leftJoin(memberEntity) + .on(auditLogEntity.userUid.eq(memberEntity.id)) + .where(userUidEq(userUid)) + .fetchOne(); + + return new PageImpl<>(foundContent, pageable, countQuery); + } + + private BooleanExpression eventEndedAtBetween(LocalDate startDate, LocalDate endDate) { + if (Objects.isNull(startDate) || Objects.isNull(endDate)) { + return null; + } + LocalDateTime startDateTime = startDate.atStartOfDay(); + LocalDateTime endDateTime = endDate.plusDays(1).atStartOfDay(); + return auditLogEntity + .createdDate + .goe(ZonedDateTime.from(startDateTime)) + .and(auditLogEntity.createdDate.lt(ZonedDateTime.from(endDateTime))); + } + + private BooleanExpression menuNameEquals(String searchValue) { + if (StringUtils.isBlank(searchValue)) { + return null; + } + return menuEntity.menuNm.contains(searchValue); + } + + private BooleanExpression loginIdOrUsernameContains(String searchValue) { + if (StringUtils.isBlank(searchValue)) { + return null; + } + return memberEntity + .employeeNo + .contains(searchValue) + .or(memberEntity.name.contains(searchValue)); + } + + private BooleanExpression eventStatusEqFailed() { + return auditLogEntity.eventStatus.eq(EventStatus.FAILED); + } + + private BooleanExpression eventTypeEq(EventType eventType) { + if (Objects.isNull(eventType)) { + return null; + } + return auditLogEntity.eventType.eq(eventType); + } + + private BooleanExpression errorLevelEq(ErrorLogDto.LogErrorLevel level) { + if (Objects.isNull(level)) { + return null; + } + return errorLogEntity.errorLevel.eq(ErrorLogDto.LogErrorLevel.valueOf(level.name())); + } + + private BooleanExpression eventEndedAtEqDate(LocalDate logDate) { + StringExpression eventEndedDate = + Expressions.stringTemplate("to_char({0}, 'YYYY-MM-DD')", auditLogEntity.createdDate); + LocalDateTime comparisonDate = logDate.atStartOfDay(); + + return eventEndedDate.eq(comparisonDate.toString()); + } + + private BooleanExpression menuUidEq(String menuUid) { + return auditLogEntity.menuUid.eq(menuUid); + } + + private BooleanExpression userUidEq(Long userUid) { + return auditLogEntity.userUid.eq(userUid); + } + + private NumberExpression readCount() { + return new CaseBuilder() + .when(auditLogEntity.eventType.eq(EventType.READ)) + .then(1) + .otherwise(0) + .sum(); + } + + private NumberExpression cudCount() { + return new CaseBuilder() + .when(auditLogEntity.eventType.in(EventType.CREATE, EventType.UPDATE, EventType.DELETE)) + .then(1) + .otherwise(0) + .sum(); + } + + private NumberExpression printCount() { + return new CaseBuilder() + .when(auditLogEntity.eventType.eq(EventType.PRINT)) + .then(1) + .otherwise(0) + .sum(); + } + + private NumberExpression downloadCount() { + return new CaseBuilder() + .when(auditLogEntity.eventType.eq(EventType.DOWNLOAD)) + .then(1) + .otherwise(0) + .sum(); + } +} diff --git a/src/main/java/com/kamco/cd/training/postgres/repository/log/ErrorLogRepository.java b/src/main/java/com/kamco/cd/training/postgres/repository/log/ErrorLogRepository.java index d4c7251..7945c1b 100644 --- a/src/main/java/com/kamco/cd/training/postgres/repository/log/ErrorLogRepository.java +++ b/src/main/java/com/kamco/cd/training/postgres/repository/log/ErrorLogRepository.java @@ -1,7 +1,7 @@ -package com.kamco.cd.training.postgres.repository.log; - -import com.kamco.cd.training.postgres.entity.ErrorLogEntity; -import org.springframework.data.jpa.repository.JpaRepository; - -public interface ErrorLogRepository - extends JpaRepository, ErrorLogRepositoryCustom {} +package com.kamco.cd.training.postgres.repository.log; + +import com.kamco.cd.training.postgres.entity.ErrorLogEntity; +import org.springframework.data.jpa.repository.JpaRepository; + +public interface ErrorLogRepository + extends JpaRepository, ErrorLogRepositoryCustom {} diff --git a/src/main/java/com/kamco/cd/training/postgres/repository/log/ErrorLogRepositoryCustom.java b/src/main/java/com/kamco/cd/training/postgres/repository/log/ErrorLogRepositoryCustom.java index b7da666..061dbfa 100644 --- a/src/main/java/com/kamco/cd/training/postgres/repository/log/ErrorLogRepositoryCustom.java +++ b/src/main/java/com/kamco/cd/training/postgres/repository/log/ErrorLogRepositoryCustom.java @@ -1,8 +1,8 @@ -package com.kamco.cd.training.postgres.repository.log; - -import com.kamco.cd.training.log.dto.ErrorLogDto; -import org.springframework.data.domain.Page; - -public interface ErrorLogRepositoryCustom { - public Page findLogByError(ErrorLogDto.ErrorSearchReq searchReq); -} +package com.kamco.cd.training.postgres.repository.log; + +import com.kamco.cd.training.log.dto.ErrorLogDto; +import org.springframework.data.domain.Page; + +public interface ErrorLogRepositoryCustom { + public Page findLogByError(ErrorLogDto.ErrorSearchReq searchReq); +} diff --git a/src/main/java/com/kamco/cd/training/postgres/repository/log/ErrorLogRepositoryImpl.java b/src/main/java/com/kamco/cd/training/postgres/repository/log/ErrorLogRepositoryImpl.java index 8186616..97b50a3 100644 --- a/src/main/java/com/kamco/cd/training/postgres/repository/log/ErrorLogRepositoryImpl.java +++ b/src/main/java/com/kamco/cd/training/postgres/repository/log/ErrorLogRepositoryImpl.java @@ -1,122 +1,122 @@ -package com.kamco.cd.training.postgres.repository.log; - -import static com.kamco.cd.training.postgres.entity.QAuditLogEntity.auditLogEntity; -import static com.kamco.cd.training.postgres.entity.QErrorLogEntity.errorLogEntity; -import static com.kamco.cd.training.postgres.entity.QMemberEntity.memberEntity; -import static com.kamco.cd.training.postgres.entity.QMenuEntity.menuEntity; - -import com.kamco.cd.training.log.dto.ErrorLogDto; -import com.kamco.cd.training.log.dto.EventStatus; -import com.kamco.cd.training.log.dto.EventType; -import com.querydsl.core.types.Projections; -import com.querydsl.core.types.dsl.BooleanExpression; -import com.querydsl.core.types.dsl.Expressions; -import com.querydsl.core.types.dsl.StringExpression; -import com.querydsl.jpa.impl.JPAQueryFactory; -import java.time.LocalDate; -import java.time.LocalDateTime; -import java.time.ZonedDateTime; -import java.util.List; -import java.util.Objects; -import lombok.RequiredArgsConstructor; -import org.springframework.data.domain.Page; -import org.springframework.data.domain.PageImpl; -import org.springframework.data.domain.Pageable; -import org.springframework.stereotype.Repository; - -@Repository -@RequiredArgsConstructor -public class ErrorLogRepositoryImpl implements ErrorLogRepositoryCustom { - - private final JPAQueryFactory queryFactory; - private final StringExpression NULL_STRING = Expressions.stringTemplate("cast(null as text)"); - - @Override - public Page findLogByError(ErrorLogDto.ErrorSearchReq searchReq) { - Pageable pageable = searchReq.toPageable(); - List foundContent = - queryFactory - .select( - Projections.constructor( - ErrorLogDto.Basic.class, - errorLogEntity.id.as("logId"), - Expressions.stringTemplate("{0}", "한국자산관리공사"), // serviceName - menuEntity.menuNm.as("menuName"), - memberEntity.employeeNo.as("loginId"), - memberEntity.name.as("userName"), - errorLogEntity.errorType.as("eventType"), - errorLogEntity.errorMessage.as( - "errorName"), // 기존에는 errorName 값이 있었는데 신규 테이블에는 없음. 에러 메세지와 동일 - errorLogEntity.errorLevel.as("errorLevel"), - errorLogEntity.errorCode.as("errorCode"), - errorLogEntity.errorMessage.as("errorMessage"), - errorLogEntity.stackTrace.as("errorDetail"), - Expressions.stringTemplate( - "to_char({0}, 'YYYY-MM-DD')", errorLogEntity.createdDate))) - .from(errorLogEntity) - .leftJoin(auditLogEntity) - .on(errorLogEntity.id.eq(auditLogEntity.errorLogUid)) - .leftJoin(menuEntity) - .on(auditLogEntity.menuUid.eq(menuEntity.menuUid)) - .leftJoin(memberEntity) - .on(errorLogEntity.handlerUid.eq(memberEntity.id)) - .where( - eventStatusEqFailed(), - eventEndedAtBetween(searchReq.getStartDate(), searchReq.getEndDate()), - eventTypeEq(searchReq.getEventType()), - errorLevelEq(searchReq.getErrorLevel())) - .offset(pageable.getOffset()) - .limit(pageable.getPageSize()) - .orderBy(errorLogEntity.createdDate.desc()) - .fetch(); - - Long countQuery = - queryFactory - .select(errorLogEntity.id.countDistinct()) - .from(errorLogEntity) - .leftJoin(auditLogEntity) - .on(errorLogEntity.id.eq(auditLogEntity.errorLogUid)) - .leftJoin(menuEntity) - .on(auditLogEntity.menuUid.eq(menuEntity.menuUid)) - .leftJoin(memberEntity) - .on(errorLogEntity.handlerUid.eq(memberEntity.id)) - .where( - eventStatusEqFailed(), - eventEndedAtBetween(searchReq.getStartDate(), searchReq.getEndDate()), - eventTypeEq(searchReq.getEventType()), - errorLevelEq(searchReq.getErrorLevel())) - .fetchOne(); - - return new PageImpl<>(foundContent, pageable, countQuery); - } - - private BooleanExpression eventEndedAtBetween(LocalDate startDate, LocalDate endDate) { - if (Objects.isNull(startDate) || Objects.isNull(endDate)) { - return null; - } - LocalDateTime startDateTime = startDate.atStartOfDay(); - LocalDateTime endDateTime = endDate.plusDays(1).atStartOfDay(); - return auditLogEntity - .createdDate - .goe(ZonedDateTime.from(startDateTime)) - .and(auditLogEntity.createdDate.lt(ZonedDateTime.from(endDateTime))); - } - - private BooleanExpression eventStatusEqFailed() { - return auditLogEntity.eventStatus.eq(EventStatus.FAILED); - } - - private BooleanExpression eventTypeEq(EventType eventType) { - if (Objects.isNull(eventType)) { - return null; - } - return auditLogEntity.eventType.eq(eventType); - } - - private BooleanExpression errorLevelEq(ErrorLogDto.LogErrorLevel level) { - if (Objects.isNull(level)) { - return null; - } - return errorLogEntity.errorLevel.eq(ErrorLogDto.LogErrorLevel.valueOf(level.name())); - } -} +package com.kamco.cd.training.postgres.repository.log; + +import static com.kamco.cd.training.postgres.entity.QAuditLogEntity.auditLogEntity; +import static com.kamco.cd.training.postgres.entity.QErrorLogEntity.errorLogEntity; +import static com.kamco.cd.training.postgres.entity.QMemberEntity.memberEntity; +import static com.kamco.cd.training.postgres.entity.QMenuEntity.menuEntity; + +import com.kamco.cd.training.log.dto.ErrorLogDto; +import com.kamco.cd.training.log.dto.EventStatus; +import com.kamco.cd.training.log.dto.EventType; +import com.querydsl.core.types.Projections; +import com.querydsl.core.types.dsl.BooleanExpression; +import com.querydsl.core.types.dsl.Expressions; +import com.querydsl.core.types.dsl.StringExpression; +import com.querydsl.jpa.impl.JPAQueryFactory; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.ZonedDateTime; +import java.util.List; +import java.util.Objects; +import lombok.RequiredArgsConstructor; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageImpl; +import org.springframework.data.domain.Pageable; +import org.springframework.stereotype.Repository; + +@Repository +@RequiredArgsConstructor +public class ErrorLogRepositoryImpl implements ErrorLogRepositoryCustom { + + private final JPAQueryFactory queryFactory; + private final StringExpression NULL_STRING = Expressions.stringTemplate("cast(null as text)"); + + @Override + public Page findLogByError(ErrorLogDto.ErrorSearchReq searchReq) { + Pageable pageable = searchReq.toPageable(); + List foundContent = + queryFactory + .select( + Projections.constructor( + ErrorLogDto.Basic.class, + errorLogEntity.id.as("logId"), + Expressions.stringTemplate("{0}", "한국자산관리공사"), // serviceName + menuEntity.menuNm.as("menuName"), + memberEntity.employeeNo.as("loginId"), + memberEntity.name.as("userName"), + errorLogEntity.errorType.as("eventType"), + errorLogEntity.errorMessage.as( + "errorName"), // 기존에는 errorName 값이 있었는데 신규 테이블에는 없음. 에러 메세지와 동일 + errorLogEntity.errorLevel.as("errorLevel"), + errorLogEntity.errorCode.as("errorCode"), + errorLogEntity.errorMessage.as("errorMessage"), + errorLogEntity.stackTrace.as("errorDetail"), + Expressions.stringTemplate( + "to_char({0}, 'YYYY-MM-DD')", errorLogEntity.createdDate))) + .from(errorLogEntity) + .leftJoin(auditLogEntity) + .on(errorLogEntity.id.eq(auditLogEntity.errorLogUid)) + .leftJoin(menuEntity) + .on(auditLogEntity.menuUid.eq(menuEntity.menuUid)) + .leftJoin(memberEntity) + .on(errorLogEntity.handlerUid.eq(memberEntity.id)) + .where( + eventStatusEqFailed(), + eventEndedAtBetween(searchReq.getStartDate(), searchReq.getEndDate()), + eventTypeEq(searchReq.getEventType()), + errorLevelEq(searchReq.getErrorLevel())) + .offset(pageable.getOffset()) + .limit(pageable.getPageSize()) + .orderBy(errorLogEntity.createdDate.desc()) + .fetch(); + + Long countQuery = + queryFactory + .select(errorLogEntity.id.countDistinct()) + .from(errorLogEntity) + .leftJoin(auditLogEntity) + .on(errorLogEntity.id.eq(auditLogEntity.errorLogUid)) + .leftJoin(menuEntity) + .on(auditLogEntity.menuUid.eq(menuEntity.menuUid)) + .leftJoin(memberEntity) + .on(errorLogEntity.handlerUid.eq(memberEntity.id)) + .where( + eventStatusEqFailed(), + eventEndedAtBetween(searchReq.getStartDate(), searchReq.getEndDate()), + eventTypeEq(searchReq.getEventType()), + errorLevelEq(searchReq.getErrorLevel())) + .fetchOne(); + + return new PageImpl<>(foundContent, pageable, countQuery); + } + + private BooleanExpression eventEndedAtBetween(LocalDate startDate, LocalDate endDate) { + if (Objects.isNull(startDate) || Objects.isNull(endDate)) { + return null; + } + LocalDateTime startDateTime = startDate.atStartOfDay(); + LocalDateTime endDateTime = endDate.plusDays(1).atStartOfDay(); + return auditLogEntity + .createdDate + .goe(ZonedDateTime.from(startDateTime)) + .and(auditLogEntity.createdDate.lt(ZonedDateTime.from(endDateTime))); + } + + private BooleanExpression eventStatusEqFailed() { + return auditLogEntity.eventStatus.eq(EventStatus.FAILED); + } + + private BooleanExpression eventTypeEq(EventType eventType) { + if (Objects.isNull(eventType)) { + return null; + } + return auditLogEntity.eventType.eq(eventType); + } + + private BooleanExpression errorLevelEq(ErrorLogDto.LogErrorLevel level) { + if (Objects.isNull(level)) { + return null; + } + return errorLogEntity.errorLevel.eq(ErrorLogDto.LogErrorLevel.valueOf(level.name())); + } +} diff --git a/src/main/java/com/kamco/cd/training/postgres/repository/members/AuthRefreshTokenImpl.java b/src/main/java/com/kamco/cd/training/postgres/repository/members/AuthRefreshTokenImpl.java index a10aa18..069d6a6 100644 --- a/src/main/java/com/kamco/cd/training/postgres/repository/members/AuthRefreshTokenImpl.java +++ b/src/main/java/com/kamco/cd/training/postgres/repository/members/AuthRefreshTokenImpl.java @@ -1,50 +1,50 @@ -package com.kamco.cd.training.postgres.repository.members; - -import com.kamco.cd.training.postgres.entity.AuthRefreshTokenEntity; -import com.kamco.cd.training.postgres.entity.QAuthRefreshTokenEntity; -import com.querydsl.jpa.impl.JPAQueryFactory; -import java.time.ZonedDateTime; -import java.util.Optional; -import lombok.RequiredArgsConstructor; -import org.springframework.stereotype.Repository; - -@Repository -@RequiredArgsConstructor -public class AuthRefreshTokenImpl implements AuthRefreshTokenRepositoryCustom { - - private final JPAQueryFactory queryFactory; - private final QAuthRefreshTokenEntity tokenEntity = - QAuthRefreshTokenEntity.authRefreshTokenEntity; - - /** - * subject로 조회 - * - * @param subject 사용자 식별(UUID) - * @return - */ - @Override - public Optional findBySubject(String subject) { - return Optional.ofNullable( - queryFactory.selectFrom(tokenEntity).where(tokenEntity.subject.eq(subject)).fetchOne()); - } - - /** - * 토큰 정보 조회 - * - * @param subject - * @param now - * @return - */ - @Override - public Optional findBySubjectAndRevokedDttmIsNullAndExpiresDttmAfter( - String subject, ZonedDateTime now) { - return Optional.ofNullable( - queryFactory - .selectFrom(tokenEntity) - .where( - tokenEntity.subject.eq(subject), - tokenEntity.revokedDttm.isNull(), - tokenEntity.expiresDttm.gt(now)) - .fetchOne()); - } -} +package com.kamco.cd.training.postgres.repository.members; + +import com.kamco.cd.training.postgres.entity.AuthRefreshTokenEntity; +import com.kamco.cd.training.postgres.entity.QAuthRefreshTokenEntity; +import com.querydsl.jpa.impl.JPAQueryFactory; +import java.time.ZonedDateTime; +import java.util.Optional; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Repository; + +@Repository +@RequiredArgsConstructor +public class AuthRefreshTokenImpl implements AuthRefreshTokenRepositoryCustom { + + private final JPAQueryFactory queryFactory; + private final QAuthRefreshTokenEntity tokenEntity = + QAuthRefreshTokenEntity.authRefreshTokenEntity; + + /** + * subject로 조회 + * + * @param subject 사용자 식별(UUID) + * @return + */ + @Override + public Optional findBySubject(String subject) { + return Optional.ofNullable( + queryFactory.selectFrom(tokenEntity).where(tokenEntity.subject.eq(subject)).fetchOne()); + } + + /** + * 토큰 정보 조회 + * + * @param subject + * @param now + * @return + */ + @Override + public Optional findBySubjectAndRevokedDttmIsNullAndExpiresDttmAfter( + String subject, ZonedDateTime now) { + return Optional.ofNullable( + queryFactory + .selectFrom(tokenEntity) + .where( + tokenEntity.subject.eq(subject), + tokenEntity.revokedDttm.isNull(), + tokenEntity.expiresDttm.gt(now)) + .fetchOne()); + } +} diff --git a/src/main/java/com/kamco/cd/training/postgres/repository/members/AuthRefreshTokenRepository.java b/src/main/java/com/kamco/cd/training/postgres/repository/members/AuthRefreshTokenRepository.java index 97e0023..e931094 100644 --- a/src/main/java/com/kamco/cd/training/postgres/repository/members/AuthRefreshTokenRepository.java +++ b/src/main/java/com/kamco/cd/training/postgres/repository/members/AuthRefreshTokenRepository.java @@ -1,7 +1,7 @@ -package com.kamco.cd.training.postgres.repository.members; - -import com.kamco.cd.training.postgres.entity.AuthRefreshTokenEntity; -import org.springframework.data.jpa.repository.JpaRepository; - -public interface AuthRefreshTokenRepository - extends JpaRepository, AuthRefreshTokenRepositoryCustom {} +package com.kamco.cd.training.postgres.repository.members; + +import com.kamco.cd.training.postgres.entity.AuthRefreshTokenEntity; +import org.springframework.data.jpa.repository.JpaRepository; + +public interface AuthRefreshTokenRepository + extends JpaRepository, AuthRefreshTokenRepositoryCustom {} diff --git a/src/main/java/com/kamco/cd/training/postgres/repository/members/AuthRefreshTokenRepositoryCustom.java b/src/main/java/com/kamco/cd/training/postgres/repository/members/AuthRefreshTokenRepositoryCustom.java index 7e59917..e1e1a3c 100644 --- a/src/main/java/com/kamco/cd/training/postgres/repository/members/AuthRefreshTokenRepositoryCustom.java +++ b/src/main/java/com/kamco/cd/training/postgres/repository/members/AuthRefreshTokenRepositoryCustom.java @@ -1,12 +1,12 @@ -package com.kamco.cd.training.postgres.repository.members; - -import com.kamco.cd.training.postgres.entity.AuthRefreshTokenEntity; -import java.time.ZonedDateTime; -import java.util.Optional; - -public interface AuthRefreshTokenRepositoryCustom { - Optional findBySubject(String subject); - - Optional findBySubjectAndRevokedDttmIsNullAndExpiresDttmAfter( - String subject, ZonedDateTime now); -} +package com.kamco.cd.training.postgres.repository.members; + +import com.kamco.cd.training.postgres.entity.AuthRefreshTokenEntity; +import java.time.ZonedDateTime; +import java.util.Optional; + +public interface AuthRefreshTokenRepositoryCustom { + Optional findBySubject(String subject); + + Optional findBySubjectAndRevokedDttmIsNullAndExpiresDttmAfter( + String subject, ZonedDateTime now); +} diff --git a/src/main/java/com/kamco/cd/training/postgres/repository/members/MembersRepository.java b/src/main/java/com/kamco/cd/training/postgres/repository/members/MembersRepository.java index 8595541..42d7302 100644 --- a/src/main/java/com/kamco/cd/training/postgres/repository/members/MembersRepository.java +++ b/src/main/java/com/kamco/cd/training/postgres/repository/members/MembersRepository.java @@ -1,7 +1,7 @@ -package com.kamco.cd.training.postgres.repository.members; - -import com.kamco.cd.training.postgres.entity.MemberEntity; -import org.springframework.data.jpa.repository.JpaRepository; - -public interface MembersRepository - extends JpaRepository, MembersRepositoryCustom {} +package com.kamco.cd.training.postgres.repository.members; + +import com.kamco.cd.training.postgres.entity.MemberEntity; +import org.springframework.data.jpa.repository.JpaRepository; + +public interface MembersRepository + extends JpaRepository, MembersRepositoryCustom {} diff --git a/src/main/java/com/kamco/cd/training/postgres/repository/members/MembersRepositoryCustom.java b/src/main/java/com/kamco/cd/training/postgres/repository/members/MembersRepositoryCustom.java index bdeac1e..66dfa83 100644 --- a/src/main/java/com/kamco/cd/training/postgres/repository/members/MembersRepositoryCustom.java +++ b/src/main/java/com/kamco/cd/training/postgres/repository/members/MembersRepositoryCustom.java @@ -1,21 +1,21 @@ -package com.kamco.cd.training.postgres.repository.members; - -import com.kamco.cd.training.members.dto.MembersDto; -import com.kamco.cd.training.postgres.entity.MemberEntity; -import java.util.Optional; -import java.util.UUID; -import org.springframework.data.domain.Page; - -public interface MembersRepositoryCustom { - boolean existsByUserId(String userId); - - boolean existsByEmployeeNo(String employeeNo); - - Optional findByEmployeeNo(String employeeNo); - - Optional findByUserId(String userId); - - Optional findByUUID(UUID uuid); - - Page findByMembers(MembersDto.SearchReq searchReq); -} +package com.kamco.cd.training.postgres.repository.members; + +import com.kamco.cd.training.members.dto.MembersDto; +import com.kamco.cd.training.postgres.entity.MemberEntity; +import java.util.Optional; +import java.util.UUID; +import org.springframework.data.domain.Page; + +public interface MembersRepositoryCustom { + boolean existsByUserId(String userId); + + boolean existsByEmployeeNo(String employeeNo); + + Optional findByEmployeeNo(String employeeNo); + + Optional findByUserId(String userId); + + Optional findByUUID(UUID uuid); + + Page findByMembers(MembersDto.SearchReq searchReq); +} diff --git a/src/main/java/com/kamco/cd/training/postgres/repository/members/MembersRepositoryImpl.java b/src/main/java/com/kamco/cd/training/postgres/repository/members/MembersRepositoryImpl.java index cffeb48..f3420c6 100644 --- a/src/main/java/com/kamco/cd/training/postgres/repository/members/MembersRepositoryImpl.java +++ b/src/main/java/com/kamco/cd/training/postgres/repository/members/MembersRepositoryImpl.java @@ -1,151 +1,151 @@ -package com.kamco.cd.training.postgres.repository.members; - -import com.kamco.cd.training.members.dto.MembersDto; -import com.kamco.cd.training.postgres.entity.MemberEntity; -import com.kamco.cd.training.postgres.entity.QMemberEntity; -import com.querydsl.core.BooleanBuilder; -import com.querydsl.jpa.impl.JPAQueryFactory; -import java.util.List; -import java.util.Optional; -import java.util.UUID; -import org.apache.commons.lang3.StringUtils; -import org.springframework.data.domain.Page; -import org.springframework.data.domain.PageImpl; -import org.springframework.data.domain.Pageable; -import org.springframework.data.jpa.repository.support.QuerydslRepositorySupport; -import org.springframework.stereotype.Repository; - -@Repository -public class MembersRepositoryImpl extends QuerydslRepositorySupport - implements MembersRepositoryCustom { - - private final JPAQueryFactory queryFactory; - private final QMemberEntity memberEntity = QMemberEntity.memberEntity; - - public MembersRepositoryImpl(JPAQueryFactory queryFactory) { - super(MemberEntity.class); - this.queryFactory = queryFactory; - } - - /** - * 사용자 ID 조회 - * - * @param userId - * @return - */ - @Override - public boolean existsByUserId(String userId) { - return queryFactory - .selectOne() - .from(memberEntity) - .where(memberEntity.userId.eq(userId)) - .fetchFirst() - != null; - } - - /** - * 사용자 사번 조회 - * - * @param employeeNo - * @return - */ - @Override - public boolean existsByEmployeeNo(String employeeNo) { - return queryFactory - .selectOne() - .from(memberEntity) - .where(memberEntity.employeeNo.eq(employeeNo)) - .fetchFirst() - != null; - } - - /** - * 사용자 조회 user id - * - * @param userId - * @return - */ - @Override - public Optional findByUserId(String userId) { - return Optional.ofNullable( - queryFactory.selectFrom(memberEntity).where(memberEntity.userId.eq(userId)).fetchOne()); - } - - /** - * 사용자 조회 employeed no - * - * @param employeeNo - * @return - */ - @Override - public Optional findByEmployeeNo(String employeeNo) { - return Optional.ofNullable( - queryFactory - .selectFrom(memberEntity) - .where(memberEntity.employeeNo.eq(employeeNo)) - .fetchOne()); - } - - /** - * 회원정보 목록 조회 - * - * @param searchReq - * @return - */ - @Override - public Page findByMembers(MembersDto.SearchReq searchReq) { - Pageable pageable = searchReq.toPageable(); - BooleanBuilder builder = new BooleanBuilder(); - - // 검색어 - if (StringUtils.isNotBlank(searchReq.getKeyword())) { - String contains = "%" + searchReq.getKeyword() + "%"; - - builder.and( - memberEntity - .name - .likeIgnoreCase(contains) - .or(memberEntity.userId.likeIgnoreCase(contains)) - .or(memberEntity.employeeNo.likeIgnoreCase(contains))); - } - - // 권한 - if (StringUtils.isNotBlank(searchReq.getUserRole())) { - builder.and(memberEntity.userRole.eq(searchReq.getUserRole())); - } - - // Entity 직접 조회 (Projections 사용 지양) - List content = - queryFactory - .selectFrom(memberEntity) - .where(builder) - .offset(pageable.getOffset()) - .limit(pageable.getPageSize()) - .orderBy(memberEntity.createdDttm.desc()) - .fetch(); - - // Count 쿼리 별도 실행 (null safe handling) - long total = - Optional.ofNullable( - queryFactory - .select(memberEntity.count()) - .from(memberEntity) - .where(builder) - .fetchOne()) - .orElse(0L); - - return new PageImpl<>(content, pageable, total); - } - - /** - * 사용자 ID 조회 UUID - * - * @param uuid - * @return - */ - @Override - public Optional findByUUID(UUID uuid) { - return Optional.ofNullable( - queryFactory.selectFrom(memberEntity).where(memberEntity.uuid.eq(uuid)).fetchOne()); - } -} +package com.kamco.cd.training.postgres.repository.members; + +import com.kamco.cd.training.members.dto.MembersDto; +import com.kamco.cd.training.postgres.entity.MemberEntity; +import com.kamco.cd.training.postgres.entity.QMemberEntity; +import com.querydsl.core.BooleanBuilder; +import com.querydsl.jpa.impl.JPAQueryFactory; +import java.util.List; +import java.util.Optional; +import java.util.UUID; +import org.apache.commons.lang3.StringUtils; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageImpl; +import org.springframework.data.domain.Pageable; +import org.springframework.data.jpa.repository.support.QuerydslRepositorySupport; +import org.springframework.stereotype.Repository; + +@Repository +public class MembersRepositoryImpl extends QuerydslRepositorySupport + implements MembersRepositoryCustom { + + private final JPAQueryFactory queryFactory; + private final QMemberEntity memberEntity = QMemberEntity.memberEntity; + + public MembersRepositoryImpl(JPAQueryFactory queryFactory) { + super(MemberEntity.class); + this.queryFactory = queryFactory; + } + + /** + * 사용자 ID 조회 + * + * @param userId + * @return + */ + @Override + public boolean existsByUserId(String userId) { + return queryFactory + .selectOne() + .from(memberEntity) + .where(memberEntity.userId.eq(userId)) + .fetchFirst() + != null; + } + + /** + * 사용자 사번 조회 + * + * @param employeeNo + * @return + */ + @Override + public boolean existsByEmployeeNo(String employeeNo) { + return queryFactory + .selectOne() + .from(memberEntity) + .where(memberEntity.employeeNo.eq(employeeNo)) + .fetchFirst() + != null; + } + + /** + * 사용자 조회 user id + * + * @param userId + * @return + */ + @Override + public Optional findByUserId(String userId) { + return Optional.ofNullable( + queryFactory.selectFrom(memberEntity).where(memberEntity.userId.eq(userId)).fetchOne()); + } + + /** + * 사용자 조회 employeed no + * + * @param employeeNo + * @return + */ + @Override + public Optional findByEmployeeNo(String employeeNo) { + return Optional.ofNullable( + queryFactory + .selectFrom(memberEntity) + .where(memberEntity.employeeNo.eq(employeeNo)) + .fetchOne()); + } + + /** + * 회원정보 목록 조회 + * + * @param searchReq + * @return + */ + @Override + public Page findByMembers(MembersDto.SearchReq searchReq) { + Pageable pageable = searchReq.toPageable(); + BooleanBuilder builder = new BooleanBuilder(); + + // 검색어 + if (StringUtils.isNotBlank(searchReq.getKeyword())) { + String contains = "%" + searchReq.getKeyword() + "%"; + + builder.and( + memberEntity + .name + .likeIgnoreCase(contains) + .or(memberEntity.userId.likeIgnoreCase(contains)) + .or(memberEntity.employeeNo.likeIgnoreCase(contains))); + } + + // 권한 + if (StringUtils.isNotBlank(searchReq.getUserRole())) { + builder.and(memberEntity.userRole.eq(searchReq.getUserRole())); + } + + // Entity 직접 조회 (Projections 사용 지양) + List content = + queryFactory + .selectFrom(memberEntity) + .where(builder) + .offset(pageable.getOffset()) + .limit(pageable.getPageSize()) + .orderBy(memberEntity.createdDttm.desc()) + .fetch(); + + // Count 쿼리 별도 실행 (null safe handling) + long total = + Optional.ofNullable( + queryFactory + .select(memberEntity.count()) + .from(memberEntity) + .where(builder) + .fetchOne()) + .orElse(0L); + + return new PageImpl<>(content, pageable, total); + } + + /** + * 사용자 ID 조회 UUID + * + * @param uuid + * @return + */ + @Override + public Optional findByUUID(UUID uuid) { + return Optional.ofNullable( + queryFactory.selectFrom(memberEntity).where(memberEntity.uuid.eq(uuid)).fetchOne()); + } +} diff --git a/src/main/java/com/kamco/cd/training/postgres/repository/menu/MenuRepository.java b/src/main/java/com/kamco/cd/training/postgres/repository/menu/MenuRepository.java index 7d67fa9..c83d2f0 100644 --- a/src/main/java/com/kamco/cd/training/postgres/repository/menu/MenuRepository.java +++ b/src/main/java/com/kamco/cd/training/postgres/repository/menu/MenuRepository.java @@ -1,6 +1,6 @@ -package com.kamco.cd.training.postgres.repository.menu; - -import com.kamco.cd.training.postgres.entity.MenuEntity; -import org.springframework.data.jpa.repository.JpaRepository; - -public interface MenuRepository extends JpaRepository, MenuRepositoryCustom {} +package com.kamco.cd.training.postgres.repository.menu; + +import com.kamco.cd.training.postgres.entity.MenuEntity; +import org.springframework.data.jpa.repository.JpaRepository; + +public interface MenuRepository extends JpaRepository, MenuRepositoryCustom {} diff --git a/src/main/java/com/kamco/cd/training/postgres/repository/menu/MenuRepositoryCustom.java b/src/main/java/com/kamco/cd/training/postgres/repository/menu/MenuRepositoryCustom.java index 60db0be..c94b233 100644 --- a/src/main/java/com/kamco/cd/training/postgres/repository/menu/MenuRepositoryCustom.java +++ b/src/main/java/com/kamco/cd/training/postgres/repository/menu/MenuRepositoryCustom.java @@ -1,9 +1,9 @@ -package com.kamco.cd.training.postgres.repository.menu; - -import com.kamco.cd.training.postgres.entity.MenuEntity; -import java.util.List; - -public interface MenuRepositoryCustom { - - List getFindAll(); -} +package com.kamco.cd.training.postgres.repository.menu; + +import com.kamco.cd.training.postgres.entity.MenuEntity; +import java.util.List; + +public interface MenuRepositoryCustom { + + List getFindAll(); +} diff --git a/src/main/java/com/kamco/cd/training/postgres/repository/menu/MenuRepositoryImpl.java b/src/main/java/com/kamco/cd/training/postgres/repository/menu/MenuRepositoryImpl.java index be5adc2..5c97d09 100644 --- a/src/main/java/com/kamco/cd/training/postgres/repository/menu/MenuRepositoryImpl.java +++ b/src/main/java/com/kamco/cd/training/postgres/repository/menu/MenuRepositoryImpl.java @@ -1,21 +1,21 @@ -package com.kamco.cd.training.postgres.repository.menu; - -import static com.kamco.cd.training.postgres.entity.QMenuEntity.menuEntity; - -import com.kamco.cd.training.postgres.entity.MenuEntity; -import com.querydsl.jpa.impl.JPAQueryFactory; -import java.util.List; -import lombok.RequiredArgsConstructor; -import org.springframework.stereotype.Repository; - -@Repository -@RequiredArgsConstructor -public class MenuRepositoryImpl implements MenuRepositoryCustom { - - private final JPAQueryFactory queryFactory; - - @Override - public List getFindAll() { - return queryFactory.selectFrom(menuEntity).where(menuEntity.deleted.isFalse()).fetch(); - } -} +package com.kamco.cd.training.postgres.repository.menu; + +import static com.kamco.cd.training.postgres.entity.QMenuEntity.menuEntity; + +import com.kamco.cd.training.postgres.entity.MenuEntity; +import com.querydsl.jpa.impl.JPAQueryFactory; +import java.util.List; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Repository; + +@Repository +@RequiredArgsConstructor +public class MenuRepositoryImpl implements MenuRepositoryCustom { + + private final JPAQueryFactory queryFactory; + + @Override + public List getFindAll() { + return queryFactory.selectFrom(menuEntity).where(menuEntity.deleted.isFalse()).fetch(); + } +} diff --git a/src/main/java/com/kamco/cd/training/postgres/repository/model/ModelDatasetMappRepository.java b/src/main/java/com/kamco/cd/training/postgres/repository/model/ModelDatasetMappRepository.java index ed30002..bfc9dc6 100644 --- a/src/main/java/com/kamco/cd/training/postgres/repository/model/ModelDatasetMappRepository.java +++ b/src/main/java/com/kamco/cd/training/postgres/repository/model/ModelDatasetMappRepository.java @@ -1,9 +1,9 @@ -package com.kamco.cd.training.postgres.repository.model; - -import com.kamco.cd.training.postgres.entity.ModelDatasetMappEntity; -import org.springframework.data.jpa.repository.JpaRepository; -import org.springframework.stereotype.Repository; - -@Repository -public interface ModelDatasetMappRepository - extends JpaRepository {} +package com.kamco.cd.training.postgres.repository.model; + +import com.kamco.cd.training.postgres.entity.ModelDatasetMappEntity; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; + +@Repository +public interface ModelDatasetMappRepository + extends JpaRepository {} diff --git a/src/main/java/com/kamco/cd/training/postgres/repository/model/ModelHyperParamRepository.java b/src/main/java/com/kamco/cd/training/postgres/repository/model/ModelHyperParamRepository.java index a9a92f7..32fc47b 100644 --- a/src/main/java/com/kamco/cd/training/postgres/repository/model/ModelHyperParamRepository.java +++ b/src/main/java/com/kamco/cd/training/postgres/repository/model/ModelHyperParamRepository.java @@ -1,14 +1,14 @@ -package com.kamco.cd.training.postgres.repository.model; - -import com.kamco.cd.training.postgres.entity.ModelHyperParamEntity; -import java.util.List; -import org.springframework.data.jpa.repository.JpaRepository; -import org.springframework.stereotype.Repository; - -@Repository -public interface ModelHyperParamRepository extends JpaRepository { - - List findByDelYnOrderByCreatedDttmDesc(String delYn); - - List findByDelYnOrderByCreatedDttmAsc(String delYn); -} +package com.kamco.cd.training.postgres.repository.model; + +import com.kamco.cd.training.postgres.entity.ModelHyperParamEntity; +import java.util.List; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; + +@Repository +public interface ModelHyperParamRepository extends JpaRepository { + + List findByDelYnOrderByCreatedDttmDesc(String delYn); + + List findByDelYnOrderByCreatedDttmAsc(String delYn); +} diff --git a/src/main/java/com/kamco/cd/training/postgres/repository/model/ModelMngRepository.java b/src/main/java/com/kamco/cd/training/postgres/repository/model/ModelMngRepository.java index d0aefdd..6da5cae 100644 --- a/src/main/java/com/kamco/cd/training/postgres/repository/model/ModelMngRepository.java +++ b/src/main/java/com/kamco/cd/training/postgres/repository/model/ModelMngRepository.java @@ -1,17 +1,17 @@ -package com.kamco.cd.training.postgres.repository.model; - -import com.kamco.cd.training.postgres.entity.ModelTrainMasterEntity; -import java.util.List; -import java.util.Optional; -import java.util.UUID; -import org.springframework.data.jpa.repository.JpaRepository; - -public interface ModelMngRepository - extends JpaRepository, ModelMngRepositoryCustom { - - List findByDelYnOrderByCreatedDttmDesc(Boolean delYn); - - Optional findFirstByStatusCdAndDelYn(String statusCd, Boolean delYn); - - Optional findByUuid(UUID uuid); -} +package com.kamco.cd.training.postgres.repository.model; + +import com.kamco.cd.training.postgres.entity.ModelTrainMasterEntity; +import java.util.List; +import java.util.Optional; +import java.util.UUID; +import org.springframework.data.jpa.repository.JpaRepository; + +public interface ModelMngRepository + extends JpaRepository, ModelMngRepositoryCustom { + + List findByDelYnOrderByCreatedDttmDesc(Boolean delYn); + + Optional findFirstByStatusCdAndDelYn(String statusCd, Boolean delYn); + + Optional findByUuid(UUID uuid); +} diff --git a/src/main/java/com/kamco/cd/training/postgres/repository/model/ModelMngRepositoryCustom.java b/src/main/java/com/kamco/cd/training/postgres/repository/model/ModelMngRepositoryCustom.java index 75bed7c..7b15582 100644 --- a/src/main/java/com/kamco/cd/training/postgres/repository/model/ModelMngRepositoryCustom.java +++ b/src/main/java/com/kamco/cd/training/postgres/repository/model/ModelMngRepositoryCustom.java @@ -1,16 +1,16 @@ -package com.kamco.cd.training.postgres.repository.model; - -import com.kamco.cd.training.model.dto.ModelMngDto; -import com.kamco.cd.training.postgres.entity.ModelTrainMasterEntity; -import org.springframework.data.domain.Page; - -public interface ModelMngRepositoryCustom { - - /** - * 모델 목록 조회 - * - * @param searchReq - * @return - */ - Page findByModels(ModelMngDto.SearchReq searchReq); -} +package com.kamco.cd.training.postgres.repository.model; + +import com.kamco.cd.training.model.dto.ModelMngDto; +import com.kamco.cd.training.postgres.entity.ModelTrainMasterEntity; +import org.springframework.data.domain.Page; + +public interface ModelMngRepositoryCustom { + + /** + * 모델 목록 조회 + * + * @param searchReq + * @return + */ + Page findByModels(ModelMngDto.SearchReq searchReq); +} diff --git a/src/main/java/com/kamco/cd/training/postgres/repository/model/ModelMngRepositoryImpl.java b/src/main/java/com/kamco/cd/training/postgres/repository/model/ModelMngRepositoryImpl.java index 2cd8e7c..cf3b6c0 100644 --- a/src/main/java/com/kamco/cd/training/postgres/repository/model/ModelMngRepositoryImpl.java +++ b/src/main/java/com/kamco/cd/training/postgres/repository/model/ModelMngRepositoryImpl.java @@ -1,61 +1,61 @@ -package com.kamco.cd.training.postgres.repository.model; - -import com.kamco.cd.training.model.dto.ModelMngDto; -import com.kamco.cd.training.postgres.entity.ModelTrainMasterEntity; -import com.kamco.cd.training.postgres.entity.QModelTrainMasterEntity; -import com.querydsl.core.BooleanBuilder; -import com.querydsl.jpa.impl.JPAQueryFactory; -import java.util.List; -import java.util.Optional; -import lombok.RequiredArgsConstructor; -import org.apache.commons.lang3.StringUtils; -import org.springframework.data.domain.Page; -import org.springframework.data.domain.PageImpl; -import org.springframework.data.domain.Pageable; -import org.springframework.stereotype.Repository; - -@Repository -@RequiredArgsConstructor -public class ModelMngRepositoryImpl implements ModelMngRepositoryCustom { - - private final JPAQueryFactory queryFactory; - private final QModelTrainMasterEntity modelMng = QModelTrainMasterEntity.modelTrainMasterEntity; - - /** - * 모델 목록 조회 - * - * @param searchReq - * @return - */ - @Override - public Page findByModels(ModelMngDto.SearchReq searchReq) { - Pageable pageable = searchReq.toPageable(); - BooleanBuilder builder = new BooleanBuilder(); - - if (StringUtils.isNotBlank(searchReq.getStatus())) { - builder.and(modelMng.statusCd.eq(searchReq.getStatus())); - } - - // Entity 직접 조회 (Projections 사용 지양) - List content = - queryFactory - .selectFrom(modelMng) - .where(builder.and(modelMng.delYn.isFalse())) - .offset(pageable.getOffset()) - .limit(pageable.getPageSize()) - .orderBy(modelMng.createdDttm.desc()) - .fetch(); - - // Count 쿼리 별도 실행 (null safe handling) - long total = - Optional.ofNullable( - queryFactory - .select(modelMng.count()) - .from(modelMng) - .where(builder.and(modelMng.delYn.isFalse())) - .fetchOne()) - .orElse(0L); - - return new PageImpl<>(content, pageable, total); - } -} +package com.kamco.cd.training.postgres.repository.model; + +import com.kamco.cd.training.model.dto.ModelMngDto; +import com.kamco.cd.training.postgres.entity.ModelTrainMasterEntity; +import com.kamco.cd.training.postgres.entity.QModelTrainMasterEntity; +import com.querydsl.core.BooleanBuilder; +import com.querydsl.jpa.impl.JPAQueryFactory; +import java.util.List; +import java.util.Optional; +import lombok.RequiredArgsConstructor; +import org.apache.commons.lang3.StringUtils; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageImpl; +import org.springframework.data.domain.Pageable; +import org.springframework.stereotype.Repository; + +@Repository +@RequiredArgsConstructor +public class ModelMngRepositoryImpl implements ModelMngRepositoryCustom { + + private final JPAQueryFactory queryFactory; + private final QModelTrainMasterEntity modelMng = QModelTrainMasterEntity.modelTrainMasterEntity; + + /** + * 모델 목록 조회 + * + * @param searchReq + * @return + */ + @Override + public Page findByModels(ModelMngDto.SearchReq searchReq) { + Pageable pageable = searchReq.toPageable(); + BooleanBuilder builder = new BooleanBuilder(); + + if (StringUtils.isNotBlank(searchReq.getStatus())) { + builder.and(modelMng.statusCd.eq(searchReq.getStatus())); + } + + // Entity 직접 조회 (Projections 사용 지양) + List content = + queryFactory + .selectFrom(modelMng) + .where(builder.and(modelMng.delYn.isFalse())) + .offset(pageable.getOffset()) + .limit(pageable.getPageSize()) + .orderBy(modelMng.createdDttm.desc()) + .fetch(); + + // Count 쿼리 별도 실행 (null safe handling) + long total = + Optional.ofNullable( + queryFactory + .select(modelMng.count()) + .from(modelMng) + .where(builder.and(modelMng.delYn.isFalse())) + .fetchOne()) + .orElse(0L); + + return new PageImpl<>(content, pageable, total); + } +} diff --git a/src/main/java/com/kamco/cd/training/postgres/repository/upload/UploadSessionRepository.java b/src/main/java/com/kamco/cd/training/postgres/repository/upload/UploadSessionRepository.java index cd8196f..836eafb 100644 --- a/src/main/java/com/kamco/cd/training/postgres/repository/upload/UploadSessionRepository.java +++ b/src/main/java/com/kamco/cd/training/postgres/repository/upload/UploadSessionRepository.java @@ -1,7 +1,7 @@ -package com.kamco.cd.training.postgres.repository.upload; - -import com.kamco.cd.training.postgres.entity.UploadSessionEntity; -import org.springframework.data.jpa.repository.JpaRepository; - -public interface UploadSessionRepository - extends JpaRepository, UploadSessionRepositoryCustom {} +package com.kamco.cd.training.postgres.repository.upload; + +import com.kamco.cd.training.postgres.entity.UploadSessionEntity; +import org.springframework.data.jpa.repository.JpaRepository; + +public interface UploadSessionRepository + extends JpaRepository, UploadSessionRepositoryCustom {} diff --git a/src/main/java/com/kamco/cd/training/postgres/repository/upload/UploadSessionRepositoryCustom.java b/src/main/java/com/kamco/cd/training/postgres/repository/upload/UploadSessionRepositoryCustom.java index 3b750a8..ccc1388 100644 --- a/src/main/java/com/kamco/cd/training/postgres/repository/upload/UploadSessionRepositoryCustom.java +++ b/src/main/java/com/kamco/cd/training/postgres/repository/upload/UploadSessionRepositoryCustom.java @@ -1,22 +1,14 @@ -package com.kamco.cd.training.postgres.repository.upload; - - - -import com.kamco.cd.training.upload.dto.UploadDto; -import jakarta.validation.Valid; -import java.util.List; -import java.util.Optional; -import org.springframework.data.domain.Page; - -public interface UploadSessionRepositoryCustom { - - - void insertUploadSession(UploadDto.UploadAddReq addReq); - - UploadDto.uploadDto findByDatasetUid(Long datasetId, String uploadDivi); - - UploadDto.uploadDto findByUuid(String uuid); - void updateUploadSessionStatus(UploadDto.UploadAddReq addReq); - - -} +package com.kamco.cd.training.postgres.repository.upload; + +import com.kamco.cd.training.upload.dto.UploadDto; + +public interface UploadSessionRepositoryCustom { + + void insertUploadSession(UploadDto.UploadAddReq addReq); + + UploadDto.uploadDto findByDatasetUid(Long datasetId, String uploadDivi); + + UploadDto.uploadDto findByUuid(String uuid); + + void updateUploadSessionStatus(UploadDto.UploadAddReq addReq); +} diff --git a/src/main/java/com/kamco/cd/training/postgres/repository/upload/UploadSessionRepositoryImpl.java b/src/main/java/com/kamco/cd/training/postgres/repository/upload/UploadSessionRepositoryImpl.java index 7461410..5314904 100644 --- a/src/main/java/com/kamco/cd/training/postgres/repository/upload/UploadSessionRepositoryImpl.java +++ b/src/main/java/com/kamco/cd/training/postgres/repository/upload/UploadSessionRepositoryImpl.java @@ -1,156 +1,132 @@ -package com.kamco.cd.training.postgres.repository.upload; - - -import static com.kamco.cd.training.postgres.entity.QUploadSessionEntity.uploadSessionEntity; - -import com.kamco.cd.training.postgres.entity.UploadSessionEntity; -import com.kamco.cd.training.upload.dto.UploadDto; -import com.querydsl.core.BooleanBuilder; -import com.querydsl.core.types.Projections; -import com.querydsl.core.types.dsl.BooleanExpression; -import com.querydsl.core.types.dsl.CaseBuilder; -import com.querydsl.core.types.dsl.Expressions; -import com.querydsl.core.types.dsl.NumberExpression; -import com.querydsl.core.types.dsl.StringExpression; -import com.querydsl.jpa.impl.JPAQueryFactory; -import jakarta.persistence.EntityManager; -import jakarta.persistence.PersistenceContext; -import jakarta.validation.Valid; -import java.time.ZonedDateTime; -import java.util.List; -import java.util.Objects; -import java.util.Optional; -import java.util.UUID; -import org.hibernate.query.Query; -import org.springframework.data.domain.Page; -import org.springframework.data.domain.PageImpl; -import org.springframework.data.domain.PageRequest; -import org.springframework.data.domain.Pageable; -import org.springframework.data.jpa.repository.support.QuerydslRepositorySupport; - -public class UploadSessionRepositoryImpl extends QuerydslRepositorySupport - implements UploadSessionRepositoryCustom { - - private final JPAQueryFactory queryFactory; - private final StringExpression NULL_STRING = Expressions.stringTemplate("cast(null as text)"); - - @PersistenceContext private EntityManager em; - - public UploadSessionRepositoryImpl(JPAQueryFactory queryFactory) { - super(UploadSessionEntity.class); - this.queryFactory = queryFactory; - } - - @Override - public void insertUploadSession(UploadDto.UploadAddReq addReq) { - long execCnt = - queryFactory - .insert(uploadSessionEntity) - .columns( - uploadSessionEntity.uploadId, - uploadSessionEntity.datasetId, - uploadSessionEntity.fileName, - uploadSessionEntity.fileSize, - uploadSessionEntity.finalPath, - uploadSessionEntity.status, - uploadSessionEntity.tempPath, - uploadSessionEntity.chunkIndex, - uploadSessionEntity.chunkTotalIndex, - uploadSessionEntity.uploadDivi, - uploadSessionEntity.fileHash, - uploadSessionEntity.uuid - ) - .values( - addReq.getUploadId(), - addReq.getDatasetId(), - addReq.getFileName(), - addReq.getFileSize(), - addReq.getFinalPath(), - addReq.getStatus(), - addReq.getTempPath(), - addReq.getChunkIndex(), - addReq.getChunkTotalIndex(), - addReq.getUploadDivi(), - addReq.getFileHash(), - addReq.getUuid() - ) - .execute(); - - - } - - - @Override - public UploadDto.uploadDto findByDatasetUid(Long datasetId, String uploadDivi) { - - UploadDto.uploadDto foundContent = - queryFactory - .select( - Projections.constructor( - UploadDto.uploadDto.class, - uploadSessionEntity.uploadId, - uploadSessionEntity.datasetId, - uploadSessionEntity.fileName, - uploadSessionEntity.fileSize, - uploadSessionEntity.finalPath, - uploadSessionEntity.uploadDivi, - uploadSessionEntity.status, - uploadSessionEntity.tempPath, - uploadSessionEntity.chunkIndex, - uploadSessionEntity.chunkTotalIndex, - uploadSessionEntity.fileHash, - uploadSessionEntity.uuid - )) - .from(uploadSessionEntity) - .where(uploadSessionEntity.datasetId.eq(datasetId) - .and(uploadSessionEntity.uploadDivi.eq(uploadDivi))) - .limit(1) - .fetchOne(); - - return foundContent; - - } - - - @Override - public UploadDto.uploadDto findByUuid(String uuid) { - - UploadDto.uploadDto foundContent = - queryFactory - .select( - Projections.constructor( - UploadDto.uploadDto.class, - uploadSessionEntity.uploadId, - uploadSessionEntity.datasetId, - uploadSessionEntity.fileName, - uploadSessionEntity.fileSize, - uploadSessionEntity.finalPath, - uploadSessionEntity.uploadDivi, - uploadSessionEntity.status, - uploadSessionEntity.tempPath, - uploadSessionEntity.chunkIndex, - uploadSessionEntity.chunkTotalIndex, - uploadSessionEntity.fileHash, - uploadSessionEntity.uuid - )) - .from(uploadSessionEntity) - .where(uploadSessionEntity.uuid.eq(UUID.fromString(uuid))) - .limit(1) - .fetchOne(); - - return foundContent; - - } - - - public void updateUploadSessionStatus(UploadDto.UploadAddReq addReq){ - long fileCount = - queryFactory - .update(uploadSessionEntity) - .set(uploadSessionEntity.chunkIndex, addReq.getChunkIndex()) - .set(uploadSessionEntity.status, addReq.getStatus()) - .where(uploadSessionEntity.uploadId.eq(addReq.getUploadId())) - .execute(); - } - -} +package com.kamco.cd.training.postgres.repository.upload; + +import static com.kamco.cd.training.postgres.entity.QUploadSessionEntity.uploadSessionEntity; + +import com.kamco.cd.training.postgres.entity.UploadSessionEntity; +import com.kamco.cd.training.upload.dto.UploadDto; +import com.querydsl.core.types.Projections; +import com.querydsl.core.types.dsl.Expressions; +import com.querydsl.core.types.dsl.StringExpression; +import com.querydsl.jpa.impl.JPAQueryFactory; +import jakarta.persistence.EntityManager; +import jakarta.persistence.PersistenceContext; +import java.util.UUID; +import org.springframework.data.jpa.repository.support.QuerydslRepositorySupport; + +public class UploadSessionRepositoryImpl extends QuerydslRepositorySupport + implements UploadSessionRepositoryCustom { + + private final JPAQueryFactory queryFactory; + private final StringExpression NULL_STRING = Expressions.stringTemplate("cast(null as text)"); + + @PersistenceContext private EntityManager em; + + public UploadSessionRepositoryImpl(JPAQueryFactory queryFactory) { + super(UploadSessionEntity.class); + this.queryFactory = queryFactory; + } + + @Override + public void insertUploadSession(UploadDto.UploadAddReq addReq) { + long execCnt = + queryFactory + .insert(uploadSessionEntity) + .columns( + uploadSessionEntity.uploadId, + uploadSessionEntity.datasetId, + uploadSessionEntity.fileName, + uploadSessionEntity.fileSize, + uploadSessionEntity.finalPath, + uploadSessionEntity.status, + uploadSessionEntity.tempPath, + uploadSessionEntity.chunkIndex, + uploadSessionEntity.chunkTotalIndex, + uploadSessionEntity.uploadDivi, + uploadSessionEntity.fileHash, + uploadSessionEntity.uuid) + .values( + addReq.getUploadId(), + addReq.getDatasetId(), + addReq.getFileName(), + addReq.getFileSize(), + addReq.getFinalPath(), + addReq.getStatus(), + addReq.getTempPath(), + addReq.getChunkIndex(), + addReq.getChunkTotalIndex(), + addReq.getUploadDivi(), + addReq.getFileHash(), + addReq.getUuid()) + .execute(); + } + + @Override + public UploadDto.uploadDto findByDatasetUid(Long datasetId, String uploadDivi) { + + UploadDto.uploadDto foundContent = + queryFactory + .select( + Projections.constructor( + UploadDto.uploadDto.class, + uploadSessionEntity.uploadId, + uploadSessionEntity.datasetId, + uploadSessionEntity.fileName, + uploadSessionEntity.fileSize, + uploadSessionEntity.finalPath, + uploadSessionEntity.uploadDivi, + uploadSessionEntity.status, + uploadSessionEntity.tempPath, + uploadSessionEntity.chunkIndex, + uploadSessionEntity.chunkTotalIndex, + uploadSessionEntity.fileHash, + uploadSessionEntity.uuid)) + .from(uploadSessionEntity) + .where( + uploadSessionEntity + .datasetId + .eq(datasetId) + .and(uploadSessionEntity.uploadDivi.eq(uploadDivi))) + .limit(1) + .fetchOne(); + + return foundContent; + } + + @Override + public UploadDto.uploadDto findByUuid(String uuid) { + + UploadDto.uploadDto foundContent = + queryFactory + .select( + Projections.constructor( + UploadDto.uploadDto.class, + uploadSessionEntity.uploadId, + uploadSessionEntity.datasetId, + uploadSessionEntity.fileName, + uploadSessionEntity.fileSize, + uploadSessionEntity.finalPath, + uploadSessionEntity.uploadDivi, + uploadSessionEntity.status, + uploadSessionEntity.tempPath, + uploadSessionEntity.chunkIndex, + uploadSessionEntity.chunkTotalIndex, + uploadSessionEntity.fileHash, + uploadSessionEntity.uuid)) + .from(uploadSessionEntity) + .where(uploadSessionEntity.uuid.eq(UUID.fromString(uuid))) + .limit(1) + .fetchOne(); + + return foundContent; + } + + public void updateUploadSessionStatus(UploadDto.UploadAddReq addReq) { + long fileCount = + queryFactory + .update(uploadSessionEntity) + .set(uploadSessionEntity.chunkIndex, addReq.getChunkIndex()) + .set(uploadSessionEntity.status, addReq.getStatus()) + .where(uploadSessionEntity.uploadId.eq(addReq.getUploadId())) + .execute(); + } +} diff --git a/src/main/java/com/kamco/cd/training/upload/UploadApiController.java b/src/main/java/com/kamco/cd/training/upload/UploadApiController.java index caa8ff4..86673c3 100644 --- a/src/main/java/com/kamco/cd/training/upload/UploadApiController.java +++ b/src/main/java/com/kamco/cd/training/upload/UploadApiController.java @@ -1,113 +1,111 @@ -package com.kamco.cd.training.upload; - -import com.kamco.cd.training.config.api.ApiResponseDto; -import com.kamco.cd.training.upload.dto.UploadDto; -import com.kamco.cd.training.upload.dto.UploadDto.DmlReturn; -import com.kamco.cd.training.upload.service.UploadService; -import io.swagger.v3.oas.annotations.Operation; -import io.swagger.v3.oas.annotations.media.Content; -import io.swagger.v3.oas.annotations.responses.ApiResponse; -import io.swagger.v3.oas.annotations.responses.ApiResponses; -import io.swagger.v3.oas.annotations.tags.Tag; -import lombok.RequiredArgsConstructor; -import org.springframework.http.MediaType; -import org.springframework.web.bind.annotation.*; -import org.springframework.web.multipart.MultipartFile; - -@Tag(name = "파일 업로드", description = "대용량 파일 업로드 API") -@RestController -@RequestMapping("/api/upload") -@RequiredArgsConstructor -public class UploadApiController { - - private final UploadService uploadService; - - /* - @Operation(summary = "데이터셋 대용량 업로드 세션 시작", description = "데이터셋 대용량 파일 업로드 세션을 시작합니다.") - @ApiResponses( - value = { - @ApiResponse( - responseCode = "201", - description = "세션 생성 성공", - content = - @Content( - mediaType = "application/json", - schema = @Schema(implementation = UploadDto.InitRes.class))), - @ApiResponse(responseCode = "400", description = "잘못된 요청 데이터", content = @Content), - @ApiResponse(responseCode = "500", description = "서버 오류", content = @Content) - }) - @PostMapping("/chunk-upload-init") - public ApiResponseDto initUpload( - @RequestBody @Valid UploadDto.InitReq initReq) { - return ApiResponseDto.createOK(uploadService.initUpload(initReq)); - } - */ - - @Operation(summary = "데이터셋 대용량 파일 분할 전송", description = "데이터셋 파일 대용량 파일을 청크 단위로 전송합니다.") - @ApiResponses( - value = { - @ApiResponse(responseCode = "200", description = "청크 업로드 성공", content = @Content), - @ApiResponse(responseCode = "400", description = "잘못된 요청 데이터", content = @Content), - @ApiResponse(responseCode = "404", description = "업로드 세션을 찾을 수 없음", content = @Content), - @ApiResponse(responseCode = "500", description = "서버 오류", content = @Content) - }) - @PostMapping(value = "/chunk-upload-dataset", consumes = MediaType.MULTIPART_FORM_DATA_VALUE) - public ApiResponseDto uploadChunkDataSetFile( - @RequestParam("datasetUid") long datasetUid, - @RequestParam("fileName") String fileName, - @RequestParam("fileSize") long fileSize, - // @RequestParam("fileHash") String fileHash, - @RequestParam("chunkIndex") Integer chunkIndex, - @RequestParam("chunkTotalIndex") Integer chunkTotalIndex, - @RequestPart("chunkFile") MultipartFile chunkFile) { - - String uploadDivi = "dataset"; - - UploadDto.UploadAddReq upAddReqDto = new UploadDto.UploadAddReq(); - upAddReqDto.setDatasetId(datasetUid); - upAddReqDto.setFileName(fileName); - upAddReqDto.setFileSize(fileSize); - upAddReqDto.setChunkIndex(chunkIndex); - upAddReqDto.setChunkTotalIndex(chunkTotalIndex); - upAddReqDto.setUploadDivi(uploadDivi); - // upAddReqDto.setFileHash(fileHash); - - return ApiResponseDto.ok(uploadService.uploadChunk(upAddReqDto, chunkFile)); - } - - - @Operation(summary = "업로드 완료된 파일 병합", description = "업로드 완료 및 파일 병합을 요청합니다.") - @ApiResponses( - value = { - @ApiResponse(responseCode = "200", description = "업로드 완료 성공", content = @Content), - @ApiResponse(responseCode = "400", description = "잘못된 요청", content = @Content), - @ApiResponse(responseCode = "404", description = "업로드 세션을 찾을 수 없음", content = @Content), - @ApiResponse(responseCode = "500", description = "서버 오류", content = @Content) - }) - @PutMapping("/chunk-upload-complete/{uuid}") - public ApiResponseDto completeUpload( - @PathVariable String uuid) { - return ApiResponseDto.ok(uploadService.completeUpload(uuid)); - } - - /* - @Operation(summary = "업로드 상태 조회", description = "업로드 진행 상태를 조회합니다.") - @ApiResponses( - value = { - @ApiResponse( - responseCode = "200", - description = "조회 성공", - content = - @Content( - mediaType = "application/json", - schema = @Schema(implementation = UploadDto.Status.class))), - @ApiResponse(responseCode = "404", description = "업로드 세션을 찾을 수 없음", content = @Content), - @ApiResponse(responseCode = "500", description = "서버 오류", content = @Content) - }) - @PostMapping("/status") - public ApiResponseDto getUploadStatus( - @RequestBody @Valid UploadDto.StatusReq statusReq) { - return ApiResponseDto.ok(uploadService.getUploadStatus(statusReq)); - } - */ -} +package com.kamco.cd.training.upload; + +import com.kamco.cd.training.config.api.ApiResponseDto; +import com.kamco.cd.training.upload.dto.UploadDto; +import com.kamco.cd.training.upload.dto.UploadDto.DmlReturn; +import com.kamco.cd.training.upload.service.UploadService; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.media.Content; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import io.swagger.v3.oas.annotations.responses.ApiResponses; +import io.swagger.v3.oas.annotations.tags.Tag; +import lombok.RequiredArgsConstructor; +import org.springframework.http.MediaType; +import org.springframework.web.bind.annotation.*; +import org.springframework.web.multipart.MultipartFile; + +@Tag(name = "파일 업로드", description = "대용량 파일 업로드 API") +@RestController +@RequestMapping("/api/upload") +@RequiredArgsConstructor +public class UploadApiController { + + private final UploadService uploadService; + + /* + @Operation(summary = "데이터셋 대용량 업로드 세션 시작", description = "데이터셋 대용량 파일 업로드 세션을 시작합니다.") + @ApiResponses( + value = { + @ApiResponse( + responseCode = "201", + description = "세션 생성 성공", + content = + @Content( + mediaType = "application/json", + schema = @Schema(implementation = UploadDto.InitRes.class))), + @ApiResponse(responseCode = "400", description = "잘못된 요청 데이터", content = @Content), + @ApiResponse(responseCode = "500", description = "서버 오류", content = @Content) + }) + @PostMapping("/chunk-upload-init") + public ApiResponseDto initUpload( + @RequestBody @Valid UploadDto.InitReq initReq) { + return ApiResponseDto.createOK(uploadService.initUpload(initReq)); + } + */ + + @Operation(summary = "데이터셋 대용량 파일 분할 전송", description = "데이터셋 파일 대용량 파일을 청크 단위로 전송합니다.") + @ApiResponses( + value = { + @ApiResponse(responseCode = "200", description = "청크 업로드 성공", content = @Content), + @ApiResponse(responseCode = "400", description = "잘못된 요청 데이터", content = @Content), + @ApiResponse(responseCode = "404", description = "업로드 세션을 찾을 수 없음", content = @Content), + @ApiResponse(responseCode = "500", description = "서버 오류", content = @Content) + }) + @PostMapping(value = "/chunk-upload-dataset", consumes = MediaType.MULTIPART_FORM_DATA_VALUE) + public ApiResponseDto uploadChunkDataSetFile( + @RequestParam("datasetUid") long datasetUid, + @RequestParam("fileName") String fileName, + @RequestParam("fileSize") long fileSize, + // @RequestParam("fileHash") String fileHash, + @RequestParam("chunkIndex") Integer chunkIndex, + @RequestParam("chunkTotalIndex") Integer chunkTotalIndex, + @RequestPart("chunkFile") MultipartFile chunkFile) { + + String uploadDivi = "dataset"; + + UploadDto.UploadAddReq upAddReqDto = new UploadDto.UploadAddReq(); + upAddReqDto.setDatasetId(datasetUid); + upAddReqDto.setFileName(fileName); + upAddReqDto.setFileSize(fileSize); + upAddReqDto.setChunkIndex(chunkIndex); + upAddReqDto.setChunkTotalIndex(chunkTotalIndex); + upAddReqDto.setUploadDivi(uploadDivi); + // upAddReqDto.setFileHash(fileHash); + + return ApiResponseDto.ok(uploadService.uploadChunk(upAddReqDto, chunkFile)); + } + + @Operation(summary = "업로드 완료된 파일 병합", description = "업로드 완료 및 파일 병합을 요청합니다.") + @ApiResponses( + value = { + @ApiResponse(responseCode = "200", description = "업로드 완료 성공", content = @Content), + @ApiResponse(responseCode = "400", description = "잘못된 요청", content = @Content), + @ApiResponse(responseCode = "404", description = "업로드 세션을 찾을 수 없음", content = @Content), + @ApiResponse(responseCode = "500", description = "서버 오류", content = @Content) + }) + @PutMapping("/chunk-upload-complete/{uuid}") + public ApiResponseDto completeUpload(@PathVariable String uuid) { + return ApiResponseDto.ok(uploadService.completeUpload(uuid)); + } + + /* + @Operation(summary = "업로드 상태 조회", description = "업로드 진행 상태를 조회합니다.") + @ApiResponses( + value = { + @ApiResponse( + responseCode = "200", + description = "조회 성공", + content = + @Content( + mediaType = "application/json", + schema = @Schema(implementation = UploadDto.Status.class))), + @ApiResponse(responseCode = "404", description = "업로드 세션을 찾을 수 없음", content = @Content), + @ApiResponse(responseCode = "500", description = "서버 오류", content = @Content) + }) + @PostMapping("/status") + public ApiResponseDto getUploadStatus( + @RequestBody @Valid UploadDto.StatusReq statusReq) { + return ApiResponseDto.ok(uploadService.getUploadStatus(statusReq)); + } + */ +} diff --git a/src/main/java/com/kamco/cd/training/upload/dto/UploadDto.java b/src/main/java/com/kamco/cd/training/upload/dto/UploadDto.java index c4fba70..1369fa8 100644 --- a/src/main/java/com/kamco/cd/training/upload/dto/UploadDto.java +++ b/src/main/java/com/kamco/cd/training/upload/dto/UploadDto.java @@ -1,221 +1,217 @@ -package com.kamco.cd.training.upload.dto; - -import io.swagger.v3.oas.annotations.media.Schema; -import jakarta.validation.constraints.NotBlank; -import java.util.UUID; -import lombok.AllArgsConstructor; -import lombok.Getter; -import lombok.NoArgsConstructor; -import lombok.Setter; - -public class UploadDto { - - @Schema(name = "InitReq", description = "업로드(Chunk) 세션 초기화") - @Getter - @Setter - @NoArgsConstructor - @AllArgsConstructor - public static class InitReq { - - @Schema(description = "데이터식별키", example = "129227333") - private Long datasetId; - - @Schema(description = "파일명", example = "data.zip") - private String fileName; - - @Schema(description = "파일 크기 (bytes)", example = "10737418240") - private Long fileSize; - - @Schema(description = "총 청크 수", example = "100") - private Integer chunkTotalIndex; - - @Schema( - description = "파일해쉬", - example = "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855") - private String fileHash; - - @Schema(description = "업로드구분", example = "dataset") - private String uploadDivi; - } - - @Schema(name = "UploadAddReq", description = "업로드 요청") - @Getter - @Setter - @NoArgsConstructor - @AllArgsConstructor - public static class UploadAddReq { - @Schema(description = "업로드 ID", example = "각데이터의 식별키") - private String uploadId; - - @Schema(description = "데이터식별키", example = "129227333") - private Long datasetId; - - @Schema(description = "파일명", example = "data.zip") - private String fileName; - - @Schema(description = "파일 크기 (bytes)", example = "10737418240") - private Long fileSize; - - @Schema(description = "파일명", example = "data.zip") - private String finalPath; - - @Schema(description = "업로드구분", example = "dataset") - private String uploadDivi; - - @Schema(description = "상태", example = "UPLOADING") - private String status; - - @Schema(description = "임시저장경로") - private String tempPath; - - @Schema(description = "업로드 청크 Index", example = "50") - private Integer chunkIndex; - - @Schema(description = "총 청크 수", example = "100") - private Integer chunkTotalIndex; - - @Schema( - description = "파일해쉬", - example = "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855") - private String fileHash; - - @Schema(description = "uuid", example = "303d4e24-1726-4272-bbc7-01ab85692b80") - private UUID uuid; - } - - @Schema(name = "UploadCompleteReq", description = "업로드 완료 요청") - @Getter - @Setter - @NoArgsConstructor - @AllArgsConstructor - public static class CompleteReq { - - @NotBlank(message = "업로드 ID는 필수입니다") - @Schema(description = "업로드 ID", example = "upload_20241218_123456_abc123") - private String uploadId; - } - - @Schema(name = "UploadStatusReq", description = "업로드 상태 조회 요청") - @Getter - @Setter - @NoArgsConstructor - @AllArgsConstructor - public static class StatusReq { - - @NotBlank(message = "업로드 ID는 필수입니다") - @Schema(description = "업로드 ID", example = "upload_20241218_123456_abc123") - private String uploadId; - } - - @Schema(name = "UploadStatus", description = "업로드 상태 정보") - @Getter - @Setter - @NoArgsConstructor - @AllArgsConstructor - public static class Status { - - @Schema(description = "업로드 ID", example = "upload_20241218_123456_abc123") - private String uploadId; - - @Schema(description = "파일명", example = "data.zip") - private String fileName; - - @Schema(description = "파일 크기 (bytes)", example = "10737418240") - private Long fileSize; - - @Schema(description = "상태", example = "UPLOADING") - private String status; - - @Schema(description = "총 청크 수", example = "100") - private Integer totalChunks; - - @Schema(description = "업로드된 청크 수", example = "50") - private Integer uploadedChunks; - - @Schema(description = "진행률 (%)", example = "50.0") - private Double progress; - - @Schema(description = "에러 메시지", example = "") - private String errorMessage; - } - - @Schema(name = "UploadAddReq", description = "업로드 요청") - @Getter - @Setter - @NoArgsConstructor - @AllArgsConstructor - public static class uploadDto { - @Schema(description = "업로드 ID", example = "각데이터의 식별키") - private String uploadId; - - @Schema(description = "데이터식별키", example = "129227333") - private Long datasetId; - - @Schema(description = "파일명", example = "data.zip") - private String fileName; - - - @Schema(description = "파일 크기 (bytes)", example = "10737418240") - private Long fileSize; - - @Schema(description = "파일명", example = "data.zip") - private String finalPath; - - @Schema(description = "업로드구분", example = "dataset") - private String uploadDivi; - - @Schema(description = "상태", example = "UPLOADING") - private String status; - - @Schema(description = "임시저장경로") - private String tempPath; - - @Schema(description = "업로드 청크 Index", example = "50") - private Integer chunkIndex; - - @Schema(description = "총 청크 Index", example = "100") - private Integer chunkTotalIndex; - - - @Schema( - description = "파일해쉬", - example = "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855") - private String fileHash; - - @Schema(description = "uuid") - private UUID uuid; - - } - - @Schema(name = "UploadRes", description = "업로드 수행 후 리턴") - @Getter - @Setter - @NoArgsConstructor - @AllArgsConstructor - public static class UploadRes { - private String res; - private String resMsg; - private String uuid; - private int chunkIndex; - private int chunkTotalIndex; - - public double getUploadRate() { - if (this.chunkTotalIndex == 0) { - return 0.0; - } - return (double) (this.chunkIndex+1) / (this.chunkTotalIndex+1) * 100.0; - } - - } - - @Schema(name = "DmlReturn", description = "수행 후 리턴") - @Getter - @Setter - @NoArgsConstructor - @AllArgsConstructor - public static class DmlReturn { - - private String flag; - private String message; - } -} +package com.kamco.cd.training.upload.dto; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotBlank; +import java.util.UUID; +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +public class UploadDto { + + @Schema(name = "InitReq", description = "업로드(Chunk) 세션 초기화") + @Getter + @Setter + @NoArgsConstructor + @AllArgsConstructor + public static class InitReq { + + @Schema(description = "데이터식별키", example = "129227333") + private Long datasetId; + + @Schema(description = "파일명", example = "data.zip") + private String fileName; + + @Schema(description = "파일 크기 (bytes)", example = "10737418240") + private Long fileSize; + + @Schema(description = "총 청크 수", example = "100") + private Integer chunkTotalIndex; + + @Schema( + description = "파일해쉬", + example = "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855") + private String fileHash; + + @Schema(description = "업로드구분", example = "dataset") + private String uploadDivi; + } + + @Schema(name = "UploadAddReq", description = "업로드 요청") + @Getter + @Setter + @NoArgsConstructor + @AllArgsConstructor + public static class UploadAddReq { + @Schema(description = "업로드 ID", example = "각데이터의 식별키") + private String uploadId; + + @Schema(description = "데이터식별키", example = "129227333") + private Long datasetId; + + @Schema(description = "파일명", example = "data.zip") + private String fileName; + + @Schema(description = "파일 크기 (bytes)", example = "10737418240") + private Long fileSize; + + @Schema(description = "파일명", example = "data.zip") + private String finalPath; + + @Schema(description = "업로드구분", example = "dataset") + private String uploadDivi; + + @Schema(description = "상태", example = "UPLOADING") + private String status; + + @Schema(description = "임시저장경로") + private String tempPath; + + @Schema(description = "업로드 청크 Index", example = "50") + private Integer chunkIndex; + + @Schema(description = "총 청크 수", example = "100") + private Integer chunkTotalIndex; + + @Schema( + description = "파일해쉬", + example = "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855") + private String fileHash; + + @Schema(description = "uuid", example = "303d4e24-1726-4272-bbc7-01ab85692b80") + private UUID uuid; + } + + @Schema(name = "UploadCompleteReq", description = "업로드 완료 요청") + @Getter + @Setter + @NoArgsConstructor + @AllArgsConstructor + public static class CompleteReq { + + @NotBlank(message = "업로드 ID는 필수입니다") + @Schema(description = "업로드 ID", example = "upload_20241218_123456_abc123") + private String uploadId; + } + + @Schema(name = "UploadStatusReq", description = "업로드 상태 조회 요청") + @Getter + @Setter + @NoArgsConstructor + @AllArgsConstructor + public static class StatusReq { + + @NotBlank(message = "업로드 ID는 필수입니다") + @Schema(description = "업로드 ID", example = "upload_20241218_123456_abc123") + private String uploadId; + } + + @Schema(name = "UploadStatus", description = "업로드 상태 정보") + @Getter + @Setter + @NoArgsConstructor + @AllArgsConstructor + public static class Status { + + @Schema(description = "업로드 ID", example = "upload_20241218_123456_abc123") + private String uploadId; + + @Schema(description = "파일명", example = "data.zip") + private String fileName; + + @Schema(description = "파일 크기 (bytes)", example = "10737418240") + private Long fileSize; + + @Schema(description = "상태", example = "UPLOADING") + private String status; + + @Schema(description = "총 청크 수", example = "100") + private Integer totalChunks; + + @Schema(description = "업로드된 청크 수", example = "50") + private Integer uploadedChunks; + + @Schema(description = "진행률 (%)", example = "50.0") + private Double progress; + + @Schema(description = "에러 메시지", example = "") + private String errorMessage; + } + + @Schema(name = "UploadAddReq", description = "업로드 요청") + @Getter + @Setter + @NoArgsConstructor + @AllArgsConstructor + public static class uploadDto { + @Schema(description = "업로드 ID", example = "각데이터의 식별키") + private String uploadId; + + @Schema(description = "데이터식별키", example = "129227333") + private Long datasetId; + + @Schema(description = "파일명", example = "data.zip") + private String fileName; + + @Schema(description = "파일 크기 (bytes)", example = "10737418240") + private Long fileSize; + + @Schema(description = "파일명", example = "data.zip") + private String finalPath; + + @Schema(description = "업로드구분", example = "dataset") + private String uploadDivi; + + @Schema(description = "상태", example = "UPLOADING") + private String status; + + @Schema(description = "임시저장경로") + private String tempPath; + + @Schema(description = "업로드 청크 Index", example = "50") + private Integer chunkIndex; + + @Schema(description = "총 청크 Index", example = "100") + private Integer chunkTotalIndex; + + @Schema( + description = "파일해쉬", + example = "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855") + private String fileHash; + + @Schema(description = "uuid") + private UUID uuid; + } + + @Schema(name = "UploadRes", description = "업로드 수행 후 리턴") + @Getter + @Setter + @NoArgsConstructor + @AllArgsConstructor + public static class UploadRes { + private String res; + private String resMsg; + private String uuid; + private int chunkIndex; + private int chunkTotalIndex; + + public double getUploadRate() { + if (this.chunkTotalIndex == 0) { + return 0.0; + } + return (double) (this.chunkIndex + 1) / (this.chunkTotalIndex + 1) * 100.0; + } + } + + @Schema(name = "DmlReturn", description = "수행 후 리턴") + @Getter + @Setter + @NoArgsConstructor + @AllArgsConstructor + public static class DmlReturn { + + private String flag; + private String message; + } +} diff --git a/src/main/java/com/kamco/cd/training/upload/service/UploadService.java b/src/main/java/com/kamco/cd/training/upload/service/UploadService.java index c3ec89d..13b083d 100644 --- a/src/main/java/com/kamco/cd/training/upload/service/UploadService.java +++ b/src/main/java/com/kamco/cd/training/upload/service/UploadService.java @@ -1,230 +1,217 @@ -package com.kamco.cd.training.upload.service; - -import com.kamco.cd.training.common.utils.FIleChecker; -import com.kamco.cd.training.postgres.core.UploadSessionCoreService; -import com.kamco.cd.training.upload.dto.UploadDto; -import com.kamco.cd.training.upload.dto.UploadDto.DmlReturn; -import java.io.File; -import java.io.FileInputStream; -import java.io.FileOutputStream; -import java.io.IOException; -import java.nio.channels.FileChannel; -import java.nio.file.Files; -import java.nio.file.Path; -import java.nio.file.Paths; -import java.nio.file.StandardOpenOption; -import java.util.UUID; -import lombok.RequiredArgsConstructor; -import lombok.extern.slf4j.Slf4j; -import org.springframework.beans.factory.annotation.Value; -import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Transactional; -import org.springframework.web.multipart.MultipartFile; - -@Slf4j -@Service -@RequiredArgsConstructor -public class UploadService { - - private final UploadSessionCoreService uploadSessionCoreService; - - @Value("${file.sync-root-dir}") - private String syncRootDir; - - @Value("${file.sync-tmp-dir}") - private String syncTmpDir; - - @Value("${file.sync-file-extention}") - private String syncFileExtention; - - @Value("${file.dataset-dir}") - private String datasetDir; - - @Value("${file.dataset-tmp-dir}") - private String datasetTmpDir; - - @Transactional - public DmlReturn initUpload(UploadDto.InitReq initReq) { - - return new DmlReturn("success", "UPLOAD CHUNK INIT"); - } - - @Transactional - public UploadDto.UploadRes uploadChunk(UploadDto.UploadAddReq upAddReqDto, MultipartFile file) { - - UploadDto.UploadRes upRes = new UploadDto.UploadRes(); - - long datasetId = upAddReqDto.getDatasetId(); - String uploadId = System.currentTimeMillis()+""; - UUID uuid = UUID.randomUUID(); - String tmpDataSetDir = ""; - String fianlDir = ""; - String uploadDivi = upAddReqDto.getUploadDivi(); - //String fileName = file.getOriginalFilename(); - String fileName = upAddReqDto.getFileName(); - Integer chunkIndex = upAddReqDto.getChunkIndex(); - Integer chunkTotalIndex = upAddReqDto.getChunkTotalIndex(); - String status = "UPLOADING"; - - if( uploadDivi.equals("dataset")) - { - tmpDataSetDir = datasetTmpDir+uuid+"/"; - fianlDir = datasetDir+uuid+"/"; - } - - upAddReqDto.setUuid(uuid); - upAddReqDto.setUploadId(uploadId); - upAddReqDto.setStatus(status); - upAddReqDto.setFileName(fileName); - upAddReqDto.setTempPath(tmpDataSetDir); - upAddReqDto.setFinalPath(fianlDir); - - //세션 신규,중복체크(초기화 포함) - UploadDto.uploadDto dto = this.checkUploadSession(upAddReqDto, upRes); - if( !upRes.getRes().equals("success") )return upRes; - - if( dto != null ) - { - tmpDataSetDir = dto.getTempPath(); - fianlDir = dto.getFinalPath(); - } - - //폴더 생성 및 체크 - if( ! checkChunkFoler(upRes, tmpDataSetDir, fianlDir) )return upRes; - - //chunk저장하기 - if( ! FIleChecker.multipartChunkSaveTo(file, tmpDataSetDir, chunkIndex ) ) - { - upRes.setRes("fail"); - upRes.setResMsg("chunkIndex:"+chunkIndex+" 업로드 애러"); - } - - //chunk완료시 merge 및 폴더에 저장 - if( chunkIndex == chunkTotalIndex ) { - - //upAddReqDto.setUploadId(dto.getUploadId()); - //upAddReqDto.setStatus("MERGING"); - //uploadSessionCoreService.updateUploadSessionStatus(upAddReqDto); - - /* - try { - this.mergeChunks(tmpDataSetDir, fianlDir, fileName, chunkTotalIndex); - } catch (IOException e) { - //throw new RuntimeException(e); - upRes.setRes("fail"); - upRes.setResMsg("파일 저장 완료(merge) 애러"); - return upRes; - } - */ - - upAddReqDto.setUploadId(dto.getUploadId()); - upAddReqDto.setStatus("COMPLETE"); - uploadSessionCoreService.updateUploadSessionStatus(upAddReqDto); - - } - - return upRes; - } - - @Transactional - public DmlReturn completeUpload(String uuid) { - - UploadDto.uploadDto dto = uploadSessionCoreService.findByUuid(uuid); - - try { - this.mergeChunks(dto.getTempPath(), dto.getFinalPath(), dto.getFileName(), dto.getChunkTotalIndex()); - } catch (IOException e) { - return new DmlReturn("mergingfail", "chunk파일 merge 애러"); - } - - return new DmlReturn("success", "병합(merge) 정상처리되었습니다."); - - } - - public boolean checkChunkFoler(UploadDto.UploadRes upRes, String tmpDataSetDir, String fianlDir) - { - if( ! FIleChecker.mkDir(tmpDataSetDir) ) - { - upRes.setRes("fail"); - upRes.setRes("CHUNK 폴더 생성 ERROR"); - return false; - } - - if( ! FIleChecker.mkDir(fianlDir) ) - { - upRes.setRes("fail"); - upRes.setRes("업로드 완료 폴더 생성 ERROR"); - return false; - } - - return true; - } - - - public UploadDto.uploadDto checkUploadSession(UploadDto.UploadAddReq upAddReqDto, UploadDto.UploadRes upRes) { - - upRes.setRes("success"); - upRes.setResMsg("정상처리되었습니다."); - - UploadDto.uploadDto dto = uploadSessionCoreService.findByDatasetUid(upAddReqDto.getDatasetId(), upAddReqDto.getUploadDivi()); - - if( upAddReqDto.getChunkIndex() == 0 ) { - if( dto != null ) - { - upRes.setRes("duplicate"); - upRes.setResMsg("이미 진행중인 업로드세션입니다."); - return dto; - } - - upAddReqDto.setStatus("UPLOADING"); - upRes.setUuid( upAddReqDto.getUuid().toString() ); - uploadSessionCoreService.createUploadSession(upAddReqDto); - } - else { - if( dto == null ){ - upRes.setRes("nosession"); - upRes.setResMsg("업로드 세션이 존재하지 않습니다."); - return dto; - } - - upAddReqDto.setStatus("UPLOADING"); - upAddReqDto.setUploadId(dto.getUploadId()); - uploadSessionCoreService.updateUploadSessionStatus(upAddReqDto); - } - - if( dto != null )upRes.setUuid( dto.getUuid().toString() ); - - upRes.setChunkIndex(upAddReqDto.getChunkIndex()); - upRes.setChunkTotalIndex(upAddReqDto.getChunkTotalIndex()); - - - return dto; - - } - - public void mergeChunks(String tmpDir, String fianlDir, String fileName, int chunkTotalIndex) throws IOException { - - Path outputPath = Paths.get(fianlDir, fileName); - try (FileChannel outChannel = FileChannel.open(outputPath, StandardOpenOption.CREATE, StandardOpenOption.WRITE)) { - for (int i = 0; i <= chunkTotalIndex; i++) { - Path chunkPath = Paths.get(tmpDir, i+""); - - try (FileChannel inChannel = FileChannel.open(chunkPath, StandardOpenOption.READ)) { - long transferred = 0; - long size = inChannel.size(); - while (transferred < size) { - transferred += inChannel.transferTo(transferred, size - transferred, outChannel); - } - } - // 병합 후 즉시 삭제하여 디스크 공간 확보 - Files.delete(chunkPath); - } - } - - //병합후 임시 폴더 삭제 - FIleChecker.deleteFolder(tmpDir); - - } - - -} +package com.kamco.cd.training.upload.service; + +import com.kamco.cd.training.common.utils.FIleChecker; +import com.kamco.cd.training.postgres.core.UploadSessionCoreService; +import com.kamco.cd.training.upload.dto.UploadDto; +import com.kamco.cd.training.upload.dto.UploadDto.DmlReturn; +import java.io.IOException; +import java.nio.channels.FileChannel; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.nio.file.StandardOpenOption; +import java.util.UUID; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.web.multipart.MultipartFile; + +@Slf4j +@Service +@RequiredArgsConstructor +public class UploadService { + + private final UploadSessionCoreService uploadSessionCoreService; + + @Value("${file.sync-root-dir}") + private String syncRootDir; + + @Value("${file.sync-tmp-dir}") + private String syncTmpDir; + + @Value("${file.sync-file-extention}") + private String syncFileExtention; + + @Value("${file.dataset-dir}") + private String datasetDir; + + @Value("${file.dataset-tmp-dir}") + private String datasetTmpDir; + + @Transactional + public DmlReturn initUpload(UploadDto.InitReq initReq) { + + return new DmlReturn("success", "UPLOAD CHUNK INIT"); + } + + @Transactional + public UploadDto.UploadRes uploadChunk(UploadDto.UploadAddReq upAddReqDto, MultipartFile file) { + + UploadDto.UploadRes upRes = new UploadDto.UploadRes(); + + long datasetId = upAddReqDto.getDatasetId(); + String uploadId = System.currentTimeMillis() + ""; + UUID uuid = UUID.randomUUID(); + String tmpDataSetDir = ""; + String fianlDir = ""; + String uploadDivi = upAddReqDto.getUploadDivi(); + // String fileName = file.getOriginalFilename(); + String fileName = upAddReqDto.getFileName(); + Integer chunkIndex = upAddReqDto.getChunkIndex(); + Integer chunkTotalIndex = upAddReqDto.getChunkTotalIndex(); + String status = "UPLOADING"; + + if (uploadDivi.equals("dataset")) { + tmpDataSetDir = datasetTmpDir + uuid + "/"; + fianlDir = datasetDir + uuid + "/"; + } + + upAddReqDto.setUuid(uuid); + upAddReqDto.setUploadId(uploadId); + upAddReqDto.setStatus(status); + upAddReqDto.setFileName(fileName); + upAddReqDto.setTempPath(tmpDataSetDir); + upAddReqDto.setFinalPath(fianlDir); + + // 세션 신규,중복체크(초기화 포함) + UploadDto.uploadDto dto = this.checkUploadSession(upAddReqDto, upRes); + if (!upRes.getRes().equals("success")) return upRes; + + if (dto != null) { + tmpDataSetDir = dto.getTempPath(); + fianlDir = dto.getFinalPath(); + } + + // 폴더 생성 및 체크 + if (!checkChunkFoler(upRes, tmpDataSetDir, fianlDir)) return upRes; + + // chunk저장하기 + if (!FIleChecker.multipartChunkSaveTo(file, tmpDataSetDir, chunkIndex)) { + upRes.setRes("fail"); + upRes.setResMsg("chunkIndex:" + chunkIndex + " 업로드 애러"); + } + + // chunk완료시 merge 및 폴더에 저장 + if (chunkIndex == chunkTotalIndex) { + + // upAddReqDto.setUploadId(dto.getUploadId()); + // upAddReqDto.setStatus("MERGING"); + // uploadSessionCoreService.updateUploadSessionStatus(upAddReqDto); + + /* + try { + this.mergeChunks(tmpDataSetDir, fianlDir, fileName, chunkTotalIndex); + } catch (IOException e) { + //throw new RuntimeException(e); + upRes.setRes("fail"); + upRes.setResMsg("파일 저장 완료(merge) 애러"); + return upRes; + } + */ + + upAddReqDto.setUploadId(dto.getUploadId()); + upAddReqDto.setStatus("COMPLETE"); + uploadSessionCoreService.updateUploadSessionStatus(upAddReqDto); + } + + return upRes; + } + + @Transactional + public DmlReturn completeUpload(String uuid) { + + UploadDto.uploadDto dto = uploadSessionCoreService.findByUuid(uuid); + + try { + this.mergeChunks( + dto.getTempPath(), dto.getFinalPath(), dto.getFileName(), dto.getChunkTotalIndex()); + } catch (IOException e) { + return new DmlReturn("mergingfail", "chunk파일 merge 애러"); + } + + return new DmlReturn("success", "병합(merge) 정상처리되었습니다."); + } + + public boolean checkChunkFoler(UploadDto.UploadRes upRes, String tmpDataSetDir, String fianlDir) { + if (!FIleChecker.mkDir(tmpDataSetDir)) { + upRes.setRes("fail"); + upRes.setRes("CHUNK 폴더 생성 ERROR"); + return false; + } + + if (!FIleChecker.mkDir(fianlDir)) { + upRes.setRes("fail"); + upRes.setRes("업로드 완료 폴더 생성 ERROR"); + return false; + } + + return true; + } + + public UploadDto.uploadDto checkUploadSession( + UploadDto.UploadAddReq upAddReqDto, UploadDto.UploadRes upRes) { + + upRes.setRes("success"); + upRes.setResMsg("정상처리되었습니다."); + + UploadDto.uploadDto dto = + uploadSessionCoreService.findByDatasetUid( + upAddReqDto.getDatasetId(), upAddReqDto.getUploadDivi()); + + if (upAddReqDto.getChunkIndex() == 0) { + if (dto != null) { + upRes.setRes("duplicate"); + upRes.setResMsg("이미 진행중인 업로드세션입니다."); + return dto; + } + + upAddReqDto.setStatus("UPLOADING"); + upRes.setUuid(upAddReqDto.getUuid().toString()); + uploadSessionCoreService.createUploadSession(upAddReqDto); + } else { + if (dto == null) { + upRes.setRes("nosession"); + upRes.setResMsg("업로드 세션이 존재하지 않습니다."); + return dto; + } + + upAddReqDto.setStatus("UPLOADING"); + upAddReqDto.setUploadId(dto.getUploadId()); + uploadSessionCoreService.updateUploadSessionStatus(upAddReqDto); + } + + if (dto != null) upRes.setUuid(dto.getUuid().toString()); + + upRes.setChunkIndex(upAddReqDto.getChunkIndex()); + upRes.setChunkTotalIndex(upAddReqDto.getChunkTotalIndex()); + + return dto; + } + + public void mergeChunks(String tmpDir, String fianlDir, String fileName, int chunkTotalIndex) + throws IOException { + + Path outputPath = Paths.get(fianlDir, fileName); + try (FileChannel outChannel = + FileChannel.open(outputPath, StandardOpenOption.CREATE, StandardOpenOption.WRITE)) { + for (int i = 0; i <= chunkTotalIndex; i++) { + Path chunkPath = Paths.get(tmpDir, i + ""); + + try (FileChannel inChannel = FileChannel.open(chunkPath, StandardOpenOption.READ)) { + long transferred = 0; + long size = inChannel.size(); + while (transferred < size) { + transferred += inChannel.transferTo(transferred, size - transferred, outChannel); + } + } + // 병합 후 즉시 삭제하여 디스크 공간 확보 + Files.delete(chunkPath); + } + } + + // 병합후 임시 폴더 삭제 + FIleChecker.deleteFolder(tmpDir); + } +} diff --git a/src/test/java/com/kamco/cd/training/KamcoTrainingApplicationTests.java b/src/test/java/com/kamco/cd/training/KamcoTrainingApplicationTests.java index 66eafa5..3ffb8d8 100644 --- a/src/test/java/com/kamco/cd/training/KamcoTrainingApplicationTests.java +++ b/src/test/java/com/kamco/cd/training/KamcoTrainingApplicationTests.java @@ -1,11 +1,11 @@ -package com.kamco.cd.training; - -import org.junit.jupiter.api.Test; -import org.springframework.boot.test.context.SpringBootTest; - -@SpringBootTest -class KamcoTrainingApplicationTests { - - @Test - void contextLoads() {} -} +package com.kamco.cd.training; + +import org.junit.jupiter.api.Test; +import org.springframework.boot.test.context.SpringBootTest; + +@SpringBootTest +class KamcoTrainingApplicationTests { + + @Test + void contextLoads() {} +}