Redis(五) - Redis企业实战之短信登录
文章目录
- 一、导入黑马点评项目
- 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企业实战之短信登录相关推荐
- 黑马点评Redis实战(短信登录;商户查询缓存)
黑马点评 通过一个类似于大众点评的项目了解学习redis在实战项目中的使用,下面是项目中会涉及到的模块: 一.导入黑马点评项目 导入springboot项目,导入sql脚本到数据库,开启nginx,更 ...
- redis的实战项目01_模拟短信登录业务
redis的实战项目01_短信登录 一.导入黑马点评项目 1. 数据库: 2. 单体项目介绍: 二.基于session实现登录 1.发送短信验证码 1.理论流程 2.代码操作: 2.短信验证码登录.注 ...
- 《Redis实战篇》一、短信登录
1.1.导入黑马点评项目 1.1.1 .导入SQL 1.1.2.有关当前模型 手机或者app端发起请求,请求我们的nginx服务器,nginx基于七层模型走的事HTTP协议,可以实现基于Lua直接绕开 ...
- 1.Redis实战—短信登录
短信登录: 基于Redis缓存: (1)发送短信验证码: 实现逻辑 : 先校验手机号 , 不符合 直接返回错误信息 , 使用的是封装的返回前端的方法 符合 , 生成一个随机验证码 , 使用的是huTo ...
- Redis实战——短信登录
目录 1 基于Seesion实现短信登录 1.1 发送短信验证码 1.2 登录功能 2 使用Redis进行短信验证码校验登录 2.1 Seesion方法存在的问题 2.2 发送短信验证码 2.3 验 ...
- Redis框架(三):大众点评项目 基于Session的短信登录
大众点评项目 基于Session的短信登录 需求:基于Session实现短信验证登录 基于Session的短信登录 发送手机验证码 实现登录 (注意MyBatisP的接口使用) 新的问题 Spring ...
- 24、短信登录(基于redis实现短信登录)
短信登录(基于redis实现短信登录) 修改代码(之前的基于session) 发送验证的逻辑:(更改就是将短信验证码存到redis中) 第一步:注入SrtingRedisTemplate (users ...
- 最新企业春节祝福短信模板内容_2022年创意公司拜年短信文案
企业春节祝福短信模板 新年快来到前,你幸福快乐是我的心"元",愿你烦恼忧愁都跑"元",好运频频来支"元",好事"元元"不 ...
- 为什么越来越多的企业使用106短信平台?
短信是手机用户的必需品,经常会收到通知.活动.认证码等信息.而如今,很多企业使用106短信平台越来越广泛,那么,为什么越来越多的企业使用106短信平台?下面,就跟着摩杜云小杜一起来看看吧. 为什么越来 ...
最新文章
- 前OnePlus视觉设计师蔡孝永:视觉设计师都在想些什么?
- Mac下使用docker下载nginx并挂载文件
- python__画图表可参考(转自:寒小阳 逻辑回归应用之Kaggle泰坦尼克之灾)
- matlab jpeg 工具包,安装Matlab JPEG Toolbox
- linux 取文件字节数,如何在Linux上的C中获取文件中的字符数(而不是字节数)
- 80%的销售来源于第4至11次的跟踪!
- Hive高级查询(group by、 order by、 join等)
- HTML iframe标签下 子页面调用父页面js 容易产生的跨域调用问题 Uncaught DOMException
- 分享铝合金车身的焊接和修复技巧,建议收藏!!!
- [ delphi ] AES-256-ECB 加密、解密算法控件说明
- 思想实验及其在科学发展中的作用
- el-pagination分页自定义前往第几页样式(下拉框形式)
- G - Tiling
- python_docx读取word的内容
- 基于成交历史的交易播放器
- C语言-- 输出大写英文字母
- flex布局:携程网移动端首页案例
- 游戏 | python打包游戏为exe可执行文件
- [目标检测]CenterNet
- 时间序列模型算法 - Prophet,LSTM(二)