From 30a35d39dda81687a6043b3e1d13e0221906192f Mon Sep 17 00:00:00 2001 From: "Hong, SeokHyeon" Date: Thu, 4 Apr 2024 02:13:33 +0900 Subject: [PATCH] =?UTF-8?q?feat:=20Redis=20=EC=97=B0=EB=8F=99=20&=20?= =?UTF-8?q?=EC=A1=B0=ED=9A=8C=EC=88=98=20=EC=A4=91=EB=B3=B5=20=EC=A6=9D?= =?UTF-8?q?=EA=B0=80=20=EB=B0=A9=EC=A7=80=20=EA=B8=B0=EB=8A=A5=20=EA=B5=AC?= =?UTF-8?q?=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- build.gradle | 3 ++ .../copro/board/application/BoardService.java | 11 ++++- .../copro/global/config/RedisConfig.java | 42 ++++++++++++++++++ .../redis/application/RedisService.java | 44 +++++++++++++++++++ src/main/resources/application.yml | 10 ++++- 5 files changed, 108 insertions(+), 2 deletions(-) create mode 100644 src/main/java/com/example/copro/global/config/RedisConfig.java create mode 100644 src/main/java/com/example/copro/global/redis/application/RedisService.java diff --git a/build.gradle b/build.gradle index 89e531d..b271b17 100644 --- a/build.gradle +++ b/build.gradle @@ -62,6 +62,9 @@ dependencies { annotationProcessor "com.querydsl:querydsl-apt:${dependencyManagement.importedProperties['querydsl.version']}:jakarta" annotationProcessor "jakarta.annotation:jakarta.annotation-api" annotationProcessor "jakarta.persistence:jakarta.persistence-api" + + // Redis + implementation 'org.springframework.boot:spring-boot-starter-data-redis' } jar { diff --git a/src/main/java/com/example/copro/board/application/BoardService.java b/src/main/java/com/example/copro/board/application/BoardService.java index d647e4e..19f9dec 100644 --- a/src/main/java/com/example/copro/board/application/BoardService.java +++ b/src/main/java/com/example/copro/board/application/BoardService.java @@ -21,6 +21,7 @@ import com.example.copro.board.exception.NotBoardOwnerException; import com.example.copro.board.exception.ScrapNotFoundException; import com.example.copro.comment.domain.repository.CommentRepository; +import com.example.copro.global.redis.application.RedisService; import com.example.copro.image.domain.Image; import com.example.copro.image.domain.repository.ImageRepository; import com.example.copro.member.domain.Member; @@ -51,6 +52,8 @@ public class BoardService { private final NotificationRepository notificationRepository; + private final RedisService redisService; + public BoardListRspDto findAll(String category, Pageable pageable) { //Page boards = boardRepository.findAllByCategory(Category.valueOf(category), pageable); Page boards = boardRepository.findAllWithCommentCount(Category.valueOf(category), pageable); @@ -163,7 +166,13 @@ public BoardListRspDto findByTitleContaining(String query, Pageable pageable) { public BoardResDto getBoard(Member member, Long boardId) { Member getMember = memberRepository.findById(member.getMemberId()).orElseThrow(MemberNotFoundException::new); Board board = boardRepository.findById(boardId).orElseThrow(() -> new BoardNotFoundException(boardId)); - board.updateViewCount(); + + if (!board.getMember().getMemberId().equals(getMember.getMemberId())) { + boolean isFirstView = redisService.checkAndAddViewByMember(getMember.getMemberId(), boardId); + if (isFirstView) { + board.updateViewCount(); + } + } boolean isHeart = memberHeartBoardRepository.existsByMemberAndBoard(getMember, board); boolean isScrap = memberScrapBoardRepository.existsByMemberAndBoard(getMember, board); diff --git a/src/main/java/com/example/copro/global/config/RedisConfig.java b/src/main/java/com/example/copro/global/config/RedisConfig.java new file mode 100644 index 0000000..3aa27e0 --- /dev/null +++ b/src/main/java/com/example/copro/global/config/RedisConfig.java @@ -0,0 +1,42 @@ +package com.example.copro.global.config; + +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.data.redis.connection.RedisConnectionFactory; +import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory; +import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.data.redis.repository.configuration.EnableRedisRepositories; +import org.springframework.data.redis.serializer.StringRedisSerializer; + +@Configuration +@EnableRedisRepositories +public class RedisConfig { + @Value("${spring.data.redis.host}") + private String host; + + @Value("${spring.data.redis.port}") + private int port; + + @Value("${spring.profiles.active}") + private String namespace; + + @Bean + public RedisConnectionFactory redisConnectionFactory() { + return new LettuceConnectionFactory(host, port); + } + + @Bean + public RedisTemplate redisTemplate() { + RedisTemplate redisTemplate = new RedisTemplate<>(); + redisTemplate.setKeySerializer(new StringRedisSerializer()); + redisTemplate.setValueSerializer(new StringRedisSerializer()); + redisTemplate.setConnectionFactory(redisConnectionFactory()); + return redisTemplate; + } + + public String getNamespace() { + return namespace; + } + +} diff --git a/src/main/java/com/example/copro/global/redis/application/RedisService.java b/src/main/java/com/example/copro/global/redis/application/RedisService.java new file mode 100644 index 0000000..5f12914 --- /dev/null +++ b/src/main/java/com/example/copro/global/redis/application/RedisService.java @@ -0,0 +1,44 @@ +package com.example.copro.global.redis.application; + +import com.example.copro.global.config.RedisConfig; +import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.stereotype.Service; +import java.time.Duration; +import java.util.ArrayList; +import java.util.List; + +@Service +public class RedisService { + private final RedisTemplate redisTemplate; + private final RedisConfig redisConfig; + + public RedisService(RedisTemplate redisTemplate, RedisConfig redisConfig) { + this.redisTemplate = redisTemplate; + this.redisConfig = redisConfig; + } + + public boolean checkAndAddViewByMember(Long memberId, Long boardId) { + String namespace = redisConfig.getNamespace(); + String redisKey = namespace + ":boardView:" + boardId; + String redisMemberKey = namespace + ":memberView:" + memberId; + + List memberViewList = getValuesList(redisMemberKey); + + if (!memberViewList.contains(redisKey)) { + setValuesListWithExpire(redisMemberKey, redisKey, Duration.ofHours(24)); + return true; + } + return false; + } + + public void setValuesListWithExpire(String key, String data, Duration duration) { + redisTemplate.opsForList().rightPush(key, data); + redisTemplate.expire(key, duration); + } + + public List getValuesList(String key) { + Long len = redisTemplate.opsForList().size(key); + return len == 0 ? new ArrayList<>() : redisTemplate.opsForList().range(key, 0, len-1); + } + +} diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index e6e72e8..5cd5a78 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -49,6 +49,10 @@ FIREBASE_KEY: ${FIREBASE_KEY} spring: profiles: active: prod + data: + redis: + host: ${spring.data.redis.host} + port: ${spring.data.redis.port} datasource: url: ${spring.datasource.url} @@ -78,6 +82,10 @@ admin: spring: profiles: active: dev + data: + redis: + host: ${spring.data.redis.host} + port: ${spring.data.redis.port} datasource: url: ${spring.datasource.url} @@ -97,4 +105,4 @@ logging: myapp: api-url: ${myapp.api-url} - local-url: ${myapp.local-url} \ No newline at end of file + local-url: ${myapp.local-url}