Skip to content
Merged
Empty file added keys
Empty file.
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,9 @@
import jakarta.servlet.http.HttpServletRequest;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
Expand All @@ -22,16 +24,23 @@
import java.util.stream.Collectors;

@Service
@RequiredArgsConstructor
@Slf4j
public class BoardViewService {

private final BoardRepository boardRepository;
private final String VIEW_COUNT_KEY = "board:view:";
private final String VIEW_RANKING_KEY = "board:ranking:";
private final RedisTemplate<String, String> redisTemplate;
private final StringRedisTemplate redisTemplate;
private final HttpServletRequest request;

public BoardViewService(
BoardRepository boardRepository,
@Qualifier("viewCountRedisTemplate")StringRedisTemplate redisTemplate,
HttpServletRequest request) {
this.boardRepository = boardRepository;
this.redisTemplate = redisTemplate;
this.request = request;
}

/**
* 조회수 높은 게시글 조회 하는 메서드입니다.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
import com.example.gamemate.global.constant.ErrorCode;
import com.example.gamemate.global.exception.ApiException;
import lombok.RequiredArgsConstructor;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
Expand All @@ -22,16 +23,21 @@

@Service
@Transactional
@RequiredArgsConstructor
public class CouponService {

private static final String COUPON_STOCK_KEY = "coupon:%d:stock";

private final CouponRepository couponRepository;
private final UserCouponRepository userCouponRepository;
private final StringRedisTemplate redisTemplate;

private static final String COUPON_STOCK_KEY = "coupon:%d:stock";

private String getCouponStockKey(Long couponId) {
return String.format(COUPON_STOCK_KEY, couponId);
public CouponService(
CouponRepository couponRepository,
UserCouponRepository userCouponRepository,
@Qualifier("couponRedisTemplate") StringRedisTemplate redisTemplate) {
this.couponRepository = couponRepository;
this.userCouponRepository = userCouponRepository;
this.redisTemplate = redisTemplate;
}

public CouponCreateResponseDto createCoupon(CouponCreateRequestDto requestDto, User loginUser) {
Expand Down Expand Up @@ -59,6 +65,12 @@ public CouponCreateResponseDto createCoupon(CouponCreateRequestDto requestDto, U
return new CouponCreateResponseDto(savedCoupon);
}

private void validateCouponDates(LocalDateTime startAt, LocalDateTime expiredAt) {
if (startAt.isAfter(expiredAt)) {
throw new ApiException(ErrorCode.INVALID_COUPON_DATE);
}
}

public CouponIssueResponseDto issueCoupon(Long couponId, User loginUser) {
Coupon coupon = couponRepository.findById(couponId)
.orElseThrow(() -> new ApiException(ErrorCode.COUPON_NOT_FOUND));
Expand Down Expand Up @@ -131,9 +143,7 @@ public void useCoupon(Long userCouponId, User loginUser) {
userCoupon.updateUsedAt();
}

private void validateCouponDates(LocalDateTime startAt, LocalDateTime expiredAt) {
if (startAt.isAfter(expiredAt)) {
throw new ApiException(ErrorCode.INVALID_COUPON_DATE);
}
private String getCouponStockKey(Long couponId) {
return String.format(COUPON_STOCK_KEY, couponId);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,8 @@
import com.example.gamemate.global.exception.ApiException;
import jakarta.annotation.PostConstruct;
import jakarta.transaction.Transactional;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.data.redis.connection.stream.StreamInfo;
import org.springframework.data.redis.connection.stream.StreamRecords;
import org.springframework.data.redis.core.RedisTemplate;
Expand All @@ -25,17 +25,28 @@
* 알림을 처리하는 서비스 클래스입니다.
*/
@Service
@RequiredArgsConstructor
@Slf4j
public class NotificationService {

private static final String STREAM_KEY = "notification_stream";
private static final String GROUP_NAME = "notification-group";
private static final Long DEFAULT_TIMEOUT = 60L * 1000 * 60;

private final NotificationRepository notificationRepository;
private final EmitterRepository emitterRepository;
private final RedisStreamService redisStreamService;
private final RedisTemplate<String, Object> redisTemplate;
private static final String STREAM_KEY = "notification_stream";
private static final String GROUP_NAME = "notification-group";
private static final Long DEFAULT_TIMEOUT = 60L * 1000 * 60;

public NotificationService(
NotificationRepository notificationRepository,
EmitterRepository emitterRepository,
RedisStreamService redisStreamService,
@Qualifier("notificationRedisTemplate") RedisTemplate<String, Object> redisTemplate) {
this.notificationRepository = notificationRepository;
this.emitterRepository = emitterRepository;
this.redisStreamService = redisStreamService;
this.redisTemplate = redisTemplate;
}

/**
* 레디스 스트림의 스트림그룹을 생성합니다.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@
import com.example.gamemate.domain.notification.dto.NotificationResponseDto;
import com.example.gamemate.domain.notification.repository.EmitterRepository;
import jakarta.annotation.PostConstruct;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.data.redis.connection.stream.*;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.scheduling.annotation.Scheduled;
Expand All @@ -16,19 +16,27 @@
import java.util.*;

@Service
@RequiredArgsConstructor
@Slf4j
public class RedisStreamService {
private final RedisTemplate<String, Object> redisTemplate;
private final EmitterRepository emitterRepository;

private static final String STREAM_KEY = "notification_stream";
private static final String GROUP_NAME = "notification-group";

private static final String CONSUMER_PREFIX = "consumer";
private static final int BATCH_SIZE = 100;
private static final Duration POLL_TIMEOUT = Duration.ofMillis(100);
private static final int MAX_STREAM_LENGTH = 1000;

private final RedisTemplate<String, Object> redisTemplate;
private final EmitterRepository emitterRepository;

public RedisStreamService(
@Qualifier("notificationRedisTemplate") RedisTemplate<String, Object> redisTemplate,
EmitterRepository emitterRepository) {
this.redisTemplate = redisTemplate;
this.emitterRepository = emitterRepository;
}

@PostConstruct
public void init() {
createStreamGroup();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import com.example.gamemate.domain.coupon.repository.CouponRepository;
import com.example.gamemate.domain.coupon.repository.UserCouponRepository;
import lombok.RequiredArgsConstructor;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.context.event.ApplicationReadyEvent;
import org.springframework.context.event.EventListener;
import org.springframework.data.redis.core.StringRedisTemplate;
Expand All @@ -12,16 +13,21 @@
import java.util.List;

@Component
@RequiredArgsConstructor
public class CouponDataSynchronizer {

private static final String COUPON_STOCK_KEY = "coupon:%d:stock";

private final CouponRepository couponRepository;
private final UserCouponRepository userCouponRepository;
private final StringRedisTemplate redisTemplate;

private static final String COUPON_STOCK_KEY = "coupon:%d:stock";

private String getCouponStockKey(Long couponId) {
return String.format(COUPON_STOCK_KEY, couponId);
public CouponDataSynchronizer(
CouponRepository couponRepository,
UserCouponRepository userCouponRepository,
@Qualifier("couponRedisTemplate") StringRedisTemplate redisTemplate) {
this.couponRepository = couponRepository;
this.userCouponRepository = userCouponRepository;
this.redisTemplate = redisTemplate;
}

@EventListener(ApplicationReadyEvent.class)
Expand All @@ -34,4 +40,9 @@ public void syncCouponStock() {
String.valueOf(remainingStock));
}
}


private String getCouponStockKey(Long couponId) {
return String.format(COUPON_STOCK_KEY, couponId);
}
}
96 changes: 94 additions & 2 deletions src/main/java/com/example/gamemate/global/config/RedisConfig.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,85 @@

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.connection.RedisStandaloneConfiguration;
import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;

@Configuration
public class RedisConfig {

// 기본 RedisConnectionFactory
@Bean
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory connectionFactory) {
@Primary
public RedisConnectionFactory redisConnectionFactory() {
RedisStandaloneConfiguration config = new RedisStandaloneConfiguration();
config.setDatabase(0);
return new LettuceConnectionFactory(config);
}

// 기본 RedisTemplate
@Bean
@Primary
public RedisTemplate<String, Object> redisTemplate() {
RedisTemplate<String, Object> template = new RedisTemplate<>();
template.setConnectionFactory(redisConnectionFactory());
template.setKeySerializer(new StringRedisSerializer());
template.setValueSerializer(new Jackson2JsonRedisSerializer<>(Object.class));
template.setHashKeySerializer(new StringRedisSerializer());
template.setHashValueSerializer(new Jackson2JsonRedisSerializer<>(Object.class));
return template;
}

// DB 1: 알림
@Bean
public RedisConnectionFactory notificationConnectionFactory() {
RedisStandaloneConfiguration config = new RedisStandaloneConfiguration();
config.setDatabase(1);
return new LettuceConnectionFactory(config);
}

// DB 2: 조회수
@Bean
public RedisConnectionFactory viewCountConnectionFactory() {
RedisStandaloneConfiguration config = new RedisStandaloneConfiguration();
config.setDatabase(2);
return new LettuceConnectionFactory(config);
}

// DB 3: 리프레시 토큰
@Bean
public RedisConnectionFactory refreshTokenConnectionFactory() {
RedisStandaloneConfiguration config = new RedisStandaloneConfiguration();
config.setDatabase(3);
return new LettuceConnectionFactory(config);
}

// DB 4: 토큰 블랙리스트
@Bean
public RedisConnectionFactory blacklistConnectionFactory() {
RedisStandaloneConfiguration config = new RedisStandaloneConfiguration();
config.setDatabase(4);
return new LettuceConnectionFactory(config);
}

// DB 5: 쿠폰
@Bean
public RedisConnectionFactory couponConnectionFactory() {
RedisStandaloneConfiguration config = new RedisStandaloneConfiguration();
config.setDatabase(5);
return new LettuceConnectionFactory(config);
}

// 알림 RedisTemplate (DB 1)
@Bean
public RedisTemplate<String, Object> notificationRedisTemplate() {
RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
redisTemplate.setConnectionFactory(connectionFactory);
redisTemplate.setConnectionFactory(notificationConnectionFactory());

// String 타입을 위한 직렬화 설정
redisTemplate.setKeySerializer(new StringRedisSerializer());
Expand All @@ -22,4 +90,28 @@ public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory connec

return redisTemplate;
}

// 조회수 RedisTemplate (DB 2)
@Bean
public StringRedisTemplate viewCountRedisTemplate() {
return new StringRedisTemplate(viewCountConnectionFactory());
}

// 리프레시 토큰 RedisTemplate (DB 3)
@Bean
public StringRedisTemplate refreshTokenRedisTemplate() {
return new StringRedisTemplate(refreshTokenConnectionFactory());
}

// 토큰 블랙리스트 RedisTemplate (DB 4)
@Bean
public StringRedisTemplate blacklistRedisTemplate() {
return new StringRedisTemplate(blacklistConnectionFactory());
}

// 쿠폰 RedisTemplate (DB 5)
@Bean
public StringRedisTemplate couponRedisTemplate() {
return new StringRedisTemplate(couponConnectionFactory());
}
}
8 changes: 5 additions & 3 deletions src/main/resources/application.properties
Original file line number Diff line number Diff line change
Expand Up @@ -61,11 +61,13 @@ spring.mail.properties.mail.smtp.starttls.enable=true

# DEBUG
logging.level.org.springframework.security=DEBUG
logging.level.org.springframework.security.oauth2=TRACE
logging.level.org.springframework.web=DEBUG
logging.level.org.springframework.web.servlet=DEBUG
logging.level.org.springframework.security.oauth2=TRACE
logging.level.org.springframework.web.client=TRACE
logging.level.org.springframework.web.client=INFO
logging.level.com.example.gamemate=DEBUG
logging.level.org.springframework.data.redis=INFO
logging.level.com.example.gamemate.domain.notification.service.RedisStreamService=INFO

# Gemini
gemini.api.url=${GEMINI_URL}
Expand All @@ -87,4 +89,4 @@ spring.jpa.properties.jakarta.persistence.lock.timeout=3000

# Redis
spring.data.redis.host=${REDIS_HOST}
spring.data.redis.port=6379
spring.data.redis.port=6379
Loading