# 点播资源选链

# 资源选链

HTTP Method:GET
请求地址:https://usk-uslink.hivoice.cn/unios-data-uslink/rest/v1/link/get_data_link

参数 类型 必填
描述
appKey String Y 应用KEY
udid String Y 设备唯一标识
deviceType String Y 设备类型(android ,ios,chip ,web)
dataType String Y 数据类型(child/music/audio)
dataSourceCode String Y 数据源(child/xmly/kuwo)
id String Y 资源ID
resourceType Integer Y 资源类型(1:视频 2:音频 )
timestamp Long Y 时间戳,有效性(10分钟内)
encryptMethod String Y 加密方式(MD5/AES/DES/SHA1/HMACSHA256),默认MD5
signature String Y 数据签名

请求参数中数据类型dataType和对应的数据源dataSourceCode列表

数据类型code 数据类型名称 数据源code 数据源名称
child 儿童资源数据类型 child 儿童资源统一数据源
music 音乐数据类型 kuwo 酷我音乐数据源
news 新闻数据类型 leting 乐听新闻数据源
audio 有声读物数据类型 qingtingFM 蜻蜓FM数据源
audio 有声读物数据类型 xmly 喜马拉雅数据源
channel 广播电台数据类型 qingtingFM 蜻蜓FM数据源

返回值

参数 类型 必填
描述
errorCode String Y 响应code,具体见错误码定义
errorMsg String Y 响应描述
costTime Integer Y 响应时间,单位:毫秒
result Object Y 返回的结果数据
└data String Y 儿童资源列表
└└id String Y 资源ID,唯一标识
└└url String Y 播放链接
└└totalContentPlayTime Integer Y 播放时长,单位秒
└└expiryTime Integer Y 音频地址过期时间,单位秒

返回结果示例:

{
    "errorCode": "0",
    "errorMsg": "请求成功",
    "result": {
        "data": {
            "id": "2000130210",
            "totalContentPlayTime": 161,
            "url": "http://dcs-resources-oss.hivoice.cn/audio/44fff002ec33f4c5ada5af4c237af6d4.mp3"
        }
    }
}
1
2
3
4
5
6
7
8
9
10
11

# 签名规则

选链服务支持以下几种签名方式:MD5(签名方式为空默认是MD5)、SHA1、AES、DES、HMACSHA256

# MD5、HMACSHA256、AES、DES签名生成规则:

A)将所有请求参数(除encryptMethod)放入Map中(MD5签名将appSecret也一并放入Map),注意:计算signature时所有参数不应进行URLEncode。
B)将格式化后的参数以字典序升序排列,拼接在一起,注意字典序中大写字母在前,空值不参与签名。
C)将B形成字符串获取摘要,即为本次请求signature(签名)的值。
参数排序示例

 private static final String SYMBOL_AND = "&";

/**
 * 格式化并排序请求参数
 * @param map
 * @return
 * @throws Exception
 */
private static String formatAndSortParams(Map<String, String> map) throws Exception {
    if (null == map || map.size() == 0) {
        return null;
    }
 
    ArrayList<String> list = new ArrayList<String>();
    for (Map.Entry<String, String> entry : map.entrySet()) {
        if (StringHandler.isNullOrEmpty(entry.getKey()) || StringHandler.isNullOrEmpty(entry.getValue())) {
            continue;
        }
        list.add(entry.getKey().trim() + "=" + UrlCoder.encode(entry.getValue().trim()) + "&");
    }
    int size = list.size();
    String[] arrayToSort = list.toArray(new String[size]);
    Arrays.sort(arrayToSort, String.CASE_INSENSITIVE_ORDER);
    StringBuilder sb = new StringBuilder();
    for (int i = 0; i < size; i++) {
        sb.append(arrayToSort[i]);
    }
    String params = sb.toString();
 
    if (params.endsWith(SYMBOL_AND)) {
        params = params.substring(0, params.length() - 1);
    }
    return params;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34

MD5签名示例

/**
 * 签名算法
 *
 * @param params
 *            要参与签名的数据对象
 * @return 签名
 * @throws IllegalAccessException
 */
public static String getSign(Map<String, String> params) throws Exception {
    if (null == params || params.isEmpty() ) {
        return null;
    }
    params.remove("signature");
    // 格式化并排序请求参数
    String result = formatAndSortParams(params);
    // Base64编码
    result = CoderUtil.encryptBASE64(result).replace("\r\n", "");
    // MD5运算
    result = new String(DigestUtils.md5Hex(result));
    return result;
}

public static void main(String[] args) {
    Map<String, String> mapParams = new LinkedHashMap<>();
    mapParams.put("appKey", "appKey");
    mapParams.put("appSecret", "appSecret");
    mapParams.put("deviceType", "android");
    mapParams.put("dataType", "child");
    mapParams.put("dataSourceCode", "child");
    mapParams.put("id", "1000208060");
    mapParams.put("resourceType", "1");
    mapParams.put("timestamp", String.valueOf(1569831595));
    mapParams.put("udid", "udid");
    
    String encrypt = getSign(mapParams);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36

HMACSHA256签名示例

public static String HMACSHA256(String data, String key) throws Exception {
    Mac sha256_HMAC = Mac.getInstance("HmacSHA256");
    SecretKeySpec secret_key = new SecretKeySpec(key.getBytes("UTF-8"), "HmacSHA256");
    sha256_HMAC.init(secret_key);
    byte[] array = sha256_HMAC.doFinal(data.getBytes("UTF-8"));
    StringBuilder sb = new StringBuilder();
    byte[] var6 = array;
    int var7 = array.length;
 
    for(int var8 = 0; var8 < var7; ++var8) {
        byte item = var6[var8];
        sb.append(Integer.toHexString(item & 255 | 256).substring(1, 3));
    }
    return sb.toString().toUpperCase();
}

public static void main(String[] args) {
    Map<String, String> mapParams = new LinkedHashMap<>();
    mapParams.put("appKey", "appKey");
    mapParams.put("deviceType", "android");
    mapParams.put("dataType", "child");
    mapParams.put("dataSourceCode", "child");
    mapParams.put("id", "2000130210");
    mapParams.put("resourceType", "2");
    mapParams.put("timestamp", String.valueOf(1569831488));
    mapParams.put("udid", "uni_uid");
    // key是appSecret值
    String key = "appSecret";
    // formatAndSortParams是参数排序方法
    String encrypt = HMACSHA256(formatAndSortParams(mapParams), key);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31

AES签名示例:

// sSrc:Map参数排序后的字符串;encodingFormat:UTF-8;sKey:appSecret的前16位;ivParameter:appSecret 从17位到最后
public static String cbc_encrypt(String sSrc, String encodingFormat, String sKey, String ivParameter) throws Exception {
   Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
   byte[] raw = sKey.getBytes();
   SecretKeySpec skeySpec = new SecretKeySpec(raw, "AES");
   IvParameterSpec iv = new IvParameterSpec(ivParameter.getBytes());// 使用CBC模式,需要一个向量iv,可增加加密算法的强度
   cipher.init(Cipher.ENCRYPT_MODE, skeySpec, iv);
   byte[] encrypted = cipher.doFinal(sSrc.getBytes(encodingFormat));
   String ret =  new BASE64Encoder().encode(encrypted);// 此处使用BASE64做转码。
   ret = ret.replaceAll("\r\n", "");
   ret = ret.replaceAll("\n", "");
   return ret;
}

public static void main(String[] args) {
    Map<String, String> mapParams = new LinkedHashMap<>();
    mapParams.put("appKey", "appKey");
    mapParams.put("deviceType", "android");
    mapParams.put("dataType", "child");
    mapParams.put("dataSourceCode", "child");
    mapParams.put("id", "2000130210");
    mapParams.put("resourceType", "2");
    mapParams.put("timestamp", String.valueOf(1569831488));
    mapParams.put("udid", "uni_uid");
    // formatAndSortParams是参数排序方法
    String enString = cbc_encrypt(formatAndSortParams(mapParams), "utf-8", appSecret.substring(0,16), appSecret.substring(16));
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27

DES签名示例:

/**
 * 3DESECB加密,key必须是长度大于等于 3*8 = 24 位
 *
 * @param src String
 * @param key   String
 * @return String
 */
public static String encryptThreeDESECB(String src, String key) throws Exception {
    if (src == null || key == null) {
        return null;
    }
    final DESedeKeySpec dks = new DESedeKeySpec(key.getBytes("UTF-8"));
    final SecretKeyFactory keyFactory = SecretKeyFactory.getInstance("DESede");
    final SecretKey securekey = keyFactory.generateSecret(dks);
 
    final Cipher cipher = Cipher.getInstance("DESede/ECB/PKCS5Padding");
    cipher.init(Cipher.ENCRYPT_MODE, securekey);
    final byte[] b = cipher.doFinal(src.getBytes("UTF-8"));
 
    final BASE64Encoder encoder = new BASE64Encoder();
    return encoder.encode(b).replaceAll("\r", "").replaceAll("\n", "");
 
}

public static void main(String[] args) {
    Map<String, String> mapParams = new LinkedHashMap<>();
    mapParams.put("appKey", "appKey");
    mapParams.put("deviceType", "android");
    mapParams.put("dataType", "child");
    mapParams.put("dataSourceCode", "child");
    mapParams.put("id", "2000130210");
    mapParams.put("resourceType", "2");
    mapParams.put("timestamp", String.valueOf(1569831488));
    mapParams.put("udid", "uni_uid");
    // key是appSecret
    String key = "appsecret";
    // formatAndSortParams是参数排序方法
    String enString = encryptThreeDESECB(formatAndSortParams(mapParams), key);
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40

# SHA1签名生成规则:

A)将所有请求参数(除encryptMethod)和appSecret的值放入List中,注意:计算signature时所有参数不应进行URLEncode。
B)将格式化后的参数以字典序升序排列,拼接在一起,注意字典序中大写字母在前,空值(null)使用空字符串代替。
C)将B形成字符串获取SHA1摘要,形成一个40位的十六进制(字母大写)字符串,即为本次请求signature(签名)的值。

/**
  * 对参数列表构造响应签名
  *
  * @param params
  * @return
  */
 public static String buildSignature(List<String> params) {
     if (params == null || params.isEmpty()) {
         return "";
     }
 
     // 升序排序参数值
     Collections.sort(params);
 
     StringBuilder sb = new StringBuilder();
     for (String param : params) {
         sb.append(param == null ? "" : param);
     }
 
     return getSHA1Digest(sb.toString());
 }
 
 /**
  * 将字符串进行SHA1获取摘要,摘要为十六进制字符串
  *
  * @param data
  * @return
  * @throws Exception
  */
 public static String getSHA1Digest(String data) {
     String digest = null;
     try {
         MessageDigest md = MessageDigest.getInstance("SHA-1");
         byte[] bytes = md.digest(data.getBytes("UTF-8"));
         digest = byte2hex(bytes);
     } catch (Exception e) {
         logger.error(e.getMessage(), e);
     }
 
     return digest;
 }
 
 /**
  * 二进制转十六进制字符串
  *
  * @param bytes
  * @return
  */
 private 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.toUpperCase());
     }
 
     return sign.toString();
 }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60

# 错误码定义及描述

errorCode errorMsg 描述
0 请求成功 请求成功
3020001 请求参数不合法 请求参数不合法
3020002 请求参数错误 业务校验参数错误
3020003 请求时间戳超出了请求有效期 时间戳与当前时间差超过10分钟
3020004 签名错误 签名校验错误
3020005 调用第三方内容服务结果为空 调用第三方内容服务结果为空
3024444 调用内容服务异常 调用第三方内容服务异常
3029999 系统异常 联系管理员