Bugly OpenAPI 接入使用说明
1. 说明
专业版域名:https://api.bugly.tds.qq.com
海外版域名:https://api-buglysgp.tds.tencent.com
1.1 接入流程
- 接入申请:接入方需要申请对应的接口方可使用,请与 Bugly 助手联系(提供产品 appid、appkey)
- 阅读本文档、参考示例代码
2. 版本发布
2.1 /v1/version/set_versions_release 设置版本发布
请求说明
| 参数名 | 参数类型 | 参数说明 | 是否必填 |
|---|---|---|---|
| product_id | string | 产品ID | Y |
| versions.release_date | number | 发布时间/灰度时间 | Y |
| versions.version | string | app版本 | Y |
| versions.version_type | number | 版本类型(1-开发版本,2-灰度版本,3-发布版本) | Y |
响应说明
| 参数名字 | 参数类型 | 参数说明 |
|---|---|---|
| baseRsp.code | int | 错误码,默认0 |
| baseRsp.msg | string | 错误信息 |
示例
curl -X POST 'https://api.bugly.tds.qq.com/v1/version/set_versions_release' \
-H 'Authorization: apiID={apiID}&hashedPayload={计算的hashedPayload}&nonce={nonce}&signMethod=HmacSHA256×tamp={timestamp}&version=202100&signature={计算的signature}' \
-H 'Content-Type: application/json' \
-H 'X-ProductId: {ProductId}' \
-H 'X-ProductKey: {ProductKey}' \
-d '{"product_id":"a278f01047","versions":[{"release_date":1760424983,"version":"1.2.3","version_type":1}]}'
3. 附录
Header 公共参数
| 字段 | 必填 | 描述 |
|---|---|---|
| X-Gateway-RequestID | 否 | 请求ID,用于定位问题,没有格式和内容的要求 • 如果客户端请求带有X-Gateway-RequestID字段,则使用客户端传来的id回复 • 如果客户端请求未指定X-Gateway-RequestID字段,则网关生成唯一的X-Gateway-RequestID回复 |
| X-ProductId | 是 | 产品注册的ID(即Bugly的APPID)产品页面的"设置/产品信息"可以查询到,后续优化可能不需要,先填着 |
| X-ProductKey | 是 | 产品注册到Bugly时,Bugly平台自动分配,用于鉴权(即Bugly的AppKey)产品页面的"设置/产品信息"可以查询到,后续优化可能不需要,先填着 |
| Authorization | 是 | 参考下面签名计算方法 |
3.1 签名计算方法
3.1.1 必要参数
| 参数名称 | 类型 | 是否必填 | 示例 | 备注 |
|---|---|---|---|---|
| apiID | string | 必填 | Bugly分配给调用方的id,一般是一个公司一个id | apiID,用于身份识别,这个信息申请接口时可以拿到 |
| apiKey | string | 必填 | - | apiKey,用于接口身份校验,需要保存在配置里不暴露给外部 |
| hashedPayload | string | 有body字段必填 | UodgxU3P77iThrEJtsiHi2kjYJmNA2jGEgYNnMD%2FX0s%3D | 包体签名 |
| nonce | int | 必填 | 随机正整数,最小值为100000 | 用于防重放攻击 |
| signature | string | 必填 | enonndonfewiodgxUEJtsiHi2kjYJmNA2jGEgYNnMD%2fX0s%3D | 请求签名 |
| signMethod | string | 必填 | 固定值:HmacSHA256 | 签名方式 |
| timestamp | int | 必填 | 当前时间戳(秒):1569490800 | 请求有效期为60秒 |
| version | string | 必填 | 固定值:202100 | 用于后续签名升级 |
3.1.2 签名的流程
1)包体签名的生成过程(hashedPayload)
(1)用 HmacSHA256 签名,生成16进制的包体签名串,示例如下:需要传入 request body 字段和 appKey 进行加密
sha := HmacSha256(body string, appKey string)
(2)签名串编码:Base64 编码
base64Str := base64.StdEncoding.EncodeToString([]byte(sha))
(3)url 编码后得到加密串
hashedPayload := url.QueryEscape(base64Str)
2)签名串的生成过程
(1)拼接需要签名的字符串
signStr := "apiID=f39d4525ad&hashedPayload=UodgxU3P77iThrEJtsiHi2kjYJmNA2jGEgYNnMD%2FX0s%3D&nonce=202100&signMethod=HmacSHA256×tamp=1569490800&version=202100"
(2)用 HmacSHA256 签名,生成16进制的包体签名串,示例如下:需要传入 request body 字段和 appKey 进行加密
sha := HmacSha256(signStr string, appKey string)
(3)签名串编码:Base64 编码
base64Str := base64.StdEncoding.EncodeToString([]byte(sha))
(4)url 编码后得到加密串
signature := url.QueryEscape(base64Str)
(5)拼接最终的签名串
- 拼接的签名串,必须是第(1)步拼接的签名串和请求签名进行拼接
- 需规定签名信息 signature 必须作为最后一个参数,拼接在最后面,以便截取,进行签名验证
- 所有请求参数的参数值均需要做 URL 编码,需要注意的是,部分语言库会自动对 URL 进行编码,重复编码会导致签名校验失败
authorization := signStr + "&signature=enonndonfewiodgxUEJtsiHi2kjYJmNA2jGEgYNnMD%2fX0s%3D"
最终生成的签名串示例:
apiID=198732198&hashedPayload=UodgxU3P77iThrEJtsiHi2kjYJmNA2jGEgYNnMD%2FX0s%3D&nonce=202100&signMethod=HmacSHA256×tamp=1569490800&version=202100&signature=enonndonfewiodgxUEJtsiHi2kjYJmNA2jGEgYNnMD%2fX0s%3D
3)将签名信息添加到 http 请求 header 的 "Authorization" 字段中
3.2 示例代码
package entity
import (
"bytes"
"crypto/hmac"
"crypto/sha256"
"encoding/base64"
"encoding/hex"
"errors"
"fmt"
"io/ioutil"
"net/http"
"net/url"
"time"
)
const signMethod = "HmacSHA256" // 签名的方法
const version = "202100" // 版本号
// computeHMacSHA256 获取加密数据
func computeHMacSHA256(message string, secret string) string {
h := hmac.New(sha256.New, []byte(secret))
h.Write([]byte(message))
// hamcSha256加密成16进制的字符串
sha := hex.EncodeToString(h.Sum(nil))
// fmt.Printf("sha: %s\n", sha)
// base64编码
b64 := base64.StdEncoding.EncodeToString([]byte(sha))
// fmt.Printf("base64Str: %s\n", base64Str)
// url编码
return url.QueryEscape(b64)
}
// generateAuthorization 获取签名认证
func generateAuthorization(apiID, apiKey, body string) string {
var signString string
nonce := time.Now().UnixNano() // 生成一个大于六位的随机数
timestamp := time.Now().Unix() // 生成时间戳
if len(body) > 0 {
hashedPayload := computeHMacSHA256(body, apiKey)
signString = fmt.Sprintf("apiID=%s&hashedPayload=%s&nonce=%d&signMethod=%s×tamp=%d&version=%s",
apiID, hashedPayload, nonce, signMethod, timestamp, version)
} else {
signString = fmt.Sprintf("apiID=%s&nonce=%d&signMethod=%s×tamp=%d&version=%s",
apiID, nonce, signMethod, timestamp, version)
}
signature := computeHMacSHA256(signString, apiKey)
return signString + "&signature=" + signature
}
// Request 发送http post请求
func Request(url string, body *bytes.Buffer) ([]byte, error) {
client := &http.Client{
CheckRedirect: func(req *http.Request, via []*http.Request) error {
return http.ErrUseLastResponse
},
}
req, err := http.NewRequest("POST", url, body) // 发送的body必须和生成签名的body数据一致,不然校验签名会不通过
if err != nil {
return nil, err
}
authorization := generateAuthorization(APIID, APIKey, body.String())
req.Header.Add("Authorization", authorization)
req.Header.Add("content-type", "application/json")
req.Header.Add("X-ProductId", ProductID)
req.Header.Add("X-ProductKey", ProductKey)
req.Header.Add("HOST", "bugly.woa.com")
res, err := client.Do(req)
if err != nil {
return nil, err
}
defer res.Body.Close()
resBody, err := ioutil.ReadAll(res.Body)
if err != nil {
return nil, err
} else if res.StatusCode != 200 {
return nil, errors.New("response error: " + string(resBody))
}
return resBody, nil
}