diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index 0aabad65..13b48476 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -14,7 +14,6 @@ env: jobs: build-and-deploy: runs-on: ubuntu-latest - steps: - name: Checkout code uses: actions/checkout@v2 diff --git a/Server/build.gradle b/Server/build.gradle index 33e8c515..68f53cb8 100644 --- a/Server/build.gradle +++ b/Server/build.gradle @@ -46,6 +46,7 @@ dependencies { runtimeOnly 'io.jsonwebtoken:jjwt-impl:0.11.5' runtimeOnly("io.jsonwebtoken:jjwt-jackson:0.11.5") annotationProcessor('org.projectlombok:lombok') + implementation 'com.fasterxml.jackson.datatype:jackson-datatype-jsr310:2.13.3' } diff --git a/Server/src/main/java/JGS/CasperEvent/domain/event/controller/eventController/RushEventController.java b/Server/src/main/java/JGS/CasperEvent/domain/event/controller/eventController/RushEventController.java index 6b3fa926..a0b7790b 100644 --- a/Server/src/main/java/JGS/CasperEvent/domain/event/controller/eventController/RushEventController.java +++ b/Server/src/main/java/JGS/CasperEvent/domain/event/controller/eventController/RushEventController.java @@ -1,8 +1,9 @@ package JGS.CasperEvent.domain.event.controller.eventController; -import JGS.CasperEvent.domain.event.dto.ResponseDto.RushEventListAndServerTimeResponseDto; +import JGS.CasperEvent.domain.event.dto.ResponseDto.RushEventListResponseDto; import JGS.CasperEvent.domain.event.dto.ResponseDto.RushEventRateResponseDto; +import JGS.CasperEvent.domain.event.dto.ResponseDto.RushEventResultResponseDto; import JGS.CasperEvent.domain.event.service.eventService.RushEventService; import JGS.CasperEvent.global.entity.BaseUser; import jakarta.servlet.http.HttpServletRequest; @@ -20,7 +21,7 @@ public RushEventController(RushEventService rushEventService) { // 전체 선착순 이벤트 조회 @GetMapping - public ResponseEntity getRushEventListAndServerTime() { + public ResponseEntity getRushEventListAndServerTime() { return ResponseEntity.ok(rushEventService.getAllRushEvents()); } @@ -47,4 +48,19 @@ public ResponseEntity rushEventRate (@PathVariable("ev RushEventRateResponseDto rushEventRateResponseDto = rushEventService.getRushEventRate(eventId); return ResponseEntity.ok(rushEventRateResponseDto); } + + // 밸런스 게임 결과 조회 + @GetMapping("/{eventId}/result") + public ResponseEntity rushEventResult(HttpServletRequest httpServletRequest, @PathVariable("eventId") Long eventId) { + BaseUser user = (BaseUser) httpServletRequest.getAttribute("user"); + RushEventResultResponseDto result = rushEventService.getRushEventResult(user, eventId); + return ResponseEntity.ok(result); + } + + // 레디스에 오늘의 이벤트 등록 테스트 api + @GetMapping("/today/test") + public ResponseEntity setTodayEvent() { + rushEventService.setTodayEventToRedis(); + return ResponseEntity.noContent().build(); + } } diff --git a/Server/src/main/java/JGS/CasperEvent/domain/event/dto/ResponseDto/LotteryEventResponseDto.java b/Server/src/main/java/JGS/CasperEvent/domain/event/dto/ResponseDto/LotteryEventResponseDto.java index 23e2e591..7690db6a 100644 --- a/Server/src/main/java/JGS/CasperEvent/domain/event/dto/ResponseDto/LotteryEventResponseDto.java +++ b/Server/src/main/java/JGS/CasperEvent/domain/event/dto/ResponseDto/LotteryEventResponseDto.java @@ -5,15 +5,14 @@ import java.time.LocalDateTime; import java.time.temporal.ChronoUnit; -public record LotteryEventResponseDto(LocalDateTime serverDateTime, LocalDateTime eventStartDate, - LocalDateTime eventEndDate, +public record LotteryEventResponseDto(LocalDateTime serverDateTime, LocalDateTime eventStartDate, LocalDateTime eventEndDate, long activePeriod) { public static LotteryEventResponseDto of(LotteryEvent lotteryEvent, LocalDateTime serverDateTime) { return new LotteryEventResponseDto( serverDateTime, - lotteryEvent.getEventStartDate(), - lotteryEvent.getEventEndDate(), - ChronoUnit.DAYS.between(lotteryEvent.getEventStartDate(), lotteryEvent.getEventEndDate()) + lotteryEvent.getStartDateTime(), + lotteryEvent.getEndDateTime(), + ChronoUnit.DAYS.between(lotteryEvent.getStartDateTime(), lotteryEvent.getEndDateTime()) ); } diff --git a/Server/src/main/java/JGS/CasperEvent/domain/event/dto/ResponseDto/MainRushEventResponseDto.java b/Server/src/main/java/JGS/CasperEvent/domain/event/dto/ResponseDto/MainRushEventResponseDto.java new file mode 100644 index 00000000..cfd61949 --- /dev/null +++ b/Server/src/main/java/JGS/CasperEvent/domain/event/dto/ResponseDto/MainRushEventResponseDto.java @@ -0,0 +1,27 @@ +package JGS.CasperEvent.domain.event.dto.ResponseDto; + +import JGS.CasperEvent.domain.event.entity.event.RushEvent; +import lombok.Getter; + +import java.time.LocalDateTime; + +@Getter +public class MainRushEventResponseDto { + private Long rushEventId; + private LocalDateTime startDateTime; + private LocalDateTime endDateTime; + + public MainRushEventResponseDto(Long rushEventId, LocalDateTime startDateTime, LocalDateTime endDateTime) { + this.rushEventId = rushEventId; + this.startDateTime = startDateTime; + this.endDateTime = endDateTime; + } + + public static MainRushEventResponseDto of (RushEvent rushEvent) { + return new MainRushEventResponseDto( + rushEvent.getRushEventId(), + rushEvent.getStartDateTime(), + rushEvent.getEndDateTime() + ); + } +} diff --git a/Server/src/main/java/JGS/CasperEvent/domain/event/dto/ResponseDto/RushEventListAndServerTimeResponseDto.java b/Server/src/main/java/JGS/CasperEvent/domain/event/dto/ResponseDto/RushEventListAndServerTimeResponseDto.java deleted file mode 100644 index cc17a58b..00000000 --- a/Server/src/main/java/JGS/CasperEvent/domain/event/dto/ResponseDto/RushEventListAndServerTimeResponseDto.java +++ /dev/null @@ -1,31 +0,0 @@ -package JGS.CasperEvent.domain.event.dto.ResponseDto; - -import java.time.LocalDateTime; -import java.util.List; - -public class RushEventListAndServerTimeResponseDto { - private List events; - private LocalDateTime serverTime; - - public RushEventListAndServerTimeResponseDto(List events, LocalDateTime serverTime) { - this.events = events; - this.serverTime = serverTime; - } - - // Getters and setters - public List getEvents() { - return events; - } - public void setEvents(List events) { - this.events = events; - } - - public LocalDateTime getServerTime() { - return serverTime; - } - - public void setServerTime(LocalDateTime serverTime) { - this.serverTime = serverTime; - } - -} diff --git a/Server/src/main/java/JGS/CasperEvent/domain/event/dto/ResponseDto/RushEventListResponseDto.java b/Server/src/main/java/JGS/CasperEvent/domain/event/dto/ResponseDto/RushEventListResponseDto.java new file mode 100644 index 00000000..abcac614 --- /dev/null +++ b/Server/src/main/java/JGS/CasperEvent/domain/event/dto/ResponseDto/RushEventListResponseDto.java @@ -0,0 +1,26 @@ +package JGS.CasperEvent.domain.event.dto.ResponseDto; + +import lombok.Getter; + +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.util.List; + +@Getter +public class RushEventListResponseDto { + private List events; + private LocalDateTime serverTime; + private Long todayEventId; + private LocalDate eventStartDate; + private LocalDate eventEndDate; + private Long activePeriod; + + public RushEventListResponseDto(List events, LocalDateTime serverTime, Long todayEventId, LocalDate eventStartDate, LocalDate eventEndDate, Long activePeriod) { + this.events = events; + this.serverTime = serverTime; + this.todayEventId = todayEventId; + this.eventStartDate = eventStartDate; + this.eventEndDate = eventEndDate; + this.activePeriod = activePeriod; + } +} diff --git a/Server/src/main/java/JGS/CasperEvent/domain/event/dto/ResponseDto/RushEventRateResponseDto.java b/Server/src/main/java/JGS/CasperEvent/domain/event/dto/ResponseDto/RushEventRateResponseDto.java index 87700f1e..a4b81c45 100644 --- a/Server/src/main/java/JGS/CasperEvent/domain/event/dto/ResponseDto/RushEventRateResponseDto.java +++ b/Server/src/main/java/JGS/CasperEvent/domain/event/dto/ResponseDto/RushEventRateResponseDto.java @@ -3,12 +3,5 @@ import lombok.Getter; @Getter -public class RushEventRateResponseDto { - long leftOption; - long rightOption; - - public RushEventRateResponseDto(long leftOption, long rightOption) { - this.leftOption = leftOption; - this.rightOption = rightOption; - } +public record RushEventRateResponseDto(long leftOption, long rightOption) { } diff --git a/Server/src/main/java/JGS/CasperEvent/domain/event/dto/ResponseDto/RushEventResponseDto.java b/Server/src/main/java/JGS/CasperEvent/domain/event/dto/ResponseDto/RushEventResponseDto.java index 359896c0..923f30d6 100644 --- a/Server/src/main/java/JGS/CasperEvent/domain/event/dto/ResponseDto/RushEventResponseDto.java +++ b/Server/src/main/java/JGS/CasperEvent/domain/event/dto/ResponseDto/RushEventResponseDto.java @@ -3,22 +3,23 @@ import JGS.CasperEvent.domain.event.entity.event.RushEvent; import JGS.CasperEvent.domain.event.entity.event.RushOption; +import java.time.LocalDate; import java.time.LocalDateTime; +import java.util.Set; -public record RushEventResponseDto(Long rushEventId, LocalDateTime startDate, LocalDateTime endDate, +public record RushEventResponseDto(Long rushEventId, LocalDateTime startDateTime, LocalDateTime endDateTime, int winnerCount, String prizeImageUrl, String prizeDescription, - RushOption leftOption, RushOption rightOption){ + Set options){ public static RushEventResponseDto of (RushEvent rushEvent){ return new RushEventResponseDto( rushEvent.getRushEventId(), - rushEvent.getEventStartDate(), - rushEvent.getEventEndDate(), + rushEvent.getStartDateTime(), + rushEvent.getEndDateTime(), rushEvent.getWinnerCount(), rushEvent.getPrizeImageUrl(), rushEvent.getPrizeDescription(), - rushEvent.getLeftOption(), - rushEvent.getRightOption() + rushEvent.getOptions() ); } diff --git a/Server/src/main/java/JGS/CasperEvent/domain/event/dto/ResponseDto/RushEventResultResponseDto.java b/Server/src/main/java/JGS/CasperEvent/domain/event/dto/ResponseDto/RushEventResultResponseDto.java new file mode 100644 index 00000000..4fee8df8 --- /dev/null +++ b/Server/src/main/java/JGS/CasperEvent/domain/event/dto/ResponseDto/RushEventResultResponseDto.java @@ -0,0 +1,22 @@ +package JGS.CasperEvent.domain.event.dto.ResponseDto; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +@Getter +@AllArgsConstructor +public class RushEventResultResponseDto { + private final long leftOption; + private final long rightOption; + private final long rank; + private final long totalParticipants; + private final long winnerCount; + + public RushEventResultResponseDto(RushEventRateResponseDto rushEventRateResponseDto, long rank, long totalParticipants, long winnerCount) { + this.leftOption = rushEventRateResponseDto.leftOption(); + this.rightOption = rushEventRateResponseDto.rightOption(); + this.rank = rank; + this.totalParticipants = totalParticipants; + this.winnerCount = winnerCount; + } +} diff --git a/Server/src/main/java/JGS/CasperEvent/domain/event/entity/event/BaseEvent.java b/Server/src/main/java/JGS/CasperEvent/domain/event/entity/event/BaseEvent.java index 305c1efe..491c8b9e 100644 --- a/Server/src/main/java/JGS/CasperEvent/domain/event/entity/event/BaseEvent.java +++ b/Server/src/main/java/JGS/CasperEvent/domain/event/entity/event/BaseEvent.java @@ -1,15 +1,38 @@ package JGS.CasperEvent.domain.event.entity.event; import JGS.CasperEvent.global.entity.BaseEntity; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.fasterxml.jackson.databind.annotation.JsonSerialize; +import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateTimeDeserializer; +import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateTimeSerializer; import jakarta.persistence.MappedSuperclass; import lombok.Getter; import java.time.LocalDateTime; -@MappedSuperclass @Getter +@MappedSuperclass public class BaseEvent extends BaseEntity { - protected LocalDateTime eventStartDate; - protected LocalDateTime eventEndDate; - protected int winnerCount; + @JsonSerialize(using = LocalDateTimeSerializer.class) + @JsonDeserialize(using = LocalDateTimeDeserializer.class) + private LocalDateTime startDateTime; + + @JsonSerialize(using = LocalDateTimeSerializer.class) + @JsonDeserialize(using = LocalDateTimeDeserializer.class) + private LocalDateTime endDateTime; + private int winnerCount; + + // 기본 생성자에서 디폴트 값 설정 + public BaseEvent() { + this.startDateTime = LocalDateTime.now(); + this.endDateTime = LocalDateTime.now().plusMinutes(10); + this.winnerCount = 0; // 기본 우승자 수를 0으로 설정 + } + + // 특정 값을 설정할 수 있는 생성자 + public BaseEvent(LocalDateTime startDateTime, LocalDateTime endDateTime, int winnerCount) { + this.startDateTime = startDateTime; + this.endDateTime = endDateTime; + this.winnerCount = winnerCount; + } } diff --git a/Server/src/main/java/JGS/CasperEvent/domain/event/entity/event/RushEvent.java b/Server/src/main/java/JGS/CasperEvent/domain/event/entity/event/RushEvent.java index 38767fbb..07ba336b 100644 --- a/Server/src/main/java/JGS/CasperEvent/domain/event/entity/event/RushEvent.java +++ b/Server/src/main/java/JGS/CasperEvent/domain/event/entity/event/RushEvent.java @@ -2,10 +2,13 @@ import JGS.CasperEvent.domain.event.entity.participants.RushParticipants; import jakarta.persistence.*; +import lombok.Getter; +import java.time.LocalDateTime; import java.util.Set; @Entity +@Getter public class RushEvent extends BaseEvent { private String prizeImageUrl; private String prizeDescription; @@ -14,13 +17,9 @@ public class RushEvent extends BaseEvent { @GeneratedValue(strategy = GenerationType.IDENTITY) private Long rushEventId; - @OneToOne(fetch = FetchType.EAGER, cascade = CascadeType.ALL) + @OneToMany(fetch = FetchType.EAGER, cascade = CascadeType.ALL, orphanRemoval = true) @JoinColumn(name = "rush_event_id") - private RushOption leftOption; - - @OneToOne(fetch = FetchType.EAGER, cascade = CascadeType.ALL) - @JoinColumn(name = "rush_event_id") - private RushOption rightOption; + private Set options; @OneToMany(mappedBy = "rushEvent", cascade = CascadeType.ALL, orphanRemoval = true) private Set rushParticipants; @@ -28,29 +27,16 @@ public class RushEvent extends BaseEvent { public RushEvent() { } - // 파라미터가 있는 생성자 public RushEvent(String prizeImageUrl, String prizeDescription) { + super(); this.prizeImageUrl = prizeImageUrl; this.prizeDescription = prizeDescription; } - public String getPrizeImageUrl() { - return prizeImageUrl; - } - - public String getPrizeDescription() { - return prizeDescription; - } - public RushOption getLeftOption() { - return leftOption; - } - - public RushOption getRightOption() { - return rightOption; - } - - public Long getRushEventId() { - return rushEventId; + public RushEvent(LocalDateTime startDateTime, LocalDateTime endDateTime, int winnerCount, String prizeImageUrl, String prizeDescription) { + super(startDateTime, endDateTime, winnerCount); + this.prizeImageUrl = prizeImageUrl; + this.prizeDescription = prizeDescription; } } diff --git a/Server/src/main/java/JGS/CasperEvent/domain/event/entity/event/RushOption.java b/Server/src/main/java/JGS/CasperEvent/domain/event/entity/event/RushOption.java index 4e771731..7d0726ba 100644 --- a/Server/src/main/java/JGS/CasperEvent/domain/event/entity/event/RushOption.java +++ b/Server/src/main/java/JGS/CasperEvent/domain/event/entity/event/RushOption.java @@ -2,19 +2,30 @@ import JGS.CasperEvent.global.entity.BaseEntity; import jakarta.persistence.*; +import lombok.NoArgsConstructor; @Entity -public class RushOption extends BaseEntity{ +@NoArgsConstructor +public class RushOption extends BaseEntity { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long optionId; - @OneToOne + @ManyToOne @JoinColumn(name = "rush_event_id") private RushEvent rushEvent; - - @Id - private int optionId; private String mainText; private String subText; private String resultMainText; private String resultSubText; private String imageUrl; + + public RushOption(RushEvent rushEvent, String mainText, String subText, String resultMainText, String resultSubText, String imageUrl) { + this.rushEvent = rushEvent; + this.mainText = mainText; + this.subText = subText; + this.resultMainText = resultMainText; + this.resultSubText = resultSubText; + this.imageUrl = imageUrl; + } } diff --git a/Server/src/main/java/JGS/CasperEvent/domain/event/entity/participants/RushParticipants.java b/Server/src/main/java/JGS/CasperEvent/domain/event/entity/participants/RushParticipants.java index f160d1ff..d3d5b213 100644 --- a/Server/src/main/java/JGS/CasperEvent/domain/event/entity/participants/RushParticipants.java +++ b/Server/src/main/java/JGS/CasperEvent/domain/event/entity/participants/RushParticipants.java @@ -2,6 +2,7 @@ import JGS.CasperEvent.domain.event.entity.event.RushEvent; import JGS.CasperEvent.global.entity.BaseUser; +import com.fasterxml.jackson.annotation.JsonBackReference; import jakarta.persistence.*; @Entity @@ -12,6 +13,7 @@ public class RushParticipants { private int optionId; @OneToOne @JoinColumn(name = "base_user_id") + @JsonBackReference private BaseUser baseUser; @ManyToOne diff --git a/Server/src/main/java/JGS/CasperEvent/domain/event/repository/eventRepository/RushEventRepository.java b/Server/src/main/java/JGS/CasperEvent/domain/event/repository/eventRepository/RushEventRepository.java index 3ff0c837..1913977f 100644 --- a/Server/src/main/java/JGS/CasperEvent/domain/event/repository/eventRepository/RushEventRepository.java +++ b/Server/src/main/java/JGS/CasperEvent/domain/event/repository/eventRepository/RushEventRepository.java @@ -2,8 +2,17 @@ import JGS.CasperEvent.domain.event.entity.event.RushEvent; import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; import org.springframework.stereotype.Repository; +import java.time.LocalDate; +import java.util.List; + @Repository public interface RushEventRepository extends JpaRepository { + @Query("SELECT e FROM RushEvent e WHERE DATE(e.startDateTime) = :eventDate") + List findByEventDate(@Param("eventDate") LocalDate eventDate); + + RushEvent findByRushEventId(Long rushEventId); } diff --git a/Server/src/main/java/JGS/CasperEvent/domain/event/repository/eventRepository/RushOptionRepository.java b/Server/src/main/java/JGS/CasperEvent/domain/event/repository/eventRepository/RushOptionRepository.java index e4e65d12..96130c15 100644 --- a/Server/src/main/java/JGS/CasperEvent/domain/event/repository/eventRepository/RushOptionRepository.java +++ b/Server/src/main/java/JGS/CasperEvent/domain/event/repository/eventRepository/RushOptionRepository.java @@ -1,7 +1,9 @@ package JGS.CasperEvent.domain.event.repository.eventRepository; +import JGS.CasperEvent.domain.event.entity.event.RushOption; +import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.stereotype.Repository; @Repository -public interface RushOptionRepository { +public interface RushOptionRepository extends JpaRepository { } diff --git a/Server/src/main/java/JGS/CasperEvent/domain/event/repository/participantsRepository/RushParticipantsRepository.java b/Server/src/main/java/JGS/CasperEvent/domain/event/repository/participantsRepository/RushParticipantsRepository.java index d8b0daea..f53b74f7 100644 --- a/Server/src/main/java/JGS/CasperEvent/domain/event/repository/participantsRepository/RushParticipantsRepository.java +++ b/Server/src/main/java/JGS/CasperEvent/domain/event/repository/participantsRepository/RushParticipantsRepository.java @@ -6,14 +6,23 @@ import org.springframework.data.repository.query.Param; import org.springframework.stereotype.Repository; +import java.util.Optional; + @Repository public interface RushParticipantsRepository extends JpaRepository { - @Query("SELECT CASE WHEN COUNT(rp) > 0 THEN TRUE ELSE FALSE END " + - "FROM RushParticipants rp " + - "WHERE rp.rushEvent.rushEventId = :eventId AND rp.baseUser.id = :userId") - boolean existsByRushEventIdAndUserId(@Param("eventId") Long eventId, @Param("userId") String userId); + boolean existsByRushEvent_RushEventIdAndBaseUser_Id(Long eventId, String userId); + long countByRushEvent_RushEventIdAndOptionId(Long eventId, int optionId); + @Query("SELECT COUNT(rp) + 1 FROM RushParticipants rp " + + "WHERE rp.rushEvent.rushEventId = :eventId " + + "AND rp.optionId = :optionId " + + "AND rp.id < (SELECT rp2.id FROM RushParticipants rp2 " + + "WHERE rp2.rushEvent.rushEventId = :eventId " + + "AND rp2.baseUser.id = :userId)") + long findUserRankByEventIdAndUserIdAndOptionId(@Param("eventId") Long eventId, + @Param("userId") String userId, + @Param("optionId") int optionId); + long countAllByOptionId(int optionId); + @Query("SELECT rp.optionId FROM RushParticipants rp WHERE rp.baseUser.id = :userId") + Optional getOptionIdByUserId(@Param("userId") String userId); - @Query("SELECT COUNT(rp) FROM RushParticipants rp " + - "WHERE rp.rushEvent.rushEventId = :eventId AND rp.optionId = :optionId") - long countByRushEventIdAndOptionId(@Param("eventId") Long eventId, @Param("optionId") int optionId); } diff --git a/Server/src/main/java/JGS/CasperEvent/domain/event/service/eventService/LotteryEventService.java b/Server/src/main/java/JGS/CasperEvent/domain/event/service/eventService/LotteryEventService.java index a5be1e61..3051249d 100644 --- a/Server/src/main/java/JGS/CasperEvent/domain/event/service/eventService/LotteryEventService.java +++ b/Server/src/main/java/JGS/CasperEvent/domain/event/service/eventService/LotteryEventService.java @@ -90,8 +90,8 @@ public LotteryParticipants registerUserIfNeed(BaseUser user) { // TODO: 가짜 API, DB 접속되도록 수정 public LotteryEventResponseDto getLotteryEvent() { return new LotteryEventResponseDto(LocalDateTime.now(), - LocalDateTime.of(2000, 9, 27, 0, 0), - LocalDateTime.of(2100, 9, 27, 0, 0), + LocalDate.of(2000, 9, 27).atStartOfDay(), + LocalDate.of(2100, 9, 27).atStartOfDay(), ChronoUnit.DAYS.between(LocalDate.of(2000, 9, 27), LocalDate.of(2100, 9, 27))); } diff --git a/Server/src/main/java/JGS/CasperEvent/domain/event/service/eventService/RushEventScheduler.java b/Server/src/main/java/JGS/CasperEvent/domain/event/service/eventService/RushEventScheduler.java new file mode 100644 index 00000000..9d91e79c --- /dev/null +++ b/Server/src/main/java/JGS/CasperEvent/domain/event/service/eventService/RushEventScheduler.java @@ -0,0 +1,33 @@ +package JGS.CasperEvent.domain.event.service.eventService; + +import JGS.CasperEvent.domain.event.entity.event.RushEvent; +import lombok.RequiredArgsConstructor; +import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.scheduling.annotation.EnableScheduling; +import org.springframework.scheduling.annotation.Scheduled; +import org.springframework.stereotype.Service; + +import java.time.LocalDate; + +@Service +@EnableScheduling +@RequiredArgsConstructor +public class RushEventScheduler { + + private final RushEventService rushEventService; + private final RedisTemplate rushEventRedisTemplate; + + // 매일 12시에 스케줄된 작업을 실행합니다. + @Scheduled(cron = "0 0 12 * * ?") + public void fetchDailyEvents() { + // 오늘의 날짜를 구합니다. + LocalDate today = LocalDate.now(); + + // EventService를 통해 오늘의 이벤트를 가져옵니다. + RushEvent todayEvent = rushEventService.getTodayRushEvent(today); + + // 가져온 이벤트에 대한 추가 작업을 수행합니다. + // 예: 캐싱, 로그 기록, 알림 발송 등 + rushEventRedisTemplate.opsForValue().set("todayEvent", todayEvent); + } +} diff --git a/Server/src/main/java/JGS/CasperEvent/domain/event/service/eventService/RushEventService.java b/Server/src/main/java/JGS/CasperEvent/domain/event/service/eventService/RushEventService.java index 35afbd36..26fb59b8 100644 --- a/Server/src/main/java/JGS/CasperEvent/domain/event/service/eventService/RushEventService.java +++ b/Server/src/main/java/JGS/CasperEvent/domain/event/service/eventService/RushEventService.java @@ -1,48 +1,82 @@ package JGS.CasperEvent.domain.event.service.eventService; -import JGS.CasperEvent.domain.event.dto.ResponseDto.RushEventResponseDto; -import JGS.CasperEvent.domain.event.dto.ResponseDto.RushEventListAndServerTimeResponseDto; -import JGS.CasperEvent.domain.event.dto.ResponseDto.RushEventRateResponseDto; +import JGS.CasperEvent.domain.event.dto.ResponseDto.*; import JGS.CasperEvent.domain.event.entity.event.RushEvent; +import JGS.CasperEvent.domain.event.entity.event.RushOption; import JGS.CasperEvent.domain.event.entity.participants.RushParticipants; import JGS.CasperEvent.domain.event.repository.eventRepository.RushEventRepository; +import JGS.CasperEvent.domain.event.repository.eventRepository.RushOptionRepository; import JGS.CasperEvent.domain.event.repository.participantsRepository.RushParticipantsRepository; import JGS.CasperEvent.global.entity.BaseUser; import JGS.CasperEvent.global.enums.CustomErrorCode; import JGS.CasperEvent.global.error.exception.CustomException; import JGS.CasperEvent.global.util.RepositoryErrorHandler; +import lombok.RequiredArgsConstructor; +import org.springframework.data.redis.core.RedisTemplate; import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import java.time.LocalDate; import java.time.LocalDateTime; +import java.util.ArrayList; import java.util.List; +import java.util.Optional; @Service +@RequiredArgsConstructor public class RushEventService { private final RushEventRepository rushEventRepository; private final RushParticipantsRepository rushParticipantsRepository; + private final RedisTemplate rushEventRedisTemplate; + private final RushOptionRepository rushOptionRepository; - public RushEventService(RushEventRepository rushEventRepository, RushParticipantsRepository rushParticipantsRepository) { - this.rushEventRepository = rushEventRepository; - this.rushParticipantsRepository = rushParticipantsRepository; - } + @Transactional + public RushEventListResponseDto getAllRushEvents() { + // 오늘의 선착순 이벤트 꺼내오기 + RushEvent todayEvent = rushEventRedisTemplate.opsForValue().get("todayEvent"); + + // 오늘의 선착순 이벤트가 redis에 등록되지 않은 경우 + if (todayEvent == null) { + throw new CustomException("오늘의 선착순 이벤트가 redis에 등록되지 않았습니다.", CustomErrorCode.TODAY_RUSH_EVENT_NOT_FOUND); + } - public RushEventListAndServerTimeResponseDto getAllRushEvents() { // DB에서 모든 RushEvent 가져오기 List rushEventList = rushEventRepository.findAll(); + + // 선착순 이벤트 전체 시작 날짜와 종료 날짜 구하기 + List dates = rushEventList.stream().map(rushEvent -> rushEvent.getStartDateTime().toLocalDate()).sorted().toList(); + + LocalDate totalStartDate = dates.get(0); + LocalDate totalEndDate = dates.get(dates.size() - 1); + + // 전체 이벤트 기간 구하기 + long activePeriod = totalStartDate.until(totalEndDate).getDays() + 1; + // RushEvent를 DTO로 전환 - List rushEventDtoList = rushEventList.stream() - .map(RushEventResponseDto::of) + List mainRushEventDtoList = rushEventList.stream() + .map(MainRushEventResponseDto::of) .toList(); + // DTO 리스트와 서버 시간을 담은 RushEventListAndServerTimeResponse 객체 생성 후 반환 - return new RushEventListAndServerTimeResponseDto(rushEventDtoList, LocalDateTime.now()); + return new RushEventListResponseDto( + mainRushEventDtoList, + LocalDateTime.now(), + todayEvent.getRushEventId(), + totalStartDate, + totalEndDate, + activePeriod + ); } + // 응모 여부 조회 public boolean isExists(Long eventId, String userId) { - return rushParticipantsRepository.existsByRushEventIdAndUserId(eventId, userId); + return rushParticipantsRepository.existsByRushEvent_RushEventIdAndBaseUser_Id(eventId, userId); } + @Transactional public void apply(BaseUser user, Long eventId, int optionId) { - if (isExists(eventId, user.getId())) { + // 이미 응모한 회원인지 검증 + if (rushParticipantsRepository.existsByRushEvent_RushEventIdAndBaseUser_Id(eventId, user.getId())) { throw new CustomException("이미 응모한 회원입니다.", CustomErrorCode.CONFLICT); } @@ -55,9 +89,106 @@ public void apply(BaseUser user, Long eventId, int optionId) { } public RushEventRateResponseDto getRushEventRate(Long eventId) { - long leftOptionCount = rushParticipantsRepository.countByRushEventIdAndOptionId(eventId, 1); - long rightOptionCount = rushParticipantsRepository.countByRushEventIdAndOptionId(eventId, 2); + long leftOptionCount = rushParticipantsRepository.countByRushEvent_RushEventIdAndOptionId(eventId, 1); + long rightOptionCount = rushParticipantsRepository.countByRushEvent_RushEventIdAndOptionId(eventId, 2); return new RushEventRateResponseDto(leftOptionCount, rightOptionCount); } + + // 이벤트 결과를 반환 + // 해당 요청은 무조건 응모한 유저일 때만 요청 가능하다고 가정 + @Transactional + public RushEventResultResponseDto getRushEventResult(BaseUser user, Long eventId) { + // 최종 선택 비율을 조회 + // TODO: 레디스에 캐시 + RushEventRateResponseDto rushEventRateResponseDto = getRushEventRate(eventId); + + // 해당 이벤트의 당첨자 수를 가져옴 + int winnerCount = rushEventRepository.findByRushEventId(eventId).getWinnerCount(); + + // 해당 유저가 응모한 optionId 가져옴 + Optional optionId = rushParticipantsRepository.getOptionIdByUserId(user.getId()); + + if (optionId.isEmpty()) { + throw new CustomException("해당 유저가 이벤트 응모를 하지 않았습니다.", CustomErrorCode.USER_NOT_FOUND); + } + + // eventId, userId, optionId 를 이용하여 해당 유저가 응모한 선택지에서 등수를 가져옴 + long rank = rushParticipantsRepository.findUserRankByEventIdAndUserIdAndOptionId(eventId, user.getId(), optionId.get()); + + // 해당 선택지를 선택한 모든 유저 수를 가져옴 + long totalParticipants = rushParticipantsRepository.countAllByOptionId(optionId.get()); + + return new RushEventResultResponseDto(rushEventRateResponseDto.getLeftOption(), rushEventRateResponseDto.rightOption(), rank, totalParticipants, winnerCount); + } + + @Transactional + // 오늘의 이벤트를 DB에 꺼내서 반환 + public RushEvent getTodayRushEvent(LocalDate today) { + // 오늘 날짜에 해당하는 모든 이벤트 꺼내옴 + List rushEventList = rushEventRepository.findByEventDate(today); + + if (rushEventList.isEmpty()) { + throw new CustomException("선착순 이벤트가 존재하지않습니다.", CustomErrorCode.NO_RUSH_EVENT); + } + + if (rushEventList.size() > 1) { + throw new CustomException("선착순 이벤트가 존재하지않습니다.", CustomErrorCode.MULTIPLE_RUSH_EVENTS_FOUND); + } + + return rushEventList.get(0); + } + + @Transactional + public void setTodayEventToRedis() { + // 테이블 초기화 + rushParticipantsRepository.deleteAllInBatch(); + rushOptionRepository.deleteAllInBatch(); + rushEventRepository.deleteAllInBatch(); + + LocalDateTime startDateTime = LocalDateTime.of(2024, 8, 11, 22, 0); + LocalDateTime endDateTime = startDateTime.plusMinutes(10); + + List rushEvents = new ArrayList<>(); + + for (int i = 0; i < 6; i++) { + // RushEvent 생성 및 초기화 + RushEvent rushEvent = new RushEvent( + startDateTime.plusDays(i), // 이벤트 시작 날짜 + endDateTime.plusDays(i), // 이벤트 종료 날짜 + 0, // 우승자 수 (winnerCount) + "http://example.com/prize" + (i + 1) + ".jpg", // 상 이미지 URL + "Prize Description " + (i + 1) // 상 설명 + ); + + // RushEvent 저장 + rushEvent = rushEventRepository.save(rushEvent); + rushEvents.add(rushEvent); + + // 첫 번째 RushOption 생성 및 저장 + RushOption option1 = new RushOption( + rushEvent, + "Option 1 Main Text for Event " + (i + 1), + "Option 1 Sub Text for Event " + (i + 1), + "Option 1 Result Main Text for Event " + (i + 1), + "Option 1 Result Sub Text for Event " + (i + 1), + "http://example.com/option1-image" + (i + 1) + ".jpg" + ); + rushOptionRepository.save(option1); + + // 두 번째 RushOption 생성 및 저장 + RushOption option2 = new RushOption( + rushEvent, + "Option 2 Main Text for Event " + (i + 1), + "Option 2 Sub Text for Event " + (i + 1), + "Option 2 Result Main Text for Event " + (i + 1), + "Option 2 Result Sub Text for Event " + (i + 1), + "http://example.com/option2-image" + (i + 1) + ".jpg" + ); + rushOptionRepository.save(option2); + } + + // 처음으로 생성된 RushEvent를 Redis에 저장 + rushEventRedisTemplate.opsForValue().set("todayEvent", rushEvents.get(0)); + } } diff --git a/Server/src/main/java/JGS/CasperEvent/global/config/RedisConfig.java b/Server/src/main/java/JGS/CasperEvent/global/config/RedisConfig.java index 355ac87a..f397261c 100644 --- a/Server/src/main/java/JGS/CasperEvent/global/config/RedisConfig.java +++ b/Server/src/main/java/JGS/CasperEvent/global/config/RedisConfig.java @@ -1,6 +1,7 @@ package JGS.CasperEvent.global.config; import JGS.CasperEvent.domain.event.dto.ResponseDto.CasperBotResponseDto; +import JGS.CasperEvent.domain.event.entity.event.RushEvent; import org.springframework.beans.factory.annotation.Value; import org.springframework.cache.annotation.EnableCaching; import org.springframework.context.annotation.Bean; @@ -29,7 +30,7 @@ public RedisConnectionFactory redisConnectionFactory(){ } @Bean - public RedisTemplate redisTemplate(){ + public RedisTemplate CasperBotRedisTemplate(){ RedisTemplate redisTemplate = new RedisTemplate<>(); redisTemplate.setConnectionFactory(redisConnectionFactory()); redisTemplate.setKeySerializer(new StringRedisSerializer()); @@ -37,4 +38,12 @@ public RedisTemplate redisTemplate(){ return redisTemplate; } + @Bean + public RedisTemplate RushEventRedisTemplate(){ + RedisTemplate redisTemplate = new RedisTemplate<>(); + redisTemplate.setConnectionFactory(redisConnectionFactory()); + redisTemplate.setKeySerializer(new StringRedisSerializer()); + redisTemplate.setValueSerializer(new GenericJackson2JsonRedisSerializer()); + return redisTemplate; + } } diff --git a/Server/src/main/java/JGS/CasperEvent/global/entity/BaseEntity.java b/Server/src/main/java/JGS/CasperEvent/global/entity/BaseEntity.java index 1f65da20..6a41fb05 100644 --- a/Server/src/main/java/JGS/CasperEvent/global/entity/BaseEntity.java +++ b/Server/src/main/java/JGS/CasperEvent/global/entity/BaseEntity.java @@ -1,8 +1,13 @@ package JGS.CasperEvent.global.entity; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.fasterxml.jackson.databind.annotation.JsonSerialize; +import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateTimeDeserializer; +import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateTimeSerializer; import jakarta.persistence.Column; import jakarta.persistence.EntityListeners; import jakarta.persistence.MappedSuperclass; +import lombok.Getter; import org.springframework.data.annotation.CreatedDate; import org.springframework.data.annotation.LastModifiedDate; import org.springframework.data.jpa.domain.support.AuditingEntityListener; @@ -12,20 +17,16 @@ @EntityListeners(AuditingEntityListener.class) @MappedSuperclass +@Getter public class BaseEntity { - @CreatedDate + @JsonSerialize(using = LocalDateTimeSerializer.class) + @JsonDeserialize(using = LocalDateTimeDeserializer.class) @Column(updatable = false) private LocalDateTime createdAt; @LastModifiedDate + @JsonSerialize(using = LocalDateTimeSerializer.class) + @JsonDeserialize(using = LocalDateTimeDeserializer.class) private LocalDateTime updatedAt; - - public LocalDateTime getCreatedAt() { - return createdAt; - } - - public LocalDateTime getUpdatedAt() { - return updatedAt; - } } diff --git a/Server/src/main/java/JGS/CasperEvent/global/entity/BaseUser.java b/Server/src/main/java/JGS/CasperEvent/global/entity/BaseUser.java index ee143b23..09f06708 100644 --- a/Server/src/main/java/JGS/CasperEvent/global/entity/BaseUser.java +++ b/Server/src/main/java/JGS/CasperEvent/global/entity/BaseUser.java @@ -1,6 +1,7 @@ package JGS.CasperEvent.global.entity; import JGS.CasperEvent.domain.event.entity.participants.LotteryParticipants; +import JGS.CasperEvent.domain.event.entity.participants.RushParticipants; import JGS.CasperEvent.global.enums.Role; import com.fasterxml.jackson.annotation.JsonManagedReference; import jakarta.persistence.*; @@ -18,6 +19,10 @@ public class BaseUser extends BaseEntity { @OneToOne(mappedBy = "baseUser", cascade = CascadeType.ALL) private LotteryParticipants lotteryParticipants; + @JsonManagedReference + @OneToOne(mappedBy = "baseUser", cascade = CascadeType.ALL) + private RushParticipants rushParticipants; + public void updateLotteryParticipants(LotteryParticipants lotteryParticipant) { this.lotteryParticipants = lotteryParticipant; } diff --git a/Server/src/main/java/JGS/CasperEvent/global/enums/CustomErrorCode.java b/Server/src/main/java/JGS/CasperEvent/global/enums/CustomErrorCode.java index ae109d03..408ef36a 100644 --- a/Server/src/main/java/JGS/CasperEvent/global/enums/CustomErrorCode.java +++ b/Server/src/main/java/JGS/CasperEvent/global/enums/CustomErrorCode.java @@ -14,9 +14,9 @@ public enum CustomErrorCode { JWT_PARSE_EXCEPTION("Json 파싱 오류입니다.", 400), JWT_EXCEPTION("JWT 오류입니다.", 400), JWT_EXPIRED("만료된 토큰입니다.", 400), - JWT_MISSING("인증 토큰이 존재하지 않습니다.", 401); - - + JWT_MISSING("인증 토큰이 존재하지 않습니다.", 401), + MULTIPLE_RUSH_EVENTS_FOUND("해당 날짜에 여러 개의 이벤트가 존재합니다.", 409), + TODAY_RUSH_EVENT_NOT_FOUND("오늘의 이벤트를 찾을 수 없습니다.", 404); // 새로운 예외 추가 private final String message; private int status;