diff --git a/build.gradle b/build.gradle index 6cd06d2..91c81fc 100644 --- a/build.gradle +++ b/build.gradle @@ -60,6 +60,9 @@ dependencies { // MacOS Silicon 라이브러리 누락 문제 runtimeOnly 'io.netty:netty-resolver-dns-native-macos:4.1.104.Final:osx-aarch_64' + + // S3 + implementation 'org.springframework.cloud:spring-cloud-starter-aws:2.2.6.RELEASE' } ext { diff --git a/src/main/java/kusitms/backend/global/config/S3Config.java b/src/main/java/kusitms/backend/global/config/S3Config.java new file mode 100644 index 0000000..090da71 --- /dev/null +++ b/src/main/java/kusitms/backend/global/config/S3Config.java @@ -0,0 +1,68 @@ +package kusitms.backend.global.config; + +import com.amazonaws.auth.AWSStaticCredentialsProvider; +import com.amazonaws.auth.BasicAWSCredentials; +import com.amazonaws.client.builder.AwsClientBuilder; +import com.amazonaws.services.s3.AmazonS3; +import com.amazonaws.services.s3.AmazonS3ClientBuilder; +import com.amazonaws.services.s3.model.BucketCrossOriginConfiguration; +import com.amazonaws.services.s3.model.CORSRule; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +import java.util.ArrayList; +import java.util.List; + +@Configuration +public class S3Config { + + @Value("${cloud.aws.credentials.access-key}") + private String accessKey; + + @Value("${cloud.aws.credentials.secret-key}") + private String secretKey; + + @Value("${cloud.aws.region.static}") + private String region; + + @Value("${cloud.aws.s3.endpoint}") + private String endPoint; + + @Value("${cloud.aws.credentials.bucket}") + private String bucketName; + + @Bean + public AmazonS3 amazonS3() { + return AmazonS3ClientBuilder.standard() + .withEndpointConfiguration(new AwsClientBuilder.EndpointConfiguration(endPoint, region)) + .withCredentials(new AWSStaticCredentialsProvider(new BasicAWSCredentials(accessKey, secretKey))) + .build(); + } + + @Bean + public BucketCrossOriginConfiguration bucketCrossOriginConfiguration(AmazonS3 amazonS3) { + configureS3Cors(amazonS3); + return new BucketCrossOriginConfiguration(); + } + + public void configureS3Cors(AmazonS3 s3) { + List methodRule = new ArrayList<>(); + methodRule.add(CORSRule.AllowedMethods.PUT); + methodRule.add(CORSRule.AllowedMethods.GET); + methodRule.add(CORSRule.AllowedMethods.POST); + CORSRule rule = new CORSRule().withId("CORSRule") + .withAllowedMethods(methodRule) + .withAllowedHeaders(List.of("*")) + .withAllowedOrigins(List.of("*")) + .withMaxAgeSeconds(3000); + + List rules = new ArrayList<>(); + rules.add(rule); + + BucketCrossOriginConfiguration configuration = new BucketCrossOriginConfiguration(); + configuration.setRules(rules); + + s3.setBucketCrossOriginConfiguration(bucketName, configuration); + } +} \ No newline at end of file diff --git a/src/main/java/kusitms/backend/result/application/ResultService.java b/src/main/java/kusitms/backend/result/application/ResultService.java index 796714c..6e6e50b 100644 --- a/src/main/java/kusitms/backend/result/application/ResultService.java +++ b/src/main/java/kusitms/backend/result/application/ResultService.java @@ -84,6 +84,7 @@ public & StadiumStatusType> SaveTopRankedZoneResponseDto save Profile profile = Profile.builder() .result(result) + .imgUrl(recommendedProfile.getImgUrl()) .nickname(recommendedProfile.getNickName()) .type(recommendedProfile.getType()) .explanation(recommendedProfile.getExplanation()) diff --git a/src/main/java/kusitms/backend/result/domain/entity/Profile.java b/src/main/java/kusitms/backend/result/domain/entity/Profile.java index caee61f..d627eae 100644 --- a/src/main/java/kusitms/backend/result/domain/entity/Profile.java +++ b/src/main/java/kusitms/backend/result/domain/entity/Profile.java @@ -24,6 +24,9 @@ public class Profile { @JoinColumn(name = "result_id", nullable = false) private Result result; + @Column(nullable = false) + private String imgUrl; + @Column(nullable = false) private String nickname; @@ -38,8 +41,9 @@ public class Profile { private List hashTags; @Builder - public Profile(Result result, String nickname, String type, String explanation, List hashTags) { + public Profile(Result result, String imgUrl, String nickname, String type, String explanation, List hashTags) { this.result = result; + this.imgUrl = imgUrl; this.nickname = nickname; this.type = type; this.explanation = explanation; diff --git a/src/main/java/kusitms/backend/result/domain/enums/ProfileStatusType.java b/src/main/java/kusitms/backend/result/domain/enums/ProfileStatusType.java index e66e223..2c6e61f 100644 --- a/src/main/java/kusitms/backend/result/domain/enums/ProfileStatusType.java +++ b/src/main/java/kusitms/backend/result/domain/enums/ProfileStatusType.java @@ -9,7 +9,8 @@ @RequiredArgsConstructor public enum ProfileStatusType { - EATING("이러다 공까지 먹어버러", + EATING("https://kr.object.ncloudstorage.com/hitzone-bucket/hitzone/recommendation/eating.png", + "이러다 공까지 먹어버러", "야구가 참 맛있고 음식이 재밌어요", "야구장에서 먹는 재미까지 놓치지 않는 당신!\n야구장을 두 배로 재밌게 즐기는군요?", List.of("#먹으러왔는데야구도한다?", "#그래서여기구장맛있는거뭐라고?"), @@ -18,7 +19,8 @@ public enum ProfileStatusType { List.of(), List.of("선수들 가까이", "열정적인 응원") ), - LIFE("주6일 야구장 출퇴근러", + LIFE("https://kr.object.ncloudstorage.com/hitzone-bucket/hitzone/recommendation/life.png", + "주6일 야구장 출퇴근러", "야구가 나의 삶이고, 야구가 나의 숨", "야구장에서 열정적인 응원을 보여주는 당신!\n당신의 응원 덕에 선수들이 더 행복해 질 거예요!", List.of("#월요일은심심해", "#18시를공기로안다"), @@ -27,7 +29,8 @@ public enum ProfileStatusType { List.of(), List.of("다른 팀 팬과") ), - ANGRY("마운드 직진러", + ANGRY("https://kr.object.ncloudstorage.com/hitzone-bucket/hitzone/recommendation/angry.png", + "마운드 직진러", "나와. 이럴 거면 내가 경기 뛸게", "야구를 통해 희노애락을 다 느끼는 당신!\n몰입하며 보는 야구가 얼마나 재밌는 지 아시는군요?", List.of("#오늘부터내가야구선수", "#우리팀승리기원n일차"), @@ -36,7 +39,8 @@ public enum ProfileStatusType { List.of(), List.of("다른 팀 팬과") ), - CALM("뜨거운 열기 속 침착러", + CALM("https://kr.object.ncloudstorage.com/hitzone-bucket/hitzone/recommendation/calm.png", + "뜨거운 열기 속 침착러", "야구란 자고로 그라운드의 열기 속에서 고요함을 느끼는 것", "경기를 조용하게 관람하는걸 좋아하는 당신!\n경기를 음미하는 것을 좋아하시는군요?", List.of("#나는나의갈길을간다", "#진짜는조용한법"), @@ -45,7 +49,8 @@ public enum ProfileStatusType { List.of(), List.of("열정적인 응원") ), - TRAVEL("베이스 낭만 여행러", + TRAVEL("https://kr.object.ncloudstorage.com/hitzone-bucket/hitzone/recommendation/travel.png", + "베이스 낭만 여행러", "야구장에서 한 페이지가 될 수 있게", "나의 소중한 직관 메이트와 추억을 쌓는 것이 좋은 당신!\n야구장에서의 추억이 행복하길 바라요~", List.of("#너와함께하는9이닝", "#우리다음에또갈까?"), @@ -55,7 +60,7 @@ public enum ProfileStatusType { List.of("나 혼자") ); - + private final String imgUrl; private final String nickName; private final String type; private final String explanation; diff --git a/src/main/java/kusitms/backend/result/dto/response/GetProfileResponseDto.java b/src/main/java/kusitms/backend/result/dto/response/GetProfileResponseDto.java index 13a8c0f..7f96294 100644 --- a/src/main/java/kusitms/backend/result/dto/response/GetProfileResponseDto.java +++ b/src/main/java/kusitms/backend/result/dto/response/GetProfileResponseDto.java @@ -6,12 +6,13 @@ public record GetProfileResponseDto( Long profileId, + String imgUrl, String nickname, String type, String explanation, List hashTags ) { public static GetProfileResponseDto from(Profile profile) { - return new GetProfileResponseDto(profile.getId(), profile.getNickname(), profile.getType(), profile.getExplanation(), profile.getHashTags()); + return new GetProfileResponseDto(profile.getId(), profile.getImgUrl(), profile.getNickname(), profile.getType(), profile.getExplanation(), profile.getHashTags()); } } diff --git a/src/main/resources/application.yaml b/src/main/resources/application.yaml index 75ec55b..91b1ee4 100644 --- a/src/main/resources/application.yaml +++ b/src/main/resources/application.yaml @@ -96,4 +96,21 @@ clova: api-key: ${CLOVA_API_KEY} api-gateway-key: ${CLOVA_API_GATEWAY_KEY} prompt: - baseball: ${BASEBALL_PROMPT} \ No newline at end of file + baseball: ${BASEBALL_PROMPT} + +cloud: + aws: + s3: + endpoint: ${S3_ENDPOINT} + chatbot-directory: ${S3_CHATBOT_DIRECTORY} + guide-directory: ${S3_GUIDE_DIRECTORY} + recommendation-directory: ${S3_RECOMMENDATION_DIRECTORY} + credentials: + access-key: ${S3_ACCESS_KEY} + secret-key: ${S3_SECRET_KEY} + bucket: ${S3_BUCKET} + region: + static: ${S3_REGION} + auto: false + stack: + auto: false \ No newline at end of file diff --git a/src/test/java/kusitms/backend/result/ResultControllerTest.java b/src/test/java/kusitms/backend/result/ResultControllerTest.java index 11f009d..216795c 100644 --- a/src/test/java/kusitms/backend/result/ResultControllerTest.java +++ b/src/test/java/kusitms/backend/result/ResultControllerTest.java @@ -104,6 +104,7 @@ public void getRecommendedProfile() throws Exception { // given GetProfileResponseDto getProfileResponseDto = new GetProfileResponseDto( 1L, + "https://kr.object.ncloudstorage.com/hitzone-bucket/hitzone/recommendation/eating.png", "이러다 공까지 먹어버러", "야구가 참 맛있고 음식이 재밌어요", "야구장에서 먹는 재미까지 놓치지 않는 당신!\n야구장을 두 배로 재밌게 즐기는군요?", @@ -126,6 +127,7 @@ public void getRecommendedProfile() throws Exception { .andExpect(jsonPath("$.code").value(200)) .andExpect(jsonPath("$.message").value("해당 결과의 프로필 정보를 조회하였습니다.")) .andExpect(jsonPath("$.payload.profileId").value(1L)) + .andExpect(jsonPath("$.payload.imgUrl").value("https://kr.object.ncloudstorage.com/hitzone-bucket/hitzone/recommendation/eating.png")) .andExpect(jsonPath("$.payload.nickname").value("이러다 공까지 먹어버러")) .andExpect(jsonPath("$.payload.type").value("야구가 참 맛있고 음식이 재밌어요")) .andExpect(jsonPath("$.payload.explanation").value("야구장에서 먹는 재미까지 놓치지 않는 당신!\n야구장을 두 배로 재밌게 즐기는군요?")) @@ -147,6 +149,7 @@ public void getRecommendedProfile() throws Exception { fieldWithPath("message").description("응답 메시지"), fieldWithPath("payload").description("응답 데이터").optional(), fieldWithPath("payload.profileId").description("해당 결과의 프로필 ID"), + fieldWithPath("payload.imgUrl").description("해당 결과의 이미지 URL"), fieldWithPath("payload.nickname").description("해당 결과의 프로필의 닉네임"), fieldWithPath("payload.type").description("해당 결과의 프로필의 타입"), fieldWithPath("payload.explanation").description("해당 결과의 프로필 설명"),