API-v3 调用说明

一、概述

OpenAPI是优酷开放平台对外开放的后台接口的统称,基于标准http协议,可支持多平台。最新版本为OpenAPI v3.0,具有接入更快捷,更安全,访问速度更快等优点。

注:历史版本v2.0支持访问但不继续维护。

二、如何使用

优酷开放平台通过API为开放者提供部分服务能力,同样遵循“先申请开通后使用”的原则,具体的申请开通流程请参见《开发者入门》和具体产品服务的说明文档。不同于API v2.0, API v3.0 接口采用 REST 风格,只需将所需参数拼装成http请求,即可调用,支持 http协议请求的程序语言(如php、java、asp等等),从而均可调用Youku开放平台API。调用原理示意图如下:

三、REST接口调用

1. 接口描述
url https://openapi.youku.com/router/rest.json
功能描述 开发平台代理接口
返回格式 Json,UTF8
HTTP请求方式 POST
2. 接口参数
参数名 参数全称 必选 参数类型 参数描述
opensysparams open system params true String 系统参数,参考1.1.3 opensysparams={"action":"youkucloud.cloudvod.videoinfo.get_videoinfo_byid", "timestamp":1448433,"client_id":"test","version":"3.0"}
其它参数 对应业务接口的参数,参考业务接口
3. 系统参数
参数名 参数全称 必选 参数类型 参数描述
action action true String API接口名称,详见
client_id client_id true String 应用的client id
access_token access token false String oauth2的token
timestamp timestamp true String 客户端当前时间戳,精确到秒,timestamp与开放平台请求时间误差为6分钟
version version true String API协议版本,默认值3.0
sign_method sign_method false String 签名的摘要算法,可选值为:HmacSHA256,md5。默认为md5
sign sign true String 对API调用参数(除sign外)的md5加密值。详情见签名方法
4. 调用示例
 http://openapi.youku.com/router/rest.json?opensysparams=
{“action”:“youkucloud.cloudvod.videoinfo.get_videoinfo_byid”,
”timestamp”:1448433,”version”:“1.0”, “sign”:“e401f24f95b91ad8fbe2655fbc771d31”,
”client_id”:“test”}&ids=10001
  
5. 返回格式
{
e:{
   desc:"IP不可访问 [缺少ip]",
   provider: "openapiv3",
code: -113
},
data: "",
cost: 0.332000732421875
} 

业务接口的结果原样返回。

6.错误码
错误码 错误描述
-100 缺少必要参数,或者参数值格式不正确,请参考系统参数说明
-101 签名错误
-102 timestamp与开放平台请求时间误差为6分钟
-103 拒绝访问,帐号被封禁,或者不在接口针对的用户范围内等
-105 token验证失败
-106 Server配置错误
-107 app没有权限访问该分组
-108 接口不存在
-111 oauth2服务异常
-113 IP不可访问
-114 clientid无效
-115 clientid访问该组接口过于频繁
-117 应用级别不够

四、签名方法

1. 概述

调用API 时需要对请求参数进行签名并传给开放平台,开放平台服务器会通过签名验证该请求的参数是否合法。签名方法如下:

(1)根据参数名称(除签名和图片)将所有请求参数按照字母先后顺序排序(系统参数为json格式,需要展开,如果系统参数与业务参数的key冲突,则系统参数在前面): key + value …. key + value 例如:将foo=1,bar=2,baz=3 排序为bar=2,baz=3,foo=1

(2)参数名和参数值连接再进行 URLEncode后,得到拼装字符串bar2baz3foo1

(3)如果是md5签名方式,将secret 拼接到参数字符串尾进行32位的小写md5加密后,格式是:md5(key1value1key2value2…secret)

(4)如果是HmacSHA256签名方式,则用secret对得到的拼装字符串bar2baz3foo1做签名,得到32位小写签名字符串

2. 签名示例(JAVA)
  /**
     * @brief 签名
     * @author luhanlin
     * @date 2016-05-12
     *
     * @param params 需加密的参数,TreeMap保证参数按升序排序,
     *               非Java语言需要先按参数名进行排序,系统参数与业务参数相同的情况下,系统参数在前
     * @param appKey 代理层获取密钥
     * @param secret 加密密钥
     *
     * @return 返回请求openapi所需参数,
     *          1、GET请求,直接遍历Map,拼接k-v即可;
     *          2、POST请求,迭代Map,封装为NameValuePair即可
     */
    public static TreeMap get_sign(TreeMap params, String appKey, String secret)
            throws Exception {
        /**
         * 用于存放与业务参数名相同的系统参数
         */
        Map serviceDuplicatePairs = new TreeMap<>();
        if(params.get("client_id") != null) {
            serviceDuplicatePairs.put("client_id", appKey);
        } else {
            params.put("client_id", appKey);
        }
        if(params.get("timestamp") != null) {
            serviceDuplicatePairs.put("timestamp", System.currentTimeMillis() / 1000);
        } else {
            params.put("timestamp", System.currentTimeMillis() / 1000);
        }
        if(params.get("version") != null) {
            serviceDuplicatePairs.put("version",  "3.0");
        } else {
            params.put("version",  "3.0");
        }

        String signMethod = params.get("sign_method") == null ? null : params.get("sign_method").toString();
        if(signMethod == null || "".equals(signMethod)) {
            signMethod = SignMethodEnum.MD5.getValue();
            params.put("sign_method", signMethod);
        } else {
            serviceDuplicatePairs.put("sign_method", signMethod);
        }

        StringBuffer signString = new StringBuffer();
        try {
            /**
             * 生成签名字符串
             */
            for(Map.Entry entry : params.entrySet()) {
                /**
                 * 同名参数,系统参数置于业务参数前
                 */
                if(serviceDuplicatePairs.get(entry.getKey()) != null) {
                    signString.append(entry.getKey());
                    /**
                     * 对参数值进行URLEncode, 不同开发语言对特殊字符encode结果可能不同,
                     * 以Java URLEncoder结果为准
                     * Encode值可以参考http://tool.chinaz.com/tools/urlencode.aspx
                     */
                    signString.append(URLEncoder.encode(serviceDuplicatePairs.get(entry.getKey()).toString(),
                            "UTF-8"));
                }
                signString.append(entry.getKey());
                signString.append(URLEncoder.encode(entry.getValue().toString(), "UTF-8"));
            }
        } catch (UnsupportedEncodingException e) {
            throw new Exception(ErrorCode.U8_ENCODE_ERROR.getDesc());
        }
//        System.out.println(signString.toString());
        String sign = "";
        if(SignMethodEnum.isHmac(signMethod)){
            try {
                sign = hmacSign(secret, signMethod, signString.toString());
            } catch (NoSuchAlgorithmException e) {
                e.printStackTrace();
            } catch (UnsupportedEncodingException e) {
                e.printStackTrace();
            } catch (InvalidKeyException e) {
                e.printStackTrace();
            }
        } else {
            signString.append(secret);
            try {
                System.out.println(signString.toString());
                sign = md5Sign(signString.toString());
            } catch(Exception e) {
                params.put("error", ErrorCode.U8_ENCODE_ERROR.getCode());
                return params;
            }
        }
        return packageRequestParams(params, serviceDuplicatePairs, appKey, sign);
    }

    /**
     * 拼接请求参数,返回Map方便post请求封装请求参数
     * @param params 所有参数
     * @param dupiicateParams 与业务参数名相同的系统参数
     * @param appKey client_id
     * @param sign 加密字符串
     * @return
     */
    private static TreeMap packageRequestParams(TreeMap params,
                                                                Map dupiicateParams,
                                                                String appKey, String sign) {
        StringBuffer buffer = new StringBuffer();
        /**
         * 拼接系统参数
         */
        buffer.append("{");
        buffer.append("\"client_id\":");
        buffer.append("\"");
        buffer.append(appKey);
        buffer.append("\",");
        buffer.append("\"timestamp\":");
        buffer.append("\"");
        buffer.append(params.get("timestamp"));
        buffer.append("\",");
        buffer.append("\"version\":");
        buffer.append("\"3.0\",");
        buffer.append("\"sign_method\":");
        buffer.append("\"");
        buffer.append(params.get("sign_method"));
        buffer.append("\",");
        buffer.append("\"sign\":");
        buffer.append("\"");
        buffer.append(sign);
        buffer.append("\",");
        buffer.append("\"action\":");
        buffer.append("\"");
        buffer.append(params.get("action"));
        buffer.append("\"");

        String access_token = (String) params.get("access_token");
        if(access_token != null && !"".equals(access_token)) {
            buffer.append(",\"access_token\":");
            buffer.append("\"");
            buffer.append(access_token);
            buffer.append("\"");
            params.remove("access_token");
        }
        buffer.append("}");
        params.put("opensysparams", buffer.toString());

        /**
         * 将Map中的系统参数移出
         */
        if(dupiicateParams.get("client_id") == null) {
            params.remove("client_id");
        }
        if(dupiicateParams.get("timestamp") == null) {
            params.remove("timestamp");
        }
        if(dupiicateParams.get("version") == null) {
            params.remove("version");
        }
        if(dupiicateParams.get("sign_method") == null) {
            params.remove("sign_method");
        }
        if(dupiicateParams.get("action") == null) {
            params.remove("action");
        }

        return params;
    }

    private static String hmacSign(String secret, String signMethod, String signString)
            throws NoSuchAlgorithmException, UnsupportedEncodingException, InvalidKeyException {
        String algorithm = null;
        if(SignMethodEnum.HMAC.getValue().equals(signMethod)){
            algorithm = SignMethodEnum.HMACMD5.getValue();
        }else{
            algorithm = signMethod;
        }
        SecretKey secretKey = new SecretKeySpec(secret.getBytes("UTF-8"), algorithm);
        Mac mac = Mac.getInstance(secretKey.getAlgorithm());
        mac.init(secretKey);
        byte[] bytes = mac.doFinal(signString.getBytes("UTF-8"));
        System.out.println(signString.toString());
        return byte2hex(bytes);
    }

    private static String md5Sign(String signString)  {
        String sign = MD5.stringToMD5(signString);
        return sign;
    }

    public static String byte2hex(byte[] bytes) {
        StringBuilder sign = new StringBuilder();
        for (int i = 0; i < bytes.length; i++) {
            String hex = Integer.toHexString(bytes[i] & 0xFF);
            if (hex.length() == 1) {
                sign.append("0");
            }
            sign.append(hex.toLowerCase());
        }
        return sign.toString();
    }