From 782ecfbab6c16411cc23fb82ae667310e21b9612 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=9D=B4=EA=B0=80=EC=97=B0?= <99721126+dlrkdus@users.noreply.github.com> Date: Sun, 28 Jul 2024 13:21:00 +0900 Subject: [PATCH 01/11] =?UTF-8?q?Chore:=20=EC=9D=98=EC=A1=B4=EC=84=B1=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- build.gradle | 3 +++ 1 file changed, 3 insertions(+) diff --git a/build.gradle b/build.gradle index d001300..2f56f52 100644 --- a/build.gradle +++ b/build.gradle @@ -27,6 +27,9 @@ dependencies { implementation 'org.springframework.boot:spring-boot-starter-data-jpa' implementation 'org.springframework.boot:spring-boot-starter-data-redis' implementation 'org.springframework.boot:spring-boot-starter-web' + + implementation platform("io.awspring.cloud:spring-cloud-aws-dependencies:3.0.1") + implementation 'io.awspring.cloud:spring-cloud-aws-starter-sqs' compileOnly 'org.projectlombok:lombok' runtimeOnly 'com.mysql:mysql-connector-j' annotationProcessor 'org.projectlombok:lombok' From 9fd34739a3c04d13440e90db18158c9458622d24 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=9D=B4=EA=B0=80=EC=97=B0?= <99721126+dlrkdus@users.noreply.github.com> Date: Sun, 28 Jul 2024 13:22:21 +0900 Subject: [PATCH 02/11] =?UTF-8?q?feat:=20SqsClient=20=EC=84=A4=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../goormy/hackathon/config/AwsConfig.java | 32 +++++++++++++++++++ 1 file changed, 32 insertions(+) create mode 100644 src/main/java/com/goormy/hackathon/config/AwsConfig.java diff --git a/src/main/java/com/goormy/hackathon/config/AwsConfig.java b/src/main/java/com/goormy/hackathon/config/AwsConfig.java new file mode 100644 index 0000000..811fab9 --- /dev/null +++ b/src/main/java/com/goormy/hackathon/config/AwsConfig.java @@ -0,0 +1,32 @@ +package com.goormy.hackathon.config; + +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import software.amazon.awssdk.auth.credentials.AwsBasicCredentials; +import software.amazon.awssdk.auth.credentials.StaticCredentialsProvider; +import software.amazon.awssdk.regions.Region; +import software.amazon.awssdk.services.sqs.SqsClient; + +@Configuration +public class AwsConfig { + + + @Value("${spring.cloud.aws.credentials.access-key}") + private String accessKey; + + @Value("${spring.cloud.aws.credentials.secret-key}") + private String secretKey; + + @Value("${spring.cloud.aws.region.static}") + private String region; + + @Bean + public SqsClient sqsClient() { + return SqsClient.builder() + .region(Region.of(region)) + .credentialsProvider(StaticCredentialsProvider.create( + AwsBasicCredentials.create(accessKey, secretKey))) + .build(); + } +} From b48f2734b0f6adaee6cf96a8b29ac07aff367c01 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=9D=B4=EA=B0=80=EC=97=B0?= <99721126+dlrkdus@users.noreply.github.com> Date: Sun, 28 Jul 2024 13:24:48 +0900 Subject: [PATCH 03/11] =?UTF-8?q?feat:=20=EB=A9=94=EC=8B=9C=EC=A7=80=20?= =?UTF-8?q?=EC=A0=84=EC=86=A1=20=EB=A1=9C=EC=A7=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../hackathon/service/FollowService.java | 39 +++++++++++++++++++ 1 file changed, 39 insertions(+) create mode 100644 src/main/java/com/goormy/hackathon/service/FollowService.java diff --git a/src/main/java/com/goormy/hackathon/service/FollowService.java b/src/main/java/com/goormy/hackathon/service/FollowService.java new file mode 100644 index 0000000..09dc2da --- /dev/null +++ b/src/main/java/com/goormy/hackathon/service/FollowService.java @@ -0,0 +1,39 @@ +package com.goormy.hackathon.service; + +import jakarta.transaction.Transactional; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Service; +import software.amazon.awssdk.services.sqs.SqsClient; +import software.amazon.awssdk.services.sqs.model.SendMessageRequest; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import software.amazon.awssdk.services.sqs.model.SendMessageResponse; + +@Service +public class FollowService { + private static final Logger logger = LoggerFactory.getLogger(FollowService.class); + + + @Autowired + private SqsClient sqsClient; + + @Value("${spring.cloud.aws.sqs.queue-url}") + private String queueUrl; + + public void sendFollowRequest(String userId, String hashtagId) { + String messageBody = String.format("{\"userId\": \"%s\", \"hashtagId\": \"%s\"}", userId, hashtagId); + SendMessageRequest sendMsgRequest = SendMessageRequest.builder() + .queueUrl(queueUrl) + .messageBody(messageBody) + .build(); + logger.info("Received follow request - userId: {}, hashtagId: {}", userId, hashtagId); + try { + SendMessageResponse sendMsgResponse = sqsClient.sendMessage(sendMsgRequest); + logger.info("Message sent to SQS: {}, Message ID: {}, HTTP Status: {}", + messageBody, sendMsgResponse.messageId(), sendMsgResponse.sdkHttpResponse().statusCode()); + } catch (Exception e) { + logger.error("Failed to send message to SQS: {}", messageBody, e); + } + } +} From f5850b10c8f2b58fd4bbfcae2c9da6c66ac223d9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=9D=B4=EA=B0=80=EC=97=B0?= <99721126+dlrkdus@users.noreply.github.com> Date: Mon, 29 Jul 2024 10:29:15 +0900 Subject: [PATCH 04/11] =?UTF-8?q?fix:=20messageBody=20=ED=98=95=EC=8B=9D?= =?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 --- .../hackathon/service/FollowService.java | 20 ++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/src/main/java/com/goormy/hackathon/service/FollowService.java b/src/main/java/com/goormy/hackathon/service/FollowService.java index 09dc2da..dac6db2 100644 --- a/src/main/java/com/goormy/hackathon/service/FollowService.java +++ b/src/main/java/com/goormy/hackathon/service/FollowService.java @@ -1,6 +1,6 @@ package com.goormy.hackathon.service; -import jakarta.transaction.Transactional; +import com.fasterxml.jackson.databind.ObjectMapper; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Service; @@ -10,6 +10,8 @@ import org.slf4j.LoggerFactory; import software.amazon.awssdk.services.sqs.model.SendMessageResponse; +import java.util.Map; + @Service public class FollowService { private static final Logger logger = LoggerFactory.getLogger(FollowService.class); @@ -22,18 +24,22 @@ public class FollowService { private String queueUrl; public void sendFollowRequest(String userId, String hashtagId) { - String messageBody = String.format("{\"userId\": \"%s\", \"hashtagId\": \"%s\"}", userId, hashtagId); + try{ + ObjectMapper objectMapper = new ObjectMapper(); + String messageBody = objectMapper.writeValueAsString(Map.of( + "userId", userId, + "hashtagId", hashtagId + )); SendMessageRequest sendMsgRequest = SendMessageRequest.builder() .queueUrl(queueUrl) .messageBody(messageBody) .build(); logger.info("Received follow request - userId: {}, hashtagId: {}", userId, hashtagId); - try { - SendMessageResponse sendMsgResponse = sqsClient.sendMessage(sendMsgRequest); - logger.info("Message sent to SQS: {}, Message ID: {}, HTTP Status: {}", - messageBody, sendMsgResponse.messageId(), sendMsgResponse.sdkHttpResponse().statusCode()); + SendMessageResponse sendMsgResponse = sqsClient.sendMessage(sendMsgRequest); + logger.info("Message sent to SQS: {}, Message ID: {}, HTTP Status: {}", + messageBody, sendMsgResponse.messageId(), sendMsgResponse.sdkHttpResponse().statusCode()); } catch (Exception e) { - logger.error("Failed to send message to SQS: {}", messageBody, e); + logger.error("Failed to send message to SQS", e); } } } From b4edb3c035c18b8b532da89587e12401ba118def Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=9D=B4=EA=B0=80=EC=97=B0?= <99721126+dlrkdus@users.noreply.github.com> Date: Tue, 30 Jul 2024 17:15:22 +0900 Subject: [PATCH 05/11] =?UTF-8?q?feat:=20Follow=20api=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .DS_Store | Bin 0 -> 6148 bytes build.gradle | 28 +++++++++++ gradlew | 0 .../controller/FollowController.java | 22 +++++++++ .../com/goormy/hackathon/entity/Follow.java | 5 +- .../com/goormy/hackathon/entity/User.java | 2 +- .../hackathon/handler/FollowHandler.java | 7 +++ .../hackathon/lambda/FollowFunction.java | 44 ++++++++++++++++++ .../repository/FollowRepository.java | 9 ++++ .../repository/HashtagRepository.java | 9 ++++ .../hackathon/repository/UserRepository.java | 7 +++ .../hackathon/service/FollowService.java | 2 +- .../com/goormy/hackathon/service/dummy.txt | 0 13 files changed, 131 insertions(+), 4 deletions(-) create mode 100644 .DS_Store mode change 100644 => 100755 gradlew create mode 100644 src/main/java/com/goormy/hackathon/controller/FollowController.java create mode 100644 src/main/java/com/goormy/hackathon/handler/FollowHandler.java create mode 100644 src/main/java/com/goormy/hackathon/lambda/FollowFunction.java create mode 100644 src/main/java/com/goormy/hackathon/repository/FollowRepository.java create mode 100644 src/main/java/com/goormy/hackathon/repository/HashtagRepository.java create mode 100644 src/main/java/com/goormy/hackathon/repository/UserRepository.java delete mode 100644 src/main/java/com/goormy/hackathon/service/dummy.txt diff --git a/.DS_Store b/.DS_Store new file mode 100644 index 0000000000000000000000000000000000000000..c2e53a36000090b40947fe53175df0cb9ac4cb5d GIT binary patch literal 6148 zcmeHKPjAyO6n}2JHC;jUut~chS=zOf7SN_?mr%L`2QCYO14AWQq7jkBQ<9QHRVnB3 z9ry}d`6PT7PVk=XX->OwWYgqVJ^vj0{ru;b*scM9jc4Hqpa%dBI$>)AlWUCX$ycnQ zONhs08{z9joCJvoR$^^|Rlq86_Y|PDy9NQwAch3Y^-EjAOZ34XX>#aBWKibkdZqsi z&XYJVhQnX1(QK`)x1F}rbv}BZMd=lOG0Vq(_JN+>3Xuetc|Uj;N9DA)`AVdPAE!|! z32_)<$npC)4MjN?voy>l*HaZv$LUOa+w=L8(SDzgcJ~&2KHq;f?DH4TcNdF}v$gZ| z;Ar|KIZMTb95XQ7o1{I|cnRMyW6bEQm!*kFPqCC353W>!0h~exK7^=IWbARI(GPS} zqxG9(+2f_ywiyd$y=6w@Gh6P~qj6LLj;UB;{a#{q;0O-!O-|qq;sDN;-*SUyZ1(dA za)_Y7UkoYsng4VC^o@KszL6%zXK+#7JRsp)lDyw$EiaFjt{XtpZkox&lr0*rW6R@aFq}on)V^0#<>0r2uP9yvZ1+q|ers y$yp1jm>o{c)U5%AOEJ3q>1f&c$u?qZG1^xm;sI?3L literal 0 HcmV?d00001 diff --git a/build.gradle b/build.gradle index 79cea4c..e4d3ca8 100644 --- a/build.gradle +++ b/build.gradle @@ -30,6 +30,11 @@ dependencies { implementation platform("io.awspring.cloud:spring-cloud-aws-dependencies:3.0.1") implementation 'io.awspring.cloud:spring-cloud-aws-starter-sqs' + implementation 'redis.clients:jedis:4.0.1' + implementation 'com.amazonaws:aws-lambda-java-core:1.2.1' + implementation 'com.amazonaws:aws-lambda-java-events:3.11.0' + implementation 'org.springframework.cloud:spring-cloud-function-adapter-aws:3.2.7' + compileOnly 'org.projectlombok:lombok' runtimeOnly 'com.mysql:mysql-connector-j' annotationProcessor 'org.projectlombok:lombok' @@ -41,3 +46,26 @@ dependencies { tasks.named('test') { useJUnitPlatform() } +jar { + manifest { + attributes( + 'Manifest-Version': '1.0', + 'Main-Class': 'com.goormy.hackathon.HackathonApplication' + ) + } +} + +task buildZip(type: Zip) { + from compileJava + from processResources + into('lib') { + from configurations.runtimeClasspath + } + into('lib') { + from jar.archiveFile + } + archiveFileName = 'lambda-function.zip' + destinationDirectory = file("$buildDir/distributions") +} + +build.dependsOn buildZip diff --git a/gradlew b/gradlew old mode 100644 new mode 100755 diff --git a/src/main/java/com/goormy/hackathon/controller/FollowController.java b/src/main/java/com/goormy/hackathon/controller/FollowController.java new file mode 100644 index 0000000..053dea7 --- /dev/null +++ b/src/main/java/com/goormy/hackathon/controller/FollowController.java @@ -0,0 +1,22 @@ +package com.goormy.hackathon.controller; + +import com.goormy.hackathon.service.FollowService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; + +@RestController +@RequestMapping("/goormy") +public class FollowController { + + @Autowired + private FollowService followService; + + @PostMapping("/follow") + public ResponseEntity follow(@RequestHeader long userId, @RequestParam long hashtagId) { + followService.sendFollowRequest(userId,hashtagId); + return ResponseEntity.noContent().build(); + } + + +} diff --git a/src/main/java/com/goormy/hackathon/entity/Follow.java b/src/main/java/com/goormy/hackathon/entity/Follow.java index aab67cc..bf1348e 100644 --- a/src/main/java/com/goormy/hackathon/entity/Follow.java +++ b/src/main/java/com/goormy/hackathon/entity/Follow.java @@ -1,15 +1,16 @@ package com.goormy.hackathon.entity; -import com.goormy.hackathon.common.entity.BaseTimeEntity; import jakarta.persistence.*; import lombok.Builder; import lombok.Getter; import lombok.NoArgsConstructor; +import lombok.Setter; @Entity @NoArgsConstructor @Getter -public class Follow extends BaseTimeEntity { +@Setter +public class Follow { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) diff --git a/src/main/java/com/goormy/hackathon/entity/User.java b/src/main/java/com/goormy/hackathon/entity/User.java index 55182a8..de36e63 100644 --- a/src/main/java/com/goormy/hackathon/entity/User.java +++ b/src/main/java/com/goormy/hackathon/entity/User.java @@ -13,7 +13,7 @@ @NoArgsConstructor @Getter @Table(name="users") -public class User extends BaseTimeEntity { +public class User { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) @Column(name = "user_id") diff --git a/src/main/java/com/goormy/hackathon/handler/FollowHandler.java b/src/main/java/com/goormy/hackathon/handler/FollowHandler.java new file mode 100644 index 0000000..30b9cc7 --- /dev/null +++ b/src/main/java/com/goormy/hackathon/handler/FollowHandler.java @@ -0,0 +1,7 @@ +package com.goormy.hackathon.handler; + +import org.springframework.cloud.function.adapter.aws.FunctionInvoker; + +public class FollowHandler extends FunctionInvoker { + +} diff --git a/src/main/java/com/goormy/hackathon/lambda/FollowFunction.java b/src/main/java/com/goormy/hackathon/lambda/FollowFunction.java new file mode 100644 index 0000000..d13937c --- /dev/null +++ b/src/main/java/com/goormy/hackathon/lambda/FollowFunction.java @@ -0,0 +1,44 @@ +package com.goormy.hackathon.lambda; + +import com.goormy.hackathon.entity.Follow; +import com.goormy.hackathon.entity.Hashtag; +import com.goormy.hackathon.entity.User; +import com.goormy.hackathon.repository.FollowRepository; +import com.goormy.hackathon.repository.HashtagRepository; +import com.goormy.hackathon.repository.UserRepository; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +import java.util.Map; +import java.util.function.Consumer; + +@Configuration +public class FollowFunction{ + + @Bean + public Consumer> processFollow(FollowRepository followRepository, UserRepository userRepository, HashtagRepository hashtagRepository) { + return messageBody -> { + try { + // userId와 hashtagId를 Number로 파싱하고 long으로 변환 + long userId = ((Number) messageBody.get("userId")).longValue(); + long hashtagId = ((Number) messageBody.get("hashtagId")).longValue(); + + User user = userRepository.findById(userId).orElseThrow(() -> new RuntimeException("존재하지 않는 사용자입니다. userId: " + userId)); + Hashtag hashtag = hashtagRepository.findById(hashtagId).orElseThrow(() -> new RuntimeException("존재하지 않는 해시태그입니다. hashtagId: " + hashtagId)); + + // Follow 객체 생성 및 설정 + Follow follow = new Follow(); + follow.setUser(user); + follow.setHashtag(hashtag); + + // MySQL에 데이터 저장 + followRepository.save(follow); + + System.out.println("Processed message: " + messageBody); + } catch (Exception e) { + System.err.println("Failed to process message: " + messageBody); + e.printStackTrace(); + } + }; + } +} diff --git a/src/main/java/com/goormy/hackathon/repository/FollowRepository.java b/src/main/java/com/goormy/hackathon/repository/FollowRepository.java new file mode 100644 index 0000000..fb8ee2f --- /dev/null +++ b/src/main/java/com/goormy/hackathon/repository/FollowRepository.java @@ -0,0 +1,9 @@ +package com.goormy.hackathon.repository; + +import com.goormy.hackathon.entity.Follow; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; + +@Repository +public interface FollowRepository extends JpaRepository { +} diff --git a/src/main/java/com/goormy/hackathon/repository/HashtagRepository.java b/src/main/java/com/goormy/hackathon/repository/HashtagRepository.java new file mode 100644 index 0000000..f13623c --- /dev/null +++ b/src/main/java/com/goormy/hackathon/repository/HashtagRepository.java @@ -0,0 +1,9 @@ +package com.goormy.hackathon.repository; + +import com.goormy.hackathon.entity.Hashtag; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; + +@Repository +public interface HashtagRepository extends JpaRepository { +} diff --git a/src/main/java/com/goormy/hackathon/repository/UserRepository.java b/src/main/java/com/goormy/hackathon/repository/UserRepository.java new file mode 100644 index 0000000..2e801fc --- /dev/null +++ b/src/main/java/com/goormy/hackathon/repository/UserRepository.java @@ -0,0 +1,7 @@ +package com.goormy.hackathon.repository; + +import com.goormy.hackathon.entity.User; +import org.springframework.data.jpa.repository.JpaRepository; + +public interface UserRepository extends JpaRepository { +} diff --git a/src/main/java/com/goormy/hackathon/service/FollowService.java b/src/main/java/com/goormy/hackathon/service/FollowService.java index dac6db2..fd120d5 100644 --- a/src/main/java/com/goormy/hackathon/service/FollowService.java +++ b/src/main/java/com/goormy/hackathon/service/FollowService.java @@ -23,7 +23,7 @@ public class FollowService { @Value("${spring.cloud.aws.sqs.queue-url}") private String queueUrl; - public void sendFollowRequest(String userId, String hashtagId) { + public void sendFollowRequest(long userId, long hashtagId) { try{ ObjectMapper objectMapper = new ObjectMapper(); String messageBody = objectMapper.writeValueAsString(Map.of( diff --git a/src/main/java/com/goormy/hackathon/service/dummy.txt b/src/main/java/com/goormy/hackathon/service/dummy.txt deleted file mode 100644 index e69de29..0000000 From 79627a7483cfe81ca5fd749f82ee2d955b48698c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=9D=B4=EA=B0=80=EC=97=B0?= <99721126+dlrkdus@users.noreply.github.com> Date: Wed, 31 Jul 2024 15:00:11 +0900 Subject: [PATCH 06/11] =?UTF-8?q?feat:=20FollowRedisRepository=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../repository/FollowRedisRepository.java | 26 +++++++++++++++++++ 1 file changed, 26 insertions(+) create mode 100644 src/main/java/com/goormy/hackathon/repository/FollowRedisRepository.java diff --git a/src/main/java/com/goormy/hackathon/repository/FollowRedisRepository.java b/src/main/java/com/goormy/hackathon/repository/FollowRedisRepository.java new file mode 100644 index 0000000..38f0df7 --- /dev/null +++ b/src/main/java/com/goormy/hackathon/repository/FollowRedisRepository.java @@ -0,0 +1,26 @@ +package com.goormy.hackathon.repository; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.stereotype.Repository; + +@Repository +public class FollowRedisRepository { + + @Autowired + RedisTemplate redisTemplate; + + public void insertFollow(Long hashtagId, Long userId) { + String key = "hashtagId: " + hashtagId.toString(); + redisTemplate.opsForList().rightPush(key, userId.toString()); + } + + public void removeFollow(Long hashtagId, Long userId) { + String key = "hashtagId:" + hashtagId.toString(); + redisTemplate.opsForList().remove(key, 0, userId.toString()); + } + + public void removeAllFollows(Long hashtagId) { + String key = "hashtagId:" + hashtagId.toString(); + redisTemplate.delete(key); + }} From 42087421df66eaff61830975b38f4d94cb172c05 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=9D=B4=EA=B0=80=EC=97=B0?= <99721126+dlrkdus@users.noreply.github.com> Date: Wed, 31 Jul 2024 15:01:03 +0900 Subject: [PATCH 07/11] =?UTF-8?q?feat:=20unfollow=20api=20=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controller/FollowController.java | 6 +++++ .../hackathon/lambda/FollowFunction.java | 26 ++++++++++++------- .../repository/FollowRepository.java | 7 +++++ .../hackathon/service/FollowService.java | 17 +++++++++--- 4 files changed, 42 insertions(+), 14 deletions(-) diff --git a/src/main/java/com/goormy/hackathon/controller/FollowController.java b/src/main/java/com/goormy/hackathon/controller/FollowController.java index 053dea7..4c68275 100644 --- a/src/main/java/com/goormy/hackathon/controller/FollowController.java +++ b/src/main/java/com/goormy/hackathon/controller/FollowController.java @@ -18,5 +18,11 @@ public ResponseEntity follow(@RequestHeader long userId, @RequestParam l return ResponseEntity.noContent().build(); } + @PostMapping("/unfollow") + public ResponseEntity unfollow(@RequestHeader long userId, @RequestParam long hashtagId) { + followService.sendUnfollowRequest(userId,hashtagId); + return ResponseEntity.noContent().build(); + } + } diff --git a/src/main/java/com/goormy/hackathon/lambda/FollowFunction.java b/src/main/java/com/goormy/hackathon/lambda/FollowFunction.java index d13937c..721156e 100644 --- a/src/main/java/com/goormy/hackathon/lambda/FollowFunction.java +++ b/src/main/java/com/goormy/hackathon/lambda/FollowFunction.java @@ -22,21 +22,27 @@ public Consumer> processFollow(FollowRepository followReposi // userId와 hashtagId를 Number로 파싱하고 long으로 변환 long userId = ((Number) messageBody.get("userId")).longValue(); long hashtagId = ((Number) messageBody.get("hashtagId")).longValue(); + String action = (String) messageBody.get("action"); User user = userRepository.findById(userId).orElseThrow(() -> new RuntimeException("존재하지 않는 사용자입니다. userId: " + userId)); Hashtag hashtag = hashtagRepository.findById(hashtagId).orElseThrow(() -> new RuntimeException("존재하지 않는 해시태그입니다. hashtagId: " + hashtagId)); - // Follow 객체 생성 및 설정 - Follow follow = new Follow(); - follow.setUser(user); - follow.setHashtag(hashtag); - - // MySQL에 데이터 저장 - followRepository.save(follow); - - System.out.println("Processed message: " + messageBody); + if ("follow".equals(action)) { + Follow follow = new Follow(); + follow.setUser(user); + follow.setHashtag(hashtag); + followRepository.save(follow); + System.out.println("팔로우 성공: " + messageBody); + } else if ("unfollow".equals(action)) { + Follow follow = followRepository.findByUserIdAndHashTagId(userId, hashtagId) + .orElseThrow(() -> new RuntimeException("존재하지 않는 팔로우입니다. userId: " + userId + " hashtagId: " + hashtagId)); + followRepository.delete(follow); + System.out.println("팔로우 취소 성공: " + messageBody); + } else { + System.out.println("존재하지 않는 action입니다 : " + action); + } } catch (Exception e) { - System.err.println("Failed to process message: " + messageBody); + System.err.println("메시지 전송 실패: " + messageBody); e.printStackTrace(); } }; diff --git a/src/main/java/com/goormy/hackathon/repository/FollowRepository.java b/src/main/java/com/goormy/hackathon/repository/FollowRepository.java index fb8ee2f..47cf486 100644 --- a/src/main/java/com/goormy/hackathon/repository/FollowRepository.java +++ b/src/main/java/com/goormy/hackathon/repository/FollowRepository.java @@ -2,8 +2,15 @@ import com.goormy.hackathon.entity.Follow; import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; import org.springframework.stereotype.Repository; +import java.util.Optional; + @Repository public interface FollowRepository extends JpaRepository { + + @Query("SELECT f FROM Follow f WHERE f.user.id = :userId AND f.hashtag.id = :hashtagId") + Optional findByUserIdAndHashTagId(@Param("userId") Long userId, @Param("hashtagId") Long hashtagId); } diff --git a/src/main/java/com/goormy/hackathon/service/FollowService.java b/src/main/java/com/goormy/hackathon/service/FollowService.java index fd120d5..8314a40 100644 --- a/src/main/java/com/goormy/hackathon/service/FollowService.java +++ b/src/main/java/com/goormy/hackathon/service/FollowService.java @@ -24,22 +24,31 @@ public class FollowService { private String queueUrl; public void sendFollowRequest(long userId, long hashtagId) { + sendRequest(userId, hashtagId, "follow"); + } + + public void sendUnfollowRequest(long userId, long hashtagId) { + sendRequest(userId, hashtagId, "unfollow"); + } + + public void sendRequest(long userId, long hashtagId, String action) { try{ ObjectMapper objectMapper = new ObjectMapper(); String messageBody = objectMapper.writeValueAsString(Map.of( "userId", userId, - "hashtagId", hashtagId + "hashtagId", hashtagId, + "action",action )); SendMessageRequest sendMsgRequest = SendMessageRequest.builder() .queueUrl(queueUrl) .messageBody(messageBody) .build(); - logger.info("Received follow request - userId: {}, hashtagId: {}", userId, hashtagId); + logger.info("메시지 송신 - action: {}, userId: {}, hashtagId: {}", action, userId, hashtagId); SendMessageResponse sendMsgResponse = sqsClient.sendMessage(sendMsgRequest); - logger.info("Message sent to SQS: {}, Message ID: {}, HTTP Status: {}", + logger.info("메시지가 전달되었습니다: {}, Message ID: {}, HTTP Status: {}", messageBody, sendMsgResponse.messageId(), sendMsgResponse.sdkHttpResponse().statusCode()); } catch (Exception e) { - logger.error("Failed to send message to SQS", e); + logger.error("메시지 전송 실패", e); } } } From 2e5087874bdd698900cbbe9d7d624bfe28d396ae Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=9D=B4=EA=B0=80=EC=97=B0?= <99721126+dlrkdus@users.noreply.github.com> Date: Wed, 31 Jul 2024 15:52:20 +0900 Subject: [PATCH 08/11] =?UTF-8?q?fix:=20setter=20=EC=A0=9C=EC=99=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/com/goormy/hackathon/entity/Follow.java | 1 - src/main/java/com/goormy/hackathon/lambda/FollowFunction.java | 4 +--- 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/src/main/java/com/goormy/hackathon/entity/Follow.java b/src/main/java/com/goormy/hackathon/entity/Follow.java index bf1348e..c369754 100644 --- a/src/main/java/com/goormy/hackathon/entity/Follow.java +++ b/src/main/java/com/goormy/hackathon/entity/Follow.java @@ -9,7 +9,6 @@ @Entity @NoArgsConstructor @Getter -@Setter public class Follow { @Id diff --git a/src/main/java/com/goormy/hackathon/lambda/FollowFunction.java b/src/main/java/com/goormy/hackathon/lambda/FollowFunction.java index 721156e..7234e83 100644 --- a/src/main/java/com/goormy/hackathon/lambda/FollowFunction.java +++ b/src/main/java/com/goormy/hackathon/lambda/FollowFunction.java @@ -28,9 +28,7 @@ public Consumer> processFollow(FollowRepository followReposi Hashtag hashtag = hashtagRepository.findById(hashtagId).orElseThrow(() -> new RuntimeException("존재하지 않는 해시태그입니다. hashtagId: " + hashtagId)); if ("follow".equals(action)) { - Follow follow = new Follow(); - follow.setUser(user); - follow.setHashtag(hashtag); + Follow follow = new Follow(user,hashtag); followRepository.save(follow); System.out.println("팔로우 성공: " + messageBody); } else if ("unfollow".equals(action)) { From 566e98447c46c4027e598a4b9e4c9880842ea38b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=9D=B4=EA=B0=80=EC=97=B0?= <99721126+dlrkdus@users.noreply.github.com> Date: Wed, 31 Jul 2024 19:36:07 +0900 Subject: [PATCH 09/11] =?UTF-8?q?feat:=20redis=20->=20DB=20=EB=A7=88?= =?UTF-8?q?=EC=9D=B4=EA=B7=B8=EB=A0=88=EC=9D=B4=EC=85=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../hackathon/HackathonApplication.java | 2 + .../hackathon/lambda/FollowFunction.java | 36 ++++++++++++++- .../repository/FollowRedisRepository.java | 46 ++++++++++++++++--- 3 files changed, 77 insertions(+), 7 deletions(-) diff --git a/src/main/java/com/goormy/hackathon/HackathonApplication.java b/src/main/java/com/goormy/hackathon/HackathonApplication.java index be8645a..80c6918 100644 --- a/src/main/java/com/goormy/hackathon/HackathonApplication.java +++ b/src/main/java/com/goormy/hackathon/HackathonApplication.java @@ -2,8 +2,10 @@ import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.scheduling.annotation.EnableScheduling; @SpringBootApplication +@EnableScheduling public class HackathonApplication { public static void main(String[] args) { diff --git a/src/main/java/com/goormy/hackathon/lambda/FollowFunction.java b/src/main/java/com/goormy/hackathon/lambda/FollowFunction.java index 7234e83..da56ff9 100644 --- a/src/main/java/com/goormy/hackathon/lambda/FollowFunction.java +++ b/src/main/java/com/goormy/hackathon/lambda/FollowFunction.java @@ -3,20 +3,35 @@ import com.goormy.hackathon.entity.Follow; import com.goormy.hackathon.entity.Hashtag; import com.goormy.hackathon.entity.User; +import com.goormy.hackathon.repository.FollowRedisRepository; import com.goormy.hackathon.repository.FollowRepository; import com.goormy.hackathon.repository.HashtagRepository; import com.goormy.hackathon.repository.UserRepository; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; +import org.springframework.scheduling.annotation.Scheduled; +import java.util.List; import java.util.Map; import java.util.function.Consumer; @Configuration public class FollowFunction{ + private final FollowRepository followRepository; + private final UserRepository userRepository; + private final HashtagRepository hashtagRepository; + private final FollowRedisRepository followRedisRepository; + + public FollowFunction(FollowRepository followRepository, UserRepository userRepository, HashtagRepository hashtagRepository, FollowRedisRepository followRedisRepository) { + this.followRepository = followRepository; + this.userRepository = userRepository; + this.hashtagRepository = hashtagRepository; + this.followRedisRepository = followRedisRepository; + } + @Bean - public Consumer> processFollow(FollowRepository followRepository, UserRepository userRepository, HashtagRepository hashtagRepository) { + public Consumer> processFollow() { return messageBody -> { try { // userId와 hashtagId를 Number로 파싱하고 long으로 변환 @@ -30,11 +45,13 @@ public Consumer> processFollow(FollowRepository followReposi if ("follow".equals(action)) { Follow follow = new Follow(user,hashtag); followRepository.save(follow); + followRedisRepository.insertFollow(hashtagId, userId); System.out.println("팔로우 성공: " + messageBody); } else if ("unfollow".equals(action)) { Follow follow = followRepository.findByUserIdAndHashTagId(userId, hashtagId) .orElseThrow(() -> new RuntimeException("존재하지 않는 팔로우입니다. userId: " + userId + " hashtagId: " + hashtagId)); followRepository.delete(follow); + followRedisRepository.removeFollow(hashtagId, userId); System.out.println("팔로우 취소 성공: " + messageBody); } else { System.out.println("존재하지 않는 action입니다 : " + action); @@ -45,4 +62,21 @@ public Consumer> processFollow(FollowRepository followReposi } }; } + + // Redis 데이터를 RDBMS에 저장하고 Redis 비우기 + @Scheduled(cron = "0 0 0 * * *") // 매일 자정에 실행 + public void migrateData() { + // Redis에서 모든 팔로우 데이터 가져오기 + List follows = followRedisRepository.getAllFollows(); + + // RDBMS에 배치 저장 + followRepository.deleteAll(); + followRepository.saveAll(follows); + + // Redis 비우기 + + System.out.println("Redis 데이터를 RDBMS로 옮기고 Redis를 초기화했습니다."); + } + + } diff --git a/src/main/java/com/goormy/hackathon/repository/FollowRedisRepository.java b/src/main/java/com/goormy/hackathon/repository/FollowRedisRepository.java index 38f0df7..b1c7a1a 100644 --- a/src/main/java/com/goormy/hackathon/repository/FollowRedisRepository.java +++ b/src/main/java/com/goormy/hackathon/repository/FollowRedisRepository.java @@ -1,26 +1,60 @@ package com.goormy.hackathon.repository; +import com.goormy.hackathon.entity.Follow; +import com.goormy.hackathon.entity.Hashtag; +import com.goormy.hackathon.entity.User; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.stereotype.Repository; +import java.util.ArrayList; +import java.util.List; +import java.util.Set; + @Repository public class FollowRedisRepository { @Autowired RedisTemplate redisTemplate; + @Autowired + FollowRepository followRepository; + public void insertFollow(Long hashtagId, Long userId) { String key = "hashtagId: " + hashtagId.toString(); - redisTemplate.opsForList().rightPush(key, userId.toString()); + redisTemplate.opsForList().rightPush(key, userId); } public void removeFollow(Long hashtagId, Long userId) { String key = "hashtagId:" + hashtagId.toString(); - redisTemplate.opsForList().remove(key, 0, userId.toString()); + redisTemplate.opsForList().remove(key, 0, userId); } - public void removeAllFollows(Long hashtagId) { - String key = "hashtagId:" + hashtagId.toString(); - redisTemplate.delete(key); - }} + public List getAllFollows() { + // Redis에서 데이터를 가져와 RDBMS로 마이그레이션 + List follows = new ArrayList<>(); + + // 모든 키 가져오기 + Set keys = redisTemplate.keys("hashtagId:*"); + + if (keys != null) { + for (String key : keys) { + List userIds = (List) (List) redisTemplate.opsForList().range(key, 0, -1); + if (userIds != null) { + for (Long userId : userIds) { + Long hashtagId = Long.parseLong(key.split(":")[1]); + Follow follow = followRepository.findByUserIdAndHashTagId(userId,hashtagId) + .orElseThrow(() -> new RuntimeException("존재하지 않는 팔로우입니다. userId: " + userId + " hashtagId: " + hashtagId)); + follows.add(follow); + } + } + } + } + + return follows; + } + + + + +} From ec84b0ed7f74981b720fa66ac399d730ee048760 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=9D=B4=EA=B0=80=EC=97=B0?= <99721126+dlrkdus@users.noreply.github.com> Date: Thu, 1 Aug 2024 09:35:16 +0900 Subject: [PATCH 10/11] =?UTF-8?q?fix=20:=20=EB=A1=9C=EC=A7=81=20=EC=88=98?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/com/goormy/hackathon/lambda/FollowFunction.java | 2 -- .../com/goormy/hackathon/repository/FollowRedisRepository.java | 2 -- 2 files changed, 4 deletions(-) diff --git a/src/main/java/com/goormy/hackathon/lambda/FollowFunction.java b/src/main/java/com/goormy/hackathon/lambda/FollowFunction.java index da56ff9..899d5d4 100644 --- a/src/main/java/com/goormy/hackathon/lambda/FollowFunction.java +++ b/src/main/java/com/goormy/hackathon/lambda/FollowFunction.java @@ -44,13 +44,11 @@ public Consumer> processFollow() { if ("follow".equals(action)) { Follow follow = new Follow(user,hashtag); - followRepository.save(follow); followRedisRepository.insertFollow(hashtagId, userId); System.out.println("팔로우 성공: " + messageBody); } else if ("unfollow".equals(action)) { Follow follow = followRepository.findByUserIdAndHashTagId(userId, hashtagId) .orElseThrow(() -> new RuntimeException("존재하지 않는 팔로우입니다. userId: " + userId + " hashtagId: " + hashtagId)); - followRepository.delete(follow); followRedisRepository.removeFollow(hashtagId, userId); System.out.println("팔로우 취소 성공: " + messageBody); } else { diff --git a/src/main/java/com/goormy/hackathon/repository/FollowRedisRepository.java b/src/main/java/com/goormy/hackathon/repository/FollowRedisRepository.java index b1c7a1a..68628a0 100644 --- a/src/main/java/com/goormy/hackathon/repository/FollowRedisRepository.java +++ b/src/main/java/com/goormy/hackathon/repository/FollowRedisRepository.java @@ -1,8 +1,6 @@ package com.goormy.hackathon.repository; import com.goormy.hackathon.entity.Follow; -import com.goormy.hackathon.entity.Hashtag; -import com.goormy.hackathon.entity.User; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.stereotype.Repository; From b186ad4fc99b1dd4ee72f789cdf8146eed3a5846 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=9D=B4=EA=B0=80=EC=97=B0?= <99721126+dlrkdus@users.noreply.github.com> Date: Thu, 1 Aug 2024 14:24:52 +0900 Subject: [PATCH 11/11] =?UTF-8?q?feat:=20scheduleHandler=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../hackathon/handler/ScheduledHandler.java | 24 +++++++++++++++++++ .../hackathon/lambda/FollowFunction.java | 3 --- 2 files changed, 24 insertions(+), 3 deletions(-) create mode 100644 src/main/java/com/goormy/hackathon/handler/ScheduledHandler.java diff --git a/src/main/java/com/goormy/hackathon/handler/ScheduledHandler.java b/src/main/java/com/goormy/hackathon/handler/ScheduledHandler.java new file mode 100644 index 0000000..b420cfe --- /dev/null +++ b/src/main/java/com/goormy/hackathon/handler/ScheduledHandler.java @@ -0,0 +1,24 @@ +package com.goormy.hackathon.handler; + +import com.amazonaws.services.lambda.runtime.Context; +import com.amazonaws.services.lambda.runtime.RequestHandler; +import com.amazonaws.services.lambda.runtime.events.ScheduledEvent; +import com.goormy.hackathon.lambda.FollowFunction; +import org.springframework.context.ApplicationContext; +import org.springframework.context.annotation.AnnotationConfigApplicationContext; + +public class ScheduledHandler implements RequestHandler { + + private static final ApplicationContext context = new AnnotationConfigApplicationContext(FollowFunction.class); + private final FollowFunction followFunction; + + public ScheduledHandler() { + followFunction = context.getBean(FollowFunction.class); + } + + @Override + public String handleRequest(ScheduledEvent event, Context context) { + followFunction.migrateData(); + return "Data migration completed"; + } +} diff --git a/src/main/java/com/goormy/hackathon/lambda/FollowFunction.java b/src/main/java/com/goormy/hackathon/lambda/FollowFunction.java index 899d5d4..836f3e5 100644 --- a/src/main/java/com/goormy/hackathon/lambda/FollowFunction.java +++ b/src/main/java/com/goormy/hackathon/lambda/FollowFunction.java @@ -9,7 +9,6 @@ import com.goormy.hackathon.repository.UserRepository; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; -import org.springframework.scheduling.annotation.Scheduled; import java.util.List; import java.util.Map; @@ -62,7 +61,6 @@ public Consumer> processFollow() { } // Redis 데이터를 RDBMS에 저장하고 Redis 비우기 - @Scheduled(cron = "0 0 0 * * *") // 매일 자정에 실행 public void migrateData() { // Redis에서 모든 팔로우 데이터 가져오기 List follows = followRedisRepository.getAllFollows(); @@ -72,7 +70,6 @@ public void migrateData() { followRepository.saveAll(follows); // Redis 비우기 - System.out.println("Redis 데이터를 RDBMS로 옮기고 Redis를 초기화했습니다."); }