验证码登录如何实现?

article/2025/10/13 3:32:27

手机验证码登录

  • 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://chatgpt.dhexx.cn/article/AGHKmsfK.shtml

相关文章

登录验证

登录验证理解&#xff1a;指的是进入一个页面或者是系统之前检验用户是否有权限进入 登录验证的步骤&#xff1a; ① 获取页面用户输入的数据&#xff0c;然后通过提交传递到控制器 ② 在控制器中对页面传过来的数据进行验证&#xff0c;检查数据数据是否为空&#xff0c;密码…

网页登录时验证码功能的实现

网页登录时验证码功能的实现 在我们日常上网时&#xff0c;经常会遇到要登录的界面&#xff0c;我们会发现他会让你输入账号&#xff0c;密码外&#xff0c;还需要输入随机生成的验证码。 作用&#xff1a; 不少网站为了防止用户利用机器人自动注册、登录、灌水&#xff0c;都…

登录验证码(前后端分离、不分离)

1.简介  Java图形验证码&#xff0c;支持gif、中文、算术等类型&#xff0c;可用于Java Web、JavaSE等项目。 2.效果展示 3.导入项目 3.1.gradle方式的引入 dependencies {compile com.github.whvcse:easy-captcha:1.6.2 }3.2.maven方式引入 <dependencies><dep…

JavaScript入门一(JS基础知识)

文章目录 一、JavaScript是什么二、网页组成三、什么是JS引擎四、JavaScript特点五、JavaScript的组成六、JavaScript的引用方式1、行内嵌入式2、页内嵌入式3、外部式&#xff08;外链式&#xff09; 七、变量1、什么是变量2、变量的命名规则3、变量的定义方式4、变量的使用 八…

JS逆向需要掌握的JS基础知识与前端知识

本文将会把我平时在JS逆向中遇到的一些JS基础知识和遇到的问题写在这里。&#xff08;比较杂&#xff09; JS逆向需要掌握JS基础知识 nodejs和v8引擎的关系NodeJS环境和浏览器环境执行JS代码区别 webpackJS逆向中常见的window.webpackJsonp分析 JS基础语法声明时用"var&qu…

Vue.js基础知识点总结

Vue基础总结 邂逅Vuejs 1.认识Vuejs Vue是一个渐进式框架, 什么是渐进式的呢? 声明式渲染→组件系统→客户端路由→集中式状态管理→项目构建渐进式意味着你可以将Vue作为你应用的一部分嵌入其中&#xff0c;带来更丰富的交互体验。 Vue有很多特点和Web开发中常见的高级功能…

JavaScript基础知识总结笔记

一、js的两种引入方式 1.<script type"text/javascript"> 自己编写的js代码 </script> 将上面的代码放在<head></head>或者<body></body>之间 2.直接保存为js文件&#xff0c;然后外部调用<script type"text/java…

JavaScript 基础知识总结(一)

这是近期学习JavaScript基础知识的学习笔记 目前在学习Web API&#xff0c;学习途中有时间的话也会发一发自己的心得吧 一.Java Script简介 JS分为三部分&#xff1a; 而学习的java script基本语法属于ECMAScript 二.书写位置 与CSS相同&#xff0c;JS的书写位置也有如下…

JavaScript基础知识总结(1)

hello小伙伴们&#xff0c;本期来更新一下JavaScript基础知识&#xff0c;当做对JS的复习。 之前更新的有CSS复习和HTML复习&#xff0c;在这里放上链接 前端大厂面试笔记&#xff08;二&#xff09;&#xff08;持续更~~&#xff09;_Ss、、帅海的博客-CSDN博客 正文开始 1…

JS 基础知识

JS 基础知识 JS简介 JavaScript是一种基于对象和事件驱动并具有安全性能的解释型脚本&#xff0c;在Web应用中得到了非常广泛的应用。它不需要编译&#xff0c;而是直接嵌入在HTTP页面中&#xff0c;把静态页面转变成支持用户交互并响应应用的动态页面。在JavaWeb程序中&#x…

网页游戏开发基础——JavaScript基础知识

对于初学编程的朋友来说&#xff0c;这篇文章有点长&#xff0c;而且会有点难懂。但是请不要放弃&#xff0c;我尽量以通俗的语言解释相关的编程概念&#xff0c;这里只讲解编写一个游戏需要的相关编程概念&#xff08;如需要会在后面的文章中随时补充相关概念&#xff09;&…

js基础知识

1、JS的组成 JS由 ECMAscript BOM DOM组成 ECMAscript是JS基础规范、定义了JS基础语法 BOM浏览器对象模型 DOM文档对象模型 2、JS数据类型 基本数据类型&#xff1a;string number boolean undefined null symbol biginit 引用数据类型&#xff1a;object function ar…

Node.js基础知识

目录 1、为什么浏览器和Node.js都可以运行JavaScript 2、浏览器中运行JavaScript和Node.js中运行JavaScript有什么区别 3、为什么在浏览器中JavaScript不能控制系统级别的API 4、Node.js能做什么 5、全局对象-Node.js和浏览器 6、模块系统 7、Node.js是如何实现模块的&…

JavaScript的基础知识

1.JavaScript简介 以下注解可作为拓展材料&#xff1a; 1、脚本语言又被称为扩建的语言&#xff0c;或者动态语言&#xff0c;是一种编程语言&#xff0c;用来控制软件应用程序&#xff0c;脚本通常以文本&#xff08;如ASCII)保存&#xff0c;只在被调用时进行解释或编译。 …

Javascript 基础知识学习

Javascript 基础知识学习 参考自&#xff1a;https://www.w3cschool.cn/javascript/ javascript 简介 JavaScript 是互联网上最流行的脚本语言&#xff0c;这门语言可用于 HTML 和 web&#xff0c;更可广泛用于服务器、PC、笔记本电脑、平板电脑和智能手机等设备。 JavaScri…

JavaScript基础知识总结

一、基本语法&#xff08;数量&#xff0c;数据类型和运算符&#xff09; 1.变量&#xff1a;指的是在数据中心保存数据的容器 变量是计算机内存中存储数据的标识符&#xff0c;根据变量名称可以获取到内存中存储的数据 也就是说&#xff0c;我们向内存中存储了一个数据&…

JS基础知识总结 (一)

一、JS简介 JavaScript是一种运行在客户端的脚本语言&#xff0c;最早是在HTML&#xff08;标准通用标记语言下的一个应用&#xff09;网页上使用&#xff0c;用来给HTML网页增加动态功能。 浏览器就是一种运行JavaScript脚本语言的客户端&#xff0c;JavaScript的解释器被称为…

最新Javascript 基础知识全总结(持续更新)

目录 一,JavaScript 是什么 1, JavaScript 是什么 2, 作用 3, JavaScript的组成 二, JavaScript 书写位置 1,内部 JavaScript 2, 外部 JavaScript 3, 内联 JavaScript 三, JavaScript 的注释 1, 单行注释 2, 多行注释 四, JavaScript的结束符 五, 输入和输出语法 …

JS入门基础知识

一、JS是什么 1、JS概述 JavaScript是一个轻量级的语句&#xff0c;他是单线程的语言&#xff08;一个线程解析&#xff09;。他是一个弱语言&#xff08;他没有固定 的类型划分 你给定的值是什么类型 他就是什么类型&#xff09;他还是一个脚本语言&#xff08;侵入 实现xss攻…

2020年4月中国编程语言排行榜

本文已过时 都7月了&#xff0c;你该看7月的数据去了&#xff1a; 2020年7月中国编程语言排行榜 2020年7月程序员工资统计&#xff0c;平均14357元&#xff0c;又跌了&#xff0c;扎心 编程语言比例 排名编程语言平均工资工资中位数最低工资最高工资人头人头百分比1rust2…