-
Notifications
You must be signed in to change notification settings - Fork 2
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat : 선착순 API 기능 일부 구현 (CC-141) (#30)
* feat : 선착순 API 기능 일부 구현 (CC-141) * test : 사용자가 숨은그림찾기 게임 정보를 조회하는 로직 테스트코드 작성, Clock 객체 모킹하여 LocalDateTime.now()에 주입 (CC-147) (#28) * chore : LocalDateTime.now() 테스트코드 작성을 위한 TimeConfig 빈 등록 (CC-147) * test :TimeConfig, Clock객체를 모킹한 테스트코드 작성 (CC-147) * fix : 필요없는 출력문 제거 (CC-147) * fix : GithubAction 컨테이너의 테스트 환경에서의 UTC 시차 테스트를 위한 코드 (CC-147) * fix : 오타 수정 (CC-148) * fix : 오타 수정 (CC-148) * chore : 스프링부트 어플리케이션 Timezone KST로 설정 (CC-147) * test : 단위테스트 Timezone KST로 설정 (CC-147) * test : 단위테스트 Timezone KST로 설정한 후 LocalDateTime.now 호출로 변경 (CC-147) * test : LocalDateTimeService ci 테스트용 코드 (CC-147) * test : LocalDateTimeService ci 테스트용 코드 (CC-147) * test : Clock.instant에 UTC+9의 시차가 정상적으로 적용되는지 확인하는 테스트코드 작성 (CC-147) * fix : 빌더 컨벤션 통일, 고정값 상수화 (CC-141) * fix : regist -> register 모두 변경 (CC-141) --------- Co-authored-by: Jinwoo Park <[email protected]>
- Loading branch information
Showing
12 changed files
with
296 additions
and
1 deletion.
There are no files selected for viewing
44 changes: 44 additions & 0 deletions
44
src/main/java/ai/softeer/caecae/findinggame/api/FindingGamePlayController.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,44 @@ | ||
package ai.softeer.caecae.findinggame.api; | ||
|
||
import ai.softeer.caecae.findinggame.domain.dto.request.AnswerRequestDto; | ||
import ai.softeer.caecae.findinggame.domain.dto.request.RegisterWinnerRequestDto; | ||
import ai.softeer.caecae.findinggame.domain.dto.response.AnswerResponseDto; | ||
import ai.softeer.caecae.findinggame.domain.dto.response.RegisterWinnerResponseDto; | ||
import ai.softeer.caecae.findinggame.service.FindingGamePlayService; | ||
import ai.softeer.caecae.global.dto.response.SuccessResponse; | ||
import ai.softeer.caecae.global.enums.SuccessCode; | ||
import lombok.RequiredArgsConstructor; | ||
import org.springframework.http.ResponseEntity; | ||
import org.springframework.web.bind.annotation.PostMapping; | ||
import org.springframework.web.bind.annotation.RequestBody; | ||
import org.springframework.web.bind.annotation.RequestMapping; | ||
import org.springframework.web.bind.annotation.RestController; | ||
|
||
@RestController | ||
@RequiredArgsConstructor | ||
@RequestMapping("/api/finding") | ||
public class FindingGamePlayController { | ||
private final FindingGamePlayService findingGamePlayService; | ||
|
||
/** | ||
* 참여자가 게임의 정답을 보냄 | ||
* | ||
* @param req | ||
* @return | ||
*/ | ||
@PostMapping("/answer") | ||
public ResponseEntity<SuccessResponse<AnswerResponseDto>> checkAnswer(@RequestBody AnswerRequestDto req) { | ||
return SuccessResponse.of(SuccessCode.OK, findingGamePlayService.checkAnswer(req)); | ||
} | ||
|
||
/** | ||
* 선착순에 든 참여자가 전화번호를 입력 | ||
* | ||
* @param req | ||
* @return | ||
*/ | ||
@PostMapping("/register") | ||
public ResponseEntity<SuccessResponse<RegisterWinnerResponseDto>> registWinner(@RequestBody RegisterWinnerRequestDto req) { | ||
return SuccessResponse.of(SuccessCode.OK, findingGamePlayService.registWinner(req)); | ||
} | ||
} |
8 changes: 8 additions & 0 deletions
8
src/main/java/ai/softeer/caecae/findinggame/domain/dto/request/AnswerRequestDto.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
package ai.softeer.caecae.findinggame.domain.dto.request; | ||
|
||
import java.util.List; | ||
|
||
public record AnswerRequestDto( | ||
List<CoordDto> answerList | ||
) { | ||
} |
7 changes: 7 additions & 0 deletions
7
src/main/java/ai/softeer/caecae/findinggame/domain/dto/request/CoordDto.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
package ai.softeer.caecae.findinggame.domain.dto.request; | ||
|
||
public record CoordDto( | ||
int coordX, | ||
int coordY | ||
) { | ||
} |
7 changes: 7 additions & 0 deletions
7
src/main/java/ai/softeer/caecae/findinggame/domain/dto/request/RegisterWinnerRequestDto.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
package ai.softeer.caecae.findinggame.domain.dto.request; | ||
|
||
public record RegisterWinnerRequestDto( | ||
String ticketId, | ||
String phone | ||
) { | ||
} |
14 changes: 14 additions & 0 deletions
14
src/main/java/ai/softeer/caecae/findinggame/domain/dto/response/AnswerResponseDto.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
package ai.softeer.caecae.findinggame.domain.dto.response; | ||
|
||
import ai.softeer.caecae.findinggame.domain.dto.request.CoordDto; | ||
import lombok.Builder; | ||
|
||
import java.util.List; | ||
|
||
@Builder | ||
public record AnswerResponseDto( | ||
List<CoordDto> correctAnswerList, | ||
String ticketId, | ||
Long startTime | ||
) { | ||
} |
9 changes: 9 additions & 0 deletions
9
...ain/java/ai/softeer/caecae/findinggame/domain/dto/response/RegisterWinnerResponseDto.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
package ai.softeer.caecae.findinggame.domain.dto.response; | ||
|
||
import lombok.Builder; | ||
|
||
@Builder | ||
public record RegisterWinnerResponseDto( | ||
boolean success | ||
) { | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
2 changes: 2 additions & 0 deletions
2
src/main/java/ai/softeer/caecae/findinggame/domain/entity/FindingGameWinnerId.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
66 changes: 66 additions & 0 deletions
66
src/main/java/ai/softeer/caecae/findinggame/repository/FindingGameRedisRepository.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,66 @@ | ||
package ai.softeer.caecae.findinggame.repository; | ||
|
||
import lombok.RequiredArgsConstructor; | ||
import lombok.extern.slf4j.Slf4j; | ||
import org.springframework.data.redis.core.RedisTemplate; | ||
import org.springframework.stereotype.Repository; | ||
|
||
@Slf4j | ||
@Repository | ||
@RequiredArgsConstructor | ||
public class FindingGameRedisRepository { | ||
private final static String TICKET_KEY = "ticket"; | ||
private final static String COUNT_KEY = "count"; | ||
private final static String WINNER_KEY = "winner"; | ||
private final RedisTemplate<String, Object> redisTemplate; | ||
|
||
/** | ||
* 당첨자 수 증가 | ||
* | ||
* @return 현재 당첨자 수 값 | ||
*/ | ||
public Long increaseCount() { | ||
return redisTemplate.opsForValue().increment(COUNT_KEY); | ||
} | ||
|
||
/** | ||
* 당첨자 수 감소 | ||
* | ||
* @return 현재 당첨자 수 값 | ||
*/ | ||
public Long decreaseCount() { | ||
return redisTemplate.opsForValue().decrement(COUNT_KEY); | ||
} | ||
|
||
|
||
/** | ||
* 선착순에 들은 참여자를 위너 목록에 추가한다. | ||
* | ||
* @param ticketId | ||
* @return 시작 시간 | ||
*/ | ||
public Long addWinner(String ticketId) { | ||
Long startTime = System.currentTimeMillis(); | ||
redisTemplate.opsForHash().put(WINNER_KEY, ticketId, startTime); | ||
return startTime; | ||
} | ||
|
||
/** | ||
* 위너 목록에서 참여자를 삭제한다. | ||
* | ||
* @param ticketId | ||
*/ | ||
public void deleteWinner(String ticketId) { | ||
redisTemplate.opsForHash().delete(WINNER_KEY, ticketId); | ||
} | ||
|
||
/** | ||
* TicketID를 가진 참여자의 입력 시작 시간을 가져온다. | ||
* | ||
* @param ticketId | ||
* @return | ||
*/ | ||
public Long getWinnerStartTime(String ticketId) { | ||
return (Long) redisTemplate.opsForHash().get(WINNER_KEY, ticketId); | ||
} | ||
} |
9 changes: 9 additions & 0 deletions
9
src/main/java/ai/softeer/caecae/findinggame/repository/FindingGameWinnerRepository.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
package ai.softeer.caecae.findinggame.repository; | ||
|
||
import ai.softeer.caecae.findinggame.domain.entity.FindingGameWinner; | ||
import org.springframework.data.jpa.repository.JpaRepository; | ||
import org.springframework.stereotype.Repository; | ||
|
||
@Repository | ||
public interface FindingGameWinnerRepository extends JpaRepository<FindingGameWinner, Integer> { | ||
} |
122 changes: 122 additions & 0 deletions
122
src/main/java/ai/softeer/caecae/findinggame/service/FindingGamePlayService.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,122 @@ | ||
package ai.softeer.caecae.findinggame.service; | ||
|
||
import ai.softeer.caecae.findinggame.domain.dto.request.AnswerRequestDto; | ||
import ai.softeer.caecae.findinggame.domain.dto.request.CoordDto; | ||
import ai.softeer.caecae.findinggame.domain.dto.request.RegisterWinnerRequestDto; | ||
import ai.softeer.caecae.findinggame.domain.dto.response.AnswerResponseDto; | ||
import ai.softeer.caecae.findinggame.domain.dto.response.RegisterWinnerResponseDto; | ||
import ai.softeer.caecae.findinggame.domain.entity.FindingGameWinner; | ||
import ai.softeer.caecae.findinggame.repository.FindingGameDbRepository; | ||
import ai.softeer.caecae.findinggame.repository.FindingGameRedisRepository; | ||
import ai.softeer.caecae.findinggame.repository.FindingGameWinnerRepository; | ||
import ai.softeer.caecae.user.domain.entity.User; | ||
import ai.softeer.caecae.user.repository.UserRepository; | ||
import jakarta.transaction.Transactional; | ||
import lombok.RequiredArgsConstructor; | ||
import lombok.extern.slf4j.Slf4j; | ||
import org.springframework.stereotype.Service; | ||
|
||
import java.util.ArrayList; | ||
import java.util.List; | ||
import java.util.UUID; | ||
|
||
@Slf4j | ||
@Service | ||
@RequiredArgsConstructor | ||
public class FindingGamePlayService { | ||
private final FindingGameRedisRepository findingGameRedisRepository; | ||
private final FindingGameDbRepository findingGameDbRepository; | ||
private final FindingGameWinnerRepository findingGameWinnerRepository; | ||
private final UserRepository userRepository; | ||
|
||
private final int MAX_ANSWER_COUNT = 2; | ||
private final int ANSWER_RANGE = 900; | ||
private final long CONSTRAINT_TIME = 1000L * 60 * 3; | ||
|
||
/** | ||
* 사용자가 보낸 정답을 채점하고 모두 맞으면 선착순 인원에 들었는지 확인하는 서비스 로직 | ||
* | ||
* @param req | ||
* @return | ||
*/ | ||
public AnswerResponseDto checkAnswer(AnswerRequestDto req) { | ||
// 정답 판단 | ||
List<CoordDto> answerList = req.answerList(); // 사용자가 보낸 리스트 | ||
List<CoordDto> correctList = new ArrayList<>(); // 정답 리스트 | ||
correctList.add(new CoordDto(10, 10)); // 임시 정답 데이터 | ||
correctList.add(new CoordDto(30, 30)); // TODO: 정답 정보 가져와야함 | ||
List<CoordDto> correctAnswerList = new ArrayList<>(); // 사용자에게 보낼 채점한 리스트 | ||
int count = 0; | ||
for (CoordDto answer : answerList) { | ||
int x = answer.coordX(), y = answer.coordY(); | ||
log.info("X: {}, Y: {}", x, y); | ||
for (CoordDto correct : correctList) { | ||
int cx = correct.coordX(), cy = correct.coordY(); | ||
int diff = (x - cx) * (x - cx) + (y - cy) * (y - cy); // 점과 점 사이의 거리 제곱 공식 | ||
if (diff <= ANSWER_RANGE) { // TODO: 정답 범위 _ 수정 요망 | ||
correctAnswerList.add(correct); | ||
count++; | ||
correctList.remove(correct); | ||
break; | ||
} | ||
} | ||
} | ||
|
||
// 정답 개수가 0이거나 1 || 남은 선착순 자리 체크 | ||
if (count != MAX_ANSWER_COUNT || findingGameRedisRepository.increaseCount() > 315L) { // TODO: 오늘 선착순 인원 정보 가져와야함 | ||
if (count == MAX_ANSWER_COUNT) findingGameRedisRepository.decreaseCount(); | ||
return AnswerResponseDto.builder() | ||
.correctAnswerList(correctAnswerList) | ||
.ticketId("") | ||
.startTime(-1L) | ||
.build(); | ||
} | ||
|
||
// TicketId 생성 | ||
String ticketId = UUID.randomUUID().toString(); | ||
Long startTime = findingGameRedisRepository.addWinner(ticketId); | ||
return AnswerResponseDto.builder() | ||
.correctAnswerList(correctAnswerList) | ||
.ticketId(ticketId) | ||
.startTime(startTime) | ||
.build(); | ||
} | ||
|
||
/** | ||
* 선착순에 든 사용자가 전화번호를 입력했을 때 처리하는 서비스 로직 | ||
* | ||
* @param req | ||
* @return 정답 리스트와 선착순 참가자 등록 정보 | ||
*/ | ||
@Transactional | ||
public RegisterWinnerResponseDto registWinner(RegisterWinnerRequestDto req) { | ||
String ticketId = req.ticketId(); | ||
Long startTime = findingGameRedisRepository.getWinnerStartTime(ticketId); | ||
if (startTime == null) return RegisterWinnerResponseDto.builder().success(false).build(); // 목록에 없으면 | ||
log.info("사용자 시작시간: {}", startTime); | ||
Long endTime = System.currentTimeMillis() - 1000L; // 1초 지연 허용 | ||
if (endTime - startTime > CONSTRAINT_TIME) { // 3분 초과 - 실패 및 당첨자 제외 | ||
findingGameRedisRepository.decreaseCount(); | ||
findingGameRedisRepository.deleteWinner(ticketId); | ||
return RegisterWinnerResponseDto.builder() | ||
.success(false) | ||
.build(); | ||
} | ||
Integer gameId = 1; | ||
String phone = req.phone(); | ||
findingGameDbRepository.findById(gameId); | ||
FindingGameWinner winner = FindingGameWinner.builder() | ||
.user(userRepository.findByPhone(phone).orElseGet(() -> userRepository.save( | ||
User.builder() | ||
.phone(phone) | ||
.build() | ||
))) | ||
.findingGame(findingGameDbRepository.findById(gameId).get()) | ||
.build(); | ||
findingGameWinnerRepository.save(winner); | ||
findingGameRedisRepository.deleteWinner(ticketId); | ||
return RegisterWinnerResponseDto.builder() | ||
.success(true) | ||
.build(); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters