package region_sync import ( "bytes" "encoding/json" "fmt" "io" "net/http" "strings" "time" "github.com/QuantumNous/new-api/common" "github.com/QuantumNous/new-api/logger" ) // SyncClient 调用远程节点的 HTTP 客户端 type SyncClient struct { endpoint string apiKey string httpClient *http.Client } func NewSyncClient(endpoint, apiKey string) *SyncClient { return &SyncClient{ endpoint: endpoint, apiKey: apiKey, httpClient: &http.Client{Timeout: 30 * time.Second}, } } func (c *SyncClient) doRequest(method, path string, body interface{}) ([]byte, error) { var reqBody io.Reader if body != nil { data, err := json.Marshal(body) if err != nil { return nil, fmt.Errorf("marshal request: %w", err) } reqBody = bytes.NewReader(data) } // 处理 URL 拼接,避免双斜杠问题 endpoint := strings.TrimRight(c.endpoint, "/") path = strings.TrimLeft(path, "/") url := endpoint + "/" + path common.SysLog(fmt.Sprintf("[RegionSync] HTTP %s %s", method, url)) logger.LogDebug(nil, "[RegionSync] HTTP %s %s", method, url) req, err := http.NewRequest(method, url, reqBody) if err != nil { return nil, fmt.Errorf("create request: %w", err) } req.Header.Set("Content-Type", "application/json") req.Header.Set("X-Sync-API-Key", c.apiKey) req.Header.Set("X-Sync-Node", c.endpoint) resp, err := c.httpClient.Do(req) if err != nil { return nil, fmt.Errorf("http request: %w", err) } defer resp.Body.Close() data, err := io.ReadAll(resp.Body) if err != nil { return nil, fmt.Errorf("read response: %w", err) } // 如果返回状态码不是 200,记录错误信息 if resp.StatusCode != http.StatusOK { common.SysError(fmt.Sprintf("[RegionSync] HTTP error %d from %s: %s", resp.StatusCode, url, string(data))) return nil, fmt.Errorf("http status %d: %s", resp.StatusCode, string(data)) } return data, nil } func (c *SyncClient) SyncUserCreate(req *SyncUserRequest) (*SyncUserResponse, error) { data, err := c.doRequest("POST", "/api/internal/sync/user/create", req) if err != nil { return nil, err } var resp SyncUserResponse if err := json.Unmarshal(data, &resp); err != nil { return nil, fmt.Errorf("unmarshal response: %w", err) } return &resp, nil } func (c *SyncClient) QueryQuota(userId int) (*QueryQuotaResponse, error) { data, err := c.doRequest("POST", "/api/internal/sync/quota/query", &QueryQuotaRequest{UserId: userId}) if err != nil { return nil, err } var resp QueryQuotaResponse if err := json.Unmarshal(data, &resp); err != nil { return nil, fmt.Errorf("unmarshal response: %w", err) } logger.LogDebug(nil, "[RegionSync] QueryQuota: userId=%d, quota=%d, success=%v", userId, resp.Quota, resp.Success) return &resp, nil } func (c *SyncClient) BatchDeduct(req *BatchDeductRequest) (*BatchDeductResponse, error) { logger.LogDebug(nil, "[RegionSync] BatchDeduct: sending %d records", len(req.Records)) data, err := c.doRequest("POST", "/api/internal/sync/quota/batch-deduct", req) if err != nil { return nil, err } var resp BatchDeductResponse if err := json.Unmarshal(data, &resp); err != nil { return nil, fmt.Errorf("unmarshal response: %w", err) } successCount := 0 for _, r := range resp.Results { if r.Success { successCount++ } } logger.LogDebug(nil, "[RegionSync] BatchDeduct: response success=%d, total=%d", successCount, len(resp.Results)) return &resp, nil } func (c *SyncClient) UpdateQuota(remoteUserId, quota int) (*UpdateQuotaResponse, error) { data, err := c.doRequest("POST", "/api/internal/sync/quota/update", &UpdateQuotaRequest{RemoteUserId: remoteUserId, Quota: quota}) if err != nil { return nil, err } var resp UpdateQuotaResponse if err := json.Unmarshal(data, &resp); err != nil { return nil, fmt.Errorf("unmarshal response: %w", err) } return &resp, nil } func (c *SyncClient) FetchConfig() (*SyncConfigResponse, error) { data, err := c.doRequest("GET", "/api/internal/sync/config", nil) if err != nil { return nil, err } var resp SyncConfigResponse if err := json.Unmarshal(data, &resp); err != nil { return nil, fmt.Errorf("unmarshal response: %w", err) } return &resp, nil } func (c *SyncClient) BatchQueryQuota(userIds []int) (*BatchQueryQuotaResponse, error) { data, err := c.doRequest("POST", "/api/internal/sync/quota/batch-query", &BatchQueryQuotaRequest{UserIds: userIds}) if err != nil { return nil, err } var resp BatchQueryQuotaResponse if err := json.Unmarshal(data, &resp); err != nil { return nil, fmt.Errorf("unmarshal response: %w", err) } return &resp, nil }