Skip to content

Commit 38aff0e

Browse files
committed
refactor 공유 제안 게시판 좋아요 수정
- redis에도 count 저장 - 페이지 조회 시 캐시값을 먼저 조회하여 정합성 증가 - 첫 토글 시 Redis 조회 후 없을 경우 DB 조회하여 캐싱
1 parent bca24c6 commit 38aff0e

File tree

3 files changed

+94
-25
lines changed

3 files changed

+94
-25
lines changed
Lines changed: 41 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,10 @@
11
package io.crops.warmletter.domain.share.cache;
2-
32
import lombok.RequiredArgsConstructor;
43
import org.springframework.data.redis.core.StringRedisTemplate;
54
import org.springframework.stereotype.Component;
6-
75
import java.util.HashMap;
86
import java.util.Map;
7+
import java.util.Optional;
98
import java.util.Set;
109

1110
@Component
@@ -14,17 +13,37 @@ public class PostLikeRedisManager {
1413

1514
private final StringRedisTemplate redisTemplate;
1615
private static final String POST_LIKE_KEY = "post:%d:like:memberId:%d";
16+
private static final String POST_LIKE_COUNT_KEY = "post:%d:like:count";
17+
18+
public void toggleLike(Long postId, Long memberId,boolean status) {
1719

18-
public void toggleLike(Long postId, Long memberId) {
1920
String key = getKey(postId, memberId);
20-
Boolean isLiked = isLiked(postId, memberId);
21-
redisTemplate.opsForValue().set(key, String.valueOf(!isLiked));
21+
redisTemplate.opsForValue().set(key, String.valueOf(status));
22+
23+
String countKey = getCountKey(postId);
24+
if (status) {
25+
redisTemplate.opsForValue().increment(countKey);
26+
} else {
27+
redisTemplate.opsForValue().decrement(countKey);
28+
}
29+
}
30+
31+
private String getCountKey(Long postId) {
32+
return String.format(POST_LIKE_COUNT_KEY, postId);
2233
}
2334

2435
public boolean isLiked(Long postId, Long memberId) {
36+
return getLikedStatus(postId, memberId).orElse(false);
37+
}
38+
39+
public Optional<Boolean> getLikedStatus(Long postId, Long memberId) {
2540
String key = getKey(postId, memberId);
2641
String value = redisTemplate.opsForValue().get(key);
27-
return value != null && Boolean.parseBoolean(value);
42+
if (value == null) {
43+
return Optional.empty(); // 캐시에 해당 정보가 없음
44+
}
45+
46+
return Optional.of(Boolean.parseBoolean(value));
2847
}
2948

3049
private String getKey(Long postId, Long memberId) {
@@ -34,22 +53,32 @@ private String getKey(Long postId, Long memberId) {
3453
public Map<String, Boolean> getAllLikeStatus() {
3554
Set<String> keys = redisTemplate.keys("post:*:like:memberId:*");
3655
Map<String, Boolean> likeStatusMap = new HashMap<>();
37-
38-
for (String key : keys) {
39-
String value = redisTemplate.opsForValue().get(key);
40-
if (value != null) {
41-
likeStatusMap.put(key, Boolean.parseBoolean(value));
56+
if (keys != null) {
57+
for (String key : keys) {
58+
String value = redisTemplate.opsForValue().get(key);
59+
if (value != null) {
60+
likeStatusMap.put(key, Boolean.parseBoolean(value));
61+
}
4262
}
4363
}
4464
return likeStatusMap;
4565
}
4666

67+
public int getLikeCount(Long postId) {
68+
String countKey = getCountKey(postId);
69+
String countValue = redisTemplate.opsForValue().get(countKey);
70+
return countValue != null ? Integer.parseInt(countValue) : 0;
71+
}
4772

4873
public void clearCache() {
4974
Set<String> keys = redisTemplate.keys("post:*:like:memberId:*");
5075
if (keys != null && !keys.isEmpty()) {
5176
redisTemplate.delete(keys);
5277
}
78+
// 좋아요 카운트도 함께 삭제
79+
Set<String> countKeys = redisTemplate.keys("post:*:like:count");
80+
if (countKeys != null && !countKeys.isEmpty()) {
81+
redisTemplate.delete(countKeys);
82+
}
5383
}
54-
5584
}

src/main/java/io/crops/warmletter/domain/share/scheduler/LikeScheduler.java

Lines changed: 28 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,17 @@
11
package io.crops.warmletter.domain.share.scheduler;
2-
32
import io.crops.warmletter.domain.share.entity.SharePostLike;
43
import io.crops.warmletter.domain.share.cache.PostLikeRedisManager;
54
import io.crops.warmletter.domain.share.repository.SharePostLikeRepository;
65
import lombok.RequiredArgsConstructor;
6+
import lombok.extern.slf4j.Slf4j;
77
import org.springframework.scheduling.annotation.Scheduled;
88
import org.springframework.stereotype.Component;
99
import org.springframework.transaction.annotation.Transactional;
10-
1110
import java.util.Map;
1211

1312
@Component
1413
@RequiredArgsConstructor
14+
@Slf4j
1515
public class LikeScheduler {
1616

1717
private static final String REDIS_KEY_DELIMITER = ":";
@@ -21,15 +21,32 @@ public class LikeScheduler {
2121
@Scheduled(fixedRate = 60000)
2222
@Transactional
2323
public void syncLikesToDatabase() {
24-
Map<String, Boolean> likeStatusMap = postLikeRedisManager.getAllLikeStatus();
2524

26-
likeStatusMap.forEach(this::processLikeEntry);
27-
28-
postLikeRedisManager.clearCache();
29-
}
25+
try {
26+
Map<String, Boolean> likeStatusMap = postLikeRedisManager.getAllLikeStatus();
27+
if (likeStatusMap != null) {
28+
// 각 좋아요 처리
29+
for (Map.Entry<String, Boolean> entry : likeStatusMap.entrySet()) {
30+
try {
31+
processLikeEntry(entry.getKey(), entry.getValue());
32+
} catch (Exception e) {
33+
log.error("해당 좋아요 항목 처리 중 오류 발생 : {}", entry.getKey(), e);
34+
}
35+
}
36+
}
37+
postLikeRedisManager.clearCache();
38+
} catch(Exception e){
39+
log.error("DB 동기화 중 오류 발생", e);
40+
}
41+
}
3042

3143
private void processLikeEntry(String key, boolean currentLikeStatus) {
3244
String[] parts = key.split(REDIS_KEY_DELIMITER);
45+
// 키 형식 검증
46+
if (parts.length < 5 || !"post".equals(parts[0]) || !"like".equals(parts[2]) || !"memberId".equals(parts[3])) {
47+
log.warn("Invalid key format: {}", key);
48+
return;
49+
}
3350
Long postId = Long.parseLong(parts[1]);
3451
Long memberId = Long.parseLong(parts[4]);
3552

@@ -41,12 +58,12 @@ private void processLikeEntry(String key, boolean currentLikeStatus) {
4158
}
4259

4360
private void updateLike(SharePostLike likeEntity, boolean redisLikeStatus) {
44-
boolean isSameStatus = likeEntity.isLiked() == redisLikeStatus;
45-
boolean newStatus = isSameStatus ? !redisLikeStatus : redisLikeStatus;
46-
47-
likeEntity.updateLikeStatus(newStatus);
61+
if (likeEntity.isLiked() != redisLikeStatus) {
62+
likeEntity.updateLikeStatus(redisLikeStatus);
63+
}
4864
}
4965

66+
//
5067
private void createLikeIfNeeded(Long postId, Long memberId, boolean currentLikeStatus) {
5168
if (currentLikeStatus) {
5269
sharePostLikeRepository.save(SharePostLike.builder()

src/main/java/io/crops/warmletter/domain/share/service/SharePostLikeService.java

Lines changed: 25 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
import io.crops.warmletter.domain.share.repository.SharePostLikeRepository;
77
import lombok.RequiredArgsConstructor;
88
import org.springframework.stereotype.Service;
9+
import java.util.Optional;
910

1011
@Service
1112
@RequiredArgsConstructor
@@ -17,7 +18,18 @@ public class SharePostLikeService {
1718

1819
public void toggleLike(Long postId) {
1920
Long memberId = authFacade.getCurrentUserId();
20-
postLikeRedisManager.toggleLike(postId, memberId);
21+
// Redis 먼저 조회 후, DB 조회
22+
Optional<Boolean> redisLikeStatus = postLikeRedisManager.getLikedStatus(postId, memberId);
23+
24+
boolean currentLikeStatus = redisLikeStatus.orElseGet(() ->
25+
sharePostLikeRepository.findBySharePostIdAndMemberId(postId, memberId)
26+
.map(entity -> entity.isLiked())
27+
.orElse(false)
28+
);
29+
30+
boolean newStatus = !currentLikeStatus;
31+
32+
postLikeRedisManager.toggleLike(postId, memberId, newStatus);
2133
}
2234

2335
public SharePostLikeResponse getLikeCountAndStatus(Long sharePostId) {
@@ -26,7 +38,18 @@ public SharePostLikeResponse getLikeCountAndStatus(Long sharePostId) {
2638

2739
if (sharePostId == null)
2840
throw new ShareInvalidInputValue();
41+
// DB 조회
42+
SharePostLikeResponse dbResponse = sharePostLikeRepository.getLikeCountAndStatus(sharePostId, memberId);
43+
// Redis에서 동기화안된 카운트 가져옴.
44+
int redisLikeCount = postLikeRedisManager.getLikeCount(sharePostId);
45+
// 좋아요 상태 확인 없으면 DB 값으로
46+
Optional<Boolean> redisLikeStatus = postLikeRedisManager.getLikedStatus(sharePostId, memberId);
47+
boolean isLiked = redisLikeStatus.orElse(dbResponse.isLiked());
48+
49+
return new SharePostLikeResponse(
50+
dbResponse.getLikeCount() + redisLikeCount,
51+
isLiked
52+
);
2953

30-
return sharePostLikeRepository.getLikeCountAndStatus(sharePostId,memberId);
3154
}
3255
}

0 commit comments

Comments
 (0)