前言:这两个星期一直在写公司项目里的千里眼系统,这个系统主要负责的就是将各个平台的快递接收与跟单推送与公司的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>>也是根据三方平台文档返回数据来对应接收的。
好啦,写到这里,基本就报本方调用三方接口的思路基本写完啦。后面的第二种模式,让三方平台调用本方的接口传送参数的代码实例与思路,我放到下篇博客接着来写。
创作不易,分享技术!喜欢我的博客的话,给我一个赞吧!