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

这篇具有很好参考价值的文章主要介绍了黑马点评-01基于Redis实现短信登陆的功能。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

环境准备

当前模型

nginx服务器的作用

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

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

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

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

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

黑马点评-01基于Redis实现短信登陆的功能,黑马点评,java,spring,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;  
            # 去掉/api前缀的URL
            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实现登录流程

黑马点评-01基于Redis实现短信登陆的功能,黑马点评,java,spring,redis,后端

实现发送短信验证码功能

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

黑马点评-01基于Redis实现短信登陆的功能,黑马点评,java,spring,redis,后端

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.保存验证码到session
    session.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();
}

实现登录功能

用户点击登录按钮发起请求
黑马点评-01基于Redis实现短信登陆的功能,黑马点评,java,spring,redis,后端

/**
* 登录功能
* @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都需要登录校验,所以我们可以把校验流程写在拦截器中

黑马点评-01基于Redis实现短信登陆的功能,黑马点评,java,spring,redis,后端

第一步; 定义一个工具类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 {
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
       //1.获取session
        HttpSession 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;
    }
    
    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws 			Exception {
        UserHolder.removeUser();
    }
}

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

@Configuration
public class MvcConfig implements WebMvcConfigurer {
    @Override
    public 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 {
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
       //1.获取session
        HttpSession 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.若存在保存用户的隐藏信息到Threadlocal
        UserHolder.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":""
    }
}

登出

用户点击退出登录按钮时,我们需要清除ThreadLocal里存放的用户信息文章来源地址https://www.toymoban.com/news/detail-727997.html

  • 这样前端发送的请求获取不到用户信息,登录拦截器从ThreadLocal中获取用户也为null,被拦截跳到登录界面
    /**
     * 登出功能
     * @return 无
     */
    @PostMapping("/logout")
    public Result logout(){
        // TODO 实现登出功能
        UserHolder.removeUser();
        return Result.ok();
    }

到了这里,关于黑马点评-01基于Redis实现短信登陆的功能的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 基于springboot+Redis的前后端分离项目(九)-【黑马点评】

    🎁🎁资源文件分享 链接:https://pan.baidu.com/s/1189u6u4icQYHg_9_7ovWmA?pwd=eh11 提取码:eh11 GEO就是Geolocation的简写形式,代表地理坐标。Redis在3.2版本中加入了对GEO的支持,允许存储地理坐标信息,帮助我们根据经纬度来检索数据。常见的命令有: GEOADD:添加一个地理空间信息,包含

    2024年02月16日
    浏览(42)
  • 基于springboot+Redis的前后端分离项目(八)-【黑马点评】

    🎁🎁资源文件分享 链接:https://pan.baidu.com/s/1189u6u4icQYHg_9_7ovWmA?pwd=eh11 提取码:eh11 针对用户的操作:可以对用户进行关注和取消关注功能。 实现思路: 需求:基于该表数据结构,实现两个接口: 关注和取关接口 判断是否关注的接口 关注是User之间的关系,是博主与粉丝的

    2024年02月16日
    浏览(45)
  • 基于springboot+Redis的前后端分离项目(二)-【黑马点评】

    🎁🎁资源文件分享 链接:https://pan.baidu.com/s/1189u6u4icQYHg_9_7ovWmA?pwd=eh11 提取码:eh11 缓存( Cache),就是数据交换的 缓冲区 ,俗称的缓存就是 缓冲区内的数据 ,一般从数据库中获取,存储于本地代码。(例如: 由于其被 Static 修饰,所以随着类的加载而被加载到 内存之中 ,作为本地缓存

    2024年02月10日
    浏览(43)
  • 基于springboot+Redis的前后端分离项目之消息队列(六)-【黑马点评】

    🎁🎁资源文件分享 链接:https://pan.baidu.com/s/1189u6u4icQYHg_9_7ovWmA?pwd=eh11 提取码:eh11 我们来回顾一下下单流程 当用户发起请求,此时会请求nginx,nginx会访问到tomcat,而tomcat中的程序,会进行串行操作,分成如下几个步骤 1、查询优惠卷 2、判断秒杀库存是否足够 3、查询订单

    2024年02月12日
    浏览(41)
  • 基于springboot+Redis的前后端分离项目之分布式锁(四)-【黑马点评】

    🎁🎁资源文件分享 链接:https://pan.baidu.com/s/1189u6u4icQYHg_9_7ovWmA?pwd=eh11 提取码:eh11 分布式锁:满足分布式系统或集群模式下多进程可见并且互斥的锁。 分布式锁的核心思想就是让大家都使用同一把锁,只要大家使用的是同一把锁,那么我们就能锁住线程,不让线程进行,让

    2024年02月11日
    浏览(51)
  • 基于springboot+Redis的前后端分离项目之分布式锁-redission(五)-【黑马点评】

    🎁🎁资源文件分享 链接:https://pan.baidu.com/s/1189u6u4icQYHg_9_7ovWmA?pwd=eh11 提取码:eh11 基于setnx实现的分布式锁存在下面的问题: 重入问题 :重入问题是指 获得锁的线程可以再次进入到相同的锁的代码块中,可重入锁的意义在于防止死锁,比如HashTable这样的代码中,他的方法都

    2024年02月11日
    浏览(43)
  • 仿大众点评项目 —— Day01【短信登录、商户查询缓存】

    ❤ 作者主页:欢迎来到我的技术博客😎 ❀ 个人介绍:大家好,本人热衷于 Java后端开发 ,欢迎来交流学习哦!( ̄▽ ̄)~* 🍊 如果文章对您有帮助,记得 关注 、 点赞 、 收藏 、 评论 ⭐️⭐️⭐️ 📣 您的支持将是我创作的动力,让我们一起加油进步吧!!!🎉🎉 1.1 基

    2024年02月13日
    浏览(41)
  • 黑马点评Redis实战(优惠卷秒杀)

    本文是上一篇文章的后续,上一篇文章链接 马点评Redis实战(短信登录;商户查询缓存) id是一个订单必备的属性,而订单的id属性是必须唯一的,首先我们会想到使用数据库主键id,并设置为自增。这样似乎就能满足唯一性。 但是,这样会存在一些问题: id的规律太过明显,因

    2024年02月04日
    浏览(35)
  • 黑马的redis实战篇-短信登录

    目录 四、实战篇-短信登录 4.1 导入黑马点评项目 1、后端: 2、前端 4.2 基于Session实现登录 1、发送验证码 2、短信验证码登录注册 3、校验登录状态 4.3 集群的session共享问题 4.4 基于Redis实现共享session登录 1、发送验证码 2、短信验证码登录注册 3、校验登录状态 项目的架构:

    2023年04月15日
    浏览(85)
  • 如何运行黑马程序员redis项目黑马点评(hm-dianping)、常见报错解决与部分接口的测试方法

    hm-dianping项目仓库地址:https://gitee.com/huyi612/hm-dianping 项目对应教学视频:https://www.bilibili.com/video/BV1cr4y1671t?p=24(p24-p95) hm-dianping项目仓库地址:https://gitee.com/huyi612/hm-dianping 以Navicat为例 1、先新建数据库hmdp 2、导入项目中的hmdp.sql文件 配置Mysql 要注意配置文件中默认的mysql配

    2024年01月17日
    浏览(45)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包