yuluo829 发表于 2022-10-14 12:37

Spring boot 整合邮件实现验证码注册功能

本帖最后由 yuluo829 于 2022-12-14 12:48 编辑

# Spring boot 整合邮件实现验证码注册功能

> spring boot整合邮件实现验证码注册功能,发送HTML模板邮件。

效果展示:

![在这里插入图片描述](https://img-blog.csdnimg.cn/d43966dbe5f04a618b75991d16497572.png#pic_center)


## 准备工作

### maven依赖

```xml
<!--发送邮件相关依赖-->
<!--邮箱验证码-->
<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>
```

### 邮箱授权码

QQ邮箱为例

设置-> 账户 -> 生成授权码

![在这里插入图片描述](https://img-blog.csdnimg.cn/3af941fd2b00483ba289958aff907471.png#pic_center)


## 编码

### controller

```java
package indi.yuluo.server.controller;

import indi.yuluo.common.model.Result;
import indi.yuluo.server.dto.EmailDTO;
import indi.yuluo.server.service.SendEmail;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.*;

import javax.annotation.Resource;
import javax.mail.MessagingException;

/**
* @Author: yuluo
* @CreateTime: 2022-10-1312:22
* @Description: TODO
*/

@Slf4j
@RestController
@RequestMapping("/email")
@Api("发送邮件控制器")
public class SendEmailController {

    @Resource
    private SendEmail sendEmail;

    /**
   * 发送邮件
   * @Param email 收件人信息
   * @Return 结果
   */
    @PostMapping("/sendEmail")
    @ApiOperation(value = "发送注册验证", httpMethod = "POST", notes = "邮箱发送")
    public Result<?> sendEmail(@RequestBody EmailDTO email) {

      email.setContent("");
      email.setSubject("xoj用户注册验证");

      String sendHtmlBool;
      try {
            sendHtmlBool = sendEmail.sendHtmlEmail(email);
      } catch (MessagingException e) {
            throw new RuntimeException("邮件发送失败!");
      }

      return Result.success(sendHtmlBool);

    }

}
```

### DTO

```java
package indi.yuluo.server.dto;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotEmpty;

/**
* @Author: yuluo
* @CreateTime: 2022-10-1312:00
* @Description: TODO
*/

@Data
@AllArgsConstructor
@NoArgsConstructor
public class EmailDTO {

    /**
   * 发送邮箱列表
   */
    @NotEmpty
    private String to;

    /**
   * 主题
   */
    @NotBlank
    private String subject;

    /**
   * 内容
   */
    @NotBlank
    private String content;

}
```

### Service

接口

```java
package indi.yuluo.server.service;

import indi.yuluo.server.dto.EmailDTO;

import javax.mail.MessagingException;

/**
* @Author: yuluo
* @CreateTime: 2022-10-1312:06
* @Description: TODO
*/

public interface SendEmail {

    /**
   * 发送邮件
   * @param email
   * @return
   */
    String sendHtmlEmail(EmailDTO email) throws MessagingException;

}
```

实现类

```java
package indi.yuluo.server.service.impl;

import indi.yuluo.server.dto.EmailDTO;
import indi.yuluo.server.service.SendEmail;
import indi.yuluo.server.utils.ValidateCodeUtils;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.mail.javamail.JavaMailSender;
import org.springframework.mail.javamail.MimeMessageHelper;
import org.springframework.stereotype.Service;
import org.thymeleaf.TemplateEngine;
import org.thymeleaf.context.Context;

import javax.annotation.Resource;
import javax.mail.MessagingException;
import javax.mail.internet.MimeMessage;
import java.util.Arrays;
import java.util.Date;

/**
* @Author: yuluo
* @CreateTime: 2022-10-1312:06
* @Description: TODO
*/

@Slf4j
@Service
public class SendHtmlEmailImpl implements SendEmail {

    @Resource
    private JavaMailSender mailSender;

    @Resource
    private TemplateEngine templateEngine;

    /**
   * 发送邮件
   * @param email
   * @return
   */
    @Override
    public String sendHtmlEmail(EmailDTO email) throws MessagingException {

      log.info(email.getTo());

      MimeMessage mimeMessage = mailSender.createMimeMessage();
      MimeMessageHelper helper = new MimeMessageHelper(mimeMessage,true, "UTF-8");
      helper.setSubject("XOJ注册验证码"); // 邮件的标题
      helper.setFrom("xxxx@qq.com"); // 发送者
      helper.setTo(email.getTo());    // 接受者
      helper.setSentDate(new Date()); //时间

      // 这里引入的是Template的Context
      Context context = new Context();
      //设置模板中的变量
      context.setVariable("verifyCode", Arrays.asList(ValidateCodeUtils.generateValidateCode4String(6).split("")));
      // 第一个参数为模板的名称
      String process = templateEngine.process("email.html", context); //这里不用写全路径
      // 第二个参数true表示这是一个html文本
      helper.setText(process,true);

      mailSender.send(mimeMessage);

      return "发送成功!";
    }
}
```

### HTML模板

```html
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>邮箱验证码</title>
<style>
    table {
      width: 700px;
      margin: 0 auto;
    }

    #top {
      width: 700px;
      border-bottom: 1px solid #ccc;
      margin: 0 auto 30px;
    }

    #top table {
      font: 12px Tahoma, Arial, 宋体;
      height: 40px;
    }

    #content {
      width: 680px;
      padding: 0 10px;
      margin: 0 auto;
    }

    #content_top {
      line-height: 1.5;
      font-size: 14px;
      margin-bottom: 25px;
      color: #4d4d4d;
    }

    #content_top strong {
      display: block;
      margin-bottom: 15px;
    }

    #content_top strong span {
      color: #f60;
      font-size: 16px;
    }

    #verificationCode {
      color: #f60;
      font-size: 24px;
    }

    #content_bottom {
      margin-bottom: 30px;
    }

    #content_bottom small {
      display: block;
      margin-bottom: 20px;
      font-size: 12px;
      color: #747474;
    }

    #bottom {
      width: 700px;
      margin: 0 auto;
    }

    #bottom div {
      padding: 10px 10px 0;
      border-top: 1px solid #ccc;
      color: #747474;
      margin-bottom: 20px;
      line-height: 1.3em;
      font-size: 12px;
    }

    #content_top strong span {
      font-size: 18px;
      color: #FE4F70;
    }

    #sign {
      text-align: right;
      font-size: 18px;
      color: #FE4F70;
      font-weight: bold;
    }

    #verificationCode {
      height: 100px;
      width: 680px;
      text-align: center;
      margin: 30px 0;
    }

    #verificationCode div {
      height: 100px;
      width: 680px;

    }

    .button {
      color: #FE4F70;
      margin-left: 10px;
      height: 80px;
      width: 80px;
      resize: none;
      font-size: 42px;
      border: none;
      outline: none;
      padding: 10px 15px;
      background: #ededed;
      text-align: center;
      border-radius: 17px;
      box-shadow: 6px 6px 12px #cccccc,
      -6px -6px 12px #ffffff;
    }

    .button:hover {
      box-shadow: inset 6px 6px 4px #d1d1d1,
      inset -6px -6px 4px #ffffff;
    }

</style>
</head>
<body>
<table>
<tbody>
<tr>
    <td>
      <div id="top">
      <table>
          <tbody><tr><td></td></tr></tbody>
      </table>
      </div>

      <div id="content">
      <div id="content_top">
          <strong>尊敬的用户:您好!</strong>
          <strong>
            您正在进行<span>注册账号</span>操作,请在验证码中输入以下验证码完成操作:
          </strong>
          <div id="verificationCode">
            <button class="button" th:each="a:${verifyCode}">[[${a}]]</button>
          </div>
      </div>
      <div id="content_bottom">
          <small>
            注意:此操作可能会修改您的密码、登录邮箱或绑定手机。如非本人操作,请及时登录并修改密码以保证帐户安全
            <br>(工作人员不会向你索取此验证码,请勿泄漏!)
          </small>
      </div>
      </div>
      <div id="bottom">
      <div>
          <p>此为系统邮件,请勿回复<br>
            请保管好您的邮箱,避免账号被他人盗用
          </p>
          <p id="sign">Xcode Online Judge</p>
      </div>
      </div>
    </td>
</tr>
</tbody>
</table>
</body>
```

### yml配置

```yml
spring
# 邮箱配置
mail:
    host: smtp.qq.com
    username: xxxx@qq.com
    password: xxxxx
    port: 465
    protocol: smtp
    default-encoding: utf-8
    properties:
      mail:
      smtp:
          auth: true
          starttls:
            enable: true
            required: true
          ssl:
            enable: true
          socketFactory:
            port: 465
            class: javax.net.ssl.SSLSocketFactory
            
# 模板配置
thymeleaf:
prefix: classpath:/template/#prefix:指定模板所在的目录
check-template-location: true#check-tempate-location: 检查模板路径是否存在
cache: true#cache: 是否缓存,开发模式下设置为false,避免改了模板还要重启服务器,线上设置为true,可以提高性能。
suffix: .html
#encoding: UTF-8
#content-type: text/html
mode: HTML
```
### 编码内容补充
#### 1 生成验证码工具类
```java
package indi.yuluo.server.utils;

/**
* @Author: yuluo
* @CreateTime: 2022-10-1312:28
* @Description: 随机生成验证码工具类
*/

import java.util.Random;

public class ValidateCodeUtils {

    /**
   * 随机生成验证码
   * @param length 长度为4位或者6位
   * @return
   */
    public static Integer generateValidateCode(int length){
      Integer code =null;
      if(length == 4){
            code = new Random().nextInt(9999);//生成随机数,最大为9999
            if(code < 1000){
                code = code + 1000;//保证随机数为4位数字
            }
      }else if(length == 6){
            code = new Random().nextInt(999999);//生成随机数,最大为999999
            if(code < 100000){
                code = code + 100000;//保证随机数为6位数字
            }
      }else{
            throw new RuntimeException("只能生成4位或6位数字验证码");
      }
      return code;
    }

    /**
   * 随机生成指定长度字符串验证码
   * @param length 长度
   * @return
   */
    public static String generateValidateCode4String(int length){
      Random rdm = new Random();
      String hash1 = Integer.toHexString(rdm.nextInt());
      String capstr = hash1.substring(0, length);
      return capstr;
    }
}
```
#### 2 Result工具类

接口定义
```java
package indi.yuluo.common.model;

/**
* @Author: yuluo
* @CreateTime: 2022-08-2614:48
* @Description: 返回结果行为接口
*/

public interface IResult {

    /**
   * 获取code
   * @return
   */
    Integer getCode();

    /**
   * 获取描述
   * @return
   */
    String getMessage();

}
```

枚举定义
```java
package indi.yuluo.common.Enum;


import indi.yuluo.common.model.IResult;

/**
* @Author: yuluo
* @CreateTime: 2022-08-2614:36
* @Description: 返回结果枚举类
*/
public enum ResultEnum implements IResult {


    SUCCESS(8291, "接口调用成功"),
    VALIDATE_FAILED(8292, "参数校验失败"),
    COMMON_FAILED(8293, "接口调用失败"),
    FORBIDDEN(8294, "没有权限访问资源");

    private Integer code;
    private String message;

    ResultEnum(Integer code, String message) {
      this.code = code;
      this.message = message;
    }

    @Override
    public Integer getCode() {
      return code;
    }

    @Override
    public String getMessage() {
      return message;
    }

}
```

```java
package indi.yuluo.common.model;

import indi.yuluo.common.Enum.ResultEnum;

import java.io.Serializable;
import java.util.Objects;

/**
* @Author: yuluo
* @CreateTime: 2022-08-2614:53
* @Description: 统一返回数据结果
*/

public class Result<T> implements Serializable {

    // 编码
    private Integer code;

    // 错误信息
    private String message;

    // 数据
    private T data;

    public Result() {
    }

    public Result(Integer code, String message, T data) {
      this.code = code;
      this.message = message;
      this.data = data;
    }

    @Override
    public boolean equals(Object o) {
      if (this == o) return true;
      if (o == null || getClass() != o.getClass()) return false;
      Result<?> result = (Result<?>) o;
      return Objects.equals(code, result.code) && Objects.equals(message, result.message) && Objects.equals(data, result.data);
    }

    @Override
    public int hashCode() {
      return Objects.hash(code, message, data);
    }

    @Override
    public String toString() {
      return "Result{" +
                "code=" + code +
                ", message='" + message + '\'' +
                ", data=" + data +
                '}';
    }

    public String getMessage() {
      return message;
    }

    public void setMessage(String message) {
      this.message = message;
    }

    public T getData() {
      return data;
    }

    public void setData(T data) {
      this.data = data;
    }

    public Integer getCode() {
      return code;
    }

    public void setCode(Integer code) {
      this.code = code;
    }

    /**
   * 只返回成功代码和描述,不返回其他数据
   */
    public static <T> Result<T> success() {
      return new Result<>(ResultEnum.SUCCESS.getCode(), ResultEnum.SUCCESS.getMessage(), null);
    }

    /**
   * 返回成功代码和描述,以及自定义数据
   */
    public static <T> Result<T> success(T data) {
      return new Result<>(ResultEnum.SUCCESS.getCode(), ResultEnum.SUCCESS.getMessage(), data);
    }

    /**
   * 返回成功代码和自定义的String类型的信息描述和数据
   */
    public static <T> Result<T> success(String message, T data) {
      return new Result<>(ResultEnum.SUCCESS.getCode(), message, data);
    }

    /**
   * 返回失败的代码和描述信息,不带数据
   */
    public static Result<?> failed() {
      return new Result<>(ResultEnum.COMMON_FAILED.getCode(), ResultEnum.COMMON_FAILED.getMessage(), null);
    }


    /**
   * 返回失败的代码和自定义的String描述信息,不带数据
   */
    public static <T> Result<T> failed(String message) {
      return new Result<>(ResultEnum.COMMON_FAILED.getCode(), message, null);
    }

    /**
   * 用于参数校验时,添加异常信息中的msg
   *
   * @param errorResult 继承IResult的枚举类
   * @param <T>         泛型
   * @return Result对象
   */
    public static <T> Result<T> failed(IResult errorResult, String message) {
      return new Result<>(errorResult.getCode(), message, null);
    }

    /**
   * 自定义选择结果枚举类中的信息
   *
   * @param errorResult 返回接口的具体实现类,通常是枚举
   * @param <T>
   * @return
   */
    public static <T> Result<T> failed(IResult errorResult) {
      return new Result<>(errorResult.getCode(), errorResult.getMessage(), null);
    }

    /**
   * 自定义返回信息
   *
   * @param code    代码
   * @param message 信息
   * @param data    数据
   * @param <T>   泛型
   * @return 返回中
   */
    public static <T> Result<T> instance(Integer code, String message, T data) {
      Result<T> result = new Result<>();

      result.setCode(code);
      result.setMessage(message);
      result.setData(data);

      return result;
    }

}
```

#### 4 pom依赖
```xml
      <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
      </dependency>

```

yuluo829 发表于 2022-12-14 12:37

嬉皮笑脸 发表于 2022-12-12 16:21
现在已经跑通了

ok,能跑通就ok,那几个工具类不是很重要,一个是通用结果返回类,一个是验证码生成的,就是random封装了一下{:1_893:}

yuluo829 发表于 2022-10-18 19:28

jzx111 发表于 2022-10-18 08:42
邮箱授权码有没有具体教程

是验证码吗?弄个随机生成的6位字符串就好了,这个不重要的

hckj1919 发表于 2022-10-14 15:36

感谢分享...

NCGZS 发表于 2022-10-14 23:54

正好需要,感谢大佬分享!

xiadongming 发表于 2022-10-15 21:37

icodeme 发表于 2022-10-16 00:09

学到了,感谢分享,,,

bearkr 发表于 2022-10-16 23:23

感谢楼主分享,学习了

飘浮 发表于 2022-10-17 09:35

感谢分享 学习下。

jzx111 发表于 2022-10-18 08:42

邮箱授权码有没有具体教程

independence 发表于 2022-10-18 19:35

这东西还能搞出很多有意思的东西
页: [1] 2 3
查看完整版本: Spring boot 整合邮件实现验证码注册功能