Skip to content
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ dependencies {

// Redis
implementation 'org.springframework.boot:spring-boot-starter-data-redis'
implementation 'org.redisson:redisson-spring-boot-starter:3.52.0'

// QueryDSL
implementation 'io.github.openfeign.querydsl:querydsl-core:7.0'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,35 +10,33 @@
import OneQ.OnSurvey.domain.survey.repository.surveyInfo.SurveyInfoRepository;
import OneQ.OnSurvey.domain.survey.service.SurveyGlobalStatsService;
import OneQ.OnSurvey.global.common.exception.CustomException;
import OneQ.OnSurvey.global.common.util.RedisUtils;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.List;
import java.util.Objects;

@Slf4j
@Service
@RequiredArgsConstructor
@Transactional
public class ResponseCommandService implements ResponseCommand {

private final StringRedisTemplate redisTemplate;

private final ResponseRepository responseRepository;
private final SurveyRepository surveyRepository;
private final SurveyInfoRepository surveyInfoRepository;
private final SurveyGlobalStatsService surveyGlobalStatsService;

@Value("${redis.survey-key-prefix.potential-count}")
private String potentialKey;

@Value("${redis.survey-key-prefix.completed-count}")
private String completedKey;

@Value("${redis.survey-key-prefix.due-count}")
private String dueCountKey;

@Value("${redis.survey-key-prefix.creator-userkey}")
private String creatorKey;

Expand All @@ -61,36 +59,29 @@ public Boolean createResponse(Long surveyId, Long memberId, Long userKey) {
.orElseThrow(() -> new CustomException(SurveyErrorCode.SURVEY_INFO_NOT_FOUND));

surveyInfo.increaseCompletedCount();
Integer currCompleted = updateCounter(surveyId, userKey);
if (currCompleted != null && currCompleted.equals(surveyInfo.getDueCount())) {
int currCompleted = updateCounter(surveyId, userKey);
if (Objects.equals(currCompleted, surveyInfo.getDueCount())) {
Survey survey = surveyRepository.getSurveyById(surveyId)
.orElseThrow(() -> new CustomException(SurveyErrorCode.SURVEY_NOT_FOUND));

survey.updateSurveyStatus(SurveyStatus.CLOSED);
deleteAllRedisKeys(surveyId);
RedisUtils.deleteKeys(List.of(
this.dueCountKey + surveyId,
this.completedKey + surveyId,
this.potentialKey + surveyId,
this.creatorKey + surveyId
));
}

return true;
}

private void deleteAllRedisKeys(Long surveyId) {
redisTemplate.delete(List.of(
this.dueCountKey + surveyId,
this.completedKey + surveyId,
this.potentialKey + surveyId,
this.creatorKey + surveyId
));
}

private Integer updateCounter(Long surveyId, Long userKey) {
String potentialKey = this.potentialKey + surveyId;
String completedKey = this.completedKey + surveyId;
String memberValue = String.valueOf(userKey);

private int updateCounter(Long surveyId, Long userKey) {
// 완료 인원 추가
Long currCompleted = redisTemplate.opsForValue().increment(completedKey);
Long currCompleted = RedisUtils.incrementValue(this.completedKey + surveyId);
// 잠재 응답자 Sorted Set에서 제거
redisTemplate.opsForZSet().remove(potentialKey, memberValue);
return currCompleted != null ? currCompleted.intValue() : null;
RedisUtils.removeFromZSet(this.potentialKey + surveyId, String.valueOf(userKey));
// 완료 인원이 없어 증가가 되지 않은 경우 (null), 기존 완료 인원을 0으로 간주하여 1로 반환
return currCompleted != null ? currCompleted.intValue() : 1;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,9 @@
import OneQ.OnSurvey.domain.survey.entity.SurveyGlobalStats;
import OneQ.OnSurvey.domain.survey.model.dto.GlobalStats;
import OneQ.OnSurvey.domain.survey.repository.SurveyGlobalStatsRepository;
import OneQ.OnSurvey.global.common.util.RedisUtils;
import lombok.RequiredArgsConstructor;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
Expand All @@ -16,11 +16,10 @@
@Transactional
public class SurveyGlobalStatsService {

private final StringRedisTemplate redisTemplate;
private final SurveyGlobalStatsRepository statsRepository;

@Value("${redis.global-key-prefix.daily-user}")
private String dailyUserKeyPrefix;
private String dailyUserKey;

private SurveyGlobalStats getOrInit() {
return statsRepository.findById(1L)
Expand All @@ -47,7 +46,11 @@ public GlobalStats getStats() {
SurveyGlobalStats surveyGlobalStats = statsRepository.findById(1L)
.orElse(SurveyGlobalStats.init());

Long dailyUserCount = redisTemplate.opsForZSet().zCard(dailyUserKeyPrefix);
// 24시간 동안 활동한 유저 수 계산
Long dailyUserCount = RedisUtils.getZSetCount(
dailyUserKey,
System.currentTimeMillis() - (24 * 60 * 60 * 1000L),
Long.MAX_VALUE);
return GlobalStats.of(
surveyGlobalStats.getTotalDueCount(),
surveyGlobalStats.getTotalCompletedCount(),
Expand All @@ -59,7 +62,9 @@ public GlobalStats getStats() {
@Scheduled(fixedRate = 3600000) // 매 시간 실행
@Transactional(propagation = Propagation.NOT_SUPPORTED)
public void removeOldDailyUsers() {
long dailyRange = System.currentTimeMillis() - (24 * 60 * 60 * 1000L);
redisTemplate.opsForZSet().removeRangeByScore(dailyUserKeyPrefix, 0, dailyRange);
RedisUtils.rangeRemoveFromZSet(
dailyUserKey,
0,
System.currentTimeMillis() - (24 * 60 * 60 * 1000L));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -26,13 +26,13 @@
import OneQ.OnSurvey.global.common.exception.CustomException;
import OneQ.OnSurvey.global.common.exception.ErrorCode;
import OneQ.OnSurvey.global.common.util.AuthorizationUtils;
import OneQ.OnSurvey.global.common.util.RedisUtils;
import OneQ.OnSurvey.global.infra.discord.notifier.AlertNotifier;
import OneQ.OnSurvey.global.infra.discord.notifier.dto.SurveySubmittedAlert;
import OneQ.OnSurvey.global.infra.transaction.AfterCommitExecutor;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

Expand All @@ -49,8 +49,6 @@
@Transactional
public class SurveyCommandService implements SurveyCommand {

private final StringRedisTemplate redisTemplate;

private final SurveyRepository surveyRepository;
private final ScreeningRepository screeningRepository;
private final SurveyInfoRepository surveyInfoRepository;
Expand All @@ -63,13 +61,10 @@ public class SurveyCommandService implements SurveyCommand {

@Value("${redis.survey-key-prefix.potential-count}")
private String potentialKey;

@Value("${redis.survey-key-prefix.completed-count}")
private String completedKey;

@Value("${redis.survey-key-prefix.due-count}")
private String dueCountKey;

@Value("${redis.survey-key-prefix.creator-userkey}")
private String creatorKey;

Expand Down Expand Up @@ -244,11 +239,11 @@ public boolean sendSurveyHeartbeat(Long surveyId, Long userKey) {
String potentialKey = this.potentialKey + surveyId;
String memberValue = String.valueOf(userKey);

if (redisTemplate.opsForZSet().score(potentialKey, memberValue) == null) {
if (RedisUtils.getZSetScore(potentialKey, memberValue) == null) {
return false;
}
// 잠재 응답자 목록에 현재 시간을 score로 사용자 갱신
redisTemplate.opsForZSet().add(potentialKey, memberValue, System.currentTimeMillis());
RedisUtils.addToZSet(potentialKey, memberValue, System.currentTimeMillis());
return true;
}

Expand All @@ -264,18 +259,6 @@ public void updateSurveyOwner(SurveyOwnerChangeDto changeDto) {
changeDto.surveyId(), changeDto.newMemberId());
}

private void setValue(String keyPrefix, Long surveyId, String value, Duration duration) {
redisTemplate.opsForValue().set(
keyPrefix + surveyId, value, duration
);
}

private void addZSetValue(String keyPrefix, Long surveyId, String value) {
redisTemplate.opsForZSet().add(
keyPrefix + surveyId, value, System.currentTimeMillis()
);
}

private Survey getSurvey(Long surveyId) {
return surveyRepository.getSurveyById(surveyId)
.orElseThrow(() -> new CustomException(ErrorCode.INVALID_REQUEST));
Expand Down Expand Up @@ -338,10 +321,11 @@ private SurveyFormResponse finalizeSubmit(
}

private void applySurveyRuntimeCache(Long surveyId, Long userKey, Integer dueCount, LocalDateTime deadline) {
Duration duration = Duration.between(LocalDateTime.now(), deadline);
setValue(this.dueCountKey, surveyId, String.valueOf(dueCount), duration);
setValue(this.completedKey, surveyId, "0", duration);
addZSetValue(this.potentialKey, surveyId, String.valueOf(userKey));
setValue(this.creatorKey, surveyId, String.valueOf(userKey), duration);
Duration ttl = Duration.between(LocalDateTime.now(), deadline);

RedisUtils.setValue(this.dueCountKey + surveyId, String.valueOf(dueCount), ttl);
RedisUtils.setValue(this.completedKey + surveyId, "0", ttl);
RedisUtils.addToZSet(this.potentialKey + surveyId, String.valueOf(userKey), System.currentTimeMillis());
RedisUtils.setValue(this.creatorKey + surveyId, String.valueOf(userKey), ttl);
}
}
Loading
Loading