redis day1

发送验证码接口

1.Controller层

复制代码
@RestController
@RequestMapping("/user")
public class UserController {
    
    @Resource
    private IUserService userService;
    
    @PostMapping("/code")
    public Result sendCode(@RequestParam("phone") String phone) {
        return userService.sendCode(phone);
    }
}

2.Service层实现

复制代码
@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements IUserService {
    
    @Resource
    private StringRedisTemplate stringRedisTemplate;
    
    @Override
    public Result sendCode(String phone) {
        // 1. 验证手机号格式
        if (RegexUtils.isPhoneInvalid(phone)) {
            return Result.fail("手机号格式错误!");
        }
        
        // 2. 生成6位随机验证码
        String code = RandomUtil.randomNumbers(6);
        
        // 3. 保存验证码到Redis(2分钟过期)
        String key = RedisConstants.LOGIN_CODE_KEY + phone; // "login:code:13800138000"
        stringRedisTemplate.opsForValue().set(key, code, 
            RedisConstants.LOGIN_CODE_TTL, TimeUnit.MINUTES);
        
        // 4. 模拟发送短信(实际项目调用短信服务)
        log.debug("发送短信验证码成功,验证码:{}", code);
        
        return Result.ok();
    }
}

登录接口

1.Controller层

复制代码
@PostMapping("/login")
public Result login(@RequestBody LoginFormDTO loginForm) {
    return userService.login(loginForm);
}

2.Service层实现

复制代码
@Override
public Result login(LoginFormDTO loginForm) {
    String phone = loginForm.getPhone();
    
    // 1. 验证手机号格式
    if (RegexUtils.isPhoneInvalid(phone)) {
        return Result.fail("手机号格式错误!");
    }
    
    // 2. 从Redis获取验证码
    String cacheKey = RedisConstants.LOGIN_CODE_KEY + phone;
    String cacheCode = stringRedisTemplate.opsForValue().get(cacheKey);
    String inputCode = loginForm.getCode();
    
    // 3. 验证码校验
    if (cacheCode == null || !cacheCode.equals(inputCode)) {
        return Result.fail("验证码错误");
    }
    
    // 4. 查询用户(MySQL)
    User user = query().eq("phone", phone).one();
    
    // 5. 用户不存在则创建
    if (user == null) {
        user = createUserWithPhone(phone);
    }
    
    // 6. 生成Token(UUID去掉连字符)
    String token = UUID.randomUUID().toString().replace("-", "");
    
    // 7. 转换为UserDTO(脱敏)
    UserDTO userDTO = BeanUtil.copyProperties(user, UserDTO.class);
    
    // 8. 用户信息转为Map(Hash存储)
    Map<String, Object> userMap = BeanUtil.beanToMap(userDTO, new HashMap<>(),
        CopyOptions.create()
            .setIgnoreNullValue(true)
            .setFieldValueEditor((fieldName, fieldValue) -> 
                fieldValue == null ? "" : fieldValue.toString()
            )
    );
    
    // 9. 保存到Redis(Hash结构,30分钟过期)
    String tokenKey = RedisConstants.LOGIN_USER_KEY + token; // "login:token:abc123"
    stringRedisTemplate.opsForHash().putAll(tokenKey, userMap);
    stringRedisTemplate.expire(tokenKey, 
        RedisConstants.LOGIN_USER_TTL, TimeUnit.MINUTES);
    
    // 10. 删除已使用的验证码
    stringRedisTemplate.delete(cacheKey);
    
    // 11. 返回token给前端
    return Result.ok(token);
}

3.拦截器实现

复制代码
@Component
public class RefreshTokenInterceptor implements HandlerInterceptor {
    
    @Resource
    private StringRedisTemplate stringRedisTemplate;
    
    @Override
    public boolean preHandle(HttpServletRequest request, 
                             HttpServletResponse response, 
                             Object handler) throws Exception {
        
        // 1. 获取请求头中的token
        String token = request.getHeader("authorization");
        if (StrUtil.isBlank(token)) {
            return true; // 没有token,放行(由LoginInterceptor处理)
        }
        
        // 2. 从Redis获取用户信息
        String key = RedisConstants.LOGIN_USER_KEY + token;
        Map<Object, Object> userMap = stringRedisTemplate.opsForHash().entries(key);
        
        // 3. 用户不存在,放行
        if (userMap.isEmpty()) {
            return true;
        }
        
        // 4. Hash转UserDTO对象
        UserDTO userDTO = BeanUtil.fillBeanWithMap(userMap, new UserDTO(), false);
        
        // 5. 保存到ThreadLocal(线程隔离)
        UserHolder.saveUser(userDTO);
        
        // 6. 刷新Token过期时间(续期30分钟)
        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();
    }
}

内存泄漏(Memory Leak) 是指程序在申请内存后,无法释放已不再使用的内存空间,导致可用内存逐渐减少,最终可能引发程序崩溃或系统性能下降。

LoginInterceptor
复制代码
@Component
public class LoginInterceptor implements HandlerInterceptor {
    
    @Override
    public boolean preHandle(HttpServletRequest request, 
                             HttpServletResponse response, 
                             Object handler) throws Exception {
        
        // 1. 从ThreadLocal获取用户
        UserDTO user = UserHolder.getUser();
        
        // 2. 用户不存在,返回401未授权
        if (user == null) {
            response.setStatus(401);
            return false;
        }
        
        // 3. 用户存在,放行
        return true;
    }
}

实现两个拦截器的原因

**第一个拦截器(RefreshTokenInterceptor)**的工作:

  1. 每次你访问网站,它都会检查你的"令牌"(token)

  2. 如果令牌有效,就把你的用户信息记下来

  3. 同时帮你延长令牌有效期(从30分钟重新开始计算)

  4. 它从不拒绝任何人访问

**第二个拦截器(LoginInterceptor)**的工作:

两个拦截器通过职责分离(一个只管刷新token,一个只管检查登录),实现了自动续期登录状态的同时精准控制接口权限,既保证用户体验(无感续期)又确保系统安全。

  1. 只在你访问需要登录的页面时才工作

  2. 检查第一个拦截器有没有记下你的信息

  3. 如果有信息,让你通过

  4. 如果没有信息,让你去登录

相关推荐
dzl843942 小时前
2025年技术栈备忘
java
b***74882 小时前
从技术复杂度到体系竞争力:2026 年前端发展的全新范式转移
前端
IT_陈寒2 小时前
Java并发编程避坑指南:这5个隐藏陷阱让你的性能暴跌50%!
前端·人工智能·后端
化作繁星2 小时前
前端设计模式详解
前端·设计模式
lynnlovemin2 小时前
从暴力到高效:C++ 算法优化实战 —— 排序与双指针篇
java·c++·算法
jinxinyuuuus2 小时前
快手在线去水印:短链解析、API逆向与视频流的元数据重构
前端·人工智能·算法·重构
棒棒的唐2 小时前
avue uploader图片预览拉伸变型的css处理方法
前端·css
BD_Marathon2 小时前
【JavaWeb】Tomcat_WebAPP的标准结构
java·tomcat·web app
sunshine~~~2 小时前
ROS 2 Jazzy + Python 3.12 + Web 前端案例
开发语言·前端·python·anaconda·ros2