@startuml participation-당첨자추첨 !theme mono title Participation Service - 당첨자 추첨 내부 시퀀스 actor "사장님" as Owner participant "API Gateway" as Gateway participant "ParticipationController" as Controller participant "ParticipationService" as Service participant "LotteryAlgorithm" as Lottery participant "ParticipantRepository" as Repo participant "DrawLogRepository" as LogRepo database "Participation DB" as DB participant "KafkaProducer" as Kafka == UFR-PART-030: 당첨자 추첨 == Owner -> Gateway: POST /api/v1/events/{eventId}/draw-winners\n{winnerCount, visitBonus, algorithm} activate Gateway Gateway -> Gateway: JWT 토큰 검증\n- 토큰 유효성 확인\n- 사장님 권한 확인 alt JWT 검증 실패 Gateway --> Owner: 401 Unauthorized deactivate Gateway else JWT 검증 성공 Gateway -> Controller: POST /participations/draw-winners\n{eventId, winnerCount, visitBonus} activate Controller Controller -> Controller: 요청 데이터 유효성 검증\n- eventId 필수\n- winnerCount > 0\n- winnerCount <= 참여자 수 alt 유효성 검증 실패 Controller --> Gateway: 400 Bad Request Gateway --> Owner: 400 Bad Request deactivate Controller deactivate Gateway else 유효성 검증 성공 Controller -> Service: drawWinners(eventId, winnerCount, visitBonus) activate Service Service -> Service: 이벤트 상태 확인\n- 이벤트 종료 여부\n- 이미 추첨 완료 여부 Service -> LogRepo: findByEventId(eventId) activate LogRepo LogRepo -> DB: SELECT * FROM draw_logs\nWHERE event_id = ? activate DB DB --> LogRepo: 추첨 로그 조회 deactivate DB LogRepo --> Service: Optional deactivate LogRepo alt 이미 추첨 완료 Service --> Controller: AlreadyDrawnException Controller --> Gateway: 409 Conflict\n{message: "이미 추첨이 완료된 이벤트입니다"} Gateway --> Owner: 409 Conflict deactivate Service deactivate Controller deactivate Gateway else 추첨 가능 상태 Service -> Repo: findAllByEventIdAndIsWinner(eventId, false) activate Repo Repo -> DB: SELECT * FROM participants\nWHERE event_id = ?\nAND is_winner = false\nORDER BY participated_at ASC activate DB DB --> Repo: 전체 참여자 목록 deactivate DB Repo --> Service: List deactivate Repo alt 참여자 수 부족 Service --> Controller: InsufficientParticipantsException Controller --> Gateway: 400 Bad Request\n{message: "참여자 수가 부족합니다"} Gateway --> Owner: 400 Bad Request deactivate Service deactivate Controller deactivate Gateway else 추첨 진행 Service -> Lottery: executeLottery(participants, winnerCount, visitBonus) activate Lottery note right of Lottery 추첨 알고리즘: 시간 복잡도: O(n log n) 공간 복잡도: O(n) 1. 난수 생성 (Crypto.randomBytes) 2. 매장 방문 가산점 적용 (옵션) - 방문 고객: 가중치 2배 - 비방문 고객: 가중치 1배 3. Fisher-Yates Shuffle - 가중치 기반 확률 분포 - 무작위 섞기 4. 상위 N명 선정 end note Lottery -> Lottery: Step 1: 난수 시드 생성\n- Crypto.randomBytes(32)\n- 예측 불가능한 난수 보장 Lottery -> Lottery: Step 2: 가산점 적용\n- visitBonus = true일 경우\n- 매장 방문 경로 참여자 가중치 증가 Lottery -> Lottery: Step 3: Fisher-Yates Shuffle\n- 가중치 기반 확률 분포\n- O(n) 시간 복잡도 Lottery -> Lottery: Step 4: 당첨자 선정\n- 상위 winnerCount명 추출 Lottery --> Service: List 당첨자 목록 deactivate Lottery Service -> Service: DB 트랜잭션 시작 alt DB 저장 실패 시 note right of Service 트랜잭션 롤백 처리: - 당첨자 업데이트 취소 - 추첨 로그 저장 취소 - 재시도 가능 상태 유지 end note end Service -> Repo: updateWinners(winnerIds) activate Repo Repo -> DB: UPDATE participants\nSET is_winner = true,\nwon_at = NOW()\nWHERE participant_id IN (?) activate DB DB --> Repo: 업데이트 완료 deactivate DB Repo --> Service: 업데이트 건수 deactivate Repo Service -> Service: DrawLog 엔티티 생성\n- drawLogId (UUID)\n- eventId\n- drawMethod: "RANDOM"\n- algorithm: "FISHER_YATES_SHUFFLE"\n- visitBonusApplied\n- winnerCount\n- drawnAt (현재시각) Service -> LogRepo: save(drawLog) activate LogRepo LogRepo -> DB: INSERT INTO draw_logs\n(draw_log_id, event_id, draw_method,\nalgorithm, visit_bonus_applied,\nwinner_count, drawn_at) activate DB note right of DB 추첨 로그 저장: - 추첨 일시 기록 - 알고리즘 버전 기록 - 가산점 적용 여부 - 감사 추적 목적 end note DB --> LogRepo: 로그 저장 완료 deactivate DB LogRepo --> Service: DrawLog 엔티티 deactivate LogRepo Service -> Service: DB 트랜잭션 커밋 Service -> Kafka: Publish Event\n"WinnerSelected"\nTopic: participant-events activate Kafka note right of Kafka Event Payload: { "eventId": "UUID", "winners": [ { "participantId": "UUID", "name": "홍길동", "phone": "010-1234-5678" } ], "timestamp": "2025-10-22T15:00:00Z" } end note Kafka --> Service: 이벤트 발행 완료 deactivate Kafka Service --> Controller: DrawWinnersResponse\n{당첨자목록, 추첨로그ID} deactivate Service Controller --> Gateway: 200 OK\n{winners[], drawLogId, message} deactivate Controller Gateway --> Owner: 200 OK deactivate Gateway Owner -> Owner: 당첨자 목록 화면 표시\n- 당첨자 정보 테이블\n- 재추첨 버튼\n- 추첨 완료 메시지 end end end end @enduml