페이지역할기능
로그인, 회원가입유저 관리- 로그인 / 로그아웃
- 회원가입
메인로또 티켓 구매- 번호 추출 타입 선택
- 로또 구입
로또 번호 추출 페이지추출한 랜덤 번호 확인- 요청한 랜덤 번호 리스트 확인(페이징)
- 메인으로 돌아가기
- 회차별 당첨자 조회 페이지 이동
이번주 동행복권 당첨번호동행복권 당첨 결과 확인
사이트 기준 당첨 결과 확인
- 이번주 동행복권 추첨 결과 확인
- 사이트 기준 순위, 당첨금액, 참여 수, 총 판매 금액
이번주 내번호 당첨확인이번주 참여 결과 확인- 이번주 내번호 당첨 결과 확인
- 이번주 추출 전체 리스트
(구입 티켓 수, 참여 회차, 로또 번호, 보너스 번호, 추첨일)
회차별 당첨 결과 확인회차 선택 기능- 이번주 동행복권 추첨 결과 확인
- 사이트 기준 순위, 당첨금액, 참여 수, 총 판매 금액

1. 코드 핵심 로직

1️⃣ 동행복권 api 요청 후 내부 DB 저장

📌 핵심 📌
: 동행복권 api를 그때그때 불러서 사용하지 않고, 데이터를 전부 DB에 저장하는 이유는
동행복권 사이트에 의존하지 않기 위해서이다.

  • Thread.sleep();
  • gson

  1. 엔트 포인트를 통해서 동행복권 마지막 회차까지 DB에 저장 요청
https://www.dhlottery.co.kr/common.do?method=getLottoNumber&drwNo=마지막회차번호
  1. gson을 통해 파싱하여 json -> API object로 생성한 뒤 LottoApi 데이터베이스에 저장한다.
try {//로직의 시간을 지연시키는 것
     Thread.sleep(100);//0.1초
 } catch (InterruptedException e) { 
     throw new RuntimeException(e);
 }
  • api를 많이 호출하면 디도스 공격으로 오해받아 block 당할 수 있다.
    따라서 Thread.sleep()메서드를 하나씩 디비에 저장할 때 스레드를 일정 시간을 멈추어 해당 이슈를 방지했다.

  • 데이터 파싱은 Gson의 라이브러리를 사용하여
    json 문자열을 lottoApi 객체로 생성하여 테이블에 저장한다


2️⃣ 매주 회차 관리

매주 마지막 회차 가져오기

📌 핵심 📌
: 번호 추출 후 추출 회차를 저장해야하기 때문에 해당 회차 번호가 필요하다
필요시마다 데이터베이스에 요청하지 않고, 프로젝트 구동시 한번 가져오는 방법을 선택했다

  • @PostContruct
  1. @PostContruct 등록하여 프로젝트 구동 시 DB에서 마지막 회차번호를 가지고 온다
	@PostConstruct 
    public void init() {
        //마지막 회차 번호 얻기
        Long lastCycleNum = isTestProperties() ? 1L : lottoApiService.getLastCycleNum();
        memory.put(LAST_CYCLE_NUM, lastCycleNum);
    }
@Component
@RequiredArgsConstructor
public class MemoryContext {
    public static Map<MemoryKey, Long> memory = new HashMap<>();
    private final LottoApiService lottoApiService;
    private final Environment environment;

    @PostConstruct 
    public void init() {
        //마지막 회차 번호 얻기
        Long lastCycleNum = isTestProperties() ? 1L : lottoApiService.getLastCycleNum();
        memory.put(LAST_CYCLE_NUM, lastCycleNum);
    }

    @RequiredArgsConstructor
    public enum MemoryKey {
        LAST_CYCLE_NUM("lastCycleNum");
        private final String name;
    }

    private boolean isTestProperties() {
        String activeProfile = environment.getActiveProfiles()[0];
        return activeProfile.equals("local") || activeProfile.equals("test");
    }

}

Scheduled을 통해 최신 회차 주기적 업데이트

주기적으로 매주 마지막 회차가 변경되므로 api를 불러와야하는데
예상한 3가지 방법중 자동화를 선택하였다.
1. 운영자가 수동으로 (엔드포인트로 계속 요청)
2. 운영자 페이지를 만들어서 동기화 버튼을 통해 변경 (관리자 페이지 생성)
3. 자동화 (스케줄러에 등록)

  • 스케줄등록 (@Configration, @Bean)
  • Environment.getActiveProfiles();
  1. Scheduler를 등록하여 주기적으로 최신 회차 업데이트
    : 현재 DB에 등록된 마지막 회차를 요청한 뒤 +1을 해준 번호를 동행복권 api에 요청 후 DB 저장
  • 로또 추첨 시간은 매주 오후 8시 35분경 생방송으로 진행되며 동행복권에 업로드 되는 시간을 9시 20분이내로 가정하여 본 사이트에서는 토요일 오후 9시 30분 스케줄 cron 타입을 지정
 @Scheduled(cron = "0 30 21 * * SAT")//토요일 오후 9시30분
    public void lottoLastCycleFetch() {
    	//1. 현재 등록된 마지막 회차 번호 가져오기
        Long lastCycleNum = lottoApiService.getLastCycleNum();
        //2. 이번주 회차번호로 업데이트 (기존 회차번호 + 1)
        memory.put(LAST_CYCLE_NUM, (lastCycleNum += 1));
        //3. 가져온 회차 api DB 저장 로직
        lottoApiService.insertOne(lastCycleNum);
        System.out.println("lastCycleNum = " + lastCycleNum);
    }

3️⃣ 로그인 / 로그아웃

  • spring scurity

4️⃣ 로또 번호 추출 요청 (Machine)

📌 핵심 📌
: 전체자동, 반자동, 전체수동 (ALLAUTO, SELECTNUM, ALLSELECT)

  • 사용자 입력값 검증
  • 타입(enum)에 따른 랜덤 번호 결과 반환

Endpoint

  • 전체 수동
http://localhost:8080/machine/lottoNum?userNo=2&lottotype=ALLSELECT&price=1000&inputNum=5,12,13,31,32,41&storageCycle=1026
  • 전체 자동
http://localhost:8080/machine/lottoNum?userNo=1&lottotype=ALLAUTO&price=1000&storageCycle=1026
  • 반자동
http://localhost:8080/machine/lottoNum?userNo=2&lottotype=SELECTNUM&price=1000&inputNum=2,3,5,6&storageCycle=1550

사용자에게 타입, 금액 입력을 DTO로 객체로 받으면서 예외 메서드를 통해 검증을 진행한다

👉 사용자 입력값 검증

  • 수동의 경우 null 값이 들어가면 안된다.
  • 금액은 1,000원 단위로 입력 가능.
  • 사용자가 string으로 한꺼번에 입력한 로또 번호 Ball 객체로 변경.
  • 번호 string -> Integer로 변경(나중에 계산을 위해서)
  • 1~45 사이의 숫자만 입력 가능.
  • 전체수동의 경우 1줄당 6개의 번호를 입력해야한다.
  1. 검증된 값 객체를 command 객체로 변경하여 service에 전달한다
  2. 타입에 따라서 랜덤 번호 추출을 요청한다

5️⃣ 로또 티켓 한줄 (6개의 번호)

📌 핵심 📌

  • HashSet<Ball> hashSet
  1. 6개의 번호를 티켓 1장으로 지정하기 때문에 상수로 지정
private static final int LENGTH = 6;
  1. 타입 수동 / 자동은 생성자를 통해 주입받는다
public SixBall() {
    this.list = new ArrayList<>(makeBallSet());
}


public SixBall(List<Integer> inputNumList) {
    this.list = new ArrayList<>(requestUserNum(inputNumList));
}
  1. 메서드는 전부 private로 외부에서 접근할 수 없도록 하였다
  2. HashSet 통해서 6개의 번호를 넣어준다

6️⃣ 로또 번호 (Ball)

📌 핵심 📌

  • new Random().nextInt(MAX) + 1
  • AttributeConverter
  1. 값을 주입하는 생성자는 한개만 열어두고 다른곳에서는 메인 생성자를 호출하는 방식으로 초기화한다
public class Ball {
    private static final int MAX = 45;
    private final Integer value;
    //값 객체

    public Ball() {
        this(new Random().nextInt(MAX) + 1);//메인 생성자 호출
        //1~45
    }

    public Ball(Integer value) {//메인 생성자
        if (value < 1 || value > MAX) {
            throw new IllegalArgumentException("1~45 사이의 숫자만 가능합니다");
        }
        this.value = value;
    }

}
  • converter를 통해서 값 객체를 변환시켜 DB에 저장한다.
public class BallConverter implements AttributeConverter<Ball, Integer> {

    @Override
    //ball을 받아서 integer로 타입을 변환
    public Integer convertToDatabaseColumn(Ball attribute) {
        return attribute.getValue();
    }

    @Override
    public Ball convertToEntityAttribute(Integer dbData) {
        return new Ball(dbData);
    }

}
@Convert(converter = BallConverter.class)
@Column(name = "ball_01")
private Ball ball1;

7️⃣ 당첨자 추첨 및 리스트 출력

1) Schedule 매주 당첨자 선별 로직

  • Schedule을 통해 매주 토요일 당첨자 선별 진행
    • 토요일 오후 동행복권 로또 추첨이 끝난 이후 실행된다.
	@Scheduled(cron = "* * 22 * * SAT")//매주 토요일 당첨자 확인
    public void checkWeekWinner() {
        log.info("당첨자 선별 후 저장하기");
        //1. 마지막 회차 번호 가져오기
        Long lastCycleNum = memory.get(LAST_CYCLE_NUM);
        //2. 당첨자 선별 후 DB 저장 및 당첨자 리스트 가져오기
        List<WinnerCommand> winnerList = winnerService.saveWinner(lastCycleNum);

        log.info("화면에 뿌려줄 winningInfo 저장하기");
        //3. 등위별 당첨금 계산
        WinningResult winningMap = winningAmountService.calcAmount(lastCycleNum);
        //4. 화면에 보낼 당첨자 정보(winningInfo) 만들기
        winningService.makeWinningInfo(winningMap);
    }

2) 사용자 요청 회차별 당첨 정보 페이지

  • 전체 회차 리스트 (select) : defalt => 마지막 회차
  • 요청 회차 당첨자 리스트


8️⃣ 내 번호 당첨 확인

  • 이번주 구입한 티켓 수
  • 이번주 당첨 결과 확인

  1. Controller 에서 @GetMapping("/result/user/{userNo}") 사용자 번호를
    @PathVariable("userNo") Long userNo로 받는다

  2. 해당 사용자 추첨 결과 정보와 현재까지의 구입 정보를 확인할 수 있다.

  • 만약 로그인을 하지 않고 해당 페이지에 접근을 요청할 경우 로그인을 해달라는 메시지와 로그인 페이지를 return해 줍니다.
  1. 당첨자 선별시 당첨자가 없다면 empty를 넘겨준다
    String으로 넘겨준 이유는 숫자형으로 할 경우 실수로 값이 변경될 수 있기 때문이다
if (winningInfoList.size() == 0) {
            gameResultMap.put("gameResult", "resultEmpty");
            return gameResultMap;
        }
  1. 이번주 로또 번호 저장소에서 해당 사용자의 id와 일치하는 추첨 list를 반환 받고,
    count는 list.size()로 확인
public List<MachineCycleStorage> getUserCycleStorage(Long cycleNum, Long userNo) {
        Optional<User> getUser = userJpaRepository.findById(userNo);
        if (getUser == null) {
            throw new NullPointerException("로그인을 해주세요.");
        }

        List<MachineCycleStorage> userCycleStorageList = winnerDbRepository.getUserCycleStorage(cycleNum, getUser.get().getNo());

        return userCycleStorageList;
    }

profile
하루 일지 보단 행동 고찰 과정에 대한 개발 블로그

0개의 댓글

Powered by GraphCDN, the GraphQL CDN