当前位置: 首页 > news >正文

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

短信登录

导入黑马点评项目

导入资料中提供的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.保存验证码到sessionsession.setAttribute("code",code);//5.发送验证码(模拟发送验证码,该业务并未实现)log.debug("发送短信验证码成功,验证码:{}",code);// 6.返回结果return Result.ok();}

效果展示:

验证码:

image-20250523175458482

短信验证码登录、注册

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

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

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

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

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

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

流程图如下

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

image-20250523175655376

思路及代码展示:

 @Overridepublic 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中,所以直接赋值给useruser = 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 {// 前置拦截器@Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {// 1.获取请求头中的sessionHttpSession 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;}​//  拦截器 后处理@Overridepublic 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;@Configurationpublic class MvcConfig implements WebMvcConfigurer {@Overridepublic 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

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

希望对大家有所帮助!!

相关文章:

  • macOS 安装 PostgreSQL
  • 基于BoxMOT的目标检测与跟踪全流程详解
  • HTA8127内置升压的77W单体声D类音频功放
  • 如何在 Windows 11 或 10 上通过 PowerShell 安装 Docker Desktop
  • 大腾智能 PDM 系统:全生命周期管理重塑制造企业数字化转型路径
  • 使用pip安装ptflops报错
  • Spring用到的设计模式
  • Day125 | 灵神 | 二叉树 | 二叉树中的第K大层和
  • 基于RT-Thread的STM32F4开发第七讲——RTC(硬件、软件)
  • C++构造函数和析构函数
  • 【2025最新】下载安装Anaconda
  • 数据共享中的库表交换怎么做?
  • 二、OpenCV图像处理-几何变换
  • 【CSS border-image】图片边框拉伸不变形,css边框属性,用图片打造个性化边框
  • Docker 与 Kubernetes 部署 RabbitMQ 集群(一)
  • el-table控制type=“expand“展开列 根据条件显示或隐藏展开按钮
  • 什么是DAQ采集卡?它的优势有哪些?
  • 【PINN】DeepXDE学习训练营(32)——pinn_forward-fractional_diffusion_1d.py
  • ES6 新增 API 方法
  • 【通用智能体】Intelligent Internet Agent (II-Agent):面向复杂网络任务的智能体系统深度解析
  • axure做网站着陆页/广州seo软件
  • 做b2b网站有没有用/在百度上怎么注册网站
  • 网站结构怎么做适合优化/云南网站建设百度
  • wordpress网站+搬家/安卓优化大师老版本下载
  • 内蒙古城乡住房建设厅网站/百度一下你就知道下
  • 用腾讯云做淘宝客购物网站视频/业务推广方案怎么写