首页 > 其他分享 >SpringBoot+Vue3项目邮箱验证码注册详细教程

SpringBoot+Vue3项目邮箱验证码注册详细教程

时间:2024-09-14 16:49:12浏览次数:13  
标签:const SpringBoot 验证码 result Vue3 import data response

引言

该篇文章是以SpringBoot+Vue3技术栈搭建的项目的邮箱注册登录流程,注册用户使用邮箱校验,使用qq邮箱发送验证码,并且把验证码存入Redis以备校验使用的详细介绍

干货部分

1.在SpringBoot项目的application.yaml文件中配置qq邮箱

spring:
  # mail邮箱
  mail:
    # SMTP服务器(我用的是QQ邮箱的SMTP服务器地址,如果用的其它邮箱请另行百度搜索)
    host: smtp.qq.com
    # 发送验证码的邮箱(发件人的邮箱)
    username: ********@qq.com
    # 授权码  去qq邮箱的账户,开启服务,获取授权码
    password: ********
    # 编码
    default-encoding: utf-8
    # 其它参数
    properties:
      mail:
        smtp:
          # 如果是用SSL方式,需要配置如下属性,使用qq邮箱的话需要开启
          ssl:
            enable: true
            required: true
          # 邮件接收时间的限制,单位毫秒
          timeout: 10000
          # 连接时间的限制,单位毫秒
          connection-timeout: 10000
          # 邮件发送时间的限制,单位毫秒
          write-timeout: 10000

2.使用代码,后端代码

服务层代码,首先验证邮箱是否为空,然后在调用发送验证码业务之前,我们先从Redis通过该key获取数据,看看是否已经存在,如果已经存在,则证明已经发送过验证码并且还未使用。那么我们可以根据业务逻辑去做相应的处理,我这里是告诉前台,已经发送过了。然后调用EmailUtil工具类的生成验证码的方法,然后创建邮箱对象,填写对应参数信息。

package com.example.bootdemo.services.impl;

import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.example.bootdemo.domain.entity.UserInfo;
import com.example.bootdemo.helper.EmailUtil;
import com.example.bootdemo.mapper.UserInfoMapper;
import com.example.bootdemo.services.IUserInfoService;
import jakarta.annotation.Resource;
import jakarta.mail.internet.InternetAddress;
import jakarta.mail.internet.MimeUtility;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.ValueOperations;
import org.springframework.mail.SimpleMailMessage;
import org.springframework.mail.javamail.JavaMailSender;
import org.springframework.stereotype.Service;
import org.apache.commons.lang3.StringUtils;

import java.util.concurrent.TimeUnit;

@Service
public class UserInfoService extends ServiceImpl<UserInfoMapper, UserInfo> implements IUserInfoService {
    @Resource
    private StringRedisTemplate stringRedisTemplate;

    @Resource
    private JavaMailSender javaMailSender;

    @Resource
    private EmailUtil emailUtil;

    @Override
    public String sendEmail(String email) {
        if (StringUtils.isBlank(email)) {
            throw new RuntimeException("未填写收件人邮箱");
        }
        // 定义Redis的key
        String key = "msg_" + email;
        //操作字符串
        ValueOperations<String, String> valueOperations = stringRedisTemplate.opsForValue();
        String verifyCode = valueOperations.get(key);
        if (verifyCode == null) {
            // 随机生成一个6位数字型的字符串
            String code = emailUtil.generateVerifyCode(6);
            // 邮件对象(邮件模板,根据自身业务修改)
            SimpleMailMessage message = new SimpleMailMessage();
            message.setSubject("**用户注册邮箱验证码");
            message.setText("尊敬的用户您好!感谢您的注册。\n\n尊敬的: " + email + "您的校验验证码为: " + code + ",有效期5分钟,请不要把验证码信息泄露给其他人,如非本人请勿操作");
            message.setTo(email);

            try {
                // 对方看到的发送人(发件人的邮箱,根据实际业务进行修改,一般填写的是企业邮箱)
                message.setFrom(new InternetAddress(MimeUtility.encodeText("**ZWQ-INT") + "<**********@qq.com>").toString());
                // 发送邮件
                javaMailSender.send(message);
                // 将生成的验证码存入Redis数据库中,并设置过期时间
                valueOperations.set(key, code, 5L, TimeUnit.MINUTES);
                log.warn ("邮件发送成功");
                return "邮件发送成功";
            } catch (Exception e) {
                log.error("邮件发送出现异常");
                log.error("异常信息为" + e.getMessage());
                log.error("异常堆栈信息为-->");
                return "邮件发送失败";
            }
        } else {
            return "验证码已发送至您的邮箱,请注意查收";
        }
    }
}

工具类EmailUtil代码,封装了验证码校验,和生成随机验证码的功能。我们校验验证码的地方结合Redis的代码。校验成功,意味着我们已经使用了,就会去删除掉这个验证码的key。

package com.example.bootdemo.helper;

import jakarta.annotation.Resource;
import org.springframework.data.redis.core.ValueOperations;
import org.springframework.stereotype.Component;

import org.springframework.data.redis.core.StringRedisTemplate;
import java.util.Properties;
import java.util.Random;

/**
 * @ClassName EmailUtil
 * @Description 邮箱
 * @Author zhangweiqi
 * @Date 2024/9/12 下午4:09
 */
@Component
public class EmailUtil {


    @Resource
    private StringRedisTemplate stringRedisTemplate;

    /**
     * 随机生成指定长度字符串验证码
     *
     * @param length 验证码长度
     */
    public String generateVerifyCode(int length) {
        String strRange = "1234567890";
        StringBuilder strBuilder = new StringBuilder();

        for (int i = 0; i < length; ++i) {
            char ch = strRange.charAt((new Random()).nextInt(strRange.length()));
            strBuilder.append(ch);
        }
        return strBuilder.toString();
    }

    /**
     * 校验验证码(可用作帐号登录、注册、修改信息等业务)
     * 思路:先检查redis中是否有key位对应email的键值对,没有代表验证码过期;如果有就判断用户输入的验证码与value是否相同,进而判断验证码是否正确。
     */
    public Integer checkVerifyCode(String email, String code) {
        int result = 1;
        ValueOperations<String, String> valueOperations = stringRedisTemplate.opsForValue();
        String msgKey = "msg_" + email;
        String verifyCode = valueOperations.get(msgKey);
        /*校验验证码:0验证码错误、1验证码正确、2验证码过期*/
        if (verifyCode == null) {
            result = 2;
        } else if (!code.equals(verifyCode)) {
            result = 0;
        }
        // 如果验证码正确,则从redis删除
        if (result == 1) {
            stringRedisTemplate.delete(msgKey);
        }
        return result;
    }

}

 注册用户,要先校验验证码,然后通过后才去注册用户

//注册用户
    @PostMapping("/registerUser")
    public Result<Boolean> registerUser(@RequestBody RegisterDTO entity){
        Result<Boolean> result = new Result<>();

        //验证码校验
        int res = emailUtil.checkVerifyCode(entity.getEmail(),entity.getCode());

        if(res == 0){
            result.setCode(StateCodeEnum.ERROR.getCode());
            result.setMsg("验证码错误!");
            result.setData(false);
            return result;
        }else if (res == 2){
            result.setCode(StateCodeEnum.ERROR.getCode());
            result.setMsg("验证码过期!");
            result.setData(false);
            return result;
        };

        //查询数据库是否已经存在
        //获取用户数据
        QueryWrapper<UserInfo> queryWrapper = new QueryWrapper<>();
        queryWrapper.eq("username", entity.getUsername()).eq("password", entity.getPassword());
        UserInfo userInfo = userInfoMapper.selectOne(queryWrapper);
        if(userInfo!=null){
            result.setCode(StateCodeEnum.ERROR.getCode());
            result.setMsg("用户已存在,注册失败!");
            result.setData(false);
            return result;
        }

        try {
            //插入用户表
            userInfoMapper.insert(BeanUtil.copyProperties(entity, UserInfo.class));
            result.setCode(StateCodeEnum.ERROR.getCode());
            result.setMsg("注册成功,请登录!");
            result.setData(true);
            return result;
        }catch (Exception e){
            result.setCode(StateCodeEnum.ERROR.getCode());
            result.setMsg(e.getMessage());
            return result;
        }
    }

 3.前端代码

登陆页面,login.vue

<template>
  <el-form
    ref="formRef"
    :model="form"
    label-width="auto"
    :rules="rules"
    style="margin: 0 auto"
  >
    <h1>ZWQ-INT</h1>
    <el-form-item label="用户名:" prop="username">
      <el-input
        v-model="form.username"
        placeholder="请输入用户名"
        style="width: 240px"
        :autocomplete="newPassword"
      >
        <template #prefix>
          <el-icon><User /></el-icon
        ></template>
      </el-input>
    </el-form-item>
    <el-form-item label="密码:" prop="password">
      <el-input
        v-model="form.password"
        placeholder="请输入密码"
        style="width: 240px"
        show-password
        autocomplete="off"
      >
        <template #prefix>
          <el-icon><Lock /></el-icon></template
      ></el-input>
    </el-form-item>
    <el-form-item>
      <el-button type="primary" @click="submitForm">登录</el-button>
      <el-button type="primary" @click="registerFun">注册</el-button>
      <el-button @click="resetForm">重置</el-button>
    </el-form-item>
  </el-form>
</template>

<script>
import { reactive, ref } from "vue";
import axios from "axios";
import { useRouter } from "vue-router";
import { User, Lock } from "@element-plus/icons-vue";
import { ElMessage } from "element-plus";

export default {
  components: {
    User,
    Lock,
  },
  setup() {
    const router = useRouter();
    const formRef = ref();
    const form = reactive({
      username: "",
      password: "",
      //http://localhost:8082/zwq/getIsLogin
    });

    const rules = reactive({
      username: [
        {
          required: true,
          message: "用户名不能为空",
          trigger: "blur",
        },
      ],
      password: [
        {
          required: true,
          message: "密码不能为空",
          trigger: "blur",
        },
      ],
    });

    //后端接口地址
    const backendUrl = "http://localhost:8082/zwq";

    //点击登录
    const submitForm = () => {
      formRef.value
        .validate()
        .then(async () => {
          if (form.username.trim() && form.password.trim()) {
            try {
              // 注意:GET请求通常通过查询参数传递数据,而不是请求体
              // 所以我们将参数附加到URL上
              // const response = await axios.get(
              //   `${backendUrl}/getIsLogin?username=${form.username}&password=${form.password}`
              // );
              // console.log(response);
              //获取token
              const response = await axios.post(`${backendUrl}/login`, {
                username: form.username.trim(),
                password: form.password.trim(),
              });
              // 假设后端返回了一个包含登录状态的对象,例如 { isLogin: true }
              //获取token
              if (response.data != null && response.data != "") {
                if (response.data.code === 200) {
                  localStorage.setItem("access-admin", response.data.data);
                  ElMessage({ message: "登陆成功", type: "success" });
                  router.push("/index");
                }
                if (response.data.code === 500) {
                  ElMessage.error(response.data.msg);
                }
              }

              //获取用户信息
            } catch (error) {
              ElMessage({ message: "登陆失败", type: "warning" });
            }
          }
        })
        .catch(() => {});
    };
    //点击注册
    const registerFun = () => {
      router.push("/register");
      console.log("进入注册!");
    };
    //点击重置
    const resetForm = () => {
      form.password = "";
      form.username = "";
    };

    return {
      form,
      rules,
      submitForm,
      resetForm,
      registerFun,
      formRef,
    };
  },
};
</script>

<style scoped>
.el-form {
  display: flex;
  justify-content: center;
  align-items: center;
  flex-direction: column; /* 如果需要垂直居中 */
}
</style>

注册页面,register.vue

<template>
  <el-form
    ref="formRef"
    :model="form"
    label-width="auto"
    style="margin: 0 auto"
    :rules="rules"
  >
    <h1>注册页面</h1>
    <el-form-item label="用户名:" prop="username">
      <el-input
        v-model="form.username"
        placeholder="请输入用户名"
        style="width: 350px"
        autocomplete="off"
      ></el-input>
    </el-form-item>
    <el-form-item label="密码:" prop="password">
      <el-input
        v-model="form.password"
        placeholder="请输入密码"
        style="width: 350px"
        show-password
        autocomplete="off"
      ></el-input>
    </el-form-item>
    <el-form-item label="确认密码:" prop="againpassword">
      <el-input
        v-model="form.againpassword"
        placeholder="请输入确认密码"
        style="width: 350px"
        show-password
        autocomplete="off"
      ></el-input>
    </el-form-item>
    <el-form-item label="邮箱:" prop="email">
      <el-input
        v-model="form.email"
        placeholder="请输入邮箱"
        style="width: 243px; margin-right: 5px"
        autocomplete="off"
      ></el-input>
      <el-button
        type="primary"
        :disabled="isClick || countdown > 0"
        @click="getCode"
        >{{
          countdown > 0 ? `${countdown}秒后重新获取` : "获取验证码"
        }}</el-button
      >
    </el-form-item>
    <el-form-item label="验证码:" prop="code">
      <el-input
        v-model="form.code"
        placeholder="请输入验证码"
        style="width: 350px"
        autocomplete="off"
      ></el-input>
    </el-form-item>
    <el-form-item>
      <el-button type="primary" @click="onSubmit">提交</el-button>
      <el-button @click="onCancel">取消</el-button>
    </el-form-item>
  </el-form>
</template>

<script>
import { onMounted, reactive, ref } from "vue";
import { useRouter } from "vue-router";
import axios from "axios";
import { ElMessage } from "element-plus";

export default {
  setup() {
    const router = useRouter();
    const formRef = ref();
    //后端接口地址
    const backendUrl = "http://localhost:8082/zwq";
    // 编辑
    const form = reactive({
      username: "",
      password: "",
      againpassword: "",
      email: "",
      code: "",
    });
    onMounted(() => {});

    const emailPattern = /[a-zA-Z0-9._-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,6}$/;
    const isClick = ref(true);

    //倒计时
    const countdown = ref(0);

    //校验
    const rules = reactive({
      username: [
        {
          required: true,
          message: "用户名不能为空",
          trigger: "blur",
        },
      ],
      password: [
        {
          required: true,
          validator: (rule, value, callback) => {
            if (!value) {
              callback(new Error("密码不能为空"));
            } else {
              callback();
            }
          },
          trigger: "change",
        },
      ],
      againpassword: [
        {
          required: true,
          validator: (rule, value, callback) => {
            if (value === "") {
              callback(new Error("确认密码不能为空"));
            } else {
              if (value !== form.password) {
                callback(new Error("两次密码输入不一致"));
              } else {
                callback();
              }
            }
          },
          trigger: "change",
        },
      ],
      email: [
        {
          required: true,
          message: "邮箱不能为空",
          trigger: "blur",
        },
        {
          validator: (rule, value, callback) => {
            if (!emailPattern.test(value)) {
              callback(new Error("邮箱格式不正确"));
              isClick.value = true;
            } else {
              isClick.value = false;
              callback();
            }
          },
          trigger: "blur",
        },
      ],
      code: [
        {
          required: true,
          message: "验证码不能为空",
          trigger: "blur",
        },
      ],
    });

    const getCode = async () => {
      try {
        const response = await axios.post(`${backendUrl}/getCode`, {
          email: form.email,
        });
        if (response.data != null && response.data != "") {
          if (response.data.code !== 200) {
            ElMessage.error(response.data.msg);
          } else {
            ElMessage({ message: response.data.msg, type: "success" });
            countdown.value = 60;
            const intervalId = setInterval(() => {
              if (countdown.value > 0) {
                countdown.value--;
              } else {
                clearInterval(intervalId);
              }
            }, 1000);
          }
        }
      } catch (error) {
        ElMessage.error("请求异常");
      }
    };

    const onSubmit = () => {
      console.log("提交前Form: ", form);
      formRef.value
        .validate()
        .then(async () => {
          try {
            console.log(form);
            const response = await axios.post(
              `${backendUrl}/registerUser`,
              form
            );

            if (response.data != null && response.data != "") {
              if (!response.data.data) {
                ElMessage({ message: response.data.msg, type: "warning" });
              } else {
                ElMessage({ message: response.data.msg, type: "success" });
                router.push("/");
              }
            }
          } catch (error) {
            ElMessage.error("请求异常");
          }
        })
        .catch(() => {});
    };
    const onCancel = async () => {
      await router.push("/");
    };
    return {
      rules,
      form,
      isClick,
      countdown,
      getCode,
      onSubmit,
      onCancel,
      formRef,
    };
  },
};
</script>

<style>
.el-form {
  display: flex;
  justify-content: center;
  align-items: center;
  flex-direction: column; /* 如果需要垂直居中 */
}
</style>

主页,index.vue

 

<template>
  <div v-if="userData">
    欢迎回来:{{ userData.username }}
    <h1>这是首页</h1>
  </div>
  <div v-else>加载中...</div>
</template>
<!-- <template>
  <div class="common-layout">
    <el-container>
      <el-aside width="200px">Aside</el-aside>
      <el-container>
        <el-header>Header</el-header>
        <el-main>Main</el-main>
      </el-container>
    </el-container>
  </div>
</template> -->
<script>
import axios from "axios";
import { ref, onMounted } from "vue";
import { useRouter } from "vue-router";

export default {
  setup() {
    const router = useRouter();
    //后端接口地址
    const backendUrl = "http://localhost:8082/zwq";
    const userData = ref(null);

    //钩子
    onMounted(async () => {
      try {
        const token = localStorage.getItem("access-admin");
        if (!token) {
          // 处理没有 token 的情况
          return;
        }

        const response = await axios({
          method: "post",
          url: `${backendUrl}/getUserData`,
          headers: {
            Authorization: `Bearer ${token}`,
          },
        });

        userData.value = response.data; // 更新响应式引用的值
        console.log(response);
        if (response.data === "") {
          alert("token验证失败,请重新登录!");
          await router.push("/");
        }
      } catch (error) {
        console.error("Error fetching user data:", error);
        // 处理错误,比如显示错误消息或重定向到登录页面
      }
    });

    return {
      userData,
    };
  },
};
</script>
<style scoped></style>

效果

登陆页面

 

注册页面

 

邮箱验证码获取成功

 

 我做了校验,重复用户信息,不准创建。

 

 注册成功

登录成功

 

登陆成功,Redis的验证码清空

 

 至此完成

总结

通过这篇文章,可以了解到,通过邮箱获取验证码,并把验证码存储到Redis里面,在注册时去Redis获取,和前端传过去的验证码做校验。来注册用户。此项目继承了JWT授权,如有想了解的,请前往Jwt授权,也是博主记录的。相对于之前的,前端接入了Element Plus UI,页面好看了不少。后端呢,封装了Result类去返回给前端。后续呢,前端会把调用接口封装一下,统一一下风格。后面这个初学项目代码会持续更新,慢慢完善。如有想要源码的,评论111。

有不对的地方欢迎指正,共同学习进步,祝您每天快乐✊。

标签:const,SpringBoot,验证码,result,Vue3,import,data,response
From: https://blog.csdn.net/qq_53924122/article/details/142256574

相关文章

  • springboot保存redis键值对出现乱码\xac\xed\x00\x05t\x00
    当使用RedisTemplate进行操作时,发现保存的key带有特殊字符。原因是RedisTemplate默认处理key为对象,改为StringRedisTemplate后,能正确保存字符串key,避免了编码问题。@SpringBootTestclassRedisApplicationTests{@AutowiredprivateRedisTemplateredisTemplate;......
  • SpringBoot框架下的房产销售系统开发
    第一章绪论1.1背景及意义房产销售也都将通过计算机进行整体智能化操作,对于房产销售系统所牵扯的管理及数据保存都是非常多的,例如管理员;首页、个人中心、用户管理、销售经理管理、房源信息管理、房源类型管理、房子户型管理、交易订单管理、预约看房管理、评价管理、我的......
  • Vue3中组件通信的几种方式
    Vue3组件通信和Vue2的区别:移出事件总线,使用mitt代替。vuex换成了pinia。把.sync优化到了v-model里面了。把$listeners所有的东西,合并到$attrs中了。$children被砍掉了。【1】props概述:props是使用频率最高的一种通信方式,常用与:父↔子。若父传子:属性值是非函数。若子传父:属性......
  • Vue3中Pinia存储和修改数据应用实践
    安装pinia:npminstallpiniamain.ts中使用pinia://引入createApp用于创建应用import{createApp}from'vue'//引入App根组件importAppfrom'./App.vue'//引入路由器importrouterfrom'./router'//创建一个应用constapp=createApp(App)//使用路由器app.use......
  • Vue3中路由传参的几种方式实践
    【1】RouterLink+query父组件脚本如下:<scriptsetuplang="ts"name="News">import{reactive}from'vue'import{RouterView,RouterLink}from'vue-router'constnewsList=reactive([{id:'asfdtrfay01',......
  • Vue2/Vue3中编程式路由导航实践总结
    【1】Vue2编程式路由导航①router.push除了使用<router-link>创建a标签来定义导航链接,我们还可以借助router的实例方法,通过编写代码来实现。router.push(location,onComplete?,onAbort?)注意:在Vue实例内部,你可以通过$router访问路由实例。因此你可以调用this.$router......
  • vscode下vue3+vite+ts+eslint项目配置
    一、创建项目pnpmcreatevue@latest注意:是否引入ESLint用于代码质量检测?选择否二、安装依赖pnpmi-Deslint@antfu/eslint-config三、在项目根目录创建文件:eslint.config.js//eslint.config.jsimportantfufrom'@antfu/eslint-config'exportdefaultantfu({......
  • 春意教育:SpringBoot在线学习平台开发
    第二章关键技术的研究2.1相关技术网络教学平台是在Java+MySQL开发环境的基础上开发的。Java是一种服务器端脚本语言,易于学习,实用且面向用户。全球超过35%的Java驱动的互联网站点使用Java。MySQL是一个数据库管理系统,因为它的体积小但速度快,成本低,或者开源受到中小型网站......
  • 【万字文档+PPT+源码】基于springboot+vue投稿和稿件处理系统-可用于毕设-课程设计-练
    博主简介:......
  • 春日教育:SpringBoot视频教学平台实现
    第三章系统分析3.1系统设计目标在线视频教育平台主要是为了用户方便对首页、个人中心、用户管理、教师管理、课程信息管理、课程类型管理、我的收藏管理、系统管理、订单管理等信息进行查询,也是为了更好的让管理员进行更好存储所有数据信息及快速方便的检索功能,对系统的......