Skip to content
Merged
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
Original file line number Diff line number Diff line change
@@ -1,25 +1,16 @@
package com.planetrush.planetrush.core.config;

import java.time.Duration;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.cache.CacheManager;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.cache.RedisCacheConfiguration;
import org.springframework.data.redis.cache.RedisCacheManager;
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.core.StringRedisTemplate;
import org.springframework.data.redis.repository.configuration.EnableRedisRepositories;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.RedisSerializationContext;
import org.springframework.data.redis.serializer.StringRedisSerializer;
import org.springframework.transaction.annotation.EnableTransactionManagement;

import com.planetrush.planetrush.member.service.dto.GetMyProgressAvgDto;

@Configuration
@EnableCaching
@EnableRedisRepositories
Expand All @@ -38,22 +29,9 @@ public RedisConnectionFactory redisConnectionFactory() {
}

@Bean
public RedisTemplate<?, ?> redisTemplate() {
RedisTemplate<byte[], byte[]> template = new RedisTemplate<>();
public RedisTemplate<String, String> redisTemplate() {
StringRedisTemplate template = new StringRedisTemplate();
template.setConnectionFactory(redisConnectionFactory());
return template;
}

@Bean
public CacheManager cacheManager(RedisConnectionFactory redisConnectionFactory) {
RedisCacheConfiguration cacheConfig = RedisCacheConfiguration.defaultCacheConfig()
.entryTtl(Duration.ofHours(24))
.serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(new StringRedisSerializer()))
.serializeValuesWith(
RedisSerializationContext.SerializationPair.fromSerializer(new Jackson2JsonRedisSerializer<>(
GetMyProgressAvgDto.class)));
return RedisCacheManager.builder(redisConnectionFactory)
.cacheDefaults(cacheConfig)
.build();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
import java.util.List;
import java.util.stream.Collectors;

import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

Expand Down Expand Up @@ -54,7 +53,7 @@ public List<PlanetCollectionDto> getPlanetCollections(CollectionSearchCond searc
* <p>이 메서드는 반환값을 캐싱하여 관리합니다.</p>
* <p>캐시 미스가 발생하는 경우에만 플라스크 서버로 API 요청을 전송하여 새로운 데이터로 캐시에 저장합니다.</p>
*/
@Cacheable(value = "myProgressAvgCache", key = "#memberId")
// TODO: 카페인 캐시를 이용하도록 변경
@Override
public GetMyProgressAvgDto getMyProgressAvgPer(Long memberId) {
Member member = memberRepository.findById(memberId)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package com.planetrush.planetrush.planet.exception;

public class DuplicatedDeleteResidentRequestException extends RuntimeException {

public DuplicatedDeleteResidentRequestException() {
}

public DuplicatedDeleteResidentRequestException(String message) {
super(message);
}

public DuplicatedDeleteResidentRequestException(String message, Throwable cause) {
super(message, cause);
}

public DuplicatedDeleteResidentRequestException(Throwable cause) {
super(cause);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package com.planetrush.planetrush.planet.exception;

public class DuplicatedRegisterResidentRequestException extends RuntimeException {

public DuplicatedRegisterResidentRequestException() {
}

public DuplicatedRegisterResidentRequestException(String message) {
super(message);
}

public DuplicatedRegisterResidentRequestException(String message, Throwable cause) {
super(message, cause);
}

public DuplicatedRegisterResidentRequestException(Throwable cause) {
super(cause);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,11 @@
import java.time.LocalDateTime;
import java.time.temporal.ChronoUnit;
import java.util.List;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.ValueOperations;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

Expand All @@ -16,6 +19,8 @@
import com.planetrush.planetrush.planet.domain.Planet;
import com.planetrush.planetrush.planet.domain.Resident;
import com.planetrush.planetrush.planet.domain.image.DefaultPlanetImg;
import com.planetrush.planetrush.planet.exception.DuplicatedDeleteResidentRequestException;
import com.planetrush.planetrush.planet.exception.DuplicatedRegisterResidentRequestException;
import com.planetrush.planetrush.planet.exception.InvalidStartDateException;
import com.planetrush.planetrush.planet.exception.PlanetNotFoundException;
import com.planetrush.planetrush.planet.exception.ResidentAlreadyExistsException;
Expand Down Expand Up @@ -48,6 +53,8 @@
@RequiredArgsConstructor
public class PlanetServiceImpl implements PlanetService {

private final RedisTemplate redisTemplate;

private final MemberRepository memberRepository;
private final PlanetRepository planetRepository;
private final ResidentRepository residentRepository;
Expand Down Expand Up @@ -304,6 +311,15 @@ public void registerResident(PlanetSubscriptionDto dto) {
.orElseThrow(() -> new MemberNotFoundException("Member not found with ID: " + dto.getMemberId()));
Planet planet = planetRepository.findByIdForUpdate(dto.getPlanetId())
.orElseThrow(() -> new PlanetNotFoundException("Planet not found with ID: " + dto.getPlanetId()));
ValueOperations ops = redisTemplate.opsForValue();
Boolean isFirstRequest = ops.setIfAbsent(
"idempotent:register-resident:" + "member:" + member.getId() + "planet:" + planet.getId(),
"true", 10,
TimeUnit.SECONDS);
if (Boolean.FALSE.equals(isFirstRequest)) {
log.error("[IDEMPOTENT] 행성 가입 중복 요청 발생, 회원={}, 행성={}", member.getId(), planet.getId());
throw new DuplicatedRegisterResidentRequestException();
}
if(residentRepositoryCustom.getReadyAndInProgressResidents(member) >= 9) {
throw new ResidentOverflowException("resident count overflow");
}
Expand All @@ -324,11 +340,22 @@ public void registerResident(PlanetSubscriptionDto dto) {
@Transactional
@Override
public void deleteResident(PlanetSubscriptionDto dto) {
Resident resident = residentRepository.findByMemberIdAndPlanetId(dto.getMemberId(), dto.getPlanetId())
.orElseThrow(() -> new ResidentNotFoundException(
"Resident not found member id: " + dto.getMemberId() + " and planet id: " + dto.getPlanetId()));
Member member = memberRepository.findById(dto.getMemberId())
.orElseThrow(() -> new MemberNotFoundException("Member not found with ID: " + dto.getMemberId()));
Planet planet = planetRepository.findByIdForUpdate(dto.getPlanetId())
.orElseThrow(() -> new PlanetNotFoundException("Planet not found with ID: " + dto.getPlanetId()));
ValueOperations ops = redisTemplate.opsForValue();
Boolean isFirstRequest = ops.setIfAbsent(
"idempotent:delete-resident:" + "member:" + member.getId() + "planet:" + planet.getId(),
"true", 10,
TimeUnit.SECONDS);
if (Boolean.FALSE.equals(isFirstRequest)) {
log.error("[IDEMPOTENT] 행성 탈퇴 중복 요청 발생, 회원={}, 행성={}", member.getId(), planet.getId());
throw new DuplicatedDeleteResidentRequestException();
}
Resident resident = residentRepository.findByMemberIdAndPlanetId(member.getId(), planet.getId())
.orElseThrow(() -> new ResidentNotFoundException(
"Resident not found member id: " + member.getId() + " and planet id: " + planet.getId()));
planet.participantLeave();
residentRepository.delete(resident);
}
Expand Down
78 changes: 78 additions & 0 deletions src/main/resources/application-test.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
spring:
servlet:
multipart:
max-file-size: 50MB
config:
import: optional:file:.env[.properties]
datasource:
url: jdbc:mysql://localhost:3306/planetrush?serverTimezone=UTC&useUnicode=yes&characterEncoding=UTF-8
driver-class-name: com.mysql.cj.jdbc.Driver
username: root
password: root

data:
redis:
host: localhost
port: 6379

jpa:
hibernate:
ddl-auto: update
show-sql: false
properties:
hibernate:
format_sql: true

logging:
level:
org:
springframework:
web: DEBUG
com:
example: DEBUG

jwt:
secret:
key: ${JWT_SECRET_KEY}
issuer: ${JWT_ISSUER}
salt: ${JWT_SALT}
access-token:
expiretime: ${JWT_ACCESS_TOKEN_EXPIRETIME}
refresh-token:
expiretime: ${JWT_REFRESH_TOKEN_EXPIRETIME}

kakao:
secret:
key: ${KAKAO_SECRET_KEY}
loginurl: ${KAKAO_LOGIN_URL}
logouturl: ${KAKAO_LOGOUT_URL}

cloud:
aws:
credentials:
access-key: ${AWS_S3_ACCESS_KEY}
secret-key: ${AWS_S3_SECRET_KEY}
region:
static: ${AWS_S3_BUCKET_REGION}
stack:
auto: false
s3:
bucket: ${AWS_S3_BUCKET_NAME}

flask:
verifyurl: ${FLASK_VERIFY_URL}
progressavgurl: ${FLASK_PROGRESS_AVG_URL}

notification:
mattermost:
enabled: false # mmSender를 사용할 지 여부, false면 알림이 오지 않는다
webhook-url: ${MATTERMOST_WEBHOOK_URL} # 위의 Webhook URL을 기입
color: ${MATTERMOST_COLOR} # attachment에 왼쪽 사이드 컬러. default=red
author-name: ${MATTERMOST_AUTHOR_NAME} # attachment의 상단에 나오는 이름
author-icon: ${MATTERMOST_AUTHOR_ICON} # author-icon 왼쪽에 나올 아이콘의 url링크
footer: # attachment에 하단에 나올 부분. default=현재 시간

scheduled-task:
change-planet-status-cron: 0 0 0 * * ?
ban-if-last-verification-older-than-three-days: 0 5 0 * * ?
planet-complete-destroy: 0 10 0 * * ?
7 changes: 7 additions & 0 deletions src/test/java/com/planetrush/planetrush/IntegrationTest.java
Original file line number Diff line number Diff line change
@@ -1,7 +1,14 @@
package com.planetrush.planetrush;

import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.web.server.LocalServerPort;
import org.springframework.test.context.ActiveProfiles;

@ActiveProfiles("test")
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
public abstract class IntegrationTest {

@LocalServerPort
private int port;

}
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,10 @@

import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.ActiveProfiles;

@SpringBootTest
@ActiveProfiles(profiles = "test")
class PlanetrushApplicationTests {

@Test
Expand Down
Loading
Loading