diff --git a/backend/src/main/java/com/coffee/backend/domain/match/controller/MatchController.java b/backend/src/main/java/com/coffee/backend/domain/match/controller/MatchController.java index 07d8a094bd..698029950d 100644 --- a/backend/src/main/java/com/coffee/backend/domain/match/controller/MatchController.java +++ b/backend/src/main/java/com/coffee/backend/domain/match/controller/MatchController.java @@ -97,11 +97,11 @@ public ResponseEntity> finishMatch(@RequestBody Matc } @GetMapping("/isMatching") - public ResponseEntity> isMatching(@RequestBody MatchIdDto dto) { + public ResponseEntity> isMatching(@RequestBody MatchIdDto dto) { DtoLogger.requestBody(dto); log.info("Check if isMathing"); - Boolean response = matchService.isMatching(dto); + MatchStatusDto response = matchService.isMatching(dto); return ResponseEntity.ok(ApiResponse.success(response)); } diff --git a/backend/src/main/java/com/coffee/backend/domain/match/service/MatchService.java b/backend/src/main/java/com/coffee/backend/domain/match/service/MatchService.java index 71562bbf59..fee9a57d8c 100644 --- a/backend/src/main/java/com/coffee/backend/domain/match/service/MatchService.java +++ b/backend/src/main/java/com/coffee/backend/domain/match/service/MatchService.java @@ -25,6 +25,7 @@ import java.util.Date; import java.util.List; import java.util.Map; +import java.util.NoSuchElementException; import java.util.Set; import java.util.UUID; import java.util.concurrent.TimeUnit; @@ -53,10 +54,9 @@ public class MatchService { public MatchDto sendMatchRequest(MatchRequestDto dto) { log.trace("sendMatchRequest()"); - validateRequest(dto); // 요청 검증 - + validateRequest(dto); String lockKey = LOCK_KEY_PREFIX + dto.getSenderId(); - validateLock(lockKey); // 락 검증 + validateLock(lockKey); long expirationTime = System.currentTimeMillis() + TimeUnit.MINUTES.toMillis(10); @@ -155,24 +155,52 @@ public List getMatchReceivedInfo(Long receiverId) { public MatchAcceptResponse acceptMatchRequest(MatchIdDto dto) { log.trace("acceptMatchRequest()"); - if (!verifyMatchRequest(dto)) { - throw new CustomException(ErrorCode.REQUEST_EXPIRED); - } + validateIfAlreadyAccepted(dto.getMatchId()); + validateTTL(dto.getMatchId()); String key = "matchId:" + dto.getMatchId(); - redisTemplate.opsForHash().put(key, "status", "accepted"); - redisTemplate.opsForHash().put(key + "-info", "status", "matching"); - Long senderId = getLongId(redisTemplate.opsForHash().get(key, "senderId")); Long receiverId = getLongId(redisTemplate.opsForHash().get(key, "receiverId")); + ChatroomCreationDto chatroomCreationDto = new ChatroomCreationDto(senderId, receiverId); + Long chatroomId = chatroomService.createChatroom(chatroomCreationDto); + + redisTemplate.opsForHash().put(key, "status", "accepted"); + + Map matchInfo = Map.of( + "senderId", senderId.toString(), + "receiverId", receiverId.toString(), + "status", "accepted" + ); + redisTemplate.opsForHash().putAll(key + "-info", matchInfo); + // 알림 User fromUser = userRepository.findByUserId(senderId).orElseThrow(); User toUser = userRepository.findByUserId(senderId).orElseThrow(); fcmService.sendPushMessageTo(toUser.getDeviceToken(), "커피챗 매칭 성공", fromUser.getNickname() + "님과 커피챗이 성사되었습니다."); - ChatroomCreationDto chatroomCreationDto = new ChatroomCreationDto(senderId, receiverId); - Long chatroomId = chatroomService.createChatroom(chatroomCreationDto); + redisTemplate.delete("receiverId:" + receiverId + "-senderId:" + senderId); + + // receiver가 보낸 다른 요청이 있었다면 해당 요청 취소 + Set keys; + try { + keys = redisTemplate.keys("receiverId:*-senderId:" + receiverId); + } catch (NoSuchElementException e) { + keys = null; // keys를 null로 설정하여 다음 로직으로 이동 + } + + if (keys != null && !keys.isEmpty()) { + String actualKey = keys.iterator().next(); // 키가 하나만 있다고 가정 + String matchId = (String) redisTemplate.opsForHash().get(actualKey, "matchId"); + if (matchId != null && !matchId.equals(dto.getMatchId())) { + redisTemplate.delete("matchId:" + matchId); + redisTemplate.delete(actualKey); + redisTemplate.delete(LOCK_KEY_PREFIX + receiverId); // 락 해제 + } + } + + redisTemplate.delete("matchId:" + dto.getMatchId()); + redisTemplate.delete(LOCK_KEY_PREFIX + senderId); // 락 해제 MatchAcceptResponse match = new MatchAcceptResponse(); match.setMatchId(dto.getMatchId()); @@ -181,20 +209,24 @@ public MatchAcceptResponse acceptMatchRequest(MatchIdDto dto) { match.setStatus("accepted"); match.setChatroomId(chatroomId); - redisTemplate.delete("receiverId:" + receiverId + "-senderId:" + senderId); - redisTemplate.delete(LOCK_KEY_PREFIX + senderId); // 락 해제 - redisTemplate.delete(LOCK_KEY_PREFIX + receiverId); // 락 해제 - return match; } + // 이미 수락된 요청인지 검증 + private void validateIfAlreadyAccepted(String matchId) { + log.trace("validateIfAlreadyAccepted()"); + + String status = (String) redisTemplate.opsForHash().get("matchId:" + matchId + "-info", "status"); + if (status != null && status.equals("accepted")) { + throw new CustomException(ErrorCode.REQUEST_ALREADY_ACCEPTED); + } + } + // 매칭 요청 거절 public MatchDto declineMatchRequest(MatchIdDto dto) { log.trace("declineMatchRequest()"); - if (!verifyMatchRequest(dto)) { - throw new CustomException(ErrorCode.REQUEST_EXPIRED); - } + validateTTL(dto.getMatchId()); String key = "matchId:" + dto.getMatchId(); Long senderId = getLongId(redisTemplate.opsForHash().get(key, "senderId")); @@ -221,9 +253,8 @@ public MatchDto declineMatchRequest(MatchIdDto dto) { // 매칭 요청 수동 취소 public MatchDto cancelMatchRequest(MatchIdDto dto) { log.trace("cancelMatchRequest()"); - if (!verifyMatchRequest(dto)) { - throw new CustomException(ErrorCode.REQUEST_EXPIRED); - } + + validateTTL(dto.getMatchId()); String key = "matchId:" + dto.getMatchId(); Long senderId = getLongId(redisTemplate.opsForHash().get(key, "senderId")); @@ -242,14 +273,19 @@ public MatchDto cancelMatchRequest(MatchIdDto dto) { } // 매칭 요청 검증 - private boolean verifyMatchRequest(MatchIdDto dto) { - log.trace("verifyMatchRequest()"); - Long ttl = redisTemplate.getExpire("matchId:" + dto.getMatchId()); - return ttl != null && ttl > 0; // true + private void validateTTL(String matchId) { + log.trace("validateTTL()"); + + Long ttl = redisTemplate.getExpire("matchId:" + matchId); + if (ttl == null || ttl <= 0) { + throw new CustomException(ErrorCode.REQUEST_EXPIRED); + } } // 락 검증 private void validateLock(String lockKey) { + log.trace("validateLock()"); + // 이미 락이 걸려 있는 경우 요청 처리 X if (redisTemplate.opsForValue().get(lockKey) != null) { throw new CustomException(ErrorCode.REQUEST_DUPLICATED); @@ -258,7 +294,10 @@ private void validateLock(String lockKey) { // 유저 검증 private void validateRequest(MatchRequestDto dto) { - // 본인에게 요청을 보내는 경우 처리 X + log.trace("validateRequest()"); + + // TODO: 향후 테스트 기간 끝나고 주석 해제 +// // 본인에게 요청을 보내는 경우 처리 X // if (dto.getSenderId().equals(dto.getReceiverId())) { // throw new CustomException(ErrorCode.REQUEST_SAME_USER); // } @@ -291,7 +330,9 @@ private Long getLongId(Object result) { public MatchStatusDto finishMatch(MatchFinishRequestDto dto) { log.trace("finishMatch()"); - String key = "matchId:" + dto.getMatchId(); + validateFinishMatch(dto.getMatchId()); + + String key = "matchId:" + dto.getMatchId() + "-info"; Long senderId = getLongId(redisTemplate.opsForHash().get(key, "senderId")); Long receiverId = getLongId(redisTemplate.opsForHash().get(key, "receiverId")); @@ -303,9 +344,12 @@ public MatchStatusDto finishMatch(MatchFinishRequestDto dto) { User toUser = userRepository.findByUserId(senderId).orElseThrow(); fcmService.sendPushMessageTo(toUser.getDeviceToken(), "커피챗 매칭 종료", toUser.getNickname() + "님과의 커피챗이 종료되었습니다."); + } else { + throw new CustomException(ErrorCode.USER_NOT_FOUND); } - redisTemplate.delete("matchId:" + dto.getMatchId() + "-info"); + // 종료로 상태 변경 + redisTemplate.opsForHash().put("matchId:" + dto.getMatchId() + "-info", "status", "finished"); MatchStatusDto match = new MatchStatusDto(); match.setMatchId(dto.getMatchId()); @@ -313,13 +357,33 @@ public MatchStatusDto finishMatch(MatchFinishRequestDto dto) { return match; } + private void validateFinishMatch(String matchId) { + log.trace("validateFinishMatch()"); + Map matchInfo = redisTemplate.opsForHash().entries("matchId:" + matchId + "-info"); + if (matchInfo.isEmpty()) { + throw new CustomException(ErrorCode.REQUEST_NOT_FOUND); + } + if (matchInfo.get("status").equals("finished")) { + throw new CustomException(ErrorCode.REQUEST_ALREADY_FINISHED); + } + } + // 매칭 요청 종료 확인 - public Boolean isMatching(MatchIdDto dto) { + public MatchStatusDto isMatching(MatchIdDto dto) { log.trace("isMatching()"); String key = "matchId:" + dto.getMatchId() + "-info"; - Object status = redisTemplate.opsForHash().get(key, "status"); - return status != null; // true + String status = (String) redisTemplate.opsForHash().get(key, "status"); + + // status가 null이면 수락되지 않은 것 + if (status == null) { + throw new CustomException(ErrorCode.REQUEST_NOT_ACCEPTED); + } + + MatchStatusDto response = new MatchStatusDto(); + response.setMatchId(dto.getMatchId()); + response.setStatus(status); + return response; } @Transactional diff --git a/backend/src/main/java/com/coffee/backend/domain/userChatroom/repository/UserChatroomRepository.java b/backend/src/main/java/com/coffee/backend/domain/userChatroom/repository/UserChatroomRepository.java index 53e1ed7779..9853a96afa 100644 --- a/backend/src/main/java/com/coffee/backend/domain/userChatroom/repository/UserChatroomRepository.java +++ b/backend/src/main/java/com/coffee/backend/domain/userChatroom/repository/UserChatroomRepository.java @@ -33,6 +33,6 @@ Optional findOtherUserChatroomByChatroomAndUser(@Param("chatroom") "FROM UserChatroom uc1 " + "JOIN UserChatroom uc2 ON uc1.chatroom = uc2.chatroom " + "WHERE uc1.user = :user AND uc2.user = :user2") - Optional findByUserAndOtherUser(User user, User user2); + Optional findByUserAndOtherUser(@Param("user") User user, @Param("user2") User user2); } diff --git a/backend/src/main/java/com/coffee/backend/exception/ErrorCode.java b/backend/src/main/java/com/coffee/backend/exception/ErrorCode.java index 84b7a4db58..5701cb7328 100644 --- a/backend/src/main/java/com/coffee/backend/exception/ErrorCode.java +++ b/backend/src/main/java/com/coffee/backend/exception/ErrorCode.java @@ -1,5 +1,6 @@ package com.coffee.backend.exception; +import com.google.api.Http; import lombok.Getter; import lombok.RequiredArgsConstructor; import org.springframework.http.HttpStatus; @@ -31,6 +32,9 @@ public enum ErrorCode { REQUEST_SAME_USER(HttpStatus.CONFLICT, "7409", "본인에게 요청을 보낼 수 없습니다."), REQUEST_NOT_FOUND(HttpStatus.NOT_FOUND, "7404", "해당 요청 정보를 찾을 수 없습니다."), REQUEST_EXPIRED(HttpStatus.UNAUTHORIZED, "7401", "요청이 만료되었습니다."), + REQUEST_ALREADY_ACCEPTED(HttpStatus.CONFLICT, "7409", "이미 수락한 요청입니다."), + REQUEST_ALREADY_FINISHED(HttpStatus.CONFLICT, "7409", "이미 종료한 요청입니다."), + REQUEST_NOT_ACCEPTED(HttpStatus.NOT_FOUND, "7404", "해당 요청은 수락되지 않았습니다."), COMPANY_NOT_FOUND(HttpStatus.NOT_FOUND, "8404", "해당 COMPANY를 찾을 수 없습니다."),