diff --git a/src/main/java/kr/pickple/back/common/config/CacheConfig.java b/src/main/java/kr/pickple/back/common/config/CacheConfig.java index 5d735a81..c4e30f17 100644 --- a/src/main/java/kr/pickple/back/common/config/CacheConfig.java +++ b/src/main/java/kr/pickple/back/common/config/CacheConfig.java @@ -20,6 +20,8 @@ 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.listener.PatternTopic; +import org.springframework.data.redis.listener.RedisMessageListenerContainer; import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer; import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer; import org.springframework.data.redis.serializer.RedisSerializationContext; @@ -32,6 +34,7 @@ import com.github.benmanes.caffeine.cache.Caffeine; import kr.pickple.back.common.config.property.RedisProperties; +import kr.pickple.back.game.service.RedisExpirationListener; import lombok.RequiredArgsConstructor; @Configuration @@ -123,4 +126,16 @@ public RedisTemplate redisTemplate() { return redisTemplate; } + + @Bean + public RedisMessageListenerContainer redisContainer( + final RedisConnectionFactory redisConnectionFactory, + final RedisExpirationListener redisExpirationListener + ) { + final RedisMessageListenerContainer container = new RedisMessageListenerContainer(); + container.setConnectionFactory(redisConnectionFactory); + container.addMessageListener(redisExpirationListener, + new PatternTopic("__keyevent@*__:expired")); + return container; + } } diff --git a/src/main/java/kr/pickple/back/game/domain/Game.java b/src/main/java/kr/pickple/back/game/domain/Game.java index 21cdbbab..2e8429b6 100644 --- a/src/main/java/kr/pickple/back/game/domain/Game.java +++ b/src/main/java/kr/pickple/back/game/domain/Game.java @@ -147,7 +147,11 @@ private Game( updateGamePositions(positions); } - + + public void updateGameStatus(final GameStatus gameStatus) { + status = gameStatus; + } + private void updateGamePositions(final List positions) { gamePositions.updateGamePositions(this, positions); } diff --git a/src/main/java/kr/pickple/back/game/service/GameService.java b/src/main/java/kr/pickple/back/game/service/GameService.java index 2c9b937b..66eca51b 100644 --- a/src/main/java/kr/pickple/back/game/service/GameService.java +++ b/src/main/java/kr/pickple/back/game/service/GameService.java @@ -2,10 +2,12 @@ import static kr.pickple.back.chat.domain.RoomType.*; import static kr.pickple.back.common.domain.RegistrationStatus.*; +import static kr.pickple.back.game.domain.GameStatus.*; import static kr.pickple.back.game.exception.GameExceptionCode.*; import static kr.pickple.back.member.exception.MemberExceptionCode.*; import java.text.MessageFormat; +import java.time.Duration; import java.time.LocalDateTime; import java.time.format.DateTimeFormatter; import java.util.List; @@ -25,6 +27,7 @@ import kr.pickple.back.alarm.event.game.GameJoinRequestNotificationEvent; import kr.pickple.back.alarm.event.game.GameMemberJoinedEvent; import kr.pickple.back.alarm.event.game.GameMemberRejectedEvent; +import kr.pickple.back.auth.repository.RedisRepository; import kr.pickple.back.chat.domain.ChatRoom; import kr.pickple.back.chat.service.ChatMessageService; import kr.pickple.back.chat.service.ChatRoomService; @@ -42,7 +45,6 @@ import kr.pickple.back.game.exception.GameException; import kr.pickple.back.game.repository.GameMemberRepository; import kr.pickple.back.game.repository.GameRepository; -import kr.pickple.back.map.repository.MapPolygonRepository; import kr.pickple.back.member.domain.Member; import kr.pickple.back.member.dto.response.MemberResponse; import kr.pickple.back.member.exception.MemberException; @@ -63,8 +65,8 @@ public class GameService { private final AddressService addressService; private final ChatRoomService chatRoomService; private final ChatMessageService chatMessageService; - private final MapPolygonRepository mapPolygonRepository; private final ApplicationEventPublisher eventPublisher; + private final RedisRepository redisRepository; @Transactional public GameIdResponse createGame(final GameCreateRequest gameCreateRequest, final Long loggedInMemberId) { @@ -81,10 +83,57 @@ public GameIdResponse createGame(final GameCreateRequest gameCreateRequest, fina game.makeNewCrewChatRoom(chatRoom); final Long savedGameId = gameRepository.save(game).getId(); + saveGameStatusUpdateEventToRedis(game, savedGameId); return GameIdResponse.from(savedGameId); } + private void saveGameStatusUpdateEventToRedis(final Game game, final Long savedGameId) { + final LocalDateTime gameCreatedDateTime = LocalDateTime.now(); + + // 경기를 생성한 시각과 경기 시작 시간의 차 + final Long secondsOfBetweenCreatedAndPlay = getSecondsBetweenCreatedAndPlay(gameCreatedDateTime, game); + + // 경기를 생성한 시각과 경기 종료 시간의 차 + final Long secondsOfBetweenCreatedAndEnd = getSecondsBetweenCreatedAndEnd(gameCreatedDateTime, game); + + final String closedGameStatusUpdateKey = makeGameStatusUpdateKey(CLOSED, savedGameId); + final String endedGameStatusUpdateKey = makeGameStatusUpdateKey(ENDED, savedGameId); + + redisRepository.saveHash(closedGameStatusUpdateKey, "", "", secondsOfBetweenCreatedAndPlay); + redisRepository.saveHash(endedGameStatusUpdateKey, "", "", secondsOfBetweenCreatedAndEnd); + } + + private Long getSecondsBetweenCreatedAndPlay(final LocalDateTime gameCreatedDateTime, final Game game) { + final LocalDateTime gamePlayDateTime = LocalDateTime.of(game.getPlayDate(), game.getPlayStartTime()); + + return getSecondsBetween(gameCreatedDateTime, gamePlayDateTime); + } + + private Long getSecondsBetweenCreatedAndEnd(final LocalDateTime gameCreatedDateTime, final Game game) { + final LocalDateTime gameEndDateTime = game.getPlayEndDatetime(); + + return getSecondsBetween(gameCreatedDateTime, gameEndDateTime); + } + + private static long getSecondsBetween( + final LocalDateTime gameCreatedDateTime, + final LocalDateTime gamePlayDateTime + ) { + return Duration.between(gameCreatedDateTime, gamePlayDateTime) + .getSeconds(); + } + + private String makeGameStatusUpdateKey(final GameStatus gameStatus, final Long id) { + return String.format("game:%s:%d", gameStatus.toString(), id); + } + + @Transactional + public void updateGameStatus(final GameStatus gameStatus, final Long gameId) { + final Game game = findGameById(gameId); + game.updateGameStatus(gameStatus); + } + private String makeGameRoomName(final Game game) { final String playDateFormat = game.getPlayDate().format(DateTimeFormatter.ofPattern("MM.dd")); final String addressDepth2Name = game.getAddressDepth2().getName(); @@ -297,7 +346,8 @@ public void reviewMannerScores( final Member loggedInMember = gameMember.getMember(); if (isNotReviewPeriod(game)) { - throw new GameException(GAME_MEMBERS_CAN_REVIEW_DURING_POSSIBLE_PERIOD, game.getPlayDate(), game.getPlayEndTime()); + throw new GameException(GAME_MEMBERS_CAN_REVIEW_DURING_POSSIBLE_PERIOD, game.getPlayDate(), + game.getPlayEndTime()); } mannerScoreReviews.forEach(review -> { diff --git a/src/main/java/kr/pickple/back/game/service/RedisExpirationListener.java b/src/main/java/kr/pickple/back/game/service/RedisExpirationListener.java new file mode 100644 index 00000000..69ae611b --- /dev/null +++ b/src/main/java/kr/pickple/back/game/service/RedisExpirationListener.java @@ -0,0 +1,32 @@ +package kr.pickple.back.game.service; + +import java.util.StringTokenizer; + +import org.springframework.data.redis.connection.Message; +import org.springframework.data.redis.connection.MessageListener; +import org.springframework.stereotype.Component; + +import kr.pickple.back.game.domain.GameStatus; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; + +@Slf4j +@Component +@RequiredArgsConstructor +public class RedisExpirationListener implements MessageListener { + + private final GameService gameService; + + @Override + public void onMessage(final Message message, final byte[] pattern) { + final StringTokenizer stringTokenizer = new StringTokenizer(message.toString(), ":"); + final String keyName = stringTokenizer.nextToken(); + + if (keyName.equals("game")) { + final GameStatus gameStatus = GameStatus.valueOf(stringTokenizer.nextToken()); + final Long gameId = Long.parseLong(stringTokenizer.nextToken()); + + gameService.updateGameStatus(gameStatus, gameId); + } + } +}