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

短信登录

导入黑马点评项目

导入资料中提供的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

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

希望对大家有所帮助!!

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

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

相关文章

AWS EC2实例安全远程访问最佳实践

EC2 远程连接方案对比 远程访问 Amazon EC2 实例主要有以下四种方式&#xff1a; Secure Shell (SSH) 远程访问AWS Systems Manager 会话管理器适用于 Linux 实例的 EC2 Serial ConsoleAmazon EC2 Instance Connect SSH 远程访问 SSH&#xff08;Secure Shell&#xff09;广…

Idea如果有参数,怎么debug

如上图&#xff0c;输入输出路径是需要运行的时候给参数。 那么 FileInputFormat.setInputPaths(job, new Path(args[0])); FileOutputFormat.setOutputPath(job, new Path(args[1])); 给上面的代码给参数的步骤为 1.在类名或者方法名上右键&#xff0c;选择More Run/Debug…

Oracle Apps R12——报表入门2:单表——报表开发流程

☆开发思路 开发表报代码流程中有几个重要的组件和重要的知识点需要搞懂&#xff0c;才能得心应手。报表通常是通过表格的形式来存在的&#xff0c;我们一般在开发代码的时候在【输出】中打印HTML,Css格式的表格&#xff0c;并把查询到的数据插入其中&#xff0c;即可完成一个报…

Servlet的继承关系和生命周期

1.继承关系&#xff1a; javax.servlet.Servlet接口->javax.servlet.GenericServlet抽象类 ->javax.servlet.http.HttpServlet抽象子类 2.相关方法&#xff1a; javax.servlet.Servlet&#xff1a; &#xff08;1&#xff09;void init(config) -初始化方法 &…

PEFT库PromptTuningConfig 配置

PEFT库 PromptTuningConfig 配置 "Prompt Tuning"的参数高效微调 PromptTuningConfig 核心参数解析 1. task_type="CAUSAL_LM" 作用:指定任务类型为因果语言模型(Causal LM)。说明:因果语言模型从左到右生成文本(如GPT系列),这与任务需求匹配(模…

【438. 找到字符串中所有字母异位词】

Leetcode算法练习 笔记记录 438. 找到字符串中所有字母异位词 438. 找到字符串中所有字母异位词 思路就是我们要找和p相同的词&#xff0c;可以先排个序&#xff0c;每次取一个和p的size长度相同的窗口去滑动&#xff0c;符合就记录&#xff0c;不符合继续滑动。 public List&l…

React Hooks底层执行逻辑详解、自定义Hooks、FiberScheduler

React Hooks底层执行逻辑详解 React Hooks 在表面上看像普通的函数调用&#xff0c;背后却隐藏着一套复杂而高效的运行时机制。要理解 React Hooks 的底层执行逻辑&#xff0c;需要从 React 如何管理组件的状态与副作用入手。 &#x1f9e0; 一、React 为什么引入 Hooks&#…

Windows命令实用工具——tcping 命令工具安装及基础使用

Windows命令实用工具——tcping 命令工具安装及使用 一、tcping 命令简介二、tcping 的安装1、tcping 官网下载安装包2、将软件包复制到 Windws 系统的 System32 目录下面3、查看 tcping 命令是否安装成功 三、tcping 工具简单使用方法 一、tcping 命令简介 tcping 的主要功能…

智慧化工园区安全风险管控平台建设方案(Word)

1 项目概况 1.1 园区概况 1.1.1 XX化工园区简况 1.1.2 企业现状 1.1.3 园区发展方向 1.1.4 园区信息化现状 1.2 项目建设背景 1.2.1 政策背景 1.3 项目建设需求分析 1.3.1 政策需求分析 1.3.2 安全生产监管需求分析 1.3.3 应急协同管理需求分析 1.3.4 工业互联网安…

【动手学深度学习】2.3. 线性代数

目录 2.3. 线性代数1&#xff09;标量2&#xff09;向量3&#xff09;矩阵4&#xff09;张量5&#xff09;张量的基本性质6&#xff09;降维7&#xff09;点积8&#xff09;矩阵-向量积9&#xff09;矩阵-矩阵乘法10&#xff09;范数11&#xff09; 小结 2.3. 线性代数 本节将…

如何在项目当中使用redis进行范围搜索

目录 如何将地理位置数据保存到 Redis 中以支持范围查询 Redis 中的 GEO 类型是什么&#xff1f; 如何保存 GEO 数据到 Redis 分段解释&#xff1a; RedisKey.POSTS_ANIMALS_LOCATIONS new Point(longitude, latitude) 如何进行范围搜索 Redis GEO 范围搜索核心语句 1…

物联网低功耗保活协同优化方案:软硬件与WiFi网关动态联动

目录 一、总体方案概述 二、架构组成 2.1 系统拓扑 2.2 硬件端(MCU + WiFi 模组) 2.3 WiFi 网关 2.4 云端服务器 三、低功耗保活技术设计模式 3.1 模式一:定时唤醒 + MQTT 保活 3.1.1 设备端 3.1.2 优势 3.2 模式二:网关保活代理 + 本地网络唤醒 3.2.1 网关功能…

UniApp+Vue3微信小程序二维码生成、转图片、截图保存整页

二维码生成工具使用uqrcode/js&#xff0c;版本4.0.7 官网地址&#xff1a;uQRCode 中文文档&#xff08;不建议看可能会被误导&#xff09; 本项目采用了npm引入方式&#xff0c;也可通过插件市场引入&#xff0c;使用上会略有不同 准备工作&#xff1a; 安装&#xff1a;pnpm…

Zenmap代理情况下无法扫描ip

原因是开了代理会报错 error “only ethernet devices can be used for raw scans on Windows” 在扫描参数后加 -sT -Pn&#xff0c;但会导致结果太多 例如&#xff1a;nmap -sT -T4 -A -v -Pn 10.44.2.0/24 如果你只是想找没人用的IP&#xff0c;你不需要搞复杂的原始层扫描&…

将多个值关联到同一个 key的map(key可以重复的map)示例

在 Java 中&#xff0c;标准的 Map 接口要求 key 必须唯一&#xff0c;如果需要 key 可重复 且保持 插入顺序 的数据结构&#xff0c;可以使用以下方案&#xff1a; 1. 使用 List<Map.Entry<K, V>> 最直接的方式是用链表存储键值对&#xff0c;允许重复 key&…

Arthas(阿尔萨斯)

一、Arthas 是什么&#xff1f; Arthas&#xff08;阿尔萨斯&#xff09;是阿里巴巴开源的一款 Java 在线诊断工具&#xff0c;基于 Java Agent 和字节码增强技术实现。它无需重启 JVM&#xff0c;即可动态追踪代码执行、实时查看 JVM 状态、修改代码逻辑&#xff0c;是生产环…

深入解读Qwen3技术报告(三):深入剖析Qwen3模型架构

重磅推荐专栏&#xff1a; 《大模型AIGC》 《课程大纲》 《知识星球》 本专栏致力于探索和讨论当今最前沿的技术趋势和应用领域&#xff0c;包括但不限于ChatGPT和Stable Diffusion等。我们将深入研究大型模型的开发和应用&#xff0c;以及与之相关的人工智能生成内容&#xff…

UE4游戏查找本地角色数据的方法-SDK

UE4中&#xff0c;玩家的表示通常涉及以下几个类&#xff1a; APlayerController: 代表玩家的控制逻辑&#xff0c;处理输入等。 APawn: 代表玩家在世界中的实体&#xff08;比如一个角色、一辆车&#xff09;。APlayerController 控制一个 APawn。 ACharacter: APawn 的一个…

springboot+vue实现服装商城系统(带用户协同过滤个性化推荐算法)

今天教大家如何设计一个服装商城 , 基于目前主流的技术&#xff1a;前端vue3&#xff0c;后端springboot。 同时还带来的项目的部署教程。 系统最大的亮点是使用了两个推荐算法: 1. 基于Jaccard算法的用户浏览历史推荐。 2. 基于用户的协同过滤算法个性化推荐。 还有核心的商…

ERROR: Could not install packages due to an OSError: [WinError 5] 拒绝访问

有可能是设置了代理 unset ALLPROXY 或者注释掉 当然也有可能是其他原因 权限不足​​ 以管理员身份运行 CMD/PowerShell&#xff0c;或使用 --user 安装 ​​文件被占用​​ 关闭杀毒软件或重启电脑 Python 环境损坏​​ 重新安装 Python 或使用虚拟环境 ​​ 杀毒软件阻止…