init spotless 적용
This commit is contained in:
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<? extends GrantedAuthority> 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<? extends GrantedAuthority> 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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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> claims = parseClaims(token);
|
||||
return !claims.getPayload().getExpiration().before(new Date());
|
||||
} catch (Exception e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private Jws<Claims> 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> claims = parseClaims(token);
|
||||
return !claims.getPayload().getExpiration().before(new Date());
|
||||
} catch (Exception e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private Jws<Claims> parseClaims(String token) {
|
||||
return Jwts.parser()
|
||||
.verifyWith(key) // SecretKey 타입
|
||||
.build()
|
||||
.parseSignedClaims(token);
|
||||
}
|
||||
|
||||
public long getRefreshTokenValidityInMs() {
|
||||
return refreshTokenValidityInMs;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<List<CommonCodeDto.Basic>> 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<CommonCodeDto.Basic> 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<ApiResponseDto.ResponseObj> 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<ApiResponseDto.ResponseObj> 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<ApiResponseDto.ResponseObj> 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<ApiResponseDto.ResponseObj> 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<List<CommonCodeDto.Basic>> 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<List<CommonCodeDto.Clazzes>> getClasses() {
|
||||
|
||||
// List<Clazzes> list =
|
||||
// Arrays.stream(DetectionClassification.values())
|
||||
// .sorted(Comparator.comparingInt(DetectionClassification::getOrder))
|
||||
// .map(Clazzes::new)
|
||||
// .toList();
|
||||
|
||||
// 변화탐지 clazz API : enum -> 공통코드로 변경
|
||||
List<CommonCodeDto.Clazzes> 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<ApiResponseDto.ResponseObj> 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<Map<String, List<CodeDto>>> 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<List<CodeDto>> 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<String> 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<List<CommonCodeDto.Basic>> 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<CommonCodeDto.Basic> 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<ApiResponseDto.ResponseObj> 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<ApiResponseDto.ResponseObj> 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<ApiResponseDto.ResponseObj> 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<ApiResponseDto.ResponseObj> 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<List<CommonCodeDto.Basic>> 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<List<CommonCodeDto.Clazzes>> getClasses() {
|
||||
|
||||
// List<Clazzes> list =
|
||||
// Arrays.stream(DetectionClassification.values())
|
||||
// .sorted(Comparator.comparingInt(DetectionClassification::getOrder))
|
||||
// .map(Clazzes::new)
|
||||
// .toList();
|
||||
|
||||
// 변화탐지 clazz API : enum -> 공통코드로 변경
|
||||
List<CommonCodeDto.Clazzes> 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<ApiResponseDto.ResponseObj> 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<Map<String, List<CodeDto>>> 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<List<CodeDto>> 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<String> refreshCommonCodeCache() {
|
||||
commonCodeService.refresh();
|
||||
return ApiResponseDto.ok("공통코드 캐시가 초기화 되었습니다.");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<CommonCodeDto.Basic> 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<CommonCodeDto.Basic> 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<CommonCodeDto.Basic> 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<CommonCodeDto.Basic> 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<Basic> 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<Basic> 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<String> getCode(String parentCodeCd, String childCodeCd) {
|
||||
return commonCodeCoreService.getCode(parentCodeCd, childCodeCd);
|
||||
}
|
||||
|
||||
/**
|
||||
* 공통코드 이름 조회
|
||||
*
|
||||
* @param parentCodeCd 상위 코드
|
||||
* @param childCodeCd 하위 코드
|
||||
* @return 공통코드명
|
||||
*/
|
||||
public Optional<String> getTypeCode(String parentCodeCd, String childCodeCd) {
|
||||
return commonCodeCoreService.getCode(parentCodeCd, childCodeCd);
|
||||
}
|
||||
|
||||
public List<CodeDto> getTypeCode(String type) {
|
||||
return Enums.getCodes(type);
|
||||
}
|
||||
|
||||
/**
|
||||
* 공통코드 리스트 조회
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public Map<String, List<CodeDto>> 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<Basic> 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<Basic> 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<String> getCode(String parentCodeCd, String childCodeCd) {
|
||||
return commonCodeCoreService.getCode(parentCodeCd, childCodeCd);
|
||||
}
|
||||
|
||||
/**
|
||||
* 공통코드 이름 조회
|
||||
*
|
||||
* @param parentCodeCd 상위 코드
|
||||
* @param childCodeCd 하위 코드
|
||||
* @return 공통코드명
|
||||
*/
|
||||
public Optional<String> getTypeCode(String parentCodeCd, String childCodeCd) {
|
||||
return commonCodeCoreService.getCode(parentCodeCd, childCodeCd);
|
||||
}
|
||||
|
||||
public List<CodeDto> getTypeCode(String type) {
|
||||
return Enums.getCodes(type);
|
||||
}
|
||||
|
||||
/**
|
||||
* 공통코드 리스트 조회
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public Map<String, List<CodeDto>> getTypeCodes() {
|
||||
return Enums.getAllCodes();
|
||||
}
|
||||
|
||||
/** 메모리 캐시 초기화 */
|
||||
@CacheEvict(value = "trainCommonCodes", allEntries = true)
|
||||
public void refresh() {}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,38 +1,38 @@
|
||||
package com.kamco.cd.training.common.service;
|
||||
|
||||
import org.springframework.data.domain.Page;
|
||||
|
||||
/**
|
||||
* Base Core Service Interface
|
||||
*
|
||||
* <p>CRUD operations를 정의하는 기본 서비스 인터페이스
|
||||
*
|
||||
* @param <T> Entity 타입
|
||||
* @param <ID> Entity의 ID 타입
|
||||
* @param <S> Search Request 타입
|
||||
*/
|
||||
public interface BaseCoreService<T, ID, S> {
|
||||
|
||||
/**
|
||||
* ID로 엔티티를 삭제합니다.
|
||||
*
|
||||
* @param id 삭제할 엔티티의 ID
|
||||
*/
|
||||
void remove(ID id);
|
||||
|
||||
/**
|
||||
* ID로 단건 조회합니다.
|
||||
*
|
||||
* @param id 조회할 엔티티의 ID
|
||||
* @return 조회된 엔티티
|
||||
*/
|
||||
T getOneById(ID id);
|
||||
|
||||
/**
|
||||
* 검색 조건과 페이징으로 조회합니다.
|
||||
*
|
||||
* @param searchReq 검색 조건
|
||||
* @return 페이징 처리된 검색 결과
|
||||
*/
|
||||
Page<T> search(S searchReq);
|
||||
}
|
||||
package com.kamco.cd.training.common.service;
|
||||
|
||||
import org.springframework.data.domain.Page;
|
||||
|
||||
/**
|
||||
* Base Core Service Interface
|
||||
*
|
||||
* <p>CRUD operations를 정의하는 기본 서비스 인터페이스
|
||||
*
|
||||
* @param <T> Entity 타입
|
||||
* @param <ID> Entity의 ID 타입
|
||||
* @param <S> Search Request 타입
|
||||
*/
|
||||
public interface BaseCoreService<T, ID, S> {
|
||||
|
||||
/**
|
||||
* ID로 엔티티를 삭제합니다.
|
||||
*
|
||||
* @param id 삭제할 엔티티의 ID
|
||||
*/
|
||||
void remove(ID id);
|
||||
|
||||
/**
|
||||
* ID로 단건 조회합니다.
|
||||
*
|
||||
* @param id 조회할 엔티티의 ID
|
||||
* @return 조회된 엔티티
|
||||
*/
|
||||
T getOneById(ID id);
|
||||
|
||||
/**
|
||||
* 검색 조건과 페이징으로 조회합니다.
|
||||
*
|
||||
* @param searchReq 검색 조건
|
||||
* @return 페이징 처리된 검색 결과
|
||||
*/
|
||||
Page<T> search(S searchReq);
|
||||
}
|
||||
|
||||
@@ -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<Basic> getAllCommonCodes() {
|
||||
try {
|
||||
return commonCodeService.getFindAll();
|
||||
} catch (Exception e) {
|
||||
log.error("공통코드 전체 조회 중 오류 발생", e);
|
||||
return List.of();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 특정 코드로 공통코드 조회
|
||||
*
|
||||
* @param code 코드값
|
||||
* @return 해당 코드의 공통코드 목록
|
||||
*/
|
||||
public List<Basic> 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<Basic> 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<String> 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<Basic> 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<Basic> getAllCommonCodes() {
|
||||
try {
|
||||
return commonCodeService.getFindAll();
|
||||
} catch (Exception e) {
|
||||
log.error("공통코드 전체 조회 중 오류 발생", e);
|
||||
return List.of();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 특정 코드로 공통코드 조회
|
||||
*
|
||||
* @param code 코드값
|
||||
* @return 해당 코드의 공통코드 목록
|
||||
*/
|
||||
public List<Basic> 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<Basic> 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<String> 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<Basic> 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 {}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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<EnumValid, String> {
|
||||
|
||||
private Set<String> 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<EnumValid, String> {
|
||||
|
||||
private Set<String> 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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<String, Class<? extends Enum<?>>> exposedEnumMap = scanExposedEnumMap();
|
||||
|
||||
// code로 enum 찾기
|
||||
public static <E extends Enum<E> & EnumType> E fromId(Class<E> 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<CodeDto> toList(Class<? extends Enum<?>> 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<CodeDto> getCodes(String type) {
|
||||
Class<? extends Enum<?>> enumClass = exposedEnumMap.get(type);
|
||||
if (enumClass == null) {
|
||||
throw new IllegalArgumentException("지원하지 않는 코드 타입: " + type);
|
||||
}
|
||||
return toList(enumClass);
|
||||
}
|
||||
|
||||
/** 전체 enum 코드 조회 */
|
||||
public static Map<String, List<CodeDto>> getAllCodes() {
|
||||
Map<String, List<CodeDto>> result = new HashMap<>();
|
||||
for (Map.Entry<String, Class<? extends Enum<?>>> e : exposedEnumMap.entrySet()) {
|
||||
result.put(e.getKey(), toList(e.getValue()));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* @CodeExpose + EnumType 인 enum만 스캔해서 Map 구성
|
||||
*/
|
||||
private static Map<String, Class<? extends Enum<?>>> scanExposedEnumMap() {
|
||||
Reflections reflections = new Reflections(BASE_PACKAGE);
|
||||
|
||||
Set<Class<?>> types = reflections.getTypesAnnotatedWith(CodeExpose.class);
|
||||
|
||||
Map<String, Class<? extends Enum<?>>> result = new HashMap<>();
|
||||
|
||||
for (Class<?> clazz : types) {
|
||||
if (clazz.isEnum() && EnumType.class.isAssignableFrom(clazz)) {
|
||||
result.put(clazz.getSimpleName(), (Class<? extends Enum<?>>) 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<String, Class<? extends Enum<?>>> exposedEnumMap = scanExposedEnumMap();
|
||||
|
||||
// code로 enum 찾기
|
||||
public static <E extends Enum<E> & EnumType> E fromId(Class<E> 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<CodeDto> toList(Class<? extends Enum<?>> 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<CodeDto> getCodes(String type) {
|
||||
Class<? extends Enum<?>> enumClass = exposedEnumMap.get(type);
|
||||
if (enumClass == null) {
|
||||
throw new IllegalArgumentException("지원하지 않는 코드 타입: " + type);
|
||||
}
|
||||
return toList(enumClass);
|
||||
}
|
||||
|
||||
/** 전체 enum 코드 조회 */
|
||||
public static Map<String, List<CodeDto>> getAllCodes() {
|
||||
Map<String, List<CodeDto>> result = new HashMap<>();
|
||||
for (Map.Entry<String, Class<? extends Enum<?>>> e : exposedEnumMap.entrySet()) {
|
||||
result.put(e.getKey(), toList(e.getValue()));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* @CodeExpose + EnumType 인 enum만 스캔해서 Map 구성
|
||||
*/
|
||||
private static Map<String, Class<? extends Enum<?>>> scanExposedEnumMap() {
|
||||
Reflections reflections = new Reflections(BASE_PACKAGE);
|
||||
|
||||
Set<Class<?>> types = reflections.getTypesAnnotatedWith(CodeExpose.class);
|
||||
|
||||
Map<String, Class<? extends Enum<?>>> result = new HashMap<>();
|
||||
|
||||
for (Class<?> clazz : types) {
|
||||
if (clazz.isEnum() && EnumType.class.isAssignableFrom(clazz)) {
|
||||
result.put(clazz.getSimpleName(), (Class<? extends Enum<?>>) clazz);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<T extends Geometry> extends StdDeserializer<T> {
|
||||
|
||||
public GeometryDeserializer(Class<T> 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<T extends Geometry> extends StdDeserializer<T> {
|
||||
|
||||
public GeometryDeserializer(Class<T> 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<T extends Geometry> extends StdSerializer<T> {
|
||||
|
||||
// TODO: test code
|
||||
public GeometrySerializer(Class<T> 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<T extends Geometry> extends StdSerializer<T> {
|
||||
|
||||
// TODO: test code
|
||||
public GeometrySerializer(Class<T> 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<Object> {
|
||||
|
||||
@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<Object> {
|
||||
|
||||
@Override
|
||||
public Object deserialize(JsonParser jsonParser, DeserializationContext deserializationContext)
|
||||
throws IOException, JacksonException {
|
||||
String value = jsonParser.getValueAsString();
|
||||
return value == null ? null : HtmlUtils.htmlEscape(value);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<String> {
|
||||
@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<String> {
|
||||
@Override
|
||||
public void serialize(
|
||||
String value, JsonGenerator jsonGenerator, SerializerProvider serializerProvider)
|
||||
throws IOException {
|
||||
if (value == null) {
|
||||
jsonGenerator.writeNull();
|
||||
} else {
|
||||
jsonGenerator.writeString(HtmlUtils.htmlUnescape(value));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<? extends Payload>[] payload() default {};
|
||||
|
||||
Class<? extends Enum<?>> 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<? extends Payload>[] payload() default {};
|
||||
|
||||
Class<? extends Enum<?>> enumClass();
|
||||
}
|
||||
|
||||
@@ -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 {}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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";
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -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<Server> 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<Server> 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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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/**");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 {}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<String, String[]> 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<MenuDto.Basic> 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<String, String[]> 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<MenuDto.Basic> 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";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
*
|
||||
* <p>createOK() → 201 CREATED ok() → 200 OK deleteOk() → 204 NO_CONTENT
|
||||
*/
|
||||
@RestControllerAdvice
|
||||
public class ApiResponseAdvice implements ResponseBodyAdvice<Object> {
|
||||
|
||||
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<? extends HttpMessageConverter<?>> converterType) {
|
||||
// ApiResponseDto를 반환하는 경우에만 적용
|
||||
return returnType.getParameterType().equals(ApiResponseDto.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object beforeBodyWrite(
|
||||
Object body,
|
||||
MethodParameter returnType,
|
||||
MediaType selectedContentType,
|
||||
Class<? extends HttpMessageConverter<?>> 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
|
||||
*
|
||||
* <p>createOK() → 201 CREATED ok() → 200 OK deleteOk() → 204 NO_CONTENT
|
||||
*/
|
||||
@RestControllerAdvice
|
||||
public class ApiResponseAdvice implements ResponseBodyAdvice<Object> {
|
||||
|
||||
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<? extends HttpMessageConverter<?>> converterType) {
|
||||
// ApiResponseDto를 반환하는 경우에만 적용
|
||||
return returnType.getParameterType().equals(ApiResponseDto.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object beforeBodyWrite(
|
||||
Object body,
|
||||
MethodParameter returnType,
|
||||
MediaType selectedContentType,
|
||||
Class<? extends HttpMessageConverter<?>> 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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<T> {
|
||||
|
||||
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 <T> ApiResponseDto<T> createOK(T data) {
|
||||
return new ApiResponseDto<>(data, HttpStatus.CREATED);
|
||||
}
|
||||
|
||||
public static <T> ApiResponseDto<T> ok(T data) {
|
||||
return new ApiResponseDto<>(data, HttpStatus.OK);
|
||||
}
|
||||
|
||||
public static <T> ApiResponseDto<ResponseObj> 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 <T> ApiResponseDto<T> deleteOk(T data) {
|
||||
return new ApiResponseDto<>(data, HttpStatus.NO_CONTENT);
|
||||
}
|
||||
|
||||
public static ApiResponseDto<String> createException(ApiResponseCode code) {
|
||||
return new ApiResponseDto<>(code);
|
||||
}
|
||||
|
||||
public static ApiResponseDto<String> createException(ApiResponseCode code, String message) {
|
||||
return new ApiResponseDto<>(code, message);
|
||||
}
|
||||
|
||||
public static ApiResponseDto<String> createException(
|
||||
ApiResponseCode code, String message, HttpStatus httpStatus) {
|
||||
return new ApiResponseDto<>(code, message, httpStatus);
|
||||
}
|
||||
|
||||
public static ApiResponseDto<String> createException(
|
||||
ApiResponseCode code, String message, HttpStatus httpStatus, Long errorLogUid) {
|
||||
return new ApiResponseDto<>(code, message, httpStatus, errorLogUid);
|
||||
}
|
||||
|
||||
public static <T> ApiResponseDto<T> 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<T> {
|
||||
|
||||
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 <T> ApiResponseDto<T> createOK(T data) {
|
||||
return new ApiResponseDto<>(data, HttpStatus.CREATED);
|
||||
}
|
||||
|
||||
public static <T> ApiResponseDto<T> ok(T data) {
|
||||
return new ApiResponseDto<>(data, HttpStatus.OK);
|
||||
}
|
||||
|
||||
public static <T> ApiResponseDto<ResponseObj> 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 <T> ApiResponseDto<T> deleteOk(T data) {
|
||||
return new ApiResponseDto<>(data, HttpStatus.NO_CONTENT);
|
||||
}
|
||||
|
||||
public static ApiResponseDto<String> createException(ApiResponseCode code) {
|
||||
return new ApiResponseDto<>(code);
|
||||
}
|
||||
|
||||
public static ApiResponseDto<String> createException(ApiResponseCode code, String message) {
|
||||
return new ApiResponseDto<>(code, message);
|
||||
}
|
||||
|
||||
public static ApiResponseDto<String> createException(
|
||||
ApiResponseCode code, String message, HttpStatus httpStatus) {
|
||||
return new ApiResponseDto<>(code, message, httpStatus);
|
||||
}
|
||||
|
||||
public static ApiResponseDto<String> createException(
|
||||
ApiResponseCode code, String message, HttpStatus httpStatus, Long errorLogUid) {
|
||||
return new ApiResponseDto<>(code, message, httpStatus, errorLogUid);
|
||||
}
|
||||
|
||||
public static <T> ApiResponseDto<T> 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<Page<DatasetDto.Basic>> 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<DatasetDto.Basic> 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<Long> 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<UUID> 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<UUID> 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<DatasetDto.Summary> 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<Page<DatasetDto.Basic>> 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<DatasetDto.Basic> 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<Long> 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<UUID> 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<UUID> 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<DatasetDto.Summary> getDatasetSummary(
|
||||
@RequestBody @Valid DatasetDto.SummaryReq summaryReq) {
|
||||
return ApiResponseDto.ok(datasetService.getDatasetSummary(summaryReq));
|
||||
}
|
||||
|
||||
*/
|
||||
|
||||
}
|
||||
|
||||
@@ -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<Page<MapSheetDto.Basic>> 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<Void> 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<Page<MapSheetDto.Basic>> 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<Void> deleteMapSheets(@RequestBody @Valid MapSheetDto.DeleteReq deleteReq) {
|
||||
mapSheetService.deleteMapSheets(deleteReq);
|
||||
return ApiResponseDto.ok(null);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<Long> 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<Long> 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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<Long> 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<Long> 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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<DatasetDto.Basic> 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<DatasetDto.Basic> 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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<MapSheetDto.Basic> 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<MapSheetDto.Basic> 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());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<AuditLogDto.DailyAuditList> getLogByDaily(
|
||||
AuditLogDto.searchReq searchRange, LocalDate startDate, LocalDate endDate) {
|
||||
return auditLogCoreService.getLogByDaily(searchRange, startDate, endDate);
|
||||
}
|
||||
|
||||
public Page<AuditLogDto.MenuAuditList> getLogByMenu(
|
||||
AuditLogDto.searchReq searchRange, String searchValue) {
|
||||
return auditLogCoreService.getLogByMenu(searchRange, searchValue);
|
||||
}
|
||||
|
||||
public Page<AuditLogDto.UserAuditList> getLogByAccount(
|
||||
AuditLogDto.searchReq searchRange, String searchValue) {
|
||||
return auditLogCoreService.getLogByAccount(searchRange, searchValue);
|
||||
}
|
||||
|
||||
public Page<AuditLogDto.DailyDetail> getLogByDailyResult(
|
||||
AuditLogDto.searchReq searchRange, LocalDate logDate) {
|
||||
return auditLogCoreService.getLogByDailyResult(searchRange, logDate);
|
||||
}
|
||||
|
||||
public Page<AuditLogDto.MenuDetail> getLogByMenuResult(
|
||||
AuditLogDto.searchReq searchRange, String menuId) {
|
||||
return auditLogCoreService.getLogByMenuResult(searchRange, menuId);
|
||||
}
|
||||
|
||||
public Page<AuditLogDto.UserDetail> 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<AuditLogDto.DailyAuditList> getLogByDaily(
|
||||
AuditLogDto.searchReq searchRange, LocalDate startDate, LocalDate endDate) {
|
||||
return auditLogCoreService.getLogByDaily(searchRange, startDate, endDate);
|
||||
}
|
||||
|
||||
public Page<AuditLogDto.MenuAuditList> getLogByMenu(
|
||||
AuditLogDto.searchReq searchRange, String searchValue) {
|
||||
return auditLogCoreService.getLogByMenu(searchRange, searchValue);
|
||||
}
|
||||
|
||||
public Page<AuditLogDto.UserAuditList> getLogByAccount(
|
||||
AuditLogDto.searchReq searchRange, String searchValue) {
|
||||
return auditLogCoreService.getLogByAccount(searchRange, searchValue);
|
||||
}
|
||||
|
||||
public Page<AuditLogDto.DailyDetail> getLogByDailyResult(
|
||||
AuditLogDto.searchReq searchRange, LocalDate logDate) {
|
||||
return auditLogCoreService.getLogByDailyResult(searchRange, logDate);
|
||||
}
|
||||
|
||||
public Page<AuditLogDto.MenuDetail> getLogByMenuResult(
|
||||
AuditLogDto.searchReq searchRange, String menuId) {
|
||||
return auditLogCoreService.getLogByMenuResult(searchRange, menuId);
|
||||
}
|
||||
|
||||
public Page<AuditLogDto.UserDetail> getLogByAccountResult(
|
||||
AuditLogDto.searchReq searchRange, Long accountId) {
|
||||
return auditLogCoreService.getLogByAccountResult(searchRange, accountId);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<ErrorLogDto.Basic> 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<ErrorLogDto.Basic> findLogByError(ErrorLogDto.ErrorSearchReq searchReq) {
|
||||
return errorLogCoreService.findLogByError(searchReq);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<TokenResponse> 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<TokenResponse> 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<ResponseEntity<Object>> 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<TokenResponse> 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<TokenResponse> 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<ResponseEntity<Object>> 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());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<Page<Basic>> 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<String> 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<Page<Basic>> 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<String> resetPassword(
|
||||
@PathVariable String memberId, @RequestBody @Valid MembersDto.InitReq initReq) {
|
||||
membersService.resetPassword(memberId, initReq);
|
||||
return ApiResponseDto.createOK(memberId);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<Basic> 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<Basic> 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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<List<MenuDto.Basic>> 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<String> 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<List<MenuDto.Basic>> 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<String> refreshCommonCodeCache() {
|
||||
menuService.refresh();
|
||||
return ApiResponseDto.ok("메뉴관리 캐시가 초기화되었습니다.");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<Basic> 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<Basic> 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<Basic> 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<Basic> 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<MenuDto.Basic> 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<MenuDto.Basic> getFindAll() {
|
||||
return menuCoreService.getFindAll();
|
||||
}
|
||||
|
||||
/** 메모리 캐시 초기화 */
|
||||
@CacheEvict(value = "trainMenuFindAll", allEntries = true)
|
||||
public void refresh() {}
|
||||
}
|
||||
|
||||
@@ -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<Page<Basic>> 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<ModelMngDto.Detail> 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<List<ModelMngDto.TrainListRes>> 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<ModelMngDto.FormConfigRes> 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<String> 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<ModelMngDto.HyperParamInfo> 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<Void> 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<ModelMngDto.TrainStartRes> 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<Void> 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<ModelMngDto.ResumeInfo> 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<ModelMngDto.ResumeResponse> 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<ModelMngDto.BestEpochResponse> 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<List<ModelMngDto.EpochMetric>> 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<Page<Basic>> 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<ModelMngDto.Detail> 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<List<ModelMngDto.TrainListRes>> 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<ModelMngDto.FormConfigRes> 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<String> 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<ModelMngDto.HyperParamInfo> 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<Void> 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<ModelMngDto.TrainStartRes> 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<Void> 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<ModelMngDto.ResumeInfo> 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<ModelMngDto.ResumeResponse> 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<ModelMngDto.BestEpochResponse> 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<List<ModelMngDto.EpochMetric>> getEpochMetrics(
|
||||
@Parameter(description = "모델 UUID") @PathVariable String uuid) {
|
||||
return ApiResponseDto.ok(modelTrainService.getEpochMetrics(uuid));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<Basic> 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<Basic> 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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<ModelMngDto.TrainListRes> 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<ModelMngDto.HyperParamInfo> hyperParams = hyperParamCoreService.findAllActiveHyperParams();
|
||||
|
||||
// 4. 데이터셋 목록
|
||||
List<ModelMngDto.DatasetInfo> 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<Long> 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<ModelMngDto.EpochMetric> getEpochMetrics(String uuid) {
|
||||
ModelTrainMasterEntity entity = modelMngCoreService.findByUuid(uuid);
|
||||
|
||||
// TODO: 실제 학습 로그 파일이나 DB에서 Epoch별 성능 지표 조회
|
||||
// 현재는 샘플 데이터 반환
|
||||
List<ModelMngDto.EpochMetric> 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<ModelMngDto.TrainListRes> 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<ModelMngDto.HyperParamInfo> hyperParams = hyperParamCoreService.findAllActiveHyperParams();
|
||||
|
||||
// 4. 데이터셋 목록
|
||||
List<ModelMngDto.DatasetInfo> 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<Long> 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<ModelMngDto.EpochMetric> getEpochMetrics(String uuid) {
|
||||
ModelTrainMasterEntity entity = modelMngCoreService.findByUuid(uuid);
|
||||
|
||||
// TODO: 실제 학습 로그 파일이나 DB에서 Epoch별 성능 지표 조회
|
||||
// 현재는 샘플 데이터 반환
|
||||
List<ModelMngDto.EpochMetric> 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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 <T> OrderSpecifier<?>[] getOrderSpecifiers(
|
||||
Pageable pageable, Class<T> entityClass, String alias) {
|
||||
PathBuilder<T> 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 <T> OrderSpecifier<?>[] getOrderSpecifiers(
|
||||
Pageable pageable, Class<T> entityClass, String alias) {
|
||||
PathBuilder<T> 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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<AnimalDto.Basic, Long, AnimalDto.SearchReq> {
|
||||
//
|
||||
// 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<AnimalDto.Basic> search(AnimalDto.SearchReq searchReq) {
|
||||
//
|
||||
// Page<AnimalEntity> 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<AnimalDto.Basic, Long, AnimalDto.SearchReq> {
|
||||
//
|
||||
// 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<AnimalDto.Basic> search(AnimalDto.SearchReq searchReq) {
|
||||
//
|
||||
// Page<AnimalEntity> animalEntities = animalRepository.listAnimal(searchReq);
|
||||
// return animalEntities.map(AnimalEntity::toDto);
|
||||
// }
|
||||
// }
|
||||
|
||||
@@ -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<AuditLogDto.DailyAuditList, Long, AuditLogDto.searchReq> {
|
||||
|
||||
private final AuditLogRepository auditLogRepository;
|
||||
|
||||
@Override
|
||||
public void remove(Long aLong) {}
|
||||
|
||||
@Override
|
||||
public AuditLogDto.DailyAuditList getOneById(Long aLong) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Page<AuditLogDto.DailyAuditList> search(AuditLogDto.searchReq searchReq) {
|
||||
return null;
|
||||
}
|
||||
|
||||
public Page<AuditLogDto.DailyAuditList> getLogByDaily(
|
||||
AuditLogDto.searchReq searchRange, LocalDate startDate, LocalDate endDate) {
|
||||
return auditLogRepository.findLogByDaily(searchRange, startDate, endDate);
|
||||
}
|
||||
|
||||
public Page<AuditLogDto.MenuAuditList> getLogByMenu(
|
||||
AuditLogDto.searchReq searchRange, String searchValue) {
|
||||
return auditLogRepository.findLogByMenu(searchRange, searchValue);
|
||||
}
|
||||
|
||||
public Page<AuditLogDto.UserAuditList> getLogByAccount(
|
||||
AuditLogDto.searchReq searchRange, String searchValue) {
|
||||
return auditLogRepository.findLogByAccount(searchRange, searchValue);
|
||||
}
|
||||
|
||||
public Page<AuditLogDto.DailyDetail> getLogByDailyResult(
|
||||
AuditLogDto.searchReq searchRange, LocalDate logDate) {
|
||||
return auditLogRepository.findLogByDailyResult(searchRange, logDate);
|
||||
}
|
||||
|
||||
public Page<AuditLogDto.MenuDetail> getLogByMenuResult(
|
||||
AuditLogDto.searchReq searchRange, String menuId) {
|
||||
return auditLogRepository.findLogByMenuResult(searchRange, menuId);
|
||||
}
|
||||
|
||||
public Page<AuditLogDto.UserDetail> 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<AuditLogDto.DailyAuditList, Long, AuditLogDto.searchReq> {
|
||||
|
||||
private final AuditLogRepository auditLogRepository;
|
||||
|
||||
@Override
|
||||
public void remove(Long aLong) {}
|
||||
|
||||
@Override
|
||||
public AuditLogDto.DailyAuditList getOneById(Long aLong) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Page<AuditLogDto.DailyAuditList> search(AuditLogDto.searchReq searchReq) {
|
||||
return null;
|
||||
}
|
||||
|
||||
public Page<AuditLogDto.DailyAuditList> getLogByDaily(
|
||||
AuditLogDto.searchReq searchRange, LocalDate startDate, LocalDate endDate) {
|
||||
return auditLogRepository.findLogByDaily(searchRange, startDate, endDate);
|
||||
}
|
||||
|
||||
public Page<AuditLogDto.MenuAuditList> getLogByMenu(
|
||||
AuditLogDto.searchReq searchRange, String searchValue) {
|
||||
return auditLogRepository.findLogByMenu(searchRange, searchValue);
|
||||
}
|
||||
|
||||
public Page<AuditLogDto.UserAuditList> getLogByAccount(
|
||||
AuditLogDto.searchReq searchRange, String searchValue) {
|
||||
return auditLogRepository.findLogByAccount(searchRange, searchValue);
|
||||
}
|
||||
|
||||
public Page<AuditLogDto.DailyDetail> getLogByDailyResult(
|
||||
AuditLogDto.searchReq searchRange, LocalDate logDate) {
|
||||
return auditLogRepository.findLogByDailyResult(searchRange, logDate);
|
||||
}
|
||||
|
||||
public Page<AuditLogDto.MenuDetail> getLogByMenuResult(
|
||||
AuditLogDto.searchReq searchRange, String menuId) {
|
||||
return auditLogRepository.findLogByMenuResult(searchRange, menuId);
|
||||
}
|
||||
|
||||
public Page<AuditLogDto.UserDetail> getLogByAccountResult(
|
||||
AuditLogDto.searchReq searchRange, Long accountId) {
|
||||
return auditLogRepository.findLogByAccountResult(searchRange, accountId);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<CommonCodeDto.Basic, Long, SearchReq> {
|
||||
|
||||
private final CommonCodeRepository commonCodeRepository;
|
||||
|
||||
/**
|
||||
* 모두 조회
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
// @Cacheable(value = "commonCodes")
|
||||
public List<CommonCodeDto.Basic> 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<CommonCodeDto.Basic> findByCode(String code) {
|
||||
return commonCodeRepository.findByCode(code).stream().map(CommonCodeEntity::toDto).toList();
|
||||
}
|
||||
|
||||
/**
|
||||
* 공통코드 이름 조회
|
||||
*
|
||||
* @param parentCodeCd
|
||||
* @param childCodeCd
|
||||
* @return
|
||||
*/
|
||||
public Optional<String> 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,
|
||||
"하위에 다른 공통코드를 가지고 있습니다.<br/>하위공통 코드를 이동한 후 삭제할 수 있습니다.");
|
||||
}
|
||||
|
||||
// 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<Basic> 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<CommonCodeDto.Basic, Long, SearchReq> {
|
||||
|
||||
private final CommonCodeRepository commonCodeRepository;
|
||||
|
||||
/**
|
||||
* 모두 조회
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
// @Cacheable(value = "commonCodes")
|
||||
public List<CommonCodeDto.Basic> 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<CommonCodeDto.Basic> findByCode(String code) {
|
||||
return commonCodeRepository.findByCode(code).stream().map(CommonCodeEntity::toDto).toList();
|
||||
}
|
||||
|
||||
/**
|
||||
* 공통코드 이름 조회
|
||||
*
|
||||
* @param parentCodeCd
|
||||
* @param childCodeCd
|
||||
* @return
|
||||
*/
|
||||
public Optional<String> 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,
|
||||
"하위에 다른 공통코드를 가지고 있습니다.<br/>하위공통 코드를 이동한 후 삭제할 수 있습니다.");
|
||||
}
|
||||
|
||||
// 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<Basic> 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, "");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<DatasetDto.Basic, Long, DatasetDto.SearchReq> {
|
||||
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<DatasetDto.Basic> search(DatasetDto.SearchReq searchReq) {
|
||||
Page<DatasetEntity> entityPage = datasetRepository.findDatasetList(searchReq);
|
||||
return entityPage.map(DatasetEntity::toDto);
|
||||
}
|
||||
|
||||
/**
|
||||
* 학습데이터 조회
|
||||
*
|
||||
* @param searchReq 검색 조건
|
||||
* @return 페이징 처리된 데이터셋 목록
|
||||
*/
|
||||
public Page<DatasetDto.Basic> 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<ModelMngDto.DatasetInfo> findAllActiveDatasetsForTraining() {
|
||||
List<DatasetEntity> entities = datasetRepository.findByDeletedOrderByCreatedDttmDesc(false);
|
||||
|
||||
return entities.stream()
|
||||
.map(
|
||||
entity -> {
|
||||
// totalSize를 읽기 쉬운 형식으로 변환
|
||||
String totalSizeStr = formatFileSize(entity.getTotalSize());
|
||||
|
||||
// classCounts JSON 파싱
|
||||
Map<String, Integer> 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<String, Integer> parseClassCounts(String jsonStr) {
|
||||
if (jsonStr == null || jsonStr.trim().isEmpty()) {
|
||||
return new HashMap<>();
|
||||
}
|
||||
|
||||
try {
|
||||
return objectMapper.readValue(jsonStr, new TypeReference<Map<String, Integer>>() {});
|
||||
} catch (Exception e) {
|
||||
log.warn("클래스 통계 JSON 파싱 실패: {}", jsonStr, e);
|
||||
return new HashMap<>();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 데이터셋의 클래스 통계 계산 및 저장
|
||||
*
|
||||
* @param datasetId 데이터셋 ID
|
||||
* @param classCounts 클래스별 카운트
|
||||
*/
|
||||
public void updateClassCounts(Long datasetId, Map<String, Integer> 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<DatasetDto.Basic, Long, DatasetDto.SearchReq> {
|
||||
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<DatasetDto.Basic> search(DatasetDto.SearchReq searchReq) {
|
||||
Page<DatasetEntity> entityPage = datasetRepository.findDatasetList(searchReq);
|
||||
return entityPage.map(DatasetEntity::toDto);
|
||||
}
|
||||
|
||||
/**
|
||||
* 학습데이터 조회
|
||||
*
|
||||
* @param searchReq 검색 조건
|
||||
* @return 페이징 처리된 데이터셋 목록
|
||||
*/
|
||||
public Page<DatasetDto.Basic> 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<ModelMngDto.DatasetInfo> findAllActiveDatasetsForTraining() {
|
||||
List<DatasetEntity> entities = datasetRepository.findByDeletedOrderByCreatedDttmDesc(false);
|
||||
|
||||
return entities.stream()
|
||||
.map(
|
||||
entity -> {
|
||||
// totalSize를 읽기 쉬운 형식으로 변환
|
||||
String totalSizeStr = formatFileSize(entity.getTotalSize());
|
||||
|
||||
// classCounts JSON 파싱
|
||||
Map<String, Integer> 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<String, Integer> parseClassCounts(String jsonStr) {
|
||||
if (jsonStr == null || jsonStr.trim().isEmpty()) {
|
||||
return new HashMap<>();
|
||||
}
|
||||
|
||||
try {
|
||||
return objectMapper.readValue(jsonStr, new TypeReference<Map<String, Integer>>() {});
|
||||
} catch (Exception e) {
|
||||
log.warn("클래스 통계 JSON 파싱 실패: {}", jsonStr, e);
|
||||
return new HashMap<>();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 데이터셋의 클래스 통계 계산 및 저장
|
||||
*
|
||||
* @param datasetId 데이터셋 ID
|
||||
* @param classCounts 클래스별 카운트
|
||||
*/
|
||||
public void updateClassCounts(Long datasetId, Map<String, Integer> 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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<ErrorLogDto.Basic, Long, ErrorLogDto.ErrorSearchReq> {
|
||||
|
||||
private final ErrorLogRepository errorLogRepository;
|
||||
|
||||
public Page<ErrorLogDto.Basic> 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<ErrorLogDto.Basic> 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<ErrorLogDto.Basic, Long, ErrorLogDto.ErrorSearchReq> {
|
||||
|
||||
private final ErrorLogRepository errorLogRepository;
|
||||
|
||||
public Page<ErrorLogDto.Basic> 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<ErrorLogDto.Basic> search(ErrorLogDto.ErrorSearchReq searchReq) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<ModelMngDto.HyperParamInfo> findAllActiveHyperParams() {
|
||||
List<ModelHyperParamEntity> 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<ModelHyperParamEntity> 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<ModelMngDto.HyperParamInfo> findAllActiveHyperParams() {
|
||||
List<ModelHyperParamEntity> 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<ModelHyperParamEntity> entities =
|
||||
hyperParamRepository.findByDelYnOrderByCreatedDttmDesc("N");
|
||||
if (entities.isEmpty()) {
|
||||
throw new NotFoundException("하이퍼파라미터가 존재하지 않습니다.");
|
||||
}
|
||||
// 가장 오래된 것이 H1이므로 리스트의 마지막 요소 반환
|
||||
return entities.get(entities.size() - 1).getHyperVer();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<MapSheetDto.Basic, Long, MapSheetDto.SearchReq> {
|
||||
|
||||
private final MapSheetRepository mapSheetRepository;
|
||||
|
||||
@Override
|
||||
public void remove(Long id) {
|
||||
Optional<MapSheetEntity> 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<MapSheetDto.Basic> search(MapSheetDto.SearchReq searchReq) {
|
||||
Page<MapSheetEntity> entityPage = mapSheetRepository.findMapSheetList(searchReq);
|
||||
return entityPage.map(MapSheetEntity::toDto);
|
||||
}
|
||||
|
||||
/**
|
||||
* 도엽 목록 조회
|
||||
*
|
||||
* @param searchReq 검색 조건
|
||||
* @return 페이징 처리된 도엽 목록
|
||||
*/
|
||||
public Page<MapSheetDto.Basic> 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<MapSheetDto.Basic, Long, MapSheetDto.SearchReq> {
|
||||
|
||||
private final MapSheetRepository mapSheetRepository;
|
||||
|
||||
@Override
|
||||
public void remove(Long id) {
|
||||
Optional<MapSheetEntity> 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<MapSheetDto.Basic> search(MapSheetDto.SearchReq searchReq) {
|
||||
Page<MapSheetEntity> entityPage = mapSheetRepository.findMapSheetList(searchReq);
|
||||
return entityPage.map(MapSheetEntity::toDto);
|
||||
}
|
||||
|
||||
/**
|
||||
* 도엽 목록 조회
|
||||
*
|
||||
* @param searchReq 검색 조건
|
||||
* @return 페이징 처리된 도엽 목록
|
||||
*/
|
||||
public Page<MapSheetDto.Basic> findMapSheetList(MapSheetDto.SearchReq searchReq) {
|
||||
return search(searchReq);
|
||||
}
|
||||
|
||||
/**
|
||||
* 도엽 삭제 (다건)
|
||||
*
|
||||
* @param deleteReq 삭제 요청
|
||||
*/
|
||||
public void deleteMapSheets(MapSheetDto.DeleteReq deleteReq) {
|
||||
for (Long id : deleteReq.getItemIds()) {
|
||||
remove(id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<Basic> findByMembers(MembersDto.SearchReq searchReq) {
|
||||
Page<MemberEntity> 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<Basic> findByMembers(MembersDto.SearchReq searchReq) {
|
||||
Page<MemberEntity> 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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<MenuDto.Basic> 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<MenuDto.Basic> getFindAll() {
|
||||
return menuRepository.getFindAll().stream().map(MenuEntity::toDto).toList();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<Basic> findByModels(ModelMngDto.SearchReq searchReq) {
|
||||
Page<ModelTrainMasterEntity> 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<ModelMngDto.TrainListRes> findAllTrainModels() {
|
||||
List<ModelTrainMasterEntity> 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<Long> 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<Basic> findByModels(ModelMngDto.SearchReq searchReq) {
|
||||
Page<ModelTrainMasterEntity> 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<ModelMngDto.TrainListRes> findAllTrainModels() {
|
||||
List<ModelTrainMasterEntity> 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<Long> 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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user