공부기록/자바 스프링

스프링 배팅시스템 만들기 (트위치 배팅시스템 참고)

동석쿠 2022. 3. 8. 23:56

지금 실전프로젝트에서 만드는 기능중에 트위치 배팅시스템처럼 유저가 참가해서 자신의 포인트를 걸고 배팅을 해서

배팅을 이기면 자기가 건 포인트에 비례하여 포인트를 더 얻고 만약 배팅에서 지면 자신의 포인트가 모두 사라지는 기능을 구현하기로 했다.

현재 진행중인 프로젝트의 와이어 프레임

 

프로젝트에서 실제 진행중인 배팅 와이어프레임

엔티티 설계는 다음과 같이 진행했다.

ChatRoom은 유저가 참여하고있는 채팅 방이다.

Vote는 채팅방과 OneToOne으로 채팅방 생성시 자동으로 그 방에 해당하는 Vote가 생긴다. 이 Vote에는 각각 유저가 투표한곳의 카운트 수와 누가 제일 많이 투표했는지, 그리고 각각 투표한곳의 totalPoint가 칼럼으로 존재한다

SaveVote는 Vote와 ManyToOne, ChatRoom과 ManyToOne, 유저와 ManyToOne으로 연결되어 있어 

ChatRoom과 Vote의 id 그리고 유저의 id가 쌓인다. 

SaveVote에는 유저가 어떤 선택을 했는지와 각각 자신이 배팅한 Point가 저장되는데 이를 토대로 배팅 로직을 만들 수 있다.

 

@PostMapping("/vote/{roomId}")
    @ApiOperation(value = "투표")
    public void vote(@PathVariable Long roomId,
                     @AuthenticationPrincipal UserDetailsImpl userDetails,
                     @RequestBody VoteRequestDto requestDto) {
        User user = userDetails.getUser();
        voteService.saveVote(roomId, user, requestDto);
    }
@Transactional
    public void saveVote(Long roomId, User user, VoteRequestDto requestDto) {
        Vote vote = voteRepository.findByChatRoom_Id(roomId); //투표 찾아서
        SaveVote savevote = saveVoteRepository.findByUser_IdAndChatRoom_IdAndVote_Id(user.getId(),roomId,vote.getId());//같은 유저가 투표한적이 있으면 null이 아님
        if (savevote == null) {
            if(user.getTotalPoint() >= requestDto.getPoint()) //내가 보유한 포인트 보다 많을경우
            {
                ChatRoom chatRoom = chatRoomRepository.findById(roomId).orElseThrow(
                        () -> new ChatRoomNotFoundException("해당 채팅방이 없습니다")
                );
                user.setTotalPoint(user.getTotalPoint()-requestDto.getPoint()); //유저의 포인트 변화
                userRepository.save(user); //저장
                Point point = new Point("투표 참여", -requestDto.getPoint(), user.getTotalPoint(), user); //포인트 내역 생성
                pointRepository.save(point); //포인트 내역 저장

                if (requestDto.getTopic()) { // topicA를 골랐을 때
                    SaveVote saveVote = new SaveVote(true, chatRoom,requestDto.getPoint() ,vote, user); // savevote 추가
                    saveVoteRepository.save(saveVote); //저장
                    vote.setTotalPointA(vote.getTotalPointA()+requestDto.getPoint()); //totalpoint 추가
                    vote.setTopicACnt(vote.getTopicACnt()+1);
                    if (vote.getTopPointA() < requestDto.getPoint()) { //toppoint찾기
                        vote.setTopPointA(requestDto.getPoint());
                    }

                    voteRepository.save(vote);
                } else {//topicB를 골랐을 때
                    SaveVote saveVote = new SaveVote(false, chatRoom,requestDto.getPoint(), vote, user);
                    saveVoteRepository.save(saveVote); //저장
                    vote.setTotalPointB(vote.getTotalPointB()+requestDto.getPoint()); //totalpoint 추가
                    vote.setTopicBCnt(vote.getTopicBCnt()+1);
                    if (vote.getTopPointB() < requestDto.getPoint()) { //toppoint찾기
                        vote.setTopPointB(requestDto.getPoint());
                    }
                    voteRepository.save(vote);
                }
            }else throw new LackPointException("보유한 포인트가 부족합니다");
        }else throw new DuplicateVoteException("이미 투표에 참여하셨습니다");
    }

먼저 유저가 각각 투표를 했을때의 서비스 코드. 

위에서부터 먼저 SaveVote에 지금 투표를 한 사람의 Id와 채팅방Id그리고 VoteId로 SaveVote를 조회해서 이미

row가 존재한다면 이미 투표에 참여했단 메세지를. 그 값이 존재하지 않는다면 다음 로직으로 이동한다.

 

그 다음 if문에서 유저가 보유한 포인트가 유저가 배팅한 포인트보다 많다면 문제없이 배팅로직으로 이동하고

만약 보유한 포인트가 부족하다면 포인트가 부족하다는 메세지를 보내준다.

 

위의 예외조건에 걸리지않고 모두 통과했다면 유저가 A를 골랐는지 B를 골랐는지 판단해서 

유저의 포인트에 변화를 주고 투표에 참여했다고 포인트 내역을 만들어 준다. 그 다음 투표한 곳에 값들을 변환시켜준다

투표를 하면 Save_Vote에 유저의 투표기록과 Vote에는 모든 투표 기록이 저장되고 Point에는 포인트 내역이 나온다.

이번 프로젝트는 채팅방의 시간이 지나면 삭제가 되는데. 삭제가되면서 투표정산 메소드를 실행시켜준다.

삭제가 될땐 Vote에는 ChatRoom에 null을 넣어연관관계를 끊어 ChatRoom과 Save_Vote만 삭제되게 한다

//투표 누가 이겼는지 확인
    @Transactional
    public void winVote(Long roomId) {
        Float winRate;
        Vote vote = voteRepository.findByChatRoom_Id(roomId); //투표 찾아서
        List<SaveVote> saveVotesTrueList = saveVoteRepository.findAllByChatRoom_IdAndPickTrue(roomId); // topicA픽한사람들 모임
        List<SaveVote> saveVotesFalseList = saveVoteRepository.findAllByChatRoom_IdAndPickFalse(roomId); // topicB픽한사람들 모임
        List<SaveVote> saveVotesTieList = saveVoteRepository.findAllByChatRoom_Id(roomId);
        List<User> userWinnerList = new ArrayList<>(); //승리한 유저들
        Float totalPoint = vote.getTotalPointA() + vote.getTotalPointB();

        if (vote.getTopicACnt() > vote.getTopicBCnt()) { // topicA가 이겻을 경우
            winRate = (totalPoint / vote.getTotalPointA()); //배당률 계산
            for (SaveVote saveVote : saveVotesTrueList) { //승리한 유저 리스트에 해당하는 픽을 한 유저들을 집어넣음
                userWinnerList.add(saveVote.getUser());
            }
            for (User user : userWinnerList) {
                SaveVote saveVote = saveVoteRepository.findByUser_IdAndChatRoom_Id(user.getId(), roomId); //유저와 채팅방 id로 savevote를 뽑아옴 (유저는 한개씩 가짐)
                user.setTotalPoint((long) (user.getTotalPoint()+ (saveVote.getPoint()*winRate))); //savevote에서 내가 얼마를 걸었는지가 있는데 거기서 뽑아와서 winRate계산을 함
                userRepository.save(user);
                Point point = new Point("투표 승리", (long) (saveVote.getPoint() * winRate), user.getTotalPoint(), user); //포인트 내역 생성
                pointRepository.save(point);
            }
        } else if (vote.getTopicACnt() < vote.getTopicBCnt()) { //topicB가 이겼을 경우
            winRate = (totalPoint / vote.getTotalPointB()); // 배당률 계산
            for (SaveVote saveVote : saveVotesFalseList) { //승리한 유저 리스트에 해당하는 픽을 한 유저들을 집어넣음
                userWinnerList.add(saveVote.getUser());
            }
            for (User user : userWinnerList) {
                SaveVote saveVote = saveVoteRepository.findByUser_IdAndChatRoom_Id(user.getId(), roomId); //유저와 채팅방 id로 savevote를 뽑아옴 (유저는 한개씩 가짐)
                user.setTotalPoint((long) (user.getTotalPoint()+ (saveVote.getPoint()*winRate))); //savevote에서 내가 얼마를 걸었는지가 있는데 거기서 뽑아와서 winRate계산을 함
                userRepository.save(user);
                Point point = new Point("투표 승리", (long) (saveVote.getPoint() * winRate), user.getTotalPoint(), user); //포인트 내역 생성
                pointRepository.save(point);
            }
        } else {//투표가 동률일경우
            for (SaveVote saveVote : saveVotesTieList) {
                userWinnerList.add(saveVote.getUser());
            }
            for (User user : userWinnerList) {
                SaveVote saveVote = saveVoteRepository.findByUser_IdAndChatRoom_Id(user.getId(), roomId); //유저와 채팅방 id로 savevote를 뽑아옴 (유저는 한개씩 가짐)
                user.setTotalPoint((user.getTotalPoint()+ (saveVote.getPoint()))); //savevote에서 내가 얼마를 걸었는지가 있는데 거기서 뽑아와서 winRate계산을 함
                userRepository.save(user);
                Point point = new Point("투표 무승부",(saveVote.getPoint()), user.getTotalPoint(), user); //포인트 내역 생성
                pointRepository.save(point);
            }
        }
    }

이 코드는 채팅방의 시간이 다해 종료될시 실행되는 메소드다. 

Vote의 Cnt를 가지고 어디가 이겼는지를 판단하여 그곳의 투표한 유저들의 리스트를 뽑아와서 배당률을 계산해준 후 계산한 포인트 수 만큼 지급해준다. 만약 무승부가 된다면 투자한 포인트를 그대로 돌려준다.

 

유저 1번부터 6번까지 투표한 상황을 만들었다. 유저 1번과 2번은 false에 투표 나머지 3~6번은 true에 투표를 하였고 그에맞게 유저의 포인트가 삭감되고 포인트 내역에는 어떤 이유로 포인트가 사용되었는지 내역이 나옴과 동시에

Vote row에는 현재 투표의 진행상황이 명시된다.

 

시간이 지나 자동으로 ChatRoom이 삭제되면 투표 종료 메소드가 실행되는데

ChatRoom이 삭제되고 Vote row에는 ChatRoom의 id가 null이 들어있는것을 볼 수 있고

나머지 참여한 유저들 중 승리한 유저들은 자신이 건 포인트에 비례하여 포인트를 얻는 것을 확인할 수 있다,

투표 종료 후 유저들의 포인트 상황이다.