- build.gradle
// java mail
implementation 'org.springframework.boot:spring-boot-starter-mail'
implementation 'javax.mail:javax.mail-api:1.6.2'
implementation 'com.sun.mail:javax.mail:1.6.2'
- application.yml (메일 보내기 설정 on/off 설정 - optional)
mail:
send: true
- MailProperties.java (메일 보내기 설정 on/off 설정 - optional)
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Configuration;
@Configuration
@ConfigurationProperties(prefix = "mail")
public class MailProperties {
private boolean send;
public boolean isSend() {
return send;
}
public void setSend(boolean send) {
this.send = send;
}
}
- 메일 전송 service 클래스
private final MailProperties mailProperties;
if(mailProperties.isSend()) {
mailService.sendReviewApplicationMail(evalUser.getEmail(), paper.getPaperNm(), paper.getAuthorList().stream().map(Author::getUserNm).collect(Collectors.joining(", ")));
}
- MailDto class
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.springframework.core.io.ClassPathResource;
import org.springframework.mail.javamail.MimeMessageHelper;
import org.springframework.mail.javamail.MimeMessagePreparator;
import org.springframework.stereotype.Component;
import org.springframework.util.FileCopyUtils;
import org.springframework.util.StringUtils;
import javax.mail.internet.MimeMessage;
import java.io.IOException;
import java.nio.charset.Charset;
import java.util.Map;
@Data
@NoArgsConstructor
@Component
public class MailDto implements MimeMessagePreparator {
private String fromEmail;
private String toEmail;
private String subject;
private String contents;
private boolean html = true;
private String tmpFilePath;
@Builder
public MailDto(String fromEmail, String toEmail, String subject, String contents, boolean html, String tmpFilePath) {
this.fromEmail = fromEmail;
this.toEmail = toEmail;
this.subject = subject;
this.contents = contents;
this.html = html;
this.tmpFilePath = tmpFilePath;
}
/**
* 템플릿 파일 경로 변수인 templateFilePath 에 있는 파일을 읽어서 메일 본문을 셋팅한다
* 메일 본문에서 변환해야 할 작업이 없을 경우엔 convertMap 변수에 null을 설정하면 파일 내용을 그대로 메일 본문에 셋팅하게 되며
* 변환할 작업이 있을 경우 찾아야 할 문자열을 Key로, 바꿔야 할 문자열을 Value로 셋팅한 Map 객체를 파라미터로 전달하면 문자열을 치환해서 메일 본문을 셋팅한다
* @param tmpFilePath
* @param convertMap
* @throws IOException
*/
public void setTmpFilePath(String tmpFilePath, Map<String, String> convertMap) throws IOException {
this.tmpFilePath = tmpFilePath;
String readFile = readFile(tmpFilePath);
if(convertMap == null) {
this.contents = readFile;
}else{
setContents(readFile, convertMap);
}
}
public void setContents(String contents, Map<String, String> convertMap) {
String result = contents;
for(Map.Entry<String, String> entry : convertMap.entrySet()) {
String key = entry.getKey();
String value = entry.getValue();
result = StringUtils.replace(result, key, value);
}
this.contents = result;
}
@Override
public void prepare(MimeMessage mimeMessage) throws Exception {
MimeMessageHelper mimeMessageHelper = new MimeMessageHelper(mimeMessage, true, "UTF-8");
mimeMessageHelper.setFrom(fromEmail);
mimeMessageHelper.setTo(toEmail);
mimeMessageHelper.setSubject(subject);
mimeMessageHelper.setText(contents, html);
}
private String readFile(String path) throws IOException {
return readFile(path, "UTF-8");
}
/**
* war로 빌드 후 서버 배포시 문제 발생 context.getRealPath을 사용하여 파일에 접근 시 파일 시스템 자원을 사용할 것으로 기대 되지만 jar 또는 war파일 내부에서 그렇게 사용할수가 없음.
* 그래서 IDE 로컬 환경에서는 동작하지만 war, jar에서는 동작하지 않는 이유
* 그래서 context.getRealPath대신 getInputStream을 사용하면 리소스의 위치에 상관없이 리소스의 콘텐츠를 읽을 수 있음
* @param path
* @param encoding
* @throws IOException
*/
private String readFile(String path, String encoding) throws IOException {
Charset charset = Charset.forName(encoding);
ClassPathResource resource = new ClassPathResource(path);
byte[] encoded = FileCopyUtils.copyToByteArray(resource.getInputStream());
return new String(encoded, charset);
}
}
- MailService.class
import ###.api.common.dto.MailDto;
import lombok.extern.slf4j.Slf4j;
import org.springframework.mail.javamail.JavaMailSender;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;
import javax.servlet.http.HttpSession;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.*;
@Slf4j
@Service
public class MailService {
private final JavaMailSender javaMailSender;
public int authNumber;
private final String USERNAME = "[email protected]";
private final String PASSWORD = "실제 비밀번호가 아닌, smtp 연결용 비밀번호";
private final Properties EMAIL_PROPERTIES;
public MailService(JavaMailSender javaMailSender) {
this.javaMailSender = javaMailSender;
EMAIL_PROPERTIES = new Properties();
EMAIL_PROPERTIES.put("mail.smtp.host", "smtp.gmail.com");
EMAIL_PROPERTIES.put("mail.smtp.port", "587");
EMAIL_PROPERTIES.put("mail.smtp.auth", "true");
EMAIL_PROPERTIES.put("mail.smtp.starttls.enable", "true");
EMAIL_PROPERTIES.put("mail.smtp.ssl.protocols", "TLSv1.2");
}
/**
* 이메일 템플릿 적용
*/
@Async
public String sendAuthCodeMail(String email, HttpSession reqSession) {
MailDto mailDto;
String emailTemplatePath = "templates/valid_code.html";
makeRandomNumber();
String authNumberStr = String.valueOf(authNumber);
Map<String, String> convertMap = new HashMap<>();
for (int i = 0; i < authNumberStr.length(); i++) {
convertMap.put("${authNumber" + (i + 1) + "}", String.valueOf(authNumberStr.charAt(i)));
}
String logoBase64 = encodeImageToBase64("src/main/resources/static/image/logo.svg");
convertMap.put("logoImage", logoBase64);
try {
mailDto = MailDto.builder()
.fromEmail(USERNAME)
.toEmail(email)
.subject("Authentication code")
.html(true)
.build();
mailDto.setTmpFilePath(emailTemplatePath, convertMap);
reqSession.setAttribute("code", authNumber);
javaMailSender.send(mailDto);
return "sent";
} catch (IOException e) {
log.error(e.getMessage(), e);
return "error";
}
}
/**
* 심사 요청 메일
* @param email
* @return
*/
@Async
public String sendReviewApplicationMail(String email, String paperNm, String author) {
MailDto mailDto;
String emailTemplatePath = "templates/review_request.html";
Map<String, String> convertMap = new HashMap<>();
String logoBase64 = encodeImageToBase64("src/main/resources/static/image/logo.svg");
convertMap.put("${paperNm}", paperNm);
convertMap.put("${author}", author);
convertMap.put("${logoImage}", logoBase64);
try {
mailDto = MailDto.builder()
.fromEmail(USERNAME)
.toEmail(email)
.subject("Request for Review")
.html(true)
.build();
mailDto.setTmpFilePath(emailTemplatePath, convertMap);
javaMailSender.send(mailDto);
return "sent";
} catch (IOException e) {
log.error(e.getMessage(), e);
return "error";
}
}
/**
* 심사 결과 완료 메일
* @param email
* @return
*/
@Async
public String sendReviewCompletedMail(String email, String paperNm) {
MailDto mailDto;
String emailTemplatePath = "templates/review_completed.html";
Map<String, String> convertMap = new HashMap<>();
String logoBase64 = encodeImageToBase64("src/main/resources/static/image/logo.svg");
convertMap.put("${paperNm}", paperNm);
convertMap.put("${logoImage}", logoBase64);
try {
mailDto = MailDto.builder()
.fromEmail(USERNAME)
.toEmail(email)
.subject("Article Review Completed")
.html(true)
.build();
mailDto.setTmpFilePath(emailTemplatePath, convertMap);
javaMailSender.send(mailDto);
return "sent";
} catch (IOException e) {
log.error(e.getMessage(), e);
return "error";
}
}
private void makeRandomNumber() {
// 난수의 범위 111111 ~ 999999 (6자리 난수)
Random r = new Random();
int checkNum = r.nextInt(888888) + 111111;
log.info("인증번호 : "+checkNum);
authNumber = checkNum;
}
}