yjp před 1 rokem
rodič
revize
a3089b7585

+ 19 - 0
.gitignore

@@ -0,0 +1,19 @@
+# Binaries for programs and plugins
+*.exe
+*.exe~
+*.dll
+*.so
+*.dylib
+
+# Test binary, build with `go test -c`
+*.test
+
+# Output of the go coverage tool, specifically when used with LiteIDE
+*.out
+
+.idea/
+.DS_Store
+**/bin
+
+/vendor/
+**/logs

+ 51 - 0
http_binding/binding_context/binding_context.go

@@ -0,0 +1,51 @@
+package binding_context
+
+import (
+	"git.sxidc.com/service-supports/fserr"
+	"git.sxidc.com/service-supports/fslog"
+	"github.com/gin-gonic/gin"
+	"io"
+	"lcp/errcode"
+	"lcp/utils"
+	"mime/multipart"
+)
+
+type Context struct {
+	*gin.Context
+}
+
+func (c *Context) GetPathParams(pathParamNames []string) ([]string, error) {
+	pathParamValues := make([]string, 0)
+	for _, paramName := range pathParamNames {
+		paramValue := c.Param(paramName)
+		if utils.IsStringEmpty(paramValue) {
+			return nil, fserr.WithCode(nil, errcode.ErrCommon, fserr.MsgOption("没有传递路径参数"+paramName))
+		}
+
+		pathParamValues = append(pathParamValues, paramValue)
+	}
+
+	return pathParamValues, nil
+}
+
+func (c *Context) GetFileHeaderBytes(fileHeader *multipart.FileHeader) (string, []byte, error) {
+	file, err := fileHeader.Open()
+	if err != nil {
+		return "", nil, err
+	}
+
+	defer func(file multipart.File) {
+		err := file.Close()
+		if err != nil {
+			fslog.Error(err)
+			return
+		}
+	}(file)
+
+	contentBytes, err := io.ReadAll(file)
+	if err != nil {
+		return "", nil, err
+	}
+
+	return fileHeader.Filename, contentBytes, nil
+}

+ 172 - 0
http_binding/http_binding.go

@@ -0,0 +1,172 @@
+package http_binding
+
+import (
+	"git.sxidc.com/service-supports/fserr"
+	"github.com/gin-gonic/gin"
+	"lcp/api/binding/http_binding/binding_context"
+	"lcp/api/binding/http_binding/middleware"
+	"lcp/api/binding/http_binding/request"
+	"lcp/api/binding/http_binding/response"
+	"lcp/utils"
+	"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
+}
+
+func NewBinding(apiVersion string, middlewares ...middleware.Func) *Binding {
+	apiPrefix := urlPrefix + "/api"
+	if utils.IsStringNotEmpty(apiVersion) && apiVersion != "root" {
+		apiPrefix += "/" + apiVersion
+	}
+
+	ginMiddlewares := make([]gin.HandlerFunc, 0)
+	for _, m := range middlewares {
+		ginMiddlewares = append(ginMiddlewares, func(c *gin.Context) {
+			m(&binding_context.Context{Context: c})
+		})
+	}
+
+	return &Binding{RouterGroup: routerInstance.Group(apiPrefix, ginMiddlewares...)}
+}
+
+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)
+}
+
+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...)
+}
+
+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 utils.IsStringEmpty(item.Path) {
+		panic("需要指定路径")
+	}
+
+	if utils.IsStringEmpty(item.Method) {
+		panic("需要指定方法")
+	}
+
+	if item.ResponseFunc == nil {
+		panic("需要指定响应函数")
+	}
+
+	var inputModel I
+	inputType := reflect.TypeOf(inputModel)
+	if inputType != nil {
+		if inputType.Kind() == reflect.Pointer {
+			panic("输入对象不能使用指针类型")
+		}
+
+		if inputType.Kind() != reflect.Struct {
+			panic("输入对象必须是结构")
+		}
+	}
+
+	ginHandleFunctions := make([]gin.HandlerFunc, 0)
+	for _, m := range middlewares {
+		ginHandleFunctions = append(ginHandleFunctions, func(c *gin.Context) {
+			m(&binding_context.Context{Context: c})
+		})
+	}
+
+	ginHandleFunctions = append(ginHandleFunctions, func(c *gin.Context) {
+		bindingContext := &binding_context.Context{Context: c}
+
+		if inputType != nil {
+			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...)
+}
+
+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)
+	}
+}

+ 53 - 0
http_binding/http_init.go

@@ -0,0 +1,53 @@
+package http_binding
+
+import (
+	"context"
+	"errors"
+	"git.sxidc.com/service-supports/fslog"
+	"github.com/gin-gonic/gin"
+	"lcp/api/binding/http_binding/binding_context"
+	"lcp/api/binding/http_binding/middleware"
+	"net/http"
+	"time"
+)
+
+var (
+	urlPrefix string
+)
+
+var serverInstance *http.Server
+var routerInstance *gin.Engine
+
+func Init(apiUrlPrefix string, port string) {
+	urlPrefix = apiUrlPrefix
+
+	routerInstance = gin.New()
+
+	routerInstance.Use(fslog.GinLogger([]string{"/" + urlPrefix + "/api/version"}), fslog.GinRecovery(),
+		func(c *gin.Context) { middleware.Cors()(&binding_context.Context{Context: c}) },
+		func(c *gin.Context) { middleware.ReadRequestBodyData()(&binding_context.Context{Context: c}) })
+
+	serverInstance = &http.Server{
+		Addr:    ":" + port,
+		Handler: routerInstance,
+	}
+
+	go func() {
+		if err := serverInstance.ListenAndServe(); err != nil && !errors.Is(err, http.ErrServerClosed) {
+			panic(err)
+		}
+	}()
+}
+
+func Destroy() {
+	ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
+	defer cancel()
+
+	if err := serverInstance.Shutdown(ctx); err != nil {
+		panic(err)
+	}
+}
+
+func GetRouter() *gin.Engine {
+	return routerInstance
+}

+ 39 - 0
http_binding/middleware/body.go

@@ -0,0 +1,39 @@
+package middleware
+
+import (
+	"bytes"
+	"git.sxidc.com/service-supports/fserr"
+	"git.sxidc.com/service-supports/fslog"
+	"io"
+	"lcp/api/binding/http_binding/binding_context"
+	"lcp/api/binding/http_binding/response"
+	"lcp/errcode"
+	"net/http"
+)
+
+func ReadRequestBodyData() Func {
+	return func(c *binding_context.Context) {
+		respFunc := response.SendMsgResponse
+
+		contentType := c.Request.Header.Get("Content-Type")
+		if c.Request.Body != nil && contentType == "application/json" &&
+			(c.Request.Method == http.MethodPost || c.Request.Method == http.MethodPut) {
+			bodyData, err := io.ReadAll(c.Request.Body)
+			if err != nil {
+				err = fserr.WithCode(err, errcode.ErrCommonGolangCall)
+				fslog.Error(err)
+				respFunc(c, http.StatusBadRequest, nil, err)
+				c.Abort()
+				return
+			}
+
+			fslog.Info(c.Request.RequestURI + " Request Body\n" + string(bodyData))
+
+			_ = c.Request.Body.Close()
+			c.Request.Body = nil
+			c.Request.Body = io.NopCloser(bytes.NewReader(bodyData))
+		}
+
+		c.Next()
+	}
+}

+ 25 - 0
http_binding/middleware/cors.go

@@ -0,0 +1,25 @@
+package middleware
+
+import (
+	"lcp/api/binding/http_binding/binding_context"
+	"net/http"
+)
+
+func Cors() Func {
+	return func(c *binding_context.Context) {
+		method := c.Request.Method
+
+		c.Header("Access-Control-Allow-Origin", "*")
+		c.Header("Access-Control-Allow-Headers", "Content-Length, Content-Type, AccessToken, X-CSRF-Token, Authorization, Token")
+		c.Header("Access-Control-Allow-Methods", "POST, GET, PUT, DELETE,OPTIONS")
+		c.Header("Access-Control-Expose-Headers", "Content-Length, Access-Control-Allow-Origin, "+
+			"Access-Control-Allow-Headers, Content-Type")
+		c.Header("Access-Control-Allow-Credentials", "true")
+
+		if method == "OPTIONS" {
+			c.AbortWithStatus(http.StatusOK)
+		}
+
+		c.Next()
+	}
+}

+ 5 - 0
http_binding/middleware/middleware.go

@@ -0,0 +1,5 @@
+package middleware
+
+import "lcp/api/binding/http_binding/binding_context"
+
+type Func func(c *binding_context.Context)

+ 41 - 0
http_binding/request/request.go

@@ -0,0 +1,41 @@
+package request
+
+import (
+	"github.com/gin-gonic/gin/binding"
+	"lcp/api/binding/http_binding/binding_context"
+	"lcp/api/binding/http_binding/response"
+	"net/http"
+)
+
+func BindingJson[O any](c *binding_context.Context, request any, sendFunc response.SendFunc[O]) bool {
+	err := c.ShouldBindJSON(request)
+	if err != nil {
+		var zero O
+		sendFunc(c, http.StatusBadRequest, zero, err)
+		return false
+	}
+
+	return true
+}
+
+func BindingQuery[O any](c *binding_context.Context, query any, sendFunc response.SendFunc[O]) bool {
+	err := c.ShouldBindQuery(query)
+	if err != nil {
+		var zero O
+		sendFunc(c, http.StatusBadRequest, zero, err)
+		return false
+	}
+
+	return true
+}
+
+func BindingMultipartForm[O any](c *binding_context.Context, request any, sendFunc response.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
+}

+ 7 - 0
http_binding/response/data.go

@@ -0,0 +1,7 @@
+package response
+
+type InfosData[T any] struct {
+	Infos      []T   `json:"infos"`
+	TotalCount int64 `json:"totalCount"`
+	PageNo     int   `json:"pageNo"`
+}

+ 138 - 0
http_binding/response/response.go

@@ -0,0 +1,138 @@
+package response
+
+import (
+	"encoding/json"
+	"git.sxidc.com/service-supports/fserr"
+	"git.sxidc.com/service-supports/fslog"
+	"git.sxidc.com/service-supports/websocket"
+	"lcp/api/binding/http_binding/binding_context"
+)
+
+func SendMsgResponse(c *binding_context.Context, statusCode int, _ any, err error) {
+	msgResp := formMsgResponse(err)
+	c.JSON(statusCode, msgResp)
+}
+
+type IDResponse[T IDType] struct {
+	MsgResponse
+	ID T `json:"id"`
+}
+
+func SendIDResponse[T IDType](c *binding_context.Context, statusCode int, id T, err error) {
+	msgResp := formMsgResponse(err)
+	c.JSON(statusCode, IDResponse[T]{
+		MsgResponse: msgResp,
+		ID:          id,
+	})
+}
+
+type InfoResponse[T any] struct {
+	MsgResponse
+	Info T `json:"info"`
+}
+
+func SendInfoResponse[T any](c *binding_context.Context, statusCode int, info T, err error) {
+	msgResp := formMsgResponse(err)
+	c.JSON(statusCode, InfoResponse[T]{
+		MsgResponse: msgResp,
+		Info:        info,
+	})
+}
+
+type InfosResponse[T any] struct {
+	MsgResponse
+	InfosData[T]
+}
+
+func SendInfosResponse[T any](c *binding_context.Context, statusCode int, data InfosData[T], err error) {
+	msgResp := formMsgResponse(err)
+	c.JSON(statusCode, InfosResponse[T]{
+		MsgResponse: msgResp,
+		InfosData:   data,
+	})
+}
+
+func StructToMap(originStruct any) map[string]any {
+	jsonBytes, err := json.Marshal(originStruct)
+	if err != nil {
+		panic(err)
+	}
+
+	retMap := make(map[string]any)
+	err = json.Unmarshal(jsonBytes, &retMap)
+	if err != nil {
+		panic(err)
+	}
+
+	return retMap
+}
+
+func SendMapResponse(c *binding_context.Context, statusCode int, data map[string]any, err error) {
+	msgRespMap := formMapMsgResponse(err)
+	for key, value := range data {
+		msgRespMap[key] = value
+	}
+
+	c.JSON(statusCode, msgRespMap)
+}
+
+type MsgResponse struct {
+	Success bool   `json:"success"`
+	ErrCode int    `json:"errCode"`
+	Msg     string `json:"msg"`
+}
+
+func formMsgResponse(err error) MsgResponse {
+	if err != nil {
+		fslog.Error(err)
+		serviceErr := fserr.ParseCode(err)
+
+		return MsgResponse{
+			Success: false,
+			ErrCode: serviceErr.BusinessCode,
+			Msg:     serviceErr.Msg,
+		}
+	}
+
+	return MsgResponse{
+		Success: true,
+		ErrCode: 0,
+		Msg:     "操作成功",
+	}
+}
+
+func formMapMsgResponse(err error) map[string]any {
+	resp := make(map[string]any)
+	if err != nil {
+		fslog.Error(err)
+		serviceErr := fserr.ParseCode(err)
+
+		resp["success"] = false
+		resp["errCode"] = serviceErr.BusinessCode
+		resp["msg"] = serviceErr.Msg
+
+		return resp
+	}
+
+	resp["success"] = true
+	resp["errCode"] = 0
+	resp["msg"] = "操作成功"
+
+	return resp
+}
+
+func WSSendMessage(groupID string, obj any) {
+	message, err := json.Marshal(obj)
+	if err != nil {
+		err = fserr.ParseCode(err)
+		fslog.Error(err)
+		return
+	}
+
+	err = websocket.GetInstance().BroadCast(groupID, message)
+	if err != nil {
+		err = fserr.ParseCode(err)
+		fslog.Error(err)
+		return
+	}
+}

+ 11 - 0
http_binding/response/type.go

@@ -0,0 +1,11 @@
+package response
+
+import (
+	"lcp/api/binding/http_binding/binding_context"
+)
+
+type SendFunc[T any] func(c *binding_context.Context, statusCode int, data T, err error)
+
+type IDType interface {
+	~string | ~uint64
+}