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

如若内容造成侵权/违法违规/事实不符,请联系一条长河网进行投诉反馈,一经查实,立即删除!

相关文章

spark on hive

需要提前搭建好hive&#xff0c;并对hive进行配置。 1、将hive的配置文件添加到spark的目录下 cp $HIVE_HOME/conf/hive-site.xml $SPARK_HOME/conf2、开启hive的hivemetastore服务 提前创建好启动日志存放路径 mkdir $HIVE_HOME/logStart nohup /usr/local/lib/apache-hi…

Vue中如何进行拖拽与排序功能实现

在Vue中实现拖拽与排序功能 在Web应用程序中&#xff0c;实现拖拽和排序功能是非常常见的需求&#xff0c;特别是在管理界面、任务列表和图形用户界面等方面。Vue.js作为一个流行的JavaScript框架&#xff0c;提供了许多工具和库来简化拖拽和排序功能的实现。本文将介绍如何使…

蓝桥杯每日一题2023.10.6

题目描述 门牌制作 - 蓝桥云课 (lanqiao.cn) 题目分析 #include<bits/stdc.h> using namespace std; int ans; int main() {for(int i 1; i < 2020; i ){int x i;while(x){int a x % 10;if(a 2)ans ;x / 10;}}cout << ans;return 0; } 题目描述 既约分数…

pytorch函数reshape()和view()的区别及张量连续性

目录 1.view() 2.reshape() 3.引用和副本&#xff1a; 4.区别 5.总结 在PyTorch中&#xff0c;tensor可以使用两种方法来改变其形状&#xff1a;view()和reshape()。这两种方法的作用是相当类似的&#xff0c;但是它们在实现上有一些细微的区别。 1.view() view()方法是…

MD5 绕过第三式:ffifdyop

文章目录 参考环境推荐阅读雾现两个 PHP 文件表结构分析 雾散ASCII 编码二进制数据到 ASCII 文本的转化绕过原理ffifdyop绕过 ffifdyop 的批量化生产批量化生产注意事项细节一字之差运算符优先级 实际需要遵守的规则 生产机器 参考 项目描述搜索引擎Bing、GoogleAI 大模型文心…

Redis缓存设计与性能优化

文章目录 一、缓存穿透二、缓存失效(击穿)三、缓存雪崩四、热点缓存key重建优化五、缓存与数据库双写不一致六、开发规范与性能优化键值设计key名设计value设计 命令使用客户端使用系统内核参数优化vm.swapinessvm.overcommit_memory(默认0)合理设置文件句柄数慢查询日志&#…

PbootCMS SQL注入漏洞

漏洞复现 访问漏洞url 数据库是mysql 构造payload&#xff0c;条件为假时&#xff0c;未查到任何数据 http://x.x.x/index.php?search 1select 0页面回显 构造payload&#xff0c;条件为真时&#xff0c;查询到数据 1select1文笔生疏&#xff0c;措辞浅薄&#xff0c;望各…

基于微信小程序的付费自习室

博主介绍&#xff1a;✌程序员徐师兄、7年大厂程序员经历。全网粉丝30W、csdn博客专家、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java技术领域和毕业项目实战✌ 文章目录 1 简介2 技术栈3 需求分析3.1用户需求分析3.1.1 学生用户3.1.3 管理员用户 4 数据库设计4.4.1 E…

Promise击鼓传花

Promise击鼓传花 Promise系列导航前言一、Promise.prototype.then()1.语法2.代码及说明&#xff08;1&#xff09;代码段&#xff1a;&#xff08;2&#xff09;代码段&#xff1a;&#xff08;3&#xff09;代码段&#xff1a;&#xff08;4&#xff09;代码段&#xff1a;&am…

mysql5.7停止维护时间

mysql5.7将于2023年10月停止官网支持和更新&#xff1b;老项目要准备升级&#xff0c;新项目的mysql必须是mysql8.0&#xff08;2023-10&#xff09; 官方升级咨询地址 oracle官方升级咨询地址https://go.oracle.com/LP116153?elq_mid247718&sh1518132002061316121320310…

【小沐学Python】Python实现Web图表功能(Dash)

文章目录 1、简介2、安装3、功能示例3.1 Hello World3.2 连接到数据3.3 可视化数据3.4 控件和回调3.5 设置应用的样式3.5.1 HTML and CSS3.5.2 Dash Design Kit (DDK)3.5.3 Dash Bootstrap Components3.5.4 Dash Mantine Components 4、更多示例4.1 Basic Dashboard4.2 Using C…

网页版”高德地图“如何设置默认城市?

问题&#xff1a; 每次打开网页版高德地图时默认定位的都是“北京”&#xff0c;想设置起始点为目前本人所在城市&#xff0c;烦恼的是高德地图默认的初始位置是北京。 解决&#xff1a; 目前网页版高德地图暂不支持设置起始点&#xff0c;打开默认都是北京&#xff0c;只能将…

高通camx开源部分学习简介

camera整体框架 sensor 上电&#xff0c;通过 MIPI协议传输&#xff0c;得到RAW图像数据。RAW图像数据经过ISP处理&#xff0c;得到YUV图像数据。YUV图像数据再经过DMA传输到DDR内存中&#xff0c;DDR内存也就是上图中标识的HOST。每个厂家的 ISP原理和功能大致相同&#xff0c…

网络初识必知会

局域网&#xff1a;把一些设备通过交换机/路由器连接起来 广域网&#xff1a;把更多的局域网也相互连接&#xff0c;当网络规模足够大的 交换机&#xff1a;组网过程中的重要设备&#xff01; 路由器&#xff1a;组网过程中的重要设备&#xff01; IP地址&#xff1a;描述一…

基于transformer的心脑血管心脏病疾病预测

视频讲解:基于transformer的心脑血管疾病预测 完整数据代码分享_哔哩哔哩_bilibili 数据展示: 完整代码: # pip install openpyxl -i https://pypi.tuna.tsinghua.edu.cn/simple/ # pip install optuna -i https://pypi.tuna.tsinghua.edu.cn/simple/ import numpy as np …

CentOS 7 上编译和安装 SQLite 3.9.0

文章目录 可能报错分析详细安装过程 可能报错分析 报错如下&#xff1a; django.core.exceptions.ImproperlyConfigured: SQLite 3.9.0 or later is required (found 3.7.17). 原因&#xff1a;版本为3.7.太低了&#xff0c;需要升级到3.9.0至少 详细安装过程 1.安装所需的…

【MySQL】索引特性

目录 MySQL索引特性 索引的概念 认识磁盘 磁盘的结构 磁盘的随机访问&#xff08;Random Access&#xff09;与连续访问&#xff08;Sequential Access&#xff09; MySQL与磁盘交互的基本单位 索引的理解 观察主键索引现象 推导主键索引结构的构建 索引结构可以采用…

overleaf在线编辑工具使用教程

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

数据结构P46(2-1~2-4)

2-1编写算法查找顺序表中值最小的结点&#xff0c;并删除该结点 #include <stdio.h> #include <stdlib.h> typedef int DataType; struct List {int Max;//最大元素 int n;//实际元素个数 DataType *elem;//首地址 }; typedef struct List*SeqList;//顺序表类型定…

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

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