• SpringBoot 项目实战 ~ 7. 短信验证码登录



    现在所教的知识,你们也许一生都用不到,但我还要教,因为这些知识是好的,应该让你们知道。


    在这里插入图片描述


    一、相关配置

    1. 阿里云短信服务

    短信服务是阿里云为用户提供的一种通信服务的能力,分为国内短信和国际/港澳台短信服务。本文介绍如何快速使用阿里云OpenAPI开发者门户或阿里云SDK完成常见操作,例如添加短信签名、添加短信模板、发送短信服务和查询短信发送详情等,将指定信息发送至国内或境外手机号码。

    通过API/SDK使用短信服务
    在这里插入图片描述



    2. 导入maven坐标

    编辑 reggie_tack_out/pom.xml 文件:

    ...
        <!--    项目依赖的 jar 包-->
        <dependencies>
            <!--阿里云短信服务-->
            <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>2.1.0</version>
            </dependency>
    ...
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15


    3. 引入工具类

    新建短信发送 src/main/java/com/reggie/utils/SMSUtils.java 类:

    package com.reggie.utils;
    
    import ...
    
    /**
     * 短信发送工具类
     */
    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();
    		}
    	}
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35

    新建生成验证码 src/main/java/com/reggie/utils/ValidateCodeUtils.java 类:

    package com.reggie.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);//生成随机数,最大为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;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43


    4. 前端代码

    编辑 src/main/resources/front/page/login.html 登录页:

    DOCTYPE html>
    <html lang="en">
        <head>
            <meta charset="utf-8">
            <meta http-equiv="X-UA-Compatible" content="IE=edge">
            
            <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>
            
            <link rel="stylesheet" href="../../backend/plugins/element-ui/index.css" />
            
            <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>
            
            <script src="./../js/vant.min.js">script>  
            
            <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 = true
                            // const res = await loginApi({phone:this.form.phone})
                            const res = await loginApi(this.form)
                            this.loading = false
                            if(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>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93

    编辑 src/main/resources/front/api/login.js 接口配置:

    ...
    
    function loginoutApi() {
      return $axios({
        'url': '/user/loginout',
        'method': 'post',
      })
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8




    二、结构搭建

    1. 业务需求

    在这里插入图片描述

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


    2. 实体类

    新建 src/main/java/com/reggie/entity/User 类:

    package com.reggie.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;
    /**
     * 用户信息
     */
    @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;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43


    3. Mapper

    新建 src/main/java/com/reggie/mapper/UserMapper 接口:

    package com.reggie.mapper;
    
    import com.baomidou.mybatisplus.core.mapper.BaseMapper;
    import com.reggie.entity.User;
    import org.apache.ibatis.annotations.Mapper;
    
    @Mapper
    public interface UserMapper extends BaseMapper<User> {
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9


    4. Service 接口

    新建 src/main/java/com/reggie/service/UserService 类:

    package com.reggie.service;
    
    import com.baomidou.mybatisplus.extension.service.IService;
    import com.reggie.entity.User;
    
    public interface UserService extends IService<User> {
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7


    5. Service 实现类

    新建 src/main/java/com/reggie/service/impl/UserServiceImpl 类:

    package com.reggie.service.impl;
    
    import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
    import com.reggie.entity.User;
    import com.reggie.mapper.UserMapper;
    import com.reggie.service.UserService;
    import org.springframework.stereotype.Service;
    
    @Service
    public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements UserService {
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11




    三、功能实现

    1. LoginCheckFilter过滤器

    编辑 src/main/java/com/reggie/filter/LoginCheckFilter.java 文件:

            ...
            
            // 定义不需要处理的请求路径
            String[] urls = new String[] {
                    "/employee/login",
                    "/employee/logout",
                    "/backend/**",
                    "/front/**",
                    "/common/**",
                    "/user/sendMsg", // 移动端发送短信
                    "/user/login", // 移动端登录
            };
            
            ...
    
            // 4_2. 判断 移动端用户 登录状态,如果已登录,则直接放行
            if(request.getSession().getAttribute("user") != null) {
                log.info("用户已登录,用户 id 为:{}", request.getSession().getAttribute("user"));
    
                // 存储用户 id 至线程
                Long userId = (Long) request.getSession().getAttribute("user");
                BaseContext.setCurrentId(userId);
    
                filterChain.doFilter(request, response);
                return;
            }
            ...
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27


    2. 发送短信验证码

    新建 src/main/java/com/reggie/controller/UserController.java 文件:

    package com.reggie.controller;
    
    import ...
    
    @RestController
    @RequestMapping("/user")
    @Slf4j
    public class UserController {
    
        @Autowired
        private UserService userService;
    
        /**
         * 发送手机短信验证码
         * @param user
         * @return
         */
        @PostMapping("/sendMsg")
        public R<String> sendMsg(@RequestBody User user, HttpSession session) {
    
            // 获取用户手机号
            String phone = user.getPhone();
            if(StringUtils.isNotEmpty(phone)) {
    
                // 生成随机四位数验证码
                String code = ValidateCodeUtils.generateValidateCode(4).toString();
    
                // 打印生成的四位数验证码
                log.info("验证码:{}", code);
    
                // 调用阿里云提供的短信服务 API 发送短信验证码
                // (因为这里不方便调用阿里云接口,所以先注释掉,根据控制台打印来查看生成的验证码)
                // SMSUtils.sendMessage("瑞吉外卖", "", phone, code);
    
                // 需要将生成的验证码保存到 session
                session.setAttribute(phone, code);
    
                return R.success("手机验证码短信发送成功");
            }
            return R.error("短信发送失败");
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42


    3. 登录

    新建 src/main/java/com/reggie/controller/UserController.java 文件:

        ...
    
        /**
         * 移动端用户登录
         * @param map
         * @return
         */
        @PostMapping("/login")
        public R<User> login(@RequestBody Map map, HttpSession session) {
            log.info("map:{}", map.toString());
    
            // 获取手机号
            String phone = map.get("phone").toString();
    
            // 获取验证码
            String code = map.get("code").toString();
    
            // 获取 Session 中保存的短信验证码
            Object codeInSession = session.getAttribute(phone);
    
            // 进行验证码比对
            if(codeInSession != null && codeInSession.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);
                    userService.save(user);
                }
                session.setAttribute("user", user.getId());
                return R.success(user);
            }
            return R.error("登录失败");
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
  • 相关阅读:
    华为ensp搭建VRRP主备份配置
    1153:绝对素数
    MogaFX—尼日利亚未能实现其金融包容性目标
    服务器时间同步架构与实现chrony
    java计算机毕业设计课题申报系统MyBatis+系统+LW文档+源码+调试部署
    电动汽车变频器
    4.中台领域建模
    keras fit_generator 增加并行度问题 use_multiprocessing
    CMMI软件能力成熟度认证指南来了
    测试小牛,全新出发!!
  • 原文地址:https://blog.csdn.net/weixin_45137565/article/details/126482143