验证码登录如何实现?

手机验证码登录

  • 1、需求分析
  • 2、数据模型
  • 3、代码开发-交互过程
  • 4、代码开发-准备工作
  • 5、代码开发-修改LoginCheckFilter
  • 6、代码开发-接口开发
  • 7、前端代码介绍
  • 8、启动测试

1、需求分析

为了方便用户登录,移动端通常都会提供通过手机验证码登录的功能。

手机验证码登录的优点:

  • 方便快捷,无需注册,直接登录
  • 使用短信验证码作为登录凭证,无需记忆密码
  • 安全

登录流程:输入手机号>获取验证码>输入验证码>点击登录>登录成功

注意:通过手机验证码登录,手机号是区分不同用户的标识。

2、数据模型

通过手机验证码登录时,涉及的表为user表,即用户表。结构如下:

在这里插入图片描述

3、代码开发-交互过程

在开发代码之前,需要梳理一下登录时前端页面和服务端的交互过程:

  1. 在登录页面(front/page/login.html)输入手机号,点击【获取验证码】按钮,页面发送ajax请求,在服务端调用短信服务API给指定手机号发送验证码短信
  2. 在登录页面输入验证码,点击【登录】按钮,发送ajax请求,在服务端处理登录请求

开发手机验证码登录功能,其实就是在服务端编写代码去处理前端页面发送的这2次请求即可。

4、代码开发-准备工作

在开发业务功能前,先将需要用到的类和接口基本结构创建好:

  • 实体类User
  • Mapper接口UserMapper
  • 业务层接口UserService
  • 业务层实现类UserServicelmpl控制层Usercontroller
  • 工具类SMSutils、ValidateCodeutils

工具类SMSutils(阿里云短信服务):

package com.mannor.reggie_take_out.Utils;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.profile.DefaultProfile;/*** 短信发送工具类*/
public class SMSUtils {/*** 发送短信* @param signName 签名* @param templateCode 模板* @param phoneNumbers 手机号* @param param 参数*/public static void sendMessage(String signName, String templateCode,String phoneNumbers,String param){DefaultProfile profile = DefaultProfile.getProfile("cn-hangzhou", "", "");IAcsClient client = new DefaultAcsClient(profile);SendSmsRequest request = new SendSmsRequest();request.setSysRegionId("cn-hangzhou");request.setPhoneNumbers(phoneNumbers);request.setSignName(signName);request.setTemplateCode(templateCode);request.setTemplateParam("{\"code\":\""+param+"\"}");try {SendSmsResponse response = client.getAcsResponse(request);System.out.println("短信发送成功");}catch (ClientException e) {e.printStackTrace();}}}

工具类:ValidateCodeutils(生成验证码):

package com.mannor.reggie_take_out.Utils;import java.util.Random;/*** 随机生成验证码工具类*/
public class ValidateCodeUtils {/*** 随机生成验证码* @param length 长度为4位或者6位* @return*/public static Integer generateValidateCode(int length){Integer code =null;if(length == 4){code = new Random().nextInt(9999);//生成随机数,最大为9999if(code < 1000){code = code + 1000;//保证随机数为4位数字}}else if(length == 6){code = new Random().nextInt(999999);//生成随机数,最大为999999if(code < 100000){code = code + 100000;//保证随机数为6位数字}}else{throw new RuntimeException("只能生成4位或6位数字验证码");}return code;}/*** 随机生成指定长度字符串验证码* @param length 长度* @return*/public static String generateValidateCode4String(int length){Random rdm = new Random();String hash1 = Integer.toHexString(rdm.nextInt());String capstr = hash1.substring(0, length);return capstr;}
}

5、代码开发-修改LoginCheckFilter

  • LoginCheckFilter过滤器的开发,此过滤器用于检查用户的登录状态。我们在进行手机验证码登录时,发送的请求需要在此过滤器处理时直接放行。(注释1)

  • 在LoginCheckFilter过滤器中扩展逻辑,判断移动端用户登录状态(注释4-1):

package com.mannor.reggie_take_out.filter;import com.alibaba.fastjson.JSON;
import com.mannor.reggie_take_out.common.BaseContext;
import com.mannor.reggie_take_out.common.R;
import lombok.extern.slf4j.Slf4j;
import org.springframework.util.AntPathMatcher;import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;@WebFilter(filterName = "LoginCheckFilter", urlPatterns = "/*")
@Slf4j
public class LoginCheckFilter implements Filter {//路径匹配器,支持通配符public static final AntPathMatcher PATH_MATCHER = new AntPathMatcher();@Overridepublic void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {HttpServletRequest request = (HttpServletRequest) servletRequest;HttpServletResponse response = (HttpServletResponse) servletResponse;// 1、获取本次请求的URIString requestURI = request.getRequestURI();log.info("拦截到请求:{}", requestURI);//定义不需要处理的路径String[] urls = new String[]{"/employee/login","/employee/logout","/backend/**","/front/**","/common/**","/user/login", //移动端登录"/user/sendMsg" //移动端发送短信};// 2、判断本次请求是否需要处理boolean check = check(urls, requestURI);// 3、如果不需要处理,则直接放行if (check) {filterChain.doFilter(request, response);log.info("本次请求{}不需要处理", requestURI);return;}// 4-1、判断员工登录状态,如果已登录,则直接放行if (request.getSession().getAttribute("EmployeeId") != null) {log.info("用户已经登录,用户id为:{}", request.getSession().getAttribute("EmployeeId"));//将id存入线程变量aLong employeeId = (Long) request.getSession().getAttribute("EmployeeId");BaseContext.setCurrentId(employeeId);filterChain.doFilter(request, response);return;}// 4-1、判断用户登录状态,如果已登录,则直接放行if (request.getSession().getAttribute("user") != null) {log.info("用户已经登录,用户id为:{}", request.getSession().getAttribute("user"));//将id存入线程变量aLong userId = (Long) request.getSession().getAttribute("EmployeeId");BaseContext.setCurrentId(userId);filterChain.doFilter(request, response);return;}// 5、如果未登录则返回未登录结果,通过输出流方式向客户端相应数据log.info("用户未登录");response.getWriter().write(JSON.toJSONString(R.error("ONT_LOGIN")));return;}public boolean check(String[] urls, String requestURI) {for (String url : urls) {boolean match = PATH_MATCHER.match(url, requestURI);if (match) {return true;}}return false;}
}

6、代码开发-接口开发

package com.mannor.reggie_take_out.controller;import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.mannor.reggie_take_out.Utils.SMSUtils;
import com.mannor.reggie_take_out.Utils.ValidateCodeUtils;
import com.mannor.reggie_take_out.common.R;
import com.mannor.reggie_take_out.entity.User;
import com.mannor.reggie_take_out.service.UserService;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;import javax.servlet.http.HttpSession;
import java.util.Map;@RestController
@Slf4j
@RequestMapping("/user")
public class UserController {@Autowiredprivate UserService userService;/*** 发送短信验证码** @param user* @param session* @return*/@PostMapping("/sendMsg")public R<String> sendMsg(@RequestBody User user, HttpSession session) {//获取手机号String phone = user.getPhone();if (StringUtils.isNotEmpty(phone)) {//生成随机的6位验证码String code = ValidateCodeUtils.generateValidateCode(6).toString();log.info("code={}", code);//调用阿里云提供的短信服务API完成发送短信//SMSUtils.sendMessage("杨自强的博客","SMS_462036405",phone,code);//需要将生成的验证码保存到Sessionsession.setAttribute(phone, code);return R.success("短信验证码发送成功!");}return R.error("短信验证码发送失败!");}@PostMapping("/login")public R<User> login(@RequestBody Map map, HttpSession session) {log.info("map={}", map);//获取手机号String phone = map.get("phone").toString();//获取验证码String code = map.get("code").toString();//从Session中获取保存的验证码Object codeInSession = session.getAttribute(phone);//进行验证码的比对(页面提交的验证码和Session中保存的验证码比对)if (codeInSession != null && codeInSession.equals(code)) {// 如果能够比对成功,说明登录成功LambdaQueryWrapper<User> lambdaQueryWrapper = new LambdaQueryWrapper<>();lambdaQueryWrapper.eq(User::getPhone, phone);User user = userService.getOne(lambdaQueryWrapper);if (user == null) {//判断当前手机号对应的用户是否为新用户,如果是新用户就自动完成注册user = new User();user.setPhone(phone);user.setStatus(1);userService.save(user);}session.setAttribute("user",user.getId());return R.success(user);}return R.error("登录失败");}
}

7、前端代码介绍

  • 登录页面
<!DOCTYPE html>
<html lang="en">
<head><meta charset="utf-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><!-- 上述3个meta标签*必须*放在最前面,任何其他内容都*必须*跟随其后! --><meta name="viewport"content="width=device-width, initial-scale=1.0, maximum-scale=1.0,user-scalable=no,minimal-ui"><title>菩提阁</title><link rel="icon" href="../images/favico.ico"><!--不同屏幕尺寸根字体设置--><script src="../js/base.js"></script><!--element-ui的样式--><link rel="stylesheet" href="../../backend/plugins/element-ui/index.css"/><!--引入vant样式--><link rel="stylesheet" href="../styles/vant.min.css"/><!-- 引入样式  --><link rel="stylesheet" href="../styles/index.css"/><!--本页面内容的样式--><link rel="stylesheet" href="../styles/login.css"/>
</head>
<body>
<div id="login" v-loading="loading"><div class="divHead">登录</div><div class="divContainer"><el-input placeholder=" 请输入手机号码" v-model="form.phone" maxlength='20'/></el-input><div class="divSplit"></div><el-input placeholder=" 请输入验证码" v-model="form.code" maxlength='20'/></el-input><span @click='getCode'>获取验证码</span></div><div class="divMsg" v-if="msgFlag">手机号输入不正确,请重新输入</div><el-button type="primary" :class="{btnSubmit:1===1,btnNoPhone:!form.phone,btnPhone:form.phone}" @click="btnLogin">登录</el-button>
</div>
<!-- 开发环境版本,包含了有帮助的命令行警告 -->
<script src="../../backend/plugins/vue/vue.js"></script>
<!-- 引入组件库 -->
<script src="../../backend/plugins/element-ui/index.js"></script>
<!-- 引入vant样式 -->
<script src="../js/vant.min.js"></script>
<!-- 引入axios -->
<script src="../../backend/plugins/axios/axios.min.js"></script>
<script src="../js/request.js"></script>
<script src="../api/login.js"></script>
</body>
<script>new Vue({el: "#login",data() {return {form: {phone: '',code: ''},msgFlag: false,loading: false}},computed: {},created() {},mounted() {},methods: {getCode() {this.form.code = ''const regex = /^(13[0-9]{9})|(15[0-9]{9})|(17[0-9]{9})|(18[0-9]{9})|(19[0-9]{9})$/;if (regex.test(this.form.phone)) {this.msgFlag = false//this.form.code = (Math.random()*1000000).toFixed(0)  //随机生成数sendMsgApi({phone: this.form.phone})} else {this.msgFlag = true}},async btnLogin() {if (this.form.phone && this.form.code) {this.loading = trueconst res = await loginApi(this.form)this.loading = falseif (res.code === 1) {sessionStorage.setItem("userPhone", this.form.phone)window.requestAnimationFrame(() => {window.location.href = '/front/index.html'})} else {this.$notify({type: 'warning', message: res.msg});}} else {this.$notify({type: 'warning', message: '请输入手机号码'});}}}})
</script>
</html>
  • 登录的js文件:
function loginApi(data) {return $axios({'url': '/user/login','method': 'post',data})
}function loginoutApi() {return $axios({'url': '/user/loginout','method': 'post',})
}function sendMsgApi(data) {return $axios({"url": "/user/sendMsg","method": "post",data})
}

8、启动测试

前端发起页面请求,进行测试。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/8398.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

手边酒店多商户版小程序V1.0.47 全开源版 安装测试教程

手边酒店多商户小程序是基于框架应用模块应用&#xff0c;需要框架基础上使用&#xff0c;总体体验了一下功能还是强大&#xff0c;非常合适酒店及民宿类小程序搭建。小程序端支持多店版支持平台入驻。手边酒店多商户版只有模块版&#xff0c;无独立版本&#xff0c;该版本核心…

Rust vs Go:常用语法对比(十)

题图来自 Rust vs. Golang: Which One is Better?[1] 182. Quine program Output the source of the program. 输出程序的源代码 package mainimport "fmt"func main() { fmt.Printf("%s%c%s%c\n", s, 0x60, s, 0x60)}var s package mainimport "fm…

桥梁安全生命周期监测解决方案

一、方案背景 建筑安全是人们生产、经营、居住等经济生活和人身安全的基本保证&#xff0c;目前我国越来越多的建筑物逐 步接近或者已经达到了使用年限&#xff0c;使得建筑物不断出现各种安全隐患&#xff0c;对居民的人身安全和财产安全产 生不利影响&#xff0c;因此房…

Bug竞技场【已经修复】

目录 1.基础知识 2.最佳组合 2.1 铁男-螳螂 2.2 弟弟组合 海克斯抽卡bug 1.基础知识 背景&#xff1a;美测服-美服-马服-可以有效地减少bug率 复盘是为了更好的战斗&#xff01; 提前观看一些视频资料也是如此。 通过看直播博主的经验&#xff0c;可以让你关注到本来对战的…

Python 生成随机图片验证码

使用Python生成图片验证码 Python 生成随机图片验证码安装pillow包pillow包生成图片基本用法生成图片验证码 Python 生成随机图片验证码 在写一个Web项目的时候一般要写登录操作&#xff0c;而为了安全起见&#xff0c;现在的登录功能都会加上输入图片验证码这一功能&#xff…

【Spring Cloud】Spring Cloud Config 会加载哪些配置文件原理浅析

文章目录 1. Spring Cloud Config 加载哪些配置文件2. git 作为配置中心存放配置文件的一些规则3. 参考资料 本文描述了 Spring Cloud Config 会加载哪些配置文件&#xff0c;以及如何在 git 仓库中合理的安排这些配置文件的结构 1. Spring Cloud Config 加载哪些配置文件 基…

代码随想录训练营day1

问题一&#xff1a;给定一个 n 个元素有序的&#xff08;升序&#xff09;整型数组 nums 和一个目标值 target &#xff0c;写一个函数搜索 nums 中的 target&#xff0c;如果目标值存在返回下标&#xff0c;否则返回 -1。 输入 输出 nums [-1,0,3,5,9,12], target 9 4 …

【Spring系列】数据库初始化

背景 最近在配置数据库的初始化时&#xff0c;遇到 sql:init:schema-locations: classpath:db/schema.sqldata-locations: classpath:db/data.sqlusername: sapassword:网上搜索资料&#xff0c;大同小异&#xff0c;都是无用的资料&#xff0c;于是决定自己研究下数据库的初始…

结构型设计模式之组合模式【设计模式系列】

系列文章目录 C技能系列 Linux通信架构系列 C高性能优化编程系列 深入理解软件架构设计系列 高级C并发线程编程 设计模式系列 期待你的关注哦&#xff01;&#xff01;&#xff01; 现在的一切都是为将来的梦想编织翅膀&#xff0c;让梦想在现实中展翅高飞。 Now everythi…

使用POI设计一个限制导出日期为三十天,且导出文件为excel

使用POI设计一个限制导出日期为三十天&#xff0c;且导出文件为excel&#xff0c;前端使用Vue的element ui进行设计&#xff0c;前端可以通过选择时间来导出具体的表格数据&#xff0c;根据用户选择的时间进行Excel文件的数据导出。 按照需求来设计代码&#xff0c;根据element…

SkyWalking链路追踪-Agent (代理人)

基础概念&#xff1a; SkyWalking链路追踪代理&#xff08;SkyWalking Tracing Agent&#xff09;是一种用于收集和传输链路追踪数据的工具。它与应用程序一起部署&#xff0c;并通过自动或手动方式来收集关于应用程序中的请求路径和操作的信息。该代理将收集到的数据发送到Sky…

Hadoop学习日记-MapReduce思想及执行流程

MapReduce思想 Map负责“拆分”&#xff1a;即将复杂问题拆分成可以并行计算的小问题&#xff0c;彼此之间几乎没有依赖联系。 Reduce负责对Map阶段的结果进行合并汇总 Map和Reduce的抽象接口如下&#xff1a; map:(k1; v1) — (k2; v2) reduce:(k2; [v2]) — (k3; v3) 一…

看了2023年的一线互联网公司时薪排行榜!值得思考

前言 根据最近针对国内的一线互联网企业做的调研&#xff0c;汇总了他们的平均时薪水平&#xff0c;最终出了一个排行榜&#xff01; 首先我们来看下&#xff0c;排行榜分哪几个Level&#xff0c;分别为初级、中级、高级、资深、专家/架构这五个&#xff0c;主要根据工程师的…

Druid(德鲁伊)连接池

Druid(德鲁伊)连接池 阿里出品&#xff0c;淘宝和支付宝专用数据库连接池&#xff0c;但它不仅仅是一个数据库连接池&#xff0c;它还包含一个ProxyDriver&#xff08;代理驱动&#xff09;&#xff0c;一系列内置的JDBC组件库&#xff0c;一个SQL Parser(sql解析器)。支持所有…

基于Javaweb实现ATM机系统开发实战(十四)交易记录分页实现

还是老规矩&#xff0c;先看前端页面查看需要传递哪些参数&#xff0c;并且把逻辑有问题的部分进行修改~ <% page language"java" contentType"text/html; charsetUTF-8" pageEncoding"UTF-8"%> <% taglib prefix"c" uri&qu…

自然语言处理14-基于文本向量和欧氏距离相似度的文本匹配,用于找到与查询语句最相似的文本

大家好&#xff0c;我是微学AI&#xff0c;今天给大家介绍一下自然语言处理14-基于文本向量和欧氏距离相似度的文本匹配&#xff0c;用于找到与查询语句最相似的文本。NLP中的文本匹配是指通过计算文本之间的相似度来找到与查询语句最相似的文本。其中一种常用的方法是基于文本…

Docker安装Redis与Python的交互

Redis数据库&#xff0c;平常做缓存用的最多&#xff0c;之前一直没有记录过redis的安装流程&#xff0c;等到用时才想起来去百度上搜索&#xff0c;然而搜出来的东西十有八九都大同小异。所以这次用docker安装一下&#xff0c;再此记录 redis是一个key-value存储系统。和Memc…

AcrelEMS企业微电网能效管理平台实现用户侧智能配电和智能用电管理-安科瑞黄安南

摘要&#xff1a;随着科技的发展&#xff0c;电力系统正逐步向智能化、数字化、互联网化迈进。智能配电与智能用电是电力产业发展的重要方向&#xff0c;将为传统电力系统带来革命性的变革。本文将对智能配电和智能用电的概念、特点、关键技术及应用进行详细介绍。 1、智能配电…

数据结构初阶--带头双向循环链表

目录 一.带头双向循环链表的定义 二.带头双向循环链表的功能实现 2.1.带头双向循环链表的定义 2.2.带头双向循环链表的结点创建 2.3.带头双向循环链表的初始化 2.4.带头双向循环链表的打印 2.5.带头双向循环链表的判空 2.6.带头双向循环链表的尾插 2.7.带头双向循环链…

【node.js】02-path模块

目录 1. path.join() 2. path.basename() 3. path.extname() 1. path.join() 使用 path.join() 方法&#xff0c;可以把多个路径片段拼接为完整的路径字符串&#xff0c;语法格式如下&#xff1a; path.join([...paths]) 例子&#xff1a; const path require(path)co…