Vue3问题:如何实现短信验证码登录?前后端!

前端功能问题系列文章,点击上方合集↑

序言

大家好,我是大澈!

本文约3400+字,整篇阅读大约需要4分钟。

本文主要内容分三部分,第一部分是需求分析,第二部分是实现步骤,第三部分是问题详解。

如果您只需要解决问题,请阅读第一、二部分即可。

如果您有更多时间,进一步学习问题相关知识点,请阅读至第三部分。

1. 需求分析

点击发送验证码按钮,获取手机短信验证码。此时,发送验证码按钮进入倒计时状态,且不可被点击。

成功获取到短信验证码并输入,点击登录按钮,完成页面跳转。

2. 实现步骤

2.1 准备工作

短信验证码登录功能的实现,借助了阿里云短信业务API,我们可以使用阿里云的短信服务向用户发送验证码、通知、营销等不同类型的短信。

总体实现步骤如下:

  • 创建阿里云账号:如果您还没有阿里云账号,需要先注册一个账号并完成身份验证。

  • 开通短信服务:登录阿里云控制台,找到短信服务并进行开通,然后完善相关信息,包括资质、签名、模板等,最后还需要购买短信套餐包。

    这里解释一下两个概念:

    短信签名:短信签名是您发送短信时用于展示发送方身份的标识,需要在阿里云短信控制台进行申请和审核。

    短信模板:短信模板是您发送短信时所使用的短信内容模板,需要在阿里云短信控制台进行申请和审核。

  • 获取API凭证:在阿里云控制台中,创建 AccessKey,并确保该密钥对拥有短信发送的权限。

  • 编写后端接口调用对应API:使用选择的编程语言和相关 SDK,调用阿里云短信业务API中相应的接口,如发送短信、查询发送记录等。

  • 编写前端代码:发送短信并验证登录。

下面是具体的代码实现,包括前端、后端两方面。

2.2 编写前端代码

对于前端来说,所做的操作并不算多,无非是要实现点击发送短信验证码后的倒计时效果,以及两个接口的调用。

对于倒计时效果,就是利用了定时器,去控制发送验证码按钮的状态。

对于调用的两个接口,一个是发送短信接口,一个是短信验证及登录接口。

相关代码实例请继续往下看,每个地方基本都给大家做了详细的注释。

模版代码:

xml 复制代码
<template>
  <div class="box">
    <el-form :model="dataForm" label-width="120px">
      <el-form-item label="手机号:">
        <el-input v-model="dataForm.phone" />
      </el-form-item>
      <el-form-item label="验证码:">
        <el-input v-model="dataForm.code" />
        <el-button type="warning" @click="sendVerificationCode" :disabled="isSendingCode || countdown > 0">
          {{ countdown > 0 ? `重新发送(${countdown})` : '发送验证码' }}
        </el-button>
      </el-form-item>
      <el-form-item>
        <el-button type="primary" @click="onSubmit" >登录</el-button>
      </el-form-item>
    </el-form>
  </div>
</template>

逻辑代码:

xml 复制代码
<script setup>
import {reactive, ref} from 'vue'
import Axios from '../api/axios';
import {ElMessage} from "element-plus";

const dataForm = reactive({
  phone: '123456',
  code: '',
})
let isSendingCode = ref(false);
let countdown = ref(0);

// 发送验证码,调验证码接口
const sendVerificationCode = () => {
  // 检查手机号是否有效
  // ...

  // 调发送短信接口
  Axios.get('/admin/send', {
    params: {
      phone: dataForm.phone,
    }
  })
  .then(res => {
    ElMessage.success(res.data)
  })
  .catch(error => {
    console.error(error);
  });


  isSendingCode.value = true;
  // 设置倒计时时间,这里假设为10秒
  countdown.value = 10;

  // 倒计时效果
  const countdownInterval = setInterval(() => {
    countdown.value--;
    if (countdown.value <= 0) {
      clearInterval(countdownInterval);
      isSendingCode.value = false;
    }
  }, 1000);
}

// 登录,调检验验证码和密码的登录接口
const onSubmit = () => {
  // 调发送短信接口
  Axios.post('/admin/checkLogin', {
    phoneNumber: dataForm.phone,
    code: dataForm.code,
  })
  .then(res => {
    ElMessage.success(res.data)
  })
  .catch(error => {
    console.error(error);
  });
}
</script>

2.3 编写后端接口

对于后端实现,要先引入阿里云短信业务依赖,再封装短信发送工具类,再编写两个接口,一个是发送短信接口,一个是短信验证及登录接口。

在发送短信接口业务层中,生成随机字符串,传入并调用短信发送工具类方法发送验证码,并把验证码存入Redis中。

在短信验证及登录接口业务层中,将前端传过来的验证码与Redis中存的验证码进行比较,校验成功执行下一步登录操作。

相关代码实例请继续往下看,每个地方基本都给大家做了详细的注释。

Pom.xml中引入依赖代码:

xml 复制代码
<!--redis-->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<!-- 阿里短信包 -->
<dependency>
    <groupId>com.aliyun</groupId>
    <artifactId>aliyun-java-sdk-core</artifactId>
    <version>4.6.0</version>
</dependency>
<dependency>
    <groupId>com.aliyun</groupId>
    <artifactId>aliyun-java-sdk-dysmsapi</artifactId>
    <version>1.1.0</version>
</dependency>
<!-- json -->
<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>fastjson</artifactId>
    <version>1.2.76</version>
</dependency>

短信发送服务工具类代码:

typescript 复制代码
package com.dache.base.common.util;

import com.alibaba.fastjson.JSONObject;
import com.aliyuncs.DefaultAcsClient;
import com.aliyuncs.IAcsClient;
import com.aliyuncs.dysmsapi.model.v20170525.SendSmsRequest;
import com.aliyuncs.dysmsapi.model.v20170525.SendSmsResponse;
import com.aliyuncs.exceptions.ClientException;
import com.aliyuncs.exceptions.ServerException;
import com.aliyuncs.profile.DefaultProfile;
import com.google.gson.Gson;

import java.util.HashMap;

/**
 * @Description 短信发送服务
 */
public class SmsUtil {
    public static String toSendMes(String phoneNumber,String code) {
        //1.连接阿里云
        /**
         "<your-region-id>",           // The region ID 地区标识
         "<your-access-key-id>",       // The AccessKey ID of the RAM account RAM账户的AccessKey ID 阿里云账号可查
         "<your-access-key-secret>",   // The AccessKey Secret of the RAM account RAM 账户的 AccessKey Secret 阿里云账号可查
         **/
        DefaultProfile profile = DefaultProfile.getProfile("cn-beijing", "your-access-key-id", "your-access-key-secret");

        IAcsClient client = new DefaultAcsClient(profile);
        //2.构建请求 自定义参数
        SendSmsRequest request = new SendSmsRequest();
        //接收短信的手机号码
        request.setPhoneNumbers(phoneNumber);
        //短信签名名称
        request.setSignName("阿里云短信测试");
        //短信模板CODE
        request.setTemplateCode("SMS_154SECRET9");
        //模版内容:您正在使用阿里云短信测试服务,体验验证码是:${code},如非本人操作,请忽略本短信!
        //短信模板变量对应的实际值
        //("{"code":"1234"}");
        HashMap<String,String> param=new HashMap<>();
        param.put("code",code);
        request.setTemplateParam(JSONObject.toJSONString(param));
        SendSmsResponse response = new SendSmsResponse();
        try {
            //3.发送请求
            response = client.getAcsResponse(request);
        } catch (ServerException e) {
            e.printStackTrace();
        } catch (ClientException e) {
            System.out.println("ErrCode:" + e.getErrCode());
            System.out.println("ErrMsg:" + e.getErrMsg());
            System.out.println("RequestId:" + e.getRequestId());
        }
        //短信成功返回json {  "RequestId": "614048FB-0619-4439-A1D5-AA8B218A****",  "Message": "OK",  "BizId": "386715418801811068^0",  "Code": "OK"}
        return response.getMessage();
    }
}

接口Controller层代码:

less 复制代码
/**
* 发送短信
*/
@ApiOperation(value = "发送短信")
@GetMapping("/send")
public CommonResult<String> toSendMessage(@RequestParam("phone") String phone){
  String message = adminService.toSendMessage(phone);
  return CommonResult.success(message);
}

/**
* 检验验证码和密码,校验成功后登录
*/
@ApiOperation(value = "检验验证码和密码,校验成功后登录")
@PostMapping("/checkLogin")
public CommonResult<String> checkLogin(@RequestBody ToCheckLoginDTO toCheckLogin){
  String checkLogin = adminService.checkLogin(toCheckLogin.getPhoneNumber(), toCheckLogin.getCode());

  // 省略用户密码加密校验
  // ...

  // 省略JWT认证
  // ...

  return CommonResult.success(checkLogin);
}

接口Service层代码:

typescript 复制代码
/**
* 发送短信
*/
@Override
public String toSendMessage(String phoneNumber) {
  //扩展 可以验证该电话号码是否注册
  //1.判定验证码是否过期
  String code = redisTemplate.opsForValue().get(phoneNumber);
  if (!StringUtils.isEmpty(code)){
      return phoneNumber+":"+"验证码未过期";
  }
  //2.已过期/无验证码 生成验证码
  //随机生成字符串 做验证码
  int toCode = (int) (Math.random() * (50000 - 40000) + 40000);
  code=Integer.toString(toCode);
  String toSendMes = SmsUtil.toSendMes(phoneNumber, code);
  if (ComConstants.OK.equals(toSendMes)){
      //redis 中存放 5分钟过期
      redisTemplate.opsForValue().set(phoneNumber,code,ComConstants.NUM_FIVE, TimeUnit.MINUTES);
      //3.发送短信
      return "短信发送成功";
  }
  return "短信发送异常";
}

/**
* 检验手机验证码并登录
*/
@Override
public String checkLogin(String phoneNumber, String code) {
  //1.redis 验证码校验
  String redisCode = redisTemplate.opsForValue().get(phoneNumber);
  if (code.equals(redisCode)){
      return "登入成功";
  }
  return "登入失败";
}

当然,上述前端和后端代码的实现中,必然会存在一些缺陷,因为它只是一个实例,具体的实现还需要在项目中根据需求进行完善。

3. 问题详解

3.1 常见短信服务API文档地址整理

阿里云短信服务API文档地址:https://help.aliyun.com/document_detail/101414.html。以下是一些其它常见短信服务供应商:

  • 腾讯云短信服务API:腾讯云提供了短信服务API,用于发送短信验证码和推广短信。文档地址:https://cloud.tencent.com/document/product/382
  • 云片网短信API:云片网是国内的一家短信服务提供商,他们提供了简单易用的短信API接口,用于发送验证码、通知短信等。文档地址:https://www.yunpian.com/doc/zh_CN/introduction/index.html
  • 极光短信API:极光推送是一家提供多种推送服务的服务提供商,其中包括短信推送服务。他们提供了短信API,可以用于发送短信验证码和通知短信。文档地址:https://docs.jiguang.cn/jpush/server/push/rest_api_v3_sms/

结语

建立这个平台的初衷:

  • 打造一个仅包含前端问题的问答平台,让大家高效搜索处理同样问题。
  • 通过不断积累问题,一起练习逻辑思维,并顺便学习相关的知识点。
  • 遇到难题,遇到有共鸣的问题,一起讨论,一起沉淀,一起成长。

感谢关注微信公众号:"程序员大澈",然后加入问答群,让我们一起解决实现所有BUG!

相关推荐
还是大剑师兰特29 分钟前
D3的竞品有哪些,D3的优势,D3和echarts的对比
前端·javascript·echarts
王解30 分钟前
【深度解析】CSS工程化全攻略(1)
前端·css
一只小白菜~36 分钟前
web浏览器环境下使用window.open()打开PDF文件不是预览,而是下载文件?
前端·javascript·pdf·windowopen预览pdf
方才coding41 分钟前
1小时构建Vue3知识体系之vue的生命周期函数
前端·javascript·vue.js
man201743 分钟前
【2024最新】基于springboot+vue的闲一品交易平台lw+ppt
vue.js·spring boot·后端
阿征学IT1 小时前
vue过滤器初步使用
前端·javascript·vue.js
王哲晓1 小时前
第四十五章 Vue之Vuex模块化创建(module)
前端·javascript·vue.js
发现你走远了1 小时前
『VUE』25. 组件事件与v-model(详细图文注释)
前端·javascript·vue.js
张张打怪兽1 小时前
css-50 Projects in 50 Days(3)
前端·css
吖秧吖1 小时前
three.js 杂记
开发语言·前端·javascript