示范两个邮箱 1. 登陆网易邮箱163,在设置中打开并勾选POP3/SMTP/IMAP服务,然后会得到一个授权码,这个邮箱和授权码将用作登陆认证。
2. 使用qq邮箱,开启POP3/SMTP服务服务
项目目录如下
为什么要建立数据库呢? (当然是为了之后写项目时邮箱服务的扩展性)
create database heartemail;
use heartemail;
-- ----------------------------
-- Table structure for email_config
-- ----------------------------
DROP TABLE IF EXISTS `email_config`;
CREATE TABLE `email_config` (
`id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT 'ID',
`from_user` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '收件人',
`host` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '邮件服务器SMTP地址',
`pass` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '密码',
`port` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '端口',
`user` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '发件者用户名',
PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 2 CHARACTER SET = utf8 COLLATE = utf8_general_ci COMMENT = '邮箱配置' ROW_FORMAT = Compact;
-- ----------------------------
-- Table structure for email_content
-- ----------------------------
DROP TABLE IF EXISTS `email_content`;
CREATE TABLE `email_content` (
`id` int(11) NOT NULL AUTO_INCREMENT COMMENT '编号',
`fromuser` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '发送者',
`touser` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '接收者',
`mail_title` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '邮件标题',
`mail_content` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '邮件内容',
`createtime` datetime(0) NULL DEFAULT CURRENT_TIMESTAMP COMMENT '发送时间',
PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-freemarker</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>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.6</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<!--邮件依赖-->
<dependency>
<groupId>javax.mail</groupId>
<artifactId>mail</artifactId>
<version>1.4.7</version>
</dependency>
<!--工具包-->
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>5.3.4</version>
</dependency>
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
<version>2.6</version>
</dependency>
<!--单元测试-->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
<version>2.8.3</version>
</dependency>
</dependencies>
spring:
freemarker:
suffix: .ftl
datasource:
driver-class-name: com.mysql.jdbc.Driver
username: root
password: 123456
url: jdbc:mysql://localhost:3306/heartemail?useUnicode=true&characterEncoding=utf8&serverTimezone=UTC&zeroDateTimeBehavior=convertToNull
#配置 Jpa
jpa:
properties:
hibernate:
ddl-auto: none
dialect: org.hibernate.dialect.MySQL5InnoDBDialect
open-in-view: true
@Entity
@Data
@Table(name = "email_config")
public class EmailConfig implements Serializable {
@Id
@Column(name = "id")
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
/*邮件服务器SMTP地址*/
@Column(name = "host")
private String host;
/*邮件服务器 SMTP 端口*/
@Column(name = "port")
private String port;
/*发件者用户名*/
@Column(name = "user")
private String user;
/*密码*/
@Column(name = "pass")
private String pass;
/*收件人*/
@Column(name = "from_user")
private String fromUser;
}
@Entity
@Data
@Table(name = "email_content")
public class EmailContent {
@Id
@Column(name = "id")
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
/*邮件发送者*/
@Column(name = "fromuser")
private String fromuser;
/*邮件接收者*/
@Column(name = "touser")
private String touser;
/*邮件标题*/
@Column(name = "mail_title")
private String mailTitle;
/*邮件内容*/
@Column(name = "mail_content")
private String mailContent;
/*邮件发送时间*/
@Column(name = "createtime")
@JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
private String createtime;
}
@Data
@NoArgsConstructor
public class EmailVO {
/**
* 收件人,支持多个收件人
*/
private List<String> tos;
/*邮箱标题*/
private String subject;
/*邮箱内容*/
private String content;
}
package cn.kt.heartmail.utils;
import javax.crypto.Cipher;
import javax.crypto.SecretKey;
import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.DESKeySpec;
import javax.crypto.spec.IvParameterSpec;
import java.nio.charset.StandardCharsets;
/**
* Created by tao.
* Date: 2021/7/21 10:40
* 描述:
*/
public class EncryptUtils {
private static final String STR_PARAM = "Passw0rd";
private static Cipher cipher;
private static final IvParameterSpec IV = new IvParameterSpec(STR_PARAM.getBytes(StandardCharsets.UTF_8));
private static DESKeySpec getDesKeySpec(String source) throws Exception {
if (source == null || source.length() == 0) {
return null;
}
cipher = Cipher.getInstance("DES/CBC/PKCS5Padding");
String strKey = "Passw0rd";
return new DESKeySpec(strKey.getBytes(StandardCharsets.UTF_8));
}
/**
* 对称加密
*/
public static String desEncrypt(String source) throws Exception {
DESKeySpec desKeySpec = getDesKeySpec(source);
SecretKeyFactory keyFactory = SecretKeyFactory.getInstance("DES");
SecretKey secretKey = keyFactory.generateSecret(desKeySpec);
cipher.init(Cipher.ENCRYPT_MODE, secretKey, IV);
return byte2hex(
cipher.doFinal(source.getBytes(StandardCharsets.UTF_8))).toUpperCase();
}
/**
* 对称解密
*/
public static String desDecrypt(String source) throws Exception {
byte[] src = hex2byte(source.getBytes(StandardCharsets.UTF_8));
DESKeySpec desKeySpec = getDesKeySpec(source);
SecretKeyFactory keyFactory = SecretKeyFactory.getInstance("DES");
SecretKey secretKey = keyFactory.generateSecret(desKeySpec);
cipher.init(Cipher.DECRYPT_MODE, secretKey, IV);
byte[] retByte = cipher.doFinal(src);
return new String(retByte);
}
private static String byte2hex(byte[] inStr) {
String stmp;
StringBuilder out = new StringBuilder(inStr.length * 2);
for (byte b : inStr) {
stmp = Integer.toHexString(b & 0xFF);
if (stmp.length() == 1) {
// 如果是0至F的单位字符串,则添加0
out.append("0").append(stmp);
} else {
out.append(stmp);
}
}
return out.toString();
}
private static byte[] hex2byte(byte[] b) {
int size = 2;
if ((b.length % size) != 0) {
throw new IllegalArgumentException("长度不是偶数");
}
byte[] b2 = new byte[b.length / 2];
for (int n = 0; n < b.length; n += size) {
String item = new String(b, n, 2);
b2[n / 2] = (byte) Integer.parseInt(item, 16);
}
return b2;
}
}
package cn.kt.heartmail.repository;
import cn.kt.heartmail.domian.EmailConfig;
import org.springframework.data.jpa.repository.JpaRepository;
/**
* Created by tao.
* Date: 2021/7/21 10:39
* 描述:
*/
public interface EmailRepository extends JpaRepository<EmailConfig,Long> {
}
package cn.kt.heartmail.service;
import cn.kt.heartmail.domian.EmailConfig;
import cn.kt.heartmail.domian.vo.EmailVO;
/**
* Created by tao.
* Date: 2021/7/21 10:35
* 描述:
*/
public interface EmailService {
/**
* 更新邮件配置
*
* @param emailConfig 邮箱配置
* @param old /
* @return /
* @throws Exception /
*/
EmailConfig config(EmailConfig emailConfig, EmailConfig old) throws Exception;
/**
* 查询配置
*
* @return EmailConfig 邮件配置
*/
EmailConfig find();
/**
* 发送邮件
*
* @param emailVo 邮件发送的内容
* @param emailConfig 邮件配置
* @throws Exception /
*/
void send(EmailVO emailVo, EmailConfig emailConfig);
}
package cn.kt.heartmail.service.impl;
import cn.hutool.extra.mail.Mail;
import cn.hutool.extra.mail.MailAccount;
import cn.kt.heartmail.domian.EmailConfig;
import cn.kt.heartmail.domian.vo.EmailVO;
import cn.kt.heartmail.repository.EmailRepository;
import cn.kt.heartmail.service.EmailService;
import cn.kt.heartmail.utils.EncryptUtils;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.cache.annotation.CachePut;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.Optional;
/**
* Created by tao.
* Date: 2021/7/21 10:37
* 描述:
*/
@Service
@RequiredArgsConstructor
@Slf4j
public class EmailServiceImpl implements EmailService {
private final EmailRepository emailRepository;
@Override
@Transactional(rollbackFor = Exception.class)
public EmailConfig config(EmailConfig emailConfig, EmailConfig old) throws Exception {
emailConfig.setId(1L);
if (!emailConfig.getPass().equals(old.getPass())) {
// 对称加密
emailConfig.setPass(EncryptUtils.desEncrypt(emailConfig.getPass()));
}
return emailRepository.save(emailConfig);
}
@Override
public EmailConfig find() {
Optional<EmailConfig> emailConfig = emailRepository.findById(1L);
return emailConfig.orElseGet(EmailConfig::new);
}
@Override
@Transactional(rollbackFor = Exception.class)
public void send(EmailVO emailVo, EmailConfig emailConfig) {
if (emailConfig == null) {
log.error("请先配置,再操作");
}
// 封装
MailAccount account = new MailAccount();
account.setUser(emailConfig.getUser());
account.setHost(emailConfig.getHost());
account.setPort(Integer.parseInt(emailConfig.getPort()));
account.setAuth(true);
try {
// 对称解密
account.setPass(EncryptUtils.desDecrypt(emailConfig.getPass()));
} catch (Exception e) {
log.error(e.getMessage());
}
account.setFrom(emailConfig.getUser() + "<" + emailConfig.getFromUser() + ">");
// ssl方式发送
account.setSslEnable(true);
// 使用STARTTLS安全连接
account.setStarttlsEnable(true);
String content = emailVo.getContent();
// 发送
try {
int size = emailVo.getTos().size();
Mail.create(account)
.setTos(emailVo.getTos().toArray(new String[size]))
.setTitle(emailVo.getSubject())
.setContent(content)
.setHtml(true)
//关闭session
.setUseGlobalSession(false)
.send();
} catch (Exception e) {
log.error(e.getMessage());
}
}
}
接下来可以进行邮箱服务测试了 稍微看一下代码,可以发现在逻辑代码中存数据库的邮箱配置只做了一条数据,只是为了这次的测试方便,如果在实际场景的有需要配置多个邮箱服务的邮箱账号,可以随时自己做一下处理,扩展性很高。
由于数据库中数据是加密的,所以需要自己写一个测试代码对数据库添加数据
@Autowired
private EmailService emailService;
@Test
void config() throws Exception {
EmailConfig emailConfig = new EmailConfig();
//EmailConfig emailConfig = emailService.find();
//选择qq邮箱服务
emailConfig.setHost("smtp.qq.com");
//发件者用户名 qq邮箱
emailConfig.setUser("***@qq.com");
//邮箱服务授权码
emailConfig.setPass("zjtyma*****jdfc");
//收件人
emailConfig.setFromUser("***@qq.com");
emailService.config(emailConfig, emailConfig);
}
生成一条数据后
@Test
void send() {
EmailConfig emailConfig = emailService.find();
EmailVO emailVO = new EmailVO();
ArrayList<String> list = new ArrayList<>();
list.add("kt1205529635@aliyun.com");
emailVO.setTos(list);
emailVO.setContent("我是测试邮件内容");
emailVO.setSubject("Hello,Email");
emailService.send(emailVO, emailConfig);
}
发送邮件成功后
邮件内容模板我选择freemarket,通过自己的测试,只要是静态的html样式,文字,图片在邮件中都可以显示出来(动态获取的就无法渲染)
在 resources/template 目录下创建code.ftl,内容如下
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
<style>
@page {
margin: 0;
}
</style>
</head>
<body style="margin: 0px;
padding: 0px;
font: 100% SimSun, Microsoft YaHei, Times New Roman, Verdana, Arial, Helvetica, sans-serif;
color: #000;">
<div style="height: auto;
width: 820px;
min-width: 820px;
margin: 0 auto;
margin-top: 20px;
border: 1px solid #eee;">
<div style="padding: 10px;padding-bottom: 0px;">
<p style="margin-bottom: 10px;padding-bottom: 0px;">尊敬的用户,您好:</p>
<p style="text-indent: 2em; margin-bottom: 10px;">您正在申请邮箱验证,您的验证码为:</p>
<p style="text-align: center;
font-family: Times New Roman;
font-size: 22px;
color: #C60024;
padding: 20px 0px;
margin-bottom: 10px;
font-weight: bold;
background: #ebebeb;">${code}</p>
<div class="foot-hr hr" style="margin: 0 auto;
z-index: 111;
width: 800px;
margin-top: 30px;
border-top: 1px solid #DA251D;">
</div>
<div style="text-align: center;
font-size: 12px;
padding: 20px 0px;
font-family: Microsoft YaHei;">
Copyright ©${.now?string["yyyy年MM月dd日"]} 如我西沉 All Rights Reserved.
</div>
</div>
</div>
</body>
</html>
在测试代码中进行读取模板文件,动态传值验证码 code,并且进行渲染。
@Test
void sendCCode() {
TemplateEngine engine = TemplateUtil.createEngine(new TemplateConfig("template", TemplateConfig.ResourceMode.CLASSPATH));
Template template = engine.getTemplate("/code.ftl");
String code = "888888";
EmailConfig emailConfig = emailService.find();
EmailVO emailVO = new EmailVO();
ArrayList<String> list = new ArrayList<>();
list.add("kt1205529635@aliyun.com");
emailVO.setTos(list);
emailVO.setContent(template.render(Dict.create().set("code", code)));
emailVO.setSubject("如我西沉的验证码");
emailService.send(emailVO, emailConfig);
}
其实这次使用邮箱服务最主要的是使用了 hutool 工具包和 mail 依赖项,使得我们只需要简单的配置一下就可以做到发邮件得到功能。但为什么需要写这么繁琐的代码呢?原因是这样维护和开发起来很方便,如果需要多个邮箱账号服务,收件人是多个,使用起来根本不需要改什么代码,这就是封装的好处,还有一点就是使用了数据库,如果需要做一个很大的邮箱服务系统,这套代码同适用。 细心的老铁会发现我们建立了两张表,为什么只使用了一张 email_config 邮箱服务的配置表,另外一张表 email_content 我想用来存每次发送邮件的邮件信息的,可以在每次发送邮件前,将本次要发送的邮件内容做一次数据库存储,可以数据备份,方便之后的数据可视化,主要还是为了扩展性。(提示一下:可以做一个给心爱之人的每日邮箱哦 ^ _ ^)