title: 开发必读

开发须知

基础概念

ISV 身份

授权对象ISV即服务商、应用开发者。

APP 身份

授权对象APP即开放应用/配送应用,由服务商进行管理、开发的应用。

ApiKey/ApiSecret

ApiKey是服务商ISV调用API的唯一身份标识,ApiSecret是对应的API密钥。

AppId/AppSecret

AppId是开放应用/配送应用APP的唯一身份标识,AppSecret是对应的应用的密钥。

HTTP Headers

HTTP Header 描述
Authorization 用于验证请求合法性的认证信息。请严格按照签名格式进行填充,否则会返回401 HTTP状态码。
Content-Type 请求或响应内容的 MIME 类型。例如Content-Type: application/json;charset=UTF-8application/x-www-form-urlencoded
User-Agent 每次请求中此HTTP Header会标识自己,若没有此信息将会拒绝请求。
X-APPID ApiKey或者AppId
X-Source ISV或者APP
X-Expiration 请求时间,请使用Unix时间戳。
X-Host 服务器域名。例如X-Host: https://api.boolc.cn

响应格式

通过API请求,响应头部信息包括:

数据格式

HTTP状态码为2xx,接口状态码为2xxxx,API执行后将返回一个JSON格式的信息对象。具体格式如下:

{
    "code": <Code int>,
    "data": <Data interface{}>,
    "msg": "<Msg string>"
}
Key 描述
code 接口状态码,该状态码不同于HTTP状态码,是接口定义的状态码。
data 接口返回的数据,详见对应接口的响应数据。
msg 正确/错误返回信息提示。

接口状态码

状态码 描述
20000 接口请求成功。
40001 参数方面的异常状态,如参数格式错误、参数长度错误等。
40003 鉴权失败,若在网关鉴权错误HTTP状态码为2xx,若在服务内部鉴权错误HTTP状态码为401
40005 系统维护、更新等状态产生的问题。
40008 未经授权的系统,和40003区别在于系统鉴权问题,将会记录黑名单。
50000 系统错误,请将请求头、参数、签名等信息截图交由技术协调处理。
50001 未知错误,请将请求头、参数、签名等信息截图交由技术协调处理。
50008 Token非法、失效等情况返回的状态码。

签名

API通过验证签名来保证请求的真实性和数据的完整性。

请求签名

请使用ApiKey/ApiSecretAppId/AppSecret对API HOST、Body等关键数据的组合进行HmacSHA256签名。请求签名通过HTTP Header Authorization传递,具体说明请见下方说明。没有携带签名或者签名验证不通过的请求,都不会被执行,并返回401 Unauthorized。

1 签名字符串

X-APPIDX-ExpirationX-HostX-Source填充后,将四个HTTP Headers(Key通过ASCII排序)以及RequestMethod、RequestURI、Body用&进行拼接,即:

<HTTP Headers Key string>=<HTTP Headers Value string>&<RequestMethod string>&<RequestURI string>&<Body string>

例如:

X-APPID=GV5CD2hnRfRv47Ju&X-Expiration=1625481243&X-Host=https://api.boolc.cn&X-Source=ISV&POST&/open/app/app&{"channel":"BOOL"}

2 签名密钥

ApiSecretAppSecretX-Expiration拼接

<ApiSecret/AppSecret string><X-Expiration string>

3 加签

将上方两步生成的签名字符串、签名密钥,使用HmacSHA256算法计算签名,然后进行Base64 encode,得到最终的签名(需要使用UTF-8字符集),将计算的签名放入Headers的Authorization中。

代码示例

import (
    "crypto/hmac"
    "crypto/sha256"
    "encoding/base64"
    "encoding/hex"
    "encoding/json"
    "net/http"
    "sort"
    "strconv"
    "strings"
    "time"
)

func sign() error {
    expiration := strconv.Itoa(int(time.Now().Unix()))
    params := map[string]string{
        "channel": "BOOL",
    }
    body, err := json.Marshal(params)
    if err != nil {
        return err
    }

    conn, err := http.NewRequest("POST", "<BoolApiURL string>", strings.NewReader(string(body)))
    if err != nil {
        return err
    }
    client := &http.Client{}
    // 设置请求头
    conn.Header.Set("X-APPID", <ApiKey string>)
    conn.Header.Set("X-Expiration", expiration)
    conn.Header.Set("X-Host", <ApiHost string>)
    conn.Header.Set("X-Source", <Source string>)

    // 拼接签名密钥
    secret := "<ApiSecret string>" + expiration

    var keys []string
    for k := range conn.Header {
        keys = append(keys, k)
    }
    sort.Strings(keys)

    var pList = make([]string, 0, 0)
    for _, key := range keys {
        pList = append(pList, key+"="+conn.Header.Get(key))
    }
    pList = append(pList, conn.Method)
    if conn.URL.RawQuery != "" {
        pList = append(pList, conn.URL.Path+"?"+conn.URL.RawQuery)
    } else {
        pList = append(pList, conn.URL.Path)
    }
    pList = append(pList, string(body))
    // 生成签名字符串
    var content = strings.Join(pList, "&")

    // 加签并发送请求
    h := hmac.New(sha256.New, []byte(secret))
    h.Write([]byte(content))
    sha := hex.EncodeToString(h.Sum(nil))
    
    conn.Header.Set("Content-Type", "application/json")
    conn.Header.Set("Authorization", base64.StdEncoding.EncodeToString([]byte(sha)))

    resp, err := client.Do(conn)
    if err != nil {
        return err
    }
    defer resp.Body.Close()
    return nil
}
#python 3.8
import time
import hmac
import hashlib
import base64
import httplib
import urllib

expiration = str(time.time())
params = '{"channel": "BOOL"}'
headers = { "X-APPID": "ApiKey", "X-Expiration": expiration, "X-Host": "API HOST", "X-Source": "Source" }

secret = "ApiSecret"+expiration
secret_enc = secret.encode('utf-8')

string_to_sign = ""
for key in sorted(headers):
    string_to_sign += "{}={}&".format(key, headers[key])

string_to_sign += "POST"
string_to_sign += "&"
string_to_sign += "/"
string_to_sign += "&"
string_to_sign += params
string_to_sign_enc = string_to_sign.encode("utf-8")

# 加签并发送请求
hmac_code = hmac.new(secret_enc, string_to_sign_enc, digestmod=hashlib.sha256).digest()
sign = base64.b64encode(hmac_code)

headers["Content-Type"] = "application/json"
headers["Authorization"] = sign
conn = httplib.HTTPConnection("BOOL API URL")
conn.request("POST", "/", params, headers)

response = conn.getresponse()
data = response.read()
conn.close()

第三方业务授权签名

1 签名字符串

其中X-APPID=app_id,X-Expiration=expiration,X-Host=host,X-Source=APP,Body为空,将四个参数(Key通过ASCII排序)以及RequestMethod、RequestURI、Body用&进行拼接,即:

X-APPID=app_id&X-Expiration=expiration&X-Host=host&X-Source=APP&RequestMethod&RequestURI&Body

例如:

X-APPID=1&X-Expiration=1683957868&X-Host=https://api.99make.com&X-Source=APP&GET&/pages/open/auth?app_id=1&callback_scene=20&callback_url=https://api.boolcms.cn&expiration=1683957868&host=https://api.99make.com&source=APP&GET&/pages/open/auth?app_id=1&callback_scene=20&callback_url=https://api.boolcms.cn&expiration=1683957868&host=https://api.99make.com&source=APP&

2 签名密钥

ApiSecretAppSecretX-Expiration拼接

<ApiSecret/AppSecret string><X-Expiration string>

3 加签

将上方两步生成的签名字符串、签名密钥,使用HmacSHA256算法计算签名,然后进行Base64 encode,得到最终的签名,将计算的签名放入URl中。

https://h5.kkpaotui.com/#/pages/open/auth?app_id=1&callback_scene=20&callback_url=https://api.boolcms.cn&expiration=1683957868&host=https://api.99make.com&source=APP&sign=W0KDlhe%20OnY3do7gKYyPnPBVc2g88zR0mEDwG0ncP24%3D