Skip to content
Open
Show file tree
Hide file tree
Changes from all 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 @@ -35,6 +35,7 @@ dependencies {
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
implementation 'org.springframework.security:spring-security-crypto'
implementation 'org.springframework.boot:spring-boot-starter-web'
implementation 'org.springframework.boot:spring-boot-starter-data-redis'
implementation 'io.micrometer:micrometer-tracing-bridge-brave'
implementation 'io.micrometer:micrometer-registry-prometheus'
implementation 'io.zipkin.reporter2:zipkin-reporter-brave'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
import org.creditto.core_banking.global.common.CurrencyCode;
import org.creditto.core_banking.global.response.error.ErrorBaseCode;
import org.creditto.core_banking.global.response.exception.CustomBaseException;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

Expand All @@ -50,6 +51,7 @@ public class RemittanceProcessorService {
private final ExchangeService exchangeService;
private final TransactionService transactionService;
private final RemittanceFeeService remittanceFeeService;
private final RedisTemplate<String, Object> redisTemplate;

/**
* 전달된 Command를 기반으로 해외송금의 모든 단계를 실행합니다.
Expand Down Expand Up @@ -135,6 +137,15 @@ public OverseasRemittanceResponseDto execute(final ExecuteRemittanceCommand comm

accountRepository.save(account);

// 정기 송금일 경우
if (command.regRemId() != null) {
String regularRemittanceHistoryKey = "regularRemittanceHistory::" + command.regRemId();
redisTemplate.delete(regularRemittanceHistoryKey);
} else { // 일회성 송금일 경우
String oneTimeRemittanceHistoryKey = "oneTimeRemittanceHistories::" + userId;
redisTemplate.delete(oneTimeRemittanceHistoryKey);
}

return OverseasRemittanceResponseDto.from(overseasRemittance);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import org.creditto.core_banking.domain.overseasremittance.dto.OverseasRemittanceResponseDto;
import org.creditto.core_banking.domain.overseasremittance.repository.OverseasRemittanceRepository;
import org.creditto.core_banking.global.response.exception.CustomBaseException;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

Expand All @@ -20,6 +21,7 @@
public class RemittanceQueryService {

private final OverseasRemittanceRepository remittanceRepository;
private final RedisTemplate<String, Object> redisTemplate;

/**
* 특정 고객의 모든 해외송금 내역을 조회합니다.
Expand All @@ -28,10 +30,24 @@ public class RemittanceQueryService {
* @return 고객의 송금 내역 DTO 리스트
*/
public List<OverseasRemittanceResponseDto> getRemittanceList(Long userId) {
return remittanceRepository.findByUserIdWithDetails(userId)
String key = "oneTimeRemittanceHistories::" + userId;

// redis에서 캐시 확인
List<OverseasRemittanceResponseDto> cachedList = (List<OverseasRemittanceResponseDto>) redisTemplate.opsForValue().get(key);
if (cachedList != null) {
return cachedList;
}

// 캐시 없으면 DB 조회
List<OverseasRemittanceResponseDto> dbList = remittanceRepository.findByUserIdWithDetails(userId)
.stream()
.map(OverseasRemittanceResponseDto::from)
.toList();

// 결과를 redis에 저장
redisTemplate.opsForValue().set(key, dbList);

return dbList;
}

/**
Expand Down
Original file line number Diff line number Diff line change
@@ -1,18 +1,19 @@
package org.creditto.core_banking.domain.regularremittance.dto;

import lombok.AccessLevel;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;

import java.math.BigDecimal;
import java.time.LocalDate;

@Getter
@Builder
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@AllArgsConstructor
public class RemittanceHistoryDto {
Long remittanceId;
BigDecimal sendAmount;
BigDecimal exchangeRate;
LocalDate createdDate;
private Long remittanceId;
private BigDecimal sendAmount;
private BigDecimal exchangeRate;
private LocalDate createdDate;
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
package org.creditto.core_banking.domain.regularremittance.service;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.creditto.core_banking.domain.account.entity.Account;
import org.creditto.core_banking.domain.account.repository.AccountRepository;
import org.creditto.core_banking.domain.overseasremittance.entity.OverseasRemittance;
Expand All @@ -15,22 +20,30 @@
import org.creditto.core_banking.domain.regularremittance.repository.RegularRemittanceRepository;
import org.creditto.core_banking.global.response.error.ErrorBaseCode;
import org.creditto.core_banking.global.response.exception.CustomBaseException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.time.DayOfWeek;
import java.util.List;
import java.util.Objects;

@Slf4j
@Service
@RequiredArgsConstructor
@Transactional(readOnly = true)
public class RegularRemittanceService {

private static final Logger log = LoggerFactory.getLogger(RegularRemittanceService.class);
private final RegularRemittanceRepository regularRemittanceRepository;
private final OverseasRemittanceRepository overseasRemittanceRepository;
private final AccountRepository accountRepository;
private final RecipientFactory recipientFactory;
private final RedisTemplate<String, Object> redisTemplate;
private final ObjectMapper objectMapper = new ObjectMapper().registerModule(new JavaTimeModule());


/**
* 특정 사용자의 모든 정기송금 설정 내역을 조회합니다.
Expand Down Expand Up @@ -97,19 +110,44 @@ public RemittanceDetailDto getScheduledRemittanceDetail(Long userId, Long regRem
* @return 해당 정기송금 설정에 대한 모든 송금 기록 목록 ({@link RemittanceHistoryDto})
*/
public List<RemittanceHistoryDto> getRegularRemittanceHistoryByRegRemId(Long userId, Long regRemId) {
String key = "regularRemittanceHistory::" + regRemId;

try {
Object cachedObject = redisTemplate.opsForValue().get(key);
if (cachedObject != null) {
if (cachedObject instanceof String) {
return objectMapper.readValue((String) cachedObject, new TypeReference<List<RemittanceHistoryDto>>() {});
}
return (List<RemittanceHistoryDto>) cachedObject;
}
} catch (Exception e) {
log.error("Redis cache deserialization error", e);
}

// 캐시 없으면 DB 조회
RegularRemittance regularRemittance = regularRemittanceRepository.findById(regRemId)
.orElseThrow(() -> new CustomBaseException(ErrorBaseCode.NOT_FOUND_REGULAR_REMITTANCE));

verifyUserOwnership(regularRemittance.getAccount().getUserId(), userId);

return overseasRemittanceRepository.findByRecur_RegRemIdOrderByCreatedAtDesc(regRemId).stream()
List<RemittanceHistoryDto> dbList = overseasRemittanceRepository.findByRecur_RegRemIdOrderByCreatedAtDesc(regRemId).stream()
.map(overseas -> new RemittanceHistoryDto(
overseas.getRemittanceId(),
overseas.getSendAmount(),
overseas.getExchange().getExchangeRate(),
overseas.getCreatedAt().toLocalDate()
))
.toList();

try {
// 결과를 JSON 문자열로 변환하여 Redis에 저장
String jsonString = objectMapper.writeValueAsString(dbList);
redisTemplate.opsForValue().set(key, jsonString);
} catch (JsonProcessingException e) {
log.error("Redis cache serialization error", e);
}

return dbList;
}

/**
Expand Down Expand Up @@ -220,10 +258,12 @@ public void updateScheduledRemittance(Long regRemId, Long userId, RegularRemitta
} else if (remittance instanceof WeeklyRegularRemittance weekly) {
weekly.updateSchedule(dto.getScheduledDay());
}
// 정기송금 "설정"이 변경되었으므로, 관련 "내역" 캐시도 삭제
redisTemplate.delete("regularRemittanceHistory::" + regRemId);
}

/**
* 기존 정기 해외송금 설정을 삭제합니다.
* 기존 정기 해외송금 설정을 삭제합니다
*
* @param regRemId 삭제할 정기송금의 ID
* @param userId 사용자 ID
Expand All @@ -235,6 +275,8 @@ public void deleteScheduledRemittance(Long regRemId, Long userId) {
verifyUserOwnership(remittance.getAccount().getUserId(), userId);

regularRemittanceRepository.delete(remittance);
// 정기송금 "설정"이 삭제되었으므로, 관련 "내역" 캐시도 삭제
redisTemplate.delete("regularRemittanceHistory::" + regRemId);
}

private void verifyUserOwnership(Long ownerId, Long requesterId) {
Expand Down Expand Up @@ -273,4 +315,4 @@ private boolean hasDuplicateRemittance(Account account, Recipient recipient, Reg
return false;
}

}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package org.creditto.core_banking.global.config;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;

@Configuration
public class RedisConfig {

@Bean
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory connectionFactory) {
RedisTemplate<String, Object> template = new RedisTemplate<>();
template.setConnectionFactory(connectionFactory);

// LocalDate, LocalDateTime 직렬화를 위한 ObjectMapper 설정
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.registerModule(new JavaTimeModule());

// JavaTimeModule이 등록된 ObjectMapper를 사용하는 Serializer 생성
GenericJackson2JsonRedisSerializer serializer = new GenericJackson2JsonRedisSerializer(objectMapper);

// key:value
template.setKeySerializer(new StringRedisSerializer());
template.setValueSerializer(serializer);

// hash key:value
template.setHashKeySerializer(new StringRedisSerializer());
template.setHashValueSerializer(serializer);

return template;
}
}
5 changes: 5 additions & 0 deletions src/main/resources/application.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,11 @@ spring:
username: ${DB_USERNAME}
password: ${DB_PASSWORD}
driver-class-name: com.mysql.cj.jdbc.Driver
data:
redis:
host: localhost
port: 6379
timeout: 2000

scheduler:
remittance:
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package org.creditto.core_banking.domain.redis;

import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.data.redis.core.RedisTemplate;

@Disabled
@SpringBootTest
public class RedisTest {

@Autowired
private RedisTemplate<String, Object> redisTemplate;

@Test
void testRedisConnection() {
String key = "test-key";
String expectedValue = "redis";
redisTemplate.opsForValue().set(key, expectedValue);
Object value = redisTemplate.opsForValue().get(key);
System.out.println("Redis Value: " + value);
}
}
Loading