Files
voidraft/internal/services/httpclient_service.go
2025-11-02 01:53:11 +08:00

174 lines
4.5 KiB
Go

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])
}