验证码登录开发----手机验证码登录

手机验证码登录

需求分析

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

手机验证码登录的优点:

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

登录流程:

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

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

代码开发–梳理交互过程

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

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

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

代码开发–准备工作

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

  • 实体类User

    /**
     * 用户信息
     */
    @Data
    public 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;
    }
    
    
  • Mapper接口UserMapper

  • 业务层接口UserService

  • 业务层实现类UserServiceImpl

  • 控制层UserController

    @Slf4j
    @RestController
    @RequestMapping("/user")
    public class UserController {
    
        @Autowired
        private UserService userService;
    
        /*
        * 发送手机短信验证码
        * */
        @PostMapping("/sendMsg")
        public R<String> sendMsg(@RequestBody User user, HttpSession session){
            //获取手机号
            String phone = user.getPhone();
            if(StringUtils.isNotEmpty(phone)){
                //生成随机的4位验证码
                String validateCode = ValidateCodeUtils.generateValidateCode(4).toString();
                log.info("ValidateCode:{}",validateCode);
                //调用阿里云提供的短信服务API完成发送短信
                //SMSUtils.sendMessage("申请签名","模板code",phone,validateCode);
                //需要将生成的验证码保存到Session
                session.setAttribute(phone,validateCode);
                return R.success("手机验证短信发送成功!!!");
            }
            return R.error("短信发送失败!!!");
        }
    
        /*
         * 移动端用户登录
         * */
        @PostMapping("/login")
        public R<User> login(@RequestBody Map map, HttpSession session){
            /*这里会出现一个问题前端传过来的验证码User类不能接收
            * 有两种解决方法:
            * 1.我们可以创建一个UserDto,继承User
            * 2.还有一种就是可以使用Map来接收,用键值对的接收
            * */
            log.info(map.toString());
            //首先获取手机号
            String phone = map.get("phone").toString();
            //其次获取验证码
            String code = map.get("code").toString();
            //从session中获得保存到的验证码
            Object codeSession = session.getAttribute(phone);
            //进行验证码的比对(页面提交的验证码和Session中保存的验证码比对)
            if (codeSession!=null&&codeSession.equals(code)){
                //如果能够比对成功,说明登录成功
    
                LambdaQueryWrapper<User>queryWrapper = new LambdaQueryWrapper<>();
                queryWrapper.eq(User::getPhone,phone);
                User user = userService.getOne(queryWrapper);//手机是唯一标识
                if (user==null){
                    //判断当前手机号对应的用户是否为新用户,如果是新用户就自动完成注册
                    user = new User();
                    user.setPhone(phone);
                    user.setStatus(1);//设置状态 1表示正常
                    userService.save(user);
                }
                session.setAttribute("user",user.getId());//经过过滤器会去校验,所以我们需要将用户id存储到session中去
    
                return R.success(user);
            }
            return R.error("登录失败!!!");
        }
    }
    
  • 工具类SMSUtils、ValidateCodeUtils

    • SMSUtils

      /**
       * 短信发送工具类
       */
      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

    /**
     * 随机生成验证码工具类
     */
    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);//生成随机数,最大为9999
                if(code < 1000){
                    code = code + 1000;//保证随机数为4位数字
                }
            }else if(length == 6){
                code = new Random().nextInt(999999);//生成随机数,最大为999999
                if(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;
        }
    }

代码开发–修改LoginCheckFilter

前面我们已经完成了LoginCheckFilter过滤器的开发,此过滤器用于检查用户的登录状态。我们在进行手机验证码登录时,发送的请求需要在此过滤器处理时直接放行

LoginCheckFilter过滤器的编写链接

@WebFilter(filterName = "LoginCheckFilter",urlPatterns = "/*")//利用过滤器拦截,拦截所有的请求  urlPatterns = "/*"
@Slf4j
public class LoginCheckFilter implements Filter {
    //路径匹配器,并支持通配符
    public static final AntPathMatcher PATH_MATCHER = new AntPathMatcher();

    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        HttpServletRequest request = (HttpServletRequest)servletRequest;
        HttpServletResponse response = (HttpServletResponse)servletResponse;


        //1. 获取本次请求的URI
        String requestURI = request.getRequestURI();
        //如果请求路径为"/backend/index.html"会与放行的"/backend/**"路径匹配不上,会导致无法识别,这时需要路径匹配器
        log.info("拦截到的请求:{}",requestURI);

        //1.2 定义不需要处理的请求路径,比如:登录页面,退出,静态资源,主要是拦截controller资源
        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"));

            Long employeeId =(Long) request.getSession().getAttribute("employee");
            BaseContext.setCurrentId(employeeId);//保存用户id,用于公共字段自动填充使用

            long id = Thread.currentThread().getId();
            log.info("线程id为:{}",id);
            //已经登录,直接放行
            filterChain.doFilter(request,response);//放行
            return;
        }

        //   4-2. 判断登录状态,如果已登录,则直接放行  移动端判断
        if(request.getSession().getAttribute("user")!=null){
            log.info("用户已登录,用户id为:{}",request.getSession().getAttribute("user"));

            Long userId =(Long) request.getSession().getAttribute("user");
            BaseContext.setCurrentId(userId);//保存用户id,用于公共字段自动填充使用

            long id = Thread.currentThread().getId();
            log.info("线程id为:{}",id);
            //已经登录,直接放行
            filterChain.doFilter(request,response);//放行
            return;
        }
        //5. 如果未登录则返回未登录结果,通过输出流的方式向客户端响应数据
        log.info("用户未登录");
        response.getWriter().write(JSON.toJSONString(R.error("NOTLOGIN")));

        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;
    }
}

在这里插入图片描述

  • 0
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
获取手机验证码一般需要使用短信平台提供的 API 接口,通过调用接口来发送短信验证码到用户手机上。具体实现流程如下: 1. 选择一个短信平台,比如阿里云、腾讯云、云片等,注册并获取 API 接口的 AppKey 和 AppSecret。 2. 在 Python 中使用 requests 库或者其他 HTTP 请求库,通过 API 接口发送短信验证码到用户手机上。 3. 用户收到短信验证码后,可以在客户端进行输入验证。 以下是一个使用阿里云短信平台发送短信验证码的 Python 代码示例: ```python import requests import json import hashlib import random import time # 阿里云短信平台的 API 地址和版本号 url = 'http://dysmsapi.aliyuncs.com/' version = '2017-05-25' # 阿里云短信平台的 AppKey 和 AppSecret access_key_id = 'your_access_key_id' access_key_secret = 'your_access_key_secret' # 发送短信验证码的函数 def send_sms_code(phone_number): # 生成随机的 6 位验证码 code = str(random.randint(100000, 999999)) # 构建请求参数 params = { 'Action': 'SendSms', 'Version': version, 'AccessKeyId': access_key_id, 'Timestamp': time.strftime('%Y-%m-%dT%H:%M:%SZ', time.gmtime()), 'Format': 'JSON', 'SignatureMethod': 'HMAC-SHA1', 'SignatureVersion': '1.0', 'SignatureNonce': str(random.randint(1000000000000000, 9999999999999999)), 'PhoneNumbers': phone_number, 'SignName': 'your_sign_name', 'TemplateCode': 'your_template_code', 'TemplateParam': json.dumps({'code': code}) } # 计算签名 sorted_params = sorted(params.items(), key=lambda x: x[0]) canonicalized_query_string = '&'.join(['%s=%s' % (k, v) for (k, v) in sorted_params]) string_to_sign = 'GET&%2F&' + requests.utils.quote(canonicalized_query_string, safe='') h = hashlib.sha1() h.update(access_key_secret.encode('utf-8') + b'&') h.update(string_to_sign.encode('utf-8')) signature = h.digest().hex() # 发送请求 params['Signature'] = signature response = requests.get(url, params=params) # 解析响应 result = json.loads(response.text) if result['Code'] == 'OK': return code else: return None ``` 其中,`access_key_id` 和 `access_key_secret` 是阿里云短信平台提供的 AppKey 和 AppSecret,`phone_number` 是要发送验证码手机号码,`your_sign_name` 是你在阿里云短信平台上申请的签名名称,`your_template_code` 是你在阿里云短信平台上申请的模板编号。该函数返回发送的验证码,如果发送失败则返回 None。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

陈毓辰

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值