文章目录

  • 一、导入黑马点评项目
    • 1. 导入SQL
    • 2. 前后端分离
    • 3. 导入后端项目
      • 3.1 将后端项目导入到 Idea 中
      • 3.2 注意:修改application.yaml文件中的mysql、redis地址信息
      • 3.3 启动项目
    • 4. 导入前端项目
      • 4.1 导入nginx文件夹
      • 4.2 运行前端项目
  • 二、基于Session实现登录流程
    • 1. 发送短信验证码
    • 2. 短信验证码登录、注册
    • 3. 登录验证功能
  • 三、集群的session共享问题
  • 四、基于Redis实现共享session的登录功能
    • 1. 选择合适的数据结构存入Redis
    • 2. 发送短信验证码
    • 3. 短信验证码登录、注册
    • 4. 解决token刷新问题

一、导入黑马点评项目

  • 黑马点评项目主要包括以下功能:

  • 这一章主要介绍短信登录功能,短信登录功能是基于Redis的共享session实现的

1. 导入SQL

  • 需要项目资料的私信我

其中的表有:

  • tb_user:用户表
  • tb_user_info:用户详情表
  • tb_shop:商户信息表
  • tb_shop_type:商户类型表
  • tb_blog:用户日记表(达人探店日记)
  • tb_follow:用户关注表
  • tb_voucher:优惠券表
  • tb_voucher_order:优惠券的订单表

注意:Mysql的版本采用5.7及以上版本

2. 前后端分离

3. 导入后端项目

3.1 将后端项目导入到 Idea 中

3.2 注意:修改application.yaml文件中的mysql、redis地址信息

  • 将mysql、redis地址信息修改为自己的信息

3.3 启动项目

  • 启动项目后,在浏览器访问:http://localhost:8081/shop-type/list ,如果可以看到数据则证明运行没有问题

4. 导入前端项目

4.1 导入nginx文件夹

  • 将nginx文件夹复制到任意目录,要确保该目录不包含中文、特殊字符和空格,例如:

4.2 运行前端项目

  • 在nginx所在目录下打开一个CMD窗口,输入命令启动nginx:
start nginx.exe

  • 打开chrome浏览器,在空白页面点击鼠标右键,选择检查,即可打开开发者工具:
  • 然后访问: http://127.0.0.1:8080 ,即可看到页面:

二、基于Session实现登录流程

  • 后端将生成的验证码和用户信息保存到session中,并将sessionId返回给前端保存到cookie中
  • 用户登录时,会携带cookie向后端发起请求,后端进行校验时,从cookie中获取sessionId,通过sessionId可以从session中获取用户信息并保存到ThreadLocal中
  • 后续每个线程都有一份ThreadLocal中的用户副本信息,不同线程拿到用户信息后可以实现不同的操作,从而起到线程隔离作用

1. 发送短信验证码

主要代码:

@Slf4j
@RestController
@RequestMapping("/user")
public class UserController {@Resourceprivate IUserService userService;/*** 发送手机验证码*/@PostMapping("code")public Result sendCode(@RequestParam("phone") String phone, HttpSession session) {// 发送短信验证码并保存验证码return userService.sendCode(phone, session);}
}
@Service
@Slf4j
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements IUserService {@Overridepublic Result sendCode(String phone, HttpSession session) {// 1.使用工具类校验手机号if (RegexUtils.isPhoneInvalid(phone)) {// 2.如果不符合,返回错误信息return Result.fail("手机号格式错误!");}// 3.符合,生成验证码String code = RandomUtil.randomNumbers(6);// 4.保存验证码到 sessionsession.setAttribute("code",code);// 5.模拟发送验证码log.debug("发送短信验证码成功,验证码:{}", code);// 返回okreturn Result.ok();}
}

2. 短信验证码登录、注册

主要代码:

  • UserController
/*** 登录功能* @param loginForm 登录参数,包含手机号、验证码;或者手机号、密码*/
@PostMapping("/login")
public Result login(@RequestBody LoginFormDTO loginForm, HttpSession session){// 实现登录功能return userService.login(loginForm, session);
}
  • UserServiceImpl
@Override
public Result login(LoginFormDTO loginForm, HttpSession session) {// 1.校验手机号String phone = loginForm.getPhone();if (RegexUtils.isPhoneInvalid(phone)) {// 如果不符合,返回错误信息return Result.fail("手机号格式错误!");}// 2.校验验证码Object cacheCode = session.getAttribute("code");String code = loginForm.getCode();if (cacheCode == null || !cacheCode.toString().equals(code)) {// 3.验证码不一致,则报错return Result.fail("验证码错误");}// 4.验证码一致,根据手机号查询用户User user = query().eq("phone", phone).one();// 5.判断用户是否存在if (user == null) {// 6.用户不存在,则创建用户并保存user = createUserWithPhone(phone);}// 7.保存用户信息到session中,UserDTO只包含简单的用户信息,// 而不是完整的User,这样可以隐藏用户的敏感信息(例如:密码等),还能减少内存使用session.setAttribute("user", BeanUtil.copyProperties(user, UserDTO.class));// 8.返回okreturn Result.ok();
}private User createUserWithPhone(String phone) {// 1.创建用户User user = new User();user.setPhone(phone);// 随机设置昵称 user_mrkuw05lokuser.setNickName(SystemConstants.USER_NICK_NAME_PREFIX + RandomUtil.randomString(10));// 2.保存用户save(user);return user;
}

3. 登录验证功能

  • 用户请求登录时,会携带cookie,cookie中包含JSEESIONID
  • 为了避免用户请求每个controller时,每次都去校验用户信息,所以可以加拦截器
  • 拦截器只需在用户请求访问时,校验一次后将用户信息保存到ThreadLocal中,供后续线程使用

主要代码:

  • 在工具类中编写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();}
}
  • 在工具类中编写登录拦截器
public class LoginInterceptor implements HandlerInterceptor {/*** 前置拦截* @param request* @param response* @param handler* @return* @throws Exception*/@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.存在,保存用户信息到ThreadLocalUserHolder.saveUser((User)user);// 6.放行return true;}/*** 后置拦截器* @param request* @param response* @param handler* @param ex* @throws Exception*/@Overridepublic void afterCompletion(HttpServletRequest request, HttpServletResponse response,Object handler, Exception ex) throws Exception {// 请求结束后移除用户,防止ThreadLocal造成内存泄漏UserHolder.removeUser();}
}
  • 在配置类中添加拦截器配置类
@Configuration
public class MvcConfig implements WebMvcConfigurer {/*** 添加拦截器* @param registry*/@Overridepublic void addInterceptors(InterceptorRegistry registry) {// 登录拦截器registry.addInterceptor(new LoginInterceptor())// 排除不需要拦截的路径.excludePathPatterns("/shop/**","/voucher/**","/shop-type/**","/upload/**","/blog/hot","/user/code","/user/login");}
}
  • UserController
@GetMapping("/me")public Result me(){// 获取当前登录的用户并返回UserDTO user = UserHolder.getUser();return Result.ok(user);}

三、集群的session共享问题

四、基于Redis实现共享session的登录功能

1. 选择合适的数据结构存入Redis

  • 手机号作为key,String类型的验证码作为value
  • 用户登录时正好会提交手机号,方便通过Redis进行校验验证码
  • token作为key,Hash类型的用户信息作为value
  • 后端校验成功后,会返回token给前端,前端会将token保存到sessionStorage中(这是浏览器的存储方式),以后前端每次请求都会携带token,方便后端通过Redis校验用户信息

    前端代码:
  • 将后端返回的token保存到sessionStorage中
  • 前端每次请求时,都会通过拦截器将token设置到请求头中,赋值给变量authorization,后端通过authorization获取前端携带的token进行校验

2. 发送短信验证码

  • 修改之前代码,将验证码存入Redis
@Service
@Slf4j
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements IUserService {@Resourceprivate StringRedisTemplate stringRedisTemplate;@Overridepublic Result sendCode(String phone, HttpSession session) {// 1.使用工具类校验手机号if (RegexUtils.isPhoneInvalid(phone)) {// 2.如果不符合,返回错误信息return Result.fail("手机号格式错误!");}// 3.符合,生成验证码String code = RandomUtil.randomNumbers(6);// 4.保存验证码到 session
//        session.setAttribute("code",code);// 4.保存验证码到 redis// "login:code:"是业务前缀,以"login:code:" + 手机号为key,过期时间2分钟stringRedisTemplate.opsForValue().set(RedisConstants.LOGIN_CODE_KEY + phone, code, RedisConstants.LOGIN_CODE_TTL, TimeUnit.MINUTES);// 5.模拟发送验证码log.debug("发送短信验证码成功,验证码:{}", code);// 返回okreturn Result.ok();}
}

3. 短信验证码登录、注册

  • 修改之前代码,从Redis获取验证码并校验
  • 随机生成token,保存用户信息到redis中,返回token
@Override
public Result login(LoginFormDTO loginForm, HttpSession session) {// 1.校验手机号String phone = loginForm.getPhone();if (RegexUtils.isPhoneInvalid(phone)) {// 如果不符合,返回错误信息return Result.fail("手机号格式错误!");}//        // 2.校验验证码
//        Object cacheCode = session.getAttribute("code");
//        String code = loginForm.getCode();
//        if (cacheCode == null || !cacheCode.toString().equals(code)) {//            // 3.验证码不一致,则报错
//            return Result.fail("验证码错误");
//        }// 2.从Redis获取验证码并校验String cacheCode = stringRedisTemplate.opsForValue().get(RedisConstants.LOGIN_CODE_KEY + phone);String code = loginForm.getCode();if (cacheCode == null || !cacheCode.equals(code)) {// 3.验证码不一致,则报错return Result.fail("验证码错误");}// 4.验证码一致,根据手机号查询用户User user = query().eq("phone", phone).one();// 5.判断用户是否存在if (user == null) {// 6.用户不存在,则创建用户并保存user = createUserWithPhone(phone);}//        // 7.保存用户信息到session中,UserDTO只包含简单的用户信息,而不是完整的User,这样可以隐藏用户的敏感信息(例如:密码等),还能减少内存使用
//        session.setAttribute("user", BeanUtil.copyProperties(user, UserDTO.class));// 7.保存用户信息到redis中// 7.1随机生成token,作为登录令牌// 使用hutool工具中的UUID,true表示不带“-”符号的UUIDString token = UUID.randomUUID().toString(true);// 7.2将User对象转为Hash类型进行存储UserDTO userDTO = BeanUtil.copyProperties(user, UserDTO.class);   // 由于使用的是stringRedisTemplate,所以存入的value中的值必须都是String类型的// 但是UserDTO中的id是Long类型的,所以进行对象属性拷贝时,需要自定义实现转换规则Map<String, Object> userMap = BeanUtil.beanToMap(userDTO, new HashMap<>(),CopyOptions.create().setIgnoreNullValue(true).setIgnoreNullValue(true).setFieldValueEditor((fieldName, fieldValue) -> fieldValue.toString()));// 7.3存入redis, "login:token:"是业务前缀,以 "login:token:" + token作为keystringRedisTemplate.opsForHash().putAll(RedisConstants.LOGIN_USER_KEY + token, userMap);// 7.4设置token有效期,有效期为30分钟stringRedisTemplate.expire(RedisConstants.LOGIN_USER_KEY + token, RedisConstants.LOGIN_USER_TTL, TimeUnit.MINUTES);// 8.返回tokenreturn Result.ok(token);
}private User createUserWithPhone(String phone) {// 1.创建用户User user = new User();user.setPhone(phone);// 随机设置昵称 user_mrkuw05lokuser.setNickName(SystemConstants.USER_NICK_NAME_PREFIX + RandomUtil.randomString(10));// 2.保存用户save(user);return user;
}

4. 解决token刷新问题

  • token刷新问题是指,用户长时间不进行界面操作时,到了过期时间,token自动失效;但是,用户一旦进行操作,就需要给token续期,即更新token过期时间
  • 为了解决token刷新问题,需要加2个拦截器
  • 第一个拦截器可以拦截所有请求,只要用户有请求就刷新token,并保存用户信息到ThreadLocal中
  • 第二个拦截器只对登录请求进行拦截,从ThreadLocal中获取用户信息进行校验

刷新token的拦截器代码:

public class RefreshTokenInterceptor implements HandlerInterceptor {// 因为LoginInterceptor不是通过Spring进行管理的Bean,所以不能再LoginInterceptor中进行注入StringRedisTemplate// 可以通过构造方法传入StringRedisTemplateprivate StringRedisTemplate stringRedisTemplate;public RefreshTokenInterceptor(StringRedisTemplate stringRedisTemplate) {this.stringRedisTemplate = stringRedisTemplate;}/*** 前置拦截* @param request* @param response* @param handler* @return* @throws Exception*/@Overridepublic 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.存在,保存用户信息到ThreadLocal
//        UserHolder.saveUser((UserDTO)user);
//        // 6.放行
//        return true;// 1.获取请求头中的tokenString token = request.getHeader("authorization");if (StrUtil.isBlank(token)) {// 不存在,则拦截,返回401状态码response.setStatus(401);return false;}// 2.通过token获取redis中的用户Map<Object, Object> userMap = stringRedisTemplate.opsForHash().entries(RedisConstants.LOGIN_USER_KEY + token);// 3.判断用户是否存在if (userMap.isEmpty()) {// 4.用户不存在,则拦截,返回401状态码response.setStatus(401);return false;}// 5.将redis中Hash类型数据转换成UserDTO对象UserDTO userDTO = BeanUtil.fillBeanWithMap(userMap, new UserDTO(), false);// 6.用户存在,保存用户信息到ThreadLocalUserHolder.saveUser(userDTO);// 7.刷新token有效期stringRedisTemplate.expire(RedisConstants.LOGIN_USER_KEY + token, RedisConstants.LOGIN_USER_TTL, TimeUnit.MINUTES);// 8.放行return true;}/*** 后置拦截器* @param request* @param response* @param handler* @param ex* @throws Exception*/@Overridepublic void afterCompletion(HttpServletRequest request, HttpServletResponse response,Object handler, Exception ex) throws Exception {// 请求结束后移除用户,防止ThreadLocal造成内存泄漏UserHolder.removeUser();}
}

登录拦截器的代码:

public class LoginInterceptor implements HandlerInterceptor {@Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {// 1.判断是否需要拦截(ThreadLocal中是否有用户)if (UserHolder.getUser() == null) {// 没有,需要拦截,设置状态码response.setStatus(401);// 拦截return false;}// 有用户,则放行return true;}}
@Configuration
public class MvcConfig implements WebMvcConfigurer {@Resourceprivate StringRedisTemplate stringRedisTemplate;/*** 添加拦截器* @param registry*/@Overridepublic void addInterceptors(InterceptorRegistry registry) {// 登录拦截器registry.addInterceptor(new LoginInterceptor())// 排除不需要拦截的路径.excludePathPatterns("/shop/**","/voucher/**","/shop-type/**","/upload/**","/blog/hot","/user/code","/user/login").order(1);// token刷新的拦截器,order越小,执行优先级越高,所以token刷新的拦截器先执行registry.addInterceptor(new RefreshTokenInterceptor(stringRedisTemplate)).addPathPatterns("/**").excludePathPatterns(// RefreshTokenInterceptor拦截器也需要放行"/user/code","/user/login",不然token过期后再重新登录就会一直被拦截"/user/code","/user/login").order(0);}
}

Redis(五) - Redis企业实战之短信登录相关推荐

  1. 黑马点评Redis实战(短信登录;商户查询缓存)

    黑马点评 通过一个类似于大众点评的项目了解学习redis在实战项目中的使用,下面是项目中会涉及到的模块: 一.导入黑马点评项目 导入springboot项目,导入sql脚本到数据库,开启nginx,更 ...

  2. redis的实战项目01_模拟短信登录业务

    redis的实战项目01_短信登录 一.导入黑马点评项目 1. 数据库: 2. 单体项目介绍: 二.基于session实现登录 1.发送短信验证码 1.理论流程 2.代码操作: 2.短信验证码登录.注 ...

  3. 《Redis实战篇》一、短信登录

    1.1.导入黑马点评项目 1.1.1 .导入SQL 1.1.2.有关当前模型 手机或者app端发起请求,请求我们的nginx服务器,nginx基于七层模型走的事HTTP协议,可以实现基于Lua直接绕开 ...

  4. 1.Redis实战—短信登录

    短信登录: 基于Redis缓存: (1)发送短信验证码: 实现逻辑 : 先校验手机号 , 不符合 直接返回错误信息 , 使用的是封装的返回前端的方法 符合 , 生成一个随机验证码 , 使用的是huTo ...

  5. Redis实战——短信登录

    目录 1 基于Seesion实现短信登录 1.1 发送短信验证码 1.2 登录功能 2 使用Redis进行短信验证码校验登录 2.1 Seesion方法存在的问题 2.2 发送短信验证码 2.3  验 ...

  6. Redis框架(三):大众点评项目 基于Session的短信登录

    大众点评项目 基于Session的短信登录 需求:基于Session实现短信验证登录 基于Session的短信登录 发送手机验证码 实现登录 (注意MyBatisP的接口使用) 新的问题 Spring ...

  7. 24、短信登录(基于redis实现短信登录)

    短信登录(基于redis实现短信登录) 修改代码(之前的基于session) 发送验证的逻辑:(更改就是将短信验证码存到redis中) 第一步:注入SrtingRedisTemplate (users ...

  8. 最新企业春节祝福短信模板内容_2022年创意公司拜年短信文案

    企业春节祝福短信模板 新年快来到前,你幸福快乐是我的心"元",愿你烦恼忧愁都跑"元",好运频频来支"元",好事"元元"不 ...

  9. 为什么越来越多的企业使用106短信平台?

    短信是手机用户的必需品,经常会收到通知.活动.认证码等信息.而如今,很多企业使用106短信平台越来越广泛,那么,为什么越来越多的企业使用106短信平台?下面,就跟着摩杜云小杜一起来看看吧. 为什么越来 ...

最新文章

  1. 前OnePlus视觉设计师蔡孝永:视觉设计师都在想些什么?
  2. Mac下使用docker下载nginx并挂载文件
  3. python__画图表可参考(转自:寒小阳 逻辑回归应用之Kaggle泰坦尼克之灾)
  4. matlab jpeg 工具包,安装Matlab JPEG Toolbox
  5. linux 取文件字节数,如何在Linux上的C中获取文件中的字符数(而不是字节数)
  6. 80%的销售来源于第4至11次的跟踪!
  7. Hive高级查询(group by、 order by、 join等)
  8. HTML iframe标签下 子页面调用父页面js 容易产生的跨域调用问题 Uncaught DOMException
  9. 分享铝合金车身的焊接和修复技巧,建议收藏!!!
  10. [ delphi ] AES-256-ECB 加密、解密算法控件说明
  11. 思想实验及其在科学发展中的作用
  12. el-pagination分页自定义前往第几页样式(下拉框形式)
  13. G - Tiling
  14. python_docx读取word的内容
  15. 基于成交历史的交易播放器
  16. C语言-- 输出大写英文字母
  17. flex布局:携程网移动端首页案例
  18. 游戏 | python打包游戏为exe可执行文件
  19. [目标检测]CenterNet
  20. 时间序列模型算法 - Prophet,LSTM(二)

热门文章

  1. Oracle之SELECT语句
  2. 根据WSDL编写JAVA调用
  3. bundle打包自动转换tiff格式的处理方法
  4. Sql(presto语法) 实现行转列和列转行
  5. 简易 Python 脚本查询嵊泗船票
  6. 使用插桩技术解决慢查询测试问题
  7. gdal支持Hammer投影的做法
  8. onbeforeunload 事件详细介绍
  9. 对抗假消息:虚假截图制作工具
  10. 项目十二 架设单位内部FTP服务器