Skip to content
4 changes: 4 additions & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,10 @@ dependencies {

}

configurations.all {
exclude group: 'commons-logging', module: 'commons-logging'
}

tasks.named('test') {
useJUnitPlatform()
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,19 +7,30 @@
import LuckyVicky.backend.enhance.repository.EnhanceItemRepository;
import LuckyVicky.backend.global.api_payload.ErrorCode;
import LuckyVicky.backend.global.exception.GeneralException;
import LuckyVicky.backend.global.fcm.converter.FcmConverter;
import LuckyVicky.backend.global.fcm.domain.UserDeviceToken;
import LuckyVicky.backend.global.fcm.dto.FcmRequestDto.FcmSimpleReqDto;
import LuckyVicky.backend.global.fcm.service.FcmService;
import LuckyVicky.backend.global.util.Constant;
import LuckyVicky.backend.item.domain.Item;
import LuckyVicky.backend.item.service.ItemService;
import LuckyVicky.backend.user.domain.User;
import LuckyVicky.backend.user.dto.UserJewelResponseDto.UserJewelResDto;
import jakarta.transaction.Transactional;
import java.io.IOException;
import java.time.LocalDate;
import java.util.List;
import lombok.RequiredArgsConstructor;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Service;

@Service
@RequiredArgsConstructor
public class EnhanceItemService {
private static final int DEFAULT_ENHANCE_LEVEL = 1;
private final EnhanceItemRepository enhanceItemRepository;
private final ItemService itemService;
private final FcmService fcmService;

public EnhanceItem findByUserAndItem(User user, Item item) {
return enhanceItemRepository.findByUserAndItem(user, item)
Expand Down Expand Up @@ -65,4 +76,37 @@ public ItemEnhanceResDto getItemEnhanceResDto(User user, List<Item> itemList) {

return EnhanceConverter.itemEnhanceResDto(itemForEnhanceResDtoList, userJewelResDtoList);
}

@Scheduled(cron = "0 0 21 ? * SUN", zone = "Asia/Seoul")
public void currentWeekAward() throws IOException {
executeCurrentWeekAward();
}

private void executeCurrentWeekAward() throws IOException {
List<Item> currentWeekItemList = itemService.getWeekItemList(LocalDate.now());

for (Item item : currentWeekItemList) {
List<EnhanceItem> enhanceItemList = enhanceItemRepository.findEnhanceItemsByItemOrderByEnhanceLevelAndReachedTime(
item);

for (EnhanceItem enhanceItem : enhanceItemList) {
if (enhanceItem.getIsGet()) {
User user = enhanceItem.getUser();

List<UserDeviceToken> userDeviceTokenList = user.getDeviceTokenList();
for (UserDeviceToken userDeviceToken : userDeviceTokenList) {
String deviceToken = userDeviceToken.getDeviceToken();
FcmSimpleReqDto fcmSimpleReqDto = FcmConverter.toFcmSimpleReqDto(deviceToken,
Constant.FCM_TITLE_CURRENT_WEEK_AWARD, createFcmBody(user, item));
fcmService.sendMessageTo(fcmSimpleReqDto);
}
}
}
}
}

private String createFcmBody(User user, Item item) {
return user.getNickname() + "님이 " + item.getName() + "을(를) 획득하셨습니다.";
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package LuckyVicky.backend.global.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.TaskScheduler;
import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler;

@Configuration
public class SchedulerConfig {

@Bean
public TaskScheduler taskScheduler() {
return new ThreadPoolTaskScheduler();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

import LuckyVicky.backend.global.api_payload.ApiResponse;
import LuckyVicky.backend.global.api_payload.SuccessCode;
import LuckyVicky.backend.global.fcm.dto.FcmRequestDto;
import LuckyVicky.backend.global.fcm.dto.FcmRequestDto.FcmSimpleReqDto;
import LuckyVicky.backend.global.fcm.service.FcmService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.responses.ApiResponses;
Expand All @@ -26,7 +26,7 @@ public class FcmController {
@io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "FCM_2001", description = "fcm 알림 전송 성공"),
})
@PostMapping("/pushMessage")
public ApiResponse<String> pushMessage(@RequestBody FcmRequestDto.FcmSimpleReqDto requestDTO) throws IOException {
public ApiResponse<String> pushMessage(@RequestBody FcmSimpleReqDto requestDTO) throws IOException {
System.out.println(requestDTO.getDeviceToken() + " "
+ requestDTO.getTitle() + " " + requestDTO.getBody());
fcmService.sendMessageTo(requestDTO);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
import LuckyVicky.backend.global.exception.GeneralException;
import LuckyVicky.backend.global.fcm.converter.FcmConverter;
import LuckyVicky.backend.global.fcm.dto.FcmMessage;
import LuckyVicky.backend.global.fcm.dto.FcmRequestDto;
import LuckyVicky.backend.global.fcm.dto.FcmRequestDto.FcmSimpleReqDto;
import LuckyVicky.backend.global.s3.AmazonS3Manager;
import LuckyVicky.backend.global.util.Constant;
import com.fasterxml.jackson.databind.ObjectMapper;
Expand All @@ -28,8 +28,7 @@ public class FcmService {
private final Constant constant;
private final AmazonS3Manager amazonS3Manager;


public void sendMessageTo(FcmRequestDto.FcmSimpleReqDto requestDTO) throws IOException {
public void sendMessageTo(FcmSimpleReqDto requestDTO) throws IOException {
String message = makeMessage(requestDTO.getDeviceToken(), requestDTO.getTitle(), requestDTO.getBody());

OkHttpClient client = new OkHttpClient();
Expand Down
4 changes: 4 additions & 0 deletions src/main/java/LuckyVicky/backend/global/util/Constant.java
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,10 @@ public class Constant {
// Notice
public static final String CONVERTER_INSTANTIATION_NOT_ALLOWED = "Converter class는 인스턴스화가 불가능합니다.";

// FCM
public static final String FCM_TITLE_CURRENT_WEEK_AWARD = "이번 주 강화 보상";


// FCM API URL 동적으로 생성
public String getFcmApiUrl() {
return "https://fcm.googleapis.com/v1/projects/" + fcmProjectId + "/messages:send";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,8 @@ public void afterConnectionClosed(WebSocketSession session, CloseStatus status)
sessions.remove(session);
}

private void processSquareSelection(WebSocketSession session, User user, long currentRound, int selectedSquare) {
private void processSquareSelection(WebSocketSession session, User user, long currentRound, int selectedSquare)
throws IOException {
System.out.println(pachinkoService.viewSelectedSquares() + "핸들러에서 processSquareSelection 시작지점");

String result = pachinkoService.selectSquare(user, currentRound, selectedSquare);
Expand Down Expand Up @@ -129,7 +130,7 @@ private void sendMessage(WebSocketSession session, String message) {
}
}

private void checkGameStatusAndCloseSessionsIfNeeded() {
private void checkGameStatusAndCloseSessionsIfNeeded() throws IOException {
if (pachinkoService.isGameOver()) {
System.out.println("게임 끝남 확인. 보상 전달 시작");
broadcastMessage("해당 판이 종료되었습니다. 10초 후 새로운 판이 시작됩니다.");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,10 @@
import LuckyVicky.backend.enhance.domain.JewelType;
import LuckyVicky.backend.global.api_payload.ErrorCode;
import LuckyVicky.backend.global.exception.GeneralException;
import LuckyVicky.backend.global.fcm.converter.FcmConverter;
import LuckyVicky.backend.global.fcm.domain.UserDeviceToken;
import LuckyVicky.backend.global.fcm.dto.FcmRequestDto.FcmSimpleReqDto;
import LuckyVicky.backend.global.fcm.service.FcmService;
import LuckyVicky.backend.pachinko.converter.PachinkoConverter;
import LuckyVicky.backend.pachinko.domain.Pachinko;
import LuckyVicky.backend.pachinko.domain.PachinkoReward;
Expand All @@ -17,6 +21,7 @@
import LuckyVicky.backend.user.repository.UserRepository;
import jakarta.annotation.PostConstruct;
import jakarta.transaction.Transactional;
import java.io.IOException;
import java.security.SecureRandom;
import java.util.ArrayList;
import java.util.Collections;
Expand Down Expand Up @@ -49,6 +54,7 @@ public class PachinkoService {
private final UserJewelRepository userJewelRepository;
private final UserRepository userRepository;
private final DisplayBoardService displayBoardService;
private final FcmService fcmService;

@Getter
private final Set<Integer> selectedSquares = Collections.synchronizedSet(new HashSet<>()); // 스레드 간 동기화 제공
Expand Down Expand Up @@ -197,7 +203,7 @@ public boolean isGameOver() {
}

@Transactional
public void giveRewards() {
public void giveRewards() throws IOException {
System.out.println("보상 전달 시작");
List<UserPachinko> userPachinkoList = userpachinkoRepository.findByRound(currentRound);

Expand All @@ -219,17 +225,24 @@ public void giveRewards() {
Pachinko pa = pachinkoRepository.findByRoundAndSquare(currentRound, sq)
.orElseThrow(() -> new GeneralException(ErrorCode.BAD_REQUEST));

if (pa.getJewelType() == JewelType.S) {
displayBoardService.addPachinkoSJewelMessage(user);
}

if (pa.getJewelType() != JewelType.F) {
UserJewel uj = userJewelRepository.findByUserAndJewelType(user, pa.getJewelType())
.orElseThrow(() -> new GeneralException(ErrorCode.BAD_REQUEST));
uj.setCount(pa.getJewelNum());
userJewelRepository.save(uj);
System.out.println("user jewel 보상에 따라 갱신완료");
}

if (pa.getJewelType() == JewelType.S) {
displayBoardService.addPachinkoSJewelMessage(user);
}

List<UserDeviceToken> userDeviceTokens = user.getDeviceTokenList();
for (UserDeviceToken userDeviceToken : userDeviceTokens) {
FcmSimpleReqDto requestDTO = FcmConverter.toFcmSimpleReqDto(userDeviceToken.getDeviceToken(),
"빠칭코 게임이 끝났습니다!", "두구두구 당신이 뽑은 칸의 결과는?!?!");
fcmService.sendMessageTo(requestDTO);
}
}
}
System.out.println("보상 전달 완료");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,26 +3,34 @@
import LuckyVicky.backend.enhance.domain.JewelType;
import LuckyVicky.backend.global.api_payload.ErrorCode;
import LuckyVicky.backend.global.exception.GeneralException;
import LuckyVicky.backend.global.fcm.converter.FcmConverter;
import LuckyVicky.backend.global.fcm.domain.UserDeviceToken;
import LuckyVicky.backend.global.fcm.dto.FcmRequestDto.FcmSimpleReqDto;
import LuckyVicky.backend.global.fcm.service.FcmService;
import LuckyVicky.backend.roulette.dto.RouletteResponseDto;
import LuckyVicky.backend.user.domain.User;
import LuckyVicky.backend.user.domain.UserJewel;
import LuckyVicky.backend.user.repository.UserJewelRepository;
import LuckyVicky.backend.user.repository.UserRepository;
import jakarta.transaction.Transactional;
import java.io.IOException;
import java.time.Duration;
import java.time.LocalDateTime;
import java.util.List;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.scheduling.TaskScheduler;
import org.springframework.stereotype.Service;

import java.time.Duration;
import java.time.LocalDateTime;

@Service
@RequiredArgsConstructor
@Slf4j
public class RouletteService {

private final UserJewelRepository userJewelRepository;
private final UserRepository userRepository;
private final FcmService fcmService;
private final TaskScheduler taskScheduler; // 동적 스케줄러

@Transactional
public RouletteResponseDto.RouletteAvailableDto checkRouletteAvailability(User user) {
Expand All @@ -41,17 +49,14 @@ public RouletteResponseDto.RouletteAvailableDto checkRouletteAvailability(User u
public void saveRouletteResult(User user, String jewelType, int jewelCount) {
log.info("사용자 {}에게 {} 보석 {}개 추가", user.getUsername(), jewelType, jewelCount);

// F 보석인 경우에는 보상 저장을 건너뜀
if (JewelType.valueOf(jewelType) == JewelType.F) {
log.info("사용자 {}는 보상 없이 10분 타이머를 갱신합니다.", user.getUsername());
} else {
if (JewelType.valueOf(jewelType) != JewelType.F) {
addJewel(user, jewelType, jewelCount);
}

// 10초호 다시 룰렛 사용 가능
user.setRouletteAvailableTime(LocalDateTime.now().plusSeconds(30));

user.setRouletteAvailableTime(LocalDateTime.now().plusMinutes(10));
userRepository.save(user);

scheduleRouletteNotification(user, 10);
}

private void addJewel(User user, String jewelType, int count) {
Expand All @@ -62,4 +67,26 @@ private void addJewel(User user, String jewelType, int count) {
jewel.increaseCount(count);
userJewelRepository.save(jewel);
}

private void scheduleRouletteNotification(User user, int delayInMinutes) {
taskScheduler.schedule(() -> {
try {
sendRouletteNotification(user);
} catch (IOException e) {
log.error("알림 전송 중 오류 발생: 사용자 {}, 메시지: {}", user.getUsername(), e.getMessage(), e);
}
},
LocalDateTime.now().plusMinutes(delayInMinutes).atZone(java.time.ZoneId.systemDefault()).toInstant());
}

private void sendRouletteNotification(User user) throws IOException {

List<UserDeviceToken> userDeviceTokens = user.getDeviceTokenList();
for (UserDeviceToken userDeviceToken : userDeviceTokens) {
FcmSimpleReqDto requestDTO = FcmConverter.toFcmSimpleReqDto(userDeviceToken.getDeviceToken(),
"룰렛 돌리러 고고씽!!", "룰렛 대기 시간 10분이 끝났습니다!");
fcmService.sendMessageTo(requestDTO);
}
}

}