Warning: error_log(/data/www/wwwroot/hmttv.cn/caches/error_log.php): failed to open stream: Permission denied in /data/www/wwwroot/hmttv.cn/phpcms/libs/functions/global.func.php on line 537 Warning: error_log(/data/www/wwwroot/hmttv.cn/caches/error_log.php): failed to open stream: Permission denied in /data/www/wwwroot/hmttv.cn/phpcms/libs/functions/global.func.php on line 537
態(tài)機(jī)簡介:
狀態(tài)機(jī)是有限狀態(tài)自動機(jī)的簡稱,是現(xiàn)實事物運(yùn)行規(guī)則抽象而成的一個數(shù)學(xué)模型。【規(guī)則的抽象】
有限狀態(tài)機(jī)一般都有以下特點:
(1)可以用狀態(tài)來描述事物,并且任一時刻,事物總是處于一種狀態(tài);
(2)事物擁有的狀態(tài)總數(shù)是有限的;
(3)通過觸發(fā)事物的某些行為,可以導(dǎo)致事物從一種狀態(tài)過渡到另一種狀態(tài);
(4)事物狀態(tài)變化是有規(guī)則的,A狀態(tài)可以變換到B,B可以變換到C,A卻不一定能變換到C;
(5)同一種行為,可以將事物從多種狀態(tài)變成同種狀態(tài),但是不能從同種狀態(tài)變成多種狀態(tài)。
狀態(tài)機(jī)這種描述客觀世界的方式就是將事物抽象成若干狀態(tài),然后所有的事件和規(guī)則導(dǎo)致事物在這些狀態(tài)中游走。最終使得事物“自圓其說”。
很多通信協(xié)議的開發(fā)都必須用到狀態(tài)機(jī);一個健壯的狀態(tài)機(jī)可以讓你的程序,不論發(fā)生何種突發(fā)事件都不會突然進(jìn)入一個不可預(yù)知的程序分支。
四大概念:
狀態(tài)(state)
一個狀態(tài)機(jī)至少要包含兩個狀態(tài)。
分為:現(xiàn)態(tài)(源狀態(tài))、次態(tài)(目標(biāo)狀態(tài))
狀態(tài)可以理解為一種結(jié)果,一種穩(wěn)態(tài)形式,沒有擾動會保持不變的。
狀態(tài)命名形式:
1.副詞+動詞;例如:待審批、待支付、待收貨
這種命名方式體現(xiàn)了:狀態(tài)機(jī)就是事件觸發(fā)狀態(tài)不斷遷徙的本質(zhì)。表達(dá)一種待觸發(fā)的感覺。
2.動詞+結(jié)果;例如:審批完成、支付完成
3.已+動詞形式;例如:已發(fā)貨、已付款
以上兩種命名方式體現(xiàn)了:狀態(tài)是一種結(jié)果或者穩(wěn)態(tài)的本質(zhì)。表達(dá)了一種已完成的感覺。
角色很多的時候,為了表示清晰,可以加上角色名:例如:待財務(wù)審批、主管批準(zhǔn)
命名考慮從用戶的理解的角度出發(fā)。
事件(event)
or
觸發(fā)條件
又稱為“條件”,就是某個操作動作的觸發(fā)條件或者口令。當(dāng)一個條件滿足時,就會觸發(fā)一個動作,或者執(zhí)行一次狀態(tài)遷徙
這個事件可以是外部調(diào)用、監(jiān)聽到消息、或者各種定時到期等觸發(fā)的事件。
對于燈泡,“打開開關(guān)”就是一個事件。
條件命名形式:動詞+結(jié)果;例如:支付成功、下單時間>5分鐘
動作(action)
事件發(fā)生以后要執(zhí)行動作。例如:事件=“打開開關(guān)指令”,動作=“開燈”。一般就對應(yīng)一個函數(shù)。
條件滿足后執(zhí)行動作。動作執(zhí)行完畢后,可以遷移到新的狀態(tài),也可以仍舊保持原狀態(tài)。
動作不是必需的,當(dāng)條件滿足后,也可以不執(zhí)行任何動作,直接遷移到新狀態(tài)。
那么如何區(qū)分“動作”和“狀態(tài)”?
“動作”是不穩(wěn)定的,即使沒有條件的觸發(fā),“動作”一旦執(zhí)行完畢就結(jié)束了;
而“狀態(tài)”是相對穩(wěn)定的,如果沒有外部條件的觸發(fā),一個狀態(tài)會一直持續(xù)下去。
變換(transition)
即從一個狀態(tài)變化到另外一個狀態(tài)
例如:“開燈過程”就是一個變化
狀態(tài)機(jī)其他表達(dá)方式:
狀態(tài)機(jī)的設(shè)計:
信息系統(tǒng)中有很多狀態(tài)機(jī),例如:業(yè)務(wù)訂單的狀態(tài)。
狀態(tài)機(jī)的設(shè)計存在的問題:什么是狀態(tài)?到底有多少個狀態(tài)?要細(xì)分到什么程度?
信息系統(tǒng)是現(xiàn)實世界的一種抽象和描述。而業(yè)務(wù)領(lǐng)域中那些已經(jīng)發(fā)生的事件就是事實,信息系統(tǒng)就是將這些事實以信息的形式存儲到數(shù)據(jù)庫中,即:信息就是一組事實
信息系統(tǒng)就是存儲這些事實,對這些事實進(jìn)行管理與追蹤,進(jìn)而起到提高工作效率的作用。
信息系統(tǒng)就是記錄已經(jīng)發(fā)生的事實,信息系統(tǒng)中的狀態(tài)基本和事實匹配。即:標(biāo)識某個事實的完成度。
業(yè)務(wù)系統(tǒng),根據(jù)實際業(yè)務(wù),具體會有哪些發(fā)生的事實需要記錄,基本上這些事實就至少對應(yīng)一個狀態(tài)。需要記錄的事實就是一種穩(wěn)態(tài),一種結(jié)果。
例如:【待支付】->【已支付】->【已收貨】->【已評價】
這些都是系統(tǒng)需要記錄的已發(fā)生的客觀事實。而這些事實就對應(yīng)了狀態(tài),而發(fā)生這些事實的事件就對應(yīng)了觸發(fā)狀態(tài)機(jī)的轉(zhuǎn)換的事件。
根據(jù)自己的業(yè)務(wù)實際進(jìn)行分析,并畫出狀態(tài)圖即可。
狀態(tài)機(jī)實現(xiàn)方式:狀態(tài)模式
下面是經(jīng)典的自動販賣機(jī)例子來說明狀態(tài)模式的用法,狀態(tài)圖如下:
分析一個這個狀態(tài)圖:
a、包含4個狀態(tài)(我們使用4個int型常量來表示)
b、包含3個暴露在外的方法(投幣、退幣、轉(zhuǎn)動手柄、(發(fā)貨動作是內(nèi)部方法,售賣機(jī)未對外提供方法,售賣機(jī)自動調(diào)用))
c、我們需要處理每個狀態(tài)下,用戶都可以觸發(fā)這三個動作。
我們可以做沒有意義的事情,在【未投幣】狀態(tài),試著退幣,或者同時投幣兩枚,此時機(jī)器會提示我們不能這么做。
實現(xiàn)邏輯:
任何一個可能的動作,我們都要檢查,看看我們所處的狀態(tài)和動作是否合適。
狀態(tài)機(jī)使用if-else或switch實現(xiàn)
測試自動售賣機(jī)
使用if-else/switch的方式實現(xiàn)狀態(tài)有如下問題:
例如:現(xiàn)在增加一個狀態(tài)。每個方法都需要添加if-else語句。
升級策略:
【封裝變化】,局部化每個狀態(tài)的行為,將每個狀態(tài)的行為放到各自類中,每個狀態(tài)只要實現(xiàn)自己的動作就可以了。
販賣機(jī)只要將動作委托給代表當(dāng)前狀態(tài)的狀態(tài)對象即可。
public interface State
{
/**
* 放錢
*/
public void insertMoney();
/**
* 退錢
*/
public void backMoney();
/**
* 轉(zhuǎn)動曲柄
*/
public void turnCrank();
/**
* 出商品
*/
public void dispense();
}
public class NoMoneyState implements State
{
private VendingMachine machine;
public NoMoneyState(VendingMachine machine)
{
this.machine=machine;
}
@Override
public void insertMoney()
{
System.out.println("投幣成功");
machine.setState(machine.getHasMoneyState());
}
@Override
public void backMoney()
{
System.out.println("您未投幣,想退錢?...");
}
@Override
public void turnCrank()
{
System.out.println("您未投幣,想拿東西么?...");
}
@Override
public void dispense()
{
throw new IllegalStateException("非法狀態(tài)!");
}
}
public class HasMoneyState implements State
{
private VendingMachine machine;
public HasMoneyState(VendingMachine machine)
{
this.machine=machine;
}
@Override
public void insertMoney()
{
System.out.println("您已經(jīng)投過幣了,無需再投....");
}
@Override
public void backMoney()
{
System.out.println("退幣成功");
machine.setState(machine.getNoMoneyState());
}
@Override
public void turnCrank()
{
System.out.println("你轉(zhuǎn)動了手柄");
machine.setState(machine.getSoldState());
}
@Override
public void dispense()
{
throw new IllegalStateException("非法狀態(tài)!");
}
}
public class SoldOutState implements State
{
private VendingMachine machine;
public SoldOutState(VendingMachine machine)
{
this.machine=machine;
}
@Override
public void insertMoney()
{
System.out.println("投幣失敗,商品已售罄");
}
@Override
public void backMoney()
{
System.out.println("您未投幣,想退錢么?...");
}
@Override
public void turnCrank()
{
System.out.println("商品售罄,轉(zhuǎn)動手柄也木有用");
}
@Override
public void dispense()
{
throw new IllegalStateException("非法狀態(tài)!");
}
}
public class SoldState implements State
{
private VendingMachine machine;
public SoldState(VendingMachine machine)
{
this.machine=machine;
}
@Override
public void insertMoney()
{
System.out.println("正在出貨,請勿投幣");
}
@Override
public void backMoney()
{
System.out.println("正在出貨,沒有可退的錢");
}
@Override
public void turnCrank()
{
System.out.println("正在出貨,請勿重復(fù)轉(zhuǎn)動手柄");
}
@Override
public void dispense()
{
machine.releaseBall();
if (machine.getCount() > 0)
{
machine.setState(machine.getNoMoneyState());
} else
{
System.out.println("商品已經(jīng)售罄");
machine.setState(machine.getSoldOutState());
}
}
}
public class VendingMachine
{
private State noMoneyState;
private State hasMoneyState;
private State soldState;
private State soldOutState;
private State winnerState ;
private int count=0;
private State currentState=noMoneyState;
public VendingMachine(int count)
{
noMoneyState=new NoMoneyState(this);
hasMoneyState=new HasMoneyState(this);
soldState=new SoldState(this);
soldOutState=new SoldOutState(this);
winnerState=new WinnerState(this);
if (count > 0)
{
this.count=count;
currentState=noMoneyState;
}
}
//將這些動作委托給當(dāng)前狀態(tài).
public void insertMoney()
{
currentState.insertMoney();
}
public void backMoney()
{
currentState.backMoney();
}
// 機(jī)器不用提供dispense動作,因為這是一個內(nèi)部動作.用戶不可以直
//接要求機(jī)器發(fā)放糖果.我們在狀態(tài)對象的turnCrank()方法中調(diào)用
//dispense方法;
//dispense無論如何,即使在nomoney狀態(tài)也會被執(zhí)行.
//讓不合法的情形下,dispense拋出異常處理。
public void turnCrank()
{
currentState.turnCrank();
currentState.dispense();
}
public void releaseBall()
{
System.out.println("發(fā)出一件商品...");
if (count !=0)
{
count -=1;
}
}
public void setState(State state)
{
this.currentState=state;
}
//getter setter omitted ...
}
我們之前說過,if-else/switch實現(xiàn)方式?jīng)]有彈性,那現(xiàn)在按照這種實現(xiàn)模式,需求變更修改起來會輕松點嗎?
紅色部分標(biāo)記了我們的需求變更:當(dāng)用戶每次轉(zhuǎn)動手柄的時候,有10%的幾率贈送一瓶。
實現(xiàn)方式:
我們遵守了【開閉】原則,只要新建一個WinnerState的類即可。然后有限的修改has_money的轉(zhuǎn)向即可。
為什么WinnerState要獨(dú)立成一個狀態(tài),其實它和sold狀態(tài)一模一樣。我把代碼寫在SoldState中不行嗎?
如果sold需求變化不一定影響到winner代碼實現(xiàn),winner需求變化時,也不一定要修改sold,比如促銷方案結(jié)束了,中獎概率變了等。
如果他們的變化不是一定互相影響到彼此的,那我們就該將他們分離,即是【隔離變化】也是遵守【單一職責(zé)】的原則。
public class WinnerState implements State
{
private VendingMachine machine;
public WinnerState(VendingMachine machine)
{
this.machine=machine;
}
@Override
public void insertMoney()
{
throw new IllegalStateException("非法狀態(tài)");
}
@Override
public void backMoney()
{
throw new IllegalStateException("非法狀態(tài)");
}
@Override
public void turnCrank()
{
throw new IllegalStateException("非法狀態(tài)");
}
@Override
public void dispense()
{
System.out.println("你中獎了,恭喜你,將得到2件商品");
machine.releaseBall();
if (machine.getCount()==0)
{
System.out.println("商品已經(jīng)售罄");
machine.setState(machine.getSoldOutState());
} else
{
machine.releaseBall();
if (machine.getCount() > 0)
{
machine.setState(machine.getNoMoneyState());
} else
{
System.out.println("商品已經(jīng)售罄");
machine.setState(machine.getSoldOutState());
}
}
}
}
public class HasMoneyState implements State
{
private VendingMachine machine;
private Random random=new Random();
public HasMoneyState(VendingMachine machine)
{
this.machine=machine;
}
@Override
public void insertMoney()
{
System.out.println("您已經(jīng)投過幣了,無需再投....");
}
@Override
public void backMoney()
{
System.out.println("退幣成功");
machine.setState(machine.getNoMoneyState());
}
@Override
public void turnCrank()
{
System.out.println("你轉(zhuǎn)動了手柄");
int winner=random.nextInt(10);
if (winner==0 && machine.getCount() > 1)
{
machine.setState(machine.getWinnerState());
} else
{
machine.setState(machine.getSoldState());
}
}
@Override
public void dispense()
{
throw new IllegalStateException("非法狀態(tài)!");
}
}
總結(jié)狀態(tài)模式:
狀態(tài)模式:允許對象在內(nèi)部狀態(tài)改變時改變它的行為,對象看起來好像修改了他的類。
解釋:
狀態(tài)模式將狀態(tài)封裝成為獨(dú)立的類,并將動作委托到代表當(dāng)前狀態(tài)的對象。
所以行為會隨著內(nèi)部狀態(tài)改變而改變。
我們通過組合簡單引用不同狀態(tài)對象來造成類改變的假象.
狀態(tài)模式策略模式
1.行為封裝的n個狀態(tài)中,不同狀態(tài)不用行為。
2.context的行為委托到不同狀態(tài)中。
3.[當(dāng)前狀態(tài)]在n個狀態(tài)中游走,context的行為也隨之[當(dāng)前狀態(tài)]的改變而改變。
4.用戶對context的狀態(tài)改變渾然不知。
5.客戶不會直接和state交互,只能通過context暴露的方法交互,state轉(zhuǎn)換是context內(nèi)部事情。
6.state可以是接口也可以是抽象類,取決于有沒公共功能可以放進(jìn)抽象類中。抽象類方便,因為可以后續(xù)加方法。
可以將重復(fù)代碼放入抽象類中。例如:"你已投入25元,不能重復(fù)投" 這種通用代碼放入抽象類中。
7.context可以決定狀態(tài)流轉(zhuǎn),如果這個狀態(tài)流轉(zhuǎn)是固定的,就適合放在context中進(jìn)行。但是如果狀態(tài)流轉(zhuǎn)是動態(tài)的就適合放在狀態(tài)中進(jìn)行。
例如通過商品的剩余數(shù)目來決定流向[已售完]或[等待投幣],這個時候放在狀態(tài)類中,因為dispense要根據(jù)狀態(tài)判斷流轉(zhuǎn)。
這個寫法決定了,有新需求時候,你是改context還是改state類。
8.可以共享所有的state對象,但是需要修改context的時候時候,需要handler中傳入context引用
1.context主動指定需要組合的策略對象是哪一個。
2.可以在啟動的時候通過工廠動態(tài)指定具體是哪個策略對象,但是沒有在策略對象之間游走,即:只組合了一個策略對象。
3.策略作為繼承之外一種彈性替代方案。因為繼承導(dǎo)致子類繼承不適用的方法,且每個類都要維護(hù),策略模式通過不同對象組合來改變行為。
4.策略模式聚焦的是互換的算法來創(chuàng)建業(yè)務(wù)。
狀態(tài)機(jī)典型應(yīng)用:訂單狀態(tài)控制
建表語句
如上圖所示:
一種典型的訂單設(shè)計。業(yè)務(wù)訂單和支付退款訂單組合,他們分別有自己的狀態(tài)機(jī)。
狀態(tài)機(jī)模式實現(xiàn)訂單狀態(tài)機(jī):
日常開發(fā)過程中,狀態(tài)機(jī)模式應(yīng)用場景之一的就是訂單模型中的狀態(tài)控制。但是區(qū)別于狀態(tài)模式的點有以下幾個:
以支付訂單為例:
/*
Title: PaymentInfo Description:
支付訂單狀態(tài)機(jī)
該類不可被spring管理,需要new出來,一個類就對應(yīng)一條數(shù)據(jù)庫中支付訂單記錄
本文來自博客園,作者:wanglifeng,轉(zhuǎn)載請注明原文鏈接:https://www.cnblogs.com/wanglifeng717/p/16214122.html
@author wanglifeng
*/
public class PaymentStateMachine {
// 數(shù)據(jù)庫中當(dāng)前支付訂單實體
private SapoPayment payment;
// 當(dāng)前狀態(tài)
private PaymentState currentState;
// 需要更新入庫的支付訂單實體。與payment屬性配合,payment為當(dāng)前數(shù)據(jù)庫中訂單實體,用于樂觀鎖的前置內(nèi)容校驗。
private SapoPayment paymentForUpdate;
/* 將最新內(nèi)容(含狀態(tài))更新入庫,并當(dāng)前狀態(tài)機(jī)狀態(tài) */
public void updateStateMachine() {
// 從Spring容器中獲取操作數(shù)據(jù)的dao
SapoDao dao=SpringUtil.getBean(SapoDao.class);
// 更新數(shù)據(jù)庫,樂觀鎖機(jī)制:帶前置內(nèi)容數(shù)據(jù)校驗,其中payment為前置內(nèi)容,paymentForUpdate為要更新的內(nèi)容,如果更新結(jié)果=0,說明該訂單被其他線程修改過。拋異常,放棄此次修改。
dao.updateSapoPaymentByNull(paymentForUpdate, payment);
// 記錄訂單操作流水
dao.insertSapoOrderStatusLog(SapoOrderStatusLog.getInstance().setOrderId(paymentForUpdate.getId())
.setOrderType(SapoOrderStatusLog.ORDER_TYPE_PAYMENT).setStatus(paymentForUpdate.getStatus()));
// 更新當(dāng)前PaymentStateMachine狀態(tài)機(jī)
this.setPayment(paymentForUpdate);
this.setCurrentState(paymentForUpdate.getStatus());
}
// 通過條件獲取一個支付訂單PaymentStateMachine實體
public static PaymentStateMachine getInstance(SapoPayment sapoPaymentForQuery) {
// 1.從spring容器中獲取dao;
SapoDao dao=SpringUtil.getBean(SapoDao.class);
// 2.查出該支付訂單
SapoPayment paymentResult=dao.getSapoPayment(sapoPaymentForQuery);
// 3.初始化訂單狀態(tài)機(jī)
PaymentStateMachine paymentStateMachine=new PaymentStateMachine();
paymentStateMachine.setPayment(paymentResult);
paymentStateMachine.setCurrentState(paymentResult.getStatus());
paymentStateMachine.setPaymentForUpdate(SapoPayment.getInstance(paymentResult));
return paymentStateMachine;
}
// 設(shè)置當(dāng)前狀態(tài)機(jī)的狀態(tài)。輸入數(shù)據(jù)庫中status字段,映射成對應(yīng)的狀態(tài)類實體。
public void setCurrentState(Integer status) {
PaymentState currentState=null;
// status數(shù)字,映射成對應(yīng)的狀態(tài)類實體
if (SapoPayment.STATUS_APPLY.equals(status)) {
currentState=SpringUtil.getBean(PaymentStateApply.class);
} else if (SapoPayment.STATUS_WAIT_PAY.equals(status)) {
currentState=SpringUtil.getBean(PaymentStateWaitPay.class);
} else if (SapoPayment.STATUS_PAY_FINISH.equals(status)) {
currentState=SpringUtil.getBean(PaymentStatePayFinish.class);
} else if (SapoPayment.STATUS_FAIL.equals(status)) {
currentState=SpringUtil.getBean(PaymentStateFail.class);
} else if (SapoPayment.STATUS_CANCEL.equals(status)) {
currentState=SpringUtil.getBean(PaymentStateCancel.class);
} else {
throw new BusinessException(ResultInfo.SYS_INNER_ERROR.getCode(),
"status not in state machine ,status: " + status);
}
this.currentState=currentState;
}
// TODO 待實現(xiàn),申請支付訂單
public void apply() {
// 委托給當(dāng)前狀態(tài)執(zhí)行,將當(dāng)前訂單狀態(tài)機(jī)對象傳進(jìn)去,使用狀態(tài)對象處理訂單
currentState.apply(this);
}
// TODO 待實現(xiàn),通知支付結(jié)果
public void resultNotify() {
// 委托給當(dāng)前狀態(tài)執(zhí)行
currentState.resultNotify(this);
}
// TODO 同步給當(dāng)前狀態(tài)執(zhí)行
public void sync() {
// 委托給當(dāng)前狀態(tài)執(zhí)行
currentState.sync(this);
}
// 取消訂單
public void cancel() {
// 委托給當(dāng)前狀態(tài)執(zhí)行
currentState.cancel(this);
}
}
public interface PaymentState {
public void apply(PaymentStateMachine paymentStateMachine);
public void resultNotify(PaymentStateMachine paymentStateMachine);
public void sync(PaymentStateMachine paymentStateMachine);
public void cancel(PaymentStateMachine paymentStateMachine);
}
@Service
public class PaymentStateApply extends BaseLogger implements PaymentState {
@Autowired
FmPayClientService fmPayClientService;
@Autowired
SapoDao dao;
@Autowired
private JacksonComponent jacksonComponent;
public void apply(PaymentStateMachine paymentStateMachine) {
}
public void sync(PaymentStateMachine paymentStateMachine) {
}
public void resultNotify(PaymentStateMachine paymentStateMachine) {
// TODO Auto-generated method stub
}
public void cancel(PaymentStateMachine paymentStateMachine) {
SapoPayment sapoPaymentForUpdate=paymentStateMachine.getPaymentForUpdate();
sapoPaymentForUpdate.setStatus(SapoPayment.STATUS_CANCEL);
sapoPaymentForUpdate.setExpireTime(null);
paymentStateMachine.updateStateMachine();
}
}
@Service
public class PaymentStateCancel extends BaseLogger implements PaymentState {
public void apply(PaymentStateMachine paymentStateMachine) {
// TODO Auto-generated method stub
}
public void resultNotify(PaymentStateMachine paymentStateMachine) {
// TODO Auto-generated method stub
}
public void sync(PaymentStateMachine paymentStateMachine) {
// TODO Auto-generated method stub
}
public void cancel(PaymentStateMachine paymentStateMachine) {
// TODO Auto-generated method stub
}
}
@Service
public class PaymentStateFail extends BaseLogger implements PaymentState {
public void apply(PaymentStateMachine paymentStateMachine) {
// TODO Auto-generated method stub
}
public void resultNotify(PaymentStateMachine paymentStateMachine) {
// TODO Auto-generated method stub
}
public void sync(PaymentStateMachine paymentStateMachine) {
// TODO Auto-generated method stub
}
public void cancel(PaymentStateMachine paymentStateMachine) {
throw new BusinessException(ResultInfo.SYS_INNER_ERROR.getCode(), "fail status can not cancel");
}
}
@Service
public class PaymentStatePayFinish extends BaseLogger implements PaymentState {
public void apply(PaymentStateMachine paymentStateMachine) {
// TODO Auto-generated method stub
}
public void resultNotify(PaymentStateMachine paymentStateMachine) {
// TODO Auto-generated method stub
}
public void sync(PaymentStateMachine paymentStateMachine) {
// TODO Auto-generated method stub
}
public void cancel(PaymentStateMachine paymentStateMachine) {
throw new BusinessException(ResultInfo.SYS_INNER_ERROR.getCode(), "payfinish status can not cancel");
}
}
@Service
public class PaymentStateWaitPay extends BaseLogger implements PaymentState {
@Autowired
FmPayClientService fmPayClientService;
@Autowired
SapoDao dao;
@Autowired
private JacksonComponent jacksonComponent;
public void payResultNotify() {
// TODO implement here
}
public void apply(PaymentStateMachine paymentStateMachine) {
throw new BusinessException(ResultInfo.SYS_INNER_ERROR.getCode(),
"applyPayPlatform not match payment state machine,currentStatus:"
+ paymentStateMachine.getPayment().getStatus());
}
public void sync(PaymentStateMachine paymentStateMachine) {
// TODO 過期去統(tǒng)一支付查詢
String payStatus=queryPayResultResponse.getPayStatus();
// 1:初始化輸入 2:支付中 3:支付成功 4:支付失敗 5:撤銷
if (QueryPayResultResponse.PAY_STATUS_INIT.equals(payStatus)) {
throw new BusinessException(ResultInfo.SYS_INNER_ERROR.getCode(),
"FMpay queryPay return init status ,we are waitpay");
}
if (QueryPayResultResponse.PAY_STATUS_ING.equals(payStatus)) {
return;
}
SapoPayment sapoPaymentForUpdate=paymentStateMachine.getPaymentForUpdate();
if (QueryPayResultResponse.PAY_STATUS_CANCEL.equals(payStatus)) {
sapoPaymentForUpdate.setStatus(SapoPayment.STATUS_CANCEL);
} else if (QueryPayResultResponse.PAY_STATUS_FAIL.equals(payStatus)) {
sapoPaymentForUpdate.setStatus(SapoPayment.STATUS_FAIL);
} else if (QueryPayResultResponse.PAY_STATUS_SUCCESS.equals(payStatus)) {
sapoPaymentForUpdate.setStatus(SapoPayment.STATUS_PAY_FINISH);
}
sapoPaymentForUpdate.setExpireTime(null);
paymentStateMachine.updateStateMachine();
}
public void resultNotify(PaymentStateMachine paymentStateMachine) {
// TODO Auto-generated method stub
}
public void cancel(PaymentStateMachine paymentStateMachine) {
throw new BusinessException(ResultInfo.SYS_INNER_ERROR.getCode(), "wait pay status can not cancel");
}
}
文章來自https://www.cnblogs.com/wanglifeng717/p/16214122.html
著過年放假在家復(fù)習(xí)了之前學(xué)的JS知識,用原生擼了一個購物車模塊,下面我來整理一下我的思路分享給大家。
1.1 廢話不多說,首先上個效果圖,如下:
購物車功能效果圖
1.2 功能介紹:
功能介紹完畢,下面開始介紹我寫這個購物車的步驟。
2.1 HTML代碼
<table>
<caption>
購物車
</caption>
<thead>
<tr>
<!-- 全選復(fù)選框 -->
<th>
<input type="checkbox" name="checkAll" id="check-all" checked /><label for="check-all">全選</label>
</th>
<th>圖片</th>
<th>品名</th>
<th>單位</th>
<th>單價/元</th>
<th>數(shù)量</th>
<th>金額/元</th>
</tr>
</thead>
<tbody>
<tr>
<td>
<input type="checkbox" name="item" value="SN-1020" checked />
</td>
<td>
<a href=""><img src="images/p1.jpg" alt="" /></a>
</td>
<td>iPhone 11</td>
<td>臺</td>
<td class="price">4799</td>
<td><input type="number" min="1" value="1" /></td>
<td class="amount">xxxx</td>
</tr>
<tr>
<td>
<input type="checkbox" name="item" value="SN-1020" checked />
</td>
<td>
<a href=""><img src="images/p2.jpg" alt="" /></a>
</td>
<td>小米pro 11</td>
<td>部</td>
<td class="price">3999</td>
<td><input type="number" min="1" value="2" /></td>
<td class="amount">xxxx</td>
</tr>
<tr>
<td>
<input type="checkbox" name="item" value="SN-1030" checked />
</td>
<td>
<a href=""><img src="images/p3.jpg" alt="" /></a>
</td>
<td>MacBook Pro</td>
<td>臺</td>
<td class="price">18999</td>
<td><input type="number" min="1" value="1" /></td>
<td class="amount">xxxx</td>
</tr>
<tr>
<td>
<input type="checkbox" name="item" value="SN-1040" checked />
</td>
<td>
<a href=""><img src="images/p4.jpg" alt="" /></a>
</td>
<td>小米75電視</td>
<td>臺</td>
<td class="price">5999</td>
<td><input type="number" min="1" value="2" /></td>
<td class="amount">xxxx</td>
</tr>
<tr>
<td>
<input type="checkbox" name="item" value="SN-1050" checked />
</td>
<td>
<a href=""><img src="images/p5.jpg" alt="" /></a>
</td>
<td>Canon 90D單反</td>
<td>臺</td>
<td class="price">9699</td>
<td><input type="number" min="1" value="1" /></td>
<td class="amount">xxxx</td>
</tr>
</tbody>
<tfoot>
<tr style="font-weight: bolder; font-size: 1.2em">
<td colspan="5">總計:</td>
<td id="sum">xxxx</td>
<td id="total-amount">xxxx</td>
</tr>
</tfoot>
</table>
2.2 CSS代碼
table {
border-collapse: collapse;
width: 90%;
text-align: center;
margin: auto;
}
table caption {
margin-bottom: 15px;
font-size: 1.5rem;
}
table th, table td {
border-bottom: 1px solid #ccc;
padding: 5px;
font-weight: normal;
}
table thead tr:first-of-type {
background-color: #e6e6e6;
height: 3em;
}
table input[type="checkbox"] {
width: 1.5em;
height: 1.5em;
}
table tbody tr {
border-bottom: 1px solid #ccc;
}
table tbody tr:hover {
background-color: #f6f6f6;
cursor: pointer;
}
tbody img {
width: 3em;
}
tbody input[type="number"] {
width: 3em;
}
button {
width: 150px;
height: 30px;
outline: none;
border: none;
background-color: teal;
color: white;
letter-spacing: 5px;
}
button:hover {
opacity: 0.7;
cursor: pointer;
}
2.3 效果圖
購物車效果圖
以上就是一個簡單的購物車頁面的HTML和CSS樣式代碼。
三、完成相關(guān)JS代碼
首先,我們先完成商品的全選與取消全選的功能,所以肯定是需要拿到全選復(fù)選框元素和商品前面的復(fù)選框元素,代碼如下:
// 獲取全選復(fù)選框,所有的商品都有一個獨(dú)立的復(fù)選框
const checkAll=document.querySelector('#check-all');
const checkItems=document.getElementsByName('item');
拿到全選和每個商品的復(fù)選框元素之后,給全選框添加一個change事件,監(jiān)聽它的checked值的變化。此時全選框的checked值可以通過事件監(jiān)聽回調(diào)函數(shù)中的ev參數(shù)下的ev.target.checked拿到。
checkALl.onchange=ev=> {
// 如果全選框處于選中狀態(tài),ev.target.checked的值就為true,反之,為false。
console.log(ev.target.checked);
};
如果想讓全選框的的狀態(tài)和每個商品前的復(fù)選框狀態(tài)保持一致,那么就使他們的checked值一致即可。因此,我們可以在全選復(fù)選框的change事件中遍歷每個商品的復(fù)選框元素。
checkALl.onchange=ev=> {
// 如果全選框處于選中狀態(tài),ev.target.checked的值就為true,反之,為false。
console.log(ev.target.checked);
checkItems.forEach(item=> item.checked=ev.target.checked);
};
這樣點擊全選框的時候,就可以實現(xiàn)全部選中,和取消全選的功能了。效果如圖:
全選與取消全選
全選和取消全選的功能完成之后,下面開始完善逐個勾選商品,直至勾選全部商品,讓全選按鈕自動變成被選中的狀態(tài)。
要完成這個功能,我們可以通過對每個商品的復(fù)選框添加一個change事件來監(jiān)聽checked的變化。因此需要通過forEach()方法對遍歷每一個商品。
checkItems.forEach(item=> item.onchange=ev=> {
// 在這里處理每一項的checked值
});
此時,我們可以這樣考慮:當(dāng)每個商品的復(fù)選框都被勾選,即:所有商品復(fù)選框的checked的值全部為true時,全選復(fù)選框才會顯示被勾選的狀態(tài),也就是全選復(fù)選框的checked的值也要為true。
由于checkAll的狀態(tài)依賴于每一項商品的checked值,那么可以利用一個數(shù)組函數(shù):Array.every()遍歷每一項商品,當(dāng)所有商品的checked值都為true時,every()方法的返回值就是一個true,然后再賦值給checkAll即可。注意:由于我們拿到的checkItems是一個NodeList數(shù)組,需要先將其轉(zhuǎn)換成數(shù)組后再進(jìn)行操作。
checkItems.forEach(item=> item.onchange=ev=> {
checkAll.checked=Array.from(checkItems).every(checkItem=> checkItem.checked);
});
點擊選中每個商品
至此,全選和單選功能全部完成了。下面開始寫自動計算金額的和總數(shù)的功能。
購物車的數(shù)量和金額不僅包含每一項商品的數(shù)量和每一項商品的總金額,還包含了計算選中的商品總數(shù),以及所有選中的商品的總金額。
下面首先完成單個商品的總金額計算,總金額=單價 * 數(shù)量,根據(jù)這個公式,我們首先拿到商品的單價和數(shù)量元素。
// 獲取單價組成的數(shù)組
const priceLists=document.querySelectorAll('.price');
// 獲取數(shù)量組成的數(shù)組
const numberLists=document.querySelectorAll('body input[type=number]');
以上單價(priceLists)和數(shù)量(numberLists)都是NodeList類型的,需要先將它們轉(zhuǎn)換成數(shù)組,由于表單中獲取的內(nèi)容都是string類型,而參與計算的需要的是整型,所以這里需要進(jìn)行一下轉(zhuǎn)換,使用parseInt()方法即可。
// 獲取商品單價組成的數(shù)組
const priceLists=document.querySelectorAll('.price');
const priceArr=Array.from(priceLists).map(item=> parseInt(item.textContent)); // [ 4799, 3999, 18999, 5999, 9699 ]
// 獲取商品數(shù)量組成的數(shù)組
const numberLists=document.querySelectorAll('body input[type=number]');
const numbersArr=Array.from(numberLists).map(item=> parseInt(item.value)); // 默認(rèn)值:[ 1, 1, 1, 1, 1 ]
注意:商品價格和商品數(shù)量在取值時有些不同。商品的單價是普通元素直接使用textContent即可拿到它內(nèi)部的值,而數(shù)量這個用的是表單控件,所以需要使用value才可以拿到值。 我剛開始寫這個功能的時候懵逼了半天,此處一定要注意。
拿到商品的單價和數(shù)量之后就可以按照上面的公式進(jìn)行計算了,由于商品的價格和商品的數(shù)量都是一個數(shù)組,并且價格和數(shù)量在數(shù)組中都是一一對應(yīng)的關(guān)系,因此可以使用JS數(shù)組的reduce()方法進(jìn)行遍歷。
let amountArr=[priceArr, numbersArr].reduce((prev, curr)=> {
return prev.map((item, index)=> {
return item * curr[index];
});
});
總感覺上述寫法有點怪怪的,是不是可以進(jìn)行簡化呢?根據(jù)箭頭函數(shù)的特征,當(dāng)只有一條返回語句的時候可以省略掉return關(guān)鍵字和大括號,因此上述方法可以簡寫成下面這樣:
let amountArr=[priceArr, numbersArr].reduce((prev, curr)=> prev.map((item, index)=> item * curr[index]));
console.log(amountArr); // [ 4799, 3999, 18999, 5999, 9699 ]
(PS:上面的方法我一開始也沒有發(fā)現(xiàn)可以簡寫,我是把代碼發(fā)給我朋友看了之后,朋友給我點醒了。還是才疏學(xué)淺呀。)
這時已經(jīng)計算出來了每個商品的總金額,那么我們將其渲染到頁面中。
// 獲取單個商品總金額的元素數(shù)組
const amountDOM=document.querySelectorAll('.amount');
amountDOM.forEach((item, index)=> item.textContent=amountArr[index]);
計算每個商品的金額并渲染到頁面中
單個商品的總金額渲染到頁面之后,下面就開始計算商品的總數(shù),和總金額了。根據(jù)某東、某寶的購物車功能,我們可以發(fā)現(xiàn),總計那里統(tǒng)計的商品總數(shù)是一般是我們勾選上的商品總數(shù),總金額也是一樣的,那么我們就需要根據(jù)商品的狀態(tài)來進(jìn)行計算了。
首先聲明一個數(shù)組,用于存儲被選中的商品的狀態(tài),如果被選中,值為1,未被選中,則為0。
let isChecked=[];
checkItems.forEach(item=> isChecked.push(item.checked===true ? 1 : 0));
// 打印出商品狀態(tài)值
console.log(isChecked);
打印商品狀態(tài)值
商品的狀態(tài)已經(jīng)記錄好了,那么現(xiàn)在就需要統(tǒng)計選中的商品對應(yīng)的數(shù)量了。
// 聲明一個用于存儲商品數(shù)量的數(shù)組,該數(shù)組的作用是用于與對應(yīng)的商品的狀態(tài)值的數(shù)組進(jìn)行相乘,得到實際的被選中的商品的數(shù)組。
let checkedNumbers=[];
numbersArr.forEach((item, index)=> checkedNumbers.push(item * isChecked[index]));
// 打印被選中的商品的數(shù)量
console.log(checkedNumbers);
打印出選中的商品的數(shù)量數(shù)組
計算出被選中的商品數(shù)量的總數(shù)并渲染到頁面中:
let checkedSum=checkedNumbers.reduce((prev, curr)=> prev + curr);
// 將獲取的數(shù)量結(jié)果渲染到頁面中
document.querySelector('#sum').textContent=checkedSum;
效果如上圖已經(jīng)出來了。
下面開始計算被選中的商品的總金額,該總金額等于上面所有被選中的商品的總金額之和。計算出結(jié)果之后渲染到頁面中。
// 聲明一個數(shù)組用于存儲每一個被選中的商品的總金額
let checkedPrice=[];
checkedNumbers.forEach((item, index)=> checkedPrice.push(item * priceArr[index]));
// 打印被選中的每個被選中的商品總金額
console.log(checkedPrice);
// 計算被選中的商品總金額
let totalAmount=checkedPrice.reduce((prev, curr)=> prev + curr);
// 將選中的商品總金額渲染到頁面中
document.querySelector('#total-amount').textContent=totalAmount;
將總金額渲染到頁面
至此,關(guān)于計算單個商品的總金額以及被選中商品的數(shù)量和總金額的功能已經(jīng)全部完成了,但是我們還需要實現(xiàn)在頁面加載以及更改某個商品數(shù)量時自動計算的功能。那么就需要將上述的計算功能封裝成一個函數(shù),以便后面每一次執(zhí)行計算時使用。
function autoCalculate() {
// 獲取單價組成的數(shù)組
const priceLists=document.querySelectorAll('.price');
const priceArr=Array.from(priceLists).map(item=> parseInt(item.textContent));
// 獲取數(shù)量組成的數(shù)組
const numberLists=document.querySelectorAll('body input[type=number]');
const numbersArr=Array.from(numberLists).map(item=> parseInt(item.value));
console.log(priceArr, numbersArr);
// 由于拿到的表單里的數(shù)據(jù)都是string類型的,所以需要先將其轉(zhuǎn)換成int類型,因此需要使用`map()`方法操作一下
let amountArr=[priceArr, numbersArr].reduce((prev, curr)=> prev.map((item, index)=> item * curr[index]));
console.log(amountArr);
const amountDOM=document.querySelectorAll('.amount');
amountDOM.forEach((item, index)=> item.textContent=amountArr[index]);
// 首先聲明一個數(shù)組,用于存儲被選中的商品的狀態(tài),如果被選中,值為1,未被選中,則為0
let isChecked=[];
checkItems.forEach(item=> isChecked.push(item.checked===true ? 1 : 0));
console.log(isChecked);
// 聲明一個用于存儲是商品數(shù)量的數(shù)組,該數(shù)組的作用是:如果商品處于被選中的狀態(tài),那么就存儲它真實的數(shù)量值,
// 如果沒有被選中,那么數(shù)量就是0
let checkedNumbers=[];
numbersArr.forEach((item, index)=> checkedNumbers.push(item * isChecked[index]));
console.log(checkedNumbers);
// 此時,被選中的商品的總數(shù)為:
let checkedSum=checkedNumbers.reduce((prev, curr)=> prev + curr);
console.log(checkedSum);
// 將獲取的數(shù)量結(jié)果渲染到頁面中
document.querySelector('#sum').textContent=checkedSum;
// 下面開始計算被選中的商品的總金額,該總金額等于上面所有被選中的商品的總金額之和。
// 聲明一個數(shù)組用于存儲每一個被選中的商品的總金額
let checkedPrice=[];
checkedNumbers.forEach((item, index)=> checkedPrice.push(item * priceArr[index]));
console.log(checkedPrice);
// 計算被選中的商品總金額
let totalAmount=checkedPrice.reduce((prev, curr)=> prev + curr);
// 將選中的商品總金額渲染到頁面中
document.querySelector('#total-amount').textContent=totalAmount;
}
將代碼封裝后我們會發(fā)現(xiàn),單個商品的總金額,商品總數(shù)以及總金額的值都沒了,如下圖:
封裝代碼后的效果
這是因為,代碼在第一次加載的時候并沒有執(zhí)行封裝后的函數(shù),因此需要加一行代碼:
// 頁面第一次加載的時候自動執(zhí)行一次。
window.onload=autoCalculate;
這樣頁面中的數(shù)據(jù)在第一次加載的時候就全部都正常了。
下面完成最后一個功能:調(diào)整商品的數(shù)量,會自動計算總數(shù)和金額。該功能還是通過change事件監(jiān)聽某個表單數(shù)據(jù)的變化來完成。效果圖下圖:
// 監(jiān)聽某個控件的事件,首先需要拿到控件元素。
const numInput=document.querySelectorAll('body input[type=number]');
// 上面都用了onchange來監(jiān)聽,這里換個方法使用addEventListener。
numInput.forEach(item=> item.addEventListener('change', autoCalculate));
但是我們會發(fā)現(xiàn)這里有個小bug,就是如果勾選沒有選中的商品,并不會自動計算商品數(shù)量和總價,原因很簡單,我們在監(jiān)聽單個商品選中和全選的時候根本就沒有執(zhí)行自動計算函數(shù),只需要在二者的事件監(jiān)聽中加上自動計算的函數(shù)即可。
checkAll.onchange=ev=> {
checkItems.forEach(item=> item.checked=ev.target.checked);
// 解決勾選全選框不會自動計算的bug
autoCalculate();
};
checkItems.forEach(item=> item.onchange=ev=> {
checkAll.checked=Array.from(checkItems).every(checkItem=> checkItem.checked);
// 解決勾選全選框不會自動計算的bug
autoCalculate();
});
寫到這里,我們購物車的所有功能都已經(jīng)完成了。購物車這個模塊看似不難,其實這里面的坑也是不少的,例如:
以上就是我個人在寫這個購物車功能的全部新的,由于本人也是新手,可能還有其他更簡介方便的寫法,如果有問題,請各位大佬批評指正,不勝感激。
如果有剛開始學(xué)習(xí)JS的同學(xué),想要源碼的各位親,可以關(guān)注并私信回復(fù)“購物車”即可。
單狀態(tài) JavaScript 資產(chǎn)
您可以通過使用 Shopify 后臺中的自定義腳本框,將 JavaScript 添加到結(jié)帳的訂單狀態(tài)頁面(以前稱為“感謝”頁面)。
Shopify.Checkout.OrderStatus JavaScript 資產(chǎn)可用于向訂單狀態(tài)頁面添加多種類型的內(nèi)容,包括:
-特定產(chǎn)品的備注
-單個發(fā)貨方式的說明
-數(shù)字產(chǎn)品的下載鏈接。
也可以通過 ScriptTag 訪問此 JavaScript 資源。
如何實現(xiàn)
Shopify.Checkout.OrderStatus 包含用于生成新內(nèi)容的函數(shù):
addContentBox(params)
此函數(shù)添加一個內(nèi)容框,從其中傳遞給該函數(shù)的每個參數(shù)都呈現(xiàn)為單獨(dú)的行。
函數(shù)內(nèi)部支持 HTML5,并且您可以在函數(shù)外部使用 liquid。
示例
您可以在使用了特定的發(fā)貨方式時添加內(nèi)容:
{% if checkout.shipping_method.title=='Pick-up at the store' %}
Shopify.Checkout.OrderStatus.addContentBox(
'
'
We are open everyday from 9am to 5pm.
'
)
{% endif %}
(來源:Shopify)
以上內(nèi)容屬作者個人觀點,不代表雨果網(wǎng)立場!如有侵權(quán),請聯(lián)系我們。
相關(guān)鏈接:Shopify后臺怎么復(fù)制現(xiàn)有訂單?Shopify復(fù)制現(xiàn)有訂單操作一覽
*請認(rèn)真填寫需求信息,我們會在24小時內(nèi)與您取得聯(lián)系。