Skip to content

Commit

Permalink
Merge pull request #281 from kookmin-sw/#279-lock
Browse files Browse the repository at this point in the history
[fix] 매칭 요청 수락 시 락 해제 #279
  • Loading branch information
tmdtmdqorekf authored May 19, 2024
2 parents 1e46aea + 7be5401 commit 62d6a8b
Show file tree
Hide file tree
Showing 4 changed files with 102 additions and 34 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -97,11 +97,11 @@ public ResponseEntity<ApiResponse<MatchStatusDto>> finishMatch(@RequestBody Matc
}

@GetMapping("/isMatching")
public ResponseEntity<ApiResponse<Boolean>> isMatching(@RequestBody MatchIdDto dto) {
public ResponseEntity<ApiResponse<MatchStatusDto>> 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));
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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);

Expand Down Expand Up @@ -155,24 +155,52 @@ public List<MatchReceivedInfoDto> 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<String, String> 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<String> 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());
Expand All @@ -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"));
Expand All @@ -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"));
Expand All @@ -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);
Expand All @@ -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);
// }
Expand Down Expand Up @@ -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"));

Expand All @@ -303,23 +344,46 @@ 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());
match.setStatus("finished");
return match;
}

private void validateFinishMatch(String matchId) {
log.trace("validateFinishMatch()");
Map<Object, Object> 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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,6 @@ Optional<UserChatroom> findOtherUserChatroomByChatroomAndUser(@Param("chatroom")
"FROM UserChatroom uc1 " +
"JOIN UserChatroom uc2 ON uc1.chatroom = uc2.chatroom " +
"WHERE uc1.user = :user AND uc2.user = :user2")
Optional<Chatroom> findByUserAndOtherUser(User user, User user2);
Optional<Chatroom> findByUserAndOtherUser(@Param("user") User user, @Param("user2") User user2);

}
Original file line number Diff line number Diff line change
@@ -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;
Expand Down Expand Up @@ -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를 찾을 수 없습니다."),

Expand Down

0 comments on commit 62d6a8b

Please sign in to comment.