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. 如果没有信息,让你去登录

相关推荐
草履虫建模1 天前
力扣算法 1768. 交替合并字符串
java·开发语言·算法·leetcode·职场和发展·idea·基础
华玥作者1 天前
[特殊字符] VitePress 对接 Algolia AI 问答(DocSearch + AI Search)完整实战(下)
前端·人工智能·ai
Mr Xu_1 天前
告别冗长 switch-case:Vue 项目中基于映射表的优雅路由数据匹配方案
前端·javascript·vue.js
qq_297574671 天前
【实战教程】SpringBoot 实现多文件批量下载并打包为 ZIP 压缩包
java·spring boot·后端
老毛肚1 天前
MyBatis插件原理及Spring集成
java·spring·mybatis
前端摸鱼匠1 天前
Vue 3 的toRefs保持响应性:讲解toRefs在解构响应式对象时的作用
前端·javascript·vue.js·前端框架·ecmascript
学嵌入式的小杨同学1 天前
【Linux 封神之路】信号编程全解析:从信号基础到 MP3 播放器实战(含核心 API 与避坑指南)
java·linux·c语言·开发语言·vscode·vim·ux
lang201509281 天前
JSR-340 :高性能Web开发新标准
java·前端·servlet
Re.不晚1 天前
Java入门17——异常
java·开发语言
缘空如是1 天前
基础工具包之JSON 工厂类
java·json·json切换