package main import ( "bytes" "context" "encoding/json" "fmt" "io" "net/http" "strings" "time" ) const sessionCookieName = "ufile_session" type apiClient struct { host string session string http *http.Client } type apiError struct { Status int Code string Message string } func (e *apiError) Error() string { return e.Message } type userResponse struct { ID uint `json:"id"` Username string `json:"username"` CanManageUsers bool `json:"canManageUsers"` CanManageFiles bool `json:"canManageFiles"` CanUpload bool `json:"canUpload"` CanAdvancedUpload bool `json:"canAdvancedUpload"` MustChangePassword bool `json:"mustChangePassword"` CreatedAt time.Time `json:"createdAt"` UpdatedAt time.Time `json:"updatedAt"` } type fileResponse struct { ID uint `json:"id"` Hash string `json:"hash"` Name string `json:"name"` MimeType string `json:"mimeType"` Size int64 `json:"size"` ExpiresAt *time.Time `json:"expiresAt"` MaxDownloads *int `json:"maxDownloads"` DownloadCount int `json:"downloadCount"` } func newAPIClient(host, session string, timeout time.Duration) *apiClient { return &apiClient{host: host, session: session, http: &http.Client{Timeout: timeout}} } func (c *apiClient) newRequest(ctx context.Context, method, path string, body io.Reader) (*http.Request, error) { request, err := http.NewRequestWithContext(ctx, method, c.host+path, body) if err != nil { return nil, err } request.Header.Set("Accept", "application/json") request.Header.Set("User-Agent", "ufile-cli/"+version) if c.session != "" { request.AddCookie(&http.Cookie{Name: sessionCookieName, Value: c.session}) } return request, nil } func (c *apiClient) doJSON(ctx context.Context, method, path string, requestBody, responseBody any) (*http.Response, error) { var body io.Reader if requestBody != nil { data, err := json.Marshal(requestBody) if err != nil { return nil, err } body = bytes.NewReader(data) } request, err := c.newRequest(ctx, method, path, body) if err != nil { return nil, err } if requestBody != nil { request.Header.Set("Content-Type", "application/json") } response, err := c.http.Do(request) if err != nil { return nil, err } defer response.Body.Close() if response.StatusCode < 200 || response.StatusCode >= 300 { return response, decodeAPIError(response) } if responseBody == nil || response.StatusCode == http.StatusNoContent { _, _ = io.Copy(io.Discard, response.Body) return response, nil } if err := json.NewDecoder(io.LimitReader(response.Body, 2<<20)).Decode(responseBody); err != nil { return response, fmt.Errorf("服务端返回了无效的 JSON: %w", err) } return response, nil } func decodeAPIError(response *http.Response) error { payload := struct { Error string `json:"error"` Code string `json:"code"` }{} data, _ := io.ReadAll(io.LimitReader(response.Body, 1<<20)) _ = json.Unmarshal(data, &payload) message := strings.TrimSpace(payload.Error) if message == "" { message = strings.TrimSpace(string(data)) } if message == "" { message = response.Status } return &apiError{Status: response.StatusCode, Code: payload.Code, Message: message} }