diff --git a/backend/ongi/src/main/java/ongi/OngiApplication.java b/backend/ongi/src/main/java/ongi/OngiApplication.java index a77782e..7e06666 100644 --- a/backend/ongi/src/main/java/ongi/OngiApplication.java +++ b/backend/ongi/src/main/java/ongi/OngiApplication.java @@ -3,9 +3,11 @@ import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.data.jpa.repository.config.EnableJpaAuditing; +import org.springframework.scheduling.annotation.EnableScheduling; @SpringBootApplication @EnableJpaAuditing +@EnableScheduling public class OngiApplication { public static void main(String[] args) { diff --git a/backend/ongi/src/main/java/ongi/temperature/repository/TemperatureRepository.java b/backend/ongi/src/main/java/ongi/temperature/repository/TemperatureRepository.java index 39559dd..9619b82 100644 --- a/backend/ongi/src/main/java/ongi/temperature/repository/TemperatureRepository.java +++ b/backend/ongi/src/main/java/ongi/temperature/repository/TemperatureRepository.java @@ -32,12 +32,12 @@ public interface TemperatureRepository extends JpaRepository @Query("SELECT COUNT(t) > 0 FROM Temperature t WHERE t.userId = :userId AND t.familyId = :familyId AND t.reason = :reason AND FUNCTION('DATE', t.createdAt) = :date") boolean existsByUserIdAndFamilyIdAndReasonAndDate(@Param("userId") java.util.UUID userId, @Param("familyId") String familyId, @Param("reason") String reason, @Param("date") java.time.LocalDate date); - // 최근 N일간 해당 유저가 가족 내에서 활동(온도 변화)이 있었는지 - @Query("SELECT COUNT(t) > 0 FROM Temperature t WHERE t.userId = :userId AND t.familyId = :familyId AND t.createdAt >= :since") + // 최근 N일간 해당 유저가 가족 내에서 활동(온도 변화)이 있었는지 (만보기 제외) + @Query("SELECT COUNT(t) > 0 FROM Temperature t WHERE t.userId = :userId AND t.familyId = :familyId AND t.reason != 'STEP_GOAL' AND t.createdAt >= :since") boolean existsByUserIdAndFamilyIdAndCreatedAtAfter(@Param("userId") java.util.UUID userId, @Param("familyId") String familyId, @Param("since") java.time.LocalDateTime since); - // 최근 N일간 가족 내에서 아무나 활동(온도 변화)이 있었는지 - @Query("SELECT COUNT(t) > 0 FROM Temperature t WHERE t.familyId = :familyId AND t.createdAt >= :since") + // 최근 N일간 가족 내에서 아무나 활동(온도 변화)이 있었는지 (만보기 제외) + @Query("SELECT COUNT(t) > 0 FROM Temperature t WHERE t.familyId = :familyId AND t.reason != 'STEP_GOAL' AND t.createdAt >= :since") boolean existsByFamilyIdAndCreatedAtAfter(@Param("familyId") String familyId, @Param("since") java.time.LocalDateTime since); @Query("SELECT SUM(t.temperature) FROM Temperature t WHERE t.familyId = :familyId AND t.createdAt < :fromDate") diff --git a/backend/ongi/src/main/java/ongi/temperature/service/TemperatureService.java b/backend/ongi/src/main/java/ongi/temperature/service/TemperatureService.java index ac94667..ac9dff2 100644 --- a/backend/ongi/src/main/java/ongi/temperature/service/TemperatureService.java +++ b/backend/ongi/src/main/java/ongi/temperature/service/TemperatureService.java @@ -32,6 +32,9 @@ public class TemperatureService { private final UserRepository userRepository; private final StepRepository stepRepository; private static final double BASE_TEMPERATURE = 36.5; + + // 가족 전체 온도 변화를 나타내는 가상 사용자 ID + private static final UUID FAMILY_WIDE_USER_ID = UUID.fromString("00000000-0000-0000-0000-000000000000"); // 소수점 1자리 반올림 유틸 private static double round(double value, int places) { @@ -137,7 +140,12 @@ public FamilyTemperatureContributionResponse getFamilyTemperatureContributions(S List contributions = temps.stream() .map(t -> { User user = userMap.get(t.getUserId()); - String userName = user != null ? user.getName() : "우리 가족"; + String userName; + if (t.getUserId().equals(FAMILY_WIDE_USER_ID)) { + userName = "우리 가족"; + } else { + userName = user.getName(); + } return new FamilyTemperatureContributionResponse.Contribution( t.getCreatedAt(), userName, @@ -156,8 +164,8 @@ public FamilyTemperatureContributionResponse getFamilyTemperatureContributions(S // 활동별 reason 상수 정의 private static final String REASON_EMOTION_UPLOAD = "EMOTION_UPLOAD"; - private static final String REASON_ALL_EMOTION_UPLOAD = "ALL_EMOTION_UPLOAD"; - private static final String REASON_STEP_GOAL = "STEP_GOAL"; + private static final String REASON_ALL_EMOTION_UPLOAD = "ALL_FAMILY_EMOTION_UPLOAD"; + private static final String REASON_STEP_GOAL = "FAMILY_STEP_GOAL"; private static final String REASON_PARENT_PAIN_INPUT = "PARENT_PAIN_INPUT"; private static final String REASON_PARENT_MED_INPUT = "PARENT_MED_INPUT"; private static final String REASON_PARENT_EXERCISE_INPUT = "PARENT_EXERCISE_INPUT"; @@ -271,7 +279,7 @@ public void increaseTemperatureForChildExerciseView(UUID userId, String familyId // 매일 23:59:59에 당일의 가족별 보너스 온도 상승 처리 - @Scheduled(cron = "59 59 23 * * *") + @Scheduled(cron = "59 37 15 * * *") @Transactional public void processFamilyBonusTemperature() { var families = familyRepository.findAll(); @@ -286,13 +294,13 @@ public void processFamilyBonusTemperature() { // 모든 구성원이 감정기록 업로드 시 보너스 지급 private void processEmotionBonus(ongi.family.entity.Family family, String familyId, LocalDate targetDate) { var memberIds = family.getMembers(); - boolean alreadyEmotionBonus = temperatureRepository.existsByUserIdAndFamilyIdAndReasonAndDate(null, familyId, REASON_ALL_EMOTION_UPLOAD, targetDate); + boolean alreadyEmotionBonus = temperatureRepository.existsByUserIdAndFamilyIdAndReasonAndDate(FAMILY_WIDE_USER_ID, familyId, REASON_ALL_EMOTION_UPLOAD, targetDate); boolean allEmotionUploaded = memberIds.stream().allMatch( userId -> temperatureRepository.existsByUserIdAndFamilyIdAndReasonAndDate(userId, familyId, REASON_EMOTION_UPLOAD, targetDate) ); if (allEmotionUploaded && !alreadyEmotionBonus) { Temperature temp = Temperature.builder() - .userId(null) + .userId(FAMILY_WIDE_USER_ID) .familyId(familyId) .temperature(0.1) .reason(REASON_ALL_EMOTION_UPLOAD) @@ -310,11 +318,11 @@ private void processStepGoalBonus(ongi.family.entity.Family family, String famil int goal = (int)(parentCount * 7000 + childCount * 10000); List steps = stepRepository.findByFamilyAndDate(family, targetDate); int totalSteps = steps.stream().mapToInt(Step::getSteps).sum(); - boolean alreadyStepBonus = temperatureRepository.existsByUserIdAndFamilyIdAndReasonAndDate(null, familyId, REASON_STEP_GOAL, targetDate); + boolean alreadyStepBonus = temperatureRepository.existsByUserIdAndFamilyIdAndReasonAndDate(FAMILY_WIDE_USER_ID, familyId, REASON_STEP_GOAL, targetDate); boolean stepGoalMet = totalSteps >= goal; if (stepGoalMet && !alreadyStepBonus) { Temperature temp = Temperature.builder() - .userId(null) + .userId(FAMILY_WIDE_USER_ID) .familyId(familyId) .temperature(0.2) .reason(REASON_STEP_GOAL) @@ -324,26 +332,24 @@ private void processStepGoalBonus(ongi.family.entity.Family family, String famil } - // 매주 일요일 23:59:59 에 만보기 경쟁 결과 보너스 온도 상승 처리 (후순위! 일단 안만들어도 됨) + // 매주 일요일 23:59:59 에 만보기 가족 경쟁 결과 보너스 온도 상승 처리 (후순위! 일단 안만들어도 됨) // TODO: 만보기 전체 경쟁 상위 10% -> +3도 // TODO: 만보기 전체 경쟁 상위 20% -> +1도 - // 매일 23:59:59에 당일의 가족별 온도 하락 처리 - @Scheduled(cron = "59 59 23 * * *") + //// 매주 일요일 23:59:59에 일주일 미접속 온도 하락 처리 + @Scheduled(cron = "59 59 23 * * 0") @Transactional - public void processFamilyTemperatureDecrease() { + public void processWeeklyTemperatureDecrease() { var families = familyRepository.findAll(); for (var family : families) { String familyId = family.getCode(); decreaseTemperatureForInactiveParent(familyId); decreaseTemperatureForInactiveChild(familyId); - decreaseTemperatureForNoLogin(familyId); } } - - // 부모 1명 이상 일주일 동안안 아무 활동 없을 시 온도 하락 + // 부모 1명 이상 일주일 동안 아무 활동 없을 시 온도 하락 public void decreaseTemperatureForInactiveParent(String familyId) { List memberIds = familyRepository.findById(familyId).get().getMembers(); @@ -357,15 +363,14 @@ public void decreaseTemperatureForInactiveParent(String familyId) { if (parentInactive) { Temperature temp = Temperature.builder() - .userId(null) + .userId(FAMILY_WIDE_USER_ID) .familyId(familyId) .temperature(-10.0) - .reason("INACTIVE_PARENT") + .reason("INACTIVE_PARENT_7DAY") .build(); temperatureRepository.save(temp); } } - // 자녀 1명 이상 일주일 동안 아무 활동 없을 시 온도 하락 public void decreaseTemperatureForInactiveChild(String familyId) { @@ -379,26 +384,38 @@ public void decreaseTemperatureForInactiveChild(String familyId) { .anyMatch(child -> !temperatureRepository.existsByUserIdAndFamilyIdAndCreatedAtAfter(child.getUuid(), familyId, since)); if (childInactive) { Temperature temp = Temperature.builder() - .userId(null) + .userId(FAMILY_WIDE_USER_ID) .familyId(familyId) .temperature(-10.0) - .reason("INACTIVE_CHILD") + .reason("INACTIVE_CHILD_7DAY") .build(); temperatureRepository.save(temp); } } - // 하루 동안 아무도 활동 없을 시 시 온도 하락 + + //// 매일 23:59:59에 하루 미접속 온도 하락 처리 + @Scheduled(cron = "59 59 23 * * *") + @Transactional + public void processDailyTemperatureDecrease() { + var families = familyRepository.findAll(); + for (var family : families) { + String familyId = family.getCode(); + decreaseTemperatureForNoLogin(familyId); + } + } + // 하루 동안 아무도 활동 없을 시 온도 하락 public void decreaseTemperatureForNoLogin(String familyId) { LocalDateTime since = LocalDate.now().atStartOfDay(); // 오늘 00:00~ + // 만보기(STEP_GOAL)를 제외한 가족 전체 활동 체크 boolean noLogin = !temperatureRepository.existsByFamilyIdAndCreatedAtAfter(familyId, since); if (noLogin) { Temperature temp = Temperature.builder() - .userId(null) + .userId(FAMILY_WIDE_USER_ID) .familyId(familyId) .temperature(-0.5) - .reason("NO_LOGIN") + .reason("INACTIVE_ALL_FAMILY_TODAY") .build(); temperatureRepository.save(temp); }