# 准入

# 接入前必读

  • 请确认授权接口权限,访问非授权接口可能造成账号封禁
  • 请确认访问授权频次,超过频次将被封禁一段时间
  • 请做好防重入工作,网络问题可能会多次回调,导致接入方收到多条相同的回调信息,如果不做防重入,可能会影响到用户
  • 请验证推送数据,避免数据伪造或丢失
  • 用户只能获取自己名下的司机信息

# 接入相关密钥申请

接入密钥和解密密钥在fleet网站的“个人中心”的“open api”模块申请,审核人员审核后会返回相关的密钥信息。

# 环境入口

生产环境:

https://open-jelly-ru.didiglobal.com/ 
1

仿真环境:

https://open-jelly-ru-sim.didiglobal.com
1

# 服务调用

生产环境:

// 请求“司机余额管理”
curl -X POST 
-H "Content-Type: application/json" 
-H "Authorization: bearer {cid}|{access_token}"
-d 'json {
    "fleet_id": 10000000000000,
    "driver_id": 50000000000000,
    "location_country": "RU",
    "lang": "ru-RU"
}' 
'https://open-jelly-ru.didiglobal.com/biz/fleet/open-api/drivers/getBalance'
1
2
3
4
5
6
7
8
9
10
11

仿真环境:

// 请求“司机余额管理”
curl -X POST 
-H "Content-Type: application/json" 
-H "Authorization: bearer {cid}|{access_token}"
-d 'json {
    "fleet_id": 10000000000000,
    "driver_id": 50000000000000,
    "location_country": "RU",
    "lang": "ru-RU"
}' 
'https://open-jelly-ru-sim.didiglobal.com/biz/fleet/open-api/drivers/getBalance'
1
2
3
4
5
6
7
8
9
10
11

注意

服务接口的调用需要 domain + "/biz" + api_url 不要忘记 "/biz"

# 安全认证

方法: POST

URL: /oauth/token

功能: Oauth2.0认证

请求参数:

参数名 类型 说明
grant_type string 申请模式. 现阶段只支持两种模式. i.客户端模式: client_credentials ii.刷新token: refresh_token
scope string 申请权限列表.
_ string 当前时间,格式为“2006-01-02T15:04:05-0700”,10分钟内有效。测试环境不检查
nostr string 随机字符串,6位,数字与字母组合,区分大小写
refresh_token string 申请token时下发的refresh_token字段(刷新时必须)

Scope 取值范围

Scope取值 说明
fleet fleet功能

请求token:

curl -X POST -H "Content-Type: application/json"
-H "Authorization: Bearer {cid}|{sign}"
-d '{"grant_type":"client_credentials","_":"2016-07-01T10:00:00+0800""nostr":"123abc"}'
'https://open-jelly-ru.didiglobal.com/oauth/token'
1
2
3
4

刷新token:

curl -X POST -H "Authorization: Bearer {cid}|{sign}" 
-H "Content-Type: application/json" 
-d '{"grant_type":"refresh_token","refresh_token":"43713d0303-49c60a08fe-835c9fc1fe","_":"2016-07-01T11:00:00+0800","nostr":"123abc"}' 
'https://open-jelly-ru.didiglobal.com/oauth/token'
1
2
3
4

正常返回:

参数名 类型 说明
access_token string 授权token,请求其他接口时必须由header提交。平台token可以多个同时有效
refresh_token string 刷新token,在access_token过期时使用,该token只一次有效,刷新之后会分配新的refresh_token,无有效期限制
expires_in_second int access_token有效时间,单位:秒
token_type string token类型,bearer/mac,目前只支持bear类型
scope string token权限等级,与申请时一致

注意

申请token有频次限制,平台 <= 10次/天,刷新 <= 10次/天,超过则封禁24小时

返回结果:

http状态

  • 200表示请求正确
  • 非200表示请求出错

HTTP/1.1 200 OK

Content-Type: application/json;charset=UTF-8

{
"access_token": "didiCB5DEFD8086CE8EAC49CFDCFFA3BF94E66BFC287113521F868B9257E8782D0DB978B81A04133BA5968D9B15B6A4655A9B3B8E7E856ED69BC1FF5848DC784C5080523D74F1AA6B2AC310219BAD3CDE24DF60E61A83B67565B0FC63DE417858FF4",
"refresh_token": "bd01a0d243-e4a056f463-138c7a00fd",
"expires_in_second": 259200,
"token_type": "bearer",
"scope": "calling_car"
}
1
2
3
4
5
6
7

说明:

  1. 请求数据格式必须为json
  2. header中需包含接入方id和签名,Authorization: bearer {cid_id}|{sign},bearer表示请求token类型,cid_id为分配的接入方id,与签名sign用"|"连接
  3. 签名算法

php版本

function generalCreateSign( $aData = array(), $sKey = '' )
{
    if ( empty( $aData ) || ! is_string( $sKey ) ) { //签名数据不能为空,签名key必须为字符串
        return FALSE;
    }

    ksort( $aData ); //签名数据按键值字典顺序排序
    $str = '';
    foreach ( $aData as $k => $v ) {
        if ( empty( $v ) && $v !== 0 ) { //空值键不参与签名
            continue;
        }

        if ( is_array( $v ) ) { 
            $v = json_encode( $v );
        }
        $str .= trim( $k ) . '=' . trim( $v ) . '&'; //键1=值1&键2=值2...
    }
    $str = trim( $str, '&' ); //去除字符串两端的&符号
    return md5( md5( $str ) . $sKey ); //2次md5,key为给接入方分配的签名key
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

golang版本

func generalCreateSign(req GetTokenReq, clientSecret string) string {
	reqData := map[string]string{}
	keys := []string{}

	t := reflect.TypeOf(req)
	v := reflect.ValueOf(req)
	for i := 0; i < t.NumField(); i++ {
		tag := t.Field(i).Tag.Get("json")
		value := v.Field(i).String() // req 所有字段都是 string 类型
		reqData[tag] = value
		keys = append(keys, tag)
	}

	// 按字典序排序
	sort.Strings(keys)

	sign := ""
	for _, k := range keys {
		if reqData[k] == "" {
			continue
		}

		// k1=v1&k2=v2...
		sign += strings.TrimSpace(k) + "=" + strings.TrimSpace(reqData[k]) + "&"
	}
	sign = strings.Trim(sign, "&")

	withSecret := fmt.Sprintf("%x", md5.Sum([]byte(sign))) + clientSecret
	return fmt.Sprintf("%x", md5.Sum([]byte(withSecret)))
}
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

c#版本

public static string GenerateMD5(string txt)
{
	byte[] buffer = Encoding.Default.GetBytes(txt);
	byte[] newBuffer = MD5.Create().ComputeHash(buffer);
	StringBuilder sb = new StringBuilder();
	for (int i = 0; i < newBuffer.Length; i++)
	{
		sb.Append(newBuffer[i].ToString("x2"));
	}
	return sb.ToString();
}

public static string GeneralCreateSign(IDictionary parameters, string secret)
{
	IDictionary sortedParams = new SortedDictionary(parameters);
	IEnumerator> iterator = sortedParams.GetEnumerator();

	StringBuilder strBuilder = new StringBuilder();
	while (iterator.MoveNext())
	{
		string key = iterator.Current.Key;
		string value = iterator.Current.Value;
		if (!string.IsNullOrEmpty(key) && !string.IsNullOrEmpty(value))
		{
			strBuilder.Append(key).Append("=").Append(value).Append("&");
		}
	}

	string finalString = strBuilder.ToString().Trim('&');

	string result = GenerateMD5(GenerateMD5(finalString) + secret);

	return result;
}
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

java版本

public static String Md5(String str) throws NoSuchAlgorithmException, UnsupportedEncodingException {
    byte s[] = MessageDigest.getInstance("md5").digest(str.getBytes("UTF8"));
    String Md5String = "";
    for (int i = 0; i < s.length; i++) {
        Md5String += Integer.toHexString((0x000000FF & s[i]) | 0xFFFFFF00).substring(6);
    }
    return Md5String;

}

public static Map sortMapByKey(Map map) {
    if (map == null || map.isEmpty()) {
        return null;
    }
    Map sortMap = new TreeMap(new Comparator() {
        public int compare(String obj1, String obj2) {
            return obj1.compareTo(obj2);//升序排序
        }
    });
    sortMap.putAll(map);
    return sortMap;
}

public static String CreateSign(Map params, String key) throws Exception {

    if (key.isEmpty() || params.isEmpty()){
        throw new Exception("sign key is empty or param is empty");
    }
    // 排序
    params = sortMapByKey(params);
    Iterator> it = params.entrySet().iterator();

    String sign = "";
    while (it.hasNext()){
        Map.Entry entry =  it.next();
        String k = entry.getKey();
        String v = entry.getValue();

        if (!v.isEmpty()){
            sign+=k.trim()+"="+v.trim()+"&";
        }
    }
    // 去掉最后的&
    sign = sign.substring(0,sign.length()-1);
    // md5
    return Md5(Md5(sign)+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
41
42
43
44
45
46
47