Interceptor拦截器
在哪些地方会使用到:
1.登录校验拦截器:没有登录不能访问部分页面
2.刷新拦截器:使用redis存储用户信息,每次请求需要记录刷新redis到期时间
3.刷新拦截器和登录校验拦截应该分开,用户访问不需要登录的页面也需要刷新到期时间
- 用户输入手机号,将验证码缓存到redis,并且进行对比
- 登录后以uuid为token,用hash的方式缓存用户信息到redis中,并且保存到ThreadLocal中,方便controller使用
- 前端通过拦截器每次请求需要在header中携带token,后端拦截器进行判断,并且更新redis到期时间
public void addInterceptors(InterceptorRegistry registry) {
// LoginInterceptor 登录拦截器
registry.addInterceptor(new LoginInterceptor()).
excludePathPatterns(
"/shop/**",
"/voucher/**",
"/shop-type/**",
"/upload/**",
"/blog/hot",
"/user/code",
"/user/login"
).order(2);
// 刷新拦截器 先执行
registry.addInterceptor(new RefreshTokenInterceptor(stringRedisTemplate)).addPathPatterns("/**").order(1);
feed
业务场景:关注的明星发布了新的微博
利用ZSet,将需要发送的消息存放到Redis,可以使用List或Set,由于我们需要排序,可以利用ZSet的score属性
//将内容信息发送到所有粉丝的邮箱内
for (Follow fan : users) {
String KEY = "feed:" + fan.getUserId();
stringRedisTemplate.opsForZSet().add(KEY, blog.getId().toString(), System.currentTimeMillis());
}
使用滚动分页,以时间戳(分数)为判断依据,每次查询比上次查询小的时间,就可以避免查询的时候发布新的文章,导致下标改变查询到重复数据。需要注意偏移量是需要跳过的个数,防止相同值会重复
// r..s(userid, 最小时间戳, 上一页最小的值=本页最大的值, 偏移量, 取多少个);
Set<ZSetOperations.TypedTuple<String>> typedTuples =
stringRedisTemplate.opsForZSet().reverseRangeByScoreWithScores(userId, 0, max, offset, 2);
BitMap
业务场景:签到,BitMap节省内存
redis使用String类型实现BitMap
//setBit(利用用户信息和日期的值拼装key,offset下标从0开始,true代表签到)
stringRedisTemplate.opsForValue().setBit(userId + day, now.getDayOfMonth() - 1, true);
HyperLogLog-UV统计
业务场景:UV统计:用户访问量,一个用户多次访问只计算一次,百万统计不超过16k,底层是概率算法,数据小部分误差
// add(名称,插入的数组)
stringRedisTemplate.opsForHyperLogLog().add(KEY, values);
锁定库存
/**
* 为某个订单锁定库存
* @param vo
* @return
*/
@Transactional(rollbackFor = Exception.class)
@Override
public boolean orderLockStock(WareSkuLockVo vo) {
/**
* 保存库存工作单详情信息
* 追溯
*/
WareOrderTaskEntity wareOrderTaskEntity = new WareOrderTaskEntity();
wareOrderTaskEntity.setOrderSn(vo.getOrderSn());
wareOrderTaskEntity.setCreateTime(new Date());
wareOrderTaskService.save(wareOrderTaskEntity);
//找到每个商品在哪个仓库都有库存
List<OrderItemVo> locks = vo.getLocks();
List<SkuWareHasStock> collect = locks.stream().map((item) -> {
SkuWareHasStock stock = new SkuWareHasStock();
Long skuId = item.getSkuId();
stock.setSkuId(skuId);
stock.setNum(item.getCount());
//查询这个商品在哪个仓库有库存
List<Long> wareIdList = wareSkuDao.listWareIdHasSkuStock(skuId);
stock.setWareId(wareIdList);
return stock;
}).collect(Collectors.toList());
//2、锁定库存
for (SkuWareHasStock hasStock : collect) {
boolean skuStocked = false;
Long skuId = hasStock.getSkuId();
List<Long> wareIds = hasStock.getWareId();
if (org.springframework.util.StringUtils.isEmpty(wareIds)) {
//没有任何仓库有这个商品的库存
throw new NoStockException(skuId);
}
//1、如果每一个商品都锁定成功,将当前商品锁定了几件的工作单记录发给MQ
//2、锁定失败。前面保存的工作单信息都回滚了。发送出去的消息,即使要解锁库存,由于在数据库查不到指定的id,所有就不用解锁
for (Long wareId : wareIds) {
//锁定成功就返回1,失败就返回0
Long count = wareSkuDao.lockSkuStock(skuId,wareId,hasStock.getNum());
if (count == 1) {
skuStocked = true;
WareOrderTaskDetailEntity taskDetailEntity = WareOrderTaskDetailEntity.builder()
.skuId(skuId)
.skuName("")
.skuNum(hasStock.getNum())
.taskId(wareOrderTaskEntity.getId())
.wareId(wareId)
.lockStatus(1)
.build();
wareOrderTaskDetailService.save(taskDetailEntity);
//TODO 告诉MQ库存锁定成功
StockLockedTo lockedTo = new StockLockedTo();
lockedTo.setId(wareOrderTaskEntity.getId());
StockDetailTo detailTo = new StockDetailTo();
BeanUtils.copyProperties(taskDetailEntity,detailTo);
lockedTo.setDetailTo(detailTo);
rabbitTemplate.convertAndSend("stock-event-exchange","stock.locked",lockedTo);
break;
} else {
//当前仓库锁失败,重试下一个仓库
}
}
if (skuStocked == false) {
//当前商品所有仓库都没有锁住
throw new NoStockException(skuId);
}
}
//3、肯定全部都是锁定成功的
return true;
}
<select id="listWareIdHasSkuStock" resultType="java.lang.Long">
SELECT
ware_id
FROM
wms_ware_sku
WHERE
sku_id = #{skuId}
AND stock - stock_locked > 0
</select>
<update id="lockSkuStock">
UPDATE wms_ware_sku
SET stock_locked = stock_locked + #{num}
WHERE
sku_id = #{skuId}
AND ware_id = #{wareId}
AND stock - stock_locked > 0
</update>