Skip to content

Commit

Permalink
Merge branch 'main' into dev
Browse files Browse the repository at this point in the history
  • Loading branch information
jonghyunhub authored Aug 31, 2023
2 parents 8695ed5 + 454e864 commit 2baab0f
Show file tree
Hide file tree
Showing 58 changed files with 3,244 additions and 1,137 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/deploy.yml
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ jobs:
- name: Grant execute permission for gradlew
run: chmod +x ./gradlew # gradlew 실행권한 부여
- name: Build with Gradle
run: ./gradlew clean build # build 하기
run: ./gradlew clean build -x test # build 하기

# UTC가 기준이기 때문에 한국시간으로 맞추려면 +9시간 해야 한다.
- name: Get current time
Expand Down
5 changes: 4 additions & 1 deletion build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,6 @@ dependencies {

implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
implementation 'mysql:mysql-connector-java'
runtimeOnly 'org.mariadb.jdbc:mariadb-java-client'
runtimeOnly 'com.h2database:h2'

implementation 'org.springframework.boot:spring-boot-starter-jdbc'
Expand All @@ -43,11 +42,15 @@ dependencies {

implementation 'org.springframework.cloud:spring-cloud-starter-openfeign:3.1.3'
runtimeOnly 'io.github.openfeign:feign-httpclient:12.1'
implementation 'com.netflix.feign:feign-jackson:8.18.0'

//querydsl 추가
implementation "com.querydsl:querydsl-jpa:5.0.0"
annotationProcessor "com.querydsl:querydsl-apt:5.0.0"

//s3 추가
implementation 'org.springframework.cloud:spring-cloud-starter-aws:2.2.6.RELEASE'

}

// Querydsl 설정 추가
Expand Down
17 changes: 17 additions & 0 deletions src/main/java/co/kr/jurumarble/client/s3/ImageUploadDto.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package co.kr.jurumarble.client.s3;

import lombok.AccessLevel;
import lombok.Getter;
import lombok.NoArgsConstructor;

@Getter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public class ImageUploadDto {
private String imageUrl;
private String message;

public ImageUploadDto(String imageUrl, String message) {
this.imageUrl = imageUrl;
this.message = message;
}
}
43 changes: 43 additions & 0 deletions src/main/java/co/kr/jurumarble/client/s3/S3Controller.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
package co.kr.jurumarble.client.s3;

import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.RequiredArgsConstructor;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
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.RestController;
import org.springframework.web.multipart.MultipartFile;

@RequiredArgsConstructor
@RestController
@RequestMapping("/api/images")
@Tag(name = "image", description = "S3 업로드 api")
public class S3Controller {

private final S3Uploader s3Uploader;
private static final Logger logger = LoggerFactory.getLogger(S3Controller.class);

@Operation(
summary = "이미지 파일 업로드",
description = "MultipartFile 형태의 이미지 파일을 'images'라는 키로 form-data 형태로 전송해주세요. 이 API는 전송된 이미지를 S3에 저장하고, 저장된 이미지의 URL을 반환합니다."
)
@PostMapping(value = "", consumes = MediaType.MULTIPART_FORM_DATA_VALUE, produces = MediaType.APPLICATION_JSON_VALUE)
public ResponseEntity uploadImage(@RequestParam("images") MultipartFile multipartFile) {
try {
String uploadedUrl = s3Uploader.uploadFiles(multipartFile, "static");
ImageUploadDto imageUpload = new ImageUploadDto(uploadedUrl, "이미지 업로드에 성공했습니다.");
return new ResponseEntity(imageUpload, HttpStatus.OK);
} catch (Exception e) {
logger.error("Error occurred while uploading image: ", e);
return new ResponseEntity("잘못된 요청입니다.", HttpStatus.BAD_REQUEST);
}
}


}
67 changes: 67 additions & 0 deletions src/main/java/co/kr/jurumarble/client/s3/S3Uploader.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
package co.kr.jurumarble.client.s3;

import com.amazonaws.services.s3.AmazonS3Client;
import com.amazonaws.services.s3.model.CannedAccessControlList;
import com.amazonaws.services.s3.model.PutObjectRequest;
import lombok.RequiredArgsConstructor;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import org.springframework.web.multipart.MultipartFile;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.Optional;
import java.util.UUID;

@Component
@RequiredArgsConstructor
public class S3Uploader {

private static final Logger logger = LoggerFactory.getLogger(S3Uploader.class);
private final AmazonS3Client amazonS3Client;
@Value("${cloud.aws.s3.bucket}")
private String bucket;

public String uploadFiles(MultipartFile multipartFile, String dirName) throws IOException {
File uploadFile = convert(multipartFile) // 파일 변환할 수 없으면 에러
.orElseThrow(() -> new IllegalArgumentException("error: MultipartFile -> File convert fail"));
return upload(uploadFile, dirName);
}

public String upload(File uploadFile, String filePath) {
String fileName = filePath + "/" + UUID.randomUUID() + uploadFile.getName(); // S3에 저장된 파일 이름
String uploadImageUrl = putS3(uploadFile, fileName); // s3로 업로드
removeNewFile(uploadFile);
return uploadImageUrl;
}

// S3로 업로드
private String putS3(File uploadFile, String fileName) {
amazonS3Client.putObject(new PutObjectRequest(bucket, fileName, uploadFile).withCannedAcl(CannedAccessControlList.PublicRead));
return amazonS3Client.getUrl(bucket, fileName).toString();
}

// 로컬에 저장된 이미지 지우기
private void removeNewFile(File targetFile) {
if (targetFile.delete()) {
logger.info("Successfully deleted local image.");
return;
}
logger.warn("Failed to delete image.");
}

// 로컬에 파일 업로드 하기
private Optional<File> convert(MultipartFile file) throws IOException {
File convertFile = new File(System.getProperty("user.dir") + "/" + file.getOriginalFilename());
if (convertFile.createNewFile()) { // 바로 위에서 지정한 경로에 File이 생성됨 (경로가 잘못되었다면 생성 불가능)
try (FileOutputStream fos = new FileOutputStream(convertFile)) { // FileOutputStream 데이터를 파일에 바이트 스트림으로 저장하기 위함
fos.write(file.getBytes());
}
return Optional.of(convertFile);
}
return Optional.empty();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package co.kr.jurumarble.client.tourApi;

import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;

@Getter
@NoArgsConstructor
@AllArgsConstructor
public class RestaurantInfoDto {

private String contentId;
private String firstImage;
private String title;


}
60 changes: 60 additions & 0 deletions src/main/java/co/kr/jurumarble/client/tourApi/TourApiClient.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
package co.kr.jurumarble.client.tourApi;

import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;

@FeignClient(value = "tour-service", url = "${tour.api.url}", configuration = TourApiClientConfig.class)
public interface TourApiClient {

//소개 정보 조회
@GetMapping(value = "/detailIntro1")
TourDetailIntroResponse getDetailIntro(@RequestParam("ServiceKey") String serviceKey,
@RequestParam("contentTypeId") int contentTypeId,
@RequestParam("contentId") String contentId,
@RequestParam("MobileOS") String mobileOS,
@RequestParam("MobileApp") String mobileApp,
@RequestParam("_type") String responseType);


//이미지 정보 조회
@GetMapping(value = "/detailImage1")
TourDetailImageResponse getDetailImages(@RequestParam("ServiceKey") String serviceKey,
@RequestParam("contentId") String contentId,
@RequestParam("MobileOS") String mobileOS,
@RequestParam("MobileApp") String mobileApp,
@RequestParam("imageYN") String imageYN,
@RequestParam("subImageYN") String subImageYN,
@RequestParam("numOfRows") int numOfRows,
@RequestParam("_type") String responseType);


//지역 기반 정보 조회
@GetMapping(value = "/areaBasedList1")
TourAreaBasedListResponse getRestaurantList(@RequestParam("ServiceKey") String serviceKey,
@RequestParam("contentTypeId") int contentTypeId,
@RequestParam("areaCode") int areaCode,
@RequestParam("MobileOS") String mobileOS,
@RequestParam("MobileApp") String mobileApp,
@RequestParam("numOfRows") int numOfRows,
@RequestParam("pageNo") int pageNo,
@RequestParam("listYN") String listYN,
@RequestParam("arrange") String arrange,
@RequestParam("cat1") String cat1, //대분류:음식
@RequestParam("cat2") String cat2, //중분류:음식점
@RequestParam("_type") String responseType);


@GetMapping(value = "/searchKeyword1")
TourSearchKeyWordResponse getRestaurantListByKeyWord(@RequestParam("ServiceKey") String serviceKey,
@RequestParam("contentTypeId") int contentTypeId,
@RequestParam("areaCode") int areaCode,
@RequestParam("MobileOS") String mobileOS,
@RequestParam("MobileApp") String mobileApp,
@RequestParam("listYN") String listYN,
@RequestParam("numOfRows") int numOfRows,
@RequestParam("pageNo") int pageNo,
@RequestParam("keyword") String keyWord,
@RequestParam("_type") String responseType);

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package co.kr.jurumarble.client.tourApi;

import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import feign.codec.Decoder;
import feign.codec.Encoder;
import feign.jackson.JacksonDecoder;
import feign.jackson.JacksonEncoder;
import org.springframework.cloud.openfeign.support.ResponseEntityDecoder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class TourApiClientConfig {

private ObjectMapper customObjectMapper = new ObjectMapper()
.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false)
.enable(DeserializationFeature.ACCEPT_EMPTY_STRING_AS_NULL_OBJECT);

@Bean
public Decoder feignDecoder() {
return new ResponseEntityDecoder(new JacksonDecoder(customObjectMapper)); // 디코딩 과정에서 사용할 수 있는 Decoder Bean을 생성
}

@Bean
public Encoder feignEncoder() {
return new JacksonEncoder(customObjectMapper); //인코딩 과정에서 사용할 수 있는 Encoder Bean을 생성
}
}
10 changes: 10 additions & 0 deletions src/main/java/co/kr/jurumarble/client/tourApi/TourApiConfig.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package co.kr.jurumarble.client.tourApi;


import org.springframework.cloud.openfeign.EnableFeignClients;
import org.springframework.context.annotation.Configuration;

@Configuration
@EnableFeignClients // Spring Boot 애플리케이션에서 Feign 클라이언트를 사용하도록 활성화
public class TourApiConfig {
}
Loading

0 comments on commit 2baab0f

Please sign in to comment.