우선 저희 프로젝트에서는 알림의 송수진자의 역할에 따라 3가지의 알림으로 구분했습니다.
저희 프로젝트에서는 3가지로 구역을 나눴습니다.
피보호자가 각 구역 간의 이동이 발생할 때 마다 알림을 보내는 로직을 구현했습니다.
피보호자의 현재 위치에 따른 구역을 계산해서 직전 구역과 다른 경우에 알림을 전송합니다.
아래 코드의 로직 순서입니다.
아래의 순서를 따라 알림을 보낼 지 말지 결정합니다.
가독성을 위해 일부 코드를 수정, 삭제했습니다. 코드 전문은 아래의 깃허브에서 확인하실 수 있습니다. (특히 optional null check)
// 위치에 따라 보내야하기 때문에 필요한 위치를 갱신하는 메서드 @PostMapping("/update-coordinate") public ResponseEntity<Map<String, String>> updateCoordinate(@RequestBody UpdateCoordinateDTO dto) { Map<String, String> result = new HashMap<>(); // 일반 멤버의 경우 if (dto.getType().equals("Member")) { if (memberService.updateMemberCoordinate(dto.getId(), dto.getLatitude(), dto.getLongitude())) { return addOkStatus(result); } return addErrorStatus(result); } // 피보호자의 경우 if (memberService.updateChildCoordinate(dto.getId(), dto.getLatitude(), dto.getLongitude())) { noticeController.sendNotice(dto.getId()); return addOkStatus(result); } return addErrorStatus(result); }
// Notice를 보내는 메서드 @Transactional public void sendNotice(String childName) { Child foundChild = memberService.findChildByChildName(childName); String currentStatus = getCurrentStatus(foundChild); String lastStatus = foundChild.getLastStatus(); List<Parenting> childParentingList = foundChild.getParentingList(); // 구역 변경 시 FCM 메시지 전송 if (currentStatus.equals("위험구역") && (!lastStatus.equals("위험구역"))) { if (! sendNoticeToMember(childParentingList, foundChild.getChildName(), NoticeLevel.WARN)) { return; } // 마지막 상태 갱신 foundChild.setLastStatus(currentStatus); } else if (!lastStatus.equals(currentStatus)) { if (! sendNoticeToMember(childParentingList, foundChild.getChildName(), NoticeLevel.INFO)) { return; } // 마지막 상태 갱신 foundChild.setLastStatus(currentStatus); } } // 구역 상태를 체크 해주는 메서드 @Transactional public String getCurrentStatus(Child foundChild) { double[] childPosition = {foundChild.getLatitude(), foundChild.getLongitude()}; // 각 구역을 사각형으로 설정하고, 각 점을 이용해 구역 내부에 있는지 확인 ArrayList<Coordinate> coordinateArrayList = coordinateRepository.findAllByChild(foundChild); for (Coordinate coordinate : coordinateArrayList) { double[][] polygon = { {coordinate.getYOfNorthWest(), coordinate.getXOfNorthWest()}, {coordinate.getYOfNorthEast(), coordinate.getXOfNorthEast()}, {coordinate.getYOfSouthEast(), coordinate.getXOfSouthEast()}, {coordinate.getYOfSouthWest(), coordinate.getXOfSouthWest()} }; if (coordinate.isLivingArea()) { if (isPointInPolygon(polygon, childPosition)) { // 안전 구역 내부에 있는 경우 return "안전구역"; } } else { if (isPointInPolygon(polygon, childPosition)) { // 위험 구역 내부에 있는 경우 return "위험구역"; } } } return "일반구역"; } // 구역 내부에 있는지 확인하는 메서드 public static boolean isPointInPolygon(double[][] polygon, double[] point) { int n = polygon.length; double px = point[0], py = point[1]; boolean inside = false; for (int i = 0, j = n - 1; i < n; j = i++) { double ix = polygon[i][0], iy = polygon[i][1]; double jx = polygon[j][0], jy = polygon[j][1]; if ((iy > py) != (jy > py) && (px < (jx - ix) * (py - iy) / (jy - iy) + ix)) { inside = !inside; } } return inside; } @Transactional public boolean sendNoticeToMember(List<Parenting> parentingList, String childName, NoticeLevel noticeLevel) { for (Parenting parenting : parentingList) { Notice notice = noticeService .createNotice( parenting.getParent().getMemberId(), childName, noticeLevel); noticeService.sendNotificationTo(notice); } return true; } public boolean sendNotificationTo(Notice notice){ FCMNotificationDTO message = makeMessage(notice); // SendNotificationByToken는 이전 포스트에 있습니다. return fcmService.SendNotificationByToken(message) != null; }
Notice 부분에서 아이의 위치를 DB에 저장하는 건 너무 많은 오버헤드를 가집니다.
실제로 앱이 상용화 되려면 수정되어야 할 부분입니다.
분량 조절 실패로 다음 포스트에서 나머지 알림 기능에 대한 로직이 이어집니다…