본문 바로가기

국비교육/국비교육

day72 - 1109

이메일 전송

의존성 추가

- Java Mail Sender 의존성 추가

 

- 의존성 추가 확인

 

** SMTP(Simple Mail Transfer Protocol)

 

JavaMailSenderImpl

- Spring의 클래스

- JavaMailSender의 구현체

- 이메일 전송에 필요한 여러 설정을 할 수 있다

 

- JavaMailSenderImpl의 메소드 중 일부

void send(MimeMessage mimeMessage) Mime Message 전송
void send(SimpleMailMessage simpleMessage) Simple Message 전송
void setHost(String host) host 이름 설정
void setPort(int port) port 번호 설정
void setUserName(String username) 사용자 아이디 설정
void setPassWord(String password) 사용자 비밀번호 설정
void setJavaMailProperties(Properties properties) Session에 대한 JavaMail의 속성 설정

 

SimpleMailMessage

- Spring의 클래스

- 보낸 사람(from), 받는 사람(to), 참조(cc), 제목(subject) 및 내용(text)를 포함하는 간단한 메시지를 전송

 

- SimpleMailMessage의 메소드 중 일부

void setFrom(String from) 보낸 사람(from) 설정
void setTo(String to) 받는 사람(to) 설정
void setCc(String cc) 참조(cc) 설정
void setBcc(String bcc) 숨은 참조(bcc) 설정 - 수신자에게 보이지 않는 참조
void setSubject(String subject) 제목(subject) 설정
void setText(String text) 내용(text) 설정

 


 

Gmail을 이용한 이메일 전송

EmailTest1

- src/test/java에 테스트용 클래스 생성

- Spring의 SimpleMailMessage 클래스를 이용한 단문 이메일 전송

 

1) JavaMailSenderImpl의 인스턴스 생성 및 기본 설정

2) Properties의 인스턴스 생성 및 설정

3) JavaMailSenderImpl의 추가 설정 - Properties 이용 

4) SimpleMailMessage의 인스턴스 생성 및 받는 사람, 제목, 내용 등 설정

5) JavaMailSenderImpl의 send 메소드 실행

 

@SpringBootTest
public class EmailTest1 {

	@Test
	public void test() {
		
		// 이메일 전송을 위한 기본 설정
		// - JavaMailSenderImpl의 인스턴스 생성 및 설정
		JavaMailSenderImpl sender = new JavaMailSenderImpl();
		sender.setHost("smtp.gmail.com"); // Host 이름 설정 - Gmail로 설정
		sender.setPort(587); // Port 번호 설정 - Gmail은 587번 포트 사용
		sender.setUsername("Gmail 아이디"); // 사용자 아이디 설정 - Gmail 아이디
		sender.setPassword("Gmail 앱비밀번호"); // 사용자 비밀번호 설정 - Gmail의 앱 비밀번호
		
		// - 추가 설정을 위한 Properties의 인스턴스 생성 및 설정 (Properties는 Map<String, String> 형태)
		Properties props = new Properties(); 
		props.setProperty("mail.smtp.auth", "true"); // 인증 여부 설정(필수)
		props.setProperty("mail.smtp.debug", "true"); // 디버깅 사용 여부 설정(선택)
		props.setProperty("mail.smtp.starttls.enable", "true"); // TLS 사용 여부(필수)
		props.setProperty("mail.smtp.ssl.protocols", "TLSv1.2"); // TLS 버전 설정(필수)
		props.setProperty("mail.smtp.ssl.trust", "smtp.gmail.com"); // 신뢰할 수 있는 대상으로 추가(필수)
		
		// 설정된 Properties의 인스턴스를 JavaMailSenderImpl의 Properties로 설정
		sender.setJavaMailProperties(props);
		
		// Simple message 작성
		SimpleMailMessage message = new SimpleMailMessage();
		message.setTo("받는사람 이메일"); // 받는 사람(to) 설정
		
		message.setSubject("테스트 이메일"); // 제목(subject) 설정
		message.setText("메롱"); // 내용(text) 설정
		
		// 설정된 SimpleMailMessage의 인스턴스를 이용하여 단문 이메일 전송
		sender.send(message);
	}
}

 


 

이메일 전송을 위한 인스턴스 생성 및 설정을 모듈화

EmailConfiguration

- @Confgiruation 어노테이션을 붙여서 configuration으로 등록

- @Bean 어노테이션을 붙여서 JavaMailSender를 반환하는 메소드를 bean으로 등록

@Configuration
public class EmailConfiguration {
	
	@Bean // JavaMailSender를 반환하는 메소드를 bean으로 등록
	public JavaMailSender javaMailSender() {
		
		// 이메일 전송을 위한 기본 설정
		// - JavaMailSenderImpl의 인스턴스 생성 및 설정
		JavaMailSenderImpl sender = new JavaMailSenderImpl();
		sender.setHost("smtp.gmail.com"); // Host 이름 설정 - Gmail로 설정
		sender.setPort(587); // Port 번호 설정 - Gmail은 587번 포트 사용
		sender.setUsername("Gmail 아이디"); // 사용자 아이디 설정 - Gmail 아이디
		sender.setPassword("Gmail 앱 비밀번호"); // 사용자 비밀번호 설정 - Gmail의 앱 비밀번호
		
		// - 추가 설정을 위한 Properties의 인스턴스 생성 및 설정 (Properties는 Map<String, String> 형태)
		Properties props = new Properties(); 
		props.setProperty("mail.smtp.auth", "true"); // 인증 여부 설정(필수)
		props.setProperty("mail.smtp.debug", "true"); // 디버깅 사용 여부 설정(선택)
		props.setProperty("mail.smtp.starttls.enable", "true"); // TLS 사용 여부(필수)
		props.setProperty("mail.smtp.ssl.protocols", "TLSv1.2"); // TLS 버전 설정(필수)
		props.setProperty("mail.smtp.ssl.trust", "smtp.gmail.com"); // 신뢰할 수 있는 대상으로 추가(필수)
		
		// 설정된 Properties의 인스턴스를 JavaMailSenderImpl의 Properties로 설정
		sender.setJavaMailProperties(props);
		
		// 설정이 끝난 JavaMailSenderImpl의 인스턴스를 반환
		// - 반환시 JavaMailSenderImpl 형태로 반환되지만 반환형은 JavaMailSender이 된다(업 캐스팅)
		return sender;
	}
}

 


EmailProperties를 이용한 EmailConfiguration 개선

 

** 기존 방식의 문제점

- 협업을 하는 경우 이메일 아이디와 비밀번호가 타인에게 노출될 수 있다

 

** 해결책

- .gitignore에 .properties를 추가하여 Git Hub에 application.properties 파일이 업로드되지 않도록 한다

- application.properties에 spring에 없는 custom 설정으로 이메일과 관련된 개인 정보를 설정한 후 

  @ConfigurationProperties 어노테이션을 이용해 필요할 때마다 설정값을 Java 클래스에 바인딩하는 방식으로 작업한다

 

src/main/resources - application.properties

- sping에 없는 custom 설정을 만들어서 이메일과 관련된 개인 정보 설정

 

EmailProperties

- application.properties의 값들을 바인딩하여 사용할 수 있도록 하는 Component

@Data
@Component
@ConfigurationProperties(prefix = "custom.email")
public class EmailProperties {

	private String host; // Host 이름
	private int port; // Port 번호
	private String username; // 사용자 아이디
	private String password; // 사용자 비밀번호
}

 

@Component

- Component로 등록하는 어노테이션

 

@ConfigurationProperties

- *.properties, *.yml 파일에 있는 property들을 Java 클래스의 값으로 가져와서(바인딩) 사용하게 해주는 어노테이션

- prefix 속성을 이용하면 해당 접두사를 가지고있는 모든 property의 값을 자동으로 바인딩해준다

ex) custom.email.port의 값인 587이 EmailProperties의 port 필드의 값으로 바인딩된다

 

EmailProperties를 이용한 EmailConfiguration 개선

@Configuration
public class EmailConfiguration {
	
	// EmailProperties에 대한 의존성 주입
	@Autowired
	private EmailProperties emailProperties;
	
	@Bean // JavaMailSender를 반환하는 메소드를 bean으로 등록
	public JavaMailSender javaMailSender() {
		
		// 이메일 전송을 위한 기본 설정 (EmailProperties의 값으로 설정하도록 변경)
		// - JavaMailSenderImpl의 인스턴스 생성 및 설정
		JavaMailSenderImpl sender = new JavaMailSenderImpl();
		sender.setHost(emailProperties.getHost()); // Host 이름 설정
		sender.setPort(emailProperties.getPort()); // Port 번호 설정
		sender.setUsername(emailProperties.getUsername()); // 사용자 아이디 설정
		sender.setPassword(emailProperties.getPassword()); // 사용자 비밀번호 설정
		
		// - 추가 설정을 위한 Properties의 인스턴스 생성 및 설정 (Properties는 Map<String, String> 형태)
		Properties props = new Properties(); 
		props.setProperty("mail.smtp.auth", "true"); // 인증 여부 설정(필수)
		props.setProperty("mail.smtp.debug", "true"); // 디버깅 사용 여부 설정(선택)
		props.setProperty("mail.smtp.starttls.enable", "true"); // TLS 사용 여부(필수)
		props.setProperty("mail.smtp.ssl.protocols", "TLSv1.2"); // TLS 버전 설정(필수)
		props.setProperty("mail.smtp.ssl.trust", "smtp.gmail.com"); // 신뢰할 수 있는 대상으로 추가(필수)
		
		// 설정된 Properties의 인스턴스를 JavaMailSenderImpl의 Properties로 설정
		sender.setJavaMailProperties(props);
		
		// 설정이 끝난 JavaMailSenderImpl의 인스턴스를 반환
		// - 반환시 JavaMailSenderImpl 형태로 반환되지만 반환형은 JavaMailSender이 된다(업 캐스팅)
		return sender;
	}
}

 

 


이메일 전송을 이용한 이메일 인증 구현

 

플로우 차트

- 인증번호가 포함된 이메일을 사용자에게 전송한다

- 동시에 해당 인증번호에 대한 발급 정보(누구에게, 인증번호, 언제)를 DB에 등록한다

- 사용자가 인증번호를 입력하면 DB에서 조회하여 인증번호 일치 여부를 검사한다

 

DB 테이블 생성

- 인증번호 테이블(CERT) 생성

** who를 PK로 하면 한 사람에게 무조건 하나의 인증번호만 발급할 수 있다

** who와 serial을 PK(복합키)로 하면 한 사람에게 여러 개의 서로 다른 인증번호를 발급할 수 있다

create table cert (
who varchar2(60) not null primary key,
serial char(6) not null,
when date default sysdate not null
);

 

CertDto

@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class CertDto {

	private String who; // 누구에게
	private String serial; // 인증번호
	private String when; // 언제
}

 

1. 인증번호 발급 및 인증메일 전송

cert-mapper.xml

<?xml version="1.0" encoding="UTF-8"?>

<!DOCTYPE mapper
  PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
  "https://mybatis.org/dtd/mybatis-3-mapper.dtd">

<mapper namespace="cert">

	<!-- 인증번호 등록 -->
	<insert id = "insert" parameterType = "CertDto">
		insert into cert(who, serial) values(#{who}, #{serial})
	</insert>

	<!-- 인증번호 삭제 -->
	<delete id = "delete" parameterType = "String">
		delete cert where who = #{who}
	</delete>

</mapper>

 

CertDao

public interface CertDao {

	// 추상 메소드 - 인증번호 등록
	void insert(CertDto certDto);
	
	// 추상 메소드 - 인증번호 삭제
	boolean delete(String who);
}

 

CertDaoImpl

@Repository
public class CertDaoImpl implements CertDao {

	// 의존성 주입
	@Autowired
	private SqlSession sqlSession;

	// 추상 메소드 - 인증번호 등록
	@Override
	public void insert(CertDto certDto) {
		sqlSession.insert("cert.insert", certDto);
	}

	// 추상 메소드 - 인증번호 삭제
	@Override
	public boolean delete(String who) {
		int count = sqlSession.delete("cert.delete", who);
		return count > 0;
	}
}

 

CreateCertTest

@SpringBootTest
public class CreateCertTest {

	// 이메일 전송을 위한 의존성 주입
	@Autowired
	private JavaMailSender javaMailSender;
	
	// DB 접근을 위한 의존성 주입
	@Autowired
	private CertDao certDao;
	
	// 테스트용 이메일
	String email = "abcd@gmail.com";
	
	@Test
	public void test() {
		
		// 1. 6자리 랜덤인증번호 생성
		// - 인증번호(난수) 생성 범위 - 10의 size 제곱
		int range = (int)Math.pow(10, size);
		
		// - 해당 범위에서 인증번호 생성
		int number = r.nextInt(range);
		System.out.println("number = " + number);
		
		// - 0이 나열된 문자열 생성 (인증번호 자릿수 조정을 위함)
		StringBuffer buffer = new StringBuffer();
		for(int i = 0 ; i < size ; i ++) {
			buffer.append("0");
		}
		
		// - 0이 나열된 문자열을 pattern으로 하는 Format 클래스의 인스턴스 생성
		Format f = new DecimalFormat(buffer.toString());
		
		// - 인증번호를 지정된 pattern 형태의 문자열 변환
		String serial = f.format(number);
		
		// 2. 이메일 발송
		SimpleMailMessage message = new SimpleMailMessage();
		message.setTo(email);
		message.setSubject("[KH정보교육원] 이메일 인증번호입니다");
		message.setText("인증번호 : " + serial);
		javaMailSender.send(message);
		
		// 3. 데이터베이스 등록
		// - 이전에 해당 이메일로 보냈던 인증번호 정보 삭제
		certDao.delete(email);
		
		// - 새로 발급된 인증번호 정보 등록
		CertDto certDto = CertDto.builder().who(email).serial(serial).build();
		certDao.insert(certDto);
	}
}

 

2. 인증번호 검사

cert-mapper.xml

- oracle에서 날짜 계산의 기본 단위는 1일이다 (날짜 계산에서 1은 1일을 의미)

  ** 1을 24로 나누면 1시간

  ** 1을 24로 나눈 후 60으로 나누면 1분

  ** 5분을 나타내는 값은 5/24/60이 된다

<?xml version="1.0" encoding="UTF-8"?>

<!DOCTYPE mapper
  PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
  "https://mybatis.org/dtd/mybatis-3-mapper.dtd">

<mapper namespace="cert">

	<!-- 인증번호 조회 -->
	<select id = "check" parameterType = "CertDto" resultType = "CertDto">
		select * from cert 
			where 
				who = #{who} 
				and 
				serial = #{serial} 
				and 
				when >= sysdate - 5/24/60
	</select>

</mapper>

 

CertDao

package com.kh.spring18.repository;

import com.kh.spring18.entity.CertDto;

public interface CertDao {

	// 추상 메소드 - 인증번호 조회(5분 이내 발급된 인증번호인지)
	boolean check(CertDto certDto);
}

 

CertDaoImpl

@Repository
public class CertDaoImpl implements CertDao {

	// 의존성 주입
	@Autowired
	private SqlSession sqlSession;

	// 추상 메소드 - 인증번호 조회(5분 이내 발급된 인증번호인지)
	@Override
	public boolean check(CertDto certDto) {
		CertDto result = sqlSession.selectOne("cert.check", certDto);
		return result != null;
	}
}

 

CheckCertTest

- 테스트용 데이터에서 해당 이메일로 5분이내 발급된 해당 인증번호가 존재(유효)하는지를 단일조회

- 해당 인증번호가 있다면 인증 성공 및 해당 인증번호 삭제

@SpringBootTest
public class CheckCertTest {

	// DB 접근을 위한 의존성 주입
	@Autowired
	private CertDao certDao;
	
	// 테스트를 위한 인증번호 정보
	String email = "abcd@gmail.com";
	String serial = "849892";
	
	@Test
	public void test() {
		
		// 주어진 정보(email, serial)로 certDto 설정
		CertDto certDto = CertDto.builder().who(email).serial(serial).build();
		
		// 설정된 certDto로 인증번호 조회(5분 이내 발급된 인증번호인지)
		boolean result = certDao.check(certDto);
		
		// 5분 이내 발급된 인증번호인지 여부 판정
		if(result) { // 5분 이내 발급된 인증번호라면(인증 성공)
			System.out.println("인증 성공");
			certDao.delete(email);
		}
		else { // 그렇지 않다면(인증 실패)
			System.out.println("인증 실패");
		}
	}
}

 


** 인증번호 생성의 Component화

- 인증번호의 자릿수를 유동적으로 조정하기 위해 추상화 구조(Dao, DaoImpl)로 클래스를 작성한다

- StringBuffer로 자릿수만큼 0을 붙인 문자열을 반환하여 Format 클래스의 생성자에 들어갈 pattern으로 사용한다

- 생성된 인증번호를 Format 클래스의 pattern 형태의 문자열로 반환한다

 

RandomGenerator

public interface RandomGenerator {

	// 추상 메소드 - 자릿수에 해당하는 숫자를 받아 인증번호를 생성
	String generateSerial(int size);
}

 

RandomGeneratorImpl

@Component
public class RandomGeneratorImpl implements RandomGenerator {

	// Random 클래스의 인스턴스 생성
	private Random r = new Random();
	
	// 추상 메소드 오버라이딩 - 자릿수에 해당하는 숫자를 받아 인증번호를 생성
	@Override
	public String generateSerial(int size) {
		
		// 인증번호(난수) 생성 범위 - 10의 size 제곱
		int range = (int)Math.pow(10, size);
		
		// 해당 범위에서 인증번호 생성
		int number = r.nextInt(range);
		System.out.println("number = " + number);
		
		// 0이 나열된 문자열 생성 (인증번호 자릿수 조정을 위함)
		StringBuffer buffer = new StringBuffer();
		for(int i = 0 ; i < size ; i ++) {
			buffer.append("0");
		}
		
		// 0이 나열된 문자열을 pattern으로 하는 Format 클래스의 인스턴스 생성
		Format f = new DecimalFormat(buffer.toString());
		
		// 인증번호를 지정된 pattern 형태의 문자열 변환
		String serial = f.format(number);
		
		// 생성된 인증번호 문자열 반환
		return serial;
	}
}

 

1. 인증번호 발송 및 등록의 Service화

- 인증번호 생성, 인증메일 발송, 이전에 발급된 인증번호 삭제, 새로 발급된 인증번호 등록을 한번에 처리

 

EmailService

public interface EmailService {

	// 추상 메소드 - 인증번호 발급 및 등록
	void sendCertMail(String email);
}

 

GmailService

@Service
public class GmailService implements EmailService {

	// 의존성 주입 - 인증번호 생성
	@Autowired
	private RandomGenerator randomGenerator;
	
	// 의존성 주입 - 인증메일 전송
	@Autowired
	private JavaMailSender javaMailSender;
	
	// 의존성 주입 - 발급한 인증번호 등록/삭제/조회
	@Autowired
	private CertDao certDao;

	// 추상 메소드 오버라이딩 - 인증번호 발급 및 등록
	@Override
	public void sendCertMail(String email) {
		
		// 1) 6자리 랜덤인증번호 생성
		String serial = randomGenerator.generateSerial(6);
		
		// 2) 이메일 발송
		SimpleMailMessage message = new SimpleMailMessage();
		message.setTo(email);
		message.setSubject("[KH정보교육원] 이메일 인증번호입니다");
		message.setText("인증번호 : " + serial);
		javaMailSender.send(message);
		
		// 3) 데이터베이스 등록
		// - 이전에 해당 이메일로 보냈던 인증번호 정보 삭제
		certDao.delete(email);
		
		// - 새로 발급된 인증번호 정보 등록
		CertDto certDto = CertDto.builder().who(email).serial(serial).build();
		certDao.insert(certDto);
	}
}

 

2. 인증번호 검사의 Service화

- 입력된 인증번호 정보로 단일 조회를 하여 인증 성공 / 실패에 따라 다른 값을 반환

 

EmailService

public interface EmailService {

	// 추상 메소드 - 인증번호 검사
	boolean checkCert(CertDto certDto);
}

 

GmailService

@Service
public class GmailService implements EmailService {
	
	// 의존성 주입 - 발급한 인증번호 등록/삭제/조회
	@Autowired
	private CertDao certDao;

	// 추상 메소드 오버라이딩 - 인증번호 검사
	@Override
	public boolean checkCert(CertDto certDto) {
		if(certDao.check(certDto)) { // 5분이내 발급된 인증번호가 존재하면(인증 성공)
			certDao.delete(certDto.getWho()); // 인증번호 삭제
			return true;
		}
		return false;
	}
}

 


 

TestController

- 인증 메일 전송 페이지(test1.jsp)에서 <form>으로 이메일 정보(who)를 입력받는다

- 인증번호 입력 페이지(test2.jsp)에서 <form>으로 이메일 정보(who)와 인증번호(serial)을 입력받는다

  (단, 이메일 정보는 hidden 형태로 하여 페이지에서 보이지 않도록 한다)

- 인증번호 입력 페이지(test2.jsp)에서 입력받은 인증번호 정보(certDto)로 단일조회하여 인증성공 여부를 판정

@Controller
public class TestController {
	
	// 의존성 주입 - 이메일 전송
	@Autowired
	private EmailService emailService;

	// 이메일 인증 메일 발송 Mapping
	@RequestMapping("/test1")
	public String test1() {
		// 인증 메일 전송 페이지(test1.jsp)로 연결
		return "test1";
	}
	
	// 인증메일 전송 및 DB 등록 Mapping
	@RequestMapping("/test2")
	public String test2(@RequestParam String who, Model model) {
		// 인증메일 전송 및 해당 인증번호에 대한 정보 등록
		emailService.sendCertMail(who);
		// 이메일을 Model에 첨부
		model.addAttribute("who", who);
		// 인증번호 입력 페이지(test2.jsp)로 연결
		return "test2";
	}
	
	// 이메일 인증번호 등록 및 인증확인 Mapping
	@RequestMapping("/test3")
	public String test3(@ModelAttribute CertDto certDto, RedirectAttributes attr) {
		// 해당 인증번호가 존재하며 5분 이내 인증번호인지 여부
		boolean result = emailService.checkCert(certDto);
		if(result) { // 인증 성공시
			// 인증 성공 Mapping으로 강제 이동(redirect)
			return "redirect:test4";
		}
		else { // 인증 실패시
			// 인증번호 입력 Mapping으로 강제 이동(redirect)
			attr.addAttribute("who", certDto.getWho());
			return "redirect:test2?error";
		}
	}
	
	// 이메일 인증 성공 Mapping
	@RequestMapping("/test4")
	public String test4() {
		// 인증 성공 페이지(test4.jsp)로 연결
		return "test4";
	}
}

 

test1.jsp

- 인증메일 전송 페이지

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>

<h1>이메일 인증</h1>

<form action = "test2" method = "get">
	이메일 : <input type = "text" name = "who" required>
	<button type = "submit">확인</button>
</form>

 

test2.jsp

- 인증번호 입력 페이지

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>

<h1>인증번호 입력</h1>
<form action = "test3" method = "get">
	<input type = "hidden" name = "who" value = "${who}">
	
	인증번호 : <input type = "text" name = "serial" required>
	<button type = "submit">확인</button>
</form>

 

test4.jsp

- 인증 성공 페이지

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>

<h1>인증 완료!</h1>

 


비동기 통신을 이용한 단일 페이지 내에서의 이메일 인증 구현

AsyncController

@Controller
public class AsyncController {

	// 의존성 주입 - 이메일 전송
	@Autowired
	private EmailService emailService;
	
	// 인증 페이지 Mapping
	@RequestMapping("/async1")
	public String async1() {
		// 인증 페이지(async.jsp)로 연결 
		return "async1";
	}
	
	// 인증메일 전송 및 DB 등록 Mapping
	@PostMapping("/async2")
	@ResponseBody
	public void async2(@RequestParam String who) {
		// 인증메일 전송 및 해당 인증번호에 대한 정보 등록
		emailService.sendCertMail(who);
	}
	
	// 인증번호 검사
	@PostMapping("/async3")
	@ResponseBody
	public boolean async3(@ModelAttribute CertDto certDto) {
		// 해당 인증번호가 존재하며 5분 이내 인증번호인지 검사 및 결과 반환
		return emailService.checkCert(certDto);
	}
}

 

async1.jsp

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>

<script src = "http://code.jquery.com/jquery-3.6.1.min.js"></script>

<script>
	$(function(){
		// 판정 객체 - 인증이 성공할 경우에만 true로 변경
		var judge = {
			emailValid : false,
		};
		
		// 확인 버튼에 대한 click 이벤트 설정
		$(".send-btn").click(function(){
			
			// memberEmail이라는 name을 가진 요소의 value를 변수 email로 지정
			var email = $("[name=memberEmail]").val();
			
			// email의 길이가 0이면(입력되지 않았으면) return
			if(email.length == 0) return;
			
			// 확인 버튼을 변수 btn으로 지정
			var btn = $(this);
			
			// 확인 버튼을 누르면 중복입력을 방지하기 위해 비활성화 설정
			btn.prop("disabled", true);
			
			// 화인 버튼을 클릭할 때 발급된 인증 번호를 등록하기 위한 비동기 통신
			$.ajax({
				url:"${pageContext.request.contextPath}/async2", 
				method : "post",
				data : {who : email},
				success : function(){
					// 성공했다면 메일은 전송되었다고 볼 수 있다
					btn.prop("disabled", false);
					
					// 인증번호 입력창 태그 재구성을 위한 변수 지정
					// <div> 태그를 변수 div로 지정
					var div = $("<div>");
					// <input> 태그를 변수 input으로 지정
					var input = $("<input>");
					// <button> 태그를 변수 button으로 지정
					// - type을 "button"으로, 태그 사이의 text를 "검사"로 지정
					var button = $("<button>").attr("type", "button").text("검사");
					
					// div에 input와 button 추가
					div.append(input).append(button);
					
					// 클래스명이 cert인 <div> 태그 안에 html(innerHtml)로 태그 생성
					$(".cert").html(div);
					
					// 검사 버튼에 대한 click 이벤트 설정
					button.click(function(){
						// input 태그에 입력되어있는 값을 변수 serial로 지정 
						var serial = input.val();
						
						// 만약 serial의 길이가 6이 아니라면(비정상 입력) return
						if(serial.length != 6) return;
						
						// 인증번호 확인에 대한 ajax
						$.ajax({
							url:"${pageContext.request.contextPath}/async3",
							method:"post",
							data : {who : email, serial : serial},
							success:function(resp){
								// checkCert(CertDto certDto)의 결과를 
								// judge의 emailValid 값으로 저장 
								judge.emailValid = resp;
								// 더이상 인증메일을 보내지 못하도록 
								// 확인 버튼(인증메일 전송 버튼)을 비활성화
								btn.prop("disabled", true);	
							}
						});
					});
				}
			});
		});
		
		// form이 전송될 때 판정 객체(judge)의 상태가 어떤지 출력
		$(".join-form").submit(function(){
			console.log(judge);
			return false;
		});
	});
</script>

<h1>회원가입</h1>

<form class = "join-form">
	이메일 : <input type = "text" name = "memberEmail">
	<button class = "send-btn">확인</button>
	<div class = "cert"></div>
	
	<button type = "submit">가입</button>
</form>

MIME(Multipurpose Internet Message Extensions) 메시지 전송

MimeMessageHelper

- MimeMessageHelper의 생성자 중 일부

MimeMessageHelper(MimeMessage mimeMessage, boolean multipart, String encoding)

** multipart 여부, encoding 방식을 추가로 설정할 수 있다

 

- MimeMessageHelper의 메소드 중 일부

void setFrom(String from) 보낸 사람(from) 설정
void setTo(String to) 받는 사람(to) 설정
void setSubject(String subject) 제목(subject) 설정
void setText(String text) 내용(text) 설정
void setText(String text, boolean html) 내용(text) 설정 + html 태그 인식 여부
void addAttachment(String attachmentFileName, File file) 해당 파일명으로 첨부파일 추가

 


MimeMessageTest1

- setText 메소드의 boolean html이 true이면 html 태그를 자동으로 인식하여 내용에 태그를 적용한다

@SpringBootTest
public class MineMessageTest1 {

	// 의존성 주입 - 이메일 전송
	@Autowired
	private JavaMailSender javaMailSender;
	
	@Test
	public void test() throws MessagingException {
		
		// 1) MimeMessage의 인스턴스 생성
		MimeMessage message = javaMailSender.createMimeMessage();
				
		// 2) MimeMessageHelper의 인스턴스 생성
		// - 생성자의 매개변수는 순서대로 MimeMessage, 
		MimeMessageHelper helper = new MimeMessageHelper(message, false, "UTF-8");
		
		// 3) 정보 설정
		helper.setTo("eomhyunyoung@gmail.com");
		helper.setSubject("마임메세지 테스트");
		// - html 태그를 적용하지 않을 경우
		// helper.setText("<h1>안녕</h1>");
		// - html 태그를 적용할 경우
		helper.setText("<h1>안녕</h1>", true);
		
		// 4) Mime 메시지 전송
		javaMailSender.send(message);
	}
}

 

MimeMessageTest2

- Mime 메시지에 첨부파일 추가

@SpringBootTest
public class MineMessageTest2 {

	// 의존성 주입 - 이메일 전송
	@Autowired
	private JavaMailSender javaMailSender;
	
	@Test
	public void test() throws MessagingException {
		
		// 1) MimeMessage의 인스턴스 생성
		MimeMessage message = javaMailSender.createMimeMessage();
				
		// 2) MimeMessageHelper의 인스턴스 생성
		// - 파일 첨부시 multipart를 true로 한다
		MimeMessageHelper helper = new MimeMessageHelper(message, true, "UTF-8");
		
		// 3) 정보 설정
		helper.setTo("eomhyunyoung@gmail.com");
		helper.setSubject("첨부파일 테스트");
		helper.setText("첨부파일을 확인해주세요");
		
		// ** 첨부파일 설정
		// - File 클래스의 인스턴스 생성
		File target = new File("D:\\", "attachmentTest.jpg");
		// - File 클래스의 인스턴스로 FileDataSource의 인스턴스 생성 후 DataSource로 업 캐스팅
		DataSource source = new FileDataSource(target);
		// - MimeMessageHelper에 해당 DataSource를 원본 파일명으로 첨부파일에 추가
		helper.addAttachment(target.getName(), source);
		
		// 4) Mime 메시지 전송
		javaMailSender.send(message);
	}
}

 

MimeMessageTest4

- Spring 프로젝트 내 resource로 저장된 html 파일 전송

- 특정 위치에 입력되어야할 값을 문자열 대체(replace)로 해결하는 방법

@SpringBootTest
public class MineMessageTest4 {

	// 의존성 주입 - 이메일 전송
	@Autowired
	private JavaMailSender javaMailSender;
	
	@Test
	public void test() throws MessagingException, FileNotFoundException, IOException {
		
		// 1) MimeMessage의 인스턴스 생성
		MimeMessage message = javaMailSender.createMimeMessage();
				
		// 2) MimeMessageHelper의 인스턴스 생성
		MimeMessageHelper helper = new MimeMessageHelper(message, false, "UTF-8");
		
		// 3) 정보 설정
		helper.setTo("eomhyunyoung@gmail.com");
		helper.setSubject("HTML 템플릿 테스트");
		
		// ** Spring 프로젝트 내 Resource 파일 읽기
		ClassPathResource resource = new ClassPathResource("email/template2.html");
		
		// ** 해당 Resource 파일의 내용을 StringBuffer로 연결
		StringBuffer buffer = new StringBuffer();
		try(Scanner sc = new Scanner(resource.getFile())){ 
			while(sc.hasNextLine()) {
				buffer.append(sc.nextLine());
			}
		}
		String text = buffer.toString();
		
		// MimeMessageHelper의 text로 설정하기 전 {{name}}과 {{address}}의 값 대체
		text = text.replace("{{name}}", "eomhyunyoung");
		text = text.replace("{{address}}", "http://localhost:8888/async1");
		
		// 내용(text)에 들어갈 값 및 html 적용여부 설정
		helper.setText(text, true);
		
		// 4) Mime 메시지 전송
		javaMailSender.send(message);
	}
}

 

MimeMessageTest5

- Spring 프로젝트 내 resource로 저장된 html 파일 전송

- Jsoup을 이용하여 문자열을 대체하는 방법

** Jsoup 의존성 추가 - https://mvnrepository.com/artifact/org.jsoup/jsoup/1.15.3

 

@SpringBootTest
public class MineMessageTest5 {

	// 의존성 주입 - 이메일 전송
	@Autowired
	private JavaMailSender javaMailSender;
	
	@Test
	public void test() throws MessagingException, FileNotFoundException, IOException {
		
		// 1) MimeMessage의 인스턴스 생성
		MimeMessage message = javaMailSender.createMimeMessage();
				
		// 2) MimeMessageHelper의 인스턴스 생성
		MimeMessageHelper helper = new MimeMessageHelper(message, false, "UTF-8");
		
		// 3) 정보 설정
		helper.setTo("eomhyunyoung@gmail.com");
		helper.setSubject("Jsoup 변환 테스트");
		
		// ** Spring 프로젝트 내 Resource 파일 읽기
		ClassPathResource resource = new ClassPathResource("email/template3.html");
		
		// ** 해당 Resource 파일의 내용을 StringBuffer로 연결
		StringBuffer buffer = new StringBuffer();
		try(Scanner sc = new Scanner(resource.getFile())){ 
			while(sc.hasNextLine()) {
				buffer.append(sc.nextLine());
			}
		}
		String text = buffer.toString();
		
		// StringBuffer로 생성한 문자열을 HTML로 분석(parse)
		Document doc = Jsoup.parse(text);
		
		// doc에서 'user-name'이라는 id를 가진 요소를 선택
		Element element = doc.getElementById("user-name"); 
		
		// 선택한 요소에 해당 문자열을 설정(대체)
		element.text("eomhyunyoung"); 
		
		// doc에서 'user-url'이라는 id를 가진 요소를 선택
		Element link = doc.getElementById("return-url");
		
		// 선택한 요소에 해당 이름의 속성(attribute)과 값을 설정
		link.attr("href", "http://localhost:8888/async1");
		
		// 내용(text)에 들어갈 값 및 html 적용여부 설정
		helper.setText(doc.toString(), true);
		
		// 4) Mime 메시지 전송
		javaMailSender.send(message);
	}
}

 

MimeMessageTest6

- Spring 프로젝트 내 resource로 저장된 html 파일 전송

- Jsoup을 이용하여 문자열을 대체하는 방법

- 주소를 자동으로 생성해주는 ServletUriComponentsBuilder 클래스 이용

@SpringBootTest
public class MineMessageTest6 {

	// 의존성 주입 - 이메일 전송
	@Autowired
	private JavaMailSender javaMailSender;
	
	@Test
	public void test() throws MessagingException, FileNotFoundException, IOException {
		
		// 1) MimeMessage의 인스턴스 생성
		MimeMessage message = javaMailSender.createMimeMessage();
				
		// 2) MimeMessageHelper의 인스턴스 생성
		MimeMessageHelper helper = new MimeMessageHelper(message, false, "UTF-8");
		
		// 3) 정보 설정
		helper.setTo("eomhyunyoung@gmail.com");
		helper.setSubject("Jsoup 변환 테스트");
		
		// ** Spring 프로젝트 내 Resource 파일 읽기
		ClassPathResource resource = new ClassPathResource("email/template3.html");
		
		// ** 해당 Resource 파일의 내용을 StringBuffer로 연결
		StringBuffer buffer = new StringBuffer();
		try(Scanner sc = new Scanner(resource.getFile())){ // scanner 자동 close
			while(sc.hasNextLine()) {
				buffer.append(sc.nextLine());
			}
		}
		String text = buffer.toString();
		
		// StringBuffer로 생성한 문자열을 HTML로 분석(parse)
		Document doc = Jsoup.parse(text);
		
		// doc에서 'user-name'이라는 id를 가진 요소를 선택
		Element element = doc.getElementById("user-name"); 
		
		// 선택한 요소에 해당 문자열을 설정(대체)
		element.text("eomhyunyoung"); 
		
		// doc에서 'user-url'이라는 id를 가진 요소를 선택
		Element link = doc.getElementById("return-url");
		
		// http로 시작하는 전체 주소 자동 생성 도구
		// (주의) 서버에서 실행될 때만 정상적으로 구동(테스트에서는 80번 포트로 인식, 실제로는 프로젝트가 실행되는 포트 번호로 인식됨)
		String url = ServletUriComponentsBuilder.fromCurrentContextPath()
												.path("/async1")
												.toUriString();
		
		// 선택한 요소에 해당 이름의 속성(attribute)과 값을 설정
		link.attr("href", url);
		
		// 내용(text)에 들어갈 값 및 html 적용여부 설정
		helper.setText(doc.toString(), true);
		
		// 4) Mime 메시지 전송
		javaMailSender.send(message);
	}
}

 


Scheduler를 이용하여 기간이 만료된 인증번호 삭제

Schedular

- 일장 시간간격마다 또는 일정 시각(설정 가능)에 특정 작업을 반복하도록 하는 설정

 

** Cron 표현식

- Scheduler 시간을 설정할 때 사용

- 총 7개의 필드로 구성되어 있으며 년도는 생략 가능

<초> <분> <시> <일> <월> <요일> <년도>

 

필드 허용되는 값 허용되는 특수 문자
0-59 , - * /
0-59 , - * /
0-23 , - * /
1-31 , - * / L W
1-12 또는 JAN ~ DEC , - * /
요일 0-6 또는 SUN ~ SAT , - * / L #
년도 1970 ~ 2099 , - * /

 

- 특수 문자

* 모든 값
? 특정 값이 없음
- 범위 지정
, 여러 값을 지정
/ 증가하는 값을 지정
L 마지막 값을 지정
W 가까운 평일
# 몇 번째 무슨 요일인지 지정

 

- 예시

* * * * * * 매 초마다
*/1 * * * * * 매 1초마다
*/2 * * * * * 매 2초마다
10,20 * * * * * 매 분 10초, 20초마다
0 * * * * * 매 분마다
0 */1 * * * * 매 1분마다
0 */5 * * * * 매 5분마다
0 0 * * * * 매 시간마다
0 0 */2 * * * 매 2시간마다
0 0 6 * * W 매 주 수요일 아침 6시 정각마다
0 0 6 L * ? 매 월 마지막날 아침 6시 정각마다
0 0 6 ? * 4L 매 월 마지막 수요일 아침 6시 정각마다
0 0 6 ? * 4#3 매 월 3주차 수요일 아침 6시 정각마다

 

cert-mapper.xml

- 5분이 지난 인증 정보 삭제

- XML에서 '<'는 태그 시작을 나타내므로 문자열로 인식하도록 하기 위해서는 <![CDATA[ ]]> 를 사용한다

** <![CDATA[ ]]> : (Unparsed) Character Data, 즉, 파싱하지 않는 문자 데이터

<?xml version="1.0" encoding="UTF-8"?>

<!DOCTYPE mapper
  PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
  "https://mybatis.org/dtd/mybatis-3-mapper.dtd">

<mapper namespace="cert">
	
	<!-- 5분 이상 지난 인증정보 삭제 -->
	<delete id = "clear">
		<![CDATA[delete cert where when < sysdate - 5/24/60]]>
	</delete>

</mapper>

 

CertDao

- 5분이 지난 인증 정보 삭제

public interface CertDao {
	
	// 추상 메소드 - 만료된 인증정보 삭제
	void clear();
}

 

CertDaoImpl

- 5분이 지난 인증 정보 삭제

@Repository
public class CertDaoImpl implements CertDao {

	// 의존성 주입
	@Autowired
	private SqlSession sqlSession;

	// 추상 메소드 오버라이딩 - 만료된 인증정보 삭제
	@Override
	public void clear() {
		sqlSession.delete("cert.clear");
	}
}

 

Spring18emailApplication.java

- @EnableScheduling : Scheduler를 사용하기 위한 설정 

@EnableScheduling // 스케쥴러 사용 설정
@SpringBootApplication
public class Spring18emailApplication {

	public static void main(String[] args) {
		SpringApplication.run(Spring18emailApplication.class, args);
	}

}

 

** 프로젝트명 + Application.java 파일의 위치

 

SchedulerService

- 만료된 인증정보 삭제

public interface SchedulerService {

	// 추상 메소드 - 만료된 인증정보 삭제
	void clearCert();
}

 

SchedulerServiceImpl

- 만료된 인증정보 삭제

- @Scheduled에 설정된 시간마다 자동으로 지정된 메소드를 실행

@Service
public class SchedularServiceImpl implements SchedulerService {
	
	// 의존성 주입
	@Autowired
	private CertDao certDao;

	// 추상 메소드 오버라이딩 - 만료된 인증정보 삭제
	@Scheduled(cron = "* * * * * *") // 매초 매분 매시 매일 매월 매요일
	@Override
	public void clearCert() {
		certDao.clear();
	}
}

'국비교육 > 국비교육' 카테고리의 다른 글

day75 - 1114  (0) 2022.11.14
day73 - 1110  (0) 2022.11.12
day71 - 1108  (0) 2022.11.08
day70 - 1107  (0) 2022.11.07
day45 - 0928  (0) 2022.09.28