目录
基于session实验短信登录
发送验证码
登录验证功能编辑
用Redis实现共享Session问题
登录拦截器的优化
商品查询缓存
什么是缓存
添加redis缓存
缓存穿透
缓存雪崩
缓存击穿
缓存工具封装编辑
基于session实验短信登录
发送验证码
登录验证功能
用Redis实现共享Session问题
登录拦截器的优化
public class LoginInterceptor implements HandlerInterceptor {
//登录优化后 创建RefreshTokenInterceptor
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
//1.判断是否需要拦截(ThreadLocal是否用户)
if ( UserHolder.getUser()==null ){
//没有,需要拦截,设置状态码
response.setStatus(401);
//拦截
return false;
}
// 有用户,则放行
return true;
}
//登录优化前
// @Override
// public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
// //1.获取session
// //HttpSession session = request.getSession();
// //TODO 1.获取请求头中的token
// String token = request.getHeader("authorization");
// if ( StrUtil.isBlank(token) ) {
// //不存在,拦截,返回401状态码
// response.setStatus(401);
// return false;
// }
// //2.获取session中的用户
Object user = session.getAttribute("user");
// //TODO 2.基于TOKEN获取redis中的用户
// String key = RedisConstants.LOGIN_USER_KEY + token;
// Map<Object, Object> userMap = stringRedisTemplate.opsForHash()
// .entries(key);
//
// //3.判断用户是否存在
// if ( userMap.isEmpty()){
// //4.不存在,拦截 返回401状态码
// response.setStatus(401);
// return false;
// }
// //TODO 5.将查询到的Hash数据转为UserDTO对象
// UserDTO userDTO = BeanUtil.fillBeanWithMap(userMap, new UserDTO(), false);
// //TODO 6.存在,保存用户信息到ThreadLocal
// UserHolder.saveUser(userDTO);
// //5.存在,保存用户信息到ThreadLocal
// //5.存在,保存用户信息到ThreadLocal
// // UserHolder.saveUser((UserDTO) user);
// //TODO 7.刷新token有效期
// stringRedisTemplate.expire(key,RedisConstants.LOGIN_USER_TTL, TimeUnit.MINUTES);
// //6.放行
// return true;
// }
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
}
}
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 {
//1.获取session
//HttpSession session = request.getSession();
//TODO 1.获取请求头中的token
String token = request.getHeader("authorization");
if ( StrUtil.isBlank(token) ) {
//不存在,拦截,返回401状态码
// response.setStatus(401);
return true;
}
//2.获取session中的用户
// Object user = session.getAttribute("user");
//TODO 2.基于TOKEN获取redis中的用户
String key = RedisConstants.LOGIN_USER_KEY + token;
Map<Object, Object> userMap = stringRedisTemplate.opsForHash()
.entries(key);
//3.判断用户是否存在
if ( userMap.isEmpty()){
//4.不存在,拦截 返回401状态码
// response.setStatus(401);
return true;
}
//TODO 5.将查询到的Hash数据转为UserDTO对象
UserDTO userDTO = BeanUtil.fillBeanWithMap(userMap, new UserDTO(), false);
//TODO 6.存在,保存用户信息到ThreadLocal
UserHolder.saveUser(userDTO);
//5.存在,保存用户信息到ThreadLocal
//5.存在,保存用户信息到ThreadLocal
// UserHolder.saveUser((UserDTO) user);
//TODO 7.刷新token有效期
stringRedisTemplate.expire(key,RedisConstants.LOGIN_USER_TTL, TimeUnit.MINUTES);
//6.放行
return true;
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
}
}
商品查询缓存
什么是缓存
添加redis缓存
缓存穿透
// TODO 封装缓存穿透
public Shop queryWithPassThrough(Long id){
String key = CACHE_SHOP_KEY + id;
//1、从redis查询商品缓存
String shopJson = stringRedisTemplate.opsForValue().get(key);
//2.判断是否存在
if ( StrUtil.isNotBlank(shopJson) ){
//TODO 如果是“”也是返回false
//3.存在,直接返回
Shop shop = JSONUtil.toBean(shopJson, Shop.class);
// return Result.ok(shop);
return shop;
}
//TODO 判断命中的是否是空值
if ( shopJson!=null ){//第一步判断后这里就只剩下null和""
// 或者 if ( "".equals(shopJson) )
//返回一个错误信息
// return Result.fail("店铺不存在!!!");
return null;
}
//4、不存在,根据id查询数据库
Shop shop = getById(id);
//5、不存在,返回错误
if ( shop==null ){
//TODO 解决缓存穿透
//将控制写入redis中
stringRedisTemplate.opsForValue().set(key,"",CACHE_NULL_TTL,TimeUnit.MINUTES);
// return Result.fail("店铺不存在");
return null;
}
//6、存在,写入redis
stringRedisTemplate.opsForValue().set(key,JSONUtil.toJsonStr(shop),CACHE_SHOP_TTL, TimeUnit.MINUTES);
//7、返回
return shop;
}
缓存雪崩
缓存击穿
// TODO 封装缓存击穿
public Shop queryWithMutex(Long id){
String key = CACHE_SHOP_KEY + id;
//1、从redis查询商品缓存
String shopJson = stringRedisTemplate.opsForValue().get(key);
//2.判断是否存在
if ( StrUtil.isNotBlank(shopJson) ){
//TODO 如果是“”也是返回false
//3.存在,直接返回
Shop shop = JSONUtil.toBean(shopJson, Shop.class);
// return Result.ok(shop);
return shop;
}
//TODO 判断命中的是否是空值
if ( shopJson!=null ){//第一步判断后这里就只剩下null和""
// 或者 if ( "".equals(shopJson) )
//返回一个错误信息
// return Result.fail("店铺不存在!!!");
return null;
}
//ToDO 4.实现缓存重建
//ToDO 4.1获取互斥锁
String lockKey = LOCK_SHOP_KEY + id;
Shop shop = null;
try {
boolean isLock = tryLock(lockKey);
//ToDO 4.2判断是否获取成功
if ( !isLock ){
//ToDO 4.3失败,则休眠并重试
Thread.sleep(50);
return queryWithMutex(id);
}
//ToDO 4.4、成功,根据id查询数据库
shop = getById(id);
//模拟重建的延时
Thread.sleep(200);
//5、不存在,返回错误
if ( shop==null ){
//TODO 解决缓存穿透
//将控制写入redis中
stringRedisTemplate.opsForValue().set(key,"",CACHE_NULL_TTL,TimeUnit.MINUTES);
// return Result.fail("店铺不存在");
return null;
}
//6、存在,写入redis
stringRedisTemplate.opsForValue().set(key,JSONUtil.toJsonStr(shop),CACHE_SHOP_TTL, TimeUnit.MINUTES);
}
catch (InterruptedException e) {
throw new RuntimeException(e);
}finally {
//TODO 释放互斥锁
unlock(lockKey);
}
//7、返回
return shop;
}
private boolean tryLock(String key){
Boolean flag = stringRedisTemplate.opsForValue().setIfAbsent(key, "1", 10, TimeUnit.SECONDS);
//防止拆箱
return BooleanUtil.isTrue(flag);
}
private void unlock(String key){
stringRedisTemplate.delete(key);
}
缓存工具封装
@Slf4j
@Component
public class CacheClient {
private final StringRedisTemplate stringRedisTemplate;
public CacheClient(StringRedisTemplate stringRedisTemplate) {
this.stringRedisTemplate = stringRedisTemplate;
}
public void set(String key, Object value, Long time , TimeUnit unit){
stringRedisTemplate.opsForValue().set(key, JSONUtil.toJsonStr(value),time,unit);
}
public void setWithLogicalExpire(String key, Object value, Long time , TimeUnit unit){
//设置逻辑过期时间
RedisData redisData = new RedisData();
redisData.setData(value);
redisData.setExpireTime(LocalDateTime.now().plusSeconds(unit.toSeconds(time)));
//写入Redis
stringRedisTemplate.opsForValue().set(key, JSONUtil.toJsonStr(redisData));
}
// TODO 封装缓存穿透
public <R,ID> R queryWithPassThrough(
String keyPrefix, ID id, Class<R> type, Function<ID,R> dbFallback,Long time , TimeUnit unit){
String key = keyPrefix + id;
//1、从redis查询商品缓存
String json = stringRedisTemplate.opsForValue().get(key);
//2.判断是否存在
if ( StrUtil.isNotBlank(json) ){
//TODO 如果是“”也是返回false
//3.存在,直接返回
return JSONUtil.toBean(json, type);
}
//TODO 判断命中的是否是空值
if ( json!=null ){//第一步判断后这里就只剩下null和""
// 或者 if ( "".equals(shopJson) )
//返回一个错误信息
return null;
}
//4、不存在,根据id查询数据库
R r = dbFallback.apply(id);
//5、不存在,返回错误
if ( r==null ){
//TODO 解决缓存穿透
//将控制写入redis中
stringRedisTemplate.opsForValue().set(key,"",CACHE_NULL_TTL,TimeUnit.MINUTES);
// return Result.fail("店铺不存在");
return null;
}
//6、存在,写入redis
this.set(key,r,time,unit);
// stringRedisTemplate.opsForValue().set(key,JSONUtil.toJsonStr(r),CACHE_SHOP_TTL, TimeUnit.MINUTES);
//7、返回
return r;
}
//TODO 线程池
private static final ExecutorService CACHE_REBUILD_EXECUTOR = Executors.newFixedThreadPool(10);
public <R,ID> R queryWithLogicalExpire(
String keyPrefix,ID id,Class<R> type,Function<ID,R> dbFallback, Long time , TimeUnit unit){
String key = keyPrefix + id;
//1、从redis查询商品缓存
String json = stringRedisTemplate.opsForValue().get(key);
//2.判断是否存在
if ( StrUtil.isBlank(json) ){
//TODO 如果是“”也是返回false
//3.不存在,直接返回
return null;
}
//4.命中,需要先把json反序列化为对象
RedisData redisData = JSONUtil.toBean(json, RedisData.class);
JSONObject data = (JSONObject)redisData.getData();
R r = JSONUtil.toBean(data, type);
LocalDateTime expireTime = redisData.getExpireTime();
//5.判断是否过期
if ( expireTime.isAfter(LocalDateTime.now()) ) {
//5.1未过期,直接返回店铺信息
return r;
}
//5.2已过期,需要缓存重建
//6.缓存重建
//6.1获取互斥锁
String lockKey = LOCK_SHOP_KEY+id;
boolean isLock = tryLock(lockKey);
//6.2判断是否获取锁成功
if ( isLock ) {
//TODO 6.3成功,开启独立线程,实现缓存重建
CACHE_REBUILD_EXECUTOR.submit(()->{
try {
//重建缓存(分下面两步)
//查询数据库
R r1 = dbFallback.apply(id);
//写入redis
this.setWithLogicalExpire(key,r1,time,unit);
}
catch (Exception e) {
e.printStackTrace();
}finally {
//释放锁
unlock(lockKey);
}
});
}
//6.4失败,返回过期的商铺信息
//7、返回
return r;
}
private boolean tryLock(String key){
Boolean flag = stringRedisTemplate.opsForValue().setIfAbsent(key, "1", 10, TimeUnit.SECONDS);
//防止拆箱
return BooleanUtil.isTrue(flag);
}
private void unlock(String key){
stringRedisTemplate.delete(key);
}
}