IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 大数据 -> 仿黑马点评-redis整合【邮件登陆部分】 -> 正文阅读

[大数据]仿黑马点评-redis整合【邮件登陆部分】

前言
👏作者简介:我是笑霸final,一名热爱技术的在校学生。
📝个人主页:个人主页1 || 笑霸final的主页2
📕系列专栏:《项目专栏》
📧如果文章知识点有错误的地方,请指正!和大家一起学习,一起进步👀
🔥如果感觉博主的文章还不错的话,👍点赞👍 + 👀关注👀 + 🤏收藏🤏

在这里插入图片描述

一、简介

课程介绍请添加图片描述
项目地址(资料都在这里):
gitee仓库
在这里插入图片描述
数据库相关信息
请添加图片描述
项目架构
请添加图片描述

🐉项目短信登陆(修改成邮件登陆)


如何使用邮件登陆 详细请看此仿瑞吉外卖 【手机登陆功能换成邮件登陆】


二、基于session实现

流程
在这里插入图片描述

🐉发送验证码

请求
在这里插入图片描述


代码步骤

  • 1导入坐标
 <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-mail</artifactId>
    </dependency>

提前注入对象

 @Autowired
    private JavaMailSender mailSender;//邮件

    @Value("${spring.mail.username}")
    private String MyFrom;

SimpleMailMessage 详细说明

SimpleMailMessage message = new SimpleMailMessage();
message.setFrom();//发送人

message.setTo();//谁要接收

message.setSubject("");//邮件标题

message.setText();邮件内容

controller代码

 /**
     * 发送邮件验证码
     */
    @PostMapping("code")
    public Result sendCode(@RequestParam("phone") String phone, HttpSession session) {

        // 验证邮箱号
        if (RegexUtils.isEmailInvalid(phone)) {
            return Result.fail("邮箱格式错误");
        }
        //验证码生成器
        String code = RandomUtil.randomNumbers(6);

        // TODO 发送短信验证码并保存验证码
        SimpleMailMessage message = new SimpleMailMessage();
        message.setFrom(MyFrom);//发送人

        message.setTo(phone);//谁要接收

        message.setSubject("验证码");//邮件标题

        message.setText("您的验证码是 \n" +code);//邮件内容

        try {
            //发生验证码
            mailSender.send(message);
            //需要保存一下验证码,后面用来验证
            session.setAttribute(phone,code);
            System.out.println("==========");
            log.info(code);
            System.out.println("==========");

        } catch (MailException e) {
            e.printStackTrace();
            return Result.fail("邮箱发生失败");
        }

        return Result.ok();
    }

🐉 邮件登陆(短信登陆)

前端请求图

在这里插入图片描述

在这里插入图片描述同时还有密码登陆所以我们是单独用个tdo
但是这里我们先不管密码登陆
在这里插入图片描述
请求 URL http://localhost:8080/api/user/login

代码

/**
     * 登录功能
     * @param loginForm 登录参数,包含手机号、验证码;或者手机号、密码
     */
    @PostMapping("/login")
    public Result login(@RequestBody LoginFormDTO loginForm, HttpSession session){
        // TODO 实现登录功能

        //1 先验证邮箱
        String phone = loginForm.getPhone();
        if (RegexUtils.isEmailInvalid(phone)) {
            return Result.fail("邮箱格式错误");
        }
        //2 在验证验证码
        Object cacheCode = session.getAttribute(phone);
        String code = loginForm.getCode();
        //注意String类型的 不能用==、!=来判断是否相等
        if(  cacheCode==null || !cacheCode.toString().equals(code)){
            return Result.fail("验证码错误");
        }

        //3.查数据库存在此手机号?
        User user = userService.findByPone(phone);
        // 3.1 不存在 创建新用户
        if(user==null){
            //存入数据库
            user=userService.creatUser(phone);
        }
        // 3.2 存入session
        session.setAttribute("user",user);

        return Result.ok();
    }

三、基于redis

在这里插入图片描述

🐉发送验证码

发生验证码:

在这里插入图片描述

/**
     * 发送邮件验证码
     */
    @PostMapping("code")
    public Result sendCode(@RequestParam("phone") String phone, HttpSession session) {

        // 验证邮箱号
        if (RegexUtils.isEmailInvalid(phone)) {
            return Result.fail("邮箱格式错误");
        }
        //验证码生成器
        String code = RandomUtil.randomNumbers(6);

        // TODO 发送短信验证码并保存验证码
        SimpleMailMessage message = new SimpleMailMessage();
        message.setFrom(MyFrom);//发送人

        message.setTo(phone);//谁要接收

        message.setSubject("验证码");//邮件标题

        message.setText("您的验证码是 \n" +code);//邮件内容

        try {
            //发生验证码
            mailSender.send(message);
            //需要保存一下验证码,后面用来验证


            //session.setAttribute(phone,code);
            //保存到redis

            stringRedisTemplate.opsForValue().set(LOGIN_CODE_KEY+phone,code,2, TimeUnit.MINUTES);
            System.out.println("==========");
            log.info(code+"======="+session.getAttribute(phone));
            System.out.println("==========");


        } catch (MailException e) {
            e.printStackTrace();
            return Result.fail("邮箱发送失败");
        }

        return Result.ok();
    }

🐉 邮件登陆(短信登陆)

验证登陆功能
login方法会把生成的token返回给前端,浏览器会将其保存到session中。
在这里插入图片描述

我们登陆信息存入redis的user信息应该用 hash结构存储,原因是:

  • 若使用String结构,以JSON字符串来保存,比较直观
  • 但Hash结构可以将对象中的每个字段独立存储,可以针对单个字段做CRUD,并且内存占用更少
/**
     * 登录功能
     * @param loginForm 登录参数,包含手机号、验证码;或者手机号、密码
     */
    @PostMapping("/login")
    public Result login(@RequestBody LoginFormDTO loginForm, HttpSession session){
        // TODO 实现登录功能

        //1 先验证邮箱
        String phone = loginForm.getPhone();
        if (RegexUtils.isEmailInvalid(phone)) {
            return Result.fail("邮箱格式错误");
        }
        //2 在验证验证码
        //Object cacheCode = session.getAttribute(phone);
        // 这里用redis获取
        String cacheCode
                = stringRedisTemplate.opsForValue().get(LOGIN_CODE_KEY + phone);
        String code = loginForm.getCode();
        //注意String类型的 不能用==、!=来判断是否相等
        if(  cacheCode==null || !cacheCode.equals(code)){
            return Result.fail("验证码错误");
        }

        //3.查数据库存在此手机号(邮箱)?
        User user = userService.findByPone(phone);
        // 3.1 不存在 创建新用户
        if(user==null){
            //存入数据库
            user=userService.creatUser(phone);
        }
        //3.1.保存用户信息到redis中
        //3.1随机生成token,作为登陆令牌
        String token = UUID.randomUUID().toString();
        //3.2将User对象转为HashMap存储
        UserDTO userDTO = BeanUtil.copyProperties(user,UserDTO.class);
        final Map<String, Object> map = BeanUtil.beanToMap(userDTO, new HashMap<>(),
                CopyOptions.create().setIgnoreNullValue(true).setFieldValueEditor((fieldName,fieldValue)->{
                    return fieldValue.toString();
                })
        );

        //3.3存储
        stringRedisTemplate.opsForHash().putAll(LOGIN_USER_KEY+token,map);
        //3.4设置token有效期
        stringRedisTemplate.expire(LOGIN_USER_KEY+token,3000,TimeUnit.MINUTES);

        //4.返回token
        return Result.ok(token);

    }

🐉优化

我们每次登陆(浏览网页)都应该去象session一样去刷新有效时间

  • 首先,对于每个请求,我们首先根据token判断用户是否已经登陆(是否已经保存到ThreadLocal中),如果没有登陆,放行交给登陆拦截器去做,如果已经登陆,刷新token的有效期,然后放行。
  • 之后来到登陆拦截器,如果ThreadLocal没有用户,说明没有登陆,拦截,否则放行。请添加图片描述

ThreadLocal的一些说明

Thread类有一个类型为ThreadLocal.ThreadLocalMap的实例变量threadLocals,也就是说每个线程有一个自己的ThreadLocalMap。

  • ThreadLocalMap有自己的独立实现,可以简单地将它的key视作ThreadLocal,value为代码中放入的值(实际上key并不是ThreadLocal本身,而是它的一个弱引用)。
  • 每个线程在往ThreadLocal里放值的时候,都会往自己的ThreadLocalMap里存,读也是以ThreadLocal作为引用,在自己的map里找对应的key,从而实现了线程隔离。
  • ThreadLocalMap有点类似HashMap的结构,只是HashMap是由数组+链表实现的,而ThreadLocalMap中并没有链表结构。
  • 我们还要注意Entry, 它的key是ThreadLocal<?> k ,继承自WeakReference, 也就是我们常说的弱引用类型。

内存泄露问题
由于ThreadLocal的key是弱引用,故在gc时,key会被回收掉,但是value是强引用没有被回收,所以在我们拦截器的方法里必须手动remove()。

官方定义了ThreadLocal工具包
在这里插入图片描述
请添加图片描述

设置拦截器(token拦截器)

public class RefreshTokenInterceptor implements HandlerInterceptor {

    private StringRedisTemplate stringRedisTemplate;
    public RefreshTokenInterceptor(StringRedisTemplate stringRedisTemplate){
        this.stringRedisTemplate=stringRedisTemplate;
    }
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        // TODO 获取请求头中的token
        String token = request.getHeader("authorization");//authorization是前端返回的
        if (StrUtil.isBlank(token)) {
            return true;
        }
        //TODO 获取redis中的token
        Map<Object, Object> userMap = stringRedisTemplate.opsForHash().entries(RedisConstants.LOGIN_USER_KEY + token);//entries获取所有的
        if(userMap.isEmpty()){
            return true;
        }
        //TODO 将查询到的数据转换为UserTdo
        UserDTO userDTO = BeanUtil.fillBeanWithMap(userMap, new UserDTO(),false);
        //TODO 将用户存储到 ThreadLocal
        UserHolder.saveUser(userDTO);
        //刷新
        stringRedisTemplate.expire(RedisConstants.LOGIN_USER_KEY+token,30, TimeUnit.MINUTES);

        return  true;
    }
}

登陆拦截器

public class LoginInterceptor implements HandlerInterceptor {

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        //判断是否需要拦截(TheadLocal是否有用户)
        if (UserHolder.getUser()==null){
            response.setStatus(401);
            return  false;
        }
        //有用户
        return  true;
    }
}

配置拦截器

@Configuration
public class MvcConfig implements WebMvcConfigurer {

    @Autowired
    private StringRedisTemplate stringRedisTemplate;
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(new RefreshTokenInterceptor(stringRedisTemplate))
                        .addPathPatterns("/**").order(0);//拦截所有
        //order(0) 数字越小越先执行
        registry.addInterceptor(new LoginInterceptor())
                .excludePathPatterns(
                        "/shop/**",
                        "/voucher/**",
                        "/shop-type/**",
                        "/upload/**",
                        "/blog/hot",
                        "/user/code",
                        "/user/login"
                ).order(1);
    }
}

四、一些问题

前端有一个/me请求来查询用户是否登陆
在这里插入图片描述

  @GetMapping("/me")
    public Result me(){
        // TODO 获取当前登录的用户并返回
        // 获取当前登录的用户并返回
        UserDTO user = UserHolder.getUser();
        return Result.ok(user);
    }
  大数据 最新文章
实现Kafka至少消费一次
亚马逊云科技:还在苦于ETL?Zero ETL的时代
初探MapReduce
【SpringBoot框架篇】32.基于注解+redis实现
Elasticsearch:如何减少 Elasticsearch 集
Go redis操作
Redis面试题
专题五 Redis高并发场景
基于GBase8s和Calcite的多数据源查询
Redis——底层数据结构原理
上一篇文章      下一篇文章      查看所有文章
加:2022-10-31 12:04:09  更:2022-10-31 12:05:04 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/28 23:40:03-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码