8個派生都這麼改還是挺麻煩的,也違背了OCP原則。另外,從領域的角度來說,logFlag參數與整個功能並無關係,只是為了完善記錄日誌才“生硬地”加這麼一個參數。所以,上面的實現方案不妥。改為封裝一個LogFlag屬性。這樣,只需修改基類,派生類無需任何改動。調用方在實例化對象後,可以為LogFlag... ...
在支付中心的中信支付渠道實現層里,關於每個支付介面的對接實現,類圖設計方式如下(後附支付中心程式框架-分層結構),諸如獲取動態支付碼、公眾號/服務窗、訂單查詢、關單、退款、代付、代付查詢等每種支付介面的api實現均繼承了同一個基類。
ClassDiagram
支付中心程式框架-分層結構
基類封裝了請求渠道方api所必須的參數驗證、簽名、生成請求報文、發起請求、驗證響應報文這一系列環節。這樣的OO設計,可以簡化每個支付介面api類的邏輯實現,它們只需構造請求模型,然後調用基類Communicate方法,然後轉換成相應模型即可。
投產後,線上交易量太大。系統運維過程中往往要通過查日誌來協助排障,比如某次訂單查詢的請求返回了錯誤的信息“訂單未支付”,那麼就要定位該次請求所對應的渠道請求報文日誌和響應報文日誌。
對於支付中心支付介面收到的每一次請求,我會生成一個隨機字元串作為logflag,來統一標識每一次請求的處理過程(涉及到項目的每一層,如webapi層、交易服務層、BLL層、DAL層)中所對應的日誌。 見下圖中的“[OrderQuery_180001914_C72FF]”、“[OrderQuery_180002492_C6E22]”、“[JSPay_102157155_D0F3F]”、“[180002CITIC648]”。
交易日誌截圖
美中不足的是,渠道通訊層所記錄的日誌沒有這個logflag,導致很難與webapi等層的日誌對應起來。 本文要說的就是這一完善,接收外部傳來的logflag參數,在記日誌的時候打上這個logflag標識。(後來與同事閑聊時,得知可以用當前Thread的Name來很容易的實現這個統一標記交易日誌的功能,本文重點是分析本次重構過程,所以姑且不講這些)
基類CiticAPIBase是抽象類:
public abstract class CiticAPIBase<TRequestModel, TResponseModel> where TRequestModel : RequestDTOBase where TResponseModel : ResponseDTOBase { readonly string LOG_FLAG = string.Format("[{0}CITIC{1}]", DateTime.Now.ToString("HHmmss"), new Random().Next(9999)); protected LogHelperUtil _LogHelperUtil; public CiticAPIBase() { _LogHelperUtil = new LogHelperUtil(LOG_FLAG); } public abstract TResponseModel Invoke(TRequestModel reqModel); public string Communicate(RequestModelCommon citicReqModel) { try { CiticCommon citicCommon = new CiticCommon(LOG_FLAG); var json = citicCommon.Invoke(_reqModel); //_LogHelperUtil.WriteLog("渠道介面處理結果:{0}", json); return json; } catch (ResponseErrorException ex) { throw new ResponseErrorException("【上游通道】" + ex.Message); } } }
派生類之一_61InitJSAPI(實現的支付介面是公眾號/服務窗),重寫Invoke方法:
public class _61InitJSAPI : CiticAPIBase<JSPayRequestDTO, JSPayResponseDTO> { public override JSPayResponseDTO Invoke(JSPayRequestDTO reqDto) { var citicReqDto = new _61InitJSAPIRequestModel() { out_trade_no = reqDto.order_no, body = reqDto.goods_name, total_fee = reqDto.pay_money, mch_create_ip = ReadIp.Ip_GetIPAddress(), notify_url = PartnerConfig.PayNotifyUrl, callback_url = reqDto.return_url, is_raw = "1", //reqModel1.is_raw, 我司與青島中信配的是原生態js支付。所以,這裡寫死。 sub_openid = reqDto.user_client_name,//TODO: mch_id = reqDto.merchant_id, }; if (citicReqDto.mch_id == PartnerConfig.MCH_ID_TEST) { citicReqDto.sub_openid = string.Empty; } //----調用中信支付通道 var json = base.Communicate(citicReqDto); var respModel = JsonConvert.DeserializeObject<_61InitJSAPIResponseModel>(json); string pay_url = string.Format("https://pay.swiftpass.cn/pay/jspay?token_id={0}&showwxtitle=1", model.token_id); var returnDto = new JSPayResponseDTO() { StatusIsSuccess = true, ReturnCodeIsSuccess = true, pay_info = respModel.pay_info, pay_url = pay_url, }; return returnDto; } }
派生類之一_8Reverse代碼(實現的支付介面是關單),重寫Invoke方法:
/// <summary> /// 8 關閉訂單介面 /// </summary> public class _8Reverse : CiticAPIBase<ReverseRequestDTO, ResponseDTOBase> { public _8Reverse(string logFlag) : base(logFlag) { } public override ResponseDTOBase Invoke(ReverseRequestDTO reqDto) { ... ... } }
接下來說實現方案。
我是從構造方法入手的,構造方法加了個logFlag參數。調用方在初始化具體的對象時,傳遞logFlag。基類變成瞭如下的樣子:
public abstract class CiticAPIBase<TRequestModel, TResponseModel> where TRequestModel : RequestDTOBase where TResponseModel : ResponseDTOBase { readonly string LOG_FLAG = string.Format("[{0}CITIC{1}]", DateTime.Now.ToString("HHmmss"), new Random().Next(9999)); protected LogHelperUtil _LogHelperUtil; public CiticAPIBase(string logFlag) { LOG_FLAG = logFlag + LOG_FLAG; _LogHelperUtil = new LogHelperUtil(LOG_FLAG); } public abstract TResponseModel Invoke(TRequestModel reqModel); public string Communicate(RequestModelCommon citicReqModel) { ... ... } }
然後,每個派生類都要顯式聲明構造方法,加上logFlag參數:
public class _61InitJSAPI : CiticAPIBase<JSPayRequestDTO, JSPayResponseDTO> { public _61InitJSAPI(string logFlag) : base(logFlag) { } public override JSPayResponseDTO Invoke(JSPayRequestDTO reqDto) { ... .... } } public class _8Reverse : CiticAPIBase<ReverseRequestDTO, ResponseDTOBase> { public _8Reverse(string logFlag) : base(logFlag) { } public override ResponseDTOBase Invoke(ReverseRequestDTO reqDto) { ... ... } }
8個派生都這麼改還是挺麻煩的,也違背了OCP原則。另外,從領域的角度來說,logFlag參數與整個功能並無關係,只是為了完善記錄日誌才“生硬地”加這麼一個參數。所以,上面的實現方案不妥。改為封裝一個LogFlag屬性。這樣,只需修改基類,派生類無需任何改動。調用方在實例化對象後,可以為LogFlag屬性賦值(if possible)。
public abstract class CiticAPIBase<TRequestModel, TResponseModel> where TRequestModel : RequestDTOBase where TResponseModel : ResponseDTOBase { public string LogFlag { set { _LOG_FLAG = value + _LOG_FLAG; } } string _LOG_FLAG = ""; protected LogHelperUtil _LogHelperUtil; public CiticAPIBase() { _LOG_FLAG = string.Format("[{0}CITIC{1}]", DateTime.Now.ToString("HHmmss"), new Random().Next(9999)); _LogHelperUtil = new LogHelperUtil(_LOG_FLAG); } public abstract TResponseModel Invoke(TRequestModel reqModel); public string Communicate(RequestModelCommon citicReqModel) { ... ... } }