P18-手机验证码登录开发
1.短信发送1.1 短信发送服务介绍1.2阿里云短信服务1.3代码开发1.3.1导入maven依赖1.3.2发送短信代码2.手机验证码登录2.1需求分析2.2数据模型3.手机验证码登录|短信发送代码开发3.1前后端交互流程分析3.2前端代码3.2.1页面代码-ront/page/login.html3.2.2js代码3.3后端代码-准备3.3.1实体类User3.3.2Mapper接口UserMapper3.3.3业务接口UserService3.3.4业务实体类UserServiceImpl3.3.5控制层UserController3.3.6工具类3.4后端代码-正式开发3.4.1登录过滤器改造-添加移动端登录状态检测3.4.2输入手机号,发送短信验证码代码实现3.2.5获取到验证码后,点击登录按钮操作代码实现3.2.6登录成功页面4.如果出现错误:ReferenceError: sendMsgApi is not defined1.短信发送
1.1 短信发送服务介绍
目前市面上有很多第三方提供的短信服务,这些第三方短信服务会和各个运营商(移动、联通、电信)对接,我们只需要注册成为会员并且按照提供的开发文档进行调用就可以发送短信。需要说明的是,这些短信服务一般都是收费服务。
常用短信服务:
阿里云华为云腾讯云京东梦网乐信
1.2阿里云短信服务
阿里云短信服务(Short Message Service)是广大企业客户快速触达手机用户所优选使用的通信能力。
调用API或用群发助手,即可发送验证码、通知类和营销类短信;国内验证短信秒级触达,到达率最高可达99%;国际/港澳台短信覆盖200多个国家和地区,安全稳定,广受出海企业选用。
应用场景:
验证码短信通知推广短信
设置 签名和模板
添加用户获取AccessKey和密码,只授权短信服务,在后续的程序中使用
1.3代码开发
1.3.1导入maven依赖
<dependency><groupId>com.aliyun</groupId><artifactId>aliyun-java-sdk-core</artifactId><version>4.5.16</version></dependency><dependency><groupId>com.aliyun</groupId><artifactId>aliyun-java-sdk-dysmsapi</artifactId><version>1.1.0</version></dependency>
1.3.2发送短信代码
package com.jq.utils;import com.aliyuncs.DefaultAcsClient;import com.aliyuncs.IAcsClient;import com.aliyuncs.dysmsapi.model.v0525.SendSmsRequest;import com.aliyuncs.dysmsapi.model.v0525.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();}}}
2.手机验证码登录
2.1需求分析
为了方便用户登录,移动端通常都会提供通过手机验证码登录的功能。手机验证码登录的优点:
方便快捷,无需注册,直接登录使用短信验证码作为登录凭证,无需记忆密码安全
登录流程:
输入手机号>获取验证码>输入验证码>点击登录>登录成功
注意:通过手机验证码登录,手机号是区分不同用户的标识。
2.2数据模型
涉及到user表
3.手机验证码登录|短信发送代码开发
3.1前后端交互流程分析
在登录页面(ront/page/login.html)输入手机号,点击[获取验证码]按钮,页面发送ajax请求,在服务端调用短信服务API给指定手机号发送验证码短信在登录页面输入验证码,点击[登录1按钮,发送ajax请求,在服务端处理登录请求开发手机验证码登录功能,其实就是在服务端编写代码去处理前端页面发送的这2次请求即可。
3.2前端代码
3.2.1页面代码-ront/page/login.html
<!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 = falsethis.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({phone:this.form.phone})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>
3.2.2js代码
发送验证码的Ajax请求function sendMsgApi(data){return $axios({'url':'/user/sendMsg','method':'post'})}
登录的Ajax请求
function loginApi(data) {return $axios({'url': '/user/login','method': 'post',data})}
退出的Ajax请求
function loginoutApi() {return $axios({'url': '/user/loginout','method': 'post',})}
3.3后端代码-准备
3.3.1实体类User
package com.jq.entity;import lombok.Data;import java.time.LocalDateTime;import java.util.Date;import java.util.List;import java.io.Serializable;import com.baomidou.mybatisplus.annotation.IdType;import com.baomidou.mybatisplus.annotation.TableId;/*** 用户信息*/@Datapublic class User implements Serializable {private static final long serialVersionUID = 1L;private Long id;//姓名private String name;//手机号private String phone;//性别 0 女 1 男private String sex;//身份证号private String idNumber;//头像private String avatar;//状态 0:禁用,1:正常private Integer status;}
3.3.2Mapper接口UserMapper
package com.jq.mapper;import com.baomidou.mybatisplus.core.mapper.BaseMapper;import com.jq.entity.User;import org.apache.ibatis.annotations.Mapper;@Mapperpublic interface UserMapper extends BaseMapper<User> {}
3.3.3业务接口UserService
package com.jq.service;import com.baomidou.mybatisplus.extension.service.IService;import com.jq.entity.User;public interface UserService extends IService<User> {}
3.3.4业务实体类UserServiceImpl
package com.jq.service.impl;import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;import com.jq.entity.User;import com.jq.mapper.UserMapper;import com.jq.service.UserService;import lombok.extern.slf4j.Slf4j;import org.springframework.stereotype.Service;@Slf4j@Servicepublic class UserServiceImpl extends ServiceImpl<UserMapper, User> implements UserService {}
3.3.5控制层UserController
package com.jq.controller;import com.jq.service.UserService;import lombok.extern.slf4j.Slf4j;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.web.bind.annotation.RequestMapping;import org.springframework.web.bind.annotation.RestController;/*** 用户管理*/@Slf4j@RestController@RequestMapping("/user")public class UserController {@Autowiredprivate UserService userService;}
3.3.6工具类
package com.jq.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;}}
3.4后端代码-正式开发
3.4.1登录过滤器改造-添加移动端登录状态检测
package com.jq.filter;import com.alibaba.fastjson.JSON;import mom.BaseContext;import mom.R;import lombok.extern.slf4j.Slf4j;import org.springframework.util.AntPathMatcher;import javax.servlet.*;import javax.servlet.annotation.WebFilter;import javax.servlet.http.HttpServlet;import javax.servlet.http.HttpServletRequest;import javax.servlet.http.HttpServletResponse;import java.io.IOException;/*** 自定义过滤器* 检查用户是否已经完成登录*/@WebFilter(filterName = "loginCheckFilter",urlPatterns = "/*")@Slf4jpublic 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、获取本次请求的URI* 2、判断本次请求是否需要处理* 3、如果不需要处理,则直接放行* 4、判断登录状态,如果已登录,则直接放行* 5、如果未登录则返回未登录结果*///1、获取本次请求的URIString requestURI =request.getRequestURI();// /backend/index.htmllog.info("拦截到请求:{}",requestURI);//定义不需要处理的请求路径String []urls=new String[]{"/employee/login","/employee/logout","/backend/**","/front/**","/common/**","/user/sendMsg","/user/login"};//2、判断本次请求是否需要处理boolean check = check(urls, requestURI);//3、如果不需要处理,则直接放行if (check){log.info("本次请求{}不需要处理",requestURI);filterChain.doFilter(request,response);return;}//4-1、后台管理系统:判断登录状态,如果已登录,则直接放行if(request.getSession().getAttribute("employee")!=null){log.info("用户已登录,用户id为:{}",request.getSession().getAttribute("employee"));//获取用户idLong empId=(Long) request.getSession().getAttribute("employee");//调用BaseContext来设置当前用户登录的idBaseContext.setCurrentId(empId);filterChain.doFilter(request,response);return;}//4-2、前端移动端用户系统:判断登录状态,如果已登录,则直接放行if(request.getSession().getAttribute("user")!=null){log.info("用户已登录,用户id为:{}",request.getSession().getAttribute("user"));//获取用户idLong userId=(Long) request.getSession().getAttribute("user");//调用BaseContext来设置当前用户登录的idBaseContext.setCurrentId(userId);filterChain.doFilter(request,response);return;}log.info("用户未登录");//5、如果未登录则返回未登录结果,通过输出流的方式向客户端页面响应数据response.getWriter().write(JSON.toJSONString(R.error("NOTLOGIN")));return;}/*** 路径匹配,检查本次请求是否需要放行* @param urls* @param requestURI* @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;}}
3.4.2输入手机号,发送短信验证码代码实现
package com.jq.controller;import mom.R;import com.jq.entity.User;import com.jq.service.UserService;import com.jq.utils.SMSUtils;import com.jq.utils.ValidateCodeUtils;import lombok.extern.slf4j.Slf4j;import mons.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;/*** 用户管理*/@Slf4j@RestController@RequestMapping("/user")public class UserController {@Autowiredprivate UserService userService;/*** 发送手机短信验证码* @param user* @return*/@PostMapping("/sendMsg")public R<String>sendMsg(@RequestBody User user, HttpSession session){//1.获取手机号String phone = user.getPhone();if (StringUtils.isNotEmpty(phone)){//2.生成随机的4位验证码String code = ValidateCodeUtils.generateValidateCode(4).toString();log.info("code={}",code);//3.调用阿里云提供的短信服务API完成发送短信//SMSUtils.sendMessage("天天外卖","",phone,code);//4.将生成的验证码保存到Session,用于后续的登录session.setAttribute(phone,code);return R.success("手机验证码发送成功");}return R.error("短信发送失败");}}
3.2.5获取到验证码后,点击登录按钮操作代码实现
需要接收的数据如下
/*** 移动端用户登录* @param map* @param session* @return*/@PostMapping("/login")public R<User>login(@RequestBody Map map, HttpSession session){log.info(map.toString());//1.获取手机号String phone = map.get("phone").toString();//2.获取验证码String code = map.get("code").toString();//3.从session中获取保存的验证码Object codeInSession = session.getAttribute(phone);//4.页面提交的和session的验证码进行比对if(codeInSession!=null &&codeInSession.equals(code)){//5.如果能够比对成功,说明登录成功LambdaQueryWrapper<User>queryWrapper=new LambdaQueryWrapper<>();queryWrapper.eq(User::getPhone,phone);User user = userService.getOne(queryWrapper);//6.判断当前手机号对应的用户是否为新用户,如果是新用户就自动完成注册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("登录失败");}
3.2.6登录成功页面
4.如果出现错误:ReferenceError: sendMsgApi is not defined
请参照文章Error-瑞吉外卖