Skip to content

Commit

Permalink
Merge pull request #181 from 42organization/fix-waiting-queue
Browse files Browse the repository at this point in the history
[fix] reserve waiting queue
  • Loading branch information
kmularise authored Aug 2, 2023
2 parents c57f24d + 054b5ab commit dae8ef3
Show file tree
Hide file tree
Showing 4 changed files with 101 additions and 22 deletions.
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
package com.gg.server.domain.match.data;

import com.gg.server.domain.match.type.MatchKey;
import com.gg.server.domain.user.exception.UserNotFoundException;
import java.time.Duration;
import java.time.LocalDateTime;
import java.util.List;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
import lombok.RequiredArgsConstructor;
import org.springframework.boot.autoconfigure.cache.CacheProperties.Redis;
import org.springframework.data.redis.core.ListOperations;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Repository;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ public class GameUpdateService {
private final NotiService notiService;
private final SnsNotiService snsNotiService;

public void make(GameAddDto addDto) {
public void make(GameAddDto addDto, Long recoveredUserId) {
SlotManagement slotManagement = slotManagementRepository.findCurrent(LocalDateTime.now())
.orElseThrow(SlotNotFoundException::new);
Game game = new Game(addDto, slotManagement.getGameInterval());
Expand All @@ -51,24 +51,25 @@ public void make(GameAddDto addDto) {
TeamUser enemyTeamUser = new TeamUser(enemyTeam, enemyUser);
List<TeamUser> matchTeamUser = List.of(enemyTeamUser, myTeamUser);
teamUserRepository.saveAll(matchTeamUser);
Noti playerNoti = notiService.createMatched(playerUser, addDto.getStartTime());
snsNotiService.sendSnsNotification(playerNoti, UserDto.from(playerUser));
Noti enemyNoti = notiService.createMatched(enemyUser, addDto.getStartTime());
snsNotiService.sendSnsNotification(enemyNoti, UserDto.from(enemyUser));
if (!playerUser.getId().equals(recoveredUserId)) {
Noti playerNoti = notiService.createMatched(playerUser, addDto.getStartTime());
snsNotiService.sendSnsNotification(playerNoti, UserDto.from(playerUser));
}
if (!enemyUser.getId().equals(recoveredUserId)) {
Noti enemyNoti = notiService.createMatched(enemyUser, addDto.getStartTime());
snsNotiService.sendSnsNotification(enemyNoti, UserDto.from(enemyUser));
}
}

/**
* game 매칭된 user 이외에 다른 user가 취소할 경우, 에러 발생
*/
public void delete(Game game, UserDto userDto) {
List<User> enemyTeam = userRepository.findEnemyByGameAndUser(game.getId(), userDto.getId());
if (enemyTeam.size() > 1) {
throw new SlotNotFoundException();
}
public void delete(Game game, List<User> enemyTeam) {
enemyTeam.forEach(enemy -> {
Noti noti = notiService.createMatchCancel(enemy, game.getStartTime());
snsNotiService.sendSnsNotification(noti, UserDto.from(enemy));
});
gameRepository.delete(game);
}

public void delete(Game game) {
gameRepository.delete(game);
}
}
64 changes: 56 additions & 8 deletions src/main/java/com/gg/server/domain/match/service/MatchService.java
Original file line number Diff line number Diff line change
Expand Up @@ -21,11 +21,15 @@
import com.gg.server.domain.rank.redis.RedisKeyManager;
import com.gg.server.domain.season.data.Season;
import com.gg.server.domain.season.service.SeasonFindService;
import com.gg.server.domain.user.User;
import com.gg.server.domain.user.UserRepository;
import com.gg.server.domain.user.dto.UserDto;
import com.gg.server.domain.user.exception.UserNotFoundException;
import java.time.LocalDateTime;
import java.util.List;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
Expand All @@ -40,6 +44,7 @@ public class MatchService {
private final GameRepository gameRepository;
private final PenaltyService penaltyService;
private final GameUpdateService gameUpdateService;
private final UserRepository userRepository;

/**
* 1) 매칭 가능한 유저 있을 경우 : 게임 생성
Expand All @@ -57,7 +62,8 @@ public synchronized void makeMatch(UserDto userDto, Option option, LocalDateTime
Optional<RedisMatchUser> enemy = matchCalculator.findEnemy(allMatchUsers);
if (enemy.isPresent()) {
GameAddDto gameDto = new GameAddDto(startTime, season, player, enemy.get());
gameUpdateService.make(gameDto);
gameUpdateService.make(gameDto, -1L);
redisMatchTimeRepository.addMatchUser(startTime, player);
cancelEnrolledSlots(List.of(enemy.get(), player), startTime);
} else {
addUserToQueue(startTime, player, option);
Expand All @@ -66,17 +72,56 @@ public synchronized void makeMatch(UserDto userDto, Option option, LocalDateTime

/**
* 1) 매칭되어 게임 생성된 후 : 게임 삭제하고 알림 전송, 취소한 유저 패널티 부과
* 복귀 유저는 매칭 가능한 상대 존재하면 다시 매칭해주고 아니면 취소 알림 보내고 큐에 등록 시킴
* 2) 매칭 전 : 큐에서 유저 삭제
* */
/**
* game 매칭된 user 이외에 다른 user가 취소할 경우, 에러 발생
*/
@Transactional
public synchronized void cancelMatch(UserDto userDto, LocalDateTime startTime) {
Optional<Game> game = gameRepository.findByStartTime(startTime);
if (game.isPresent()) {
gameUpdateService.delete(game.get(), userDto);//cascade 테스트
penaltyService.givePenalty(userDto, 30);
return;
List<User> enemyTeam = userRepository.findEnemyByGameAndUser(game.get().getId(), userDto.getId());
if (enemyTeam.size() > 1) {
throw new SlotNotFoundException();
}
cancelGame(userDto, startTime, game.get(), enemyTeam);
}else {
deleteUserFromQueue(userDto, startTime);
};
}

private void cancelGame(UserDto userDto, LocalDateTime startTime, Game game, List<User> enemyTeam) {
/**취소한 유저 큐에서 삭제 후 패널티 부과*/
Long recoveredUserId = enemyTeam.get(0).getId();
List<RedisMatchUser> allMatchUsers = redisMatchTimeRepository.getAllMatchUsers(startTime);
RedisMatchUser penaltyUser = allMatchUsers.stream()
.filter(ele -> ele.getUserId().equals(userDto.getId()))
.findFirst()
.orElseThrow(UserNotFoundException::new);
RedisMatchUser recoveredUser = allMatchUsers.stream()
.filter(ele -> ele.getUserId().equals(recoveredUserId))
.findFirst()
.orElseThrow(UserNotFoundException::new);
redisMatchTimeRepository.deleteMatchUser(startTime, penaltyUser);
penaltyService.givePenalty(userDto, 30);
/**취소 당한 유저 매칭 상대 찾고 있으면 다시 게임 생성 아니면 취소 알림*/
Season season = seasonFindService.findCurrentSeason(startTime);
MatchCalculator matchCalculator = new MatchCalculator(season.getPppGap(), recoveredUser);
List<RedisMatchUser> targetPlayers = allMatchUsers.stream()
.filter(ele -> !ele.getUserId().equals(userDto.getId())
&& !ele.getUserId().equals(recoveredUserId))
.collect(Collectors.toList());
Optional<RedisMatchUser> enemy = matchCalculator.findEnemy(targetPlayers);
if (enemy.isPresent()) {
gameUpdateService.delete(game);
GameAddDto gameDto = new GameAddDto(startTime, season, recoveredUser, enemy.get());
gameUpdateService.make(gameDto, recoveredUserId);
} else {
gameUpdateService.delete(game, enemyTeam);
redisMatchUserRepository.addMatchTime(recoveredUserId, startTime, recoveredUser.getOption());
}
deleteUserFromQueue(userDto, startTime);
}

private void checkValid(UserDto userDto, LocalDateTime startTime) {
Expand Down Expand Up @@ -105,10 +150,13 @@ private void addUserToQueue(LocalDateTime startTime, RedisMatchUser matchUser, O
redisMatchUserRepository.addMatchTime(matchUser.getUserId(), startTime, option);
}

private void cancelEnrolledSlots(List<RedisMatchUser> players, LocalDateTime startTime) {
redisMatchTimeRepository.deleteMatchTime(startTime);
private void cancelEnrolledSlots(List<RedisMatchUser> players, LocalDateTime targetTIme) {
for (RedisMatchUser player : players) {
Set<RedisMatchTime> matchTimes = redisMatchUserRepository.getAllMatchTime(player.getUserId());
Set<RedisMatchTime> matchTimes = redisMatchUserRepository
.getAllMatchTime(player.getUserId())
.stream()
.filter(ele -> !ele.getStartTime().equals(targetTIme))
.collect(Collectors.toSet());
matchTimes.stream().forEach(ele -> redisMatchTimeRepository.deleteMatchUser(ele.getStartTime(), player));
redisMatchUserRepository.deleteMatchUser(player.getUserId());
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -144,10 +144,38 @@ void makeGameWithNormalAndBoth() {
@DisplayName("Queue에 매칭 가능한 normal 상대가 있을 경우 게임 생성")
@Test
void addMatchSameNormalOption() {
matchService.makeMatch(UserDto.from(users.get(2)), Option.RANK, this.slotTimes.get(0));
matchService.makeMatch(UserDto.from(users.get(0)), Option.NORMAL, this.slotTimes.get(0));
matchService.makeMatch(UserDto.from(users.get(1)), Option.NORMAL, this.slotTimes.get(0));
matchService.cancelMatch(UserDto.from(users.get(0)), this.slotTimes.get(0));
Optional<Game> game = gameRepository.findByStartTime(slotTimes.get(0));
Assertions.assertThat(game.isEmpty()).isEqualTo(false);
Assertions.assertThat(game).isEmpty();
Long size = redisTemplate.opsForList().size(MatchKey.getTime(this.slotTimes.get(0)));
List<RedisMatchUser> allMatchUsers = redisMatchTimeRepository.getAllMatchUsers(this.slotTimes.get(0));
Assertions.assertThat(size).isEqualTo(2L);
RedisMatchUser remainedUser = (RedisMatchUser) redisTemplate.opsForList().index(MatchKey.getTime(slotTimes.get(0)), 0);
Assertions.assertThat(remainedUser.getUserId()).isEqualTo(users.get(2).getId());
Assertions.assertThat(notiRepository.findAllByUser(users.get(1)).size()).isEqualTo(2);
Assertions.assertThat(notiRepository.findAllByUser(users.get(0)).size()).isEqualTo(1);
Assertions.assertThat(notiRepository.findAllByUser(users.get(2)).size()).isEqualTo(0);
}

@DisplayName("게임 재생성 테스트")
@Test
void remakeGameAfterCancelling() {
matchService.makeMatch(UserDto.from(users.get(2)), Option.RANK, this.slotTimes.get(0));
matchService.makeMatch(UserDto.from(users.get(0)), Option.NORMAL, this.slotTimes.get(0));
matchService.makeMatch(UserDto.from(users.get(1)), Option.BOTH, this.slotTimes.get(0));
matchService.cancelMatch(UserDto.from(users.get(2)), this.slotTimes.get(0));
Optional<Game> game = gameRepository.findByStartTime(slotTimes.get(0));
Assertions.assertThat(game).isPresent();
Long size = redisTemplate.opsForList().size(MatchKey.getTime(this.slotTimes.get(0)));
Assertions.assertThat(size).isEqualTo(2L);
RedisMatchUser remainedUser = (RedisMatchUser) redisTemplate.opsForList().index(MatchKey.getTime(slotTimes.get(0)), 0);
Assertions.assertThat(remainedUser.getUserId()).isEqualTo(users.get(0).getId());
Assertions.assertThat(notiRepository.findAllByUser(users.get(1)).size()).isEqualTo(1);
Assertions.assertThat(notiRepository.findAllByUser(users.get(0)).size()).isEqualTo(1);
Assertions.assertThat(notiRepository.findAllByUser(users.get(2)).size()).isEqualTo(1);
}

@DisplayName("Queue에 user가 선택한 random option으로 매칭 가능한 상대가 없을 경우")
Expand Down

0 comments on commit dae8ef3

Please sign in to comment.