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,5 +1,6 @@
package com.umc.yeogi_gal_lae.api.vote.repository;

import com.umc.yeogi_gal_lae.api.tripPlan.domain.TripPlan;
import com.umc.yeogi_gal_lae.api.user.domain.User;
import com.umc.yeogi_gal_lae.api.vote.domain.Vote;
import org.springframework.data.jpa.repository.JpaRepository;
Expand All @@ -19,11 +20,10 @@ public interface VoteRepository extends JpaRepository<Vote,Long> {
@Query("SELECT v FROM Vote v WHERE v.tripPlan.id = :tripPlanId")
List<Vote> findAllVotesByTripPlanId(@Param("tripPlanId") Long tripPlanId);

// 사용자 ID로 Vote 목록을 조회하는 메서드 추가
@Query("SELECT u.vote FROM User u WHERE u.id = :userId")
List<Vote> findByUserId(@Param("userId") Long userId);

@Modifying
@Query("DELETE FROM Vote v WHERE v.voteRoom IN (SELECT vr FROM VoteRoom vr WHERE vr.tripPlan.user = :user)")
void deleteByVoteRoomUser(@Param("user") User user);

Optional<Vote> findByUserAndTripPlan(User user, TripPlan tripPlan);

}
Original file line number Diff line number Diff line change
Expand Up @@ -71,74 +71,68 @@ public VoteResponse.VoteInfoDTO getTripPlanInfoForVote(Long tripId, Long roomId


@Transactional
public void createVote(VoteRequest.createVoteReq request, String userEmail){

User user = userRepository.findByEmail(userEmail).orElseThrow(()-> new BusinessException(ErrorCode.USER_NOT_FOUND));
TripPlan tripPlan = tripPlanRepository.findById(request.getTripId()).orElseThrow(()-> new BusinessException(ErrorCode.TRIP_PLAN_NOT_FOUND));
public void createVote(VoteRequest.createVoteReq request, String userEmail) {
User user = userRepository.findByEmail(userEmail).orElseThrow(() -> new BusinessException(ErrorCode.USER_NOT_FOUND));
TripPlan tripPlan = tripPlanRepository.findById(request.getTripId()).orElseThrow(() -> new BusinessException(ErrorCode.TRIP_PLAN_NOT_FOUND));
VoteRoom voteRoom = voteRoomRepository.findByTripPlanId(tripPlan.getId()).orElseThrow(() -> new BusinessException(VOTE_ROOM_NOT_FOUND));
VoteType voteType = VoteType.valueOf(request.getType().trim().toUpperCase());

// Key 기반의 분산 락을 적용하므로써, 사용자의 동시 투표 방지
// Key 기반의 분산 락 적용 (동시 투표 방지)
RLock lock = redissonClient.getLock("voteLock:" + userEmail);
boolean isLocked = false;
try{
try {
isLocked = lock.tryLock(5, 10, TimeUnit.SECONDS);
log.info("락 획득 여부: {}, 현재 스레드가 락을 보유하고 있는가? {}", isLocked, lock.isHeldByCurrentThread());
if (!isLocked) { throw new BusinessException(ErrorCode.VOTE_CONCURRENT_UPDATE);}

// 락 획득 성공 시, 레디스에 저장된 투표 데이터를 확인하여 캐싱된 데이터가 있을 시, DB 조회 x
Vote vote = getCachedVoteByTripPlan(tripPlan.getId());
if (vote == null) {
vote = voteRepository.save(Vote.builder()
.tripPlan(tripPlan)
.voteRoom(voteRoom)
.type(voteType)
.build());

// 새로 생성된 경우 캐싱
Vote finalVote = vote;
Optional.ofNullable(cacheManager.getCache("votes"))
.ifPresent(cache -> cache.put(tripPlan.getId(), finalVote));
if (!isLocked) {
throw new BusinessException(ErrorCode.VOTE_CONCURRENT_UPDATE);
}

// 투표 시작 알림 생성
notificationService.createStartNotification(tripPlan.getRoom().getName(), user.getUsername(), user.getEmail(),NotificationType.VOTE_START, tripPlan.getId(), tripPlan.getTripPlanType());
// 기존 투표 여부 확인
Optional<Vote> existingVote = voteRepository.findByUserAndTripPlan(user, tripPlan);

// 기존 투표 이력 확인
Vote currentVote = user.getVote();
if (currentVote == null) {
user.setVote(vote);
}
else if (currentVote.getTripPlan().getId().equals(tripPlan.getId())) {
if (Thread.currentThread().isInterrupted()) {
log.warn("BusinessException 발생 후 스레드가 인터럽트된 상태 - 사용자: {}", userEmail);
Thread.interrupted(); // 인터럽트 상태 초기화
if (existingVote.isPresent()) {
Vote currentVote = existingVote.get();
if (currentVote.getType().equals(voteType)) {
throw new BusinessException(ErrorCode.DUPLICATE_VOTE_NOT_ALLOWED);
}
if (currentVote.getType().equals(voteType)) { throw new BusinessException(ErrorCode.DUPLICATE_VOTE_NOT_ALLOWED);}

currentVote.setType(voteType);
voteRepository.save(currentVote);

// 사용자가 이미 투표한 내역을 변경할 경우, 레디스에 저장된 데이터를 삭제하여 최신화
Optional.ofNullable(cacheManager.getCache("votes"))
.ifPresent(cache -> cache.evict(tripPlan.getId()));
} else {
user.setVote(vote);
// 새 투표 생성
Vote newVote = Vote.builder()
.tripPlan(tripPlan)
.voteRoom(voteRoom)
.type(voteType)
.build();
voteRepository.save(newVote);

// User 엔티티에 투표 반영
user.setVote(newVote);
userRepository.save(user);
}
userRepository.save(user);

// 캐시 최신화 (이전 데이터 삭제 후 새로운 데이터 저장)
Optional.ofNullable(cacheManager.getCache("votes"))
.ifPresent(cache -> cache.evict(tripPlan.getId()));

// 투표 시작 알림 생성
notificationService.createStartNotification(tripPlan.getRoom().getName(), user.getUsername(), user.getEmail(), NotificationType.VOTE_START, tripPlan.getId(), tripPlan.getTripPlanType());

} catch (InterruptedException e) {
Thread.currentThread().interrupt();
throw new BusinessException(ErrorCode.VOTE_CONCURRENT_UPDATE);
} catch (Exception e) {
log.error("예외 발생 함. 사용자: {} - 예외 타입: {}", userEmail, e.getClass().getName(), e);
throw e;
}finally {
} finally {
log.info("현재 스레드 사용자 {} 에 대한 락을 해제합니다.", userEmail);
if (isLocked && lock.isHeldByCurrentThread()) { lock.unlock(); }
if (isLocked && lock.isHeldByCurrentThread()) {
lock.unlock();
}
}
}


public List<VoteResponse.ResultDTO> getVoteResults(String userEmail, Long tripId){

Long userId = userRepository.findByEmail(userEmail).orElseThrow(()-> new BusinessException(ErrorCode.USER_NOT_FOUND)).getId();
Expand Down