Merge pull request 'feat/dev_251201' (#79) from feat/dev_251201 into develop

Reviewed-on: https://kamco.gitea.gs.dabeeo.com/dabeeo/kamco-dabeeo-backoffice/pulls/79
This commit is contained in:
2025-12-19 11:55:17 +09:00
18 changed files with 210 additions and 768 deletions

View File

@@ -1,43 +0,0 @@
package com.kamco.cd.kamcoback.model;
import com.kamco.cd.kamcoback.config.api.ApiResponseDto;
import com.kamco.cd.kamcoback.model.dto.ModelMgmtDto;
import com.kamco.cd.kamcoback.model.service.ModelMgmtService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.transaction.Transactional;
import java.time.LocalDate;
import lombok.RequiredArgsConstructor;
import org.springframework.data.domain.Page;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
@Tag(name = "모델 관리", description = "모델 관리 API")
@RequiredArgsConstructor
@RestController
@RequestMapping("/api/model")
@Transactional
public class ModelMgmtApiController {
private final ModelMgmtService modelMgmtService;
@Operation(summary = "모델관리 목록")
@GetMapping
public ApiResponseDto<Page<ModelMgmtDto.ModelList>> findModelMgmtList(
@RequestParam(required = false) LocalDate startDate,
@RequestParam(required = false) LocalDate endDate,
@RequestParam(required = false, defaultValue = "createCompleteDttm") String sortColumn,
@RequestParam(required = false) String modelType,
@RequestParam(required = false) String searchVal,
@RequestParam(defaultValue = "0") int page,
@RequestParam(defaultValue = "20") int size) {
ModelMgmtDto.searchReq searchReq = new ModelMgmtDto.searchReq(page, size, sortColumn + ",desc");
Page<ModelMgmtDto.ModelList> result =
modelMgmtService.findModelMgmtList(searchReq, startDate, endDate, modelType, searchVal);
return ApiResponseDto.ok(result);
}
}

View File

@@ -1,104 +1,42 @@
package com.kamco.cd.kamcoback.model; package com.kamco.cd.kamcoback.model;
import com.kamco.cd.kamcoback.code.dto.CommonCodeDto;
import com.kamco.cd.kamcoback.config.api.ApiResponseDto; import com.kamco.cd.kamcoback.config.api.ApiResponseDto;
import com.kamco.cd.kamcoback.model.dto.ModelMngDto; import com.kamco.cd.kamcoback.model.dto.ModelMngDto;
import com.kamco.cd.kamcoback.model.dto.ModelVerDto;
import com.kamco.cd.kamcoback.model.service.ModelMngService; import com.kamco.cd.kamcoback.model.service.ModelMngService;
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.Schema;
import io.swagger.v3.oas.annotations.responses.ApiResponse;
import io.swagger.v3.oas.annotations.responses.ApiResponses;
import io.swagger.v3.oas.annotations.tags.Tag; import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.transaction.Transactional; import jakarta.transaction.Transactional;
import java.time.LocalDate; import java.time.LocalDate;
import java.util.List;
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.web.bind.annotation.*; import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
@Tag(name = "모델 관리", description = "모델 관리 API") @Tag(name = "모델 관리", description = "모델 관리 API")
@RequiredArgsConstructor @RequiredArgsConstructor
@RestController @RestController
@RequestMapping("/api/model/deprecated") @RequestMapping("/api/model")
@Transactional @Transactional
@Deprecated
public class ModelMngApiController { public class ModelMngApiController {
private final ModelMngService modelMngService; private final ModelMngService modelMngService;
@Operation(summary = "목록 조회", description = "모든 모델 조회") @Operation(summary = "모델관리 목록")
@ApiResponses(
value = {
@ApiResponse(
responseCode = "200",
description = "조회 성공",
content =
@Content(
mediaType = "application/json",
schema = @Schema(implementation = CommonCodeDto.Basic.class))),
@ApiResponse(responseCode = "404", description = "코드를 찾을 수 없음", content = @Content),
@ApiResponse(responseCode = "500", description = "서버 오류", content = @Content)
})
@GetMapping @GetMapping
public ApiResponseDto<List<ModelMngDto.Basic>> getFindAll() { public ApiResponseDto<Page<ModelMngDto.ModelList>> findModelMgmtList(
return ApiResponseDto.ok(modelMngService.findModelMngAll());
}
/**
* 최종 등록 모델 정보
*
* @return ModelMngDto.FinalModelDto
*/
@Operation(summary = "최종 등록 모델 조회", description = "최종 등록 모델 조회")
@GetMapping("/final-model-info")
public ApiResponseDto<Optional<ModelMngDto.FinalModelDto>> getFinalModelInfo() {
return ApiResponseDto.ok(modelMngService.getFinalModelInfo());
}
/**
* 모델 등록 => 모델, 버전 동시 등록 (UI 상 따로 등록하는 곳 없음)
*
* @param addReq 모델 입력 값
* @return ModelVerDto.Basic
*/
@Operation(summary = "모델 등록", description = "모델 등록")
@PostMapping
public ApiResponseDto<ModelVerDto.Basic> save(@RequestBody ModelMngDto.AddReq addReq) {
return ApiResponseDto.createOK(modelMngService.save(addReq));
}
@Operation(summary = "모델 수정", description = "모델 수정")
@PutMapping("/{id}")
public ApiResponseDto<Long> update(
@PathVariable Long id, @RequestBody ModelMngDto.AddReq addReq) {
return ApiResponseDto.ok(modelMngService.update(id, addReq));
}
@Operation(summary = "모델 삭제", description = "모델 삭제")
@DeleteMapping("/{id}")
public ApiResponseDto<Long> delete(@PathVariable Long id) {
return ApiResponseDto.deleteOk(modelMngService.delete(id));
}
@Operation(summary = "모델 등록 이력", description = "모델 등록 이력")
@GetMapping("/reg-history")
public ApiResponseDto<Page<ModelMngDto.ModelRegHistory>> getRegHistoryList(
@RequestParam(required = false) LocalDate startDate, @RequestParam(required = false) LocalDate startDate,
@RequestParam(required = false) LocalDate endDate, @RequestParam(required = false) LocalDate endDate,
@RequestParam int page, @RequestParam(required = false, defaultValue = "createCompleteDttm") String sortColumn,
@RequestParam(defaultValue = "20") int size, @RequestParam(required = false) String modelType,
@RequestParam(required = false) String searchVal, @RequestParam(required = false) String searchVal,
@RequestParam(required = false) String searchColumn) { @RequestParam(defaultValue = "0") int page,
ModelMngDto.searchReq searchReq = @RequestParam(defaultValue = "20") int size) {
new ModelMngDto.searchReq( ModelMngDto.searchReq searchReq = new ModelMngDto.searchReq(page, size, sortColumn + ",desc");
page, size, Optional.ofNullable(searchColumn).orElse("createdDate") + ",desc");
// searchColumn:: Entity 컬럼명칭으로 -> 기본값 = 등록일 createdDate, (선택) 배포일 deployDttm
Page<ModelMngDto.ModelRegHistory> result = Page<ModelMngDto.ModelList> result =
modelMngService.getRegHistoryList(searchReq, startDate, endDate, searchVal); modelMngService.findModelMgmtList(searchReq, startDate, endDate, modelType, searchVal);
return ApiResponseDto.ok(result); return ApiResponseDto.ok(result);
} }

View File

@@ -1,119 +0,0 @@
package com.kamco.cd.kamcoback.model.dto;
import com.kamco.cd.kamcoback.common.utils.interfaces.JsonFormatDttm;
import io.swagger.v3.oas.annotations.media.Schema;
import java.math.BigDecimal;
import java.time.ZonedDateTime;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
public class ModelMgmtDto {
@Schema(name = "ModelMgmtDto Basic", description = "모델관리 엔티티 기본 정보")
@Getter
@NoArgsConstructor
public static class Basic {
private String modelVer;
private String hyperVer;
private String epochVer;
private String dockerFileNm;
@JsonFormatDttm private ZonedDateTime createCompleteDttm;
@JsonFormatDttm private ZonedDateTime recentUseDttm;
private Boolean deleted;
@JsonFormatDttm private ZonedDateTime createdDttm;
private Long createdUid;
@JsonFormatDttm private ZonedDateTime updatedDttm;
private Long updatedUid;
public Basic(
String modelVer,
String hyperVer,
String epochVer,
String dockerFileNm,
ZonedDateTime createCompleteDttm,
ZonedDateTime recentUseDttm,
Boolean deleted,
ZonedDateTime createdDttm,
Long createdUid,
ZonedDateTime updatedDttm,
Long updatedUid) {
this.modelVer = modelVer;
this.hyperVer = hyperVer;
this.epochVer = epochVer;
this.dockerFileNm = dockerFileNm;
this.createCompleteDttm = createCompleteDttm;
this.recentUseDttm = recentUseDttm;
this.deleted = deleted;
this.createdDttm = createdDttm;
this.createdUid = createdUid;
this.updatedDttm = updatedDttm;
this.updatedUid = updatedUid;
}
}
@Schema(name = "ModelList", description = "모델관리 목록")
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
public static class ModelList {
private Integer rowNum;
private String modelVer;
private String dockerFileNm;
private String modelType;
private String createCompleteDttm;
private String recentUseDttm; // TODO: 추론관리 테이블 나오면 분석진행중일 때 사용중으로 리턴하기
private BigDecimal f1Score;
private BigDecimal precision;
private BigDecimal recall;
private BigDecimal accuracy;
private BigDecimal iou;
private Boolean deleted;
}
@Schema(name = "ModelAddReq", description = "모델 등록 req")
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
public static class AddReq {
private String modelType;
private String dockerFileNm;
private String modelVer;
private String hyperVer;
private String epochVer;
}
@Schema(name = "searchReq", description = "검색 요청")
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
public static class searchReq {
// 페이징 파라미터
private int page = 0;
private int size = 20;
private String sort;
public Pageable toPageable() {
if (sort != null && !sort.isEmpty()) {
String[] sortParams = sort.split(",");
String property = sortParams[0];
Sort.Direction direction =
sortParams.length > 1 ? Sort.Direction.fromString(sortParams[1]) : Sort.Direction.ASC;
return PageRequest.of(page, size, Sort.by(direction, property));
}
return PageRequest.of(page, size);
}
}
}

View File

@@ -0,0 +1,21 @@
package com.kamco.cd.kamcoback.model.dto;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
public class ModelMngBakDto {
@Schema(name = "AddReq", description = "등록 req")
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
public static class AddReq {
private String modelCate;
private String modelVer;
private String modelPath;
}
}

View File

@@ -2,7 +2,7 @@ package com.kamco.cd.kamcoback.model.dto;
import com.kamco.cd.kamcoback.common.utils.interfaces.JsonFormatDttm; import com.kamco.cd.kamcoback.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 java.math.BigDecimal;
import java.time.ZonedDateTime; import java.time.ZonedDateTime;
import lombok.AllArgsConstructor; import lombok.AllArgsConstructor;
import lombok.Getter; import lombok.Getter;
@@ -14,98 +14,86 @@ import org.springframework.data.domain.Sort;
public class ModelMngDto { public class ModelMngDto {
@Schema(name = "ModelMng Basic", description = "모델관리 엔티티 기본 정보") @Schema(name = "ModelMgmtDto Basic", description = "모델관리 엔티티 기본 정보")
@Getter @Getter
@NoArgsConstructor
public static class Basic { public static class Basic {
private final Long id; private String modelVer;
private final String modelNm; private String hyperVer;
private final String modelCate; private String epochVer;
private final String modelPath; private String dockerFileNm;
@JsonFormatDttm private final ZonedDateTime createdDttm; @JsonFormatDttm private ZonedDateTime createCompleteDttm;
private final Long createdUid; @JsonFormatDttm private ZonedDateTime recentUseDttm;
private Boolean deleted;
@JsonFormatDttm private ZonedDateTime createdDttm;
private Long createdUid;
@JsonFormatDttm private final ZonedDateTime updatedDttm; @JsonFormatDttm private ZonedDateTime updatedDttm;
private final Long updatedUid; private Long updatedUid;
private final String modelCntnt;
public Basic( public Basic(
Long id, String modelVer,
String modelNm, String hyperVer,
String modelCate, String epochVer,
String modelPath, String dockerFileNm,
ZonedDateTime createCompleteDttm,
ZonedDateTime recentUseDttm,
Boolean deleted,
ZonedDateTime createdDttm, ZonedDateTime createdDttm,
Long createdUid, Long createdUid,
ZonedDateTime updatedDttm, ZonedDateTime updatedDttm,
Long updatedUid, Long updatedUid) {
String modelCntnt) { this.modelVer = modelVer;
this.id = id; this.hyperVer = hyperVer;
this.modelNm = modelNm; this.epochVer = epochVer;
this.modelCate = modelCate; this.dockerFileNm = dockerFileNm;
this.modelPath = modelPath; this.createCompleteDttm = createCompleteDttm;
this.recentUseDttm = recentUseDttm;
this.deleted = deleted;
this.createdDttm = createdDttm; this.createdDttm = createdDttm;
this.createdUid = createdUid; this.createdUid = createdUid;
this.updatedDttm = updatedDttm; this.updatedDttm = updatedDttm;
this.updatedUid = updatedUid; this.updatedUid = updatedUid;
this.modelCntnt = modelCntnt;
} }
} }
@Schema(name = "FinalModelDto", description = "최종 등록 모델") @Schema(name = "ModelList", description = "모델관리 목록")
@Getter @Getter
public static class FinalModelDto { @Setter
private final Long modelUid; @NoArgsConstructor
private final String modelNm; @AllArgsConstructor
private final String modelCate; public static class ModelList {
private final Long modelVerUid; private Integer rowNum;
private final String modelVer; private String modelVer;
private final String usedState; private String dockerFileNm;
private final String modelState; private String modelType;
private final Double qualityProb; private String createCompleteDttm;
private final String deployState; private String recentUseDttm; // TODO: 추론관리 테이블 나오면 분석진행중일 때 사용중으로 리턴하기
private final String modelPath; private BigDecimal f1Score;
private BigDecimal precision;
public FinalModelDto( private BigDecimal recall;
Long modelUid, private BigDecimal accuracy;
String modelNm, private BigDecimal iou;
String modelCate, private Boolean deleted;
Long modelVerUid,
String modelVer,
String usedState,
String modelState,
Double qualityProb,
String deployState,
String modelPath) {
this.modelUid = modelUid;
this.modelNm = modelNm;
this.modelCate = modelCate;
this.modelVerUid = modelVerUid;
this.modelVer = modelVer;
this.usedState = usedState;
this.deployState = deployState;
this.modelState = modelState;
this.qualityProb = qualityProb;
this.modelPath = modelPath;
}
} }
@Schema(name = "ModelAddReq", description = "모델 버전 등록 req") @Schema(name = "ModelAddReq", description = "모델 등록 req")
@Getter @Getter
@Setter @Setter
@NoArgsConstructor @NoArgsConstructor
@AllArgsConstructor @AllArgsConstructor
public static class AddReq { public static class AddReq {
private String modelNm; private String modelType;
private String modelCate; private String dockerFileNm;
private String modelPath; private String modelVer;
private String hyperVer;
@NotEmpty private String modelVer; private String epochVer;
private String modelCntnt;
} }
@Schema(name = "searchReq", description = "등록이력보기 검색 요청") @Schema(name = "searchReq", description = "검색 요청")
@Getter @Getter
@Setter @Setter
@NoArgsConstructor @NoArgsConstructor
@@ -128,29 +116,4 @@ public class ModelMngDto {
return PageRequest.of(page, size); return PageRequest.of(page, size);
} }
} }
@Schema(name = "ModelRegHistory", description = "모델 등록 이력")
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
public static class ModelRegHistory {
private String modelNm;
private String modelCate;
private String modelVer;
private String strCreatedDttm;
private String usedState;
private String deployState;
private String strDeployDttm;
}
@Schema(name = "ModelDmlReturn", description = "모델 등록/수정/삭제 리턴")
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
public static class ModelDmlReturn {
private String execStatus;
private String message;
}
} }

View File

@@ -1,25 +0,0 @@
package com.kamco.cd.kamcoback.model.service;
import com.kamco.cd.kamcoback.model.dto.ModelMgmtDto;
import com.kamco.cd.kamcoback.postgres.core.ModelMgmtCoreService;
import java.time.LocalDate;
import lombok.RequiredArgsConstructor;
import org.springframework.data.domain.Page;
import org.springframework.stereotype.Service;
@Service
@RequiredArgsConstructor
public class ModelMgmtService {
private final ModelMgmtCoreService modelMgmtCoreService;
public Page<ModelMgmtDto.ModelList> findModelMgmtList(
ModelMgmtDto.searchReq searchReq,
LocalDate startDate,
LocalDate endDate,
String modelType,
String searchVal) {
return modelMgmtCoreService.findModelMgmtList(
searchReq, startDate, endDate, modelType, searchVal);
}
}

View File

@@ -1,45 +1,25 @@
package com.kamco.cd.kamcoback.model.service; package com.kamco.cd.kamcoback.model.service;
import com.kamco.cd.kamcoback.model.dto.ModelMngDto; import com.kamco.cd.kamcoback.model.dto.ModelMngDto;
import com.kamco.cd.kamcoback.model.dto.ModelVerDto;
import com.kamco.cd.kamcoback.postgres.core.ModelMngCoreService; import com.kamco.cd.kamcoback.postgres.core.ModelMngCoreService;
import java.time.LocalDate; import java.time.LocalDate;
import java.util.List;
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;
import org.springframework.transaction.annotation.Transactional;
@Service @Service
@RequiredArgsConstructor @RequiredArgsConstructor
@Transactional(readOnly = true)
public class ModelMngService { public class ModelMngService {
private final ModelMngCoreService modelMngCoreService; private final ModelMngCoreService modelMngCoreService;
public List<ModelMngDto.Basic> findModelMngAll() { public Page<ModelMngDto.ModelList> findModelMgmtList(
return modelMngCoreService.findModelMngAll(); ModelMngDto.searchReq searchReq,
} LocalDate startDate,
LocalDate endDate,
public Optional<ModelMngDto.FinalModelDto> getFinalModelInfo() { String modelType,
return modelMngCoreService.getFinalModelInfo(); String searchVal) {
} return modelMngCoreService.findModelMgmtList(
searchReq, startDate, endDate, modelType, searchVal);
public ModelVerDto.Basic save(ModelMngDto.AddReq addReq) {
return modelMngCoreService.save(addReq);
}
public Long update(Long id, ModelMngDto.AddReq addReq) {
return modelMngCoreService.update(id, addReq);
}
public Long delete(Long id) {
return modelMngCoreService.delete(id);
}
public Page<ModelMngDto.ModelRegHistory> getRegHistoryList(
ModelMngDto.searchReq searchReq, LocalDate startDate, LocalDate endDate, String searchVal) {
return modelMngCoreService.getRegHistoryList(searchReq, startDate, endDate, searchVal);
} }
} }

View File

@@ -1,25 +0,0 @@
package com.kamco.cd.kamcoback.postgres.core;
import com.kamco.cd.kamcoback.model.dto.ModelMgmtDto;
import com.kamco.cd.kamcoback.postgres.repository.model.ModelMgmtRepository;
import java.time.LocalDate;
import lombok.RequiredArgsConstructor;
import org.springframework.data.domain.Page;
import org.springframework.stereotype.Service;
@Service
@RequiredArgsConstructor
public class ModelMgmtCoreService {
private final ModelMgmtRepository modelMgmtRepository;
public Page<ModelMgmtDto.ModelList> findModelMgmtList(
ModelMgmtDto.searchReq searchReq,
LocalDate startDate,
LocalDate endDate,
String modelType,
String searchVal) {
return modelMgmtRepository.findModelMgmtList(
searchReq, startDate, endDate, modelType, searchVal);
}
}

View File

@@ -1,15 +1,8 @@
package com.kamco.cd.kamcoback.postgres.core; package com.kamco.cd.kamcoback.postgres.core;
import com.kamco.cd.kamcoback.model.dto.ModelMngDto; import com.kamco.cd.kamcoback.model.dto.ModelMngDto;
import com.kamco.cd.kamcoback.model.dto.ModelVerDto;
import com.kamco.cd.kamcoback.postgres.entity.ModelMngBakEntity;
import com.kamco.cd.kamcoback.postgres.entity.ModelVerEntity;
import com.kamco.cd.kamcoback.postgres.repository.model.ModelMngRepository; import com.kamco.cd.kamcoback.postgres.repository.model.ModelMngRepository;
import com.kamco.cd.kamcoback.postgres.repository.model.ModelVerRepository;
import jakarta.persistence.EntityNotFoundException;
import java.time.LocalDate; import java.time.LocalDate;
import java.util.List;
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;
@@ -19,72 +12,14 @@ import org.springframework.stereotype.Service;
public class ModelMngCoreService { public class ModelMngCoreService {
private final ModelMngRepository modelMngRepository; private final ModelMngRepository modelMngRepository;
private final ModelVerRepository modelVerRepository;
public List<ModelMngDto.Basic> findModelMngAll() { public Page<ModelMngDto.ModelList> findModelMgmtList(
return modelMngRepository.findModelMngAll().stream().map(ModelMngBakEntity::toDto).toList(); ModelMngDto.searchReq searchReq,
} LocalDate startDate,
LocalDate endDate,
public Optional<ModelMngDto.FinalModelDto> getFinalModelInfo() { String modelType,
return modelMngRepository.getFinalModelInfo(); String searchVal) {
} return modelMngRepository.findModelMgmtList(
searchReq, startDate, endDate, modelType, searchVal);
public ModelVerDto.Basic save(ModelMngDto.AddReq addReq) {
ModelMngBakEntity modelMngBakEntity =
new ModelMngBakEntity(
addReq.getModelNm(),
addReq.getModelCate(),
addReq.getModelPath(),
1L,
1L,
addReq.getModelCntnt()); // TODO: 로그인 기능 붙이면 Uid 넣어야 함
ModelMngBakEntity saved = modelMngRepository.save(modelMngBakEntity);
ModelVerEntity modelVerEntity =
new ModelVerEntity(
saved.getId(),
addReq.getModelCate(),
addReq.getModelVer(),
"NONE",
"NONE",
0.0,
"NONE",
addReq.getModelPath(),
1L,
1L);
return modelVerRepository.save(modelVerEntity).toDto();
}
public Long update(Long id, ModelMngDto.AddReq addReq) {
// 조회
ModelVerEntity existData =
modelVerRepository
.findModelVerById(id)
.orElseThrow(EntityNotFoundException::new); // 데이터 없는 경우 exception
existData.update(addReq);
// TODO: 추후 수정 단계에서 도커파일 업로드하면 버전 업데이트 하는 로직 필요
return existData.getId();
}
public Long delete(Long id) {
// 조회
ModelVerEntity verEntity =
modelVerRepository
.findModelVerById(id)
.orElseThrow(() -> new EntityNotFoundException("버전 id 에 대한 정보를 찾을 수 없습니다. id : " + id));
// usedState가 USED 이거나 이미 삭제된 상태이면 삭제 불가
if (verEntity.getUsedState().equals("USED") || verEntity.isDeleted().equals(true)) {
throw new IllegalStateException("해당 모델이 사용중이라 삭제 불가"); // TODO: 추후 규칙 정의되면 수정 필요
}
// id 코드 deleted = true 업데이트
verEntity.deleted();
return verEntity.getId();
}
public Page<ModelMngDto.ModelRegHistory> getRegHistoryList(
ModelMngDto.searchReq searchReq, LocalDate startDate, LocalDate endDate, String searchVal) {
return modelMngRepository.getRegHistoryList(searchReq, startDate, endDate, searchVal);
} }
} }

View File

@@ -1,6 +1,5 @@
package com.kamco.cd.kamcoback.postgres.entity; package com.kamco.cd.kamcoback.postgres.entity;
import com.kamco.cd.kamcoback.model.dto.ModelMngDto;
import com.kamco.cd.kamcoback.postgres.CommonDateEntity; import com.kamco.cd.kamcoback.postgres.CommonDateEntity;
import jakarta.persistence.*; import jakarta.persistence.*;
import jakarta.validation.constraints.Size; import jakarta.validation.constraints.Size;
@@ -63,17 +62,4 @@ public class ModelMngBakEntity extends CommonDateEntity {
this.createdUid = createdUid; this.createdUid = createdUid;
this.updatedUid = updatedUid; this.updatedUid = updatedUid;
} }
public ModelMngDto.Basic toDto() {
return new ModelMngDto.Basic(
this.id,
this.modelNm,
this.modelCate,
this.modelPath,
super.getCreatedDate(),
this.createdUid,
super.getModifiedDate(),
this.updatedUid,
this.modelCntnt);
}
} }

View File

@@ -10,7 +10,7 @@ import lombok.Setter;
@Setter @Setter
@Entity @Entity
@Table(name = "tb_model_mgmt") @Table(name = "tb_model_mgmt")
public class ModelMgmtEntity extends CommonDateEntity { public class ModelMngEntity extends CommonDateEntity {
@Id @Id
@GeneratedValue(strategy = GenerationType.IDENTITY) @GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "model_uid") @Column(name = "model_uid")

View File

@@ -1,6 +1,6 @@
package com.kamco.cd.kamcoback.postgres.entity; package com.kamco.cd.kamcoback.postgres.entity;
import com.kamco.cd.kamcoback.model.dto.ModelMngDto; import com.kamco.cd.kamcoback.model.dto.ModelMngBakDto;
import com.kamco.cd.kamcoback.model.dto.ModelVerDto; import com.kamco.cd.kamcoback.model.dto.ModelVerDto;
import com.kamco.cd.kamcoback.postgres.CommonDateEntity; import com.kamco.cd.kamcoback.postgres.CommonDateEntity;
import jakarta.persistence.*; import jakarta.persistence.*;
@@ -161,7 +161,7 @@ public class ModelVerEntity extends CommonDateEntity {
this.updatedUid); this.updatedUid);
} }
public void update(ModelMngDto.AddReq addReq) { public void update(ModelMngBakDto.AddReq addReq) {
this.modelCate = addReq.getModelCate(); this.modelCate = addReq.getModelCate();
this.modelVer = addReq.getModelVer(); this.modelVer = addReq.getModelVer();
this.modelPath = addReq.getModelPath(); this.modelPath = addReq.getModelPath();

View File

@@ -1,7 +0,0 @@
package com.kamco.cd.kamcoback.postgres.repository.model;
import com.kamco.cd.kamcoback.postgres.entity.ModelMgmtEntity;
import org.springframework.data.jpa.repository.JpaRepository;
public interface ModelMgmtRepository
extends JpaRepository<ModelMgmtEntity, Integer>, ModelMgmtRepositoryCustom {}

View File

@@ -1,15 +0,0 @@
package com.kamco.cd.kamcoback.postgres.repository.model;
import com.kamco.cd.kamcoback.model.dto.ModelMgmtDto;
import java.time.LocalDate;
import org.springframework.data.domain.Page;
public interface ModelMgmtRepositoryCustom {
Page<ModelMgmtDto.ModelList> findModelMgmtList(
ModelMgmtDto.searchReq searchReq,
LocalDate startDate,
LocalDate endDate,
String modelType,
String searchVal);
}

View File

@@ -1,155 +0,0 @@
package com.kamco.cd.kamcoback.postgres.repository.model;
import static com.kamco.cd.kamcoback.postgres.entity.QModelMgmtEntity.modelMgmtEntity;
import static com.kamco.cd.kamcoback.postgres.entity.QModelResultMetricEntity.modelResultMetricEntity;
import com.kamco.cd.kamcoback.model.dto.ModelMgmtDto;
import com.kamco.cd.kamcoback.postgres.QuerydslOrderUtil;
import com.kamco.cd.kamcoback.postgres.entity.ModelMgmtEntity;
import com.querydsl.core.BooleanBuilder;
import com.querydsl.core.types.Expression;
import com.querydsl.core.types.Projections;
import com.querydsl.core.types.dsl.BooleanExpression;
import com.querydsl.core.types.dsl.Expressions;
import com.querydsl.core.types.dsl.NumberPath;
import com.querydsl.core.types.dsl.StringExpression;
import com.querydsl.jpa.impl.JPAQueryFactory;
import java.math.BigDecimal;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageImpl;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.data.jpa.repository.support.QuerydslRepositorySupport;
public class ModelMgmtRepositoryImpl extends QuerydslRepositorySupport
implements ModelMgmtRepositoryCustom {
private final JPAQueryFactory queryFactory;
private final StringExpression NULL_STRING = Expressions.stringTemplate("cast(null as text)");
public ModelMgmtRepositoryImpl(JPAQueryFactory queryFactory) {
super(ModelMgmtEntity.class);
this.queryFactory = queryFactory;
}
@Override
public Page<ModelMgmtDto.ModelList> findModelMgmtList(
ModelMgmtDto.searchReq searchReq,
LocalDate startDate,
LocalDate endDate,
String modelType,
String searchVal) {
Pageable pageable = searchReq.toPageable();
Sort sort = pageable.getSort();
String property = "createCompleteDttm"; // 기본으로 생성완료일 기준
Map<String, Expression<?>> sortColumnMap =
Map.of(
"createCompleteDttm", modelMgmtEntity.createCompleteDttm,
"recentUseDttm", modelMgmtEntity.recentUseDttm);
if (sort.isSorted()) {
Sort.Order order = sort.iterator().next();
property = order.getProperty();
}
Expression<?> sortColumn = sortColumnMap.get(property);
List<ModelMgmtDto.ModelList> foundContent =
queryFactory
.select(
Projections.constructor(
ModelMgmtDto.ModelList.class,
Expressions.numberTemplate(
Integer.class, "row_number() over(order by {0} desc)", sortColumn),
Expressions.stringTemplate(
"concat({0}, {1}, {2}, {3}, {4})",
modelMgmtEntity.modelVer,
".",
modelMgmtEntity.hyperVer,
".",
modelMgmtEntity.epochVer)
.as("modelVer"),
modelMgmtEntity.dockerFileNm,
modelMgmtEntity.modelVer.as("modelType"),
Expressions.stringTemplate(
"to_char({0}, 'YYYY-MM-DD')", modelMgmtEntity.createCompleteDttm),
Expressions.stringTemplate(
"to_char({0}, 'YYYY-MM-DD')", modelMgmtEntity.recentUseDttm),
roundNumericToPercent(modelResultMetricEntity.f1Score),
roundNumericToPercent(modelResultMetricEntity.precision),
roundNumericToPercent(modelResultMetricEntity.recall),
roundNumericToPercent(modelResultMetricEntity.loss),
roundNumericToPercent(modelResultMetricEntity.iou),
modelMgmtEntity.deleted))
.from(modelMgmtEntity)
.innerJoin(modelResultMetricEntity)
.on(modelMgmtEntity.modelUid.longValue().eq(modelResultMetricEntity.modelUid))
.where(
eventEndedAtBetween(startDate, endDate, property),
searchModelVersion(modelType, searchVal))
.offset(pageable.getOffset())
.limit(pageable.getPageSize())
.orderBy(
QuerydslOrderUtil.getOrderSpecifiers(
pageable, ModelMgmtEntity.class, "modelMgmtEntity"))
.fetch();
Long countQuery =
queryFactory
.select(modelMgmtEntity.modelUid.count())
.from(modelMgmtEntity)
.where(
eventEndedAtBetween(startDate, endDate, property),
searchModelVersion(modelType, searchVal))
.fetchOne();
return new PageImpl<>(foundContent, pageable, countQuery);
}
private BooleanExpression eventEndedAtBetween(
LocalDate startDate, LocalDate endDate, String sortColumn) {
if (Objects.isNull(startDate) || Objects.isNull(endDate)) {
return null;
}
ZoneId zone = ZoneId.systemDefault();
LocalDateTime startDateTime = startDate.atStartOfDay();
LocalDateTime endDateTime = endDate.plusDays(1).atStartOfDay();
if (sortColumn.equals("createCompleteDttm")) {
return modelMgmtEntity
.createCompleteDttm
.goe(startDateTime.atZone(zone))
.and(modelMgmtEntity.createCompleteDttm.lt(endDateTime.atZone(zone)));
} else {
return modelMgmtEntity
.recentUseDttm
.goe(startDateTime.atZone(zone))
.and(modelMgmtEntity.recentUseDttm.lt(endDateTime.atZone(zone)));
}
}
private BooleanBuilder searchModelVersion(String modelType, String searchVal) {
BooleanBuilder builder = new BooleanBuilder();
if (Objects.nonNull(modelType)) {
builder.and(modelMgmtEntity.modelVer.eq(modelType));
}
if (Objects.nonNull(searchVal)) {
builder.and(modelMgmtEntity.dockerFileNm.likeIgnoreCase("%" + searchVal + "%"));
}
return builder;
}
private Expression<BigDecimal> roundNumericToPercent(NumberPath<Double> ratio) {
return Expressions.numberTemplate(BigDecimal.class, "function('round', {0} * 100, 2)", ratio);
}
}

View File

@@ -1,7 +1,7 @@
package com.kamco.cd.kamcoback.postgres.repository.model; package com.kamco.cd.kamcoback.postgres.repository.model;
import com.kamco.cd.kamcoback.postgres.entity.ModelMngBakEntity; import com.kamco.cd.kamcoback.postgres.entity.ModelMngEntity;
import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.JpaRepository;
public interface ModelMngRepository public interface ModelMngRepository
extends JpaRepository<ModelMngBakEntity, Long>, ModelMngRepositoryCustom {} extends JpaRepository<ModelMngEntity, Integer>, ModelMngRepositoryCustom {}

View File

@@ -1,18 +1,15 @@
package com.kamco.cd.kamcoback.postgres.repository.model; package com.kamco.cd.kamcoback.postgres.repository.model;
import com.kamco.cd.kamcoback.model.dto.ModelMngDto; import com.kamco.cd.kamcoback.model.dto.ModelMngDto;
import com.kamco.cd.kamcoback.postgres.entity.ModelMngBakEntity;
import java.time.LocalDate; import java.time.LocalDate;
import java.util.List;
import java.util.Optional;
import org.springframework.data.domain.Page; import org.springframework.data.domain.Page;
public interface ModelMngRepositoryCustom { public interface ModelMngRepositoryCustom {
List<ModelMngBakEntity> findModelMngAll(); Page<ModelMngDto.ModelList> findModelMgmtList(
ModelMngDto.searchReq searchReq,
Optional<ModelMngDto.FinalModelDto> getFinalModelInfo(); LocalDate startDate,
LocalDate endDate,
Page<ModelMngDto.ModelRegHistory> getRegHistoryList( String modelType,
ModelMngDto.searchReq searchReq, LocalDate startDate, LocalDate endDate, String searchVal); String searchVal);
} }

View File

@@ -1,28 +1,30 @@
package com.kamco.cd.kamcoback.postgres.repository.model; package com.kamco.cd.kamcoback.postgres.repository.model;
import static com.kamco.cd.kamcoback.postgres.entity.QModelDeployHstEntity.modelDeployHstEntity; import static com.kamco.cd.kamcoback.postgres.entity.QModelMngEntity.modelMngEntity;
import static com.kamco.cd.kamcoback.postgres.entity.QModelMngBakEntity.modelMngBakEntity; import static com.kamco.cd.kamcoback.postgres.entity.QModelResultMetricEntity.modelResultMetricEntity;
import static com.kamco.cd.kamcoback.postgres.entity.QModelVerEntity.modelVerEntity;
import com.kamco.cd.kamcoback.model.dto.ModelMngDto; import com.kamco.cd.kamcoback.model.dto.ModelMngDto;
import com.kamco.cd.kamcoback.postgres.QuerydslOrderUtil; import com.kamco.cd.kamcoback.postgres.QuerydslOrderUtil;
import com.kamco.cd.kamcoback.postgres.entity.ModelMngBakEntity; import com.kamco.cd.kamcoback.postgres.entity.ModelMngEntity;
import com.kamco.cd.kamcoback.postgres.entity.ModelVerEntity; import com.querydsl.core.BooleanBuilder;
import com.querydsl.core.types.Expression;
import com.querydsl.core.types.Projections; import com.querydsl.core.types.Projections;
import com.querydsl.core.types.dsl.BooleanExpression; import com.querydsl.core.types.dsl.BooleanExpression;
import com.querydsl.core.types.dsl.Expressions; import com.querydsl.core.types.dsl.Expressions;
import com.querydsl.core.types.dsl.NumberPath;
import com.querydsl.core.types.dsl.StringExpression; import com.querydsl.core.types.dsl.StringExpression;
import com.querydsl.jpa.impl.JPAQueryFactory; import com.querydsl.jpa.impl.JPAQueryFactory;
import io.micrometer.common.util.StringUtils; import java.math.BigDecimal;
import java.time.LocalDate; import java.time.LocalDate;
import java.time.LocalDateTime; import java.time.LocalDateTime;
import java.time.ZonedDateTime; import java.time.ZoneId;
import java.util.List; import java.util.List;
import java.util.Map;
import java.util.Objects; import java.util.Objects;
import java.util.Optional;
import org.springframework.data.domain.Page; import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageImpl; import org.springframework.data.domain.PageImpl;
import org.springframework.data.domain.Pageable; import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.data.jpa.repository.support.QuerydslRepositorySupport; import org.springframework.data.jpa.repository.support.QuerydslRepositorySupport;
public class ModelMngRepositoryImpl extends QuerydslRepositorySupport public class ModelMngRepositoryImpl extends QuerydslRepositorySupport
@@ -32,113 +34,122 @@ public class ModelMngRepositoryImpl extends QuerydslRepositorySupport
private final StringExpression NULL_STRING = Expressions.stringTemplate("cast(null as text)"); private final StringExpression NULL_STRING = Expressions.stringTemplate("cast(null as text)");
public ModelMngRepositoryImpl(JPAQueryFactory queryFactory) { public ModelMngRepositoryImpl(JPAQueryFactory queryFactory) {
super(ModelMngBakEntity.class); super(ModelMngEntity.class);
this.queryFactory = queryFactory; this.queryFactory = queryFactory;
} }
@Override @Override
public List<ModelMngBakEntity> findModelMngAll() { public Page<ModelMngDto.ModelList> findModelMgmtList(
return queryFactory.selectFrom(modelMngBakEntity).orderBy(modelMngBakEntity.id.desc()).fetch(); ModelMngDto.searchReq searchReq,
} LocalDate startDate,
LocalDate endDate,
@Override String modelType,
public Optional<ModelMngDto.FinalModelDto> getFinalModelInfo() { String searchVal) {
return queryFactory
.select(
Projections.constructor(
ModelMngDto.FinalModelDto.class,
modelMngBakEntity.id.as("modelUid"),
modelMngBakEntity.modelNm,
modelMngBakEntity.modelCate,
modelVerEntity.id.as("modelVerUid"),
modelVerEntity.modelVer,
Expressions.stringTemplate(
"fn_codenm_to_misc({0}, {1})", 51, modelVerEntity.usedState), // 사용여부 한글 명칭
Expressions.stringTemplate(
"fn_codenm_to_misc({0}, {1})", 51, modelVerEntity.modelState), // 모델상태 한글 명칭
modelVerEntity.qualityProb,
Expressions.stringTemplate(
"fn_codenm_to_misc({0}, {1})", 52, modelVerEntity.deployState), // 배포상태 한글 명칭
modelVerEntity.modelPath))
.from(modelMngBakEntity)
.innerJoin(modelVerEntity)
.on(modelMngBakEntity.id.eq(modelVerEntity.modelUid))
.where(modelVerEntity.usedState.eq("USED")) // USED 인 것 중에
.orderBy(modelVerEntity.modelVer.desc()) // Version 높은 것 기준
.stream()
.findFirst();
}
@Override
public Page<ModelMngDto.ModelRegHistory> getRegHistoryList(
ModelMngDto.searchReq searchReq, LocalDate startDate, LocalDate endDate, String searchVal) {
Pageable pageable = searchReq.toPageable(); Pageable pageable = searchReq.toPageable();
List<ModelMngDto.ModelRegHistory> foundContent = Sort sort = pageable.getSort();
String property = "createCompleteDttm"; // 기본으로 생성완료일 기준
Map<String, Expression<?>> sortColumnMap =
Map.of(
"createCompleteDttm", modelMngEntity.createCompleteDttm,
"recentUseDttm", modelMngEntity.recentUseDttm);
if (sort.isSorted()) {
Sort.Order order = sort.iterator().next();
property = order.getProperty();
}
Expression<?> sortColumn = sortColumnMap.get(property);
List<ModelMngDto.ModelList> foundContent =
queryFactory queryFactory
.select( .select(
Projections.constructor( Projections.constructor(
ModelMngDto.ModelRegHistory.class, ModelMngDto.ModelList.class,
modelMngBakEntity.modelNm, Expressions.numberTemplate(
modelMngBakEntity.modelCate, Integer.class, "row_number() over(order by {0} desc)", sortColumn),
modelVerEntity.modelVer,
Expressions.stringTemplate( Expressions.stringTemplate(
"to_char({0}, 'YYYY-MM-DD')", modelVerEntity.createdDate) "concat({0}, {1}, {2}, {3}, {4})",
.as("createdDttm"), modelMngEntity.modelVer,
".",
modelMngEntity.hyperVer,
".",
modelMngEntity.epochVer)
.as("modelVer"),
modelMngEntity.dockerFileNm,
modelMngEntity.modelVer.as("modelType"),
Expressions.stringTemplate( Expressions.stringTemplate(
"fn_codenm_to_misc({0}, {1})", 51, modelVerEntity.usedState), // 사용여부 한글 명칭 "to_char({0}, 'YYYY-MM-DD')", modelMngEntity.createCompleteDttm),
Expressions.stringTemplate( Expressions.stringTemplate(
"fn_codenm_to_misc({0}, {1})", "to_char({0}, 'YYYY-MM-DD')", modelMngEntity.recentUseDttm),
52, modelVerEntity.deployState), // 배포상태 한글 명칭 roundNumericToPercent(modelResultMetricEntity.f1Score),
Expressions.stringTemplate( roundNumericToPercent(modelResultMetricEntity.precision),
"to_char({0}, 'YYYY-MM-DD')", modelDeployHstEntity.deployDttm) roundNumericToPercent(modelResultMetricEntity.recall),
.as("deployDttm"))) roundNumericToPercent(modelResultMetricEntity.loss),
.from(modelMngBakEntity) roundNumericToPercent(modelResultMetricEntity.iou),
.innerJoin(modelVerEntity) modelMngEntity.deleted))
.on(modelMngBakEntity.id.eq(modelVerEntity.modelUid)) .from(modelMngEntity)
.leftJoin(modelDeployHstEntity) .innerJoin(modelResultMetricEntity)
.on( .on(modelMngEntity.modelUid.longValue().eq(modelResultMetricEntity.modelUid))
modelVerEntity .where(
.id eventEndedAtBetween(startDate, endDate, property),
.eq(modelDeployHstEntity.modelVerUid) searchModelVersion(modelType, searchVal))
.and(modelDeployHstEntity.serverId.eq(1L)) // 1건만 조회해야 하기에 1번 서버만 조회하기
)
.where(eventEndedAtBetween(startDate, endDate), searchModelVerLike(searchVal))
.offset(pageable.getOffset()) .offset(pageable.getOffset())
.limit(pageable.getPageSize()) .limit(pageable.getPageSize())
.orderBy( .orderBy(
QuerydslOrderUtil.getOrderSpecifiers( QuerydslOrderUtil.getOrderSpecifiers(
pageable, ModelVerEntity.class, "modelVerEntity")) pageable, ModelMngEntity.class, "modelMngEntity"))
.fetch(); .fetch();
Long countQuery = Long countQuery =
queryFactory queryFactory
.select(modelVerEntity.id.count()) .select(modelMngEntity.modelUid.count())
.from(modelMngBakEntity) .from(modelMngEntity)
.innerJoin(modelVerEntity) .where(
.on(modelMngBakEntity.id.eq(modelVerEntity.modelUid)) eventEndedAtBetween(startDate, endDate, property),
.where(eventEndedAtBetween(startDate, endDate), searchModelVerLike(searchVal)) searchModelVersion(modelType, searchVal))
.fetchOne(); .fetchOne();
return new PageImpl<>(foundContent, pageable, countQuery); return new PageImpl<>(foundContent, pageable, countQuery);
} }
private BooleanExpression eventEndedAtBetween(LocalDate startDate, LocalDate endDate) { private BooleanExpression eventEndedAtBetween(
LocalDate startDate, LocalDate endDate, String sortColumn) {
if (Objects.isNull(startDate) || Objects.isNull(endDate)) { if (Objects.isNull(startDate) || Objects.isNull(endDate)) {
return null; return null;
} }
ZoneId zone = ZoneId.systemDefault();
LocalDateTime startDateTime = startDate.atStartOfDay(); LocalDateTime startDateTime = startDate.atStartOfDay();
LocalDateTime endDateTime = endDate.plusDays(1).atStartOfDay(); LocalDateTime endDateTime = endDate.plusDays(1).atStartOfDay();
return modelMngBakEntity
.createdDate if (sortColumn.equals("createCompleteDttm")) {
.goe(ZonedDateTime.from(startDateTime)) return modelMngEntity
.and(modelMngBakEntity.modifiedDate.lt(ZonedDateTime.from(endDateTime))); .createCompleteDttm
.goe(startDateTime.atZone(zone))
.and(modelMngEntity.createCompleteDttm.lt(endDateTime.atZone(zone)));
} else {
return modelMngEntity
.recentUseDttm
.goe(startDateTime.atZone(zone))
.and(modelMngEntity.recentUseDttm.lt(endDateTime.atZone(zone)));
}
} }
private BooleanExpression searchModelVerLike(String searchVal) { private BooleanBuilder searchModelVersion(String modelType, String searchVal) {
if (StringUtils.isBlank(searchVal)) { BooleanBuilder builder = new BooleanBuilder();
return null;
if (Objects.nonNull(modelType)) {
builder.and(modelMngEntity.modelVer.eq(modelType));
} }
return modelVerEntity.modelVer.contains(searchVal);
if (Objects.nonNull(searchVal)) {
builder.and(modelMngEntity.dockerFileNm.likeIgnoreCase("%" + searchVal + "%"));
}
return builder;
}
private Expression<BigDecimal> roundNumericToPercent(NumberPath<Double> ratio) {
return Expressions.numberTemplate(BigDecimal.class, "function('round', {0} * 100, 2)", ratio);
} }
} }