not-a-gardener는 "일주일에 한 번 물을 주세요"에서 벗어나기 위해 만든 서비스다.
물주기 기능에서 구현한 것들은 다음과 같다.
- 두 개 이상의 물주기를 기록하면 '최근 물주기'가 생성된다
- 기록된 주기를 사용해 다음 물주기 스케줄을 알려준다
- 물주기가 줄어들거나(식물 성장, 계절 변화), 물주기가 늘어나면(식물 건강의 문제, 계절 변화), 변경된 물주기를 자동으로 반영한다.
위의 기능에 더불어 지난 주, 다음 주가 포함된 캘린더에 날짜를 계산해서 물주기 데이터를 가져온 로직을 소개할 것이다.
처음으로 물주기를 기록하셨네요
물주기가 줄어들었어요
etc...
public WateringDto.ByDate addWatering(WateringDto.Request wateringRequest){
// 물주기 저장
Watering watering = wateringDao.addWatering(wateringRequest);
// 물주기 계산 로직
WateringDto.Message wateringMsg = wateringUtil.getWateringMsg(watering.getPlant().getPlantId());
// 식물 테이블의 averageWateringDate 업데이트 필요 X
if (wateringMsg.getAfterWateringCode() == 3) {
// 바로 리턴
return WateringDto.ByDate.from(watering, watering.getPlant(), watering.getChemical());
}
// 필요시 물주기 정보 업데이트
Plant newPlant = wateringUtil.updateWateringPeriod(watering.getPlant(), wateringMsg.getAverageWateringDate());
return WateringDto.ByDate.from(watering, newPlant, watering.getChemical());
}
public WateringDto.Message getWateringMsg(Plant plant){
// 첫번째 물주기면
if (plant.getWaterings().size() == 1) {
return new WateringDto.Message(AfterWateringCode.FIRST_WATERING.getCode(), plant.getRecentWateringPeriod());
}
// 두번째 물주기: 최초 물주기가 기록되었다고 알려줌
if(plant.getWaterings().size() == 2){
return new WateringDto.Message(AfterWateringCode.SECOND_WATERING.getCode(), plant.getRecentWateringPeriod());
}
// 가장 최근 물 준 날짜
LocalDateTime latestWateringDate = plant.getWaterings().get(0).getWateringDate().atStartOfDay();
// 두 번째로 최근 물 준 날짜
LocalDateTime prevWateringDate = plant.getWaterings().get(1).getWateringDate().atStartOfDay();
// 두 날짜 간의 차를 계산
int period = (int) Duration.between(prevWateringDate, latestWateringDate).toDays();
// 관수 코드를 계산
int wateringCode = getAfterWateringCode(period, plant.getRecentWateringPeriod());
return new WateringDto.Message(wateringCode, period);
}
// 관수 코드 계산
public int getAfterWateringCode(int period, int prevWateringPeriod) {
// 물주기 짧아짐: -1
// 물주기 똑같음: 0
// 물주기 길어짐: 1
// 인간의 게으름 혹은 환경 문제이므로 DB 반영하지 않음
return Integer.compare(period, prevWateringPeriod);
}
이후 관수 코드에 따라 다음처럼 알림창이 뜨게 된다.
7월을 기준으로, 위 캘린더를 채우기 위해선 6/25~8/05의 데이터가 필요하다.
선택된 달을 기준으로 42일 간의 데이터를 받아오는 로직을 구현했다.
코드 설명은 주석으로 대체한다.
@Override
public Map<LocalDate, List<WateringDto.ByDate>> getWateringList(Long gardenerId, int month) {
///// 날짜 계산 로직
// 1일이 일요일이면 뒤로 2주 더
// 1일이 일요일이 아니면 앞으로 한 주 뒤로 한 주
// TODO 연도도 파라미터로 받기
LocalDate firstDayOfMonth = LocalDate.of(2023, month, 1);
// 월 화 수 목 금 토 일 => 1, 2, 3, 4, 5, 6, 7
// 일요일이면 가만히 두고, 나머지 요일이면 getDayOfWeek().getValue()를 빼면
// 7로 나눈 나머지를 뺌
LocalDate startDate = firstDayOfMonth.minusDays(firstDayOfMonth.getDayOfWeek().getValue() % 7);
// end date: 합쳐서 42가 되게 만드는 값
// x = 42 - 이번달 - startDate으로 더한 값
int tmp = 42 - firstDayOfMonth.lengthOfMonth() - firstDayOfMonth.getDayOfWeek().getValue() % 7;
LocalDate endDate = firstDayOfMonth.plusDays(firstDayOfMonth.lengthOfMonth() - 1 + tmp);
///// 데이터 가공
Map<LocalDate, List<WateringDto.ByDate>> map = new HashMap<>(); // 날짜: 리스트
for (Watering watering : wateringDao.getAllWateringListByGardenerNo(gardenerId, startDate, endDate)) {
List<WateringDto.ByDate> tmpList = map.get(watering.getWateringDate());
if(tmpList == null){
List<WateringDto.ByDate> list = new ArrayList<>();
list.add(WateringDto.ByDate.from(watering));
map.put(watering.getWateringDate(), list);
continue;
}
tmpList.add(WateringDto.ByDate.from(watering));
map.put(watering.getWateringDate(), tmpList);
}
log.debug("map: {}", map);
return map;
}