Prechádzať zdrojové kódy

完成binding开发

yjp 1 rok pred
rodič
commit
f758676b87

+ 27 - 0
api/router.go

@@ -12,6 +12,9 @@ type Router interface {
 	RegisterVersionedRouter(version string, middlewares ...Handler) Router
 	GetVersionedRouter(version string) Router
 	AddRoute(relativePath string, method string, middlewares ...Handler) Router
+	Static(relativePath string, root string)
+	StaticFile(relativePath string, filepath string)
+	BasePath() string
 }
 
 type RootRouter struct {
@@ -38,6 +41,18 @@ func (r *RootRouter) RegisterVersionedRouter(version string, middlewares ...Hand
 	return r.versioned[version]
 }
 
+func (r *RootRouter) Static(relativePath string, root string) {
+	r.engine.Static(relativePath, root)
+}
+
+func (r *RootRouter) StaticFile(relativePath string, filepath string) {
+	r.engine.StaticFile(relativePath, filepath)
+}
+
+func (r *RootRouter) BasePath() string {
+	return r.engine.BasePath()
+}
+
 func (r *RootRouter) GetVersionedRouter(version string) Router {
 	return r.versioned[version]
 }
@@ -88,6 +103,18 @@ func (r *PrefixRouter) AddRoute(method string, relativePath string, middlewares
 	return r
 }
 
+func (r *PrefixRouter) Static(relativePath string, root string) {
+	r.groupRouter.Static(relativePath, root)
+}
+
+func (r *PrefixRouter) StaticFile(relativePath string, filepath string) {
+	r.groupRouter.StaticFile(relativePath, filepath)
+}
+
+func (r *PrefixRouter) BasePath() string {
+	return r.groupRouter.BasePath()
+}
+
 func transferHandlers(handlers ...Handler) []gin.HandlerFunc {
 	ginHandlers := make([]gin.HandlerFunc, 0)
 	for _, handler := range handlers {

+ 214 - 0
binding/binding.go

@@ -0,0 +1,214 @@
+package binding
+
+import (
+	"git.sxidc.com/go-framework/baize/api"
+	"git.sxidc.com/go-framework/baize/domain"
+	"git.sxidc.com/go-tools/utils/strutils"
+	"git.sxidc.com/service-supports/fserr"
+	"net/http"
+	"strings"
+)
+
+func PostBind[O any](router api.Router, item *SimpleBindItem[O], middlewares ...api.Handler) {
+	item.bind(router, http.MethodPost, middlewares...)
+}
+
+func DeleteBind[O any](router api.Router, item *SimpleBindItem[O], middlewares ...api.Handler) {
+	item.bind(router, http.MethodDelete, middlewares...)
+}
+
+func PutBind[O any](router api.Router, item *SimpleBindItem[O], middlewares ...api.Handler) {
+	item.bind(router, http.MethodPut, middlewares...)
+}
+
+func GetBind[O any](router api.Router, item *SimpleBindItem[O], middlewares ...api.Handler) {
+	item.bind(router, http.MethodGet, middlewares...)
+}
+
+func Bind[O any](router api.Router, item *BindItem[O], middlewares ...api.Handler) {
+	item.bind(router, middlewares...)
+}
+
+func Static(router api.Router, item *StaticBindItem) {
+	item.bind(router)
+}
+
+func StaticFile(router api.Router, item *StaticFileBindItem) {
+	item.bind(router)
+}
+
+type DTOBindFunc func(c *api.Context, dto DTO) error
+type FormDomainObjectsFunc func(c *api.Context, dto DTO) ([]domain.Object, error)
+type ServiceFunc[O any] func(c *api.Context, dto DTO, objects []domain.Object) (O, error)
+type ResponseFunc[O any] func(c *api.Context, statusCode int, data O, err error)
+
+// SimpleBindItem  路由条目
+type SimpleBindItem[O any] struct {
+	// URL相对路径
+	Path string
+
+	// 使用的dto,非必传,当dto为nil时,说明该接口没有参数
+	DTO DTO
+
+	// 可选的dto绑定函数
+	// 非必传,POST和PUT请求默认为JsonBody,DELETE默认为PathParams,Get默认为QueryParams
+	// 额外还提供了一些转化函数:
+	// MultipartForm: 绑定multipart form
+	// Form: 绑定form body
+	// XMLBody: 绑定XML body
+	DTOBindFunc DTOBindFunc
+
+	// 通过DTO构造使用的领域对象,之后在ServiceFunc中会按照构造实体的顺序进行回调
+	// 非必传,如果为nil,则说明没有领域对象
+	// 与Objects字段二选一使用,如果都指定,会按照该字段处理
+	FormDomainObjectsFunc FormDomainObjectsFunc
+
+	// 使用的领域对象,当使用Tag对实体进行标注后,可以直接通过该字段给定实体,之后在ServiceFunc中会按照给定实体的顺序进行回调
+	// 非必传,如果为nil或长度为0,则说明没有领域对象
+	// 与FormObjectsFunc字段二选一使用,如果都指定,会按照FormObjectsFunc字段处理
+	Objects []domain.Object
+
+	// 应用服务泛型函数
+	// 非必传,为nil时相当于使用了SmartUI
+	ServiceFunc ServiceFunc[O]
+
+	// 响应泛型函数,如果不响应,需要使用NoResponse零值占位
+	ResponseFunc ResponseFunc[O]
+}
+
+func (item *SimpleBindItem[O]) bind(router api.Router, method string, middlewares ...api.Handler) {
+	bindingItem := &BindItem[O]{
+		Method:         method,
+		SimpleBindItem: item,
+	}
+
+	bindingItem.bind(router, middlewares...)
+}
+
+// BindItem 路由条目结构
+type BindItem[O any] struct {
+	Method string
+	*SimpleBindItem[O]
+}
+
+func (item *BindItem[O]) bind(router api.Router, middlewares ...api.Handler) {
+	if strutils.IsStringEmpty(item.Path) {
+		panic("需要指定路径")
+	}
+
+	if strutils.IsStringEmpty(item.Method) {
+		panic("需要指定方法")
+	}
+
+	if item.ResponseFunc == nil {
+		panic("需要指定响应函数")
+	}
+
+	// 给单个路由增加中间件
+	handlers := []api.Handler{
+		func(c *api.Context) {
+			dto := item.DTO
+
+			// 有请求数据
+			if dto != nil {
+				// 将请求数据解析到dto中
+				if item.DTOBindFunc != nil {
+					err := item.DTOBindFunc(c, dto)
+					if err != nil {
+						var outputZero O
+						item.ResponseFunc(c, http.StatusBadRequest, outputZero, err)
+						return
+					}
+				} else {
+					switch item.Method {
+					case http.MethodPost:
+						fallthrough
+					case http.MethodPut:
+						err := JsonBody(c, dto)
+						if err != nil {
+							var outputZero O
+							item.ResponseFunc(c, http.StatusBadRequest, outputZero, err)
+							return
+						}
+					case http.MethodGet:
+						err := QueryParams(c, dto)
+						if err != nil {
+							var outputZero O
+							item.ResponseFunc(c, http.StatusBadRequest, outputZero, err)
+							return
+						}
+					case http.MethodDelete:
+						err := PathParams(c, dto)
+						if err != nil {
+							var outputZero O
+							item.ResponseFunc(c, http.StatusBadRequest, outputZero, err)
+							return
+						}
+					}
+				}
+			}
+
+			// 进行领域对象转化
+			var domainObjects []domain.Object
+			if item.FormDomainObjectsFunc != nil {
+				innerDomainObjects, err := item.FormDomainObjectsFunc(c, dto)
+				if err != nil {
+					var outputZero O
+					item.ResponseFunc(c, http.StatusOK, outputZero, err)
+					return
+				}
+
+				domainObjects = innerDomainObjects
+			} else {
+				// TODO Tag处理
+			}
+
+			// 执行业务函数
+			if item.ServiceFunc != nil {
+				statusCode := http.StatusOK
+
+				outputModel, err := item.ServiceFunc(c, dto, domainObjects)
+				if err != nil {
+					statusCode = fserr.ParseCode(err).HttpCode
+				}
+
+				item.ResponseFunc(c, statusCode, outputModel, err)
+				return
+			}
+		},
+	}
+
+	handlers = append(handlers, middlewares...)
+
+	// 所有的函数加入到执行链中
+	router.AddRoute(item.Method, item.Path, handlers...)
+}
+
+// StaticBindItem 静态路由item
+type StaticBindItem struct {
+	RelativePath string
+	Root         string
+	WithBasePath bool
+}
+
+func (item *StaticBindItem) bind(router api.Router) {
+	if item.WithBasePath {
+		router.Static(strings.TrimPrefix(item.RelativePath, router.BasePath()), item.Root)
+	} else {
+		router.Static(item.RelativePath, item.Root)
+	}
+}
+
+type StaticFileBindItem struct {
+	RelativePath string
+	FilePath     string
+	WithBasePath bool
+}
+
+func (item *StaticFileBindItem) bind(router api.Router) {
+	if item.WithBasePath {
+		router.StaticFile(strings.TrimPrefix(item.RelativePath, router.BasePath()), item.FilePath)
+	} else {
+		router.StaticFile(item.RelativePath, item.FilePath)
+	}
+}

+ 43 - 0
binding/dto.go

@@ -0,0 +1,43 @@
+package binding
+
+import (
+	"git.sxidc.com/go-framework/baize/api"
+	"git.sxidc.com/service-supports/fserr"
+	"github.com/gin-gonic/gin/binding"
+)
+
+type DTO interface{}
+
+func ToConcreteDTO[T DTO](object DTO) (T, error) {
+	concrete, ok := object.(T)
+	if !ok {
+		var zero T
+		return zero, fserr.New("DTO转化失败")
+	}
+
+	return concrete, nil
+}
+
+func JsonBody(c *api.Context, dto DTO) error {
+	return c.ShouldBindJSON(dto)
+}
+
+func QueryParams(c *api.Context, dto DTO) error {
+	return c.ShouldBindQuery(dto)
+}
+
+func PathParams(c *api.Context, dto DTO) error {
+	return c.ShouldBindUri(dto)
+}
+
+func MultipartForm(c *api.Context, dto DTO) error {
+	return c.ShouldBindWith(dto, binding.FormMultipart)
+}
+
+func Form(c *api.Context, dto DTO) error {
+	return c.ShouldBindWith(dto, binding.Form)
+}
+
+func XMLBody(c *api.Context, dto DTO) error {
+	return c.ShouldBindXML(dto)
+}

+ 0 - 62
binding/request.go

@@ -1,62 +0,0 @@
-package binding
-
-import (
-	"git.sxidc.com/go-framework/baize/api"
-	"github.com/gin-gonic/gin/binding"
-	"net/http"
-)
-
-func JsonBody[O any](c *api.Context, request any, sendFunc SendFunc[O]) bool {
-	err := c.ShouldBindJSON(request)
-	if err != nil {
-		var zero O
-		sendFunc(c, http.StatusBadRequest, zero, err)
-		return false
-	}
-
-	return true
-}
-
-func QueryParams[O any](c *api.Context, query any, sendFunc SendFunc[O]) bool {
-	err := c.ShouldBindQuery(query)
-	if err != nil {
-		var zero O
-		sendFunc(c, http.StatusBadRequest, zero, err)
-		return false
-	}
-
-	return true
-}
-
-func MultipartForm[O any](c *api.Context, request any, sendFunc SendFunc[O]) bool {
-	err := c.ShouldBindWith(request, binding.FormMultipart)
-	if err != nil {
-		var zero O
-		sendFunc(c, http.StatusBadRequest, zero, err)
-		return false
-	}
-
-	return true
-}
-
-func Form[T any](c *api.Context, request interface{}, sendFunc SendFunc[T]) bool {
-	err := c.ShouldBindWith(request, binding.Form)
-	if err != nil {
-		var zero T
-		sendFunc(c, http.StatusBadRequest, zero, err)
-		return false
-	}
-
-	return true
-}
-
-func XMLBody[T any](c *api.Context, request interface{}, sendFunc SendFunc[T]) bool {
-	err := c.ShouldBindXML(request)
-	if err != nil {
-		var zero T
-		sendFunc(c, http.StatusBadRequest, zero, err)
-		return false
-	}
-
-	return true
-}

+ 0 - 4
binding/response_type.go

@@ -1,9 +1,5 @@
 package binding
 
-import "git.sxidc.com/go-framework/baize/api"
-
-type SendFunc[T any] func(c *api.Context, statusCode int, data T, err error)
-
 type IDType interface {
 	~string | ~uint64
 }

+ 20 - 0
domain/object.go

@@ -0,0 +1,20 @@
+package domain
+
+import "git.sxidc.com/service-supports/fserr"
+
+type IDType interface {
+	~string | ~uint64
+}
+
+type Object interface {
+}
+
+func ToConcreteObject[T Object](object Object) (T, error) {
+	concrete, ok := object.(T)
+	if !ok {
+		var zero T
+		return zero, fserr.New("领域对象转化失败")
+	}
+
+	return concrete, nil
+}

+ 0 - 50
examples/bind/main.go

@@ -1,50 +0,0 @@
-package main
-
-import (
-	"git.sxidc.com/go-framework/baize"
-	"git.sxidc.com/go-framework/baize/application"
-	DEATH "github.com/vrecan/death"
-	"syscall"
-)
-
-type Hello struct {
-	What string `json:"what"`
-}
-
-func main() {
-	app := baize.NewApplication(application.Config{
-		ApiConfig: application.ApiConfig{
-			UrlPrefix: "test",
-			Port:      "10000",
-		},
-	})
-
-	versionedRouter := app.Api().PrefixRouter().RegisterVersionedRouter("v1")
-
-	binding.GetBind(versionedRouter, &binding.SimpleBindItem[any, map[string]any]{
-		Path:         "/version",
-		ResponseFunc: response.SendMapResponse,
-		BusinessFunc: func(c *binding_context.Context, inputModel any) (map[string]any, error) {
-			return map[string]any{
-				"version": "v1.0.0",
-			}, nil
-		},
-	})
-
-	go func() {
-		err := app.Start()
-		if err != nil {
-			panic(err)
-		}
-	}()
-
-	defer func() {
-		err := app.Finish()
-		if err != nil {
-			panic(err)
-		}
-	}()
-
-	death := DEATH.NewDeath(syscall.SIGINT, syscall.SIGTERM)
-	_ = death.WaitForDeath()
-}

+ 288 - 0
examples/binding/main.go

@@ -0,0 +1,288 @@
+package main
+
+import (
+	"fmt"
+	"git.sxidc.com/go-framework/baize"
+	"git.sxidc.com/go-framework/baize/api"
+	"git.sxidc.com/go-framework/baize/application"
+	"git.sxidc.com/go-framework/baize/binding"
+	"git.sxidc.com/go-framework/baize/domain"
+	"git.sxidc.com/go-tools/utils/strutils"
+	DEATH "github.com/vrecan/death"
+	"syscall"
+)
+
+type CreateClassJsonBody struct {
+	Name string `json:"name" binding:"required"`
+}
+
+type DeleteClassPathParams struct {
+	ID string `uri:"id" binding:"required"`
+}
+
+type UpdateClassJsonBody struct {
+	ID   string `json:"id" binding:"required"`
+	Name string `json:"name"`
+}
+
+type QueryClassesQueryParams struct {
+	Name     string `form:"id"`
+	PageNo   int    `form:"pageNo"`
+	PageSize int    `form:"pageSize"`
+}
+
+type GetClassQueryParams struct {
+	ID string `form:"id" binding:"required"`
+}
+
+type Class struct {
+	ID   string
+	Name string
+}
+
+type ClassInfo struct {
+	ID   string `json:"id"`
+	Name string `json:"name"`
+}
+
+var classMap = make(map[string]*Class, 0)
+
+func main() {
+	app := baize.NewApplication(application.Config{
+		ApiConfig: application.ApiConfig{
+			UrlPrefix: "test",
+			Port:      "10000",
+		},
+	})
+
+	v1Router := app.Api().PrefixRouter().RegisterVersionedRouter("v1")
+
+	// 创建班级
+	binding.PostBind(v1Router, &binding.SimpleBindItem[string]{
+		Path:         "/class/create",
+		ResponseFunc: binding.SendIDResponse[string],
+		DTO:          &CreateClassJsonBody{},
+		FormDomainObjectsFunc: func(c *api.Context, dto binding.DTO) ([]domain.Object, error) {
+			jsonBody, err := binding.ToConcreteDTO[*CreateClassJsonBody](dto)
+			if err != nil {
+				return nil, err
+			}
+
+			return []domain.Object{
+				&Class{
+					Name: jsonBody.Name,
+				},
+			}, nil
+		},
+		ServiceFunc: func(c *api.Context, dto binding.DTO, objects []domain.Object) (string, error) {
+			e, err := domain.ToConcreteObject[*Class](objects[0])
+			if err != nil {
+				return "", err
+			}
+
+			e.ID = strutils.SimpleUUID()
+			classMap[e.ID] = e
+
+			return e.ID, nil
+		},
+	})
+
+	// 删除班级
+	binding.DeleteBind(v1Router, &binding.SimpleBindItem[any]{
+		Path:         "/class/delete",
+		ResponseFunc: binding.SendMsgResponse,
+		DTO:          &DeleteClassPathParams{},
+		FormDomainObjectsFunc: func(c *api.Context, dto binding.DTO) ([]domain.Object, error) {
+			pathParams, err := binding.ToConcreteDTO[*DeleteClassPathParams](dto)
+			if err != nil {
+				return nil, err
+			}
+
+			return []domain.Object{
+				&Class{
+					ID: pathParams.ID,
+				},
+			}, nil
+		},
+		ServiceFunc: func(c *api.Context, dto binding.DTO, objects []domain.Object) (any, error) {
+			e, err := domain.ToConcreteObject[*Class](objects[0])
+			if err != nil {
+				return nil, err
+			}
+
+			delete(classMap, e.ID)
+
+			fmt.Println("Deleted Entity:" + e.ID)
+			return nil, nil
+		},
+	})
+
+	// 修改班级
+	binding.PutBind(v1Router, &binding.SimpleBindItem[any]{
+		Path:         "/class/update",
+		ResponseFunc: binding.SendMsgResponse,
+		DTO:          &UpdateClassJsonBody{},
+		FormDomainObjectsFunc: func(c *api.Context, dto binding.DTO) ([]domain.Object, error) {
+			jsonBody, err := binding.ToConcreteDTO[*UpdateClassJsonBody](dto)
+			if err != nil {
+				return nil, err
+			}
+
+			return []domain.Object{
+				&Class{
+					ID:   jsonBody.ID,
+					Name: jsonBody.Name,
+				},
+			}, nil
+		},
+		ServiceFunc: func(c *api.Context, dto binding.DTO, objects []domain.Object) (any, error) {
+			e, err := domain.ToConcreteObject[*Class](objects[0])
+			if err != nil {
+				return nil, err
+			}
+
+			existEntity, ok := classMap[e.ID]
+			if !ok {
+				fmt.Println("Update Entity:" + e.ID)
+				fmt.Println("Not Find")
+				return nil, nil
+			}
+
+			existEntity.Name = e.Name
+			classMap[e.ID] = existEntity
+
+			fmt.Println("Update Entity:" + existEntity.ID)
+			fmt.Println("Name:" + existEntity.Name)
+
+			return nil, nil
+		},
+	})
+
+	// 查询班级
+	binding.GetBind(v1Router, &binding.SimpleBindItem[binding.InfosData[ClassInfo]]{
+		Path:         "/class/query",
+		ResponseFunc: binding.SendInfosResponse[ClassInfo],
+		DTO:          &QueryClassesQueryParams{},
+		FormDomainObjectsFunc: func(c *api.Context, dto binding.DTO) ([]domain.Object, error) {
+			queryParams, err := binding.ToConcreteDTO[*QueryClassesQueryParams](dto)
+			if err != nil {
+				return nil, err
+			}
+
+			return []domain.Object{
+				&Class{
+					Name: queryParams.Name,
+				},
+			}, nil
+		},
+		ServiceFunc: func(c *api.Context, dto binding.DTO, objects []domain.Object) (binding.InfosData[ClassInfo], error) {
+			classInfos := make([]ClassInfo, 0)
+
+			e, err := domain.ToConcreteObject[*Class](objects[0])
+			if err != nil {
+				return binding.InfosData[ClassInfo]{
+					Infos:      classInfos,
+					TotalCount: 0,
+					PageNo:     0,
+				}, err
+			}
+
+			queryParams, err := binding.ToConcreteDTO[*QueryClassesQueryParams](dto)
+			if err != nil {
+				return binding.InfosData[ClassInfo]{
+					Infos:      classInfos,
+					TotalCount: 0,
+					PageNo:     0,
+				}, err
+			}
+
+			startCount := 1
+			if queryParams.PageNo != 0 && queryParams.PageSize != 0 {
+				startCount = queryParams.PageNo*queryParams.PageSize + 1
+			}
+
+			endCount := len(classMap)
+			if queryParams.PageNo != 0 && queryParams.PageSize != 0 {
+				endCount = queryParams.PageSize
+			}
+
+			count := 1
+			for _, existEntity := range classMap {
+				if count >= endCount {
+					break
+				}
+
+				if count >= startCount {
+					if existEntity.Name == e.Name {
+						classInfos = append(classInfos, ClassInfo{
+							ID:   existEntity.ID,
+							Name: existEntity.Name,
+						})
+					}
+				}
+
+				count++
+			}
+
+			return binding.InfosData[ClassInfo]{
+				Infos:      classInfos,
+				TotalCount: int64(len(classMap)),
+				PageNo:     queryParams.PageNo,
+			}, nil
+		},
+	})
+
+	// 通过ID获取班级
+	binding.GetBind(v1Router, &binding.SimpleBindItem[*ClassInfo]{
+		Path:         "/class/get",
+		ResponseFunc: binding.SendInfoResponse[*ClassInfo],
+		DTO:          &GetClassQueryParams{},
+		FormDomainObjectsFunc: func(c *api.Context, dto binding.DTO) ([]domain.Object, error) {
+			queryParams, err := binding.ToConcreteDTO[*GetClassQueryParams](dto)
+			if err != nil {
+				return nil, err
+			}
+
+			return []domain.Object{
+				&Class{
+					ID: queryParams.ID,
+				},
+			}, nil
+		},
+		ServiceFunc: func(c *api.Context, dto binding.DTO, objects []domain.Object) (*ClassInfo, error) {
+			e, err := domain.ToConcreteObject[*Class](objects[0])
+			if err != nil {
+				return &ClassInfo{}, err
+			}
+
+			classInfo := new(ClassInfo)
+			for _, existEntity := range classMap {
+				if existEntity.ID == e.ID {
+					classInfo = &ClassInfo{
+						ID:   existEntity.ID,
+						Name: existEntity.Name,
+					}
+				}
+			}
+
+			return classInfo, nil
+		},
+	})
+
+	go func() {
+		err := app.Start()
+		if err != nil {
+			panic(err)
+		}
+	}()
+
+	defer func() {
+		err := app.Finish()
+		if err != nil {
+			panic(err)
+		}
+	}()
+
+	death := DEATH.NewDeath(syscall.SIGINT, syscall.SIGTERM)
+	_ = death.WaitForDeath()
+}