如何设计优雅的OpenAPI?针对国内四大厂商的OpenAPI方案对比分析

最近因为工作的需要,对接了国内四大厂商应用商店的API,获取广告投放数据,针对四大厂商的OpenAPI实现方案进行分析。帮忙我们设计一个优雅的openapi。

1.华为应用商店的OpenAPI对接方案

1.1华为采用Oauth2.0中的客户端模式来获取Token

请求示例

POST /api/oauth2/v1/token
Host: connect-api.cloud.huawei.com
Content-Type: application/json
{
   "grant_type":"client_credentials",
   "client_id":"26********20",
   "client_secret":"************************"
}

响应示例

HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8
{
    "access_token": "eyJhbGciOiJIUzU****************",    
    "expires_in": 172800
}

调用示例

public static String getToken(String domain, String clientId, String clientSecret) {
    String token = null;
    try {
        HttpPost post = new HttpPost(domain + "/oauth2/v1/token");

        JSONObject keyString = new JSONObject();
        keyString.put("client_id", "18893***83957248");
        keyString.put("client_secret", "B15B497B44E080EBE2C4DE4E74930***52409516B2A1A5C8F0FCD2C579A8EB14");
        keyString.put("grant_type", "client_credentials");

        StringEntity entity = new StringEntity(keyString.toString(), Charset.forName("UTF-8"));
        entity.setContentEncoding("UTF-8");
        entity.setContentType("application/json");
        post.setEntity(entity);

        CloseableHttpClient httpClient = HttpClients.createDefault();
        HttpResponse response = httpClient.execute(post);
        int statusCode = response.getStatusLine().getStatusCode();
        if (statusCode == HttpStatus.SC_OK) {

            BufferedReader br =
                new BufferedReader(new InputStreamReader(response.getEntity().getContent(), Consts.UTF_8));
            String result = br.readLine();
            JSONObject object = JSON.parseObject(result);
            token = object.getString("access_token");
        }

        post.releaseConnection();
        httpClient.close();
    } catch (Exception e) {

    }
    return token;
}

1.2通过API查询广告子任务统计报表

请求示例

POST https://connect-api.cloud.huawei.com/api/marketing-api/v1/report/ad/subtask
Content-type: application/json
Authorization: Bearer ***
client_id:***

{
    "startDate":"2021-09-14",
    "endDate":"2021-09-14",
    "groupby": [
        "statDate",
        "taskId",
        "subTaskId"
    ],
    "filtering": {
        "taskIds": [
            "101153900"
        ],
        "subTaskIds": [
            "1437676090851860480"
        ]
    }
}

响应示例

{
    "code": 20770001,
    "msg": "",
    "total": 1,
    "datas": [{
        "statDate": "2021-09-14",
        "taskId": 101153900,
        "subTaskId": "1437676090851860480",
        "cost": 180,
        "show": 0,
        "exposure": 120,
        "click": 50,
        "clickRate": 0.42,
        "download": 31,
        "downloadRate": 0.26,
        "downloadAverageCost":  5.8,
        "conversions": [
          {
             "conversionBehaviorId": "123",
             "conversionCount": 6,
             "conversionRate": 0.3,
             "conversionAvgPrice": 6.6
          }
        ]
    }]
}

调用示例

Curl

curl -X POST https://connect-api.cloud.huawei.com/api/marketing-api/v1/report/ad/subtask -H "Authorization:Bearer ***" -H "client_id:***" -H "Content-type: application/json"-d '{"startDate":"2021-04-06","endDate":"2021-04-20"}'

1.3华为实现方案分析

该实现方案是目前最流行的实现方案,目前阿里,腾讯,百度都使用该方案来作为API的对接方案。

2.OPPO应用商店的OpenAPI对接方案

2.1OPPO应用商店的API对接是通过SDK来对接的,官方提供操作的SDK

如何安装运行


    com.heytap.ads.omni
    omni-api-sdk
    1.0.2
    OPPO Omni API SDK

如何使用

SDK数组参数调用的方法名与API接口一一对应,如v3/ad/get接口就对应OmniAds.getInstance().ads().v3AdGet()方法。

2.2使用SDK-DEMO示例(根据配置类型获取配置项示例)


/**
 * 根据配置类型获取配置项
 */
public class V3CommunalConfigGetList {
    //定义omniAds
    public OmniAds omniAds;
    //注意:owner_id是Long类型,传入的参数结尾要加“L”
    private final static Long OWNER_ID = 分配到的owner_id;
    //API授权接入方唯一身份标识;
    private final static String API_ID = 分配的apiId;
    //开通API授权后获得的私钥,在开通授权邮件中会与app_id一块提供;
    private final static String API_KEY = 分配的apiKey;
    //重要提示:返回类型暂定ResultDto,该返回类型由omniAds.adsCommunal().v3CommunalConfigGetList(data)决定,调用该方法后获取返回类型再进行修改
    public ResultDto getConfigList() throws Exception {
        OmniAds omniAds = new OmniAds(OWNER_ID, API_ID, API_KEY);
        // 默认使用沙箱环境(测试环境)
        omniAds.useSandbox();
        //线上环境
        //omniAds.useProduction();
        // debug==true 会打印请求详细信息
        omniAds.setDebug(true);
        //传递参数分为4种情况:
        //1.传递对象参数---接口备注详情里标注了需要实例化的传参对象,通过data.setxxx 设置必要参数, 详情见文档
         (例)  AdsConfigReq data = new AdsConfigReq();
                data.setType("EXTENSION_TYPE");
        //2.map动态传递参数---文档里未标注实例化的传参对象则用map传递,通过map.put()设置必要参数,详情见文档
         (例)  Map map = new HashMap();
                map.put("type","EXTENSION_TYPE");
        //3.字段传参,详情见文档
         (例) String type = "EXTENSION_TYPE";
        //4.不传参
        //访问接口,每个功能点对应不同的集群,接口备注详情里都有标注
        ResultDto res = omniAds.adsCommunal().v3CommunalConfigGetList(data);
        return res;
    }
    public static void main(String[] args) {
        try {
            //实例化调用类
            V3CommunalConfigGetList entity = new V3CommunalConfigGetList();
            //重要提示:类型暂定ResultDto,该返回类型由omniAds.adsCommunal().v3CommunalConfigGetList(data)决定,调用该方法后获取返回类型再进行修改
            ResultDto response = entity.getConfigList();
            //response.getData();获取响应data数据
        } catch (OmniAdsResponseException e) {
            e.printStackTrace();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

2.3方案分析

缺点:通过SDK接入看起来简单,但是如果处理不好,就会引起不可遇见的依赖冲突问题,有时甚至需要修改SDK中的源码来适配当前工程,得不偿失。例如我引入这个“omni-api-sdk”依赖之后,它给我引入了很多乱七八糟的依赖包进来,如下所示:

+- com.heytap.ads.omni:omni-api-sdk:jar:1.0.2:compile
|  +- com.squareup.okhttp:okhttp:jar:2.7.5:compile
|  +- io.gsonfire:gson-fire:jar:1.8.0:compile
|  +- com.google.inject:guice:jar:4.0:compile
|  |  +- javax.inject:javax.inject:jar:1:compile
|  |  - aopalliance:aopalliance:jar:1.0:compile
|  +- org.threeten:threetenbp:jar:1.3.5:compile
|  +- com.squareup.okhttp:logging-interceptor:jar:2.7.5:compile
|  +- org.hibernate.validator:hibernate-validator:jar:6.2.3.Final:compile
|  |  +- jakarta.validation:jakarta.validation-api:jar:2.0.2:compile
|  |  +- org.jboss.logging:jboss-logging:jar:3.4.3.Final:compile
|  |  - com.fasterxml:classmate:jar:1.5.1:compile
|  +- com.github.fge:json-schema-validator:jar:2.2.3:compile
|  |  +- com.github.fge:json-schema-core:jar:1.2.1:compile
|  |  |  +- com.github.fge:uri-template:jar:0.9:compile
|  |  |  |  - com.github.fge:msg-simple:jar:1.1:compile
|  |  |  |     - com.github.fge:btf:jar:1.2:compile
|  |  |  +- com.github.fge:jackson-coreutils:jar:1.6:compile
|  |  |  - org.mozilla:rhino:jar:1.7R4:compile
|  |  +- com.googlecode.libphonenumber:libphonenumber:jar:6.0:compile
|  |  +- javax.mail:mailapi:jar:1.4.3:compile
|  |  |  - javax.activation:activation:jar:1.1:compile
|  |  +- com.google.code.findbugs:jsr305:jar:2.0.1:compile
|  |  - net.sf.jopt-simple:jopt-simple:jar:4.6:compile
|  +- org.apache.tomcat.embed:tomcat-embed-core:jar:9.0.62:compile
|  |  - org.apache.tomcat:tomcat-annotations-api:jar:9.0.62:compile
|  +- com.alibaba:dubbo:jar:2.5.3:compile
|  |  +- org.springframework:spring:jar:2.5.6.SEC03:compile
|  |  - org.jboss.netty:netty:jar:3.2.5.Final:compile
|  +- cn.hutool:hutool-all:jar:4.0.2:compile
|  +- io.swagger:swagger-annotations:jar:1.5.20:compile
|  - com.baomidou:mybatis-plus-annotation:jar:3.5.1:compile

这样导致我的项目出现了jar包冲突的问题,我就不得不去查找到底是那个jar引起的冲突,然后再去一个一个的排除,排除配置如下:


            com.heytap.ads.omni
            omni-api-sdk
            1.0.2
            
                
                    org.springframework
                    spring
                
                
                    com.alibaba
                    dubbo
                
                
                    org.apache.tomcat.embed
                    tomcat-embed-core
                
                
                    io.swagger
                    swagger-annotations
                
                
                    com.baomidou
                    mybatis-plus-annotation
                
            
        

个人总结:SDK的实现如果实现得好,可以大大节省我们的对接时间,同时SDK设计时需要注意对那个与对接功能不相关的代码,需要从SDK中剔除出去,不相关的依赖不要引进了,以免导致各种冲突的问题。

3.VIVO应用商店的OpenAPI对接方案

3.1VIVO采用Oauth2.0授权码的方式获取token

vivo MarketingAPI采用OAuth2.0进行授权认证,目前支持授权码模式(authorization code).在您开始使用MarketingAPI推广前,您需要引导客户至少完成一次OAuth2.0认证操作,以获得调用接口必须的access_token。进行OAuth2.0认证需要完成以下步骤:

1、 引导客户进入OAuth2.0授权页面;

授权页面的地址为 https://open-ad.vivo.com.cn/OAuth?clientId={您的client_id}&state={开发者标识}&redirectUri={您的redirectUri}

注意: redirectUri(回调地址)域名需与申请应用时填写的redirectUri域名一致;state必填(内容不限)例如:授权的营销平台账户名称或账户ID。

2、 客户登录营销平台广告主账号或二代账号;

3、客户登录并确认授权后,vivo商业开放平台向您的应用程序返回一个授权码(Authorization Code),code的有效期是10分钟;

注意:广告主或二代点击同意授权后,vivo商业开放平台会请求redirectUri地址,并携带 state参数和 code参数。

4、应用程序调用接口用授权码(Authorization Code)获得 access_token,同时获得一个用于刷新 access_token 的 refresh_token;

5、在 access_token 过期后,使用 refresh_token 获得新的 access_token(可选);

3.2请求API的示例如下

OkHttpClient client = new OkHttpClient().newBuilder()
  .build();
MediaType mediaType = MediaType.parse("application/json");
RequestBody body = RequestBody.create(mediaType, "{"startDate":"20200601","endDate":"20200801","pageIndex":1,"pageSize":100,"summaryType":"SUMMARY","level":"CREATIVE"}");
Request request = new Request.Builder()
  .url("https://marketing-api.vivo.com.cn/openapi/v1/adstatement/summary/query?access_token=202c6e7b154ff55da04b20c7c316cf5f33e12642a3f1abee9c82ec4749d3fa73 tamp=1598359942000&nonce=1598359942&advertiser_id=6a556980efd4b57e6c8c")
  .method("POST", body)
  .addHeader("Content-Type", "application/json")
  .build();
Response response = client.newCall(request).execute();

3.3实现方案分析

该方案适用于客户端的对接实现,但是针对服务器的对接,个人觉得不合适。

缺点
1)首次获取token必须要人工介入,通过用户登录系统点击授权,才能获取到授权码,再用授权码去换取token,这种方式虽然安全,但是中途需要人工介入。

2)再次获取token,针对服务器自动对接无需人工介入的情况,我们需要持久化refresh_token,定时去刷新token防止token过期。所以总得来说,授权码模式不太适用了服务器API对接,不但实现起来麻烦,安全性也由于refresh_token的存在而大打折扣。

4.小米应用商店的OpenAPI对接方案

4.1Marketing API 请求参数说明

  • 1. 接口请求方式1.1 参数说明1.2 sign 计算方式1.3 请求示例

4.2接口请求方式

api.e.mi.com + <接口路径> + <接口入参> + signId + sign

4.3参数说明

  1. sign:根据规则生成的签名,参与生成sign的只有url中的参数,requestBody 中参数不参与生成签名。
  2. signId 和 secretKey_value 均可在邮件中获取 。

4.4sign 计算方式

sign = MD5(paramStr)

parmStr = sort_by_key 【 接口入参(key1value1key2value2) + ( signId_key + signId_value ) 】 + secretKey_value

sort_by_key 参数按照英文字母升序排序,signId 需要加入排序。

4.5请求示例

某客户A(账户ID:321)开通OpenAPI权限并绑定邮箱,获取密钥后系统发放的邮件内容为:

signId: 12345678

secretKey: 00000000

4.6GET请求

获取A客户2018年8月5号通过viewSum升序排列的计划列表请求应该为:

http://api.e.mi.com/campaign/list?customerId=321&edate=2018-08-05&orderby=viewSum&sdate=2018-08-05&signId=12345678&sortMode=1&sign=8054b81bdb0454a99846e04f34bf0de5

4.7url详细生成过程:

  1. 所需入参的kv分别为
  2. keyvaluecustomerId321sdate2018-08-05edate2018-08-05orderbyviewSumsortMode1signId12345678
  3. 将其按照key排序(默认按照英文字母升序),新的顺序为:
  4. keyvaluecustomerId321edate2018-08-05orderbyviewSumsdate2018-08-05signId12345678sortMode1
  5. 将所有的key value 拼接成字符串,并在最后加上secretKey_value,得到:
  6. customerId321edate2018-08-05orderbyviewSumsdate2018-08-05signId12345678sortMode100000000
  7. 对其进行32位小写md5加密得到sign:
  8. 8054b81bdb0454a99846e04f34bf0de5

4.8POST请求示例

POST接口参与生成sign的只有url中的参数,requestBody 中参数不参与生成签名.

编辑A客户的创意(创意ID:67812)修改 RTA Token的POST请求应该为:

http://api.e.mi.com/campaign/list?customerId=321&creativeId=67812&signId=12345678&sign=aeb8db9ea50a30f55b8f0fd1cb9f7d4b

4.9小米实现方案分析

小米的实现方案是通过申请到的signId 和 secretKey对请求参数进行MD5加密,然后在服务器验签,确保请求的合法,这也是比较多公司喜欢用的一种实现方案。

缺点

虽然在方案上无可挑剔,但是这个sign怎么计算出来的呢?缺少代码实现,这种通用的sign计算方法如果可以将实现代码贴出来给我们参考,那么可以大大节省我们每个对接公司的时间。


针对以上四大厂商的对接方案,你们更喜欢那个对接方案呢?认为那个对接方案更加优秀呢?欢迎发表一下个人见解。

页面更新:2024-03-12

标签:华为   升序   方案   示例   优雅   接口   商店   厂商   参数   类型   客户   方式   国内

1 2 3 4 5

上滑加载更多 ↓
Top