From 20b22858cbf9cb6e6dada2cc22f1b56851c869a8 Mon Sep 17 00:00:00 2001 From: hw130 Date: Sun, 28 Jan 2024 14:16:14 +0900 Subject: [PATCH 1/3] =?UTF-8?q?feat:=20=ED=8C=8C=EC=9D=BC=20=EB=8B=A4?= =?UTF-8?q?=EC=9A=B4=EB=A1=9C=EB=93=9C=20&=20=EC=83=81=EC=84=B8=20?= =?UTF-8?q?=EC=A1=B0=ED=9A=8C=20=EA=B8=B0=EB=8A=A5=20=EC=B6=94=EA=B0=80=20?= =?UTF-8?q?(#29)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controller/PhotoController.java | 20 +++++++++ .../global/config/aws/S3Controller.java | 10 ++--- .../global/config/aws/S3Service.java | 41 +++++++++++++++++++ .../config/security/SecurityConfig.java | 3 +- .../developjeans/service/PhotoService.java | 19 +++++++++ 5 files changed, 87 insertions(+), 6 deletions(-) diff --git a/src/main/java/com/example/developjeans/controller/PhotoController.java b/src/main/java/com/example/developjeans/controller/PhotoController.java index 788215f..793e9ae 100644 --- a/src/main/java/com/example/developjeans/controller/PhotoController.java +++ b/src/main/java/com/example/developjeans/controller/PhotoController.java @@ -1,13 +1,17 @@ package com.example.developjeans.controller; import com.example.developjeans.exception.BusinessLogicException; import com.example.developjeans.exception.ExceptionCode; +import com.example.developjeans.global.config.aws.S3Service; import com.example.developjeans.service.PhotoService; import com.fasterxml.jackson.databind.ser.Serializers; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.tags.Tag; +import lombok.Getter; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; +import org.springframework.core.io.UrlResource; import org.springframework.data.domain.Page; +import org.springframework.http.HttpHeaders; import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; @@ -16,6 +20,8 @@ import org.springframework.web.bind.annotation.*; import org.springframework.web.multipart.MultipartFile; +import java.io.IOException; + @RestController @RequestMapping("/api/v1/photo") @@ -85,6 +91,20 @@ public ResponseEntity getChartLikes(@RequestParam String sort, } + + @Operation(summary = "사진 상세 조회") + @GetMapping("/detail") + public ResponseEntity getPhotoDetail(@RequestParam Long photoId){ + return ResponseEntity.ok(photoService.getDetail(photoId)); + } + + @Operation(summary = "사진 다운로드") + @GetMapping("/download") + public ResponseEntity downloadImage(@RequestParam Long photoId) throws IOException { + return ResponseEntity.ok(photoService.downloadImage(photoId)); + } + + // @Operation(summary = "사진 차트", description = "모든 사진을 모아볼 수 있는 기능입니다.") // @GetMapping("/chart") // public ResponseEntity> getChartLikes( diff --git a/src/main/java/com/example/developjeans/global/config/aws/S3Controller.java b/src/main/java/com/example/developjeans/global/config/aws/S3Controller.java index 8d29845..3926c34 100644 --- a/src/main/java/com/example/developjeans/global/config/aws/S3Controller.java +++ b/src/main/java/com/example/developjeans/global/config/aws/S3Controller.java @@ -1,20 +1,20 @@ package com.example.developjeans.global.config.aws; +import io.swagger.v3.oas.annotations.Operation; import lombok.RequiredArgsConstructor; +import org.springframework.core.io.UrlResource; +import org.springframework.http.HttpHeaders; +import org.springframework.http.ResponseEntity; 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; @RestController -@RequestMapping("/api/v1/categories") +@RequestMapping("/api/v1") @RequiredArgsConstructor public class S3Controller { private final S3Service s3Service; -// @GetMapping() -// public BaseResponse getFileList(@RequestParam String categoryName){ -// return new BaseResponse<>(s3Service.getImageList(categoryName)); -// } } diff --git a/src/main/java/com/example/developjeans/global/config/aws/S3Service.java b/src/main/java/com/example/developjeans/global/config/aws/S3Service.java index 25ed761..182692b 100644 --- a/src/main/java/com/example/developjeans/global/config/aws/S3Service.java +++ b/src/main/java/com/example/developjeans/global/config/aws/S3Service.java @@ -4,10 +4,15 @@ import com.amazonaws.services.s3.AmazonS3; import com.amazonaws.services.s3.AmazonS3Client; import com.amazonaws.services.s3.model.*; +import com.amazonaws.util.IOUtils; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Value; +import org.springframework.core.io.UrlResource; +import org.springframework.http.HttpHeaders; import org.springframework.http.HttpStatus; +import org.springframework.http.MediaType; +import org.springframework.http.ResponseEntity; import org.springframework.stereotype.Component; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -17,10 +22,13 @@ import java.io.File; import java.io.FileOutputStream; import java.io.IOException; +import java.net.URLEncoder; import java.time.LocalDateTime; import java.time.format.DateTimeFormatter; import java.util.*; +import static org.springframework.web.servlet.function.RequestPredicates.contentType; + @Slf4j @Service @Transactional @@ -104,6 +112,39 @@ public String getFile(String fileName) { throw new ResponseStatusException(HttpStatus.FORBIDDEN, "파일이 없습니다"); } } + + public ResponseEntity download(String fileUrl) throws IOException { // 객체 다운 fileUrl : 폴더명/파일네임.파일확장자 + + S3Object s3Object = amazonS3.getObject(new GetObjectRequest(bucket, fileUrl)); + S3ObjectInputStream objectInputStream = s3Object.getObjectContent(); + byte[] bytes = IOUtils.toByteArray(objectInputStream); + + HttpHeaders httpHeaders = new HttpHeaders(); + httpHeaders.setContentType(contentType(fileUrl)); + httpHeaders.setContentLength(bytes.length); + + String[] arr = fileUrl.split("/"); + String type = arr[arr.length - 1]; + String fileName = URLEncoder.encode(type, "UTF-8").replaceAll("\\+", "%20"); + httpHeaders.setContentDispositionFormData("attachment", fileName); // 파일이름 지정 + + return new ResponseEntity<>(bytes, httpHeaders, HttpStatus.OK); + } + + private MediaType contentType(String keyname) { + String[] arr = keyname.split("\\."); + String type = arr[arr.length - 1]; + switch (type) { + case "txt": + return MediaType.TEXT_PLAIN; + case "png": + return MediaType.IMAGE_PNG; + case "jpg": + return MediaType.IMAGE_JPEG; + default: + return MediaType.APPLICATION_OCTET_STREAM; + } + } } /* @Service diff --git a/src/main/java/com/example/developjeans/global/config/security/SecurityConfig.java b/src/main/java/com/example/developjeans/global/config/security/SecurityConfig.java index 812b5c4..ea481ae 100644 --- a/src/main/java/com/example/developjeans/global/config/security/SecurityConfig.java +++ b/src/main/java/com/example/developjeans/global/config/security/SecurityConfig.java @@ -77,7 +77,8 @@ public SecurityFilterChain filterChain(HttpSecurity http, HandlerMappingIntrospe .requestMatchers(new MvcRequestMatcher(introspector, "/swagger-ui/**")).permitAll() .requestMatchers(new MvcRequestMatcher(introspector, "/v3/api-docs/**")).permitAll() .requestMatchers(new MvcRequestMatcher(introspector, "/api/v1/photo/chart")).permitAll() - .requestMatchers(new MvcRequestMatcher(introspector, "/api/v1/categories/**")).permitAll() + .requestMatchers(new MvcRequestMatcher(introspector, "/api/v1/photo/detail")).permitAll() + .requestMatchers(new MvcRequestMatcher(introspector, "/api/v1/photo/download")).permitAll() // 나머지는 시큐리티 적용 .anyRequest().authenticated() diff --git a/src/main/java/com/example/developjeans/service/PhotoService.java b/src/main/java/com/example/developjeans/service/PhotoService.java index 28dda26..4aeafd2 100644 --- a/src/main/java/com/example/developjeans/service/PhotoService.java +++ b/src/main/java/com/example/developjeans/service/PhotoService.java @@ -21,12 +21,17 @@ import com.example.developjeans.entity.res.GetChartResponse; import jakarta.persistence.EntityNotFoundException; import lombok.RequiredArgsConstructor; +import org.springframework.core.io.ByteArrayResource; +import org.springframework.core.io.Resource; import org.springframework.data.domain.*; +import org.springframework.http.ResponseEntity; import org.springframework.security.core.Authentication; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import org.springframework.web.multipart.MultipartFile; + +import java.io.IOException; import java.util.ArrayList; import java.util.List; import java.util.Optional; @@ -245,6 +250,20 @@ public GetChartResponse getChart(String sort, int size, Long lastPageId) { return GetChartResponse.of(photoCursor, totalElements); } + public PhotoDto.PhotoResponseDto getDetail(Long photoId){ + + Photo photo = photoRepository.findById(photoId) + .orElseThrow(() -> new EntityNotFoundException("존재하지 않은 사진 ID: " + photoId)); + + return photoMapper.toResponseDto(photo); + + } + + public ResponseEntity downloadImage(Long photoId) throws IOException { + Photo photo = photoRepository.findById(photoId) + .orElseThrow(() -> new EntityNotFoundException("존재하지 않은 사진 ID: " + photoId)); + return s3Service.download(photo.getImgUrl()); + } // @Transactional(readOnly = true) // public List getChart(String standard, int size){ // From 9d79e5c09df914d6ad982488b928ad7a7f46f7f5 Mon Sep 17 00:00:00 2001 From: hw130 Date: Sun, 28 Jan 2024 14:27:15 +0900 Subject: [PATCH 2/3] =?UTF-8?q?fix:=20=EC=84=9C=EB=B8=8C=EB=AA=A8=EB=93=88?= =?UTF-8?q?=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/resources/application.yml | 65 ++++++++++++++++++++++++++++++ 1 file changed, 65 insertions(+) create mode 100644 src/main/resources/application.yml diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml new file mode 100644 index 0000000..e3e4865 --- /dev/null +++ b/src/main/resources/application.yml @@ -0,0 +1,65 @@ +server: + port: 9000 + +spring: + profiles: + group: + "local" : "local, jwt, oauth" + active : local + +--- +spring: + config: + activate: + on-profile: "local" + + jpa: + show-sql: true + properties: + hibernate: + format_sql: true + jdbc: + time_zone: Asia/Seoul + open-in-view: false + servlet: + multipart: + max-file-size: 10MB + max-request-size: 10MB + datasource: + driver-class-name: com.mysql.cj.jdbc.Driver + url: jdbc:mysql://database-2.chjxpiygt7p4.ap-northeast-2.rds.amazonaws.com/najakgil?serverTimezone=Asia/Seoul&characterEncoding=UTF-8 + username: najakgil + password: devgil0819 + + sql: + init: + platform: mysql + mvc: + pathmatch: + matching-strategy: ant_path_matcher + +cloud: + aws: + credentials: + access-key: AKIAWNFTLQPXNPS4JKL2 + secret-key: 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 + s3: #버킷이름 + bucket: najakgil + region: #S3 지역 + static: ap-northeast-2 + stack: + auto: false + +jwt: + secret: "Tz2mze7c52RlnOnnhObQaKJijW9bD1lY3xBISlh1J7y6BmXN27qf6RfHSmFeJcL9weOOseaJGDyL9CTMaSAcDg==" + access: + #expiration: 300000 #5분 (5 * 60) * 1000 + expiration: 864000000 #10일 (60 * 60 * 24 * 10) * 1000 + header: "X-AUTH-TOKEN" + refresh: + expiration: 864000000 #10일 (60 * 60 * 24 * 10) * 1000 + header: "X-REFRESH-TOKEN" + + + + From d86a9b41da1e40680bcb3026f779ad628d51dd42 Mon Sep 17 00:00:00 2001 From: hw130 Date: Sun, 28 Jan 2024 14:42:53 +0900 Subject: [PATCH 3/3] =?UTF-8?q?fix:=20=EC=84=9C=EB=B8=8C=EB=AA=A8=EB=93=88?= =?UTF-8?q?=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- najakgil-private | 1 + 1 file changed, 1 insertion(+) create mode 160000 najakgil-private diff --git a/najakgil-private b/najakgil-private new file mode 160000 index 0000000..ee76508 --- /dev/null +++ b/najakgil-private @@ -0,0 +1 @@ +Subproject commit ee765087326e1c4c8a5c4d541d3f43fe4373778d