本篇将介绍如何封装接口给第三方平台来调用。
正文开始:
既然是对接快递平台,那么无非就有两种对接的模式。第一种,是本方去调用第三方的接口,例如快递查询接口、路由查询接口、路由订阅接口等。这些都需要本方主动去请求三方提供的url,按照三方要去的请求参数来推送参数获得数据。第二种,与第一种相反,是三方调我们的接口。例如路由推送接口等。这种模式需要本方封装一个接口,并提供地址给第三方,然后第三方来调我们自己的接口从而实现我们封装的接口实现的逻辑。
今天我们说说第二种,三方调用本方接口。
二、三方来调用本方接口(注:本文以申通路由推送接口为例,可以参照申通官方文档 https://open.sto.cn/#/apiDocument/STO_TRACE_PLATFORM_PUSH ,以便理解)
在上一篇中,我们介绍调用三方接口的时候,是我们按照三方的要求来,按照他给的参数进行入参请求。那么我们写接口给三方来调,是不是就需要三方完全按照我们的要求来呢?其实并不是,为了让三方调用我们接口,我们需要首先去与三方沟通,拿到他们的请求参数格式,按照他们请求参数的格式,来设计我们的接口(ps:没错,就是这么的卑微,毕竟三方才是爸爸0^0)。在拿到三方的标准格式后,我们就可以设计接口了。与主动调用在manager层写代码不同,被动接受调用,需要从manager层到service层再到controller层,三层设计封装然后接口才算完成。
1.manager层
在manager层,我们依然可以使用调用三方接口的apimanager类,以及三层请求与三层返回类,这里需要改的是请求与返回的第三层类,具体代码如下:
1.1申通路由推送接口请求类:
@EqualsAndHashCode(callSuper = true)
@Data
public class StoTracePushRequest extends BaseStoRequest<StoTracePushResponse> {
@Override
public String apiName() {
return getApiName();
}
public static String getApiName() {
return "STO_TRACE_PLATFORM_PUSH";
}
@Override
public String toAppKey() {
return null;
}
@Override
public Class<StoTracePushResponse> getResponseClass() {
return StoTracePushResponse.class;
}
/**
* 运单号
*/
private String waybillNo;
/**
* 订单号
*/
private String linkCode;
/**
* 轨迹信息
*/
private StoTracePushInfo trace;
}
1.2 申通路由推送接口返回类:
public class StoTracePushResponse extends BaseStoResponse<StoTracePushDTO> {
}
这里的 StoTracePushDTO 类,是申通api文档里注明的响应参数,大家可以依据具体的规范设计,代码我也放出来吧:
@Data
public class StoTracePushDTO {
/**
* 运单号
*/
private String waybillNo;
/**
* 是否需要重试: 用于正常收到请求,但程序处理异常时,可以传true,不管success是true还是false,申通侧都会重新推送;
*/
private Boolean needRetry;
}
那么manager层的设计就到这里了,apiManeger类的话在上一篇博客里面有发出来,这里不做任何改动可以直接调用。
2.Service层
Service层与一般开发架构是一样的,先声明service接口,然后直接用serviceImpl类来实现接口。
首先是service接口类:
public interface StoService {
/**
* 处理推送数据
*
* @param request 推送请求
* @param retryMethodEntry 重试
*/
void routerAsync(StoTracePushRequest request, RetryMethodEntry retryMethodEntry);
/**
* 扫描信息注册通知
*
* @param waybillNo 运单号
*/
void scanRegisterNotify(String waybillNo);
/**
* 路由信息查询
*
* @param waybillNo 运单号
* @return 路由信息
*/
StoTraceQueryResponse traceQuery(String waybillNo);
}
这里我们会用到前面的两个我方调用三方的接口,但是主要的推送接口是routerAsync方法。
然后是实现类:
@Service
public class StoServiceImpl implements StoService {
/**
* stoApiManager
*/
@Autowired
private StoApiManager stoApiManager;
/**
* receiveDataService
*/
@Autowired
private ReceiveDataService receiveDataService;
/**
* 流程状态映射
*/
private final Map<String, FlowTypeEnum> flowTypeRelation = new HashMap<String, FlowTypeEnum>() {
{
put("收件", FlowTypeEnum.COLLECT);
put("发件", FlowTypeEnum.TRANSIT);
put("到件", FlowTypeEnum.TRANSIT);
put("派件", FlowTypeEnum.DISPATCH);
put("第三方代派", FlowTypeEnum.DISPATCH);
put("问题件", FlowTypeEnum.SIGN_FINISH);
put("退回件", FlowTypeEnum.SIGN_FINISH);
put("留仓件", FlowTypeEnum.SIGN_FINISH);
put("快件取出", FlowTypeEnum.SIGN_FINISH);
put("代取快递", FlowTypeEnum.SIGN_FINISH);
put("派件入柜", FlowTypeEnum.SIGN_FINISH);
put("柜机代收", FlowTypeEnum.SIGN_FINISH);
put("驿站代收", FlowTypeEnum.SIGN_FINISH);
put("第三方代收", FlowTypeEnum.SIGN_FINISH);
put("签收", FlowTypeEnum.SIGN_FINISH);
put("客户签收", FlowTypeEnum.SIGN_FINISH);
}
};
@Override
@RetryMethod
public void routerAsync(StoTracePushRequest request, RetryMethodEntry retryMethodEntry) {
if (request == null || request.getTrace() == null) {
return;
}
String waybillNo = request.getWaybillNo();
LogisticsCompanyEnum logisticsCompany = LogisticsCompanyEnum.STO;
// 处理接收数据
try {
saveRecords(waybillNo, logisticsCompany, Collections.singletonList(request.getTrace()));
} catch (NotFoundPreNodeException ex) {
// 目前仅处理揽收异常的数据
if (ex.getFlowType().equals(FlowTypeEnum.COLLECT)) {
// 调用查询接口,获取揽收时间的节点
StoTraceQueryResponse response = traceQuery(waybillNo);
if (response.getIsError() || response.getData() == null
|| CollectionUtils.isEmpty(response.getData().get(waybillNo))) {
throw new SntProjectException("[查询路由数据失败] " + response.getErrMsg() + " 响应报文:" + response.getBody());
}
// 全量保存查询到的数据
saveRecords(waybillNo, logisticsCompany, response.getData().get(waybillNo));
}
throw ex;
}
}
private void saveRecords(String waybillNo, LogisticsCompanyEnum logisticsCompany, List<StoTracePushInfo> records) {
// 按照接收时间排序
records.sort(Comparator.comparing(StoTracePushInfo::getOpTime));
HashMap<FlowTypeEnum, Date> typeAndTimeMap = new HashMap<>(5);
for (StoTracePushInfo record : records) {
FlowTypeEnum type = flowTypeRelation.get(record.getScanType());
if (type == null) {
continue;
}
// 所有可以映射的状态都存储
typeAndTimeMap.putIfAbsent(type, record.getOpTime());
}
// 生成记录到表格存储的运单流转信息
List<ExpressDeliveryRouteDTO> expressRouteDtos = records.stream().map(record -> {
ExpressDeliveryRouteDTO deliveryRouteDTO = new ExpressDeliveryRouteDTO();
deliveryRouteDTO.setTimeStamp(record.getOpTime().getTime());
deliveryRouteDTO.setRawJson(JsonUtil.toJson(record));
deliveryRouteDTO.setMemo(record.getMemo());
deliveryRouteDTO.setFlowType(flowTypeRelation.getOrDefault(record.getScanType(), FlowTypeEnum.NONE));
deliveryRouteDTO.setLocation(record.getOpOrgCityName());
return deliveryRouteDTO;
}).collect(Collectors.toList());
// 处理接收数据
receiveDataService.receiveData(waybillNo, logisticsCompany, typeAndTimeMap, expressRouteDtos);
}
@Override
public void scanRegisterNotify(String waybillNo) {
StoTraceSubscribeRequest request = new StoTraceSubscribeRequest();
request.setSubscribeInfos(new ArrayList<>());
StoSubscribeInfo subscribeInfo = new StoSubscribeInfo();
subscribeInfo.setWaybillNo(waybillNo);
request.getSubscribeInfos().add(subscribeInfo);
StoTraceSubscribeResponse response = stoApiManager.execute(request);
if (response.getIsError()) {
throw new SntProjectException("注册通知失败," + response.getErrMsg() + ",响应报文:" + response.getBody());
}
}
@Override
public StoTraceQueryResponse traceQuery(String waybillNo) {
StoTraceQueryRequest request = new StoTraceQueryRequest();
request.setWaybillNos(Collections.singletonList(waybillNo));
return stoApiManager.execute(request);
}
}
3.controller层
controller层一样分为controller接口与controllerImpl实现类,
首先是controller接口:
@Api(tags = "StoRouterController - 申通推送数据接收网关")
public interface StoRouterController {
/**
* 申通推送数据接收网关
*/
String ROUTER_STO = "/router/sto";
/**
* 申通推送数据接收网关
*
* @param request request
* @param body body
*/
@PostMapping(value = ROUTER_STO, produces = {MediaType.APPLICATION_XML_VALUE})
@ApiOperation(value = "申通推送数据接收网关")
ResponseEntity<String> router(HttpServletRequest request, @RequestBody(required = false) String body);
}
然后是controllerImpl来实现接口:
@Slf4j
@RestController
public class StoRouterControllerImpl implements StoRouterController {
/**
* stoConfig
*/
@Autowired
private StoConfig stoConfig;
/**
* objectMapper
*/
@Autowired
private ObjectMapper objectMapper;
/**
* stoApiManager
*/
@Autowired
private StoApiManager stoApiManager;
/**
* stoService
*/
@Autowired
private StoService stoService;
@Override
public ResponseEntity<String> router(HttpServletRequest request, String body) {
BaseStoResponse<?> response = execute(request, body);
String json;
try {
json = objectMapper.writeValueAsString(response);
} catch (Exception e) {
e.printStackTrace();
log.error("[序列化异常] 入参:" + body);
json = "{\"success\":false,\"errorCode\":\"S03\",\"errorMsg\":\"序列化异常\"}";
}
return ResponseEntity.ok().header("Content-Type", "application/json;charset=UTF-8").body(json);
}
private BaseStoResponse<?> execute(HttpServletRequest request, String body) {
try {
// 验证签名
String appKey = request.getParameter("to_appkey");
String content = request.getParameter("content");
String sign = request.getParameter("data_digest");
if (StringUtils.isBlank(appKey) || !stoConfig.getFromAppKey().equals(appKey) || StringUtils.isBlank(content)
|| StringUtils.isBlank(sign) || !stoApiManager.doSign(content).equals(sign)) {
throw new SntProjectException("签名有误");
}
// 异步处理
String apiName = request.getParameter("api_name");
if (!apiName.equals(StoTracePushRequest.getApiName())) {
throw new SntProjectException("暂不支持此接口");
}
StoTracePushRequest stoTracePushRequest = objectMapper.readValue(content, StoTracePushRequest.class);
StoTracePushDTO stoTracePushDTO = new StoTracePushDTO();
stoTracePushDTO.setWaybillNo(stoTracePushRequest.getWaybillNo());
stoTracePushDTO.setNeedRetry(Boolean.FALSE);
stoService.routerAsync(stoTracePushRequest, null);
return StoTracePushResponse.ok(stoTracePushDTO);
} catch (SntProjectException e) {
log.error("[请求异常] 入参:" + body + " 异常:" + e.getMsg());
return StoTracePushResponse.fail("请求异常," + e.getMsg());
} catch (Exception e) {
log.error("[请求异常] 入参:" + body + " 异常:" + e);
return StoTracePushResponse.fail("请求异常");
}
}
}
到这里,推送接口就全部写完了。
然后本次的总结到这里也就结束了,对你有帮助、喜欢的话给个赞吧!