Skip to content

Commit

Permalink
Merge pull request #205 from PawWithU/feat/204-scheduling-notification
Browse files Browse the repository at this point in the history
  • Loading branch information
kyeong-hyeok authored May 21, 2024
2 parents 95128b3 + 0fdcfd5 commit fcabdd2
Show file tree
Hide file tree
Showing 10 changed files with 315 additions and 36 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import com.querydsl.core.Tuple;
import org.springframework.data.domain.Pageable;

import java.time.LocalDate;
import java.util.List;
import java.util.Optional;

Expand All @@ -21,4 +22,7 @@ public interface CustomApplicationRepository {
List<ApplicationIntermediaryCompletedResponse> getIntermediaryCompletedApplications(Long intermediaryId, Pageable pageable);
List<Tuple> getCountOfApplicationsByStatus(Long id);
boolean existsByPostIdAndPostStatus(Long postId);
void updateExpiredApplications(LocalDate today);
List<Application> getYesterdayExpiredApplications(LocalDate date);
List<Application> getExpiredProgressingPosts(LocalDate date);
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,17 @@
import com.pawwithu.connectdog.domain.application.entity.Application;
import com.pawwithu.connectdog.domain.application.entity.ApplicationStatus;
import com.pawwithu.connectdog.domain.application.repository.CustomApplicationRepository;
import com.pawwithu.connectdog.domain.post.entity.Post;
import com.querydsl.core.Tuple;
import com.querydsl.core.types.Projections;
import com.querydsl.jpa.impl.JPAQueryFactory;
import jakarta.persistence.EntityManager;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.domain.Pageable;
import org.springframework.stereotype.Repository;

import java.time.LocalDate;
import java.util.List;
import java.util.Optional;

Expand All @@ -30,6 +33,7 @@
public class CustomApplicationRepositoryImpl implements CustomApplicationRepository {

private final JPAQueryFactory queryFactory;
private final EntityManager em;

@Override
public List<ApplicationVolunteerWaitingResponse> getVolunteerWaitingApplications(Long volunteerId, Pageable pageable) {
Expand Down Expand Up @@ -191,4 +195,36 @@ public boolean existsByPostIdAndPostStatus(Long postId) {
.fetchOne() != null;
}

// 어제 모집 마감된 신청 가져오기
@Override
public List<Application> getYesterdayExpiredApplications(LocalDate date) {
return queryFactory.selectFrom(application)
.where(application.status.eq(ApplicationStatus.REJECTED)
.and(application.post.endDate.eq(date)))
.fetch();
}

// 모집 마감 시 신청 자동 반려
@Override
public void updateExpiredApplications(LocalDate date) {
queryFactory.update(application)
.set(application.status, ApplicationStatus.REJECTED)
.where(application.status.eq(ApplicationStatus.WAITING)
.and(application.post.endDate.before(date)))
.execute();

em.flush();
em.clear();
}

// 어제 일정이 종료된 진행중인 봉사 신청 가져오기
@Override
public List<Application> getExpiredProgressingPosts(LocalDate date) {
return queryFactory.selectFrom(application)
.join(application.post, post)
.where(application.status.eq(ApplicationStatus.PROGRESSING)
.and(post.endDate.eq(date)))
.fetch();
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,15 @@ public enum NotificationMessage {
CONFIRM("이동봉사 승인", "이동봉사가 승인되었어요🎉\n모집자의 연락을 기다려 주세요!"),
REJECT("이동봉사 반려", "이동봉사가 반려되었어요😥\n다른 이동봉사를 찾아볼까요?"),
COMPLETED("이동봉사 완료", "이동봉사 진행이 완료되었어요🐾\n소중한 후기를 들려주세요!"),
EXPIRED_REJECT("이동봉사 반려", "모집 기간이 마감되어 이동봉사가 반려되었어요😥"),

// 모집자
APPLICATION("이동봉사 신청", "님이 이동봉사를 신청하셨어요.\n지금 확인해 보세요!"),
CANCELED("이동봉사 신청 취소", "님이 이동봉사를 취소하셨어요.\n해당 공고는 모집중 상태로 변경됩니다."),
REVIEW_REGISTERED("이동봉사 후기 등록", "봉사 후기가 등록되었습니다.\n지금 확인해 보세요!"),
EXPIRED("이동봉사 모집 기간 만료", "모집 기간 만료로 공고가 마감되었습니다.\n아직 봉사자를 구하지 못했다면 기간을 조정해 보세요!"),
COMPLETED_REQUEST("이동봉사 진행 완료", "이동봉사 진행이 완료되었나요?\n봉사 완료 버튼을 눌러주세요!");
EXPIRED("이동봉사 모집 기간 만료", "모집 기간 만료로 공고가 마감되었습니다."),
COMPLETED_REQUEST("이동봉사 진행 완료", "이동봉사 진행이 완료되었나요?\n봉사 완료 버튼을 눌러주세요!"),
BEFORE_EXPIRED("공고 마감", "공고 마감이 12시간 남았어요.");

private final String title;
private final String body;
Expand All @@ -30,4 +32,8 @@ public String getBodyWithName(String nickname) {
return nickname + body;
}

public String getBodyWithContent(String content) {
return body + content;
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,11 @@ public enum NotificationType {

// 봉사자
REJECTED("반려 확인"), CONFIRMED("승인 확인"), COMPLETED("봉사 완료"), BADGE("배지 확득"),
GUIDE("이동봉사 시작하기"),

// 모집자
APPLICATION("신청 확인"), CANCELED("봉사 취소"), REVIEW_REGISTERED("후기 확인"), EXPIRED("모집 마감"), COMPLETED_REQUEST("봉사 완료 요청");
APPLICATION("신청 확인"), CANCELED("봉사 취소"), REVIEW_REGISTERED("후기 확인"), EXPIRED("모집 마감"),
COMPLETED_REQUEST("이동봉사 진행 완료"), BEFORE_EXPIRED("공고 마감");

private final String key;
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,11 @@
import com.pawwithu.connectdog.domain.intermediary.dto.response.IntermediaryGetPostsResponse;
import com.pawwithu.connectdog.domain.post.dto.request.PostSearchRequest;
import com.pawwithu.connectdog.domain.post.dto.response.*;
import com.pawwithu.connectdog.domain.post.entity.Post;
import com.pawwithu.connectdog.domain.post.entity.PostStatus;
import org.springframework.data.domain.Pageable;

import java.time.LocalDate;
import java.util.List;
import java.util.Map;

Expand All @@ -25,4 +27,8 @@ public interface CustomPostRepository {
Long getCountOfCompletedPosts(Long intermediaryId);
PostIntermediaryGetOneResponse getIntermediaryOnePost(Long postId);
Map<PostStatus, Long> getCountOfPostStatus(Long intermediaryId, PostStatus status);
void updateExpiredPosts(LocalDate date);
List<Post> getBeforeExpiredRecruitingPosts(LocalDate date);
List<Post> getBeforeExpiredWaitingPosts(LocalDate date);
List<Post> getYesterdayExpiredPosts(LocalDate yesterday);
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,15 @@
import com.pawwithu.connectdog.domain.intermediary.dto.response.IntermediaryGetPostsResponse;
import com.pawwithu.connectdog.domain.post.dto.request.PostSearchRequest;
import com.pawwithu.connectdog.domain.post.dto.response.*;
import com.pawwithu.connectdog.domain.post.entity.Post;
import com.pawwithu.connectdog.domain.post.entity.PostStatus;
import com.pawwithu.connectdog.domain.post.repository.CustomPostRepository;
import com.querydsl.core.types.Order;
import com.querydsl.core.types.OrderSpecifier;
import com.querydsl.core.types.Projections;
import com.querydsl.core.types.dsl.BooleanExpression;
import com.querydsl.jpa.impl.JPAQueryFactory;
import jakarta.persistence.EntityManager;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.domain.Pageable;
Expand All @@ -33,6 +35,7 @@
public class CustomPostRepositoryImpl implements CustomPostRepository {

private final JPAQueryFactory queryFactory;
private final EntityManager em;

// 홈 화면 공고 6개 조회
@Override
Expand Down Expand Up @@ -264,4 +267,43 @@ private OrderSpecifier[] createOrderSpecifierCE(String orderCondition) {
new OrderSpecifier(Order.DESC, post.createdDate)}
: defaultOrder;
}

// 모집 마감 공고 업데이트
@Override
public void updateExpiredPosts(LocalDate date) {
queryFactory.update(post)
.set(post.status, PostStatus.EXPIRED)
.where(post.status.in(PostStatus.RECRUITING, PostStatus.WAITING)
.and(post.endDate.before(date)))
.execute();

em.flush();
em.clear();
}

// 모집 마감 하루 전 모집중 공고 가져오기
@Override
public List<Post> getBeforeExpiredRecruitingPosts(LocalDate date) {
return queryFactory.selectFrom(post)
.where(post.status.eq(PostStatus.RECRUITING)
.and(post.endDate.eq(date)))
.fetch();
}

// 모집 마감 하루 전 승인대기중 공고 가져오기
@Override
public List<Post> getBeforeExpiredWaitingPosts(LocalDate date) {
return queryFactory.selectFrom(post)
.where(post.status.eq(PostStatus.WAITING)
.and(post.endDate.eq(date)))
.fetch();
}

@Override
public List<Post> getYesterdayExpiredPosts(LocalDate date) {
return queryFactory.selectFrom(post)
.where(post.status.eq(PostStatus.EXPIRED)
.and(post.endDate.eq(date)))
.fetch();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
package com.pawwithu.connectdog.domain.scheduler;

import com.pawwithu.connectdog.domain.application.entity.Application;
import com.pawwithu.connectdog.domain.application.repository.CustomApplicationRepository;
import com.pawwithu.connectdog.domain.fcm.entity.IntermediaryFcm;
import com.pawwithu.connectdog.domain.fcm.entity.VolunteerFcm;
import com.pawwithu.connectdog.domain.fcm.repository.IntermediaryFcmRepository;
import com.pawwithu.connectdog.domain.fcm.repository.VolunteerFcmRepository;
import com.pawwithu.connectdog.domain.fcm.service.FcmService;
import com.pawwithu.connectdog.domain.notification.entity.NotificationType;
import com.pawwithu.connectdog.domain.post.entity.Post;
import com.pawwithu.connectdog.domain.post.repository.CustomPostRepository;
import com.pawwithu.connectdog.domain.volunteer.entity.Volunteer;
import com.pawwithu.connectdog.domain.volunteer.repository.CustomVolunteerRepository;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.time.LocalDate;
import java.util.List;

import static com.pawwithu.connectdog.domain.fcm.dto.NotificationMessage.*;

@Slf4j
@Service
@Transactional
@RequiredArgsConstructor
public class SchedulerService {

private final CustomPostRepository customPostRepository;
private final CustomApplicationRepository customApplicationRepository;
private final FcmService fcmService;
private final VolunteerFcmRepository volunteerFcmRepository;
private final IntermediaryFcmRepository intermediaryFcmRepository;
private final CustomVolunteerRepository customVolunteerRepository;

// 매일 00시 - 공고 모집 마감 업데이트, 신청 자동 반려
@Scheduled(cron = "0 0 0 * * *")
public void updateExpiredPostsAndApplications() {
LocalDate today = LocalDate.now();
// 공고 모집 마감 업데이트
customPostRepository.updateExpiredPosts(today);
// 신청 자동 반려
customApplicationRepository.updateExpiredApplications(today);
}

// 매일 9시 - 모집 마감된 공고를 신청했던 봉사자에게 반려 알림 전송 및 모집자에게 공고 모집 기간 만료 알림 전송
@Scheduled(cron = "0 0 9 * * *")
public void sendRejectNotification() {
LocalDate today = LocalDate.now();
LocalDate yesterday = today.minusDays(1);
// 모집 마감된 공고를 신청했던 봉사자에게 반려 알림 전송
List<Application> applications = customApplicationRepository.getYesterdayExpiredApplications(yesterday);
for (Application application : applications) {
VolunteerFcm volunteerFcm = volunteerFcmRepository.findByVolunteerId(application.getVolunteer().getId()).orElse(null);
if (volunteerFcm != null) {
fcmService.sendMessageToVolunteer(volunteerFcm.getFcmToken(), application.getVolunteer(),
application.getPost().getMainImage().getImage(), NotificationType.REJECTED, EXPIRED_REJECT.getTitle(), EXPIRED_REJECT.getBody());
} else {
log.info("----------모집 마감 공고 신청 반려 알림 전송 실패----------");
}
}
// 모집자에게 모집 기간 만료 알림 전송
List<Post> posts = customPostRepository.getYesterdayExpiredPosts(yesterday);
for (Post post : posts) {
IntermediaryFcm intermediaryFcm = intermediaryFcmRepository.findByIntermediaryId(post.getIntermediary().getId()).orElse(null);
if (intermediaryFcm != null) {
fcmService.sendMessageToIntermediary(intermediaryFcm.getFcmToken(), post.getIntermediary(), post.getMainImage().getImage(),
NotificationType.EXPIRED, EXPIRED.getTitle(), EXPIRED.getBody());
} else {
log.info("----------공고 마감 사전 알림 전송 실패----------");
}
}

}

// 매일 오후 12시 - 공고 모집 마감 알림 12시간 전 알림
@Scheduled(cron = "0 0 12 * * *")
public void sendBeforeExpiredNotification() {
LocalDate today = LocalDate.now();
// 모집 마감 하루 전 모집중 공고 알림 전송
List<Post> recruitingPosts = customPostRepository.getBeforeExpiredRecruitingPosts(today);
for (Post post : recruitingPosts) {
IntermediaryFcm intermediaryFcm = intermediaryFcmRepository.findByIntermediaryId(post.getIntermediary().getId()).orElse(null);
if (intermediaryFcm != null) {
fcmService.sendMessageToIntermediary(intermediaryFcm.getFcmToken(), post.getIntermediary(), post.getMainImage().getImage(),
NotificationType.BEFORE_EXPIRED, BEFORE_EXPIRED.getTitle(), BEFORE_EXPIRED.getBodyWithContent(" 아직 봉사자를 구하지 못했다면 기간을 조정해보세요!"));
} else {
log.info("----------공고 마감 사전 알림 전송 실패----------");
}
}
// 모집 마감 하루 전 승인대기중 공고 알림 전송
List<Post> waitingPosts = customPostRepository.getBeforeExpiredWaitingPosts(today);
for (Post post : waitingPosts) {
IntermediaryFcm intermediaryFcm = intermediaryFcmRepository.findByIntermediaryId(post.getIntermediary().getId()).orElse(null);
if (intermediaryFcm != null) {
fcmService.sendMessageToIntermediary(intermediaryFcm.getFcmToken(), post.getIntermediary(), post.getMainImage().getImage(),
NotificationType.BEFORE_EXPIRED, BEFORE_EXPIRED.getTitle(), BEFORE_EXPIRED.getBodyWithContent(" 신청자가 있으니 빠르게 확인해주세요!"));
} else {
log.info("----------공고 마감 사전 알림 전송 실패----------");
}
}
}

// 매일 오전 10시 - 이동봉사 진행 완료 요청 알림
@Scheduled(cron = "0 0 10 * * *")
public void sendCompleteRequestNotification() {
LocalDate today = LocalDate.now();
LocalDate yesterday = today.minusDays(1);
List<Application> applications = customApplicationRepository.getExpiredProgressingPosts(yesterday);
for (Application application : applications) {
IntermediaryFcm intermediaryFcm = intermediaryFcmRepository.findByIntermediaryId(application.getPost().getIntermediary().getId()).orElse(null);
if (intermediaryFcm != null) {
fcmService.sendMessageToIntermediary(intermediaryFcm.getFcmToken(), application.getIntermediary(), application.getPost().getMainImage().getImage(),
NotificationType.COMPLETED_REQUEST, COMPLETED_REQUEST.getTitle(), COMPLETED_REQUEST.getBody());
} else {
log.info("----------이동봉사 진행 완료 요청 알림 전송 실패----------");
}
}
}

// 매일 15시 - 이동봉사 가이드 알림
@Scheduled(cron = "0 0 15 * * *")
public void sendGuideNotification() {
LocalDate today = LocalDate.now();
LocalDate yesterday = today.minusDays(1);
List<Volunteer> volunteers = customVolunteerRepository.getYesterdaySignUpVolunteers(yesterday);
for (Volunteer volunteer : volunteers) {
VolunteerFcm volunteerFcm = volunteerFcmRepository.findByVolunteerId(volunteer.getId()).orElse(null);
if (volunteerFcm != null) {
fcmService.sendMessageToVolunteer(volunteerFcm.getFcmToken(), volunteer,
volunteer.getProfileImageNum() + "", NotificationType.GUIDE, GUIDE.getTitle(), GUIDE.getBody());
} else {
log.info("----------이동봉사 가이드 알림 전송 실패----------");
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package com.pawwithu.connectdog.domain.volunteer.repository;

import com.pawwithu.connectdog.domain.volunteer.entity.Volunteer;

import java.time.LocalDate;
import java.util.List;

public interface CustomVolunteerRepository {

List<Volunteer> getYesterdaySignUpVolunteers(LocalDate date);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package com.pawwithu.connectdog.domain.volunteer.repository.impl;

import com.pawwithu.connectdog.domain.volunteer.entity.Volunteer;
import com.pawwithu.connectdog.domain.volunteer.repository.CustomVolunteerRepository;
import com.querydsl.jpa.impl.JPAQueryFactory;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Repository;

import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.util.List;

import static com.pawwithu.connectdog.domain.volunteer.entity.QVolunteer.volunteer;

@Repository
@RequiredArgsConstructor
@Slf4j
public class CustomVolunteerRepositoryImpl implements CustomVolunteerRepository {

private final JPAQueryFactory queryFactory;

@Override
public List<Volunteer> getYesterdaySignUpVolunteers(LocalDate date) {
LocalDateTime startOfDay = date.atStartOfDay();
LocalDateTime endOfDay = date.atTime(LocalTime.MAX);
return queryFactory.selectFrom(volunteer)
.where(volunteer.createdDate.between(startOfDay, endOfDay))
.fetch();
}
}
Loading

0 comments on commit fcabdd2

Please sign in to comment.