Skip to content

Commit

Permalink
Merge pull request #53 from studio-recoding/feat-email-send
Browse files Browse the repository at this point in the history
[🚀feat] 이메일 전송 스케쥴링 구현(템플릿 수정 완료, 테스트 완료)
  • Loading branch information
JeonHaeseung authored May 2, 2024
2 parents 089dab4 + 14e8170 commit 7a665aa
Show file tree
Hide file tree
Showing 9 changed files with 152 additions and 45 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,8 @@ public OAuth2User loadUser(OAuth2UserRequest oAuth2UserRequest) throws OAuth2Aut
Member member;
/*이메일로 회원 가입 여부 확인*/
if (!memberRepository.existsByEmail(email) && !Objects.equals(password, DEFAULT_STRING)) {
memberService.createMember(email, password, picture, nickname, name);
// 이메일 알림 기능은 디폴트로 false
memberService.createMember(email, password, picture, nickname, name, false);
}
member = memberRepository.findMemberByEmail(email);

Expand Down
25 changes: 0 additions & 25 deletions src/main/java/Ness/Backend/domain/email/EmailController.java

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;

import java.util.List;

@Repository
public interface MemberRepository extends JpaRepository<Member, Long> {
// 특정 맴버 ID로 맴버 엔티티 반환
Expand All @@ -13,4 +15,7 @@ public interface MemberRepository extends JpaRepository<Member, Long> {
Member findMemberByEmail(String email);

boolean existsByEmail(String email);

// 이메일 전송 기능 활성화된 멤버 반환
List<Member> findMembersByProfileIsEmailActive(Boolean isActive);
}
3 changes: 2 additions & 1 deletion src/main/java/Ness/Backend/domain/member/MemberService.java
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ public void deleteMember(Member member) {
memberRepository.save(member);
}

public Member createMember(String email, String password, String picture, String nickname, String name) {
public Member createMember(String email, String password, String picture, String nickname, String name, Boolean isEmailActive) {
Member member = Member.builder()
.email(email)
.password(bCryptPasswordEncoder.encode(password)) //비밀번호는 해싱해서 DB에 저장
Expand All @@ -33,6 +33,7 @@ public Member createMember(String email, String password, String picture, String
.nickname(nickname)
.name(name)
.member(member)
.isEmailActive(isEmailActive)
.build();

profileRepository.save(profile);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package Ness.Backend.domain.profile.email;

import Ness.Backend.domain.member.entity.Member;
import Ness.Backend.global.auth.AuthUser;
import io.swagger.v3.oas.annotations.Operation;
import lombok.RequiredArgsConstructor;
import org.springframework.http.HttpStatusCode;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;

@RestController
@RequiredArgsConstructor
@RequestMapping("/email")
public class EmailController {
private final EmailService emailService;

@PatchMapping("")
@Operation(summary = "이메일 알림 기능 활성화/비활성화 API", description = "이메일 기능 활성화/비활성화를 맡는 API입니다.")
public ResponseEntity<?> sendOverview(@AuthUser Member member, @RequestParam Boolean isActive){
emailService.setEmail(member.getId(), isActive);
return new ResponseEntity<>(HttpStatusCode.valueOf(200));
}

@PatchMapping("/test")
@Operation(summary = "이메일 기능 테스트 API", description = "사용자가 어떤 이메일이 오는지 궁금할 때 테스트할 수 있는 API 입ㄴ디ㅏ.")
public ResponseEntity<?> sendOverview(@AuthUser Member member){
emailService.sendEmailNotice(member.getEmail());
return new ResponseEntity<>(HttpStatusCode.valueOf(200));
}
}
Original file line number Diff line number Diff line change
@@ -1,11 +1,17 @@
package Ness.Backend.domain.email;
package Ness.Backend.domain.profile.email;

import Ness.Backend.domain.member.MemberRepository;
import Ness.Backend.domain.member.entity.Member;
import Ness.Backend.domain.profile.ProfileRepository;
import Ness.Backend.domain.profile.entity.Profile;
import jakarta.mail.internet.MimeMessage;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.mail.javamail.JavaMailSender;
import org.springframework.mail.javamail.MimeMessageHelper;
import org.springframework.scheduling.annotation.Async;
import org.springframework.scheduling.annotation.EnableScheduling;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.thymeleaf.context.Context;
Expand All @@ -15,22 +21,45 @@
import java.time.ZoneId;
import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatter;
import java.util.List;

@Service
@RequiredArgsConstructor
@Slf4j
@EnableScheduling
@Transactional(readOnly = true)
public class EmailService {
private final JavaMailSender javaMailSender;
private final SpringTemplateEngine templateEngine;
private final ProfileRepository profileRepository;
private final MemberRepository memberRepository;

@Transactional
public void setEmail(Long memberId, Boolean isActive){
Profile profile = profileRepository.findProfileByMember_Id(memberId);
profile.updateMailActive(isActive); // 이메일 활성화 여부 변경
}

// 매일 오전 자정에 스케쥴링
@Scheduled(cron = "0 0 12 * * *")
public void scheduleEmailCron(){
log.info("스케쥴링 활성화");
List<Member> activeMembers = memberRepository.findMembersByProfileIsEmailActive(true);

for (Member member : activeMembers) {
String email = member.getEmail();
sendEmailNotice(email);
}
}

@Async
public void sendEmailNotice(String email){
MimeMessage mimeMessage = javaMailSender.createMimeMessage();
try {
MimeMessageHelper mimeMessageHelper = new MimeMessageHelper(mimeMessage, false, "UTF-8");
MimeMessageHelper mimeMessageHelper = new MimeMessageHelper(mimeMessage, true, "UTF-8");
mimeMessageHelper.setTo(email); // 메일 수신자
mimeMessageHelper.setSubject("Today's Overview on NESS"); // 메일 제목
mimeMessageHelper.setText(setContext(todayDate()), true); // 메일 본문 내용, HTML 여부
mimeMessageHelper.setSubject("End of Today with NESS"); // 메일 제목
mimeMessageHelper.setText(setContext(getTodayDate()), true);
javaMailSender.send(mimeMessage);

log.info("Succeeded to send Email");
Expand All @@ -40,7 +69,7 @@ public void sendEmailNotice(String email){
}
}

public String todayDate(){
public String getTodayDate(){
ZonedDateTime todayDate = LocalDateTime.now(ZoneId.of("Asia/Seoul")).atZone(ZoneId.of("Asia/Seoul"));
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("M월 d일");
return todayDate.format(formatter);
Expand All @@ -50,6 +79,7 @@ public String todayDate(){
public String setContext(String date) {
Context context = new Context();
context.setVariable("date", date);
return templateEngine.process("todo", context);
context.setVariable("image", "https://ness-static-s3.s3.ap-northeast-2.amazonaws.com/email_sample.png");
return templateEngine.process("end-of-today", context);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ public class Profile {

private String name;

private Boolean isEmailActive;

@OneToOne
@JoinColumn(name = "member_id")
private Member member;
Expand All @@ -29,11 +31,16 @@ public void updateNickname(String nickname){
this.nickname = nickname;
}

public void updateMailActive(Boolean isEmailActive){
this.isEmailActive = isEmailActive;
}

@Builder
public Profile(String pictureUrl, String nickname, String name, Member member){
public Profile(String pictureUrl, String nickname, String name, Member member, Boolean isEmailActive){
this.pictureUrl = pictureUrl;
this.nickname = nickname;
this.name = name;
this.member = member;
this.isEmailActive = isEmailActive;
}
}
69 changes: 69 additions & 0 deletions src/main/resources/templates/end-of-today.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="utf-8">
<link rel="stylesheet" as="style" crossorigin href="https://cdn.jsdelivr.net/gh/orioncactus/[email protected]/dist/web/variable/pretendardvariable.min.css" />
<style>
body {
font-family: "Pretendard Variable", Pretendard, -apple-system, BlinkMacSystemFont, system-ui, Roboto, "Helvetica Neue", "Segoe UI", "Apple SD Gothic Neo", "Noto Sans KR", "Malgun Gothic", "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", sans-serif;
}
</style>
</head>
<body>
<div style="margin:0px; width:100%; background-color:#F2F0FF; padding:0px; padding-top:8px;">
<table style="width: 512px; align-content: center; position: relative; overflow: hidden; background: #fff;
margin: 0px auto; max-width:512px; background-color:#ffffff; padding:0px">
<tr>
<td style="height: 49px;">
<img
src="https://ness-static-s3.s3.ap-northeast-2.amazonaws.com/ness_logo.png"
style="width: 40px; height: 40px; vertical-align: middle;"
/>
<p style="font-size: 20px; font-weight: 700; text-align: left; color: #140f33; display: inline-block; vertical-align: middle; margin-left: 10px;"
th:text="|${date}, 오늘 하루도 수고한 당신에게|">
오늘 하루도 수고한 당신에게
</p>
</td>
</tr>
<!--구분선-->
<tr>
<td colspan="2" style="height: 1px; background-color: #3E426A; margin-bottom: 10px;"></td>
</tr>
<tr>
<td colspan="2" style="text-align: center; padding-top: 15px; padding-left: 5px; padding-right: 5px;">
<img
th:if="${image != null}" th:src="${image}"
src="https://ness-static-s3.s3.ap-northeast-2.amazonaws.com/email_sample.png"
style="object-fit: cover;"
alt="email-image"
/>
</td>
</tr>
<tr>
<td colspan="2" style="text-align: center;">
<p style="font-size: 16px; font-weight: 300; padding-left: 5px; padding-right: 5px; text-align: left; color: #140f33;">
<span>오늘 정말 수고 많았어요! 너무 고생했어요! 당신의 노력과 열정에 정말 감탄해요. 하루 종일
바쁘게 움직이면서도 절대 포기하지 않고 최선을 다하고 있는 모습이 정말 멋져요. 함께 있는
모든 순간이 너무 즐거워요! 이런 당신이 있어서 정말 행복해요. 앞으로도 함께 화이팅해요!
함께 하는 모든 순간이 특별하고 의미있어요. 오늘도 정말 고마워요! 오늘도 정말 고맙습니다.
함께한 하루가 뜻깊게 기억될 것입니다.</span><br />
<span>내일은 개발, 토익 공부하기 등의 활동이 있습니다. 이를 바탕으로 백엔드 공부를 더
공부하시는 건 어떨까요? 토익 공부도 체계적으로 진행해보시면 좋겠어요. 또한, 미리 발표
준비를 하는 것도 좋은 것 같아요.
</span>
</p>
</td>
</tr>

<tr>
<td colspan="2" style="height: 1px; background-color: #3E426A; margin-bottom: 10px;"></td>
</tr>
<tr>
<td colspan="2" style="font-size: 14px; font-weight: 300; text-align: center; color: #686868; padding-top: 5px;">
© 2024 Re:coding Service, All rights reserved.
</td>
</tr>
</table>
</div>
</body>
</html>
11 changes: 0 additions & 11 deletions src/main/resources/templates/todo.html

This file was deleted.

0 comments on commit 7a665aa

Please sign in to comment.