Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -25,5 +25,4 @@ public interface CategoryBudgetRepository extends JpaRepository<CategoryBudget,
CategoryBudget findCategoryBudgetByUserIdAndExpenseCategoryAndMonth(@Param("userId") Long userId,
@Param("category") ExpenseCategory category,
@Param("monthDate") LocalDate monthDate);
CategoryBudget findCategoryBudgetByMonthlyBudgetAndExpenseCategory(MonthlyBudget monthlyBudget, ExpenseCategory category);
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import tamtam.mooney.domain.mission.dto.MissionDto;
Expand All @@ -16,6 +17,7 @@
import java.time.LocalDate;
import java.util.List;

@Slf4j
@Tag(name = "Mission")
@RestController
@RequestMapping("/missions")
Expand All @@ -40,8 +42,13 @@ public ResponseEntity<MissionTabDto> getMissionResultByDate() {
@Operation(summary = "해당 사용자에 대해서만 미션 스케줄링 강제로 수행하기")
@PostMapping("/run")
public ResponseEntity<List<String>> getNewMission(@RequestParam LocalDate startDate) {
long startTime = System.currentTimeMillis(); // 시작 시간 기록
User user = userService.getCurrentUser();
List<String> newMissions = missionScheduler.runSchedulerManually(user, startDate);
long endTime = System.currentTimeMillis(); // 끝 시간 기록
long duration = endTime - startTime;

log.info("미션 스케줄러 수동 실행 시간: {}ms", duration);
return ResponseEntity.ok(newMissions);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,6 @@ public Mission(MissionType missionType, LocalDate startDate, LocalDate endDate,
this.max = max;
}


public void updateResult(Float result) {
this.result = result;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,22 +10,22 @@
import java.util.Optional;

public interface MissionRepository extends JpaRepository<Mission, Long> {
Float findMissionResultByMissionId(Long missionId);

// List<Mission> 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<Mission> 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<Mission> 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<Long> 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<Mission> findWeeklyMissionsByUserWithFetch(@Param("userId") Long userId,
@Param("today") LocalDate today);



//현재 진행중인 미션의 place를 가져오기
@Query("SELECT m.place FROM Mission m " +
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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<User> users = userRepository.findAll(); // 🔥 모든 사용자 조회

for (User user : users) {
try {
List<String> missionTitles = missionService.generateWeeklyMissions(user, startDate); // 🔁 사용자마다 미션 생성
// 생성된 미션 확인 로그
System.out.println("생성된 미션 목록: " + missionTitles);
} catch (Exception e) {
log.warn("미션 생성 실패 - userId: {}, error: {}", user.getUserId(), e.getMessage());
}
}
List<User> users = userRepository.findAll(); // 모든 사용자 조회

//비동기+병렬
List<CompletableFuture<Void>> futures = users.stream()
.map(user -> CompletableFuture.runAsync(() -> {
try {
List<String> 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<String> 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); // 다음 주 월요일
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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<UserHomeWeeklyMissionDto> getWeeklyMissions(LocalDate today) {
User user = userService.getCurrentUser();
List<Mission> missions = missionRepository.findWeeklyMissionsByUser(user.getUserId(), today);
List<Mission> missions = missionRepository.findWeeklyMissionsByUserWithFetch(user.getUserId(), today);
updateMissionResult(today);

return missions.stream()
Expand All @@ -57,7 +60,7 @@ public List<UserHomeWeeklyMissionDto> getWeeklyMissions(LocalDate today) {
// 저장해놓은 미션 가져오기(미션탭)
public List<MissionDto> getWeeklyMissionsDetail(LocalDate today) {
User user = userService.getCurrentUser();
List<Mission> missions = missionRepository.findWeeklyMissionsByUser(user.getUserId(), today);
List<Mission> missions = missionRepository.findWeeklyMissionsByUserWithFetch(user.getUserId(), today);
updateMissionResult(today);

return missions.stream()
Expand Down Expand Up @@ -85,14 +88,13 @@ public void updateMission(User user, String payee, long amount){
//미션 상태 업데이트
public float updateMissionResult(LocalDate today){
User user = userService.getCurrentUser();
List<Mission> missions = missionRepository.findWeeklyMissionsByUser(user.getUserId(), today);
List<Mission> 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")){ //방문 기반 미션
Expand Down Expand Up @@ -209,10 +211,10 @@ private float clamp(float value, float min, float max) {
//# FastAPI 서버에서 카테고리 및 예상 지출 금액을 가져와 현재 주별 예산과 비교하는 동기 메서드(1~3 포함)
// => 지출 > 예상인 카테고리에 한해 카테고리, realWeeklyCategoryBudget, predictedSpending을 return
private List<Map<String, Object>> getSelectedCategories(User user) {
// 1️⃣ FastAPI에서 예상 지출 데이터 가져오기 (동기 방식)
// FastAPI에서 예상 지출 데이터 가져오기 (동기 방식)
List<Map<String, Object>> categoryDataList = fetchPredictedSpending(user);

// 2️⃣ 주별 예산과 비교하여 데이터 반환
// 2주별 예산과 비교하여 데이터 반환
return compareWithWeeklyBudget(user, categoryDataList);
}

Expand All @@ -231,12 +233,12 @@ private List<Map<String, Object>> fetchPredictedSpending(User user) {
.bodyToMono(new ParameterizedTypeReference<Map<String, Object>>() {})
.block();

System.out.println("Received Response from FastAPI: " + response);
System.out.println("Received Response from FastAPI: " + response);

// 🔥 "predict_results" 키에서 실제 데이터를 추출하여 리스트로 변환
// "predict_results" 키에서 실제 데이터를 추출하여 리스트로 변환
List<Map<String, Object>> predictResults = (List<Map<String, Object>>) response.get("predict_results");

// 🔥 String을 ExpenseCategory Enum으로 변환
// String을 ExpenseCategory Enum으로 변환
List<Map<String, Object>> convertedResults = predictResults.stream().map(entry -> {
Map<String, Object> newEntry = new HashMap<>(entry);
String categoryStr = (String) entry.get("Category"); // 🔥 FastAPI에서 온 문자열
Expand All @@ -251,7 +253,7 @@ private List<Map<String, Object>> fetchPredictedSpending(User user) {
return newEntry;
}).collect(Collectors.toList());

System.out.println("Converted Prediction Results: " + convertedResults);
System.out.println("Converted Prediction Results: " + convertedResults);
return convertedResults;
}

Expand Down Expand Up @@ -405,6 +407,7 @@ private Long calculateWeeklyBudget(User user, ExpenseCategory category) {
* "WeeklyBudget", realWeeklyCategoryBudget,
* "PredictedSpending", predictedSpending
**/
@Transactional(readOnly = false)
public List<String> generateWeeklyMissions(User user, LocalDate missionStartDate) {
//User user = userService.getCurrentUser();
long userId = user.getUserId();
Expand Down Expand Up @@ -533,7 +536,7 @@ private Mission generateCategoryMission(List<Object[]> 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; // 방문 제한 후 예상 소비 금액
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down
Loading