【java】【项目实战】[外卖七]短信验证码、手机短信登录开发

news2024/12/28 19:57:25

目录

一、发送短信

1 短信服务介绍

2 阿里云短信服务(个人现在不太好申请了)

2.1 介绍

 2.2 注册账号

2.3 设置短信签名

2.4 设置短信模版

 2.5 设置AccessKey

3 代码开发

3.1 导包

 3.2 短信发送工具类SMSUtils 

二、手机验证码登录

1 需求分析

2 数据模型

3 代码开发

3.1 实体类User

 3.2 Mapper接口UserMapper

3.3 业务层接口 UserService

3.4 业务层实现类 UserServiceImpl

3.5 控制层 UserController

3.6 工具类ValidateCodeUtils  

3.7 修改LoginCheckFilter

3.8 控制层 UserController 代码实现

3.9 login.html

4 功能测试


前言:发送短信和手机验证码登录

一、发送短信

1 短信服务介绍

2 阿里云短信服务(个人现在不太好申请了)

2.1 介绍

 2.2 注册账号

2.3 设置短信签名

2.4 设置短信模版

 2.5 设置AccessKey

3 代码开发

3.1 导包

    <!--阿里云短信服务-->
        <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>

 3.2 短信发送工具类SMSUtils 

package com.runa.reggie.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();
		}
	}

}

二、手机验证码登录

1 需求分析

2 数据模型

3 代码开发

 

3.1 实体类User

package com.runa.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;
}

 3.2 Mapper接口UserMapper

package com.runa.reggie.mapper;

import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.runa.reggie.entity.User;
import org.apache.ibatis.annotations.Mapper;

@Mapper
public interface UserMapper extends BaseMapper<User>{
}

3.3 业务层接口 UserService

package com.runa.reggie.service;

import com.baomidou.mybatisplus.extension.service.IService;
import com.runa.reggie.entity.Employee;
import com.runa.reggie.entity.User;

public interface UserService extends IService<User> {
}

3.4 业务层实现类 UserServiceImpl

package com.runa.reggie.service.impl;

import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.runa.reggie.entity.User;
import com.runa.reggie.mapper.UserMapper;
import com.runa.reggie.service.UserService;
import org.springframework.stereotype.Service;

@Service
public class UserServiceImpl extends ServiceImpl<UserMapper,User> implements UserService{
}

3.5 控制层 UserController

package com.runa.reggie.controller;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.runa.reggie.common.R;
import com.runa.reggie.entity.User;
import com.runa.reggie.service.UserService;
import com.runa.reggie.utils.SMSUtils;
import com.runa.reggie.utils.ValidateCodeUtils;
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
@RequestMapping("/user")
@Slf4j
public class UserController {

    @Autowired
    private UserService userService;

   

}

3.6 工具类ValidateCodeUtils  

package com.runa.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;
    }
}

3.7 修改LoginCheckFilter

 

package com.runa.reggie.filter;

import com.alibaba.fastjson.JSON;
import com.runa.reggie.common.BaseContext;
import com.runa.reggie.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;

@Slf4j
@WebFilter(filterName = "loginCheckFilter", urlPatterns = "/*")
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
        //        request.getRequestURL()返回的是全路径。
        //        request.getRequestURI()返回的是除去协议,域名(IP+端口号)之后的路由部分。
        String requestURI = request.getRequestURI();
        log.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("用户已登录,请求url为:【{}】, 用户id为:【 {} 】 ",requestURI,request.getSession().getAttribute("employee"));
            // 获取ID 塞进线程
            Long empId = (Long) request.getSession().getAttribute("employee");
            BaseContext.setCurrentId(empId);

            filterChain.doFilter(request,response);
            return;
        }

        // 4 -2(移动端) 判断登录状态,如果已登录,则直接放行
        if(request.getSession().getAttribute("user") != null){
            log.info("用户已登录,请求url为:【{}】, 用户id为:【 {} 】 ",requestURI,request.getSession().getAttribute("user"));
            // 获取ID 塞进线程
            Long userId = (Long) request.getSession().getAttribute("user");
            BaseContext.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;
            }
        }
        // 循环都匹配不上就返回false
        return false;
    }
}

 登录手机页面

http://localhost:8080/front/page/login.html

3.8 控制层 UserController 代码实现

package com.runa.reggie.controller;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.runa.reggie.common.R;
import com.runa.reggie.entity.User;
import com.runa.reggie.service.UserService;
import com.runa.reggie.utils.SMSUtils;
import com.runa.reggie.utils.ValidateCodeUtils;
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
@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)){
            //生成随机的4位验证码
            String code = ValidateCodeUtils.generateValidateCode(4).toString();
            log.info("code={}",code);

            //调用阿里云提供的短信服务API完成发送短信
            //SMSUtils.sendMessage("菠菜","",phone,code);


            //需要将生成的验证码保存到Session
            session.setAttribute(phone,code);

            return R.success("手机验证码短信发送成功");
        }

        return R.error("短信发送失败");
    }

    /**
     * 移动端用户登录
     * @param map
     * @param session
     * @return
     */
    @PostMapping("/login")
    public R<User> login(@RequestBody Map map, HttpSession session){
        log.info(map.toString());

        //获取手机号
        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> 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("登录失败");
    }

}

3.9 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 = 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(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>

4 功能测试

​
localhost:8080/front/page/login.html

​

 输入手机号码获取验证码到控制台看

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

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

相关文章

C++自创题目——第一期

一、题目描述&#xff1a; 在一段时间内&#xff0c;到达港口的船有n艘&#xff0c;其中每艘船的信息包括:到达时间t(表示第t秒)&#xff0c;船上乘客数k&#xff0c;以及k名乘客的国籍。输出前3600s内每艘船上国籍种数&#xff0c;并输出国籍种数最少的船只的到达时间。 二、…

ThreeJS模型加载动画——从下向上加载

从下向上动态加载模型,模型本身是由点-线-面组成,setDynamic动态控制模型每个point的位置,让模型出现一个动态加载的效果,如下图: 1、首先将模型压扁,获取point的position位置,然后设置y轴的高度为0,并将原始高度记录到modelPositons用于后面还原高度。 //将物…

华为鲲鹏服务器

1.简介 鲲鹏通用计算平台提供基于鲲鹏处理器的TaiShan服务器、鲲鹏主板及开发套件。硬件厂商可以基于鲲鹏主板发展自有品牌的产品和解决方案&#xff1b;软件厂商基于openEuler开源OS以及配套的数据库、中间件等平台软件发展应用软件和服务&#xff1b;鲲鹏开发套件可帮助开发…

前端刷题-深浅拷贝

深拷贝 function deepClone(obj) {if (obj null || typeof obj ! "object") {return obj;}if (obj instanceof Date) {return new Date(obj);}if (obj instanceof Array) {const cloneArray [];for (let i 0; i < obj.length; i) {cloneArray[i] deepClone(o…

Android修改默认gradle路径

Android Studio每次新建项目&#xff0c;都会默认在C盘生成并下载gradle相关文件&#xff0c;由于C盘空间有限&#xff0c;没多久C盘就飘红了&#xff0c;于是就需要把gradle相关文件转移到其他盘 1、到C盘找到gradle文件 具体路径一般是&#xff1a;C:\Users\用户\ .gradle …

Python+PIL+qrcode实现二维码自由—普通二维码+彩色二维码+logo二维码+动态二维码(附完整代码)

有时候我们需要自己制作一个二维码&#xff0c;然后进行打印下来&#xff0c;或者说在二维码中提前写上一段话比如搞笑的话&#xff0c;然后印在衣服上&#xff0c;然后穿出去玩&#xff01;的&#x1f923; 那么今天我们分享一下制作二维码的几种方式&#xff1a; 哎&#x…

Linux服务器安装部署MongoDB数据库 – 【无公网IP远程连接】

文章目录 前言1.配置Mongodb源2.安装MongoDB数据库3.局域网连接测试4.安装cpolar内网穿透5.配置公网访问地址6.公网远程连接7.固定连接公网地址8.使用固定公网地址连接 前言 MongoDB是一个基于分布式文件存储的数据库。由 C 语言编写&#xff0c;旨在为 WEB 应用提供可扩展的高…

word表格左侧边线明明有,但却不显示

如题&#xff0c;解决方法&#xff1a; 方案一&#xff1a; 1&#xff09;选中表格 2&#xff09;布局菜单--->自动调整 3&#xff09;自动调整中&#xff0c;选择“根据窗口自动调整表格” 4&#xff09;表格左侧边线就显示出来了。 方案二&#xff1a;把表格粘贴到新…

macOS上制作arm64的jdk17镜像

公司之前一直用的openjdk17的镜像&#xff0c;docker官网可以直接下载&#xff0c;但是最近对接的一个项目&#xff0c;对方用的是jdk17&#xff0c;在对接的时候有加解密异常的问题&#xff0c;为了排查是不是jdk版本的问题&#xff0c;需要制作jdk17的镜像。docker官网上的第…

【AI】《动手学-深度学习-PyTorch版》笔记(二十一):目标检测

AI学习目录汇总 1、简述 通过前面的学习,已经了解了图像分类模型的原理及实现。图像分类是假定图像中只有一个目标,算法上是对整个图像做的分类。 下面我们来学习“目标检测”,即从一张图像中找出需要的目标,并标记出位置。 2、边界框 边界框:bounding box,就是一个方…

走进Linux系统

1.开机关机和基本目录介绍 /www 目录:存放服务器网站相关资源&#xff0c;环境&#xff0c;网站的项目

【GO】LGTM_Grafana_Tempo(2)_官方用例改后实操

最近在尝试用 LGTM 来实现 Go 微服务的可观测性&#xff0c;就顺便整理一下文档。 Tempo 会分为 4 篇文章&#xff1a; Tempo 的架构官网测试实操跑通gin 框架发送 trace 数据到 tempogo-zero 微服务框架使用发送数据到 tempo 根据官方文档实操跑起来 tempo&#xff0c;中间根…

数据分析师职业发展道路,工作内容是什么?

很多同学问&#xff0c;参加数据分析就业班后之的就业发展道路是怎样的&#xff0c;工作又能做什么呢&#xff1f; 市面上的常见的工作类型有有运营类、技术类及分析类等&#xff0c;可以根据自己的意愿去做适合自己的工作&#xff0c;但是任何工作其实都是需要一技之长。…

对 K8s Pod 安全有多少认识?

写在前面 简单整理&#xff0c;博文内容涉及&#xff1a; PSP 的由来PSA 的发展PSA 使用认知 不涉及使用&#xff0c;用于了解 Pod 安全 API 资源理解不足小伙伴帮忙指正 对每个人而言&#xff0c;真正的职责只有一个&#xff1a;找到自我。然后在心中坚守其一生&#xff0c;全…

File类/IO流介绍

一. File 概要&#xff1a;实现将数据从内存中存储到计算机硬盘中&#xff0c;存储方式以File的形式进行存储 File是Java.io包下的类&#xff0c;File类对象既可表示文件&#xff0c;又可表示文件夹 File类只能对文件本身进行操作&#xff0c;不能读写文件里存储的数据&#x…

Spring-SpringBoot-SpringMVC-MyBatis常见面试题

文章目录 Spring篇springbean是安全的的?什么是AOP你们工作中有用过AOP吗spring中的事务是如何实现的spring中事务失效场景Spring的生命周期spring中的循坏依赖springMVC的执行流程springboot的启动原理常用注解MyBatis执行流程Mybatis是否支持延迟加载&#xff1f;Mybatis的一…

Android studio实现圆形进度条

参考博客 效果图 MainActivity import androidx.appcompat.app.AppCompatActivity; import android.graphics.Color; import android.os.Bundle; import android.widget.TextView;import java.util.Timer; import java.util.TimerTask;public class MainActivity extends App…

【记录】Truenas scale|NFSv4数据集的子目录或文件的ACL完全访问权限继承老是继承不了怎么回事

我遇到了数据集下新建文件夹或文件&#xff0c;新建的文件夹或文件没有和数据集的ACL设置相符合的情况。其根本原因是NFSv4的完全访问权限要想继承的话&#xff0c;它的访问设置权限要设置“用户”和“组”的&#xff0c;就是&#xff0c;一定要选择中文的那个设置。纯owner和g…

计算机竞赛 基于机器视觉的行人口罩佩戴检测

简介 2020新冠爆发以来&#xff0c;疫情牵动着全国人民的心&#xff0c;一线医护工作者在最前线抗击疫情的同时&#xff0c;我们也可以看到很多科技行业和人工智能领域的从业者&#xff0c;也在贡献着他们的力量。近些天来&#xff0c;旷视、商汤、海康、百度都多家科技公司研…