package utils import ( "errors" "io" "net/http" "net/url" "strconv" "strings" "time" ) type curl struct { data map[string]string method string uri string baseUrl string header map[string]string cookie map[string]string contentType string } func Curl(baseUrl string) *curl { return &curl{baseUrl: baseUrl} } func (c *curl) SetCookie(cookie map[string]string) *curl { c.cookie = cookie return c } func (c *curl) SetHeader(header map[string]string) *curl { c.header = header return c } func (c *curl) SetData(data map[string]string) *curl { c.data = data return c } func (c *curl) SerUrl(url string) *curl { c.baseUrl = url return c } // SetType 类型,json还是form // Type = "json" or "form" , 默认form func (c *curl) SetType(Type string) *curl { c.contentType = Type return c } func (c *curl) Get(uri string) (string, error) { url := c.getGetUrl(uri) // 1. 临时构造带超时client,不改动原有curl成员 client := &http.Client{Timeout: 10 * time.Second} resp, err := client.Get(url) // 请求失败直接返回,resp是nil,不能执行Body.Close if err != nil { Logger.Error(url, err) return "", err } // 安全defer关闭,单独捕获关闭错误,不污染外层err defer func() { closeErr := resp.Body.Close() if closeErr != nil { Logger.Error(url, "resp body close err:", closeErr) } }() if resp.StatusCode != http.StatusOK { Logger.Error(url, resp.StatusCode) return "", errors.New("http status not 200, is " + strconv.Itoa(resp.StatusCode)) } // 不再忽略读取错误 body, readErr := io.ReadAll(resp.Body) if readErr != nil { Logger.Error(url, "read body err:", readErr) return "", readErr } return string(body), nil } func (c *curl) Run(uri, method string) (string, error) { client := &http.Client{} targetUrl := c.getUrl(uri) data := c.getData() if strings.ToUpper(method) == http.MethodGet { data = nil targetUrl = c.getGetUrl(uri) } req, err := http.NewRequest(http.MethodPost, targetUrl, strings.NewReader(data.Encode())) if err != nil { return "", err } Type := "application/x-www-form-urlencoded" if strings.ToLower(c.contentType) == "json" { Type = "application/json" } //设置请求头 req.Header.Set("content-type", Type) req.Header.Set("user-agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Safari/537.36") if len(c.header) > 0 { for k, v := range c.header { req.Header.Add(k, v) } } if len(c.cookie) > 0 { for k, v := range c.header { cookie := &http.Cookie{Name: k, Value: v} req.AddCookie(cookie) } } resp, err := client.Do(req) if resp == nil { // 👈 必须加这个判断! return "", err } defer func(Body io.ReadCloser) { err := Body.Close() if err != nil { Logger.Error(err) } }(resp.Body) if err != nil { return "", err } body, _ := io.ReadAll(resp.Body) return string(body), nil } func (c *curl) getData() url.Values { data := url.Values{} if len(c.data) > 0 { for k, v := range c.data { data.Add(k, v) } } return data } func (c *curl) getUrl(uri string) string { return c.baseUrl + uri } func (c *curl) getGetUrl(uri string) string { targetUrl := c.getUrl(uri) if len(c.data) > 0 { u, _ := url.ParseRequestURI(targetUrl) // URL param data := c.getData() u.RawQuery = data.Encode() // URL encode return u.String() } return targetUrl }