黑马点评-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.rhkb.cn/news/149692.html

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

相关文章

二分查找:34. 在排序数组中查找元素的第一个和最后一个位置

个人主页 &#xff1a; 个人主页 个人专栏 &#xff1a; 《数据结构》 《C语言》《C》《算法》 文章目录 前言一、题目解析二、解题思路1. 暴力查找2. 一次二分查找 部分遍历3. 两次二分查找分别查找左右端点1.查找区间左端点2. 查找区间右端点 三、代码实现总结 前言 本篇文…

力扣 -- 873. 最长的斐波那契子序列的长度

解题步骤&#xff1a; 参考代码&#xff1a; class Solution { public:int lenLongestFibSubseq(vector<int>& nums) {int nnums.size();unordered_map<int,int> hash;for(int i0;i<n;i){hash[nums[i]]i;}int ret2;vector<vector<int>> dp(n,v…

基于SSM的旅游网站设计与实现

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

国庆看坚如磐石

坚如磐石上映了&#xff0c;可以在爱奇艺观看。 而博主在使用蓝牙耳机连接电脑的过程中&#xff0c;发现没有蓝牙开启选项&#xff0c;并且在服务的设备管理器中也没有找到&#xff0c;很明显这是缺少驱动导致的&#xff0c;因此便去联想官方网站下载对应的驱动。 这里可以输入…

外包做了3个月,技术退步明显。。。。。

先说一下自己的情况&#xff0c;大专生&#xff0c;17年通过校招进入广州某软件公司&#xff0c;干了接近4年的功能测试&#xff0c;今年年初&#xff0c;感觉自己不能够在这样下去了&#xff0c;长时间呆在一个舒适的环境会让一个人堕落!而我已经在一个企业干了四年的功能测试…

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

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

初级数值计算理论总结

本文用于总结复习与研究生面试 一问&#xff0c;小伙子会不会数值计算啊一答&#xff1a;会二问&#xff1a;哦&#xff0c;讲讲看二答&#xff1a;讲不出来三问&#xff1a;...... 数值求根 二分法Jacobi 迭代法 Jacobi 迭代改进算法&#xff08;事后加速法&#xff09;&#…

频次直方图、KDE和密度图

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

用JMeter对HTTP接口进行压测(一)压测脚本的书写、调试思路

文章目录 安装JMeter和Groovy为什么选择Groovy&#xff1f; 压测需求以及思路准备JMeter脚本以及脚本正确性验证使用Test Script Recorder来获取整条业务线上涉及的接口为什么使用Test Script Recorder&#xff1f; 配置Test Script Recorder对接口进行动态化处理处理全局变量以…

几种开源协议的区别(Apache、MIT、BSD、MPL、GPL、LGPL)

作为一名软件开发人员&#xff0c;你一定也是经常接触到开源软件&#xff0c;但你真的就了解这些开源软件使用的开源许可协议吗&#xff1f; 你不会真的认为&#xff0c;开源就是完全免费吧&#xff1f;那么让我们通过本文来寻找答案。 一、开源许可协议简述 开源许可协议是指开…

Iphone文件传到电脑用什么软件,看这里

在数字化时代&#xff0c;文件传输已经成为我们日常生活中不可或缺的一部分。然而&#xff0c;苹果用户在将手机文件传输到电脑时&#xff0c;往往会面临一些困扰。曾经的“文件传输助手”并不能完全满足用户的需求。于是&#xff0c;很多人开始寻找更便捷的解决方案。在本文中…

在Ubuntu上通过Portainer部署微服务项目

这篇文章主要记录自己在ubuntu上部署自己的微服务应用的过程&#xff0c;文章中使用了docker、docker-compose和portainer&#xff0c;在部署过程中遇到了不少问题&#xff0c;因为博主也是初学docker-compose&#xff0c;通过这次部署实战确实有所收获&#xff0c;在这篇文章一…

lv7 嵌入式开发-网络编程开发 06 socket套接字及TCP的实现框架

目录 1 socket套接字 1.1 体系结构的两种形式 1.2 几种常见的网络编程接口 1.3 socket套接字 2 socket常用API介绍 2.1 API 2.2 地址族结构体 2.3 套接字类型 2.4 socket套接字 3 TCP通信的实现过程 4 练习 1 socket套接字 1.1 体系结构的两种形式 网络的体系结构 …

【Python】如何使用PyInstaller打包自己写好的代码

使用PyInstaller打包自己写好的代码 零、需求 最近接到一个小单&#xff0c;需要批量修改文档内容&#xff0c;用Python做好后要打包成exe程序给客户的Win7电脑使用&#xff0c;此时需要用到PyInstaller打包自己的代码&#xff0c;想到还要有给用户试用的需求&#xff0c;所以…

Springboot+vue的开放性实验室管理系统(有报告)。Javaee项目,springboot vue前后端分离项目。

演示视频&#xff1a; Springbootvue的开放性实验室管理系统&#xff08;有报告&#xff09;。Javaee项目&#xff0c;springboot vue前后端分离项目。 项目介绍&#xff1a; 本文设计了一个基于Springbootvue的前后端分离的开放性实验室管理系统&#xff0c;采用M&#xff08…

【C++设计模式之备忘录模式:行为型】分析及示例

简介 备忘录模式&#xff08;Memento Pattern&#xff09;是一种行为型设计模式&#xff0c;它用于保存和恢复对象的状态。备忘录模式通过将对象的状态封装成一个备忘录&#xff08;Memento&#xff09;&#xff0c;并将备忘录保存在一个管理者&#xff08;Caretaker&#xff…

阿里云GPU服务器新手购买流程(图文详解)

阿里云GPU服务器怎么购买&#xff1f;GPU云服务器购买流程很简单&#xff0c;在云服务器ECS页面&#xff0c;架构选择“GPU/FPGA/ASIC”&#xff0c;即可购买&#xff0c;阿里云服务器网分享阿里云GPU服务器购买流程&#xff0c;新手入门教程&#xff1a; 阿里云GPU服务器购买…

SpringCloud-Bus

接上文 SpringCloud-消息组件 1 注册Bus Bus需要基于一个具体的消息队列实现&#xff0c;比如RabbitMQ.还使用最开始的服务拆分项目&#xff0c;比如现在借阅服务的某个接口调用时&#xff0c;能给用户服务和图书服务发送一个通知。 首先父项目导入SpringCloud依赖 <depend…

C/C++学习 -- SHA-256算法

SHA-256算法概述 SHA-256代表"Secure Hash Algorithm 256-bit"&#xff0c;是一种安全的哈希算法&#xff0c;输出固定长度的256位&#xff08;32字节&#xff09;哈希值。SHA-256被广泛用于加密、数字签名、密码学以及区块链等领域&#xff0c;因为它提供了高度的安…

黑马点评-02使用Redis代替session,Redis + token机制实现

Redis代替session session共享问题 每个Tomcat中都有一份属于自己的session,所以多台Tomcat并不共享session存储空间,当请求切换到不同tomcat服务时可能会导致数据丢失 用户第一次访问1号tomcat并把自己的信息存放session域中, 如果第二次访问到了2号tomcat就无法获取到在1号…