From 9bf0373779a756847a75a030064b496e436db74f Mon Sep 17 00:00:00 2001 From: bjkim Date: Wed, 17 Sep 2025 19:24:56 +0900 Subject: [PATCH] =?UTF-8?q?[ADD]=20DataGroup=20=EA=B4=80=EB=A6=AC=20?= =?UTF-8?q?=EA=B8=B0=EB=8A=A5=20=EC=B6=94=EA=B0=80=20(Entity,=20Repository?= =?UTF-8?q?,=20Service,=20Controller)=20=EB=B0=8F=20=EB=8D=B0=EC=9D=B4?= =?UTF-8?q?=ED=84=B0=EC=85=8B=20=EC=83=9D=EC=84=B1=20=EB=A1=9C=EC=A7=81=20?= =?UTF-8?q?=EA=B0=9C=EC=84=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controllers/DatasetController.java | 150 ------------------ .../autoflow/controllers/MinIOController.java | 18 ++- .../etri/autoflow/entity/DatasetEntity.java | 72 --------- .../repository/DatasetRepository.java | 8 - .../etri/autoflow/service/DatasetService.java | 134 ---------------- .../specification/DatasetSpecification.java | 80 ---------- 6 files changed, 12 insertions(+), 450 deletions(-) delete mode 100644 src/main/java/kr/re/etri/autoflow/controllers/DatasetController.java delete mode 100644 src/main/java/kr/re/etri/autoflow/entity/DatasetEntity.java delete mode 100644 src/main/java/kr/re/etri/autoflow/repository/DatasetRepository.java delete mode 100644 src/main/java/kr/re/etri/autoflow/service/DatasetService.java delete mode 100644 src/main/java/kr/re/etri/autoflow/specification/DatasetSpecification.java diff --git a/src/main/java/kr/re/etri/autoflow/controllers/DatasetController.java b/src/main/java/kr/re/etri/autoflow/controllers/DatasetController.java deleted file mode 100644 index eb33a34..0000000 --- a/src/main/java/kr/re/etri/autoflow/controllers/DatasetController.java +++ /dev/null @@ -1,150 +0,0 @@ -package kr.re.etri.autoflow.controllers; - -import io.swagger.v3.oas.annotations.Operation; -import io.swagger.v3.oas.annotations.Parameter; -import io.swagger.v3.oas.annotations.enums.ParameterIn; -import io.swagger.v3.oas.annotations.tags.Tag; -import kr.re.etri.autoflow.entity.DatasetEntity; -import kr.re.etri.autoflow.entity.ProjectEntity; -import kr.re.etri.autoflow.payload.request.BaseSearchRequest; -import kr.re.etri.autoflow.service.DatasetService; -import lombok.RequiredArgsConstructor; -import org.springdoc.core.annotations.ParameterObject; -import org.springframework.data.domain.Page; -import org.springframework.http.MediaType; -import org.springframework.http.ResponseEntity; -import org.springframework.web.bind.annotation.*; -import org.springframework.web.multipart.MultipartFile; - -import java.util.List; - -@Tag(name = "데이터셋 API", description = "Dataset CRUD 기능 제공") -@CrossOrigin(origins = "*", maxAge = 3600) -@RestController -@RequestMapping("/api/datasets") -@RequiredArgsConstructor -public class DatasetController { - - private final DatasetService datasetService; - - @Operation(summary = "전체 데이터셋 목록 조회") - @GetMapping - public ResponseEntity> getAllDatasets() { - return ResponseEntity.ok(datasetService.findAll()); - } - - @Operation(summary = "ID로 데이터셋 조회") - @GetMapping("/{id}") - public ResponseEntity getDatasetById( - @Parameter(description = "조회할 데이터셋 ID", required = true, in = ParameterIn.PATH) - @PathVariable("id") Long id) { - - return datasetService.findById(id) - .map(ResponseEntity::ok) - .orElse(ResponseEntity.notFound().build()); - } - - @Operation(summary = "검색 및 페이지네이션 데이터셋 목록 조회") - @GetMapping("/search") - public ResponseEntity> searchDatasets( - @ParameterObject @ModelAttribute BaseSearchRequest request) { - Page page = datasetService.search(request); - return ResponseEntity.ok(page); - } - -// @Operation(summary = "데이터셋 생성 (Swagger 전용, 첨부파일 포함, DTO 미사용)") -// @PostMapping(consumes = MediaType.MULTIPART_FORM_DATA_VALUE) -// public ResponseEntity createDataset( -// @Parameter(description = "데이터셋 이름", required = true) -// @RequestParam("dsNm") String dsNm, -// @Parameter(description = "데이터셋 설명", required = false) -// @RequestParam(value = "dsDesc", required = false) String dsDesc, -// @Parameter(description = "삭제 여부", required = false, example = "N") -// @RequestParam(value = "delYn", required = false, defaultValue = "N") String delYn, -// @Parameter(description = "등록 유저 ID", required = true) -// @RequestParam("regUserId") String regUserId, -// @Parameter(description = "등록 유저 이름", required = false) -// @RequestParam(value = "regUserNm", required = false) String regUserNm, -// @Parameter(description = "첨부파일", required = false) -// @RequestPart(value = "files", required = false) List files -// ) { -// // Entity 생성 -// DatasetEntity dataset = DatasetEntity.builder() -// .dsNm(dsNm) -// .dsDesc(dsDesc) -// .delYn(delYn) -// .regUserId(regUserId) -// .regUserNm(regUserNm) -// .build(); -// -// // Service 호출 (Dataset 저장 + 첨부파일 업로드 처리) -// DatasetEntity savedDataset = datasetService.createWithFiles(dataset, files); -// -// return ResponseEntity.ok(savedDataset); -// } - - - @Operation(summary = "데이터셋 생성 + 첨부파일 업로드 (Swagger 전용)") - @PostMapping(consumes = MediaType.MULTIPART_FORM_DATA_VALUE) - public ResponseEntity createDatasetWithFiles( - @Parameter(description = "데이터셋 이름", required = true) - @RequestParam("dsNm") String dsNm, - @Parameter(description = "데이터셋 설명", required = false) - @RequestParam(value = "dsDesc", required = false) String dsDesc, - @Parameter(description = "삭제 여부", required = false, example = "N") - @RequestParam(value = "delYn", required = false, defaultValue = "N") String delYn, - @Parameter(description = "등록 유저 ID", required = true) - @RequestParam("regUserId") String regUserId, - @Parameter(description = "등록 유저 이름", required = false) - @RequestParam(value = "regUserNm", required = false) String regUserNm, - @Parameter(description = "첨부파일", required = false) - @RequestPart(value = "files", required = false) List files - ) { - DatasetEntity dataset = DatasetEntity.builder() - .dsNm(dsNm) - .dsDesc(dsDesc) - .delYn(delYn) - .regUserId(regUserId) - .regUserNm(regUserNm) - .build(); - - DatasetEntity savedDataset = datasetService.createWithFiles(dataset, files); - return ResponseEntity.ok(savedDataset); - } - - -// -// @Operation(summary = "데이터셋 생성") -// @PostMapping -// public ResponseEntity createDataset(@RequestBody DatasetEntity dataset) { -// try { -// DatasetEntity saved = datasetService.create(dataset); -// return ResponseEntity.ok(saved); -// } catch (IllegalArgumentException e) { -// return ResponseEntity.badRequest().body(e.getMessage()); -// } -// } - -// @Operation(summary = "데이터셋 수정") -// @PutMapping("/{id}") -// public ResponseEntity updateDataset( -// @Parameter(description = "수정할 데이터셋 ID", required = true, in = ParameterIn.PATH) -// @PathVariable("id") Long id, -// @RequestBody DatasetEntity dataset) { // DTO 없이 엔티티 사용 -// return datasetService.update(id, dataset) -// .map(ResponseEntity::ok) -// .orElse(ResponseEntity.notFound().build()); -// } - - - @Operation(summary = "데이터셋 삭제") - @DeleteMapping("/{id}") - public ResponseEntity deleteDataset( - @Parameter(description = "삭제할 데이터셋 ID", required = true, in = ParameterIn.PATH) - @PathVariable("id") Long id) { - if (datasetService.delete(id)) { - return ResponseEntity.noContent().build(); - } - return ResponseEntity.notFound().build(); - } -} diff --git a/src/main/java/kr/re/etri/autoflow/controllers/MinIOController.java b/src/main/java/kr/re/etri/autoflow/controllers/MinIOController.java index e223e62..f3782cd 100644 --- a/src/main/java/kr/re/etri/autoflow/controllers/MinIOController.java +++ b/src/main/java/kr/re/etri/autoflow/controllers/MinIOController.java @@ -19,9 +19,7 @@ import org.springframework.web.multipart.MultipartFile; import java.io.InputStream; import java.time.LocalDateTime; -import java.util.ArrayList; -import java.util.List; -import java.util.UUID; +import java.util.*; @RestController @RequestMapping("/api/minio") @@ -77,7 +75,7 @@ public class MinIOController { @Operation(summary = "파일 업로드", description = "MultipartFile을 MinIO 버킷에 업로드하고 DB에 기록합니다.") @PostMapping(value = "/upload", consumes = MediaType.MULTIPART_FORM_DATA_VALUE) - public ResponseEntity uploadFile( + public ResponseEntity> uploadFile( @Parameter(description = "업로드할 파일") @RequestPart("file") MultipartFile file, @Parameter(description = "저장 경로 (선택)") @@ -87,7 +85,6 @@ public class MinIOController { @RequestParam(value = "version", required = false, defaultValue = "1") Integer version, @RequestParam(value = "regUserId") String regUserId, @RequestParam(value = "refId") Long refId - ) { try (InputStream is = file.getInputStream()) { String storedName = UUID.randomUUID() + "-" + file.getOriginalFilename(); @@ -121,7 +118,15 @@ public class MinIOController { DatasetAttachmentEntity saved = datasetAttachmentRepository.save(attachment); - return ResponseEntity.ok(saved); + // MinIO URL 생성 + String minioUrl = String.format("%s/%s/%s", minioEndpoint, bucketName, objectName); + + Map response = new HashMap<>(); + response.put("attachment", saved); + response.put("minioUrl", minioUrl); + + return ResponseEntity.ok(response); + } catch (Exception e) { log.error("파일 업로드 실패", e); return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).build(); @@ -129,6 +134,7 @@ public class MinIOController { } + @Operation(summary = "다중 파일 업로드", description = "MultipartFile들을 MinIO 버킷에 업로드하고 DB에 기록합니다.") @PostMapping(value = "/upload-multiple", consumes = MediaType.MULTIPART_FORM_DATA_VALUE) public ResponseEntity> uploadFiles( diff --git a/src/main/java/kr/re/etri/autoflow/entity/DatasetEntity.java b/src/main/java/kr/re/etri/autoflow/entity/DatasetEntity.java deleted file mode 100644 index ae2fa51..0000000 --- a/src/main/java/kr/re/etri/autoflow/entity/DatasetEntity.java +++ /dev/null @@ -1,72 +0,0 @@ -package kr.re.etri.autoflow.entity; - -import io.swagger.v3.oas.annotations.media.Schema; -import jakarta.persistence.*; -import lombok.*; -import org.hibernate.annotations.Comment; -import org.springframework.data.annotation.CreatedDate; -import org.springframework.data.annotation.LastModifiedDate; - -import java.time.LocalDateTime; -import java.util.ArrayList; -import java.util.List; - -@Schema(description = "데이터셋") -@Comment("데이터셋") -@Entity -@Table(name = "tb_dataset") -@Getter -@Setter -@NoArgsConstructor -@AllArgsConstructor -@Builder -public class DatasetEntity { - - @Id - @GeneratedValue(strategy = GenerationType.IDENTITY) - @Schema(description = "ID", example = "null") - @Comment("ID") - private Long id; - - @Schema(description = "데이터셋 이름", example = "배터리 상태 데이터 셋") - @Comment("데이터셋 이름") - private String dsNm; - - @Schema(description = "데이터셋 설명", example = "EV6 차량의 배터리 상태 모음") - @Comment("데이터셋 설명") - private String dsDesc; - - @Schema(description = "삭제 여부", example = "N") - @Comment("삭제 여부") - private String delYn; - - @CreatedDate - @Schema(description = "등록 일자") - @Comment("등록 일자") - private LocalDateTime regDate; - - @Schema(description = "등록 유저 ID", example = "system") - @Comment("등록 유저 ID") - private String regUserId; - - @Schema(description = "등록 유저 이름", example = "시스템") - @Comment("등록 유저 이름") - private String regUserNm; - - @LastModifiedDate - @Schema(description = "수정 일자") - @Comment("수정 일자") - private LocalDateTime modDate; - - @Schema(description = "수정 유저 ID", example = "system") - @Comment("수정 유저 ID") - private String modUserId; - - @Schema(description = "수정 유저 이름", example = "시스템") - @Comment("수정 유저 이름") - private String modUserNm; - - @OneToMany - @JoinColumn(name = "refId", referencedColumnName = "id", insertable = false, updatable = false) - private List files = new ArrayList<>(); -} diff --git a/src/main/java/kr/re/etri/autoflow/repository/DatasetRepository.java b/src/main/java/kr/re/etri/autoflow/repository/DatasetRepository.java deleted file mode 100644 index 0bee698..0000000 --- a/src/main/java/kr/re/etri/autoflow/repository/DatasetRepository.java +++ /dev/null @@ -1,8 +0,0 @@ -package kr.re.etri.autoflow.repository; - -import kr.re.etri.autoflow.entity.DatasetEntity; -import org.springframework.data.jpa.repository.JpaRepository; -import org.springframework.data.jpa.repository.JpaSpecificationExecutor; - -public interface DatasetRepository extends JpaRepository, JpaSpecificationExecutor { -} diff --git a/src/main/java/kr/re/etri/autoflow/service/DatasetService.java b/src/main/java/kr/re/etri/autoflow/service/DatasetService.java deleted file mode 100644 index 665aa93..0000000 --- a/src/main/java/kr/re/etri/autoflow/service/DatasetService.java +++ /dev/null @@ -1,134 +0,0 @@ -package kr.re.etri.autoflow.service; - -import kr.re.etri.autoflow.entity.DatasetAttachmentEntity; -import kr.re.etri.autoflow.entity.DatasetEntity; -import kr.re.etri.autoflow.payload.request.BaseSearchRequest; -import kr.re.etri.autoflow.repository.DatasetAttachmentRepository; -import kr.re.etri.autoflow.repository.DatasetRepository; -import kr.re.etri.autoflow.specification.DatasetSpecification; -import lombok.RequiredArgsConstructor; -import org.springframework.beans.BeanUtils; -import org.springframework.data.domain.*; -import org.springframework.data.jpa.domain.Specification; -import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Transactional; -import org.springframework.web.multipart.MultipartFile; - -import java.time.LocalDate; -import java.time.LocalDateTime; -import java.time.format.DateTimeFormatter; -import java.time.format.DateTimeParseException; -import java.util.ArrayList; -import java.util.List; -import java.util.Optional; -import java.util.UUID; - -@Service -@RequiredArgsConstructor -@Transactional(readOnly = true) -public class DatasetService { - - private final DatasetRepository datasetRepository; - private final DatasetAttachmentRepository datasetAttachmentRepository; - - private final DatasetSpecification datasetSpecification; - - private final DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd"); - - public List findAll() { - return datasetRepository.findAll(); - } - - public Optional findById(Long id) { - return datasetRepository.findById(id); - } - - public Page search(BaseSearchRequest request) { - int pageIndex = request.getPage() > 0 ? request.getPage() - 1 : 0; - - Pageable pageable = PageRequest.of( - pageIndex, - request.getSize(), - Sort.by(Sort.Direction.fromString(request.getSortDirection()), request.getSortField()) - ); - - LocalDate startDate = parseDate(request.getStartDate()); - LocalDate endDate = parseDate(request.getEndDate()); - - Specification spec = datasetSpecification.searchByConditions( - request.getSearchType(), - request.getKeyword(), - startDate, - endDate - ); - - return datasetRepository.findAll(spec, pageable); - } - - private LocalDate parseDate(String dateStr) { - if (dateStr == null || dateStr.isBlank()) return null; - try { - return LocalDate.parse(dateStr, formatter); - } catch (DateTimeParseException e) { - throw new IllegalArgumentException("날짜 형식이 잘못되었습니다. yyyy-MM-dd 형식이어야 합니다: " + dateStr); - } - } - - @Transactional - public DatasetEntity create(DatasetEntity dataset) { - return datasetRepository.save(dataset); - } - - @Transactional - public Optional update(Long id) { - return datasetRepository.findById(id); - } - - @Transactional - public boolean delete(Long id) { - if (!datasetRepository.existsById(id)) { - return false; - } - datasetRepository.deleteById(id); - return true; - } - - @Transactional - public DatasetEntity createWithFiles(DatasetEntity dataset, List files) { - DatasetEntity saved = datasetRepository.save(dataset); - - if (files != null && !files.isEmpty()) { - List attachments = new ArrayList<>(); - for (MultipartFile file : files) { - DatasetAttachmentEntity attachment = DatasetAttachmentEntity.builder() - .originalName(file.getOriginalFilename()) - .storedName("df") // 실제 파일 저장 로직 필요 - .contentType(file.getContentType()) - .size(file.getSize()) - .regUserId(dataset.getRegUserId()) - .regDt(LocalDateTime.now()) - .title(file.getOriginalFilename()) - .version(1) - .description("") - .build(); - attachments.add(attachment); - } - // attachment 저장 - datasetAttachmentRepository.saveAll(attachments); - - // 엔티티에 첨부파일 리스트 설정 - saved.setFiles(attachments); - } - - return saved; - } - - // 파일 저장 예시 - private String storeFile(MultipartFile file) { - // TODO: MinIO 또는 로컬 스토리지에 실제 저장 후 경로 반환 - String storedName = UUID.randomUUID() + "-" + file.getOriginalFilename(); - // file.transferTo(new File(uploadPath + storedName)); - return storedName; - } - -} diff --git a/src/main/java/kr/re/etri/autoflow/specification/DatasetSpecification.java b/src/main/java/kr/re/etri/autoflow/specification/DatasetSpecification.java deleted file mode 100644 index cdcfc19..0000000 --- a/src/main/java/kr/re/etri/autoflow/specification/DatasetSpecification.java +++ /dev/null @@ -1,80 +0,0 @@ -package kr.re.etri.autoflow.specification; - -import jakarta.annotation.PostConstruct; -import jakarta.persistence.EntityManager; -import jakarta.persistence.criteria.Predicate; -import jakarta.persistence.metamodel.Attribute; -import jakarta.persistence.metamodel.EntityType; -import jakarta.persistence.metamodel.Metamodel; -import kr.re.etri.autoflow.entity.DatasetEntity; -import lombok.extern.slf4j.Slf4j; -import org.springframework.data.jpa.domain.Specification; -import org.springframework.stereotype.Component; - -import java.time.LocalDate; -import java.util.Set; -import java.util.stream.Collectors; - -@Slf4j -@Component -public class DatasetSpecification { - - private final EntityManager entityManager; - - private Set stringFields; - - public DatasetSpecification(EntityManager entityManager) { - this.entityManager = entityManager; - } - - @PostConstruct - public void init() { - Metamodel metamodel = entityManager.getMetamodel(); - EntityType entityType = metamodel.entity(DatasetEntity.class); - - // 문자열 타입 필드명만 추출 - stringFields = entityType.getAttributes().stream() - .filter(attr -> attr.getJavaType().equals(String.class)) - .map(Attribute::getName) - .collect(Collectors.toSet()); - - log.info("DatasetEntity string fields: {}", stringFields); - } - - public Specification searchByConditions( - String searchType, String keyword, - LocalDate startDate, LocalDate endDate) { - - return (root, query, cb) -> { - Predicate predicate = cb.conjunction(); - - if (keyword != null && !keyword.isEmpty()) { - if (searchType == null || searchType.isEmpty() || - "전체".equalsIgnoreCase(searchType) || "all".equalsIgnoreCase(searchType)) { - Predicate orPredicate = cb.disjunction(); - for (String field : stringFields) { - orPredicate = cb.or(orPredicate, - cb.like(cb.lower(root.get(field)), "%" + keyword.toLowerCase() + "%")); - } - predicate = cb.and(predicate, orPredicate); - - } else if (stringFields.contains(searchType)) { - predicate = cb.and(predicate, - cb.like(cb.lower(root.get(searchType)), "%" + keyword.toLowerCase() + "%")); - } - } - - if (startDate != null) { - predicate = cb.and(predicate, - cb.greaterThanOrEqualTo(root.get("regDate"), startDate.atStartOfDay())); - } - - if (endDate != null) { - predicate = cb.and(predicate, - cb.lessThanOrEqualTo(root.get("regDate"), endDate.atTime(23, 59, 59))); - } - - return predicate; - }; - } -}