
8월 들어서 달에 한 번 정도는 네이버에서 작업해보려고 한다. 첫째 주에라던지.. 동기부여도 되고 좋잖아
암튼 오늘은 어제 하던거에 이어서 개발하려고 한다. 어제 문의드린 것도 답이 왔고
근데 좀 눈치보임;;
다행히 9시 반이 넘어서 커넥트 라운지로 넘어왔다. 여기는 그래도 좀 편하게 작업할 수 있는듯
가족단위 사람도 있고.. 네이버 탐방기 블로그도 써볼까. 암튼 되게 만족스러웠음. 9시 직전의 딱 출근 분위기도 멋져 새로워 최고야 스타벅스에서 딸기라떼 주문하면서 구경 좀 했다.
사실 눈치보여서 많이는 못함
다음달에 오면 좀 더 적극적으로 둘러봅시다.

어쨌든 의견이 들어왔으니까 이렇게 개발하기로 한다.
우선 어제부터 하던 투표url에 접근하면 투표할 가게의 리스트를 반환하는 서비스의 작업부터 해줬다.
public ResponseEntity<StoreForVoteResponseDTO> VoteService(String secret, HttpServletRequest request, voteRequstDTO voteRequestDTO) {
MemberDTO memberDTO = jwtFilter.getTokenFromHeader(secret, request).getMemberInfo();
if (memberDTO != null) { //회원
String userId = memberDTO.getIdentifier();
Member member = memberRepository.findByUserId(userId).get(0);
if (member != null) {
if (!member.isVoted()) { //이미 투표함. 투표 불가능
Iterable<StoreForVote> voteList = getStoresForVote();
StoreForVoteResponseDTO storeForVoteResponseDTO = StoreForVoteResponseDTO.builder()
.status(200)
.message("성공(이미 투표한 사용자)")
.storeForVote(voteList)
.voteAvailable(false)
.build();
return ResponseEntity.ok(storeForVoteResponseDTO);
} else {
Iterable<StoreForVote> voteList = getStoresForVote();
StoreForVoteResponseDTO storeForVoteResponseDTO = StoreForVoteResponseDTO.builder()
.status(200)
.message("성공(투표가능한 사용자")
.storeForVote(voteList)
.voteAvailable(true)
.build();
return ResponseEntity.ok((storeForVoteResponseDTO));
}
} else { //유저정보가 없음
StoreForVoteResponseDTO storeForVoteResponseDTO = StoreForVoteResponseDTO.builder()
.status(200)
.message("유저의 정보가 없습니다.")
.voteAvailable(false)
.build();
return ResponseEntity
.status(HttpStatus.BAD_REQUEST)
.body(storeForVoteResponseDTO);
}
} else { //회원아님
StoreForVoteResponseDTO storeForVoteResponseDTO = StoreForVoteResponseDTO.builder()
.status(400)
.message("회원이 아닙니다.")
.build();
return ResponseEntity
.status(HttpStatus.BAD_REQUEST)
.body(storeForVoteResponseDTO);
}
}
public Iterable<StoreForVote> getStoresForVote() {
return storeForVoteRepository.findAll();
}
}
대충 플로우는 접근한 요청에서 JWT토큰 여부를 조사한 뒤, 있다면 회원으로 간주하고, JWT토큰에 저장된 회원정보를 기반으로 DB에 있는 isvoted 를 불러와 이미 투표했을경우(isvoted가 false) 그냥 투표 현황만 보여주고 하지 않았을 경우에는 투표도 할 수 있게해주었다.
만약 JWT토큰이 없다면 비회원으로 간주하고 접근거부
그리고 토큰에서 얻은 회원정보로 DB에 접근했을 때 DB에 값이 없으면 접근 거부. 이거는 Optional로 짜도 되는데 깜빡하고 그냥 해버려서.. 그냥 새로 if문을 만들었다.
또한 이 코드와 함께 밑에 투표기능에 대해서도 만들어뒀는데.. 왜 그랬는지는 모르겠다. 접근시 정보제공하는 서비스와 투표 서비스를 한 클래스에다 욱여넣을 생각을 하다니...
테스트하다가 생각해보니 로그인이 안되었지만 JWT토큰만 그럴싸하게 만들어서 전해주는 경우도 있을 것 같아서 예외처리를 해주었다.
if (memberRepository.findByUserId(userId).size() == 0 ) {
StoreForVoteResponseDTO storeForVoteResponseDTO = StoreForVoteResponseDTO.builder()
.status(200)
.message("사용자정보가 없습니다.")
.voteAvailable(false)
.build();
return ResponseEntity
.status(HttpStatus.BAD_REQUEST)
.body(storeForVoteResponseDTO);
}
Member member = memberRepository.findByUserId(userId).get(0);
그리고 테스트하려다가 MemberRepository에도 JPA를 적용시켰다. 이제 JDBC는 안녕~

비회원일 경우

회원인 경우
voteAvailable이 false인 이유는 Member 객체에 선언해둔 isVoted의 기본값이 false이기 때문이다. 그런데 true로 바꿔야할듯. 매달 1일에 초기화시키려고 한다.

이렇게 STORE_FOR_VOTE DB에 점포가 등록되면 보여주기도한다.
우선은 네이버에서는 여기까지 하기로 결정 왜냐하면 나는 배가 고프거든
9시 20분 정도에서 11시까지 했는데, 생각보다 능률이 좋은데? 내가 카공을 별로 안좋아하는데 카개(카페개발)은 은근 효과가 있을지도
그리고 다시 집으로 돌아옴
판교에서 안양까지 1시간 안으로 뚫는데 역시 빠르다. 네이버 갈 때는 1303이 한 번에 가서 탔는데 올 때는 판교에서 3330 타고 왔다.
암튼 마저 해보자면
지금까지 한 것이 투표창으로 이동하면 투표할 리스트를 주는거고 이제 개발해야할 기능은 진짜 투표기능이다.
프론트엔드에서 투표한 상점의 ID값을 주면 내가 이걸 받아서 기타 처리를 해 줄 예정이다.

API 명세서
'기타 처리'에는
- STORE_FOR_VOTE DB에 접근해서 득표수를 1 늘리고 저장하고
- JWT토큰에 들어온 사용자의 아이디로 사용자 DB에 접근해 isVoted를 false로 바꾸고 저장하고
이정도면 될 듯?
우선 STORE_FOR_VOTE DB에 접근해서 득표수를 1 늘리는 것부터 해봅시다.
..라고 하려고 했는데, 저렇게 JSON으로 전달할 때 "id"로 key 값이 겹치면 마지막 하나만 유효하다고해서 투표한 가게의 아이디만 리스트로 넘기는 것으로 변경

public ResponseEntity<StoreForVoteResponseDTO> voteMainService(
String secret,
List<Long> votedIds,
HttpServletRequest request) {
List<StoreForVote> storeForVoteList = new ArrayList<>(); //저장용 리스트 / 저장할 때 사용함
String userId = jwtFilter.getTokenFromHeader(secret, request)
.getMemberInfo()
.getIdentifier(); //헤더의 토큰에서 유저아이디를 뽑아뽑아
if (userId == null) { //토큰이 없을 경우
StoreForVoteResponseDTO storeForVoteResponseDTO = StoreForVoteResponseDTO.builder()
.status(200)
.message("토큰이 전달되지 않았습니다.")
.build();
return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body(storeForVoteResponseDTO);
}
boolean isVoted = memberRepository.findByUserId(userId).get(0).isVoted(); //사용자 투표여부 조사
if (isVoted == false) { //이미투표한사용자
StoreForVoteResponseDTO storeForVoteResponseDTO = StoreForVoteResponseDTO.builder()
.status(200)
.message("이미 투표한 사용자입니다.")
.build();
return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body(storeForVoteResponseDTO);
}
if (votedIds.size() == 0) { //사용자가 투표한 상점들의 id 리스트 - 메서드 인자로 제공됨
StoreForVoteResponseDTO storeForVoteResponseDTO = StoreForVoteResponseDTO.builder()
.status(400)
.message("반드시 하나를 선택해주세요.")
.build();
return ResponseEntity
.status(HttpStatus.BAD_REQUEST)
.body(storeForVoteResponseDTO);
}
if (votedIds.size() > 3) { //3개이상 선택불가
StoreForVoteResponseDTO storeForVoteResponseDTO = StoreForVoteResponseDTO.builder()
.status(400)
.message("3개 이상은 선택이 불가합니다.")
.build();
return ResponseEntity
.status(HttpStatus.BAD_REQUEST)
.body(storeForVoteResponseDTO);
}
for (Long id : votedIds) { //리스트를 순회하며 아이디를 따고 상점정보를 가져와 저장함
Optional<StoreForVote> storeForVoteInOptional = storeForVoteRepository.findById(id);
if (storeForVoteInOptional.isPresent()) { //DB에 값이 있는 경우
StoreForVote storeForVote = storeForVoteInOptional.get();
storeForVoteList.add(storeForVote);
Integer votedCount = storeForVote.getVotedCount() + 1;
storeForVote.setVotedCount(votedCount);
storeForVoteRepository.save(storeForVote);
} else { //DB에 값이 없는 경우 - 저장 취소 및 오류 반환
StoreForVoteResponseDTO storeForVoteResponseDTO = StoreForVoteResponseDTO.builder()
.status(400)
.message("입력된 값이 존재하지 않습니다.")
.build();
storeForVoteList.clear();
return ResponseEntity
.status(HttpStatus.BAD_REQUEST)
.body(storeForVoteResponseDTO);
}
}
for (StoreForVote storeForVote : storeForVoteList) { // 인자로 받은 값들이 모두 정상. DB에 저장하는 과정
storeForVoteRepository.save(storeForVote);
}
StoreForVoteResponseDTO storeForVoteResponseDTO = StoreForVoteResponseDTO.builder()
.status(200)
.message("성공")
.build();
return ResponseEntity
.status(HttpStatus.OK)
.body(storeForVoteResponseDTO);
}
}
대충 이렇게 짰다.
주석과 조건문의 연속.. 당연히 조건이 많으니 조건문도 많다.
왠지 주석처리 많이하면 GPT 솜씨같고 그러는데 이건 순도 97% 나의 코드 (Optional 쪽에서 GPT에게 물어보긴했다)
저 코드에서 추가로 Member DB의 투표여부를 저장하는 isVoted 변수를 isVoteAvailable 로 변경했다. 좀 더 직관적으로 알아챌 수 있도록.. VoteAvailable이 true면 투표가 가능하고, false면 투표가 불가능하다.
이 정도면은 된 것 같고.. 그 다음에 해야할 것은
중복투표, 재투표 방지하기
private boolean voteAvailable = true;
우선 이렇게 Member 테이블에 있는 voteAvailable 값을 true가 기본이 되게 설정한다. 이렇게 되면 투표경험이 없는 사람의 경우 바로 투표가 가능하고, 투표 후에 이를 false로, 매달 1일에 다시 true로 바꾸는 코드만 짜면 된다.
...라고 했는데 내가 Builder 어노테이션을 사용했다. Builder 어노테이션은 저렇게 설정한 값을 무시하고 null, false 등의 기본값으로 처리해서 내가 회원가입 할 때 true로 지정해주어야함.
public ResponseDTO modifyUserVoteAvailable(String userId) {
Member member = memberRepository.findByUserId(userId).get(0);
member.setVoteAvailable(false);
memberRepository.save(member);
return ResponseDTO.builder()
.status(200)
.build();
}
이렇게 메서드를 따로 빼두고
for (StoreForVote storeForVote : storeForVoteList) { // 인자로 받은 값들이 모두 정상. DB에 저장하는 과정
storeForVoteRepository.save(storeForVote);
}
StoreForVoteResponseDTO storeForVoteResponseDTO = StoreForVoteResponseDTO.builder()
.status(200)
.message("성공")
.build();
modifyUserVoteAvailable(userId);
return ResponseEntity
.status(HttpStatus.OK)
.body(storeForVoteResponseDTO);
이렇게 DB에 값을 저장하고 상태를 반환하기 위해 DTO를 만들고 ResponseEntity에 이를 담아 반환하는 과정 사이에 업데이트하기로한다.
원래 조건문 써서 DTO의 코드가 200일 때만 뒤에 코드를 실행시키려했는데, 인자로 주는 userId가 애초에 DB에서 가져온거기도하고, userId가 DB에 없으면 미리 걸러지도록 앞에서 조건문을 써놔서 굳이 그러지는 않았다. 그냥 void로 리턴타입을 정할걸 그랬나
테스트
테스트합ㄴ시다.
테스트 한 번에 회원가입도 하고 로그인도 하고 상점DB에 상점도 등록하고 STORE_FOR_VOTE DB에 가게를 등록해야 서비스 기본세팅이 끝난다. classpath에 SQL문 하나를 넣거나 main 메서드에 save하도록 기본적으로 추가하면 테스트가 곰방곰방 끝나는데 귀찮아서 그냥 POSTMAN으로 처리하는 중

세팅성공
무슨 테스트용 디버깅을 1시간동안 하다니...

이미 투표한 사용자의 경우
계속 URL 치고 가게정보 정리하고 이게 귀찮아서..
@PostConstruct
public void test() {
Store store1 = Store.builder()
.storeName("네이버")
.storeLocation("경기도 성남시 분당구 정자일로 95")
.storeImg("이미지경로")
.storeInfoOneline("네이버1784")
.build();
Store store2 = Store.builder()
.storeName("홍익홍익")
.storeLocation("서울특별시 마포구 와우산로 94")
.storeImg("이미지경로")
.storeInfoOneline("홍문관")
.build();
Store store3 = Store.builder()
.storeName("잠실야구장")
.storeLocation("서울특별시 송파구 올림픽로 19-2")
.storeImg("이미지경로")
.storeInfoOneline("잠실야구장입니다.")
.build();
Store store4 = Store.builder()
.storeName("서 울 역")
.storeLocation("서울특별시 용산구 한강대로 405")
.storeImg("이미지경로")
.storeInfoOneline("서울역입니다.")
.build();
Store store5 = Store.builder()
.storeName("궁평항")
.storeLocation("경기도 화성시 서신면 궁평항로 1049-24")
.storeImg("이미지경로")
.storeInfoOneline("바다에 들어가지 마세요!")
.build();
storeRepositoryInterface.save(store1);
storeRepositoryInterface.save(store2);
storeRepositoryInterface.save(store3);
storeRepositoryInterface.save(store4);
storeRepositoryInterface.save(store5);
}
그냥 임의의 가상의 가게 5개를 저장하는 코드를 만들어두었다.
원래 메인메서드에 하려고 했는데 storeRepositoryInterface는 인스턴스라 메인메서드에 못들어간다. (헤드퍼스트에서 배운 정적메서드를 여기 써먹다)
PostConstruct 어노테이션을 사용하면 애플리케이션이 시작되자마자 작동되는 메서드다.

편-안


--- 투표 후 ---

성공

MEMBER DB에서도 성공적으로 바뀌었다.
https://www.youtube.com/watch?v=RkFR-6yp8W0
구동영상
음... 이거말고 또 어떤 기능을 개발해야할지 고민 중이다.
몇가지 기능개발이 남아있긴하다. 근데 투표기능은 거의 개발해서 굵직굵진한 것은 끝났고.. 남은것은 월별투표조회, 나의 투표기록 조회, 매달 투표 관련기능 자동초기화 등이 있는데,
약간 빡셀수도...? 암튼 목금토에 열심히 해볼예정
토요일 19시 30분에 코어타임이니까 열심히 또 달리면 어떻게 될지 모르겠다.
해야할거
- 중복회원가입 방지 -> DB에 멤버 하나만!
- 월별투표기록조회 기능개발
- 나의투표기록조회 기능개발
- 투표기능 자동초기화
아마 어려운 순으로 해보려면 투표기능 자동초기화-나의투표기록-월별투표기록 순으로 개발을 해보면 좋을 것 같다.
끝~
'팀 프로젝트 > [2025][GDG]홍대 맛집 아카이빙 프로젝트' 카테고리의 다른 글
| [GDG]홍대 맛집 아카이빙 프로젝트 #15 - 투표기능 마무리도전하기 (4) | 2025.08.09 |
|---|---|
| [GDG]홍대 맛집 아카이빙 프로젝트 #14 - 기타 투표기능 개발 (4) | 2025.08.08 |
| [GDG] 홍대 맛집 아카이빙 프로젝트 #12 - 투표기능 DB연결 (2) | 2025.08.06 |
| [GDG] 홍대 맛집 아카이빙 프로젝트 #11 - 로그인/회원가입 마무리 (진) (2) | 2025.07.31 |
| [GDG] 개발코스 4주차 WIL (5) | 2025.07.29 |