diff --git a/mooney/src/main/java/tamtam/mooney/domain/budget/repository/CategoryBudgetRepository.java b/mooney/src/main/java/tamtam/mooney/domain/budget/repository/CategoryBudgetRepository.java index aef6b13..199244e 100644 --- a/mooney/src/main/java/tamtam/mooney/domain/budget/repository/CategoryBudgetRepository.java +++ b/mooney/src/main/java/tamtam/mooney/domain/budget/repository/CategoryBudgetRepository.java @@ -25,5 +25,4 @@ public interface CategoryBudgetRepository extends JpaRepository getMissionResultByDate() { @Operation(summary = "해당 사용자에 대해서만 미션 스케줄링 강제로 수행하기") @PostMapping("/run") public ResponseEntity> getNewMission(@RequestParam LocalDate startDate) { + long startTime = System.currentTimeMillis(); // 시작 시간 기록 User user = userService.getCurrentUser(); List newMissions = missionScheduler.runSchedulerManually(user, startDate); + long endTime = System.currentTimeMillis(); // 끝 시간 기록 + long duration = endTime - startTime; + + log.info("미션 스케줄러 수동 실행 시간: {}ms", duration); return ResponseEntity.ok(newMissions); } diff --git a/mooney/src/main/java/tamtam/mooney/domain/mission/entity/Mission.java b/mooney/src/main/java/tamtam/mooney/domain/mission/entity/Mission.java index 6042ab3..c29bebc 100644 --- a/mooney/src/main/java/tamtam/mooney/domain/mission/entity/Mission.java +++ b/mooney/src/main/java/tamtam/mooney/domain/mission/entity/Mission.java @@ -73,7 +73,6 @@ public Mission(MissionType missionType, LocalDate startDate, LocalDate endDate, this.max = max; } - public void updateResult(Float result) { this.result = result; } diff --git a/mooney/src/main/java/tamtam/mooney/domain/mission/repository/MissionRepository.java b/mooney/src/main/java/tamtam/mooney/domain/mission/repository/MissionRepository.java index b53ddf0..1c8e492 100644 --- a/mooney/src/main/java/tamtam/mooney/domain/mission/repository/MissionRepository.java +++ b/mooney/src/main/java/tamtam/mooney/domain/mission/repository/MissionRepository.java @@ -10,22 +10,22 @@ import java.util.Optional; public interface MissionRepository extends JpaRepository { - Float findMissionResultByMissionId(Long missionId); -// List getMissionByCategoryBudget_Id(Long categoryBudgetId); - - // 1️⃣ 사용자의 이번 주 미션 가져오기 + // 1사용자의 이번 주 미션 가져오기 +// @Query("SELECT m FROM Mission m " + +// "WHERE m.categoryBudget.monthlyBudget.user.userId = :userId " + +// "AND :today BETWEEN m.startDate AND m.endDate") +// List findWeeklyMissionsByUser(@Param("userId") Long userId, @Param("today") LocalDate today); + // 1사용자의 이번 주 미션 가져오기 - fetchJoin으로 @Query("SELECT m FROM Mission m " + - "WHERE m.categoryBudget.monthlyBudget.user.userId = :userId " + - "AND :today BETWEEN m.startDate AND m.endDate") - List findWeeklyMissionsByUser(@Param("userId") Long userId, @Param("today") LocalDate today); - - // 2️⃣ 해당 미션에 맞는 사용자 ID 가져오기 - @Query("SELECT mb.user.userId FROM Mission m " + - "JOIN m.categoryBudget cb " + - "JOIN cb.monthlyBudget mb " + - "WHERE m.missionId = :missionId") - Optional findUserIdByMissionId(@Param("missionId") Long missionId); + " JOIN FETCH m.categoryBudget cb" + + " JOIN FETCH cb.monthlyBudget mb" + + " WHERE mb.user.userId = :userId" + + " AND :today BETWEEN m.startDate AND m.endDate") + List findWeeklyMissionsByUserWithFetch(@Param("userId") Long userId, + @Param("today") LocalDate today); + + //현재 진행중인 미션의 place를 가져오기 @Query("SELECT m.place FROM Mission m " + diff --git a/mooney/src/main/java/tamtam/mooney/domain/mission/service/MissionScheduler.java b/mooney/src/main/java/tamtam/mooney/domain/mission/service/MissionScheduler.java index 819621a..20d9cec 100644 --- a/mooney/src/main/java/tamtam/mooney/domain/mission/service/MissionScheduler.java +++ b/mooney/src/main/java/tamtam/mooney/domain/mission/service/MissionScheduler.java @@ -12,6 +12,8 @@ import java.time.LocalDateTime; import java.time.format.DateTimeFormatter; import java.util.List; +import java.util.concurrent.CompletableFuture; +import java.util.stream.Collectors; @Slf4j @Component @@ -25,32 +27,42 @@ public MissionScheduler(MissionService missionService, UserRepository userReposi this.userRepository = userRepository; } + //@Scheduled(cron="0 * * * * *") @Scheduled(cron = "00 50 23 * * 0") // 매주 일요일 23:50:00에 실행 - //@Scheduled(cron = "10 * * * * *") public void scheduleWeeklyMissionGeneration() { + long startTime = System.currentTimeMillis(); // 시작 시간 기록 LocalDate startDate = getNextMonday(); System.out.println("미션 자동 생성 시작: " + LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"))); - List users = userRepository.findAll(); // 🔥 모든 사용자 조회 - - for (User user : users) { - try { - List missionTitles = missionService.generateWeeklyMissions(user, startDate); // 🔁 사용자마다 미션 생성 - // 생성된 미션 확인 로그 - System.out.println("생성된 미션 목록: " + missionTitles); - } catch (Exception e) { - log.warn("미션 생성 실패 - userId: {}, error: {}", user.getUserId(), e.getMessage()); - } - } + List users = userRepository.findAll(); // 모든 사용자 조회 + + //비동기+병렬 + List> futures = users.stream() + .map(user -> CompletableFuture.runAsync(() -> { + try { + List missionTitles = missionService.generateWeeklyMissions(user, startDate); + log.info("생성된 미션 목록: {}", missionTitles); + } catch (Exception e) { + log.warn("미션 생성 실패 - userId: {}, error: {}", user.getUserId(), e.getMessage()); + } + })) + .collect(Collectors.toList()); + + CompletableFuture.allOf(futures.toArray(new CompletableFuture[0])).join(); // 비동기 병렬 작업 완료 대기 + + + long endTime = System.currentTimeMillis(); // 끝 시간 기록 + long duration = endTime - startTime; + log.info("미션 스케줄러 자동 실행 시간: {}ms", duration); } - // ✅ 수동 실행을 위한 메서드 추가 + // 수동 실행을 위한 메서드 추가 public List runSchedulerManually(User user, LocalDate startDate) { System.out.println("⚡ 수동 실행: 미션 자동 생성 시작 (startDate: " + startDate + ")"); return missionService.generateWeeklyMissions(user, startDate); } - // ✅ 다음 주 월요일을 구하는 메서드 + // 다음 주 월요일을 구하는 메서드 private LocalDate getNextMonday() { LocalDate today = LocalDate.now(); return today.with(DayOfWeek.MONDAY).plusWeeks(1); // 다음 주 월요일 diff --git a/mooney/src/main/java/tamtam/mooney/domain/mission/service/MissionService.java b/mooney/src/main/java/tamtam/mooney/domain/mission/service/MissionService.java index ddddc42..1e051bf 100644 --- a/mooney/src/main/java/tamtam/mooney/domain/mission/service/MissionService.java +++ b/mooney/src/main/java/tamtam/mooney/domain/mission/service/MissionService.java @@ -37,14 +37,17 @@ public class MissionService { private final TransactionRepository transactionRepository; private final CategoryBudgetRepository categoryBudgetRepository; private final WebClient webClient; // FastAPI 서버에서 데이터 가져오기 위한 클라이언트 - private static final String FASTAPI_URL = "https://mooney-ai.o-r.kr/predict"; // FastAPI URL + //private static final String FASTAPI_URL = "https://mooney-ai.o-r.kr/predict"; // FastAPI URL + private static final String FASTAPI_URL = "http://127.0.0.1:8000/predict"; + + private final UserService userService; // 저장해놓은 미션 가져오기(홈) public List getWeeklyMissions(LocalDate today) { User user = userService.getCurrentUser(); - List missions = missionRepository.findWeeklyMissionsByUser(user.getUserId(), today); + List missions = missionRepository.findWeeklyMissionsByUserWithFetch(user.getUserId(), today); updateMissionResult(today); return missions.stream() @@ -57,7 +60,7 @@ public List getWeeklyMissions(LocalDate today) { // 저장해놓은 미션 가져오기(미션탭) public List getWeeklyMissionsDetail(LocalDate today) { User user = userService.getCurrentUser(); - List missions = missionRepository.findWeeklyMissionsByUser(user.getUserId(), today); + List missions = missionRepository.findWeeklyMissionsByUserWithFetch(user.getUserId(), today); updateMissionResult(today); return missions.stream() @@ -85,14 +88,13 @@ public void updateMission(User user, String payee, long amount){ //미션 상태 업데이트 public float updateMissionResult(LocalDate today){ User user = userService.getCurrentUser(); - List missions = missionRepository.findWeeklyMissionsByUser(user.getUserId(), today); + List missions = missionRepository.findWeeklyMissionsByUserWithFetch(user.getUserId(), today); int currentDayOfWeek = today.getDayOfWeek().getValue(); float sum = 0; - System.out.println("📌 오늘 요일: " + currentDayOfWeek); + System.out.println("오늘 요일: " + currentDayOfWeek); for(Mission mission : missions){ - System.out.println("📌 미션 종류: " + mission.getMissionType()); - - System.out.println("📌 소비액: " + mission.getAmountOfExpense() + ", 방문횟수: " + mission.getNumOfExpense()); + System.out.println("미션 종류: " + mission.getMissionType()); + System.out.println("소비액: " + mission.getAmountOfExpense() + ", 방문횟수: " + mission.getNumOfExpense()); float result = 0; if(mission.getMissionType().equals("VISIT")){ //방문 기반 미션 @@ -209,10 +211,10 @@ private float clamp(float value, float min, float max) { //# FastAPI 서버에서 카테고리 및 예상 지출 금액을 가져와 현재 주별 예산과 비교하는 동기 메서드(1~3 포함) // => 지출 > 예상인 카테고리에 한해 카테고리, realWeeklyCategoryBudget, predictedSpending을 return private List> getSelectedCategories(User user) { - // 1️⃣ FastAPI에서 예상 지출 데이터 가져오기 (동기 방식) + // FastAPI에서 예상 지출 데이터 가져오기 (동기 방식) List> categoryDataList = fetchPredictedSpending(user); - // 2️⃣ 주별 예산과 비교하여 데이터 반환 + // 2주별 예산과 비교하여 데이터 반환 return compareWithWeeklyBudget(user, categoryDataList); } @@ -231,12 +233,12 @@ private List> fetchPredictedSpending(User user) { .bodyToMono(new ParameterizedTypeReference>() {}) .block(); - System.out.println("✅ Received Response from FastAPI: " + response); + System.out.println("Received Response from FastAPI: " + response); - // 🔥 "predict_results" 키에서 실제 데이터를 추출하여 리스트로 변환 + // "predict_results" 키에서 실제 데이터를 추출하여 리스트로 변환 List> predictResults = (List>) response.get("predict_results"); - // 🔥 String을 ExpenseCategory Enum으로 변환 + // String을 ExpenseCategory Enum으로 변환 List> convertedResults = predictResults.stream().map(entry -> { Map newEntry = new HashMap<>(entry); String categoryStr = (String) entry.get("Category"); // 🔥 FastAPI에서 온 문자열 @@ -251,7 +253,7 @@ private List> fetchPredictedSpending(User user) { return newEntry; }).collect(Collectors.toList()); - System.out.println("✅ Converted Prediction Results: " + convertedResults); + System.out.println("Converted Prediction Results: " + convertedResults); return convertedResults; } @@ -405,6 +407,7 @@ private Long calculateWeeklyBudget(User user, ExpenseCategory category) { * "WeeklyBudget", realWeeklyCategoryBudget, * "PredictedSpending", predictedSpending **/ + @Transactional(readOnly = false) public List generateWeeklyMissions(User user, LocalDate missionStartDate) { //User user = userService.getCurrentUser(); long userId = user.getUserId(); @@ -533,7 +536,7 @@ private Mission generateCategoryMission(List visitData, long maxAllowedVisits = 0; if (averageCost != 0) { - maxAllowedVisits = (long) Math.max(0, (realWeeklyCategoryBudget * 0.8) / averageCost); + maxAllowedVisits = (long) Math.max(1, (realWeeklyCategoryBudget * 0.8) / averageCost); } float expectedVisitSpending = maxAllowedVisits * averageCost; // 방문 제한 후 예상 소비 금액 diff --git a/mooney/src/main/java/tamtam/mooney/domain/transaction/service/ExpenseService.java b/mooney/src/main/java/tamtam/mooney/domain/transaction/service/ExpenseService.java index 0fb90b1..ffb4611 100644 --- a/mooney/src/main/java/tamtam/mooney/domain/transaction/service/ExpenseService.java +++ b/mooney/src/main/java/tamtam/mooney/domain/transaction/service/ExpenseService.java @@ -49,6 +49,7 @@ public String createExpense(ExpenseAddRequestDto request) { transactionRepository.save(expense); //들어온 지출의 payee가 현재 진행중인 미션 place에 해당된다면 missionRepo에 save + //TODO: 지출 삭제 시에 count 없어지는 거 안했다.... missionService.updateMission(user, request.payee(),request.amount()); return expense.getExpenseCategory().name();