219 lines
7.5 KiB
Go
219 lines
7.5 KiB
Go
package http
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"fmt"
|
|
"io"
|
|
"net/http"
|
|
"reflect"
|
|
"regexp"
|
|
"strings"
|
|
|
|
_ "gitea.redpowerfuture.com/red-future/common/consul"
|
|
"gitea.redpowerfuture.com/red-future/common/jaeger"
|
|
"gitea.redpowerfuture.com/red-future/common/utils"
|
|
"github.com/gogf/gf/v2/frame/g"
|
|
"github.com/gogf/gf/v2/net/gclient"
|
|
"github.com/gogf/gf/v2/net/ghttp"
|
|
"github.com/gogf/gf/v2/net/gsvc"
|
|
"github.com/gogf/gf/v2/os/gtime"
|
|
"github.com/gogf/gf/v2/util/gconv"
|
|
)
|
|
|
|
var (
|
|
Httpserver = g.Server()
|
|
Httpclient = g.Client()
|
|
filterPaths = map[string]bool{"/": true, "/*": true, "/api.json": true}
|
|
legalRegisteredPaths = make(map[string]bool)
|
|
)
|
|
|
|
func init() {
|
|
err := gtime.SetTimeZone("Asia/Shanghai")
|
|
if err != nil {
|
|
panic("设置时区失败")
|
|
}
|
|
//s.Use(common.Cors) //中间件验证
|
|
//s.EnablePProf() //启用性能分析
|
|
//Httpserver.BindMiddlewareDefault(validateFilterPathMiddleware)
|
|
Httpserver.SetOpenApiPath("/api.json")
|
|
Httpserver.SetDumpRouterMap(true) //关闭打印路由注册信息
|
|
Httpserver.BindMiddlewareDefault(ghttp.MiddlewareHandlerResponse)
|
|
Httpclient.SetDiscovery(gsvc.GetRegistry())
|
|
}
|
|
|
|
func validateFilterPathMiddleware(r *ghttp.Request) {
|
|
path := r.URL.Path
|
|
if filterPaths[path] && !legalRegisteredPaths[path] {
|
|
routes := Httpserver.GetRoutes()
|
|
for _, route := range routes {
|
|
if filterPaths[route.Handler.Router.Uri] && route.Handler.Router.Uri != "/api.json" {
|
|
g.Log().Errorf(r.GetCtx(), "路径 %s 中间件 %s 必须通过 SkipMiddleware 注册", route.Handler.Router.Uri, route.Handler.Name)
|
|
}
|
|
}
|
|
r.ExitAll()
|
|
}
|
|
r.Middleware.Next()
|
|
}
|
|
|
|
func SkipMiddleware(h func(r *ghttp.Request), path string) (handler ghttp.HandlerFunc) {
|
|
if filterPaths[path] {
|
|
legalRegisteredPaths[path] = true
|
|
}
|
|
return func(r *ghttp.Request) {
|
|
if filterPaths[path] {
|
|
r.Middleware.Next()
|
|
return
|
|
}
|
|
h(r)
|
|
}
|
|
}
|
|
|
|
func RouteRegister(controllers []interface{}) {
|
|
Httpserver.Group("/log", func(group *ghttp.RouterGroup) {
|
|
group.Middleware(jaeger.NewTracer)
|
|
//group.Bind(controller.OperationLog)
|
|
})
|
|
re := regexp.MustCompile("[A-Z]")
|
|
for _, t := range controllers {
|
|
sName := reflect.ValueOf(t).Elem().Type().Name()
|
|
convertedStr := re.ReplaceAllStringFunc(sName, func(s string) string {
|
|
return fmt.Sprintf("/%s", strings.ToLower(s))
|
|
})
|
|
Httpserver.Group(convertedStr, func(group *ghttp.RouterGroup) {
|
|
group.Bind(t)
|
|
})
|
|
}
|
|
go Httpserver.Run()
|
|
}
|
|
|
|
// 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
|
|
if (method == http.MethodPost || method == http.MethodPut || method == http.MethodDelete) && len(data) > 0 {
|
|
client = client.ContentJson()
|
|
}
|
|
|
|
// 最后设置headers,确保不会被ContentJson覆盖
|
|
if len(headers) > 0 {
|
|
client.SetHeaderMap(headers)
|
|
} else if r := g.RequestFromCtx(ctx); r != nil {
|
|
client.SetHeader("Authorization", r.GetHeader("Authorization"))
|
|
}
|
|
|
|
// 修复:避免data...展开导致的双重包装问题
|
|
// 当只有一个元素时,直接传递该元素,避免被包装成数组
|
|
var response *gclient.Response
|
|
var err error
|
|
// 对于GET请求,将参数转换为map
|
|
if method == http.MethodGet && len(data) > 0 && len(data)%2 == 0 {
|
|
queryParams := make(map[string]string)
|
|
for i := 0; i < len(data); i += 2 {
|
|
if key, ok := data[i].(string); ok && i+1 < len(data) {
|
|
queryParams[key] = gconv.String(data[i+1])
|
|
}
|
|
}
|
|
g.Log().Infof(ctx, "[HTTP] GET请求构建的query参数: %+v", queryParams)
|
|
response, err = client.DoRequest(ctx, method, url, queryParams)
|
|
} else if len(data) == 1 {
|
|
response, err = client.DoRequest(ctx, method, url, data[0])
|
|
} 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 nil, errors.New("响应解析失败: " + err.Error())
|
|
}
|
|
|
|
// 添加调试日志:打印解析后的结构
|
|
g.Log().Debugf(ctx, "[HTTP] 解析后结构: Code=%d, Message=%s, Data类型=%T, Data值=%+v",
|
|
resultStrut.Code, resultStrut.Message, resultStrut.Data, resultStrut.Data)
|
|
|
|
if resultStrut.Code == 200 || resultStrut.Code == 0 {
|
|
if err = gconv.Struct(resultStrut.Data, target); err != nil { // 修复:增加err检查
|
|
return nil, errors.New("数据解析失败: " + err.Error())
|
|
}
|
|
// 添加调试日志:打印最终的target
|
|
g.Log().Debugf(ctx, "[HTTP] 最终target: %+v", target)
|
|
} else {
|
|
err = errors.New(resultStrut.Message)
|
|
}
|
|
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, 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, 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, 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, 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...)
|
|
}
|