1/Lời mở đầu.
Gửi mail là một trong những tính năng bắt buộc trong nhiều hệ thống hiện nay để phục vục cho các mục đích như xác nhận thông tin người dùng, kích hoạt tài khoản khi người dùng khi đăng ký...
Với mỗi ngôn ngữ lập trình, chính xác là các Framework của mỗi ngôn ngữ lập trình khác nhau sẽ có những cách triển khai chức năng này khác nhau.
Và trong bài viết hôm nay mình sẽ cùng anh em tìm hiểu xem với Spring Boot (Java) thì việc gửi mail này được thực hiện như thế nào.
2/Chúng ta code cái gì?
Để anh em nắm được nội dung cũng như idea thì mình sẽ mô tả những gì chúng làm trước. Trường hợp trong bài viết này là khi chúng ta thực hiện đăng ký tài khoản trên một trang web nào đó.
Các hệ thống hiện đại họ sẽ không tin tưởng người dùng tuyệt đối 100% được. Chính vì vậy khi đăng ký tài khoản hoặc các thao tác đòi hỏi tính bảo mật cao thường họ sẽ phải xác thực thông qua một bên thứ 3.
Nói về xác thực sử dụng bên thứ 3 thì có rất nhiều phương pháp khác nhau. Ví dụ như trong bức ảnh trên hệ thống đã TỰ SINH ra các thông tin bảo mật (mật khẩu, token...) rồi gửi lại cho khách hàng thông qua email họ cung cấp.
Và ở đây giả sử mình sử dụng hệ thống Gmail của Google thì chắc chắn là về độ bảo mật là có thể tin tưởng được. Chỉ khi gmail đó tồn tại và thuộc sở hữu của chính người dùng kia thì họ mới có được thông tin gửi về từ ứng dụng.
Có thể nói đây là một phương pháp xác thực khá phổ biến hiện nay, và trong bài viết này mình sẽ cùng anh em triển khai nó trong một ứng dụng Spring Boot.
3/Các công cụ mình sử dụng.
+ IDE: IntelliJ Idea (ver 2021.1) (các bạn có thể sử dụng các công cụ khác như Eclipse, Netbean...)
+ JDK: Trong bài viết này mình cài JDK 11 (các bạn có thể chọn các phiên bản JDK khác như 8, hoặc các phiên bản phiên bản lớn hơn. Nhưng mình thấy sử dụng JDK 8 hoặc 11 là ổn định nhất)
+ Tools: Trong bài viết này mình sử dụng Maven (các bạn có thể sử dụng các công cụ build khác như Gradle)
+ Thư viện: Bài viết này mình sử dụng Spring boot (2.5.3) và một số thư viện khác minh sẽ đề cập sau trong bài viết.
4/Khởi tạo ứng dụng Spring Boot.
Có rất nhiều các khởi tạo một ứng dụng Spring Boot, cách đơn giản nhất là các bạn có thể truy cập vào trang: https://start.spring.io/ vào tạo mới project như sau:
Đầu tiên nói về cấu trúc của chương trình thì các bạn có thể tham khảo trong phần mã nguồn mình để ở cuối bài viết.
Tip: Các bạn có thể download mã nguồn của mình về sau đó vừa đọc bài viết vừa đối chiếu với code trong đó. Cách này có thể sẽ giúp các bạn dễ hình dung hơn!
5.1 - Cấu hình
Các thư viện mà mình sử dụng gồm có
spring-boot-starter-mail
, spring-boot-starter thymeleaf
, spring-boot-starter-web
, org.projectlombok
Cụ thể các bạn có thể xem trong file
pom.xml
bên dưới như sau:<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.5.3</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.laptrinhb2a.springboot</groupId>
<artifactId>mail-sender</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>mail-sender</name>
<description>Spring Boot Send Mail</description>
<properties>
<java.version>11</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-mail</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.swagger</groupId>
<artifactId>swagger-annotations</artifactId>
<version>1.5.20</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<excludes>
<exclude>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</exclude>
</excludes>
</configuration>
</plugin>
</plugins>
</build>
</project>
Tiếp theo trong phần cấu hình thì trong bài viết này mình sử dụng file application.yml
để cấu hình với nội dung như sau (các bạn có thể sử dụng file .properties
với nội dung tương tự)mailServer:
host: smtp.gmail.com
port: 587
email: [email của bạn]
password: [mật khẩu đăng nhập email đó]
protocol: smtp
isSSL: false
Đồng thời bài viết này mình sử dụng Gmail của Google nên có một lưu ý đó là các bạn phải mở chế độ cho phép truy cập từ ứng dụng bên ngoài của gmail tại đây thì mới có thể gửi mail được.Và sau đó mình tạo một lớp là
MailConfig.java
dùng để đọc các thông tin cấu hình bên trên và trả về một đối tượng JavaMailSender
như sau:@Configuration
public class MailConfig {
@Value("${mailServer.host}")
private String host;
@Value("${mailServer.port}")
private Integer port;
@Value("${mailServer.email}")
private String email;
@Value("${mailServer.password}")
private String password;
@Value("${mailServer.isSSL}")
private String isSSL;
@Bean
public JavaMailSender getJavaMailSender() {
JavaMailSenderImpl mailSender = new JavaMailSenderImpl();
mailSender.setHost(host);
mailSender.setPort(port);
mailSender.setUsername(email);
mailSender.setPassword(password);
mailSender.setDefaultEncoding("UTF-8");
Properties props = mailSender.getJavaMailProperties();
props.put("mail.transport.protocol", "smtp");
props.put("mail.smtp.auth", "true");
props.put("mail.smtp.starttls.enable", "true");
props.put("mail.smtp.ssl.enable", isSSL);
props.put("mail.smtp.from", email);
props.put("mail.debug", "true");
return mailSender;
}
}
Tiếp theo trong bài viết này mình thực hiện gửi mail thông qua template, có nghĩa là mình không chỉ gửi một đoạn text thông thường để làm mail content.
Mình sẽ gửi một template html với các thông tin có thể thay đổi động tùy thuộc vào các giá trị đầu vào thông qua các biến đó là
name
, username
, password
. Các biến này sẽ được mình truyền vào trong quá trình tạo thông tin đối tượng.<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns:th="http://www.thymeleaf.org" xmlns="http://www.w3.org/1999/xhtml">
<head>
<link href='http://fonts.googleapis.com/css?family=Roboto' rel='stylesheet' type='text/css'/>
<style>
body {
font-family: 'Roboto', sans-serif;
font-size: 48px;
}
</style>
</head>
<body>
<p th:text="${'Xin chào, ' + name}"></p>
<p> Chúng tôi gửi thông tin truy cập hệ thống của bạn: </p>
<p th:text="${'- Tên truy cập: ' + username}"></p>
<p th:text="${'- Mật khẩu truy cập tạm thời: ' + password}"></p>
<p>Lưu ý: Đây là mật khẩu mặc định được tạo bởi hệ thống và chỉ có giá trị trong vòng 24h!</p>
<p>Bạn vui lòng đổi lại để đảm bảo an toàn thông tin. </p>
<p>Đây là email tự động vui lòng không trả lời.</p>
</body>
</html>
Và mình cũng cần một file cấu hình là
ThymeleafTemplateConfig.java
để có thể đọc nội dung file template bên trên (cho bạn nào chưa biết thì khoản này Spring Boot rất mạnh vì chỉ với vài dòng code như bên dưới là chúng ta đã có thể đọc được cấu hình rồi. Trong khi với các công nghệ cũ hơn thì việc này sẽ phức tạp một chút)@Configuration
public class ThymeleafTemplateConfig {
@Bean
public ITemplateResolver templateResolver() {
ClassLoaderTemplateResolver templateResolver = new ClassLoaderTemplateResolver();
templateResolver.setPrefix("templates/");
templateResolver.setSuffix(".html");
templateResolver.setTemplateMode("HTML");
templateResolver.setCharacterEncoding("UTF-8");
return templateResolver;
}
@Bean
public SpringTemplateEngine springTemplateEngine(ITemplateResolver templateResolver) {
SpringTemplateEngine templateEngine = new SpringTemplateEngine();
templateEngine.setTemplateResolver(templateResolver);
return templateEngine;
}
}
Một email thường sẽ bao gồm các thành phần MAIL SUBJECT (chủ đề của mail), MAIL TO (mail được gửi đến ai) và MAIL CONTENT (nội dung của mail đó)
Vì vậy trong code mình cũng phải tạo ra một lớp như vậy để trung chuyển dữ liệu mà mình nhận được từ client thành một đối tượng email và lớp đó là
DataMailDTO.java
@Data
@AllArgsConstructor
@NoArgsConstructor
public class DataMailDTO {
private String to;
private String subject;
private String content;
private Map<String, Object> props;
}
Trong đó: to: là email mình sẽ gửi thông tin đến (do người dùng cung cấp).
subject: chủ đề của email mình tạo.
content: là nội dung của email đó.
props: ở đây mình sử dụng để truyền các thông tin "động" ví dụ như mật khẩu, token... vào template và gửi cho người dùng.
Tiếp theo mình sẽ tạo các lớp mail service để phục vụ cho mục đích sinh ra mail template và thực hiện gửi email với các thông tin được truyền vào khi nhận từ người dùng cũng như thông tin sinh ra từ hệ thông.
Đầu tiên là interface
MailService.java
public interface MailService {
void sendHtmlMail(DataMailDTO dataMail, String templateName) throws MessagingException;
}
Và lớp MailServiceImpl.java
implements interface MailService.java
bên trên: @Service
@Slf4j
public class MailServiceImpl implements MailService {
@Autowired
private JavaMailSender mailSender;
@Autowired
private SpringTemplateEngine templateEngine;
@Override
public void sendHtmlMail(DataMailDTO dataMail, String templateName) throws MessagingException {
MimeMessage message = mailSender.createMimeMessage();
MimeMessageHelper helper = new MimeMessageHelper(message, true, "utf-8");
Context context = new Context();
context.setVariables(dataMail.getProps());
String html = templateEngine.process(templateName, context);
helper.setTo(dataMail.getTo());
helper.setSubject(dataMail.getSubject());
helper.setText(html, true);
mailSender.send(message);
}
}
1. Tại sao lại phải dùng một class để implements một interface như vậy.
2. Chúng ta sẽ dùng hàm
sendHtmlMail()
để làm gì, dùng ở đâu và dùng như thế nào?Với câu hỏi đâu tiên thì đơn giản đây là một cách để các bạn thực thi tính đa hình thôi, ví dụ mình có một class khác cũng implements interface
MailService.java
nhưng lại triển khai hàm sendHtmlMail()
theo một cách khác cách mình đang triển khai để phù hợp với nghiệp vụ hơn.Với câu hỏi thứ hai thì chúng ta sẽ dùng tới hàm này trong lúc tạo tạo một đối tượng mà web truyền xuống và dùng thế nào thì các đọc tiếp phần dưới nha.
Đến đây về cơ bản chúng ta đã xong việc với các phần liên quan đến việc cấu hình cũng như tạo mail service để sử dụng.
Tiếp theo chúng ta sẽ viết một API để nhận request từ ứng dụng web và thực hiện tạo thông tin của đối tượng tương ứng sau đó là gửi mail xác nhận cho người tạo.
#Tạo API
Để đơn giản trong bài viết này mình sẽ không sử dụng đến database mà chỉ tạo "tạm" thông tin đối tượng rồi gửi mail là xong (thực tế thì các bạn phải tạo được đối tượng đó, lưu vào trong database rồi mới được gửi mail xác thực)
Vì bỏ đi tầng database nên chúng ta sẽ bắt đầu từ tầng service luôn, ở đây mình tạo một interface là
ClientService.java
với nội dung như sau:public interface ClientService {
Boolean create(ClientSdi sdi);
}
Lớp ClientSdi.java
có nội dung như sau: @Data
public class ClientSdi {
private String name;
private String username;
private String email;
}
Và lớp ClientServiceImpl.java
sẽ implements interface ClientService.java
như sau: @Service
public class ClientServiceImpl implements ClientService {
@Autowired
private MailService mailService;
@Override
public Boolean create(ClientSdi sdi) {
try {
DataMailDTO dataMail = new DataMailDTO();
dataMail.setTo(sdi.getEmail());
dataMail.setSubject(Const.SEND_MAIL_SUBJECT.CLIENT_REGISTER);
Map<String, Object> props = new HashMap<>();
props.put("name", sdi.getName());
props.put("username", sdi.getUsername());
props.put("password", DataUtils.generateTempPwd(6));
dataMail.setProps(props);
mailService.sendHtmlMail(dataMail, Const.TEMPLATE_FILE_NAME.CLIENT_REGISTER);
return true;
} catch (MessagingException exp){
exp.printStackTrace();
}
return false;
}
}
Mình sẽ không giải thích quá chi tiết về hàm
create()
này vì các bạn có thể thấy ở đây mình đã tạo ra một DataMailDTO rồi gọi hàm sendHtmlMail()
và truyền tham số tương ứng để thực hiện việc gửi mail.Và đó cũng chính là câu trả lời cho câu hỏi thứ 2 mà mình đặt bên trên, thực tế thì nghiệp vụ ở chỗ tạo thông tin này còn phức tạp hợn rất nhiều, việc gửi mail gần như là bước cuối cùng khi mà các thông tin đã được lưu vào cơ sở dữ liệu.
Service xong rồi, bây giờ chúng ta tạo Controller để hoàn thiện API rồi test thôi. Controller sẽ có một method POST với request body là đối tượng mà chúng ta sẽ nhận từ ứng dụng web truyền xuống.
Đồng thời ở đây mình cũng đã gọi đến hàm
create()
ở tầng service để xử lý dữ liệu cũng như gửi mail cho người dùng.@RestController
@RequestMapping("/client")
public class ClientController {
@Autowired
private ClientService clientService;
@PostMapping(value = "create")
public ResponseEntity<Boolean> create(
@RequestBody ClientSdi sdi
) {
return ResponseEntity.ok(clientService.create(sdi));
}
}
#Test APIĐể cho đơn giản thì trong bài viết này mình sẽ sử dụng Postman để gửi một request với method POST tới địa chỉ http://localhost:8080/client/create với request body là:
{
"name" : "Nguyễn Văn An",
"username" : "an_123",
"email" : "[một email hợp lệ của bạn]"
}
Cụ thể các bạn có thể xem trong ảnh bên dưới mình gửi request bằng Postman.Vậy là trong bài viết mình đã cùng anh em tìm hiểu cách để gửi một email trong ứng dụng Spring Boot thông qua một ví dụ có thể nói là khá phổ biến hiện nay.
Tất nhiên bài viết chưa thể đi sâu vào nhiều trường hợp hơn vì mình cũng không muốn phức tạp và chèn nhiều thông tin vào.
Chi tiết hơn các bạn có thể tải mã nguồn mình đã viết ở đây và ngâm cứu thêm nhé. Hẹn gặp lại anh em trong các bài viết tiếp theo nhé.
bài rất chi tiết, cảm ơn tác giả
Trả lờiXóa