数据引擎-快手平台数据抽取bug修复

This commit is contained in:
2026-06-16 10:44:10 +08:00
parent e5133eea34
commit b4fc6f54af
22 changed files with 1324 additions and 487 deletions

View File

@@ -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()