【JAVA】美好生活

技术介绍

image-20241127161308182

image-20241127162116508

导入项目

image-20241127161949425

image-20241127164332311

短信登录(Session->Redis)

image-20241127164728906

代码实现

发送短信验证码

image-20241127164859040

@Override
public 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);
    //5.发送验证码
    log.debug("发送短信验证码成功,验证码为:{}",code);
    //返回ok
    return Result.ok();
}

短信验证码登录

屏幕截图 2024-11-27 170216

@Override
public Result login(LoginFormDTO loginForm, HttpSession session) {
   
   
    //1.校验手机号
    String phone = loginForm.getPhone();
    if(RegexUtils.isPhoneInvalid(phone)){
   
   
        //2.如果不符合,返回错误信息
        return Result.fail("手机号格式错误!");
    }
    //2.校验验证码
    Object CacheCode = session.getAttribute("code");
    String code = loginForm.getCode();
    if(!CacheCode.toString().equals(code) || CacheCode==null){
   
   
        //3.不一致,报错
        return Result.fail("验证码错误");
    }

    //4.一致,根据手机号查询用户 select * from tb_user where phone=? 使用mybatisPlus查
    User user = query().eq("phone", phone).one();

    //5.判断用户是否存在
    if(user == null){
   
   
        //6。不存在创建
        user = createUserWithPhone(phone);
    }

    //7.保存用户信息到session中
    session.setAttribute("user",user);
    return Result.ok();
}

private User createUserWithPhone(String phone) {
   
   
    //1.创建用户
    User user = new User();
    user.setPhone(phone);
    user.setNickName("user_"+RandomUtil.randomString(10));
    //2.保存用户,使用mybatisPlus
    save(user);
    return user;
}

登录验证功能

image-20241127181501522

//定义登录拦截器
public class LoginInterceptor implements HandlerInterceptor {
   
   

    //请求前拦截
    @Override
    public 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
        UserHolder.saveUser((User) user);
        //6.放行
        return true;
    }

    //在整个请求处理流程结束之后执行,清理资源
    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
   
   
        //移除用户
        UserHolder.removeUser();
    }

}

//配置拦截器
@Configuration
public class MvcConfig implements WebMvcConfigurer {
   
   

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
   
   
        registry.addInterceptor(new LoginInterceptor())
                .excludePathPatterns(
                        "/user/code",
                        "/user/login",
                        "/blog/hot",
                        "/shop/**",
                        "/upload/**",
                        "/voucher/**",
                        "/shop-type/**");
    }
}

集群Session共享问题

image-20241129152311538

Redis代替Session

重要,pullUserDTO对象那里)

image-20241129153028514

image-20241129153141496

image-20241129163144175

//service层
@Override
public Result sendCode(String phone, HttpSession session) {
   
   
    //1.校验手机号,正则表达式去校验
    if(RegexUtils.isPhoneInvalid(phone)){
   
   
        //2.如果不符合,返回错误信息
        return Result.fail("手机号格式错误!");
    }
    //3.符合,生成验证码,调用工具包生成六位随机数
    String code = RandomUtil.randomNumbers(6);
    //4.保存到Redis当中
    stringRedisTemplate.opsForValue().set("login:code:" + phone, code,2, TimeUnit.MINUTES);
    //5.发送验证码,
    log.debug("发送短信验证码成功,验证码为:{}",code);
    //返回ok
    return Result.ok();
}

@Override
public Result login(LoginFormDTO loginForm, HttpSession session) {
   
   
    //1.校验手机号
    String phone = loginForm.getPhone();
    if(RegexUtils.isPhoneInvalid(phone)){
   
   
        //2.如果不符合,返回错误信息
        return Result.fail("手机号格式错误!");
    }
    //2.从redis获取验证码校验
    String CacheCode = stringRedisTemplate.opsForValue().get("login:code:" + phone);
    String code = loginForm.getCode();
    if(!CacheCode.toString().equals(code) || CacheCode==null){
   
   
        //3.不一致,报错
        return Result.fail("验证码错误");
    }

    //4.一致,根据手机号查询用户 select * from tb_user where phone=? 使用mybatisPlus查
    User user = query().eq("phone", phone).one();

    //5.判断用户是否存在
    if(user == null){
   
   
        //6。不存在创建
        user = createUserWithPhone(phone);
    }

    //7.保存用户DTO信息到Redis中
    //7.1随机生成token,作为登录令牌
    String token = UUID.randomUUID().toString(true);
    //7.2将user对象转为hash存储
    UserDTO userDTO = BeanUtil.copyProperties(user, UserDTO.class);
    //确保所有map字段的值都是String类型
    Map<String, Object> userMap = BeanUtil.beanToMap(userDTO,new HashMap<>(),
            CopyOptions.create()
                    .setIgnoreNullValue(true)
                    .setFieldValueEditor((fiedName,fiedValue)->fiedValue.toString()));
    //7.3存储到redis
    stringRedisTemplate.opsForHash().putAll("login:token"+token,userMap);
    //设置有效期
    stringRedisTemplate.expire("login:token"+token,30, TimeUnit.MINUTES);
    //8.返回token
    return Result.ok(token);
}
//拦截器
//请求前拦截
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
   
   
        //1.获取token
        String token = request.getHeader("authorization");
        if(StrUtil.isBlank(token)){
   
   
            //不存在拦截,返回401状态码
            response.setStatus(401);
            return false;
        }
        //2.根据token获取redis中的用户
        Map<Object, Object> userMap = stringRedisTemplate.opsForHash().entries("login:token" + token);
        //3.判断用户是否存在
        if(userMap.isEmpty()) {
   
   
            //4.不存在拦截,返回401状态码
            response.setStatus(401);
            return false;
        }
        //5.将查询到的Hash转化为UserDTO对象
        UserDTO userDTO = BeanUtil.fillBeanWithMap(userMap, new UserDTO(), false);
        //6.存在,用户信息保存到threadLocal
        //自定义工具类UserHolder
        UserHolder.saveUser(userDTO);
        //7.刷新token有效期
        stringRedisTemplate.expire("login:token" + token,30, TimeUnit.MINUTES);
        //8.放行
        return true;
    }

    //在整个请求处理流程结束之后执行,清理资源
    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
   
   
        //移除用户
        UserHolder.removeUser();
    }

登录拦截器优化

image-20241129165607861

因为有些页面不需要登录也能看,如果用户一直访问不需要登录的请求,那么redis中用户缓存不会被刷新,因为login那个拦截器只会拦截部分请求,拦截一切路径那个拦截器如果里面带token了就会进行刷新

商户缓存

添加Redis缓存

@Override
public Result queryById(Long id) {
   
   
    //1.从redis查询商户缓存
    String shopJson = stringRedisTemplate.opsForValue().get("cache:shop:" + id);
    //2.判断是否存在
    if (StrUtil.isNotBlank(shopJson)) {
   
   
        //3.存在,直接返回
        Shop shop = JSONUtil.toBean(shopJson, Shop.class);
        return Result.ok(shop);
    }
    //4.不存在,查询数据库,mybatisPlus
    Shop shop = getById(id);
    //5.数据库不存在,返回错误
    if(shop==null){
   
   
        return Result.fail("店铺不存在");
    }
    //6.数据库存在,写入redis缓存
    stringRedisTemplate.opsForValue().set("cache:shop:" + id, JSONUtil.toJsonStr(shop));
    //7.返回
    return Result.ok(shop);
}

缓存更新策略

image-20241202133709094

image-20241202134022176

image-20241202134316249

image-20241202134820963

image-20241202134844413

开发

@Transactional
public Result update(Shop shop) {
   
   
    Long id = shop.getId();
    if (id == null) {
   
   
        return Result.fail("店铺id不能为空");
    }
    //1.更新数据库.mybatisPlus
    updateById(shop);
    //2.删除缓存
    stringRedisTemplate.delete("cache:shop:" + shop.getId());
    return null;
}

缓存穿透

image-20241202141401037

实现

image-20241202141540381

public Result queryById(Long id) {
   
   
    //1.从redis查询商户缓存
    String shopJson = stringRedisTemplate.opsForValue().get("cache:shop:" + id);
    //2.判断是否存在
    if (StrUtil.isNotBlank(shopJson)) {
   
   
        //3.存在,直接返回
        Shop shop = JSONUtil.toBean(shopJson, Shop.class);
        return Result.ok(shop);
    }
    //判断缓存命中是否为空值"",是则返回
    if(shopJson !=null){
   
   
        return Result.fail("店铺不存在!");
    }
    //4.不存在,查询数据库,mybatisPlus
    Shop shop = getById(id);
    //5.数据库不存在,返回错误
    if(shop==null){
   
   
        //将空值写入redis
        stringRedisTemplate.opsForValue().set("cache:shop:" + id, "",2L, TimeUnit.MINUTES);
        //返回错误信息
        return Result.fail("店铺不存在");
    }
    //6.数据库存在,写入redis缓存
    stringRedisTemplate.opsForValue().set("cache:shop:" + id, JSONUtil.toJsonStr(shop),30L, TimeUnit.MINUTES);
    //7.返回
    return Result.ok(shop);
}

缓存雪崩

image-20241202143307003

缓存击穿

image-20241202144021256

image-20241202144448700

image-20241202144504208

互斥锁解决缓存击穿

使用setnx

image-20241202152046761

//互斥锁缓存击穿
public Shop queryWithMutex(Long id){
   
   
    //1.从redis查询商户缓存
    String shopJson = stringRedisTemplate.opsForValue().get("cache:shop:" + id);
    //2.判断是否存在
    if (StrUtil.isNotBlank(shopJson)) {
   
   
        //3.存在,直接返回
        return JSONUtil.toBean(shopJson, Shop.class);
    }
    //判断缓存命中是否为空值"",是则返回
    if(shopJson !=null){
   
   
        return null;
    }
    //4.不存在,缓存重建
    //4.1获取互斥锁
    String lockKey="lock:shop:" + id;
    Shop shop = null;
    try {
   
   
        boolean isLock = tryLock(lockKey);
        //4.2判断获取是否成功
        if(!isLock){
   
   
            //4.3失败,休眠并重试
            Thread.sleep(50);
            return queryWithMutex(id);
        }
        //4.4获取锁成功,mybatisPlus,根据id查询数据库
        //获取完锁后进行DoubleCheck
        String shopJson1 = stringRedisTemplate.opsForValue().get("cache:shop:" + id);
        if (StrUtil.isNotBlank(shopJson1)) {
   
   
            //存在,直接返回
            return JSONUtil.toBean(shopJson1, Shop.class);
        }
        shop = getById(id);
        //模拟延迟
        Thread.sleep(200);
        //5.数据库不存在,返回错误
        if(shop==null){
   
   
            //将空值写入redis
            stringRedisTemplate.opsForValue().set("cache:shop:" + id, "",2L, TimeUnit.MINUTES);
            //返回错误信息
            return null;
        }
        //6.数据库存在,写入redis缓存
        stringRedisTemplate.opsForValue().set("cache:shop:" + id, JSONUtil.toJsonStr(shop),30L, TimeUnit.MINUTES);


    } catch (InterruptedException e) {
   
   
        throw new RuntimeException(e);
    }finally {
   
   
        
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值