DevJong12

[Picasso][Spring] JavaMailSender를 유틸화해 간편히 메일 사용하기 본문

프로젝트/Picasso

[Picasso][Spring] JavaMailSender를 유틸화해 간편히 메일 사용하기

Jong12 2023. 10. 2. 22:27
728x90

목차

    개요

    이번에 프로젝트를 진행하면서 프로젝트 내부에서 보내야 하는 메일 종류가 매우 많았다.
    가벼운 생각만으로도 5종류가 넘었는데, 깊게 생각하고 보니 10종류가 넘어갔다. (실제 시간이 부족해서 구현한 메일은 사실 6개인가 밖에 안되는건 안비밀)

    어쨋든... 그러다 보니 여러종류의 메일에 대처를 해야 했고, 템플릿화 해서 메일을 관리하는 수밖에 없었다.
    내가 템플릿화하여 어떻게 진행하였는지를 기록해 보고자 한다.

    추가 의존성

    다음 두가지를 추가하였다, 필자는 Thymeleaf를 사용해 thymeleaf용 html을 미리 template화 하는데 활용하였으며, 
    메일 발송을 위한 spring-boot-starter-mail을 추가하였다
    implementation 'org.springframework.boot:spring-boot-starter-thymeleaf'
    implementation  'org.springframework.boot:spring-boot-starter-mail'

    메일 IMAP 설정 오픈 - 구글

    구글은 앱 비밀번호를 생성해야 정상적인 활용이 가능하다 더보기의 이미지 시리즈를 통해서 설정을 하도록 하자.
    추가적으로 Gmail에서 IMAP에 대한 설정도 사용하도록 변경해야 한다.

    Gmail IMAP 오픈 방법

     

    구글 앱 비밀번호 생성법

    더보기

    구글 계정 - 앱 비밀번호 검색

     

     

    사용할 앱 명 생성

     

     

    생성된 앱 패스워드 저장


    YML Properties 주입방법

    username의 경우 실제 Google 이메일 계정을, password의 경우에는 앱비밀번호로 생성한 값을 대입해주면 된다.
    spring:
      mail:
        host: smtp.gmail.com
        port: 587
        username: ${username}
        password: ${password}
        properties:
          mail:
            smtp:
              auth: true
              starttls:
                enable: true

     

    실제 필자가 활용한 방법으로, 이유는 해당 값은 노출을 하면 안되기 때문에 VMOption이라는 방법을 선택하였다.

    더보기

    username과 password는 application.yml에서 제거해버렸다.

    이후 VMOptions를 통해 값을 주입하는 방법을 택하였다.


     

    메일발송 공통기능 제작

    모든 메일은 해당 Util Class의 sendMail메소드를 통해서만 발송을 진행한다. 
    수신자, 메일제목, Template Path, Template에서 사용되는 값을 담은 Map이 들어가게 된다.

    그러면 SpringTemplateEngine을 통해서 getContent메소드에서 Thymeleaf로 제작된 템플릿에 Map을 전송해 값을 치환하여 HTML문자열을 반환해오고 해당 내용을 mail의 setText에 담아 html의 형태로 전송을 진행하게 된다.
    import jakarta.mail.Message;
    import jakarta.mail.MessagingException;
    import jakarta.mail.internet.MimeMessage;
    import lombok.RequiredArgsConstructor;
    import lombok.extern.slf4j.Slf4j;
    import org.springframework.mail.javamail.JavaMailSender;
    import org.springframework.stereotype.Component;
    import org.thymeleaf.context.Context;
    import org.thymeleaf.spring6.SpringTemplateEngine;
    import picasso.server.common.exception.IllegalArgumentException;
    import picasso.server.common.exception.MailSendException;
    
    import java.util.Map;
    
    import static org.apache.commons.codec.CharEncoding.UTF_8;
    
    @Slf4j
    @Component
    @RequiredArgsConstructor
    public class SendMailUtil {
        private final JavaMailSender javaMailSender;
        private final SpringTemplateEngine templateEngine;
    
    
        /**
         * Key는 현재 Mail에 사용중인 변수를, Value는 해당 메일 페이지에 들어갈 값을 입력하면 됩니다.
         *
         * @param toUser   발송자 메일
         * @param title    메일 제목
         * @param pagePath ThymeLeaf(HTML)파일 경로
         * @param content  ThymeLeaf에 들어갈 변수 Map (Not Null!!!)
         * @return
         */
        public boolean sendMail(String toUser, String title, String pagePath, Map<String, Object> content) {
            try {
                parameterValidate(toUser, title, content);
                MimeMessage message = javaMailSender.createMimeMessage();
                message.addRecipients(Message.RecipientType.TO, toUser);
                message.setSubject(title, UTF_8);
                message.setText(getContent(pagePath, content), UTF_8, "html");
                javaMailSender.send(message);
                log.info("[SendMail Success] ToUser >>> {}, Title >>> {}, PagePath >>> {}", toUser, title, pagePath);
                return true;
            } catch (MessagingException e) {
                log.error("[SendMail Failed] Exception Reason >>> MessageException, ToUser >>> {}, Title >>> {}, PagePath >>> {}", toUser, title, pagePath);
                throw MailSendException.EXCEPTION;
            } catch (Exception e) {
                log.error("[SendMail Failed] Exception Reason >>> {}, ToUser >>> {}, Title >>> {}, PagePath >>> {}", e.getClass(), toUser, title, pagePath);
                throw MailSendException.EXCEPTION;
            }
        }
    
    
        //Valid, NotNull를 쓸까했는데... 안먹는걸로 기억이나서..
        private void parameterValidate(String toUser, String title, Map<String, Object> content) {
            if (toUser == null || title == null || content == null ||
                    title.isEmpty() || toUser.isEmpty()) {
                throw IllegalArgumentException.EXCEPTION;
            }
        }
    
        // html파일에서 메일내용을 가져옴
        private String getContent(String pagePath, Map<String, Object> content) {
            Context context = new Context();
            content.forEach(context::setVariable);
            return templateEngine.process(pagePath, context);
        }
    
    }

    실제 메일 발송 로직

    메일 발송시, 유틸리티에서 기록한 대로 메일의 수신자, 제목, 템플릿의 존재위치, 사용하는 파라미터를 설정해 메소드를 실행하면 된다.
    public void adminApproveMail(Picture picture) {
        sendMailUtil.sendMail(
                picture.getUser().getEmail(),
                REJECT.getMailTitle(),
                PICTURE_APPROVE_MAIL,
                new HashMap<>() {{
                    put("pictureName", picture.getPictureName());
                    put("startBidDate", picture.getBidStartDate().toString());
                    put("link", picassoProperties.getDomain() + "pictures/" + picture.getPictureId());
                }}
        );
    }

    실제 템플릿 값 사용 모습

    이런 모습으로 템플릿을 사용하게 된다...! 값이 치환되서 렌더링할 수 있는 html문자열을 반환한다...!


    발송된 메일

    위의 과정을 통해 메일을 작업하게 되면 다음과 같은 모습으로 메일을 발송해서 받아 볼 수 있게된다


    기능을 만들어보면서..

    사실 공통의 기능을 만드는건 어쩔수 없이 필요하다 생각을 많이 한다.
    각자가 따로따로 만들게 되면 비효율 적이다 보니, 어떻게 해야 누가 봐도 쉬운 코드를 작성해 줄 수 있을까 많은 고민을 했던 것 같다.

    뭐 팀원분들이 이해했을지 못했을지는..모르지만 말이다..

    그래도 타인과 같은 기능을 사용하기 위한 코드를 만드는 것은 음....파일 업로드기능도 그렇지만 재밌게 할 수 있는 영역이었다고 많은 생각을 하게 된다.

    근데 난 메일 발송이 이렇게 시간이 오래 걸릴줄은 생각을 못했다..거의 1초라니...역시 무거워...
    728x90
    Comments