数据引擎-快手平台数据抽取bug修复
This commit is contained in:
@@ -78,13 +78,7 @@ func (c *ApiClient) Close() {
|
||||
|
||||
// Request 通用请求方法(支持 GET/POST,支持参数在 query 或 body)
|
||||
func (c *ApiClient) Request(ctx context.Context, method, path string, params map[string]interface{}, paramsInQuery bool) (*ApiResult, error) {
|
||||
if paramsInQuery {
|
||||
return c.doRequest(ctx, method, path, params, true)
|
||||
}
|
||||
if method == "GET" {
|
||||
return c.doRequest(ctx, "GET", path, params, true)
|
||||
}
|
||||
return c.doRequest(ctx, method, path, params, false)
|
||||
return c.doRequest(ctx, method, path, params, paramsInQuery)
|
||||
}
|
||||
|
||||
func (c *ApiClient) doRequest(ctx context.Context, method, path string, body interface{}, paramsInQuery bool) (result *ApiResult, err error) {
|
||||
@@ -124,30 +118,76 @@ func (c *ApiClient) execute(ctx context.Context, method, path string, body inter
|
||||
start := time.Now()
|
||||
fullURL := c.config.GetApiUrl(path)
|
||||
|
||||
// 先注入认证参数
|
||||
// 先注入认证参数到 URL
|
||||
fullURL = c.applyAuthURL(fullURL)
|
||||
|
||||
// 将 URL 认证参数注入 body 并清除 URL(避免重复参数)
|
||||
var reqBody io.Reader
|
||||
var reqBodyBytes []byte
|
||||
if body != nil && !paramsInQuery {
|
||||
b, err := json.Marshal(body)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("JSON序列化请求体失败: %w", err)
|
||||
if paramsMap, ok := body.(map[string]interface{}); ok {
|
||||
// 从 URL 注入认证参数到 body
|
||||
if parsed, _ := url.Parse(fullURL); parsed != nil {
|
||||
q := parsed.Query()
|
||||
for k, vs := range q {
|
||||
if len(vs) > 0 {
|
||||
if _, exists := paramsMap[k]; !exists {
|
||||
paramsMap[k] = vs[0]
|
||||
}
|
||||
q.Del(k)
|
||||
}
|
||||
}
|
||||
parsed.RawQuery = q.Encode()
|
||||
fullURL = parsed.String()
|
||||
}
|
||||
// Form body
|
||||
formStr := c.buildFormBody(paramsMap)
|
||||
reqBodyBytes = []byte(formStr)
|
||||
reqBody = strings.NewReader(formStr)
|
||||
} else {
|
||||
b, err := json.Marshal(body)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("JSON序列化请求体失败: %w", err)
|
||||
}
|
||||
reqBodyBytes = b
|
||||
reqBody = bytes.NewBuffer(b)
|
||||
}
|
||||
reqBodyBytes = b
|
||||
reqBody = bytes.NewBuffer(b)
|
||||
}
|
||||
|
||||
// 如果参数在查询字符串中,拼接到 URL
|
||||
// GET query 模式
|
||||
if body != nil && paramsInQuery {
|
||||
if paramsMap, ok := body.(map[string]interface{}); ok {
|
||||
fullURL = c.buildQueryURL(fullURL, paramsMap)
|
||||
}
|
||||
}
|
||||
|
||||
// 计算签名并追加(如快手 API 的 MD5 签名)
|
||||
// 计算固定签名
|
||||
fullURL = c.applySignature(fullURL, body, paramsInQuery)
|
||||
logrus.Infof("请求 URL: %s", fullURL)
|
||||
|
||||
// 将 sign 注入 body 并从 URL 清除
|
||||
if !paramsInQuery && reqBodyBytes != nil {
|
||||
if parsed, _ := url.Parse(fullURL); parsed != nil {
|
||||
if signVal := parsed.Query().Get("sign"); signVal != "" {
|
||||
reqBodyBytes = append(reqBodyBytes, []byte("&sign="+signVal)...)
|
||||
reqBody = bytes.NewReader(reqBodyBytes)
|
||||
q := parsed.Query()
|
||||
q.Del("sign")
|
||||
parsed.RawQuery = q.Encode()
|
||||
fullURL = parsed.String()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 打印等效 curl
|
||||
curlCmd := fmt.Sprintf("curl -X %s '%s'", method, fullURL)
|
||||
if reqBodyBytes != nil && len(reqBodyBytes) > 0 {
|
||||
for _, pair := range strings.Split(string(reqBodyBytes), "&") {
|
||||
if pair != "" {
|
||||
curlCmd += fmt.Sprintf(" --data-urlencode '%s'", pair)
|
||||
}
|
||||
}
|
||||
}
|
||||
logrus.Infof("等效curl: %s", curlCmd)
|
||||
|
||||
req, err := http.NewRequestWithContext(ctx, method, fullURL, reqBody)
|
||||
if err != nil {
|
||||
@@ -157,7 +197,11 @@ func (c *ApiClient) execute(ctx context.Context, method, path string, body inter
|
||||
c.applyAuthHeader(req, reqBodyBytes)
|
||||
req.Header.Set("User-Agent", "data-engine/1.0")
|
||||
if body != nil && !paramsInQuery {
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
if _, ok := body.(map[string]interface{}); ok {
|
||||
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
|
||||
} else {
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
}
|
||||
}
|
||||
|
||||
resp, err := c.client.Do(req)
|
||||
@@ -224,6 +268,28 @@ func (c *ApiClient) buildQueryURL(rawURL string, params map[string]interface{})
|
||||
return parsed.String()
|
||||
}
|
||||
|
||||
// buildFormBody 将 params 编码为 application/x-www-form-urlencoded 字符串
|
||||
func (c *ApiClient) buildFormBody(params map[string]interface{}) string {
|
||||
q := make(url.Values)
|
||||
for k, v := range params {
|
||||
switch val := v.(type) {
|
||||
case string:
|
||||
q.Set(k, val)
|
||||
case float64:
|
||||
if val == float64(int64(val)) {
|
||||
q.Set(k, fmt.Sprintf("%d", int64(val)))
|
||||
} else {
|
||||
q.Set(k, fmt.Sprintf("%v", val))
|
||||
}
|
||||
case int, int8, int16, int32, int64:
|
||||
q.Set(k, fmt.Sprintf("%d", val))
|
||||
default:
|
||||
q.Set(k, fmt.Sprintf("%v", v))
|
||||
}
|
||||
}
|
||||
return q.Encode()
|
||||
}
|
||||
|
||||
func (c *ApiClient) applyAuthURL(rawURL string) string {
|
||||
cfg := c.config.AuthConfig
|
||||
token := c.config.AccessToken
|
||||
@@ -264,6 +330,10 @@ func (c *ApiClient) applyAuthURL(rawURL string) string {
|
||||
for k, v := range extraParams {
|
||||
q.Set(k, v)
|
||||
}
|
||||
// 注入 appkey
|
||||
if appKey, ok := cfg["app_key"].(string); ok && appKey != "" {
|
||||
q.Set("appkey", appKey)
|
||||
}
|
||||
parsed.RawQuery = q.Encode()
|
||||
return parsed.String()
|
||||
}
|
||||
@@ -379,7 +449,8 @@ func generateNonce() string {
|
||||
return fmt.Sprintf("%012d%04d", nanoPart, r.Int64())
|
||||
}
|
||||
|
||||
// applySignature 计算签名并追加到 URL(支持快手等平台的 MD5 签名)
|
||||
// applySignature 计算签名并追加到 URL
|
||||
// 快手签名: 字母序拼接 key=value&...&signSecret=<secret>, 取 MD5
|
||||
func (c *ApiClient) applySignature(rawURL string, body interface{}, paramsInQuery bool) string {
|
||||
cfg := c.config.AuthConfig
|
||||
if cfg == nil {
|
||||
@@ -390,11 +461,16 @@ func (c *ApiClient) applySignature(rawURL string, body interface{}, paramsInQuer
|
||||
if signAlgo == "" {
|
||||
return rawURL
|
||||
}
|
||||
appSecret, _ := cfg["app_secret"].(string)
|
||||
if appSecret == "" && c.config.AppSecret != "" {
|
||||
appSecret = c.config.AppSecret
|
||||
|
||||
// 获取 signSecret(签名专用密钥)
|
||||
signSecret, _ := cfg["sign_secret"].(string)
|
||||
if signSecret == "" {
|
||||
signSecret, _ = cfg["app_secret"].(string)
|
||||
}
|
||||
if appSecret == "" {
|
||||
if signSecret == "" && c.config.AppSecret != "" {
|
||||
signSecret = c.config.AppSecret
|
||||
}
|
||||
if signSecret == "" {
|
||||
return rawURL
|
||||
}
|
||||
|
||||
@@ -405,7 +481,16 @@ func (c *ApiClient) applySignature(rawURL string, body interface{}, paramsInQuer
|
||||
}
|
||||
q := parsed.Query()
|
||||
|
||||
// 收集所有参数并按 key 排序
|
||||
// POST: 合并 body 参数
|
||||
if !paramsInQuery {
|
||||
if bodyMap, ok := body.(map[string]interface{}); ok {
|
||||
for k, v := range bodyMap {
|
||||
q.Set(k, fmt.Sprintf("%v", v))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 收集参数(排除 sign),按 key 排序
|
||||
keys := make([]string, 0, len(q))
|
||||
for k := range q {
|
||||
if k == "sign" {
|
||||
@@ -415,12 +500,20 @@ func (c *ApiClient) applySignature(rawURL string, body interface{}, paramsInQuer
|
||||
}
|
||||
sort.Strings(keys)
|
||||
|
||||
// 拼接: key1=value1&key2=value2&...
|
||||
var signStr string
|
||||
for _, k := range keys {
|
||||
signStr += k + "=" + q.Get(k) + "&"
|
||||
for i, k := range keys {
|
||||
if i > 0 {
|
||||
signStr += "&"
|
||||
}
|
||||
signStr += k + "=" + q.Get(k)
|
||||
}
|
||||
signStr += "key=" + appSecret
|
||||
// 追加 signSecret
|
||||
signStr += "&signSecret=" + signSecret
|
||||
|
||||
logrus.Infof("签名原文: %s", signStr)
|
||||
|
||||
// 计算签名
|
||||
var sign string
|
||||
switch signAlgo {
|
||||
case "md5":
|
||||
@@ -432,6 +525,7 @@ func (c *ApiClient) applySignature(rawURL string, body interface{}, paramsInQuer
|
||||
default:
|
||||
return rawURL
|
||||
}
|
||||
logrus.Infof("签名值 sign=%s", sign)
|
||||
|
||||
q.Set("sign", sign)
|
||||
parsed.RawQuery = q.Encode()
|
||||
|
||||
Reference in New Issue
Block a user