# 准入
# 接入前必读
- 请确认授权接口权限,访问非授权接口可能造成账号封禁
- 请确认访问授权频次,超过频次将被封禁一段时间
- 请做好防重入工作,网络问题可能会多次回调,导致接入方收到多条相同的回调信息,如果不做防重入,可能会影响到用户
- 请验证推送数据,避免数据伪造或丢失
- 用户只能获取自己名下的司机信息
# 接入相关密钥申请
接入密钥和解密密钥在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
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
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
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
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
2
3
4
5
6
7
说明:
- 请求数据格式必须为json
- header中需包含接入方id和签名,Authorization: bearer {cid_id}|{sign},bearer表示请求token类型,cid_id为分配的接入方id,与签名sign用"|"连接
- 签名算法
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
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
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
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
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