package laosiji import ( "context" "encoding/json" "errors" "fmt" "io" "net/http" "strings" "time" ) var ( // LaosijiDomains 老司机上传凭证缓存。由 web server 初始化。 LaosijiDomains *Domains // LaosijiClient 老司机业务 API 客户端。由 web server 初始化。 LaosijiClient *Client ) // Client 老司机「业务 API」客户端(区别于上传站 upload_url)。 type Client struct { baseURL string appid string appkey string hc *http.Client } // New 创建业务 API 客户端。baseURL 含 /lsjapi 前缀。 func New(baseURL, appid, appkey string) *Client { return &Client{ baseURL: strings.TrimRight(baseURL, "/"), appid: appid, appkey: appkey, hc: &http.Client{Timeout: 40 * time.Second}, } } // SetHTTPClient 替换底层 http.Client(测试/自定义超时用)。 func (c *Client) SetHTTPClient(hc *http.Client) { c.hc = hc } type apiResp struct { Status string `json:"status"` Data string `json:"data"` Error string `json:"error"` } // ErrEmptyData status=y 但 data 为空。 var ErrEmptyData = errors.New("laosiji: empty response data") // Call 发起一次业务 API 调用。 func (c *Client) Call(ctx context.Context, path string, req, out interface{}) error { reqJSON := []byte("{}") if req != nil { b, err := json.Marshal(req) if err != nil { return fmt.Errorf("laosiji: marshal req: %w", err) } reqJSON = b } encBody, err := EncryptBase64(reqJSON, c.appkey) if err != nil { return fmt.Errorf("laosiji: encrypt req: %w", err) } httpReq, err := http.NewRequestWithContext(ctx, http.MethodPost, c.baseURL+path, strings.NewReader(encBody)) if err != nil { return err } httpReq.Header.Set("appid", c.appid) httpReq.Header.Set("Content-Type", "application/json") resp, err := c.hc.Do(httpReq) if err != nil { return fmt.Errorf("laosiji: http do: %w", err) } defer func() { _ = resp.Body.Close() }() body, err := io.ReadAll(resp.Body) if err != nil { return fmt.Errorf("laosiji: read resp: %w", err) } if resp.StatusCode != http.StatusOK { return fmt.Errorf("laosiji: http %d: %s", resp.StatusCode, truncate(body, 200)) } var outer apiResp if err := json.Unmarshal(body, &outer); err != nil { return fmt.Errorf("laosiji: unmarshal outer: %w (body=%s)", err, truncate(body, 200)) } if outer.Status != "y" { return fmt.Errorf("laosiji: api status=%q error=%q", outer.Status, outer.Error) } if out == nil { return nil } if outer.Data == "" { return ErrEmptyData } plain, err := DecryptBase64(outer.Data, c.appkey) if err != nil { return fmt.Errorf("laosiji: decrypt data: %w", err) } if err := json.Unmarshal(plain, out); err != nil { return fmt.Errorf("laosiji: unmarshal data: %w (plain=%s)", err, truncate(plain, 200)) } return nil } func truncate(b []byte, n int) string { if len(b) > n { return string(b[:n]) + "..." } return string(b) }