[ADD] MinIO 활용 파일 업로드, 다운로드 및 삭제 API 추가, 관련 Service 및 Controller 로직 구현, application.properties 파일의 MLflow 비밀번호 수정

main
bjkim 8 months ago
parent 1452b1265a
commit 0a59aa1523

@ -0,0 +1,27 @@
package kr.re.etri.autoflow.common;
import lombok.AllArgsConstructor;
import lombok.Getter;
import java.util.Map;
@AllArgsConstructor
@Getter
public enum MinioType {
TYPE1("mlpipeline", "http://192.168.10.135:31795", "minio", "minio123"),
TYPE2("mlflow-artifacts", "http://192.168.10.135:31000", "admin", "YAt76ajBtr");
private final String bucket;
private final String endpoint;
private final String accessKey;
private final String secretKey;
public static final Map<String, MinioType> TYPE_MAP = Map.of(
"type1", TYPE1,
"type2", TYPE2
);
public static MinioType of(String type) {
return TYPE_MAP.getOrDefault(type.toLowerCase(), TYPE1);
}
}

@ -0,0 +1,136 @@
package kr.re.etri.autoflow.controllers;
import kr.re.etri.autoflow.entity.MinioAttachmentEntity;
import kr.re.etri.autoflow.payload.request.ProjectBaseSearchRequest;
import kr.re.etri.autoflow.service.DynamicMinioAttachmentService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.domain.Page;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@Slf4j
@RestController
@RequestMapping("/api/minio")
@RequiredArgsConstructor
public class DynamicMinioAttachmentController {
private final DynamicMinioAttachmentService minioService;
/**
*
*/
@PostMapping("/upload")
public ResponseEntity<Map<String, Object>> uploadFile(
@RequestParam MultipartFile file,
@RequestParam(required = false) String path,
@RequestParam Long refId,
@RequestParam(defaultValue = "DATASET") String refType,
@RequestParam(required = false) String title,
@RequestParam(required = false) String description,
@RequestParam(defaultValue = "1") Integer version,
@RequestParam String regUserId,
@RequestParam Long projectId,
@RequestParam(defaultValue = "type1") String type
) {
try {
MinioAttachmentEntity attachment = minioService.uploadFile(
file, path, refId, refType, title, description, version, regUserId, projectId, type
);
Map<String, Object> response = new HashMap<>();
response.put("attachment", attachment);
return ResponseEntity.ok(response);
} catch (Exception e) {
log.error("파일 업로드 실패", e);
return ResponseEntity.internalServerError()
.body(Map.of("error", e.getMessage()));
}
}
/**
*
*/
@GetMapping("/download")
public ResponseEntity<byte[]> downloadFile(
@RequestParam String objectName,
@RequestParam(defaultValue = "type1") String type
) {
try {
byte[] bytes = minioService.downloadFile(objectName, type);
String encodedFileName = URLEncoder.encode(objectName, StandardCharsets.UTF_8)
.replaceAll("\\+", "%20");
return ResponseEntity.ok()
.header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename*=UTF-8''" + encodedFileName)
.contentType(MediaType.APPLICATION_OCTET_STREAM)
.body(bytes);
} catch (Exception e) {
log.error("파일 다운로드 실패", e);
return ResponseEntity.internalServerError().build();
}
}
/**
* YAML
*/
@GetMapping(value = "/readYamlText", produces = MediaType.TEXT_PLAIN_VALUE)
public ResponseEntity<String> readYamlText(
@RequestParam String objectName,
@RequestParam(defaultValue = "type1") String type
) {
try {
String content = minioService.readYamlText(objectName, type);
return ResponseEntity.ok(content);
} catch (Exception e) {
log.error("MinIO YAML 읽기 실패: {}", objectName, e);
return ResponseEntity.internalServerError()
.body("Error reading file: " + e.getMessage());
}
}
/**
*
*/
@DeleteMapping("/delete")
public ResponseEntity<Map<String, Object>> deleteFile(
@RequestParam Long id,
@RequestParam(defaultValue = "type1") String type
) {
boolean result = minioService.deleteFile(id, type);
return ResponseEntity.ok(Map.of("deleted", result));
}
/**
*
*/
@GetMapping("/list")
public ResponseEntity<List<MinioAttachmentEntity>> listAll() {
List<MinioAttachmentEntity> list = minioService.findAll();
return ResponseEntity.ok(list);
}
/**
* +
*/
@PostMapping("/search")
public ResponseEntity<Page<MinioAttachmentEntity>> search(
@RequestBody ProjectBaseSearchRequest request,
@RequestParam String refType,
@RequestParam Integer refId
) {
Page<MinioAttachmentEntity> page = minioService.search(request, refType, refId);
return ResponseEntity.ok(page);
}
}

@ -23,7 +23,7 @@ public class MlflowController {
public MlflowController() { public MlflowController() {
this.webClient = WebClient.builder() this.webClient = WebClient.builder()
.baseUrl("http://192.168.10.135:30128/api/2.0/mlflow") .baseUrl("http://192.168.10.135:30128/api/2.0/mlflow")
.defaultHeaders(headers -> headers.setBasicAuth("user", "aBZ1RzEDGutI")) .defaultHeaders(headers -> headers.setBasicAuth("user", "WjWjIi13KEkO"))
.build(); .build();
} }

@ -0,0 +1,205 @@
package kr.re.etri.autoflow.service;
import io.minio.GetObjectArgs;
import io.minio.MinioClient;
import io.minio.PutObjectArgs;
import io.minio.RemoveObjectArgs;
import jakarta.transaction.Transactional;
import kr.re.etri.autoflow.common.MinioType;
import kr.re.etri.autoflow.entity.MinioAttachmentEntity;
import kr.re.etri.autoflow.payload.request.ProjectBaseSearchRequest;
import kr.re.etri.autoflow.repository.MinioAttachmentRepository;
import kr.re.etri.autoflow.specification.MinioAttachmentSpecification;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
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.web.multipart.MultipartFile;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import java.time.format.DateTimeParseException;
import java.util.List;
import java.util.Optional;
import java.util.UUID;
@Slf4j
@Service
@RequiredArgsConstructor
@Transactional
public class DynamicMinioAttachmentService {
private final MinioAttachmentRepository minioAttachmentRepository;
private final MinioAttachmentSpecification minioAttachmentSpecification;
private final DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd");
/** MinioClient 생성 (type 기반) */
private MinioClient getClientByType(String type) {
MinioType config = MinioType.of(type);
return MinioClient.builder()
.endpoint(config.getEndpoint())
.credentials(config.getAccessKey(), config.getSecretKey())
.build();
}
/** Bucket 조회 (type 기반) */
private String getBucketByType(String type) {
return MinioType.of(type).getBucket();
}
/** 파일 업로드 & DB 저장 */
public MinioAttachmentEntity uploadFile(MultipartFile file,
String path,
Long refId,
String refType,
String title,
String description,
Integer version,
String regUserId,
Long projectId,
String type
) throws Exception {
MinioClient client = getClientByType(type);
String bucketName = getBucketByType(type);
try (InputStream is = file.getInputStream()) {
String storedName = UUID.randomUUID() + "-" + file.getOriginalFilename();
String objectName = (path == null || path.isEmpty())
? storedName
: path + "/" + storedName;
// MinIO 업로드
client.putObject(
PutObjectArgs.builder()
.bucket(bucketName)
.object(objectName)
.stream(is, is.available(), -1)
.contentType(file.getContentType())
.build()
);
// DB 저장
MinioAttachmentEntity attachment = MinioAttachmentEntity.builder()
.refId(refId)
.refType(refType)
.originalName(file.getOriginalFilename())
.storedName(storedName)
.contentType(file.getContentType())
.size(file.getSize())
.storagePath(objectName)
.title(title != null ? title : file.getOriginalFilename())
.version(version)
.description(description)
.regUserId(regUserId)
.projectId(projectId)
.build();
return minioAttachmentRepository.save(attachment);
}
}
/** 파일 다운로드 */
public byte[] downloadFile(String objectName, String type) {
MinioClient client = getClientByType(type);
String bucketName = getBucketByType(type);
try (InputStream is = client.getObject(
GetObjectArgs.builder().bucket(bucketName).object(objectName).build()
)) {
return is.readAllBytes();
} catch (Exception e) {
throw new RuntimeException("MinIO 파일 다운로드 실패: " + objectName, e);
}
}
/** YAML 텍스트 읽기 */
public String readYamlText(String objectName, String type) {
MinioClient client = getClientByType(type);
String bucketName = getBucketByType(type);
try (InputStream is = client.getObject(
GetObjectArgs.builder().bucket(bucketName).object(objectName).build()
)) {
return new String(is.readAllBytes(), StandardCharsets.UTF_8);
} catch (Exception e) {
throw new RuntimeException("MinIO YAML 읽기 실패: " + objectName, e);
}
}
/** 파일 삭제 */
public boolean deleteFile(Long id, String type) {
Optional<MinioAttachmentEntity> attachmentOpt = minioAttachmentRepository.findById(id);
if (attachmentOpt.isEmpty()) return false;
MinioAttachmentEntity attachment = attachmentOpt.get();
MinioClient client = getClientByType(type);
String bucketName = getBucketByType(type);
try {
client.removeObject(
RemoveObjectArgs.builder()
.bucket(bucketName)
.object(attachment.getStoragePath())
.build()
);
minioAttachmentRepository.deleteById(id);
return true;
} catch (Exception e) {
log.error("MinIO 파일 삭제 실패: " + attachment.getStoragePath(), e);
return false;
}
}
// 이하 기존 search, findById, update 등 기존 DB 관련 메서드는 그대로 재사용 가능
public List<MinioAttachmentEntity> findAll() {
return minioAttachmentRepository.findAll();
}
public Optional<MinioAttachmentEntity> findById(Long id) {
return minioAttachmentRepository.findById(id);
}
public Page<MinioAttachmentEntity> search(ProjectBaseSearchRequest request, String refType, Integer refId) {
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<MinioAttachmentEntity> spec =
minioAttachmentSpecification.searchByConditions(
refType,
refId,
request.getSearchType(),
request.getKeyword(),
startDate,
endDate
);
if (request.getProjectId() != null) {
spec = spec.and((root, query, cb) ->
cb.equal(root.get("projectId"), request.getProjectId())
);
}
return minioAttachmentRepository.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);
}
}
}

@ -63,7 +63,7 @@ kubeflow.url=http://192.168.10.135:32473/
# MLflow # MLflow
mlflow.url=http://192.168.10.135:30128/ mlflow.url=http://192.168.10.135:30128/
mlflow.user=user mlflow.user=user
mlflow.password=aBZ1RzEDGutI mlflow.password=WjWjIi13KEkO
server.servlet.encoding.charset=UTF-8 server.servlet.encoding.charset=UTF-8
server.servlet.encoding.enabled=true server.servlet.encoding.enabled=true

Loading…
Cancel
Save