天天看點

限時訂單實作方案1. 限時訂單?2. 限時訂單現象3. 解決方法一4. 解決方法二5. 解決方法三   

1. 限時訂單?

          在各種電商網站下訂單後會保留一個時間段,時間段内未支付則自動将訂單狀态設定為已過期。 

2. 限時訂單現象

在我們生活中處處可見限時訂單的現象,如:在淘寶購物下單後沒有付款,會提示多長時間訂單失效;春季過年回家買火車  票,下了訂單後半個小時不付款改訂單就會取消;點外賣。。。

3. 解決方法一

          輪詢資料庫:到實作一個定時器,每隔一段時間去檢查一遍資料庫裡的所有訂單,檢視其狀态是否是未支付并且已經期。并修改這些資料的狀态為已過期。

          優點:方法簡單,容易實作

          缺點:訂單狀态處理不及時,輪詢資料庫的次數中可能很多都并沒有修改訂單,資料庫頻繁多次被連接配接浪費資料庫資源開銷,因為資料庫資源非常寶貴。

          是以以上方式實際開發中基本不予采用。

4. 解決方法二

    1.采用延時隊列

        采用延時隊列并且與時間有關系的延時隊列DelayQueue。

         實作原理:

限時訂單實作方案1. 限時訂單?2. 限時訂單現象3. 解決方法一4. 解決方法二5. 解決方法三   
  1.  使用者下單,儲存訂單到資料庫的同時,将該訂單以及訂單的過期時間推入DelayQueue;
  2.  啟動一個檢查訂單到期的線程,該線程使用delayQueue的take()方法擷取到期訂單,該方法為阻塞方法,如果目前沒有到期訂單,該方法會一直阻塞等待,直到擷取到訂單後繼續往下執行;
  3. 當take()擷取到一個到期訂單後,該線程按擷取到的訂單的id去資料庫查詢訂單并去檢查訂單狀态,如果為未支付,則将狀态修改為已關閉;
  4. 當項目重新開機後,DelayQueue中的資訊都沒有了。是以項目啟動掃描所有過期未支付的訂單并修改為已關閉狀态,掃描所有未過期未支付的訂單到DelayQueue中。

   2. 代碼實作     

       延時隊列實體bean:             

/**
 * 
 *  延時隊列實體Delayed
 * @author reyco
 *
 */
public class DelayedVo<T> implements Delayed{
	/**
	 * 過期時長/機關毫秒
	 */
	private Long expireTime;
	/**
	 * 目标對象
	 */
	private T target;
	
	public DelayedVo(Long expireTime, T target) {
		super();
		this.expireTime = expireTime+System.currentTimeMillis();
		this.target = target;
	}

	@Override
	public int compareTo(Delayed o) {
		return (int)(this.getDelay(TimeUnit.MILLISECONDS) - o.getDelay(TimeUnit.MILLISECONDS));
	}

	@Override
	public long getDelay(TimeUnit unit) {
		return unit.convert(expireTime - System.currentTimeMillis() , TimeUnit.MILLISECONDS);
	}

	public T getTarget() {
		return this.target;
	}

}
           

       延時訂單:

/**
 * 延時訂單....
 * @author reyco
 *
 */
@Service
public class DelayOrderService{
	
	/**
	 * 訂單狀态   1未支付 2已付款 3訂單關閉 4訂單完成
	 */
	/**
	 * 未支付
	 */
	private final static Integer UNPAID = 1;
	/**
	 * 訂單關閉
	 */
	private final static Integer CLOSE = 3;
	
	@Autowired
	private OrderDao orderDao;
	
	private static DelayQueue<DelayedVo<OrderEntity>> delayQueue = new DelayQueue<DelayedVo<OrderEntity>>();
	/**
	 * 添加訂單到DelayQueue
	 * @param orderEntity
	 * @param expireTime
	 */
	public void save(OrderEntity orderEntity,Long expireTime) {
		DelayedVo<OrderEntity> delayedVo = new DelayedVo<>(expireTime, orderEntity);
		delayQueue.put(delayedVo);
		System.out.println("訂單【逾時時間:"+expireTime+"毫秒】被推入延時隊列,訂單詳情:"+orderEntity);
	}
	/**
	 * 異步線程處理DelayQueue
	 * @author reyco
	 *
	 */
	class OrderTask implements Runnable{
		@Override
		public void run() {
			try {
				while(true) {
					DelayedVo<OrderEntity> delayedVo = delayQueue.take();
					OrderEntity orderEntity = (OrderEntity)delayedVo.getTarget();
					OrderEntity selOrderEntity = orderDao.get(orderEntity.getId());
					//判斷資料庫中訂單是否未支付
					if(selOrderEntity.getState()==UNPAID) {
						selOrderEntity.setState(CLOSE);
						System.out.println("訂單關閉:order="+selOrderEntity);
						orderDao.update(selOrderEntity);
					}else {
						System.out.println("訂單已處理:orderEntity="+selOrderEntity);
					}
				}
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
	}
	/**
	 * 啟動異步線程
	 */
	@PostConstruct
	public void init() {
		new Thread(new OrderTask() ).start();
	}
	/**
	 * 啟動修改過期未支付訂單為已關閉狀态
	 * 啟動掃描資料庫中的訂單未過期未支付到DelayQueue
	 */
	@PostConstruct
	public void initDelayOrder() {
		//1. 處理過期未支付的訂單...
		Integer count = orderDao.updateExpire();
		System.out.println("系統啟動,掃描處理【"+count+"】個過期未支付的訂單...");
		
		//2. 擷取未過期未支付的訂單
		List<OrderEntity> orders = orderDao.listOrderNoExpire();
		System.out.println("系統啟動,發現【"+orders.size()+"】個未過期未支付的訂單...");
		//3. 未過期未支付的訂單推入延時隊列
		if(null!=orders && orders.size()>0) {
			for (OrderEntity order : orders) {
				long expireTime = order.getGmtExpire().getTime()-(new Date().getTime());
				DelayedVo<OrderEntity> delayedVo = new DelayedVo<>(expireTime, order);
				delayQueue.put(delayedVo);
				System.out.println("訂單【逾時時間:"+expireTime+"毫秒】被推入延時隊列,訂單詳情:"+order);
			}
		}
	}
}
           

        訂單Service:   

/**
 * 訂單Service
 * @author reyco
 *
 */
@Service
public class OrderServiceImpl implements OrderService{

	@Autowired
	private OrderDao orderedDao;
	
	@Autowired
	private DelayOrderService delayOrderService;
	
	@Override
	@Transactional(propagation=Propagation.REQUIRED)
	public void save(OrderEntity orderEntity) {
		//訂單号
		Long no = new SnowFlake(2,3).nextId();
		//逾時時長
		long expireTime = 1000 * 60 * 1;
		Date gmtExpire = new Date();
		gmtExpire.setTime(System.currentTimeMillis() + expireTime);
		
		orderEntity = new OrderEnitiyBuilder()
				.builderNo(no.toString())
				.builderContent("500塊錢的羽絨服。。。")
				.builderState(1)
				.builderGmtExpire(gmtExpire)
				.builderGmtDesc("備注")
				.builder();
		// 儲存到資料庫
		orderedDao.save(orderEntity);
		// 
		delayOrderService.save(orderEntity, expireTime);
	}

}
           

        訂單controller:      

@RequestMapping("/api/order")
@RestController
public class OrderController {
	
	@Autowired
	private OrderService orderService;
	
	@PostMapping("save")
	public String save(@RequestBody OrderEntity orderEntity) {
		orderService.save(orderEntity);
		return "ok";
	}
}
           

5. 解決方法三   

        采用消息隊列,原理類似,不在一一介紹。。。。

繼續閱讀