当前位置:   article > 正文

【发邮件】SpringBoot 通过 hutool 方式发送邮件_hutool exchange

hutool exchange

SpringBoot 通过 hutool 方式发送邮件

本文的码云地址:点击传送

本文项目效果:

  1. 发送普通邮件
  2. 发送随机验证码邮件并把验证码缓存到redis
  3. 发送带格式模板的邮件

本项目代码都在码云里,传送门在上面,我这里把几个重点的地方详讲一下,所贴代码会删减一部分不重要的,以便阅读

1. 项目结构

在这里插入图片描述


2. 项目配置

application.yml

【注意】如果是腾讯个人qq邮箱,这里的密码不是邮箱登录密码,而是授权码,否则将会出现密码不对的情况。
授权码:网页QQ邮箱 > 设置 > 账户 > POP3/IMAP/SMTP/Exchange/CardDAV/CalDAV服务 > 开启STMP并获得授权码

server:
  port: 8080

#邮箱验证码有效时间/秒
code:
  expiration: 300

# 电子邮件配置
# 如果是腾讯个人qq邮箱,这里的密码不是邮箱登录密码,而是授权码:
# 授权码:网页QQ邮箱 > 设置 > 账户 > POP3/IMAP/SMTP/Exchange/CardDAV/CalDAV服务 > 开启STMP并获得授权码
email:
  host: smtp.qiye.aliyun.com # SMTP地址
  port: 465
  from: 昵称<xxx@xxx.com> # 发件人昵称
  pass: xxxxxx
  user: xxx@xxx.com # 邮箱地址

# 验证码有效时长(秒)
verification:
  expiration-time: 600

spring:
  redis:
    database: 2
    host: xxx.xxx.xxx.xxx
    port: xxxxx
    password: xxxxxx
    timeout: 10000ms

#是否开启 swagger-ui
swagger:
  enabled: true
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32



3. 邮件配置类 EmailConfig.java

这个配置类从上面的配置文件中读取数据填入到属性中。

import io.swagger.annotations.ApiModelProperty;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import javax.persistence.Entity;

@Component
public class EmailConfig {

    /**
     * 发送方 邮件服务器 SMTP 地址
     */
    @Value("${email.host}")
    @ApiModelProperty(value = "发送方 邮件服务器SMTP地址")
    private String host;

    /**
     * 发送方 邮件服务器 SMTP 端口
     */
    @Value("${email.port}")
    @ApiModelProperty(value = "发送方 邮件服务器 SMTP 端口")
    private String port;

    /**
     * 发送方 昵称
     */
    @Value("${email.from}")
    @ApiModelProperty(value = "发送方 昵称")
    private String from;

    /**
     * 发送方 密码
     */
    @Value("${email.pass}")
    @ApiModelProperty(value = "发送方 密码")
    private String pass;

    /**
     * 发送方 邮箱地址
     */
    @Value("${email.user}")
    @ApiModelProperty(value = "发送方 邮箱地址")
    private String user;

	//--TODO 此处省略 Get、Set 方法
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45

【注意1】这个类需要加 @Component 注解才能被 Spring 扫描到
【注意2】这个类直接 new 时,属性中的值是空的,是因为被 new 是我们主动创建而不是被 Spring 创建出来的,所以属性值并没有被注入。所以我们需要在使用时使用 @Autowired 注解,被 Spring 托管。

本项目中,采用了一个巧妙的方式,即:该配置类可外部设置并以参数形式传入,如果该参数为null,那么这个配置就有 Spring 负责注入配置文件的数据,等会儿在下方的 4【EmailServiceImpl.java】就能看到。



4. 邮件逻辑层实现层 EmailServiceImpl.java

  1. 当邮件发送模块没有问题并作为工具被使用时,建议使用异步执行,这样有效的降低请求的响应时间。
  2. 发件邮箱配置可外部参数传入,是为了方便上层使用其他邮箱作为发送方预留的
@Service
public class EmailServiceImpl implements EmailService {

    /**
     * 验证码有效时长(秒)
     */
    @Value("${verification.expiration-time}")
    private Long expirationTime;

    @Autowired
    private RedisUtils redisUtils;

	// 这里就是交由 Spring 托管的配置类,为默认配置
    @Autowired
    private EmailConfig defaultConfig;

	/**
     * 发送邮件
     * @param emailVo 邮件发送的内容
     * @param emailConfig 邮件配置,null:使用默认配置
     * @throws Exception /
     */
    @Override
    @Async // 发送邮件为异步执行
    @Transactional(rollbackFor = Exception.class)
    public void send(EmailVo emailVo, EmailConfig emailConfig) {
    	// 这里可以选择使用外部传入的配置,如果外部传入为null,那么便使用默认配置。
        if(emailConfig == null) {
            emailConfig = defaultConfig;
        } else if (StringUtils.isBlank(emailConfig.getHost()) || StringUtils.isBlank(emailConfig.getPort())
                || StringUtils.isBlank(emailConfig.getFrom()) || StringUtils.isBlank(emailConfig.getPass())
                || StringUtils.isBlank(emailConfig.getUser())) {
            throw new BadConfigurationException("请先配置邮箱数据,再操作!");
        }

        MailAccount account = new MailAccount();
        // 发件人昵称
        account.setFrom(emailConfig.getFrom());
        // 发件人邮箱地址
        account.setUser(emailConfig.getUser());
        account.setHost(emailConfig.getHost());
        account.setPort(Integer.parseInt(emailConfig.getPort()));

        account.setAuth(true);
        account.setPass(emailConfig.getPass());

        // ssl 方式发送
        account.setSslEnable(true);
        // 使用STARTTLS 安全连接
        account.setStarttlsEnable(true);
        // 发送
        try {
           int size = emailVo.getTos().size();
            Mail.create(account)
                    .setTos(emailVo.getTos().toArray(new String[size]))
                    .setTitle(emailVo.getSubject())
                    .setContent(emailVo.getContent())
                    .setHtml(true)
                    // 关闭session
                    .setUseGlobalSession(false)
                    .send();
        } catch (Exception e) {
            throw new BadRequestException(e.getMessage());
        }
    }

	/**
     * 获取验证码的邮件参数类
     * @param toEmail 目标邮箱
     * @return
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public EmailVo getVerificationCodeEmailVo(String toEmail) {
        String content;
        String redisKey = EmailCodeEnum.VERIFICATION_CODE.getKey() + toEmail;

        TemplateEngine engine = TemplateUtil.createEngine(new TemplateConfig("template", TemplateConfig.ResourceMode.CLASSPATH));
        Template template = engine.getTemplate("email/verificationCode.ftlh");
        Object oldCode = redisUtils.get(redisKey);

        // 如果Redis不存在有效验证码,就创建一个新的
        // 存在就使用原来的验证码,并将有效期更新
        if (oldCode == null) {
            String code = RandomUtil.randomNumbers(6); // 6位验证码,修改这里可以更改位数
            // 存入缓存
            boolean saveResult = redisUtils.set(redisKey, code, expirationTime);
            if(!saveResult) {
                throw new RedisException("Redis服务异常! 验证码未保存成功!");
            }
            content = template.render(Dict.create().set("code", code));
        } else {
        	// 重置Redis中这个key的有效期
            redisUtils.expire(redisKey, expirationTime);
            content = template.render(Dict.create().set("code", oldCode));
        }
        return new EmailVo(Collections.singletonList(toEmail), "Hutool-" + EmailCodeEnum.VERIFICATION_CODE.getDescription(), content);
    }

	/**
     * 获取简单通知的邮件参数类
     * @param toEmail 目标邮箱
     * @return
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public EmailVo getSimpleNotificationEmailVo(String toEmail, String msg) {
        String redisKey = EmailCodeEnum.VERIFICATION_CODE.getKey() + toEmail;
        TemplateEngine engine = TemplateUtil.createEngine(new TemplateConfig("template", TemplateConfig.ResourceMode.CLASSPATH));
        Template template = engine.getTemplate("email/simple_notification.ftlh");
        String content = template.render(Dict.create().set("msg", msg));
        return new EmailVo(Collections.singletonList(toEmail), "Hutool-" + EmailCodeEnum.NOTIFICATION.getDescription(), content);
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114



5. 邮件控制层 EmailController.java

很显然,控制器没什么好讲的~

如果代码写的很完善了,那么代码中的很多校验都可以删除或者放到前端,比如:判断目标邮箱、邮件标题等不为空等。

@RestController
@RequestMapping("api/email")
@Api(tags = "工具:邮件管理")
public class EmailController {

    @Autowired
    private EmailService emailService;

    /**
     * 发送邮件
     * @param toEmail 目标邮箱
     * @param subject 邮件标题
     * @param content 邮件内容
     * @return
     */
    @PostMapping("/sendMail")
    public String SendEmail(@RequestParam String toEmail, @RequestParam String subject, @RequestParam String content){
        if(StringUtils.isBlank(toEmail)) {
            throw  new BadRequestException("目标邮箱不能为空!");
        }
        if(StringUtils.isBlank(subject)) {
            throw new BadRequestException("邮件标题不能为空!");
        }
        if(StringUtils.isBlank(content)) {
            throw new BadRequestException("邮件内容不能为空!");
        }
        List<String> toEmails = new ArrayList<>();
        toEmails.add(toEmail);
        EmailVo emailVo = new EmailVo(toEmails, subject, content);
        emailService.send(emailVo, null);
        return "邮件已发送!";
    }

    /**
     * 发送验证码
     * @param toEmail 目标邮箱
     * @return
     */
    @PostMapping("/sendVerificationCode")
    public String SendVerificationCode(@RequestParam String toEmail) {
        EmailVo emailVo = emailService.getVerificationCodeEmailVo(toEmail);
        emailService.send(emailVo, null);
        return "邮件已发送!";
    }

    /**
     * 发送验证码
     * @param toEmail 目标邮箱
     * @param msg 通知的信息
     * @return
     */
    @PostMapping("/sendSimpleNotification")
    public String SendSimpleNotification(@RequestParam String toEmail, @RequestParam String msg) {
        if(StringUtils.isBlank(msg)) {
            throw new BadRequestException("通知信息为空!");
        }
        EmailVo emailVo = emailService.getSimpleNotificationEmailVo(toEmail, msg);
        emailService.send(emailVo, null);
        return "邮件已发送!";
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/在线问答5/article/detail/908892
推荐阅读
相关标签
  

闽ICP备14008679号