1. 限時訂單?
在各種電商網站下訂單後會保留一個時間段,時間段內未支付則自動將訂單狀態設置爲已過期。
2. 限時訂單現象
在我們生活中處處可見限時訂單的現象,如:在淘寶購物下單後沒有付款,會提示多長時間訂單失效;春季過年回家買火車 票,下了訂單後半個小時不付款改訂單就會取消;點外賣。。。
3. 解決方法一
輪詢數據庫:到實現一個定時器,每隔一段時間去檢查一遍數據庫裏的所有訂單,查看其狀態是否是未支付並且已經期。並修改這些數據的狀態爲已過期。
優點:方法簡單,容易實現
缺點:訂單狀態處理不及時,輪詢數據庫的次數中可能很多都並沒有修改訂單,數據庫頻繁多次被連接浪費數據庫資源開銷,因爲數據庫資源非常寶貴。
因此以上方式實際開發中基本不予採用。
4. 解決方法二
1.採用延時隊列
採用延時隊列並且與時間有關係的延時隊列DelayQueue。
實現原理:
- 用戶下單,保存訂單到數據庫的同時,將該訂單以及訂單的過期時間推入DelayQueue;
- 啓動一個檢查訂單到期的線程,該線程使用delayQueue的take()方法獲取到期訂單,該方法爲阻塞方法,如果當前沒有到期訂單,該方法會一直阻塞等待,直到獲取到訂單後繼續往下執行;
- 當take()獲取到一個到期訂單後,該線程按獲取到的訂單的id去數據庫查詢訂單並去檢查訂單狀態,如果爲未支付,則將狀態修改爲已關閉;
- 當項目重啓後,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. 解決方法三
採用消息隊列,原理類似,不在一一介紹。。。。