SpringBoot

Spring学习笔记(二十四)——springboot实现邮箱服务

Nick · 7月22日 · 2021年 · · 本文13265字 · 阅读34分钟204

邮箱服务

邮箱服务的一些概念

  1. 为什么要用邮箱服务
    互联网发展到现在,大家都知道发送邮件应该是网站的必备功能之一:用户注册发送邮箱验证、忘记密码、监控提醒以及发送营销信息等,使用邮箱服务也可以推送一些信息给用户。
  2. 什么是SMTP
    SMTP全称为Simple Mail Transfer Protocol(简单邮件传输协议),它是一组用于从源地址到目的地址传输邮件的规范,通过它来控制邮件的中转方式。SMTP认证要求必须提供账号和密码才能登陆服务器,其设计目的在于避免用户受到垃圾邮件的侵扰。
  3. 什么是POP3
    POP3全称为Post Office Protocol 3(邮局协议),POP3支持客户端远程管理服务器端的邮件。POP3常用于“离线”邮件处理,即允许客户端下载服务器邮件,然后服务器上的邮件将会被删除。目前很多POP3的邮件服务器只提供下载邮件功能,服务器本身并不删除邮件,这种属于改进版的POP3协议。
  4. 什么是IMAP
    IMAP全称为Internet Message Access Protocol(互联网邮件访问协议),IMAP允许从邮件服务器上获取邮件的信息、下载邮件等。IMAP与POP类似,都是一种邮件获取协议。
  5. IMAP和POP3协议有什么不同呢?
    两者最大的区别在于,IMAP允许双向通信,即在客户端的操作会反馈到服务器上,例如在客户端收取邮件、标记已读等操作,服务器会跟着同步这些操作。而对于POP协议虽然也允许客户端下载服务器邮件,但是在客户端的操作并不会同步到服务器上面的,例如在客户端收取或标记已读邮件,服务器不会同步这些操作。

开启邮件服务

示范两个邮箱
1. 登陆网易邮箱163,在设置中打开并勾选POP3/SMTP/IMAP服务,然后会得到一个授权码,这个邮箱和授权码将用作登陆认证。
Spring学习笔记(二十四)——springboot实现邮箱服务-左眼会陪右眼哭の博客
2. 使用qq邮箱,开启POP3/SMTP服务服务

Spring学习笔记(二十四)——springboot实现邮箱服务-左眼会陪右眼哭の博客
在这里插入图片描述

springboot优雅的配置和使用邮箱服务

项目目录如下
在这里插入图片描述

1. 建立邮箱服务数据库

为什么要建立数据库呢?
(当然是为了之后写项目时邮箱服务的扩展性)

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;

2. pom.xml 项目依赖

<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>

3. application.yml(配置文件)

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

4. EmailConfig.java、EmailContent.java、EmailVO.java(实体类)

@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;
}

5. EncryptUtils.java(加密工具类)

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;
    }
}

6. EmailRepository.java(邮箱服务dao层)

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> {
}
  1. EmailService.java、EmailServiceImpl.java(邮箱服务service层,邮箱服务的核心代码)
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());
        }
    }
}

邮箱服务测试

接下来可以进行邮箱服务测试了
稍微看一下代码,可以发现在逻辑代码中存数据库的邮箱配置只做了一条数据,只是为了这次的测试方便,如果在实际场景的有需要配置多个邮箱服务的邮箱账号,可以随时自己做一下处理,扩展性很高。

1. 添加邮箱服务数据

由于数据库中数据是加密的,所以需要自己写一个测试代码对数据库添加数据

@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);
    }

生成一条数据后
在这里插入图片描述

2. 可以进行发送邮件的测试

代码如下:
  @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);
    }

发送邮件成功后

结果如下

在这里插入图片描述

3. 自定义邮件内容模板

邮件内容模板我选择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 我想用来存每次发送邮件的邮件信息的,可以在每次发送邮件前,将本次要发送的邮件内容做一次数据库存储,可以数据备份,方便之后的数据可视化,主要还是为了扩展性。(提示一下:可以做一个给心爱之人的每日邮箱哦 ^ _ ^)

0 条回应
在线人数:1人 来访统计
隐藏