springboot项目:瑞吉外卖 前后端 代码、思路 详细分析 part5

part1
part2
part3
part4
part5 本页

6 移动端短信发送和手机验证码登入

6.1 短信发送
6.2 手机验证码登入

6.1 短信发送

6.1.1整体分析

在这里插入图片描述
2.
在这里插入图片描述
3. 注册登入阿里云账户。找到短信服务,设置短信签名(上面图片的阿里云、菜鸟裹裹、天猫…),模板等等
在这里插入图片描述
4. 设置AccessKey
5. 看帮助文档,导入对应的Util包

6.2 手机验证码登入

6.2.1 整体思路整理

  1. 需求分析在这里插入图片描述
  2. 涉及表的操作(数据模型)
    在这里插入图片描述
  3. 代码开发思路
    在这里插入图片描述
    想要判断当前手机号是否在表中,如果不在,说明是新用户,将改号码保存
  4. 使用浏览器的手机模式查看H5的页面
    在这里插入图片描述
  5. 拦截器改造,现在加入移动端的登入(session中是否有user,user是成功登入后设置的),设置threadLocal等信息。
  6. 后端思路:短信发送的controller处理完之后,把发送的验证码存到session中。之后是登入的controller,把前端提交的短信验证码和session中的验证码进行比较,如果手机号和验证码都没错,那就放行(设置一个User的session,拦截器就会放行),同时查数据库,看看有没有对应的手机号,没有的话就插入这条手机号的数据

6.2.2 前端代码分析

  1. login.html显示到页面上的代码如下:
<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>
  1. 获取验证码,注意发送请求的参数是sendMsgApi({phone:this.form.phone}),json的格式,后端使用注解@RequestParam接收,可以直接接收到一个对象中,属性含有phone字段
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
        //sendMsgApi({phone:this.form.phone}) 一个json,key是phone,value是前端输入的手机号
        this.form.code = (Math.random()*1000000).toFixed(0)
    }else{
    
    
        this.msgFlag = true
    }
},

这里的msgFlag作用是对应之前的:<div class="divMsg" v-if="msgFlag">手机号输入不正确,请重新输入</div>,即已经通过了验证,不需要弹出提示信息
正常情况下,通过sendMsgApi发送axios请求,请求到后端发送验证码

function sendMsgApi(data) {
    
    
    return $axios({
    
    
        'url': '/user/sendMsg',
        'method': 'post',
        data
    })
}
  1. 登入模块,前端只需要给后端传loginApi(this.form),这个form:{phone:‘’“,code:‘’” } 是一个json,需要把phone和前端的code都传给后端,后端之前设置了session:session.setAttribute(phone,code); 进行一个验证就好了。
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:'请输入手机号码'});
    }
}

6.2.3 后端代码分析

整体思路:短信发送的controller处理完之后,把发送的验证码存到session中。之后是登入的controller,把前端提交的短信验证码和session中的验证码进行比较,如果手机号和验证码都没错,那就放行(设置一个User的session,拦截器就会放行),同时查数据库,看看有没有对应的手机号,没有的话就插入这条手机号的数据

  1. 发送短信controller: 前端发送请求的参数是sendMsgApi({phone:this.form.phone}),json的格式,后端使用注解@RequestParam接收,可以直接接收到一个对象中,属性含有phone字段
@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("短信发送失败");
}
  1. 注意这里不能再使用User接收,主要是User中没有code验证码属性,解决方法一是使用Dto继承User,方法二是使用map接收。这里用方法二,因为code验证码只需要和session做验证就好了,没有存到数据库里的需求。
/**
     * 登入验证
     * @param map 前端传过来电话和验证码,User没有验证码的属性,接不住,所以用map,也可以用Dto
     * @param session 验证成功了,就把user存入session,拦截器那边放行
     * @return
     */
    @PostMapping("/login")
    public RetObj loginController(@RequestBody Map<String,String> map, HttpSession session){
    
    
        String phone = map.get("phone");
        String code = map.get("code");
        String sessionCode = session.getAttribute(phone).toString();
        if (StringUtils.isNotBlank(sessionCode) && code.equals(sessionCode)){
    
    
            //用户名秘密验证成功!
            //先查电话号码在不在数据库里,如果不在,就要保存
            LambdaQueryWrapper<User> lambdaQueryWrapper = new LambdaQueryWrapper<>();
            lambdaQueryWrapper.eq(User::getPhone,phone);
            User user = userService.getOne(lambdaQueryWrapper);
            if (user ==  null){
    
    
                //存入数据库
                User user1 = new User();
                user1.setPhone(phone);
                user1.setStatus(1);
                userService.save(user1);
                //session.setAttribute("user",user1.getId());
            }else {
    
    
                session.setAttribute(Contants.SESSION_USERID,user.getId());
            }
            return RetObj.success("登入成功!");
        }else {
    
    
            return RetObj.error("登入失败!");
        }
  1. 写完之后配置拦截器,有两个类要配置
package cn.edu.uestc.ruijitakeout.common.interceptor;
@Slf4j
public class LoginInterceptor implements HandlerInterceptor {
    
    
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
    
    
        String uri = request.getRequestURI();
        log.info("当前路径:{}", uri);

        /**
         * HandlerMethod=>Controller中标注@RequestMapping的方法
         *  需要配置静态资源不拦截时,添加这块逻辑  => 前后端分离项目
         *
         */
        // 是我们的controller中的方法就拦截,如果不是的话,放行,给加载静态资源
        if (!(handler instanceof HandlerMethod)) {
    
    
            log.info("是静态资源或非controller中的方法,放行");
            return true;
        }

        //1-通过session判断是否登入
        if (request.getSession().getAttribute(Contants.SESSIONLOGIN) != null) {
    
    
            log.info("用户已经登入,id={}", request.getSession().getAttribute(Contants.SESSIONLOGIN));

            //进入拦截器后,给threadLocal绑定session,让后面需要的公共字段自动填充的时候,填充这个updateUser
            //每次http请求,会分配一个新的线程来处理
            BaseContext.setThreadLocal((Long) request.getSession().getAttribute(Contants.SESSIONLOGIN));
            log.info("拦截器这里设置了ThreadLocal,值为:{}",(Long) request.getSession().getAttribute(Contants.SESSIONLOGIN));
            log.info("当前线程id={}",Thread.currentThread().getId());
            return true;

        } else if (request.getSession().getAttribute(Contants.SESSION_USERID) != null) {
    
    
            log.info("用户已经登入,id={}", request.getSession().getAttribute(Contants.SESSION_USERID));

            //进入拦截器后,给threadLocal绑定session,让后面需要的公共字段自动填充的时候,填充这个updateUser
            //每次http请求,会分配一个新的线程来处理
            BaseContext.setThreadLocal((Long) request.getSession().getAttribute(Contants.SESSION_USERID));
            log.info("拦截器这里设置了ThreadLocal,值为:{}",(Long) request.getSession().getAttribute(Contants.SESSION_USERID));
            log.info("当前线程id={}",Thread.currentThread().getId());
            return true;
        }
        //这里应该跳转到登入页面,如何做?
        //5、如果未登录则返回未登录结果,通过输出流方式向客户端页面响应数据
        log.info("用户未登入,通过输出流方式向客户端页面响应数据,打回登入页面");
        response.getWriter().write(JSON.toJSONString(RetObj.error("NOTLOGIN")));//与前端request.js中的代码呼应
        return false;
    }

package cn.edu.uestc.ruijitakeout.common.config;
@Slf4j
@Configuration
public class WebMvcConfig implements WebMvcConfigurer {
    
    
    /**
     * 拓展消息转换器
     * @param converters
     */
    @Override
    public void extendMessageConverters(List<HttpMessageConverter<?>> converters) {
    
    
        log.info("拓展消息转换器成功加载");
        //创建消息转换器对象
        MappingJackson2HttpMessageConverter messageConverter = new MappingJackson2HttpMessageConverter();
        //设置对象转换器,底层使用Jackson将Java对象转为json
        messageConverter.setObjectMapper(new JacksonObjectMapper());
        //将上面的消息转换器对象追加到mvc框架的转换器集合中
        converters.add(0,messageConverter);
    }

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
    
    
        //重写方法,添加拦截器方法
        registry.addInterceptor(loginInterceptor())
                //拦截哪些路径
                .addPathPatterns("/**")
                //不拦截路径
                .excludePathPatterns("/employee/backend/page/login/login.do",
                        //"/backend/**",
                        "/employee/backend/page/login/logout.do",
                        //"/front/**",
                        "/error",
                        "/user/**"


                );
    }

    @Bean
    public LoginInterceptor loginInterceptor(){
    
    
        return new LoginInterceptor();
    }

}

猜你喜欢

转载自blog.csdn.net/YiGeiGiaoGiao/article/details/130403851