短信登录
session共享问题
redis替代session
推荐使用哈希
@Override
public Result sendCode(String phone, HttpSession session) {
//校验手机号
if(RegexUtils.isPhoneInvalid(phone))
return Result.fail("手机号格式错误");
//如果不符合返回错误信息
//生产验证码
String code = RandomUtil.randomNumbers(6);
//保存在redis,设置有效期2分钟,使用哈希
stringRedisTemplate.opsForValue().set(LOGIN_CODE_KEY+phone,code,2, TimeUnit.MINUTES);
//再返发送
log.debug("发送短信验证码成功"+code);
return Result.ok();
}
private StringRedisTemplate stringRedisTemplate;//key value都必须是String
@Override
public Result login(LoginFormDTO loginForm, HttpSession session) {
String phone = loginForm.getPhone();
//校验手机号
if(RegexUtils.isPhoneInvalid(phone))
return Result.fail("手机号格式错误");
//redis获取校验验证码,报错
String cacheCode = stringRedisTemplate.opsForValue().get(LOGIN_CODE_KEY + phone);
if (cacheCode==null || !cacheCode.toString().equals(loginForm.getCode()))
return Result.fail("账号与验证码不符合");
//看手机号查出用户
User user = query().eq("phone", phone).one();
//判断用户是否存在,不存在创建并保存
//保存用户信息到redis
//随机生成token登录令牌
String token = UUID.randomUUID().toString(true);
//将uer对象转为哈希
UserDTO userDTO = BeanUtil.copyProperties(user, UserDTO.class);
//确保都是String类
Map<String, Object> userMap = BeanUtil.beanToMap(userDTO,new HashMap<>(),
CopyOptions.create().setIgnoreNullValue(true).setFieldValueEditor((filedName,filedValue) -> filedValue.toString()));
//存储
String tokenKey = LOGIN_USER_KEY + token;
stringRedisTemplate.opsForHash().putAll(tokenKey,userMap);
//并设置token有效期30分钟
stringRedisTemplate.expire(tokenKey,LOGIN_USER_TTL,TimeUnit.MINUTES);
//如果使用则刷新Token有效期
//返回token
return Result.ok(token);
}
第一个拦截一切,作用是得到用户并刷新
第二个拦截器是拦截登录的
如果采用第一个方案,那么假设我们每次操作数据库后,都操作缓存,但是中间如果没有人查询,那么这个更新动作实际上只有最后一次生效,中间的更新动作意义并不大,我们可以把缓存删除,等待再次查询时,将缓存中的数据加载出来
-
删除缓存还是更新缓存?
-
更新缓存:每次更新数据库都更新缓存,无效写操作较多 X
-
删除缓存:更新数据库时让缓存失效,查询时再更新缓存 √
-
-
如何保证缓存与数据库的操作的同时成功或失败?
-
单体系统,将缓存与数据库操作放在一个事务
-
分布式系统,利用TCC等分布式事务方案
-
应该具体操作缓存还是操作数据库,我们应当是先操作数据库,再删除缓存,原因在于,如果你选择第一种方案,在两个线程并发来访问时,假设线程1先来,他先把缓存删了,此时线程2过来,他查询缓存数据并不存在,此时他写入缓存,当他写入缓存后,线程1再执行更新动作时,实际上写入的就是旧的数据,新的数据被旧数据覆盖了。
-
先操作缓存还是先操作数据库?
-
先删除缓存,再操作数据库 X
-
先操作数据库,再删除缓存 √
-
缓存更新策略
{
"area": "大关",
"openHours": "10:00-22:00",
"sold": 4215,
"address": "金华路锦昌文华苑29号",
"comments": 3035,
"avgPrice": 80,
"score":37,
"name": "105茶餐厅",
"typeId": 1,
"id": 1
}
缓存移除了
@Override
@Transactional
public Result update(Shop shop) {
//更新数据库
updateById(shop);
//删除缓存
Long id = shop.getId();
if (id==null)
return Result.fail("店铺id不能为空哦");
stringRedisTemplate.delete(CACHE_SHOP_KEY+ id);
return Result.ok();
}
缓存穿透(缓存和数据库中都不存在)
缓存穿透问题的解决思路
缓存穿透 :缓存穿透是指客户端请求的数据在缓存中和数据库中都不存在,这样缓存永远不会生效,这些请求都会打到数据库。
常见的解决方案有两种:
-
缓存空对象
-
优点:实现简单,维护方便
-
缺点:
-
额外的内存消耗(设置短一点的有效期)
-
可能造成短期的不一致
-
-
-
布隆过滤
-
优点:内存占用较少,没有多余key
-
缺点:
-
实现复杂
-
存在误判可能
-
-
左侧存储空、右侧布隆过滤器
选择第一种