diff --git a/db/gfdb/gfdb.go b/db/gfdb/gfdb.go index 14ad5b8..e9782e5 100644 --- a/db/gfdb/gfdb.go +++ b/db/gfdb/gfdb.go @@ -439,6 +439,7 @@ var ( type Gfdb interface { GetAll(ctx context.Context, sql string, args ...any) (gdb.Result, error) + GetOne(ctx context.Context, sql string, args ...any) (gdb.Record, error) Exec(ctx context.Context, sql string, args ...any) (sql.Result, error) Model(ctx context.Context, tableNameOrStruct ...any) *model Transaction(ctx context.Context, f func(ctx context.Context, tx gdb.TX) error) error diff --git a/http/http.go b/http/http.go index a68f67f..6b92cf7 100644 --- a/http/http.go +++ b/http/http.go @@ -4,6 +4,7 @@ import ( "context" "errors" "fmt" + "io" "net/http" "reflect" "regexp" @@ -86,12 +87,9 @@ func RouteRegister(controllers []interface{}) { go Httpserver.Run() } -// doRequest 统一HTTP请求处理(DELETE用ContentJson发送body,gconv.Struct增加err检查) -func doRequest(ctx context.Context, method string, url string, headers map[string]string, target any, data ...any) (err error) { - err = utils.ValidStructPtr(target) - if err != nil { - return - } +// doRequestRaw 执行HTTP请求,返回gclient.Response,调用方需自行Close +// 统一处理:client克隆、ContentJson设置、请求头注入、GET查询参数转换 +func doRequestRaw(ctx context.Context, method string, url string, headers map[string]string, data ...any) (*gclient.Response, error) { client := Httpclient.Clone() // POST/PUT/DELETE请求都需要显式用ContentJson序列化body @@ -109,9 +107,9 @@ func doRequest(ctx context.Context, method string, url string, headers map[strin // 修复:避免data...展开导致的双重包装问题 // 当只有一个元素时,直接传递该元素,避免被包装成数组 var response *gclient.Response + var err error // 对于GET请求,将参数转换为map if method == http.MethodGet && len(data) > 0 && len(data)%2 == 0 { - // 构建query参数map queryParams := make(map[string]string) for i := 0; i < len(data); i += 2 { if key, ok := data[i].(string); ok && i+1 < len(data) { @@ -125,17 +123,33 @@ func doRequest(ctx context.Context, method string, url string, headers map[strin } else { response, err = client.DoRequest(ctx, method, url, data...) } + return response, err +} + +// doRequest 统一HTTP请求处理(同步/异步,解析内部API响应格式并填充target) +func doRequest(ctx context.Context, method string, url string, headers map[string]string, target any, respParse bool, data ...any) (res []byte, err error) { + if target != nil || respParse { + err = utils.ValidStructPtr(target) + if err != nil { + return + } + } + + response, err := doRequestRaw(ctx, method, url, headers, data...) if err != nil { return } defer response.Close() result := response.ReadAll() + if !respParse { + return result, nil + } // 统一处理内部API响应格式:{code:200,message:"",data:{...}} resultStrut := &ghttp.DefaultHandlerResponse{} if err = gconv.Struct(result, &resultStrut); err != nil { // 修复:增加err检查 - return errors.New("响应解析失败: " + err.Error()) + return nil, errors.New("响应解析失败: " + err.Error()) } // 添加调试日志:打印解析后的结构 @@ -144,7 +158,7 @@ func doRequest(ctx context.Context, method string, url string, headers map[strin if resultStrut.Code == 200 || resultStrut.Code == 0 { if err = gconv.Struct(resultStrut.Data, target); err != nil { // 修复:增加err检查 - return errors.New("数据解析失败: " + err.Error()) + return nil, errors.New("数据解析失败: " + err.Error()) } // 添加调试日志:打印最终的target g.Log().Debugf(ctx, "[HTTP] 最终target: %+v", target) @@ -153,19 +167,52 @@ func doRequest(ctx context.Context, method string, url string, headers map[strin } return } + func Get(ctx context.Context, url string, headers map[string]string, target any, data ...any) (err error) { - err = doRequest(ctx, http.MethodGet, url, headers, target, data...) + _, err = doRequest(ctx, http.MethodGet, url, headers, target, true, data...) return } func Post(ctx context.Context, url string, headers map[string]string, target any, data ...any) (err error) { - err = doRequest(ctx, http.MethodPost, url, headers, target, data...) + _, err = doRequest(ctx, http.MethodPost, url, headers, target, true, data...) return } func Put(ctx context.Context, url string, headers map[string]string, target any, data ...any) (err error) { - err = doRequest(ctx, http.MethodPut, url, headers, target, data...) + _, err = doRequest(ctx, http.MethodPut, url, headers, target, true, data...) return } func Delete(ctx context.Context, url string, headers map[string]string, target any, data ...any) (err error) { - err = doRequest(ctx, http.MethodDelete, url, headers, target, data...) + _, err = doRequest(ctx, http.MethodDelete, url, headers, target, true, data...) return } + +func GetNotParse(ctx context.Context, url string, headers map[string]string, data ...any) (res []byte, err error) { + res, err = doRequest(ctx, http.MethodGet, url, headers, nil, false, data...) + return +} +func PostNotParse(ctx context.Context, url string, headers map[string]string, data ...any) (res []byte, err error) { + res, err = doRequest(ctx, http.MethodPost, url, headers, nil, false, data...) + return +} + +// DoStream 流式HTTP请求,返回响应体io.ReadCloser,由调用方自行控制读取和关闭 +// 注意:返回的是原始响应流,不会解析内部API响应格式,调用方必须在使用后Close +func doStream(ctx context.Context, method string, url string, headers map[string]string, data ...any) (io.ReadCloser, error) { + response, err := doRequestRaw(ctx, method, url, headers, data...) + if err != nil { + return nil, err + } + + // 检查HTTP状态码,提前返回错误 + if response.StatusCode < 200 || response.StatusCode >= 300 { + bodyBytes, _ := io.ReadAll(response.Body) + response.Close() + return nil, fmt.Errorf("[HTTP][Stream] 状态码异常: %d, body=%s", response.StatusCode, string(bodyBytes)) + } + + return response.Body, nil +} + +// PostStream POST流式请求,返回响应体io.ReadCloser +func PostStream(ctx context.Context, url string, headers map[string]string, data ...any) (io.ReadCloser, error) { + return doStream(ctx, http.MethodPost, url, headers, data...) +}