0
点赞
收藏
分享

微信扫一扫

【存货系列】Feign实战之对接智运开放平台

杨小羊_ba17 2022-03-17 阅读 77
JAVA

Feign实战之对接智运开放平台

背景简述

前脚刚结束ISApI协议的对接工作,马上又有类似的接口对接类项目来了,现在我作为Feign1的脑残粉,当然是毋庸置疑的想到用这个技术。

此项目需要对接智运开放平台2的GPS数据,以及相应的驶入驶出事件,因为和之前对接ISAPI在协议层有较大的区别,以及存在一些特殊性。所以我觉得有必要整理一下分享出来,作为Feign实战的一个案例,或许可以更好的帮助其他同事举一反三。

这是继之前总结分享之后再出的一个实战系列,所以对于Feign相关的知识点,此文就不再赘述,有兴趣的的直接参考前文“通过Feign对接ISAPI协议接口的总结分享”即可。

智运开放平台

概述

智运开放平台整合了道路货运行业内重型载货汽车的全量数据。在基于700万重载货运车辆的大数据基础上、经过数据清洗、整合、处理后,开放提供多维度、多层次的车辆数据供已获取授权客户在道路货运相关业务场景中使用。

智运开放平台API 服务目前已为物流、金融、交通、保险、政府、车厂六大领域全面放开数据,开放的接口覆盖: 订单跟踪 、 信息验证、信息查询、车辆事件、运营评估、找车、统计分析、辅助工具八大类。

接口联调流程

第一步:获取账密

由你方商务人员向我方商务人员发起申请,申请成功后将为您提供调用接口所需的API账号、密码、私钥及客户端ID信息。

第二步:编写接口

编写调用API接口的客户端代码,按照接口定义的https调用方式及传输转换后的参数进行调用,调用示例代码详见调用示例章节(可以直接通过SDK3调用)。

第三步:联调测试

联调测试环境域名为:openapitest.sinoiov.cn,请根据文档中的示例参数或示例程序进行联调,联调环境为非正式数据,需使用我方指定的参数进行联调。接口调用成功且返回数据可正常解析后,表示联调成功。

联调过程中如遇到问题,请与我方运营人员联系,联调通过后如需使用正式环境则与我方商务人员联系,会为您提供正式环境域名及账号、密码、客户端ID等信息。

技术要求

接口调用方式

API接口通过https方式对外提供接口服务,遵循API接口规范,发送https请求,支持POST数据格式为form数据交换接口将验证API用户的合法性和安全性,然后提供接口服务,接口数据采用UTF8格式编码。

接口调用方式

用户认证和接口安全

将根据第三方开发者访问的车辆范围,进行用户授权等权限验证,保障用户数据的安全性。
用户首次调用接口,须先登录,认证通过后生成令牌。

令牌长期有效,登录后之前的令牌将立即失效,多服务调用业务接口时,建议由统一服务调用登录接口将令牌缓存起来,多个服务统一从共享缓存中获取令牌。令牌失效后再调用登录接口统一获取令牌,避免频繁调用登录接口,建议一天内登录次数不超过10次,超过10次将触发安全系统报警。

另外,为了您账号的安全,建议每月定时更新令牌。根据国家法律法规,并考虑数据安全的需要,系统会有接口访问上限的限制。

接口事例

示例 1:登录接口

请求地址格式:https://openapitest.sinoiov.cn/save/apis/login 。

请求参数示例:

拼接参数参数说明参数值是否必选
user账号xxx
pwd密码xxx
cid客户端xxx
srt私钥xxx

返回数据示例:

参数说明
status登录状态:详见“状态码说明”章节
result令牌
{
	"status": 1001,
	"result": "301f4556f9bd4e9c9a3dc0cd7a6ad7db"
}
示例 2:运输时效管理接口

请求地址格式:https://openapitest.sinoiov.cn/save/apis/transTimeManage 。

请求参数示例:

拼接参数参数说明参数值是否必选
token令牌xxx
cid客户端xxx
srt私钥xxx
vclN车牌号陕YH0008
vco车牌颜色2

返回数据示例:

参数说明
status登录状态:详见“状态码说明”章节
result车辆位置信息对象
{
	"result": {
		"country": "衡东县",
		"city": "衡阳市",
		"utc": "1646356351000",
		"spd": "15.9",
		"lon": "67761939",
		"adr": "湖南省衡阳市衡东县",
		"drc": "319",
		"province": "湖南省",
		"lat": "16363728",
		"offlineState": false
	},
	"status": 1001
}

状态码说明

状态码说明状态码说明
1001接口执行成功1014无接口权限
1002参数不正确1015用户认证系统已升级,
请使用令牌访问
1003车辆调用数量已达上限1016令牌失效
1004接口调用次数已达上限1017账号欠费
1005该API 账号未授权指定
所属行政区划数据范围
1018授权的接口已禁用
1006无结果1019授权的接口已过期
1010用户名或密码不正确1020该车调用次数已达上限
1011IP不在白名单列表1021client_id不正确
1012账号已禁用1031签名验证失败
1013账号已过有效期9001系统异常

对接设计实现

需求分析

从智运开放平台的技术要求上可以看出来来,他的请求认证方式有点特殊,甚至有点操蛋,竟然将认证信息通过请求参数的方式传输,而不是传统的放在头文件中进行基础认证或者摘要认证。

而且他自己有独立的调用SDK,虽然也有开放内部的认证信息计算逻辑,但是最理想的方式还是通过集成已有的SDK进行接口的调用,方便以后出新版本后的迭代升级。

对于请求返回实体,将认证请求失败的情况也以200的状态进行返回,这方面也值得思考如何进行统一处理。

设计思路

首先把Feign的架构图拉出来看了下,因为认证方式的特殊性,所以可能需要需要重新实现Client请求框架

接口的错误信息是通过Response Body进行传输的,所以还需要重新实现ErrorDecoder异常处理,将错误信息进行转义打印以及其他一些操作。

Feign设计架构

为什么不通过拦截器实现认证信息注入?

可以看到认证信息和业务参数都是通过表单提交的方式进行提交,而我们把拦截器的接口拉出来看了眼。

RequestInterceptor拦截器只有一个简单的apply接口,入参为RequestTemplate,那么我们再把RequestTemplate搂出来看眼。

public interface RequestInterceptor {

  /**
   * Called for every request. Add data using methods on the supplied {@link RequestTemplate}.
   */
  void apply(RequestTemplate template);
}

通过RequestTemplate可以很明显看出来可以很方便的设置headers,因为是一个Map数据对象。但是想要修改body内容,好像就没有那么简单了,它是一个byte[]数据结构,操作起来并不方便。这也就是为什么没有通过拦截器的方式去实现认证参数的注入的一个原因。

/**
 * Builds a request to an http target. Not thread safe. <br> <br><br><b>relationship to JAXRS
 * 2.0</b><br> <br> A combination of {@code javax.ws.rs.client.WebTarget} and {@code
 * javax.ws.rs.client.Invocation.Builder}, ensuring you can modify any part of the request. However,
 * this object is mutable, so needs to be guarded with the copy constructor.
 */
public final class RequestTemplate implements Serializable {

  private static final long serialVersionUID = 1L;
  private final Map<String, Collection<String>> queries =
      new LinkedHashMap<String, Collection<String>>();
  private final Map<String, Collection<String>> headers =
      new LinkedHashMap<String, Collection<String>>();
  private String method;
  /* final to encourage mutable use vs replacing the object. */
  private StringBuilder url = new StringBuilder();
  private transient Charset charset;
  private byte[] body;
  private String bodyTemplate;
  private boolean decodeSlash = true;
}

为什么要重写Client请求框架?

一方面愿意就是如上所述的认证信息注入问题;另一方面是为了简化请求逻辑,需要用到官方提供的SDK。综合这两个因素,因此只能考虑在Client请求层做文章。

接下来看眼Client的接口定义,一如既往的简介明了~

/**
 * Submits HTTP {@link Request requests}. Implementations are expected to be thread-safe.
 */
public interface Client {

  /**
   * Executes a request against its {@link Request#url() url} and returns a response.
   *
   * @param request safe to replay.
   * @param options options to apply to this request.
   * @return connected response, {@link Response.Body} is absent or unread.
   * @throws IOException on a network error connecting to {@link Request#url()}.
   */
  Response execute(Request request, Options options) throws IOException;
}

为什么自定义实现ErrorDecoder异常处理?

接口的调用存在一个费用计算问题,所以未返回实际数据的接口调用都不应该进行计费操作,约定为无效查询。因为一些本身无效查询,服务端还是以200状态进行返回,然后在返回消息体内进行错误提示。为了统一计费以及错误的处理,所以想到把无效查询也进行抛出异常进行处理。

那么这时候就只能够重新实现ErrorDecoder进行解析处理以及统一的错误异常日志格式化打印,看眼ErrorDecoder接口。

public interface ErrorDecoder {

  /**
   * Implement this method in order to decode an HTTP {@link Response} when {@link
   * Response#status()} is not in the 2xx range. Please raise  application-specific exceptions where
   * possible. If your exception is retryable, wrap or subclass {@link RetryableException}
   *
   * @param methodKey {@link feign.Feign#configKey} of the java method that invoked the request.
   *                  ex. {@code IAM#getUser()}
   * @param response  HTTP response where {@link Response#status() status} is greater than or equal
   *                  to {@code 300}.
   * @return Exception IOException, if there was a network error reading the response or an
   * application-specific exception decoded by the implementation. If the throwable is retryable, it
   * should be wrapped, or a subtype of {@link RetryableException}
   */
  public Exception decode(String methodKey, Response response);
}

如何处理设计Token的获取策略以及注入时机?

因为Token本身没有时效性问题,而每天登陆获取Token存在次数限制(10次),所以考虑到首次项目启动的时候就将Token进行固化存储,之后的接口调用直接读取此Token;并且下次项目启动的时候,判断已经存有Token文件,跳过登陆环节,直接加载Token;如果需要重新加载Token,手动删除Token文件即可,或者考虑文件设定时效性,如一个月失效后重新写入。流程如下:

Token获取策略流程

设计实现

Feign通过Builder对象进行客户端对象构造,其中大部分属性的接口都有Default默认实现类,主要就是为了简化客户端构造逻辑。

本次调用大部分都是取用市面上常用的实现方案:SpringMvcContractJacksonEncoderJacksonDecoder()Slf4jLogger

其中就ClientErrorDecoder两个接口进行重新实现。

Feign构造器UML

Client请求框架

基于SDK重写Client,实现通用认证信息的注入以及接口SDK调用。

/**
 * 智运请求SDK
 *
 * @author yuanzp
 * @date 2022/2/22 15:20
 * @digression done is better than perfect.
 */
public class ZhiYunSdkClient implements Client {
    private DataExchangeService dataExchangeService;
    private ZhiYunProperties zhiYunProperties;

    public ZhiYunSdkClient(ZhiYunProperties zhiYunProperties) {
        this.zhiYunProperties = zhiYunProperties;
        try {
            dataExchangeService = new DataExchangeService(zhiYunProperties.getFeignConnectTimeout(),
                    zhiYunProperties.getFeignReadTimeout());
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    @Override
    public Response execute(Request request, Request.Options options) throws IOException {
        String httpResponseBody = null;
        int status = Status.HTTP_OK;
        String reason = null;
        try {
            String body = StrUtil.str(request.body(), CharsetUtil.UTF_8);
            Map<String, String> bodyMap = JSONObject.parseObject(body, Map.class);
            String token = zhiYunProperties.getToken();
            if (StrUtil.isNotBlank(token)) {
                bodyMap.put("token", token);
                bodyMap.put("cid", zhiYunProperties.getClientId());
                bodyMap.put("srt", zhiYunProperties.getPrivateKey());
            }
            httpResponseBody = dataExchangeService.postHttps(request.url(), bodyMap);
        } catch (Exception e) {
            status = Status.HTTP_INTERNAL_ERROR;
            reason = e.getMessage();
        }
        if (httpResponseBody.indexOf(String.valueOf(StatusEnum.STATUS_1001.getCode())) == -1) {
            status = Status.HTTP_INTERNAL_ERROR;
        }
        return toFeignResponse(request, status, reason, httpResponseBody);
    }

    private Response toFeignResponse(Request request, int status, String reason, String httpResponseBody) {
        return Response.builder().request(request)
                .status(status).reason(reason).body(httpResponseBody, CharsetUtil.CHARSET_UTF_8)
                .headers(CollUtil.newHashMap()).build();
    }
}

ErrorDecoder异常处理

对于一些常见的错误,可以进行预判断及统一打印处理,在业务层就可以更便捷的使用接口,不需要使用扎堆的try catch进行异常捕捉处理。

/**
 * 智能分析自定义异常处置
 *
 * @author yuanzp
 * @date 2022/2/22 8:25
 * @digression done is better than perfect.
 */
@Slf4j
public class ZhiYunErrorDecoder extends ErrorDecoder.Default {
    @Override
    public Exception decode(String methodKey, Response response) {
        Exception exception = super.decode(methodKey, response);
        String responseBody = response.body().toString();
        Integer status = Optional.ofNullable(responseBody)
                .map(JSONUtil::parseObj).map(jsonObject -> jsonObject.getInt("status")).orElse(9999);
        StatusEnum statusEnum = EnumUtil.likeValueOf(StatusEnum.class, status);
        log.error(StrUtil.concat(true, statusEnum.getExplain(),
                StrUtil.LF, JSONUtil.formatJsonStr(responseBody)));
        return exception;
    }
}

Feign构造器

进行Feign客户端的构造以及Bean注入。

/**
 * 智运开放平台服务 自定义feign客户端
 *
 * @author yuanzp
 * @date 2022/2/22 9:54
 */
@Slf4j
@Configuration
public class ZhiYunFeignConfiguration {
    private static final Contract DEFAULT_CONTRACT = new SpringMvcContract();
    private static Client DEFAULT_CLIENT = new ApacheHttpClient();
    private static final Encoder DEFAULT_ENCODER = new JacksonEncoder();
    private static final Decoder DEFAULT_DECODER = new JacksonDecoder();
    private static final ErrorDecoder DEFAULT_ERROR_DECODER = new ZhiYunErrorDecoder();

    private ZhiYunProperties zhiYunProperties;

    @Autowired
    public ZhiYunFeignConfiguration(ZhiYunProperties zhiYunProperties) {
        this.zhiYunProperties = zhiYunProperties;
        DEFAULT_CLIENT = new ZhiYunSdkClient(zhiYunProperties);
    }

    /**
     * 登录服务
     * @return
     */
    @Bean("loginServiceClient")
    public LoginServiceClient loginServiceClient() {
        return createFeignClient(LoginServiceClient.class);
    }

    /**
     * 订单跟踪服务
     * @return
     */
    @Bean("orderTrackingServiceClient")
    public OrderTrackingServiceClient orderTrackingServiceClient() {
        return createFeignClient(OrderTrackingServiceClient.class);
    }

    /**
     * 车辆事件服务
     * @return
     */
    @Bean("vehicleEventServiceClient")
    public VehicleEventServiceClient vehicleEventServiceClient() {
        return createFeignClient(VehicleEventServiceClient.class);
    }

    /**
     * Feign客户端构造函数
     * @param tClass
     * @param <T>
     * @return
     */
    private <T> T createFeignClient(Class<T> tClass) {
        return Feign.builder().contract(DEFAULT_CONTRACT).client(DEFAULT_CLIENT)
                .encoder(DEFAULT_ENCODER).decoder(DEFAULT_DECODER).errorDecoder(DEFAULT_ERROR_DECODER)
                .logger(new Slf4jLogger(tClass)).logLevel(Logger.Level.FULL)
                .target(tClass, zhiYunProperties.getFeignUrl());
    }
}

Token认证信息固化

因为本身Token没有时效性的校验,所以当项目启动时候就可以将Token进行文件固化,在之后的一段时间内可以一直使用此Token,而不需要频繁请求Login请求。

/**
 * 智运连接注册(主要就是获取Token)
 *
 * @author yuanzp
 * @date 2022/2/22 14:01
 */
@Slf4j
@Component
@Order(value = 1)
public class ZhiYunRegisterRunner implements CommandLineRunner {


    public static final String REGISTER_CONFIGURATION_FILE_NAME = "zhiyun_register_configuration.txt";

    @Resource
    private LoginService loginService;

    @Override
    public void run(String... args) {
        String path = ClassUtils.getDefaultClassLoader().getResource(StrUtil.EMPTY).getPath() + REGISTER_CONFIGURATION_FILE_NAME;
        path = URLUtil.decode(path);
        boolean exist = FileUtil.exist(path);
        if (exist) {
            log.info("智运服务已注册,如要更新Token,请删除文件:\n{}", path);
            List<String> lines = FileUtil.readUtf8Lines(path);
            loginService.setToken(lines.get(1));
            return;
        }
        String token = loginService.getToken();
        if (StrUtil.isNotBlank(token)) {
            String s = StrUtil.concat(true, DateUtil.now(),
                    " 智运服务注册成功,token获取成功!", StrUtil.LF, token);
            FileUtil.writeUtf8String(s, path);
            log.info("{}\n信息详见:{}", s, path);
            loginService.setToken(token);
        }else{
            log.info("智运服务注册失败,token获取失败!");
        }
    }
}

调用记录设计

通过AOP切面实现接口调用记录的统一打印或者保存,以此实现接口调用量统计或者相应的费用计算。

接口枚举

/**
 * 请求接口枚举
 *
 * @author yuanzp
 * @date 2022/3/1 11:21
 */
public enum InterfaceEnum {
    /**
     *
     */
    TRANS_TIME_MANAGE(ZhiYunConstants.CALL_TYPE_REQUEST, "运输时效管理", "/save/apis/transTimeManage", "0.5元/车天"),
    ROUTER_PATH(ZhiYunConstants.CALL_TYPE_REQUEST, "订单路径跟踪", "/save/apis/routerPath", "1元/车天"),
    DRIVE_INTO(ZhiYunConstants.CALL_TYPE_CALLBACK, "车辆驶入", "/zhiYun/vehicleEvent/driveInto", "阶梯计费(月)\n" +
            "1-1000次   0.5元/次\n" +
            "1001-5000次  0.4元/次\n" +
            "5001次及以上 0.3元/次"),
    DRIVE_OUT(ZhiYunConstants.CALL_TYPE_CALLBACK, "车辆驶出", "/zhiYun/vehicleEvent/driveOut", "阶梯计费(月)\n" +
            "1-1000次   0.5元/次\n" +
            "1001-5000次  0.4元/次\n" +
            "5001次及以上 0.3元/次");

    InterfaceEnum(String callType, String name, String url, String exercisePrice) {
        this.callType = callType;
        this.name = name;
        this.url = url;
        this.exercisePrice = exercisePrice;
    }

    /**
     * 接口名称
     */
    private String name;

    /**
     * 访问类型(request:请求;callback:回调)
     */
    private String callType;
    /**
     * 接口地址
     */
    private String url;
    /**
     * 执行价格
     */
    private String exercisePrice;
}

切面注解

/**
 * 智运接口请求账单注解
 *
 * @author yuanzp
 * @date 2022/3/1 14:15
 */
@Target({ElementType.PARAMETER, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface ZhiYunBillingHelper {
    /**
     * 接口枚举
     * @return
     */
    InterfaceEnum interfaceEnum();

    /**
     * 使用spel表达式获取请求车牌
     * @return
     */
    String plateNo();
}

切面逻辑

/**
 * 智运接口调用计费切面
 *
 * @author yuanzp
 * @date 2022/3/1 14:18
 */
@Component
@Scope
@Aspect
@Slf4j
public class ZhiYunBillingHelperAspect {
    private ExpressionParser parser = new SpelExpressionParser();
    private LocalVariableTableParameterNameDiscoverer discoverer = new LocalVariableTableParameterNameDiscoverer();
    @Autowired
    private ZhiYunBillingService zhiYunBillingService;

    @Pointcut("@annotation(com.hikvision.osurveillance.warn.modules.annotation.ZhiYunBillingHelper)")
    public void myPointcut() {
    }

    /**
     * 正常请求之后
     * @param joinPoint
     */
    @AfterReturning(pointcut = "myPointcut()")
    public void afterCallZhiYunMethod(JoinPoint joinPoint) {
        Method method = getMethod(joinPoint);
        ZhiYunBillingHelper zhiYunBillingHelper = method.getAnnotation(ZhiYunBillingHelper.class);
        InterfaceEnum interfaceEnum = zhiYunBillingHelper.interfaceEnum();
        Object[] args = joinPoint.getArgs();
        String args_str = JSONUtil.toJsonStr(JSONUtil.parseArray(args));
        String plateNo = getPlateNo(args, method, zhiYunBillingHelper);
        log.info("\n{} 触发智运接口【{}{}】," +
                        "\n请求地址【{}】," +
                        "\n车牌【{}】," +
                        "\n请求参数【{}】," +
                        "\n计费规则【{}】", DateUtil.now(),
                interfaceEnum.name(), interfaceEnum.getName(), interfaceEnum.getUrl(), plateNo, args_str, interfaceEnum.getExercisePrice());
        //日志保存
        zhiYunBillingService.save(interfaceEnum, plateNo, args_str);
    }

    private String getPlateNo(Object[] args, Method method, ZhiYunBillingHelper zhiYunBillingHelper) {
        //获取方法的参数值
        EvaluationContext context = this.bindParam(method, args);

        //根据spel表达式获取值
        Expression expression = parser.parseExpression(zhiYunBillingHelper.plateNo());
        Object key = expression.getValue(context);
        return String.valueOf(key);
    }

    /**
     * 将方法的参数名和参数值绑定
     *
     * @param method 方法,根据方法获取参数名
     * @param args   方法的参数值
     * @return
     */
    private EvaluationContext bindParam(Method method, Object[] args) {
        //获取方法的参数名
        String[] params = discoverer.getParameterNames(method);

        //将参数名与参数值对应起来
        EvaluationContext context = new StandardEvaluationContext();
        for (int len = 0; len < params.length; len++) {
            context.setVariable(params[len], args[len]);
        }
        return context;
    }

    /**
     * 获取请求方法
     *
     * @param joinPoint
     * @return
     */
    private Method getMethod(JoinPoint joinPoint) {
        Signature signature = joinPoint.getSignature();
        return ((MethodSignature) signature).getMethod();
    }

}

实现方式比对

代码比对

官方SDK调用示例

/**
 * 用户登陆
 */
public void login() {
    try {
        Map<String, String> map = new HashMap<String, String>(4);
        map.put("user", "您的API账号");
        map.put("pwd", "您的API账号密码");
        map.put("srt", "您的私钥");
        map.put("cid", "您的客户端ID");
        String url = "https://openapi-test.sinoiov.cn/save/apis/login";
        //设置http读写超时
        DataExchangeService des = new DataExchangeService(5000, 8000);
        System.out.println("请求地址:" + url);
        //通过https方式调用,此方法内部会使用私钥生成签名参数sign,私钥不会发送
        String res = des.postHttps(url, map);
        System.out.println("返回:" + res);
    } catch (Exception e) {
        System.out.println("e:" + e.getMessage());
    }
}

/**
 * 一、 运输时效管理 接口
 * 本接口提供指定车牌号的车辆 运输时效 。
 */
public static void transTimeManage() {
    try {
        Map<String, String> map = new HashMap<String, String>(4);
        map.put("token", "您的令牌");
        map.put("cid", "您的客户端ID");
        map.put("srt", "您的私钥");
        map.put("vclN", "陕YH0009");
        map.put("vco", "2");
        String url = "https://openapi-test.sinoiov.cn/save/apis/transTimeManage";
        //设置http读写超时
        DataExchangeService des = new DataExchangeService(5000, 8000);
        System.out.println("请求地址:" + url);
        //通过https方式调用,此方法内部会使用私钥生成签名参数sign,私钥不会发送
        String res = des.postHttps(url, map);
        System.out.println("返回:" + res);
    } catch (Exception e) {
        System.out.println("e:" + e.getMessage());
    }
}

Feign方式调用示例

接口定义:

/**
 * 登录服务
 *
 * @author yuanzp
 * @date 2022/2/22 9:49
 * @digression done is better than perfect.
 */
public interface LoginServiceClient {
    /**
     * 登录
     * @param loginReqDTO
     * @return
     */
    @RequestMapping(method = RequestMethod.POST, value = "/save/apis/login")
    PublicResult<String> login(LoginReqDTO loginReqDTO);
}

/**
 * 订单跟踪服务
 *
 * @author yuanzp
 * @date 2022/2/22 9:49
 * @digression done is better than perfect.
 */
@RequestMapping(value = "/save/apis")
public interface OrderTrackingServiceClient {
    /**
     * 运输时效管理(车辆实时位置)
     * @param transTimeManageReqDTO
     * @return
     */
    @RequestMapping(method = RequestMethod.POST, value = "/transTimeManage")
    PublicResult<TransTimeManageRspDTO> transTimeManage(TransTimeManageReqDTO transTimeManageReqDTO);
}

接口调用:


/**
 * 用户登陆
 */
public void login() {
    LoginReqDTO loginReqDTO = new LoginReqDTO();
    loginReqDTO.setUser(zhiYunProperties.getUser());
    loginReqDTO.setPwd(zhiYunProperties.getPassword());
    loginReqDTO.setSrt(zhiYunProperties.getPrivateKey());
    loginReqDTO.setCid(zhiYunProperties.getClientId());
    PublicResult<String> publicResult = loginServiceClient.login(loginReqDTO);
}

/**
 * 一、 运输时效管理 接口
 * 本接口提供指定车牌号的车辆 运输时效 。
 */
public static void transTimeManage() {
    TransTimeManageReqDTO transTimeManageReqDTO = new TransTimeManageReqDTO(plateNo);
    PublicResult<TransTimeManageRspDTO> transTimeManageRspDTOPublicResult 
        = orderTrackingServiceClient.transTimeManage(transTimeManageReqDTO);
}

综合分析比对

综合分析比对

Feign调用找扩展性、功能性、易用性方面都更占优势;而SDK调用则在移植性方面占有一定优势,当然这也是相对而言。

综合来说Feign的方式更具备优势,所以完全想不到什么理由,再去用传统的方式写那一坨坨的代码,至少我不会再那么干了。

参考资料

  • 智运开放平台技术白皮书-V4.0.3版本.pdf

  • openapi-sdk-6.0.jar

  • 通过Feign对接ISAPI协议接口的总结分享

写在最后面的话

正因为有了之前Feign的基础知识,才能在这次对接中快速加以集成实现。因此就深刻的感受到,有时候了解到一个技术点的内部原理及本质,对于项目实战中的帮助会是那么的巨大。

那么,祝你好运吧!盆友~4


  1. Feign是一个声明式WebService客户端。使用Feign能让编写Web Service客户端更加简单, 它的使用方法是定义一个接口,然后在上面添加注解,同时也支持JAX-RS标准的注解。Feign也支持可拔插式的编码器和解码器。SpringCloud对Feign进行了封装,使其支持了Spring MVC标准注解和HttpMessageConverters。Feign可以与Eureka和Ribbon组合使用以支持负载均衡。 ↩︎

  2. 智运开放平台由北京中交兴路车联网科技有限公司研发,公司以定位服务为基础,以数据服务为核心, 致力于推动中国货运行业转型升级的数据科技公司; ↩︎

  3. SDK是一些被软件工程师用于为特定的软件包、软件框架、硬件平台、操作系统等建立应用软件的开发工具的集合。 ↩︎

  4. “我们该去更好的地方,还是在这片荒芜之地寻求更好的自己? ”—created by 袁志鹏 on 二月 25,2022;last edited by 袁志鹏 on 三月 4,2022 ↩︎

举报

相关推荐

0 条评论