实战!SpringBoot整合Vue3完美实现发送邮件的功能!

createh53周前 (12-15)技术教程15

1.效果演示

2.思维导图

3.前言

本篇文章主要讲解 Springboot 整合 Vue3 实现校验 qq 邮箱验证码之后重置用户密码的功能。

开发功能之前我们先梳理一下主要逻辑:

1.我们要想让系统给其他用户发送邮件,那么系统肯定要先绑定一个已注册的用户 A 的邮箱。用户 A 必须开通 qq 邮箱的相关服务。开通服务的 A 的邮箱号码作为发件人

2.在前端页面用户输入邮箱号码,点击获取验证码按钮调用后台接口,后台随机生成几个数字的验证码,并将该用户的邮箱号码和验证码存入 Redis。

3.用户输入收到的验证码。点击下一步调用后台校验验证码功能。后台将用户输入的验证码和 Redis 里面的数据进行比对,校验无误返回 true。验证失败返回错误的提示信息。

4.用户验证码校验无误之后,就进入到修改密码的页面,接着完成修改密码的功能。

4.开通 qq 邮箱服务

我们要让 A 用户给其他用户发送邮件,那么用户 A 必须开通相关的服务。

1.进入 qq 邮箱,点击右上角的账号与安全

2.开启邮箱服务,保存生成的邮箱授权码

5.前端

5.1 创建并配置 vue 项目

这里我们使用的前端脚手架是 vite,vue 版本是 vue3,前端组件库是 Element plus。

5.1.1 创建 vue3 项目

npm create vite@latest vue3-zhifou -- --template vue

5.1.2 安装配置 Element Plus

npm install element-plus --save

在 main.js 里面配置 Element plus

5.1.3 安装配置 axios

npm i axios -- save

在 src/util 下面新建 axios.js 文件

import axios from "axios";
import router from "../router/index"
import { ElMessage } from 'element-plus'

// 1. 创建axios实例
const instance = axios.create({
  // 接口
  baseURL: "/api",
  // 超时时间
  timeout: 60000,
});
// 2.请求拦截
instance.interceptors.request.use(
  config => {
   // let token = sessionStorage.getItem('token');
   // if (token) {
     // config.headers['token'] = token
   // }
    return config;
  },
  error => {
    //  请求发生错误,抛出异常
    Promise.reject(error);
  }
);

// 3.响应拦截
instance.interceptors.response.use(
  res => {
    // 关闭进度条
    return res;
  },
  error => {
    // 关闭进度条
    if (error && error.response) {
      const status = error.response.status
      switch (status) {
        case 400:
          ElMessage.error("请求错误");
          break;
        case 401:
          ElMessage.error("未授权,请重新登录");
          break;
        case 404:
          ElMessage.error("请求错误,未找到相应的资源");
          break;
        case 500:
          ElMessage.error("服务器错误");
          break;
        default:
          ElMessage.error("请求失败");
      }
    } else {
      if (JSON.stringify(error).includes("timeout")) {
        error.code = "TIMEOUT";
        error.message = "服务器响应超时,请刷新页面";
      }
    }
    return Promise.reject(error);
  }
);
// 4.导出 axios 实例
export default instance;

5.1.4 封装常用的 http 请求

在 /src/util 下面新建 http.js 文件

import instance from "./axios";

const post = (url, data) => {
    return new Promise((resolve, reject) => {
        instance
            .post(url, data)
            .then((res) => {
                resolve(res);
            })
            .catch((err) => {
                reject(err);
            });
    });
};
const get = (url, data) => {
    return new Promise((resolve, reject) => {
        instance
            .get(url, { params: data })
            .then((res) => {
                resolve(res);
            })
            .catch((err) => {
                reject(err);
            });
    });
};
const put = (url, data) => {
    return new Promise((resolve, reject) => {
        instance
            .put(url, data)
            .then((res) => {
                resolve(res);
            })
            .catch((err) => {
                reject(err);
            });
    });
};

const del = (url, data) => {
    return new Promise((resolve, reject) => {
        instance
            .delete(url, { params: data })
            .then((res) => {
                resolve(res);
            })
            .catch((err) => {
                reject(err);
            });
    });
};

export default {
    post,
    get,
    put,
    del,
};

5.1.5 配置后端服务 IP 与端口

import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import path from 'path';

export default defineConfig({
  plugins: [vue()],
  // 设置别名
  resolve: {
    alias: [
      {
        // 设置别名, '@' 指向 'src' 目录
        find: "@",
        replacement: path.resolve(__dirname, './src')
      },
    ]
  },
  server: {
    open: true,
    port: 3000,
    proxy: {
      "/api": {
        target: "http://127.0.0.1:8081/springboot-vue3-email", //
        changeOrigin: true,
        rewrite: (path) => path.replace(/^\/api/, ""),
      },
    },
  },
})

5.1.6 配置访问后台的接口文件

在 /src/api 文件夹下新建接口文件:index.js

import http from "../utils/http";
// 发送邮箱验证码
const sendEmailVerifyCode = (data) => {
  return http.get("/index/sendEmailVerifyCode", data);
};
// 校验邮箱验证码
const checkEmailVerifyCode = (data) => {
  return http.post("/index/checkEmailVerifyCode", data);
};

// 通过邮箱重置用户密码
const resetPasswordByEmail = (data) => {
  return http.post("/sysUser/resetPasswordByEmail", data);
};
export default { sendEmailVerifyCode, resetPasswordByEmail, checkEmailVerifyCode }

5.2 创建发送邮箱验证码的组件

在 /src/components 文件夹下新建 resetPassword.vue 文件。这里不再贴出完整代码,只讲核心代码,后面会有完整代码。

其实整个页面稍微有点复杂的就是发送验证码的功能

当用户点击获取验证码按钮,获取验证码这几个文字变成“x秒后重新发送”,并且禁止点击。

所以要定义一个数字变量用来显示倒计时,还要定义一个变量和 disabled 进行绑定。

const isSend = ref(false);
const countDown = ref(0);

用户点击获取验证码按钮之后调用方法:

当后台发送验证码成功之后,disabled 绑定的变量值就为 true,countDown 初始值为 60。

然后通过 setInterval 定时器每隔一秒将 countDown 的值减去 1。当 countDown 的值小于等于 0 时,disabled 绑定的变量值就为 false,countDown 的值变为 0,并清除定时器。

接着点击下一步按钮,校验验证码是否正确:

// 校验邮箱验证码
const checkEmailCode = () => {
  emailFormRef.value.validate(async (valid) => {
    if (valid) {
      const res = await userApi.checkEmailVerifyCode(form);
      if (res.data.code === 200) {
        // 校验通过
        if (res.data.data) {
          isCheckEmail.value = true;
        }
      } else {
        ElMessage.error(res.data.message);
      }
    } else {
      return false;
    }
  });
};

当校验通过之后就显示重置用户密码的表单信息,否则提示校验失败的提示信息。

这里用户发送邮箱验证码和重置密码的表单项都在同一个 el-form 里面,只不过是通过一个变量的值进行控制显示。

默认 isCheckEmail 的值是 false,当验证码校验通过之后就修改为 true。

6.后端6.1 创建 Springboot 项目

6.1 pom 文件引入相关依赖

<!--web-->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!--redis-->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<!--mybatis-plus-->
<dependency>
    <groupId>com.baomidou</groupId>
    <artifactId>mybatis-plus-boot-starter</artifactId>
    <version>3.4.0</version>
</dependency>
<!--lombok-->
<dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
    <version>1.18.10</version>
</dependency>
<!--mysql-->
<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <version>8.0.30</version>
</dependency>
<!--druid-->
<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>druid</artifactId>
    <version>1.2.4</version>
</dependency>
<!--hutool-->
<dependency>
    <groupId>cn.hutool</groupId>
    <artifactId>hutool-all</artifactId>
    <version>5.7.15</version>
</dependency>
<!-- 邮箱验证码  -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-mail</artifactId>
</dependency>

6.2 yml 配置 email 相关属性

server:
  port: 8081
  servlet:
    context-path: /springboot-vue3-email
spring:
  # 数据库相关
  datasource:
    url: jdbc:mysql://127.0.0.1:3306/zc_online_order?allowPublicKeyRetrieval=true&useSSL=false
    username: root
    password: zhiFou2024@!
    driver-class-name: com.mysql.cj.jdbc.Driver
    type: com.alibaba.druid.pool.DruidDataSource
  # redis相关
  redis:
    host: 127.0.0.1
    port: 6379
    password: zhiFou2024@!
    database: 0
  # 邮箱相关
  mail:
    host: smtp.qq.com  #邮箱服务器地址
    username: 2xxxxxx@qq.com  #邮箱账号
    password: xxxxxxxx       #邮箱授权码
    default-encoding: utf-8    #默认编码

6.3 创建 Email 工具类

/**
 * @author 知否技术
 * @description 邮箱工具类
 * @date 2024-09-25 10:31
 */
@Component
@Slf4j
public class EmailUtil {
    @Autowired
    private JavaMailSender javaMailSender;

    @Autowired
    private RedisTemplate redisTemplate;
    /**
     * 发件人邮箱
     */
    @Value("${spring.mail.username}")
    private String username ;


    /**
     * 发送邮箱验证码
     * @param email
     * @return
     */
    public boolean sendEmailCode(String email){
        // 创建邮件消息
        SimpleMailMessage message = new SimpleMailMessage();
        String emailCode = RandomUtil.randomNumbers(6);
         // 邮箱验证码存入redis
        String redisKey = String.format("redis:resetPassword:sendMessage:%s", email);
        redisTemplate.opsForValue().set(redisKey, emailCode, 20, TimeUnit.MINUTES);
        // 设置邮件主题
        message.setSubject("【知否技术】你此次重置密码的验证码是:" + emailCode);
        // 设置邮件发送者,昵称+<邮箱地址>
        message.setFrom("发件人" + '<' + username + '>');
        // 设置邮件接收者,可以有多个接收者,多个接受者参数需要数组形式
        message.setTo(email);
        // 设置邮件发送日期
        message.setSentDate(new Date());
        // 设置邮件的正文
        message.setText("你此次重置密码的邮箱验证码是" + emailCode + ",请在20分钟内输入验证码进行下一步操作。如非本人操作,请忽略本次邮件!");
        try {
            // 发送邮件
            javaMailSender.send(message);
            return Boolean.TRUE;
        } catch (Exception e) {
            log.error("邮箱验证码异常结果: " + e.getMessage());
            return Boolean.FALSE;
        }
    }

    /**
     * 校验邮箱验证码
     * @param checkEmailParam
     * @return
     */
    public boolean checkEmailVerifyCode(CheckEmailParam checkEmailParam) {
        String redisKey = String.format("redis:resetPassword:sendMessage:%s", checkEmailParam.getEmail());
        // 从Redis获取邮箱验证码
        String redisEmailCode = (String)redisTemplate.opsForValue().get(redisKey);
        if(StrUtil.isBlank(redisEmailCode)){
            throw new RuntimeException("验证码已过期,请重新发送!");
        }
        if(!checkEmailParam.getVerifyCode().equals(redisEmailCode)){
            throw new RuntimeException("验证码不正确,请重新填写!");
        }
        return true;
    }
}

6.4 封装 Redis 配置类

新建 RedisConfig 类

@Slf4j
@Configuration
public class RedisConfig {
    @Resource
    private RedisConnectionFactory factory;

    @Bean
    public RedisTemplate<String, Object> redisTemplate() {
        RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
        redisTemplate.setConnectionFactory(factory);
        GenericJackson2JsonRedisSerializer genericJackson2JsonRedisSerializer = new GenericJackson2JsonRedisSerializer();
        StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();
        redisTemplate.setKeySerializer(stringRedisSerializer);
        redisTemplate.setValueSerializer(genericJackson2JsonRedisSerializer);
        redisTemplate.setHashKeySerializer(stringRedisSerializer);
        redisTemplate.setHashValueSerializer(genericJackson2JsonRedisSerializer);
        redisTemplate.afterPropertiesSet();
        return redisTemplate;
    }

}

6.5 配置全局异常处理器

新建 GlobalExceptionHandler 类

@Slf4j
@RestControllerAdvice
public class GlobalExceptionHandler {

    /**
     * 捕获全局异常
     *
     * @param e
     * @return
     */
    @ExceptionHandler(RuntimeException.class)
    public Result handler(RuntimeException e) {
        log.error("全局异常:{}", e.getMessage());
        return Result.fail(e.getMessage());
    }
}

6.6 创建 controller

@RestController
@RequestMapping("/index")
public class IndexController {
    @Autowired
    private EmailUtil emailUtil;

    /**
     * 发送邮箱验证码
     * @param email
     * @return
     */
    @GetMapping("/sendEmailVerifyCode")
    public Result  sendEmailVerifyCode(@RequestParam String email) {
        boolean flag = emailUtil.sendEmailCode(email);
        if (flag) {
            return Result.success(Boolean.TRUE);
        } else {
            return Result.fail();
        }
    }
    
   /**
     * 校验邮箱验证码
     * @param checkEmailParam
     * @return
     */
    @PostMapping("/checkEmailVerifyCode")
    public Result checkEmailVerifyCode(@RequestBody CheckEmailParam checkEmailParam){
        boolean flag = emailUtil.checkEmailVerifyCode(checkEmailParam);
        if (flag) {
            return Result.success(Boolean.TRUE);
        } else {
            return Result.fail();
        }
    }
}

相关文章

邮件协议以及java实现邮件功能

前言公司项目要求,需要在某个时间点向全公司未填报工时的员工推送提醒邮件。借着这个机会在这边给大家分享一下邮件的一些协议以及如何通过java实现发送代码协议介绍Smtp协议:邮件服务器之间传递消息所使用...

100个Java工具类之15:免费发送邮件

该系列为java工具类系列,主要展示100个常用的java工具类。本系列工具类的核心目的主要有三点:1,以便他用:提供可用的Java工具类,方便大家使用,避免重复造轮子2,个人记录:作为个人记录,同时...

Spring Boot集成Spring Email发送邮件

引言在当今数字化时代,电子邮件仍然是重要的沟通工具之一。在使用Spring Boot框架开发应用程序时,有时需要集成邮件发送功能,以便在用户注册、找回密码、接收通知等场景中向用户发送邮件。本文将介绍如...

spring boot集成spring-boot-starter-mail邮件功能

前情提要以目前IT系统功能来看,邮件功能是非常重要的一个功能。例如:找回密码、邮箱验证,邮件动态码、忘记密码,邮件营销等,都需要用到邮件功能。结合当下最流行的spring boot微服务,推出了spr...

阿里二面:说下如何基于SpringBoot发送邮件?

在我们实际业务开发中邮件发送其实是一个非常常见的需求,用户注册,找回密码等地方都会用到,使用JavaSE代码发送邮件步骤还是挺繁琐的。SpringBoot中对于邮件发送,提供了相关的自动化配置类,使得...

Java二十周年特别策划——谈谈我与Java的那些年、这些事

Java作为当今最流行的编程技术之一,它的出现不仅给软件产业带来了深远影响,也改变了许多人的人生轨迹。得益于其技术平台的通用性、高效性、平台移植性和安全性,许多企业纷纷选用Java作为技术支撑,获得了...