仿黑马点评-redis整合【邮件登陆部分】
创始人
2024-04-02 00:31:00
0

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

在这里插入图片描述

目录

  • 一、简介
    • 🐉项目短信登陆(修改成邮件登陆)
  • 二、基于session实现
    • 🐉发送验证码
    • 🐉 邮件登陆(短信登陆)
  • 三、基于redis
    • 🐉发送验证码
    • 🐉 邮件登陆(短信登陆)
    • 🐉优化
  • 四、一些问题

一、简介

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

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


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


二、基于session实现

流程
在这里插入图片描述

🐉发送验证码

请求
在这里插入图片描述


代码步骤

  • 1导入坐标
 org.springframework.bootspring-boot-starter-mail
  • 邮件配置
    如何使用邮件登陆 详细请看此仿瑞吉外卖 【手机登陆功能换成邮件登陆】

提前注入对象

 @Autowiredprivate 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 存入sessionsession.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);//保存到redisstringRedisTemplate.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 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.返回tokenreturn 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;}@Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {// TODO 获取请求头中的tokenString token = request.getHeader("authorization");//authorization是前端返回的if (StrUtil.isBlank(token)) {return true;}//TODO 获取redis中的tokenMap userMap = stringRedisTemplate.opsForHash().entries(RedisConstants.LOGIN_USER_KEY + token);//entries获取所有的if(userMap.isEmpty()){return true;}//TODO 将查询到的数据转换为UserTdoUserDTO userDTO = BeanUtil.fillBeanWithMap(userMap, new UserDTO(),false);//TODO 将用户存储到 ThreadLocalUserHolder.saveUser(userDTO);//刷新stringRedisTemplate.expire(RedisConstants.LOGIN_USER_KEY+token,30, TimeUnit.MINUTES);return  true;}
}

登陆拦截器

public class LoginInterceptor implements HandlerInterceptor {@Overridepublic 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 {@Autowiredprivate StringRedisTemplate stringRedisTemplate;@Overridepublic 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);}
}

四、一些问题

  • 分享一个免费的redis可视化工具
    https://gitee.com/qishibo/AnotherRedisDesktopManager/releases
    点击跳转
  • 此外此项目前端部分问题解决方法借鉴于一下博文
    点击跳转==>黑马点评项目-短信登录功能

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

  @GetMapping("/me")public Result me(){// TODO 获取当前登录的用户并返回// 获取当前登录的用户并返回UserDTO user = UserHolder.getUser();return Result.ok(user);}

相关内容

热门资讯

保存时出现了1个错误,导致这篇... 当保存文章时出现错误时,可以通过以下步骤解决问题:查看错误信息:查看错误提示信息可以帮助我们了解具体...
汇川伺服电机位置控制模式参数配... 1. 基本控制参数设置 1)设置位置控制模式   2)绝对值位置线性模...
不能访问光猫的的管理页面 光猫是现代家庭宽带网络的重要组成部分,它可以提供高速稳定的网络连接。但是,有时候我们会遇到不能访问光...
表格中数据未显示 当表格中的数据未显示时,可能是由于以下几个原因导致的:HTML代码问题:检查表格的HTML代码是否正...
本地主机上的图像未显示 问题描述:在本地主机上显示图像时,图像未能正常显示。解决方法:以下是一些可能的解决方法,具体取决于问...
表格列调整大小出现问题 问题描述:表格列调整大小出现问题,无法正常调整列宽。解决方法:检查表格的布局方式是否正确。确保表格使...
不一致的条件格式 要解决不一致的条件格式问题,可以按照以下步骤进行:确定条件格式的规则:首先,需要明确条件格式的规则是...
Android|无法访问或保存... 这个问题可能是由于权限设置不正确导致的。您需要在应用程序清单文件中添加以下代码来请求适当的权限:此外...
【NI Multisim 14...   目录 序言 一、工具栏 🍊1.“标准”工具栏 🍊 2.视图工具...
银河麒麟V10SP1高级服务器... 银河麒麟高级服务器操作系统简介: 银河麒麟高级服务器操作系统V10是针对企业级关键业务...