package services import ( "context" "fmt" "net/http" "net/url" "strings" "time" "github.com/wailsapp/wails/v3/pkg/services/log" "resty.dev/v3" ) // HttpClientService HTTP客户端服务 type HttpClientService struct { logger *log.LogService client *resty.Client } // HttpRequest HTTP请求结构 type HttpRequest struct { Method string `json:"method"` URL string `json:"url"` Headers map[string]string `json:"headers"` BodyType string `json:"bodyType,omitempty"` // json, formdata, urlencoded, text Body interface{} `json:"body,omitempty"` } // HttpResponse HTTP响应结构 type HttpResponse struct { Status string `json:"status"` // 使用resp.Status()返回完整状态如"200 OK" Time int64 `json:"time"` // 响应时间(毫秒) RequestSize string `json:"requestSize"` // 请求大小 Body interface{} `json:"body"` Headers http.Header `json:"headers"` Timestamp time.Time `json:"timestamp"` Error interface{} `json:"error,omitempty"` } // NewHttpClientService 创建新的HTTP客户端服务实例 func NewHttpClientService(logger *log.LogService) *HttpClientService { client := resty.New(). SetTimeout(30 * time.Second). SetRetryCount(0). EnableTrace(). SetHeaders(map[string]string{ "Access-Control-Allow-Origin": "*", "Access-Control-Allow-Methods": "*", "Access-Control-Allow-Headers": "*", "Access-Control-Allow-Credentials": "true", }) return &HttpClientService{ logger: logger, client: client, } } // ExecuteRequest 执行HTTP请求 func (hcs *HttpClientService) ExecuteRequest(ctx context.Context, request *HttpRequest) (*HttpResponse, error) { // 创建请求 req := hcs.client.R().SetContext(ctx) // 设置请求头 if request.Headers != nil { req.SetHeaders(request.Headers) } // 设置请求体 if err := hcs.setRequestBody(req, request); err != nil { return nil, fmt.Errorf("set request body failed: %w", err) } // 执行请求 resp, err := req.Execute(strings.ToUpper(request.Method), request.URL) // 构建响应对象 return hcs.buildResponse(resp, err) } // setRequestBody 设置请求体 func (hcs *HttpClientService) setRequestBody(req *resty.Request, request *HttpRequest) error { if request.Body == nil { return nil } switch strings.ToLower(request.BodyType) { case "json": req.SetHeader("Content-Type", "application/json") req.SetBody(request.Body) case "formdata": if formData, ok := request.Body.(map[string]interface{}); ok { for key, value := range formData { valueStr := fmt.Sprintf("%v", value) // 检查是否是文件类型,使用 @file 关键词 if strings.HasPrefix(valueStr, "@file ") { // 提取文件路径(去掉 @file 前缀) filePath := strings.TrimSpace(strings.TrimPrefix(valueStr, "@file ")) req.SetFile(key, filePath) } else { // 普通表单字段 req.SetFormData(map[string]string{key: valueStr}) } } } case "urlencoded": req.SetHeader("Content-Type", "application/x-www-form-urlencoded") if formData, ok := request.Body.(map[string]interface{}); ok { values := url.Values{} for key, value := range formData { values.Set(key, fmt.Sprintf("%v", value)) } req.SetBody(values.Encode()) } case "text": req.SetHeader("Content-Type", "text/plain") req.SetBody(fmt.Sprintf("%v", request.Body)) default: return fmt.Errorf("unsupported body type: %s", request.BodyType) } return nil } // buildResponse 构建响应对象 func (hcs *HttpClientService) buildResponse(resp *resty.Response, err error) (*HttpResponse, error) { httpResp := &HttpResponse{ Timestamp: time.Now(), } // 如果有错误,设置错误状态并返回 if err != nil { httpResp.Status = "Request Failed" httpResp.Error = err.Error() return httpResp, nil } // 设置基本响应信息 httpResp.Status = resp.Status() httpResp.Time = resp.Duration().Milliseconds() httpResp.RequestSize = hcs.formatBytes(resp.Size()) if errorData := resp.Error(); errorData != nil { httpResp.Error = errorData } // 设置响应头 httpResp.Headers = resp.Header() httpResp.Body = resp.String() return httpResp, nil } // formatBytes 格式化字节大小 func (hcs *HttpClientService) formatBytes(bytes int64) string { if bytes < 0 { return "0 B" } const unit = 1024 if bytes < unit { return fmt.Sprintf("%d B", bytes) } div, exp := int64(unit), 0 for n := bytes / unit; n >= unit; n /= unit { div *= unit exp++ } return fmt.Sprintf("%.1f %cB", float64(bytes)/float64(div), "KMGTPE"[exp]) }