黑马点评-01基于Redis实现短信登陆的功能

环境准备

当前模型

nginx服务器的作用

  • 手机或者app端向nginx服务器发起请求,nginx基于七层模型走的是HTTP协议,可以实现基于Lua直接绕开tomcat访问Redis

  • nginx也可以作为静态资源服务器,轻松扛下上万并发并负载均衡到下游的tomcat服务器,利用集群支撑起整个项目

  • 使用nginx部署前端项目后还可以做到动静分离,进一步降低tomcat服务的压力

企业级MySQL加上固态硬盘能够支撑的并发大概就是4000起~7000左右,对于上万的并发如果让tomcat直接访问Mysql,瞬间会让Mysql服务器的cpu和硬盘全部打满

  • 在高并发场景下需要选择使用MySQL集群,同时为了进一步降低MySQL的压力增加访问的性能,一般还需要使用Redis集群

在这里插入图片描述

导入数据/前端/后端

执行项目所需要的SQL脚本,MySQL的版本要采用5.7及以上版本,否则执行脚本时部分SQL语句无法执行

说明
tb_user用户表
tb_user_info用户详情表
tb_shop商户信息表
tb_shop_type商户类型表
tb_blog用户日记表(达人探店日记)
tb_follow用户关注表
tb_voucher优惠券表
tb_voucher_order优惠券的订单表

导入后端项目,将项目放到你的idea工作空间,然后利用idea打开即可

  • 第一步: 修改application.yaml文件中的MySQL和Reids的连接地址为自己服务所在的地址
  • 第二步: 启动项目,在浏览器访问http://localhost:8081/shop-type/list,如果可以看到JSON数据则说明导入成功

导入前端工程,将nginx的解压目录放到一个自己指定的目录(确保目录不含中文,特殊字符和空格)

  • 第一步: 在ngnix所在目录打开一个CMD窗口,执行start nginx.exe命令启动ngnix服务
  • 第二步: 打开浏览器并将页面调整为手机模式,访问http://localhost:8080/访问项目首页(页面的数据是从后端查询得到的)
server {listen       9999;server_name  localhost;# 指定前端项目所在的位置location / {root   html/hmdp;index  index.html index.htm;}error_page   500 502 503 504  /50x.html;location = /50x.html {root   html;}# 监听的路径location /api {  default_type  application/json;#internal;  keepalive_timeout   30s;  keepalive_requests  1000;  #支持keep-alive  proxy_http_version 1.1;  rewrite /api(/.*) $1 break;  proxy_pass_request_headers on;#more_clear_input_headers Accept-Encoding;  proxy_next_upstream error timeout;  # 反向代理的位置proxy_pass http://127.0.0.1:8081;#proxy_pass http://backend;}}

短信登录

基于Session实现登录流程

在这里插入图片描述

实现发送短信验证码功能

用户点击发生验证码按钮发起请求

在这里插入图片描述

UserController中的sendCode方法处理发生验证码请求,在UserServiceImpl编写具体的业务逻辑

  • 使用邮箱验证时我们还需要去数据库中修改phone的字段类型,将varchar(11)改为varchar(100)
/**
*发送手机验证码
*/	
@PostMapping("code")
public Result sendCode(@RequestParam("phone") String phone, HttpSession session) {return userService.sendcode(phone,session);
}
/**
* 发送手机验证码
*/
@Override
public Result sendcode(String phone, HttpSession session) {// 1.校验手机号,RegexUtils是我们创建的工具类,里面还需要用到RegexPatterns工具类if (RegexUtils.isPhoneInvalid(phone)) {// 2.如果不符合,返回错误信息return Result.fail("手机号格式错误!");}// 3.符合生成验证码,RandomUtil是hutool-all的工具类 String code = RandomUtil.randomNumbers(6);// 4.保存验证码到sessionsession.setAttribute("code",code);// 5.发送验证码log.debug("发送短信验证码成功,验证码:{}", code);// 6.返回成功的信息return Result.ok();
}/**
* 发送邮箱验证码
*/
@Override
public Result sendCode(@RequestParam("phone") String phone, HttpSession session) throws MessagingException {// TODO 发送短信验证码并保存验证码if (RegexUtils.isEmailInvalid(phone)) {return Result.fail("邮箱格式不正确");}String code = MailUtils.achieveCode();session.setAttribute(phone, code);log.info("发送登录验证码:{}", code);MailUtils.sendTestMail(phone, code);return Result.ok();
}

实现登录功能

用户点击登录按钮发起请求
在这里插入图片描述

/**
* 登录功能
* @param loginForm 登录参数,包含手机号、验证码;或者手机号、密码
*/
@PostMapping("/login")
public Result login(@RequestBody LoginFormDTO loginForm, HttpSession session){return userService.login(loginForm, session);
}
// loginForm中封装了登录参数,包含手机号、验证码或者手机号、密码
@Data
public class LoginFormDTO {private String phone;private String code;private String password;
}
@Override
public Result login(LoginFormDTO loginForm, HttpSession session) {// 1.校验手机号的格式String phone = loginForm.getPhone();if (RegexUtils.isPhoneInvalid(phone)) {// 2.如果不符合,返回错误信息return Result.fail("手机号格式错误!");}// 3.将从session中获取的验证码与用户提交的验证码进行比较Object cacheCode = session.getAttribute("code");String code = loginForm.getCode();// 从session中默认获取的对象都是Object类型,所以我们需要转化为String类型if(cacheCode == null || !cacheCode.toString().equals(code)){// 4.验证码不一致则报错return Result.fail("验证码错误");}// 5.验证码一致则根据用户提交的手机号取数据库中查询用户User user = query().eq("phone", phone).one();// 判断用户是否存在if(user == null){//6.不存在则创建user =  createUserWithPhone(phone);}//7.将用户的信息保存到session中session.setAttribute("user",user);// 返回成功的信息return Result.ok();
}// 创建一个新用户
private User createUserWithPhone(String phone) {//创建用户User user = new User();//设置手机号user.setPhone(phone);//设置昵称(默认名),一个固定前缀+随机字符串,USER_NICK_NAME_PREFIX是工具类中的系统常量user.setNickName(USER_NICK_NAME_PREFIX+ RandomUtil.randomString(8));//将用户信息保存到数据库save(user);return user;
}

实现登录验证功能(拦截器)

当我们登录成功后,前端还会发起一个user/me的请求用于登录校验,由于访问每个Controller都需要登录校验,所以我们可以把校验流程写在拦截器中

在这里插入图片描述

第一步; 定义一个工具类UserHolder专门用来存储用户的信息,而且每个线程都对应一个自己的用户信息

public class UserHolder {private static final ThreadLocal<User> tl = new ThreadLocal<>();public static void saveUser(User user){// 保存用户的信息,默认的key就是当前线程tl.set(user);}public static User getUser(){// 获取用户信息,默认的key就是当前线程return tl.get();}public static void removeUser(){// 删除用户信息,默认的key就是当前线程tl.remove();}
}

第二步: 创建一个LoginInterceptor类实现HandlerInterceptor接口,防止用户直接通过url路径访问项目的功能,重写前置拦截器方法和完成处理方法

  • preHandle方法: 用于我们登陆之前的权限校验,同时将从session中获取的用户信息保存到UserHolder的ThreadLocal中,方便以后在Controller中获取用户信息
  • afterCompletion方法: 用于处理登录后的信息避免内存泄露
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.用户存在则将用户信息保存到UserHolder的Threadlocal中UserHolder.saveUser((User)user);//6.放行return true;}@Overridepublic void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws 			Exception {UserHolder.removeUser();}
}

第三步:编写配置类,注册登录拦截器并设置该拦截器需要拦截的请求

@Configuration
public class MvcConfig implements WebMvcConfigurer {@Overridepublic void addInterceptors(InterceptorRegistry registry) {// 注册登录拦截器使其生效registry.addInterceptor(new LoginInterceptor()).excludePathPatterns(// 设置拦截器不需要拦截的请求"/shop/**","/voucher/**","/shop-type/**","/upload/**","/blog/hot","/user/code","/user/login");}
}

第四步: 获取当前线程对应的登录用户信息并响应到前端完成登录校验

@GetMapping("/me")
public Result me() {User user = UserHolder.getUser();return Result.ok(user);
}

隐藏用户敏感信息

在进行登录校验时将登录用户的全部信息响应给浏览器的行为是不安全的,所以我们应当在返回登录用户信息之前将用户的敏感信息进行隐藏

{"success":true,"data":{"id":1010,"phone":"1586385296","password":"","nickName":"user_i1b3ir09","icon":"","createTime":"2022-10-22T14:20:33","updateTime":"2022-10-22T14:20:33"}
}

第一步: 新建一个不含用户敏感信息UserDto对象,在进行登录校验时返回的含有用户敏感信息的User对象转化成UserDto对象

@Data
public class UserDTO {private Long id;private String nickName;private String icon;
}

第二步: 修改UserHolder工具类中ThreadLocal的泛型

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();}
}

第三步: 修改login方法中,将保存到session域中的User对象转换成UserDto对象

@Override
public Result login(LoginFormDTO loginForm, HttpSession session) {// 1.校验手机号的格式String phone = loginForm.getPhone();if (RegexUtils.isPhoneInvalid(phone)) {// 2.如果不符合,返回错误信息return Result.fail("手机号格式错误!");}// 3.将从session中获取的验证码与用户提交的验证码进行比较Object cacheCode = session.getAttribute("code");String code = loginForm.getCode();// 从session中默认获取的对象都是Object类型,所以我们需要转化为String类型if(cacheCode == null || !cacheCode.toString().equals(code)){// 4.验证码不一致则报错return Result.fail("验证码错误");}// 5.验证码一致则根据用户提交的手机号取数据库中查询用户User user = query().eq("phone", phone).one();// 判断用户是否存在if(user == null){//6.不存在则创建user =  createUserWithPhone(phone);}//7.将用户的信息隐藏后再存到到session中session.setAttribute("user",user);//UserDTO userDTO = BeanUtil.copyProperties(user, UserDTO.class);      //session.setAttribute("user", userDTO);session.setAttribute("user", BeanUtils.copyProperties(user,UserDTO.class));// 返回成功的信息return Result.ok();}

第四步: 修改拦截器中将从session中获取的隐藏了用户信息的UserDTO对象保存到UserHolder类的ThreadLocal中

public class LoginInterceptor implements HandlerInterceptor {@Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {//1.获取sessionHttpSession session = request.getSession();//2.获取session中隐藏了用户信息的UserDTO类型的用户对象,并保存到UserHolder类的ThreadLocal中UserDTO user = (UserDTO) session.getAttribute("user");UserHolder.saveUser(user);        //3.判断用户是否存在if(user == null){//4.不存在则拦截当前请求并返回401状态码response.setStatus(401);return false;}//5.若存在保存用户的隐藏信息到ThreadlocalUserHolder.saveUser((User)user);//6.放行return true;}
}

第五步: 修改登录校验的方法,将UserHolder中的ThreadLocal保存的UserDTO对象返回

// 修改获取的类型为用户的隐藏信息
@GetMapping("/me")
public Result me() {UserDTO user = UserHolder.getUser();return Result.ok(user);
}

第六步: 重启服务器,登录校验后返回的用户信息已经不含敏感信息

{"success":true,"data":{"id":1016,"nickName":"user_zkhf7cfv","icon":""}
}

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

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

相关文章

黑马JVM总结(二十七)

&#xff08;1&#xff09;synchronized代码块 synchronized代码块的底层原理&#xff0c;它是给一个对象进行一个加锁操作&#xff0c;它是如何保证如果你出现了synchronized代码块中出现了问题&#xff0c;它需要给这个对象有一个正确的解锁操作呢&#xff0c;加锁解锁是成对…

【c++_containers】10分钟带你学会list

前言 链表作为一个像是用“链子”链接起来的容器&#xff0c;在数据的存储等方面极为便捷。虽然单链表单独在实际的应用中没用什么作用&#xff0c;但是当他可以结合其他结构&#xff0c;比如哈希桶之类的。不过今天学习的list其实是一个带头双向链表。 言归正传&#xff0c;让…

overleaf在线编辑工具使用教程

文章目录 1 用 orcid注册overleaf获取模板2 使用模板 1 用 orcid注册overleaf获取模板 通常来说&#xff0c;在期刊投稿网站information for author中找template 。下载压缩包后上传到over leaf中。 加入找不到官方模板&#xff0c;用overleaf中的 2 使用模板 .bib文件&…

3D孪生场景SDK:Viwer 孪生世界

NSDT 编辑器 提供三维场景构建、场景效果设计、场景服务发布全流程工具等&#xff0c;其场景编辑器支持资产管理、灯光设置、骨骼动画等功能&#xff1b;致力于协助资源不足的中小企业及个人快速开发数字孪生场景&#xff0c;帮助企业提高生产力、实现降本增效。 NSDT编辑器简…

adb详细教程(四)-使用adb启动应用、关闭应用、清空应用数据、获取设备已安装应用列表

adb对于安卓移动端来说&#xff0c;是个非常重要的调试工具。本篇介绍常用的adb指令 文章目录 一、启动应用&#xff1a;adb shell am start二、使用浏览器打开指定网址&#xff1a;adb shell am start三、杀死应用进程adb shell am force-stop/adb shell am kill四、删除应用所…

【AI视野·今日CV 计算机视觉论文速览 第262期】Fri, 6 Oct 2023

AI视野今日CS.CV 计算机视觉论文速览 Fri, 6 Oct 2023 Totally 73 papers &#x1f449;上期速览✈更多精彩请移步主页 Daily Computer Vision Papers Improved Baselines with Visual Instruction Tuning Authors Haotian Liu, Chunyuan Li, Yuheng Li, Yong Jae Lee大型多模…

python开发幸运水果抽奖大转盘

概述 当我女朋友跟我说要吃水果&#xff0c;又不知道吃啥水果时候&#xff0c;她以为难为到我了&#xff0c;有啥事难为到程序员的呢&#xff01; 今天用python利用第三方tkinterthreadingtime库开发一个幸运水果抽奖大转盘&#xff01;抽到啥吃啥 详细 老规矩&#xff01;咱…

基于A4988/DRV8825的四路步进电机驱动器

概述 简化板的CNC sheild V3.0&#xff0c;仅保留步进电机速度与方向的控制引脚STEP/DIR、使能端EN、芯片供电VCC\GND&#xff0c;共计11个引脚。PCB四周开设四个M3通孔&#xff0c;以便于安装固定。此外&#xff0c;将板载的焊死的保险丝更改为可更换的保险座保险丝&#xff…

Labview 实战 99乘法表

基于新手小白&#xff0c;使用Labview实现99乘法表&#xff0c;敢于发表自己的一点方法&#xff0c;还请各位大侠放过&#xff01; 如下&#xff1a; 运行效果如下&#xff1a; 思路为&#xff1a;将要显示出来的数据&#xff0c;全部转换为字符串形式&#xff0c;再塞入到数组…

Java基于SSM+Vue的平时成绩管理系统

末尾获取源码 开发语言&#xff1a;Java Java开发工具&#xff1a;JDK1.8 后端框架&#xff1a;SSM 前端&#xff1a;采用Vue技术开发 数据库&#xff1a;MySQL5.7和Navicat管理工具结合 服务器&#xff1a;Tomcat8.5 开发软件&#xff1a;IDEA / Eclipse 是否Maven项目&#x…

频次直方图、KDE和密度图

Seaborn的主要思想是用高级命令为统计数据探索和统计模型拟合创建各种图形&#xff0c;下面将介绍一些Seaborn中的数据集和图形类型。 虽然所有这些图形都可以用Matplotlib命令实现&#xff08;其实Matplotlib就是Seaborn的底层&#xff09;&#xff0c;但是用 Seaborn API会更…

基于SSM+Vue的鲜花销售系统设计与实现

末尾获取源码 开发语言&#xff1a;Java Java开发工具&#xff1a;JDK1.8 后端框架&#xff1a;SSM 前端&#xff1a;采用Vue技术开发 数据库&#xff1a;MySQL5.7和Navicat管理工具结合 服务器&#xff1a;Tomcat8.5 开发软件&#xff1a;IDEA / Eclipse 是否Maven项目&#x…

Kafka快速实战以及基本原理详解

这一部分主要是接触 Kafka &#xff0c;并熟悉 Kafka 的使用方式。快速熟练的搭建 kafka 服务&#xff0c;对于快速验证一些基于Kafka 的解决方案&#xff0c;也是非常有用的。 一、 Kafka 介绍 ChatGPT 对于 Apache Kafka 的介绍&#xff1a; 1 、 MQ 的作用 MQ &#xff1a;…

【AI视野·今日NLP 自然语言处理论文速览 四十九期】Fri, 6 Oct 2023

AI视野今日CS.NLP 自然语言处理论文速览 Fri, 6 Oct 2023 Totally 44 papers &#x1f449;上期速览✈更多精彩请移步主页 Daily Computation and Language Papers MathCoder: Seamless Code Integration in LLMs for Enhanced Mathematical Reasoning Authors Ke Wang, Houxi…

【Java 进阶篇】使用 JDBCTemplate 执行 DQL 语句详解

在前面的文章中&#xff0c;我们已经学习了如何使用 Spring 的 JDBCTemplate 执行 DML&#xff08;Data Manipulation Language&#xff09;操作&#xff0c;包括插入、更新和删除操作。现在&#xff0c;让我们来深入了解如何使用 JDBCTemplate 执行 DQL&#xff08;Data Query…

Youtube视频下载工具分享-油管视频,音乐,字幕下载方法汇总

YouTube视频下载方法简介 互联网上存在很多 YouTube 下载工具&#xff0c;但我们经常会发现自己收藏的工具没过多久就会失效&#xff0c;我们为大家整理的这几种方法&#xff0c;是存在时间较久并且亲测可用的。后续如果这些工具失效或者有更好的工具&#xff0c;我们也会分享…

算法通过村第十二关-字符串|白银笔记|经典面试题

文章目录 前言1. 反转问题1.1 反转字符串1.2 k个一组反转1.3 仅仅反转字母1.3.1 采用栈实现操作1.3.2 采用双指针实现操作 1.4 反转字符串里面的单词1.4.1 使用语言提供的方法来解决(内置API)1.4.2 如何优雅自己实现上述功能 2. 验证回文串3. 字符串中的第一个唯一字符4. 判断是…

GitHub爬虫项目详解

前言 闲来无事浏览GitHub的时候&#xff0c;看到一个仓库&#xff0c;里边列举了Java的优秀开源项目列表&#xff0c;包括说明、仓库地址等&#xff0c;还是很具有学习意义的。但是大家也知道&#xff0c;国内访问GitHub的时候&#xff0c;经常存在访问超时的问题&#xff0c;…

鸡群优化(CSO)算法(含MATLAB代码)

先做一个声明&#xff1a;文章是由我的个人公众号中的推送直接复制粘贴而来&#xff0c;因此对智能优化算法感兴趣的朋友&#xff0c;可关注我的个人公众号&#xff1a;启发式算法讨论。我会不定期在公众号里分享不同的智能优化算法&#xff0c;经典的&#xff0c;或者是近几年…

《Secure Analytics-Federated Learning and Secure Aggregation》论文阅读

背景 机器学习模型对数据的分析具有很大的优势&#xff0c;很多敏感数据分布在用户各自的终端。若大规模收集用户的敏感数据具有泄露的风险。 对于安全分析的一般背景就是认为有n方有敏感数据&#xff0c;并且不愿意分享他们的数据&#xff0c;但可以分享聚合计算后的结果。 联…