diff --git a/src/main/java/com/kamco/cd/kamcoback/common/utils/zip/CsvFileProcessor.java b/src/main/java/com/kamco/cd/kamcoback/common/utils/zip/CsvFileProcessor.java new file mode 100644 index 00000000..5028d5b8 --- /dev/null +++ b/src/main/java/com/kamco/cd/kamcoback/common/utils/zip/CsvFileProcessor.java @@ -0,0 +1,33 @@ +package com.kamco.cd.kamcoback.common.utils.zip; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Component; + +@Slf4j +@Component +public class CsvFileProcessor implements ZipEntryProcessor { + + @Override + public boolean supports(String fileName) { + return fileName.toLowerCase().endsWith(".csv"); + } + + @Override + public void process(String fileName, InputStream is) throws IOException { + try (BufferedReader br = new BufferedReader(new InputStreamReader(is))) { + br.lines() + .forEach( + line -> { + String[] cols = line.split(","); + // CSV 처리 + for (String col : cols) { + log.info(col); //TODO : 추후에 csv 파일 읽어서 작업 필요할 때 정의하기 + } + }); + } + } +} diff --git a/src/main/java/com/kamco/cd/kamcoback/common/utils/zip/JsonStreamingFileProcessor.java b/src/main/java/com/kamco/cd/kamcoback/common/utils/zip/JsonStreamingFileProcessor.java new file mode 100644 index 00000000..740a86f2 --- /dev/null +++ b/src/main/java/com/kamco/cd/kamcoback/common/utils/zip/JsonStreamingFileProcessor.java @@ -0,0 +1,73 @@ +package com.kamco.cd.kamcoback.common.utils.zip; + +import com.fasterxml.jackson.core.JsonFactory; +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.core.JsonToken; +import com.fasterxml.jackson.databind.ObjectMapper; +import java.io.IOException; +import java.io.InputStream; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Component; + +@Slf4j +@Component +public class JsonStreamingFileProcessor implements ZipEntryProcessor { + + private final JsonFactory jsonFactory; + + public JsonStreamingFileProcessor(ObjectMapper objectMapper) { + // ZipInputStream 보호용 설정 + objectMapper.configure(JsonParser.Feature.AUTO_CLOSE_SOURCE, false); + this.jsonFactory = objectMapper.getFactory(); + } + + @Override + public boolean supports(String fileName) { + return fileName.toLowerCase().endsWith(".json"); + } + + @Override + public void process(String fileName, InputStream is) throws IOException { + + log.info("JSON process start: {}", fileName); + + JsonParser parser = jsonFactory.createParser(is); + + // JSON 구조에 상관없이 token 단위로 순회 + while (parser.nextToken() != null) { + handleToken(parser); + } + + log.info("JSON process end: {}", fileName); + } + + private void handleToken(JsonParser parser) throws IOException { + JsonToken token = parser.currentToken(); + + if (token == JsonToken.FIELD_NAME) { + String fieldName = parser.getCurrentName(); + //TODO: json 파일 읽어야 할 내용 정의되면 항목 확정하기 + switch (fieldName) { + case "type" -> { + parser.nextToken(); + String type = parser.getValueAsString(); + log.info("type: {}", type); + } + case "name" -> { + parser.nextToken(); + String name = parser.getValueAsString(); + log.info("Name: {}", name); + } + case "features" -> { + parser.nextToken(); + String features = parser.readValueAsTree().toString(); + log.info("features: {}", features); + } + default -> { + parser.nextToken(); + parser.skipChildren(); + } + } + } + } +} diff --git a/src/main/java/com/kamco/cd/kamcoback/common/utils/zip/TextFileProcessor.java b/src/main/java/com/kamco/cd/kamcoback/common/utils/zip/TextFileProcessor.java new file mode 100644 index 00000000..94cb5e94 --- /dev/null +++ b/src/main/java/com/kamco/cd/kamcoback/common/utils/zip/TextFileProcessor.java @@ -0,0 +1,27 @@ +package com.kamco.cd.kamcoback.common.utils.zip; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Component; + +@Slf4j +@Component +public class TextFileProcessor implements ZipEntryProcessor { + + @Override + public boolean supports(String fileName) { + return fileName.toLowerCase().endsWith(".txt"); + } + + @Override + public void process(String fileName, InputStream is) throws IOException { + BufferedReader br = new BufferedReader(new InputStreamReader(is)); + String line; + while ((line = br.readLine()) != null) { + log.info(line); //TODO : 추후 txt 파일 읽어서 작업할 때 정의하기 + } + } +} diff --git a/src/main/java/com/kamco/cd/kamcoback/common/utils/zip/ZipEntryProcessor.java b/src/main/java/com/kamco/cd/kamcoback/common/utils/zip/ZipEntryProcessor.java new file mode 100644 index 00000000..985b6a8b --- /dev/null +++ b/src/main/java/com/kamco/cd/kamcoback/common/utils/zip/ZipEntryProcessor.java @@ -0,0 +1,11 @@ +package com.kamco.cd.kamcoback.common.utils.zip; + +import java.io.IOException; +import java.io.InputStream; + +public interface ZipEntryProcessor { + + boolean supports(String fileName); + + void process(String fileName, InputStream is) throws IOException; +} diff --git a/src/main/java/com/kamco/cd/kamcoback/common/utils/zip/ZipUtils.java b/src/main/java/com/kamco/cd/kamcoback/common/utils/zip/ZipUtils.java new file mode 100644 index 00000000..1fe276b2 --- /dev/null +++ b/src/main/java/com/kamco/cd/kamcoback/common/utils/zip/ZipUtils.java @@ -0,0 +1,49 @@ +package com.kamco.cd.kamcoback.common.utils.zip; + +import java.io.IOException; +import java.io.InputStream; +import java.io.UncheckedIOException; +import java.util.List; +import java.util.zip.ZipEntry; +import java.util.zip.ZipInputStream; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Component; + +@Slf4j +@Component +public class ZipUtils { + + private final List processors; + + public ZipUtils(List processors) { + this.processors = processors; + } + + public void processZip(InputStream zipStream) throws IOException { + try (ZipInputStream zis = new ZipInputStream(zipStream)) { + + ZipEntry entry; + while ((entry = zis.getNextEntry()) != null) { + + if (entry.isDirectory()) { + continue; + } + + String fileName = entry.getName(); + processors.stream() + .filter(p -> p.supports(fileName)) + .findFirst() + .ifPresent( + processor -> { + try { + processor.process(fileName, zis); + } catch (IOException ioe) { + throw new UncheckedIOException(ioe); + } + }); + + zis.closeEntry(); + } + } + } +} diff --git a/src/main/java/com/kamco/cd/kamcoback/model/ModelMngApiController.java b/src/main/java/com/kamco/cd/kamcoback/model/ModelMngApiController.java index 802a24e1..72085a42 100644 --- a/src/main/java/com/kamco/cd/kamcoback/model/ModelMngApiController.java +++ b/src/main/java/com/kamco/cd/kamcoback/model/ModelMngApiController.java @@ -1,5 +1,6 @@ package com.kamco.cd.kamcoback.model; +import com.kamco.cd.kamcoback.common.utils.zip.ZipUtils; import com.kamco.cd.kamcoback.config.api.ApiResponseDto; import com.kamco.cd.kamcoback.model.dto.ModelMngDto; import com.kamco.cd.kamcoback.model.service.ModelMngService; @@ -10,15 +11,21 @@ import io.swagger.v3.oas.annotations.responses.ApiResponse; import io.swagger.v3.oas.annotations.responses.ApiResponses; import io.swagger.v3.oas.annotations.tags.Tag; import jakarta.transaction.Transactional; +import java.io.IOException; import java.time.LocalDate; import lombok.RequiredArgsConstructor; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.domain.Page; +import org.springframework.http.MediaType; import org.springframework.web.bind.annotation.DeleteMapping; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RequestPart; import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.multipart.MultipartFile; @Tag(name = "모델 관리", description = "모델 관리 API") @RequiredArgsConstructor @@ -29,45 +36,54 @@ public class ModelMngApiController { private final ModelMngService modelMngService; + @Autowired + private ZipUtils zipUtils; + @Operation(summary = "모델관리 목록") @GetMapping public ApiResponseDto> 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) { + @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) { ModelMngDto.searchReq searchReq = new ModelMngDto.searchReq(page, size, sortColumn + ",desc"); Page result = - modelMngService.findModelMgmtList(searchReq, startDate, endDate, modelType, searchVal); + modelMngService.findModelMgmtList(searchReq, startDate, endDate, modelType, searchVal); return ApiResponseDto.ok(result); } @Operation(summary = "삭제", description = "모델을 삭제 합니다.") @ApiResponses( - value = { - @ApiResponse( - responseCode = "204", - description = "모델 삭제 성공", - content = - @Content( - mediaType = "application/json", - schema = @Schema(implementation = Long.class))), - @ApiResponse(responseCode = "400", description = "잘못된 요청 데이터", content = @Content), - @ApiResponse(responseCode = "404", description = "코드를 찾을 수 없음", content = @Content), - @ApiResponse(responseCode = "500", description = "서버 오류", content = @Content) - }) + value = { + @ApiResponse( + responseCode = "204", + description = "모델 삭제 성공", + content = + @Content( + mediaType = "application/json", + schema = @Schema(implementation = Long.class))), + @ApiResponse(responseCode = "400", description = "잘못된 요청 데이터", content = @Content), + @ApiResponse(responseCode = "404", description = "코드를 찾을 수 없음", content = @Content), + @ApiResponse(responseCode = "500", description = "서버 오류", content = @Content) + }) @DeleteMapping("/{modelVer}") public ApiResponseDto removeModel( - @io.swagger.v3.oas.annotations.parameters.RequestBody( - description = "모델 삭제 요청 정보", - required = true) - @PathVariable - String modelVer) { + @io.swagger.v3.oas.annotations.parameters.RequestBody( + description = "모델 삭제 요청 정보", + required = true) + @PathVariable + String modelVer) { return ApiResponseDto.okObject(modelMngService.removeModel(modelVer)); } + + @Operation(summary = "모델 zip 파일 업로드", description = "모델 zip 파일 업로드") + @PostMapping(value = "/upload/zip", consumes = MediaType.MULTIPART_FORM_DATA_VALUE) + public void upload(@RequestPart MultipartFile zipFilie) throws IOException { + zipUtils.processZip(zipFilie.getInputStream()); + } }