Skip to content

Commit

Permalink
Merge pull request #24 from dlrkdus/d#6
Browse files Browse the repository at this point in the history
Feat/follow service#6
  • Loading branch information
dlrkdus authored Aug 1, 2024
2 parents c081921 + b186ad4 commit 1c90803
Show file tree
Hide file tree
Showing 17 changed files with 348 additions and 3 deletions.
Binary file added .DS_Store
Binary file not shown.
31 changes: 31 additions & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,14 @@ 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'
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'
Expand All @@ -38,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
Empty file modified gradlew
100644 → 100755
Empty file.
2 changes: 2 additions & 0 deletions src/main/java/com/goormy/hackathon/HackathonApplication.java
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down
32 changes: 32 additions & 0 deletions src/main/java/com/goormy/hackathon/config/AwsConfig.java
Original file line number Diff line number Diff line change
@@ -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();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
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<String> follow(@RequestHeader long userId, @RequestParam long hashtagId) {
followService.sendFollowRequest(userId,hashtagId);
return ResponseEntity.noContent().build();
}

@PostMapping("/unfollow")
public ResponseEntity<String> unfollow(@RequestHeader long userId, @RequestParam long hashtagId) {
followService.sendUnfollowRequest(userId,hashtagId);
return ResponseEntity.noContent().build();
}


}
4 changes: 2 additions & 2 deletions src/main/java/com/goormy/hackathon/entity/Follow.java
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
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 {
public class Follow {

@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
Expand Down
2 changes: 1 addition & 1 deletion src/main/java/com/goormy/hackathon/entity/User.java
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Expand Down
7 changes: 7 additions & 0 deletions src/main/java/com/goormy/hackathon/handler/FollowHandler.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package com.goormy.hackathon.handler;

import org.springframework.cloud.function.adapter.aws.FunctionInvoker;

public class FollowHandler extends FunctionInvoker {

}
24 changes: 24 additions & 0 deletions src/main/java/com/goormy/hackathon/handler/ScheduledHandler.java
Original file line number Diff line number Diff line change
@@ -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<ScheduledEvent, String> {

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";
}
}
77 changes: 77 additions & 0 deletions src/main/java/com/goormy/hackathon/lambda/FollowFunction.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
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.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 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<Map<String, Object>> processFollow() {
return messageBody -> {
try {
// 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));

if ("follow".equals(action)) {
Follow follow = new Follow(user,hashtag);
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));
followRedisRepository.removeFollow(hashtagId, userId);
System.out.println("팔로우 취소 성공: " + messageBody);
} else {
System.out.println("존재하지 않는 action입니다 : " + action);
}
} catch (Exception e) {
System.err.println("메시지 전송 실패: " + messageBody);
e.printStackTrace();
}
};
}

// Redis 데이터를 RDBMS에 저장하고 Redis 비우기
public void migrateData() {
// Redis에서 모든 팔로우 데이터 가져오기
List<Follow> follows = followRedisRepository.getAllFollows();

// RDBMS에 배치 저장
followRepository.deleteAll();
followRepository.saveAll(follows);

// Redis 비우기
System.out.println("Redis 데이터를 RDBMS로 옮기고 Redis를 초기화했습니다.");
}


}
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
package com.goormy.hackathon.repository;

import com.goormy.hackathon.entity.Follow;
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<String, Object> redisTemplate;

@Autowired
FollowRepository followRepository;

public void insertFollow(Long hashtagId, Long userId) {
String key = "hashtagId: " + hashtagId.toString();
redisTemplate.opsForList().rightPush(key, userId);
}

public void removeFollow(Long hashtagId, Long userId) {
String key = "hashtagId:" + hashtagId.toString();
redisTemplate.opsForList().remove(key, 0, userId);
}

public List<Follow> getAllFollows() {
// Redis에서 데이터를 가져와 RDBMS로 마이그레이션
List<Follow> follows = new ArrayList<>();

// 모든 키 가져오기
Set<String> keys = redisTemplate.keys("hashtagId:*");

if (keys != null) {
for (String key : keys) {
List<Long> userIds = (List<Long>) (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;
}




}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package com.goormy.hackathon.repository;

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<Follow, Long> {

@Query("SELECT f FROM Follow f WHERE f.user.id = :userId AND f.hashtag.id = :hashtagId")
Optional<Follow> findByUserIdAndHashTagId(@Param("userId") Long userId, @Param("hashtagId") Long hashtagId);
}
Original file line number Diff line number Diff line change
@@ -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<Hashtag, Long> {
}
Original file line number Diff line number Diff line change
@@ -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<User, Long> {
}
Loading

0 comments on commit 1c90803

Please sign in to comment.