使用Redis实现短信登陆
使用Redis实现发送验证码;验证码登陆、注册;登陆校验拦截、登陆状态刷新等一系列问题。
验证码发送和验证登陆注册
思路流程
整体的思路以及流程如题:
代码实现
-
实体类
User实体类
@Data @EqualsAndHashCode(callSuper = false) @Accessors(chain = true) @TableName("tb_user") public class User implements Serializable { private static final long serialVersionUID = 1L; @TableId(value = "id", type = IdType.AUTO) private Long id; private String phone; private String password; private String nickName; private String icon = ""; private LocalDateTime createTime; private LocalDateTime updateTime; }
UserDTO
import lombok.Data; @Data public class UserDTO { private Long id; private String nickName; private String icon; }
-
发送手机验证码
import cn.hutool.core.util.RandomUtil; @Resource private StringRedisTemplate stringRedisTemplate; public static final String LOGIN_CODE_KEY = "login:code:"; public static final Long LOGIN_CODE_TTL = 2L; /** * 发送手机验证码 */ @PostMapping("code") public Result sendCode(@RequestParam("phone") String phone) { // 校验手机号 if (RegexUtils.isPhoneInvalid(phone)) { // 不符合,返回错误信息 return Result.fail("手机号格式错误"); } // 符合,生成验证码 String code = RandomUtil.randomNumbers(6); String key = LOGIN_CODE_KEY + phone; // 保存验证码, redis stringRedisTemplate.opsForValue().set(key, code, LOGIN_CODE_TTL, TimeUnit.MINUTES); // 发送验证码,模拟 log.debug("发送短信验证码成功,验证码:{}", code); return Result.ok(); }
-
短信验证登陆注册
import cn.hutool.core.lang.UUID; @Resource private IUserService userService; @Resource private StringRedisTemplate stringRedisTemplate; public static final String LOGIN_CODE_KEY = "login:code:"; public static final String LOGIN_USER_KEY = "login:token:"; public static final Long LOGIN_USER_TTL = 30L; /** * 登录功能 * @param loginForm 登录参数,包含手机号、验证码;或者手机号、密码 */ @PostMapping("/login") public Result login(@RequestBody LoginFormDTO loginForm){ String phone = loginForm.getPhone(); // 校验手机号 if (RegexUtils.isPhoneInvalid(phone)) { // 不符合,返回错误信息 return Result.fail("手机号格式错误"); } // 校验验证码 String key = LOGIN_CODE_KEY + phone; String cacheCode = stringRedisTemplate.opsForValue().get(key); String code = loginForm.getCode(); if (cacheCode == null || !cacheCode.equals(code)) { // 不一致,返回错误信息 return Result.fail("验证码错误"); } // 一致,根据手机号查询用户 User user = userService.queryUser(phone); if (user == null) { // 不存在,创建新用户并保存 user = createUserWithPhone(phone); userService.save(user); } // 保存用户信息到 redis String token = UUID.randomUUID().toString(true); UserDTO userDTO = BeanUtil.copyProperties(user, UserDTO.class); Map<String, Object> userMap = BeanUtil.beanToMap(userDTO, new HashMap<>(), CopyOptions.create().setIgnoreNullValue(true).setFieldValueEditor((fieldName, fieldValue) -> fieldValue.toString() )); String tokenKey = LOGIN_USER_KEY + token; stringRedisTemplate.opsForHash().putAll(tokenKey, userMap); stringRedisTemplate.expire(tokenKey, LOGIN_USER_TTL, TimeUnit.MINUTES); // 返回token return Result.ok(token); } /** * 创建用户 * @param phone 手机号 */ private User createUserWithPhone(String phone) { User user = new User(); user.setPhone(phone); user.setNickName(USER_NICK_NAME_PREFIX + RandomUtil.randomString(10)); return user; }
登陆校验拦截
思路流程
- 第一个拦截器是为了刷新token的有效期,防止用户在访问不需要登陆操作页面时间过长而导致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 { // 获取请求头中的token String token = request.getHeader("authorization"); if (StringUtils.isBlank(token)) { return true; } // 获取redis中的用户 String key = RedisConstants.LOGIN_USER_KEY + token; Map<Object, Object> entries = stringRedisTemplate.opsForHash().entries(key); if (entries.isEmpty()) { return true; } UserDTO userDTO = BeanUtil.fillBeanWithMap(entries, new UserDTO(), false); // 存在,保存到ThreadLocal UserHolder.saveUser(userDTO); // 刷新token有效期 stringRedisTemplate.expire(key, RedisConstants.LOGIN_USER_TTL, TimeUnit.MINUTES); return true; } @Override public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception { UserHolder.removeUser(); }
-
第二个拦截器
public class LoginInterceptor implements HandlerInterceptor { @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { if (UserHolder.getUser() == null) { // 不存在,拦截,返回401状态码 response.setStatus(401); return false; } return true; } }
-
注册拦截器
注意拦截顺序,默认按照添加顺序,也可手动指定。
@Configuration public class MvcConfig implements WebMvcConfigurer { @Resource private StringRedisTemplate stringRedisTemplate; @Override public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(new RefreshTokenInterceptor(stringRedisTemplate)).order(0); registry.addInterceptor(new LoginInterceptor()) .excludePathPatterns( "/voucher/**", "/shop-type/**", "/shop/**", "/user/code", "/user/login", "/blog/hot" ).order(1); } }
上一篇文章:Redis快速入门
下一篇文章:
本文来自博客园,作者:sw-code,转载请注明原文链接:https://www.cnblogs.com/sw-code/p/16828656.html