秒杀下单接口设计

秒杀下单接口设计

Posted by wenfengSAT on July 24, 2020

秒杀下单接口设计


1、新建秒杀产品,设置秒杀产品属性:秒杀开始时间、秒杀结束时间、秒杀价格、秒杀库存等属性
2、秒杀开始前10分钟,将秒杀产品信息同步到Redis
3、秒杀开始,根据秒杀时间及秒杀实时库存判断秒杀是否成功
4、定时更新秒杀时间结束、秒杀库存售罄的秒杀产品状态


/**
 * 
 * @Description: 秒杀订单
 * 
 * @author [ wenfengSAT@163.com ] on [2020年4月11日下午4:19:43]
 * @Modified By: [修改人] on [修改日期] for [修改说明]
 *
 */
@Slf4j
@Service(version = "1.0.0")
public class SeckillOrderImpl implements SeckillOrderService {

	@Autowired
	private SeckillComponentService seckillComponentService;

	/**
	 * 
	 * @Description:秒杀下单
	 * 
	 * @author [ wenfengSAT@163.com ]
	 * @Date [2020年4月11日下午4:25:54]
	 * @param req
	 * @return
	 *
	 */
	@Override
	public CreateSeckillOrderResp createSeckillOrder(CreateSeckillOrderReq req) {

		seckillComponentService.createBlockingQueue().add(req);

		CompletableFuture<CreateSeckillOrderResp> future = CompletableFuture.supplyAsync(() -> {
			try {
				return seckillComponentService.seckillDealResult();
			} catch (Exception e) {
				log.error("creatSeckillOrder error:[{}]", e);
				return new CreateSeckillOrderResp(ErrorCode.CREATE_SECKILL_ORDER_FIAL);
			}
		});

		try {
			CreateSeckillOrderResp response = future.get(); 
			return response;
		} catch (Exception e) {
			log.error("createSeckillOrder error:[{}]", e);
			return new CreateSeckillOrderResp(ErrorCode.CREATE_SECKILL_ORDER_FIAL);
		}
	}

}

/**
 * 
 * @Description: 秒杀组件
 * 
 * @author [ wenfengSAT@163.com ] on [2020年4月11日下午4:15:24]
 * @Modified By: [修改人] on [修改日期] for [修改说明]
 *
 */
@Slf4j
@Service
public class SeckillComponentService {

	private static final BlockingQueue<CreateSeckillOrderReq> queue = new LinkedBlockingQueue<CreateSeckillOrderReq>();

	public BlockingQueue<CreateSeckillOrderReq> createBlockingQueue() {
		return queue;
	}

	@Autowired
	private MapperFactory mapperFactory;

	@Autowired
	private OrderService orderService;

	/**
	 * 
	 * @Description: 秒杀处理结果
	 * 
	 * @author [ wenfengSAT@163.com ]
	 * @Date [2020年4月12日上午10:02:29]
	 * @return
	 * @throws Exception
	 *
	 */
	@Transactional
	public CreateSeckillOrderResp seckillDealResult() throws Exception {
		log.debug("seckillDealResult start.");
		if (CollUtil.isNotEmpty(queue)) {
			CreateSeckillOrderReq req = queue.take();
			return creatOrder(req);
		}
		return new CreateSeckillOrderResp(ErrorCode.CREATE_SECKILL_ORDER_FIAL);
	}

	/**
	 * 
	 * @Description: 创建订单
	 * 
	 * @author [ wenfengSAT@163.com ]
	 * @Date [2020年4月11日下午4:53:39]
	 * @param req
	 * @return
	 *
	 */
	public CreateSeckillOrderResp creatOrder(CreateSeckillOrderReq req) {
		CreatSeckillOrderReqBody body = req.getBody();
		String userId = mapperFactory.getApiTokenUtil().getTokenMsgByKey(req.getTspReqheader().getToken(),
				ApiTokenUtil.AUTH_TOKEN_ACCTID_KEY);
		boolean validateFlag = validateSign(body);
		if (!validateFlag) {
			return new CreateSeckillOrderResp(ErrorCode.SIGN_VALIDATE_ERROR);
		}
		String productId = body.getProductId();
		String seckillId = body.getSeckillId();
		int selNumber = Integer.parseInt(body.getSelNumber());
		body.setUserId(userId);
		ProductAttr productAttr = mapperFactory.getProductAttrMapper().queryProductByProductId(productId);
		if (Objects.isNull(productAttr)) {
			return new CreateSeckillOrderResp(ErrorCode.CREATE_ORDER_ERROR);
		}
		// 检查库存
		String seckillStockKey = SeckillConst.seckillStockKey + seckillId + SeckillConst.UNDERLINE_SYMBOL
				+ productAttr.getProductCode();
		boolean checkStockFlag = decrStock(seckillStockKey, selNumber);
		if (!checkStockFlag) {
			return new CreateSeckillOrderResp(ErrorCode.PRODUCT_STOCK_IS_ZERO);
		}
		String orderNo = IdUtil.getSnowflake(0, 0).nextIdStr();
		Date now = new Date();
		// 存储订单主表信息
		CreateOrderReqBodyMsg msg = BeanConvertUtils.convert(body, CreateOrderReqBodyMsg.class);
		int result = orderService.saveOrderHeader(msg, orderNo, now, productAttr,
				OrderSourceEnum.SECKILL_ORDER.getValue());
		String orderDetailId = IdUtil.getSnowflake(0, 0).nextIdStr();
		// 存储订单明细信息
		int resultDetail = orderService.saveOrderDetails(orderNo, now, productAttr, orderDetailId, msg.getSelNumber());
		// 存储订单扩展属性信息
		int resultDetailSpec = orderService.saveOrderDetailSpec(msg, productAttr, orderDetailId);
		// 保存订单-秒杀活动关系
		saveSeckillOrder(orderNo, seckillId);
		if (result < 1 && resultDetail < 1 && resultDetailSpec < 1) {
			return new CreateSeckillOrderResp(ErrorCode.CREATE_ORDER_ERROR);
		}
		CreateSeckillOrderResp resp = new CreateSeckillOrderResp();
		CreateOrderRespBodyMsg createOrderResult = new CreateOrderRespBodyMsg();
		createOrderResult.setOrderNo(orderNo);
		resp.setCreateOrderRespBodyMsg(createOrderResult);
		return resp;
	}

	/**
	 * 
	 * @Description: 保存订单-秒杀活动关系
	 * 
	 * @author [ wenfengSAT@163.com ]
	 * @Date [2020年7月5日下午2:56:45]
	 * @param orderId
	 * @param seckillId
	 *
	 */
	public void saveSeckillOrder(String orderId, String seckillId) {
		Map<String, String> param = new HashMap<String, String>();
		param.put("orderId", orderId);
		param.put("seckillId", seckillId);
		mapperFactory.getOrderHeaderMapper().insertSeckillOrder(param);
	}


	/**
	 * 
	 * @Description: 扣减库存
	 * 
	 * @author [ wenfengSAT@163.com ]
	 * @Date [2020年4月12日下午2:42:45]
	 * @param key-库存key
	 * @param num-扣减库存数量
	 * @return
	 *
	 */
	public boolean decrStock(String key, int num) {
		int operatNum = 0;
		for (int i = 0; i < num; i++) {
			long value = mapperFactory.getRedisService().decr(key);
			if (value < 0) {
				mapperFactory.getRedisService().incr(key);
				break;
			}
			operatNum++;
		}
		if (operatNum < num) {
			resetStock(key, operatNum);
			return false;
		}
		return true;
	}

	/**
	 * 
	 * @Description: 还原秒杀库存
	 * 
	 * @author [ wenfengSAT@163.com ]
	 * @Date [2020年4月12日下午2:42:18]
	 * @param key-库存key
	 * @param num-还原库存数量
	 *
	 */
	public void resetStock(String key, int num) {
		IntStream.range(0, num).forEach(index -> {
			mapperFactory.getRedisService().incr(key);
		});
	}

	/**
	 * 
	 * @Description: 验签
	 * 
	 * @author [ wenfengSAT@163.com ]
	 * @Date [2020年4月12日上午10:40:55]
	 * @param body
	 * @return
	 *
	 */
	public boolean validateSign(CreatSeckillOrderReqBody body) {
		String productId = body.getProductId();
		String productSelPrice = body.getProductSelPrice();
		String userId = body.getUserId();
		String selNumber = body.getSelNumber();
		String productName = body.getProductName();
		String carNO = body.getCarNO();
		String isPayIntegral = body.getIsPayIntegral();
		String sign = body.getSign();
		Digester sha1 = new Digester(DigestAlgorithm.SHA256);
		StringBuffer sb = new StringBuffer();
		sb.append("productId").append("=").append(productId);
		sb.append("&").append("userId").append("=").append(userId);
		sb.append("&").append("productName").append("=").append(productName);
		sb.append("&").append("productSelPrice").append("=").append(productSelPrice);
		sb.append("&").append("selNumber").append("=").append(selNumber);
		if (sha1.digestHex(sb.toString()).equals(sign)) {
			return true;
		}
		return false;
	}
}