專案整合第三方支付(易寶)時,PayAction 需同時處理兩種請求:
SendData 模型)BackData 模型)但 Struts2 ModelDriven<T> 僅支援單一 Model,若強行繼承共同父類,違反單一職責且模型無實質關聯。
Struts2 ParameterAware 介面提供 setParameters(Map<String, String[]>),可在攔截器鏈中 ServletConfigInterceptor(早於 ModelDrivenInterceptor)注入完整請求參數 Map。利用此特性,在 getModel() 中依參數特徵動態決定回傳哪個 Model 實例。
@Controller("payAction")
@Scope("prototype")
public class PayAction extends BaseAction<Object> implements ParameterAware {
private Map<String, String[]> parameters;
@Override
public void setParameters(Map<String, String[]> parameters) {
this.parameters = parameters;
}
@Override
public Object getModel() {
// 易寶發起支付時會帶 pd_FrpId(銀行代碼),回調則無
if (parameters != null && parameters.containsKey("pd_FrpId")) {
model = new SendData(); // 發起支付模型
} else {
model = new BackData(); // 接收回調模型
}
return model;
}
// 發起支付
public String goBank() {
SendData sendData = (SendData) model;
// 組裝表單、簽名、導向易寶...
return "goBank";
}
// 接收回調
public void backBank() {
BackData backData = (BackData) model;
// 驗簽、更新訂單狀態、發送通知...
}
}
1. 建立 Action 實例
2. 執行攔截器鏈(依 struts-default.xml 順序)
├─ ServletConfigInterceptor → 注入 parameters Map
├─ ModelDrivenInterceptor → 呼叫 getModel()
└─ ...其他攔截器
3. 呼叫 Action 方法
4. 反向執行攔截器鏈
因 ServletConfigInterceptor 在 ModelDrivenInterceptor 之前,故 getModel() 時 parameters 已就緒,可安全判斷。
public void backBank() {
BackData backData = (BackData) model;
boolean verified = payService.verifyCallback(backData);
if (verified) {
// 1. 更新訂單狀態(易寶回傳訂單號在 r6_Order)
String orderId = backData.getR6_Order();
forderService.updateStatusById(Integer.parseInt(orderId), 2); // 2=已付款
// 2. 發送 Email、簡訊通知(r8_MP 格式:email,phone)
String[] mp = backData.getR8_MP().split(",");
emailUtil.sendEmail(mp[0], orderId);
messageUtil.sendSms(mp[1], orderId);
}
}
SendData、BackData 互不相干,各司其職struts.xml 增加 result 或 actiongetModel() 增加判斷分支pd_FrpId)必須是兩類請求「絕對互斥」的特徵參數BaseAction 泛型需改為 Object,避免類型衝突ModelDriven,建議統一規範:單一 Model 用 ModelDriven,多 Model 用 ParameterAware透過 ParameterAware 搭配 Struts2 攔截器執行順序,優雅解決單一 Action 處理多種 Model 請求的問題,避免強行繼承或拆分 Action 導致的代碼碎片化,是整合第三方回調介面的實用模式。