From eb0af2b85e80effc3cd8bbfc90fc7bccdd166c38 Mon Sep 17 00:00:00 2001 From: sungwoo8763 Date: Mon, 26 May 2025 19:00:31 +0900 Subject: [PATCH 1/4] =?UTF-8?q?=EB=8D=B0=EC=9D=B4=ED=84=B0=20=EA=B4=80?= =?UTF-8?q?=EB=A6=AC=20=ED=8E=98=EC=9D=B4=EC=A7=80=20=EB=B0=8F=20=EB=8C=80?= =?UTF-8?q?=EC=8B=9C=EB=B3=B4=EB=93=9C=20=EC=97=B0=EA=B2=B0=20=EC=99=84?= =?UTF-8?q?=EB=A3=8C(=EC=82=AC=EC=9A=A9=EC=9E=90=20=EC=A0=9C=EC=99=B8)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/resources/application.properties | 46 ----------------------- 1 file changed, 46 deletions(-) delete mode 100644 src/main/resources/application.properties diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties deleted file mode 100644 index 5d8ed83..0000000 --- a/src/main/resources/application.properties +++ /dev/null @@ -1,46 +0,0 @@ -spring.application.name=Favicon - -spring.mail.host=smtp.gmail.com -spring.mail.port=587 -spring.mail.username=${SPRING_MAIL_USERNAME} -spring.mail.password=${SPRING_MAIL_PASSWORD} -spring.mail.properties.mail.smtp.auth=ture -spring.mail.properties.mail.smtp.starttls.required=true - -spring.data.redis.host=redis -spring.data.redis.port=6379 - -# AWS -aws.s3.bucket-name=${AWS_S3_BUCKET} -aws.s3.region=${AWS_S3_REGION} -aws.s3.access-key=${AWS_S3_ACCESS_KEY_ID} -aws.s3.secret-key=${AWS_S3_SECRET_ACCESS_KEY} - - -# DB -spring.datasource.url=${SPRING_RDS_URL} -spring.datasource.username=${SPRING_RDS_USERNAME} -spring.datasource.password=${SPRING_RDS_PASSWORD} -spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.PostgreSQLDialect -spring.jpa.hibernate.ddl-auto=update -spring.jpa.show-sql=true -spring.datasource.driver-class-name=org.postgresql.Driver - -# tomcat UTF-8 -server.tomcat.uri-encoding=UTF-8 -server.servlet.encoding.charset=UTF-8 -server.servlet.encoding.enabled=true -server.servlet.encoding.force=true - -# api-docs -spring.api-docs.enabled=true -spring.api-docs.version=openapi_3_0 -spring.api-docs.packagesToScan=mokindang.jubging -spring.api-docs.path=/v3/api-docs - -# swagger -springdoc.default-consumes-media-type=application/json -springdoc.auto-tag-classes=true -springdoc.api-docs.groups.enabled=false -springdoc.swagger-ui.operations-sorter=method -springdoc.swagger-ui.path=/swagger-ui.html \ No newline at end of file From 201a0a17f9a7b4f02baf2ec996c83437a35413bd Mon Sep 17 00:00:00 2001 From: sungwoo8763 Date: Mon, 26 May 2025 23:54:44 +0900 Subject: [PATCH 2/4] request download --- .../favicon/config/SecurityConfig.java | 2 +- .../S3FileDownloadServiceImpl.java | 36 +++++++++++++++++++ .../service/S3FileDownloadService.java | 2 ++ .../favicon/user/application/RequestImpl.java | 25 ++++++++++++- .../application/service/RequestService.java | 5 +++ .../user/controller/RequestController.java | 29 +++++++++++++++ 6 files changed, 97 insertions(+), 2 deletions(-) diff --git a/src/main/java/com/capstone/favicon/config/SecurityConfig.java b/src/main/java/com/capstone/favicon/config/SecurityConfig.java index 5907e1e..f17a423 100644 --- a/src/main/java/com/capstone/favicon/config/SecurityConfig.java +++ b/src/main/java/com/capstone/favicon/config/SecurityConfig.java @@ -22,7 +22,7 @@ public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Excepti "/notice/create", "/notice/list", "/notice/{noticeId}", "/notice/view/{noticeId}", "/faq/create", "/faq/{faqId}", "/data-set/filter", "/data-set/count","/data-set/ratio", "/data-set/incrementDownload/{datasetId}", "/data-set/top9", "/data-set/theme", "/data-set/{datasetId}", "/data-set/category/{themeId}", "/data-set/filter", "/faq/list", "/faq/{faqId}", - "/s3/upload", "/s3/delete/{resourceId}", "/analysis", "/data-set/stats", "/request/stats", + "/s3/upload", "/s3/delete/{resourceId}", "/analysis", "/data-set/stats", "/request/stats", "/request/download/{requestId}", "/data-set", "/request/list","/request/list/{requestId}/review", "/request/{requestId}","/request/question", "/request/question/{questionId}", "/request/answer", "/request/answer/{answerId}","/trend/**", "/data-set/search-sorted", "/data-set/search-sorted/{category}", "/v3/api-docs/**", "/swagger-ui/**", "/swagger-ui.html", "/data-set/download/{datasetId}", "/data-set/group-by-theme", "/region").permitAll() diff --git a/src/main/java/com/capstone/favicon/dataset/application/S3FileDownloadServiceImpl.java b/src/main/java/com/capstone/favicon/dataset/application/S3FileDownloadServiceImpl.java index 85c6f0e..2b35aed 100644 --- a/src/main/java/com/capstone/favicon/dataset/application/S3FileDownloadServiceImpl.java +++ b/src/main/java/com/capstone/favicon/dataset/application/S3FileDownloadServiceImpl.java @@ -5,6 +5,7 @@ import com.capstone.favicon.dataset.application.service.ResourceService; import com.capstone.favicon.dataset.application.service.S3FileDownloadService; import com.capstone.favicon.dataset.domain.FileExtension; +import com.capstone.favicon.user.application.service.RequestService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Service; @@ -22,6 +23,8 @@ public class S3FileDownloadServiceImpl extends S3Config implements S3FileDownloa private ResourceService resourceService; @Autowired private FilePathService filePathService; + @Autowired + private RequestService requestService; @Value("${aws.s3.bucket-name}") private String bucketName; @@ -88,4 +91,37 @@ private File createFileWithExtension(String directory, String fileName, FileExte // String encodedFileName = encodeFileName(fileName); return new File(downloadDir, fileName + "." + extension.name().toLowerCase()); } + + + + public File downloadFileFromDataRequest(Long dataRequestId) throws IOException { + String downloadDir = filePathService.getDownloadDir(); + + String fileUrl = requestService.getFileUrlByRequestId(dataRequestId); + FileExtension fileExtension = requestService.getFileExtensionByRequestId(dataRequestId); + + String key = extractKeyFromUrl(fileUrl); + String fileName = extractFileNameFromKey(key); + String encodedFileName = encodeFileName(fileName); + File file = createFileWithExtension(downloadDir, encodedFileName, fileExtension); + + GetObjectRequest getObjectRequest = GetObjectRequest.builder() + .bucket(bucketName) + .key(key) + .build(); + + try (ResponseInputStream s3Object = s3Client.getObject(getObjectRequest); + FileOutputStream fos = new FileOutputStream(file)) { + byte[] buffer = new byte[1024]; + int length; + while ((length = s3Object.read(buffer)) != -1) { + fos.write(buffer, 0, length); + } + } + + return file; + } + + + } \ No newline at end of file diff --git a/src/main/java/com/capstone/favicon/dataset/application/service/S3FileDownloadService.java b/src/main/java/com/capstone/favicon/dataset/application/service/S3FileDownloadService.java index 50b36c1..7d69999 100644 --- a/src/main/java/com/capstone/favicon/dataset/application/service/S3FileDownloadService.java +++ b/src/main/java/com/capstone/favicon/dataset/application/service/S3FileDownloadService.java @@ -5,4 +5,6 @@ public interface S3FileDownloadService { File downloadFile(Long datasetId) throws IOException; + + File downloadFileFromDataRequest(Long dataRequestId) throws IOException; } diff --git a/src/main/java/com/capstone/favicon/user/application/RequestImpl.java b/src/main/java/com/capstone/favicon/user/application/RequestImpl.java index 7400e75..5703da7 100644 --- a/src/main/java/com/capstone/favicon/user/application/RequestImpl.java +++ b/src/main/java/com/capstone/favicon/user/application/RequestImpl.java @@ -1,6 +1,7 @@ package com.capstone.favicon.user.application; import com.capstone.favicon.config.S3Config; +import com.capstone.favicon.dataset.domain.FileExtension; import com.capstone.favicon.user.domain.DataRequest; import com.capstone.favicon.user.domain.Question; import com.capstone.favicon.user.domain.Answer; @@ -113,7 +114,6 @@ public List getAnswersByQuestion(Long questionId) { return answerRepository.findByQuestion_User_UserId(questionId); } - // --- ✨ 추가 기능들 --- @Override @Transactional @@ -241,4 +241,27 @@ public RequestStatsDto getRequestStats() { ); } + + + + @Override + public String getFileUrlByRequestId(Long requestId) { + DataRequest dataRequest = dataRequestRepository.findById(requestId) + .orElseThrow(() -> new IllegalArgumentException("해당 ID의 요청이 존재하지 않습니다: " + requestId)); + return dataRequest.getFileUrl(); + } + + @Override + public FileExtension getFileExtensionByRequestId(Long requestId) { + DataRequest dataRequest = dataRequestRepository.findById(requestId) + .orElseThrow(() -> new IllegalArgumentException("해당 ID의 요청이 존재하지 않습니다: " + requestId)); + return extractExtension(dataRequest.getFileUrl()); + } + + private FileExtension extractExtension(String fileUrl) { + String ext = fileUrl.substring(fileUrl.lastIndexOf('.') + 1).toUpperCase(); + return FileExtension.valueOf(ext); + } + + } \ No newline at end of file diff --git a/src/main/java/com/capstone/favicon/user/application/service/RequestService.java b/src/main/java/com/capstone/favicon/user/application/service/RequestService.java index 89f6e28..f1b925b 100644 --- a/src/main/java/com/capstone/favicon/user/application/service/RequestService.java +++ b/src/main/java/com/capstone/favicon/user/application/service/RequestService.java @@ -1,5 +1,6 @@ package com.capstone.favicon.user.application.service; +import com.capstone.favicon.dataset.domain.FileExtension; import com.capstone.favicon.user.domain.DataRequest; import com.capstone.favicon.user.domain.Question; import com.capstone.favicon.user.domain.Answer; @@ -26,4 +27,8 @@ public interface RequestService { Answer createAnswer(Answer answer); Answer updateAnswer(Long answerId, Answer updatedAnswer); void deleteAnswer(Long answerId); + + + String getFileUrlByRequestId(Long requestId); + FileExtension getFileExtensionByRequestId(Long requestId); } \ No newline at end of file diff --git a/src/main/java/com/capstone/favicon/user/controller/RequestController.java b/src/main/java/com/capstone/favicon/user/controller/RequestController.java index c29f16f..24ee3e6 100644 --- a/src/main/java/com/capstone/favicon/user/controller/RequestController.java +++ b/src/main/java/com/capstone/favicon/user/controller/RequestController.java @@ -1,6 +1,8 @@ package com.capstone.favicon.user.controller; import com.capstone.favicon.config.APIResponse; +import com.capstone.favicon.dataset.application.service.S3FileDownloadService; +import org.springframework.core.io.Resource; import com.capstone.favicon.user.domain.DataRequest; import com.capstone.favicon.user.dto.DataRequestDto; import com.capstone.favicon.user.domain.Question; @@ -8,11 +10,16 @@ import com.capstone.favicon.user.application.service.RequestService; import com.capstone.favicon.user.dto.RequestStatsDto; import lombok.RequiredArgsConstructor; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.core.io.FileSystemResource; +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.io.File; +import java.io.IOException; import java.util.List; @RestController @@ -168,4 +175,26 @@ public ResponseEntity> deleteAnswer(@PathVariable Long answerId) } } + + + @Autowired + private S3FileDownloadService s3FileDownloadService; + + @GetMapping("/download/{requestId}") + public ResponseEntity downloadDataRequestFile(@PathVariable Long requestId) throws IOException { + File downloadedFile = s3FileDownloadService.downloadFileFromDataRequest(requestId); + Resource fileResource = new FileSystemResource(downloadedFile); + String fileName = downloadedFile.getName(); + + return ResponseEntity.ok() + .header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename*=UTF-8''" + fileName) + .contentType(MediaType.APPLICATION_OCTET_STREAM) + .body(fileResource); + } + + + + + + } \ No newline at end of file From 507a243b54c1a0604818c7cf6d13da1660526346 Mon Sep 17 00:00:00 2001 From: sungwoo8763 Date: Wed, 28 May 2025 16:41:59 +0900 Subject: [PATCH 3/4] =?UTF-8?q?=EC=9A=94=EC=B2=AD=20=EB=8B=A4=EC=9A=B4?= =?UTF-8?q?=EB=A1=9C=EB=93=9C=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../capstone/favicon/aws/S3Controller.java | 82 ++++++++++--------- .../controller/s3FileDownloadController.java | 11 +-- src/main/resources/application.properties | 48 ----------- 3 files changed, 48 insertions(+), 93 deletions(-) delete mode 100644 src/main/resources/application.properties diff --git a/src/main/java/com/capstone/favicon/aws/S3Controller.java b/src/main/java/com/capstone/favicon/aws/S3Controller.java index a1606bd..364318e 100644 --- a/src/main/java/com/capstone/favicon/aws/S3Controller.java +++ b/src/main/java/com/capstone/favicon/aws/S3Controller.java @@ -38,48 +38,50 @@ public S3Controller(@Qualifier("s3Config") S3Config s3Config, DatasetRepository } @PostMapping("/upload") - public ResponseEntity> uploadFile(@RequestParam("file") MultipartFile file) { + public ResponseEntity> uploadFile(@RequestParam("files") MultipartFile[] files) { try { - if (file.isEmpty() || file.getOriginalFilename() == null || file.getOriginalFilename().trim().isEmpty()) { - return ResponseEntity.badRequest().body(APIResponse.errorAPI("파일 이름이 올바르지 않습니다.")); + for (MultipartFile file : files) { + if (file.isEmpty() || file.getOriginalFilename() == null || file.getOriginalFilename().trim().isEmpty()) { + return ResponseEntity.badRequest().body(APIResponse.errorAPI("파일 이름이 올바르지 않습니다.")); + } + + String originalFileName = file.getOriginalFilename().trim(); + String directory = "preprocessing"; + String fileUrl = s3Config.uploadFile(file, directory); + + List datasetThemes = datasetThemeRepository.findAll(); + String s3FileName = directory + "/" + originalFileName; + DatasetMetadata metadata = MetadataParser.extractMetadata(s3FileName, datasetThemes); + + DatasetTheme datasetTheme = datasetThemes.stream() + .filter(theme -> theme.getDatasetThemeId().equals(metadata.getDatasetThemeId())) + .findFirst() + .orElseThrow(() -> new IllegalArgumentException("해당 dataset_theme_id가 존재하지 않습니다: " + metadata.getDatasetThemeId())); + + Dataset dataset = datasetRepository + .findByDatasetThemeAndNameAndOrganization(datasetTheme, metadata.getName(), metadata.getOrganization()) + .orElseGet(() -> { + LocalDate lastModified = s3Config.getLastModifiedDate(s3FileName); + return datasetRepository.save(new Dataset( + datasetTheme, + metadata.getName(), + metadata.getTitle(), + metadata.getOrganization(), + metadata.getDescription(), + s3FileName, + LocalDate.now(), + lastModified, + 0, + 0 + )); + }); + + FileExtension type = FileExtension.valueOf(metadata.getType()); + Resource resource = new Resource(dataset, originalFileName, type, fileUrl); + resourceRepository.save(resource); } - String originalFileName = file.getOriginalFilename().trim(); - String directory = "preprocessing"; - String fileUrl = s3Config.uploadFile(file, directory); - - List datasetThemes = datasetThemeRepository.findAll(); - String s3FileName = directory + "/" + originalFileName; - DatasetMetadata metadata = MetadataParser.extractMetadata(s3FileName, datasetThemes); - - DatasetTheme datasetTheme = datasetThemes.stream() - .filter(theme -> theme.getDatasetThemeId().equals(metadata.getDatasetThemeId())) - .findFirst() - .orElseThrow(() -> new IllegalArgumentException("해당 dataset_theme_id가 존재하지 않습니다: " + metadata.getDatasetThemeId())); - - Dataset dataset = datasetRepository - .findByDatasetThemeAndNameAndOrganization(datasetTheme, metadata.getName(), metadata.getOrganization()) - .orElseGet(() -> { - LocalDate lastModified = s3Config.getLastModifiedDate(s3FileName); - return datasetRepository.save(new Dataset( - datasetTheme, - metadata.getName(), - metadata.getTitle(), - metadata.getOrganization(), - metadata.getDescription(), - s3FileName, - LocalDate.now(), - lastModified, - 0, - 0 - )); - }); - - FileExtension type = FileExtension.valueOf(metadata.getType()); - Resource resource = new Resource(dataset, originalFileName, type, fileUrl); - resourceRepository.save(resource); - - return ResponseEntity.ok(APIResponse.successAPI("파일이 업로드되었습니다", fileUrl)); + return ResponseEntity.ok(APIResponse.successAPI("파일이 업로드되었습니다", null)); } catch (IllegalArgumentException | IOException e) { return ResponseEntity.badRequest().body(APIResponse.errorAPI(e.getMessage())); @@ -98,7 +100,7 @@ public ResponseEntity> deleteFile(@PathVariable Long resourc Dataset dataset = resource.getDataset(); s3Config.deleteFile(resource.getResourceUrl()); - dataset.setResource(null); // 연결 해제 + dataset.setResource(null); resourceRepository.delete(resource); resourceRepository.flush(); diff --git a/src/main/java/com/capstone/favicon/dataset/controller/s3FileDownloadController.java b/src/main/java/com/capstone/favicon/dataset/controller/s3FileDownloadController.java index 136f90b..219d872 100644 --- a/src/main/java/com/capstone/favicon/dataset/controller/s3FileDownloadController.java +++ b/src/main/java/com/capstone/favicon/dataset/controller/s3FileDownloadController.java @@ -13,6 +13,7 @@ import java.io.File; import java.io.IOException; +import java.net.URLEncoder; @RestController @RequestMapping("/data-set") @@ -21,16 +22,16 @@ public class s3FileDownloadController { @Autowired private S3FileDownloadService s3FileDownloadService; - @GetMapping(value="/download/{datasetId}", produces = "application/json; charset=UTF-8") - public ResponseEntity> downloadFile(@PathVariable Long datasetId) throws IOException { - + @GetMapping("/download/{datasetId}") + public ResponseEntity downloadFile(@PathVariable Long datasetId) throws IOException { File downloadedFile = s3FileDownloadService.downloadFile(datasetId); Resource fileResource = new FileSystemResource(downloadedFile); String fileName = downloadedFile.getName(); return ResponseEntity.ok() - .header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename*=UTF-8" + fileName) + .header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=\"" + URLEncoder.encode(fileName, "UTF-8") + "\"") .contentType(MediaType.APPLICATION_OCTET_STREAM) - .body(APIResponse.successAPI("success", fileResource)); + .body(fileResource); } + } diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties deleted file mode 100644 index 22e3753..0000000 --- a/src/main/resources/application.properties +++ /dev/null @@ -1,48 +0,0 @@ -spring.application.name=Favicon - -spring.mail.host=smtp.gmail.com -spring.mail.port=587 -spring.mail.username=${SPRING_MAIL_USERNAME} -spring.mail.password=${SPRING_MAIL_PASSWORD} -spring.mail.properties.mail.smtp.auth=ture -spring.mail.properties.mail.smtp.starttls.required=true - -spring.data.redis.host=redis -spring.data.redis.port=6379 - -# AWS -aws.s3.bucket-name=${AWS_S3_BUCKET} -aws.s3.region=${AWS_S3_REGION} -aws.s3.access-key=${AWS_S3_ACCESS_KEY_ID} -aws.s3.secret-key=${AWS_S3_SECRET_ACCESS_KEY} - - -# DB -spring.datasource.url=${SPRING_RDS_URL} -spring.datasource.username=${SPRING_RDS_USERNAME} -spring.datasource.password=${SPRING_RDS_PASSWORD} -spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.PostgreSQLDialect -spring.jpa.hibernate.ddl-auto=update -spring.jpa.show-sql=true -spring.datasource.driver-class-name=org.postgresql.Driver - -# tomcat UTF-8 -server.tomcat.uri-encoding=UTF-8 -server.servlet.encoding.charset=UTF-8 -server.servlet.encoding.enabled=true -server.servlet.encoding.force=true - -# api-docs -spring.api-docs.enabled=true -spring.api-docs.version=openapi_3_0 -spring.api-docs.packagesToScan=mokindang.jubging -spring.api-docs.path=/v3/api-docs - -# swagger -springdoc.default-consumes-media-type=application/json -springdoc.auto-tag-classes=true -springdoc.api-docs.groups.enabled=false -springdoc.swagger-ui.operations-sorter=method -springdoc.swagger-ui.path=/swagger-ui.html - -server.port=8082 \ No newline at end of file From 461206b5362d400d5943c57c37105944149c9269 Mon Sep 17 00:00:00 2001 From: sungwoo8763 Date: Thu, 29 May 2025 16:19:59 +0900 Subject: [PATCH 4/4] =?UTF-8?q?download=20api=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/resources/application.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index 5d8ed83..83c367f 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -7,7 +7,7 @@ spring.mail.password=${SPRING_MAIL_PASSWORD} spring.mail.properties.mail.smtp.auth=ture spring.mail.properties.mail.smtp.starttls.required=true -spring.data.redis.host=redis +spring.data.redis.host=${REDIS_HOST} spring.data.redis.port=6379 # AWS