前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Spring学习笔记(二十四)——springboot实现邮箱服务

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

作者头像
不愿意做鱼的小鲸鱼
发布2022-09-26 18:07:38
3940
发布2022-09-26 18:07:38
举报
文章被收录于专栏:web全栈web全栈

邮箱服务

邮箱服务的一些概念

  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实现邮箱服务-左眼会陪右眼哭の博客
Spring学习笔记(二十四)——springboot实现邮箱服务-左眼会陪右眼哭の博客

2. 使用qq邮箱,开启POP3/SMTP服务服务

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

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

项目目录如下

在这里插入图片描述
在这里插入图片描述
1. 建立邮箱服务数据库

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

代码语言:javascript
复制
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 项目依赖
代码语言:javascript
复制
<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(配置文件)
代码语言:javascript
复制
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(实体类)
代码语言:javascript
复制
@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;
}
代码语言:javascript
复制
@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;
}
代码语言:javascript
复制
@Data
@NoArgsConstructor
public class EmailVO {

    /**
     * 收件人,支持多个收件人
     */
    private List<String> tos;

    /*邮箱标题*/
    private String subject;

    /*邮箱内容*/
    private String content;
}
5. EncryptUtils.java(加密工具类)
代码语言:javascript
复制
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层)
代码语言:javascript
复制
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层,邮箱服务的核心代码)
代码语言:javascript
复制
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);
}
代码语言:javascript
复制
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. 添加邮箱服务数据

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

代码语言:javascript
复制
@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. 可以进行发送邮件的测试
代码如下:
代码语言:javascript
复制
  @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,内容如下

代码语言:javascript
复制
<!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,并且进行渲染。

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

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 邮箱服务
    • 邮箱服务的一些概念
      • 开启邮件服务
        • springboot优雅的配置和使用邮箱服务
          • 1. 建立邮箱服务数据库
          • 2. pom.xml 项目依赖
          • 3. application.yml(配置文件)
          • 4. EmailConfig.java、EmailContent.java、EmailVO.java(实体类)
          • 5. EncryptUtils.java(加密工具类)
          • 6. EmailRepository.java(邮箱服务dao层)
        • 邮箱服务测试
          • 1. 添加邮箱服务数据
          • 2. 可以进行发送邮件的测试
          • 3. 自定义邮件内容模板
      • 总结
      相关产品与服务
      数据库
      云数据库为企业提供了完善的关系型数据库、非关系型数据库、分析型数据库和数据库生态工具。您可以通过产品选择和组合搭建,轻松实现高可靠、高可用性、高性能等数据库需求。云数据库服务也可大幅减少您的运维工作量,更专注于业务发展,让企业一站式享受数据上云及分布式架构的技术红利!
      领券
      问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档