package http_binding import ( "errors" "git.sxidc.com/go-tools/api_binding/http_binding/binding_context" "git.sxidc.com/go-tools/api_binding/http_binding/middleware" "git.sxidc.com/go-tools/api_binding/http_binding/request" "git.sxidc.com/go-tools/api_binding/http_binding/response" "git.sxidc.com/go-tools/utils/strutils" "git.sxidc.com/service-supports/fserr" "git.sxidc.com/service-supports/fslog" "github.com/gin-gonic/gin" "net/http" "reflect" "strings" ) type BusinessFunc[I any, O any] func(c *binding_context.Context, inputModel I) (O, error) type BindingFunc[O any] func(c *binding_context.Context, request any, sendFunc response.SendFunc[O]) bool type Binding struct { routerGroup *gin.RouterGroup } // NewBinding 创建版本对应的binding func NewBinding(apiVersion string, middlewares ...middleware.Func) *Binding { apiPrefix := urlPrefix + "/api" if strutils.IsStringNotEmpty(apiVersion) && apiVersion != "root" { apiPrefix += "/" + apiVersion } // 把binding.Middleware转换为gin的中间件函数 ginMiddlewares := make([]gin.HandlerFunc, 0) for _, m := range middlewares { innerM := m ginMiddlewares = append(ginMiddlewares, func(c *gin.Context) { innerM(&binding_context.Context{Context: c}) }) } // 返回创建的路由组 return &Binding{routerGroup: routerInstance.Group(apiPrefix, ginMiddlewares...)} } type HandleFunc func(c *binding_context.Context) func (binding *Binding) AddHandler(method string, relativePath string, handleFunctions ...HandleFunc) error { if strutils.IsStringEmpty(method) { return errors.New("没有传递方法名") } if strutils.IsStringEmpty(relativePath) { return errors.New("没有传递相对路径") } if handleFunctions == nil || len(handleFunctions) == 0 { return errors.New("没有传递处理函数") } routeInfos := routerInstance.Routes() for _, routeInfo := range routeInfos { if routeInfo.Method == method && strings.TrimPrefix(routeInfo.Path, binding.routerGroup.BasePath()+"/") == relativePath { fslog.Error("添加的路径已存在: " + routeInfo.Path) return nil } } // 给单个路由增加中间件 ginHandleFunctions := make([]gin.HandlerFunc, 0) for _, handleFunction := range handleFunctions { innerFunction := handleFunction ginHandleFunctions = append(ginHandleFunctions, func(c *gin.Context) { innerFunction(&binding_context.Context{Context: c}) }) } binding.routerGroup.Handle(method, relativePath, ginHandleFunctions...) return nil } func PostBind[I any, O any](b *Binding, item *SimpleBindItem[I, O], middlewares ...middleware.Func) { item.bind(b.routerGroup, http.MethodPost, middlewares...) } func DeleteBind[I any, O any](b *Binding, item *SimpleBindItem[I, O], middlewares ...middleware.Func) { item.bind(b.routerGroup, http.MethodDelete, middlewares...) } func PutBind[I any, O any](b *Binding, item *SimpleBindItem[I, O], middlewares ...middleware.Func) { item.bind(b.routerGroup, http.MethodPut, middlewares...) } func GetBind[I any, O any](b *Binding, item *SimpleBindItem[I, O], middlewares ...middleware.Func) { item.bind(b.routerGroup, http.MethodGet, middlewares...) } func Bind[I any, O any](b *Binding, item *BindItem[I, O], middlewares ...middleware.Func) { item.bind(b.routerGroup, middlewares...) } func Static(b *Binding, item *StaticBindItem) { item.bind(b.routerGroup) } func StaticFile(b *Binding, item *StaticFileBindItem) { item.bind(b.routerGroup) } // SimpleBindItem 路由条目 type SimpleBindItem[I any, O any] struct { Path string // 请求路径 ResponseFunc response.SendFunc[O] // 响应泛型函数 BusinessFunc BusinessFunc[I, O] // 业务泛型函数 OptionalBindingFunc BindingFunc[O] // 可选的绑定函数 } func (item *SimpleBindItem[I, O]) bind(routerGroup *gin.RouterGroup, method string, middlewares ...middleware.Func) { bindingItem := &BindItem[I, O]{ Method: method, SimpleBindItem: item, } bindingItem.bind(routerGroup, middlewares...) } // BindItem 路由条目结构 type BindItem[I any, O any] struct { Method string *SimpleBindItem[I, O] } func (item *BindItem[I, O]) bind(routerGroup *gin.RouterGroup, middlewares ...middleware.Func) { if strutils.IsStringEmpty(item.Path) { panic("需要指定路径") } if strutils.IsStringEmpty(item.Method) { panic("需要指定方法") } if item.ResponseFunc == nil { panic("需要指定响应函数") } var inputCheckModel I inputType := reflect.TypeOf(inputCheckModel) if inputType != nil { if inputType.Kind() == reflect.Pointer { panic("输入对象不能使用指针类型") } if inputType.Kind() != reflect.Struct { panic("输入对象必须是结构") } } // 给单个路由增加中间件 ginHandleFunctions := make([]gin.HandlerFunc, 0) for _, m := range middlewares { innerM := m ginHandleFunctions = append(ginHandleFunctions, func(c *gin.Context) { innerM(&binding_context.Context{Context: c}) }) } ginHandleFunctions = append(ginHandleFunctions, func(c *gin.Context) { bindingContext := &binding_context.Context{Context: c} var inputModel I // 请求的结构类型不为any if inputType != nil { // 将请求数据解析到inputModel中 if item.OptionalBindingFunc != nil { ok := item.OptionalBindingFunc(bindingContext, &inputModel, item.ResponseFunc) if !ok { return } } else { switch item.Method { case http.MethodPost: fallthrough case http.MethodPut: ok := request.BindingJson(bindingContext, &inputModel, item.ResponseFunc) if !ok { return } case http.MethodGet: fallthrough case http.MethodDelete: ok := request.BindingQuery(bindingContext, &inputModel, item.ResponseFunc) if !ok { return } } } } // 执行业务函数 if item.BusinessFunc != nil { statusCode := http.StatusOK outputModel, err := item.BusinessFunc(bindingContext, inputModel) if err != nil { statusCode = fserr.ParseCode(err).HttpCode } item.ResponseFunc(bindingContext, statusCode, outputModel, err) return } }) // 所有的函数加入到执行链中 routerGroup.Handle(item.Method, item.Path, ginHandleFunctions...) } // StaticBindItem 静态路由item type StaticBindItem struct { RelativePath string Root string WithBasePath bool } func (item *StaticBindItem) bind(routerGroup *gin.RouterGroup) { if item.WithBasePath { routerGroup.Static(strings.TrimPrefix(item.RelativePath, routerGroup.BasePath()), item.Root) } else { routerGroup.Static(item.RelativePath, item.Root) } } type StaticFileBindItem struct { RelativePath string FilePath string WithBasePath bool } func (item *StaticFileBindItem) bind(routerGroup *gin.RouterGroup) { if item.WithBasePath { routerGroup.StaticFile(strings.TrimPrefix(item.RelativePath, routerGroup.BasePath()), item.FilePath) } else { routerGroup.StaticFile(item.RelativePath, item.FilePath) } }