diff --git a/LearnsMate/src/main/java/intbyte4/learnsmate/admin/service/EmailService.java b/LearnsMate/src/main/java/intbyte4/learnsmate/admin/service/EmailService.java index cc8c574b..b57274ac 100644 --- a/LearnsMate/src/main/java/intbyte4/learnsmate/admin/service/EmailService.java +++ b/LearnsMate/src/main/java/intbyte4/learnsmate/admin/service/EmailService.java @@ -26,14 +26,13 @@ public EmailService(StringRedisTemplate stringRedisTemplate, JavaMailSender mail private final long VERIFICATION_CODE_TTL = 5; // 5분 + // 인증 코드 발송 메소드 public void sendVerificationEmail(String email) { - log.info("start sending verification email"); + log.info("Start sending verification email"); String verificationCode = generateVerificationCode(); - SimpleMailMessage message = new SimpleMailMessage(); - message.setTo(email); - message.setSubject("[LearnsMate] 이메일 인증 코드 안내"); - message.setText("안녕하세요,\n\n" + String subject = "[LearnsMate] 이메일 인증 코드 안내"; + String content = "안녕하세요,\n\n" + "LearnsMate를 이용해 주셔서 진심으로 감사드립니다.\n" + "아래의 인증 코드를 입력하여 이메일 인증을 완료해 주시기 바랍니다.\n\n" + "인증 코드: " + verificationCode + "\n\n" @@ -41,13 +40,40 @@ public void sendVerificationEmail(String email) { + "본 이메일은 LearnsMate 서비스 이용과 관련하여 발송되었습니다. " + "궁금하신 사항이 있으시면 LearnsMate 고객 지원팀으로 언제든 문의해 주시기 바랍니다.\n\n" + "감사합니다.\n\n" - + "LearnsMate 드림"); - - mailSender.send(message); + + "LearnsMate 드림"; + sendEmail(email, subject, content); saveVerificationCode(email, verificationCode); } + // 캠페인 이메일 발송 메소드 추가 + public void sendCampaignEmail(String email, String campaignTitle, String campaignContents) { + log.info("Start sending campaign email to: " + email); + + String subject = "[LearnsMate]: " + campaignTitle; + String content = campaignContents + "\n\n" + + "더 자세한 사항은 LearnsMate 홈페이지를 방문해 주세요.\n\n" + + "감사합니다.\n\n" + + "LearnsMate 드림"; + + sendEmail(email, subject, content); + } + + // 이메일 발송 공통 메소드 + private void sendEmail(String to, String subject, String content) { + SimpleMailMessage message = new SimpleMailMessage(); + message.setTo(to); + message.setSubject(subject); + message.setText(content); + + try { + mailSender.send(message); + log.info("Email sent successfully to: " + to); + } catch (Exception e) { + log.error("Failed to send email to: " + to + ", error: " + e.getMessage()); + } + } + //필기. 해당 이메일의 코드가 일치하는지 확인하는 코드 public boolean verifyCode(String email, String code) { String savedCode = stringRedisTemplate.opsForValue().get(email); //필기. Redis에서 코드 가져오기 diff --git a/LearnsMate/src/main/java/intbyte4/learnsmate/campaign/domain/entity/Campaign.java b/LearnsMate/src/main/java/intbyte4/learnsmate/campaign/domain/entity/Campaign.java index 28daabcb..5bc494ea 100644 --- a/LearnsMate/src/main/java/intbyte4/learnsmate/campaign/domain/entity/Campaign.java +++ b/LearnsMate/src/main/java/intbyte4/learnsmate/campaign/domain/entity/Campaign.java @@ -35,6 +35,8 @@ public class Campaign { @JsonFormat(pattern = "yyyy-MM-dd'T'HH:mm") private LocalDateTime campaignSendDate; + @Column(name="campaign_send_flag" , nullable = true) + private Boolean campaignSendFlag; @Column(name = "created_at") @CreationTimestamp @@ -47,4 +49,15 @@ public class Campaign { @JoinColumn(name = "admin_code", nullable = false) private Admin admin; + // 엔티티가 처음 저장되기 전에 실행되어 기본값을 설정해 줌 + @PrePersist + public void prePersist() { + if (campaignSendFlag == null) { + campaignSendFlag = false; // 기본값 설정 + } + } + + public void updateSendFlag() { + this.campaignSendFlag = true; + } } diff --git a/LearnsMate/src/main/java/intbyte4/learnsmate/campaign/repository/CampaignRepository.java b/LearnsMate/src/main/java/intbyte4/learnsmate/campaign/repository/CampaignRepository.java index 85d60e07..c75ba85d 100644 --- a/LearnsMate/src/main/java/intbyte4/learnsmate/campaign/repository/CampaignRepository.java +++ b/LearnsMate/src/main/java/intbyte4/learnsmate/campaign/repository/CampaignRepository.java @@ -1,6 +1,7 @@ package intbyte4.learnsmate.campaign.repository; import intbyte4.learnsmate.campaign.domain.entity.Campaign; +import intbyte4.learnsmate.campaign.domain.entity.CampaignTypeEnum; import org.springframework.context.annotation.Primary; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.stereotype.Repository; @@ -13,5 +14,6 @@ @Primary public interface CampaignRepository extends JpaRepository, CampaignRepositoryCustom { - List findByCampaignSendDateLessThanEqual(LocalDateTime currentTime); + // campaign_type이 RESERVATION 이고 lessthan이고 flag가 false인 것들에 한해서 + List findByCampaignSendDateLessThanEqualAndCampaignSendFlagFalseAndCampaignType(LocalDateTime currentTime, CampaignTypeEnum campaignTypeEnum); } diff --git a/LearnsMate/src/main/java/intbyte4/learnsmate/campaign/service/CampaignService.java b/LearnsMate/src/main/java/intbyte4/learnsmate/campaign/service/CampaignService.java index 5965e3a4..f1629fb7 100644 --- a/LearnsMate/src/main/java/intbyte4/learnsmate/campaign/service/CampaignService.java +++ b/LearnsMate/src/main/java/intbyte4/learnsmate/campaign/service/CampaignService.java @@ -32,4 +32,6 @@ CampaignDTO editCampaign(CampaignDTO requestCampaign (CampaignFilterDTO request, int page, int size); List findCampaignListByConditionWithExcel(CampaignFilterDTO filterDTO); List findAllCampaignListWithExcel(); + + void updateCampaignSendFlag(Long campaignCode); } diff --git a/LearnsMate/src/main/java/intbyte4/learnsmate/campaign/service/CampaignServiceImpl.java b/LearnsMate/src/main/java/intbyte4/learnsmate/campaign/service/CampaignServiceImpl.java index 00ba6907..67790a75 100644 --- a/LearnsMate/src/main/java/intbyte4/learnsmate/campaign/service/CampaignServiceImpl.java +++ b/LearnsMate/src/main/java/intbyte4/learnsmate/campaign/service/CampaignServiceImpl.java @@ -4,6 +4,7 @@ import intbyte4.learnsmate.admin.domain.entity.Admin; import intbyte4.learnsmate.admin.mapper.AdminMapper; import intbyte4.learnsmate.admin.service.AdminService; +import intbyte4.learnsmate.admin.service.EmailService; import intbyte4.learnsmate.campaign.batch.CampaignIssueCouponReader; import intbyte4.learnsmate.campaign.domain.dto.*; import intbyte4.learnsmate.campaign.domain.entity.Campaign; @@ -55,6 +56,7 @@ public class CampaignServiceImpl implements CampaignService { private final MemberService memberService; private final CouponService couponService; private final IssueCouponService issueCouponService; + private final EmailService emailService; private final CampaignMapper campaignMapper; private final AdminMapper adminMapper; private final JobLauncher jobLauncher; @@ -114,8 +116,11 @@ else if (foundCoupon.getTutorCode() != null) { if (Objects.equals(requestCampaign.getCampaignType(), CampaignTypeEnum.INSTANT.getType())) { // 즉시 발송 issueCouponService.issueCouponsToStudents(studentCodes, couponCodes); + requestStudentList.forEach(memberDTO -> { + emailService.sendCampaignEmail(memberDTO.getMemberEmail(), campaign.getCampaignTitle(), campaign.getCampaignContents()); + }); } else { - // 예약 발송은 스케줄러에 등록 아직 + // 예약 발송은 스케줄러에 등록 registerScheduledCampaign(requestStudentList, requestCouponList, savedCampaignDTO.getCampaignSendDate()); } @@ -149,7 +154,8 @@ public void registerScheduledCampaign(List studentList, List getReadyCampaigns(LocalDateTime currentTime) { // 예약 시간이 현재 시간보다 이전이면서 아직 발송되지 않은 캠페인 조회 - List readyCampaigns = campaignRepository.findByCampaignSendDateLessThanEqual(currentTime); + List readyCampaigns = campaignRepository + .findByCampaignSendDateLessThanEqualAndCampaignSendFlagFalseAndCampaignType(currentTime, CampaignTypeEnum.RESERVATION); return readyCampaigns.stream() .map(campaignMapper::toDTO) .toList(); @@ -169,9 +175,9 @@ public CampaignDTO editCampaign(CampaignDTO requestCampaign AdminDTO adminDTO = adminService.findByAdminCode(requestCampaign.getAdminCode()); Admin admin = adminMapper.toEntity(adminDTO); - log.info("서비스에서 조회하는 adminDTO: {}", adminDTO); + Campaign updatedCampaign = campaignMapper.toEntity(requestCampaign, admin); - log.info("서비스에서 조회하는 toEntity(requestCampaign,admin): {}", updatedCampaign); + campaignRepository.save(updatedCampaign); editStudent(requestCampaign, requestStudentList, updatedCampaign); @@ -209,9 +215,7 @@ private void editCoupon(CampaignDTO requestCampaign private void editStudent(CampaignDTO requestCampaign , List requestStudentList , Campaign updatedCampaign) { - log.info("editStuden메서드의 requestCampaign: {}", requestCampaign); - log.info("editStuden메서드의 requestStudentList: {}", requestStudentList); - log.info("editStuden메서드의 updatedCampaign: {}", updatedCampaign); + List existingStudentList = userPerCampaignService .findByCampaignCode(updatedCampaign); @@ -329,4 +333,15 @@ public List findAllCampaignListWithExcel() { return findAllCampaignsDTOList; } + + @Override + public void updateCampaignSendFlag(Long campaignCode) { + Campaign campaign = campaignRepository.findById(campaignCode) + .orElseThrow(() -> new CommonException(StatusEnum.CAMPAIGN_NOT_FOUND)); + + campaign.updateSendFlag(); + + campaignRepository.save(campaign); + } + } diff --git a/LearnsMate/src/main/java/intbyte4/learnsmate/common/config/SchedulerConfig.java b/LearnsMate/src/main/java/intbyte4/learnsmate/common/config/SchedulerConfig.java index 227f619f..ee12f994 100644 --- a/LearnsMate/src/main/java/intbyte4/learnsmate/common/config/SchedulerConfig.java +++ b/LearnsMate/src/main/java/intbyte4/learnsmate/common/config/SchedulerConfig.java @@ -1,8 +1,15 @@ package intbyte4.learnsmate.common.config; +import intbyte4.learnsmate.admin.service.EmailService; import intbyte4.learnsmate.campaign.domain.dto.CampaignDTO; import intbyte4.learnsmate.campaign.service.CampaignService; +import intbyte4.learnsmate.member.domain.MemberType; +import intbyte4.learnsmate.member.domain.dto.MemberDTO; +import intbyte4.learnsmate.member.service.MemberService; +import intbyte4.learnsmate.userpercampaign.domain.dto.UserPerCampaignDTO; +import intbyte4.learnsmate.userpercampaign.service.UserPerCampaignService; import intbyte4.learnsmate.voc.service.VOCAiService; +import lombok.extern.slf4j.Slf4j; import org.springframework.batch.core.Job; import org.springframework.batch.core.JobParameters; import org.springframework.batch.core.JobParametersBuilder; @@ -17,18 +24,25 @@ @Configuration @EnableScheduling +@Slf4j public class SchedulerConfig { private final VOCAiService vocAiService; private final CampaignService campaignService; private final JobLauncher jobLauncher; private final Job campaignJob; + private final MemberService memberService; + private final EmailService emailService; + private final UserPerCampaignService userPerCampaignService; - public SchedulerConfig(VOCAiService vocAiService, CampaignService campaignService, JobLauncher jobLauncher, Job campaignJob) { + public SchedulerConfig(VOCAiService vocAiService, CampaignService campaignService, JobLauncher jobLauncher, Job campaignJob, MemberService memberService, EmailService emailService , UserPerCampaignService userPerCampaignService) { this.vocAiService = vocAiService; this.campaignService = campaignService; this.jobLauncher = jobLauncher; this.campaignJob = campaignJob; + this.memberService = memberService; + this.emailService = emailService; + this.userPerCampaignService = userPerCampaignService; } @Scheduled(cron = "0 0 9 * * MON") @@ -46,6 +60,15 @@ public void scheduleCampaigns() { .addDate("startTime", new Date()) .toJobParameters(); jobLauncher.run(campaignJob, jobParameters); + + List userPerCampaignDTOList = userPerCampaignService.findUserByCampaignCode(campaign.getCampaignCode()); + for (UserPerCampaignDTO userPerCampaignDTO : userPerCampaignDTOList) { + MemberDTO member = memberService.findMemberByMemberCode(userPerCampaignDTO.getStudentCode(), MemberType.STUDENT); + if (member != null) { + emailService.sendCampaignEmail(member.getMemberEmail(), campaign.getCampaignTitle(), campaign.getCampaignContents()); + } + } + campaignService.updateCampaignSendFlag(campaign.getCampaignCode()); } catch (Exception e) { System.err.println("Failed to launch campaign job: " + e.getMessage()); } diff --git a/LearnsMate/src/main/java/intbyte4/learnsmate/userpercampaign/repository/UserPerCampaignRepository.java b/LearnsMate/src/main/java/intbyte4/learnsmate/userpercampaign/repository/UserPerCampaignRepository.java index c6713661..027193b0 100644 --- a/LearnsMate/src/main/java/intbyte4/learnsmate/userpercampaign/repository/UserPerCampaignRepository.java +++ b/LearnsMate/src/main/java/intbyte4/learnsmate/userpercampaign/repository/UserPerCampaignRepository.java @@ -1,8 +1,11 @@ package intbyte4.learnsmate.userpercampaign.repository; import intbyte4.learnsmate.campaign.domain.entity.Campaign; +import intbyte4.learnsmate.userpercampaign.domain.dto.UserPerCampaignDTO; import intbyte4.learnsmate.userpercampaign.domain.entity.UserPerCampaign; import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; import org.springframework.stereotype.Repository; import java.util.List; @@ -12,4 +15,8 @@ public interface UserPerCampaignRepository extends JpaRepository findByCampaign(Campaign campaign); void deleteByCampaign_CampaignCode(Long campaignCode); + + @Query("SELECT new intbyte4.learnsmate.userpercampaign.domain.dto.UserPerCampaignDTO(u.userPerCampaignCode, u.campaign.campaignCode, u.student.memberCode) " + + "FROM userPerCampaign u WHERE u.campaign.campaignCode = :campaignCode") + List findStudentsByCampaignCode(@Param("campaignCode") Long campaignCode); } diff --git a/LearnsMate/src/main/java/intbyte4/learnsmate/userpercampaign/service/UserPerCampaignService.java b/LearnsMate/src/main/java/intbyte4/learnsmate/userpercampaign/service/UserPerCampaignService.java index 21610dcd..d7bc897a 100644 --- a/LearnsMate/src/main/java/intbyte4/learnsmate/userpercampaign/service/UserPerCampaignService.java +++ b/LearnsMate/src/main/java/intbyte4/learnsmate/userpercampaign/service/UserPerCampaignService.java @@ -12,4 +12,5 @@ public interface UserPerCampaignService { List findByCampaignCode(Campaign campaign); void removeUserPerCampaign(Long userPerCampaignCode); void removeByCampaignCode(Long campaignCode); + List findUserByCampaignCode(Long campaignCode); } diff --git a/LearnsMate/src/main/java/intbyte4/learnsmate/userpercampaign/service/UserPerCampaignServiceImpl.java b/LearnsMate/src/main/java/intbyte4/learnsmate/userpercampaign/service/UserPerCampaignServiceImpl.java index 38b97378..7a58a388 100644 --- a/LearnsMate/src/main/java/intbyte4/learnsmate/userpercampaign/service/UserPerCampaignServiceImpl.java +++ b/LearnsMate/src/main/java/intbyte4/learnsmate/userpercampaign/service/UserPerCampaignServiceImpl.java @@ -78,4 +78,9 @@ public void removeUserPerCampaign(Long userPerCampaignCode) { public void removeByCampaignCode(Long campaignCode) { userPerCampaignRepository.deleteByCampaign_CampaignCode(campaignCode); } + + @Override + public List findUserByCampaignCode(Long campaignCode) { + return userPerCampaignRepository.findStudentsByCampaignCode(campaignCode); + } }