前言:本篇博客是接着上篇總結寫的,想了解怎么對接第三方平台接口的同學可以看我上一篇博客,地址是 https://www.cnblogs.com/alanturingson/p/13377500.html ,本篇將介紹如何封裝接口給第三方平台來調用。
正文開始:
既然是對接快遞平台,那么無非就有兩種對接的模式。第一種,是本方去調用第三方的接口,例如快遞查詢接口、路由查詢接口、路由訂閱接口等。這些都需要本方主動去請求三方提供的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("請求異常"); } } }
到這里,推送接口就全部寫完了。
然后本次的總結到這里也就結束了,對你有幫助、喜歡的話給個贊吧!