• 关于调用三方平台接口与推送接口的总结<二>(2020.7.27)


    前言:本篇博客是接着上篇总结写的,想了解怎么对接第三方平台接口的同学可以看我上一篇博客,地址是  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("请求异常");
            }
        }
    }  

    到这里,推送接口就全部写完了。

    然后本次的总结到这里也就结束了,对你有帮助、喜欢的话给个赞吧!

  • 相关阅读:
    静态联编和动态联编
    常用Oracle分析函数详解
    Web Service , 不详细的介绍
    Eclipse安装JSEclipse和Spket
    IE中页面不居中,火狐谷歌等正常
    ExtJS3 详解与实践 之 第二章
    IPV6正则
    很漂亮、兼容火狐的Js日期选择,自动填充到输入框
    使用googlecodeprettify高亮显示网页代码
    ExtJS3 详解与实践 之 第三章
  • 原文地址:https://www.cnblogs.com/alanturingson/p/13384096.html
Copyright © 2020-2023  润新知