程序员scholar 程序员scholar
首页
  • Java 基础

    • JavaSE
    • JavaIO
    • JavaAPI速查
  • Java 高级

    • JUC
    • JVM
    • Java新特性
    • 设计模式
  • Web 开发

    • Servlet
    • Java网络编程
  • Web 标准

    • HTML
    • CSS
    • JavaScript
  • 前端框架

    • Vue2
    • Vue3
    • Vue3 + TS
    • 微信小程序
    • uni-app
  • 工具与库

    • jQuery
    • Ajax
    • Axios
    • Webpack
    • Vuex
    • WebSocket
    • 第三方登录
  • 后端与语言扩展

    • ES6
    • Typescript
    • node.js
  • Element-UI
  • Apache ECharts
  • 数据结构
  • HTTP协议
  • HTTPS协议
  • 计算机网络
  • Linux常用命令
  • Windows常用命令
  • SQL数据库

    • MySQL
    • MySQL速查
  • NoSQL数据库

    • Redis
    • ElasticSearch
  • 数据库

    • MyBatis
    • MyBatis-Plus
  • 消息中间件

    • RabbitMQ
  • 服务器

    • Nginx
  • Spring框架

    • Spring6
    • SpringMVC
    • SpringBoot
    • SpringSecurity
  • SpringCould微服务

    • SpringCloud基础
    • 微服务之DDD架构思想
  • 日常必备

    • 开发常用工具包
    • Hutoll工具包
    • IDEA常用配置
    • 开发笔记
    • 日常记录
    • 项目部署
    • 网站导航
    • 产品学习
    • 英语学习
  • 代码管理

    • Maven
    • Git教程
    • Git小乌龟教程
  • 运维工具

    • Docker
    • Jenkins
    • Kubernetes
  • 算法笔记

    • 算法思想
    • 刷题笔记
  • 面试问题常见

    • 十大经典排序算法
    • 面试常见问题集锦
关于
GitHub (opens new window)
首页
  • Java 基础

    • JavaSE
    • JavaIO
    • JavaAPI速查
  • Java 高级

    • JUC
    • JVM
    • Java新特性
    • 设计模式
  • Web 开发

    • Servlet
    • Java网络编程
  • Web 标准

    • HTML
    • CSS
    • JavaScript
  • 前端框架

    • Vue2
    • Vue3
    • Vue3 + TS
    • 微信小程序
    • uni-app
  • 工具与库

    • jQuery
    • Ajax
    • Axios
    • Webpack
    • Vuex
    • WebSocket
    • 第三方登录
  • 后端与语言扩展

    • ES6
    • Typescript
    • node.js
  • Element-UI
  • Apache ECharts
  • 数据结构
  • HTTP协议
  • HTTPS协议
  • 计算机网络
  • Linux常用命令
  • Windows常用命令
  • SQL数据库

    • MySQL
    • MySQL速查
  • NoSQL数据库

    • Redis
    • ElasticSearch
  • 数据库

    • MyBatis
    • MyBatis-Plus
  • 消息中间件

    • RabbitMQ
  • 服务器

    • Nginx
  • Spring框架

    • Spring6
    • SpringMVC
    • SpringBoot
    • SpringSecurity
  • SpringCould微服务

    • SpringCloud基础
    • 微服务之DDD架构思想
  • 日常必备

    • 开发常用工具包
    • Hutoll工具包
    • IDEA常用配置
    • 开发笔记
    • 日常记录
    • 项目部署
    • 网站导航
    • 产品学习
    • 英语学习
  • 代码管理

    • Maven
    • Git教程
    • Git小乌龟教程
  • 运维工具

    • Docker
    • Jenkins
    • Kubernetes
  • 算法笔记

    • 算法思想
    • 刷题笔记
  • 面试问题常见

    • 十大经典排序算法
    • 面试常见问题集锦
关于
GitHub (opens new window)
npm

(进入注册为作者充电)

  • 后端开发

    • Spring Boot多模块项目开发
    • Spring Boot图片资源返回
    • Spring Boot文件上传
    • Spring Boot文件下载
    • 对接第三方文件上传
    • Servlet 原生API
    • HttpServletResponse 和 ResponseEntity
    • 后端解决跨域问题
    • 后端拦截器
    • SpringBoot+Vue实现邮件发送与验证码验证
      • 一、依赖引入
      • 二、配置文件
      • 三、Redis 配置
      • 四、工具类:统一响应结果封装
      • 五、服务层接口和实现
        • 1. 邮件发送服务接口与实现
        • EmailService.java
        • EmailServiceImpl.java
        • 2. 验证码发送服务接口与实现
        • VerifyCodeService.java
        • VerifyCodeServiceImpl.java
        • 3. HTML 邮件模板
        • email-template.html
      • 六、控制器层
        • VerifyCodeController.java
      • 七、前端 Vue 代码集成
        • request.js
        • Vue 组件代码
    • 谷歌验证码
    • 利用hutool工具类实现图片验证码
    • 统一返回格式
    • 通用 普通 登录模块
    • 通用 JWT 登录认证模块
    • 通用 普通 注册模块
    • 基于 MyBatis curd
    • 基于 MyBatis-Plus curd
    • Java 常见对象模型
    • 开发枚举的使用
    • MyBatis与MyBatis-Plus日期类型处理
    • 接口日志拦截基础版
    • 接口日志拦截进阶版
    • 文件操作工具类
    • Spring Boot 数据校验
    • 幂等性
  • 前端开发

  • 开发笔记
  • 后端开发
scholar
2024-12-27
目录

SpringBoot+Vue实现邮件发送与验证码验证

# Spring Boot + Vue 实现邮件发送与验证码验证


前言

在企业级应用中,用户身份验证是一个重要的环节,尤其是在登录、注册、找回密码等场景中,验证码验证是一种常用且有效的手段。通过邮件发送验证码并进行验证,不仅可以提高安全性,还能增强用户体验。本节将详细介绍如何使用 Spring Boot 和 Vue 来实现一个完整的邮件发送与验证码验证功能,结合 Redis 实现验证码的存储与管理,确保系统在高并发、分布式环境下的可靠性与一致性。

# 一、依赖引入

在 Spring Boot 项目中,需要先引入邮件发送和 Redis 的相关依赖。

pom.xml 依赖配置

<dependencies>
    <!-- Spring Boot 邮件发送依赖 -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-mail</artifactId>
    </dependency>

    <!-- Spring Boot Redis 依赖 -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-data-redis</artifactId>
    </dependency>
</dependencies>
1
2
3
4
5
6
7
8
9
10
11
12
13

关键依赖说明:

  • spring-boot-starter-mail:简化邮件发送的配置和操作。
  • spring-boot-starter-data-redis:提供对 Redis 的支持,用于存储验证码等临时数据。

# 二、配置文件

在 application.yml 中配置邮件服务器和 Redis。

application.yml 配置

spring:
  mail:
    host: smtp.qq.com # SMTP服务器地址,例如QQ邮箱
    port: 587 # SMTP服务器端口
    username: your-email@example.com # 发件人邮箱地址
    password: your-email-password # 授权码(而非邮箱密码)
    properties:
      mail:
        smtp:
          auth: true # 启用SMTP认证
          starttls:
            enable: true # 启用STARTTLS加密
  redis:
    host: localhost
    port: 6379
    timeout: 6000ms
    database: 0
    lettuce:
      pool:
        max-active: 8
        max-idle: 8
        min-idle: 0
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

配置说明:

  • spring.mail.password 这里是邮箱的授权码(例如QQ邮箱),而非登录密码。
  • spring.redis 相关配置用于连接 Redis 数据库,用于存储验证码数据。

# 三、Redis 配置

为了更好地管理 Redis 连接,推荐通过配置类进行自定义配置。

RedisConfig.java

package com.example.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.StringRedisSerializer;

@Configuration
public class RedisConfig {

    /**
     * 自定义 RedisTemplate,用于序列化 Redis 中的键和值
     * @param lettuceConnectionFactory 连接工厂,必须配置
     * @return RedisTemplate<String, Object>
     */
    @Bean
    public RedisTemplate<String, Object> redisTemplate(LettuceConnectionFactory lettuceConnectionFactory) {
        RedisTemplate<String, Object> template = new RedisTemplate<>();
        template.setConnectionFactory(lettuceConnectionFactory);

        // 设置键和值的序列化器,避免出现乱码问题
        template.setKeySerializer(new StringRedisSerializer());
        template.setValueSerializer(new StringRedisSerializer());
        template.setHashKeySerializer(new StringRedisSerializer());
        template.setHashValueSerializer(new StringRedisSerializer());

        template.afterPropertiesSet();
        return template;
    }
}
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

关键点:

  • LettuceConnectionFactory:用于创建 Redis 连接。
  • StringRedisSerializer:指定 Redis 中存储的键和值的序列化方式,确保数据能够正确存取。

# 四、工具类:统一响应结果封装

在企业项目中,统一的响应格式有助于前后端协同工作。

AjaxResult.java

package com.example.util;

/**
 * 统一响应结果封装类
 */
public class AjaxResult {

    private int code; // 响应状态码,200表示成功,500表示失败
    private String message; // 响应消息
    private Object data; // 响应数据,可能为空

    /**
     * 操作成功时调用
     * @param data 返回的数据
     * @return AjaxResult
     */
    public static AjaxResult success(Object data) {
        return new AjaxResult(200, "操作成功", data);
    }

    /**
     * 操作失败时调用
     * @param message 错误信息
     * @return AjaxResult
     */
    public static AjaxResult fail(String message) {
        return new AjaxResult(500, message, null);
    }

    /**
     * 自定义状态码和消息的失败响应
     * @param code 自定义状态码
     * @param message 错误信息
     * @return AjaxResult
     */
    public static AjaxResult fail(int code, String message) {
        return new AjaxResult(code, message, null);
    }

    private AjaxResult(int code, String message, Object data) {
        this.code = code;
        this.message = message;
        this.data = data;
    }

    // Getters and Setters
}
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

关键点:

  • 通过静态方法 success 和 fail,快速生成响应结果,便于控制器返回统一的格式。

# 五、服务层接口和实现

# 1. 邮件发送服务接口与实现

邮件发送逻辑通过 EmailService 统一管理,封装了发送邮件的具体过程,包括支持 HTML 格式的邮件内容。

# EmailService.java

package com.example.service;

import javax.mail.MessagingException;

public interface EmailService {

    /**
     * 发送邮件
     *
     * @param to      收件人邮箱地址,必须提供,不能为空
     * @param subject 邮件主题,必须提供,不能为空
     * @param text    邮件正文,支持HTML格式,必须提供,不能为空
     * @throws MessagingException 当邮件发送失败时抛出该异常
     */
    void sendEmail(String to, String subject, String text) throws MessagingException;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

接口说明:

  • to:收件人邮箱地址,必须提供且不能为空。
  • subject:邮件主题,必须提供且不能为空。
  • text:邮件正文,支持 HTML 格式,必须提供且不能为空。

# EmailServiceImpl.java

package com.example.service.impl;

import com.example.service.EmailService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.mail.javamail.JavaMailSender;
import org.springframework.mail.javamail.MimeMessageHelper;
import org.springframework.stereotype.Service;

import javax.mail.MessagingException;
import javax.mail.internet.MimeMessage;

@Service
public class EmailServiceImpl implements EmailService {

    @Autowired
    private JavaMailSender javaMailSender;

    @Value("${spring.mail.username}")
    private String fromEmail; // 从配置文件中读取发件人邮箱

    @Override
    public void sendEmail(String to, String subject, String text) throws MessagingException {
        // 创建MimeMessage对象,用于构建邮件信息
        MimeMessage message = javaMailSender.createMimeMessage();

        // 使用MimeMessageHelper来帮助设置邮件内容
        MimeMessageHelper helper = new MimeMessageHelper(message, true); // true表示支持HTML格式
        helper.setFrom(fromEmail); // 设置发件人
        helper.setTo(to); // 设置收件人
        helper.setSubject(subject); // 设置邮件主题
        helper.setText(text, true); // 设置邮件正文,支持HTML格式

        // 发送邮件
        javaMailSender.send(message);
    }
}
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

关键点:

  • JavaMailSender:Spring 提供的邮件发送接口,简化了邮件发送操作。
  • MimeMessageHelper:辅助构建复杂的邮件内容,包括支持 HTML 格式的正文。

# 2. 验证码发送服务接口与实现

验证码的生成、发送和存储逻辑集中在 VerifyCodeService 中,通过 Redis 存储验证码,确保在分布式系统中的一致性。

# VerifyCodeService.java

package com.example.service;

import com.example.util.AjaxResult;

public interface VerifyCodeService {

    /**
     * 发送验证码到指定邮箱
     *
     * @param email 收件人邮箱地址,必须提供且格式正确
     * @return AjaxResult 返回包含UUID的结果,供后续验证使用
     */
    AjaxResult sendVerificationCode(String email);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14

接口说明:

  • email:收件人邮箱地址,必须提供且格式正确。

# VerifyCodeServiceImpl.java

package com.example.service.impl;

import com.example.service.EmailService;
import com.example.service.VerifyCodeService;
import com.example.util.AjaxResult;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.core.io.Resource;
import org.springframework.core.io.ResourceLoader;

import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.nio.charset.StandardCharsets;
import java.util.UUID;
import java.util.concurrent.TimeUnit;
import java.util.regex.Pattern;
import java.util.stream.Collectors;

@Service
public class VerifyCodeServiceImpl implements VerifyCodeService {

    @Autowired
    private EmailService emailService;

    @Autowired
    private RedisTemplate<String, String> redisTemplate;

    @Autowired
    private ResourceLoader resourceLoader; // 用于加载HTML模板

    @Override
    public AjaxResult sendVerificationCode(String email) {
        // 验证邮箱格式
        String emailRegex = "^[A-Za-z0-9+_.-]+@[A-Za-z0-9.-]+$";
        if (!Pattern.matches(emailRegex, email)) {
            return AjaxResult.fail("无效的邮箱地址");
        }

        // 生成6位随机验证码
        String verificationCode = generateVerificationCode();

        // 加载并填充HTML模板
        String emailContent = loadEmailTemplate(verificationCode);

        // 发送邮件
        try {
            emailService.sendEmail(email, "邮箱验证码", emailContent);
        } catch (Exception e) {
            return AjaxResult.fail("邮件发送失败");
        }

        // 将验证码存入Redis,设置5分钟有效期
        String uuid = UUID.randomUUID().toString();
        redisTemplate.opsForValue().set("emailCode:" + uuid, verificationCode, 5, TimeUnit.MINUTES);

        // 返回UUID给前端,供后续验证使用
        return AjaxResult.success(uuid);
    }

    // 生成随机验证码
    private String generateVerificationCode() {
        return String.valueOf((int) ((Math.random() * 9 + 1) * 100000)); // 生成6位随机数
    }

    // 加载并填充HTML模板
    private String loadEmailTemplate(String code) {
        try {
            // 加载模板文件
            Resource resource = resourceLoader.getResource("classpath:templates/email-template.html");

            // 读取模板内容
            String template = new BufferedReader(new InputStreamReader(resource.getInputStream(), StandardCharsets.UTF_8))
                    .lines()
                    .collect(Collectors.joining(System.lineSeparator()));

            // 替换占位符
            return template.replace("{{code}}", code);
        } catch (Exception e) {
            throw new RuntimeException("加载邮件模板失败", e);
        }
    }
}
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

关键点:

  • 验证码生成、邮件发送和 Redis 存储的逻辑集中在服务层。
  • 使用 ResourceLoader 动态加载 HTML 模板,并使用占位符替换的方式填充验证码。

# 3. HTML 邮件模板

为了提升代码的维护性和复用性,HTML 模板被抽离出来放置在 resources/templates 目录下。

# email-template.html

<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>验证码邮件</title>
    <style>
        .container {
            width: 600px;
            margin: 0 auto;
            padding: 20px;
            font-family: Arial, sans-serif;
            background-color: #f9f9f9;
            border: 1px solid #e8e8e8;
            box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
        }
        .header {
            text-align: center;
            font-size: 24px;
            font-weight: bold;
            margin-bottom: 20px;
        }
        .content {
            font-size: 16px;
            color: #333333;
            line-height: 1.6;
        }
        .code {
            font-size: 24px;
            font-weight: bold;
            color: #f44336;
            margin: 20px 0;
            text-align: center;
        }
        .footer {
            font-size: 12px;
            color: #999999;
            text-align: center;
            margin-top: 30px;
        }
        .footer p {
            margin: 5px 0;
        }
    </style>
</head>
<body>
    <div class="container">
        <div class="header">验证码邮件通知</div>
        <div class="content">
            <p>尊敬的用户,您好:</p>
            <p>您正在进行重要操作,请在验证码输入框中输入下方的验证码:</p>
            <div class="code">{{code}}</div>
            <p>请注意:该验证码有效期为5分钟,请勿泄露给他人。</p>
            <p>如果您没有进行此操作,请忽略此邮件。</p>
        </div>
        <div class="footer">
            <p>此邮件为系统自动发送,请勿回复。</p>
            <p>感谢您的使用!</p>
            <p>企业团队</p>
        </div>
    </div>
</body>
</html>
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

# 六、控制器层

控制器负责接收前端请求并调用服务层接口。

# VerifyCodeController.java

package com.example.controller;

import com.example.service.VerifyCodeService;
import com.example.util.AjaxResult;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;

@RestController
@RequestMapping("/api/verify")
public class VerifyCodeController {

    @Autowired
    private VerifyCodeService verifyCodeService;

    /**
     * 发送验证码到用户邮箱
     *
     * @param admin 请求体,包含用户邮箱
     * @return 发送结果和UUID
     */
    @PostMapping("/send-code")
    public AjaxResult sendVerificationCode(@RequestBody Admin admin) {
        return verifyCodeService.sendVerificationCode(admin.getEmail());
    }
}
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

关键点:

  • 控制器的职责仅限于接收请求和返回响应,具体业务逻辑由服务层实现。

# 七、前端 Vue 代码集成

前端通过 Axios 调用后端接口,实现验证码的发送。

# request.js

import axios from 'axios';

// 创建axios实例
const request = axios.create({
    baseURL: 'http://localhost:8080/api', // 后端服务基础路径
    timeout: 10000 // 请求超时
});

// 请求拦截器
request.interceptors.request.use(config => {
    config.headers['Content-Type'] = 'application/json;charset=utf-8';
    return config;
}, error => {
    return Promise.reject(error);
});

// 响应拦截器
request.interceptors.response.use(response => {
    return response.data;
}, error => {
    return Promise.reject(error);
});

export default request;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24

# Vue 组件代码

<template>
  <div>
    <el-form>
      <el-form-item>
        <el-input v-model="admin.email" placeholder="请输入邮箱"></el-input>
      </el-form-item>
      <el-button type="primary" @click="sendCode">发送验证码</el-button>
    </el-form>
  </div>
</template>

<script>
import request from '@/utils/request';

export default {
  data() {
    return {
      admin: {
        email: ''
      }
    };
  },
  methods: {
    async sendCode() {
      try {
        const response = await request.post('/verify/send-code', this.admin);
        if (response.code === 200) {
          this.$message.success('验证码发送成功,请检查您的邮箱');
        } else {
          this.$message.error(response.message);
        }
      } catch (error) {
        this.$message.error('验证码发送失败');
      }
    }
  }
};
</script>
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

关键点:

  • 通过 Axios 发送 POST 请求,提交邮箱信息以请求发送验证码。
  • 验证码发送成功后,前端提示用户检查邮箱。

总结

  1. 依赖配置与代码结构清晰:项目遵循了分层架构,控制器、服务层和工具类职责明确。
  2. 服务层封装核心业务逻辑:验证码生成、邮件发送和存储均在服务层实现,控制器只负责调用。
  3. Redis 实现分布式数据一致性:验证码存储在 Redis 中,确保数据在分布式环境中的一致性和可用性。
  4. 前后端分离:前端 Vue 代码通过 Axios 调用后端 API,实现了验证码发送功能。
编辑此页 (opens new window)
上次更新: 2025/03/16, 22:19:39
后端拦截器
谷歌验证码

← 后端拦截器 谷歌验证码→

Theme by Vdoing | Copyright © 2019-2025 程序员scholar
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式