//内存标记,商品是否卖完
private HashMap<Long, Boolean> localOverMap = new HashMap<Long, Boolean>();
/**
* 系统初始化,加载商品库存,并为每个商品初始化内存标记
* */
public void afterPropertiesSet() throws Exception { //该Controller实现InitializingBean
List<GoodsVo> goodsList = goodsService.listGoodsVo();
if(goodsList == null) {
return;
}
for(GoodsVo goods : goodsList) {
redisService.set(GoodsKey.getMiaoshaGoodsStock, ""+goods.getId(), goods.getStockCount());
localOverMap.put(goods.getId(), false);
}
}
//这里使用pathVariable,为了拼接动态路径,也是一种防刷手段
@RequestMapping(value="/{path}/do_miaosha", method=RequestMethod.POST)
@ResponseBody
public Result<Integer> miaosha(Model model,MiaoshaUser user,
@RequestParam("goodsId")long goodsId,
@PathVariable("path") String path) {
model.addAttribute("user", user);
if(user == null) {
return Result.error(CodeMsg.SESSION_ERROR);
}
//验证path
boolean check = miaoshaService.checkPath(user, goodsId, path);
if(!check){
return Result.error(CodeMsg.REQUEST_ILLEGAL);
}
//内存标记,减少redis访问,如果没有这一步,也可以,直接去预减库存,但是,会造成redis的负担,所以加一个非线程安全的map,去做一层简单过滤
//同一时间,有可能100个人去抢最后一件商品(get为false),但是没关系,有redis的decr去做原子保障,保证不会超卖
boolean over = localOverMap.get(goodsId);
if(over) {
return Result.error(CodeMsg.MIAO_SHA_OVER);
}
//开始主流程:预减库存->判断重复秒杀->加入消息队列,返回成功(即排队中)
//预减库存
long stock = redisService.decr(GoodsKey.getMiaoshaGoodsStock, ""+goodsId);//10
if(stock < 0) {
localOverMap.put(goodsId, true);
redisService.set(GoodsKey.getMiaoshaGoodsStock, ""+goodsId,0);
return Result.error(CodeMsg.MIAO_SHA_OVER);
}
//判断是否已经秒杀到了
//redisService.set(OrderKey.getMiaoshaOrderByUidGid, ""+user.getId()+"_"+goods.getId(), miaoshaOrder);
MiaoshaOrder order = orderService.getMiaoshaOrderByUserIdGoodsId(user.getId(), goodsId);
if(order != null) {
redisService.incr(GoodsKey.getMiaoshaGoodsStock, ""+goodsId);
localOverMap.put(goodsId, false);
return Result.error(CodeMsg.REPEATE_MIAOSHA);
}
//入队
MiaoshaMessage mm = new MiaoshaMessage();
mm.setUser(user);
mm.setGoodsId(goodsId);
//消息队列发送者,发送消息
sender.sendMiaoshaMessage(mm);
return Result.success(0);//排队中,用户点击秒杀后(内部去请求path,然后用path去请求秒杀服务,然后调轮询接口,查询结果)
}
/**做延迟任务setTimeOut,轮询秒杀结果
* orderId:成功
* -1:秒杀失败
* 0: 排队中
* */
@RequestMapping(value="/result", method=RequestMethod.GET)
@ResponseBody
public Result<Long> miaoshaResult(Model model,MiaoshaUser user,
@RequestParam("goodsId")long goodsId) {
model.addAttribute("user", user);
if(user == null) {
return Result.error(CodeMsg.SESSION_ERROR);
}
long result =miaoshaService.getMiaoshaResult(user.getId(), goodsId);
return Result.success(result);
}
@AccessLimit(seconds=5, maxCount=5, needLogin=true)//自定义注解,接口防刷
@RequestMapping(value="/path", method=RequestMethod.GET)//先获取path,才能请求秒杀接口
@ResponseBody
public Result<String> getMiaoshaPath(HttpServletRequest request, MiaoshaUser user,
@RequestParam("goodsId")long goodsId,
@RequestParam(value="verifyCode", defaultValue="0")int verifyCode
) {
if(user == null) {
return Result.error(CodeMsg.SESSION_ERROR);
}
boolean check = miaoshaService.checkVerifyCode(user, goodsId, verifyCode);
if(!check) {
return Result.error(CodeMsg.REQUEST_ILLEGAL);
}
String path = miaoshaService.createMiaoshaPath(user, goodsId);
return Result.success(path);
}
}