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}