秒杀

秒杀

秒杀功能的分析:

商家申请秒杀,运营商审核,

审核通过后创建定时任务,在秒杀的前一天把秒杀商品放到Redis

到了秒杀时候,去Redis查询秒杀商品

点击秒杀按钮,根据id查询秒杀商品详情

点击抢购按钮,提交订单,然后后台减库存,当库存为0或者时间没到无法下单

秒杀订单提交成功,跳转支付页面调用微信支付接口,付款成功,填写收货信息,5分钟内没付款,取消订单,恢复库存

1.定时任务-提前把商品放入Redis
@Component
public class SeckillGoodsTask {
   @Autowired
   private TbSeckillGoodsMapper seckillGoodsMapper;
   @Autowired
   private RedisTemplate redisTemplate;
   //    把mysql中需要秒杀的商品放入到redis中
   @Scheduled(cron = "59 14 19 14 12 ?") // Spring的定时任务注解
   public void initSeckillGoods() {
       // 1、查询符合要求的数据
       // 要求:审核通过+时间范围之内+库存大于0
       // select * from tb_seckill_goods where STATUS='1' and stock_count>0
       // and  start_time<now() and  end_time>now()
       TbSeckillGoodsExample example = new TbSeckillGoodsExample();
       example.createCriteria()
               .andStatusEqualTo("1")
               .andStockCountGreaterThan(0)
               .andStartTimeLessThan(new Date())
               .andEndTimeGreaterThan(new Date());
       List<TbSeckillGoods> tbSeckillGoods = seckillGoodsMapper.selectByExample(example);
       // 2、把数据一个一个地放到redis中
       for (TbSeckillGoods tbSeckillGood : tbSeckillGoods) {
           redisTemplate.boundHashOps("seckill_goods").put(tbSeckillGood.getId(), tbSeckillGood);
           for (int i = 0; i < tbSeckillGood.getStockCount(); i++) {
               // 把商品的id放入到Redis队列中
               redisTemplate.boundListOps("seckill_goods_" + tbSeckillGood.getId()).rightPush(tbSeckillGood.getId());
           }
       }
       System.out.println("商品已放入到redis中");
   }
}
2.js

// 查询秒杀商品
$scope.findSeckillGoods=function () {
   seckillService.findSeckillGoods().success(function (response) {
        $scope.list=response;
   })
}
// 按ID查询秒杀商品
$scope.findOne=function () {
  var id = $location.search()['id'];
   seckillService.findOne(id).success(function (response) {
       $scope.entity=response;
       //结束时间和当前时间相差的秒数并且向下取整Math.floor 13376.91--->13376
       var totalSeconds = Math.floor((new Date($scope.entity.endTime).getTime() - new Date().getTime())/1000);
       $interval(function () {
           $scope.timeStr="";
           var days = Math.floor(totalSeconds/60/60/24);  //13.5233---13天
           var hours =Math.floor( (totalSeconds-days*24*60*60)/60/60); //12.234545-->12
           var minuts =Math.floor( (totalSeconds-days*24*60*60-hours*60*60)/60); //20.1322-->20
           var seconds = totalSeconds-days*24*60*60-hours*60*60-minuts*60;
           if(days<10){
               days="0"+days;
           }
           if(hours<10){
               hours="0"+hours;
           }
           if(minuts<10){
               minuts="0"+minuts;
           }
           if(seconds<10){
               seconds="0"+seconds;
           }
           if(days==0){
               $scope.timeStr+=hours+":"+minuts+":"+seconds;
           }else{
               $scope.timeStr+=days+"天 "+hours+":"+minuts+":"+seconds;
           }
           totalSeconds--;
       },1000,totalSeconds);
       // 距离结束: 0天 12:56:78
   })
}
// 保存秒杀订单
$scope.saveOrder=function () {
   seckillService.saveOrder($scope.entity.id).success(function (response) {
       if(response.success){
           location.href="pay.html";
       }else{
           alert(response.message);
       }
   })
}
3.Controller
@RestController
@RequestMapping("/seckill")
public class SeckillController {
   @Reference
   private SeckillService seckillService;
   @RequestMapping("/findSeckillGoods")
   private List<TbSeckillGoods> findSeckillGoods() {
       return seckillService.findSeckillGoods();
   }
   @RequestMapping("/findOne")
   private TbSeckillGoods findOne(Long id) {
       return seckillService.findOne(id);
   }
   @RequestMapping("/saveOrder")
   public Result saveOrder(Long id){
       try {
           String userId = SecurityContextHolder.getContext().getAuthentication().getName();
           if(userId.equals("anonymousUser")){
               return new Result(false,"请先登录");
           }
           seckillService.saveOrder(id,userId);
           return new Result(true,"");
       }catch (RuntimeException e){
           return new Result(false,e.getMessage());
       }
       catch (Exception e) {
           e.printStackTrace();
           return new Result(false,"抢购失败");
       }
   }

}
4.Service
@Service
public class SeckillServiceImpl implements SeckillService {
   @Autowired
   private RedisTemplate redisTemplate;
   @Autowired
   private   executor;
   @Autowired
   private CreateOrder createOrder;
 
   @Override
   public List<TbSeckillGoods> findSeckillGoods() {
       return redisTemplate.boundHashOps("seckill_goods").values();
   }

   @Override
   public TbSeckillGoods findOne(Long id) {
       return (TbSeckillGoods) redisTemplate.boundHashOps("seckill_goods").get(id);
   }

   @Override
   public void saveOrder(Long id, String userId) {
       Object object = redisTemplate.boundHashOps("seckill_order").get(userId);//判断是否下过单
       if (object != null) {
           throw new RuntimeException("请先支付您其他的秒杀订单");
       }
     
      // 采用redis出栈的方式控制超卖问题
       Object o = redisTemplate.boundListOps("seckill_goods_" + id).leftPop();
       if (o == null) {
           throw new RuntimeException("商品已售罄");
       }
       // 把多线程中需要的参数通过redis队列传递过去
       Map map = new HashMap();
       map.put("id", id);
       map.put("userId", userId);
       redisTemplate.boundListOps("userid_id").rightPush(map);
       // 触发多线程的代码
       executor.execute(createOrder);
   }


}
5.多线程任务
@Component
public class CreateOrder implements Runnable {

   @Autowired
   private RedisTemplate redisTemplate;
   @Autowired
   private IdWorker idWorker;
   @Autowired
   private TbSeckillOrderMapper seckillOrderMapper;
   @Autowired
   private TbSeckillGoodsMapper seckillGoodsMapper;

   @Override
   public void run() {
       Map map = (Map) redisTemplate.boundListOps("userid_id").leftPop();
       Long id = (Long) map.get("id");
       String userId = (String) map.get("userId");

       TbSeckillGoods seckillGoods = (TbSeckillGoods) redisTemplate.boundHashOps("seckill_goods").get(id);

       TbSeckillOrder seckillOrder = new TbSeckillOrder();
       seckillOrder.setId(idWorker.nextId());
       seckillOrder.setSeckillId(id);
       seckillOrder.setMoney(seckillGoods.getCostPrice());
       seckillOrder.setUserId(userId);
       seckillOrder.setSellerId(seckillGoods.getSellerId());
       seckillOrder.setCreateTime(new Date());
       seckillOrder.setStatus("0");
       seckillOrderMapper.insert(seckillOrder);
       // 减库存 规则:如果减完库存后为0 同步更新到mysql中 并且从redis中中移除
       seckillGoods.setStockCount(seckillGoods.getStockCount() - 1);
       if (seckillGoods.getStockCount() == 0) {
           seckillGoodsMapper.updateByPrimaryKey(seckillGoods); // 更新数据库秒杀商品的库存
           redisTemplate.boundHashOps("seckill_goods").delete(id);
       } else {
           redisTemplate.boundHashOps("seckill_goods").put(id, seckillGoods);
       }
       redisTemplate.boundHashOps("seckill_order").put(userId, seckillOrder); //把个人下的单放入到redis中
   }
}
6.多线程的配置
<bean class="org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor" id="executor">
   <!-- 核心线程数,默认为1-->
   <property name="corePoolSize" value="10" />
   <!--最大线程数,默认为Integer.MAX_VALUE-->
   <property name="maxPoolSize" value="50" />
   <!--队列最大长度,一般需要设置值>=notifyScheduledMainExecutor.maxNum;默认为Integer.MAX_VALUE-->
   <property name="queueCapacity" value="10000" />
   <!--线程池维护线程所允许的空闲时间,默认为60s-->
   <property name="keepAliveSeconds" value="300" />
   <!--线程池对拒绝任务(无线程可用)的处理策略,目前只支持AbortPolicy、CallerRunsPolicy;默认为后者-->
   <property name="rejectedExecutionHandler">
       <bean class="java.util.concurrent.ThreadPoolExecutor$CallerRunsPolicy" />
   </property>
</bean>


发表评论