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

part1
part2
part3
part4
part5 本页

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

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

6.1 短信发送

6.1.1整体分析

springboot项目:瑞吉外卖 前后端 代码、思路 详细分析 part5_第1张图片
2.
springboot项目:瑞吉外卖 前后端 代码、思路 详细分析 part5_第2张图片
3. 注册登入阿里云账户。找到短信服务,设置短信签名(上面图片的阿里云、菜鸟裹裹、天猫…),模板等等
springboot项目:瑞吉外卖 前后端 代码、思路 详细分析 part5_第3张图片
4. 设置AccessKey
5. 看帮助文档,导入对应的Util包

6.2 手机验证码登入

6.2.1 整体思路整理

  1. 需求分析springboot项目:瑞吉外卖 前后端 代码、思路 详细分析 part5_第4张图片
  2. 涉及表的操作(数据模型)
    springboot项目:瑞吉外卖 前后端 代码、思路 详细分析 part5_第5张图片
  3. 代码开发思路
    springboot项目:瑞吉外卖 前后端 代码、思路 详细分析 part5_第6张图片
    想要判断当前手机号是否在表中,如果不在,说明是新用户,将改号码保存
  4. 使用浏览器的手机模式查看H5的页面
    springboot项目:瑞吉外卖 前后端 代码、思路 详细分析 part5_第7张图片
  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作用是对应之前的:

手机号输入不正确,请重新输入
,即已经通过了验证,不需要弹出提示信息
正常情况下,通过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();
    }

}

你可能感兴趣的:(前后端详细分析,spring,boot,数据库,java,前端,后端)