關於呼叫三方平臺介面與推送介面的總結(2020.7.25)

Son發表於2020-07-25

前言:這兩個星期一直在寫公司專案裡的千里眼系統,這個系統主要負責的就是將各個平臺的快遞接收與跟單推送與公司的WMS倉儲系統跟維密客服系統對接起來。詳細的內容我就不做過多闡述,寫下這篇部落格主要的原因也就是為了把這兩個星期所學的記錄一下,另外也方便以後的回顧複習。

 

正文開始:

既然是對接快遞平臺,那麼無非就有兩種對接的模式。第一種,是本方去呼叫第三方的介面,例如快遞查詢介面、路由查詢介面、路由訂閱介面等。這些都需要本方主動去請求三方提供的url,按照三方要去的請求引數來推送引數獲得資料。第二種,與第一種相反,是三方調我們的介面。例如路由推送介面等。這種模式需要本方封裝一個介面,並提供地址給第三方,然後第三方來調我們自己的介面從而實現我們封裝的介面實現的邏輯。

那麼我們首先說說第一種,本方呼叫三方介面。

一、本方呼叫三方介面

1、第一步,我們首先要做的,是先將http請求的基本框架搭好,那麼開始設計吧(提醒一下,為了便於理解,這裡我以申通平臺為例,申通的官方api地址:https://open.sto.cn/#/apiDocument/STO_TRACE_QUERY_COMMON  參照這來看,更容易理解哦!)。

首先是請求的基本方法:

 

/**
     * 執行請求
     *
     * @param request 請求
     * @param <T>     響應型別
     * @return 響應值
     */
    public <T extends BaseStoResponse> T execute(BaseStoRequest<T> request) {
        T response;
        String body = null;
        try {
            // 生成http請求
            Request httpRequest = generateHttpRequest(request);
            Call call = httpClient.newCall(httpRequest);
            Response execute = call.execute();
            if (execute.code() == 200) {
                body = execute.body().byteString().string(Charset.forName(charsetName));
                response = objectMapper.readValue(body, request.getResponseClass());
            } else {
                response = request.fail("請求三方平臺介面發生異常,狀態碼:" + execute.code());
            }
        } catch (Exception e) {
            response = request.fail("請求三方平臺介面發生異常," + e.getMessage());
        }
        response.setBody(body);
        return response;
    }

這個方法裡,請求引數會放到 BaseSfRequest 類裡,返回的引數用 BaseSfResponse 類接收。這裡的兩個base類,後面會再細說。

然後就是生成http請求的方法 generateHttpRequest(request)了。具體程式碼如下:

/**
* 生成 Http 請求
*
* @param request 請求物件
* @return Http 請求物件
* @throws Exception 生成簽名異常
*/
private Request generateHttpRequest(BaseStoRequest<?> request) throws Exception {
// 請求報文
String content = objectMapper.writeValueAsString(request);

// 計算簽名
String sign = doSign(content);

// 生成 Request
return new Request.Builder().url(stoConfig.getUrl()).addHeader("Accept", "application/json")
.post(new FormBody.Builder().add("content", content).add("data_digest", sign)
.add("api_name", request.apiName()).add("from_appkey", stoConfig.getFromAppKey())
.add("from_code", stoConfig.getFromCode()).add("to_appkey", request.toAppKey())
.add("to_code", request.toAppKey()).build())
.build();
}

這裡會根據每個平臺的具體要求來傳公共引數與業務引數,另外還會對加密方法進行宣告。下面是最尋常的 base64(md5(引數))加密方法。

/**
     * 計算簽名
     *
     * @param content 報文內容
     * @return 簽名
     * @throws Exception 異常
     */
    public String doSign(String content) throws Exception {
        return Base64.getEncoder().encodeToString(
            MessageDigest.getInstance("MD5").digest((content + stoConfig.getSecretKey()).getBytes(charsetName)));
    }

以上三個方法,就實現了基本的http請求與請求引數的封裝了,這三個方法,我們把它放到manager層裡面。

@Component
public class StoApiManager {

    /**
     * httpClient
     */
    @Autowired
    private OkHttpClient httpClient;

    /**
     * stoConfig
     */
    @Autowired
    private StoConfig stoConfig;

    /**
     * 字元編碼格式
     */
    private final String charsetName = "UTF-8";

    /**
     * objectMapper
     */
    @Autowired
    private ObjectMapper objectMapper;

    /**
     * 執行請求
     *
     * @param request 請求
     * @param <T> 響應型別
     * @return 響應值
     */
    public <T extends BaseStoResponse<?>> T execute(BaseStoRequest<T> request) {
        T response;
        String body = null;
        try {
            // 生成http請求
            Request httpRequest = generateHttpRequest(request);
            Call call = httpClient.newCall(httpRequest);
            Response execute = call.execute();
            if (execute.code() == 200) {
                body = execute.body().byteString().string(Charset.forName(charsetName));
                response = objectMapper.readValue(body, request.getResponseClass());
            } else {
                response = request.fail("請求三方介面發生異常,狀態碼:" + execute.code());
            }
        } catch (Exception e) {
            response = request.fail("請求三方介面發生異常," + e.getMessage());
        }
        response.setBody(body);
        return response;
    }

    /**
     * 生成 Http 請求
     *
     * @param request 請求物件
     * @return Http 請求物件
     * @throws Exception 生成簽名異常
     */
    private Request generateHttpRequest(BaseStoRequest<?> request) throws Exception {
        // 請求報文
        String content = objectMapper.writeValueAsString(request);

        // 計算簽名
        String sign = doSign(content);

        // 生成 Request
        return new Request.Builder().url(stoConfig.getUrl()).addHeader("Accept", "application/json")
            .post(new FormBody.Builder().add("content", content).add("data_digest", sign)
                .add("api_name", request.apiName()).add("from_appkey", stoConfig.getFromAppKey())
                .add("from_code", stoConfig.getFromCode()).add("to_appkey", request.toAppKey())
                .add("to_code", request.toAppKey()).build())
            .build();
    }

    /**
     * 計算簽名
     *
     * @param content 報文內容
     * @return 簽名
     * @throws Exception 異常
     */
    public String doSign(String content) throws Exception {
        return Base64.getEncoder().encodeToString(
            MessageDigest.getInstance("MD5").digest((content + stoConfig.getSecretKey()).getBytes(charsetName)));
    }
}

 

接下來就是關於如何封裝請求引數與封裝返回引數的問題。

1.1 請求引數的封裝

分三步,其實自己寫的話分兩步封裝就可以了,但是這裡我們的架構大神規定了框架,所以都是按照baseRequest <一  baseStoRequest  <一 baseTraceQueryRequest(路由查詢介面)來三級封裝的。也就是基本公共請求,基本平臺公共請求,平臺具體介面請求來寫。

首先是基本公共請求類程式碼(這裡的方法是所有三方平臺去請求都會用到的):

public abstract class BaseRequest<T extends BaseResponse> {

    /**
     * @return 獲取響應物件型別
     */
    @JsonIgnore
    public abstract Class<T> getResponseClass();

    /**
     * @return 預設響應值
     */
    public T generateDefaultResponse() {
        Object response;
        try {
            response = getResponseClass().newInstance();
        } catch (Exception e) {
            e.printStackTrace();
            throw new SntProjectException(SystemCodeEnum.PARAMETER_UNKNOWN, "生成 DefaultResponse 異常");
        }
        return (T)response;
    }

    /**
     * @param errMsg 失敗資訊
     * @return 失敗響應
     */
    public T fail(String errMsg) {
        T response = generateDefaultResponse();
        response.setFail(errMsg);
        return response;
    }
}

基本平臺公共請求類程式碼(這裡的方法與引數是該三方平臺所有介面都會用到的):

public abstract class BaseStoRequest<T extends BaseStoResponse<?>> extends BaseRequest<T> {

    /**
     * @return 介面名稱
     */
    public abstract String apiName();

    /**
     * @return AppKey
     */
    public abstract String toAppKey();


}

三方平臺具體介面請求類程式碼(以申通的路由查詢為例):

@EqualsAndHashCode(callSuper = true)
@Data
public class StoTraceQueryRequest extends BaseStoRequest<StoTraceQueryResponse> {

    @Override
    public Class<StoTraceQueryResponse> getResponseClass() {
        return StoTraceQueryResponse.class;
    }

    @Override
    public String apiName() {
        return "STO_TRACE_QUERY_COMMON";
    }

    @Override
    public String toAppKey() {
        return "sto_trace_query";
    }

    /**
     * 運單號集合
     */
    @JsonProperty("waybillNoList")
    private List<String> waybillNos;
}

不知道大家注意到上面宣告的  private List<String> waybillNos 了沒,因為這個是申通介面文件裡宣告的,所以我們秩序要照著封裝就行了。這裡可以跟大家說一下,現在的三方平臺介面,一般都提供了xml與json兩種格式的引數,我推薦是用json的來封裝。而json的話,{  }代表一個object物件,[  ]代表一個list陣列。

 

1.2 接收引數的封裝

同樣的,我們的架構大神將返回引數的封裝同樣分為三層。這裡我就直接放程式碼了,跟上面的請求三級封裝邏輯是一樣的。

基本公共返回類程式碼:

@Data
public class BaseResponse {

    /**
     * 響應原始報文
     */
    private String body;

    /**
     * 是否成功
     */
    @JsonIgnore
    private Boolean isError;

    /**
     * 原因
     */
    @JsonIgnore
    private String errMsg;

    /**
     * 設定失敗響應
     *
     * @param errMsg 失敗原因
     */
    @JsonIgnore
    public void setFail(String errMsg) {
        setIsError(true);
        setErrMsg(errMsg);
    }
}

基本平臺公共返回類程式碼(這裡的方法與引數是該三方平臺所有介面都會用到的):

@EqualsAndHashCode(callSuper = true)
@Data
public class BaseStoResponse<T> extends BaseResponse {

    /**
     * 是否成功
     */
    private Boolean success;

    /**
     * 錯誤編碼
     */
    private String errorCode;

    /**
     * 錯誤資訊
     */
    private String errorMsg;

    /**
     * 是否重試
     */
    private Boolean needRetry;

    /**
     * 請求id
     */
    private String requestId;

    /**
     * 異常資訊
     */
    private String expInfo;         

    /**
     * 資料
     */
    private T data;

    @Override
    public Boolean getIsError() {
        return BooleanUtils.isNotTrue(success);
    }

    @Override
    public void setIsError(Boolean isError) {
        success = BooleanUtils.isFalse(isError);
    }

    @Override
    public String getErrMsg() {
        return errorMsg;
    }

    @Override
    public void setErrMsg(String errMsg) {
        errorMsg = errMsg;
    }

    public static BaseStoResponse<?> fail(String errMsg) {
        BaseStoResponse<?> response = new BaseStoResponse<String>();
        response.setFail(errMsg);
        response.setNeedRetry(Boolean.TRUE);
        return response;
    }

    public static <T> BaseStoResponse<T> ok(T data) {
        BaseStoResponse<T> response = new BaseStoResponse<T>();
        response.setSuccess(Boolean.TRUE);
        response.setData(data);
        return response;
    }
}

注意一下,這裡的 fail 與 ok方法,是後面的路由推送介面也就是三方平臺調我們介面會用到的。

三方平臺具體介面返回類程式碼(以申通的路由查詢為例):

public class StoTraceQueryResponse extends BaseStoResponse<Map<String, List<StoTraceDTO>>> {
}

 這裡的 Map<String, List<StoTraceDTO>>也是根據三方平臺文件返回資料來對應接收的。

 

好啦,寫到這裡,基本就報本方呼叫三方介面的思路基本寫完啦。後面的第二種模式,讓三方平臺呼叫本方的介面傳送引數的程式碼例項與思路,我放到下篇部落格接著來寫。

創作不易,分享技術!喜歡我的部落格的話,給我一個贊吧!

 

相關文章