Files
mayfly-go/server/pkg/httpclient/httpclient.go
2023-09-23 22:52:05 +08:00

246 lines
5.6 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package httpclient
import (
"bytes"
"encoding/json"
"errors"
"fmt"
"io"
"mayfly-go/pkg/logx"
"mayfly-go/pkg/utils/stringx"
"mime/multipart"
"net/http"
"os"
"time"
)
// 默认超时
const DefTimeout = 60
type RequestWrapper struct {
client http.Client
url string
method string
timeout int
body io.Reader
header map[string]string
}
type MultipartFile struct {
FieldName string // 字段名
FileName string // 文件名
FilePath string // 文件路径,文件路径不为空,则优先读取文件路径的内容
Bytes []byte // 文件内容
}
// 创建一个请求
func NewRequest(url string) *RequestWrapper {
return &RequestWrapper{url: url, client: http.Client{}}
}
func (r *RequestWrapper) Url(url string) *RequestWrapper {
r.url = url
return r
}
func (r *RequestWrapper) Header(name, value string) *RequestWrapper {
if r.header == nil {
r.header = make(map[string]string)
}
r.header[name] = value
return r
}
func (r *RequestWrapper) Timeout(timeout int) *RequestWrapper {
r.timeout = timeout
return r
}
func (r *RequestWrapper) GetByQuery(queryMap map[string]any) *ResponseWrapper {
var params string
for k, v := range queryMap {
if params != "" {
params += "&"
}
params += k + "=" + stringx.AnyToStr(v)
}
r.url += "?" + params
return r.Get()
}
func (r *RequestWrapper) Get() *ResponseWrapper {
r.method = "GET"
r.body = nil
return sendRequest(r)
}
func (r *RequestWrapper) PostJson(body string) *ResponseWrapper {
buf := bytes.NewBufferString(body)
r.method = "POST"
r.body = buf
if r.header == nil {
r.header = make(map[string]string)
}
r.header["Content-type"] = "application/json"
return sendRequest(r)
}
func (r *RequestWrapper) PostObj(body any) *ResponseWrapper {
marshal, err := json.Marshal(body)
if err != nil {
return &ResponseWrapper{err: errors.New("解析json obj错误")}
}
return r.PostJson(string(marshal))
}
func (r *RequestWrapper) PostForm(params string) *ResponseWrapper {
buf := bytes.NewBufferString(params)
r.method = "POST"
r.body = buf
if r.header == nil {
r.header = make(map[string]string)
}
r.header["Content-type"] = "application/x-www-form-urlencoded"
return sendRequest(r)
}
func (r *RequestWrapper) PostMulipart(files []MultipartFile, reqParams map[string]string) *ResponseWrapper {
buf := &bytes.Buffer{}
// 文件写入 buf
writer := multipart.NewWriter(buf)
for _, uploadFile := range files {
var reader io.Reader
// 如果文件路径不为空则读取该路径文件否则使用bytes
if uploadFile.FilePath != "" {
file, err := os.Open(uploadFile.FilePath)
if err != nil {
return &ResponseWrapper{err: err}
}
defer file.Close()
reader = file
} else {
reader = bytes.NewBuffer(uploadFile.Bytes)
}
part, err := writer.CreateFormFile(uploadFile.FieldName, uploadFile.FileName)
if err != nil {
return &ResponseWrapper{err: err}
}
io.Copy(part, reader)
}
// 如果有其他参数则写入body
for k, v := range reqParams {
if err := writer.WriteField(k, v); err != nil {
return &ResponseWrapper{err: err}
}
}
if err := writer.Close(); err != nil {
return &ResponseWrapper{err: err}
}
r.method = "POST"
r.body = buf
if r.header == nil {
r.header = make(map[string]string)
}
r.header["Content-type"] = writer.FormDataContentType()
return sendRequest(r)
}
func sendRequest(rw *RequestWrapper) *ResponseWrapper {
respWrapper := &ResponseWrapper{}
timeout := rw.timeout
if timeout > 0 {
rw.client.Timeout = time.Duration(timeout) * time.Second
} else {
timeout = DefTimeout
}
req, err := http.NewRequest(rw.method, rw.url, rw.body)
if err != nil {
respWrapper.err = fmt.Errorf("创建请求错误-%s", err.Error())
return respWrapper
}
setRequestHeader(req, rw.header)
resp, err := rw.client.Do(req)
return &ResponseWrapper{resp: resp, err: err}
}
func setRequestHeader(req *http.Request, header map[string]string) {
req.Header.Set("User-Agent", "golang/mayfly")
for k, v := range header {
req.Header.Set(k, v)
}
}
type ResponseWrapper struct {
resp *http.Response
err error
}
// 将响应体通过json解析转为指定结构体
func (r *ResponseWrapper) BodyToObj(objPtr any) error {
bodyBytes, err := r.BodyBytes()
if err != nil {
return err
}
err = json.Unmarshal(bodyBytes, &objPtr)
if err != nil {
return fmt.Errorf("解析响应体-json解析失败-%s", err.Error())
}
return nil
}
// 将响应体转为strings
func (r *ResponseWrapper) BodyToString() (string, error) {
bodyBytes, err := r.BodyBytes()
if err != nil {
return "", err
}
return string(bodyBytes), nil
}
// 将响应体通过json解析转为map
func (r *ResponseWrapper) BodyToMap() (map[string]any, error) {
var res map[string]any
return res, r.BodyToObj(&res)
}
// 获取响应体的字节数组
func (r *ResponseWrapper) BodyBytes() ([]byte, error) {
resp, err := r.GetHttpResp()
if err != nil {
return nil, err
}
body, err := io.ReadAll(resp.Body)
defer resp.Body.Close()
if err != nil {
return nil, fmt.Errorf("读取响应体数据失败-%s", err.Error())
}
return body, err
}
// 获取http响应结果结构体
func (r *ResponseWrapper) GetHttpResp() (*http.Response, error) {
if r.err != nil {
return nil, fmt.Errorf("请求失败-%s", r.err.Error())
}
if r.resp == nil {
return nil, errors.New("请求失败-响应结构体为空,请检查请求url等信息")
}
statusCode := r.resp.StatusCode
if isFailureStatusCode(statusCode) {
logx.Warnf("请求响应状态码为为失败状态: %v", statusCode)
}
return r.resp, nil
}
func isFailureStatusCode(statusCode int) bool {
return statusCode < http.StatusOK || statusCode >= http.StatusBadRequest
}