黑马点评--短信登录实现

发布于:2025-05-24 ⋅ 阅读:(18) ⋅ 点赞:(0)

短信登录

导入黑马点评项目

导入资料中提供的SQL文件

其中的核心表有:

  • tb_user :用户表

  • tb_user_info :用户详情表

  • tb_shop:用户信息表

  • tb_shop_type:商户类型表

  • tb_blog:用户日记表(达人探店日记)

  • tb_follow:用户关注表

  • tb_voucher: 优惠劵表

  • tb_voucher:优惠劵的订单表

  • tb_voucher_order

注意事项:MySQL版本最好采用5.7及以上版本

该项目为单体项目,采用前后端分离的模式,将后端部署在Tomcat上,前端会将其放置在Nginx服务器上。

在PC端或者移动端在请求页面时,其实是想Nginx发起请求得到的静态资源,页面在通过Ajax像我们的服务端发起请求去查询数据,这些数据可能来自于Redis集群,或者来自MySQL集群。然后再将查询到的数据返回给前端,前端完成渲染即可。是一种前后端分离的架构。

还会考虑项目的并发能力,项目还具备水平拓展的能力,即项目部署在Tomcat上后,如果单台Tomcat上支撑不住,还可以做一个水平的扩展形成一个负载均衡的集群,在多台Tomcat上部署代码,一旦步入集群之后,将来就会存在一些集群间的数据共享的问题。

!

导入后端模板

从资料中拿到项目源码,将其复制到IDEA中,然后利用IDEA打开,注意springboot与MyBatis-plus的版本冲突。

启动项目后,在浏览器访问:http://localhost:8081/shop-type/list,如果看到数据则证明运行没有问题

注意事项:不要忘记修改application.yml的MySQL,redis地址信息。

导入前端项目:

该项目将整个前端代码部署到Nginx里,

在资料中提供了Nginx文件夹,将其复制到任意目录,确保该目录不包含中文、特殊字符和空格

在该文件夹下打开命令行界面,输入 start nginx.exe

然后在浏览器中点击F12,打开手机模式,并访问 http://localhost:8080

基于session实现登录

短信登录包括短信的发送,然后是基于短信验证码的登录,最后是对登录状态的一个校验

发送短信验证码

用户会提交自己的手机号码,服务端接收到手机号后,我们要去验证用户的手机号是否合法。

校验失败则重新提交,校验成功则生成验证码,还要去保存验证码(我们发送验证码的目的是让用户去做登录,需要校验验证码,需要现将其保存到session中)。

最后发送验证码。

流程图如下

实现步骤:

首先查看请求,请求方式为POST,请求路径为/user/code,请求参数为phone,电话号码,无返回值

先写发送手机验证码的代码,业务代码需要在服务层编译,所以先在Controller层写出方法名以及要调用的参数,phone,以及session(将验证码保存在session中)

代码展示:

 
public Result sendCode(String phone, HttpSession session) {
     //1.校验手机号
     if (RegexUtils.isPhoneInvalid(phone)) {
         //2.如果不符合,返回错误信息
         return Result.fail("手机号格式错误");
     }
     //3.如果符合,生成验证码
     String code = RandomUtil.randomNumbers(6);
     //4.保存验证码到session
     session.setAttribute("code",code);
     //5.发送验证码(模拟发送验证码,该业务并未实现)
     log.debug("发送短信验证码成功,验证码:{}",code);
         // 6.返回结果
     return Result.ok();
 }

效果展示:

验证码:

image-20250523175458482

短信验证码登录、注册

用户收到验证码则用来做验证或者登录。

首先需要提交手机号和验证码到后台,后台则去验证提交的两个数据,先需要校验验证码(将验证码与上一步保存在session中的验证码做一个比较)。

不一致则会回退上一步,重新提交。如果一样则根据手机号到数据库中去查询该用户是否存在。

如果不存在,则说明从未访问过服务器,手机号是全新的,这是需要去将其注册成一个新用户,填充一些基本信息,将新用户写入到数据库中。

如果存在,则可以登录,服务器需要去保存用户信息到session中。

登录与注册是在一个工程中完成的,

流程图如下

实现步骤:先输入验证码,发送请求并查看,发现为post请求,且负载信息为json字符串,并且传入的是验证码以及电话号码

image-20250523175655376

思路及代码展示:

 @Override
 public Result login(LoginFormDTO loginForm, HttpSession session) {
     //1.校验手机号
     String phone = loginForm.getPhone();
     if (RegexUtils.isPhoneInvalid(phone)) {
         //2.如果不符合,返回错误信息
         return Result.fail("手机号格式错误");
     }
     //2.校验验证码
     Object cacheCode = session.getAttribute("code");
     String code = loginForm.getCode();
     // 一般校验时,从反向校验,这种校验不需要if嵌套,否则会嵌套if,避免if嵌套过深
     if (cacheCode == null || !cacheCode.toString().equals(code)){
         //3.不一致,返回错误信息
         return Result.fail("验证码错误");
     }
     //4.一致,根据手机号查询用户 select   * from user where phone = ?
     User user = query().eq("phone", phone).one();
     //5.判断用户是否存在
     if (user == null){
         //6.不存在,创建新用户并保存
         // 方法定义在函数中 创建用户
         //在创建完用户到数据库后还需要保存在session中,所以直接赋值给user
         user = createUserWithPhone(phone);
     }
     //7.保存用户信息到session并返回结果
     session.setAttribute("user",user);
     //这里不需要返回一个登录凭证,因为用户信息已经保存在session中,所以不需要返回一个登录凭证
     //session的原理是在服务器端保存用户信息,并在客户端保存一个标识符。
     // 这个标识符就是sessionId,这个标识符在客户端的cookie中保存,这样客户端就可以通过这个标识符来获取服务器端保存的用户信息。
     return Result.ok();
 }

效果展示:

image-20250523185233667

image-20250523185242262

查询数据库得用户插入成功

image-20250523185308689

完成。

校验登录状态

用户登录成功后,访问一些关键的数据时需要去校验登录状态。

服务器将用户信息保存在session中,那将来用户访问时,需要基于session进行校验。

session是基于cookie的,每一个session都会有一个对应的sessionID保存在浏览器的cookie中,所以当用户访问服务时一定会携带cookie(包含sessionID)。

这时服务器就可以基于cookie中的sessionID拿到session,在从session中拿到用户,服务器只需要验证session中是否有用户信息即可。

如果经过判断没有用户,直接拦截请求。

如果判断存在,就将用户信息缓存起来到ThreadLocal

(线程域对象,核心作用是让每一个线程都有自己的数据副本,避免共享资源引发的竞态条件。 在实际开发业务中,每一个请求到达微服务时,都是一个独立的线程,如果没有使用ThreadLocal,而是将用户信息存放在本地变量中,可能出现多线程并发修改的安全问题,ThreadLocal会将数据保存到每一个线程的内部,在县城内部创建一个ThreadLocalMap去保存,每一个线程都有自己独立的存储空间,相互之间没有干扰)

中(在之后的业务中一定会用到当前登录的用户信息,将其缓存方便后续的业务使用),然后服务器放行。

流程图如下:

实现步骤:

在前面已经完成了发送短信验证码以及短信验证码登录注册的功能,但登录校验的功能并没有完成。

登录校验的URL为http://localhost:8080/user/me,用来查询当前登录的用户信息,然后服务端返回正确的用户信息,则登录校验成功,并将其跳转到首页。

再流程图中用户的请求会带上cookie,而cookie中含有登录凭证SessionID,而服务端只需要根据sessionID得到session,在从session中获取用户,判断用户是否存在。

但是其中有些问题:

我们的登录校验是在UserController中编译的,前端会向userController发起请求,而服务端在UserController的是在对应业务中编译业务逻辑代码,而在后期,越来越多的业务需要去校验用户的登录,我们不能在每一个Controller中都去编译一段登录校验的业务代码,

而在springmvc中的拦截器是在所有的controller执行之前去执行,有了拦截器之后,用户的请求就不能直接去访问服务端的controller,所有请求必须要先经过拦截器,再由拦截器判断该不该放行,那就可以将登录校验的业务代码中放到拦截器中去执行。

而拦截器确实可以实现对用户登录的校验,校验之后,业务可能需要该用户信息。

但用户信息却在拦截器中,Controller并不能拿到。因此需要将拦截器中的用户信息传递到controller中。

需要考虑线程安全问题,把用户信息存储在session中,则需要在跨各种处理组件或页面时都要从session中去取得用户信息,这增加了访问session的开销。

所以我们这里用ThreadLocal。

ThreadLocal就可以解决该问题,ThreadLocal是一个线程域对象,每一个进入Tomcat的请求都是一个独立的线程,而ThreadLocal的核心作用就是让每一个线程都有自己的数据副本,避免共享资源引发的竞态条件,这样就可以让每一个线程都有对应的独立内存空间取保存对应用户,线程之间互不干扰,因此无论几条请求访问哪些Controller都可以做到独立线程,都有自己的独立用户信息,当需要用户信息时,则Controller从ThreadLocal中取出用户即可。

因此我们要在拦截器中实现校验登录,以及将用户信息存入到ThreadLocal。

隐藏用户敏感信息

服务端在登录校验成功后返回的信息较多,包括用户密码,时间,电话等敏感信息,存在泄漏风险,在UserController中,登录校验的业务代码拿到的是用户的全部信息存入session,session是Tomcat的内存空间,这里面的信息越多,对于服务来讲,压力越大。因此不需要存储全部信息 。而我们在登录业务中直接将user的全部信息放入session中,因此需要在DTO包下新建UserDTO,里面只有一些不重要的信息属性字段,因此只需要在登录业务中在存入session调用hutool中的工具类将User属性拷贝给UserDTO。

 session.setAttribute("user", BeanUtil.copyProperties(user, UserDTO.class));

然后将拦截器中取出的User对象改为USerDTO对象即可。也需要将UserHolder(用于新建ThreadLocal并调用的工具类)中的User对象改为UserDTO

 public class UserHolder {
     private static final ThreadLocal<UserDTO> tl = new ThreadLocal<>();
 ​
     public static void saveUser(UserDTO user){
         tl.set(user);
     }
 ​
     public static UserDTO getUser(){
         return tl.get();
     }
 ​
     public static void removeUser(){
         tl.remove();
     }
 }

代码展示:

在utils包下新建Logininterceptor类(拦截器)

 package com.hmdp.utils;
 ​
 import com.hmdp.dto.UserDTO;
 import com.hmdp.entity.User;
 import jakarta.servlet.http.HttpServletRequest;
 import jakarta.servlet.http.HttpServletResponse;
 import jakarta.servlet.http.HttpSession;
 import org.springframework.web.servlet.HandlerInterceptor;
 ​
 public class LoginInterceptor implements HandlerInterceptor {
     // 前置拦截器
     @Override
     public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
         // 1.获取请求头中的session
         HttpSession session = request.getSession();
         //  2.获取session的用户
         Object user = session.getAttribute("user");
         //  3.判断用户是否存在
         if (user == null) {
             //  4.不存在 拦截器拦截 返回401状态码 未授权
             response.setStatus(401);
             return false;
         }
         //  5.存在,保存用户信息到ThreadLocal 并放行
         // 在工具类中定义了一个UserHolder 是一个线程安全的ThreadLocal变量,用于保存当前线程的用户信息。
         // 其中有三个方法:saveUser( 保存),getUser(拿到),removeUser(移除)。
         UserHolder.saveUser((UserDTO) user);
         return true;
     }
 ​
     //  拦截器 后处理
     @Override
     public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        //  移除用户 避免用户泄漏
         UserHolder.removeUser();
     }
 }

在配置包下新建MVCConfig类

代码如下:

 package com.hmdp.config;
 ​
 import com.hmdp.utils.LoginInterceptor;
 import org.springframework.context.annotation.Configuration;
 import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
 import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
 @Configuration
 public class MvcConfig implements WebMvcConfigurer {
     @Override
     public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(new LoginInterceptor())
                //  除了这些路径,其他路径都进行拦截
                .excludePathPatterns(
                        "/user/code",
                        "/user/login",
                        "/blog/hot",
                        "/voucher/**",
                        "/shop/**",
                        "/shop-type/**",
                        "/upload/**",
                        "/blog/query/hot",
                        "/druid/**"
                );
 ​
     }
 }

至此,校验登录状态业务完成。

测试:

image-20250523230143009

登录校验成功,并且数据也未泄露。

希望对大家有所帮助!!


网站公告

今日签到

点亮在社区的每一天
去签到