inti
This commit is contained in:
@@ -0,0 +1,106 @@
|
||||
package hfb
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"crypto/hmac"
|
||||
"crypto/sha256"
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
const uploadPath = "/api/open/listing-uploads"
|
||||
|
||||
type Client struct {
|
||||
Server string
|
||||
Secret string
|
||||
Timeout time.Duration
|
||||
HTTPClient *http.Client
|
||||
}
|
||||
|
||||
func (c Client) Upload(ctx context.Context, req ExternalUploadRequest) (string, error) {
|
||||
body, err := json.Marshal(req)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("生成上传 JSON 失败:%w", err)
|
||||
}
|
||||
endpoint, err := normalizeEndpoint(c.Server)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
httpReq, err := http.NewRequestWithContext(ctx, http.MethodPost, endpoint, bytes.NewReader(body))
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("创建上传请求失败:%w", err)
|
||||
}
|
||||
httpReq.Header.Set("Content-Type", "application/json")
|
||||
httpReq.Header.Set("User-Agent", "updata_hfb/1.0")
|
||||
if strings.TrimSpace(c.Secret) != "" {
|
||||
timestamp := strconvFormatUnix(time.Now())
|
||||
httpReq.Header.Set("X-HFB-Timestamp", timestamp)
|
||||
httpReq.Header.Set("X-HFB-Signature", signBody(c.Secret, timestamp, body))
|
||||
}
|
||||
|
||||
httpClient := c.HTTPClient
|
||||
if httpClient == nil {
|
||||
timeout := c.Timeout
|
||||
if timeout <= 0 {
|
||||
timeout = 15 * time.Second
|
||||
}
|
||||
httpClient = &http.Client{Timeout: timeout}
|
||||
}
|
||||
resp, err := httpClient.Do(httpReq)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("请求服务器失败:%w", err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
respBody, err := io.ReadAll(io.LimitReader(resp.Body, 1<<20))
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("读取服务器响应失败:%w", err)
|
||||
}
|
||||
text := strings.TrimSpace(string(respBody))
|
||||
if resp.StatusCode < 200 || resp.StatusCode >= 300 {
|
||||
if text == "" {
|
||||
text = resp.Status
|
||||
}
|
||||
return "", fmt.Errorf("服务器返回失败状态 %s:%s", resp.Status, text)
|
||||
}
|
||||
if text == "" {
|
||||
return resp.Status, nil
|
||||
}
|
||||
return text, nil
|
||||
}
|
||||
|
||||
func normalizeEndpoint(raw string) (string, error) {
|
||||
raw = strings.TrimSpace(raw)
|
||||
if raw == "" {
|
||||
return "", fmt.Errorf("服务器地址不能为空")
|
||||
}
|
||||
parsed, err := url.Parse(raw)
|
||||
if err != nil || parsed.Scheme == "" || parsed.Host == "" {
|
||||
return "", fmt.Errorf("服务器地址不正确:%s", raw)
|
||||
}
|
||||
if strings.Contains(parsed.Path, uploadPath) {
|
||||
return parsed.String(), nil
|
||||
}
|
||||
basePath := strings.TrimRight(parsed.Path, "/")
|
||||
parsed.Path = basePath + uploadPath
|
||||
return parsed.String(), nil
|
||||
}
|
||||
|
||||
func signBody(secret, timestamp string, body []byte) string {
|
||||
mac := hmac.New(sha256.New, []byte(strings.TrimSpace(secret)))
|
||||
mac.Write([]byte(timestamp))
|
||||
mac.Write([]byte("."))
|
||||
mac.Write(body)
|
||||
return hex.EncodeToString(mac.Sum(nil))
|
||||
}
|
||||
|
||||
func strconvFormatUnix(t time.Time) string {
|
||||
return fmt.Sprintf("%d", t.Unix())
|
||||
}
|
||||
Reference in New Issue
Block a user