可以哦 發表於 2025-9-26 17:51:00

Spring Boot接入邮箱,完成邮箱验证码

<p>原文出处:</p>
<h1 id="先知">先知</h1>
<h3 id="邮箱的一些基本概念">邮箱的一些基本概念:</h3>
<h5 id="发送邮件">发送邮件</h5>
<ol>
<li>
<p>STMP协议:</p>
<ol>
<li>
<p>Simple Mail Transfer Protocol ,简单邮箱传输协议,用于<strong>发送</strong>邮件的协议。</p>
</li>
<li>
<p>基于TCP,保证可靠性,但不安全,是明文传输</p>
</li>
<li>
<p>Spring Boot默认也是基于此协议进行发送邮件</p>
</li>
</ol>
</li>
</ol>
<h5 id="接收邮件">接收邮件</h5>
<ol>
<li>
<p>POP3协议:</p>
<ol>
<li>
<p>Post Office Protocol 3:邮局通信协议第三版,用于<strong>接收</strong>邮箱的标准协议。</p>
</li>
<li>
<p>“<strong>一次性取信</strong>”,客户端把邮件下载到本地之后,通常会从服务器上<strong>删除</strong>(可以配置保留副本)。如果没有设置,电脑上收了邮件,手机上就没法再看到这封邮件</p>
</li>
</ol>
</li>
<li>
<p>IMAP协议:</p>
<ol>
<li>
<p>Internet Message Access Protocol : 互联网消息访问协议,是POP3的代替,也是用于<strong>接收</strong>邮件的协议</p>
</li>
<li>
<p>“<strong>云端管理</strong>”:客户端拉去邮件,只是将“视图”同步到本地,不会删除,<strong>始终保存</strong>在服务器上。</p>
</li>
<li>
<p>Gmail、QQ邮箱、OutLook等现代邮箱,都是用的IMAP协议</p>
</li>
</ol>
</li>
</ol>
<h3 id="邮箱的传送流程">邮箱的传送流程</h3>
<p><img src="https://img2024.cnblogs.com/blog/3626985/202509/3626985-20250926172548193-1317399581.png"></p>
<p>当你用QQ邮箱向网易邮箱发送了一封邮件,会发生什么?</p>
<ol>
<li>
<p>QQ邮箱客户端会使用SMTP协议将 邮件 <strong>发送</strong>到自家的邮件服务器上</p>
</li>
<li>
<p>QQ邮箱服务器,接收到一封邮件,然后会<strong>解析</strong>目标地址的域名</p>
</li>
<li>
<p>QQ邮箱服务器,识别到是其他服务器上的域名,就会进行<strong>转发</strong>,同样使用SMTP协议发送</p>
</li>
<li>
<p>网易邮箱服务器接收到一封邮件,发现是目标地址是自己的域名,就将信封<strong>存储</strong>在服务器上</p>
</li>
<li>
<p>网易邮箱客户器上线,查看服务器上有没有邮件,如果有,就<strong>拉取</strong>。使用IMAP或者POP3协议</p>
</li>
</ol>
<h1 id="邮箱开通第三方服务">邮箱开通第三方服务</h1>
<ol>
<li>
<h3 id="如果你使用的是飞书邮箱">如果你使用的是飞书邮箱</h3>
</li>
</ol>
<p>找到邮箱的位置,“第三方邮箱客户端登陆”</p>
<p><img src="https://img2024.cnblogs.com/blog/3626985/202509/3626985-20250926172548286-1761705809.png"></p>
<p>随便选择一个设备</p>
<p><img src="https://img2024.cnblogs.com/blog/3626985/202509/3626985-20250926172548239-1048206194.png"></p>
<p>生成,获取到授权码、用户名、发信服务器</p>
<p><img src="https://img2024.cnblogs.com/blog/3626985/202509/3626985-20250926172548132-1095493678.png"></p>
<ol start="2">
<li>
<h3 id="如果你使用的是qq邮箱">如果你使用的是QQ邮箱</h3>
</li>
</ol>
<p>找到账户中心</p>
<p><img src="https://img2024.cnblogs.com/blog/3626985/202509/3626985-20250926172548213-1083295880.png"></p>
<p>开启POP3、IMAP等服务</p>
<p><img src="https://img2024.cnblogs.com/blog/3626985/202509/3626985-20250926172548287-515148377.png"></p>
<p>选择你的验证方式,即可生成授权码</p>
<p><img src="https://img2024.cnblogs.com/blog/3626985/202509/3626985-20250926172548316-1410778902.png"></p>
<ol start="6">
<li>其他的网易邮箱等,获取授权码的方式类似</li>
</ol>
<h1 id="邮箱集成使用">邮箱集成使用</h1>
<blockquote>
<p>说明:以下的使用,参考Spring中文网</p>
</blockquote>
<ol>
<li>
<h3 id="依赖">依赖</h3>
</li>
</ol>
<pre><code class="language-Java">&lt;dependency&gt;   
    &lt;groupId&gt;org.springframework.boot&lt;/groupId&gt;   
    &lt;artifactId&gt;spring-boot-starter-mail&lt;/artifactId&gt;
&lt;/dependency&gt;
</code></pre>
<ol start="2">
<li>
<h3 id="配置文件">配置文件</h3>
</li>
</ol>
<pre><code class="language-YAML">spring:
mail:
# 指定邮箱的服务器地址
    host: smtp.feishu.cn
    # 执行邮箱的发送者,由哪一个邮箱账号来发送邮件
    username: huag****@ifree8.com
    # 邮件的授权码,邮箱账号需要开通第三方服务
    password: 29F7s********
    default-encoding: UTF-8

</code></pre>
<ol start="3">
<li>
<h3 id="测试">测试</h3>
</li>
</ol>
<pre><code class="language-Java">@Test
public void test() throws Exception {

    // 直接创建 JavaMailSenderImpl 实现类
    JavaMailSenderImpl javaMailSender = new JavaMailSenderImpl();

    javaMailSender.setDefaultEncoding("utf-8");
    javaMailSender.setHost("smtp.qq.com");            
    javaMailSender.setPort(465);                     
    javaMailSender.setUsername("747692844@qq.com");   
    javaMailSender.setPassword("&lt;你的密码/授权码&gt;");   
    javaMailSender.setProtocol("smtps");

    // 创建一个邮件消息
    MimeMessage message = javaMailSender.createMimeMessage();

    // 创建 MimeMessageHelper
    MimeMessageHelper helper = new MimeMessageHelper(message, false);

    // 发件人邮箱和名称
    helper.setFrom("747692844@qq.com", "springdoc");
    // 收件人邮箱
    helper.setTo("admin@springboot.io");
    // 邮件标题
    helper.setSubject("Hello");
    // 邮件正文,第二个参数表示是否是HTML正文
    helper.setText("Hello &lt;strong&gt; World&lt;/strong&gt;!", true);

    // 发送
    javaMailSender.send(message);
}
</code></pre>
<h1 id="类之间的关系">类之间的关系</h1>
<p>在测试类中可以看到,主要涉及的主要类就是:</p>
<ol>
<li>
<p>JavaMailSender</p>
</li>
<li>
<p>MimeMessage</p>
</li>
<li>
<p>MimeMessageHelper</p>
</li>
</ol>
<h3 id="javamailsender">JavaMailSender</h3>
<p>类的继承关系</p>
<p><img src="https://img2024.cnblogs.com/blog/3626985/202509/3626985-20250926172548173-661401433.png"></p>
<p><img src="https://img2024.cnblogs.com/blog/3626985/202509/3626985-20250926172548271-845986925.png"></p>
<p><img src="https://img2024.cnblogs.com/blog/3626985/202509/3626985-20250926172548255-1999341637.png"></p>
<ul>
<li>
<p>底层实现自MaiSender接口,接口中抽象定义了send(),而JavaMailSenderImpl就是实现类</p>
</li>
<li>
<p>JavaMailSender的作用就是主要用来<strong>发送邮件</strong>的</p>
</li>
<li>
<p>抽象理解:真实发送快递的“快递员”</p>
</li>
</ul>
<h3 id="mimemessage">MimeMessage</h3>
<p>有了“快递员”,我们还需要“快递”——Message</p>
<pre><code class="language-Java">
public class MimeMessage extends Message implements MimePart {
    ......

    public void setFrom(Address address) throws MessagingException {
      if (address == null) {
            this.removeHeader("From");
      } else {
            this.setAddressHeader("From", new Address[]{address});
      }
   
    }
   
    ......
}
</code></pre>
<p>来看看里面都有哪些方法和属性</p>
<p><img src="https://img2024.cnblogs.com/blog/3626985/202509/3626985-20250926172548228-1076333716.png"></p>
<ol>
<li>
<p>setFrom():设置邮箱展示的信息,比如名称和邮箱等</p>
</li>
<li>
<p>setRecipients():设置收件人的邮箱</p>
</li>
<li>
<p>setText():邮件内容</p>
</li>
</ol>
<ul>
<li>所以,Message的作用:主要是定义邮件内容、收件人的信息</li>
</ul>
<h3 id="mimemessagehelper">MimeMessageHelper</h3>
<p>Helper,“包装快递”的好帮手</p>
<p>看看Helper中提供了些什么方法?</p>
<p><img src="https://img2024.cnblogs.com/blog/3626985/202509/3626985-20250926172548372-1992956463.png"></p>
<p>为什么Helper中提供的主要方法跟Message中的方法差不多呢?</p>
<p>其实Helper就是用来帮助封装Message中的信息的</p>
<pre><code class="language-Java">public class MimeMessageHelper {
    ......
   
    public void setFrom(String from) throws MessagingException {
      Assert.notNull(from, "From address must not be null");
      this.setFrom(this.parseAddress(from));
    }
    public void setFrom(InternetAddress from) throws MessagingException {
      Assert.notNull(from, "From address must not be null");
      this.validateAddress(from);
      this.mimeMessage.setFrom(from);
    }
   
    ......
}
</code></pre>
<pre><code class="language-Java">
public class MimeMessage extends Message implements MimePart {
    ......

    public void setFrom(Address address) throws MessagingException {
      if (address == null) {
            this.removeHeader("From");
      } else {
            this.setAddressHeader("From", new Address[]{address});
      }
   
    }
   
    ......
}
</code></pre>
<ul>
<li>可以看到,其实Helper提供的方法中,底层操作的对象,其实就是Message邮件信息</li>
</ul>
<p>区别在于:</p>
<ol>
<li>
<p><strong>MimeMessage</strong>是<strong>底层的****API</strong>,能够做所有的事情</p>
</li>
<li>
<p>MimeMessage适用的参数大多是已经封装好的,比如setFrom()方法中的参数是Address,这个类包装了邮件的地址等很多信息。</p>
</li>
<li>
<p><strong>MimeMessageHelper</strong>是提供的<strong>工具类</strong>,旨在于帮助用户更方便的设置Message的属性</p>
</li>
<li>
<p>MimeMessageHelper在设置复杂邮件内容(HTML 模板邮件 + 附件),会帮助用户省去很多封装的步骤,底层进行处理,简化代码。比如setFrom()方法中的参数是String,数据更加的原始。</p>
</li>
</ol>
<ul>
<li>所以,在面对更加复杂的场景,使用Helper能够帮助我们剩下很多力气</li>
</ul>
<h1 id="邮箱验证码案例">邮箱验证码案例</h1>
<ol>
<li>
<h3 id="依赖-1">依赖</h3>
</li>
</ol>
<pre><code class="language-XML">&lt;dependencies&gt;
    &lt;!-- Spring Boot --&gt;
    &lt;dependency&gt;
      &lt;groupId&gt;org.springframework.boot&lt;/groupId&gt;
      &lt;artifactId&gt;spring-boot-starter-web&lt;/artifactId&gt;
    &lt;/dependency&gt;
    &lt;dependency&gt;
      &lt;groupId&gt;org.springframework.boot&lt;/groupId&gt;
      &lt;artifactId&gt;spring-boot-starter-test&lt;/artifactId&gt;
    &lt;/dependency&gt;
    &lt;dependency&gt;
      &lt;groupId&gt;org.springframework.boot&lt;/groupId&gt;
      &lt;artifactId&gt;spring-boot-starter-mail&lt;/artifactId&gt;
    &lt;/dependency&gt;
    &lt;!--redis--&gt;
    &lt;dependency&gt;
      &lt;groupId&gt;org.springframework.boot&lt;/groupId&gt;
      &lt;artifactId&gt;spring-boot-starter-data-redis&lt;/artifactId&gt;
    &lt;/dependency&gt;
    &lt;!-- Apache Commons Pool2 for Redis connection pooling --&gt;
    &lt;dependency&gt;
      &lt;groupId&gt;org.apache.commons&lt;/groupId&gt;
      &lt;artifactId&gt;commons-pool2&lt;/artifactId&gt;
    &lt;/dependency&gt;
    &lt;!--redisson工具集--&gt;
    &lt;dependency&gt;
      &lt;groupId&gt;org.redisson&lt;/groupId&gt;
      &lt;artifactId&gt;redisson-spring-boot-starter&lt;/artifactId&gt;
    &lt;/dependency&gt;

    &lt;!--hutool--&gt;
    &lt;dependency&gt;
      &lt;groupId&gt;cn.hutool&lt;/groupId&gt;
      &lt;artifactId&gt;hutool-all&lt;/artifactId&gt;
    &lt;/dependency&gt;
    &lt;!--Thymeleaf模板引擎--&gt;
    &lt;dependency&gt;
      &lt;groupId&gt;org.springframework.boot&lt;/groupId&gt;
      &lt;artifactId&gt;spring-boot-starter-thymeleaf&lt;/artifactId&gt;
    &lt;/dependency&gt;
    &lt;!--OGNL表达式语言--&gt;
    &lt;dependency&gt;
      &lt;groupId&gt;ognl&lt;/groupId&gt;
      &lt;artifactId&gt;ognl&lt;/artifactId&gt;
      &lt;version&gt;3.3.4&lt;/version&gt;
    &lt;/dependency&gt;
    &lt;!--lombok--&gt;
    &lt;dependency&gt;
      &lt;groupId&gt;org.projectlombok&lt;/groupId&gt;
      &lt;artifactId&gt;lombok&lt;/artifactId&gt;
    &lt;/dependency&gt;
&lt;/dependencies&gt;
</code></pre>
<ol start="2">
<li>
<h3 id="使用配置类config读取配置文件方便后续的使用">使用配置类config读取配置文件,方便后续的使用</h3>
</li>
</ol>
<pre><code class="language-YAML">spring:
mail:
    host: smtp.feishu.cn
    username: hua******@ifree8.com
    password: 29******
    default-encoding: UTF-8
    mailFrom: hua******@ifree8.com
    mailPersonal: 通用Ai智能体
    mailSubject: 邮箱验证码
    regExpr: ^(?!.*\.\.)+@+\.{2,}$
    variable: code
    htmlTemplate: MailVerifyTemplate.html
</code></pre>
<p><img src="https://icngmmn50bm4.feishu.cn/space/api/box/stream/download/asynccode/?code=ZTkxNDk3ZWJjNDhkM2Q1MTVkMWM2MWM3MzFlNWFjM2VfQnE5NHdzRURMOHpiaEJ0NTJMSGVKQURBbTFjRU5rcWxfVG9rZW46SDBEdWJ4YW5Ub1NhTm94ODlNZWNGTjVXblZnXzE3NTg4ODAxMDI6MTc1ODg4MzcwMl9WNA"></p>
<p><img src="https://icngmmn50bm4.feishu.cn/space/api/box/stream/download/asynccode/?code=ZGE5MDU0Mzc4MDE1MGVjZTZkNTA2OWQ0OTc4ZTZlYWFfdjZRM1V2bTVqNWFMUHdyZENLTXhoNzVkeGw4Rng2Z2RfVG9rZW46WFg0bWIxQ1ZrbzhhMGx4WklZUmMyYzhhbkdzXzE3NTg4ODAxMDI6MTc1ODg4MzcwMl9WNA"></p>
<ol start="3">
<li>
<h3 id="生成模板">生成模板</h3>
</li>
</ol>
<p><img src="https://icngmmn50bm4.feishu.cn/space/api/box/stream/download/asynccode/?code=NzBmMGNhNTVhNzE3ODFhMDA3YjExZmNmNTFiMWJhNGZfQ0duc3oyblNQcDdTZU9IWW1SUnFadDlVVnAycXVSaVNfVG9rZW46TXBRTmJKS1Jjb0pPQTd4SGM4ZWN6S3Q2bjlkXzE3NTg4ODAxMDI6MTc1ODg4MzcwMl9WNA"></p>
<pre><code class="language-HTML">&lt;!DOCTYPE html&gt;
&lt;html&gt;
&lt;head&gt;
    &lt;meta charset="UTF-8"&gt;
    &lt;title&gt;邮箱验证码&lt;/title&gt;
&lt;/head&gt;
&lt;body style="font-family: Arial, Helvetica, sans-serif; background-color: #f6f8fa; padding: 20px;"&gt;
&lt;div style="max-width: 600px; margin: auto; background: #ffffff; border-radius: 8px; padding: 30px; box-shadow: 0 2px 6px rgba(0,0,0,0.1);"&gt;
    &lt;h2 style="text-align: center; color: #333333;"&gt;邮箱验证码&lt;/h2&gt;
    &lt;p style="font-size: 16px; color: #333;"&gt;您好,&lt;/p&gt;
    &lt;p style="font-size: 14px; color: #555;"&gt;
      您正在进行邮箱验证,本次验证码如下(5分钟内有效):
    &lt;/p&gt;
    &lt;div style="text-align: center; margin: 30px 0;"&gt;
      &lt;span style="font-size: 28px; font-weight: bold; color: #2d89ef; letter-spacing: 3px;" th:text="${code}"&gt;
      88888
      &lt;/span&gt;
    &lt;/div&gt;
    &lt;p style="font-size: 14px; color: #555;"&gt;
      请在验证页面输入上方验证码完成验证。为保障账号安全,请勿将验证码告知他人。
    &lt;/p&gt;
    &lt;hr style="margin: 30px 0; border: none; border-top: 1px solid #eee;"&gt;
    &lt;p style="font-size: 12px; color: #999; text-align: center;"&gt;
      如果这不是您本人的操作,请忽略此邮件。
    &lt;/p&gt;
&lt;/div&gt;
&lt;/body&gt;
&lt;/html&gt;
</code></pre>
<ol start="4">
<li>
<h3 id="生成自定义的javamailsender">生成自定义的JavaMailSender</h3>
</li>
</ol>
<p><img src="https://icngmmn50bm4.feishu.cn/space/api/box/stream/download/asynccode/?code=NzEyMTcyNWYzNmJhNzQ3YWJhZjkzN2Q1ZjBlMjgxZWVfa2RDM2ozdXRkUEpoM0tobGI4YWFraTR2SUoxZlhQYTlfVG9rZW46SXVHTWI0R2Nzb3RPQnl4ZGpkWGM3Ull1bnRjXzE3NTg4ODAxMDI6MTc1ODg4MzcwMl9WNA"></p>
<p>因为邮箱验证码的场景,邮箱发送者一般是固定不变的,所以初始化为Bean,使用的时候直接注入即可。</p>
<ol start="5">
<li>
<h3 id="使用">使用</h3>
</li>
</ol>
<pre><code class="language-TypeScript">public interface AuthService {
    /**
   * 发送邮箱验证码
   * @param targetMail 接收方邮箱
   * @return 执行结果响应体
   */
    ResponseEntity sendCode2Mail(String targetMail);

    /**
   * 验证邮箱验证码
   * @param mail 验证的邮箱
   * @param userCode用户给的验证码
   * @return 执行结果响应体
   */
    ResponseEntity verifyMailCode(String mail,String userCode);

}
</code></pre>
<pre><code class="language-Java">@Service
@Slf4j
public class AuthServiceImpl implements AuthService {
    //邮箱配置类
    @Autowired
    private MailConfig mailConfig;   
    //Sender ,“快递员”
    @Autowired
    @Qualifier("defaultJavaMailSender")
    private JavaMailSender javaMailSender;

    //Redis,操作工具类。其实就是RedisTemplate一些方法的封装而已。也可以直接使用RedisTemplate。
    //具体不在这里详说,网上也有很多教程(其实就是懒,求原谅)
    @Autowired
    private RedisBase redisBase;

    //包含一些Key prefix,expire time等
    @Autowired
    private RedisConstant redisConstant;


    @Override
    public ResponseEntity&lt;String&gt; sendCode2Mail(String targetMail) {
      //1. 校验邮箱
      if (StrUtil.isBlank(targetMail)) {
            return ResponseEntity.failBusinessException(FAIL,"邮箱不能为空");
      }
      if (!ReUtil.isMatch(mailConfig.getRegExpr(), targetMail)) {
            return ResponseEntity.failBusinessException(FAIL,"邮箱格式不正确");
      }
      //2. 处理消息模板
      //读取模板
      TemplateEngine engine = TemplateUtil.createEngine(new TemplateConfig("templates", TemplateConfig.ResourceMode.CLASSPATH));
      Template template = engine.getTemplate(mailConfig.getHtmlTemplate());
      //生成随机数字
      int codeNum = NumberUtil.generateRandomNumber(200000, 999999,1);
      String code = String.valueOf(codeNum);
      //渲染模板的变量
      String htmlContent = template.render(Map.of(mailConfig.getVariable(), code));
      //3. 封装邮件
      //创建消息
      MimeMessage message = javaMailSender.createMimeMessage();
      //创建helper
      MimeMessageHelper helper = null;
      //封装消息
      try {
            helper = new MimeMessageHelper(message,false);
            helper.setFrom(mailConfig.getMailFrom(),mailConfig.getMailPersonal()); //邮件展示,哪个邮箱账号,名称
            helper.setTo(targetMail);//邮件接收方
            helper.setSubject(mailConfig.getMailSubject()); //邮件主题
            helper.setText(htmlContent,true); //邮件内容
      } catch (Exception e) {
            log.error("happen error:", e);
            return ResponseEntity.failBusinessException(FAIL,"发生异常,请稍后重试!");
      }
      //4. 存储验证码到Redis
      boolean setnxFlag = redisBase.setnx(redisConstant.getMailVerifyCodeKeyPrefix() + targetMail, code, redisConstant.getMailVerifyCodeExpireSeconds());
      if (!setnxFlag) {
            return ResponseEntity.failBusinessException(FAIL,"redis异常");
      }
      //5. 发送邮件
      javaMailSender.send(message);
      //6. 返回结果
      return ResponseEntity.ok(null,"邮箱发送成功,请注意查收!");
    }



    @Override
    public ResponseEntity verifyMailCode(String mail, String registMailCode) {
      //1. 使用邮箱从redis中获取验证码
      String key = redisConstant.getMailVerifyCodeKeyPrefix()+mail;
      String systemCode = (String)redisBase.get(key);
      //2. 校验验证码
      if (systemCode==null) {
            return ResponseEntity.failBusinessException(FAIL,"尚未存在邮箱验证码或验证码已过期,请重新发送邮箱验证码!");
      }
      if (!StrUtil.equals(registMailCode,systemCode)) {
            return ResponseEntity.failBusinessException(FAIL,"验证码错误!");
      }
      //3. 返回结果
      return ResponseEntity.ok();
    }
}
</code></pre>
<h1 id="总结">总结:</h1>
<p>到这,我们已经学习了:</p>
<ol>
<li>
<p>邮箱协议的基本知识</p>
</li>
<li>
<p>邮箱的发送和接收,在客户端和服务端的流程过程</p>
</li>
<li>
<p>还知道了如何开通邮箱的第三方客户端使用服务</p>
</li>
<li>
<p>还使用SpringBoot集成了邮箱,能够使用代码的形式,向邮箱发送邮件</p>
</li>
</ol>
<p>以上内容是我个人根据网上的资料进行学习和转化,如果有错误,欢迎指出,共同进步哦~</p>
<p>(第一次写博客,写得很烂,哈哈哈见谅,希望你能够从垃圾中,灵活抽取出有用的东西吧)</p><br><br>
来源:https://www.cnblogs.com/huaguoniang/p/19113882
頁: [1]
查看完整版本: Spring Boot接入邮箱,完成邮箱验证码