package upstreamimpl import ( "context" "crypto/md5" "encoding/hex" "encoding/json" "fmt" "io" "net/http" "strings" "time" sharedtoken "git.5iot.com/iotlcm-public/iotlcm-upstream-chan/internal/token" ) type QianxunTokenConfig struct { BaseURL string Path string OperatorID string AppID string TokenType string Key string Timeout time.Duration } type QianxunTokenProvider struct { baseURL, path, operatorID, appID, tokenType, key string client *http.Client } type qianxunTokenResponse struct { Code int `json:"code"` Msg string `json:"msg"` Data struct { Token string `json:"token"` ExpireDate int64 `json:"expireDate"` } `json:"data"` } func NewQianxunTokenProvider(config QianxunTokenConfig) *QianxunTokenProvider { timeout := config.Timeout if timeout <= 0 { timeout = defaultUpstreamHTTPTimeout } return &QianxunTokenProvider{ baseURL: strings.TrimRight(strings.TrimSpace(config.BaseURL), "/"), path: normalizeProviderPath(config.Path, "/"), operatorID: strings.TrimSpace(config.OperatorID), appID: strings.TrimSpace(config.AppID), tokenType: strings.TrimSpace(config.TokenType), key: config.Key, client: &http.Client{Timeout: timeout}, } } func qianxunSharedSign(operatorID, appID, key string) string { sum := md5.Sum([]byte(operatorID + appID + key)) return hex.EncodeToString(sum[:]) } func (p *QianxunTokenProvider) Fetch(ctx context.Context) (sharedtoken.OneLinkToken, error) { if p == nil || p.client == nil { return sharedtoken.OneLinkToken{}, fmt.Errorf("qianxun token provider is not initialized") } if p.baseURL == "" || p.operatorID == "" || p.appID == "" || p.tokenType == "" || p.key == "" { return sharedtoken.OneLinkToken{}, fmt.Errorf("qianxun token endpoint and credentials are required") } payload := map[string]string{"operatorId": p.operatorID, "appId": p.appID, "tokenType": p.tokenType, "sign": qianxunSharedSign(p.operatorID, p.appID, p.key)} body, err := json.Marshal(payload) if err != nil { return sharedtoken.OneLinkToken{}, fmt.Errorf("encode qianxun token request: %w", err) } req, err := http.NewRequestWithContext(ctx, http.MethodPost, joinProviderURL(p.baseURL, p.path), strings.NewReader(string(body))) if err != nil { return sharedtoken.OneLinkToken{}, fmt.Errorf("create qianxun token request: %w", err) } req.Header.Set("Content-Type", "application/json") req.Header.Set("Accept", "application/json") resp, err := p.client.Do(req) if err != nil { return sharedtoken.OneLinkToken{}, fmt.Errorf("request qianxun token: %w", err) } defer resp.Body.Close() responseBody, err := io.ReadAll(resp.Body) if err != nil { return sharedtoken.OneLinkToken{}, fmt.Errorf("read qianxun token response: %w", err) } if resp.StatusCode < 200 || resp.StatusCode >= 300 { return sharedtoken.OneLinkToken{}, fmt.Errorf("qianxun token HTTP status %d", resp.StatusCode) } var output qianxunTokenResponse if err := json.Unmarshal(responseBody, &output); err != nil { return sharedtoken.OneLinkToken{}, fmt.Errorf("decode qianxun token response: %w", err) } if output.Code != 0 { return sharedtoken.OneLinkToken{}, fmt.Errorf("qianxun token failed: code=%d msg=%s", output.Code, output.Msg) } value := strings.TrimSpace(output.Data.Token) if value == "" { return sharedtoken.OneLinkToken{}, fmt.Errorf("qianxun token is empty") } now := time.Now() var expiresAt time.Time if output.Data.ExpireDate > 0 { expiresAt = now.Add(time.Duration(output.Data.ExpireDate) * time.Second) } return sharedtoken.OneLinkToken{Value: value, CreatedAt: now, ExpiresAt: expiresAt}, nil } func (p *QianxunTokenProvider) FetchToken(ctx context.Context, _ string) (sharedtoken.OneLinkToken, error) { return p.Fetch(ctx) } type qianxunFetcher struct{ provider *QianxunTokenProvider } func (f qianxunFetcher) FetchToken(ctx context.Context, _ string) (sharedtoken.OneLinkToken, error) { return f.provider.Fetch(ctx) }