限時訂單實現方案

1. 限時訂單?

          在各種電商網站下訂單後會保留一個時間段,時間段內未支付則自動將訂單狀態設置爲已過期。 

 

2. 限時訂單現象

 

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

 

3. 解決方法一

          輪詢數據庫:到實現一個定時器,每隔一段時間去檢查一遍數據庫裏的所有訂單,查看其狀態是否是未支付並且已經期。並修改這些數據的狀態爲已過期。

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

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

          因此以上方式實際開發中基本不予採用。

4. 解決方法二

    1.採用延時隊列

        採用延時隊列並且與時間有關係的延時隊列DelayQueue。

         實現原理:

  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. 解決方法三   

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

 

 

 

 

 

 

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章