[GDG]홍대 맛집 아카이빙 프로젝트 #14 - 기타 투표기능 개발

2025. 8. 8. 01:47·팀 프로젝트/[2025][GDG]홍대 맛집 아카이빙 프로젝트

어제 투표기능을 개발했고, 이제 '투표' 자체는 동작한다. 프론트엔드와 합치면 어떻게 될 지는 잘 모르겠지만...


중복회원가입 방지하기

 

지금은 이게 된다.

원래 회원가입 플로우가 아이디와 닉네임 중복확인을 진행한 다음에 회원가입이 가능하도록 막아두는건데, (이건 프론트에서 하는듯?) 우선 백엔드만 POSTMAN으로 처리하면 저렇게 중복 회원가입이 가능하다.

 

문제는 이러면 로그인에서 문제가 생긴다는거

 

Resolved [org.springframework.web.servlet.resource.NoResourceFoundException: No static resource login.]

암튼 이런 문제가 생겨서 회원가입 시에 중복회원가입 방지를 위한 기능을 또 하나 넣을 예정이다.

식별자로는 회원가입 할 때 중복확인을 해야하는 닉네임 / 아이디와 함께 이메일로 지정해 셋 중 하나라도 DB에 겹치면 회원가입 불가

 

boolean existsByUserIdAndNicknameAndEmail(String userId, String email, String nickname);

MemberRepositoryInterface에는 이렇게만 담으면 된다.

..사실 JPA를 쓸 때 메서드이름과 인자의 순서가 같아야한단다. 저건 메서드명에 ID-닉네임-이메일 순으로 전해준다고 하였으나 인자로는 ID - 이메일 - 닉네임 순으로 전달한다. 물론 오류가 생기지는 않고, DB에서 값을 찾지 못하므로 영원히 false를 반환한다.

 

boolean existsByUserIdAndEmailAndNickname(String userId, String email, String nickname);

수정

 

boolean isAlreadyExist = memberRepository.existsByUserIdAndNicknameAndEmail(
                    userId, email, nickname);

            if (isAlreadyExist) {
                ResponseDTO responseDTO = ResponseDTO.builder()
                        .status(400)
                        .message("이미 가입한 사용자입니다.")
                        .build();

                return ResponseEntity
                        .status(HttpStatus.BAD_REQUEST)
                        .body(responseDTO);
            }

그리고 회원가입 로직에서 별다른 문제가 없어 DB에 회원정보를 저장하기 직전에 이 코드를 수행하면 끝

JPA에서 알아서 로직을 짤 때 boolean에 대해서 명확히 어떤 뜻을 담고있는지 파악해야하는데, isAlreadyExist 가 true면 이미 존재한다, false면 존재하지 않는다. (사실 처음에 반대로 코드짬)

 

성공


월별투표조회

이건... DB에 리스트들을 담아두고 만들어야겠다.

 

1. 현재투표조회

    @GetMapping("/result")
    public ResponseEntity<StoreForVoteResponseDTO> ASSC(
            HttpServletRequest request,
            @Value("${spring.jwt.secret}") String secret
    ) {
        return voteReachService.VoteService(secret, request);
    }

이건 현재 투표 중인 가게들 현황보기

그렇다. /votes로 GET요청 보냈을 때 코드 복붙이다. 근데 저거 JWT토큰이 있어야하는데 이 기능은 토큰이 없어도 되는 기능이라 아씨 새로 만들어야함

 

성성공공

 

API 명세서

거의 쪽대본급으로 개발하는 느낌이라 이거라도 없으면 어떻게 개발해야할지 모르겠다. 클래스 / 메서드마다 명확하게 할 일을 정해두고 개발하고 싶은데 그러기엔 시간이 적어서..

 

isSelected를 수정했다. true면 선정, 아니면 미선정

isSelected를 설정하는 방식을 매달말에 투표가 끝나면 상위 3개만 isSelected를 true로 바꾸고 과거투표저장용 DB로 이동시킬지, 아니면 매번 불러올 때마다 선정된 가게들이 모여있는 DB를 조회할까 생각했는데 전자가 나을 것 같다.

 

개발하다보니 month가 SQL예약어라서 그대로 사용하면 에러가 발생했다.

month -> whenForVote로 수정

 

이러고 자꾸 에러가 나서 뭔가했더니 레포지토리 인터페이스에 @Repository 어노테이션을 설정하지 않은데다가 메서드명도 getPastStoreForVoteByMonth에서 getPastStoreForVoteByWhenForVote로 바꿔야했다.

 

성공.

리스트는 비어있는게 맞다. 내가 아무것도 안넣어서


나의 투표기록

이건 어떻게 처리할까 생각해봤는데, 투표 기록용 DB에 유저아이디, 투표한 가게의 아이디, 몇월에 투표했는지를 저장하면 된다. 이 방법이 좀 더 낫네. 원래는 투표DB마다 투표한 사용자의 DB를 저장하려했는데 훨씬 간단하겠다.

 

도메인과 레포지토리를 세팅했으니 투표 후 DB에 저장하는 과정이 있으면 된다.

for (long ids : votedIds) {
            SimpleDateFormat formatter = new SimpleDateFormat("yyyy-MM");
            String formattedDate = formatter.format(date);
            
            VoteRecord voteRecord = VoteRecord.builder()
                    .userId(userId)
                    .votedId(ids)
                    .whenForVote(formattedDate)
                    .build();

            voteRecordRepository.save(voteRecord);
}

이렇게 코드를 짰다. 어째 코드가 너무 지저분해지는 것 같다. 근데 막상 또 리팩토링을하려니 어디서부터 손을 댈 지 엄두가 안난다.

 

 

암튼 저 코드는 위에

Date date = new Date();

가 있다. 인스턴스 선언을하면서 같이 선언한 탓에 코드 맨 위에 들어감

 

암튼 성공적으로 들어갔다. 저 VOTED_ID는 STORE_FOR_VOTE에 들어가는 ID값이다.

홍슐랭 선정 여부도 넣을까 고민중

 

1차 개발

    public ResponseEntity<PastStoreForVoteResponseDTO> accessMyVoteService(HttpServletRequest request, String secret) {
        String userId = jwtFilter.getTokenFromHeader(secret, request).getMemberInfo().getIdentifier();
        List<VoteRecord> recordList = voteRecordRepository.findByUserId(userId);
        System.out.println(recordList);
        List<PastStoreForVote> pastStoreForVoteList = new ArrayList<>();

        for (VoteRecord record : recordList) {
            System.out.println(record);
            System.out.println(record.getVotedId());

            long votedId = record.getVotedId();
            String whenForVote = record.getWhenForVote();

            PastStoreForVote pastStoreForVote = pastStoreRepository.findByIdAndWhenForVote(votedId, whenForVote);
            pastStoreForVoteList.add(pastStoreForVote);
        }

        PastStoreForVoteResponseDTO pastStoreForVoteResponseDTO = PastStoreForVoteResponseDTO.builder()
                .status(200)
                .message("성공")
                .storeForPastVote(pastStoreForVoteList)
                .build();

        return ResponseEntity.ok(pastStoreForVoteResponseDTO);

우선은 이렇게 코드를 짰는데 출력문은 디버깅용으로 넣은거고

JWT토큰에서 userId를 가져오고, 그 값을 매개로 VOTE_RECORD DB테이블에 접근해서 해당되는 컬럼의 값들을 가져와 리스트형태로 반환

 

그 리스트를 순회하면서 PAST_STORE_FOR_VOTE DB테이블에 접근해 (STORE_FOR_VOTE 테이블과 구성은 같으나 홍슐랭 선정여부인 IS_SELECTED가 추가됨) VOTED_ID와 WHEN_FOR_VOTE를 동시에 만족하는 경우에 투표에 추가함

 

우선 한 번 실패함

아마 DB에서 가져오는 과정에서 문제가 생긴 것 같은데..

 

로그 찍힌 것을 보니까 VOTED_ID까지는 잘 구했다.

알고보니까 VOTED_ID 컬럼이 PAST_STORE_FOR_VOTE 컬럼에 없었다. 그래서 찾질 못하니 null이 반환된거고..

 

STORE_FOR_VOTE나 PAST_STORE_FOR_VOTE나 한 가게의 ID값이 VOTED_ID 값으로 넘어가니 그냥 ID만 조사하면 된다. 나는 PAST_STORE_FOR_VOTE에 아무값도 저장시켜두지 않았으니 오류가 발생한거고.

 

        PastStoreForVote ps1 = PastStoreForVote.builder()
                .storeName("네이버")
                .storeLocation("경기도 성남시 분당구 정자일로 95")
                .storeImg("이미지경로")
                .isSelected(true)
                .whenForVote("2025-08")
                .storeInfoOneline("네이버1784")
                .build();

        PastStoreForVote ps2 = PastStoreForVote.builder()
                .storeName("서 울 역")
                .storeLocation("서울특별시 용산구 한강대로 405")
                .storeImg("이미지경로")
                .storeInfoOneline("서울역입니다.")
                .isSelected(false)
                .whenForVote("2025-08")
                .build();

대충 만들긴했는데 이게 ID값이 자동저장되는거라 이렇게 되면 실패한다.

왜냐하면 PAST_STORE_FOR_VOTE 컬럼에 저장된 ID값을 불러와야하는데 저렇게 되면 그냥 아이디값을 1,2로 설정하기 때문. 실제 내 코드에서는 1,3을 투표하고, 1,3을 불러온다.

 

실제로도 그렇고.. 3번 값이 없어서 null이 들어왔다.

그럼 우선 이정도로 기능은 완성되었다고하고, 투표가 끝나면 자동으로 STORE_FOR_VOTE 테이블에서 PAST_STORE_FOR_VOTE로 이동시키는 로직을 구현한 후에 (그리고 STORE_FOR_VOTE 테이블은 아무것도 없게 만들고) 다시 테스트해봐야할 것 같다.


DB테이블 자동초기화

우리는 매달 20일에 투표가 시작되고 매달말일에 투표가 끝난다. 그 중간에는 저 위에 메서드 /votes/result/month?month=... 로 연결시켜서 이전 투표결과를 보이게하면 된다.

 

그래서 매달 첫째날에 현재 투표 중인 상점들의 리스트를 보여주는 STORE_FOR_VOTE 테이블에서 상위 3개의 요소를 뽑아서 홍슐랭에 선정된 DB테이블로 이동시키고 STORE_FOR_VOTE 테이블의 요소들을 모두 지우고 각 요소들에 isSelected와 WhenForVote 변수를 추가해 PAST_STORE_FOR_VOTE 테이블에 넣으면 된다.

 

어휴 어려워

바이브코딩 시대에서 살아남으려면 내 머리로 로직짜는 연습을 해야한다.

 

원래는 매달 1일에 자동초기화를 시켜야하는데 우선은 9월 1일이 되면 프로젝트 세션이 끝나버리니까 5~10분 정도마다 초기화되도록 할 예정이다.

 

https://dev-coco.tistory.com/176

 

[Spring Boot] @Scheduled을 이용해 일정 시간 마다 코드 실행하기

@Scheduled Spring Boot에서 @Scheduled 어노테이션을 사용하면 일정한 시간 간격으로, 혹은 특정 시간에 코드가 실행되도록 설정할 수 있다. 주기적으로 실행해야 하는 작업이 있을 때 적용해 쉽게 사용

dev-coco.tistory.com

일정간격으로 로직을 작동시키려면 Scheduled 어노테이션을 사용하면 된다.

 

그 전에 로직부터 짜고

서비스 이름을 ModifyDBInEndDayService로 정했다.


말일에 DB정리하기

 

그래서 매달 첫째날에 현재 투표 중인 상점들의 리스트를 보여주는 STORE_FOR_VOTE 테이블에서 상위 3개의 요소를 뽑아서 홍슐랭에 선정된 DB테이블로 이동시키고 STORE_FOR_VOTE 테이블의 요소들을 모두 지우고 각 요소들에 isSelected와 WhenForVote 변수를 추가해 PAST_STORE_FOR_VOTE 테이블에 넣으면 된다.

 

이게 로직이고, 이걸 잘게 쪼개서 부분부분 만들어나가기로 한다. 먼저

 

STORE_FOR_VOTE 테이블에서 득표수 상위 3개의 요소를 뽑아서... 부분

StoreForVoteRepository에서 가장 투표값이 높은거 3개만 뽑아서 리스트에 저장하는 것을 목표로 한다. JPA가 메서드명만 잘 정하면 알아서 해줘서 좋은데 '메서드명만 잘 정하'는게 어렵다

 

    @PostConstruct
    public ResponseEntity<ResponseDTO> modifyInEnd(){
        List<StoreForVote> stores = storeForVoteRepository.findTop3ByOrderByVotedCountDesc();
        System.out.println(stores);

        return null;
    }

우선 PostConstruct를 통해서 득표수 상위 3개를 뽑아서 출력하는 코드

[StoreForVote(id=1, storeName=네이버, storeImg=이미지경로, storeLocation=경기도 성남시 분당구 정자일로 95, storeInfoOneline=네이버1784, votedCount=0)]

성공

 

근데 문제는 득표수가 동점일 때는 일부 값이 누락될 수도 있다는 것이다.

예를들어 득표수가 2,2,2,2,0,0 이라고하면 위에 3개만 가져온다. 뒤에 2는 무시된다. 이러면 또 따로 처리해야하는데 이건 또 나중에 알아보기로한다.

 

홍슐랭에 선정된 DB테이블로 이동시키고... 부분

이건 DB테이블을 새로 하나 만들어야한다.

저장할 값은 STORE DB에서의 ID와 선정된 달, 득표수 정도 하려고 했는데 생각해보니 단순 ID만 저장할지, 아니면 전체적인 정보를 저장할지하다가 우선은 ID만 저장하기로 했다.

 

우선은 지금 시간이 늦어서 여기까지만...

내일 마저 합시다~!


해야할 것

매달말 자동처리 -> 기존 투표후보들을 pastDB로 보내고 상위 3개를 isSelected를 true로 변환

토큰이 없는 경우, 토큰이 잘못된 경우에 예외처리

상점 삭제, 폐업 시 사용할 boolean closed 사용하기. STORE DB에 저장됨. true - 폐업

'팀 프로젝트 > [2025][GDG]홍대 맛집 아카이빙 프로젝트' 카테고리의 다른 글

[GDG]홍대 맛집 아카이빙 프로젝트 #16 - 테스트 돌리기  (2) 2025.08.09
[GDG]홍대 맛집 아카이빙 프로젝트 #15 - 투표기능 마무리도전하기  (4) 2025.08.09
[GDG]홍대 맛집 아카이빙 프로젝트 #13 - 투표기능  (6) 2025.08.06
[GDG] 홍대 맛집 아카이빙 프로젝트 #12 - 투표기능 DB연결  (2) 2025.08.06
[GDG] 홍대 맛집 아카이빙 프로젝트 #11 - 로그인/회원가입 마무리 (진)  (2) 2025.07.31
'팀 프로젝트/[2025][GDG]홍대 맛집 아카이빙 프로젝트' 카테고리의 다른 글
  • [GDG]홍대 맛집 아카이빙 프로젝트 #16 - 테스트 돌리기
  • [GDG]홍대 맛집 아카이빙 프로젝트 #15 - 투표기능 마무리도전하기
  • [GDG]홍대 맛집 아카이빙 프로젝트 #13 - 투표기능
  • [GDG] 홍대 맛집 아카이빙 프로젝트 #12 - 투표기능 DB연결
_cortisol_
_cortisol_
개발을 합니다.
  • _cortisol_
    Cortisol
    _cortisol_
  • 전체
    오늘
    어제
    • 분류 전체보기 (211)
      • 신년사 (3)
        • 2025년 (2)
        • 2026년 (1)
      • CS (59)
        • JVM (12)
        • 백엔드 (20)
        • 언어구현 (1)
        • 객체지향 (1)
        • 논리회로 (5)
        • 컴퓨터구조 (9)
        • 데이터베이스 (1)
        • 컴퓨터 네트워크 (10)
      • 언어공부 (64)
        • Java | Kotlin (48)
        • JavaScript | TypeScript (9)
        • C | C++ (6)
      • 개인 프로젝트 (11)
        • [2025] Happy2SendingMails (3)
        • [2026] 골든리포트! (8)
        • [2026] 순수자바로 개발하기 (0)
        • 기타 이것저것 (0)
      • 팀 프로젝트 (29)
        • [2025][GDG]홍대 맛집 아카이빙 프로젝트 (29)
      • 알고리즘 (13)
        • 백준풀이기록 (11)
      • 놀이터 (0)
      • 에러 수정일지 (2)
      • 고찰 (24)
        • CEOS 23기 회고록 (2)
  • 링크

    • 컬러잇 개발블로그
  • 공지사항

  • 인기 글

  • 태그

    144
  • 최근 댓글

  • 최근 글

  • hELLO· Designed By정상우.v4.10.5
_cortisol_
[GDG]홍대 맛집 아카이빙 프로젝트 #14 - 기타 투표기능 개발
상단으로

티스토리툴바