init spotless 적용

This commit is contained in:
2026-02-02 15:48:23 +09:00
parent 495ef7d86c
commit a1ffad1c4e
153 changed files with 12870 additions and 12931 deletions

View File

@@ -1,14 +1,14 @@
package com.kamco.cd.training; package com.kamco.cd.training;
import org.springframework.boot.SpringApplication; import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.scheduling.annotation.EnableScheduling; import org.springframework.scheduling.annotation.EnableScheduling;
@SpringBootApplication @SpringBootApplication
@EnableScheduling @EnableScheduling
public class KamcoTrainingApplication { public class KamcoTrainingApplication {
public static void main(String[] args) { public static void main(String[] args) {
SpringApplication.run(KamcoTrainingApplication.class, args); SpringApplication.run(KamcoTrainingApplication.class, args);
} }
} }

View File

@@ -1,22 +1,22 @@
package com.kamco.cd.training.auth; package com.kamco.cd.training.auth;
import java.security.SecureRandom; import java.security.SecureRandom;
import java.util.Base64; import java.util.Base64;
public class BCryptSaltGenerator { public class BCryptSaltGenerator {
public static String generateSaltWithEmployeeNo(String employeeNo) { public static String generateSaltWithEmployeeNo(String employeeNo) {
// bcrypt salt는 16바이트(128비트) 필요 // bcrypt salt는 16바이트(128비트) 필요
byte[] randomBytes = new byte[16]; byte[] randomBytes = new byte[16];
new SecureRandom().nextBytes(randomBytes); new SecureRandom().nextBytes(randomBytes);
String base64 = Base64.getEncoder().encodeToString(randomBytes); String base64 = Base64.getEncoder().encodeToString(randomBytes);
// 사번을 포함 (22자 제한 → 잘라내기) // 사번을 포함 (22자 제한 → 잘라내기)
String mixedSalt = (employeeNo + base64).substring(0, 22); String mixedSalt = (employeeNo + base64).substring(0, 22);
// bcrypt 포맷에 맞게 구성 // bcrypt 포맷에 맞게 구성
return "$2a$10$" + mixedSalt; return "$2a$10$" + mixedSalt;
} }
} }

View File

@@ -1,62 +1,62 @@
package com.kamco.cd.training.auth; package com.kamco.cd.training.auth;
import com.kamco.cd.training.common.enums.StatusType; import com.kamco.cd.training.common.enums.StatusType;
import com.kamco.cd.training.common.enums.error.AuthErrorCode; import com.kamco.cd.training.common.enums.error.AuthErrorCode;
import com.kamco.cd.training.common.exception.CustomApiException; import com.kamco.cd.training.common.exception.CustomApiException;
import com.kamco.cd.training.postgres.entity.MemberEntity; import com.kamco.cd.training.postgres.entity.MemberEntity;
import com.kamco.cd.training.postgres.repository.members.MembersRepository; import com.kamco.cd.training.postgres.repository.members.MembersRepository;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import org.mindrot.jbcrypt.BCrypt; import org.mindrot.jbcrypt.BCrypt;
import org.springframework.security.authentication.AuthenticationProvider; import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication; import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException; import org.springframework.security.core.AuthenticationException;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
@Component @Component
@RequiredArgsConstructor @RequiredArgsConstructor
public class CustomAuthenticationProvider implements AuthenticationProvider { public class CustomAuthenticationProvider implements AuthenticationProvider {
private final MembersRepository membersRepository; private final MembersRepository membersRepository;
@Override @Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException { public Authentication authenticate(Authentication authentication) throws AuthenticationException {
String username = authentication.getName(); String username = authentication.getName();
String rawPassword = authentication.getCredentials().toString(); String rawPassword = authentication.getCredentials().toString();
// 유저 조회 // 유저 조회
MemberEntity member = MemberEntity member =
membersRepository membersRepository
.findByEmployeeNo(username) .findByEmployeeNo(username)
.orElseThrow(() -> new CustomApiException(AuthErrorCode.LOGIN_ID_NOT_FOUND)); .orElseThrow(() -> new CustomApiException(AuthErrorCode.LOGIN_ID_NOT_FOUND));
// 미사용 상태 // 미사용 상태
if (member.getStatus().equals(StatusType.INACTIVE.getId())) { if (member.getStatus().equals(StatusType.INACTIVE.getId())) {
throw new CustomApiException(AuthErrorCode.LOGIN_ID_NOT_FOUND); throw new CustomApiException(AuthErrorCode.LOGIN_ID_NOT_FOUND);
} }
// jBCrypt + 커스텀 salt 로 저장된 패스워드 비교 // jBCrypt + 커스텀 salt 로 저장된 패스워드 비교
if (!BCrypt.checkpw(rawPassword, member.getPassword())) { if (!BCrypt.checkpw(rawPassword, member.getPassword())) {
// 실패 카운트 저장 // 실패 카운트 저장
int cnt = member.getLoginFailCount() + 1; int cnt = member.getLoginFailCount() + 1;
member.setLoginFailCount(cnt); member.setLoginFailCount(cnt);
membersRepository.save(member); membersRepository.save(member);
throw new CustomApiException(AuthErrorCode.LOGIN_PASSWORD_MISMATCH); throw new CustomApiException(AuthErrorCode.LOGIN_PASSWORD_MISMATCH);
} }
// 로그인 실패 체크 // 로그인 실패 체크
if (member.getLoginFailCount() >= 5) { if (member.getLoginFailCount() >= 5) {
throw new CustomApiException(AuthErrorCode.LOGIN_PASSWORD_EXCEEDED); throw new CustomApiException(AuthErrorCode.LOGIN_PASSWORD_EXCEEDED);
} }
// 인증 성공 → UserDetails 생성 // 인증 성공 → UserDetails 생성
CustomUserDetails userDetails = new CustomUserDetails(member); CustomUserDetails userDetails = new CustomUserDetails(member);
return new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities()); return new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());
} }
@Override @Override
public boolean supports(Class<?> authentication) { public boolean supports(Class<?> authentication) {
return UsernamePasswordAuthenticationToken.class.isAssignableFrom(authentication); return UsernamePasswordAuthenticationToken.class.isAssignableFrom(authentication);
} }
} }

View File

@@ -1,56 +1,56 @@
package com.kamco.cd.training.auth; package com.kamco.cd.training.auth;
import com.kamco.cd.training.postgres.entity.MemberEntity; import com.kamco.cd.training.postgres.entity.MemberEntity;
import java.util.Collection; import java.util.Collection;
import java.util.List; import java.util.List;
import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority; import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.core.userdetails.UserDetails;
public class CustomUserDetails implements UserDetails { public class CustomUserDetails implements UserDetails {
private final MemberEntity member; private final MemberEntity member;
public CustomUserDetails(MemberEntity member) { public CustomUserDetails(MemberEntity member) {
this.member = member; this.member = member;
} }
@Override @Override
public Collection<? extends GrantedAuthority> getAuthorities() { public Collection<? extends GrantedAuthority> getAuthorities() {
return List.of(new SimpleGrantedAuthority("ROLE_" + member.getUserRole())); return List.of(new SimpleGrantedAuthority("ROLE_" + member.getUserRole()));
} }
@Override @Override
public String getPassword() { public String getPassword() {
return member.getPassword(); return member.getPassword();
} }
@Override @Override
public String getUsername() { public String getUsername() {
return String.valueOf(member.getUuid()); return String.valueOf(member.getUuid());
} }
@Override @Override
public boolean isAccountNonExpired() { public boolean isAccountNonExpired() {
return true; // 추후 상태 필드에 따라 수정 가능 return true; // 추후 상태 필드에 따라 수정 가능
} }
@Override @Override
public boolean isAccountNonLocked() { public boolean isAccountNonLocked() {
return true; return true;
} }
@Override @Override
public boolean isCredentialsNonExpired() { public boolean isCredentialsNonExpired() {
return true; return true;
} }
@Override @Override
public boolean isEnabled() { public boolean isEnabled() {
return "ACTIVE".equals(member.getStatus()); return "ACTIVE".equals(member.getStatus());
} }
public MemberEntity getMember() { public MemberEntity getMember() {
return member; return member;
} }
} }

View File

@@ -1,70 +1,70 @@
package com.kamco.cd.training.auth; package com.kamco.cd.training.auth;
import jakarta.servlet.FilterChain; import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException; import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse; import jakarta.servlet.http.HttpServletResponse;
import java.io.IOException; import java.io.IOException;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import org.springframework.lang.NonNull; import org.springframework.lang.NonNull;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import org.springframework.util.AntPathMatcher; import org.springframework.util.AntPathMatcher;
import org.springframework.web.filter.OncePerRequestFilter; import org.springframework.web.filter.OncePerRequestFilter;
@Component @Component
@RequiredArgsConstructor @RequiredArgsConstructor
public class JwtAuthenticationFilter extends OncePerRequestFilter { public class JwtAuthenticationFilter extends OncePerRequestFilter {
private final JwtTokenProvider jwtTokenProvider; private final JwtTokenProvider jwtTokenProvider;
private final UserDetailsService userDetailsService; private final UserDetailsService userDetailsService;
private static final AntPathMatcher PATH_MATCHER = new AntPathMatcher(); private static final AntPathMatcher PATH_MATCHER = new AntPathMatcher();
private static final String[] EXCLUDE_PATHS = { private static final String[] EXCLUDE_PATHS = {
"/api/auth/signin", "/api/auth/refresh", "/api/auth/logout", "/api/members/*/password" "/api/auth/signin", "/api/auth/refresh", "/api/auth/logout", "/api/members/*/password"
}; };
@Override @Override
protected void doFilterInternal( protected void doFilterInternal(
@NonNull HttpServletRequest request, @NonNull HttpServletRequest request,
@NonNull HttpServletResponse response, @NonNull HttpServletResponse response,
@NonNull FilterChain filterChain) @NonNull FilterChain filterChain)
throws ServletException, IOException { throws ServletException, IOException {
String token = resolveToken(request); String token = resolveToken(request);
if (token != null && jwtTokenProvider.isValidToken(token)) { if (token != null && jwtTokenProvider.isValidToken(token)) {
String username = jwtTokenProvider.getSubject(token); String username = jwtTokenProvider.getSubject(token);
UserDetails userDetails = userDetailsService.loadUserByUsername(username); UserDetails userDetails = userDetailsService.loadUserByUsername(username);
UsernamePasswordAuthenticationToken authentication = UsernamePasswordAuthenticationToken authentication =
new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities()); new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());
SecurityContextHolder.getContext().setAuthentication(authentication); SecurityContextHolder.getContext().setAuthentication(authentication);
} }
filterChain.doFilter(request, response); filterChain.doFilter(request, response);
} }
@Override @Override
protected boolean shouldNotFilter(HttpServletRequest request) { protected boolean shouldNotFilter(HttpServletRequest request) {
String path = request.getServletPath(); String path = request.getServletPath();
// JWT 필터를 타지 않게 할 URL 패턴들 // JWT 필터를 타지 않게 할 URL 패턴들
for (String pattern : EXCLUDE_PATHS) { for (String pattern : EXCLUDE_PATHS) {
if (PATH_MATCHER.match(pattern, path)) { if (PATH_MATCHER.match(pattern, path)) {
return true; return true;
} }
} }
return false; return false;
} }
private String resolveToken(HttpServletRequest request) { private String resolveToken(HttpServletRequest request) {
String bearer = request.getHeader("Authorization"); String bearer = request.getHeader("Authorization");
if (bearer != null && bearer.startsWith("Bearer ")) { if (bearer != null && bearer.startsWith("Bearer ")) {
return bearer.substring(7); return bearer.substring(7);
} }
return null; return null;
} }
} }

View File

@@ -1,72 +1,72 @@
package com.kamco.cd.training.auth; package com.kamco.cd.training.auth;
import io.jsonwebtoken.Claims; import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jws; import io.jsonwebtoken.Jws;
import io.jsonwebtoken.Jwts; import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.security.Keys; import io.jsonwebtoken.security.Keys;
import jakarta.annotation.PostConstruct; import jakarta.annotation.PostConstruct;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import java.util.Date; import java.util.Date;
import javax.crypto.SecretKey; import javax.crypto.SecretKey;
import org.springframework.beans.factory.annotation.Value; import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
@Component @Component
public class JwtTokenProvider { public class JwtTokenProvider {
@Value("${jwt.secret}") @Value("${jwt.secret}")
private String secret; private String secret;
@Value("${jwt.access-token-validity-in-ms}") @Value("${jwt.access-token-validity-in-ms}")
private long accessTokenValidityInMs; private long accessTokenValidityInMs;
@Value("${jwt.refresh-token-validity-in-ms}") @Value("${jwt.refresh-token-validity-in-ms}")
private long refreshTokenValidityInMs; private long refreshTokenValidityInMs;
private SecretKey key; private SecretKey key;
@PostConstruct @PostConstruct
public void init() { public void init() {
// HS256용 SecretKey // HS256용 SecretKey
this.key = Keys.hmacShaKeyFor(secret.getBytes(StandardCharsets.UTF_8)); this.key = Keys.hmacShaKeyFor(secret.getBytes(StandardCharsets.UTF_8));
} }
public String createAccessToken(String subject) { public String createAccessToken(String subject) {
return createToken(subject, accessTokenValidityInMs); return createToken(subject, accessTokenValidityInMs);
} }
public String createRefreshToken(String subject) { public String createRefreshToken(String subject) {
return createToken(subject, refreshTokenValidityInMs); return createToken(subject, refreshTokenValidityInMs);
} }
private String createToken(String subject, long validityInMs) { private String createToken(String subject, long validityInMs) {
Date now = new Date(); Date now = new Date();
Date expiry = new Date(now.getTime() + validityInMs); Date expiry = new Date(now.getTime() + validityInMs);
return Jwts.builder().subject(subject).issuedAt(now).expiration(expiry).signWith(key).compact(); return Jwts.builder().subject(subject).issuedAt(now).expiration(expiry).signWith(key).compact();
} }
public String getSubject(String token) { public String getSubject(String token) {
var claims = parseClaims(token).getPayload(); var claims = parseClaims(token).getPayload();
return claims.getSubject(); return claims.getSubject();
} }
public boolean isValidToken(String token) { public boolean isValidToken(String token) {
try { try {
Jws<Claims> claims = parseClaims(token); Jws<Claims> claims = parseClaims(token);
return !claims.getPayload().getExpiration().before(new Date()); return !claims.getPayload().getExpiration().before(new Date());
} catch (Exception e) { } catch (Exception e) {
return false; return false;
} }
} }
private Jws<Claims> parseClaims(String token) { private Jws<Claims> parseClaims(String token) {
return Jwts.parser() return Jwts.parser()
.verifyWith(key) // SecretKey 타입 .verifyWith(key) // SecretKey 타입
.build() .build()
.parseSignedClaims(token); .parseSignedClaims(token);
} }
public long getRefreshTokenValidityInMs() { public long getRefreshTokenValidityInMs() {
return refreshTokenValidityInMs; return refreshTokenValidityInMs;
} }
} }

View File

@@ -1,299 +1,299 @@
package com.kamco.cd.training.code; package com.kamco.cd.training.code;
import com.kamco.cd.training.code.dto.CommonCodeDto; import com.kamco.cd.training.code.dto.CommonCodeDto;
import com.kamco.cd.training.code.service.CommonCodeService; import com.kamco.cd.training.code.service.CommonCodeService;
import com.kamco.cd.training.common.utils.CommonCodeUtil; import com.kamco.cd.training.common.utils.CommonCodeUtil;
import com.kamco.cd.training.common.utils.enums.CodeDto; import com.kamco.cd.training.common.utils.enums.CodeDto;
import com.kamco.cd.training.config.api.ApiResponseDto; import com.kamco.cd.training.config.api.ApiResponseDto;
import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.media.Content; import io.swagger.v3.oas.annotations.media.Content;
import io.swagger.v3.oas.annotations.media.Schema; import io.swagger.v3.oas.annotations.media.Schema;
import io.swagger.v3.oas.annotations.responses.ApiResponse; import io.swagger.v3.oas.annotations.responses.ApiResponse;
import io.swagger.v3.oas.annotations.responses.ApiResponses; import io.swagger.v3.oas.annotations.responses.ApiResponses;
import io.swagger.v3.oas.annotations.tags.Tag; import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.validation.Valid; import jakarta.validation.Valid;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.*; import org.springframework.web.bind.annotation.*;
@Tag(name = "공통코드 관리", description = "공통코드 관리 API") @Tag(name = "공통코드 관리", description = "공통코드 관리 API")
@RestController @RestController
@RequiredArgsConstructor @RequiredArgsConstructor
@RequestMapping("/api/code") @RequestMapping("/api/code")
public class CommonCodeApiController { public class CommonCodeApiController {
private final CommonCodeService commonCodeService; private final CommonCodeService commonCodeService;
private final CommonCodeUtil commonCodeUtil; private final CommonCodeUtil commonCodeUtil;
@Operation(summary = "목록 조회", description = "모든 공통코드 조회") @Operation(summary = "목록 조회", description = "모든 공통코드 조회")
@ApiResponses( @ApiResponses(
value = { value = {
@ApiResponse( @ApiResponse(
responseCode = "200", responseCode = "200",
description = "조회 성공", description = "조회 성공",
content = content =
@Content( @Content(
mediaType = "application/json", mediaType = "application/json",
schema = @Schema(implementation = CommonCodeDto.Basic.class))), schema = @Schema(implementation = CommonCodeDto.Basic.class))),
@ApiResponse(responseCode = "404", description = "코드를 찾을 수 없음", content = @Content), @ApiResponse(responseCode = "404", description = "코드를 찾을 수 없음", content = @Content),
@ApiResponse(responseCode = "500", description = "서버 오류", content = @Content) @ApiResponse(responseCode = "500", description = "서버 오류", content = @Content)
}) })
@GetMapping @GetMapping
public ApiResponseDto<List<CommonCodeDto.Basic>> getFindAll() { public ApiResponseDto<List<CommonCodeDto.Basic>> getFindAll() {
return ApiResponseDto.createOK(commonCodeService.getFindAll()); return ApiResponseDto.createOK(commonCodeService.getFindAll());
} }
@Operation(summary = "단건 조회", description = "단건 조회") @Operation(summary = "단건 조회", description = "단건 조회")
@ApiResponses( @ApiResponses(
value = { value = {
@ApiResponse( @ApiResponse(
responseCode = "200", responseCode = "200",
description = "조회 성공", description = "조회 성공",
content = content =
@Content( @Content(
mediaType = "application/json", mediaType = "application/json",
schema = @Schema(implementation = CommonCodeDto.Basic.class))), schema = @Schema(implementation = CommonCodeDto.Basic.class))),
@ApiResponse(responseCode = "404", description = "코드를 찾을 수 없음", content = @Content), @ApiResponse(responseCode = "404", description = "코드를 찾을 수 없음", content = @Content),
@ApiResponse(responseCode = "500", description = "서버 오류", content = @Content) @ApiResponse(responseCode = "500", description = "서버 오류", content = @Content)
}) })
@GetMapping("/{id}") @GetMapping("/{id}")
public ApiResponseDto<CommonCodeDto.Basic> getOneById( public ApiResponseDto<CommonCodeDto.Basic> getOneById(
@io.swagger.v3.oas.annotations.parameters.RequestBody(description = "단건 조회", required = true) @io.swagger.v3.oas.annotations.parameters.RequestBody(description = "단건 조회", required = true)
@PathVariable @PathVariable
Long id) { Long id) {
return ApiResponseDto.ok(commonCodeService.getOneById(id)); return ApiResponseDto.ok(commonCodeService.getOneById(id));
} }
@Operation(summary = "저장", description = "공통코드를 저장 합니다.") @Operation(summary = "저장", description = "공통코드를 저장 합니다.")
@ApiResponses( @ApiResponses(
value = { value = {
@ApiResponse( @ApiResponse(
responseCode = "201", responseCode = "201",
description = "공통코드 저장 성공", description = "공통코드 저장 성공",
content = content =
@Content( @Content(
mediaType = "application/json", mediaType = "application/json",
schema = @Schema(implementation = Long.class))), schema = @Schema(implementation = Long.class))),
@ApiResponse(responseCode = "400", description = "잘못된 요청 데이터", content = @Content), @ApiResponse(responseCode = "400", description = "잘못된 요청 데이터", content = @Content),
@ApiResponse(responseCode = "404", description = "코드를 찾을 수 없음", content = @Content), @ApiResponse(responseCode = "404", description = "코드를 찾을 수 없음", content = @Content),
@ApiResponse(responseCode = "500", description = "서버 오류", content = @Content) @ApiResponse(responseCode = "500", description = "서버 오류", content = @Content)
}) })
@PostMapping @PostMapping
public ApiResponseDto<ApiResponseDto.ResponseObj> save( public ApiResponseDto<ApiResponseDto.ResponseObj> save(
@io.swagger.v3.oas.annotations.parameters.RequestBody( @io.swagger.v3.oas.annotations.parameters.RequestBody(
description = "공통코드 생성 요청 정보", description = "공통코드 생성 요청 정보",
required = true, required = true,
content = content =
@Content( @Content(
mediaType = "application/json", mediaType = "application/json",
schema = @Schema(implementation = CommonCodeDto.AddReq.class))) schema = @Schema(implementation = CommonCodeDto.AddReq.class)))
@RequestBody @RequestBody
@Valid @Valid
CommonCodeDto.AddReq req) { CommonCodeDto.AddReq req) {
return ApiResponseDto.okObject(commonCodeService.save(req)); return ApiResponseDto.okObject(commonCodeService.save(req));
} }
@Operation(summary = "수정", description = "공통코드를 수정 합니다.") @Operation(summary = "수정", description = "공통코드를 수정 합니다.")
@ApiResponses( @ApiResponses(
value = { value = {
@ApiResponse( @ApiResponse(
responseCode = "204", responseCode = "204",
description = "공통코드 수정 성공", description = "공통코드 수정 성공",
content = content =
@Content( @Content(
mediaType = "application/json", mediaType = "application/json",
schema = @Schema(implementation = Long.class))), schema = @Schema(implementation = Long.class))),
@ApiResponse(responseCode = "400", description = "잘못된 요청 데이터", content = @Content), @ApiResponse(responseCode = "400", description = "잘못된 요청 데이터", content = @Content),
@ApiResponse(responseCode = "404", description = "코드를 찾을 수 없음", content = @Content), @ApiResponse(responseCode = "404", description = "코드를 찾을 수 없음", content = @Content),
@ApiResponse(responseCode = "500", description = "서버 오류", content = @Content) @ApiResponse(responseCode = "500", description = "서버 오류", content = @Content)
}) })
@PutMapping("/{id}") @PutMapping("/{id}")
public ApiResponseDto<ApiResponseDto.ResponseObj> update( public ApiResponseDto<ApiResponseDto.ResponseObj> update(
@io.swagger.v3.oas.annotations.parameters.RequestBody( @io.swagger.v3.oas.annotations.parameters.RequestBody(
description = "공통코드 수정 요청 정보", description = "공통코드 수정 요청 정보",
required = true, required = true,
content = content =
@Content( @Content(
mediaType = "application/json", mediaType = "application/json",
schema = @Schema(implementation = CommonCodeDto.ModifyReq.class))) schema = @Schema(implementation = CommonCodeDto.ModifyReq.class)))
@PathVariable @PathVariable
Long id, Long id,
@RequestBody @Valid CommonCodeDto.ModifyReq req) { @RequestBody @Valid CommonCodeDto.ModifyReq req) {
return ApiResponseDto.okObject(commonCodeService.update(id, req)); return ApiResponseDto.okObject(commonCodeService.update(id, req));
} }
@Operation(summary = "삭제", description = "공통코드를 삭제 합니다.") @Operation(summary = "삭제", description = "공통코드를 삭제 합니다.")
@ApiResponses( @ApiResponses(
value = { value = {
@ApiResponse( @ApiResponse(
responseCode = "204", responseCode = "204",
description = "공통코드 삭제 성공", description = "공통코드 삭제 성공",
content = content =
@Content( @Content(
mediaType = "application/json", mediaType = "application/json",
schema = @Schema(implementation = Long.class))), schema = @Schema(implementation = Long.class))),
@ApiResponse(responseCode = "400", description = "잘못된 요청 데이터", content = @Content), @ApiResponse(responseCode = "400", description = "잘못된 요청 데이터", content = @Content),
@ApiResponse(responseCode = "404", description = "코드를 찾을 수 없음", content = @Content), @ApiResponse(responseCode = "404", description = "코드를 찾을 수 없음", content = @Content),
@ApiResponse(responseCode = "500", description = "서버 오류", content = @Content) @ApiResponse(responseCode = "500", description = "서버 오류", content = @Content)
}) })
@DeleteMapping("/{id}") @DeleteMapping("/{id}")
public ApiResponseDto<ApiResponseDto.ResponseObj> remove( public ApiResponseDto<ApiResponseDto.ResponseObj> remove(
@io.swagger.v3.oas.annotations.parameters.RequestBody( @io.swagger.v3.oas.annotations.parameters.RequestBody(
description = "공통코드 삭제 요청 정보", description = "공통코드 삭제 요청 정보",
required = true) required = true)
@PathVariable @PathVariable
Long id) { Long id) {
return ApiResponseDto.okObject(commonCodeService.removeCode(id)); return ApiResponseDto.okObject(commonCodeService.removeCode(id));
} }
@Operation(summary = "순서 변경", description = "공통코드 순서를 변경 합니다.") @Operation(summary = "순서 변경", description = "공통코드 순서를 변경 합니다.")
@ApiResponses( @ApiResponses(
value = { value = {
@ApiResponse( @ApiResponse(
responseCode = "204", responseCode = "204",
description = "공통코드 순서 변경 성공", description = "공통코드 순서 변경 성공",
content = content =
@Content( @Content(
mediaType = "application/json", mediaType = "application/json",
schema = @Schema(implementation = Long.class))), schema = @Schema(implementation = Long.class))),
@ApiResponse(responseCode = "400", description = "잘못된 요청 데이터", content = @Content), @ApiResponse(responseCode = "400", description = "잘못된 요청 데이터", content = @Content),
@ApiResponse(responseCode = "404", description = "코드를 찾을 수 없음", content = @Content), @ApiResponse(responseCode = "404", description = "코드를 찾을 수 없음", content = @Content),
@ApiResponse(responseCode = "500", description = "서버 오류", content = @Content) @ApiResponse(responseCode = "500", description = "서버 오류", content = @Content)
}) })
@PutMapping("/order") @PutMapping("/order")
public ApiResponseDto<ApiResponseDto.ResponseObj> updateOrder( public ApiResponseDto<ApiResponseDto.ResponseObj> updateOrder(
@io.swagger.v3.oas.annotations.parameters.RequestBody( @io.swagger.v3.oas.annotations.parameters.RequestBody(
description = "공통코드 순서변경 요청 정보", description = "공통코드 순서변경 요청 정보",
required = true, required = true,
content = content =
@Content( @Content(
mediaType = "application/json", mediaType = "application/json",
schema = @Schema(implementation = CommonCodeDto.OrderReq.class))) schema = @Schema(implementation = CommonCodeDto.OrderReq.class)))
@RequestBody @RequestBody
@Valid @Valid
CommonCodeDto.OrderReq req) { CommonCodeDto.OrderReq req) {
return ApiResponseDto.okObject(commonCodeService.updateOrder(req)); return ApiResponseDto.okObject(commonCodeService.updateOrder(req));
} }
@Operation(summary = "code 기반 조회", description = "code 기반 조회") @Operation(summary = "code 기반 조회", description = "code 기반 조회")
@ApiResponses( @ApiResponses(
value = { value = {
@ApiResponse( @ApiResponse(
responseCode = "200", responseCode = "200",
description = "code 기반 조회 성공", description = "code 기반 조회 성공",
content = content =
@Content( @Content(
mediaType = "application/json", mediaType = "application/json",
schema = @Schema(implementation = Long.class))), schema = @Schema(implementation = Long.class))),
@ApiResponse(responseCode = "400", description = "잘못된 요청 데이터", content = @Content), @ApiResponse(responseCode = "400", description = "잘못된 요청 데이터", content = @Content),
@ApiResponse(responseCode = "404", description = "코드를 찾을 수 없음", content = @Content), @ApiResponse(responseCode = "404", description = "코드를 찾을 수 없음", content = @Content),
@ApiResponse(responseCode = "500", description = "서버 오류", content = @Content) @ApiResponse(responseCode = "500", description = "서버 오류", content = @Content)
}) })
@GetMapping("/used") @GetMapping("/used")
public ApiResponseDto<List<CommonCodeDto.Basic>> getByCode( public ApiResponseDto<List<CommonCodeDto.Basic>> getByCode(
@io.swagger.v3.oas.annotations.parameters.RequestBody( @io.swagger.v3.oas.annotations.parameters.RequestBody(
description = "공통코드 순서변경 요청 정보", description = "공통코드 순서변경 요청 정보",
required = true) required = true)
@RequestParam @RequestParam
String code) { String code) {
return ApiResponseDto.ok(commonCodeService.findByCode(code)); return ApiResponseDto.ok(commonCodeService.findByCode(code));
} }
@Operation(summary = "변화탐지 분류 코드 목록", description = "변화탐지 분류 코드 목록(공통코드 기반)") @Operation(summary = "변화탐지 분류 코드 목록", description = "변화탐지 분류 코드 목록(공통코드 기반)")
@GetMapping("/clazz") @GetMapping("/clazz")
public ApiResponseDto<List<CommonCodeDto.Clazzes>> getClasses() { public ApiResponseDto<List<CommonCodeDto.Clazzes>> getClasses() {
// List<Clazzes> list = // List<Clazzes> list =
// Arrays.stream(DetectionClassification.values()) // Arrays.stream(DetectionClassification.values())
// .sorted(Comparator.comparingInt(DetectionClassification::getOrder)) // .sorted(Comparator.comparingInt(DetectionClassification::getOrder))
// .map(Clazzes::new) // .map(Clazzes::new)
// .toList(); // .toList();
// 변화탐지 clazz API : enum -> 공통코드로 변경 // 변화탐지 clazz API : enum -> 공통코드로 변경
List<CommonCodeDto.Clazzes> list = List<CommonCodeDto.Clazzes> list =
commonCodeUtil.getChildCodesByParentCode("0000").stream() commonCodeUtil.getChildCodesByParentCode("0000").stream()
.map( .map(
child -> child ->
new CommonCodeDto.Clazzes( new CommonCodeDto.Clazzes(
child.getCode(), child.getName(), child.getOrder(), child.getProps2())) child.getCode(), child.getName(), child.getOrder(), child.getProps2()))
.toList(); .toList();
return ApiResponseDto.ok(list); return ApiResponseDto.ok(list);
} }
@Operation(summary = "공통코드 중복여부 체크", description = "공통코드 중복여부 체크") @Operation(summary = "공통코드 중복여부 체크", description = "공통코드 중복여부 체크")
@ApiResponses( @ApiResponses(
value = { value = {
@ApiResponse( @ApiResponse(
responseCode = "200", responseCode = "200",
description = "조회 성공", description = "조회 성공",
content = content =
@Content( @Content(
mediaType = "application/json", mediaType = "application/json",
schema = @Schema(implementation = CommonCodeDto.Basic.class))), schema = @Schema(implementation = CommonCodeDto.Basic.class))),
@ApiResponse(responseCode = "404", description = "코드를 찾을 수 없음", content = @Content), @ApiResponse(responseCode = "404", description = "코드를 찾을 수 없음", content = @Content),
@ApiResponse(responseCode = "500", description = "서버 오류", content = @Content) @ApiResponse(responseCode = "500", description = "서버 오류", content = @Content)
}) })
@GetMapping("/check-duplicate") @GetMapping("/check-duplicate")
public ApiResponseDto<ApiResponseDto.ResponseObj> getCodeCheckDuplicate( public ApiResponseDto<ApiResponseDto.ResponseObj> getCodeCheckDuplicate(
@io.swagger.v3.oas.annotations.parameters.RequestBody(description = "단건 조회", required = true) @io.swagger.v3.oas.annotations.parameters.RequestBody(description = "단건 조회", required = true)
@RequestParam(required = false) @RequestParam(required = false)
Long parentId, Long parentId,
@RequestParam String code) { @RequestParam String code) {
return ApiResponseDto.okObject(commonCodeService.getCodeCheckDuplicate(parentId, code)); return ApiResponseDto.okObject(commonCodeService.getCodeCheckDuplicate(parentId, code));
} }
@Operation(summary = "코드 조회", description = "코드 리스트 조회") @Operation(summary = "코드 조회", description = "코드 리스트 조회")
@ApiResponses( @ApiResponses(
value = { value = {
@ApiResponse( @ApiResponse(
responseCode = "200", responseCode = "200",
description = "코드 조회 성공", description = "코드 조회 성공",
content = content =
@Content( @Content(
mediaType = "application/json", mediaType = "application/json",
schema = @Schema(implementation = CodeDto.class))), schema = @Schema(implementation = CodeDto.class))),
@ApiResponse(responseCode = "500", description = "서버 오류", content = @Content) @ApiResponse(responseCode = "500", description = "서버 오류", content = @Content)
}) })
@GetMapping("/type/codes") @GetMapping("/type/codes")
public ApiResponseDto<Map<String, List<CodeDto>>> getTypeCodes() { public ApiResponseDto<Map<String, List<CodeDto>>> getTypeCodes() {
return ApiResponseDto.ok(commonCodeService.getTypeCodes()); return ApiResponseDto.ok(commonCodeService.getTypeCodes());
} }
@Operation(summary = "코드 단건 조회", description = "코드 조회") @Operation(summary = "코드 단건 조회", description = "코드 조회")
@ApiResponses( @ApiResponses(
value = { value = {
@ApiResponse( @ApiResponse(
responseCode = "200", responseCode = "200",
description = "코드 조회 성공", description = "코드 조회 성공",
content = content =
@Content( @Content(
mediaType = "application/json", mediaType = "application/json",
schema = @Schema(implementation = CodeDto.class))), schema = @Schema(implementation = CodeDto.class))),
@ApiResponse(responseCode = "500", description = "서버 오류", content = @Content) @ApiResponse(responseCode = "500", description = "서버 오류", content = @Content)
}) })
@GetMapping("/type/{type}") @GetMapping("/type/{type}")
public ApiResponseDto<List<CodeDto>> getTypeCode(@PathVariable String type) { public ApiResponseDto<List<CodeDto>> getTypeCode(@PathVariable String type) {
return ApiResponseDto.ok(commonCodeService.getTypeCode(type)); return ApiResponseDto.ok(commonCodeService.getTypeCode(type));
} }
@Operation(summary = "캐시 초기화", description = "공통코드 캐시를 초기화합니다.") @Operation(summary = "캐시 초기화", description = "공통코드 캐시를 초기화합니다.")
@ApiResponses( @ApiResponses(
value = { value = {
@ApiResponse( @ApiResponse(
responseCode = "200", responseCode = "200",
description = "캐시 초기화 성공", description = "캐시 초기화 성공",
content = content =
@Content( @Content(
mediaType = "application/json", mediaType = "application/json",
schema = @Schema(implementation = String.class))), schema = @Schema(implementation = String.class))),
@ApiResponse(responseCode = "500", description = "서버 오류", content = @Content) @ApiResponse(responseCode = "500", description = "서버 오류", content = @Content)
}) })
@PostMapping("/cache/refresh") @PostMapping("/cache/refresh")
public ApiResponseDto<String> refreshCommonCodeCache() { public ApiResponseDto<String> refreshCommonCodeCache() {
commonCodeService.refresh(); commonCodeService.refresh();
return ApiResponseDto.ok("공통코드 캐시가 초기화 되었습니다."); return ApiResponseDto.ok("공통코드 캐시가 초기화 되었습니다.");
} }
} }

View File

@@ -1,179 +1,179 @@
package com.kamco.cd.training.code.dto; package com.kamco.cd.training.code.dto;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize; import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import com.fasterxml.jackson.databind.annotation.JsonSerialize; import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import com.kamco.cd.training.common.utils.html.HtmlEscapeDeserializer; 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.html.HtmlUnescapeSerializer;
import com.kamco.cd.training.common.utils.interfaces.JsonFormatDttm; import com.kamco.cd.training.common.utils.interfaces.JsonFormatDttm;
import io.swagger.v3.oas.annotations.media.Schema; import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotEmpty; import jakarta.validation.constraints.NotEmpty;
import jakarta.validation.constraints.NotNull; import jakarta.validation.constraints.NotNull;
import java.time.ZonedDateTime; import java.time.ZonedDateTime;
import java.util.List; import java.util.List;
import lombok.AllArgsConstructor; import lombok.AllArgsConstructor;
import lombok.Getter; import lombok.Getter;
import lombok.NoArgsConstructor; import lombok.NoArgsConstructor;
import lombok.Setter; import lombok.Setter;
import org.springframework.data.domain.PageRequest; import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable; import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort; import org.springframework.data.domain.Sort;
public class CommonCodeDto { public class CommonCodeDto {
@Schema(name = "CodeAddReq", description = "공통코드 저장 정보") @Schema(name = "CodeAddReq", description = "공통코드 저장 정보")
@Getter @Getter
@Setter @Setter
@NoArgsConstructor @NoArgsConstructor
@AllArgsConstructor @AllArgsConstructor
public static class AddReq { public static class AddReq {
@NotEmpty private String code; @NotEmpty private String code;
@NotEmpty private String name; @NotEmpty private String name;
private String description; private String description;
private int order; private int order;
private boolean used; private boolean used;
private Long parentId; private Long parentId;
@JsonDeserialize(using = HtmlEscapeDeserializer.class) @JsonDeserialize(using = HtmlEscapeDeserializer.class)
private String props1; private String props1;
@JsonDeserialize(using = HtmlEscapeDeserializer.class) @JsonDeserialize(using = HtmlEscapeDeserializer.class)
private String props2; private String props2;
@JsonDeserialize(using = HtmlEscapeDeserializer.class) @JsonDeserialize(using = HtmlEscapeDeserializer.class)
private String props3; private String props3;
} }
@Schema(name = "CodeModifyReq", description = "공통코드 수정 정보") @Schema(name = "CodeModifyReq", description = "공통코드 수정 정보")
@Getter @Getter
@Setter @Setter
@NoArgsConstructor @NoArgsConstructor
@AllArgsConstructor @AllArgsConstructor
public static class ModifyReq { public static class ModifyReq {
@NotEmpty private String name; @NotEmpty private String name;
private String description; private String description;
private boolean used; private boolean used;
@JsonDeserialize(using = HtmlEscapeDeserializer.class) @JsonDeserialize(using = HtmlEscapeDeserializer.class)
private String props1; private String props1;
@JsonDeserialize(using = HtmlEscapeDeserializer.class) @JsonDeserialize(using = HtmlEscapeDeserializer.class)
private String props2; private String props2;
@JsonDeserialize(using = HtmlEscapeDeserializer.class) @JsonDeserialize(using = HtmlEscapeDeserializer.class)
private String props3; private String props3;
} }
@Schema(name = "CodeOrderReq", description = "공통코드 순서 변경 정보") @Schema(name = "CodeOrderReq", description = "공통코드 순서 변경 정보")
@Getter @Getter
@Setter @Setter
@NoArgsConstructor @NoArgsConstructor
@AllArgsConstructor @AllArgsConstructor
public static class OrderReq { public static class OrderReq {
@NotNull private Long id; @NotNull private Long id;
@NotNull private Integer order; @NotNull private Integer order;
} }
@Schema(name = "CommonCode Basic", description = "공통코드 기본 정보") @Schema(name = "CommonCode Basic", description = "공통코드 기본 정보")
@Getter @Getter
public static class Basic { public static class Basic {
private Long id; private Long id;
private String code; private String code;
private String description; private String description;
private String name; private String name;
private Integer order; private Integer order;
private Boolean used; private Boolean used;
private Boolean deleted; private Boolean deleted;
private List<CommonCodeDto.Basic> children; private List<CommonCodeDto.Basic> children;
@JsonFormatDttm private ZonedDateTime createdDttm; @JsonFormatDttm private ZonedDateTime createdDttm;
@JsonFormatDttm private ZonedDateTime updatedDttm; @JsonFormatDttm private ZonedDateTime updatedDttm;
@JsonSerialize(using = HtmlUnescapeSerializer.class) @JsonSerialize(using = HtmlUnescapeSerializer.class)
private String props1; private String props1;
@JsonSerialize(using = HtmlUnescapeSerializer.class) @JsonSerialize(using = HtmlUnescapeSerializer.class)
private String props2; private String props2;
@JsonSerialize(using = HtmlUnescapeSerializer.class) @JsonSerialize(using = HtmlUnescapeSerializer.class)
private String props3; private String props3;
@JsonFormatDttm private ZonedDateTime deletedDttm; @JsonFormatDttm private ZonedDateTime deletedDttm;
public Basic( public Basic(
Long id, Long id,
String code, String code,
String description, String description,
String name, String name,
Integer order, Integer order,
Boolean used, Boolean used,
Boolean deleted, Boolean deleted,
List<CommonCodeDto.Basic> children, List<CommonCodeDto.Basic> children,
ZonedDateTime createdDttm, ZonedDateTime createdDttm,
ZonedDateTime updatedDttm, ZonedDateTime updatedDttm,
String props1, String props1,
String props2, String props2,
String props3, String props3,
ZonedDateTime deletedDttm) { ZonedDateTime deletedDttm) {
this.id = id; this.id = id;
this.code = code; this.code = code;
this.description = description; this.description = description;
this.name = name; this.name = name;
this.order = order; this.order = order;
this.used = used; this.used = used;
this.deleted = deleted; this.deleted = deleted;
this.children = children; this.children = children;
this.createdDttm = createdDttm; this.createdDttm = createdDttm;
this.updatedDttm = updatedDttm; this.updatedDttm = updatedDttm;
this.props1 = props1; this.props1 = props1;
this.props2 = props2; this.props2 = props2;
this.props3 = props3; this.props3 = props3;
this.deletedDttm = deletedDttm; this.deletedDttm = deletedDttm;
} }
} }
@Schema(name = "SearchReq", description = "검색 요청") @Schema(name = "SearchReq", description = "검색 요청")
@Getter @Getter
@Setter @Setter
@NoArgsConstructor @NoArgsConstructor
@AllArgsConstructor @AllArgsConstructor
public static class SearchReq { public static class SearchReq {
// 검색 조건 // 검색 조건
private String name; private String name;
// 페이징 파라미터 // 페이징 파라미터
private int page = 0; private int page = 0;
private int size = 20; private int size = 20;
private String sort; private String sort;
public Pageable toPageable() { public Pageable toPageable() {
if (sort != null && !sort.isEmpty()) { if (sort != null && !sort.isEmpty()) {
String[] sortParams = sort.split(","); String[] sortParams = sort.split(",");
String property = sortParams[0]; String property = sortParams[0];
Sort.Direction direction = Sort.Direction direction =
sortParams.length > 1 ? Sort.Direction.fromString(sortParams[1]) : Sort.Direction.ASC; 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, Sort.by(direction, property));
} }
return PageRequest.of(page, size); return PageRequest.of(page, size);
} }
} }
@Getter @Getter
public static class Clazzes { public static class Clazzes {
private String code; private String code;
private String name; private String name;
private Integer order; private Integer order;
private String color; private String color;
public Clazzes(String code, String name, Integer order, String color) { public Clazzes(String code, String name, Integer order, String color) {
this.code = code; this.code = code;
this.name = name; this.name = name;
this.order = order; this.order = order;
this.color = color; this.color = color;
} }
} }
} }

View File

@@ -1,155 +1,155 @@
package com.kamco.cd.training.code.service; package com.kamco.cd.training.code.service;
import com.kamco.cd.training.code.dto.CommonCodeDto.AddReq; 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.Basic;
import com.kamco.cd.training.code.dto.CommonCodeDto.ModifyReq; import com.kamco.cd.training.code.dto.CommonCodeDto.ModifyReq;
import com.kamco.cd.training.code.dto.CommonCodeDto.OrderReq; 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.CodeDto;
import com.kamco.cd.training.common.utils.enums.Enums; import com.kamco.cd.training.common.utils.enums.Enums;
import com.kamco.cd.training.config.api.ApiResponseDto; import com.kamco.cd.training.config.api.ApiResponseDto;
import com.kamco.cd.training.postgres.core.CommonCodeCoreService; import com.kamco.cd.training.postgres.core.CommonCodeCoreService;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Optional; import java.util.Optional;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import org.springframework.cache.annotation.CacheEvict; import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.Cacheable; import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional; import org.springframework.transaction.annotation.Transactional;
// training 서버는 Redis 사용하지 않고 Spring Boot 메모리 캐시를 사용함 // training 서버는 Redis 사용하지 않고 Spring Boot 메모리 캐시를 사용함
// => org.springframework.cache.annotation.Cacheable // => org.springframework.cache.annotation.Cacheable
@Service @Service
@RequiredArgsConstructor @RequiredArgsConstructor
@Transactional(readOnly = true) @Transactional(readOnly = true)
public class CommonCodeService { public class CommonCodeService {
private final CommonCodeCoreService commonCodeCoreService; private final CommonCodeCoreService commonCodeCoreService;
/** /**
* 공통코드 목록 조회 * 공통코드 목록 조회
* *
* @return 모튼 코드 정보 * @return 모튼 코드 정보
*/ */
@Cacheable("trainCommonCodes") @Cacheable("trainCommonCodes")
public List<Basic> getFindAll() { public List<Basic> getFindAll() {
return commonCodeCoreService.findAll(); return commonCodeCoreService.findAll();
} }
/** /**
* 공통코드 단건 조회 * 공통코드 단건 조회
* *
* @param id * @param id
* @return 코드 아이디로 조회한 코드 정보 * @return 코드 아이디로 조회한 코드 정보
*/ */
public Basic getOneById(Long id) { public Basic getOneById(Long id) {
return commonCodeCoreService.getOneById(id); return commonCodeCoreService.getOneById(id);
} }
/** /**
* 공통코드 생성 요청 * 공통코드 생성 요청
* *
* @param req 생성요청 정보 * @param req 생성요청 정보
* @return 생성된 코드 id * @return 생성된 코드 id
*/ */
@Transactional @Transactional
@CacheEvict(value = "trainCommonCodes", allEntries = true) @CacheEvict(value = "trainCommonCodes", allEntries = true)
public ApiResponseDto.ResponseObj save(AddReq req) { public ApiResponseDto.ResponseObj save(AddReq req) {
return commonCodeCoreService.save(req); return commonCodeCoreService.save(req);
} }
/** /**
* 공통코드 수정 요청 * 공통코드 수정 요청
* *
* @param id 코드 아이디 * @param id 코드 아이디
* @param req 수정요청 정보 * @param req 수정요청 정보
*/ */
@Transactional @Transactional
@CacheEvict(value = "trainCommonCodes", allEntries = true) @CacheEvict(value = "trainCommonCodes", allEntries = true)
public ApiResponseDto.ResponseObj update(Long id, ModifyReq req) { public ApiResponseDto.ResponseObj update(Long id, ModifyReq req) {
return commonCodeCoreService.update(id, req); return commonCodeCoreService.update(id, req);
} }
/** /**
* 공통코드 삭제 처리 * 공통코드 삭제 처리
* *
* @param id 코드 아이디 * @param id 코드 아이디
*/ */
@Transactional @Transactional
@CacheEvict(value = "trainCommonCodes", allEntries = true) @CacheEvict(value = "trainCommonCodes", allEntries = true)
public ApiResponseDto.ResponseObj removeCode(Long id) { public ApiResponseDto.ResponseObj removeCode(Long id) {
return commonCodeCoreService.removeCode(id); return commonCodeCoreService.removeCode(id);
} }
/** /**
* 공통코드 순서 변경 * 공통코드 순서 변경
* *
* @param req id, order 정보를 가진 List * @param req id, order 정보를 가진 List
*/ */
@Transactional @Transactional
@CacheEvict(value = "trainCommonCodes", allEntries = true) @CacheEvict(value = "trainCommonCodes", allEntries = true)
public ApiResponseDto.ResponseObj updateOrder(OrderReq req) { public ApiResponseDto.ResponseObj updateOrder(OrderReq req) {
return commonCodeCoreService.updateOrder(req); return commonCodeCoreService.updateOrder(req);
} }
/** /**
* 코드기반 조회 * 코드기반 조회
* *
* @param code 코드 * @param code 코드
* @return 코드로 조회한 공통코드 정보 * @return 코드로 조회한 공통코드 정보
*/ */
public List<Basic> findByCode(String code) { public List<Basic> findByCode(String code) {
return commonCodeCoreService.findByCode(code); return commonCodeCoreService.findByCode(code);
} }
/** /**
* 중복 체크 * 중복 체크
* *
* @param parentId * @param parentId
* @param code * @param code
* @return * @return
*/ */
public ApiResponseDto.ResponseObj getCodeCheckDuplicate(Long parentId, String code) { public ApiResponseDto.ResponseObj getCodeCheckDuplicate(Long parentId, String code) {
return commonCodeCoreService.getCodeCheckDuplicate(parentId, code); return commonCodeCoreService.getCodeCheckDuplicate(parentId, code);
} }
/** /**
* 공통코드 이름 조회 * 공통코드 이름 조회
* *
* @param parentCodeCd 상위 코드 * @param parentCodeCd 상위 코드
* @param childCodeCd 하위 코드 * @param childCodeCd 하위 코드
* @return 공통코드명 * @return 공통코드명
*/ */
public Optional<String> getCode(String parentCodeCd, String childCodeCd) { public Optional<String> getCode(String parentCodeCd, String childCodeCd) {
return commonCodeCoreService.getCode(parentCodeCd, childCodeCd); return commonCodeCoreService.getCode(parentCodeCd, childCodeCd);
} }
/** /**
* 공통코드 이름 조회 * 공통코드 이름 조회
* *
* @param parentCodeCd 상위 코드 * @param parentCodeCd 상위 코드
* @param childCodeCd 하위 코드 * @param childCodeCd 하위 코드
* @return 공통코드명 * @return 공통코드명
*/ */
public Optional<String> getTypeCode(String parentCodeCd, String childCodeCd) { public Optional<String> getTypeCode(String parentCodeCd, String childCodeCd) {
return commonCodeCoreService.getCode(parentCodeCd, childCodeCd); return commonCodeCoreService.getCode(parentCodeCd, childCodeCd);
} }
public List<CodeDto> getTypeCode(String type) { public List<CodeDto> getTypeCode(String type) {
return Enums.getCodes(type); return Enums.getCodes(type);
} }
/** /**
* 공통코드 리스트 조회 * 공통코드 리스트 조회
* *
* @return * @return
*/ */
public Map<String, List<CodeDto>> getTypeCodes() { public Map<String, List<CodeDto>> getTypeCodes() {
return Enums.getAllCodes(); return Enums.getAllCodes();
} }
/** 메모리 캐시 초기화 */ /** 메모리 캐시 초기화 */
@CacheEvict(value = "trainCommonCodes", allEntries = true) @CacheEvict(value = "trainCommonCodes", allEntries = true)
public void refresh() {} public void refresh() {}
} }

View File

@@ -1,19 +1,19 @@
package com.kamco.cd.training.common.enums; package com.kamco.cd.training.common.enums;
import com.kamco.cd.training.common.utils.enums.CodeExpose; import com.kamco.cd.training.common.utils.enums.CodeExpose;
import com.kamco.cd.training.common.utils.enums.EnumType; import com.kamco.cd.training.common.utils.enums.EnumType;
import lombok.AllArgsConstructor; import lombok.AllArgsConstructor;
import lombok.Getter; import lombok.Getter;
@CodeExpose @CodeExpose
@Getter @Getter
@AllArgsConstructor @AllArgsConstructor
public enum DeployTargetType implements EnumType { public enum DeployTargetType implements EnumType {
// @formatter:off // @formatter:off
GUKU("GUKU", "국토교통부"), GUKU("GUKU", "국토교통부"),
PROD("PROD", "운영계"); PROD("PROD", "운영계");
// @formatter:on // @formatter:on
private final String id; private final String id;
private final String text; private final String text;
} }

View File

@@ -1,55 +1,55 @@
package com.kamco.cd.training.common.enums; package com.kamco.cd.training.common.enums;
import lombok.AllArgsConstructor; import lombok.AllArgsConstructor;
import lombok.Getter; import lombok.Getter;
@Getter @Getter
@AllArgsConstructor @AllArgsConstructor
public enum DetectionClassification { public enum DetectionClassification {
BUILDING("building", "건물", 10), BUILDING("building", "건물", 10),
CONTAINER("container", "컨테이너", 20), CONTAINER("container", "컨테이너", 20),
FIELD("field", "경작지", 30), FIELD("field", "경작지", 30),
FOREST("forest", "", 40), FOREST("forest", "", 40),
GRASS("grass", "초지", 50), GRASS("grass", "초지", 50),
GREENHOUSE("greenhouse", "비닐하우스", 60), GREENHOUSE("greenhouse", "비닐하우스", 60),
LAND("land", "일반토지", 70), LAND("land", "일반토지", 70),
ORCHARD("orchard", "과수원", 80), ORCHARD("orchard", "과수원", 80),
ROAD("road", "도로", 90), ROAD("road", "도로", 90),
STONE("stone", "모래/자갈", 100), STONE("stone", "모래/자갈", 100),
TANK("tank", "물탱크", 110), TANK("tank", "물탱크", 110),
TUMULUS("tumulus", "토분(무덤)", 120), TUMULUS("tumulus", "토분(무덤)", 120),
WASTE("waste", "폐기물", 130), WASTE("waste", "폐기물", 130),
WATER("water", "", 140), WATER("water", "", 140),
ETC("ETC", "기타", 200); // For 'etc' (miscellaneous/other) ETC("ETC", "기타", 200); // For 'etc' (miscellaneous/other)
private final String id; private final String id;
private final String desc; private final String desc;
private final int order; private final int order;
/** /**
* Optional: Helper method to get the enum from a String, case-insensitive, or return ETC if not * Optional: Helper method to get the enum from a String, case-insensitive, or return ETC if not
* found. * found.
*/ */
public static DetectionClassification fromString(String text) { public static DetectionClassification fromString(String text) {
if (text == null || text.trim().isEmpty()) { if (text == null || text.trim().isEmpty()) {
return ETC; return ETC;
} }
try { try {
return DetectionClassification.valueOf(text.toUpperCase()); return DetectionClassification.valueOf(text.toUpperCase());
} catch (IllegalArgumentException e) { } catch (IllegalArgumentException e) {
// If the string doesn't match any enum constant name, return ETC // If the string doesn't match any enum constant name, return ETC
return ETC; return ETC;
} }
} }
/** /**
* Desc 한글명 get 하기 * Desc 한글명 get 하기
* *
* @return * @return
*/ */
public static String fromStrDesc(String text) { public static String fromStrDesc(String text) {
DetectionClassification dtf = fromString(text); DetectionClassification dtf = fromString(text);
return dtf.getDesc(); return dtf.getDesc();
} }
} }

View File

@@ -1,26 +1,26 @@
package com.kamco.cd.training.common.enums; package com.kamco.cd.training.common.enums;
import com.kamco.cd.training.common.utils.enums.EnumType; import com.kamco.cd.training.common.utils.enums.EnumType;
import lombok.AllArgsConstructor; import lombok.AllArgsConstructor;
import lombok.Getter; import lombok.Getter;
@Getter @Getter
@AllArgsConstructor @AllArgsConstructor
public enum LearnDataRegister implements EnumType { public enum LearnDataRegister implements EnumType {
READY("준비"), READY("준비"),
UPLOADING("업로드중"), UPLOADING("업로드중"),
UPLOAD_FAILED("업로드 실패"), UPLOAD_FAILED("업로드 실패"),
COMPLETED("완료"); COMPLETED("완료");
private final String desc; private final String desc;
@Override @Override
public String getId() { public String getId() {
return name(); return name();
} }
@Override @Override
public String getText() { public String getText() {
return desc; return desc;
} }
} }

View File

@@ -1,26 +1,26 @@
package com.kamco.cd.training.common.enums; package com.kamco.cd.training.common.enums;
import com.kamco.cd.training.common.utils.enums.CodeExpose; import com.kamco.cd.training.common.utils.enums.CodeExpose;
import com.kamco.cd.training.common.utils.enums.EnumType; import com.kamco.cd.training.common.utils.enums.EnumType;
import lombok.AllArgsConstructor; import lombok.AllArgsConstructor;
import lombok.Getter; import lombok.Getter;
@CodeExpose @CodeExpose
@Getter @Getter
@AllArgsConstructor @AllArgsConstructor
public enum LearnDataType implements EnumType { public enum LearnDataType implements EnumType {
DELIVER("납품"), DELIVER("납품"),
PRODUCTION("제작"); PRODUCTION("제작");
private final String desc; private final String desc;
@Override @Override
public String getId() { public String getId() {
return name(); return name();
} }
@Override @Override
public String getText() { public String getText() {
return desc; return desc;
} }
} }

View File

@@ -1,27 +1,27 @@
package com.kamco.cd.training.common.enums; package com.kamco.cd.training.common.enums;
import com.kamco.cd.training.common.utils.enums.CodeExpose; import com.kamco.cd.training.common.utils.enums.CodeExpose;
import com.kamco.cd.training.common.utils.enums.EnumType; import com.kamco.cd.training.common.utils.enums.EnumType;
import lombok.AllArgsConstructor; import lombok.AllArgsConstructor;
import lombok.Getter; import lombok.Getter;
@CodeExpose @CodeExpose
@Getter @Getter
@AllArgsConstructor @AllArgsConstructor
public enum ModelMngStatusType implements EnumType { public enum ModelMngStatusType implements EnumType {
READY("준비"), READY("준비"),
IN_PROGRESS("진행중"), IN_PROGRESS("진행중"),
COMPLETED("완료"); COMPLETED("완료");
private String desc; private String desc;
@Override @Override
public String getId() { public String getId() {
return name(); return name();
} }
@Override @Override
public String getText() { public String getText() {
return desc; return desc;
} }
} }

View File

@@ -1,20 +1,20 @@
package com.kamco.cd.training.common.enums; package com.kamco.cd.training.common.enums;
import com.kamco.cd.training.common.utils.enums.CodeExpose; import com.kamco.cd.training.common.utils.enums.CodeExpose;
import com.kamco.cd.training.common.utils.enums.EnumType; import com.kamco.cd.training.common.utils.enums.EnumType;
import lombok.AllArgsConstructor; import lombok.AllArgsConstructor;
import lombok.Getter; import lombok.Getter;
@CodeExpose @CodeExpose
@Getter @Getter
@AllArgsConstructor @AllArgsConstructor
public enum ProcessStepType implements EnumType { public enum ProcessStepType implements EnumType {
// @formatter:off // @formatter:off
STEP1("STEP1", "학습 중"), STEP1("STEP1", "학습 중"),
STEP2("STEP2", "테스트 중"), STEP2("STEP2", "테스트 중"),
STEP3("STEP3", "완료"); STEP3("STEP3", "완료");
// @formatter:on // @formatter:on
private final String id; private final String id;
private final String text; private final String text;
} }

View File

@@ -1,25 +1,25 @@
package com.kamco.cd.training.common.enums; package com.kamco.cd.training.common.enums;
import com.kamco.cd.training.common.utils.enums.EnumType; import com.kamco.cd.training.common.utils.enums.EnumType;
import lombok.AllArgsConstructor; import lombok.AllArgsConstructor;
import lombok.Getter; import lombok.Getter;
@Getter @Getter
@AllArgsConstructor @AllArgsConstructor
public enum RoleType implements EnumType { public enum RoleType implements EnumType {
ROLE_ADMIN("시스템 관리자"), ROLE_ADMIN("시스템 관리자"),
ROLE_LABELER("라벨러"), ROLE_LABELER("라벨러"),
ROLE_REVIEWER("검수자"); ROLE_REVIEWER("검수자");
private final String desc; private final String desc;
@Override @Override
public String getId() { public String getId() {
return name(); return name();
} }
@Override @Override
public String getText() { public String getText() {
return desc; return desc;
} }
} }

View File

@@ -1,25 +1,25 @@
package com.kamco.cd.training.common.enums; package com.kamco.cd.training.common.enums;
import com.kamco.cd.training.common.utils.enums.EnumType; import com.kamco.cd.training.common.utils.enums.EnumType;
import lombok.AllArgsConstructor; import lombok.AllArgsConstructor;
import lombok.Getter; import lombok.Getter;
@Getter @Getter
@AllArgsConstructor @AllArgsConstructor
public enum StatusType implements EnumType { public enum StatusType implements EnumType {
ACTIVE("사용"), ACTIVE("사용"),
INACTIVE("미사용"), INACTIVE("미사용"),
PENDING("계정등록"); PENDING("계정등록");
private final String desc; private final String desc;
@Override @Override
public String getId() { public String getId() {
return name(); return name();
} }
@Override @Override
public String getText() { public String getText() {
return desc; return desc;
} }
} }

View File

@@ -1,22 +1,22 @@
package com.kamco.cd.training.common.enums; package com.kamco.cd.training.common.enums;
import com.kamco.cd.training.common.utils.enums.CodeExpose; import com.kamco.cd.training.common.utils.enums.CodeExpose;
import com.kamco.cd.training.common.utils.enums.EnumType; import com.kamco.cd.training.common.utils.enums.EnumType;
import lombok.AllArgsConstructor; import lombok.AllArgsConstructor;
import lombok.Getter; import lombok.Getter;
@CodeExpose @CodeExpose
@Getter @Getter
@AllArgsConstructor @AllArgsConstructor
public enum TrainStatusType implements EnumType { public enum TrainStatusType implements EnumType {
// @formatter:off // @formatter:off
READY("READY", "대기"), READY("READY", "대기"),
ING("ING", "진행중"), ING("ING", "진행중"),
COMPLETED("COMPLETED", "완료"), COMPLETED("COMPLETED", "완료"),
STOPPED("STOPPED", "중단됨"), STOPPED("STOPPED", "중단됨"),
ERROR("ERROR", "오류"); ERROR("ERROR", "오류");
// @formatter:on // @formatter:on
private final String id; private final String id;
private final String text; private final String text;
} }

View File

@@ -1,26 +1,26 @@
package com.kamco.cd.training.common.enums.error; package com.kamco.cd.training.common.enums.error;
import com.kamco.cd.training.common.utils.ErrorCode; import com.kamco.cd.training.common.utils.ErrorCode;
import lombok.Getter; import lombok.Getter;
import org.springframework.http.HttpStatus; import org.springframework.http.HttpStatus;
@Getter @Getter
public enum AuthErrorCode implements ErrorCode { public enum AuthErrorCode implements ErrorCode {
LOGIN_ID_NOT_FOUND("LOGIN_ID_NOT_FOUND", HttpStatus.UNAUTHORIZED), LOGIN_ID_NOT_FOUND("LOGIN_ID_NOT_FOUND", HttpStatus.UNAUTHORIZED),
LOGIN_PASSWORD_MISMATCH("LOGIN_PASSWORD_MISMATCH", HttpStatus.UNAUTHORIZED), LOGIN_PASSWORD_MISMATCH("LOGIN_PASSWORD_MISMATCH", HttpStatus.UNAUTHORIZED),
LOGIN_PASSWORD_EXCEEDED("LOGIN_PASSWORD_EXCEEDED", HttpStatus.UNAUTHORIZED), LOGIN_PASSWORD_EXCEEDED("LOGIN_PASSWORD_EXCEEDED", HttpStatus.UNAUTHORIZED),
REFRESH_TOKEN_EXPIRED_OR_REVOKED("REFRESH_TOKEN_EXPIRED_OR_REVOKED", HttpStatus.UNAUTHORIZED), REFRESH_TOKEN_EXPIRED_OR_REVOKED("REFRESH_TOKEN_EXPIRED_OR_REVOKED", HttpStatus.UNAUTHORIZED),
REFRESH_TOKEN_MISMATCH("REFRESH_TOKEN_MISMATCH", HttpStatus.UNAUTHORIZED); REFRESH_TOKEN_MISMATCH("REFRESH_TOKEN_MISMATCH", HttpStatus.UNAUTHORIZED);
private final String code; private final String code;
private final HttpStatus status; private final HttpStatus status;
AuthErrorCode(String code, HttpStatus status) { AuthErrorCode(String code, HttpStatus status) {
this.code = code; this.code = code;
this.status = status; this.status = status;
} }
} }

View File

@@ -1,10 +1,10 @@
package com.kamco.cd.training.common.exception; package com.kamco.cd.training.common.exception;
import org.springframework.http.HttpStatus; import org.springframework.http.HttpStatus;
public class BadRequestException extends CustomApiException { public class BadRequestException extends CustomApiException {
public BadRequestException(String message) { public BadRequestException(String message) {
super("BAD_REQUEST", HttpStatus.BAD_REQUEST, message); super("BAD_REQUEST", HttpStatus.BAD_REQUEST, message);
} }
} }

View File

@@ -1,28 +1,28 @@
package com.kamco.cd.training.common.exception; package com.kamco.cd.training.common.exception;
import com.kamco.cd.training.common.utils.ErrorCode; import com.kamco.cd.training.common.utils.ErrorCode;
import lombok.Getter; import lombok.Getter;
import org.springframework.http.HttpStatus; import org.springframework.http.HttpStatus;
@Getter @Getter
public class CustomApiException extends RuntimeException { public class CustomApiException extends RuntimeException {
private final String codeName; // ApiResponseCode enum name과 맞추는 용도 (예: "UNPROCESSABLE_ENTITY") private final String codeName; // ApiResponseCode enum name과 맞추는 용도 (예: "UNPROCESSABLE_ENTITY")
private final HttpStatus status; // 응답으로 내려줄 HttpStatus private final HttpStatus status; // 응답으로 내려줄 HttpStatus
public CustomApiException(String codeName, HttpStatus status, String message) { public CustomApiException(String codeName, HttpStatus status, String message) {
super(message); super(message);
this.codeName = codeName; this.codeName = codeName;
this.status = status; this.status = status;
} }
public CustomApiException(String codeName, HttpStatus status) { public CustomApiException(String codeName, HttpStatus status) {
this.codeName = codeName; this.codeName = codeName;
this.status = status; this.status = status;
} }
public CustomApiException(ErrorCode errorCode) { public CustomApiException(ErrorCode errorCode) {
this.codeName = errorCode.getCode(); this.codeName = errorCode.getCode();
this.status = errorCode.getStatus(); this.status = errorCode.getStatus();
} }
} }

View File

@@ -1,7 +1,7 @@
package com.kamco.cd.training.common.exception; package com.kamco.cd.training.common.exception;
public class DuplicateFileException extends RuntimeException { public class DuplicateFileException extends RuntimeException {
public DuplicateFileException(String message) { public DuplicateFileException(String message) {
super(message); super(message);
} }
} }

View File

@@ -1,10 +1,10 @@
package com.kamco.cd.training.common.exception; package com.kamco.cd.training.common.exception;
import org.springframework.http.HttpStatus; import org.springframework.http.HttpStatus;
public class NotFoundException extends CustomApiException { public class NotFoundException extends CustomApiException {
public NotFoundException(String message) { public NotFoundException(String message) {
super("NOT_FOUND", HttpStatus.NOT_FOUND, message); super("NOT_FOUND", HttpStatus.NOT_FOUND, message);
} }
} }

View File

@@ -1,7 +1,7 @@
package com.kamco.cd.training.common.exception; package com.kamco.cd.training.common.exception;
public class ValidationException extends RuntimeException { public class ValidationException extends RuntimeException {
public ValidationException(String message) { public ValidationException(String message) {
super(message); super(message);
} }
} }

View File

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

View File

@@ -1,152 +1,152 @@
package com.kamco.cd.training.common.utils; package com.kamco.cd.training.common.utils;
import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.ObjectMapper;
import com.kamco.cd.training.code.dto.CommonCodeDto.Basic; import com.kamco.cd.training.code.dto.CommonCodeDto.Basic;
import com.kamco.cd.training.code.service.CommonCodeService; import com.kamco.cd.training.code.service.CommonCodeService;
import com.kamco.cd.training.config.api.ApiResponseDto; import com.kamco.cd.training.config.api.ApiResponseDto;
import java.util.List; import java.util.List;
import java.util.Optional; import java.util.Optional;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
/** /**
* 공통코드 조회 유틸리티 클래스 애플리케이션 전역에서 공통코드를 조회하기 위한 유틸리티입니다. training 서버는 Redis 사용하고 Spring 내장 메모리 캐시 * 공통코드 조회 유틸리티 클래스 애플리케이션 전역에서 공통코드를 조회하기 위한 유틸리티입니다. training 서버는 Redis 사용하고 Spring 내장 메모리 캐시
* 사용합니다. * 사용합니다.
*/ */
// //
@Slf4j @Slf4j
@Component @Component
public class CommonCodeUtil { public class CommonCodeUtil {
private final CommonCodeService commonCodeService; private final CommonCodeService commonCodeService;
@Autowired private ObjectMapper objectMapper; @Autowired private ObjectMapper objectMapper;
public CommonCodeUtil(CommonCodeService commonCodeService) { public CommonCodeUtil(CommonCodeService commonCodeService) {
this.commonCodeService = commonCodeService; this.commonCodeService = commonCodeService;
} }
/** /**
* 모든 공통코드 조회 * 모든 공통코드 조회
* *
* @return 캐시된 모든 공통코드 목록 * @return 캐시된 모든 공통코드 목록
*/ */
public List<Basic> getAllCommonCodes() { public List<Basic> getAllCommonCodes() {
try { try {
return commonCodeService.getFindAll(); return commonCodeService.getFindAll();
} catch (Exception e) { } catch (Exception e) {
log.error("공통코드 전체 조회 중 오류 발생", e); log.error("공통코드 전체 조회 중 오류 발생", e);
return List.of(); return List.of();
} }
} }
/** /**
* 특정 코드로 공통코드 조회 * 특정 코드로 공통코드 조회
* *
* @param code 코드값 * @param code 코드값
* @return 해당 코드의 공통코드 목록 * @return 해당 코드의 공통코드 목록
*/ */
public List<Basic> getCommonCodesByCode(String code) { public List<Basic> getCommonCodesByCode(String code) {
if (code == null || code.isEmpty()) { if (code == null || code.isEmpty()) {
log.warn("유효하지 않은 코드: {}", code); log.warn("유효하지 않은 코드: {}", code);
return List.of(); return List.of();
} }
try { try {
return commonCodeService.findByCode(code); return commonCodeService.findByCode(code);
} catch (Exception e) { } catch (Exception e) {
log.error("코드 기반 공통코드 조회 중 오류 발생: {}", code, e); log.error("코드 기반 공통코드 조회 중 오류 발생: {}", code, e);
return List.of(); return List.of();
} }
} }
/** /**
* 특정 ID로 공통코드 단건 조회 * 특정 ID로 공통코드 단건 조회
* *
* @param id 공통코드 ID * @param id 공통코드 ID
* @return 조회된 공통코드 * @return 조회된 공통코드
*/ */
public Optional<Basic> getCommonCodeById(Long id) { public Optional<Basic> getCommonCodeById(Long id) {
if (id == null || id <= 0) { if (id == null || id <= 0) {
log.warn("유효하지 않은 ID: {}", id); log.warn("유효하지 않은 ID: {}", id);
return Optional.empty(); return Optional.empty();
} }
try { try {
return Optional.of(commonCodeService.getOneById(id)); return Optional.of(commonCodeService.getOneById(id));
} catch (Exception e) { } catch (Exception e) {
log.error("ID 기반 공통코드 조회 중 오류 발생: {}", id, e); log.error("ID 기반 공통코드 조회 중 오류 발생: {}", id, e);
return Optional.empty(); return Optional.empty();
} }
} }
/** /**
* 상위 코드와 하위 코드로 공통코드명 조회 * 상위 코드와 하위 코드로 공통코드명 조회
* *
* @param parentCode 상위 코드 * @param parentCode 상위 코드
* @param childCode 하위 코드 * @param childCode 하위 코드
* @return 공통코드명 * @return 공통코드명
*/ */
public Optional<String> getCodeName(String parentCode, String childCode) { public Optional<String> getCodeName(String parentCode, String childCode) {
if (parentCode == null || parentCode.isEmpty() || childCode == null || childCode.isEmpty()) { if (parentCode == null || parentCode.isEmpty() || childCode == null || childCode.isEmpty()) {
log.warn("유효하지 않은 코드: parentCode={}, childCode={}", parentCode, childCode); log.warn("유효하지 않은 코드: parentCode={}, childCode={}", parentCode, childCode);
return Optional.empty(); return Optional.empty();
} }
try { try {
return commonCodeService.getCode(parentCode, childCode); return commonCodeService.getCode(parentCode, childCode);
} catch (Exception e) { } catch (Exception e) {
log.error("코드명 조회 중 오류 발생: parentCode={}, childCode={}", parentCode, childCode, e); log.error("코드명 조회 중 오류 발생: parentCode={}, childCode={}", parentCode, childCode, e);
return Optional.empty(); return Optional.empty();
} }
} }
/** /**
* 상위 코드를 기반으로 하위 코드 조회 * 상위 코드를 기반으로 하위 코드 조회
* *
* @param parentCode 상위 코드 * @param parentCode 상위 코드
* @return 해당 상위 코드의 하위 공통코드 목록 * @return 해당 상위 코드의 하위 공통코드 목록
*/ */
public List<Basic> getChildCodesByParentCode(String parentCode) { public List<Basic> getChildCodesByParentCode(String parentCode) {
if (parentCode == null || parentCode.isEmpty()) { if (parentCode == null || parentCode.isEmpty()) {
log.warn("유효하지 않은 상위 코드: {}", parentCode); log.warn("유효하지 않은 상위 코드: {}", parentCode);
return List.of(); return List.of();
} }
try { try {
return commonCodeService.getFindAll().stream() return commonCodeService.getFindAll().stream()
.filter(code -> parentCode.equals(code.getCode())) .filter(code -> parentCode.equals(code.getCode()))
.findFirst() .findFirst()
.map(Basic::getChildren) .map(Basic::getChildren)
.orElse(List.of()); .orElse(List.of());
} catch (Exception e) { } catch (Exception e) {
log.error("상위 코드 기반 하위 코드 조회 중 오류 발생: {}", parentCode, e); log.error("상위 코드 기반 하위 코드 조회 중 오류 발생: {}", parentCode, e);
return List.of(); return List.of();
} }
} }
/** /**
* 코드 사용 가능 여부 확인 * 코드 사용 가능 여부 확인
* *
* @param parentId 상위 코드 ID -> 1depth 는 null 허용 (파라미터 미필수로 변경) * @param parentId 상위 코드 ID -> 1depth 는 null 허용 (파라미터 미필수로 변경)
* @param code 확인할 코드값 * @param code 확인할 코드값
* @return 사용 가능 여부 (true: 사용 가능, false: 중복 또는 오류) * @return 사용 가능 여부 (true: 사용 가능, false: 중복 또는 오류)
*/ */
public boolean isCodeAvailable(Long parentId, String code) { public boolean isCodeAvailable(Long parentId, String code) {
if (parentId <= 0 || code == null || code.isEmpty()) { if (parentId <= 0 || code == null || code.isEmpty()) {
log.warn("유효하지 않은 입력: parentId={}, code={}", parentId, code); log.warn("유효하지 않은 입력: parentId={}, code={}", parentId, code);
return false; return false;
} }
try { try {
ApiResponseDto.ResponseObj response = commonCodeService.getCodeCheckDuplicate(parentId, code); ApiResponseDto.ResponseObj response = commonCodeService.getCodeCheckDuplicate(parentId, code);
// ResponseObj의 code 필드 : OK이면 성공, 아니면 실패 // ResponseObj의 code 필드 : OK이면 성공, 아니면 실패
return response.getCode() != null return response.getCode() != null
&& response.getCode().equals(ApiResponseDto.ApiResponseCode.OK); && response.getCode().equals(ApiResponseDto.ApiResponseCode.OK);
} catch (Exception e) { } catch (Exception e) {
log.error("코드 중복 확인 중 오류 발생: parentId={}, code={}", parentId, code, e); log.error("코드 중복 확인 중 오류 발생: parentId={}, code={}", parentId, code, e);
return false; return false;
} }
} }
} }

View File

@@ -1,32 +1,32 @@
package com.kamco.cd.training.common.utils; package com.kamco.cd.training.common.utils;
import com.kamco.cd.training.auth.BCryptSaltGenerator; import com.kamco.cd.training.auth.BCryptSaltGenerator;
import java.util.regex.Pattern; import java.util.regex.Pattern;
import org.mindrot.jbcrypt.BCrypt; import org.mindrot.jbcrypt.BCrypt;
public class CommonStringUtils { public class CommonStringUtils {
/** /**
* 영문, 숫자, 특수문자를 모두 포함하여 8~20자 이내의 비밀번호 * 영문, 숫자, 특수문자를 모두 포함하여 8~20자 이내의 비밀번호
* *
* @param password 벨리데이션 필요한 패스워드 * @param password 벨리데이션 필요한 패스워드
* @return * @return
*/ */
public static boolean isValidPassword(String password) { public static boolean isValidPassword(String password) {
String passwordPattern = String passwordPattern =
"^(?=.*[A-Za-z])(?=.*\\d)(?=.*[!@#$%^&*()_+\\-\\[\\]{};':\"\\\\|,.<>/?]).{8,20}$"; "^(?=.*[A-Za-z])(?=.*\\d)(?=.*[!@#$%^&*()_+\\-\\[\\]{};':\"\\\\|,.<>/?]).{8,20}$";
return Pattern.matches(passwordPattern, password); return Pattern.matches(passwordPattern, password);
} }
/** /**
* 패스워드 암호화 * 패스워드 암호화
* *
* @param password 암호화 필요한 패스워드 * @param password 암호화 필요한 패스워드
* @param employeeNo salt 생성에 필요한 사원번호 * @param employeeNo salt 생성에 필요한 사원번호
* @return * @return
*/ */
public static String hashPassword(String password, String employeeNo) { public static String hashPassword(String password, String employeeNo) {
String salt = BCryptSaltGenerator.generateSaltWithEmployeeNo(employeeNo.trim()); String salt = BCryptSaltGenerator.generateSaltWithEmployeeNo(employeeNo.trim());
return BCrypt.hashpw(password.trim(), salt); return BCrypt.hashpw(password.trim(), salt);
} }
} }

View File

@@ -1,10 +1,10 @@
package com.kamco.cd.training.common.utils; package com.kamco.cd.training.common.utils;
import org.springframework.http.HttpStatus; import org.springframework.http.HttpStatus;
public interface ErrorCode { public interface ErrorCode {
String getCode(); String getCode();
HttpStatus getStatus(); HttpStatus getStatus();
} }

View File

@@ -1,43 +1,43 @@
package com.kamco.cd.training.common.utils; package com.kamco.cd.training.common.utils;
import java.util.regex.Matcher; import java.util.regex.Matcher;
import java.util.regex.Pattern; import java.util.regex.Pattern;
public class NameValidator { public class NameValidator {
private static final String HANGUL_REGEX = ".*\\p{IsHangul}.*"; private static final String HANGUL_REGEX = ".*\\p{IsHangul}.*";
private static final Pattern HANGUL_PATTERN = Pattern.compile(HANGUL_REGEX); private static final Pattern HANGUL_PATTERN = Pattern.compile(HANGUL_REGEX);
private static final String WHITESPACE_REGEX = ".*\\s.*"; private static final String WHITESPACE_REGEX = ".*\\s.*";
private static final Pattern WHITESPACE_PATTERN = Pattern.compile(WHITESPACE_REGEX); private static final Pattern WHITESPACE_PATTERN = Pattern.compile(WHITESPACE_REGEX);
public static boolean containsKorean(String str) { public static boolean containsKorean(String str) {
if (str == null || str.isEmpty()) { if (str == null || str.isEmpty()) {
return false; return false;
} }
Matcher matcher = HANGUL_PATTERN.matcher(str); Matcher matcher = HANGUL_PATTERN.matcher(str);
return matcher.matches(); return matcher.matches();
} }
public static boolean containsWhitespaceRegex(String str) { public static boolean containsWhitespaceRegex(String str) {
if (str == null || str.isEmpty()) { if (str == null || str.isEmpty()) {
return false; return false;
} }
Matcher matcher = WHITESPACE_PATTERN.matcher(str); Matcher matcher = WHITESPACE_PATTERN.matcher(str);
// find()를 사용하여 문자열 내에서 패턴이 일치하는 부분이 있는지 확인 // find()를 사용하여 문자열 내에서 패턴이 일치하는 부분이 있는지 확인
return matcher.find(); return matcher.find();
} }
public static boolean isNullOrEmpty(String str) { public static boolean isNullOrEmpty(String str) {
if (str == null) { if (str == null) {
return true; return true;
} }
if (str.isEmpty()) { if (str.isEmpty()) {
return true; return true;
} }
return false; return false;
} }
} }

View File

@@ -1,41 +1,41 @@
package com.kamco.cd.training.common.utils; package com.kamco.cd.training.common.utils;
import com.kamco.cd.training.auth.CustomUserDetails; import com.kamco.cd.training.auth.CustomUserDetails;
import com.kamco.cd.training.members.dto.MembersDto; import com.kamco.cd.training.members.dto.MembersDto;
import com.kamco.cd.training.postgres.entity.MemberEntity; import com.kamco.cd.training.postgres.entity.MemberEntity;
import java.util.Optional; import java.util.Optional;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
@Component @Component
@RequiredArgsConstructor @RequiredArgsConstructor
public class UserUtil { public class UserUtil {
public MembersDto.Member getCurrentUser() { public MembersDto.Member getCurrentUser() {
return Optional.ofNullable(SecurityContextHolder.getContext().getAuthentication()) return Optional.ofNullable(SecurityContextHolder.getContext().getAuthentication())
.filter(auth -> auth.getPrincipal() instanceof CustomUserDetails) .filter(auth -> auth.getPrincipal() instanceof CustomUserDetails)
.map( .map(
auth -> { auth -> {
CustomUserDetails user = (CustomUserDetails) auth.getPrincipal(); CustomUserDetails user = (CustomUserDetails) auth.getPrincipal();
MemberEntity m = user.getMember(); MemberEntity m = user.getMember();
return new MembersDto.Member(m.getId(), m.getName(), m.getEmployeeNo()); return new MembersDto.Member(m.getId(), m.getName(), m.getEmployeeNo());
}) })
.orElse(null); .orElse(null);
} }
public Long getId() { public Long getId() {
MembersDto.Member user = getCurrentUser(); MembersDto.Member user = getCurrentUser();
return user != null ? user.getId() : null; return user != null ? user.getId() : null;
} }
public String getName() { public String getName() {
MembersDto.Member user = getCurrentUser(); MembersDto.Member user = getCurrentUser();
return user != null ? user.getName() : null; return user != null ? user.getName() : null;
} }
public String getEmployeeNo() { public String getEmployeeNo() {
MembersDto.Member user = getCurrentUser(); MembersDto.Member user = getCurrentUser();
return user != null ? user.getEmployeeNo() : null; return user != null ? user.getEmployeeNo() : null;
} }
} }

View File

@@ -1,20 +1,20 @@
package com.kamco.cd.training.common.utils.enums; package com.kamco.cd.training.common.utils.enums;
public class CodeDto { public class CodeDto {
private String code; private String code;
private String name; private String name;
public CodeDto(String code, String name) { public CodeDto(String code, String name) {
this.code = code; this.code = code;
this.name = name; this.name = name;
} }
public String getCode() { public String getCode() {
return code; return code;
} }
public String getName() { public String getName() {
return name; return name;
} }
} }

View File

@@ -1,10 +1,10 @@
package com.kamco.cd.training.common.utils.enums; package com.kamco.cd.training.common.utils.enums;
import java.lang.annotation.ElementType; import java.lang.annotation.ElementType;
import java.lang.annotation.Retention; import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy; import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target; import java.lang.annotation.Target;
@Target(ElementType.TYPE) @Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME) @Retention(RetentionPolicy.RUNTIME)
public @interface CodeExpose {} public @interface CodeExpose {}

View File

@@ -1,8 +1,8 @@
package com.kamco.cd.training.common.utils.enums; package com.kamco.cd.training.common.utils.enums;
public interface EnumType { public interface EnumType {
String getId(); String getId();
String getText(); String getText();
} }

View File

@@ -1,26 +1,26 @@
package com.kamco.cd.training.common.utils.enums; package com.kamco.cd.training.common.utils.enums;
import com.kamco.cd.training.common.utils.interfaces.EnumValid; import com.kamco.cd.training.common.utils.interfaces.EnumValid;
import jakarta.validation.ConstraintValidator; import jakarta.validation.ConstraintValidator;
import jakarta.validation.ConstraintValidatorContext; import jakarta.validation.ConstraintValidatorContext;
import java.util.Arrays; import java.util.Arrays;
import java.util.Set; import java.util.Set;
import java.util.stream.Collectors; import java.util.stream.Collectors;
public class EnumValidator implements ConstraintValidator<EnumValid, String> { public class EnumValidator implements ConstraintValidator<EnumValid, String> {
private Set<String> acceptedValues; private Set<String> acceptedValues;
@Override @Override
public void initialize(EnumValid constraintAnnotation) { public void initialize(EnumValid constraintAnnotation) {
acceptedValues = acceptedValues =
Arrays.stream(constraintAnnotation.enumClass().getEnumConstants()) Arrays.stream(constraintAnnotation.enumClass().getEnumConstants())
.map(Enum::name) .map(Enum::name)
.collect(Collectors.toSet()); .collect(Collectors.toSet());
} }
@Override @Override
public boolean isValid(String value, ConstraintValidatorContext context) { public boolean isValid(String value, ConstraintValidatorContext context) {
return value != null && acceptedValues.contains(value); return value != null && acceptedValues.contains(value);
} }
} }

View File

@@ -1,76 +1,76 @@
package com.kamco.cd.training.common.utils.enums; package com.kamco.cd.training.common.utils.enums;
import java.util.Arrays; import java.util.Arrays;
import java.util.HashMap; import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Set; import java.util.Set;
import org.reflections.Reflections; import org.reflections.Reflections;
public class Enums { public class Enums {
private static final String BASE_PACKAGE = "com.kamco.cd.training"; private static final String BASE_PACKAGE = "com.kamco.cd.training";
/** 노출 가능한 enum만 모아둔 맵 key: enum simpleName (예: RoleType) value: enum Class */ /** 노출 가능한 enum만 모아둔 맵 key: enum simpleName (예: RoleType) value: enum Class */
private static final Map<String, Class<? extends Enum<?>>> exposedEnumMap = scanExposedEnumMap(); private static final Map<String, Class<? extends Enum<?>>> exposedEnumMap = scanExposedEnumMap();
// code로 enum 찾기 // code로 enum 찾기
public static <E extends Enum<E> & EnumType> E fromId(Class<E> enumClass, String id) { public static <E extends Enum<E> & EnumType> E fromId(Class<E> enumClass, String id) {
if (id == null) { if (id == null) {
return null; return null;
} }
for (E e : enumClass.getEnumConstants()) { for (E e : enumClass.getEnumConstants()) {
if (id.equalsIgnoreCase(e.getId())) { if (id.equalsIgnoreCase(e.getId())) {
return e; return e;
} }
} }
return null; return null;
} }
// enum -> CodeDto list // enum -> CodeDto list
public static List<CodeDto> toList(Class<? extends Enum<?>> enumClass) { public static List<CodeDto> toList(Class<? extends Enum<?>> enumClass) {
Object[] enums = enumClass.getEnumConstants(); Object[] enums = enumClass.getEnumConstants();
return Arrays.stream(enums) return Arrays.stream(enums)
.map(e -> (EnumType) e) .map(e -> (EnumType) e)
.map(e -> new CodeDto(e.getId(), e.getText())) .map(e -> new CodeDto(e.getId(), e.getText()))
.toList(); .toList();
} }
/** 특정 타입(enum)만 조회 /codes/{type} -> type = RoleType 같은 값 */ /** 특정 타입(enum)만 조회 /codes/{type} -> type = RoleType 같은 값 */
public static List<CodeDto> getCodes(String type) { public static List<CodeDto> getCodes(String type) {
Class<? extends Enum<?>> enumClass = exposedEnumMap.get(type); Class<? extends Enum<?>> enumClass = exposedEnumMap.get(type);
if (enumClass == null) { if (enumClass == null) {
throw new IllegalArgumentException("지원하지 않는 코드 타입: " + type); throw new IllegalArgumentException("지원하지 않는 코드 타입: " + type);
} }
return toList(enumClass); return toList(enumClass);
} }
/** 전체 enum 코드 조회 */ /** 전체 enum 코드 조회 */
public static Map<String, List<CodeDto>> getAllCodes() { public static Map<String, List<CodeDto>> getAllCodes() {
Map<String, List<CodeDto>> result = new HashMap<>(); Map<String, List<CodeDto>> result = new HashMap<>();
for (Map.Entry<String, Class<? extends Enum<?>>> e : exposedEnumMap.entrySet()) { for (Map.Entry<String, Class<? extends Enum<?>>> e : exposedEnumMap.entrySet()) {
result.put(e.getKey(), toList(e.getValue())); result.put(e.getKey(), toList(e.getValue()));
} }
return result; return result;
} }
/** /**
* @CodeExpose + EnumType 인 enum만 스캔해서 Map 구성 * @CodeExpose + EnumType 인 enum만 스캔해서 Map 구성
*/ */
private static Map<String, Class<? extends Enum<?>>> scanExposedEnumMap() { private static Map<String, Class<? extends Enum<?>>> scanExposedEnumMap() {
Reflections reflections = new Reflections(BASE_PACKAGE); Reflections reflections = new Reflections(BASE_PACKAGE);
Set<Class<?>> types = reflections.getTypesAnnotatedWith(CodeExpose.class); Set<Class<?>> types = reflections.getTypesAnnotatedWith(CodeExpose.class);
Map<String, Class<? extends Enum<?>>> result = new HashMap<>(); Map<String, Class<? extends Enum<?>>> result = new HashMap<>();
for (Class<?> clazz : types) { for (Class<?> clazz : types) {
if (clazz.isEnum() && EnumType.class.isAssignableFrom(clazz)) { if (clazz.isEnum() && EnumType.class.isAssignableFrom(clazz)) {
result.put(clazz.getSimpleName(), (Class<? extends Enum<?>>) clazz); result.put(clazz.getSimpleName(), (Class<? extends Enum<?>>) clazz);
} }
} }
return result; return result;
} }
} }

View File

@@ -1,36 +1,36 @@
package com.kamco.cd.training.common.utils.geometry; package com.kamco.cd.training.common.utils.geometry;
import com.fasterxml.jackson.core.JacksonException; import com.fasterxml.jackson.core.JacksonException;
import com.fasterxml.jackson.core.JsonParser; import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.databind.DeserializationContext; import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.deser.std.StdDeserializer; import com.fasterxml.jackson.databind.deser.std.StdDeserializer;
import java.io.IOException; import java.io.IOException;
import org.locationtech.jts.geom.Geometry; import org.locationtech.jts.geom.Geometry;
import org.locationtech.jts.io.geojson.GeoJsonReader; import org.locationtech.jts.io.geojson.GeoJsonReader;
import org.springframework.util.StringUtils; import org.springframework.util.StringUtils;
public class GeometryDeserializer<T extends Geometry> extends StdDeserializer<T> { public class GeometryDeserializer<T extends Geometry> extends StdDeserializer<T> {
public GeometryDeserializer(Class<T> targetType) { public GeometryDeserializer(Class<T> targetType) {
super(targetType); super(targetType);
} }
// TODO: test code // TODO: test code
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
@Override @Override
public T deserialize(JsonParser jsonParser, DeserializationContext deserializationContext) public T deserialize(JsonParser jsonParser, DeserializationContext deserializationContext)
throws IOException, JacksonException { throws IOException, JacksonException {
String json = jsonParser.readValueAsTree().toString(); String json = jsonParser.readValueAsTree().toString();
if (!StringUtils.hasText(json)) { if (!StringUtils.hasText(json)) {
return null; return null;
} }
try { try {
GeoJsonReader reader = new GeoJsonReader(); GeoJsonReader reader = new GeoJsonReader();
return (T) reader.read(json); return (T) reader.read(json);
} catch (Exception e) { } catch (Exception e) {
throw new IllegalArgumentException("Failed to deserialize GeoJSON into Geometry", e); throw new IllegalArgumentException("Failed to deserialize GeoJSON into Geometry", e);
} }
} }
} }

View File

@@ -1,31 +1,31 @@
package com.kamco.cd.training.common.utils.geometry; package com.kamco.cd.training.common.utils.geometry;
import com.fasterxml.jackson.core.JsonGenerator; import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.databind.SerializerProvider; import com.fasterxml.jackson.databind.SerializerProvider;
import com.fasterxml.jackson.databind.ser.std.StdSerializer; import com.fasterxml.jackson.databind.ser.std.StdSerializer;
import java.io.IOException; import java.io.IOException;
import java.util.Objects; import java.util.Objects;
import org.locationtech.jts.geom.Geometry; import org.locationtech.jts.geom.Geometry;
import org.locationtech.jts.io.geojson.GeoJsonWriter; import org.locationtech.jts.io.geojson.GeoJsonWriter;
public class GeometrySerializer<T extends Geometry> extends StdSerializer<T> { public class GeometrySerializer<T extends Geometry> extends StdSerializer<T> {
// TODO: test code // TODO: test code
public GeometrySerializer(Class<T> targetType) { public GeometrySerializer(Class<T> targetType) {
super(targetType); super(targetType);
} }
@Override @Override
public void serialize( public void serialize(
T geometry, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) T geometry, JsonGenerator jsonGenerator, SerializerProvider serializerProvider)
throws IOException { throws IOException {
if (Objects.nonNull(geometry)) { if (Objects.nonNull(geometry)) {
// default: 8자리 강제로 반올림시킴. 16자리로 늘려줌 // default: 8자리 강제로 반올림시킴. 16자리로 늘려줌
GeoJsonWriter writer = new GeoJsonWriter(16); GeoJsonWriter writer = new GeoJsonWriter(16);
String json = writer.write(geometry); String json = writer.write(geometry);
jsonGenerator.writeRawValue(json); jsonGenerator.writeRawValue(json);
} else { } else {
jsonGenerator.writeNull(); jsonGenerator.writeNull();
} }
} }
} }

View File

@@ -1,18 +1,18 @@
package com.kamco.cd.training.common.utils.html; package com.kamco.cd.training.common.utils.html;
import com.fasterxml.jackson.core.JacksonException; import com.fasterxml.jackson.core.JacksonException;
import com.fasterxml.jackson.core.JsonParser; import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.databind.DeserializationContext; import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.JsonDeserializer; import com.fasterxml.jackson.databind.JsonDeserializer;
import java.io.IOException; import java.io.IOException;
import org.springframework.web.util.HtmlUtils; import org.springframework.web.util.HtmlUtils;
public class HtmlEscapeDeserializer extends JsonDeserializer<Object> { public class HtmlEscapeDeserializer extends JsonDeserializer<Object> {
@Override @Override
public Object deserialize(JsonParser jsonParser, DeserializationContext deserializationContext) public Object deserialize(JsonParser jsonParser, DeserializationContext deserializationContext)
throws IOException, JacksonException { throws IOException, JacksonException {
String value = jsonParser.getValueAsString(); String value = jsonParser.getValueAsString();
return value == null ? null : HtmlUtils.htmlEscape(value); return value == null ? null : HtmlUtils.htmlEscape(value);
} }
} }

View File

@@ -1,20 +1,20 @@
package com.kamco.cd.training.common.utils.html; package com.kamco.cd.training.common.utils.html;
import com.fasterxml.jackson.core.JsonGenerator; import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.databind.JsonSerializer; import com.fasterxml.jackson.databind.JsonSerializer;
import com.fasterxml.jackson.databind.SerializerProvider; import com.fasterxml.jackson.databind.SerializerProvider;
import java.io.IOException; import java.io.IOException;
import org.springframework.web.util.HtmlUtils; import org.springframework.web.util.HtmlUtils;
public class HtmlUnescapeSerializer extends JsonSerializer<String> { public class HtmlUnescapeSerializer extends JsonSerializer<String> {
@Override @Override
public void serialize( public void serialize(
String value, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) String value, JsonGenerator jsonGenerator, SerializerProvider serializerProvider)
throws IOException { throws IOException {
if (value == null) { if (value == null) {
jsonGenerator.writeNull(); jsonGenerator.writeNull();
} else { } else {
jsonGenerator.writeString(HtmlUtils.htmlUnescape(value)); jsonGenerator.writeString(HtmlUtils.htmlUnescape(value));
} }
} }
} }

View File

@@ -1,23 +1,23 @@
package com.kamco.cd.training.common.utils.interfaces; package com.kamco.cd.training.common.utils.interfaces;
import com.kamco.cd.training.common.utils.enums.EnumValidator; import com.kamco.cd.training.common.utils.enums.EnumValidator;
import jakarta.validation.Constraint; import jakarta.validation.Constraint;
import jakarta.validation.Payload; import jakarta.validation.Payload;
import java.lang.annotation.ElementType; import java.lang.annotation.ElementType;
import java.lang.annotation.Retention; import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy; import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target; import java.lang.annotation.Target;
@Target(ElementType.FIELD) @Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME) @Retention(RetentionPolicy.RUNTIME)
@Constraint(validatedBy = EnumValidator.class) @Constraint(validatedBy = EnumValidator.class)
public @interface EnumValid { public @interface EnumValid {
String message() default "올바르지 않은 값입니다."; String message() default "올바르지 않은 값입니다.";
Class<?>[] groups() default {}; Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {}; Class<? extends Payload>[] payload() default {};
Class<? extends Enum<?>> enumClass(); Class<? extends Enum<?>> enumClass();
} }

View File

@@ -1,15 +1,15 @@
package com.kamco.cd.training.common.utils.interfaces; package com.kamco.cd.training.common.utils.interfaces;
import com.fasterxml.jackson.annotation.JacksonAnnotationsInside; import com.fasterxml.jackson.annotation.JacksonAnnotationsInside;
import com.fasterxml.jackson.annotation.JsonFormat; import com.fasterxml.jackson.annotation.JsonFormat;
import java.lang.annotation.*; import java.lang.annotation.*;
@Target({ElementType.FIELD, ElementType.METHOD}) @Target({ElementType.FIELD, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME) @Retention(RetentionPolicy.RUNTIME)
@Documented @Documented
@JacksonAnnotationsInside @JacksonAnnotationsInside
@JsonFormat( @JsonFormat(
shape = JsonFormat.Shape.STRING, shape = JsonFormat.Shape.STRING,
pattern = "yyyy-MM-dd'T'HH:mm:ssXXX", pattern = "yyyy-MM-dd'T'HH:mm:ssXXX",
timezone = "Asia/Seoul") timezone = "Asia/Seoul")
public @interface JsonFormatDttm {} public @interface JsonFormatDttm {}

View File

@@ -1,11 +1,11 @@
package com.kamco.cd.training.config; package com.kamco.cd.training.config;
import org.springframework.cache.annotation.EnableCaching; import org.springframework.cache.annotation.EnableCaching;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
@EnableCaching @EnableCaching
@Configuration @Configuration
public class CacheConfig { public class CacheConfig {
// training 서버는 Redis 사용하지 않고 Spring Boot 메모리 캐시를 사용함 // training 서버는 Redis 사용하지 않고 Spring Boot 메모리 캐시를 사용함
// => org.springframework.cache.annotation.Cacheable // => org.springframework.cache.annotation.Cacheable
} }

View File

@@ -1,27 +1,27 @@
package com.kamco.cd.training.config; package com.kamco.cd.training.config;
import lombok.Getter; import lombok.Getter;
import lombok.Setter; import lombok.Setter;
import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
/** GeoJSON 파일 모니터링 설정 */ /** GeoJSON 파일 모니터링 설정 */
@Component @Component
@ConfigurationProperties(prefix = "file.config") @ConfigurationProperties(prefix = "file.config")
@Getter @Getter
@Setter @Setter
public class FileConfig { public class FileConfig {
// private String rootDir = "D:\\app/"; // private String rootDir = "D:\\app/";
private String rootDir = "/app/"; private String rootDir = "/app/";
private String rootSyncDir = rootDir + "original-images/"; private String rootSyncDir = rootDir + "original-images/";
private String tmpSyncDir = rootSyncDir + "tmp/"; private String tmpSyncDir = rootSyncDir + "tmp/";
private String dataSetDir = rootDir + "dataset/"; private String dataSetDir = rootDir + "dataset/";
private String tmpDataSetDir = dataSetDir + "tmp/"; private String tmpDataSetDir = dataSetDir + "tmp/";
// private String rootSyncDir = "/app/original-images/"; // private String rootSyncDir = "/app/original-images/";
// private String tmpSyncDir = rootSyncDir + "tmp/"; // private String tmpSyncDir = rootSyncDir + "tmp/";
private String syncFileExt = "tfw,tif"; private String syncFileExt = "tfw,tif";
} }

View File

@@ -1,77 +1,77 @@
package com.kamco.cd.training.config; package com.kamco.cd.training.config;
import io.swagger.v3.oas.models.Components; import io.swagger.v3.oas.models.Components;
import io.swagger.v3.oas.models.OpenAPI; import io.swagger.v3.oas.models.OpenAPI;
import io.swagger.v3.oas.models.info.Info; import io.swagger.v3.oas.models.info.Info;
import io.swagger.v3.oas.models.security.SecurityRequirement; import io.swagger.v3.oas.models.security.SecurityRequirement;
import io.swagger.v3.oas.models.security.SecurityScheme; import io.swagger.v3.oas.models.security.SecurityScheme;
import io.swagger.v3.oas.models.servers.Server; import io.swagger.v3.oas.models.servers.Server;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import org.springframework.beans.factory.annotation.Value; import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
@Configuration @Configuration
public class OpenApiConfig { public class OpenApiConfig {
@Value("${swagger.local-port}") @Value("${swagger.local-port}")
private String localPort; private String localPort;
@Value("${spring.profiles.active:local}") @Value("${spring.profiles.active:local}")
private String profile; private String profile;
@Value("${swagger.dev-url:https://kamco.training-dev-api.gs.dabeeo.com}") @Value("${swagger.dev-url:https://kamco.training-dev-api.gs.dabeeo.com}")
private String devUrl; private String devUrl;
@Value("${swagger.prod-url:https://api.training-kamco.com}") @Value("${swagger.prod-url:https://api.training-kamco.com}")
private String prodUrl; private String prodUrl;
@Bean @Bean
public OpenAPI kamcoOpenAPI() { public OpenAPI kamcoOpenAPI() {
// 1) SecurityScheme 정의 (Bearer JWT) // 1) SecurityScheme 정의 (Bearer JWT)
SecurityScheme bearerAuth = SecurityScheme bearerAuth =
new SecurityScheme() new SecurityScheme()
.type(SecurityScheme.Type.HTTP) .type(SecurityScheme.Type.HTTP)
.scheme("bearer") .scheme("bearer")
.bearerFormat("JWT") .bearerFormat("JWT")
.in(SecurityScheme.In.HEADER) .in(SecurityScheme.In.HEADER)
.name("Authorization"); .name("Authorization");
// 2) SecurityRequirement (기본으로 BearerAuth 사용) // 2) SecurityRequirement (기본으로 BearerAuth 사용)
SecurityRequirement securityRequirement = new SecurityRequirement().addList("BearerAuth"); SecurityRequirement securityRequirement = new SecurityRequirement().addList("BearerAuth");
// 3) Components 에 SecurityScheme 등록 // 3) Components 에 SecurityScheme 등록
Components components = new Components().addSecuritySchemes("BearerAuth", bearerAuth); Components components = new Components().addSecuritySchemes("BearerAuth", bearerAuth);
// profile 별 server url 분기 // profile 별 server url 분기
List<Server> servers = new ArrayList<>(); List<Server> servers = new ArrayList<>();
if ("dev".equals(profile)) { if ("dev".equals(profile)) {
servers.add(new Server().url(devUrl).description("개발 서버")); servers.add(new Server().url(devUrl).description("개발 서버"));
servers.add(new Server().url("http://localhost:" + localPort).description("로컬 서버")); servers.add(new Server().url("http://localhost:" + localPort).description("로컬 서버"));
// servers.add(new Server().url(prodUrl).description("운영 서버")); // servers.add(new Server().url(prodUrl).description("운영 서버"));
} else if ("prod".equals(profile)) { } else if ("prod".equals(profile)) {
// servers.add(new Server().url(prodUrl).description("운영 서버")); // servers.add(new Server().url(prodUrl).description("운영 서버"));
servers.add(new Server().url("http://localhost:" + localPort).description("로컬 서버")); servers.add(new Server().url("http://localhost:" + localPort).description("로컬 서버"));
servers.add(new Server().url(devUrl).description("개발 서버")); servers.add(new Server().url(devUrl).description("개발 서버"));
} else { } else {
servers.add(new Server().url("http://localhost:" + localPort).description("로컬 서버")); servers.add(new Server().url("http://localhost:" + localPort).description("로컬 서버"));
servers.add(new Server().url(devUrl).description("개발 서버")); servers.add(new Server().url(devUrl).description("개발 서버"));
// servers.add(new Server().url(prodUrl).description("운영 서버")); // servers.add(new Server().url(prodUrl).description("운영 서버"));
} }
return new OpenAPI() return new OpenAPI()
.info( .info(
new Info() new Info()
.title("KAMCO Change Detection API") .title("KAMCO Change Detection API")
.description( .description(
"KAMCO 변화 탐지 시스템 API 문서\n\n" "KAMCO 변화 탐지 시스템 API 문서\n\n"
+ "이 API는 지리공간 데이터를 활용한 변화 탐지 시스템을 제공합니다.\n" + "이 API는 지리공간 데이터를 활용한 변화 탐지 시스템을 제공합니다.\n"
+ "GeoJSON 형식의 공간 데이터를 처리하며, PostgreSQL/PostGIS 기반으로 동작합니다.") + "GeoJSON 형식의 공간 데이터를 처리하며, PostgreSQL/PostGIS 기반으로 동작합니다.")
.version("v1.0.0")) .version("v1.0.0"))
.servers(servers) .servers(servers)
// 만들어둔 components를 넣어야 함 // 만들어둔 components를 넣어야 함
.components(components) .components(components)
.addSecurityItem(securityRequirement); .addSecurityItem(securityRequirement);
} }
} }

View File

@@ -1,136 +1,136 @@
package com.kamco.cd.training.config; package com.kamco.cd.training.config;
import com.kamco.cd.training.auth.CustomAuthenticationProvider; import com.kamco.cd.training.auth.CustomAuthenticationProvider;
import com.kamco.cd.training.auth.JwtAuthenticationFilter; import com.kamco.cd.training.auth.JwtAuthenticationFilter;
import java.util.List; import java.util.List;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpMethod; import org.springframework.http.HttpMethod;
import org.springframework.security.authentication.AuthenticationManager; import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration; 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.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityCustomizer; import org.springframework.security.config.annotation.web.configuration.WebSecurityCustomizer;
import org.springframework.security.config.http.SessionCreationPolicy; import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.SecurityFilterChain; import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.security.web.firewall.HttpFirewall; import org.springframework.security.web.firewall.HttpFirewall;
import org.springframework.security.web.firewall.StrictHttpFirewall; import org.springframework.security.web.firewall.StrictHttpFirewall;
import org.springframework.web.cors.CorsConfiguration; import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.CorsConfigurationSource; import org.springframework.web.cors.CorsConfigurationSource;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource; import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
@Configuration @Configuration
@EnableWebSecurity @EnableWebSecurity
public class SecurityConfig { public class SecurityConfig {
@Bean @Bean
public SecurityFilterChain securityFilterChain( public SecurityFilterChain securityFilterChain(
org.springframework.security.config.annotation.web.builders.HttpSecurity http, org.springframework.security.config.annotation.web.builders.HttpSecurity http,
JwtAuthenticationFilter jwtAuthenticationFilter, JwtAuthenticationFilter jwtAuthenticationFilter,
CustomAuthenticationProvider customAuthenticationProvider) CustomAuthenticationProvider customAuthenticationProvider)
throws Exception { throws Exception {
http.cors(cors -> cors.configurationSource(corsConfigurationSource())) http.cors(cors -> cors.configurationSource(corsConfigurationSource()))
.csrf(csrf -> csrf.disable()) .csrf(csrf -> csrf.disable())
.sessionManagement(sm -> sm.sessionCreationPolicy(SessionCreationPolicy.STATELESS)) .sessionManagement(sm -> sm.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
.formLogin(form -> form.disable()) .formLogin(form -> form.disable())
// /monitor 에서 Basic 인증을 쓰려면 disable 하면 안됨 // /monitor 에서 Basic 인증을 쓰려면 disable 하면 안됨
.httpBasic(basic -> {}) .httpBasic(basic -> {})
.logout(logout -> logout.disable()) .logout(logout -> logout.disable())
.authenticationProvider(customAuthenticationProvider) .authenticationProvider(customAuthenticationProvider)
.authorizeHttpRequests( .authorizeHttpRequests(
auth -> auth ->
auth auth
// monitor // monitor
.requestMatchers("/monitor/health", "/monitor/health/**") .requestMatchers("/monitor/health", "/monitor/health/**")
.permitAll() .permitAll()
.requestMatchers("/monitor/**") .requestMatchers("/monitor/**")
.authenticated() // Basic으로 인증되게끔 .authenticated() // Basic으로 인증되게끔
// mapsheet // mapsheet
.requestMatchers("/api/mapsheet/**") .requestMatchers("/api/mapsheet/**")
.permitAll() .permitAll()
.requestMatchers(HttpMethod.POST, "/api/mapsheet/upload") .requestMatchers(HttpMethod.POST, "/api/mapsheet/upload")
.permitAll() .permitAll()
// test role // test role
.requestMatchers("/api/test/admin") .requestMatchers("/api/test/admin")
.hasRole("ADMIN") .hasRole("ADMIN")
.requestMatchers("/api/test/label") .requestMatchers("/api/test/label")
.hasAnyRole("ADMIN", "LABELER") .hasAnyRole("ADMIN", "LABELER")
.requestMatchers("/api/test/review") .requestMatchers("/api/test/review")
.hasAnyRole("ADMIN", "REVIEWER") .hasAnyRole("ADMIN", "REVIEWER")
// common permit // common permit
.requestMatchers("/error") .requestMatchers("/error")
.permitAll() .permitAll()
.requestMatchers(HttpMethod.OPTIONS, "/**") .requestMatchers(HttpMethod.OPTIONS, "/**")
.permitAll() .permitAll()
.requestMatchers( .requestMatchers(
"/api/auth/signin", "/api/auth/signin",
"/api/auth/refresh", "/api/auth/refresh",
"/api/auth/logout", "/api/auth/logout",
"/swagger-ui/**", "/swagger-ui/**",
"/v3/api-docs/**", "/v3/api-docs/**",
"/api/members/*/password", "/api/members/*/password",
"/api/upload/chunk-upload-dataset", "/api/upload/chunk-upload-dataset",
"/api/upload/chunk-upload-complete") "/api/upload/chunk-upload-complete")
.permitAll() .permitAll()
// default // default
.anyRequest() .anyRequest()
.authenticated()) .authenticated())
// JWT 필터는 앞단에 // JWT 필터는 앞단에
.addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class); .addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class);
return http.build(); return http.build();
} }
@Bean @Bean
public AuthenticationManager authenticationManager(AuthenticationConfiguration configuration) public AuthenticationManager authenticationManager(AuthenticationConfiguration configuration)
throws Exception { throws Exception {
return configuration.getAuthenticationManager(); return configuration.getAuthenticationManager();
} }
@Bean @Bean
public PasswordEncoder passwordEncoder() { public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder(); return new BCryptPasswordEncoder();
} }
/** CORS 설정 */ /** CORS 설정 */
@Bean @Bean
public CorsConfigurationSource corsConfigurationSource() { public CorsConfigurationSource corsConfigurationSource() {
CorsConfiguration config = new CorsConfiguration(); // CORS 객체 생성 CorsConfiguration config = new CorsConfiguration(); // CORS 객체 생성
config.setAllowedOriginPatterns(List.of("*")); // 도메인 허용 config.setAllowedOriginPatterns(List.of("*")); // 도메인 허용
config.setAllowedMethods(List.of("GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS")); config.setAllowedMethods(List.of("GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS"));
config.setAllowedHeaders(List.of("*")); // 헤더요청 Authorization, Content-Type, X-Custom-Header config.setAllowedHeaders(List.of("*")); // 헤더요청 Authorization, Content-Type, X-Custom-Header
config.setAllowCredentials(true); // 쿠키, Authorization 헤더, Bearer Token 등 자격증명 포함 요청을 허용할지 설정 config.setAllowCredentials(true); // 쿠키, Authorization 헤더, Bearer Token 등 자격증명 포함 요청을 허용할지 설정
config.setExposedHeaders(List.of("Content-Disposition")); config.setExposedHeaders(List.of("Content-Disposition"));
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(); UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
/** "/**" → 모든 API 경로에 대해 이 CORS 규칙을 적용 /api/** 같이 특정 경로만 지정 가능. */ /** "/**" → 모든 API 경로에 대해 이 CORS 규칙을 적용 /api/** 같이 특정 경로만 지정 가능. */
source.registerCorsConfiguration("/**", config); // CORS 정책을 등록 source.registerCorsConfiguration("/**", config); // CORS 정책을 등록
return source; return source;
} }
@Bean @Bean
public HttpFirewall httpFirewall() { public HttpFirewall httpFirewall() {
StrictHttpFirewall firewall = new StrictHttpFirewall(); StrictHttpFirewall firewall = new StrictHttpFirewall();
firewall.setAllowUrlEncodedSlash(true); firewall.setAllowUrlEncodedSlash(true);
firewall.setAllowUrlEncodedDoubleSlash(true); firewall.setAllowUrlEncodedDoubleSlash(true);
firewall.setAllowUrlEncodedPercent(true); firewall.setAllowUrlEncodedPercent(true);
firewall.setAllowSemicolon(true); firewall.setAllowSemicolon(true);
return firewall; return firewall;
} }
/** 완전 제외(필터 자체를 안 탐) */ /** 완전 제외(필터 자체를 안 탐) */
@Bean @Bean
public WebSecurityCustomizer webSecurityCustomizer() { public WebSecurityCustomizer webSecurityCustomizer() {
return (web) -> web.ignoring().requestMatchers("/api/mapsheet/**"); return (web) -> web.ignoring().requestMatchers("/api/mapsheet/**");
} }
} }

View File

@@ -1,96 +1,96 @@
package com.kamco.cd.training.config; package com.kamco.cd.training.config;
import com.zaxxer.hikari.HikariDataSource; import com.zaxxer.hikari.HikariDataSource;
import javax.sql.DataSource; import javax.sql.DataSource;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.context.event.ApplicationReadyEvent; import org.springframework.boot.context.event.ApplicationReadyEvent;
import org.springframework.context.event.EventListener; import org.springframework.context.event.EventListener;
import org.springframework.core.env.Environment; import org.springframework.core.env.Environment;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
@Slf4j @Slf4j
@Component @Component
@RequiredArgsConstructor @RequiredArgsConstructor
public class StartupLogger { public class StartupLogger {
private final Environment environment; private final Environment environment;
private final DataSource dataSource; private final DataSource dataSource;
@EventListener(ApplicationReadyEvent.class) @EventListener(ApplicationReadyEvent.class)
public void logStartupInfo() { public void logStartupInfo() {
String[] activeProfiles = environment.getActiveProfiles(); String[] activeProfiles = environment.getActiveProfiles();
String profileInfo = activeProfiles.length > 0 ? String.join(", ", activeProfiles) : "default"; String profileInfo = activeProfiles.length > 0 ? String.join(", ", activeProfiles) : "default";
// Database connection information // Database connection information
String dbUrl = environment.getProperty("spring.datasource.url"); String dbUrl = environment.getProperty("spring.datasource.url");
String dbUsername = environment.getProperty("spring.datasource.username"); String dbUsername = environment.getProperty("spring.datasource.username");
String dbDriver = environment.getProperty("spring.datasource.driver-class-name"); String dbDriver = environment.getProperty("spring.datasource.driver-class-name");
// HikariCP pool settings // HikariCP pool settings
String poolInfo = ""; String poolInfo = "";
if (dataSource instanceof HikariDataSource hikariDs) { if (dataSource instanceof HikariDataSource hikariDs) {
poolInfo = poolInfo =
String.format( String.format(
""" """
│ Pool Size : min=%d, max=%d │ Pool Size : min=%d, max=%d
│ Connection Timeout: %dms │ Connection Timeout: %dms
│ Idle Timeout : %dms │ Idle Timeout : %dms
│ Max Lifetime : %dms""", │ Max Lifetime : %dms""",
hikariDs.getMinimumIdle(), hikariDs.getMinimumIdle(),
hikariDs.getMaximumPoolSize(), hikariDs.getMaximumPoolSize(),
hikariDs.getConnectionTimeout(), hikariDs.getConnectionTimeout(),
hikariDs.getIdleTimeout(), hikariDs.getIdleTimeout(),
hikariDs.getMaxLifetime()); hikariDs.getMaxLifetime());
} }
// JPA/Hibernate settings // JPA/Hibernate settings
String showSql = environment.getProperty("spring.jpa.show-sql", "false"); String showSql = environment.getProperty("spring.jpa.show-sql", "false");
String ddlAuto = environment.getProperty("spring.jpa.hibernate.ddl-auto", "none"); String ddlAuto = environment.getProperty("spring.jpa.hibernate.ddl-auto", "none");
String batchSize = String batchSize =
environment.getProperty("spring.jpa.properties.hibernate.jdbc.batch_size", "N/A"); environment.getProperty("spring.jpa.properties.hibernate.jdbc.batch_size", "N/A");
String batchFetchSize = String batchFetchSize =
environment.getProperty("spring.jpa.properties.hibernate.default_batch_fetch_size", "N/A"); environment.getProperty("spring.jpa.properties.hibernate.default_batch_fetch_size", "N/A");
String startupMessage = String startupMessage =
String.format( String.format(
""" """
╔════════════════════════════════════════════════════════════════════════════════╗ ╔════════════════════════════════════════════════════════════════════════════════╗
║ 🚀 APPLICATION STARTUP INFORMATION ║ ║ 🚀 APPLICATION STARTUP INFORMATION ║
╠════════════════════════════════════════════════════════════════════════════════╣ ╠════════════════════════════════════════════════════════════════════════════════╣
║ PROFILE CONFIGURATION ║ ║ PROFILE CONFIGURATION ║
╠────────────────────────────────────────────────────────────────────────────────╣ ╠────────────────────────────────────────────────────────────────────────────────╣
│ Active Profile(s): %s │ Active Profile(s): %s
╠════════════════════════════════════════════════════════════════════════════════╣ ╠════════════════════════════════════════════════════════════════════════════════╣
║ DATABASE CONFIGURATION ║ ║ DATABASE CONFIGURATION ║
╠────────────────────────────────────────────────────────────────────────────────╣ ╠────────────────────────────────────────────────────────────────────────────────╣
│ Database URL : %s │ Database URL : %s
│ Username : %s │ Username : %s
│ Driver : %s │ Driver : %s
╠════════════════════════════════════════════════════════════════════════════════╣ ╠════════════════════════════════════════════════════════════════════════════════╣
║ HIKARICP CONNECTION POOL ║ ║ HIKARICP CONNECTION POOL ║
╠────────────────────────────────────────────────────────────────────────────────╣ ╠────────────────────────────────────────────────────────────────────────────────╣
%s %s
╠════════════════════════════════════════════════════════════════════════════════╣ ╠════════════════════════════════════════════════════════════════════════════════╣
║ JPA/HIBERNATE CONFIGURATION ║ ║ JPA/HIBERNATE CONFIGURATION ║
╠────────────────────────────────────────────────────────────────────────────────╣ ╠────────────────────────────────────────────────────────────────────────────────╣
│ Show SQL : %s │ Show SQL : %s
│ DDL Auto : %s │ DDL Auto : %s
│ JDBC Batch Size : %s │ JDBC Batch Size : %s
│ Fetch Batch Size : %s │ Fetch Batch Size : %s
╚════════════════════════════════════════════════════════════════════════════════╝ ╚════════════════════════════════════════════════════════════════════════════════╝
""", """,
profileInfo, profileInfo,
dbUrl != null ? dbUrl : "N/A", dbUrl != null ? dbUrl : "N/A",
dbUsername != null ? dbUsername : "N/A", dbUsername != null ? dbUsername : "N/A",
dbDriver != null ? dbDriver : "PostgreSQL JDBC Driver (auto-detected)", dbDriver != null ? dbDriver : "PostgreSQL JDBC Driver (auto-detected)",
poolInfo, poolInfo,
showSql, showSql,
ddlAuto, ddlAuto,
batchSize, batchSize,
batchFetchSize); batchFetchSize);
log.info(startupMessage); log.info(startupMessage);
} }
} }

View File

@@ -1,13 +1,13 @@
package com.kamco.cd.training.config; package com.kamco.cd.training.config;
import io.swagger.v3.oas.annotations.enums.SecuritySchemeType; import io.swagger.v3.oas.annotations.enums.SecuritySchemeType;
import io.swagger.v3.oas.annotations.security.SecurityScheme; import io.swagger.v3.oas.annotations.security.SecurityScheme;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
@Configuration @Configuration
@SecurityScheme( @SecurityScheme(
name = "BearerAuth", name = "BearerAuth",
type = SecuritySchemeType.HTTP, type = SecuritySchemeType.HTTP,
scheme = "bearer", scheme = "bearer",
bearerFormat = "JWT") bearerFormat = "JWT")
public class SwaggerConfig {} public class SwaggerConfig {}

View File

@@ -1,32 +1,32 @@
package com.kamco.cd.training.config; package com.kamco.cd.training.config;
import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.module.SimpleModule; import com.fasterxml.jackson.databind.module.SimpleModule;
import com.kamco.cd.training.common.utils.geometry.GeometryDeserializer; import com.kamco.cd.training.common.utils.geometry.GeometryDeserializer;
import com.kamco.cd.training.common.utils.geometry.GeometrySerializer; import com.kamco.cd.training.common.utils.geometry.GeometrySerializer;
import org.locationtech.jts.geom.Geometry; import org.locationtech.jts.geom.Geometry;
import org.locationtech.jts.geom.Point; import org.locationtech.jts.geom.Point;
import org.locationtech.jts.geom.Polygon; import org.locationtech.jts.geom.Polygon;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
import org.springframework.http.converter.json.Jackson2ObjectMapperBuilder; import org.springframework.http.converter.json.Jackson2ObjectMapperBuilder;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Configuration @Configuration
public class WebConfig implements WebMvcConfigurer { public class WebConfig implements WebMvcConfigurer {
@Bean @Bean
public ObjectMapper objectMapper() { public ObjectMapper objectMapper() {
SimpleModule module = new SimpleModule(); SimpleModule module = new SimpleModule();
module.addSerializer(Geometry.class, new GeometrySerializer<>(Geometry.class)); module.addSerializer(Geometry.class, new GeometrySerializer<>(Geometry.class));
module.addDeserializer(Geometry.class, new GeometryDeserializer<>(Geometry.class)); module.addDeserializer(Geometry.class, new GeometryDeserializer<>(Geometry.class));
module.addSerializer(Polygon.class, new GeometrySerializer<>(Polygon.class)); module.addSerializer(Polygon.class, new GeometrySerializer<>(Polygon.class));
module.addDeserializer(Polygon.class, new GeometryDeserializer<>(Polygon.class)); module.addDeserializer(Polygon.class, new GeometryDeserializer<>(Polygon.class));
module.addSerializer(Point.class, new GeometrySerializer<>(Point.class)); module.addSerializer(Point.class, new GeometrySerializer<>(Point.class));
module.addDeserializer(Point.class, new GeometryDeserializer<>(Point.class)); module.addDeserializer(Point.class, new GeometryDeserializer<>(Point.class));
return Jackson2ObjectMapperBuilder.json().modulesToInstall(module).build(); return Jackson2ObjectMapperBuilder.json().modulesToInstall(module).build();
} }
} }

View File

@@ -1,28 +1,28 @@
package com.kamco.cd.training.config.api; package com.kamco.cd.training.config.api;
import jakarta.servlet.FilterChain; import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException; import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse; import jakarta.servlet.http.HttpServletResponse;
import java.io.IOException; import java.io.IOException;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import org.springframework.web.filter.OncePerRequestFilter; import org.springframework.web.filter.OncePerRequestFilter;
import org.springframework.web.util.ContentCachingRequestWrapper; import org.springframework.web.util.ContentCachingRequestWrapper;
import org.springframework.web.util.ContentCachingResponseWrapper; import org.springframework.web.util.ContentCachingResponseWrapper;
@Component @Component
public class ApiLogFilter extends OncePerRequestFilter { public class ApiLogFilter extends OncePerRequestFilter {
@Override @Override
protected void doFilterInternal( protected void doFilterInternal(
HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
throws ServletException, IOException { throws ServletException, IOException {
ContentCachingRequestWrapper wrappedRequest = new ContentCachingRequestWrapper(request); ContentCachingRequestWrapper wrappedRequest = new ContentCachingRequestWrapper(request);
ContentCachingResponseWrapper wrappedResponse = new ContentCachingResponseWrapper(response); ContentCachingResponseWrapper wrappedResponse = new ContentCachingResponseWrapper(response);
filterChain.doFilter(wrappedRequest, wrappedResponse); filterChain.doFilter(wrappedRequest, wrappedResponse);
// 반드시 response body copy // 반드시 response body copy
wrappedResponse.copyBodyToResponse(); wrappedResponse.copyBodyToResponse();
} }
} }

View File

@@ -1,132 +1,132 @@
package com.kamco.cd.training.config.api; package com.kamco.cd.training.config.api;
import com.kamco.cd.training.log.dto.EventStatus; import com.kamco.cd.training.log.dto.EventStatus;
import com.kamco.cd.training.log.dto.EventType; import com.kamco.cd.training.log.dto.EventType;
import com.kamco.cd.training.menu.dto.MenuDto; import com.kamco.cd.training.menu.dto.MenuDto;
import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletRequest;
import java.io.UnsupportedEncodingException; import java.io.UnsupportedEncodingException;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import org.springframework.web.util.ContentCachingRequestWrapper; import org.springframework.web.util.ContentCachingRequestWrapper;
public class ApiLogFunction { public class ApiLogFunction {
// 클라이언트 IP 추출 // 클라이언트 IP 추출
public static String getClientIp(HttpServletRequest request) { public static String getClientIp(HttpServletRequest request) {
String[] headers = { String[] headers = {
"X-Forwarded-For", "X-Forwarded-For",
"Proxy-Client-IP", "Proxy-Client-IP",
"WL-Proxy-Client-IP", "WL-Proxy-Client-IP",
"HTTP_CLIENT_IP", "HTTP_CLIENT_IP",
"HTTP_X_FORWARDED_FOR" "HTTP_X_FORWARDED_FOR"
}; };
for (String header : headers) { for (String header : headers) {
String ip = request.getHeader(header); String ip = request.getHeader(header);
if (ip != null && ip.length() != 0 && !"unknown".equalsIgnoreCase(ip)) { if (ip != null && ip.length() != 0 && !"unknown".equalsIgnoreCase(ip)) {
return ip.split(",")[0]; return ip.split(",")[0];
} }
} }
String ip = request.getRemoteAddr(); String ip = request.getRemoteAddr();
if ("0:0:0:0:0:0:0:1".equals(ip)) { // local 일 때 if ("0:0:0:0:0:0:0:1".equals(ip)) { // local 일 때
ip = "127.0.0.1"; ip = "127.0.0.1";
} }
return ip; return ip;
} }
// 사용자 ID 추출 예시 (Spring Security 기준) // 사용자 ID 추출 예시 (Spring Security 기준)
public static String getUserId(HttpServletRequest request) { public static String getUserId(HttpServletRequest request) {
try { try {
return request.getUserPrincipal() != null ? request.getUserPrincipal().getName() : null; return request.getUserPrincipal() != null ? request.getUserPrincipal().getName() : null;
} catch (Exception e) { } catch (Exception e) {
return null; return null;
} }
} }
public static EventType getEventType(HttpServletRequest request) { public static EventType getEventType(HttpServletRequest request) {
String method = request.getMethod().toUpperCase(); String method = request.getMethod().toUpperCase();
String uri = request.getRequestURI().toLowerCase(); String uri = request.getRequestURI().toLowerCase();
// URL 기반 DOWNLOAD/PRINT 분류 // URL 기반 DOWNLOAD/PRINT 분류
if (uri.contains("/download") || uri.contains("/export")) { if (uri.contains("/download") || uri.contains("/export")) {
return EventType.DOWNLOAD; return EventType.DOWNLOAD;
} }
if (uri.contains("/print")) { if (uri.contains("/print")) {
return EventType.PRINT; return EventType.PRINT;
} }
// 일반 CRUD // 일반 CRUD
return switch (method) { return switch (method) {
case "POST" -> EventType.CREATE; case "POST" -> EventType.CREATE;
case "GET" -> EventType.READ; case "GET" -> EventType.READ;
case "DELETE" -> EventType.DELETE; case "DELETE" -> EventType.DELETE;
case "PUT", "PATCH" -> EventType.UPDATE; case "PUT", "PATCH" -> EventType.UPDATE;
default -> EventType.OTHER; default -> EventType.OTHER;
}; };
} }
public static String getRequestBody( public static String getRequestBody(
HttpServletRequest servletRequest, ContentCachingRequestWrapper contentWrapper) { HttpServletRequest servletRequest, ContentCachingRequestWrapper contentWrapper) {
StringBuilder resultBody = new StringBuilder(); StringBuilder resultBody = new StringBuilder();
// GET, form-urlencoded POST 파라미터 // GET, form-urlencoded POST 파라미터
Map<String, String[]> paramMap = servletRequest.getParameterMap(); Map<String, String[]> paramMap = servletRequest.getParameterMap();
String queryParams = String queryParams =
paramMap.entrySet().stream() paramMap.entrySet().stream()
.map(e -> e.getKey() + "=" + String.join(",", e.getValue())) .map(e -> e.getKey() + "=" + String.join(",", e.getValue()))
.collect(Collectors.joining("&")); .collect(Collectors.joining("&"));
resultBody.append(queryParams.isEmpty() ? "" : queryParams); resultBody.append(queryParams.isEmpty() ? "" : queryParams);
// JSON Body // JSON Body
if ("POST".equalsIgnoreCase(servletRequest.getMethod()) if ("POST".equalsIgnoreCase(servletRequest.getMethod())
&& servletRequest.getContentType() != null && servletRequest.getContentType() != null
&& servletRequest.getContentType().contains("application/json")) { && servletRequest.getContentType().contains("application/json")) {
try { try {
// json인 경우는 Wrapper를 통해 가져오기 // json인 경우는 Wrapper를 통해 가져오기
resultBody.append(getBodyData(contentWrapper)); resultBody.append(getBodyData(contentWrapper));
} catch (Exception e) { } catch (Exception e) {
resultBody.append("cannot read JSON body ").append(e.toString()); resultBody.append("cannot read JSON body ").append(e.toString());
} }
} }
// Multipart form-data // Multipart form-data
if ("POST".equalsIgnoreCase(servletRequest.getMethod()) if ("POST".equalsIgnoreCase(servletRequest.getMethod())
&& servletRequest.getContentType() != null && servletRequest.getContentType() != null
&& servletRequest.getContentType().startsWith("multipart/form-data")) { && servletRequest.getContentType().startsWith("multipart/form-data")) {
resultBody.append("multipart/form-data request"); resultBody.append("multipart/form-data request");
} }
return resultBody.toString(); return resultBody.toString();
} }
// JSON Body 읽기 // JSON Body 읽기
public static String getBodyData(ContentCachingRequestWrapper request) { public static String getBodyData(ContentCachingRequestWrapper request) {
byte[] buf = request.getContentAsByteArray(); byte[] buf = request.getContentAsByteArray();
if (buf.length == 0) { if (buf.length == 0) {
return null; return null;
} }
try { try {
return new String(buf, request.getCharacterEncoding()); return new String(buf, request.getCharacterEncoding());
} catch (UnsupportedEncodingException e) { } catch (UnsupportedEncodingException e) {
return new String(buf); return new String(buf);
} }
} }
// ApiResponse 의 Status가 2xx 범위이면 SUCCESS, 아니면 FAILED // ApiResponse 의 Status가 2xx 범위이면 SUCCESS, 아니면 FAILED
public static EventStatus isSuccessFail(ApiResponseDto<?> apiResponse) { public static EventStatus isSuccessFail(ApiResponseDto<?> apiResponse) {
return apiResponse.getHttpStatus().is2xxSuccessful() ? EventStatus.SUCCESS : EventStatus.FAILED; return apiResponse.getHttpStatus().is2xxSuccessful() ? EventStatus.SUCCESS : EventStatus.FAILED;
} }
public static String getUriMenuInfo(List<MenuDto.Basic> menuList, String uri) { public static String getUriMenuInfo(List<MenuDto.Basic> menuList, String uri) {
MenuDto.Basic m = MenuDto.Basic m =
menuList.stream() menuList.stream()
.filter(menu -> menu.getMenuApiUrl() != null && uri.contains(menu.getMenuApiUrl())) .filter(menu -> menu.getMenuApiUrl() != null && uri.contains(menu.getMenuApiUrl()))
.findFirst() .findFirst()
.orElse(null); .orElse(null);
return m != null ? m.getMenuUid() : "SYSTEM"; return m != null ? m.getMenuUid() : "SYSTEM";
} }
} }

View File

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

View File

@@ -1,199 +1,199 @@
package com.kamco.cd.training.config.api; package com.kamco.cd.training.config.api;
import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.annotation.JsonInclude;
import com.kamco.cd.training.common.utils.enums.EnumType; import com.kamco.cd.training.common.utils.enums.EnumType;
import lombok.Getter; import lombok.Getter;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import lombok.ToString; import lombok.ToString;
import org.springframework.http.HttpStatus; import org.springframework.http.HttpStatus;
@Getter @Getter
@ToString @ToString
public class ApiResponseDto<T> { public class ApiResponseDto<T> {
private T data; private T data;
@JsonInclude(JsonInclude.Include.NON_NULL) @JsonInclude(JsonInclude.Include.NON_NULL)
private Error error; private Error error;
@JsonInclude(JsonInclude.Include.NON_NULL) @JsonInclude(JsonInclude.Include.NON_NULL)
private T errorData; private T errorData;
@JsonIgnore private HttpStatus httpStatus; @JsonIgnore private HttpStatus httpStatus;
@JsonIgnore private Long errorLogUid; @JsonIgnore private Long errorLogUid;
public ApiResponseDto(T data) { public ApiResponseDto(T data) {
this.data = data; this.data = data;
} }
private ApiResponseDto(T data, HttpStatus httpStatus) { private ApiResponseDto(T data, HttpStatus httpStatus) {
this.data = data; this.data = data;
this.httpStatus = httpStatus; this.httpStatus = httpStatus;
} }
public ApiResponseDto(ApiResponseCode code) { public ApiResponseDto(ApiResponseCode code) {
this.error = new Error(code.getId(), code.getMessage()); this.error = new Error(code.getId(), code.getMessage());
} }
public ApiResponseDto(ApiResponseCode code, String message) { public ApiResponseDto(ApiResponseCode code, String message) {
this.error = new Error(code.getId(), message); this.error = new Error(code.getId(), message);
} }
public ApiResponseDto(ApiResponseCode code, String message, HttpStatus httpStatus) { public ApiResponseDto(ApiResponseCode code, String message, HttpStatus httpStatus) {
this.error = new Error(code.getId(), message); this.error = new Error(code.getId(), message);
this.httpStatus = httpStatus; this.httpStatus = httpStatus;
} }
public ApiResponseDto( public ApiResponseDto(
ApiResponseCode code, String message, HttpStatus httpStatus, Long errorLogUid) { ApiResponseCode code, String message, HttpStatus httpStatus, Long errorLogUid) {
this.error = new Error(code.getId(), message); this.error = new Error(code.getId(), message);
this.httpStatus = httpStatus; this.httpStatus = httpStatus;
this.errorLogUid = errorLogUid; this.errorLogUid = errorLogUid;
} }
public ApiResponseDto(ApiResponseCode code, String message, T errorData) { public ApiResponseDto(ApiResponseCode code, String message, T errorData) {
this.error = new Error(code.getId(), message); this.error = new Error(code.getId(), message);
this.errorData = errorData; this.errorData = errorData;
} }
// HTTP 상태 코드가 내장된 ApiResponseDto 반환 메서드들 // HTTP 상태 코드가 내장된 ApiResponseDto 반환 메서드들
public static <T> ApiResponseDto<T> createOK(T data) { public static <T> ApiResponseDto<T> createOK(T data) {
return new ApiResponseDto<>(data, HttpStatus.CREATED); return new ApiResponseDto<>(data, HttpStatus.CREATED);
} }
public static <T> ApiResponseDto<T> ok(T data) { public static <T> ApiResponseDto<T> ok(T data) {
return new ApiResponseDto<>(data, HttpStatus.OK); return new ApiResponseDto<>(data, HttpStatus.OK);
} }
public static <T> ApiResponseDto<ResponseObj> okObject(ResponseObj data) { public static <T> ApiResponseDto<ResponseObj> okObject(ResponseObj data) {
if (data.getCode().equals(ApiResponseCode.OK)) { if (data.getCode().equals(ApiResponseCode.OK)) {
return new ApiResponseDto<>(data, HttpStatus.NO_CONTENT); return new ApiResponseDto<>(data, HttpStatus.NO_CONTENT);
} else { } else {
return new ApiResponseDto<>(data.getCode(), data.getMessage(), HttpStatus.CONFLICT); return new ApiResponseDto<>(data.getCode(), data.getMessage(), HttpStatus.CONFLICT);
} }
} }
public static <T> ApiResponseDto<T> deleteOk(T data) { public static <T> ApiResponseDto<T> deleteOk(T data) {
return new ApiResponseDto<>(data, HttpStatus.NO_CONTENT); return new ApiResponseDto<>(data, HttpStatus.NO_CONTENT);
} }
public static ApiResponseDto<String> createException(ApiResponseCode code) { public static ApiResponseDto<String> createException(ApiResponseCode code) {
return new ApiResponseDto<>(code); return new ApiResponseDto<>(code);
} }
public static ApiResponseDto<String> createException(ApiResponseCode code, String message) { public static ApiResponseDto<String> createException(ApiResponseCode code, String message) {
return new ApiResponseDto<>(code, message); return new ApiResponseDto<>(code, message);
} }
public static ApiResponseDto<String> createException( public static ApiResponseDto<String> createException(
ApiResponseCode code, String message, HttpStatus httpStatus) { ApiResponseCode code, String message, HttpStatus httpStatus) {
return new ApiResponseDto<>(code, message, httpStatus); return new ApiResponseDto<>(code, message, httpStatus);
} }
public static ApiResponseDto<String> createException( public static ApiResponseDto<String> createException(
ApiResponseCode code, String message, HttpStatus httpStatus, Long errorLogUid) { ApiResponseCode code, String message, HttpStatus httpStatus, Long errorLogUid) {
return new ApiResponseDto<>(code, message, httpStatus, errorLogUid); return new ApiResponseDto<>(code, message, httpStatus, errorLogUid);
} }
public static <T> ApiResponseDto<T> createException( public static <T> ApiResponseDto<T> createException(
ApiResponseCode code, String message, T data) { ApiResponseCode code, String message, T data) {
return new ApiResponseDto<>(code, message, data); return new ApiResponseDto<>(code, message, data);
} }
@Getter @Getter
public static class Error { public static class Error {
private final String code; private final String code;
private final String message; private final String message;
public Error(String code, String message) { public Error(String code, String message) {
this.code = code; this.code = code;
this.message = message; this.message = message;
} }
} }
/** Error가 아닌 Business상 성공이거나 실패인 경우, 메세지 함께 전달하기 위한 object */ /** Error가 아닌 Business상 성공이거나 실패인 경우, 메세지 함께 전달하기 위한 object */
@Getter @Getter
public static class ResponseObj { public static class ResponseObj {
private final ApiResponseCode code; private final ApiResponseCode code;
private final String message; private final String message;
public ResponseObj(ApiResponseCode code, String message) { public ResponseObj(ApiResponseCode code, String message) {
this.code = code; this.code = code;
this.message = message; this.message = message;
} }
} }
@Getter @Getter
@RequiredArgsConstructor @RequiredArgsConstructor
public enum ApiResponseCode implements EnumType { public enum ApiResponseCode implements EnumType {
// @formatter:off // @formatter:off
OK("요청이 성공하였습니다."), OK("요청이 성공하였습니다."),
BAD_REQUEST("요청 파라미터가 잘못되었습니다."), BAD_REQUEST("요청 파라미터가 잘못되었습니다."),
BAD_GATEWAY("네트워크 상태가 불안정합니다."), BAD_GATEWAY("네트워크 상태가 불안정합니다."),
ALREADY_EXIST_MALL("이미 등록된 쇼핑센터입니다."), ALREADY_EXIST_MALL("이미 등록된 쇼핑센터입니다."),
NOT_FOUND_MAP("지도를 찾을 수 없습니다."), NOT_FOUND_MAP("지도를 찾을 수 없습니다."),
UNAUTHORIZED("권한이 없습니다."), UNAUTHORIZED("권한이 없습니다."),
CONFLICT("이미 등록된 컨텐츠입니다."), CONFLICT("이미 등록된 컨텐츠입니다."),
NOT_FOUND("Resource를 찾을 수 없습니다."), NOT_FOUND("Resource를 찾을 수 없습니다."),
NOT_FOUND_DATA("데이터를 찾을 수 없습니다."), NOT_FOUND_DATA("데이터를 찾을 수 없습니다."),
NOT_FOUND_WEATHER_DATA("날씨 데이터를 찾을 수 없습니다."), NOT_FOUND_WEATHER_DATA("날씨 데이터를 찾을 수 없습니다."),
FAIL_SEND_MESSAGE("메시지를 전송하지 못했습니다."), FAIL_SEND_MESSAGE("메시지를 전송하지 못했습니다."),
TOO_MANY_CONNECTED_MACHINES("연결된 기기가 너무 많습니다."), TOO_MANY_CONNECTED_MACHINES("연결된 기기가 너무 많습니다."),
UNAUTHENTICATED("인증에 실패하였습니다."), UNAUTHENTICATED("인증에 실패하였습니다."),
INVALID_TOKEN("잘못된 토큰입니다."), INVALID_TOKEN("잘못된 토큰입니다."),
EXPIRED_TOKEN("만료된 토큰입니다."), EXPIRED_TOKEN("만료된 토큰입니다."),
INTERNAL_SERVER_ERROR("서버에 문제가 발생 하였습니다."), INTERNAL_SERVER_ERROR("서버에 문제가 발생 하였습니다."),
FORBIDDEN("권한을 확인해주세요."), FORBIDDEN("권한을 확인해주세요."),
INVALID_PASSWORD("잘못된 비밀번호 입니다."), INVALID_PASSWORD("잘못된 비밀번호 입니다."),
NOT_FOUND_CAR_IN("입차정보가 없습니다."), NOT_FOUND_CAR_IN("입차정보가 없습니다."),
WRONG_STATUS("잘못된 상태입니다."), WRONG_STATUS("잘못된 상태입니다."),
FAIL_VERIFICATION("인증에 실패하였습니다."), FAIL_VERIFICATION("인증에 실패하였습니다."),
INVALID_EMAIL("잘못된 형식의 이메일입니다."), INVALID_EMAIL("잘못된 형식의 이메일입니다."),
REQUIRED_EMAIL("이메일은 필수 항목입니다."), REQUIRED_EMAIL("이메일은 필수 항목입니다."),
WRONG_PASSWORD("잘못된 패스워드입니다."), WRONG_PASSWORD("잘못된 패스워드입니다."),
DUPLICATE_EMAIL("이미 가입된 이메일입니다."), DUPLICATE_EMAIL("이미 가입된 이메일입니다."),
DUPLICATE_DATA("이미 등록되어 있습니다."), DUPLICATE_DATA("이미 등록되어 있습니다."),
DATA_INTEGRITY_ERROR("데이터 무결성이 위반되어 요청을 처리할수 없습니다."), DATA_INTEGRITY_ERROR("데이터 무결성이 위반되어 요청을 처리할수 없습니다."),
FOREIGN_KEY_ERROR("참조 중인 데이터가 있어 삭제할 수 없습니다."), FOREIGN_KEY_ERROR("참조 중인 데이터가 있어 삭제할 수 없습니다."),
DUPLICATE_EMPLOYEEID("이미 가입된 사번입니다."), DUPLICATE_EMPLOYEEID("이미 가입된 사번입니다."),
NOT_FOUND_USER_FOR_EMAIL("이메일로 유저를 찾을 수 없습니다."), NOT_FOUND_USER_FOR_EMAIL("이메일로 유저를 찾을 수 없습니다."),
NOT_FOUND_USER("사용자를 찾을 수 없습니다."), NOT_FOUND_USER("사용자를 찾을 수 없습니다."),
UNPROCESSABLE_ENTITY("이 데이터는 삭제할 수 없습니다."), UNPROCESSABLE_ENTITY("이 데이터는 삭제할 수 없습니다."),
LOGIN_ID_NOT_FOUND("아이디를 잘못 입력하셨습니다."), LOGIN_ID_NOT_FOUND("아이디를 잘못 입력하셨습니다."),
LOGIN_PASSWORD_MISMATCH("비밀번호를 잘못 입력하셨습니다."), LOGIN_PASSWORD_MISMATCH("비밀번호를 잘못 입력하셨습니다."),
LOGIN_PASSWORD_EXCEEDED("비밀번호 오류 횟수를 초과하여 이용하실 수 없습니다.\n로그인 오류에 대해 관리자에게 문의하시기 바랍니다."), LOGIN_PASSWORD_EXCEEDED("비밀번호 오류 횟수를 초과하여 이용하실 수 없습니다.\n로그인 오류에 대해 관리자에게 문의하시기 바랍니다."),
REFRESH_TOKEN_EXPIRED_OR_REVOKED("토큰 정보가 만료 되었습니다."), REFRESH_TOKEN_EXPIRED_OR_REVOKED("토큰 정보가 만료 되었습니다."),
REFRESH_TOKEN_MISMATCH("토큰 정보가 일치하지 않습니다."), REFRESH_TOKEN_MISMATCH("토큰 정보가 일치하지 않습니다."),
INACTIVE_ID("사용할 수 없는 계정입니다."), INACTIVE_ID("사용할 수 없는 계정입니다."),
INVALID_EMAIL_TOKEN( INVALID_EMAIL_TOKEN(
"You can only reset your password within 24 hours from when the email was sent.\n" "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" + "To reset your password again, please submit a new request through \"Forgot"
+ " Password.\""), + " Password.\""),
; ;
// @formatter:on // @formatter:on
private final String message; private final String message;
@Override @Override
public String getId() { public String getId() {
return name(); return name();
} }
@Override @Override
public String getText() { public String getText() {
return message; return message;
} }
public static ApiResponseCode getCode(String name) { public static ApiResponseCode getCode(String name) {
return ApiResponseCode.valueOf(name.toUpperCase()); return ApiResponseCode.valueOf(name.toUpperCase());
} }
public static String getMessage(String name) { public static String getMessage(String name) {
return ApiResponseCode.valueOf(name.toUpperCase()).getText(); return ApiResponseCode.valueOf(name.toUpperCase()).getText();
} }
} }
} }

View File

@@ -1,154 +1,154 @@
package com.kamco.cd.training.dataset; package com.kamco.cd.training.dataset;
import com.kamco.cd.training.config.api.ApiResponseDto; import com.kamco.cd.training.config.api.ApiResponseDto;
import com.kamco.cd.training.dataset.dto.DatasetDto; import com.kamco.cd.training.dataset.dto.DatasetDto;
import com.kamco.cd.training.dataset.service.DatasetService; import com.kamco.cd.training.dataset.service.DatasetService;
import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter; import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.media.Content; import io.swagger.v3.oas.annotations.media.Content;
import io.swagger.v3.oas.annotations.media.Schema; import io.swagger.v3.oas.annotations.media.Schema;
import io.swagger.v3.oas.annotations.responses.ApiResponse; import io.swagger.v3.oas.annotations.responses.ApiResponse;
import io.swagger.v3.oas.annotations.responses.ApiResponses; import io.swagger.v3.oas.annotations.responses.ApiResponses;
import io.swagger.v3.oas.annotations.tags.Tag; import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.validation.Valid; import jakarta.validation.Valid;
import java.util.UUID; import java.util.UUID;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import org.springframework.data.domain.Page; import org.springframework.data.domain.Page;
import org.springframework.web.bind.annotation.*; import org.springframework.web.bind.annotation.*;
@Tag(name = "데이터셋 관리", description = "어드민 홈 > 학습데이터관리 > 전체데이터 API") @Tag(name = "데이터셋 관리", description = "어드민 홈 > 학습데이터관리 > 전체데이터 API")
@RestController @RestController
@RequestMapping("/api/datasets") @RequestMapping("/api/datasets")
@RequiredArgsConstructor @RequiredArgsConstructor
public class DatasetApiController { public class DatasetApiController {
private final DatasetService datasetService; private final DatasetService datasetService;
@Operation(summary = "데이터셋 목록 조회", description = "데이터셋(회차) 목록을 조회합니다.") @Operation(summary = "데이터셋 목록 조회", description = "데이터셋(회차) 목록을 조회합니다.")
@ApiResponses( @ApiResponses(
value = { value = {
@ApiResponse( @ApiResponse(
responseCode = "200", responseCode = "200",
description = "조회 성공", description = "조회 성공",
content = content =
@Content( @Content(
mediaType = "application/json", mediaType = "application/json",
schema = @Schema(implementation = Page.class))), schema = @Schema(implementation = Page.class))),
@ApiResponse(responseCode = "400", description = "잘못된 검색 조건", content = @Content), @ApiResponse(responseCode = "400", description = "잘못된 검색 조건", content = @Content),
@ApiResponse(responseCode = "500", description = "서버 오류", content = @Content) @ApiResponse(responseCode = "500", description = "서버 오류", content = @Content)
}) })
@GetMapping @GetMapping
public ApiResponseDto<Page<DatasetDto.Basic>> searchDatasets( public ApiResponseDto<Page<DatasetDto.Basic>> searchDatasets(
@Parameter(description = "구분", example = "DELIVER(납품), PRODUCTION(제작)") @Parameter(description = "구분", example = "DELIVER(납품), PRODUCTION(제작)")
@RequestParam(required = false) @RequestParam(required = false)
String groupTitle, String groupTitle,
@Parameter(description = "제목", example = "") @RequestParam(required = false) String title, @Parameter(description = "제목", example = "") @RequestParam(required = false) String title,
@Parameter(description = "페이지 번호 (0부터 시작)", example = "0") @RequestParam(defaultValue = "0") @Parameter(description = "페이지 번호 (0부터 시작)", example = "0") @RequestParam(defaultValue = "0")
int page, int page,
@Parameter(description = "페이지 크기", example = "20") @RequestParam(defaultValue = "20") @Parameter(description = "페이지 크기", example = "20") @RequestParam(defaultValue = "20")
int size) { int size) {
DatasetDto.SearchReq searchReq = new DatasetDto.SearchReq(); DatasetDto.SearchReq searchReq = new DatasetDto.SearchReq();
searchReq.setTitle(title); searchReq.setTitle(title);
searchReq.setGroupTitle(groupTitle); searchReq.setGroupTitle(groupTitle);
searchReq.setPage(page); searchReq.setPage(page);
searchReq.setSize(size); searchReq.setSize(size);
return ApiResponseDto.ok(datasetService.searchDatasets(searchReq)); return ApiResponseDto.ok(datasetService.searchDatasets(searchReq));
} }
@Operation(summary = "데이터셋 상세 조회", description = "데이터셋 상세 정보를 조회합니다.") @Operation(summary = "데이터셋 상세 조회", description = "데이터셋 상세 정보를 조회합니다.")
@ApiResponses( @ApiResponses(
value = { value = {
@ApiResponse( @ApiResponse(
responseCode = "200", responseCode = "200",
description = "조회 성공", description = "조회 성공",
content = content =
@Content( @Content(
mediaType = "application/json", mediaType = "application/json",
schema = @Schema(implementation = DatasetDto.Basic.class))), schema = @Schema(implementation = DatasetDto.Basic.class))),
@ApiResponse(responseCode = "404", description = "데이터셋을 찾을 수 없음", content = @Content), @ApiResponse(responseCode = "404", description = "데이터셋을 찾을 수 없음", content = @Content),
@ApiResponse(responseCode = "500", description = "서버 오류", content = @Content) @ApiResponse(responseCode = "500", description = "서버 오류", content = @Content)
}) })
@GetMapping("/{uuid}") @GetMapping("/{uuid}")
public ApiResponseDto<DatasetDto.Basic> getDatasetDetail(@PathVariable UUID uuid) { public ApiResponseDto<DatasetDto.Basic> getDatasetDetail(@PathVariable UUID uuid) {
return ApiResponseDto.ok(datasetService.getDatasetDetail(uuid)); return ApiResponseDto.ok(datasetService.getDatasetDetail(uuid));
} }
@Operation(summary = "데이터셋 등록", description = "신규 데이터셋(회차)을 생성합니다.") @Operation(summary = "데이터셋 등록", description = "신규 데이터셋(회차)을 생성합니다.")
@ApiResponses( @ApiResponses(
value = { value = {
@ApiResponse( @ApiResponse(
responseCode = "201", responseCode = "201",
description = "등록 성공", description = "등록 성공",
content = content =
@Content( @Content(
mediaType = "application/json", mediaType = "application/json",
schema = @Schema(implementation = Long.class))), schema = @Schema(implementation = Long.class))),
@ApiResponse(responseCode = "400", description = "잘못된 요청 데이터", content = @Content), @ApiResponse(responseCode = "400", description = "잘못된 요청 데이터", content = @Content),
@ApiResponse(responseCode = "500", description = "서버 오류", content = @Content) @ApiResponse(responseCode = "500", description = "서버 오류", content = @Content)
}) })
@PostMapping("/register") @PostMapping("/register")
public ApiResponseDto<Long> registerDataset( public ApiResponseDto<Long> registerDataset(
@RequestBody @Valid DatasetDto.RegisterReq registerReq) { @RequestBody @Valid DatasetDto.RegisterReq registerReq) {
Long id = datasetService.registerDataset(registerReq); Long id = datasetService.registerDataset(registerReq);
return ApiResponseDto.createOK(id); return ApiResponseDto.createOK(id);
} }
@Operation(summary = "데이터셋 수정", description = "데이터셋 정보를 수정합니다.") @Operation(summary = "데이터셋 수정", description = "데이터셋 정보를 수정합니다.")
@ApiResponses( @ApiResponses(
value = { value = {
@ApiResponse( @ApiResponse(
responseCode = "200", responseCode = "200",
description = "수정 성공", description = "수정 성공",
content = content =
@Content( @Content(
mediaType = "application/json", mediaType = "application/json",
schema = @Schema(implementation = Long.class))), schema = @Schema(implementation = Long.class))),
@ApiResponse(responseCode = "400", description = "잘못된 요청 데이터", content = @Content), @ApiResponse(responseCode = "400", description = "잘못된 요청 데이터", content = @Content),
@ApiResponse(responseCode = "404", description = "데이터셋을 찾을 수 없음", content = @Content), @ApiResponse(responseCode = "404", description = "데이터셋을 찾을 수 없음", content = @Content),
@ApiResponse(responseCode = "500", description = "서버 오류", content = @Content) @ApiResponse(responseCode = "500", description = "서버 오류", content = @Content)
}) })
@PutMapping("/{uuid}") @PutMapping("/{uuid}")
public ApiResponseDto<UUID> updateDataset( public ApiResponseDto<UUID> updateDataset(
@PathVariable UUID uuid, @RequestBody DatasetDto.UpdateReq updateReq) { @PathVariable UUID uuid, @RequestBody DatasetDto.UpdateReq updateReq) {
datasetService.updateDataset(uuid, updateReq); datasetService.updateDataset(uuid, updateReq);
return ApiResponseDto.ok(uuid); return ApiResponseDto.ok(uuid);
} }
@Operation(summary = "데이터셋 삭제", description = "데이터셋을 삭제합니다.") @Operation(summary = "데이터셋 삭제", description = "데이터셋을 삭제합니다.")
@ApiResponses( @ApiResponses(
value = { value = {
@ApiResponse(responseCode = "201", description = "삭제 성공", content = @Content), @ApiResponse(responseCode = "201", description = "삭제 성공", content = @Content),
@ApiResponse(responseCode = "400", description = "잘못된 요청", content = @Content), @ApiResponse(responseCode = "400", description = "잘못된 요청", content = @Content),
@ApiResponse(responseCode = "404", description = "데이터셋을 찾을 수 없음", content = @Content), @ApiResponse(responseCode = "404", description = "데이터셋을 찾을 수 없음", content = @Content),
@ApiResponse(responseCode = "500", description = "서버 오류", content = @Content) @ApiResponse(responseCode = "500", description = "서버 오류", content = @Content)
}) })
@DeleteMapping("/{uuid}") @DeleteMapping("/{uuid}")
public ApiResponseDto<UUID> deleteDatasets(@PathVariable UUID uuid) { public ApiResponseDto<UUID> deleteDatasets(@PathVariable UUID uuid) {
datasetService.deleteDatasets(uuid); datasetService.deleteDatasets(uuid);
return ApiResponseDto.ok(uuid); return ApiResponseDto.ok(uuid);
} }
/* /*
@Operation(summary = "데이터셋 통계 요약", description = "선택 데이터셋의 통계를 요약합니다.") @Operation(summary = "데이터셋 통계 요약", description = "선택 데이터셋의 통계를 요약합니다.")
@ApiResponses( @ApiResponses(
value = { value = {
@ApiResponse( @ApiResponse(
responseCode = "200", responseCode = "200",
description = "조회 성공", description = "조회 성공",
content = content =
@Content( @Content(
mediaType = "application/json", mediaType = "application/json",
schema = @Schema(implementation = DatasetDto.Summary.class))), schema = @Schema(implementation = DatasetDto.Summary.class))),
@ApiResponse(responseCode = "400", description = "잘못된 요청", content = @Content), @ApiResponse(responseCode = "400", description = "잘못된 요청", content = @Content),
@ApiResponse(responseCode = "500", description = "서버 오류", content = @Content) @ApiResponse(responseCode = "500", description = "서버 오류", content = @Content)
}) })
@PostMapping("/summary") @PostMapping("/summary")
public ApiResponseDto<DatasetDto.Summary> getDatasetSummary( public ApiResponseDto<DatasetDto.Summary> getDatasetSummary(
@RequestBody @Valid DatasetDto.SummaryReq summaryReq) { @RequestBody @Valid DatasetDto.SummaryReq summaryReq) {
return ApiResponseDto.ok(datasetService.getDatasetSummary(summaryReq)); return ApiResponseDto.ok(datasetService.getDatasetSummary(summaryReq));
} }
*/ */
} }

View File

@@ -1,56 +1,56 @@
package com.kamco.cd.training.dataset; package com.kamco.cd.training.dataset;
import com.kamco.cd.training.config.api.ApiResponseDto; import com.kamco.cd.training.config.api.ApiResponseDto;
import com.kamco.cd.training.dataset.dto.MapSheetDto; import com.kamco.cd.training.dataset.dto.MapSheetDto;
import com.kamco.cd.training.dataset.service.MapSheetService; import com.kamco.cd.training.dataset.service.MapSheetService;
import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.media.Content; import io.swagger.v3.oas.annotations.media.Content;
import io.swagger.v3.oas.annotations.media.Schema; import io.swagger.v3.oas.annotations.media.Schema;
import io.swagger.v3.oas.annotations.responses.ApiResponse; import io.swagger.v3.oas.annotations.responses.ApiResponse;
import io.swagger.v3.oas.annotations.responses.ApiResponses; import io.swagger.v3.oas.annotations.responses.ApiResponses;
import io.swagger.v3.oas.annotations.tags.Tag; import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.validation.Valid; import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import org.springframework.data.domain.Page; import org.springframework.data.domain.Page;
import org.springframework.web.bind.annotation.*; import org.springframework.web.bind.annotation.*;
@Tag(name = "도엽 관리", description = "도엽(MapSheet) 관리 API") @Tag(name = "도엽 관리", description = "도엽(MapSheet) 관리 API")
@RestController @RestController
@RequiredArgsConstructor @RequiredArgsConstructor
public class MapSheetApiController { public class MapSheetApiController {
private final MapSheetService mapSheetService; private final MapSheetService mapSheetService;
@Operation(summary = "도엽 목록 조회", description = "데이터셋의 도엽 목록을 조회합니다.") @Operation(summary = "도엽 목록 조회", description = "데이터셋의 도엽 목록을 조회합니다.")
@ApiResponses( @ApiResponses(
value = { value = {
@ApiResponse( @ApiResponse(
responseCode = "200", responseCode = "200",
description = "조회 성공", description = "조회 성공",
content = content =
@Content( @Content(
mediaType = "application/json", mediaType = "application/json",
schema = @Schema(implementation = Page.class))), schema = @Schema(implementation = Page.class))),
@ApiResponse(responseCode = "400", description = "잘못된 검색 조건", content = @Content), @ApiResponse(responseCode = "400", description = "잘못된 검색 조건", content = @Content),
@ApiResponse(responseCode = "500", description = "서버 오류", content = @Content) @ApiResponse(responseCode = "500", description = "서버 오류", content = @Content)
}) })
@PostMapping("/api/datasets/items/search") @PostMapping("/api/datasets/items/search")
public ApiResponseDto<Page<MapSheetDto.Basic>> searchMapSheets( public ApiResponseDto<Page<MapSheetDto.Basic>> searchMapSheets(
@RequestBody @Valid MapSheetDto.SearchReq searchReq) { @RequestBody @Valid MapSheetDto.SearchReq searchReq) {
return ApiResponseDto.ok(mapSheetService.searchMapSheets(searchReq)); return ApiResponseDto.ok(mapSheetService.searchMapSheets(searchReq));
} }
@Operation(summary = "도엽 삭제", description = "도엽을 삭제합니다 (다건 지원).") @Operation(summary = "도엽 삭제", description = "도엽을 삭제합니다 (다건 지원).")
@ApiResponses( @ApiResponses(
value = { value = {
@ApiResponse(responseCode = "200", description = "삭제 성공", content = @Content), @ApiResponse(responseCode = "200", description = "삭제 성공", content = @Content),
@ApiResponse(responseCode = "400", description = "잘못된 요청", content = @Content), @ApiResponse(responseCode = "400", description = "잘못된 요청", content = @Content),
@ApiResponse(responseCode = "404", description = "도엽을 찾을 수 없음", content = @Content), @ApiResponse(responseCode = "404", description = "도엽을 찾을 수 없음", content = @Content),
@ApiResponse(responseCode = "500", description = "서버 오류", content = @Content) @ApiResponse(responseCode = "500", description = "서버 오류", content = @Content)
}) })
@PostMapping("/api/datasets/items/delete") @PostMapping("/api/datasets/items/delete")
public ApiResponseDto<Void> deleteMapSheets(@RequestBody @Valid MapSheetDto.DeleteReq deleteReq) { public ApiResponseDto<Void> deleteMapSheets(@RequestBody @Valid MapSheetDto.DeleteReq deleteReq) {
mapSheetService.deleteMapSheets(deleteReq); mapSheetService.deleteMapSheets(deleteReq);
return ApiResponseDto.ok(null); return ApiResponseDto.ok(null);
} }
} }

View File

@@ -1,212 +1,212 @@
package com.kamco.cd.training.dataset.dto; package com.kamco.cd.training.dataset.dto;
import com.kamco.cd.training.common.enums.LearnDataRegister; import com.kamco.cd.training.common.enums.LearnDataRegister;
import com.kamco.cd.training.common.enums.LearnDataType; import com.kamco.cd.training.common.enums.LearnDataType;
import com.kamco.cd.training.common.utils.enums.Enums; import com.kamco.cd.training.common.utils.enums.Enums;
import com.kamco.cd.training.common.utils.interfaces.JsonFormatDttm; import com.kamco.cd.training.common.utils.interfaces.JsonFormatDttm;
import io.swagger.v3.oas.annotations.media.Schema; import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotBlank; import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull; import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Size; import jakarta.validation.constraints.Size;
import java.time.ZonedDateTime; import java.time.ZonedDateTime;
import java.util.List; import java.util.List;
import java.util.UUID; import java.util.UUID;
import lombok.AllArgsConstructor; import lombok.AllArgsConstructor;
import lombok.Getter; import lombok.Getter;
import lombok.NoArgsConstructor; import lombok.NoArgsConstructor;
import lombok.Setter; import lombok.Setter;
import org.springframework.data.domain.PageRequest; import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable; import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort; import org.springframework.data.domain.Sort;
public class DatasetDto { public class DatasetDto {
@Schema(name = "Dataset Basic", description = "데이터셋 기본 정보") @Schema(name = "Dataset Basic", description = "데이터셋 기본 정보")
@Getter @Getter
@Setter @Setter
@NoArgsConstructor @NoArgsConstructor
@AllArgsConstructor @AllArgsConstructor
public static class Basic { public static class Basic {
private Long id; private Long id;
private UUID uuid; private UUID uuid;
private String groupTitle; private String groupTitle;
private String groupTitleCd; private String groupTitleCd;
private String title; private String title;
private Long roundNo; private Long roundNo;
private String totalSize; private String totalSize;
private String memo; private String memo;
@JsonFormatDttm private ZonedDateTime createdDttm; @JsonFormatDttm private ZonedDateTime createdDttm;
private String status; private String status;
private String statusCd; private String statusCd;
private Boolean deleted; private Boolean deleted;
public Basic( public Basic(
Long id, Long id,
UUID uuid, UUID uuid,
String groupTitle, String groupTitle,
String title, String title,
Long roundNo, Long roundNo,
Long totalSize, Long totalSize,
String memo, String memo,
ZonedDateTime createdDttm, ZonedDateTime createdDttm,
String status, String status,
Boolean deleted) { Boolean deleted) {
this.id = id; this.id = id;
this.uuid = uuid; this.uuid = uuid;
this.groupTitle = getGroupTitle(groupTitle); this.groupTitle = getGroupTitle(groupTitle);
this.groupTitleCd = groupTitle; this.groupTitleCd = groupTitle;
this.title = title; this.title = title;
this.roundNo = roundNo; this.roundNo = roundNo;
this.totalSize = getTotalSize(totalSize); this.totalSize = getTotalSize(totalSize);
this.memo = memo; this.memo = memo;
this.createdDttm = createdDttm; this.createdDttm = createdDttm;
this.status = getStatus(status); this.status = getStatus(status);
this.statusCd = status; this.statusCd = status;
this.deleted = deleted; this.deleted = deleted;
} }
public String getTotalSize(Long totalSize) { public String getTotalSize(Long totalSize) {
if (totalSize == null) return "0G"; if (totalSize == null) return "0G";
double giga = totalSize / (1024.0 * 1024 * 1024); double giga = totalSize / (1024.0 * 1024 * 1024);
return String.format("%.2fG", giga); return String.format("%.2fG", giga);
} }
public String getGroupTitle(String groupTitleCd) { public String getGroupTitle(String groupTitleCd) {
LearnDataType type = Enums.fromId(LearnDataType.class, groupTitleCd); LearnDataType type = Enums.fromId(LearnDataType.class, groupTitleCd);
return type == null ? null : type.getText(); return type == null ? null : type.getText();
} }
public String getStatus(String status) { public String getStatus(String status) {
LearnDataRegister type = Enums.fromId(LearnDataRegister.class, status); LearnDataRegister type = Enums.fromId(LearnDataRegister.class, status);
return type == null ? null : type.getText(); return type == null ? null : type.getText();
} }
} }
@Schema(name = "Dataset Detail", description = "데이터셋 상세 정보") @Schema(name = "Dataset Detail", description = "데이터셋 상세 정보")
@Getter @Getter
@Setter @Setter
@NoArgsConstructor @NoArgsConstructor
@AllArgsConstructor @AllArgsConstructor
public static class Detail { public static class Detail {
private Long id; private Long id;
private String groupTitle; private String groupTitle;
private String title; private String title;
private Long roundNo; private Long roundNo;
private String totalSize; private String totalSize;
private String memo; private String memo;
@JsonFormatDttm private ZonedDateTime createdDttm; @JsonFormatDttm private ZonedDateTime createdDttm;
private String status; private String status;
private Boolean deleted; private Boolean deleted;
} }
@Schema(name = "DatasetSearchReq", description = "데이터셋 목록 조회 요청") @Schema(name = "DatasetSearchReq", description = "데이터셋 목록 조회 요청")
@Getter @Getter
@Setter @Setter
@NoArgsConstructor @NoArgsConstructor
@AllArgsConstructor @AllArgsConstructor
public static class SearchReq { public static class SearchReq {
@Schema(description = "구분", example = "DELIVER(납품), PRODUCTION(제작)") @Schema(description = "구분", example = "DELIVER(납품), PRODUCTION(제작)")
private String groupTitle; private String groupTitle;
@Schema(description = "제목 (부분 검색)", example = "1차") @Schema(description = "제목 (부분 검색)", example = "1차")
private String title; private String title;
@Schema(description = "페이지 번호 (1부터 시작)", example = "1") @Schema(description = "페이지 번호 (1부터 시작)", example = "1")
private int page = 1; private int page = 1;
@Schema(description = "페이지 크기", example = "20") @Schema(description = "페이지 크기", example = "20")
private int size = 20; private int size = 20;
public Pageable toPageable() { public Pageable toPageable() {
// API에서는 1부터 시작하지만 내부적으로는 0부터 시작 // API에서는 1부터 시작하지만 내부적으로는 0부터 시작
int pageIndex = Math.max(0, page - 1); int pageIndex = Math.max(0, page - 1);
return PageRequest.of(pageIndex, size, Sort.by(Sort.Direction.DESC, "createdDttm")); return PageRequest.of(pageIndex, size, Sort.by(Sort.Direction.DESC, "createdDttm"));
} }
} }
@Schema(name = "DatasetDetailReq", description = "데이터셋 상세 조회 요청") @Schema(name = "DatasetDetailReq", description = "데이터셋 상세 조회 요청")
@Getter @Getter
@Setter @Setter
@NoArgsConstructor @NoArgsConstructor
@AllArgsConstructor @AllArgsConstructor
public static class DetailReq { public static class DetailReq {
@NotNull(message = "데이터셋 ID는 필수입니다") @NotNull(message = "데이터셋 ID는 필수입니다")
@Schema(description = "데이터셋 ID", example = "101") @Schema(description = "데이터셋 ID", example = "101")
private Long datasetId; private Long datasetId;
} }
@Schema(name = "DatasetRegisterReq", description = "데이터셋 등록 요청") @Schema(name = "DatasetRegisterReq", description = "데이터셋 등록 요청")
@Getter @Getter
@Setter @Setter
@NoArgsConstructor @NoArgsConstructor
@AllArgsConstructor @AllArgsConstructor
public static class RegisterReq { public static class RegisterReq {
@NotBlank(message = "제목은 필수입니다") @NotBlank(message = "제목은 필수입니다")
@Size(max = 200, message = "제목은 최대 200자까지 입력 가능합니다") @Size(max = 200, message = "제목은 최대 200자까지 입력 가능합니다")
@Schema(description = "제목", example = "1차 제작") @Schema(description = "제목", example = "1차 제작")
private String title; private String title;
@NotBlank(message = "연도는 필수입니다") @NotBlank(message = "연도는 필수입니다")
@Size(max = 4, message = "연도는 4자리입니다") @Size(max = 4, message = "연도는 4자리입니다")
@Schema(description = "연도 (YYYY)", example = "2024") @Schema(description = "연도 (YYYY)", example = "2024")
private String year; private String year;
@Schema(description = "회차", example = "1") @Schema(description = "회차", example = "1")
private Long roundNo; private Long roundNo;
@Schema(description = "메모", example = "데이터셋 설명") @Schema(description = "메모", example = "데이터셋 설명")
private String memo; private String memo;
} }
@Schema(name = "DatasetUpdateReq", description = "데이터셋 수정 요청") @Schema(name = "DatasetUpdateReq", description = "데이터셋 수정 요청")
@Getter @Getter
@Setter @Setter
@NoArgsConstructor @NoArgsConstructor
@AllArgsConstructor @AllArgsConstructor
public static class UpdateReq { public static class UpdateReq {
@Size(max = 200, message = "제목은 최대 200자까지 입력 가능합니다") @Size(max = 200, message = "제목은 최대 200자까지 입력 가능합니다")
@Schema(description = "제목", example = "1차 제작") @Schema(description = "제목", example = "1차 제작")
private String title; private String title;
@Schema(description = "메모", example = "데이터셋 설명") @Schema(description = "메모", example = "데이터셋 설명")
private String memo; private String memo;
} }
@Schema(name = "DatasetSummaryReq", description = "데이터셋 통계 요약 요청") @Schema(name = "DatasetSummaryReq", description = "데이터셋 통계 요약 요청")
@Getter @Getter
@Setter @Setter
@NoArgsConstructor @NoArgsConstructor
@AllArgsConstructor @AllArgsConstructor
public static class SummaryReq { public static class SummaryReq {
@NotNull(message = "데이터셋 ID 목록은 필수입니다") @NotNull(message = "데이터셋 ID 목록은 필수입니다")
@Schema(description = "데이터셋 ID 목록", example = "[101, 105]") @Schema(description = "데이터셋 ID 목록", example = "[101, 105]")
private List<Long> datasetIds; private List<Long> datasetIds;
} }
@Schema(name = "DatasetSummary", description = "데이터셋 통계 요약") @Schema(name = "DatasetSummary", description = "데이터셋 통계 요약")
@Getter @Getter
@Setter @Setter
@NoArgsConstructor @NoArgsConstructor
@AllArgsConstructor @AllArgsConstructor
public static class Summary { public static class Summary {
@Schema(description = "총 데이터셋 수", example = "2") @Schema(description = "총 데이터셋 수", example = "2")
private int totalDatasets; private int totalDatasets;
@Schema(description = "총 도엽 수", example = "1500") @Schema(description = "총 도엽 수", example = "1500")
private long totalMapSheets; private long totalMapSheets;
@Schema(description = "총 파일 크기 (bytes)", example = "10737418240") @Schema(description = "총 파일 크기 (bytes)", example = "10737418240")
private long totalFileSize; private long totalFileSize;
@Schema(description = "평균 도엽 수", example = "750") @Schema(description = "평균 도엽 수", example = "750")
private double averageMapSheets; private double averageMapSheets;
} }
} }

View File

@@ -1,103 +1,103 @@
package com.kamco.cd.training.dataset.dto; package com.kamco.cd.training.dataset.dto;
import com.kamco.cd.training.common.utils.interfaces.JsonFormatDttm; import com.kamco.cd.training.common.utils.interfaces.JsonFormatDttm;
import io.swagger.v3.oas.annotations.media.Schema; import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotNull; import jakarta.validation.constraints.NotNull;
import java.time.ZonedDateTime; import java.time.ZonedDateTime;
import java.util.List; import java.util.List;
import lombok.AllArgsConstructor; import lombok.AllArgsConstructor;
import lombok.Getter; import lombok.Getter;
import lombok.NoArgsConstructor; import lombok.NoArgsConstructor;
import lombok.Setter; import lombok.Setter;
import org.springframework.data.domain.PageRequest; import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable; import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort; import org.springframework.data.domain.Sort;
public class MapSheetDto { public class MapSheetDto {
@Schema(name = "MapSheet Basic", description = "도엽 기본 정보") @Schema(name = "MapSheet Basic", description = "도엽 기본 정보")
@Getter @Getter
@Setter @Setter
@NoArgsConstructor @NoArgsConstructor
@AllArgsConstructor @AllArgsConstructor
public static class Basic { public static class Basic {
private Long id; private Long id;
private Long datasetId; private Long datasetId;
private String sheetNum; private String sheetNum;
private String fileName; private String fileName;
private Long fileSize; private Long fileSize;
private String filePath; private String filePath;
private String status; private String status;
private String memo; private String memo;
private Boolean deleted; private Boolean deleted;
@JsonFormatDttm private ZonedDateTime createdDttm; @JsonFormatDttm private ZonedDateTime createdDttm;
@JsonFormatDttm private ZonedDateTime updatedDttm; @JsonFormatDttm private ZonedDateTime updatedDttm;
} }
@Schema(name = "MapSheetSearchReq", description = "도엽 목록 조회 요청") @Schema(name = "MapSheetSearchReq", description = "도엽 목록 조회 요청")
@Getter @Getter
@Setter @Setter
@NoArgsConstructor @NoArgsConstructor
@AllArgsConstructor @AllArgsConstructor
public static class SearchReq { public static class SearchReq {
@NotNull(message = "데이터셋 ID는 필수입니다") @NotNull(message = "데이터셋 ID는 필수입니다")
@Schema(description = "데이터셋 ID", example = "101") @Schema(description = "데이터셋 ID", example = "101")
private Long datasetId; private Long datasetId;
@Schema(description = "페이지 번호 (1부터 시작)", example = "1") @Schema(description = "페이지 번호 (1부터 시작)", example = "1")
private int page = 1; private int page = 1;
@Schema(description = "페이지 크기", example = "20") @Schema(description = "페이지 크기", example = "20")
private int size = 20; private int size = 20;
public Pageable toPageable() { public Pageable toPageable() {
int pageIndex = Math.max(0, page - 1); int pageIndex = Math.max(0, page - 1);
return PageRequest.of(pageIndex, size, Sort.by(Sort.Direction.DESC, "createdDttm")); return PageRequest.of(pageIndex, size, Sort.by(Sort.Direction.DESC, "createdDttm"));
} }
} }
@Schema(name = "MapSheetDeleteReq", description = "도엽 삭제 요청 (다건)") @Schema(name = "MapSheetDeleteReq", description = "도엽 삭제 요청 (다건)")
@Getter @Getter
@Setter @Setter
@NoArgsConstructor @NoArgsConstructor
@AllArgsConstructor @AllArgsConstructor
public static class DeleteReq { public static class DeleteReq {
@NotNull(message = "삭제할 도엽 ID 목록은 필수입니다") @NotNull(message = "삭제할 도엽 ID 목록은 필수입니다")
@Schema(description = "삭제할 도엽 ID 목록", example = "[9991, 9992]") @Schema(description = "삭제할 도엽 ID 목록", example = "[9991, 9992]")
private List<Long> itemIds; private List<Long> itemIds;
} }
@Schema(name = "MapSheetCheckReq", description = "도엽 번호 유효성 검증 요청") @Schema(name = "MapSheetCheckReq", description = "도엽 번호 유효성 검증 요청")
@Getter @Getter
@Setter @Setter
@NoArgsConstructor @NoArgsConstructor
@AllArgsConstructor @AllArgsConstructor
public static class CheckReq { public static class CheckReq {
@NotNull(message = "도엽 번호는 필수입니다") @NotNull(message = "도엽 번호는 필수입니다")
@Schema(description = "도엽 번호", example = "377055") @Schema(description = "도엽 번호", example = "377055")
private String sheetNum; private String sheetNum;
} }
@Schema(name = "MapSheetCheckRes", description = "도엽 번호 유효성 검증 응답") @Schema(name = "MapSheetCheckRes", description = "도엽 번호 유효성 검증 응답")
@Getter @Getter
@Setter @Setter
@NoArgsConstructor @NoArgsConstructor
@AllArgsConstructor @AllArgsConstructor
public static class CheckRes { public static class CheckRes {
@Schema(description = "유효 여부", example = "true") @Schema(description = "유효 여부", example = "true")
private boolean valid; private boolean valid;
@Schema(description = "메시지", example = "유효한 도엽 번호입니다") @Schema(description = "메시지", example = "유효한 도엽 번호입니다")
private String message; private String message;
@Schema(description = "중복 여부", example = "false") @Schema(description = "중복 여부", example = "false")
private boolean duplicate; private boolean duplicate;
} }
} }

View File

@@ -1,86 +1,86 @@
package com.kamco.cd.training.dataset.service; package com.kamco.cd.training.dataset.service;
import com.kamco.cd.training.dataset.dto.DatasetDto; import com.kamco.cd.training.dataset.dto.DatasetDto;
import com.kamco.cd.training.postgres.core.DatasetCoreService; import com.kamco.cd.training.postgres.core.DatasetCoreService;
import java.util.UUID; import java.util.UUID;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.data.domain.Page; import org.springframework.data.domain.Page;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional; import org.springframework.transaction.annotation.Transactional;
@Slf4j @Slf4j
@Service @Service
@RequiredArgsConstructor @RequiredArgsConstructor
@Transactional(readOnly = true) @Transactional(readOnly = true)
public class DatasetService { public class DatasetService {
private final DatasetCoreService datasetCoreService; private final DatasetCoreService datasetCoreService;
/** /**
* 데이터셋 목록 조회 * 데이터셋 목록 조회
* *
* @param searchReq 검색 조건 * @param searchReq 검색 조건
* @return 데이터셋 목록 * @return 데이터셋 목록
*/ */
public Page<DatasetDto.Basic> searchDatasets(DatasetDto.SearchReq searchReq) { public Page<DatasetDto.Basic> searchDatasets(DatasetDto.SearchReq searchReq) {
log.info("데이터셋 목록 조회 - 조건: {}", searchReq); log.info("데이터셋 목록 조회 - 조건: {}", searchReq);
return datasetCoreService.findDatasetList(searchReq); return datasetCoreService.findDatasetList(searchReq);
} }
/** /**
* 데이터셋 상세 조회 * 데이터셋 상세 조회
* *
* @param id 상세 조회할 목록 Id * @param id 상세 조회할 목록 Id
* @return 데이터셋 상세 정보 * @return 데이터셋 상세 정보
*/ */
public DatasetDto.Basic getDatasetDetail(UUID id) { public DatasetDto.Basic getDatasetDetail(UUID id) {
return datasetCoreService.getOneByUuid(id); return datasetCoreService.getOneByUuid(id);
} }
/** /**
* 데이터셋 등록 * 데이터셋 등록
* *
* @param registerReq 등록 요청 * @param registerReq 등록 요청
* @return 등록된 데이터셋 ID * @return 등록된 데이터셋 ID
*/ */
@Transactional @Transactional
public Long registerDataset(DatasetDto.RegisterReq registerReq) { public Long registerDataset(DatasetDto.RegisterReq registerReq) {
log.info("데이터셋 등록 - 요청: {}", registerReq); log.info("데이터셋 등록 - 요청: {}", registerReq);
DatasetDto.Basic saved = datasetCoreService.save(registerReq); DatasetDto.Basic saved = datasetCoreService.save(registerReq);
log.info("데이터셋 등록 완료 - ID: {}", saved.getId()); log.info("데이터셋 등록 완료 - ID: {}", saved.getId());
return saved.getId(); return saved.getId();
} }
/** /**
* 데이터셋 수정 * 데이터셋 수정
* *
* @param updateReq 수정 요청 * @param updateReq 수정 요청
* @return 수정된 데이터셋 ID * @return 수정된 데이터셋 ID
*/ */
@Transactional @Transactional
public void updateDataset(UUID uuid, DatasetDto.UpdateReq updateReq) { public void updateDataset(UUID uuid, DatasetDto.UpdateReq updateReq) {
datasetCoreService.update(uuid, updateReq); datasetCoreService.update(uuid, updateReq);
} }
/** /**
* 데이터셋 삭제 (다건) * 데이터셋 삭제 (다건)
* *
* @param uuid 삭제 요청 * @param uuid 삭제 요청
*/ */
@Transactional @Transactional
public void deleteDatasets(UUID uuid) { public void deleteDatasets(UUID uuid) {
datasetCoreService.deleteDatasets(uuid); datasetCoreService.deleteDatasets(uuid);
} }
/** /**
* 데이터셋 통계 요약 * 데이터셋 통계 요약
* *
* @param summaryReq 요약 요청 * @param summaryReq 요약 요청
* @return 통계 요약 * @return 통계 요약
*/ */
public DatasetDto.Summary getDatasetSummary(DatasetDto.SummaryReq summaryReq) { public DatasetDto.Summary getDatasetSummary(DatasetDto.SummaryReq summaryReq) {
log.info("데이터셋 통계 요약 - 요청: {}", summaryReq); log.info("데이터셋 통계 요약 - 요청: {}", summaryReq);
return datasetCoreService.getDatasetSummary(summaryReq); return datasetCoreService.getDatasetSummary(summaryReq);
} }
} }

View File

@@ -1,41 +1,41 @@
package com.kamco.cd.training.dataset.service; package com.kamco.cd.training.dataset.service;
import com.kamco.cd.training.dataset.dto.MapSheetDto; import com.kamco.cd.training.dataset.dto.MapSheetDto;
import com.kamco.cd.training.postgres.core.MapSheetCoreService; import com.kamco.cd.training.postgres.core.MapSheetCoreService;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.data.domain.Page; import org.springframework.data.domain.Page;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional; import org.springframework.transaction.annotation.Transactional;
@Slf4j @Slf4j
@Service @Service
@RequiredArgsConstructor @RequiredArgsConstructor
@Transactional(readOnly = true) @Transactional(readOnly = true)
public class MapSheetService { public class MapSheetService {
private final MapSheetCoreService mapSheetCoreService; private final MapSheetCoreService mapSheetCoreService;
/** /**
* 도엽 목록 조회 * 도엽 목록 조회
* *
* @param searchReq 검색 조건 * @param searchReq 검색 조건
* @return 도엽 목록 * @return 도엽 목록
*/ */
public Page<MapSheetDto.Basic> searchMapSheets(MapSheetDto.SearchReq searchReq) { public Page<MapSheetDto.Basic> searchMapSheets(MapSheetDto.SearchReq searchReq) {
log.info("도엽 목록 조회 - 조건: {}", searchReq); log.info("도엽 목록 조회 - 조건: {}", searchReq);
return mapSheetCoreService.findMapSheetList(searchReq); return mapSheetCoreService.findMapSheetList(searchReq);
} }
/** /**
* 도엽 삭제 (다건) * 도엽 삭제 (다건)
* *
* @param deleteReq 삭제 요청 * @param deleteReq 삭제 요청
*/ */
@Transactional @Transactional
public void deleteMapSheets(MapSheetDto.DeleteReq deleteReq) { public void deleteMapSheets(MapSheetDto.DeleteReq deleteReq) {
log.info("도엽 삭제 - 요청: {}", deleteReq); log.info("도엽 삭제 - 요청: {}", deleteReq);
mapSheetCoreService.deleteMapSheets(deleteReq); mapSheetCoreService.deleteMapSheets(deleteReq);
log.info("도엽 삭제 완료 - 개수: {}", deleteReq.getItemIds().size()); log.info("도엽 삭제 완료 - 개수: {}", deleteReq.getItemIds().size());
} }
} }

View File

@@ -1,229 +1,229 @@
package com.kamco.cd.training.log.dto; package com.kamco.cd.training.log.dto;
import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonIgnore;
import com.kamco.cd.training.common.utils.interfaces.JsonFormatDttm; import com.kamco.cd.training.common.utils.interfaces.JsonFormatDttm;
import io.swagger.v3.oas.annotations.media.Schema; import io.swagger.v3.oas.annotations.media.Schema;
import java.time.ZonedDateTime; import java.time.ZonedDateTime;
import lombok.AllArgsConstructor; import lombok.AllArgsConstructor;
import lombok.Getter; import lombok.Getter;
import lombok.NoArgsConstructor; import lombok.NoArgsConstructor;
import lombok.Setter; import lombok.Setter;
import org.springframework.data.domain.PageRequest; import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable; import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort; import org.springframework.data.domain.Sort;
public class AuditLogDto { public class AuditLogDto {
@Schema(name = "AuditLogBasic", description = "감사로그 기본 정보") @Schema(name = "AuditLogBasic", description = "감사로그 기본 정보")
@Getter @Getter
public static class Basic { public static class Basic {
@JsonIgnore private final Long id; @JsonIgnore private final Long id;
private final Long userUid; private final Long userUid;
private final EventType eventType; private final EventType eventType;
private final EventStatus eventStatus; private final EventStatus eventStatus;
private final String menuUid; private final String menuUid;
private final String ipAddress; private final String ipAddress;
private final String requestUri; private final String requestUri;
private final String requestBody; private final String requestBody;
private final Long errorLogUid; private final Long errorLogUid;
@JsonFormatDttm private final ZonedDateTime createdDttm; @JsonFormatDttm private final ZonedDateTime createdDttm;
public Basic( public Basic(
Long id, Long id,
Long userUid, Long userUid,
EventType eventType, EventType eventType,
EventStatus eventStatus, EventStatus eventStatus,
String menuUid, String menuUid,
String ipAddress, String ipAddress,
String requestUri, String requestUri,
String requestBody, String requestBody,
Long errorLogUid, Long errorLogUid,
ZonedDateTime createdDttm) { ZonedDateTime createdDttm) {
this.id = id; this.id = id;
this.userUid = userUid; this.userUid = userUid;
this.eventType = eventType; this.eventType = eventType;
this.eventStatus = eventStatus; this.eventStatus = eventStatus;
this.menuUid = menuUid; this.menuUid = menuUid;
this.ipAddress = ipAddress; this.ipAddress = ipAddress;
this.requestUri = requestUri; this.requestUri = requestUri;
this.requestBody = requestBody; this.requestBody = requestBody;
this.errorLogUid = errorLogUid; this.errorLogUid = errorLogUid;
this.createdDttm = createdDttm; this.createdDttm = createdDttm;
} }
} }
@Schema(name = "AuditCommon", description = "목록 공통") @Schema(name = "AuditCommon", description = "목록 공통")
@Getter @Getter
@AllArgsConstructor @AllArgsConstructor
public static class AuditCommon { public static class AuditCommon {
private int readCount; private int readCount;
private int cudCount; private int cudCount;
private int printCount; private int printCount;
private int downloadCount; private int downloadCount;
private Long totalCount; private Long totalCount;
} }
@Schema(name = "DailyAuditList", description = "일자별 목록") @Schema(name = "DailyAuditList", description = "일자별 목록")
@Getter @Getter
public static class DailyAuditList extends AuditCommon { public static class DailyAuditList extends AuditCommon {
private final String baseDate; private final String baseDate;
public DailyAuditList( public DailyAuditList(
int readCount, int readCount,
int cudCount, int cudCount,
int printCount, int printCount,
int downloadCount, int downloadCount,
Long totalCount, Long totalCount,
String baseDate) { String baseDate) {
super(readCount, cudCount, printCount, downloadCount, totalCount); super(readCount, cudCount, printCount, downloadCount, totalCount);
this.baseDate = baseDate; this.baseDate = baseDate;
} }
} }
@Schema(name = "MenuAuditList", description = "메뉴별 목록") @Schema(name = "MenuAuditList", description = "메뉴별 목록")
@Getter @Getter
public static class MenuAuditList extends AuditCommon { public static class MenuAuditList extends AuditCommon {
private final String menuId; private final String menuId;
private final String menuName; private final String menuName;
public MenuAuditList( public MenuAuditList(
String menuId, String menuId,
String menuName, String menuName,
int readCount, int readCount,
int cudCount, int cudCount,
int printCount, int printCount,
int downloadCount, int downloadCount,
Long totalCount) { Long totalCount) {
super(readCount, cudCount, printCount, downloadCount, totalCount); super(readCount, cudCount, printCount, downloadCount, totalCount);
this.menuId = menuId; this.menuId = menuId;
this.menuName = menuName; this.menuName = menuName;
} }
} }
@Schema(name = "UserAuditList", description = "사용자별 목록") @Schema(name = "UserAuditList", description = "사용자별 목록")
@Getter @Getter
public static class UserAuditList extends AuditCommon { public static class UserAuditList extends AuditCommon {
private final Long accountId; private final Long accountId;
private final String loginId; private final String loginId;
private final String username; private final String username;
public UserAuditList( public UserAuditList(
Long accountId, Long accountId,
String loginId, String loginId,
String username, String username,
int readCount, int readCount,
int cudCount, int cudCount,
int printCount, int printCount,
int downloadCount, int downloadCount,
Long totalCount) { Long totalCount) {
super(readCount, cudCount, printCount, downloadCount, totalCount); super(readCount, cudCount, printCount, downloadCount, totalCount);
this.accountId = accountId; this.accountId = accountId;
this.loginId = loginId; this.loginId = loginId;
this.username = username; this.username = username;
} }
} }
@Schema(name = "AuditDetail", description = "감사 로그 상세 공통") @Schema(name = "AuditDetail", description = "감사 로그 상세 공통")
@Getter @Getter
@AllArgsConstructor @AllArgsConstructor
public static class AuditDetail { public static class AuditDetail {
private Long logId; private Long logId;
private EventType eventType; private EventType eventType;
private LogDetail detail; private LogDetail detail;
} }
@Schema(name = "DailyDetail", description = "일자별 로그 상세") @Schema(name = "DailyDetail", description = "일자별 로그 상세")
@Getter @Getter
public static class DailyDetail extends AuditDetail { public static class DailyDetail extends AuditDetail {
private final String userName; private final String userName;
private final String loginId; private final String loginId;
private final String menuName; private final String menuName;
public DailyDetail( public DailyDetail(
Long logId, Long logId,
String userName, String userName,
String loginId, String loginId,
String menuName, String menuName,
EventType eventType, EventType eventType,
LogDetail detail) { LogDetail detail) {
super(logId, eventType, detail); super(logId, eventType, detail);
this.userName = userName; this.userName = userName;
this.loginId = loginId; this.loginId = loginId;
this.menuName = menuName; this.menuName = menuName;
} }
} }
@Schema(name = "MenuDetail", description = "메뉴별 로그 상세") @Schema(name = "MenuDetail", description = "메뉴별 로그 상세")
@Getter @Getter
public static class MenuDetail extends AuditDetail { public static class MenuDetail extends AuditDetail {
private final String logDateTime; private final String logDateTime;
private final String userName; private final String userName;
private final String loginId; private final String loginId;
public MenuDetail( public MenuDetail(
Long logId, Long logId,
String logDateTime, String logDateTime,
String userName, String userName,
String loginId, String loginId,
EventType eventType, EventType eventType,
LogDetail detail) { LogDetail detail) {
super(logId, eventType, detail); super(logId, eventType, detail);
this.logDateTime = logDateTime; this.logDateTime = logDateTime;
this.userName = userName; this.userName = userName;
this.loginId = loginId; this.loginId = loginId;
} }
} }
@Schema(name = "UserDetail", description = "사용자별 로그 상세") @Schema(name = "UserDetail", description = "사용자별 로그 상세")
@Getter @Getter
public static class UserDetail extends AuditDetail { public static class UserDetail extends AuditDetail {
private final String logDateTime; private final String logDateTime;
private final String menuNm; private final String menuNm;
public UserDetail( public UserDetail(
Long logId, String logDateTime, String menuNm, EventType eventType, LogDetail detail) { Long logId, String logDateTime, String menuNm, EventType eventType, LogDetail detail) {
super(logId, eventType, detail); super(logId, eventType, detail);
this.logDateTime = logDateTime; this.logDateTime = logDateTime;
this.menuNm = menuNm; this.menuNm = menuNm;
} }
} }
@Getter @Getter
@Setter @Setter
@AllArgsConstructor @AllArgsConstructor
public static class LogDetail { public static class LogDetail {
String serviceName; String serviceName;
String parentMenuName; String parentMenuName;
String menuName; String menuName;
String menuUrl; String menuUrl;
String menuDescription; String menuDescription;
Long sortOrder; Long sortOrder;
boolean used; boolean used;
} }
@Schema(name = "searchReq", description = "일자별 로그 검색 요청") @Schema(name = "searchReq", description = "일자별 로그 검색 요청")
@Getter @Getter
@Setter @Setter
@NoArgsConstructor @NoArgsConstructor
@AllArgsConstructor @AllArgsConstructor
public static class searchReq { public static class searchReq {
// 페이징 파라미터 // 페이징 파라미터
private int page = 0; private int page = 0;
private int size = 20; private int size = 20;
private String sort; private String sort;
public Pageable toPageable() { public Pageable toPageable() {
if (sort != null && !sort.isEmpty()) { if (sort != null && !sort.isEmpty()) {
String[] sortParams = sort.split(","); String[] sortParams = sort.split(",");
String property = sortParams[0]; String property = sortParams[0];
Sort.Direction direction = Sort.Direction direction =
sortParams.length > 1 ? Sort.Direction.fromString(sortParams[1]) : Sort.Direction.ASC; 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, Sort.by(direction, property));
} }
return PageRequest.of(page, size); return PageRequest.of(page, size);
} }
} }
} }

View File

@@ -1,101 +1,101 @@
package com.kamco.cd.training.log.dto; package com.kamco.cd.training.log.dto;
import com.kamco.cd.training.common.utils.enums.EnumType; import com.kamco.cd.training.common.utils.enums.EnumType;
import io.swagger.v3.oas.annotations.media.Schema; import io.swagger.v3.oas.annotations.media.Schema;
import java.time.LocalDate; import java.time.LocalDate;
import lombok.AllArgsConstructor; import lombok.AllArgsConstructor;
import lombok.Getter; import lombok.Getter;
import lombok.NoArgsConstructor; import lombok.NoArgsConstructor;
import lombok.Setter; import lombok.Setter;
import org.springframework.data.domain.PageRequest; import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable; import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort; import org.springframework.data.domain.Sort;
public class ErrorLogDto { public class ErrorLogDto {
@Schema(name = "ErrorLogBasic", description = "에러로그 기본 정보") @Schema(name = "ErrorLogBasic", description = "에러로그 기본 정보")
@Getter @Getter
@Setter @Setter
@AllArgsConstructor @AllArgsConstructor
public static class Basic { public static class Basic {
private final Long id; private final Long id;
private final String serviceName; private final String serviceName;
private final String menuNm; private final String menuNm;
private final String loginId; private final String loginId;
private final String userName; private final String userName;
private final EventType errorType; private final EventType errorType;
private final String errorName; private final String errorName;
private final LogErrorLevel errorLevel; private final LogErrorLevel errorLevel;
private final String errorCode; private final String errorCode;
private final String errorMessage; private final String errorMessage;
private final String errorDetail; private final String errorDetail;
private final String createDate; // to_char해서 가져옴 private final String createDate; // to_char해서 가져옴
} }
@Schema(name = "ErrorSearchReq", description = "에러로그 검색 요청") @Schema(name = "ErrorSearchReq", description = "에러로그 검색 요청")
@Getter @Getter
@Setter @Setter
@NoArgsConstructor @NoArgsConstructor
@AllArgsConstructor @AllArgsConstructor
public static class ErrorSearchReq { public static class ErrorSearchReq {
LogErrorLevel errorLevel; LogErrorLevel errorLevel;
EventType eventType; EventType eventType;
LocalDate startDate; LocalDate startDate;
LocalDate endDate; LocalDate endDate;
// 페이징 파라미터 // 페이징 파라미터
private int page = 0; private int page = 0;
private int size = 20; private int size = 20;
private String sort; private String sort;
public ErrorSearchReq( public ErrorSearchReq(
LogErrorLevel errorLevel, LogErrorLevel errorLevel,
EventType eventType, EventType eventType,
LocalDate startDate, LocalDate startDate,
LocalDate endDate, LocalDate endDate,
int page, int page,
int size) { int size) {
this.errorLevel = errorLevel; this.errorLevel = errorLevel;
this.eventType = eventType; this.eventType = eventType;
this.startDate = startDate; this.startDate = startDate;
this.endDate = endDate; this.endDate = endDate;
this.page = page; this.page = page;
this.size = size; this.size = size;
} }
public Pageable toPageable() { public Pageable toPageable() {
if (sort != null && !sort.isEmpty()) { if (sort != null && !sort.isEmpty()) {
String[] sortParams = sort.split(","); String[] sortParams = sort.split(",");
String property = sortParams[0]; String property = sortParams[0];
Sort.Direction direction = Sort.Direction direction =
sortParams.length > 1 ? Sort.Direction.fromString(sortParams[1]) : Sort.Direction.ASC; 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, Sort.by(direction, property));
} }
return PageRequest.of(page, size); return PageRequest.of(page, size);
} }
} }
public enum LogErrorLevel implements EnumType { public enum LogErrorLevel implements EnumType {
WARNING("Warning"), WARNING("Warning"),
ERROR("Error"), ERROR("Error"),
CRITICAL("Critical"); CRITICAL("Critical");
private final String desc; private final String desc;
LogErrorLevel(String desc) { LogErrorLevel(String desc) {
this.desc = desc; this.desc = desc;
} }
@Override @Override
public String getId() { public String getId() {
return name(); return name();
} }
@Override @Override
public String getText() { public String getText() {
return desc; return desc;
} }
} }
} }

View File

@@ -1,24 +1,24 @@
package com.kamco.cd.training.log.dto; package com.kamco.cd.training.log.dto;
import com.kamco.cd.training.common.utils.enums.EnumType; import com.kamco.cd.training.common.utils.enums.EnumType;
import lombok.AllArgsConstructor; import lombok.AllArgsConstructor;
import lombok.Getter; import lombok.Getter;
@Getter @Getter
@AllArgsConstructor @AllArgsConstructor
public enum EventStatus implements EnumType { public enum EventStatus implements EnumType {
SUCCESS("이벤트 결과 성공"), SUCCESS("이벤트 결과 성공"),
FAILED("이벤트 결과 실패"); FAILED("이벤트 결과 실패");
private final String desc; private final String desc;
@Override @Override
public String getId() { public String getId() {
return name(); return name();
} }
@Override @Override
public String getText() { public String getText() {
return desc; return desc;
} }
} }

View File

@@ -1,29 +1,29 @@
package com.kamco.cd.training.log.dto; package com.kamco.cd.training.log.dto;
import com.kamco.cd.training.common.utils.enums.EnumType; import com.kamco.cd.training.common.utils.enums.EnumType;
import lombok.AllArgsConstructor; import lombok.AllArgsConstructor;
import lombok.Getter; import lombok.Getter;
@Getter @Getter
@AllArgsConstructor @AllArgsConstructor
public enum EventType implements EnumType { public enum EventType implements EnumType {
CREATE("생성"), CREATE("생성"),
READ("조회"), READ("조회"),
UPDATE("수정"), UPDATE("수정"),
DELETE("삭제"), DELETE("삭제"),
DOWNLOAD("다운로드"), DOWNLOAD("다운로드"),
PRINT("출력"), PRINT("출력"),
OTHER("기타"); OTHER("기타");
private final String desc; private final String desc;
@Override @Override
public String getId() { public String getId() {
return name(); return name();
} }
@Override @Override
public String getText() { public String getText() {
return desc; return desc;
} }
} }

View File

@@ -1,46 +1,46 @@
package com.kamco.cd.training.log.service; package com.kamco.cd.training.log.service;
import com.kamco.cd.training.log.dto.AuditLogDto; import com.kamco.cd.training.log.dto.AuditLogDto;
import com.kamco.cd.training.postgres.core.AuditLogCoreService; import com.kamco.cd.training.postgres.core.AuditLogCoreService;
import java.time.LocalDate; import java.time.LocalDate;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import org.springframework.data.domain.Page; import org.springframework.data.domain.Page;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional; import org.springframework.transaction.annotation.Transactional;
@Service @Service
@RequiredArgsConstructor @RequiredArgsConstructor
@Transactional(readOnly = true) @Transactional(readOnly = true)
public class AuditLogService { public class AuditLogService {
private final AuditLogCoreService auditLogCoreService; private final AuditLogCoreService auditLogCoreService;
public Page<AuditLogDto.DailyAuditList> getLogByDaily( public Page<AuditLogDto.DailyAuditList> getLogByDaily(
AuditLogDto.searchReq searchRange, LocalDate startDate, LocalDate endDate) { AuditLogDto.searchReq searchRange, LocalDate startDate, LocalDate endDate) {
return auditLogCoreService.getLogByDaily(searchRange, startDate, endDate); return auditLogCoreService.getLogByDaily(searchRange, startDate, endDate);
} }
public Page<AuditLogDto.MenuAuditList> getLogByMenu( public Page<AuditLogDto.MenuAuditList> getLogByMenu(
AuditLogDto.searchReq searchRange, String searchValue) { AuditLogDto.searchReq searchRange, String searchValue) {
return auditLogCoreService.getLogByMenu(searchRange, searchValue); return auditLogCoreService.getLogByMenu(searchRange, searchValue);
} }
public Page<AuditLogDto.UserAuditList> getLogByAccount( public Page<AuditLogDto.UserAuditList> getLogByAccount(
AuditLogDto.searchReq searchRange, String searchValue) { AuditLogDto.searchReq searchRange, String searchValue) {
return auditLogCoreService.getLogByAccount(searchRange, searchValue); return auditLogCoreService.getLogByAccount(searchRange, searchValue);
} }
public Page<AuditLogDto.DailyDetail> getLogByDailyResult( public Page<AuditLogDto.DailyDetail> getLogByDailyResult(
AuditLogDto.searchReq searchRange, LocalDate logDate) { AuditLogDto.searchReq searchRange, LocalDate logDate) {
return auditLogCoreService.getLogByDailyResult(searchRange, logDate); return auditLogCoreService.getLogByDailyResult(searchRange, logDate);
} }
public Page<AuditLogDto.MenuDetail> getLogByMenuResult( public Page<AuditLogDto.MenuDetail> getLogByMenuResult(
AuditLogDto.searchReq searchRange, String menuId) { AuditLogDto.searchReq searchRange, String menuId) {
return auditLogCoreService.getLogByMenuResult(searchRange, menuId); return auditLogCoreService.getLogByMenuResult(searchRange, menuId);
} }
public Page<AuditLogDto.UserDetail> getLogByAccountResult( public Page<AuditLogDto.UserDetail> getLogByAccountResult(
AuditLogDto.searchReq searchRange, Long accountId) { AuditLogDto.searchReq searchRange, Long accountId) {
return auditLogCoreService.getLogByAccountResult(searchRange, accountId); return auditLogCoreService.getLogByAccountResult(searchRange, accountId);
} }
} }

View File

@@ -1,19 +1,19 @@
package com.kamco.cd.training.log.service; package com.kamco.cd.training.log.service;
import com.kamco.cd.training.log.dto.ErrorLogDto; import com.kamco.cd.training.log.dto.ErrorLogDto;
import com.kamco.cd.training.postgres.core.ErrorLogCoreService; import com.kamco.cd.training.postgres.core.ErrorLogCoreService;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import org.springframework.data.domain.Page; import org.springframework.data.domain.Page;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional; import org.springframework.transaction.annotation.Transactional;
@Service @Service
@RequiredArgsConstructor @RequiredArgsConstructor
@Transactional(readOnly = true) @Transactional(readOnly = true)
public class ErrorLogService { public class ErrorLogService {
private final ErrorLogCoreService errorLogCoreService; private final ErrorLogCoreService errorLogCoreService;
public Page<ErrorLogDto.Basic> findLogByError(ErrorLogDto.ErrorSearchReq searchReq) { public Page<ErrorLogDto.Basic> findLogByError(ErrorLogDto.ErrorSearchReq searchReq) {
return errorLogCoreService.findLogByError(searchReq); return errorLogCoreService.findLogByError(searchReq);
} }
} }

View File

@@ -1,241 +1,241 @@
package com.kamco.cd.training.members; package com.kamco.cd.training.members;
import com.kamco.cd.training.auth.CustomUserDetails; import com.kamco.cd.training.auth.CustomUserDetails;
import com.kamco.cd.training.auth.JwtTokenProvider; import com.kamco.cd.training.auth.JwtTokenProvider;
import com.kamco.cd.training.common.enums.StatusType; import com.kamco.cd.training.common.enums.StatusType;
import com.kamco.cd.training.common.exception.CustomApiException; import com.kamco.cd.training.common.exception.CustomApiException;
import com.kamco.cd.training.config.api.ApiResponseDto; import com.kamco.cd.training.config.api.ApiResponseDto;
import com.kamco.cd.training.members.dto.MembersDto; import com.kamco.cd.training.members.dto.MembersDto;
import com.kamco.cd.training.members.dto.SignInRequest; import com.kamco.cd.training.members.dto.SignInRequest;
import com.kamco.cd.training.members.dto.TokenResponse; import com.kamco.cd.training.members.dto.TokenResponse;
import com.kamco.cd.training.members.service.AuthService; import com.kamco.cd.training.members.service.AuthService;
import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.media.Content; import io.swagger.v3.oas.annotations.media.Content;
import io.swagger.v3.oas.annotations.media.ExampleObject; import io.swagger.v3.oas.annotations.media.ExampleObject;
import io.swagger.v3.oas.annotations.media.Schema; import io.swagger.v3.oas.annotations.media.Schema;
import io.swagger.v3.oas.annotations.responses.ApiResponse; import io.swagger.v3.oas.annotations.responses.ApiResponse;
import io.swagger.v3.oas.annotations.responses.ApiResponses; import io.swagger.v3.oas.annotations.responses.ApiResponses;
import io.swagger.v3.oas.annotations.tags.Tag; import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.servlet.http.HttpServletResponse; import jakarta.servlet.http.HttpServletResponse;
import java.nio.file.AccessDeniedException; import java.nio.file.AccessDeniedException;
import java.time.Duration; import java.time.Duration;
import java.util.UUID; import java.util.UUID;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import org.springframework.beans.factory.annotation.Value; import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.HttpHeaders; import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus; import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseCookie; import org.springframework.http.ResponseCookie;
import org.springframework.http.ResponseEntity; import org.springframework.http.ResponseEntity;
import org.springframework.security.authentication.AuthenticationManager; import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication; import org.springframework.security.core.Authentication;
import org.springframework.web.ErrorResponse; import org.springframework.web.ErrorResponse;
import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController; import org.springframework.web.bind.annotation.RestController;
@Tag(name = "인증(Auth)", description = "로그인, 토큰 재발급, 로그아웃 API") @Tag(name = "인증(Auth)", description = "로그인, 토큰 재발급, 로그아웃 API")
@RestController @RestController
@RequestMapping("/api/auth") @RequestMapping("/api/auth")
@RequiredArgsConstructor @RequiredArgsConstructor
public class AuthController { public class AuthController {
private final AuthenticationManager authenticationManager; private final AuthenticationManager authenticationManager;
private final JwtTokenProvider jwtTokenProvider; private final JwtTokenProvider jwtTokenProvider;
private final AuthService authService; private final AuthService authService;
@Value("${token.refresh-cookie-name}") @Value("${token.refresh-cookie-name}")
private String refreshCookieName; private String refreshCookieName;
@Value("${token.refresh-cookie-secure:true}") @Value("${token.refresh-cookie-secure:true}")
private boolean refreshCookieSecure; private boolean refreshCookieSecure;
@PostMapping("/signin") @PostMapping("/signin")
@Operation(summary = "로그인", description = "사번으로 로그인하여 액세스/리프레시 토큰을 발급.") @Operation(summary = "로그인", description = "사번으로 로그인하여 액세스/리프레시 토큰을 발급.")
@ApiResponses({ @ApiResponses({
@ApiResponse( @ApiResponse(
responseCode = "200", responseCode = "200",
description = "로그인 성공", description = "로그인 성공1",
content = @Content(schema = @Schema(implementation = TokenResponse.class))), content = @Content(schema = @Schema(implementation = TokenResponse.class))),
@ApiResponse( @ApiResponse(
responseCode = "401", responseCode = "401",
description = "로그인 실패 (아이디/비밀번호 오류, 계정잠금 등)", description = "로그인 실패 (아이디/비밀번호 오류, 계정잠금 등)",
content = content =
@Content( @Content(
schema = @Schema(implementation = ErrorResponse.class), schema = @Schema(implementation = ErrorResponse.class),
examples = { examples = {
@ExampleObject( @ExampleObject(
name = "사번 입력 오류", name = "사번 입력 오류",
description = "존재하지 않는 아이디", description = "존재하지 않는 아이디",
value = value =
""" """
{ {
"code": "LOGIN_ID_NOT_FOUND", "code": "LOGIN_ID_NOT_FOUND",
"message": "사번을 잘못 입력하셨습니다." "message": "사번을 잘못 입력하셨습니다."
} }
"""), """),
@ExampleObject( @ExampleObject(
name = "비밀번호 입력 오류 (4회 이하)", name = "비밀번호 입력 오류 (4회 이하)",
description = "아이디는 정상, 비밀번호를 여러 번 틀린 경우", description = "아이디는 정상, 비밀번호를 여러 번 틀린 경우",
value = value =
""" """
{ {
"code": "LOGIN_PASSWORD_MISMATCH", "code": "LOGIN_PASSWORD_MISMATCH",
"message": "비밀번호를 잘못 입력하셨습니다." "message": "비밀번호를 잘못 입력하셨습니다."
} }
"""), """),
@ExampleObject( @ExampleObject(
name = "비밀번호 오류 횟수 초과", name = "비밀번호 오류 횟수 초과",
description = "비밀번호 5회 이상 오류로 계정 잠김", description = "비밀번호 5회 이상 오류로 계정 잠김",
value = value =
""" """
{ {
"code": "LOGIN_PASSWORD_EXCEEDED", "code": "LOGIN_PASSWORD_EXCEEDED",
"message": "비밀번호 오류 횟수를 초과하여 이용하실 수 없습니다. 로그인 오류에 대해 관리자에게 문의하시기 바랍니다." "message": "비밀번호 오류 횟수를 초과하여 이용하실 수 없습니다. 로그인 오류에 대해 관리자에게 문의하시기 바랍니다."
} }
"""), """),
@ExampleObject( @ExampleObject(
name = "사용 중지 된 계정의 로그인 시도", name = "사용 중지 된 계정의 로그인 시도",
description = "사용 중지 된 계정의 로그인 시도", description = "사용 중지 된 계정의 로그인 시도",
value = value =
""" """
{ {
"code": "INACTIVE_ID", "code": "INACTIVE_ID",
"message": "사용할 수 없는 계정입니다." "message": "사용할 수 없는 계정입니다."
} }
""") """)
})) }))
}) })
public ApiResponseDto<TokenResponse> signin( public ApiResponseDto<TokenResponse> signin(
@io.swagger.v3.oas.annotations.parameters.RequestBody( @io.swagger.v3.oas.annotations.parameters.RequestBody(
description = "로그인 요청 정보", description = "로그인 요청 정보",
required = true) required = true)
@RequestBody @RequestBody
SignInRequest request, SignInRequest request,
HttpServletResponse response) { HttpServletResponse response) {
// 사용자 상태 조회 // 사용자 상태 조회
String status = authService.getUserStatus(request); String status = authService.getUserStatus(request);
if(StatusType.INACTIVE.getId().equals(status)) { if (StatusType.INACTIVE.getId().equals(status)) {
throw new CustomApiException("INACTIVE_ID", HttpStatus.UNAUTHORIZED); throw new CustomApiException("INACTIVE_ID", HttpStatus.UNAUTHORIZED);
} }
Authentication authentication = null; Authentication authentication = null;
MembersDto.Member member = new MembersDto.Member(); MembersDto.Member member = new MembersDto.Member();
authentication = authentication =
authenticationManager.authenticate( authenticationManager.authenticate(
new UsernamePasswordAuthenticationToken(request.getUsername(), request.getPassword())); new UsernamePasswordAuthenticationToken(request.getUsername(), request.getPassword()));
String username = authentication.getName(); // UserDetailsService 에서 사용한 username String username = authentication.getName(); // UserDetailsService 에서 사용한 username
String accessToken = jwtTokenProvider.createAccessToken(username); String accessToken = jwtTokenProvider.createAccessToken(username);
String refreshToken = jwtTokenProvider.createRefreshToken(username); String refreshToken = jwtTokenProvider.createRefreshToken(username);
// 토큰 저장 // 토큰 저장
authService.tokenSave(username, refreshToken, jwtTokenProvider.getRefreshTokenValidityInMs()); authService.tokenSave(username, refreshToken, jwtTokenProvider.getRefreshTokenValidityInMs());
// HttpOnly + Secure 쿠키에 RefreshToken 저장 // HttpOnly + Secure 쿠키에 RefreshToken 저장
ResponseCookie cookie = ResponseCookie cookie =
ResponseCookie.from(refreshCookieName, refreshToken) ResponseCookie.from(refreshCookieName, refreshToken)
.httpOnly(true) .httpOnly(true)
.secure(refreshCookieSecure) .secure(refreshCookieSecure)
.path("/") .path("/")
.maxAge(Duration.ofMillis(jwtTokenProvider.getRefreshTokenValidityInMs())) .maxAge(Duration.ofMillis(jwtTokenProvider.getRefreshTokenValidityInMs()))
.sameSite("Strict") .sameSite("Strict")
.build(); .build();
response.addHeader(HttpHeaders.SET_COOKIE, cookie.toString()); response.addHeader(HttpHeaders.SET_COOKIE, cookie.toString());
CustomUserDetails user = (CustomUserDetails) authentication.getPrincipal(); CustomUserDetails user = (CustomUserDetails) authentication.getPrincipal();
member.setId(user.getMember().getId()); member.setId(user.getMember().getId());
member.setName(user.getMember().getName()); member.setName(user.getMember().getName());
member.setEmployeeNo(user.getMember().getEmployeeNo()); member.setEmployeeNo(user.getMember().getEmployeeNo());
// PENDING 비활성 상태(새로운 패스워드 입력 해야함) // PENDING 비활성 상태(새로운 패스워드 입력 해야함)
if (StatusType.PENDING.getId().equals(status)) { if (StatusType.PENDING.getId().equals(status)) {
member.setEmployeeNo(request.getUsername()); member.setEmployeeNo(request.getUsername());
return ApiResponseDto.ok(new TokenResponse(status, accessToken, refreshToken, member)); return ApiResponseDto.ok(new TokenResponse(status, accessToken, refreshToken, member));
} }
// 인증 성공 로그인 시간 저장 // 인증 성공 로그인 시간 저장
authService.saveLogin(UUID.fromString(username)); authService.saveLogin(UUID.fromString(username));
return ApiResponseDto.ok(new TokenResponse(status, accessToken, refreshToken, member)); return ApiResponseDto.ok(new TokenResponse(status, accessToken, refreshToken, member));
} }
@PostMapping("/refresh") @PostMapping("/refresh")
@Operation(summary = "토큰 재발급", description = "리프레시 토큰으로 새로운 액세스/리프레시 토큰을 재발급합니다.") @Operation(summary = "토큰 재발급", description = "리프레시 토큰으로 새로운 액세스/리프레시 토큰을 재발급합니다.")
@ApiResponses({ @ApiResponses({
@ApiResponse( @ApiResponse(
responseCode = "200", responseCode = "200",
description = "재발급 성공", description = "재발급 성공",
content = @Content(schema = @Schema(implementation = TokenResponse.class))), content = @Content(schema = @Schema(implementation = TokenResponse.class))),
@ApiResponse( @ApiResponse(
responseCode = "403", responseCode = "403",
description = "만료되었거나 유효하지 않은 리프레시 토큰", description = "만료되었거나 유효하지 않은 리프레시 토큰",
content = @Content(schema = @Schema(implementation = ErrorResponse.class))) content = @Content(schema = @Schema(implementation = ErrorResponse.class)))
}) })
public ResponseEntity<TokenResponse> refresh(String refreshToken, HttpServletResponse response) public ResponseEntity<TokenResponse> refresh(String refreshToken, HttpServletResponse response)
throws AccessDeniedException { throws AccessDeniedException {
if (refreshToken == null || !jwtTokenProvider.isValidToken(refreshToken)) { if (refreshToken == null || !jwtTokenProvider.isValidToken(refreshToken)) {
throw new AccessDeniedException("만료되었거나 유효하지 않은 리프레시 토큰 입니다."); throw new AccessDeniedException("만료되었거나 유효하지 않은 리프레시 토큰 입니다.");
} }
String username = jwtTokenProvider.getSubject(refreshToken); String username = jwtTokenProvider.getSubject(refreshToken);
// 저장된 RefreshToken과 일치하는지 확인 // 저장된 RefreshToken과 일치하는지 확인
authService.validateRefreshToken(username, refreshToken); authService.validateRefreshToken(username, refreshToken);
// 새 토큰 발급 // 새 토큰 발급
String newAccessToken = jwtTokenProvider.createAccessToken(username); String newAccessToken = jwtTokenProvider.createAccessToken(username);
String newRefreshToken = jwtTokenProvider.createRefreshToken(username); String newRefreshToken = jwtTokenProvider.createRefreshToken(username);
// 토큰 저장 // 토큰 저장
authService.tokenSave(username, refreshToken, jwtTokenProvider.getRefreshTokenValidityInMs()); authService.tokenSave(username, refreshToken, jwtTokenProvider.getRefreshTokenValidityInMs());
// 쿠키 갱신 // 쿠키 갱신
ResponseCookie cookie = ResponseCookie cookie =
ResponseCookie.from(refreshCookieName, newRefreshToken) ResponseCookie.from(refreshCookieName, newRefreshToken)
.httpOnly(true) .httpOnly(true)
.secure(refreshCookieSecure) .secure(refreshCookieSecure)
.path("/") .path("/")
.maxAge(Duration.ofMillis(jwtTokenProvider.getRefreshTokenValidityInMs())) .maxAge(Duration.ofMillis(jwtTokenProvider.getRefreshTokenValidityInMs()))
.sameSite("Strict") .sameSite("Strict")
.build(); .build();
response.addHeader(HttpHeaders.SET_COOKIE, cookie.toString()); response.addHeader(HttpHeaders.SET_COOKIE, cookie.toString());
MembersDto.Member member = new MembersDto.Member(); MembersDto.Member member = new MembersDto.Member();
return ResponseEntity.ok(new TokenResponse("ACTIVE", newAccessToken, newRefreshToken, member)); return ResponseEntity.ok(new TokenResponse("ACTIVE", newAccessToken, newRefreshToken, member));
} }
@PostMapping("/logout") @PostMapping("/logout")
@Operation(summary = "로그아웃", description = "현재 사용자의 토큰을 무효화(리프레시 토큰 삭제)합니다.") @Operation(summary = "로그아웃", description = "현재 사용자의 토큰을 무효화(리프레시 토큰 삭제)합니다.")
@ApiResponses({ @ApiResponses({
@ApiResponse( @ApiResponse(
responseCode = "200", responseCode = "200",
description = "로그아웃 성공", description = "로그아웃 성공",
content = @Content(schema = @Schema(implementation = Void.class))) content = @Content(schema = @Schema(implementation = Void.class)))
}) })
public ApiResponseDto<ResponseEntity<Object>> logout( public ApiResponseDto<ResponseEntity<Object>> logout(
Authentication authentication, HttpServletResponse response) { Authentication authentication, HttpServletResponse response) {
if (authentication != null) { if (authentication != null) {
String username = authentication.getName(); String username = authentication.getName();
authService.logout(username); authService.logout(username);
} }
// 쿠키 삭제 (Max-Age=0) // 쿠키 삭제 (Max-Age=0)
ResponseCookie cookie = ResponseCookie cookie =
ResponseCookie.from(refreshCookieName, "") ResponseCookie.from(refreshCookieName, "")
.httpOnly(true) .httpOnly(true)
.secure(refreshCookieSecure) .secure(refreshCookieSecure)
.path("/") .path("/")
.maxAge(0) .maxAge(0)
.sameSite("Strict") .sameSite("Strict")
.build(); .build();
response.addHeader(HttpHeaders.SET_COOKIE, cookie.toString()); response.addHeader(HttpHeaders.SET_COOKIE, cookie.toString());
return ApiResponseDto.createOK(ResponseEntity.noContent().build()); return ApiResponseDto.createOK(ResponseEntity.noContent().build());
} }
} }

View File

@@ -1,69 +1,69 @@
package com.kamco.cd.training.members; package com.kamco.cd.training.members;
import com.kamco.cd.training.config.api.ApiResponseDto; import com.kamco.cd.training.config.api.ApiResponseDto;
import com.kamco.cd.training.members.dto.MembersDto; import com.kamco.cd.training.members.dto.MembersDto;
import com.kamco.cd.training.members.dto.MembersDto.Basic; import com.kamco.cd.training.members.dto.MembersDto.Basic;
import com.kamco.cd.training.members.service.MembersService; import com.kamco.cd.training.members.service.MembersService;
import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.media.Content; import io.swagger.v3.oas.annotations.media.Content;
import io.swagger.v3.oas.annotations.media.Schema; import io.swagger.v3.oas.annotations.media.Schema;
import io.swagger.v3.oas.annotations.responses.ApiResponse; import io.swagger.v3.oas.annotations.responses.ApiResponse;
import io.swagger.v3.oas.annotations.responses.ApiResponses; import io.swagger.v3.oas.annotations.responses.ApiResponses;
import io.swagger.v3.oas.annotations.tags.Tag; import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.validation.Valid; import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import org.springframework.data.domain.Page; import org.springframework.data.domain.Page;
import org.springframework.security.authentication.AuthenticationManager; import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.web.bind.annotation.*; import org.springframework.web.bind.annotation.*;
@Tag(name = "회원정보 관리", description = "회원정보 관리 API") @Tag(name = "회원정보 관리", description = "회원정보 관리 API")
@RestController @RestController
@RequestMapping("/api/members") @RequestMapping("/api/members")
@RequiredArgsConstructor @RequiredArgsConstructor
public class MembersApiController { public class MembersApiController {
private final AuthenticationManager authenticationManager; private final AuthenticationManager authenticationManager;
private final MembersService membersService; private final MembersService membersService;
@Operation(summary = "회원정보 목록", description = "회원정보 조회") @Operation(summary = "회원정보 목록", description = "회원정보 조회")
@ApiResponses( @ApiResponses(
value = { value = {
@ApiResponse( @ApiResponse(
responseCode = "200", responseCode = "200",
description = "검색 성공", description = "검색 성공",
content = content =
@Content( @Content(
mediaType = "application/json", mediaType = "application/json",
schema = @Schema(implementation = Page.class))), schema = @Schema(implementation = Page.class))),
@ApiResponse(responseCode = "400", description = "잘못된 검색 조건", content = @Content), @ApiResponse(responseCode = "400", description = "잘못된 검색 조건", content = @Content),
@ApiResponse(responseCode = "500", description = "서버 오류", content = @Content) @ApiResponse(responseCode = "500", description = "서버 오류", content = @Content)
}) })
@PostMapping("/search") @PostMapping("/search")
public ApiResponseDto<Page<Basic>> getMemberList( public ApiResponseDto<Page<Basic>> getMemberList(
@RequestBody @Valid MembersDto.SearchReq searchReq) { @RequestBody @Valid MembersDto.SearchReq searchReq) {
return ApiResponseDto.ok(membersService.findByMembers(searchReq)); return ApiResponseDto.ok(membersService.findByMembers(searchReq));
} }
@Operation( @Operation(
summary = "사용자 비밀번호 변경", summary = "사용자 비밀번호 변경",
description = "로그인 성공후 status가 INACTIVE일때 로그인 id를 memberId로 path 생성필요") description = "로그인 성공후 status가 INACTIVE일때 로그인 id를 memberId로 path 생성필요")
@ApiResponses( @ApiResponses(
value = { value = {
@ApiResponse( @ApiResponse(
responseCode = "201", responseCode = "201",
description = "사용자 비밀번호 변경", description = "사용자 비밀번호 변경",
content = content =
@Content( @Content(
mediaType = "application/json", mediaType = "application/json",
schema = @Schema(implementation = Long.class))), schema = @Schema(implementation = Long.class))),
@ApiResponse(responseCode = "400", description = "잘못된 요청 데이터", content = @Content), @ApiResponse(responseCode = "400", description = "잘못된 요청 데이터", content = @Content),
@ApiResponse(responseCode = "404", description = "코드를 찾을 수 없음", content = @Content), @ApiResponse(responseCode = "404", description = "코드를 찾을 수 없음", content = @Content),
@ApiResponse(responseCode = "500", description = "서버 오류", content = @Content) @ApiResponse(responseCode = "500", description = "서버 오류", content = @Content)
}) })
@PatchMapping("/{memberId}/password") @PatchMapping("/{memberId}/password")
public ApiResponseDto<String> resetPassword( public ApiResponseDto<String> resetPassword(
@PathVariable String memberId, @RequestBody @Valid MembersDto.InitReq initReq) { @PathVariable String memberId, @RequestBody @Valid MembersDto.InitReq initReq) {
membersService.resetPassword(memberId, initReq); membersService.resetPassword(memberId, initReq);
return ApiResponseDto.createOK(memberId); return ApiResponseDto.createOK(memberId);
} }
} }

View File

@@ -1,183 +1,183 @@
package com.kamco.cd.training.members.dto; package com.kamco.cd.training.members.dto;
import com.kamco.cd.training.common.enums.RoleType; import com.kamco.cd.training.common.enums.RoleType;
import com.kamco.cd.training.common.enums.StatusType; import com.kamco.cd.training.common.enums.StatusType;
import com.kamco.cd.training.common.utils.enums.Enums; 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.EnumValid;
import com.kamco.cd.training.common.utils.interfaces.JsonFormatDttm; import com.kamco.cd.training.common.utils.interfaces.JsonFormatDttm;
import io.swagger.v3.oas.annotations.media.Schema; import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotBlank; import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.Size; import jakarta.validation.constraints.Size;
import java.time.ZonedDateTime; import java.time.ZonedDateTime;
import java.util.UUID; import java.util.UUID;
import lombok.AllArgsConstructor; import lombok.AllArgsConstructor;
import lombok.Getter; import lombok.Getter;
import lombok.NoArgsConstructor; import lombok.NoArgsConstructor;
import lombok.Setter; import lombok.Setter;
import org.springframework.data.domain.PageRequest; import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable; import org.springframework.data.domain.Pageable;
public class MembersDto { public class MembersDto {
@Getter @Getter
@Setter @Setter
public static class Basic { public static class Basic {
private Long id; private Long id;
private UUID uuid; private UUID uuid;
private String userRole; private String userRole;
private String userRoleName; private String userRoleName;
private String name; private String name;
private String employeeNo; private String employeeNo;
private String status; private String status;
private String statusName; private String statusName;
@JsonFormatDttm private ZonedDateTime createdDttm; @JsonFormatDttm private ZonedDateTime createdDttm;
@JsonFormatDttm private ZonedDateTime firstLoginDttm; @JsonFormatDttm private ZonedDateTime firstLoginDttm;
@JsonFormatDttm private ZonedDateTime lastLoginDttm; @JsonFormatDttm private ZonedDateTime lastLoginDttm;
@JsonFormatDttm private ZonedDateTime statusChgDttm; @JsonFormatDttm private ZonedDateTime statusChgDttm;
public Basic( public Basic(
Long id, Long id,
UUID uuid, UUID uuid,
String userRole, String userRole,
String name, String name,
String employeeNo, String employeeNo,
String status, String status,
ZonedDateTime createdDttm, ZonedDateTime createdDttm,
ZonedDateTime firstLoginDttm, ZonedDateTime firstLoginDttm,
ZonedDateTime lastLoginDttm, ZonedDateTime lastLoginDttm,
ZonedDateTime statusChgDttm, ZonedDateTime statusChgDttm,
Boolean pwdResetYn) { Boolean pwdResetYn) {
this.id = id; this.id = id;
this.uuid = uuid; this.uuid = uuid;
this.userRole = userRole; this.userRole = userRole;
this.userRoleName = getUserRoleName(userRole); this.userRoleName = getUserRoleName(userRole);
this.name = name; this.name = name;
this.employeeNo = employeeNo; this.employeeNo = employeeNo;
this.status = status; this.status = status;
this.statusName = getStatusName(status, pwdResetYn); this.statusName = getStatusName(status, pwdResetYn);
this.createdDttm = createdDttm; this.createdDttm = createdDttm;
this.firstLoginDttm = firstLoginDttm; this.firstLoginDttm = firstLoginDttm;
this.lastLoginDttm = lastLoginDttm; this.lastLoginDttm = lastLoginDttm;
this.statusChgDttm = statusChgDttm; this.statusChgDttm = statusChgDttm;
} }
private String getUserRoleName(String roleId) { private String getUserRoleName(String roleId) {
RoleType type = Enums.fromId(RoleType.class, roleId); RoleType type = Enums.fromId(RoleType.class, roleId);
return type.getText(); return type.getText();
} }
private String getStatusName(String status, Boolean pwdResetYn) { private String getStatusName(String status, Boolean pwdResetYn) {
StatusType type = Enums.fromId(StatusType.class, status); StatusType type = Enums.fromId(StatusType.class, status);
pwdResetYn = pwdResetYn != null && pwdResetYn; pwdResetYn = pwdResetYn != null && pwdResetYn;
if (type.equals(StatusType.PENDING) && pwdResetYn) { if (type.equals(StatusType.PENDING) && pwdResetYn) {
type = StatusType.ACTIVE; type = StatusType.ACTIVE;
} }
return type.getText(); return type.getText();
} }
} }
@Getter @Getter
@Setter @Setter
@NoArgsConstructor @NoArgsConstructor
@AllArgsConstructor @AllArgsConstructor
public static class SearchReq { public static class SearchReq {
@Schema(description = "전체, 관리자(ADMIN), 라벨러(LABELER), 검수자(REVIEWER)", example = "") @Schema(description = "전체, 관리자(ADMIN), 라벨러(LABELER), 검수자(REVIEWER)", example = "")
private String userRole; private String userRole;
@Schema(description = "키워드", example = "홍길동") @Schema(description = "키워드", example = "홍길동")
private String keyword; private String keyword;
// 페이징 파라미터 // 페이징 파라미터
@Schema(description = "페이지 번호 (0부터 시작) ", example = "0") @Schema(description = "페이지 번호 (0부터 시작) ", example = "0")
private int page = 0; private int page = 0;
@Schema(description = "페이지 크기", example = "20") @Schema(description = "페이지 크기", example = "20")
private int size = 20; private int size = 20;
public Pageable toPageable() { public Pageable toPageable() {
return PageRequest.of(page, size); return PageRequest.of(page, size);
} }
} }
@Getter @Getter
@Setter @Setter
public static class AddReq { public static class AddReq {
@Schema(description = "관리자 유형", example = "ADMIN") @Schema(description = "관리자 유형", example = "ADMIN")
@NotBlank @NotBlank
@EnumValid(enumClass = RoleType.class, message = "userRole은 ADMIN, LABELER, REVIEWER 만 가능합니다.") @EnumValid(enumClass = RoleType.class, message = "userRole은 ADMIN, LABELER, REVIEWER 만 가능합니다.")
private String userRole; private String userRole;
@Schema(description = "사번", example = "K20251212001") @Schema(description = "사번", example = "K20251212001")
@Size(max = 50) @Size(max = 50)
private String employeeNo; private String employeeNo;
@Schema(description = "이름", example = "홍길동") @Schema(description = "이름", example = "홍길동")
@NotBlank @NotBlank
@Size(min = 2, max = 100) @Size(min = 2, max = 100)
private String name; private String name;
@NotBlank @NotBlank
@Schema(description = "패스워드", example = "") @Schema(description = "패스워드", example = "")
@Size(max = 255) @Size(max = 255)
private String password; private String password;
public AddReq(String userRole, String employeeNo, String name, String password) { public AddReq(String userRole, String employeeNo, String name, String password) {
this.userRole = userRole; this.userRole = userRole;
this.employeeNo = employeeNo; this.employeeNo = employeeNo;
this.name = name; this.name = name;
this.password = password; this.password = password;
} }
} }
@Getter @Getter
@Setter @Setter
public static class UpdateReq { public static class UpdateReq {
@Schema(description = "이름", example = "홍길동") @Schema(description = "이름", example = "홍길동")
@Size(min = 2, max = 100) @Size(min = 2, max = 100)
private String name; private String name;
@Schema(description = "상태", example = "ACTIVE") @Schema(description = "상태", example = "ACTIVE")
@EnumValid(enumClass = StatusType.class, message = "status는 ACTIVE, INACTIVE, DELETED 만 가능합니다.") @EnumValid(enumClass = StatusType.class, message = "status는 ACTIVE, INACTIVE, DELETED 만 가능합니다.")
private String status; private String status;
@Schema(description = "패스워드", example = "") @Schema(description = "패스워드", example = "")
@Size(max = 255) @Size(max = 255)
private String password; private String password;
public UpdateReq(String name, String status, String password) { public UpdateReq(String name, String status, String password) {
this.name = name; this.name = name;
this.status = status; this.status = status;
this.password = password; this.password = password;
} }
} }
@Getter @Getter
@Setter @Setter
public static class InitReq { public static class InitReq {
@Schema(description = "기존 패스워드", example = "") @Schema(description = "기존 패스워드", example = "")
@Size(max = 255) @Size(max = 255)
@NotBlank @NotBlank
private String oldPassword; private String oldPassword;
@Schema(description = "신규 패스워드", example = "") @Schema(description = "신규 패스워드", example = "")
@Size(max = 255) @Size(max = 255)
@NotBlank @NotBlank
private String newPassword; private String newPassword;
} }
@Getter @Getter
@Setter @Setter
@AllArgsConstructor @AllArgsConstructor
@NoArgsConstructor @NoArgsConstructor
public static class Member { public static class Member {
private Long id; private Long id;
private String name; private String name;
private String employeeNo; private String employeeNo;
} }
} }

View File

@@ -1,20 +1,20 @@
package com.kamco.cd.training.members.dto; package com.kamco.cd.training.members.dto;
import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.annotation.JsonProperty;
import io.swagger.v3.oas.annotations.media.Schema; import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Getter; import lombok.Getter;
import lombok.Setter; import lombok.Setter;
import lombok.ToString; import lombok.ToString;
@Getter @Getter
@Setter @Setter
@ToString(exclude = "password") @ToString(exclude = "password")
public class SignInRequest { public class SignInRequest {
@Schema(description = "사용자 ID", example = "1234567") @Schema(description = "사용자 ID", example = "1234567")
private String username; private String username;
@Schema(description = "비밀번호", example = "Admin2!@#") @Schema(description = "비밀번호", example = "Admin2!@#")
@JsonProperty(access = JsonProperty.Access.WRITE_ONLY) @JsonProperty(access = JsonProperty.Access.WRITE_ONLY)
private String password; private String password;
} }

View File

@@ -1,16 +1,16 @@
package com.kamco.cd.training.members.dto; package com.kamco.cd.training.members.dto;
import lombok.AllArgsConstructor; import lombok.AllArgsConstructor;
import lombok.Getter; import lombok.Getter;
import lombok.Setter; import lombok.Setter;
@Getter @Getter
@Setter @Setter
@AllArgsConstructor @AllArgsConstructor
public class TokenResponse { public class TokenResponse {
private String status; private String status;
private String accessToken; private String accessToken;
private String refreshToken; private String refreshToken;
private MembersDto.Member member; private MembersDto.Member member;
} }

View File

@@ -1,50 +1,50 @@
package com.kamco.cd.training.members.exception; package com.kamco.cd.training.members.exception;
import lombok.Getter; import lombok.Getter;
@Getter @Getter
public class MemberException { public class MemberException {
// *** Duplicate Member Exception *** // *** Duplicate Member Exception ***
@Getter @Getter
public static class DuplicateMemberException extends RuntimeException { public static class DuplicateMemberException extends RuntimeException {
public enum Field { public enum Field {
USER_ID, USER_ID,
EMPLOYEE_NO, EMPLOYEE_NO,
DEFAULT DEFAULT
} }
private final Field field; private final Field field;
private final String value; private final String value;
public DuplicateMemberException(Field field, String value) { public DuplicateMemberException(Field field, String value) {
super(field.name() + " duplicate: " + value); super(field.name() + " duplicate: " + value);
this.field = field; this.field = field;
this.value = value; this.value = value;
} }
} }
// *** Member Not Found Exception *** // *** Member Not Found Exception ***
public static class MemberNotFoundException extends RuntimeException { public static class MemberNotFoundException extends RuntimeException {
public MemberNotFoundException() { public MemberNotFoundException() {
super("Member not found"); super("Member not found");
} }
public MemberNotFoundException(String message) { public MemberNotFoundException(String message) {
super(message); super(message);
} }
} }
public static class PasswordNotFoundException extends RuntimeException { public static class PasswordNotFoundException extends RuntimeException {
public PasswordNotFoundException() { public PasswordNotFoundException() {
super("Password not found"); super("Password not found");
} }
public PasswordNotFoundException(String message) { public PasswordNotFoundException(String message) {
super(message); super(message);
} }
} }
} }

View File

@@ -1,77 +1,77 @@
package com.kamco.cd.training.members.service; package com.kamco.cd.training.members.service;
import com.kamco.cd.training.common.enums.error.AuthErrorCode; import com.kamco.cd.training.common.enums.error.AuthErrorCode;
import com.kamco.cd.training.common.exception.CustomApiException; import com.kamco.cd.training.common.exception.CustomApiException;
import com.kamco.cd.training.members.dto.SignInRequest; import com.kamco.cd.training.members.dto.SignInRequest;
import com.kamco.cd.training.postgres.core.MembersCoreService; import com.kamco.cd.training.postgres.core.MembersCoreService;
import com.kamco.cd.training.postgres.core.TokenCoreService; import com.kamco.cd.training.postgres.core.TokenCoreService;
import java.util.UUID; import java.util.UUID;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional; import org.springframework.transaction.annotation.Transactional;
@Service @Service
@RequiredArgsConstructor @RequiredArgsConstructor
@Transactional(readOnly = true) @Transactional(readOnly = true)
public class AuthService { public class AuthService {
private final MembersCoreService membersCoreService; private final MembersCoreService membersCoreService;
private final TokenCoreService tokenCoreService; private final TokenCoreService tokenCoreService;
/** /**
* 토큰 저장 * 토큰 저장
* *
* @param subject * @param subject
* @param refreshToken * @param refreshToken
* @param validityMs * @param validityMs
*/ */
@Transactional @Transactional
public void tokenSave(String subject, String refreshToken, long validityMs) { public void tokenSave(String subject, String refreshToken, long validityMs) {
tokenCoreService.save(subject, refreshToken, validityMs); tokenCoreService.save(subject, refreshToken, validityMs);
} }
/** /**
* refreshToken을 DB와 비교 검증 * refreshToken을 DB와 비교 검증
* *
* @param subject 사용자 식별(UUID) * @param subject 사용자 식별(UUID)
* @param requestRefreshToken refresh token * @param requestRefreshToken refresh token
*/ */
public void validateRefreshToken(String subject, String requestRefreshToken) { public void validateRefreshToken(String subject, String requestRefreshToken) {
String savedToken = tokenCoreService.getValidTokenOrThrow(subject); String savedToken = tokenCoreService.getValidTokenOrThrow(subject);
if (!savedToken.equals(requestRefreshToken)) { if (!savedToken.equals(requestRefreshToken)) {
throw new CustomApiException(AuthErrorCode.REFRESH_TOKEN_MISMATCH); throw new CustomApiException(AuthErrorCode.REFRESH_TOKEN_MISMATCH);
} }
} }
/** /**
* 로그아웃(토큰폐기) * 로그아웃(토큰폐기)
* *
* @param subject 사용자 식별(UUID) * @param subject 사용자 식별(UUID)
*/ */
@Transactional @Transactional
public void logout(String subject) { public void logout(String subject) {
// RefreshToken 폐기 // RefreshToken 폐기
tokenCoreService.revokeBySubject(subject); tokenCoreService.revokeBySubject(subject);
} }
/** /**
* 로그인 일시 저장 * 로그인 일시 저장
* *
* @param uuid * @param uuid
*/ */
@Transactional @Transactional
public void saveLogin(UUID uuid) { public void saveLogin(UUID uuid) {
membersCoreService.saveLogin(uuid); membersCoreService.saveLogin(uuid);
} }
/** /**
* 사용자 상태 조회 * 사용자 상태 조회
* *
* @param request * @param request
* @return * @return
*/ */
public String getUserStatus(SignInRequest request) { public String getUserStatus(SignInRequest request) {
return membersCoreService.getUserStatus(request); return membersCoreService.getUserStatus(request);
} }
} }

View File

@@ -1,29 +1,29 @@
package com.kamco.cd.training.members.service; package com.kamco.cd.training.members.service;
import com.kamco.cd.training.auth.CustomUserDetails; import com.kamco.cd.training.auth.CustomUserDetails;
import com.kamco.cd.training.postgres.entity.MemberEntity; import com.kamco.cd.training.postgres.entity.MemberEntity;
import com.kamco.cd.training.postgres.repository.members.MembersRepository; import com.kamco.cd.training.postgres.repository.members.MembersRepository;
import java.util.UUID; import java.util.UUID;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException; import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
@Service @Service
@RequiredArgsConstructor @RequiredArgsConstructor
public class MemberDetailsService implements UserDetailsService { public class MemberDetailsService implements UserDetailsService {
private final MembersRepository membersRepository; private final MembersRepository membersRepository;
@Override @Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
UUID uuid = UUID.fromString(username); UUID uuid = UUID.fromString(username);
MemberEntity member = MemberEntity member =
membersRepository membersRepository
.findByUUID(uuid) .findByUUID(uuid)
.orElseThrow(() -> new UsernameNotFoundException("USER NOT FOUND")); .orElseThrow(() -> new UsernameNotFoundException("USER NOT FOUND"));
return new CustomUserDetails(member); return new CustomUserDetails(member);
} }
} }

View File

@@ -1,45 +1,45 @@
package com.kamco.cd.training.members.service; package com.kamco.cd.training.members.service;
import com.kamco.cd.training.common.exception.CustomApiException; import com.kamco.cd.training.common.exception.CustomApiException;
import com.kamco.cd.training.common.utils.CommonStringUtils; import com.kamco.cd.training.common.utils.CommonStringUtils;
import com.kamco.cd.training.members.dto.MembersDto; import com.kamco.cd.training.members.dto.MembersDto;
import com.kamco.cd.training.members.dto.MembersDto.Basic; import com.kamco.cd.training.members.dto.MembersDto.Basic;
import com.kamco.cd.training.postgres.core.MembersCoreService; import com.kamco.cd.training.postgres.core.MembersCoreService;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import org.springframework.data.domain.Page; import org.springframework.data.domain.Page;
import org.springframework.http.HttpStatus; import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional; import org.springframework.transaction.annotation.Transactional;
@Service @Service
@Transactional(readOnly = true) @Transactional(readOnly = true)
@RequiredArgsConstructor @RequiredArgsConstructor
public class MembersService { public class MembersService {
private final MembersCoreService membersCoreService; private final MembersCoreService membersCoreService;
/** /**
* 회원목록 조회 * 회원목록 조회
* *
* @param searchReq * @param searchReq
* @return * @return
*/ */
public Page<Basic> findByMembers(MembersDto.SearchReq searchReq) { public Page<Basic> findByMembers(MembersDto.SearchReq searchReq) {
return membersCoreService.findByMembers(searchReq); return membersCoreService.findByMembers(searchReq);
} }
/** /**
* 패스워드 사용자 변경 * 패스워드 사용자 변경
* *
* @param id * @param id
* @param initReq * @param initReq
*/ */
@Transactional @Transactional
public void resetPassword(String id, MembersDto.InitReq initReq) { public void resetPassword(String id, MembersDto.InitReq initReq) {
if (!CommonStringUtils.isValidPassword(initReq.getNewPassword())) { if (!CommonStringUtils.isValidPassword(initReq.getNewPassword())) {
throw new CustomApiException("WRONG_PASSWORD", HttpStatus.BAD_REQUEST); throw new CustomApiException("WRONG_PASSWORD", HttpStatus.BAD_REQUEST);
} }
membersCoreService.resetPassword(id, initReq); membersCoreService.resetPassword(id, initReq);
} }
} }

View File

@@ -1,62 +1,62 @@
package com.kamco.cd.training.menu; package com.kamco.cd.training.menu;
import com.kamco.cd.training.config.api.ApiResponseDto; import com.kamco.cd.training.config.api.ApiResponseDto;
import com.kamco.cd.training.menu.dto.MenuDto; import com.kamco.cd.training.menu.dto.MenuDto;
import com.kamco.cd.training.menu.service.MenuService; import com.kamco.cd.training.menu.service.MenuService;
import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.media.Content; import io.swagger.v3.oas.annotations.media.Content;
import io.swagger.v3.oas.annotations.media.Schema; import io.swagger.v3.oas.annotations.media.Schema;
import io.swagger.v3.oas.annotations.responses.ApiResponse; import io.swagger.v3.oas.annotations.responses.ApiResponse;
import io.swagger.v3.oas.annotations.responses.ApiResponses; import io.swagger.v3.oas.annotations.responses.ApiResponses;
import io.swagger.v3.oas.annotations.tags.Tag; import io.swagger.v3.oas.annotations.tags.Tag;
import java.util.List; import java.util.List;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import org.springframework.data.domain.Page; import org.springframework.data.domain.Page;
import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController; import org.springframework.web.bind.annotation.RestController;
@Tag(name = "메뉴 관리", description = "메뉴 관리 API") @Tag(name = "메뉴 관리", description = "메뉴 관리 API")
@RestController @RestController
@RequestMapping("/api/menu") @RequestMapping("/api/menu")
@RequiredArgsConstructor @RequiredArgsConstructor
public class MenuApiController { public class MenuApiController {
private final MenuService menuService; private final MenuService menuService;
@Operation(summary = "메뉴 목록", description = "메뉴 목록 조회") @Operation(summary = "메뉴 목록", description = "메뉴 목록 조회")
@ApiResponses( @ApiResponses(
value = { value = {
@ApiResponse( @ApiResponse(
responseCode = "200", responseCode = "200",
description = "검색 성공", description = "검색 성공",
content = content =
@Content( @Content(
mediaType = "application/json", mediaType = "application/json",
schema = @Schema(implementation = Page.class))), schema = @Schema(implementation = Page.class))),
@ApiResponse(responseCode = "400", description = "잘못된 검색 조건", content = @Content), @ApiResponse(responseCode = "400", description = "잘못된 검색 조건", content = @Content),
@ApiResponse(responseCode = "500", description = "서버 오류", content = @Content) @ApiResponse(responseCode = "500", description = "서버 오류", content = @Content)
}) })
@GetMapping @GetMapping
public ApiResponseDto<List<MenuDto.Basic>> getFindAll() { public ApiResponseDto<List<MenuDto.Basic>> getFindAll() {
return ApiResponseDto.ok(menuService.getFindAll()); return ApiResponseDto.ok(menuService.getFindAll());
} }
@Operation(summary = "캐시 초기화", description = "메뉴관리 캐시를 초기화합니다.") @Operation(summary = "캐시 초기화", description = "메뉴관리 캐시를 초기화합니다.")
@ApiResponses( @ApiResponses(
value = { value = {
@ApiResponse( @ApiResponse(
responseCode = "200", responseCode = "200",
description = "캐시 초기화 성공", description = "캐시 초기화 성공",
content = content =
@Content( @Content(
mediaType = "application/json", mediaType = "application/json",
schema = @Schema(implementation = String.class))), schema = @Schema(implementation = String.class))),
@ApiResponse(responseCode = "500", description = "서버 오류", content = @Content) @ApiResponse(responseCode = "500", description = "서버 오류", content = @Content)
}) })
@GetMapping("/cache/refresh") @GetMapping("/cache/refresh")
public ApiResponseDto<String> refreshCommonCodeCache() { public ApiResponseDto<String> refreshCommonCodeCache() {
menuService.refresh(); menuService.refresh();
return ApiResponseDto.ok("메뉴관리 캐시가 초기화되었습니다."); return ApiResponseDto.ok("메뉴관리 캐시가 초기화되었습니다.");
} }
} }

View File

@@ -1,64 +1,64 @@
package com.kamco.cd.training.menu.dto; package com.kamco.cd.training.menu.dto;
import com.kamco.cd.training.common.utils.interfaces.JsonFormatDttm; import com.kamco.cd.training.common.utils.interfaces.JsonFormatDttm;
import io.swagger.v3.oas.annotations.media.Schema; import io.swagger.v3.oas.annotations.media.Schema;
import java.time.ZonedDateTime; import java.time.ZonedDateTime;
import java.util.List; import java.util.List;
import lombok.Getter; import lombok.Getter;
import lombok.NoArgsConstructor; import lombok.NoArgsConstructor;
public class MenuDto { public class MenuDto {
@Schema(name = "Menu Basic", description = "메뉴 기본 정보") @Schema(name = "Menu Basic", description = "메뉴 기본 정보")
@Getter @Getter
@NoArgsConstructor @NoArgsConstructor
public static class Basic { public static class Basic {
private String menuUid; private String menuUid;
private String menuNm; private String menuNm;
private String menuUrl; private String menuUrl;
private String description; private String description;
private Long menuOrder; private Long menuOrder;
private Boolean isUse; private Boolean isUse;
private Boolean deleted; private Boolean deleted;
private Long createdUid; private Long createdUid;
private Long updatedUid; private Long updatedUid;
private List<Basic> children; private List<Basic> children;
@JsonFormatDttm private ZonedDateTime createdDttm; @JsonFormatDttm private ZonedDateTime createdDttm;
@JsonFormatDttm private ZonedDateTime updatedDttm; @JsonFormatDttm private ZonedDateTime updatedDttm;
private String menuApiUrl; private String menuApiUrl;
public Basic( public Basic(
String menuUid, String menuUid,
String menuNm, String menuNm,
String menuUrl, String menuUrl,
String description, String description,
Long menuOrder, Long menuOrder,
Boolean isUse, Boolean isUse,
Boolean deleted, Boolean deleted,
Long createdUid, Long createdUid,
Long updatedUid, Long updatedUid,
List<Basic> children, List<Basic> children,
ZonedDateTime createdDttm, ZonedDateTime createdDttm,
ZonedDateTime updatedDttm, ZonedDateTime updatedDttm,
String menuApiUrl) { String menuApiUrl) {
this.menuUid = menuUid; this.menuUid = menuUid;
this.menuNm = menuNm; this.menuNm = menuNm;
this.menuUrl = menuUrl; this.menuUrl = menuUrl;
this.description = description; this.description = description;
this.menuOrder = menuOrder; this.menuOrder = menuOrder;
this.isUse = isUse; this.isUse = isUse;
this.deleted = deleted; this.deleted = deleted;
this.createdUid = createdUid; this.createdUid = createdUid;
this.updatedUid = updatedUid; this.updatedUid = updatedUid;
this.children = children; this.children = children;
this.createdDttm = createdDttm; this.createdDttm = createdDttm;
this.updatedDttm = updatedDttm; this.updatedDttm = updatedDttm;
this.menuApiUrl = menuApiUrl; this.menuApiUrl = menuApiUrl;
} }
} }
} }

View File

@@ -1,27 +1,27 @@
package com.kamco.cd.training.menu.service; package com.kamco.cd.training.menu.service;
import com.kamco.cd.training.menu.dto.MenuDto; import com.kamco.cd.training.menu.dto.MenuDto;
import com.kamco.cd.training.postgres.core.MenuCoreService; import com.kamco.cd.training.postgres.core.MenuCoreService;
import java.util.List; import java.util.List;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import org.springframework.cache.annotation.CacheEvict; import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.Cacheable; import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
// training 서버는 Redis 사용하지 않고 Spring Boot 메모리 캐시를 사용함 // training 서버는 Redis 사용하지 않고 Spring Boot 메모리 캐시를 사용함
// => org.springframework.cache.annotation.Cacheable // => org.springframework.cache.annotation.Cacheable
@Service @Service
@RequiredArgsConstructor @RequiredArgsConstructor
public class MenuService { public class MenuService {
private final MenuCoreService menuCoreService; private final MenuCoreService menuCoreService;
@Cacheable("trainMenuFindAll") @Cacheable("trainMenuFindAll")
public List<MenuDto.Basic> getFindAll() { public List<MenuDto.Basic> getFindAll() {
return menuCoreService.getFindAll(); return menuCoreService.getFindAll();
} }
/** 메모리 캐시 초기화 */ /** 메모리 캐시 초기화 */
@CacheEvict(value = "trainMenuFindAll", allEntries = true) @CacheEvict(value = "trainMenuFindAll", allEntries = true)
public void refresh() {} public void refresh() {}
} }

View File

@@ -1,286 +1,286 @@
package com.kamco.cd.training.model; package com.kamco.cd.training.model;
import com.kamco.cd.training.config.api.ApiResponseDto; import com.kamco.cd.training.config.api.ApiResponseDto;
import com.kamco.cd.training.model.dto.ModelMngDto; 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.Basic;
import com.kamco.cd.training.model.service.ModelMngService; import com.kamco.cd.training.model.service.ModelMngService;
import com.kamco.cd.training.model.service.ModelTrainService; import com.kamco.cd.training.model.service.ModelTrainService;
import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter; import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.media.Content; import io.swagger.v3.oas.annotations.media.Content;
import io.swagger.v3.oas.annotations.media.Schema; import io.swagger.v3.oas.annotations.media.Schema;
import io.swagger.v3.oas.annotations.responses.ApiResponse; import io.swagger.v3.oas.annotations.responses.ApiResponse;
import io.swagger.v3.oas.annotations.responses.ApiResponses; import io.swagger.v3.oas.annotations.responses.ApiResponses;
import io.swagger.v3.oas.annotations.tags.Tag; import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.validation.Valid; import jakarta.validation.Valid;
import java.util.List; import java.util.List;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import org.springframework.data.domain.Page; import org.springframework.data.domain.Page;
import org.springframework.web.bind.annotation.DeleteMapping; import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController; import org.springframework.web.bind.annotation.RestController;
@RestController @RestController
@RequiredArgsConstructor @RequiredArgsConstructor
@Tag(name = "모델관리", description = "모델관리 (학습 모델, 하이퍼파라미터, 메모)") @Tag(name = "모델관리", description = "모델관리 (학습 모델, 하이퍼파라미터, 메모)")
@RequestMapping("/api/models") @RequestMapping("/api/models")
public class ModelMngApiController { public class ModelMngApiController {
private final ModelMngService modelMngService; private final ModelMngService modelMngService;
private final ModelTrainService modelTrainService; private final ModelTrainService modelTrainService;
@Operation(summary = "학습 모델 목록 조회", description = "학습 모델 목록을 조회합니다") @Operation(summary = "학습 모델 목록 조회", description = "학습 모델 목록을 조회합니다")
@ApiResponses( @ApiResponses(
value = { value = {
@ApiResponse( @ApiResponse(
responseCode = "200", responseCode = "200",
description = "검색 성공", description = "검색 성공",
content = content =
@Content( @Content(
mediaType = "application/json", mediaType = "application/json",
schema = @Schema(implementation = Page.class))), schema = @Schema(implementation = Page.class))),
@ApiResponse(responseCode = "400", description = "잘못된 검색 조건", content = @Content), @ApiResponse(responseCode = "400", description = "잘못된 검색 조건", content = @Content),
@ApiResponse(responseCode = "500", description = "서버 오류", content = @Content) @ApiResponse(responseCode = "500", description = "서버 오류", content = @Content)
}) })
@GetMapping @GetMapping
public ApiResponseDto<Page<Basic>> findByModels( public ApiResponseDto<Page<Basic>> findByModels(
@Parameter(description = "상태 코드") @RequestParam(required = false) String status, @Parameter(description = "상태 코드") @RequestParam(required = false) String status,
@Parameter(description = "페이지 번호") @RequestParam(defaultValue = "0") int page, @Parameter(description = "페이지 번호") @RequestParam(defaultValue = "0") int page,
@Parameter(description = "페이지 크기") @RequestParam(defaultValue = "20") int size) { @Parameter(description = "페이지 크기") @RequestParam(defaultValue = "20") int size) {
ModelMngDto.SearchReq searchReq = new ModelMngDto.SearchReq(status, page, size); ModelMngDto.SearchReq searchReq = new ModelMngDto.SearchReq(status, page, size);
return ApiResponseDto.ok(modelMngService.findByModels(searchReq)); return ApiResponseDto.ok(modelMngService.findByModels(searchReq));
} }
@Operation(summary = "학습 모델 상세 조회", description = "학습 모델의 상세 정보를 UUID로 조회합니다") @Operation(summary = "학습 모델 상세 조회", description = "학습 모델의 상세 정보를 UUID로 조회합니다")
@ApiResponses( @ApiResponses(
value = { value = {
@ApiResponse( @ApiResponse(
responseCode = "200", responseCode = "200",
description = "조회 성공", description = "조회 성공",
content = content =
@Content( @Content(
mediaType = "application/json", mediaType = "application/json",
schema = @Schema(implementation = ModelMngDto.Detail.class))), schema = @Schema(implementation = ModelMngDto.Detail.class))),
@ApiResponse(responseCode = "404", description = "모델을 찾을 수 없음", content = @Content), @ApiResponse(responseCode = "404", description = "모델을 찾을 수 없음", content = @Content),
@ApiResponse(responseCode = "500", description = "서버 오류", content = @Content) @ApiResponse(responseCode = "500", description = "서버 오류", content = @Content)
}) })
@GetMapping("/{uuid}") @GetMapping("/{uuid}")
public ApiResponseDto<ModelMngDto.Detail> getModelDetail( public ApiResponseDto<ModelMngDto.Detail> getModelDetail(
@Parameter(description = "모델 UUID", example = "b7e99739-6736-45f9-a224-8161ecddf287") @Parameter(description = "모델 UUID", example = "b7e99739-6736-45f9-a224-8161ecddf287")
@PathVariable @PathVariable
String uuid) { String uuid) {
return ApiResponseDto.ok(modelMngService.getModelDetailByUuid(uuid)); return ApiResponseDto.ok(modelMngService.getModelDetailByUuid(uuid));
} }
// ==================== 학습 모델학습관리 API (5종) ==================== // ==================== 학습 모델학습관리 API (5종) ====================
@Operation(summary = "학습 모델 통합 조회", description = "학습 관리 화면에서 학습 이력 리스트와 현재 상태를 조회합니다") @Operation(summary = "학습 모델 통합 조회", description = "학습 관리 화면에서 학습 이력 리스트와 현재 상태를 조회합니다")
@ApiResponses( @ApiResponses(
value = { value = {
@ApiResponse( @ApiResponse(
responseCode = "200", responseCode = "200",
description = "조회 성공", description = "조회 성공",
content = content =
@Content( @Content(
mediaType = "application/json", mediaType = "application/json",
schema = @Schema(implementation = List.class))), schema = @Schema(implementation = List.class))),
@ApiResponse(responseCode = "500", description = "서버 오류", content = @Content) @ApiResponse(responseCode = "500", description = "서버 오류", content = @Content)
}) })
@GetMapping("/train") @GetMapping("/train")
public ApiResponseDto<List<ModelMngDto.TrainListRes>> getTrainModelList() { public ApiResponseDto<List<ModelMngDto.TrainListRes>> getTrainModelList() {
return ApiResponseDto.ok(modelTrainService.getTrainModelList()); return ApiResponseDto.ok(modelTrainService.getTrainModelList());
} }
@Operation(summary = "학습 설정 통합 조회", description = "학습 실행 팝업 구성에 필요한 모든 데이터를 한 번에 반환합니다") @Operation(summary = "학습 설정 통합 조회", description = "학습 실행 팝업 구성에 필요한 모든 데이터를 한 번에 반환합니다")
@ApiResponses( @ApiResponses(
value = { value = {
@ApiResponse( @ApiResponse(
responseCode = "200", responseCode = "200",
description = "조회 성공", description = "조회 성공",
content = content =
@Content( @Content(
mediaType = "application/json", mediaType = "application/json",
schema = @Schema(implementation = ModelMngDto.FormConfigRes.class))), schema = @Schema(implementation = ModelMngDto.FormConfigRes.class))),
@ApiResponse(responseCode = "500", description = "서버 오류", content = @Content) @ApiResponse(responseCode = "500", description = "서버 오류", content = @Content)
}) })
@GetMapping("/train/form-config") @GetMapping("/train/form-config")
public ApiResponseDto<ModelMngDto.FormConfigRes> getFormConfig() { public ApiResponseDto<ModelMngDto.FormConfigRes> getFormConfig() {
return ApiResponseDto.ok(modelTrainService.getFormConfig()); return ApiResponseDto.ok(modelTrainService.getFormConfig());
} }
@Operation(summary = "하이퍼파라미터 등록", description = "Step 1 에서 파라미터를 수정하여 신규 버전으로 저장합니다") @Operation(summary = "하이퍼파라미터 등록", description = "Step 1 에서 파라미터를 수정하여 신규 버전으로 저장합니다")
@ApiResponses( @ApiResponses(
value = { value = {
@ApiResponse( @ApiResponse(
responseCode = "200", responseCode = "200",
description = "등록 성공", description = "등록 성공",
content = content =
@Content( @Content(
mediaType = "application/json", mediaType = "application/json",
schema = @Schema(implementation = String.class))), schema = @Schema(implementation = String.class))),
@ApiResponse(responseCode = "400", description = "잘못된 요청", content = @Content), @ApiResponse(responseCode = "400", description = "잘못된 요청", content = @Content),
@ApiResponse(responseCode = "500", description = "서버 오류", content = @Content) @ApiResponse(responseCode = "500", description = "서버 오류", content = @Content)
}) })
@PostMapping("/hyper-params") @PostMapping("/hyper-params")
public ApiResponseDto<String> createHyperParam( public ApiResponseDto<String> createHyperParam(
@Valid @RequestBody ModelMngDto.HyperParamCreateReq createReq) { @Valid @RequestBody ModelMngDto.HyperParamCreateReq createReq) {
String newVersion = modelTrainService.createHyperParam(createReq); String newVersion = modelTrainService.createHyperParam(createReq);
return ApiResponseDto.ok(newVersion); return ApiResponseDto.ok(newVersion);
} }
@Operation(summary = "하이퍼파라미터 단건 조회", description = "특정 버전의 하이퍼파라미터 상세 정보를 조회합니다") @Operation(summary = "하이퍼파라미터 단건 조회", description = "특정 버전의 하이퍼파라미터 상세 정보를 조회합니다")
@ApiResponses( @ApiResponses(
value = { value = {
@ApiResponse( @ApiResponse(
responseCode = "200", responseCode = "200",
description = "조회 성공", description = "조회 성공",
content = content =
@Content( @Content(
mediaType = "application/json", mediaType = "application/json",
schema = @Schema(implementation = ModelMngDto.HyperParamInfo.class))), schema = @Schema(implementation = ModelMngDto.HyperParamInfo.class))),
@ApiResponse(responseCode = "404", description = "하이퍼파라미터를 찾을 수 없음", content = @Content), @ApiResponse(responseCode = "404", description = "하이퍼파라미터를 찾을 수 없음", content = @Content),
@ApiResponse(responseCode = "500", description = "서버 오류", content = @Content) @ApiResponse(responseCode = "500", description = "서버 오류", content = @Content)
}) })
@GetMapping("/hyper-params/{hyperVer}") @GetMapping("/hyper-params/{hyperVer}")
public ApiResponseDto<ModelMngDto.HyperParamInfo> getHyperParam( public ApiResponseDto<ModelMngDto.HyperParamInfo> getHyperParam(
@Parameter(description = "하이퍼파라미터 버전", example = "H1") @PathVariable String hyperVer) { @Parameter(description = "하이퍼파라미터 버전", example = "H1") @PathVariable String hyperVer) {
return ApiResponseDto.ok(modelTrainService.getHyperParam(hyperVer)); return ApiResponseDto.ok(modelTrainService.getHyperParam(hyperVer));
} }
@Operation(summary = "하이퍼파라미터 삭제", description = "특정 버전의 하이퍼파라미터를 삭제합니다 (H1은 삭제 불가)") @Operation(summary = "하이퍼파라미터 삭제", description = "특정 버전의 하이퍼파라미터를 삭제합니다 (H1은 삭제 불가)")
@ApiResponses( @ApiResponses(
value = { value = {
@ApiResponse(responseCode = "200", description = "삭제 성공", content = @Content), @ApiResponse(responseCode = "200", description = "삭제 성공", content = @Content),
@ApiResponse(responseCode = "400", description = "H1은 삭제 불가", content = @Content), @ApiResponse(responseCode = "400", description = "H1은 삭제 불가", content = @Content),
@ApiResponse(responseCode = "404", description = "하이퍼파라미터를 찾을 수 없음", content = @Content), @ApiResponse(responseCode = "404", description = "하이퍼파라미터를 찾을 수 없음", content = @Content),
@ApiResponse(responseCode = "500", description = "서버 오류", content = @Content) @ApiResponse(responseCode = "500", description = "서버 오류", content = @Content)
}) })
@DeleteMapping("/hyper-params/{hyperVer}") @DeleteMapping("/hyper-params/{hyperVer}")
public ApiResponseDto<Void> deleteHyperParam( public ApiResponseDto<Void> deleteHyperParam(
@Parameter(description = "하이퍼파라미터 버전", example = "V3.99.251221.120518") @PathVariable @Parameter(description = "하이퍼파라미터 버전", example = "V3.99.251221.120518") @PathVariable
String hyperVer) { String hyperVer) {
modelTrainService.deleteHyperParam(hyperVer); modelTrainService.deleteHyperParam(hyperVer);
return ApiResponseDto.ok(null); return ApiResponseDto.ok(null);
} }
@Operation(summary = "학습 시작", description = "모든 설정(Step 1~3)을 마치고 최종적으로 학습 프로세스를 시작합니다") @Operation(summary = "학습 시작", description = "모든 설정(Step 1~3)을 마치고 최종적으로 학습 프로세스를 시작합니다")
@ApiResponses( @ApiResponses(
value = { value = {
@ApiResponse( @ApiResponse(
responseCode = "200", responseCode = "200",
description = "학습 시작 성공", description = "학습 시작 성공",
content = content =
@Content( @Content(
mediaType = "application/json", mediaType = "application/json",
schema = @Schema(implementation = ModelMngDto.TrainStartRes.class))), schema = @Schema(implementation = ModelMngDto.TrainStartRes.class))),
@ApiResponse(responseCode = "400", description = "잘못된 요청", content = @Content), @ApiResponse(responseCode = "400", description = "잘못된 요청", content = @Content),
@ApiResponse(responseCode = "500", description = "서버 오류", content = @Content) @ApiResponse(responseCode = "500", description = "서버 오류", content = @Content)
}) })
@PostMapping("/train") @PostMapping("/train")
public ApiResponseDto<ModelMngDto.TrainStartRes> startTraining( public ApiResponseDto<ModelMngDto.TrainStartRes> startTraining(
@Valid @RequestBody ModelMngDto.TrainStartReq trainReq) { @Valid @RequestBody ModelMngDto.TrainStartReq trainReq) {
return ApiResponseDto.ok(modelTrainService.startTraining(trainReq)); return ApiResponseDto.ok(modelTrainService.startTraining(trainReq));
} }
@Operation(summary = "학습 모델 삭제", description = "목록에서 특정 학습 모델을 삭제합니다") @Operation(summary = "학습 모델 삭제", description = "목록에서 특정 학습 모델을 삭제합니다")
@ApiResponses( @ApiResponses(
value = { value = {
@ApiResponse(responseCode = "200", description = "삭제 성공", content = @Content), @ApiResponse(responseCode = "200", description = "삭제 성공", content = @Content),
@ApiResponse(responseCode = "400", description = "진행 중인 모델은 삭제 불가", content = @Content), @ApiResponse(responseCode = "400", description = "진행 중인 모델은 삭제 불가", content = @Content),
@ApiResponse(responseCode = "404", description = "모델을 찾을 수 없음", content = @Content), @ApiResponse(responseCode = "404", description = "모델을 찾을 수 없음", content = @Content),
@ApiResponse(responseCode = "500", description = "서버 오류", content = @Content) @ApiResponse(responseCode = "500", description = "서버 오류", content = @Content)
}) })
@DeleteMapping("/train/{uuid}") @DeleteMapping("/train/{uuid}")
public ApiResponseDto<Void> deleteTrainModel( public ApiResponseDto<Void> deleteTrainModel(
@Parameter(description = "모델 UUID") @PathVariable String uuid) { @Parameter(description = "모델 UUID") @PathVariable String uuid) {
modelTrainService.deleteTrainModel(uuid); modelTrainService.deleteTrainModel(uuid);
return ApiResponseDto.ok(null); return ApiResponseDto.ok(null);
} }
// ==================== Resume Training (학습 재시작) ==================== // ==================== Resume Training (학습 재시작) ====================
@Operation(summary = "학습 재시작 정보 조회", description = "중단된 학습의 재시작 가능 여부와 Checkpoint 정보를 조회합니다") @Operation(summary = "학습 재시작 정보 조회", description = "중단된 학습의 재시작 가능 여부와 Checkpoint 정보를 조회합니다")
@ApiResponses( @ApiResponses(
value = { value = {
@ApiResponse( @ApiResponse(
responseCode = "200", responseCode = "200",
description = "조회 성공", description = "조회 성공",
content = content =
@Content( @Content(
mediaType = "application/json", mediaType = "application/json",
schema = @Schema(implementation = ModelMngDto.ResumeInfo.class))), schema = @Schema(implementation = ModelMngDto.ResumeInfo.class))),
@ApiResponse(responseCode = "404", description = "모델을 찾을 수 없음", content = @Content), @ApiResponse(responseCode = "404", description = "모델을 찾을 수 없음", content = @Content),
@ApiResponse(responseCode = "500", description = "서버 오류", content = @Content) @ApiResponse(responseCode = "500", description = "서버 오류", content = @Content)
}) })
@GetMapping("/train/{uuid}/resume-info") @GetMapping("/train/{uuid}/resume-info")
public ApiResponseDto<ModelMngDto.ResumeInfo> getResumeInfo( public ApiResponseDto<ModelMngDto.ResumeInfo> getResumeInfo(
@Parameter(description = "모델 UUID") @PathVariable String uuid) { @Parameter(description = "모델 UUID") @PathVariable String uuid) {
return ApiResponseDto.ok(modelTrainService.getResumeInfo(uuid)); return ApiResponseDto.ok(modelTrainService.getResumeInfo(uuid));
} }
@Operation(summary = "학습 재시작", description = "중단된 지점(Checkpoint)부터 학습을 재개합니다") @Operation(summary = "학습 재시작", description = "중단된 지점(Checkpoint)부터 학습을 재개합니다")
@ApiResponses( @ApiResponses(
value = { value = {
@ApiResponse( @ApiResponse(
responseCode = "200", responseCode = "200",
description = "재시작 성공", description = "재시작 성공",
content = content =
@Content( @Content(
mediaType = "application/json", mediaType = "application/json",
schema = @Schema(implementation = ModelMngDto.ResumeResponse.class))), schema = @Schema(implementation = ModelMngDto.ResumeResponse.class))),
@ApiResponse(responseCode = "400", description = "재시작 불가능한 상태", content = @Content), @ApiResponse(responseCode = "400", description = "재시작 불가능한 상태", content = @Content),
@ApiResponse(responseCode = "404", description = "모델을 찾을 수 없음", content = @Content), @ApiResponse(responseCode = "404", description = "모델을 찾을 수 없음", content = @Content),
@ApiResponse(responseCode = "500", description = "서버 오류", content = @Content) @ApiResponse(responseCode = "500", description = "서버 오류", content = @Content)
}) })
@PostMapping("/train/{uuid}/resume") @PostMapping("/train/{uuid}/resume")
public ApiResponseDto<ModelMngDto.ResumeResponse> resumeTraining( public ApiResponseDto<ModelMngDto.ResumeResponse> resumeTraining(
@Parameter(description = "모델 UUID") @PathVariable String uuid, @Parameter(description = "모델 UUID") @PathVariable String uuid,
@Valid @RequestBody ModelMngDto.ResumeRequest resumeReq) { @Valid @RequestBody ModelMngDto.ResumeRequest resumeReq) {
return ApiResponseDto.ok(modelTrainService.resumeTraining(uuid, resumeReq)); return ApiResponseDto.ok(modelTrainService.resumeTraining(uuid, resumeReq));
} }
// ==================== Best Epoch Setting (Best Epoch 설정) ==================== // ==================== Best Epoch Setting (Best Epoch 설정) ====================
@Operation(summary = "Best Epoch 설정", description = "사용자가 직접 Best Epoch를 선택하여 설정합니다") @Operation(summary = "Best Epoch 설정", description = "사용자가 직접 Best Epoch를 선택하여 설정합니다")
@ApiResponses( @ApiResponses(
value = { value = {
@ApiResponse( @ApiResponse(
responseCode = "200", responseCode = "200",
description = "설정 성공", description = "설정 성공",
content = content =
@Content( @Content(
mediaType = "application/json", mediaType = "application/json",
schema = @Schema(implementation = ModelMngDto.BestEpochResponse.class))), schema = @Schema(implementation = ModelMngDto.BestEpochResponse.class))),
@ApiResponse(responseCode = "404", description = "모델을 찾을 수 없음", content = @Content), @ApiResponse(responseCode = "404", description = "모델을 찾을 수 없음", content = @Content),
@ApiResponse(responseCode = "500", description = "서버 오류", content = @Content) @ApiResponse(responseCode = "500", description = "서버 오류", content = @Content)
}) })
@PostMapping("/train/{uuid}/best-epoch") @PostMapping("/train/{uuid}/best-epoch")
public ApiResponseDto<ModelMngDto.BestEpochResponse> setBestEpoch( public ApiResponseDto<ModelMngDto.BestEpochResponse> setBestEpoch(
@Parameter(description = "모델 UUID") @PathVariable String uuid, @Parameter(description = "모델 UUID") @PathVariable String uuid,
@Valid @RequestBody ModelMngDto.BestEpochRequest bestEpochReq) { @Valid @RequestBody ModelMngDto.BestEpochRequest bestEpochReq) {
return ApiResponseDto.ok(modelTrainService.setBestEpoch(uuid, bestEpochReq)); return ApiResponseDto.ok(modelTrainService.setBestEpoch(uuid, bestEpochReq));
} }
@Operation(summary = "Epoch별 성능 지표 조회", description = "학습된 모델의 Epoch별 성능 지표를 조회합니다") @Operation(summary = "Epoch별 성능 지표 조회", description = "학습된 모델의 Epoch별 성능 지표를 조회합니다")
@ApiResponses( @ApiResponses(
value = { value = {
@ApiResponse( @ApiResponse(
responseCode = "200", responseCode = "200",
description = "조회 성공", description = "조회 성공",
content = content =
@Content( @Content(
mediaType = "application/json", mediaType = "application/json",
schema = @Schema(implementation = List.class))), schema = @Schema(implementation = List.class))),
@ApiResponse(responseCode = "404", description = "모델을 찾을 수 없음", content = @Content), @ApiResponse(responseCode = "404", description = "모델을 찾을 수 없음", content = @Content),
@ApiResponse(responseCode = "500", description = "서버 오류", content = @Content) @ApiResponse(responseCode = "500", description = "서버 오류", content = @Content)
}) })
@GetMapping("/train/{uuid}/epoch-metrics") @GetMapping("/train/{uuid}/epoch-metrics")
public ApiResponseDto<List<ModelMngDto.EpochMetric>> getEpochMetrics( public ApiResponseDto<List<ModelMngDto.EpochMetric>> getEpochMetrics(
@Parameter(description = "모델 UUID") @PathVariable String uuid) { @Parameter(description = "모델 UUID") @PathVariable String uuid) {
return ApiResponseDto.ok(modelTrainService.getEpochMetrics(uuid)); return ApiResponseDto.ok(modelTrainService.getEpochMetrics(uuid));
} }
} }

View File

@@ -1,219 +1,219 @@
package com.kamco.cd.training.model.dto; package com.kamco.cd.training.model.dto;
import com.kamco.cd.training.common.utils.interfaces.JsonFormatDttm; import com.kamco.cd.training.common.utils.interfaces.JsonFormatDttm;
import com.kamco.cd.training.postgres.entity.ModelHyperParamEntity; import com.kamco.cd.training.postgres.entity.ModelHyperParamEntity;
import io.swagger.v3.oas.annotations.media.Schema; import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotBlank; import jakarta.validation.constraints.NotBlank;
import java.time.ZonedDateTime; import java.time.ZonedDateTime;
import lombok.AllArgsConstructor; import lombok.AllArgsConstructor;
import lombok.Getter; import lombok.Getter;
import lombok.NoArgsConstructor; import lombok.NoArgsConstructor;
import lombok.Setter; import lombok.Setter;
public class HyperParamDto { public class HyperParamDto {
@Schema(name = "HyperParam Basic", description = "하이퍼파라미터 기본 정보") @Schema(name = "HyperParam Basic", description = "하이퍼파라미터 기본 정보")
@Getter @Getter
@Setter @Setter
@NoArgsConstructor @NoArgsConstructor
@AllArgsConstructor @AllArgsConstructor
public static class Basic { public static class Basic {
private String hyperVer; private String hyperVer;
// Important // Important
private String backbone; private String backbone;
private String inputSize; private String inputSize;
private String cropSize; private String cropSize;
private Integer epochCnt; private Integer epochCnt;
private Integer batchSize; private Integer batchSize;
// Architecture // Architecture
private Double dropPathRate; private Double dropPathRate;
private Integer frozenStages; private Integer frozenStages;
private String neckPolicy; private String neckPolicy;
private String decoderChannels; private String decoderChannels;
private String classWeight; private String classWeight;
private Integer numLayers; private Integer numLayers;
// Optimization // Optimization
private Double learningRate; private Double learningRate;
private Double weightDecay; private Double weightDecay;
private Double layerDecayRate; private Double layerDecayRate;
private Boolean ddpFindUnusedParams; private Boolean ddpFindUnusedParams;
private Integer ignoreIndex; private Integer ignoreIndex;
// Data // Data
private Integer trainNumWorkers; private Integer trainNumWorkers;
private Integer valNumWorkers; private Integer valNumWorkers;
private Integer testNumWorkers; private Integer testNumWorkers;
private Boolean trainShuffle; private Boolean trainShuffle;
private Boolean trainPersistent; private Boolean trainPersistent;
private Boolean valPersistent; private Boolean valPersistent;
// Evaluation // Evaluation
private String metrics; private String metrics;
private String saveBest; private String saveBest;
private String saveBestRule; private String saveBestRule;
private Integer valInterval; private Integer valInterval;
private Integer logInterval; private Integer logInterval;
private Integer visInterval; private Integer visInterval;
// Hardware // Hardware
private Integer gpuCnt; private Integer gpuCnt;
private String gpuIds; private String gpuIds;
private Integer masterPort; private Integer masterPort;
// Augmentation // Augmentation
private Double rotProb; private Double rotProb;
private Double flipProb; private Double flipProb;
private String rotDegree; private String rotDegree;
private Double exchangeProb; private Double exchangeProb;
private Integer brightnessDelta; private Integer brightnessDelta;
private String contrastRange; private String contrastRange;
private String saturationRange; private String saturationRange;
private Integer hueDelta; private Integer hueDelta;
// Legacy (deprecated) // Legacy (deprecated)
private Double dropoutRatio; private Double dropoutRatio;
private Integer cnnFilterCnt; private Integer cnnFilterCnt;
// Common // Common
private String memo; private String memo;
@JsonFormatDttm private ZonedDateTime createdDttm; @JsonFormatDttm private ZonedDateTime createdDttm;
public Basic(ModelHyperParamEntity entity) { public Basic(ModelHyperParamEntity entity) {
this.hyperVer = entity.getHyperVer(); this.hyperVer = entity.getHyperVer();
// Important // Important
this.backbone = entity.getBackbone(); this.backbone = entity.getBackbone();
this.inputSize = entity.getInputSize(); this.inputSize = entity.getInputSize();
this.cropSize = entity.getCropSize(); this.cropSize = entity.getCropSize();
this.epochCnt = entity.getEpochCnt(); this.epochCnt = entity.getEpochCnt();
this.batchSize = entity.getBatchSize(); this.batchSize = entity.getBatchSize();
// Architecture // Architecture
this.dropPathRate = entity.getDropPathRate(); this.dropPathRate = entity.getDropPathRate();
this.frozenStages = entity.getFrozenStages(); this.frozenStages = entity.getFrozenStages();
this.neckPolicy = entity.getNeckPolicy(); this.neckPolicy = entity.getNeckPolicy();
this.decoderChannels = entity.getDecoderChannels(); this.decoderChannels = entity.getDecoderChannels();
this.classWeight = entity.getClassWeight(); this.classWeight = entity.getClassWeight();
this.numLayers = entity.getNumLayers(); this.numLayers = entity.getNumLayers();
// Optimization // Optimization
this.learningRate = entity.getLearningRate(); this.learningRate = entity.getLearningRate();
this.weightDecay = entity.getWeightDecay(); this.weightDecay = entity.getWeightDecay();
this.layerDecayRate = entity.getLayerDecayRate(); this.layerDecayRate = entity.getLayerDecayRate();
this.ddpFindUnusedParams = entity.getDdpFindUnusedParams(); this.ddpFindUnusedParams = entity.getDdpFindUnusedParams();
this.ignoreIndex = entity.getIgnoreIndex(); this.ignoreIndex = entity.getIgnoreIndex();
// Data // Data
this.trainNumWorkers = entity.getTrainNumWorkers(); this.trainNumWorkers = entity.getTrainNumWorkers();
this.valNumWorkers = entity.getValNumWorkers(); this.valNumWorkers = entity.getValNumWorkers();
this.testNumWorkers = entity.getTestNumWorkers(); this.testNumWorkers = entity.getTestNumWorkers();
this.trainShuffle = entity.getTrainShuffle(); this.trainShuffle = entity.getTrainShuffle();
this.trainPersistent = entity.getTrainPersistent(); this.trainPersistent = entity.getTrainPersistent();
this.valPersistent = entity.getValPersistent(); this.valPersistent = entity.getValPersistent();
// Evaluation // Evaluation
this.metrics = entity.getMetrics(); this.metrics = entity.getMetrics();
this.saveBest = entity.getSaveBest(); this.saveBest = entity.getSaveBest();
this.saveBestRule = entity.getSaveBestRule(); this.saveBestRule = entity.getSaveBestRule();
this.valInterval = entity.getValInterval(); this.valInterval = entity.getValInterval();
this.logInterval = entity.getLogInterval(); this.logInterval = entity.getLogInterval();
this.visInterval = entity.getVisInterval(); this.visInterval = entity.getVisInterval();
// Hardware // Hardware
this.gpuCnt = entity.getGpuCnt(); this.gpuCnt = entity.getGpuCnt();
this.gpuIds = entity.getGpuIds(); this.gpuIds = entity.getGpuIds();
this.masterPort = entity.getMasterPort(); this.masterPort = entity.getMasterPort();
// Augmentation // Augmentation
this.rotProb = entity.getRotProb(); this.rotProb = entity.getRotProb();
this.flipProb = entity.getFlipProb(); this.flipProb = entity.getFlipProb();
this.rotDegree = entity.getRotDegree(); this.rotDegree = entity.getRotDegree();
this.exchangeProb = entity.getExchangeProb(); this.exchangeProb = entity.getExchangeProb();
this.brightnessDelta = entity.getBrightnessDelta(); this.brightnessDelta = entity.getBrightnessDelta();
this.contrastRange = entity.getContrastRange(); this.contrastRange = entity.getContrastRange();
this.saturationRange = entity.getSaturationRange(); this.saturationRange = entity.getSaturationRange();
this.hueDelta = entity.getHueDelta(); this.hueDelta = entity.getHueDelta();
// Legacy // Legacy
this.dropoutRatio = entity.getDropoutRatio(); this.dropoutRatio = entity.getDropoutRatio();
this.cnnFilterCnt = entity.getCnnFilterCnt(); this.cnnFilterCnt = entity.getCnnFilterCnt();
// Common // Common
this.memo = entity.getMemo(); this.memo = entity.getMemo();
this.createdDttm = entity.getCreatedDttm(); this.createdDttm = entity.getCreatedDttm();
} }
} }
@Schema(name = "HyperParam AddReq", description = "하이퍼파라미터 등록 요청") @Schema(name = "HyperParam AddReq", description = "하이퍼파라미터 등록 요청")
@Getter @Getter
@Setter @Setter
@NoArgsConstructor @NoArgsConstructor
@AllArgsConstructor @AllArgsConstructor
public static class AddReq { public static class AddReq {
@NotBlank(message = "버전명은 필수입니다") @NotBlank(message = "버전명은 필수입니다")
private String hyperVer; private String hyperVer;
// Important // Important
private String backbone; private String backbone;
private String inputSize; private String inputSize;
private String cropSize; private String cropSize;
private Integer epochCnt; private Integer epochCnt;
private Integer batchSize; private Integer batchSize;
// Architecture // Architecture
private Double dropPathRate; private Double dropPathRate;
private Integer frozenStages; private Integer frozenStages;
private String neckPolicy; private String neckPolicy;
private String decoderChannels; private String decoderChannels;
private String classWeight; private String classWeight;
private Integer numLayers; private Integer numLayers;
// Optimization // Optimization
private Double learningRate; private Double learningRate;
private Double weightDecay; private Double weightDecay;
private Double layerDecayRate; private Double layerDecayRate;
private Boolean ddpFindUnusedParams; private Boolean ddpFindUnusedParams;
private Integer ignoreIndex; private Integer ignoreIndex;
// Data // Data
private Integer trainNumWorkers; private Integer trainNumWorkers;
private Integer valNumWorkers; private Integer valNumWorkers;
private Integer testNumWorkers; private Integer testNumWorkers;
private Boolean trainShuffle; private Boolean trainShuffle;
private Boolean trainPersistent; private Boolean trainPersistent;
private Boolean valPersistent; private Boolean valPersistent;
// Evaluation // Evaluation
private String metrics; private String metrics;
private String saveBest; private String saveBest;
private String saveBestRule; private String saveBestRule;
private Integer valInterval; private Integer valInterval;
private Integer logInterval; private Integer logInterval;
private Integer visInterval; private Integer visInterval;
// Hardware // Hardware
private Integer gpuCnt; private Integer gpuCnt;
private String gpuIds; private String gpuIds;
private Integer masterPort; private Integer masterPort;
// Augmentation // Augmentation
private Double rotProb; private Double rotProb;
private Double flipProb; private Double flipProb;
private String rotDegree; private String rotDegree;
private Double exchangeProb; private Double exchangeProb;
private Integer brightnessDelta; private Integer brightnessDelta;
private String contrastRange; private String contrastRange;
private String saturationRange; private String saturationRange;
private Integer hueDelta; private Integer hueDelta;
// Legacy (deprecated) // Legacy (deprecated)
private Double dropoutRatio; private Double dropoutRatio;
private Integer cnnFilterCnt; private Integer cnnFilterCnt;
// Common // Common
private String memo; private String memo;
} }
} }

File diff suppressed because it is too large Load Diff

View File

@@ -1,61 +1,61 @@
package com.kamco.cd.training.model.dto; package com.kamco.cd.training.model.dto;
import com.kamco.cd.training.common.utils.interfaces.JsonFormatDttm; import com.kamco.cd.training.common.utils.interfaces.JsonFormatDttm;
import io.swagger.v3.oas.annotations.media.Schema; import io.swagger.v3.oas.annotations.media.Schema;
import java.time.ZonedDateTime; import java.time.ZonedDateTime;
import lombok.Getter; import lombok.Getter;
public class ModelVerDto { public class ModelVerDto {
@Schema(name = "modelVer Basic", description = "모델버전 엔티티 기본 정보") @Schema(name = "modelVer Basic", description = "모델버전 엔티티 기본 정보")
@Getter @Getter
public static class Basic { public static class Basic {
private final Long id; private final Long id;
private final Long modelUid; private final Long modelUid;
private final String modelCate; private final String modelCate;
private final String modelVer; private final String modelVer;
private final String usedState; private final String usedState;
private final String modelState; private final String modelState;
private final Double qualityProb; private final Double qualityProb;
private final String deployState; private final String deployState;
private final String modelPath; private final String modelPath;
@JsonFormatDttm private final ZonedDateTime createdDttm; @JsonFormatDttm private final ZonedDateTime createdDttm;
private final Long createdUid; private final Long createdUid;
@JsonFormatDttm private final ZonedDateTime updatedDttm; @JsonFormatDttm private final ZonedDateTime updatedDttm;
private final Long updatedUid; private final Long updatedUid;
public Basic( public Basic(
Long id, Long id,
Long modelUid, Long modelUid,
String modelCate, String modelCate,
String modelVer, String modelVer,
String usedState, String usedState,
String modelState, String modelState,
Double qualityProb, Double qualityProb,
String deployState, String deployState,
String modelPath, String modelPath,
ZonedDateTime createdDttm, ZonedDateTime createdDttm,
Long createdUid, Long createdUid,
ZonedDateTime updatedDttm, ZonedDateTime updatedDttm,
Long updatedUid) { Long updatedUid) {
this.id = id; this.id = id;
this.modelUid = modelUid; this.modelUid = modelUid;
this.modelCate = modelCate; this.modelCate = modelCate;
this.modelVer = modelVer; this.modelVer = modelVer;
this.usedState = usedState; this.usedState = usedState;
this.modelState = modelState; this.modelState = modelState;
this.qualityProb = qualityProb; this.qualityProb = qualityProb;
this.deployState = deployState; this.deployState = deployState;
this.modelPath = modelPath; this.modelPath = modelPath;
this.createdDttm = createdDttm; this.createdDttm = createdDttm;
this.createdUid = createdUid; this.createdUid = createdUid;
this.updatedDttm = updatedDttm; this.updatedDttm = updatedDttm;
this.updatedUid = updatedUid; this.updatedUid = updatedUid;
} }
} }
} }

View File

@@ -1,50 +1,50 @@
package com.kamco.cd.training.model.service; package com.kamco.cd.training.model.service;
import com.kamco.cd.training.model.dto.ModelMngDto; 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.Basic;
import com.kamco.cd.training.model.dto.ModelMngDto.SearchReq; import com.kamco.cd.training.model.dto.ModelMngDto.SearchReq;
import com.kamco.cd.training.postgres.core.ModelMngCoreService; import com.kamco.cd.training.postgres.core.ModelMngCoreService;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.data.domain.Page; import org.springframework.data.domain.Page;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional; import org.springframework.transaction.annotation.Transactional;
@Service @Service
@RequiredArgsConstructor @RequiredArgsConstructor
@Transactional(readOnly = true) @Transactional(readOnly = true)
@Slf4j @Slf4j
public class ModelMngService { public class ModelMngService {
private final ModelMngCoreService modelMngCoreService; private final ModelMngCoreService modelMngCoreService;
/** /**
* 모델 목록 조회 * 모델 목록 조회
* *
* @param searchReq 검색 조건 * @param searchReq 검색 조건
* @return 페이징 처리된 모델 목록 * @return 페이징 처리된 모델 목록
*/ */
public Page<Basic> findByModels(SearchReq searchReq) { public Page<Basic> findByModels(SearchReq searchReq) {
return modelMngCoreService.findByModels(searchReq); return modelMngCoreService.findByModels(searchReq);
} }
/** /**
* 모델 상세 조회 * 모델 상세 조회
* *
* @param modelUid 모델 UID * @param modelUid 모델 UID
* @return 모델 상세 정보 * @return 모델 상세 정보
*/ */
public ModelMngDto.Detail getModelDetail(Long modelUid) { public ModelMngDto.Detail getModelDetail(Long modelUid) {
return modelMngCoreService.getModelDetail(modelUid); return modelMngCoreService.getModelDetail(modelUid);
} }
/** /**
* 모델 상세 조회 (UUID 기반) * 모델 상세 조회 (UUID 기반)
* *
* @param uuid 모델 UUID * @param uuid 모델 UUID
* @return 모델 상세 정보 * @return 모델 상세 정보
*/ */
public ModelMngDto.Detail getModelDetailByUuid(String uuid) { public ModelMngDto.Detail getModelDetailByUuid(String uuid) {
return modelMngCoreService.getModelDetailByUuid(uuid); return modelMngCoreService.getModelDetailByUuid(uuid);
} }
} }

View File

@@ -1,393 +1,393 @@
package com.kamco.cd.training.model.service; package com.kamco.cd.training.model.service;
import com.kamco.cd.training.common.exception.BadRequestException; import com.kamco.cd.training.common.exception.BadRequestException;
import com.kamco.cd.training.common.exception.NotFoundException; import com.kamco.cd.training.common.exception.NotFoundException;
import com.kamco.cd.training.model.dto.ModelMngDto; import com.kamco.cd.training.model.dto.ModelMngDto;
import com.kamco.cd.training.postgres.core.DatasetCoreService; import com.kamco.cd.training.postgres.core.DatasetCoreService;
import com.kamco.cd.training.postgres.core.HyperParamCoreService; import com.kamco.cd.training.postgres.core.HyperParamCoreService;
import com.kamco.cd.training.postgres.core.ModelMngCoreService; import com.kamco.cd.training.postgres.core.ModelMngCoreService;
import com.kamco.cd.training.postgres.core.SystemMetricsCoreService; import com.kamco.cd.training.postgres.core.SystemMetricsCoreService;
import com.kamco.cd.training.postgres.entity.ModelTrainMasterEntity; import com.kamco.cd.training.postgres.entity.ModelTrainMasterEntity;
import java.util.List; import java.util.List;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional; import org.springframework.transaction.annotation.Transactional;
@Service @Service
@RequiredArgsConstructor @RequiredArgsConstructor
@Transactional(readOnly = true) @Transactional(readOnly = true)
@Slf4j @Slf4j
public class ModelTrainService { public class ModelTrainService {
private final ModelMngCoreService modelMngCoreService; private final ModelMngCoreService modelMngCoreService;
private final HyperParamCoreService hyperParamCoreService; private final HyperParamCoreService hyperParamCoreService;
private final DatasetCoreService datasetCoreService; private final DatasetCoreService datasetCoreService;
private final SystemMetricsCoreService systemMetricsCoreService; private final SystemMetricsCoreService systemMetricsCoreService;
/** /**
* 학습 모델 목록 조회 * 학습 모델 목록 조회
* *
* @return 학습 모델 목록 * @return 학습 모델 목록
*/ */
public List<ModelMngDto.TrainListRes> getTrainModelList() { public List<ModelMngDto.TrainListRes> getTrainModelList() {
return modelMngCoreService.findAllTrainModels(); return modelMngCoreService.findAllTrainModels();
} }
/** /**
* 학습 설정 통합 조회 * 학습 설정 통합 조회
* *
* @return 학습 설정 폼 데이터 * @return 학습 설정 폼 데이터
*/ */
public ModelMngDto.FormConfigRes getFormConfig() { public ModelMngDto.FormConfigRes getFormConfig() {
// 1. 현재 실행 중인 모델 확인 // 1. 현재 실행 중인 모델 확인
String runningModelUuid = modelMngCoreService.findRunningModelUuid(); String runningModelUuid = modelMngCoreService.findRunningModelUuid();
boolean isTrainAvailable = (runningModelUuid == null); boolean isTrainAvailable = (runningModelUuid == null);
// 2. 저장공간 체크 (10GB 미만 시 학습 불가) // 2. 저장공간 체크 (10GB 미만 시 학습 불가)
if (isTrainAvailable) { if (isTrainAvailable) {
isTrainAvailable = systemMetricsCoreService.isStorageAvailableForTraining(); isTrainAvailable = systemMetricsCoreService.isStorageAvailableForTraining();
long availableMB = systemMetricsCoreService.getAvailableStorageMB(); long availableMB = systemMetricsCoreService.getAvailableStorageMB();
log.info("저장공간 체크 완료: {}MB 사용 가능, 학습 가능 여부: {}", availableMB, isTrainAvailable); log.info("저장공간 체크 완료: {}MB 사용 가능, 학습 가능 여부: {}", availableMB, isTrainAvailable);
} }
// 3. 하이퍼파라미터 목록 // 3. 하이퍼파라미터 목록
List<ModelMngDto.HyperParamInfo> hyperParams = hyperParamCoreService.findAllActiveHyperParams(); List<ModelMngDto.HyperParamInfo> hyperParams = hyperParamCoreService.findAllActiveHyperParams();
// 4. 데이터셋 목록 // 4. 데이터셋 목록
List<ModelMngDto.DatasetInfo> datasets = datasetCoreService.findAllActiveDatasetsForTraining(); List<ModelMngDto.DatasetInfo> datasets = datasetCoreService.findAllActiveDatasetsForTraining();
return ModelMngDto.FormConfigRes.builder() return ModelMngDto.FormConfigRes.builder()
.isTrainAvailable(isTrainAvailable) .isTrainAvailable(isTrainAvailable)
.hyperParams(hyperParams) .hyperParams(hyperParams)
.datasets(datasets) .datasets(datasets)
.runningModelUuid(runningModelUuid) .runningModelUuid(runningModelUuid)
.build(); .build();
} }
/** /**
* 하이퍼파라미터 등록 * 하이퍼파라미터 등록
* *
* @param createReq 등록 요청 * @param createReq 등록 요청
* @return 생성된 버전명 * @return 생성된 버전명
*/ */
@Transactional @Transactional
public String createHyperParam(ModelMngDto.HyperParamCreateReq createReq) { public String createHyperParam(ModelMngDto.HyperParamCreateReq createReq) {
// 신규 버전 추가 시 baseHyperVer가 없으면 H1으로 설정 // 신규 버전 추가 시 baseHyperVer가 없으면 H1으로 설정
if (createReq.getBaseHyperVer() == null || createReq.getBaseHyperVer().isEmpty()) { if (createReq.getBaseHyperVer() == null || createReq.getBaseHyperVer().isEmpty()) {
String firstVersion = hyperParamCoreService.getFirstHyperParamVersion(); String firstVersion = hyperParamCoreService.getFirstHyperParamVersion();
createReq.setBaseHyperVer(firstVersion); createReq.setBaseHyperVer(firstVersion);
log.info("baseHyperVer가 없어 첫 번째 버전으로 설정: {}", firstVersion); log.info("baseHyperVer가 없어 첫 번째 버전으로 설정: {}", firstVersion);
} }
String newVersion = hyperParamCoreService.createHyperParam(createReq); String newVersion = hyperParamCoreService.createHyperParam(createReq);
log.info("하이퍼파라미터 등록 완료: {}", newVersion); log.info("하이퍼파라미터 등록 완료: {}", newVersion);
return newVersion; return newVersion;
} }
/** /**
* 하이퍼파라미터 단건 조회 * 하이퍼파라미터 단건 조회
* *
* @param hyperVer 하이퍼파라미터 버전 * @param hyperVer 하이퍼파라미터 버전
* @return 하이퍼파라미터 정보 * @return 하이퍼파라미터 정보
*/ */
public ModelMngDto.HyperParamInfo getHyperParam(String hyperVer) { public ModelMngDto.HyperParamInfo getHyperParam(String hyperVer) {
return hyperParamCoreService.findByHyperVer(hyperVer); return hyperParamCoreService.findByHyperVer(hyperVer);
} }
/** /**
* 하이퍼파라미터 삭제 * 하이퍼파라미터 삭제
* *
* @param hyperVer 하이퍼파라미터 버전 * @param hyperVer 하이퍼파라미터 버전
*/ */
@Transactional @Transactional
public void deleteHyperParam(String hyperVer) { public void deleteHyperParam(String hyperVer) {
hyperParamCoreService.deleteHyperParam(hyperVer); hyperParamCoreService.deleteHyperParam(hyperVer);
log.info("하이퍼파라미터 삭제 완료: {}", hyperVer); log.info("하이퍼파라미터 삭제 완료: {}", hyperVer);
} }
/** /**
* 학습 시작 * 학습 시작
* *
* @param trainReq 학습 시작 요청 * @param trainReq 학습 시작 요청
* @return 학습 시작 응답 * @return 학습 시작 응답
*/ */
@Transactional @Transactional
public ModelMngDto.TrainStartRes startTraining(ModelMngDto.TrainStartReq trainReq) { public ModelMngDto.TrainStartRes startTraining(ModelMngDto.TrainStartReq trainReq) {
// 1. 동시 실행 방지 검증 // 1. 동시 실행 방지 검증
String runningModelUuid = modelMngCoreService.findRunningModelUuid(); String runningModelUuid = modelMngCoreService.findRunningModelUuid();
if (runningModelUuid != null) { if (runningModelUuid != null) {
throw new BadRequestException( throw new BadRequestException(
"이미 실행 중인 학습이 있습니다. 학습은 한 번에 한 개만 실행할 수 있습니다. (실행 중인 모델: " + runningModelUuid + ")"); "이미 실행 중인 학습이 있습니다. 학습은 한 번에 한 개만 실행할 수 있습니다. (실행 중인 모델: " + runningModelUuid + ")");
} }
// 2. 저장공간 체크 (10GB 미만 시 학습 불가) // 2. 저장공간 체크 (10GB 미만 시 학습 불가)
if (!systemMetricsCoreService.isStorageAvailableForTraining()) { if (!systemMetricsCoreService.isStorageAvailableForTraining()) {
long availableMB = systemMetricsCoreService.getAvailableStorageMB(); long availableMB = systemMetricsCoreService.getAvailableStorageMB();
long requiredMB = 10 * 1024; // 10GB long requiredMB = 10 * 1024; // 10GB
throw new BadRequestException( throw new BadRequestException(
String.format( String.format(
"저장공간이 부족하여 학습을 시작할 수 없습니다. (필요: %dMB, 사용 가능: %dMB)", requiredMB, availableMB)); "저장공간이 부족하여 학습을 시작할 수 없습니다. (필요: %dMB, 사용 가능: %dMB)", requiredMB, availableMB));
} }
// 3. 데이터셋 상태 검증 (COMPLETED 상태만 학습 가능) // 3. 데이터셋 상태 검증 (COMPLETED 상태만 학습 가능)
validateDatasetStatus(trainReq.getDatasetIds()); validateDatasetStatus(trainReq.getDatasetIds());
// 4. 데이터 분할 비율 검증 (예: "7:2:1" 형식) // 4. 데이터 분할 비율 검증 (예: "7:2:1" 형식)
if (trainReq.getDatasetRatio() != null && !trainReq.getDatasetRatio().isEmpty()) { if (trainReq.getDatasetRatio() != null && !trainReq.getDatasetRatio().isEmpty()) {
validateDatasetRatio(trainReq.getDatasetRatio()); validateDatasetRatio(trainReq.getDatasetRatio());
} }
// 5. 학습 마스터 생성 // 5. 학습 마스터 생성
ModelTrainMasterEntity entity = modelMngCoreService.createTrainMaster(trainReq); ModelTrainMasterEntity entity = modelMngCoreService.createTrainMaster(trainReq);
// 5. 데이터셋 매핑 생성 // 5. 데이터셋 매핑 생성
modelMngCoreService.createDatasetMappings(entity.getId(), trainReq.getDatasetIds()); modelMngCoreService.createDatasetMappings(entity.getId(), trainReq.getDatasetIds());
// 6. 실제 UUID 사용 // 6. 실제 UUID 사용
String uuid = entity.getUuid().toString(); String uuid = entity.getUuid().toString();
log.info( log.info(
"학습 시작: uuid={}, hyperVer={}, epoch={}, datasets={}", "학습 시작: uuid={}, hyperVer={}, epoch={}, datasets={}",
uuid, uuid,
trainReq.getHyperVer(), trainReq.getHyperVer(),
trainReq.getEpoch(), trainReq.getEpoch(),
trainReq.getDatasetIds()); trainReq.getDatasetIds());
// TODO: 비동기 GPU 학습 프로세스 트리거 로직 추가 // TODO: 비동기 GPU 학습 프로세스 트리거 로직 추가
return ModelMngDto.TrainStartRes.builder().uuid(uuid).status(entity.getStatusCd()).build(); return ModelMngDto.TrainStartRes.builder().uuid(uuid).status(entity.getStatusCd()).build();
} }
/** /**
* 데이터셋 상태 검증 * 데이터셋 상태 검증
* *
* @param datasetIds 데이터셋 ID 목록 * @param datasetIds 데이터셋 ID 목록
*/ */
private void validateDatasetStatus(List<Long> datasetIds) { private void validateDatasetStatus(List<Long> datasetIds) {
for (Long datasetId : datasetIds) { for (Long datasetId : datasetIds) {
try { try {
var dataset = datasetCoreService.getOneById(datasetId); var dataset = datasetCoreService.getOneById(datasetId);
// COMPLETED 상태가 아닌 데이터셋이 포함되어 있으면 예외 발생 // COMPLETED 상태가 아닌 데이터셋이 포함되어 있으면 예외 발생
if (dataset.getStatus() == null || !"COMPLETED".equals(dataset.getStatus())) { if (dataset.getStatus() == null || !"COMPLETED".equals(dataset.getStatus())) {
throw new BadRequestException( throw new BadRequestException(
String.format( String.format(
"학습에 사용할 수 없는 데이터셋입니다. (ID: %d, 상태: %s). COMPLETED 상태의 데이터셋만 선택 가능합니다.", "학습에 사용할 수 없는 데이터셋입니다. (ID: %d, 상태: %s). COMPLETED 상태의 데이터셋만 선택 가능합니다.",
datasetId, dataset.getStatus() != null ? dataset.getStatus() : "NULL")); datasetId, dataset.getStatus() != null ? dataset.getStatus() : "NULL"));
} }
log.debug("데이터셋 상태 검증 통과: ID={}, Status={}", datasetId, dataset.getStatus()); log.debug("데이터셋 상태 검증 통과: ID={}, Status={}", datasetId, dataset.getStatus());
} catch (NotFoundException e) { } catch (NotFoundException e) {
throw new BadRequestException("존재하지 않는 데이터셋입니다. ID: " + datasetId); throw new BadRequestException("존재하지 않는 데이터셋입니다. ID: " + datasetId);
} }
} }
log.info("모든 데이터셋 상태 검증 완료: {} 개", datasetIds.size()); log.info("모든 데이터셋 상태 검증 완료: {} 개", datasetIds.size());
} }
/** /**
* 데이터 분할 비율 검증 * 데이터 분할 비율 검증
* *
* @param datasetRatio 데이터셋 비율 (예: "7:2:1") * @param datasetRatio 데이터셋 비율 (예: "7:2:1")
*/ */
private void validateDatasetRatio(String datasetRatio) { private void validateDatasetRatio(String datasetRatio) {
try { try {
String[] parts = datasetRatio.split(":"); String[] parts = datasetRatio.split(":");
if (parts.length != 3) { if (parts.length != 3) {
throw new BadRequestException("데이터 분할 비율은 'Training:Validation:Test' 형식이어야 합니다 (예: 7:2:1)"); throw new BadRequestException("데이터 분할 비율은 'Training:Validation:Test' 형식이어야 합니다 (예: 7:2:1)");
} }
int train = Integer.parseInt(parts[0].trim()); int train = Integer.parseInt(parts[0].trim());
int validation = Integer.parseInt(parts[1].trim()); int validation = Integer.parseInt(parts[1].trim());
int test = Integer.parseInt(parts[2].trim()); int test = Integer.parseInt(parts[2].trim());
int sum = train + validation + test; int sum = train + validation + test;
if (sum != 10) { if (sum != 10) {
throw new BadRequestException( throw new BadRequestException(
String.format("데이터 분할 비율의 합계는 10이어야 합니다. (현재 합계: %d, 입력값: %s)", sum, datasetRatio)); String.format("데이터 분할 비율의 합계는 10이어야 합니다. (현재 합계: %d, 입력값: %s)", sum, datasetRatio));
} }
if (train <= 0 || validation < 0 || test < 0) { if (train <= 0 || validation < 0 || test < 0) {
throw new BadRequestException("데이터 분할 비율은 모두 0 이상이어야 합니다 (Training은 1 이상)"); throw new BadRequestException("데이터 분할 비율은 모두 0 이상이어야 합니다 (Training은 1 이상)");
} }
log.info( log.info(
"데이터 분할 비율 검증 완료: Training={}0%, Validation={}0%, Test={}0%", train, validation, test); "데이터 분할 비율 검증 완료: Training={}0%, Validation={}0%, Test={}0%", train, validation, test);
} catch (NumberFormatException e) { } catch (NumberFormatException e) {
throw new BadRequestException("데이터 분할 비율은 숫자로만 구성되어야 합니다: " + datasetRatio); throw new BadRequestException("데이터 분할 비율은 숫자로만 구성되어야 합니다: " + datasetRatio);
} }
} }
/** /**
* 학습 모델 삭제 * 학습 모델 삭제
* *
* @param uuid 모델 UUID * @param uuid 모델 UUID
*/ */
@Transactional @Transactional
public void deleteTrainModel(String uuid) { public void deleteTrainModel(String uuid) {
modelMngCoreService.deleteByUuid(uuid); modelMngCoreService.deleteByUuid(uuid);
log.info("학습 모델 삭제 완료: uuid={}", uuid); log.info("학습 모델 삭제 완료: uuid={}", uuid);
} }
// ==================== Resume Training (학습 재시작) ==================== // ==================== Resume Training (학습 재시작) ====================
/** /**
* 학습 재시작 정보 조회 * 학습 재시작 정보 조회
* *
* @param uuid 모델 UUID * @param uuid 모델 UUID
* @return 재시작 정보 * @return 재시작 정보
*/ */
public ModelMngDto.ResumeInfo getResumeInfo(String uuid) { public ModelMngDto.ResumeInfo getResumeInfo(String uuid) {
ModelTrainMasterEntity entity = modelMngCoreService.findByUuid(uuid); ModelTrainMasterEntity entity = modelMngCoreService.findByUuid(uuid);
return ModelMngDto.ResumeInfo.builder() return ModelMngDto.ResumeInfo.builder()
.canResume(entity.getCanResume() != null && entity.getCanResume()) .canResume(entity.getCanResume() != null && entity.getCanResume())
.lastEpoch(entity.getLastCheckpointEpoch()) .lastEpoch(entity.getLastCheckpointEpoch())
.totalEpoch(entity.getEpochCnt()) .totalEpoch(entity.getEpochCnt())
.checkpointPath(entity.getCheckpointPath()) .checkpointPath(entity.getCheckpointPath())
.failedAt( .failedAt(
entity.getStopDttm() != null entity.getStopDttm() != null
? entity.getStopDttm().atZone(java.time.ZoneId.systemDefault()) ? entity.getStopDttm().atZone(java.time.ZoneId.systemDefault())
: null) : null)
.build(); .build();
} }
/** /**
* 학습 재시작 * 학습 재시작
* *
* @param uuid 모델 UUID * @param uuid 모델 UUID
* @param resumeReq 재시작 요청 * @param resumeReq 재시작 요청
* @return 재시작 응답 * @return 재시작 응답
*/ */
@Transactional @Transactional
public ModelMngDto.ResumeResponse resumeTraining( public ModelMngDto.ResumeResponse resumeTraining(
String uuid, ModelMngDto.ResumeRequest resumeReq) { String uuid, ModelMngDto.ResumeRequest resumeReq) {
ModelTrainMasterEntity entity = modelMngCoreService.findByUuid(uuid); ModelTrainMasterEntity entity = modelMngCoreService.findByUuid(uuid);
// 재시작 가능 여부 검증 // 재시작 가능 여부 검증
if (entity.getCanResume() == null || !entity.getCanResume()) { if (entity.getCanResume() == null || !entity.getCanResume()) {
throw new IllegalStateException("학습 재시작이 불가능한 모델입니다: " + uuid); throw new IllegalStateException("학습 재시작이 불가능한 모델입니다: " + uuid);
} }
if (entity.getLastCheckpointEpoch() == null) { if (entity.getLastCheckpointEpoch() == null) {
throw new IllegalStateException("Checkpoint가 존재하지 않습니다: " + uuid); throw new IllegalStateException("Checkpoint가 존재하지 않습니다: " + uuid);
} }
// 상태 업데이트 // 상태 업데이트
entity.setStatusCd("RUNNING"); entity.setStatusCd("RUNNING");
entity.setProgressRate(0); entity.setProgressRate(0);
// 총 Epoch 수 변경 (선택사항) // 총 Epoch 수 변경 (선택사항)
if (resumeReq.getNewTotalEpoch() != null) { if (resumeReq.getNewTotalEpoch() != null) {
entity.setEpochCnt(resumeReq.getNewTotalEpoch()); entity.setEpochCnt(resumeReq.getNewTotalEpoch());
} }
log.info( log.info(
"학습 재시작: uuid={}, resumeFromEpoch={}, totalEpoch={}", "학습 재시작: uuid={}, resumeFromEpoch={}, totalEpoch={}",
uuid, uuid,
resumeReq.getResumeFromEpoch(), resumeReq.getResumeFromEpoch(),
entity.getEpochCnt()); entity.getEpochCnt());
// TODO: 비동기 GPU 학습 재시작 프로세스 트리거 로직 추가 // TODO: 비동기 GPU 학습 재시작 프로세스 트리거 로직 추가
// - Checkpoint 파일 로드 // - Checkpoint 파일 로드
// - 지정된 Epoch부터 학습 재개 // - 지정된 Epoch부터 학습 재개
return ModelMngDto.ResumeResponse.builder() return ModelMngDto.ResumeResponse.builder()
.uuid(uuid) .uuid(uuid)
.status(entity.getStatusCd()) .status(entity.getStatusCd())
.resumedFromEpoch(resumeReq.getResumeFromEpoch()) .resumedFromEpoch(resumeReq.getResumeFromEpoch())
.build(); .build();
} }
// ==================== Best Epoch Setting (Best Epoch 설정) ==================== // ==================== Best Epoch Setting (Best Epoch 설정) ====================
/** /**
* Best Epoch 설정 * Best Epoch 설정
* *
* @param uuid 모델 UUID * @param uuid 모델 UUID
* @param bestEpochReq Best Epoch 요청 * @param bestEpochReq Best Epoch 요청
* @return Best Epoch 응답 * @return Best Epoch 응답
*/ */
@Transactional @Transactional
public ModelMngDto.BestEpochResponse setBestEpoch( public ModelMngDto.BestEpochResponse setBestEpoch(
String uuid, ModelMngDto.BestEpochRequest bestEpochReq) { String uuid, ModelMngDto.BestEpochRequest bestEpochReq) {
ModelTrainMasterEntity entity = modelMngCoreService.findByUuid(uuid); ModelTrainMasterEntity entity = modelMngCoreService.findByUuid(uuid);
// 1차 학습 완료 상태 검증 // 1차 학습 완료 상태 검증
if (!"STEP1_COMPLETED".equals(entity.getStatusCd()) if (!"STEP1_COMPLETED".equals(entity.getStatusCd())
&& !"STEP1".equals(entity.getProcessStep())) { && !"STEP1".equals(entity.getProcessStep())) {
log.warn( log.warn(
"Best Epoch 설정 시도: 현재 상태={}, processStep={}", "Best Epoch 설정 시도: 현재 상태={}, processStep={}",
entity.getStatusCd(), entity.getStatusCd(),
entity.getProcessStep()); entity.getProcessStep());
} }
Integer previousBestEpoch = entity.getConfirmedBestEpoch(); Integer previousBestEpoch = entity.getConfirmedBestEpoch();
// 사용자가 확정한 Best Epoch 설정 // 사용자가 확정한 Best Epoch 설정
entity.setConfirmedBestEpoch(bestEpochReq.getBestEpoch()); entity.setConfirmedBestEpoch(bestEpochReq.getBestEpoch());
// 2차 학습(Test) 단계로 상태 전이 // 2차 학습(Test) 단계로 상태 전이
entity.setProcessStep("STEP2"); entity.setProcessStep("STEP2");
entity.setStatusCd("STEP2_RUNNING"); entity.setStatusCd("STEP2_RUNNING");
entity.setProgressRate(0); entity.setProgressRate(0);
entity.setUpdatedDttm(java.time.ZonedDateTime.now()); entity.setUpdatedDttm(java.time.ZonedDateTime.now());
log.info( log.info(
"Best Epoch 설정 및 2차 학습 시작: uuid={}, newBestEpoch={}, previousBestEpoch={}, reason={}, newStatus={}", "Best Epoch 설정 및 2차 학습 시작: uuid={}, newBestEpoch={}, previousBestEpoch={}, reason={}, newStatus={}",
uuid, uuid,
bestEpochReq.getBestEpoch(), bestEpochReq.getBestEpoch(),
previousBestEpoch, previousBestEpoch,
bestEpochReq.getReason(), bestEpochReq.getReason(),
entity.getStatusCd()); entity.getStatusCd());
// TODO: 비동기 GPU 2차 학습(Test) 프로세스 트리거 로직 추가 // TODO: 비동기 GPU 2차 학습(Test) 프로세스 트리거 로직 추가
// - Best Epoch 모델 로드 // - Best Epoch 모델 로드
// - Test 데이터셋으로 성능 평가 실행 // - Test 데이터셋으로 성능 평가 실행
// - 완료 시 STEP2_COMPLETED 상태로 전환 // - 완료 시 STEP2_COMPLETED 상태로 전환
return ModelMngDto.BestEpochResponse.builder() return ModelMngDto.BestEpochResponse.builder()
.uuid(uuid) .uuid(uuid)
.bestEpoch(entity.getBestEpoch()) // 자동 선택된 값 .bestEpoch(entity.getBestEpoch()) // 자동 선택된 값
.confirmedBestEpoch(entity.getConfirmedBestEpoch()) // 사용자 확정 값 .confirmedBestEpoch(entity.getConfirmedBestEpoch()) // 사용자 확정 값
.previousBestEpoch(previousBestEpoch) .previousBestEpoch(previousBestEpoch)
.build(); .build();
} }
/** /**
* Epoch별 성능 지표 조회 * Epoch별 성능 지표 조회
* *
* @param uuid 모델 UUID * @param uuid 모델 UUID
* @return Epoch별 성능 지표 목록 * @return Epoch별 성능 지표 목록
*/ */
public List<ModelMngDto.EpochMetric> getEpochMetrics(String uuid) { public List<ModelMngDto.EpochMetric> getEpochMetrics(String uuid) {
ModelTrainMasterEntity entity = modelMngCoreService.findByUuid(uuid); ModelTrainMasterEntity entity = modelMngCoreService.findByUuid(uuid);
// TODO: 실제 학습 로그 파일이나 DB에서 Epoch별 성능 지표 조회 // TODO: 실제 학습 로그 파일이나 DB에서 Epoch별 성능 지표 조회
// 현재는 샘플 데이터 반환 // 현재는 샘플 데이터 반환
List<ModelMngDto.EpochMetric> metrics = new java.util.ArrayList<>(); List<ModelMngDto.EpochMetric> metrics = new java.util.ArrayList<>();
if (entity.getEpochCnt() != null && entity.getBestEpoch() != null) { if (entity.getEpochCnt() != null && entity.getBestEpoch() != null) {
// 샘플 데이터 생성 (실제로는 학습 로그 파일 파싱 또는 별도 테이블 조회) // 샘플 데이터 생성 (실제로는 학습 로그 파일 파싱 또는 별도 테이블 조회)
for (int i = 1; i <= Math.min(entity.getEpochCnt(), 10); i++) { for (int i = 1; i <= Math.min(entity.getEpochCnt(), 10); i++) {
int epoch = entity.getBestEpoch() - 5 + i; int epoch = entity.getBestEpoch() - 5 + i;
if (epoch <= 0 || epoch > entity.getEpochCnt()) { if (epoch <= 0 || epoch > entity.getEpochCnt()) {
continue; continue;
} }
metrics.add( metrics.add(
ModelMngDto.EpochMetric.builder() ModelMngDto.EpochMetric.builder()
.epoch(epoch) .epoch(epoch)
.mIoU(0.80 + (Math.random() * 0.15)) // 샘플 데이터 .mIoU(0.80 + (Math.random() * 0.15)) // 샘플 데이터
.mFscore(0.85 + (Math.random() * 0.10)) // 샘플 데이터 .mFscore(0.85 + (Math.random() * 0.10)) // 샘플 데이터
.loss(0.3 - (Math.random() * 0.15)) // 샘플 데이터 .loss(0.3 - (Math.random() * 0.15)) // 샘플 데이터
.isBest(entity.getBestEpoch() != null && epoch == entity.getBestEpoch()) .isBest(entity.getBestEpoch() != null && epoch == entity.getBestEpoch())
.build()); .build());
} }
} }
log.info("Epoch별 성능 지표 조회: uuid={}, metricsCount={}", uuid, metrics.size()); log.info("Epoch별 성능 지표 조회: uuid={}, metricsCount={}", uuid, metrics.size());
return metrics; return metrics;
} }
} }

View File

@@ -1,22 +1,22 @@
package com.kamco.cd.training.postgres; package com.kamco.cd.training.postgres;
import jakarta.persistence.Column; import jakarta.persistence.Column;
import jakarta.persistence.MappedSuperclass; import jakarta.persistence.MappedSuperclass;
import jakarta.persistence.PrePersist; import jakarta.persistence.PrePersist;
import java.time.ZonedDateTime; import java.time.ZonedDateTime;
import lombok.Getter; import lombok.Getter;
import org.springframework.data.annotation.CreatedDate; import org.springframework.data.annotation.CreatedDate;
@Getter @Getter
@MappedSuperclass @MappedSuperclass
public class CommonCreateEntity { public class CommonCreateEntity {
@CreatedDate @CreatedDate
@Column(name = "created_dttm", updatable = false, nullable = false) @Column(name = "created_dttm", updatable = false, nullable = false)
private ZonedDateTime createdDate; private ZonedDateTime createdDate;
@PrePersist @PrePersist
protected void onPersist() { protected void onPersist() {
this.createdDate = ZonedDateTime.now(); this.createdDate = ZonedDateTime.now();
} }
} }

View File

@@ -1,34 +1,34 @@
package com.kamco.cd.training.postgres; package com.kamco.cd.training.postgres;
import jakarta.persistence.Column; import jakarta.persistence.Column;
import jakarta.persistence.MappedSuperclass; import jakarta.persistence.MappedSuperclass;
import jakarta.persistence.PrePersist; import jakarta.persistence.PrePersist;
import jakarta.persistence.PreUpdate; import jakarta.persistence.PreUpdate;
import java.time.ZonedDateTime; import java.time.ZonedDateTime;
import lombok.Getter; import lombok.Getter;
import org.springframework.data.annotation.CreatedDate; import org.springframework.data.annotation.CreatedDate;
import org.springframework.data.annotation.LastModifiedDate; import org.springframework.data.annotation.LastModifiedDate;
@Getter @Getter
@MappedSuperclass @MappedSuperclass
public class CommonDateEntity { public class CommonDateEntity {
@CreatedDate @CreatedDate
@Column(name = "created_dttm", updatable = false, nullable = false) @Column(name = "created_dttm", updatable = false, nullable = false)
private ZonedDateTime createdDate; private ZonedDateTime createdDate;
@LastModifiedDate @LastModifiedDate
@Column(name = "updated_dttm", nullable = false) @Column(name = "updated_dttm", nullable = false)
private ZonedDateTime modifiedDate; private ZonedDateTime modifiedDate;
@PrePersist @PrePersist
protected void onPersist() { protected void onPersist() {
this.createdDate = ZonedDateTime.now(); this.createdDate = ZonedDateTime.now();
this.modifiedDate = ZonedDateTime.now(); this.modifiedDate = ZonedDateTime.now();
} }
@PreUpdate @PreUpdate
protected void onUpdate() { protected void onUpdate() {
this.modifiedDate = ZonedDateTime.now(); this.modifiedDate = ZonedDateTime.now();
} }
} }

View File

@@ -1,19 +1,19 @@
package com.kamco.cd.training.postgres; package com.kamco.cd.training.postgres;
import com.querydsl.jpa.impl.JPAQueryFactory; import com.querydsl.jpa.impl.JPAQueryFactory;
import jakarta.persistence.EntityManager; import jakarta.persistence.EntityManager;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
@RequiredArgsConstructor @RequiredArgsConstructor
@Configuration @Configuration
public class QueryDslConfig { public class QueryDslConfig {
private final EntityManager entityManager; private final EntityManager entityManager;
@Bean @Bean
public JPAQueryFactory jpaQueryFactory() { public JPAQueryFactory jpaQueryFactory() {
return new JPAQueryFactory(entityManager); return new JPAQueryFactory(entityManager);
} }
} }

View File

@@ -1,30 +1,30 @@
package com.kamco.cd.training.postgres; package com.kamco.cd.training.postgres;
import com.querydsl.core.types.Order; import com.querydsl.core.types.Order;
import com.querydsl.core.types.OrderSpecifier; import com.querydsl.core.types.OrderSpecifier;
import com.querydsl.core.types.dsl.PathBuilder; import com.querydsl.core.types.dsl.PathBuilder;
import org.springframework.data.domain.Pageable; import org.springframework.data.domain.Pageable;
public class QuerydslOrderUtil { public class QuerydslOrderUtil {
/** /**
* Pageable의 Sort 정보를 QueryDSL OrderSpecifier 배열로 변환 * Pageable의 Sort 정보를 QueryDSL OrderSpecifier 배열로 변환
* *
* @param pageable Spring Pageable * @param pageable Spring Pageable
* @param entityClass 엔티티 클래스 (예: User.class) * @param entityClass 엔티티 클래스 (예: User.class)
* @param alias Q 엔티티 alias (예: "user") * @param alias Q 엔티티 alias (예: "user")
*/ */
public static <T> OrderSpecifier<?>[] getOrderSpecifiers( public static <T> OrderSpecifier<?>[] getOrderSpecifiers(
Pageable pageable, Class<T> entityClass, String alias) { Pageable pageable, Class<T> entityClass, String alias) {
PathBuilder<T> entityPath = new PathBuilder<>(entityClass, alias); PathBuilder<T> entityPath = new PathBuilder<>(entityClass, alias);
return pageable.getSort().stream() return pageable.getSort().stream()
.map( .map(
sort -> { sort -> {
Order order = sort.isAscending() ? Order.ASC : Order.DESC; Order order = sort.isAscending() ? Order.ASC : Order.DESC;
// PathBuilder.get()는 컬럼명(String)을 동적 Path로 반환 // PathBuilder.get()는 컬럼명(String)을 동적 Path로 반환
return new OrderSpecifier<>( return new OrderSpecifier<>(
order, entityPath.get(sort.getProperty(), Comparable.class)); order, entityPath.get(sort.getProperty(), Comparable.class));
}) })
.toArray(OrderSpecifier[]::new); .toArray(OrderSpecifier[]::new);
} }
} }

View File

@@ -1,77 +1,77 @@
// package com.kamco.cd.kamcoback.postgres.core; // package com.kamco.cd.kamcoback.postgres.core;
// //
// import com.kamco.cd.kamcoback.common.service.BaseCoreService; // import com.kamco.cd.kamcoback.common.service.BaseCoreService;
// import com.kamco.cd.kamcoback.postgres.entity.AnimalEntity; // import com.kamco.cd.kamcoback.postgres.entity.AnimalEntity;
// import com.kamco.cd.kamcoback.postgres.entity.ZooEntity; // import com.kamco.cd.kamcoback.postgres.entity.ZooEntity;
// import com.kamco.cd.kamcoback.postgres.repository.AnimalRepository; // import com.kamco.cd.kamcoback.postgres.repository.AnimalRepository;
// import com.kamco.cd.kamcoback.postgres.repository.ZooRepository; // import com.kamco.cd.kamcoback.postgres.repository.ZooRepository;
// import com.kamco.cd.kamcoback.zoo.dto.AnimalDto; // import com.kamco.cd.kamcoback.zoo.dto.AnimalDto;
// import jakarta.persistence.EntityNotFoundException; // import jakarta.persistence.EntityNotFoundException;
// import lombok.RequiredArgsConstructor; // import lombok.RequiredArgsConstructor;
// import org.springframework.data.domain.Page; // import org.springframework.data.domain.Page;
// import org.springframework.stereotype.Service; // import org.springframework.stereotype.Service;
// import org.springframework.transaction.annotation.Transactional; // import org.springframework.transaction.annotation.Transactional;
// //
// @Service // @Service
// @RequiredArgsConstructor // @RequiredArgsConstructor
// @Transactional(readOnly = true) // @Transactional(readOnly = true)
// public class AnimalCoreService // public class AnimalCoreService
// implements BaseCoreService<AnimalDto.Basic, Long, AnimalDto.SearchReq> { // implements BaseCoreService<AnimalDto.Basic, Long, AnimalDto.SearchReq> {
// //
// private final AnimalRepository animalRepository; // private final AnimalRepository animalRepository;
// private final ZooRepository zooRepository; // private final ZooRepository zooRepository;
// //
// @Transactional(readOnly = true) // @Transactional(readOnly = true)
// public AnimalDto.Basic getDataByUuid(String uuid) { // public AnimalDto.Basic getDataByUuid(String uuid) {
// AnimalEntity getZoo = // AnimalEntity getZoo =
// animalRepository // animalRepository
// .getAnimalByUuid(uuid) // .getAnimalByUuid(uuid)
// .orElseThrow(() -> new EntityNotFoundException("Zoo not found with uuid: " + uuid)); // .orElseThrow(() -> new EntityNotFoundException("Zoo not found with uuid: " + uuid));
// return getZoo.toDto(); // return getZoo.toDto();
// } // }
// //
// // AddReq를 받는 추가 메서드 // // AddReq를 받는 추가 메서드
// @Transactional // @Transactional
// public AnimalDto.Basic create(AnimalDto.AddReq req) { // public AnimalDto.Basic create(AnimalDto.AddReq req) {
// ZooEntity zoo = null; // ZooEntity zoo = null;
// if (req.getZooUuid() != null) { // if (req.getZooUuid() != null) {
// zoo = // zoo =
// zooRepository // zooRepository
// .getZooByUuid(req.getZooUuid()) // .getZooByUuid(req.getZooUuid())
// .orElseThrow( // .orElseThrow(
// () -> // () ->
// new EntityNotFoundException("Zoo not found with uuid: " + // new EntityNotFoundException("Zoo not found with uuid: " +
// req.getZooUuid())); // req.getZooUuid()));
// } // }
// AnimalEntity entity = new AnimalEntity(req.getCategory(), req.getSpecies(), req.getName(), // AnimalEntity entity = new AnimalEntity(req.getCategory(), req.getSpecies(), req.getName(),
// zoo); // zoo);
// AnimalEntity saved = animalRepository.save(entity); // AnimalEntity saved = animalRepository.save(entity);
// return saved.toDto(); // return saved.toDto();
// } // }
// //
// @Override // @Override
// @Transactional // @Transactional
// public void remove(Long id) { // public void remove(Long id) {
// AnimalEntity getAnimal = // AnimalEntity getAnimal =
// animalRepository // animalRepository
// .getAnimalByUid(id) // .getAnimalByUid(id)
// .orElseThrow(() -> new EntityNotFoundException("getAnimal not found with id: " + id)); // .orElseThrow(() -> new EntityNotFoundException("getAnimal not found with id: " + id));
// getAnimal.deleted(); // getAnimal.deleted();
// } // }
// //
// @Override // @Override
// public AnimalDto.Basic getOneById(Long id) { // public AnimalDto.Basic getOneById(Long id) {
// AnimalEntity getAnimal = // AnimalEntity getAnimal =
// animalRepository // animalRepository
// .getAnimalByUid(id) // .getAnimalByUid(id)
// .orElseThrow(() -> new EntityNotFoundException("Zoo not found with id: " + id)); // .orElseThrow(() -> new EntityNotFoundException("Zoo not found with id: " + id));
// return getAnimal.toDto(); // return getAnimal.toDto();
// } // }
// //
// @Override // @Override
// public Page<AnimalDto.Basic> search(AnimalDto.SearchReq searchReq) { // public Page<AnimalDto.Basic> search(AnimalDto.SearchReq searchReq) {
// //
// Page<AnimalEntity> animalEntities = animalRepository.listAnimal(searchReq); // Page<AnimalEntity> animalEntities = animalRepository.listAnimal(searchReq);
// return animalEntities.map(AnimalEntity::toDto); // return animalEntities.map(AnimalEntity::toDto);
// } // }
// } // }

View File

@@ -1,62 +1,62 @@
package com.kamco.cd.training.postgres.core; package com.kamco.cd.training.postgres.core;
import com.kamco.cd.training.common.service.BaseCoreService; import com.kamco.cd.training.common.service.BaseCoreService;
import com.kamco.cd.training.log.dto.AuditLogDto; import com.kamco.cd.training.log.dto.AuditLogDto;
import com.kamco.cd.training.postgres.repository.log.AuditLogRepository; import com.kamco.cd.training.postgres.repository.log.AuditLogRepository;
import java.time.LocalDate; import java.time.LocalDate;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import org.springframework.data.domain.Page; import org.springframework.data.domain.Page;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional; import org.springframework.transaction.annotation.Transactional;
@Service @Service
@RequiredArgsConstructor @RequiredArgsConstructor
@Transactional(readOnly = true) @Transactional(readOnly = true)
public class AuditLogCoreService public class AuditLogCoreService
implements BaseCoreService<AuditLogDto.DailyAuditList, Long, AuditLogDto.searchReq> { implements BaseCoreService<AuditLogDto.DailyAuditList, Long, AuditLogDto.searchReq> {
private final AuditLogRepository auditLogRepository; private final AuditLogRepository auditLogRepository;
@Override @Override
public void remove(Long aLong) {} public void remove(Long aLong) {}
@Override @Override
public AuditLogDto.DailyAuditList getOneById(Long aLong) { public AuditLogDto.DailyAuditList getOneById(Long aLong) {
return null; return null;
} }
@Override @Override
public Page<AuditLogDto.DailyAuditList> search(AuditLogDto.searchReq searchReq) { public Page<AuditLogDto.DailyAuditList> search(AuditLogDto.searchReq searchReq) {
return null; return null;
} }
public Page<AuditLogDto.DailyAuditList> getLogByDaily( public Page<AuditLogDto.DailyAuditList> getLogByDaily(
AuditLogDto.searchReq searchRange, LocalDate startDate, LocalDate endDate) { AuditLogDto.searchReq searchRange, LocalDate startDate, LocalDate endDate) {
return auditLogRepository.findLogByDaily(searchRange, startDate, endDate); return auditLogRepository.findLogByDaily(searchRange, startDate, endDate);
} }
public Page<AuditLogDto.MenuAuditList> getLogByMenu( public Page<AuditLogDto.MenuAuditList> getLogByMenu(
AuditLogDto.searchReq searchRange, String searchValue) { AuditLogDto.searchReq searchRange, String searchValue) {
return auditLogRepository.findLogByMenu(searchRange, searchValue); return auditLogRepository.findLogByMenu(searchRange, searchValue);
} }
public Page<AuditLogDto.UserAuditList> getLogByAccount( public Page<AuditLogDto.UserAuditList> getLogByAccount(
AuditLogDto.searchReq searchRange, String searchValue) { AuditLogDto.searchReq searchRange, String searchValue) {
return auditLogRepository.findLogByAccount(searchRange, searchValue); return auditLogRepository.findLogByAccount(searchRange, searchValue);
} }
public Page<AuditLogDto.DailyDetail> getLogByDailyResult( public Page<AuditLogDto.DailyDetail> getLogByDailyResult(
AuditLogDto.searchReq searchRange, LocalDate logDate) { AuditLogDto.searchReq searchRange, LocalDate logDate) {
return auditLogRepository.findLogByDailyResult(searchRange, logDate); return auditLogRepository.findLogByDailyResult(searchRange, logDate);
} }
public Page<AuditLogDto.MenuDetail> getLogByMenuResult( public Page<AuditLogDto.MenuDetail> getLogByMenuResult(
AuditLogDto.searchReq searchRange, String menuId) { AuditLogDto.searchReq searchRange, String menuId) {
return auditLogRepository.findLogByMenuResult(searchRange, menuId); return auditLogRepository.findLogByMenuResult(searchRange, menuId);
} }
public Page<AuditLogDto.UserDetail> getLogByAccountResult( public Page<AuditLogDto.UserDetail> getLogByAccountResult(
AuditLogDto.searchReq searchRange, Long accountId) { AuditLogDto.searchReq searchRange, Long accountId) {
return auditLogRepository.findLogByAccountResult(searchRange, accountId); return auditLogRepository.findLogByAccountResult(searchRange, accountId);
} }
} }

View File

@@ -1,217 +1,217 @@
package com.kamco.cd.training.postgres.core; package com.kamco.cd.training.postgres.core;
import com.kamco.cd.training.code.dto.CommonCodeDto; 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.Basic;
import com.kamco.cd.training.code.dto.CommonCodeDto.SearchReq; import com.kamco.cd.training.code.dto.CommonCodeDto.SearchReq;
import com.kamco.cd.training.common.service.BaseCoreService; 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.ApiResponseCode;
import com.kamco.cd.training.config.api.ApiResponseDto.ResponseObj; import com.kamco.cd.training.config.api.ApiResponseDto.ResponseObj;
import com.kamco.cd.training.postgres.entity.CommonCodeEntity; import com.kamco.cd.training.postgres.entity.CommonCodeEntity;
import com.kamco.cd.training.postgres.repository.code.CommonCodeRepository; import com.kamco.cd.training.postgres.repository.code.CommonCodeRepository;
import jakarta.persistence.EntityNotFoundException; import jakarta.persistence.EntityNotFoundException;
import java.util.List; import java.util.List;
import java.util.Optional; import java.util.Optional;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import org.springframework.data.domain.Page; import org.springframework.data.domain.Page;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
@Service @Service
@RequiredArgsConstructor @RequiredArgsConstructor
public class CommonCodeCoreService public class CommonCodeCoreService
implements BaseCoreService<CommonCodeDto.Basic, Long, SearchReq> { implements BaseCoreService<CommonCodeDto.Basic, Long, SearchReq> {
private final CommonCodeRepository commonCodeRepository; private final CommonCodeRepository commonCodeRepository;
/** /**
* 모두 조회 * 모두 조회
* *
* @return * @return
*/ */
// @Cacheable(value = "commonCodes") // @Cacheable(value = "commonCodes")
public List<CommonCodeDto.Basic> findAll() { public List<CommonCodeDto.Basic> findAll() {
return commonCodeRepository.findByAll().stream().map(CommonCodeEntity::toDto).toList(); return commonCodeRepository.findByAll().stream().map(CommonCodeEntity::toDto).toList();
} }
/** /**
* 등록 * 등록
* *
* @param req * @param req
* @return * @return
*/ */
// @CacheEvict(value = "commonCodes", allEntries = true) // @CacheEvict(value = "commonCodes", allEntries = true)
public ResponseObj save(CommonCodeDto.AddReq req) { public ResponseObj save(CommonCodeDto.AddReq req) {
String regex = "^[A-Z0-9]+(_[A-Z0-9]+)*$"; String regex = "^[A-Z0-9]+(_[A-Z0-9]+)*$";
boolean isValid = req.getCode().matches(regex); boolean isValid = req.getCode().matches(regex);
if (!isValid) { if (!isValid) {
return new ResponseObj(ApiResponseCode.CONFLICT, "공통코드에 영문 대문자, 숫자, 언더바(_)만 입력 가능합니다."); return new ResponseObj(ApiResponseCode.CONFLICT, "공통코드에 영문 대문자, 숫자, 언더바(_)만 입력 가능합니다.");
} }
Long existsCount = Long existsCount =
commonCodeRepository.findByParentIdCodeExists(req.getParentId(), req.getCode()); commonCodeRepository.findByParentIdCodeExists(req.getParentId(), req.getCode());
if (existsCount > 0) { if (existsCount > 0) {
return new ResponseObj(ApiResponseCode.DUPLICATE_DATA, "이미 사용중인 공통코드ID 입니다."); return new ResponseObj(ApiResponseCode.DUPLICATE_DATA, "이미 사용중인 공통코드ID 입니다.");
} }
CommonCodeEntity entity = CommonCodeEntity entity =
new CommonCodeEntity( new CommonCodeEntity(
req.getCode(), req.getCode(),
req.getName(), req.getName(),
req.getDescription(), req.getDescription(),
req.getOrder(), req.getOrder(),
req.isUsed(), req.isUsed(),
req.getProps1(), req.getProps1(),
req.getProps2(), req.getProps2(),
req.getProps3()); req.getProps3());
if (req.getParentId() != null) { if (req.getParentId() != null) {
CommonCodeEntity parentCommonCodeEntity = CommonCodeEntity parentCommonCodeEntity =
commonCodeRepository commonCodeRepository
.findById(req.getParentId()) .findById(req.getParentId())
.orElseThrow( .orElseThrow(
() -> () ->
new EntityNotFoundException( new EntityNotFoundException(
"parent id 를 찾을 수 없습니다. id : " + req.getParentId())); "parent id 를 찾을 수 없습니다. id : " + req.getParentId()));
entity.addParent(parentCommonCodeEntity); entity.addParent(parentCommonCodeEntity);
} else { } else {
entity.addParent(null); entity.addParent(null);
} }
commonCodeRepository.save(entity).toDto(); commonCodeRepository.save(entity).toDto();
return new ResponseObj(ApiResponseCode.OK, ""); return new ResponseObj(ApiResponseCode.OK, "");
} }
/** /**
* 수정 * 수정
* *
* @param id * @param id
* @param req * @param req
* @return * @return
*/ */
// @CacheEvict(value = "commonCodes", allEntries = true) // @CacheEvict(value = "commonCodes", allEntries = true)
public ResponseObj update(Long id, CommonCodeDto.ModifyReq req) { public ResponseObj update(Long id, CommonCodeDto.ModifyReq req) {
CommonCodeEntity found = CommonCodeEntity found =
commonCodeRepository commonCodeRepository
.findByCodeId(id) .findByCodeId(id)
.orElseThrow(() -> new EntityNotFoundException("common code 를 찾을 수 없습니다. id : " + id)); .orElseThrow(() -> new EntityNotFoundException("common code 를 찾을 수 없습니다. id : " + id));
found.update( found.update(
req.getName(), req.getName(),
req.getDescription(), req.getDescription(),
req.isUsed(), req.isUsed(),
req.getProps1(), req.getProps1(),
req.getProps2(), req.getProps2(),
req.getProps3()); req.getProps3());
return new ResponseObj(ApiResponseCode.OK, ""); return new ResponseObj(ApiResponseCode.OK, "");
} }
/** /**
* 순서 변경 * 순서 변경
* *
* @param req * @param req
* @return * @return
*/ */
// @CacheEvict(value = "commonCodes", allEntries = true) // @CacheEvict(value = "commonCodes", allEntries = true)
public ResponseObj updateOrder(CommonCodeDto.OrderReq req) { public ResponseObj updateOrder(CommonCodeDto.OrderReq req) {
CommonCodeEntity found = CommonCodeEntity found =
commonCodeRepository commonCodeRepository
.findByCodeId(req.getId()) .findByCodeId(req.getId())
.orElseThrow( .orElseThrow(
() -> new EntityNotFoundException("common code 를 찾을 수 없습니다. id : " + req.getId())); () -> new EntityNotFoundException("common code 를 찾을 수 없습니다. id : " + req.getId()));
found.updateOrder(req.getOrder()); found.updateOrder(req.getOrder());
return new ResponseObj(ApiResponseCode.OK, ""); return new ResponseObj(ApiResponseCode.OK, "");
} }
public List<CommonCodeDto.Basic> findByCode(String code) { public List<CommonCodeDto.Basic> findByCode(String code) {
return commonCodeRepository.findByCode(code).stream().map(CommonCodeEntity::toDto).toList(); return commonCodeRepository.findByCode(code).stream().map(CommonCodeEntity::toDto).toList();
} }
/** /**
* 공통코드 이름 조회 * 공통코드 이름 조회
* *
* @param parentCodeCd * @param parentCodeCd
* @param childCodeCd * @param childCodeCd
* @return * @return
*/ */
public Optional<String> getCode(String parentCodeCd, String childCodeCd) { public Optional<String> getCode(String parentCodeCd, String childCodeCd) {
return commonCodeRepository.getCode(parentCodeCd, childCodeCd); return commonCodeRepository.getCode(parentCodeCd, childCodeCd);
} }
/** /**
* 공통코드 삭제 * 공통코드 삭제
* *
* @param id * @param id
* @return * @return
*/ */
// @CacheEvict(value = "commonCodes", allEntries = true) // @CacheEvict(value = "commonCodes", allEntries = true)
public ResponseObj removeCode(Long id) { public ResponseObj removeCode(Long id) {
CommonCodeEntity entity = CommonCodeEntity entity =
commonCodeRepository commonCodeRepository
.findByCodeId(id) .findByCodeId(id)
.orElseThrow(() -> new EntityNotFoundException("code를 찾을 수 없습니다. id " + id)); .orElseThrow(() -> new EntityNotFoundException("code를 찾을 수 없습니다. id " + id));
// 하위코드가 있으면 삭제 불가 // 하위코드가 있으면 삭제 불가
if (!entity.getChildren().isEmpty()) { if (!entity.getChildren().isEmpty()) {
return new ResponseObj( return new ResponseObj(
ApiResponseCode.UNPROCESSABLE_ENTITY, ApiResponseCode.UNPROCESSABLE_ENTITY,
"하위에 다른 공통코드를 가지고 있습니다.<br/>하위공통 코드를 이동한 후 삭제할 수 있습니다."); "하위에 다른 공통코드를 가지고 있습니다.<br/>하위공통 코드를 이동한 후 삭제할 수 있습니다.");
} }
// id 코드 deleted = false 업데이트 // id 코드 deleted = false 업데이트
entity.deleted(); entity.deleted();
return new ResponseObj(ApiResponseCode.OK, ""); return new ResponseObj(ApiResponseCode.OK, "");
} }
@Override @Override
public void remove(Long aLong) { public void remove(Long aLong) {
// 미사용 // 미사용
} }
/** /**
* id 로 단건 조회 * id 로 단건 조회
* *
* @param id * @param id
* @return * @return
*/ */
@Override @Override
public Basic getOneById(Long id) { public Basic getOneById(Long id) {
CommonCodeEntity entity = CommonCodeEntity entity =
commonCodeRepository commonCodeRepository
.findByCodeId(id) .findByCodeId(id)
.orElseThrow(() -> new EntityNotFoundException("code를 찾을 수 없습니다. id " + id)); .orElseThrow(() -> new EntityNotFoundException("code를 찾을 수 없습니다. id " + id));
return entity.toDto(); return entity.toDto();
} }
@Override @Override
public Page<Basic> search(SearchReq searchReq) { public Page<Basic> search(SearchReq searchReq) {
return null; return null;
} }
/** /**
* 중복 체크 * 중복 체크
* *
* @param parentId * @param parentId
* @param code * @param code
* @return * @return
*/ */
public ResponseObj getCodeCheckDuplicate(Long parentId, String code) { public ResponseObj getCodeCheckDuplicate(Long parentId, String code) {
Long existsCount = commonCodeRepository.findByParentIdCodeExists(parentId, code); Long existsCount = commonCodeRepository.findByParentIdCodeExists(parentId, code);
String regex = "^[A-Z0-9]+(_[A-Z0-9]+)*$"; String regex = "^[A-Z0-9]+(_[A-Z0-9]+)*$";
boolean isValid = code.matches(regex); boolean isValid = code.matches(regex);
if (!isValid) { if (!isValid) {
return new ResponseObj(ApiResponseCode.CONFLICT, "공통코드에 영문 대문자, 숫자, 언더바(_)만 입력 가능합니다."); return new ResponseObj(ApiResponseCode.CONFLICT, "공통코드에 영문 대문자, 숫자, 언더바(_)만 입력 가능합니다.");
} }
if (existsCount > 0) { if (existsCount > 0) {
return new ResponseObj(ApiResponseCode.DUPLICATE_DATA, "이미 사용중인 공통코드ID 입니다."); return new ResponseObj(ApiResponseCode.DUPLICATE_DATA, "이미 사용중인 공통코드ID 입니다.");
} }
return new ResponseObj(ApiResponseCode.OK, ""); return new ResponseObj(ApiResponseCode.OK, "");
} }
} }

View File

@@ -1,276 +1,276 @@
package com.kamco.cd.training.postgres.core; package com.kamco.cd.training.postgres.core;
import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.ObjectMapper;
import com.kamco.cd.training.common.exception.NotFoundException; import com.kamco.cd.training.common.exception.NotFoundException;
import com.kamco.cd.training.common.service.BaseCoreService; import com.kamco.cd.training.common.service.BaseCoreService;
import com.kamco.cd.training.dataset.dto.DatasetDto; import com.kamco.cd.training.dataset.dto.DatasetDto;
import com.kamco.cd.training.model.dto.ModelMngDto; import com.kamco.cd.training.model.dto.ModelMngDto;
import com.kamco.cd.training.postgres.entity.DatasetEntity; import com.kamco.cd.training.postgres.entity.DatasetEntity;
import com.kamco.cd.training.postgres.repository.dataset.DatasetRepository; import com.kamco.cd.training.postgres.repository.dataset.DatasetRepository;
import java.time.ZonedDateTime; import java.time.ZonedDateTime;
import java.util.HashMap; import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.UUID; import java.util.UUID;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
import org.springframework.data.domain.Page; import org.springframework.data.domain.Page;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
@Service @Service
@RequiredArgsConstructor @RequiredArgsConstructor
@Slf4j @Slf4j
public class DatasetCoreService public class DatasetCoreService
implements BaseCoreService<DatasetDto.Basic, Long, DatasetDto.SearchReq> { implements BaseCoreService<DatasetDto.Basic, Long, DatasetDto.SearchReq> {
private final DatasetRepository datasetRepository; private final DatasetRepository datasetRepository;
private final ObjectMapper objectMapper; private final ObjectMapper objectMapper;
/** /**
* 학습 데이터 삭제 * 학습 데이터 삭제
* *
* @param id 데이터셋 ID * @param id 데이터셋 ID
*/ */
@Override @Override
public void remove(Long id) { public void remove(Long id) {
DatasetEntity entity = DatasetEntity entity =
datasetRepository datasetRepository
.findById(id) .findById(id)
.orElseThrow(() -> new NotFoundException("데이터셋을 찾을 수 없습니다. ID: " + id)); .orElseThrow(() -> new NotFoundException("데이터셋을 찾을 수 없습니다. ID: " + id));
entity.setDeleted(true); entity.setDeleted(true);
datasetRepository.save(entity); datasetRepository.save(entity);
} }
public void remove(UUID id) { public void remove(UUID id) {
DatasetEntity entity = DatasetEntity entity =
datasetRepository datasetRepository
.findByUuid(id) .findByUuid(id)
.orElseThrow(() -> new NotFoundException("데이터셋을 찾을 수 없습니다. ID: " + id)); .orElseThrow(() -> new NotFoundException("데이터셋을 찾을 수 없습니다. ID: " + id));
entity.setDeleted(true); entity.setDeleted(true);
datasetRepository.save(entity); datasetRepository.save(entity);
} }
@Override @Override
public DatasetDto.Basic getOneById(Long id) { public DatasetDto.Basic getOneById(Long id) {
DatasetEntity entity = DatasetEntity entity =
datasetRepository datasetRepository
.findById(id) .findById(id)
.orElseThrow(() -> new NotFoundException("데이터셋을 찾을 수 없습니다. ID: " + id)); .orElseThrow(() -> new NotFoundException("데이터셋을 찾을 수 없습니다. ID: " + id));
if (entity.getDeleted()) { if (entity.getDeleted()) {
throw new NotFoundException("삭제된 데이터셋입니다. ID: " + id); throw new NotFoundException("삭제된 데이터셋입니다. ID: " + id);
} }
return entity.toDto(); return entity.toDto();
} }
public DatasetDto.Basic getOneByUuid(UUID id) { public DatasetDto.Basic getOneByUuid(UUID id) {
DatasetEntity entity = DatasetEntity entity =
datasetRepository datasetRepository
.findByUuid(id) .findByUuid(id)
.orElseThrow(() -> new NotFoundException("데이터셋을 찾을 수 없습니다. uuid: " + id)); .orElseThrow(() -> new NotFoundException("데이터셋을 찾을 수 없습니다. uuid: " + id));
return entity.toDto(); return entity.toDto();
} }
@Override @Override
public Page<DatasetDto.Basic> search(DatasetDto.SearchReq searchReq) { public Page<DatasetDto.Basic> search(DatasetDto.SearchReq searchReq) {
Page<DatasetEntity> entityPage = datasetRepository.findDatasetList(searchReq); Page<DatasetEntity> entityPage = datasetRepository.findDatasetList(searchReq);
return entityPage.map(DatasetEntity::toDto); return entityPage.map(DatasetEntity::toDto);
} }
/** /**
* 학습데이터 조회 * 학습데이터 조회
* *
* @param searchReq 검색 조건 * @param searchReq 검색 조건
* @return 페이징 처리된 데이터셋 목록 * @return 페이징 처리된 데이터셋 목록
*/ */
public Page<DatasetDto.Basic> findDatasetList(DatasetDto.SearchReq searchReq) { public Page<DatasetDto.Basic> findDatasetList(DatasetDto.SearchReq searchReq) {
return search(searchReq); return search(searchReq);
} }
/** /**
* 학습데이터 등록 * 학습데이터 등록
* *
* @param registerReq 등록 요청 데이터 * @param registerReq 등록 요청 데이터
* @return 등록된 데이터셋 정보 * @return 등록된 데이터셋 정보
*/ */
public DatasetDto.Basic save(DatasetDto.RegisterReq registerReq) { public DatasetDto.Basic save(DatasetDto.RegisterReq registerReq) {
// 먼저 id1 필드를 임시값(0)으로 설정하여 저장 // 먼저 id1 필드를 임시값(0)으로 설정하여 저장
DatasetEntity entity = new DatasetEntity(); DatasetEntity entity = new DatasetEntity();
entity.setTitle(registerReq.getTitle()); entity.setTitle(registerReq.getTitle());
entity.setYear(registerReq.getYear()); entity.setYear(registerReq.getYear());
entity.setGroupTitle("PRODUCTION"); entity.setGroupTitle("PRODUCTION");
entity.setDataYear(registerReq.getYear()); entity.setDataYear(registerReq.getYear());
entity.setRoundNo(registerReq.getRoundNo() != null ? registerReq.getRoundNo() : 1L); entity.setRoundNo(registerReq.getRoundNo() != null ? registerReq.getRoundNo() : 1L);
entity.setMemo(registerReq.getMemo()); entity.setMemo(registerReq.getMemo());
entity.setStatus("READY"); entity.setStatus("READY");
entity.setDataType("CREATE"); entity.setDataType("CREATE");
entity.setTotalItems(0L); entity.setTotalItems(0L);
entity.setTotalSize(0L); entity.setTotalSize(0L);
entity.setItemCount(0L); entity.setItemCount(0L);
entity.setDeleted(false); entity.setDeleted(false);
entity.setCreatedDttm(ZonedDateTime.now()); entity.setCreatedDttm(ZonedDateTime.now());
entity.setId1(0L); // 임시값 entity.setId1(0L); // 임시값
DatasetEntity savedEntity = datasetRepository.save(entity); DatasetEntity savedEntity = datasetRepository.save(entity);
// 저장 후 id1을 dataset_uid와 동일하게 업데이트 // 저장 후 id1을 dataset_uid와 동일하게 업데이트
savedEntity.setId1(savedEntity.getId()); savedEntity.setId1(savedEntity.getId());
savedEntity = datasetRepository.save(savedEntity); savedEntity = datasetRepository.save(savedEntity);
return savedEntity.toDto(); return savedEntity.toDto();
} }
/** /**
* 학습 데이터 수정 * 학습 데이터 수정
* *
* @param updateReq 수정 요청 데이터 * @param updateReq 수정 요청 데이터
* @return 수정된 데이터셋 정보 * @return 수정된 데이터셋 정보
*/ */
public void update(UUID uuid, DatasetDto.UpdateReq updateReq) { public void update(UUID uuid, DatasetDto.UpdateReq updateReq) {
DatasetEntity entity = DatasetEntity entity =
datasetRepository datasetRepository
.findByUuid(uuid) .findByUuid(uuid)
.orElseThrow(() -> new NotFoundException("데이터셋을 찾을 수 없습니다. uuid: " + uuid)); .orElseThrow(() -> new NotFoundException("데이터셋을 찾을 수 없습니다. uuid: " + uuid));
if (StringUtils.isNotBlank(updateReq.getTitle())) { if (StringUtils.isNotBlank(updateReq.getTitle())) {
entity.setTitle(updateReq.getTitle()); entity.setTitle(updateReq.getTitle());
} }
if (StringUtils.isNotBlank(updateReq.getMemo())) { if (StringUtils.isNotBlank(updateReq.getMemo())) {
entity.setMemo(updateReq.getMemo()); entity.setMemo(updateReq.getMemo());
} }
datasetRepository.save(entity); datasetRepository.save(entity);
} }
/** /**
* 학습데이터 삭제 * 학습데이터 삭제
* *
* @param uuid 삭제 요청 (데이터셋 ID 목록) * @param uuid 삭제 요청 (데이터셋 ID 목록)
*/ */
public void deleteDatasets(UUID uuid) { public void deleteDatasets(UUID uuid) {
remove(uuid); remove(uuid);
} }
public DatasetDto.Summary getDatasetSummary(DatasetDto.SummaryReq summaryReq) { public DatasetDto.Summary getDatasetSummary(DatasetDto.SummaryReq summaryReq) {
long totalMapSheets = 0; long totalMapSheets = 0;
long totalFileSize = 0; long totalFileSize = 0;
for (Long datasetId : summaryReq.getDatasetIds()) { for (Long datasetId : summaryReq.getDatasetIds()) {
DatasetEntity entity = DatasetEntity entity =
datasetRepository datasetRepository
.findById(datasetId) .findById(datasetId)
.orElseThrow(() -> new NotFoundException("데이터셋을 찾을 수 없습니다. ID: " + datasetId)); .orElseThrow(() -> new NotFoundException("데이터셋을 찾을 수 없습니다. ID: " + datasetId));
if (!entity.getDeleted()) { if (!entity.getDeleted()) {
totalMapSheets += entity.getTotalItems() != null ? entity.getTotalItems() : 0; totalMapSheets += entity.getTotalItems() != null ? entity.getTotalItems() : 0;
totalFileSize += entity.getTotalSize() != null ? entity.getTotalSize() : 0; totalFileSize += entity.getTotalSize() != null ? entity.getTotalSize() : 0;
} }
} }
double averageMapSheets = double averageMapSheets =
!summaryReq.getDatasetIds().isEmpty() !summaryReq.getDatasetIds().isEmpty()
? (double) totalMapSheets / summaryReq.getDatasetIds().size() ? (double) totalMapSheets / summaryReq.getDatasetIds().size()
: 0; : 0;
return new DatasetDto.Summary( return new DatasetDto.Summary(
summaryReq.getDatasetIds().size(), totalMapSheets, totalFileSize, averageMapSheets); summaryReq.getDatasetIds().size(), totalMapSheets, totalFileSize, averageMapSheets);
} }
/** /**
* 활성 데이터셋 전체 조회 (학습 관리용) * 활성 데이터셋 전체 조회 (학습 관리용)
* *
* @return 데이터셋 정보 목록 * @return 데이터셋 정보 목록
*/ */
public List<ModelMngDto.DatasetInfo> findAllActiveDatasetsForTraining() { public List<ModelMngDto.DatasetInfo> findAllActiveDatasetsForTraining() {
List<DatasetEntity> entities = datasetRepository.findByDeletedOrderByCreatedDttmDesc(false); List<DatasetEntity> entities = datasetRepository.findByDeletedOrderByCreatedDttmDesc(false);
return entities.stream() return entities.stream()
.map( .map(
entity -> { entity -> {
// totalSize를 읽기 쉬운 형식으로 변환 // totalSize를 읽기 쉬운 형식으로 변환
String totalSizeStr = formatFileSize(entity.getTotalSize()); String totalSizeStr = formatFileSize(entity.getTotalSize());
// classCounts JSON 파싱 // classCounts JSON 파싱
Map<String, Integer> classCounts = entity.getClassCounts(); Map<String, Integer> classCounts = entity.getClassCounts();
return ModelMngDto.DatasetInfo.builder() return ModelMngDto.DatasetInfo.builder()
.id(entity.getId()) .id(entity.getId())
.title(entity.getTitle()) .title(entity.getTitle())
.groupTitle(entity.getGroupTitle()) .groupTitle(entity.getGroupTitle())
.totalItems(entity.getTotalItems()) .totalItems(entity.getTotalItems())
.totalSize(totalSizeStr) .totalSize(totalSizeStr)
.classCounts(classCounts) .classCounts(classCounts)
.memo(entity.getMemo()) .memo(entity.getMemo())
.createdDttm(entity.getCreatedDttm()) .createdDttm(entity.getCreatedDttm())
.build(); .build();
}) })
.toList(); .toList();
} }
/** /**
* JSON 문자열을 Map으로 파싱 * JSON 문자열을 Map으로 파싱
* *
* @param jsonStr JSON 문자열 * @param jsonStr JSON 문자열
* @return 클래스별 카운트 맵 * @return 클래스별 카운트 맵
*/ */
private Map<String, Integer> parseClassCounts(String jsonStr) { private Map<String, Integer> parseClassCounts(String jsonStr) {
if (jsonStr == null || jsonStr.trim().isEmpty()) { if (jsonStr == null || jsonStr.trim().isEmpty()) {
return new HashMap<>(); return new HashMap<>();
} }
try { try {
return objectMapper.readValue(jsonStr, new TypeReference<Map<String, Integer>>() {}); return objectMapper.readValue(jsonStr, new TypeReference<Map<String, Integer>>() {});
} catch (Exception e) { } catch (Exception e) {
log.warn("클래스 통계 JSON 파싱 실패: {}", jsonStr, e); log.warn("클래스 통계 JSON 파싱 실패: {}", jsonStr, e);
return new HashMap<>(); return new HashMap<>();
} }
} }
/** /**
* 데이터셋의 클래스 통계 계산 및 저장 * 데이터셋의 클래스 통계 계산 및 저장
* *
* @param datasetId 데이터셋 ID * @param datasetId 데이터셋 ID
* @param classCounts 클래스별 카운트 * @param classCounts 클래스별 카운트
*/ */
public void updateClassCounts(Long datasetId, Map<String, Integer> classCounts) { public void updateClassCounts(Long datasetId, Map<String, Integer> classCounts) {
DatasetEntity entity = DatasetEntity entity =
datasetRepository datasetRepository
.findById(datasetId) .findById(datasetId)
.orElseThrow(() -> new NotFoundException("데이터셋을 찾을 수 없습니다. ID: " + datasetId)); .orElseThrow(() -> new NotFoundException("데이터셋을 찾을 수 없습니다. ID: " + datasetId));
try { try {
entity.setClassCounts(classCounts); entity.setClassCounts(classCounts);
datasetRepository.save(entity); datasetRepository.save(entity);
log.info("데이터셋 클래스 통계 업데이트 완료: datasetId={}, classes={}", datasetId, classCounts.keySet()); log.info("데이터셋 클래스 통계 업데이트 완료: datasetId={}, classes={}", datasetId, classCounts.keySet());
} catch (Exception e) { } catch (Exception e) {
log.error("클래스 통계 JSON 변환 실패: datasetId={}", datasetId, e); log.error("클래스 통계 JSON 변환 실패: datasetId={}", datasetId, e);
} }
} }
/** /**
* 파일 크기를 읽기 쉬운 형식으로 변환 * 파일 크기를 읽기 쉬운 형식으로 변환
* *
* @param size 바이트 단위 크기 * @param size 바이트 단위 크기
* @return 형식화된 문자열 (예: "1.5GB") * @return 형식화된 문자열 (예: "1.5GB")
*/ */
private String formatFileSize(Long size) { private String formatFileSize(Long size) {
if (size == null || size == 0) { if (size == null || size == 0) {
return "0 GB"; return "0 GB";
} }
double gb = size / (1024.0 * 1024.0 * 1024.0); double gb = size / (1024.0 * 1024.0 * 1024.0);
if (gb >= 1.0) { if (gb >= 1.0) {
return String.format("%.2f GB", gb); return String.format("%.2f GB", gb);
} }
double mb = size / (1024.0 * 1024.0); double mb = size / (1024.0 * 1024.0);
if (mb >= 1.0) { if (mb >= 1.0) {
return String.format("%.2f MB", mb); return String.format("%.2f MB", mb);
} }
double kb = size / 1024.0; double kb = size / 1024.0;
return String.format("%.2f KB", kb); return String.format("%.2f KB", kb);
} }
} }

View File

@@ -1,35 +1,35 @@
package com.kamco.cd.training.postgres.core; package com.kamco.cd.training.postgres.core;
import com.kamco.cd.training.common.service.BaseCoreService; import com.kamco.cd.training.common.service.BaseCoreService;
import com.kamco.cd.training.log.dto.ErrorLogDto; import com.kamco.cd.training.log.dto.ErrorLogDto;
import com.kamco.cd.training.postgres.repository.log.ErrorLogRepository; import com.kamco.cd.training.postgres.repository.log.ErrorLogRepository;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import org.springframework.data.domain.Page; import org.springframework.data.domain.Page;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional; import org.springframework.transaction.annotation.Transactional;
@Service @Service
@RequiredArgsConstructor @RequiredArgsConstructor
@Transactional(readOnly = true) @Transactional(readOnly = true)
public class ErrorLogCoreService public class ErrorLogCoreService
implements BaseCoreService<ErrorLogDto.Basic, Long, ErrorLogDto.ErrorSearchReq> { implements BaseCoreService<ErrorLogDto.Basic, Long, ErrorLogDto.ErrorSearchReq> {
private final ErrorLogRepository errorLogRepository; private final ErrorLogRepository errorLogRepository;
public Page<ErrorLogDto.Basic> findLogByError(ErrorLogDto.ErrorSearchReq searchReq) { public Page<ErrorLogDto.Basic> findLogByError(ErrorLogDto.ErrorSearchReq searchReq) {
return errorLogRepository.findLogByError(searchReq); return errorLogRepository.findLogByError(searchReq);
} }
@Override @Override
public void remove(Long aLong) {} public void remove(Long aLong) {}
@Override @Override
public ErrorLogDto.Basic getOneById(Long aLong) { public ErrorLogDto.Basic getOneById(Long aLong) {
return null; return null;
} }
@Override @Override
public Page<ErrorLogDto.Basic> search(ErrorLogDto.ErrorSearchReq searchReq) { public Page<ErrorLogDto.Basic> search(ErrorLogDto.ErrorSearchReq searchReq) {
return null; return null;
} }
} }

View File

@@ -1,337 +1,337 @@
package com.kamco.cd.training.postgres.core; package com.kamco.cd.training.postgres.core;
import com.kamco.cd.training.common.exception.BadRequestException; import com.kamco.cd.training.common.exception.BadRequestException;
import com.kamco.cd.training.common.exception.NotFoundException; import com.kamco.cd.training.common.exception.NotFoundException;
import com.kamco.cd.training.model.dto.ModelMngDto; import com.kamco.cd.training.model.dto.ModelMngDto;
import com.kamco.cd.training.postgres.entity.ModelHyperParamEntity; import com.kamco.cd.training.postgres.entity.ModelHyperParamEntity;
import com.kamco.cd.training.postgres.repository.model.ModelHyperParamRepository; import com.kamco.cd.training.postgres.repository.model.ModelHyperParamRepository;
import java.time.ZonedDateTime; import java.time.ZonedDateTime;
import java.util.List; import java.util.List;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
@Service @Service
@RequiredArgsConstructor @RequiredArgsConstructor
public class HyperParamCoreService { public class HyperParamCoreService {
private final ModelHyperParamRepository hyperParamRepository; private final ModelHyperParamRepository hyperParamRepository;
/** /**
* 하이퍼파라미터 전체 조회 (삭제되지 않은 것만) * 하이퍼파라미터 전체 조회 (삭제되지 않은 것만)
* *
* @return 하이퍼파라미터 목록 * @return 하이퍼파라미터 목록
*/ */
public List<ModelMngDto.HyperParamInfo> findAllActiveHyperParams() { public List<ModelMngDto.HyperParamInfo> findAllActiveHyperParams() {
List<ModelHyperParamEntity> entities = List<ModelHyperParamEntity> entities =
hyperParamRepository.findByDelYnOrderByCreatedDttmDesc("N"); hyperParamRepository.findByDelYnOrderByCreatedDttmDesc("N");
return entities.stream().map(this::mapToHyperParamInfo).toList(); return entities.stream().map(this::mapToHyperParamInfo).toList();
} }
private ModelMngDto.HyperParamInfo mapToHyperParamInfo(ModelHyperParamEntity entity) { private ModelMngDto.HyperParamInfo mapToHyperParamInfo(ModelHyperParamEntity entity) {
return ModelMngDto.HyperParamInfo.builder() return ModelMngDto.HyperParamInfo.builder()
.hyperVer(entity.getHyperVer()) .hyperVer(entity.getHyperVer())
// Important // Important
.backbone(entity.getBackbone()) .backbone(entity.getBackbone())
.inputSize(entity.getInputSize()) .inputSize(entity.getInputSize())
.cropSize(entity.getCropSize()) .cropSize(entity.getCropSize())
.epochCnt(entity.getEpochCnt()) .epochCnt(entity.getEpochCnt())
.batchSize(entity.getBatchSize()) .batchSize(entity.getBatchSize())
// Architecture // Architecture
.dropPathRate(entity.getDropPathRate()) .dropPathRate(entity.getDropPathRate())
.frozenStages(entity.getFrozenStages()) .frozenStages(entity.getFrozenStages())
.neckPolicy(entity.getNeckPolicy()) .neckPolicy(entity.getNeckPolicy())
.decoderChannels(entity.getDecoderChannels()) .decoderChannels(entity.getDecoderChannels())
.classWeight(entity.getClassWeight()) .classWeight(entity.getClassWeight())
.numLayers(entity.getNumLayers()) .numLayers(entity.getNumLayers())
// Optimization // Optimization
.learningRate(entity.getLearningRate()) .learningRate(entity.getLearningRate())
.weightDecay(entity.getWeightDecay()) .weightDecay(entity.getWeightDecay())
.layerDecayRate(entity.getLayerDecayRate()) .layerDecayRate(entity.getLayerDecayRate())
.ddpFindUnusedParams(entity.getDdpFindUnusedParams()) .ddpFindUnusedParams(entity.getDdpFindUnusedParams())
.ignoreIndex(entity.getIgnoreIndex()) .ignoreIndex(entity.getIgnoreIndex())
// Data // Data
.trainNumWorkers(entity.getTrainNumWorkers()) .trainNumWorkers(entity.getTrainNumWorkers())
.valNumWorkers(entity.getValNumWorkers()) .valNumWorkers(entity.getValNumWorkers())
.testNumWorkers(entity.getTestNumWorkers()) .testNumWorkers(entity.getTestNumWorkers())
.trainShuffle(entity.getTrainShuffle()) .trainShuffle(entity.getTrainShuffle())
.trainPersistent(entity.getTrainPersistent()) .trainPersistent(entity.getTrainPersistent())
.valPersistent(entity.getValPersistent()) .valPersistent(entity.getValPersistent())
// Evaluation // Evaluation
.metrics(entity.getMetrics()) .metrics(entity.getMetrics())
.saveBest(entity.getSaveBest()) .saveBest(entity.getSaveBest())
.saveBestRule(entity.getSaveBestRule()) .saveBestRule(entity.getSaveBestRule())
.valInterval(entity.getValInterval()) .valInterval(entity.getValInterval())
.logInterval(entity.getLogInterval()) .logInterval(entity.getLogInterval())
.visInterval(entity.getVisInterval()) .visInterval(entity.getVisInterval())
// Hardware // Hardware
.gpuCnt(entity.getGpuCnt()) .gpuCnt(entity.getGpuCnt())
.gpuIds(entity.getGpuIds()) .gpuIds(entity.getGpuIds())
.masterPort(entity.getMasterPort()) .masterPort(entity.getMasterPort())
// Augmentation // Augmentation
.rotProb(entity.getRotProb()) .rotProb(entity.getRotProb())
.flipProb(entity.getFlipProb()) .flipProb(entity.getFlipProb())
.rotDegree(entity.getRotDegree()) .rotDegree(entity.getRotDegree())
.exchangeProb(entity.getExchangeProb()) .exchangeProb(entity.getExchangeProb())
.brightnessDelta(entity.getBrightnessDelta()) .brightnessDelta(entity.getBrightnessDelta())
.contrastRange(entity.getContrastRange()) .contrastRange(entity.getContrastRange())
.saturationRange(entity.getSaturationRange()) .saturationRange(entity.getSaturationRange())
.hueDelta(entity.getHueDelta()) .hueDelta(entity.getHueDelta())
// Legacy // Legacy
.dropoutRatio(entity.getDropoutRatio()) .dropoutRatio(entity.getDropoutRatio())
.cnnFilterCnt(entity.getCnnFilterCnt()) .cnnFilterCnt(entity.getCnnFilterCnt())
// Common // Common
.memo(entity.getMemo()) .memo(entity.getMemo())
.createdDttm(entity.getCreatedDttm()) .createdDttm(entity.getCreatedDttm())
.build(); .build();
} }
/** /**
* 하이퍼파라미터 등록 * 하이퍼파라미터 등록
* *
* @param createReq 등록 요청 * @param createReq 등록 요청
* @return 등록된 버전명 * @return 등록된 버전명
*/ */
public String createHyperParam(ModelMngDto.HyperParamCreateReq createReq) { public String createHyperParam(ModelMngDto.HyperParamCreateReq createReq) {
// 중복 체크 // 중복 체크
if (hyperParamRepository.existsById(createReq.getNewHyperVer())) { if (hyperParamRepository.existsById(createReq.getNewHyperVer())) {
throw new BadRequestException("이미 존재하는 버전입니다: " + createReq.getNewHyperVer()); throw new BadRequestException("이미 존재하는 버전입니다: " + createReq.getNewHyperVer());
} }
// 기준 버전 조회 // 기준 버전 조회
ModelHyperParamEntity baseEntity = ModelHyperParamEntity baseEntity =
hyperParamRepository hyperParamRepository
.findById(createReq.getBaseHyperVer()) .findById(createReq.getBaseHyperVer())
.orElseThrow( .orElseThrow(
() -> new NotFoundException("기준 버전을 찾을 수 없습니다: " + createReq.getBaseHyperVer())); () -> new NotFoundException("기준 버전을 찾을 수 없습니다: " + createReq.getBaseHyperVer()));
// 신규 엔티티 생성 (기준 값 복사 후 변경된 값만 적용) // 신규 엔티티 생성 (기준 값 복사 후 변경된 값만 적용)
ModelHyperParamEntity entity = new ModelHyperParamEntity(); ModelHyperParamEntity entity = new ModelHyperParamEntity();
entity.setHyperVer(createReq.getNewHyperVer()); entity.setHyperVer(createReq.getNewHyperVer());
// Important // Important
entity.setBackbone( entity.setBackbone(
createReq.getBackbone() != null ? createReq.getBackbone() : baseEntity.getBackbone()); createReq.getBackbone() != null ? createReq.getBackbone() : baseEntity.getBackbone());
entity.setInputSize( entity.setInputSize(
createReq.getInputSize() != null ? createReq.getInputSize() : baseEntity.getInputSize()); createReq.getInputSize() != null ? createReq.getInputSize() : baseEntity.getInputSize());
entity.setCropSize( entity.setCropSize(
createReq.getCropSize() != null ? createReq.getCropSize() : baseEntity.getCropSize()); createReq.getCropSize() != null ? createReq.getCropSize() : baseEntity.getCropSize());
entity.setEpochCnt( entity.setEpochCnt(
createReq.getEpochCnt() != null ? createReq.getEpochCnt() : baseEntity.getEpochCnt()); createReq.getEpochCnt() != null ? createReq.getEpochCnt() : baseEntity.getEpochCnt());
entity.setBatchSize( entity.setBatchSize(
createReq.getBatchSize() != null ? createReq.getBatchSize() : baseEntity.getBatchSize()); createReq.getBatchSize() != null ? createReq.getBatchSize() : baseEntity.getBatchSize());
// Architecture // Architecture
entity.setDropPathRate( entity.setDropPathRate(
createReq.getDropPathRate() != null createReq.getDropPathRate() != null
? createReq.getDropPathRate() ? createReq.getDropPathRate()
: baseEntity.getDropPathRate()); : baseEntity.getDropPathRate());
entity.setFrozenStages( entity.setFrozenStages(
createReq.getFrozenStages() != null createReq.getFrozenStages() != null
? createReq.getFrozenStages() ? createReq.getFrozenStages()
: baseEntity.getFrozenStages()); : baseEntity.getFrozenStages());
entity.setNeckPolicy( entity.setNeckPolicy(
createReq.getNeckPolicy() != null ? createReq.getNeckPolicy() : baseEntity.getNeckPolicy()); createReq.getNeckPolicy() != null ? createReq.getNeckPolicy() : baseEntity.getNeckPolicy());
entity.setDecoderChannels( entity.setDecoderChannels(
createReq.getDecoderChannels() != null createReq.getDecoderChannels() != null
? createReq.getDecoderChannels() ? createReq.getDecoderChannels()
: baseEntity.getDecoderChannels()); : baseEntity.getDecoderChannels());
entity.setClassWeight( entity.setClassWeight(
createReq.getClassWeight() != null createReq.getClassWeight() != null
? createReq.getClassWeight() ? createReq.getClassWeight()
: baseEntity.getClassWeight()); : baseEntity.getClassWeight());
entity.setNumLayers( entity.setNumLayers(
createReq.getNumLayers() != null ? createReq.getNumLayers() : baseEntity.getNumLayers()); createReq.getNumLayers() != null ? createReq.getNumLayers() : baseEntity.getNumLayers());
// Optimization // Optimization
entity.setLearningRate( entity.setLearningRate(
createReq.getLearningRate() != null createReq.getLearningRate() != null
? createReq.getLearningRate() ? createReq.getLearningRate()
: baseEntity.getLearningRate()); : baseEntity.getLearningRate());
entity.setWeightDecay( entity.setWeightDecay(
createReq.getWeightDecay() != null createReq.getWeightDecay() != null
? createReq.getWeightDecay() ? createReq.getWeightDecay()
: baseEntity.getWeightDecay()); : baseEntity.getWeightDecay());
entity.setLayerDecayRate( entity.setLayerDecayRate(
createReq.getLayerDecayRate() != null createReq.getLayerDecayRate() != null
? createReq.getLayerDecayRate() ? createReq.getLayerDecayRate()
: baseEntity.getLayerDecayRate()); : baseEntity.getLayerDecayRate());
entity.setDdpFindUnusedParams( entity.setDdpFindUnusedParams(
createReq.getDdpFindUnusedParams() != null createReq.getDdpFindUnusedParams() != null
? createReq.getDdpFindUnusedParams() ? createReq.getDdpFindUnusedParams()
: baseEntity.getDdpFindUnusedParams()); : baseEntity.getDdpFindUnusedParams());
entity.setIgnoreIndex( entity.setIgnoreIndex(
createReq.getIgnoreIndex() != null createReq.getIgnoreIndex() != null
? createReq.getIgnoreIndex() ? createReq.getIgnoreIndex()
: baseEntity.getIgnoreIndex()); : baseEntity.getIgnoreIndex());
// Data // Data
entity.setTrainNumWorkers( entity.setTrainNumWorkers(
createReq.getTrainNumWorkers() != null createReq.getTrainNumWorkers() != null
? createReq.getTrainNumWorkers() ? createReq.getTrainNumWorkers()
: baseEntity.getTrainNumWorkers()); : baseEntity.getTrainNumWorkers());
entity.setValNumWorkers( entity.setValNumWorkers(
createReq.getValNumWorkers() != null createReq.getValNumWorkers() != null
? createReq.getValNumWorkers() ? createReq.getValNumWorkers()
: baseEntity.getValNumWorkers()); : baseEntity.getValNumWorkers());
entity.setTestNumWorkers( entity.setTestNumWorkers(
createReq.getTestNumWorkers() != null createReq.getTestNumWorkers() != null
? createReq.getTestNumWorkers() ? createReq.getTestNumWorkers()
: baseEntity.getTestNumWorkers()); : baseEntity.getTestNumWorkers());
entity.setTrainShuffle( entity.setTrainShuffle(
createReq.getTrainShuffle() != null createReq.getTrainShuffle() != null
? createReq.getTrainShuffle() ? createReq.getTrainShuffle()
: baseEntity.getTrainShuffle()); : baseEntity.getTrainShuffle());
entity.setTrainPersistent( entity.setTrainPersistent(
createReq.getTrainPersistent() != null createReq.getTrainPersistent() != null
? createReq.getTrainPersistent() ? createReq.getTrainPersistent()
: baseEntity.getTrainPersistent()); : baseEntity.getTrainPersistent());
entity.setValPersistent( entity.setValPersistent(
createReq.getValPersistent() != null createReq.getValPersistent() != null
? createReq.getValPersistent() ? createReq.getValPersistent()
: baseEntity.getValPersistent()); : baseEntity.getValPersistent());
// Evaluation // Evaluation
entity.setMetrics( entity.setMetrics(
createReq.getMetrics() != null ? createReq.getMetrics() : baseEntity.getMetrics()); createReq.getMetrics() != null ? createReq.getMetrics() : baseEntity.getMetrics());
entity.setSaveBest( entity.setSaveBest(
createReq.getSaveBest() != null ? createReq.getSaveBest() : baseEntity.getSaveBest()); createReq.getSaveBest() != null ? createReq.getSaveBest() : baseEntity.getSaveBest());
entity.setSaveBestRule( entity.setSaveBestRule(
createReq.getSaveBestRule() != null createReq.getSaveBestRule() != null
? createReq.getSaveBestRule() ? createReq.getSaveBestRule()
: baseEntity.getSaveBestRule()); : baseEntity.getSaveBestRule());
entity.setValInterval( entity.setValInterval(
createReq.getValInterval() != null createReq.getValInterval() != null
? createReq.getValInterval() ? createReq.getValInterval()
: baseEntity.getValInterval()); : baseEntity.getValInterval());
entity.setLogInterval( entity.setLogInterval(
createReq.getLogInterval() != null createReq.getLogInterval() != null
? createReq.getLogInterval() ? createReq.getLogInterval()
: baseEntity.getLogInterval()); : baseEntity.getLogInterval());
entity.setVisInterval( entity.setVisInterval(
createReq.getVisInterval() != null createReq.getVisInterval() != null
? createReq.getVisInterval() ? createReq.getVisInterval()
: baseEntity.getVisInterval()); : baseEntity.getVisInterval());
// Hardware // Hardware
entity.setGpuCnt( entity.setGpuCnt(
createReq.getGpuCnt() != null ? createReq.getGpuCnt() : baseEntity.getGpuCnt()); createReq.getGpuCnt() != null ? createReq.getGpuCnt() : baseEntity.getGpuCnt());
entity.setGpuIds( entity.setGpuIds(
createReq.getGpuIds() != null ? createReq.getGpuIds() : baseEntity.getGpuIds()); createReq.getGpuIds() != null ? createReq.getGpuIds() : baseEntity.getGpuIds());
entity.setMasterPort( entity.setMasterPort(
createReq.getMasterPort() != null ? createReq.getMasterPort() : baseEntity.getMasterPort()); createReq.getMasterPort() != null ? createReq.getMasterPort() : baseEntity.getMasterPort());
// Augmentation // Augmentation
entity.setRotProb( entity.setRotProb(
createReq.getRotProb() != null ? createReq.getRotProb() : baseEntity.getRotProb()); createReq.getRotProb() != null ? createReq.getRotProb() : baseEntity.getRotProb());
entity.setFlipProb( entity.setFlipProb(
createReq.getFlipProb() != null ? createReq.getFlipProb() : baseEntity.getFlipProb()); createReq.getFlipProb() != null ? createReq.getFlipProb() : baseEntity.getFlipProb());
entity.setRotDegree( entity.setRotDegree(
createReq.getRotDegree() != null ? createReq.getRotDegree() : baseEntity.getRotDegree()); createReq.getRotDegree() != null ? createReq.getRotDegree() : baseEntity.getRotDegree());
entity.setExchangeProb( entity.setExchangeProb(
createReq.getExchangeProb() != null createReq.getExchangeProb() != null
? createReq.getExchangeProb() ? createReq.getExchangeProb()
: baseEntity.getExchangeProb()); : baseEntity.getExchangeProb());
entity.setBrightnessDelta( entity.setBrightnessDelta(
createReq.getBrightnessDelta() != null createReq.getBrightnessDelta() != null
? createReq.getBrightnessDelta() ? createReq.getBrightnessDelta()
: baseEntity.getBrightnessDelta()); : baseEntity.getBrightnessDelta());
entity.setContrastRange( entity.setContrastRange(
createReq.getContrastRange() != null createReq.getContrastRange() != null
? createReq.getContrastRange() ? createReq.getContrastRange()
: baseEntity.getContrastRange()); : baseEntity.getContrastRange());
entity.setSaturationRange( entity.setSaturationRange(
createReq.getSaturationRange() != null createReq.getSaturationRange() != null
? createReq.getSaturationRange() ? createReq.getSaturationRange()
: baseEntity.getSaturationRange()); : baseEntity.getSaturationRange());
entity.setHueDelta( entity.setHueDelta(
createReq.getHueDelta() != null ? createReq.getHueDelta() : baseEntity.getHueDelta()); createReq.getHueDelta() != null ? createReq.getHueDelta() : baseEntity.getHueDelta());
// Legacy // Legacy
entity.setDropoutRatio( entity.setDropoutRatio(
createReq.getDropoutRatio() != null createReq.getDropoutRatio() != null
? createReq.getDropoutRatio() ? createReq.getDropoutRatio()
: baseEntity.getDropoutRatio()); : baseEntity.getDropoutRatio());
entity.setCnnFilterCnt( entity.setCnnFilterCnt(
createReq.getCnnFilterCnt() != null createReq.getCnnFilterCnt() != null
? createReq.getCnnFilterCnt() ? createReq.getCnnFilterCnt()
: baseEntity.getCnnFilterCnt()); : baseEntity.getCnnFilterCnt());
// Common // Common
entity.setMemo(createReq.getMemo()); entity.setMemo(createReq.getMemo());
entity.setDelYn("N"); entity.setDelYn("N");
entity.setCreatedDttm(ZonedDateTime.now()); entity.setCreatedDttm(ZonedDateTime.now());
ModelHyperParamEntity saved = hyperParamRepository.save(entity); ModelHyperParamEntity saved = hyperParamRepository.save(entity);
return saved.getHyperVer(); return saved.getHyperVer();
} }
/** /**
* 하이퍼파라미터 단건 조회 * 하이퍼파라미터 단건 조회
* *
* @param hyperVer 하이퍼파라미터 버전 * @param hyperVer 하이퍼파라미터 버전
* @return 하이퍼파라미터 정보 * @return 하이퍼파라미터 정보
*/ */
public ModelMngDto.HyperParamInfo findByHyperVer(String hyperVer) { public ModelMngDto.HyperParamInfo findByHyperVer(String hyperVer) {
ModelHyperParamEntity entity = ModelHyperParamEntity entity =
hyperParamRepository hyperParamRepository
.findById(hyperVer) .findById(hyperVer)
.orElseThrow(() -> new NotFoundException("하이퍼파라미터를 찾을 수 없습니다: " + hyperVer)); .orElseThrow(() -> new NotFoundException("하이퍼파라미터를 찾을 수 없습니다: " + hyperVer));
if ("Y".equals(entity.getDelYn())) { if ("Y".equals(entity.getDelYn())) {
throw new NotFoundException("삭제된 하이퍼파라미터입니다: " + hyperVer); throw new NotFoundException("삭제된 하이퍼파라미터입니다: " + hyperVer);
} }
return mapToHyperParamInfo(entity); return mapToHyperParamInfo(entity);
} }
/** /**
* 하이퍼파라미터 수정 (기존 버전은 수정 불가) * 하이퍼파라미터 수정 (기존 버전은 수정 불가)
* *
* @param hyperVer 하이퍼파라미터 버전 * @param hyperVer 하이퍼파라미터 버전
* @param updateReq 수정 요청 * @param updateReq 수정 요청
*/ */
public void updateHyperParam(String hyperVer, ModelMngDto.HyperParamCreateReq updateReq) { public void updateHyperParam(String hyperVer, ModelMngDto.HyperParamCreateReq updateReq) {
// 기존 버전은 수정 불가 // 기존 버전은 수정 불가
throw new BadRequestException("기존 버전은 수정할 수 없습니다. 신규 버전을 생성해주세요."); throw new BadRequestException("기존 버전은 수정할 수 없습니다. 신규 버전을 생성해주세요.");
} }
/** /**
* 하이퍼파라미터 삭제 (논리 삭제) * 하이퍼파라미터 삭제 (논리 삭제)
* *
* @param hyperVer 하이퍼파라미터 버전 * @param hyperVer 하이퍼파라미터 버전
*/ */
public void deleteHyperParam(String hyperVer) { public void deleteHyperParam(String hyperVer) {
// H1은 디폴트 버전이므로 삭제 불가 // H1은 디폴트 버전이므로 삭제 불가
if ("H1".equals(hyperVer)) { if ("H1".equals(hyperVer)) {
throw new BadRequestException("H1은 디폴트 하이퍼파라미터 버전이므로 삭제할 수 없습니다."); throw new BadRequestException("H1은 디폴트 하이퍼파라미터 버전이므로 삭제할 수 없습니다.");
} }
ModelHyperParamEntity entity = ModelHyperParamEntity entity =
hyperParamRepository hyperParamRepository
.findById(hyperVer) .findById(hyperVer)
.orElseThrow(() -> new NotFoundException("하이퍼파라미터를 찾을 수 없습니다: " + hyperVer)); .orElseThrow(() -> new NotFoundException("하이퍼파라미터를 찾을 수 없습니다: " + hyperVer));
if ("Y".equals(entity.getDelYn())) { if ("Y".equals(entity.getDelYn())) {
throw new BadRequestException("이미 삭제된 하이퍼파라미터입니다: " + hyperVer); throw new BadRequestException("이미 삭제된 하이퍼파라미터입니다: " + hyperVer);
} }
// 논리 삭제 처리 // 논리 삭제 처리
entity.setDelYn("Y"); entity.setDelYn("Y");
hyperParamRepository.save(entity); hyperParamRepository.save(entity);
} }
/** /**
* 첫 번째 하이퍼파라미터 버전 조회 (H1 확인용) * 첫 번째 하이퍼파라미터 버전 조회 (H1 확인용)
* *
* @return 첫 번째 하이퍼파라미터 버전 * @return 첫 번째 하이퍼파라미터 버전
*/ */
public String getFirstHyperParamVersion() { public String getFirstHyperParamVersion() {
List<ModelHyperParamEntity> entities = List<ModelHyperParamEntity> entities =
hyperParamRepository.findByDelYnOrderByCreatedDttmDesc("N"); hyperParamRepository.findByDelYnOrderByCreatedDttmDesc("N");
if (entities.isEmpty()) { if (entities.isEmpty()) {
throw new NotFoundException("하이퍼파라미터가 존재하지 않습니다."); throw new NotFoundException("하이퍼파라미터가 존재하지 않습니다.");
} }
// 가장 오래된 것이 H1이므로 리스트의 마지막 요소 반환 // 가장 오래된 것이 H1이므로 리스트의 마지막 요소 반환
return entities.get(entities.size() - 1).getHyperVer(); return entities.get(entities.size() - 1).getHyperVer();
} }
} }

View File

@@ -1,72 +1,72 @@
package com.kamco.cd.training.postgres.core; package com.kamco.cd.training.postgres.core;
import com.kamco.cd.training.common.exception.NotFoundException; import com.kamco.cd.training.common.exception.NotFoundException;
import com.kamco.cd.training.common.service.BaseCoreService; import com.kamco.cd.training.common.service.BaseCoreService;
import com.kamco.cd.training.dataset.dto.MapSheetDto; import com.kamco.cd.training.dataset.dto.MapSheetDto;
import com.kamco.cd.training.postgres.entity.MapSheetEntity; import com.kamco.cd.training.postgres.entity.MapSheetEntity;
import com.kamco.cd.training.postgres.repository.dataset.MapSheetRepository; import com.kamco.cd.training.postgres.repository.dataset.MapSheetRepository;
import java.util.Optional; import java.util.Optional;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import org.springframework.data.domain.Page; import org.springframework.data.domain.Page;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
@Service @Service
@RequiredArgsConstructor @RequiredArgsConstructor
public class MapSheetCoreService public class MapSheetCoreService
implements BaseCoreService<MapSheetDto.Basic, Long, MapSheetDto.SearchReq> { implements BaseCoreService<MapSheetDto.Basic, Long, MapSheetDto.SearchReq> {
private final MapSheetRepository mapSheetRepository; private final MapSheetRepository mapSheetRepository;
@Override @Override
public void remove(Long id) { public void remove(Long id) {
Optional<MapSheetEntity> mapSheet = mapSheetRepository.findById(id); Optional<MapSheetEntity> mapSheet = mapSheetRepository.findById(id);
if (mapSheet.isEmpty()) { if (mapSheet.isEmpty()) {
return; return;
} }
MapSheetEntity entity = mapSheet.get(); MapSheetEntity entity = mapSheet.get();
entity.setDeleted(true); entity.setDeleted(true);
mapSheetRepository.save(entity); mapSheetRepository.save(entity);
} }
@Override @Override
public MapSheetDto.Basic getOneById(Long id) { public MapSheetDto.Basic getOneById(Long id) {
MapSheetEntity entity = MapSheetEntity entity =
mapSheetRepository mapSheetRepository
.findById(id) .findById(id)
.orElseThrow(() -> new NotFoundException("도엽을 찾을 수 없습니다. ID: " + id)); .orElseThrow(() -> new NotFoundException("도엽을 찾을 수 없습니다. ID: " + id));
if (entity.getDeleted()) { if (entity.getDeleted()) {
throw new NotFoundException("삭제된 도엽입니다. ID: " + id); throw new NotFoundException("삭제된 도엽입니다. ID: " + id);
} }
return entity.toDto(); return entity.toDto();
} }
@Override @Override
public Page<MapSheetDto.Basic> search(MapSheetDto.SearchReq searchReq) { public Page<MapSheetDto.Basic> search(MapSheetDto.SearchReq searchReq) {
Page<MapSheetEntity> entityPage = mapSheetRepository.findMapSheetList(searchReq); Page<MapSheetEntity> entityPage = mapSheetRepository.findMapSheetList(searchReq);
return entityPage.map(MapSheetEntity::toDto); return entityPage.map(MapSheetEntity::toDto);
} }
/** /**
* 도엽 목록 조회 * 도엽 목록 조회
* *
* @param searchReq 검색 조건 * @param searchReq 검색 조건
* @return 페이징 처리된 도엽 목록 * @return 페이징 처리된 도엽 목록
*/ */
public Page<MapSheetDto.Basic> findMapSheetList(MapSheetDto.SearchReq searchReq) { public Page<MapSheetDto.Basic> findMapSheetList(MapSheetDto.SearchReq searchReq) {
return search(searchReq); return search(searchReq);
} }
/** /**
* 도엽 삭제 (다건) * 도엽 삭제 (다건)
* *
* @param deleteReq 삭제 요청 * @param deleteReq 삭제 요청
*/ */
public void deleteMapSheets(MapSheetDto.DeleteReq deleteReq) { public void deleteMapSheets(MapSheetDto.DeleteReq deleteReq) {
for (Long id : deleteReq.getItemIds()) { for (Long id : deleteReq.getItemIds()) {
remove(id); remove(id);
} }
} }
} }

View File

@@ -1,167 +1,168 @@
package com.kamco.cd.training.postgres.core; package com.kamco.cd.training.postgres.core;
import com.kamco.cd.training.auth.BCryptSaltGenerator; import com.kamco.cd.training.auth.BCryptSaltGenerator;
import com.kamco.cd.training.common.enums.StatusType; import com.kamco.cd.training.common.enums.StatusType;
import com.kamco.cd.training.common.enums.error.AuthErrorCode; import com.kamco.cd.training.common.enums.error.AuthErrorCode;
import com.kamco.cd.training.common.exception.CustomApiException; import com.kamco.cd.training.common.exception.CustomApiException;
import com.kamco.cd.training.common.utils.CommonStringUtils; import com.kamco.cd.training.common.utils.CommonStringUtils;
import com.kamco.cd.training.common.utils.UserUtil; import com.kamco.cd.training.common.utils.UserUtil;
import com.kamco.cd.training.members.dto.MembersDto; 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.AddReq;
import com.kamco.cd.training.members.dto.MembersDto.Basic; import com.kamco.cd.training.members.dto.MembersDto.Basic;
import com.kamco.cd.training.members.dto.SignInRequest; 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;
import com.kamco.cd.training.members.exception.MemberException.DuplicateMemberException.Field; import com.kamco.cd.training.members.exception.MemberException.DuplicateMemberException.Field;
import com.kamco.cd.training.members.exception.MemberException.MemberNotFoundException; import com.kamco.cd.training.members.exception.MemberException.MemberNotFoundException;
import com.kamco.cd.training.postgres.entity.MemberEntity; import com.kamco.cd.training.postgres.entity.MemberEntity;
import com.kamco.cd.training.postgres.repository.members.MembersRepository; import com.kamco.cd.training.postgres.repository.members.MembersRepository;
import java.time.ZonedDateTime; import java.time.ZonedDateTime;
import java.util.UUID; import java.util.UUID;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
import org.mindrot.jbcrypt.BCrypt; import org.mindrot.jbcrypt.BCrypt;
import org.springframework.data.domain.Page; import org.springframework.data.domain.Page;
import org.springframework.http.HttpStatus; import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
@Service @Service
@RequiredArgsConstructor @RequiredArgsConstructor
public class MembersCoreService { public class MembersCoreService {
private final MembersRepository membersRepository; private final MembersRepository membersRepository;
private final UserUtil userUtil; private final UserUtil userUtil;
/** /**
* 관리자 계정 등록 * 관리자 계정 등록
* *
* @param addReq * @param addReq
* @return * @return
*/ */
public Long saveMembers(AddReq addReq) { public Long saveMembers(AddReq addReq) {
if (membersRepository.existsByEmployeeNo(addReq.getEmployeeNo())) { if (membersRepository.existsByEmployeeNo(addReq.getEmployeeNo())) {
throw new DuplicateMemberException(Field.EMPLOYEE_NO, addReq.getEmployeeNo()); throw new DuplicateMemberException(Field.EMPLOYEE_NO, addReq.getEmployeeNo());
} }
// salt 생성, 사번이 salt // salt 생성, 사번이 salt
String salt = BCryptSaltGenerator.generateSaltWithEmployeeNo(addReq.getEmployeeNo().trim()); String salt = BCryptSaltGenerator.generateSaltWithEmployeeNo(addReq.getEmployeeNo().trim());
// 패스워드 암호화, 초기 패스워드 고정 // 패스워드 암호화, 초기 패스워드 고정
String hashedPassword = BCrypt.hashpw(addReq.getPassword(), salt); String hashedPassword = BCrypt.hashpw(addReq.getPassword(), salt);
MemberEntity memberEntity = new MemberEntity(); MemberEntity memberEntity = new MemberEntity();
memberEntity.setUserId(addReq.getEmployeeNo()); memberEntity.setUserId(addReq.getEmployeeNo());
memberEntity.setUserRole(addReq.getUserRole()); memberEntity.setUserRole(addReq.getUserRole());
memberEntity.setPassword(hashedPassword); memberEntity.setPassword(hashedPassword);
memberEntity.setName(addReq.getName()); memberEntity.setName(addReq.getName());
memberEntity.setEmployeeNo(addReq.getEmployeeNo()); memberEntity.setEmployeeNo(addReq.getEmployeeNo());
memberEntity.setRgstrUidl(userUtil.getId()); memberEntity.setRgstrUidl(userUtil.getId());
memberEntity.setStatus(StatusType.PENDING.getId()); memberEntity.setStatus(StatusType.PENDING.getId());
return membersRepository.save(memberEntity).getId(); return membersRepository.save(memberEntity).getId();
} }
/** /**
* 관리자 계정 수정 * 관리자 계정 수정
* *
* @param uuid * @param uuid
* @param updateReq * @param updateReq
*/ */
public void updateMembers(UUID uuid, MembersDto.UpdateReq updateReq) { public void updateMembers(UUID uuid, MembersDto.UpdateReq updateReq) {
MemberEntity memberEntity = MemberEntity memberEntity =
membersRepository.findByUUID(uuid).orElseThrow(MemberNotFoundException::new); membersRepository.findByUUID(uuid).orElseThrow(MemberNotFoundException::new);
if (StringUtils.isNotBlank(updateReq.getName())) { if (StringUtils.isNotBlank(updateReq.getName())) {
memberEntity.setName(updateReq.getName()); memberEntity.setName(updateReq.getName());
} }
if (StringUtils.isNotBlank(updateReq.getStatus())) { if (StringUtils.isNotBlank(updateReq.getStatus())) {
memberEntity.changeStatus(updateReq.getStatus()); memberEntity.changeStatus(updateReq.getStatus());
} }
if (StringUtils.isNotBlank(updateReq.getPassword())) { if (StringUtils.isNotBlank(updateReq.getPassword())) {
// 패스워드 유효성 검사 // 패스워드 유효성 검사
if (!CommonStringUtils.isValidPassword(updateReq.getPassword())) { if (!CommonStringUtils.isValidPassword(updateReq.getPassword())) {
throw new CustomApiException("WRONG_PASSWORD", HttpStatus.BAD_REQUEST); throw new CustomApiException("WRONG_PASSWORD", HttpStatus.BAD_REQUEST);
} }
String password = String password =
CommonStringUtils.hashPassword(updateReq.getPassword(), memberEntity.getEmployeeNo()); CommonStringUtils.hashPassword(updateReq.getPassword(), memberEntity.getEmployeeNo());
memberEntity.setStatus(StatusType.PENDING.getId()); memberEntity.setStatus(StatusType.PENDING.getId());
memberEntity.setLoginFailCount(0); memberEntity.setLoginFailCount(0);
memberEntity.setPassword(password); memberEntity.setPassword(password);
memberEntity.setPwdResetYn(true); memberEntity.setPwdResetYn(true);
} }
memberEntity.setUpdtrUid(userUtil.getId()); memberEntity.setUpdtrUid(userUtil.getId());
membersRepository.save(memberEntity); membersRepository.save(memberEntity);
} }
/** /**
* 패스워드 변경 * 패스워드 변경
* *
* @param id * @param id
*/ */
public void resetPassword(String id, MembersDto.InitReq initReq) { public void resetPassword(String id, MembersDto.InitReq initReq) {
MemberEntity memberEntity = MemberEntity memberEntity =
membersRepository.findByEmployeeNo(id).orElseThrow(() -> new MemberNotFoundException()); membersRepository.findByEmployeeNo(id).orElseThrow(() -> new MemberNotFoundException());
// 기존 패스워드 확인 // 기존 패스워드 확인
if (!BCrypt.checkpw(initReq.getOldPassword(), memberEntity.getPassword())) { if (!BCrypt.checkpw(initReq.getOldPassword(), memberEntity.getPassword())) {
throw new CustomApiException(AuthErrorCode.LOGIN_PASSWORD_MISMATCH); throw new CustomApiException(AuthErrorCode.LOGIN_PASSWORD_MISMATCH);
} }
String password = String password =
CommonStringUtils.hashPassword(initReq.getOldPassword(), memberEntity.getEmployeeNo()); CommonStringUtils.hashPassword(initReq.getOldPassword(), memberEntity.getEmployeeNo());
memberEntity.setPassword(password); memberEntity.setPassword(password);
memberEntity.setStatus(StatusType.ACTIVE.getId()); memberEntity.setStatus(StatusType.ACTIVE.getId());
memberEntity.setUpdatedDttm(ZonedDateTime.now()); memberEntity.setUpdatedDttm(ZonedDateTime.now());
memberEntity.setUpdtrUid(memberEntity.getId()); memberEntity.setUpdtrUid(memberEntity.getId());
memberEntity.setPwdResetYn(false); memberEntity.setPwdResetYn(false);
membersRepository.save(memberEntity); membersRepository.save(memberEntity);
} }
// //
/** /**
* 회원목록 조회 * 회원목록 조회
* *
* @param searchReq * @param searchReq
* @return * @return
*/ */
public Page<Basic> findByMembers(MembersDto.SearchReq searchReq) { public Page<Basic> findByMembers(MembersDto.SearchReq searchReq) {
Page<MemberEntity> entityPage = membersRepository.findByMembers(searchReq); Page<MemberEntity> entityPage = membersRepository.findByMembers(searchReq);
return entityPage.map(MemberEntity::toDto); return entityPage.map(MemberEntity::toDto);
} }
/** /**
* 사용자 상태 조회 * 사용자 상태 조회
* *
* @param request * @param request
* @return * @return
*/ */
public String getUserStatus(SignInRequest request) { public String getUserStatus(SignInRequest request) {
MemberEntity memberEntity = MemberEntity memberEntity =
membersRepository membersRepository
.findByEmployeeNo(request.getUsername()) .findByEmployeeNo(request.getUsername())
.orElseThrow(() -> new CustomApiException("LOGIN_ID_NOT_FOUND", HttpStatus.UNAUTHORIZED)); .orElseThrow(
return memberEntity.getStatus(); () -> new CustomApiException("LOGIN_ID_NOT_FOUND", HttpStatus.UNAUTHORIZED));
} return memberEntity.getStatus();
}
/**
* 최초 로그인 저장 마지막 로그인 저장 /**
* * 최초 로그인 저장 마지막 로그인 저장
* @param uuid *
*/ * @param uuid
public void saveLogin(UUID uuid) { */
MemberEntity memberEntity = public void saveLogin(UUID uuid) {
membersRepository.findByUUID(uuid).orElseThrow(() -> new MemberNotFoundException()); MemberEntity memberEntity =
membersRepository.findByUUID(uuid).orElseThrow(() -> new MemberNotFoundException());
if (memberEntity.getFirstLoginDttm() == null) {
memberEntity.setFirstLoginDttm(ZonedDateTime.now()); if (memberEntity.getFirstLoginDttm() == null) {
} memberEntity.setFirstLoginDttm(ZonedDateTime.now());
memberEntity.setLastLoginDttm(ZonedDateTime.now()); }
memberEntity.setLoginFailCount(0); memberEntity.setLastLoginDttm(ZonedDateTime.now());
membersRepository.save(memberEntity); memberEntity.setLoginFailCount(0);
} membersRepository.save(memberEntity);
} }
}

View File

@@ -1,19 +1,19 @@
package com.kamco.cd.training.postgres.core; package com.kamco.cd.training.postgres.core;
import com.kamco.cd.training.menu.dto.MenuDto; import com.kamco.cd.training.menu.dto.MenuDto;
import com.kamco.cd.training.postgres.entity.MenuEntity; import com.kamco.cd.training.postgres.entity.MenuEntity;
import com.kamco.cd.training.postgres.repository.menu.MenuRepository; import com.kamco.cd.training.postgres.repository.menu.MenuRepository;
import java.util.List; import java.util.List;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
@Service @Service
@RequiredArgsConstructor @RequiredArgsConstructor
public class MenuCoreService { public class MenuCoreService {
private final MenuRepository menuRepository; private final MenuRepository menuRepository;
public List<MenuDto.Basic> getFindAll() { public List<MenuDto.Basic> getFindAll() {
return menuRepository.getFindAll().stream().map(MenuEntity::toDto).toList(); return menuRepository.getFindAll().stream().map(MenuEntity::toDto).toList();
} }
} }

View File

@@ -1,202 +1,202 @@
package com.kamco.cd.training.postgres.core; package com.kamco.cd.training.postgres.core;
import com.kamco.cd.training.common.exception.BadRequestException; import com.kamco.cd.training.common.exception.BadRequestException;
import com.kamco.cd.training.common.exception.NotFoundException; import com.kamco.cd.training.common.exception.NotFoundException;
import com.kamco.cd.training.model.dto.ModelMngDto; 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.Basic;
import com.kamco.cd.training.postgres.entity.ModelDatasetMappEntity; import com.kamco.cd.training.postgres.entity.ModelDatasetMappEntity;
import com.kamco.cd.training.postgres.entity.ModelTrainMasterEntity; 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.ModelDatasetMappRepository;
import com.kamco.cd.training.postgres.repository.model.ModelMngRepository; import com.kamco.cd.training.postgres.repository.model.ModelMngRepository;
import java.time.ZonedDateTime; import java.time.ZonedDateTime;
import java.util.List; import java.util.List;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import org.springframework.data.domain.Page; import org.springframework.data.domain.Page;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
@Service @Service
@RequiredArgsConstructor @RequiredArgsConstructor
public class ModelMngCoreService { public class ModelMngCoreService {
private final ModelMngRepository modelMngRepository; private final ModelMngRepository modelMngRepository;
private final ModelDatasetMappRepository modelDatasetMappRepository; private final ModelDatasetMappRepository modelDatasetMappRepository;
/** /**
* 모델 목록 조회 * 모델 목록 조회
* *
* @param searchReq 검색 조건 * @param searchReq 검색 조건
* @return 페이징 처리된 모델 목록 * @return 페이징 처리된 모델 목록
*/ */
public Page<Basic> findByModels(ModelMngDto.SearchReq searchReq) { public Page<Basic> findByModels(ModelMngDto.SearchReq searchReq) {
Page<ModelTrainMasterEntity> entityPage = modelMngRepository.findByModels(searchReq); Page<ModelTrainMasterEntity> entityPage = modelMngRepository.findByModels(searchReq);
return entityPage.map(ModelTrainMasterEntity::toDto); return entityPage.map(ModelTrainMasterEntity::toDto);
} }
/** /**
* 모델 상세 조회 * 모델 상세 조회
* *
* @param modelUid 모델 UID * @param modelUid 모델 UID
* @return 모델 상세 정보 * @return 모델 상세 정보
*/ */
public ModelMngDto.Detail getModelDetail(Long modelUid) { public ModelMngDto.Detail getModelDetail(Long modelUid) {
ModelTrainMasterEntity entity = ModelTrainMasterEntity entity =
modelMngRepository modelMngRepository
.findById(modelUid) .findById(modelUid)
.orElseThrow(() -> new NotFoundException("모델을 찾을 수 없습니다. ID: " + modelUid)); .orElseThrow(() -> new NotFoundException("모델을 찾을 수 없습니다. ID: " + modelUid));
if (Boolean.TRUE.equals(entity.getDelYn())) { if (Boolean.TRUE.equals(entity.getDelYn())) {
throw new NotFoundException("삭제된 모델입니다. ID: " + modelUid); throw new NotFoundException("삭제된 모델입니다. ID: " + modelUid);
} }
return ModelMngDto.Detail.builder() return ModelMngDto.Detail.builder()
.uuid(entity.getUuid().toString()) .uuid(entity.getUuid().toString())
.modelVer(entity.getModelVer()) .modelVer(entity.getModelVer())
.hyperVer(entity.getHyperVer()) .hyperVer(entity.getHyperVer())
.epochVer(entity.getEpochVer()) .epochVer(entity.getEpochVer())
.processStep(entity.getProcessStep()) .processStep(entity.getProcessStep())
.statusCd(entity.getStatusCd()) .statusCd(entity.getStatusCd())
.trainStartDttm(entity.getTrainStartDttm()) .trainStartDttm(entity.getTrainStartDttm())
.epochCnt(entity.getEpochCnt()) .epochCnt(entity.getEpochCnt())
.datasetRatio(entity.getDatasetRatio()) .datasetRatio(entity.getDatasetRatio())
.bestEpoch(entity.getBestEpoch()) .bestEpoch(entity.getBestEpoch())
.confirmedBestEpoch(entity.getConfirmedBestEpoch()) .confirmedBestEpoch(entity.getConfirmedBestEpoch())
.step1EndDttm(entity.getStep1EndDttm()) .step1EndDttm(entity.getStep1EndDttm())
.step1Duration(entity.getStep1Duration()) .step1Duration(entity.getStep1Duration())
.step2EndDttm(entity.getStep2EndDttm()) .step2EndDttm(entity.getStep2EndDttm())
.step2Duration(entity.getStep2Duration()) .step2Duration(entity.getStep2Duration())
.progressRate(entity.getProgressRate()) .progressRate(entity.getProgressRate())
.createdDttm(entity.getCreatedDttm()) .createdDttm(entity.getCreatedDttm())
.updatedDttm(entity.getUpdatedDttm()) .updatedDttm(entity.getUpdatedDttm())
.modelPath(entity.getModelPath()) .modelPath(entity.getModelPath())
.errorMsg(entity.getErrorMsg()) .errorMsg(entity.getErrorMsg())
.build(); .build();
} }
/** /**
* 모델 상세 조회 (UUID 기반) * 모델 상세 조회 (UUID 기반)
* *
* @param uuid 모델 UUID * @param uuid 모델 UUID
* @return 모델 상세 정보 * @return 모델 상세 정보
*/ */
public ModelMngDto.Detail getModelDetailByUuid(String uuid) { public ModelMngDto.Detail getModelDetailByUuid(String uuid) {
ModelTrainMasterEntity entity = findByUuid(uuid); ModelTrainMasterEntity entity = findByUuid(uuid);
return getModelDetail(entity.getId()); return getModelDetail(entity.getId());
} }
/** /**
* 학습 모델 전체 목록 조회 (삭제되지 않은 것만) * 학습 모델 전체 목록 조회 (삭제되지 않은 것만)
* *
* @return 학습 모델 목록 * @return 학습 모델 목록
*/ */
public List<ModelMngDto.TrainListRes> findAllTrainModels() { public List<ModelMngDto.TrainListRes> findAllTrainModels() {
List<ModelTrainMasterEntity> entities = List<ModelTrainMasterEntity> entities =
modelMngRepository.findByDelYnOrderByCreatedDttmDesc(false); modelMngRepository.findByDelYnOrderByCreatedDttmDesc(false);
return entities.stream() return entities.stream()
.map( .map(
entity -> entity ->
ModelMngDto.TrainListRes.builder() ModelMngDto.TrainListRes.builder()
.uuid(entity.getUuid().toString()) .uuid(entity.getUuid().toString())
.modelVer(entity.getModelVer()) .modelVer(entity.getModelVer())
.status(entity.getStatusCd()) .status(entity.getStatusCd())
.processStep(entity.getProcessStep()) .processStep(entity.getProcessStep())
.trainStartDttm(entity.getTrainStartDttm()) .trainStartDttm(entity.getTrainStartDttm())
.progressRate(entity.getProgressRate()) .progressRate(entity.getProgressRate())
.epochCnt(entity.getEpochCnt()) .epochCnt(entity.getEpochCnt())
.step1EndDttm(entity.getStep1EndDttm()) .step1EndDttm(entity.getStep1EndDttm())
.step1Duration(entity.getStep1Duration()) .step1Duration(entity.getStep1Duration())
.step2EndDttm(entity.getStep2EndDttm()) .step2EndDttm(entity.getStep2EndDttm())
.step2Duration(entity.getStep2Duration()) .step2Duration(entity.getStep2Duration())
.createdDttm(entity.getCreatedDttm()) .createdDttm(entity.getCreatedDttm())
.errorMsg(entity.getErrorMsg()) .errorMsg(entity.getErrorMsg())
.canResume(entity.getCanResume()) .canResume(entity.getCanResume())
.lastCheckpointEpoch(entity.getLastCheckpointEpoch()) .lastCheckpointEpoch(entity.getLastCheckpointEpoch())
.build()) .build())
.toList(); .toList();
} }
/** /**
* 현재 실행 중인 모델 확인 * 현재 실행 중인 모델 확인
* *
* @return 실행 중인 모델 UUID (없으면 null) * @return 실행 중인 모델 UUID (없으면 null)
*/ */
public String findRunningModelUuid() { public String findRunningModelUuid() {
return modelMngRepository return modelMngRepository
.findFirstByStatusCdAndDelYn("RUNNING", false) .findFirstByStatusCdAndDelYn("RUNNING", false)
.map(entity -> entity.getUuid().toString()) .map(entity -> entity.getUuid().toString())
.orElse(null); .orElse(null);
} }
/** /**
* 학습 마스터 생성 * 학습 마스터 생성
* *
* @param trainReq 학습 시작 요청 * @param trainReq 학습 시작 요청
* @return 생성된 모델 Entity * @return 생성된 모델 Entity
*/ */
public ModelTrainMasterEntity createTrainMaster(ModelMngDto.TrainStartReq trainReq) { public ModelTrainMasterEntity createTrainMaster(ModelMngDto.TrainStartReq trainReq) {
ModelTrainMasterEntity entity = new ModelTrainMasterEntity(); ModelTrainMasterEntity entity = new ModelTrainMasterEntity();
entity.setModelVer(trainReq.getHyperVer()); entity.setModelVer(trainReq.getHyperVer());
entity.setHyperVer(trainReq.getHyperVer()); entity.setHyperVer(trainReq.getHyperVer());
entity.setEpochVer(String.valueOf(trainReq.getEpoch())); entity.setEpochVer(String.valueOf(trainReq.getEpoch()));
entity.setProcessStep("STEP1"); entity.setProcessStep("STEP1");
entity.setStatusCd("READY"); entity.setStatusCd("READY");
entity.setTrainStartDttm(ZonedDateTime.now()); entity.setTrainStartDttm(ZonedDateTime.now());
entity.setEpochCnt(trainReq.getEpoch()); entity.setEpochCnt(trainReq.getEpoch());
entity.setDatasetRatio(trainReq.getDatasetRatio()); entity.setDatasetRatio(trainReq.getDatasetRatio());
entity.setDelYn(false); entity.setDelYn(false);
entity.setCreatedDttm(ZonedDateTime.now()); entity.setCreatedDttm(ZonedDateTime.now());
entity.setProgressRate(0); entity.setProgressRate(0);
return modelMngRepository.save(entity); return modelMngRepository.save(entity);
} }
/** /**
* 데이터셋 매핑 생성 * 데이터셋 매핑 생성
* *
* @param modelUid 모델 UID * @param modelUid 모델 UID
* @param datasetIds 데이터셋 ID 목록 * @param datasetIds 데이터셋 ID 목록
*/ */
public void createDatasetMappings(Long modelUid, List<Long> datasetIds) { public void createDatasetMappings(Long modelUid, List<Long> datasetIds) {
for (Long datasetId : datasetIds) { for (Long datasetId : datasetIds) {
ModelDatasetMappEntity mapping = new ModelDatasetMappEntity(); ModelDatasetMappEntity mapping = new ModelDatasetMappEntity();
mapping.setModelUid(modelUid); mapping.setModelUid(modelUid);
mapping.setDatasetUid(datasetId); mapping.setDatasetUid(datasetId);
mapping.setDatasetType("TRAIN"); mapping.setDatasetType("TRAIN");
modelDatasetMappRepository.save(mapping); modelDatasetMappRepository.save(mapping);
} }
} }
/** /**
* UUID로 모델 조회 * UUID로 모델 조회
* *
* @param uuid UUID * @param uuid UUID
* @return 모델 Entity * @return 모델 Entity
*/ */
public ModelTrainMasterEntity findByUuid(String uuid) { public ModelTrainMasterEntity findByUuid(String uuid) {
try { try {
java.util.UUID uuidObj = java.util.UUID.fromString(uuid); java.util.UUID uuidObj = java.util.UUID.fromString(uuid);
return modelMngRepository return modelMngRepository
.findByUuid(uuidObj) .findByUuid(uuidObj)
.orElseThrow(() -> new NotFoundException("모델을 찾을 수 없습니다. UUID: " + uuid)); .orElseThrow(() -> new NotFoundException("모델을 찾을 수 없습니다. UUID: " + uuid));
} catch (IllegalArgumentException e) { } catch (IllegalArgumentException e) {
throw new BadRequestException("잘못된 UUID 형식입니다: " + uuid); throw new BadRequestException("잘못된 UUID 형식입니다: " + uuid);
} }
} }
/** /**
* 모델 삭제 (논리 삭제) * 모델 삭제 (논리 삭제)
* *
* @param uuid UUID * @param uuid UUID
*/ */
public void deleteByUuid(String uuid) { public void deleteByUuid(String uuid) {
ModelTrainMasterEntity entity = findByUuid(uuid); ModelTrainMasterEntity entity = findByUuid(uuid);
// 진행 중인 모델은 삭제 불가 // 진행 중인 모델은 삭제 불가
if ("RUNNING".equals(entity.getStatusCd())) { if ("RUNNING".equals(entity.getStatusCd())) {
throw new BadRequestException("진행 중인 모델은 삭제할 수 없습니다."); throw new BadRequestException("진행 중인 모델은 삭제할 수 없습니다.");
} }
entity.setDelYn(true); entity.setDelYn(true);
entity.setUpdatedDttm(ZonedDateTime.now()); entity.setUpdatedDttm(ZonedDateTime.now());
modelMngRepository.save(entity); modelMngRepository.save(entity);
} }
} }

View File

@@ -1,66 +1,66 @@
package com.kamco.cd.training.postgres.core; package com.kamco.cd.training.postgres.core;
import com.kamco.cd.training.postgres.entity.SystemMetricsEntity; import com.kamco.cd.training.postgres.entity.SystemMetricsEntity;
import com.kamco.cd.training.postgres.repository.SystemMetricsRepository; import com.kamco.cd.training.postgres.repository.SystemMetricsRepository;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional; import org.springframework.transaction.annotation.Transactional;
@Service @Service
@RequiredArgsConstructor @RequiredArgsConstructor
@Transactional(readOnly = true) @Transactional(readOnly = true)
@Slf4j @Slf4j
public class SystemMetricsCoreService { public class SystemMetricsCoreService {
private final SystemMetricsRepository systemMetricsRepository; private final SystemMetricsRepository systemMetricsRepository;
/** /**
* 현재 사용 가능한 저장공간 조회 (MB 단위) * 현재 사용 가능한 저장공간 조회 (MB 단위)
* *
* @return 사용 가능한 저장공간 (MB) * @return 사용 가능한 저장공간 (MB)
*/ */
public long getAvailableStorageMB() { public long getAvailableStorageMB() {
SystemMetricsEntity latestMetrics = SystemMetricsEntity latestMetrics =
systemMetricsRepository systemMetricsRepository
.findLatestMetrics() .findLatestMetrics()
.orElseThrow(() -> new IllegalStateException("시스템 메트릭 정보를 조회할 수 없습니다")); .orElseThrow(() -> new IllegalStateException("시스템 메트릭 정보를 조회할 수 없습니다"));
Long kbmemfree = latestMetrics.getKbmemfree(); Long kbmemfree = latestMetrics.getKbmemfree();
Long kbmemused = latestMetrics.getKbmemused(); Long kbmemused = latestMetrics.getKbmemused();
if (kbmemfree == null || kbmemused == null) { if (kbmemfree == null || kbmemused == null) {
log.warn("시스템 메트릭에 메모리 정보가 없습니다"); log.warn("시스템 메트릭에 메모리 정보가 없습니다");
return 0L; return 0L;
} }
// 남은 용량 = kbmemfree - kbmemused (KB 단위) // 남은 용량 = kbmemfree - kbmemused (KB 단위)
long availableKB = kbmemfree - kbmemused; long availableKB = kbmemfree - kbmemused;
// KB를 MB로 변환 // KB를 MB로 변환
long availableMB = availableKB / 1024; long availableMB = availableKB / 1024;
log.info( log.info(
"사용 가능한 저장공간: {}MB (kbmemfree: {}KB, kbmemused: {}KB)", availableMB, kbmemfree, kbmemused); "사용 가능한 저장공간: {}MB (kbmemfree: {}KB, kbmemused: {}KB)", availableMB, kbmemfree, kbmemused);
return Math.max(0L, availableMB); return Math.max(0L, availableMB);
} }
/** /**
* 학습 실행 가능 여부 확인 (10GB 이상 필요) * 학습 실행 가능 여부 확인 (10GB 이상 필요)
* *
* @return 학습 실행 가능 여부 * @return 학습 실행 가능 여부
*/ */
public boolean isStorageAvailableForTraining() { public boolean isStorageAvailableForTraining() {
long availableMB = getAvailableStorageMB(); long availableMB = getAvailableStorageMB();
long requiredMB = 10 * 1024; // 10GB = 10,240MB long requiredMB = 10 * 1024; // 10GB = 10,240MB
boolean isAvailable = availableMB >= requiredMB; boolean isAvailable = availableMB >= requiredMB;
if (!isAvailable) { if (!isAvailable) {
log.warn("저장공간 부족: 현재 {}MB, 필요 {}MB", availableMB, requiredMB); log.warn("저장공간 부족: 현재 {}MB, 필요 {}MB", availableMB, requiredMB);
} }
return isAvailable; return isAvailable;
} }
} }

View File

@@ -1,57 +1,57 @@
package com.kamco.cd.training.postgres.core; package com.kamco.cd.training.postgres.core;
import com.kamco.cd.training.common.enums.error.AuthErrorCode; import com.kamco.cd.training.common.enums.error.AuthErrorCode;
import com.kamco.cd.training.common.exception.CustomApiException; import com.kamco.cd.training.common.exception.CustomApiException;
import com.kamco.cd.training.postgres.entity.AuthRefreshTokenEntity; import com.kamco.cd.training.postgres.entity.AuthRefreshTokenEntity;
import com.kamco.cd.training.postgres.repository.members.AuthRefreshTokenRepository; import com.kamco.cd.training.postgres.repository.members.AuthRefreshTokenRepository;
import java.time.ZonedDateTime; import java.time.ZonedDateTime;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
@Service @Service
@RequiredArgsConstructor @RequiredArgsConstructor
public class TokenCoreService { public class TokenCoreService {
private final AuthRefreshTokenRepository tokenRepository; private final AuthRefreshTokenRepository tokenRepository;
public void save(String subject, String refreshToken, long validityMs) { public void save(String subject, String refreshToken, long validityMs) {
ZonedDateTime expiresAt = ZonedDateTime.now().plusSeconds(validityMs / 1000); ZonedDateTime expiresAt = ZonedDateTime.now().plusSeconds(validityMs / 1000);
tokenRepository tokenRepository
.findBySubject(subject) .findBySubject(subject)
.ifPresentOrElse( .ifPresentOrElse(
entity -> entity.rotate(refreshToken, expiresAt), entity -> entity.rotate(refreshToken, expiresAt),
() -> () ->
tokenRepository.save(new AuthRefreshTokenEntity(subject, refreshToken, expiresAt))); tokenRepository.save(new AuthRefreshTokenEntity(subject, refreshToken, expiresAt)));
} }
/** /**
* refreshToken을 DB와 비교 검증 * refreshToken을 DB와 비교 검증
* *
* @param subject 사용자 식별(UUID) * @param subject 사용자 식별(UUID)
* @return * @return
*/ */
public String getValidTokenOrThrow(String subject) { public String getValidTokenOrThrow(String subject) {
// DB 기준 유효한 RefreshToken 문자열 반환 (없으면 401 예외) // DB 기준 유효한 RefreshToken 문자열 반환 (없으면 401 예외)
return tokenRepository return tokenRepository
.findBySubjectAndRevokedDttmIsNullAndExpiresDttmAfter(subject, ZonedDateTime.now()) .findBySubjectAndRevokedDttmIsNullAndExpiresDttmAfter(subject, ZonedDateTime.now())
.orElseThrow(() -> new CustomApiException(AuthErrorCode.REFRESH_TOKEN_EXPIRED_OR_REVOKED)) .orElseThrow(() -> new CustomApiException(AuthErrorCode.REFRESH_TOKEN_EXPIRED_OR_REVOKED))
.getToken(); .getToken();
} }
/** /**
* 로그아웃 * 로그아웃
* *
* @param subject * @param subject
*/ */
public void revokeBySubject(String subject) { public void revokeBySubject(String subject) {
AuthRefreshTokenEntity token = AuthRefreshTokenEntity token =
tokenRepository tokenRepository
.findBySubjectAndRevokedDttmIsNullAndExpiresDttmAfter(subject, ZonedDateTime.now()) .findBySubjectAndRevokedDttmIsNullAndExpiresDttmAfter(subject, ZonedDateTime.now())
.orElseThrow(() -> new CustomApiException(AuthErrorCode.REFRESH_TOKEN_MISMATCH)); .orElseThrow(() -> new CustomApiException(AuthErrorCode.REFRESH_TOKEN_MISMATCH));
// save() 호출 안 해도 됨 (영속 상태 + 트랜잭션) // save() 호출 안 해도 됨 (영속 상태 + 트랜잭션)
token.revoke(); token.revoke();
} }
} }

Some files were not shown because too many files have changed in this diff Show More