Browse Source

完成基本api开发

yjp 11 months ago
parent
commit
4fe85d4399
17 changed files with 940 additions and 14 deletions
  1. 77 0
      api/api.go
  2. 52 0
      api/context.go
  3. 0 3
      api/http.go
  4. 31 0
      api/options.go
  5. 101 0
      api/router.go
  6. 47 0
      application/application.go
  7. 72 0
      application/config.go
  8. 9 3
      baize.go
  9. 3 5
      examples/bind/main.go
  10. 1 2
      examples/common_crud/main.go
  11. 76 0
      examples/quick_start/main.go
  12. 44 1
      go.mod
  13. 117 0
      go.sum
  14. 12 0
      logger/level.go
  15. 208 0
      logger/logger.go
  16. 73 0
      logger/opt_file.go
  17. 17 0
      logger/opt_logger.go

+ 77 - 0
api/api.go

@@ -0,0 +1,77 @@
+package api
+
+import (
+	"context"
+	"git.sxidc.com/go-tools/utils/strutils"
+	"github.com/gin-gonic/gin"
+	"github.com/pkg/errors"
+	"net/http"
+)
+
+type Api struct {
+	options Options
+
+	server       *http.Server
+	rootRouter   *RootRouter
+	prefixRouter *PrefixRouter
+}
+
+func New(opts ...Option) *Api {
+	options := new(Options)
+
+	for _, opt := range opts {
+		opt(options)
+	}
+
+	if strutils.IsStringEmpty(options.port) {
+		options.port = "8080"
+	}
+
+	engine := gin.New()
+
+	server := &http.Server{
+		Addr:    ":" + options.port,
+		Handler: engine,
+	}
+
+	api := &Api{
+		options:    *options,
+		server:     server,
+		rootRouter: newRootRouter(engine),
+	}
+
+	if strutils.IsStringNotEmpty(options.urlPrefix) {
+		api.prefixRouter = newPrefixRouter(engine.Group(options.urlPrefix))
+	}
+
+	return api
+}
+
+func (api *Api) Start() error {
+	err := api.server.ListenAndServe()
+	if err != nil && !errors.Is(err, http.ErrServerClosed) {
+		return err
+	}
+
+	return nil
+}
+
+func (api *Api) Finish() error {
+	return api.server.Shutdown(context.Background())
+}
+
+func (api *Api) Options() Options {
+	return api.options
+}
+
+func (api *Api) RootRouter() Router {
+	return api.rootRouter
+}
+
+func (api *Api) PrefixRouter() Router {
+	if api.prefixRouter == nil {
+		return api.rootRouter
+	}
+
+	return api.prefixRouter
+}

+ 52 - 0
api/context.go

@@ -0,0 +1,52 @@
+package api
+
+import (
+	"git.sxidc.com/go-tools/utils/strutils"
+	"git.sxidc.com/service-supports/fslog"
+	"github.com/gin-gonic/gin"
+	"github.com/pkg/errors"
+	"io"
+	"mime/multipart"
+)
+
+type Context struct {
+	*gin.Context
+}
+
+// GetPathParams 根据给定的路径参数名字获取参数值
+func (c *Context) GetPathParams(pathParamNames []string) ([]string, error) {
+	pathParamValues := make([]string, 0)
+	for _, paramName := range pathParamNames {
+		paramValue := c.Param(paramName)
+		if strutils.IsStringEmpty(paramValue) {
+			return nil, errors.New("没有传递路径参数" + paramName)
+		}
+
+		pathParamValues = append(pathParamValues, paramValue)
+	}
+
+	return pathParamValues, nil
+}
+
+// GetFileHeaderBytes 获取传递的文件名和文件内容
+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
+}

+ 0 - 3
api/http.go

@@ -1,3 +0,0 @@
-package api
-
-type Api struct{}

+ 31 - 0
api/options.go

@@ -0,0 +1,31 @@
+package api
+
+type Options struct {
+	// URL前缀
+	urlPrefix string
+
+	// 端口
+	port string
+}
+
+func (options Options) GetPort() string {
+	return options.port
+}
+
+func (options Options) GetUrlPrefix() string {
+	return options.urlPrefix
+}
+
+type Option func(options *Options)
+
+func WithUrlPrefix(urlPrefix string) Option {
+	return func(options *Options) {
+		options.urlPrefix = urlPrefix
+	}
+}
+
+func WithPort(port string) Option {
+	return func(options *Options) {
+		options.port = port
+	}
+}

+ 101 - 0
api/router.go

@@ -0,0 +1,101 @@
+package api
+
+import (
+	"github.com/gin-gonic/gin"
+)
+
+// Handler 请求处理函数
+type Handler func(c *Context)
+
+type Router interface {
+	AddMiddlewares(middlewares ...Handler) Router
+	RegisterVersionedRouter(version string, middlewares ...Handler) Router
+	GetVersionedRouter(version string) Router
+	AddRoute(relativePath string, method string, middlewares ...Handler) Router
+}
+
+type RootRouter struct {
+	engine    *gin.Engine
+	versioned map[string]*PrefixRouter
+}
+
+func newRootRouter(engine *gin.Engine) *RootRouter {
+	return &RootRouter{
+		engine:    engine,
+		versioned: make(map[string]*PrefixRouter),
+	}
+}
+
+func (r *RootRouter) AddMiddlewares(middlewares ...Handler) Router {
+	r.engine.Use(transferHandlers(middlewares...)...)
+	return r
+}
+
+func (r *RootRouter) RegisterVersionedRouter(version string, middlewares ...Handler) Router {
+	versioned := r.engine.Group(version, transferHandlers(middlewares...)...)
+	r.versioned[version] = newPrefixRouter(versioned)
+
+	return r.versioned[version]
+}
+
+func (r *RootRouter) GetVersionedRouter(version string) Router {
+	return r.versioned[version]
+}
+
+func (r *RootRouter) AddRoute(method string, relativePath string, middlewares ...Handler) Router {
+	r.engine.Handle(method, relativePath, transferHandlers(middlewares...)...)
+	return r
+}
+
+type PrefixRouter struct {
+	groupRouter *gin.RouterGroup
+	versioned   map[string]*PrefixRouter
+}
+
+func newPrefixRouter(group *gin.RouterGroup) *PrefixRouter {
+	return &PrefixRouter{
+		groupRouter: group,
+		versioned:   make(map[string]*PrefixRouter),
+	}
+}
+
+func (r *PrefixRouter) AddMiddlewares(middlewares ...Handler) Router {
+	r.groupRouter.Use(transferHandlers(middlewares...)...)
+	return r
+}
+
+func (r *PrefixRouter) RegisterVersionedRouter(version string, middlewares ...Handler) Router {
+	ginMiddlewares := make([]gin.HandlerFunc, 0)
+	for _, m := range middlewares {
+		innerM := m
+		ginMiddlewares = append(ginMiddlewares, func(c *gin.Context) {
+			innerM(&Context{Context: c})
+		})
+	}
+
+	versioned := r.groupRouter.Group(version, ginMiddlewares...)
+	r.versioned[version] = newPrefixRouter(versioned)
+
+	return r.versioned[version]
+}
+
+func (r *PrefixRouter) GetVersionedRouter(version string) Router {
+	return r.versioned[version]
+}
+
+func (r *PrefixRouter) AddRoute(method string, relativePath string, middlewares ...Handler) Router {
+	r.groupRouter.Handle(method, relativePath, transferHandlers(middlewares...)...)
+	return r
+}
+
+func transferHandlers(handlers ...Handler) []gin.HandlerFunc {
+	ginHandlers := make([]gin.HandlerFunc, 0)
+	for _, handler := range handlers {
+		innerHandler := handler
+		ginHandlers = append(ginHandlers, func(c *gin.Context) {
+			innerHandler(&Context{Context: c})
+		})
+	}
+
+	return ginHandlers
+}

+ 47 - 0
application/application.go

@@ -1,4 +1,51 @@
 package application
 
+import (
+	"git.sxidc.com/go-framework/baize/api"
+	"git.sxidc.com/go-framework/baize/logger"
+)
+
 type App struct {
+	// api实例
+	apiInstance *api.Api
+
+	// 日志
+	loggerInstance *logger.Logger
+}
+
+// New 创建Application
+func New(api *api.Api) *App {
+	return &App{
+		apiInstance: api,
+	}
+}
+
+// Start 运行应用
+func (app *App) Start() error {
+	err := app.apiInstance.Start()
+	if err != nil {
+		return err
+	}
+
+	return nil
+}
+
+// Finish 终止应用
+func (app *App) Finish() error {
+	err := app.apiInstance.Finish()
+	if err != nil {
+		return err
+	}
+
+	return nil
+}
+
+// Api 获取api实例
+func (app *App) Api() *api.Api {
+	return app.apiInstance
+}
+
+// Logger 获取logger实例
+func (app *App) Logger() *logger.Logger {
+	return app.loggerInstance
 }

+ 72 - 0
application/config.go

@@ -0,0 +1,72 @@
+package application
+
+import (
+	"encoding/json"
+	"git.sxidc.com/go-tools/utils/fileutils"
+	"github.com/pkg/errors"
+	"gopkg.in/yaml.v3"
+	"os"
+)
+
+type Config struct {
+	ApiConfig `json:"api" yaml:"api"`
+}
+
+type ApiConfig struct {
+	UrlPrefix string `json:"url_prefix" yaml:"url_prefix"`
+	Port      string `json:"port" yaml:"port"`
+}
+
+func LoadFromJsonFile(jsonFilePath string) (Config, error) {
+	if !fileutils.PathExists(jsonFilePath) {
+		return Config{}, errors.New("配置文件不存在")
+	}
+
+	jsonBytes, err := os.ReadFile(jsonFilePath)
+	if err != nil {
+		return Config{}, err
+	}
+
+	return loadFromJson(jsonBytes)
+}
+
+func LoadFromYamlFile(yamlFilePath string) (Config, error) {
+	if !fileutils.PathExists(yamlFilePath) {
+		return Config{}, errors.New("配置文件不存在")
+	}
+
+	yamlBytes, err := os.ReadFile(yamlFilePath)
+	if err != nil {
+		return Config{}, err
+	}
+
+	return loadFromYaml(yamlBytes)
+}
+
+func LoadFromJson(jsonStr string) (Config, error) {
+	return loadFromJson([]byte(jsonStr))
+}
+
+func LoadFromYaml(yamlStr string) (Config, error) {
+	return loadFromYaml([]byte(yamlStr))
+}
+
+func loadFromJson(jsonBytes []byte) (Config, error) {
+	conf := new(Config)
+	err := json.Unmarshal(jsonBytes, conf)
+	if err != nil {
+		return Config{}, err
+	}
+
+	return *conf, nil
+}
+
+func loadFromYaml(yamlBytes []byte) (Config, error) {
+	conf := new(Config)
+	err := yaml.Unmarshal(yamlBytes, conf)
+	if err != nil {
+		return Config{}, err
+	}
+
+	return *conf, nil
+}

+ 9 - 3
baize.go

@@ -1,9 +1,15 @@
 package baize
 
 import (
-	"git.sxidc.com/go-framework/baize.git/application"
+	"git.sxidc.com/go-framework/baize/api"
+	"git.sxidc.com/go-framework/baize/application"
 )
 
-func NewApplication() *application.App {
-	return &application.App{}
+func NewApplication(conf application.Config) *application.App {
+	return application.New(
+		api.New(
+			api.WithUrlPrefix(conf.ApiConfig.UrlPrefix),
+			api.WithPort(conf.ApiConfig.Port),
+		),
+	)
 }

+ 3 - 5
examples/bind/main.go

@@ -13,13 +13,12 @@ package main
 //}
 
 func main() {
-	//app, err := baize.NewApplication("10000", baize.NewDBPersistence())
+	//app, err := baize.NewApplication("10000", WithDB(config))
 	//if err != nil {
 	//	panic(err)
 	//}
 	//
-	//app.AddRouter("root", middlewares...).
-	//	GetRouter("root").
+	//app.RegisterRouter("root", middlewares...).
 	//	BindRoute(binding, &http_binding.SimpleBindItem[any, map[string]any]{
 	//		Path:         "/version",
 	//		ResponseFunc: response.SendMapResponse,
@@ -30,8 +29,7 @@ func main() {
 	//		},
 	//	})
 	//
-	//app.AddRouter("v1", middlewares...).
-	//	GetRouter("v1").
+	//app.RegisterRouter("v1", middlewares...).
 	//	BindPost(binding, &http_binding.SimpleBindItem[Hello, map[string]any]{
 	//		Path:         "/hello",
 	//		ResponseFunc: response.SendMapResponse,

+ 1 - 2
examples/common_crud/main.go

@@ -24,13 +24,12 @@ package main
 //}
 
 func main() {
-	//app, err := baize.NewApplication("10000", baize.NewDBPersistence())
+	//app, err := baize.NewApplication("10000", WithDB(config))
 	//if err != nil {
 	//	panic(err)
 	//}
 	//
 	//app.RegisterRouter("v1", middlewares...).
-	//	GetRouter("v1").
 	//	RegisterCommonCrud(&Class{}).
 	//	RegisterCrud(&common.Crud{
 	//		Api:          common.CrudApi{},

+ 76 - 0
examples/quick_start/main.go

@@ -1,5 +1,81 @@
 package main
 
+import (
+	"fmt"
+	"git.sxidc.com/go-framework/baize"
+	"git.sxidc.com/go-framework/baize/api"
+	"git.sxidc.com/go-framework/baize/application"
+	DEATH "github.com/vrecan/death"
+	"net/http"
+	"syscall"
+)
+
 func main() {
+	app := baize.NewApplication(application.Config{
+		ApiConfig: application.ApiConfig{
+			UrlPrefix: "test",
+			Port:      "10000",
+		},
+	})
+
+	app.Api().
+		RootRouter().
+		AddMiddlewares(func(c *api.Context) {
+			fmt.Println("Global Before1")
+			c.Next()
+			fmt.Println("Global After1")
+		}, func(c *api.Context) {
+			fmt.Println("Global Before2")
+			c.Next()
+			fmt.Println("Global After2")
+		}).
+		AddRoute(http.MethodGet, "/ping", func(c *api.Context) {
+			c.String(http.StatusOK, "pong")
+		}, func(c *api.Context) {
+			fmt.Println("Root Route Before1")
+			c.Next()
+			fmt.Println("Root Route After1")
+		}, func(c *api.Context) {
+			fmt.Println("Root Route Before2")
+			c.Next()
+			fmt.Println("Root Route After2")
+		})
+
+	app.Api().
+		PrefixRouter().
+		RegisterVersionedRouter("v1", func(c *api.Context) {
+			fmt.Println("Global Before1")
+			c.Next()
+			fmt.Println("Global After1")
+		}, func(c *api.Context) {
+			fmt.Println("Global Before2")
+			c.Next()
+			fmt.Println("Global After2")
+		}).
+		AddRoute(http.MethodGet, "/ping", func(c *api.Context) {
+			c.String(http.StatusOK, "pong")
+		}, func(c *api.Context) {
+			fmt.Println("Versioned Route Before1")
+			c.Next()
+			fmt.Println("Versioned Route After1")
+		}, func(c *api.Context) {
+			fmt.Println("Versioned Route Before2")
+			c.Next()
+			fmt.Println("Versioned Route After2")
+		})
+
+	go func() {
+		if err := app.Start(); err != nil {
+			panic(err)
+		}
+	}()
+
+	defer func() {
+		if err := app.Finish(); err != nil {
+			panic(err)
+		}
+	}()
 
+	death := DEATH.NewDeath(syscall.SIGINT, syscall.SIGTERM)
+	_ = death.WaitForDeath()
 }

+ 44 - 1
go.mod

@@ -1,3 +1,46 @@
-module git.sxidc.com/go-framework/baize.git
+module git.sxidc.com/go-framework/baize
 
 go 1.22.3
+
+require (
+	git.sxidc.com/go-tools/utils v1.5.7
+	git.sxidc.com/service-supports/fslog v0.5.9
+	github.com/gin-gonic/gin v1.10.0
+	github.com/pkg/errors v0.9.1
+	github.com/vrecan/death v3.0.1+incompatible
+	go.uber.org/zap v1.27.0
+	gopkg.in/natefinch/lumberjack.v2 v2.2.1
+	gopkg.in/yaml.v3 v3.0.1
+)
+
+require (
+	github.com/bytedance/sonic v1.11.6 // indirect
+	github.com/bytedance/sonic/loader v0.1.1 // indirect
+	github.com/cihub/seelog v0.0.0-20170130134532-f561c5e57575 // indirect
+	github.com/cloudwego/base64x v0.1.4 // indirect
+	github.com/cloudwego/iasm v0.2.0 // indirect
+	github.com/gabriel-vasile/mimetype v1.4.3 // indirect
+	github.com/gin-contrib/sse v0.1.0 // indirect
+	github.com/go-playground/locales v0.14.1 // indirect
+	github.com/go-playground/universal-translator v0.18.1 // indirect
+	github.com/go-playground/validator/v10 v10.20.0 // indirect
+	github.com/goccy/go-json v0.10.2 // indirect
+	github.com/json-iterator/go v1.1.12 // indirect
+	github.com/klauspost/cpuid/v2 v2.2.7 // indirect
+	github.com/leodido/go-urn v1.4.0 // indirect
+	github.com/mattn/go-isatty v0.0.20 // indirect
+	github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
+	github.com/modern-go/reflect2 v1.0.2 // indirect
+	github.com/pelletier/go-toml/v2 v2.2.2 // indirect
+	github.com/satori/go.uuid v1.2.0 // indirect
+	github.com/smartystreets/goconvey v1.8.1 // indirect
+	github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
+	github.com/ugorji/go/codec v1.2.12 // indirect
+	go.uber.org/multierr v1.11.0 // indirect
+	golang.org/x/arch v0.8.0 // indirect
+	golang.org/x/crypto v0.23.0 // indirect
+	golang.org/x/net v0.25.0 // indirect
+	golang.org/x/sys v0.20.0 // indirect
+	golang.org/x/text v0.15.0 // indirect
+	google.golang.org/protobuf v1.34.1 // indirect
+)

+ 117 - 0
go.sum

@@ -0,0 +1,117 @@
+git.sxidc.com/go-tools/utils v1.5.7 h1:vVtOfbyZPdmogyWGti2hqctv3uy+2R6Rqg88Ge3CCUg=
+git.sxidc.com/go-tools/utils v1.5.7/go.mod h1:fkobAXFpOMTvkZ82TQXWcpsayePcyk/MS5TN6GTlRDg=
+git.sxidc.com/service-supports/fslog v0.5.9 h1:q2XIK2o/fk/qmByy4x5kKLC+k7kolT5LrXHcWRSffXQ=
+git.sxidc.com/service-supports/fslog v0.5.9/go.mod h1:/m03ATmmOle75qtEgvEw8a1+Dcg6iHp08M1bGFXJTBU=
+github.com/bytedance/sonic v1.11.6 h1:oUp34TzMlL+OY1OUWxHqsdkgC/Zfc85zGqw9siXjrc0=
+github.com/bytedance/sonic v1.11.6/go.mod h1:LysEHSvpvDySVdC2f87zGWf6CIKJcAvqab1ZaiQtds4=
+github.com/bytedance/sonic/loader v0.1.1 h1:c+e5Pt1k/cy5wMveRDyk2X4B9hF4g7an8N3zCYjJFNM=
+github.com/bytedance/sonic/loader v0.1.1/go.mod h1:ncP89zfokxS5LZrJxl5z0UJcsk4M4yY2JpfqGeCtNLU=
+github.com/cihub/seelog v0.0.0-20170130134532-f561c5e57575 h1:kHaBemcxl8o/pQ5VM1c8PVE1PubbNx3mjUr09OqWGCs=
+github.com/cihub/seelog v0.0.0-20170130134532-f561c5e57575/go.mod h1:9d6lWj8KzO/fd/NrVaLscBKmPigpZpn5YawRPw+e3Yo=
+github.com/cloudwego/base64x v0.1.4 h1:jwCgWpFanWmN8xoIUHa2rtzmkd5J2plF/dnLS6Xd/0Y=
+github.com/cloudwego/base64x v0.1.4/go.mod h1:0zlkT4Wn5C6NdauXdJRhSKRlJvmclQ1hhJgA0rcu/8w=
+github.com/cloudwego/iasm v0.2.0 h1:1KNIy1I1H9hNNFEEH3DVnI4UujN+1zjpuk6gwHLTssg=
+github.com/cloudwego/iasm v0.2.0/go.mod h1:8rXZaNYT2n95jn+zTI1sDr+IgcD2GVs0nlbbQPiEFhY=
+github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
+github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/gabriel-vasile/mimetype v1.4.3 h1:in2uUcidCuFcDKtdcBxlR0rJ1+fsokWf+uqxgUFjbI0=
+github.com/gabriel-vasile/mimetype v1.4.3/go.mod h1:d8uq/6HKRL6CGdk+aubisF/M5GcPfT7nKyLpA0lbSSk=
+github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE=
+github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
+github.com/gin-gonic/gin v1.10.0 h1:nTuyha1TYqgedzytsKYqna+DfLos46nTv2ygFy86HFU=
+github.com/gin-gonic/gin v1.10.0/go.mod h1:4PMNQiOhvDRa013RKVbsiNwoyezlm2rm0uX/T7kzp5Y=
+github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s=
+github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
+github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA=
+github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY=
+github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY=
+github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
+github.com/go-playground/validator/v10 v10.20.0 h1:K9ISHbSaI0lyB2eWMPJo+kOS/FBExVwjEviJTixqxL8=
+github.com/go-playground/validator/v10 v10.20.0/go.mod h1:dbuPbCMFw/DrkbEynArYaCwl3amGuJotoKCe95atGMM=
+github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU=
+github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
+github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU=
+github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
+github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
+github.com/gopherjs/gopherjs v1.17.2 h1:fQnZVsXk8uxXIStYb0N4bGk7jeyTalG/wsZjQ25dO0g=
+github.com/gopherjs/gopherjs v1.17.2/go.mod h1:pRRIvn/QzFLrKfvEz3qUuEhtE/zLCWfreZ6J5gM2i+k=
+github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
+github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
+github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo=
+github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
+github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
+github.com/klauspost/cpuid/v2 v2.2.7 h1:ZWSB3igEs+d0qvnxR/ZBzXVmxkgt8DdzP6m9pfuVLDM=
+github.com/klauspost/cpuid/v2 v2.2.7/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws=
+github.com/knz/go-libedit v1.10.1/go.mod h1:MZTVkCWyz0oBc7JOWP3wNAzd002ZbM/5hgShxwh4x8M=
+github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ=
+github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI=
+github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
+github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
+github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
+github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
+github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
+github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
+github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
+github.com/pelletier/go-toml/v2 v2.2.2 h1:aYUidT7k73Pcl9nb2gScu7NSrKCSHIDE89b3+6Wq+LM=
+github.com/pelletier/go-toml/v2 v2.2.2/go.mod h1:1t835xjRzz80PqgE6HHgN2JOsmgYu/h4qDAS4n929Rs=
+github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
+github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
+github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
+github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
+github.com/satori/go.uuid v1.2.0 h1:0uYX9dsZ2yD7q2RtLRtPSdGDWzjeM3TbMJP9utgA0ww=
+github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0=
+github.com/smarty/assertions v1.15.0 h1:cR//PqUBUiQRakZWqBiFFQ9wb8emQGDb0HeGdqGByCY=
+github.com/smarty/assertions v1.15.0/go.mod h1:yABtdzeQs6l1brC900WlRNwj6ZR55d7B+E8C6HtKdec=
+github.com/smartystreets/goconvey v1.8.1 h1:qGjIddxOk4grTu9JPOU31tVfq3cNdBlNa5sSznIX1xY=
+github.com/smartystreets/goconvey v1.8.1/go.mod h1:+/u4qLyY6x1jReYOp7GOM2FSt8aP9CzCZL03bI28W60=
+github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
+github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
+github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
+github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
+github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
+github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
+github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
+github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
+github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
+github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
+github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
+github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
+github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI=
+github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08=
+github.com/ugorji/go/codec v1.2.12 h1:9LC83zGrHhuUA9l16C9AHXAqEV/2wBQ4nkvumAE65EE=
+github.com/ugorji/go/codec v1.2.12/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg=
+github.com/vrecan/death v3.0.1+incompatible h1:hYRRqrdyoUAbymk2KJ8tNHmZFKcVeThRUySCqwC5Itg=
+github.com/vrecan/death v3.0.1+incompatible/go.mod h1:ektTae4lwvcXJ7pytrLb2N0w7mwhzmu+f5vRHYzy33E=
+go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
+go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
+go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
+go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
+go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8=
+go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E=
+golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8=
+golang.org/x/arch v0.8.0 h1:3wRIsP3pM4yUptoR96otTUOXI367OS0+c9eeRi9doIc=
+golang.org/x/arch v0.8.0/go.mod h1:FEVrYAQjsQXMVJ1nsMoVVXPZg6p2JE2mx8psSWTDQys=
+golang.org/x/crypto v0.23.0 h1:dIJU/v2J8Mdglj/8rJ6UUOM3Zc9zLZxVZwwxMooUSAI=
+golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8=
+golang.org/x/net v0.25.0 h1:d/OCCoBEUq33pjydKrGQhw7IlUPI2Oylr+8qLx49kac=
+golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM=
+golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.20.0 h1:Od9JTbYCk261bKm4M/mw7AklTlFYIa0bIp9BgSm1S8Y=
+golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
+golang.org/x/text v0.15.0 h1:h1V/4gjBv8v9cjcR6+AR5+/cIYK5N/WAgiv4xlsEtAk=
+golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
+golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=
+golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+google.golang.org/protobuf v1.34.1 h1:9ddQBjfCyZPOHPUiPxpYESBLc+T8P3E+Vo4IbKZgFWg=
+google.golang.org/protobuf v1.34.1/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
+gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
+gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+gopkg.in/natefinch/lumberjack.v2 v2.2.1 h1:bBRl1b0OH9s/DuPhuXpNl+VtCaJXFZ5/uEFST95x9zc=
+gopkg.in/natefinch/lumberjack.v2 v2.2.1/go.mod h1:YD8tP3GAjkrDg1eZH7EGmyESg/lsYskCTPBJVb9jqSc=
+gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
+gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
+gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
+nullprogram.com/x/optparse v1.0.0/go.mod h1:KdyPE+Igbe0jQUrVfMqDMeJQIJZEuyV7pjYmp6pbG50=
+rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4=

+ 12 - 0
logger/level.go

@@ -0,0 +1,12 @@
+package logger
+
+import (
+	"go.uber.org/zap/zapcore"
+)
+
+const (
+	DebugLv = zapcore.DebugLevel
+	InfoLv  = zapcore.InfoLevel
+	WarnLv  = zapcore.WarnLevel
+	ErrorLv = zapcore.ErrorLevel
+)

+ 208 - 0
logger/logger.go

@@ -0,0 +1,208 @@
+package logger
+
+import (
+	"fmt"
+	"go.uber.org/zap"
+	"go.uber.org/zap/zapcore"
+	"gopkg.in/natefinch/lumberjack.v2"
+	"io"
+	"sync"
+)
+
+// Logger 是对 zap.SugaredLogger 的封装,简化其对外使用的接口,
+// 并且针对于 Logger 所有配置都是支持运行时动态修改,且并发安全。
+// Logger 也支持了针对于 zapcore.Core 的扩展,调用 Logger.AddCore 即可,
+//
+// 构建该对象请使用 New 进行创建,在该操作中会对部分必要属性进行初始化,
+// 直接使用结构体创建会导致结构体不可用(甚至panic)。
+//
+// 如非深度定制化扩展,非必要不建议使用 Logger.AddCore 进行扩展,该操作会
+// 导致客户端应用程序对zap包编译依赖,不保证fslog切换内部日志实现。
+type Logger struct {
+	// 日志打印用
+	Logger zap.SugaredLogger
+
+	// 互斥量
+	// 用于内部不可并发逻辑使用
+	lock *sync.Mutex
+
+	// 当前日志打印级别
+	// 借助zap内部级别设置机制,该机制
+	// 内部使用乐观锁,协程安全
+	lv zap.AtomicLevel
+
+	// 所有的core
+	cores []zapcore.Core
+
+	writers []io.Writer
+	*Options
+}
+
+func New(opts ...Option) *Logger {
+	options := new(Options)
+
+	if opts == nil || len(opts) == 0 {
+		opts = []Option{WithSkipCaller(1)}
+	}
+
+	for _, opt := range opts {
+		opt(options)
+	}
+
+	logger := &Logger{
+		lock:    &sync.Mutex{},
+		lv:      zap.NewAtomicLevelAt(zap.DebugLevel),
+		Options: options,
+	}
+
+	logger.flushLogger()
+
+	return logger
+}
+
+// Debug 格式化打印调试级别日志
+// 不同于zap内部可变参数逻辑,该可变参数是用于,字符串格式化的
+func (l *Logger) Debug(msg string, vs ...any) {
+	if len(vs) == 0 {
+		l.Logger.Debug(msg)
+		return
+	}
+	l.Logger.Debugf(msg, vs...)
+}
+
+// Info 格式化打印信息级别日志
+// 不同于zap内部可变参数逻辑,该可变参数是用于,字符串格式化的
+func (l *Logger) Info(msg string, vs ...any) {
+	if len(vs) == 0 {
+		l.Logger.Info(msg)
+		return
+	}
+	l.Logger.Infof(msg, vs...)
+}
+
+// Warn 格式化打印警告级别日志
+// 不同于zap内部可变参数逻辑,该可变参数是用于,字符串格式化的
+func (l *Logger) Warn(msg string, vs ...any) {
+	if len(vs) == 0 {
+		l.Logger.Warn(msg)
+		return
+	}
+	l.Logger.Warnf(msg, vs...)
+}
+
+// Error 打印错误级别日志
+// 该方法具有两种传参形式:
+//  1. error类型:会直接格式化打印%+v日志
+//  2. 信息(格式化)
+//  3. error+格式化信息:error会作为 With 格式存在,且依旧以%+v格式输出
+func (l *Logger) Error(vs ...any) {
+	if len(vs) == 0 {
+		return
+	}
+
+	err, ok := vs[0].(error)
+	if ok {
+		if len(vs) == 1 {
+			l.Logger.Errorf("%+v", err)
+			return
+		}
+
+		withed := l.Logger.With(zap.String("err", fmt.Sprintf("%+v", err)))
+		msg, ok := vs[1].(string)
+		if ok {
+			withed.Errorf(msg, vs[2:])
+			return
+		}
+
+		withed.Error(vs[1:])
+		return
+	}
+
+	msg, ok := vs[0].(string)
+	if ok {
+		if len(vs) > 1 {
+			l.Logger.Errorf(msg, vs[1:]...)
+			return
+		}
+
+		if len(vs) == 1 {
+			l.Logger.Error(vs[0])
+			return
+		}
+	}
+}
+
+// Lv 获取当前日志打印级别
+func (l *Logger) Lv() int8 {
+	return int8(l.lv.Level())
+}
+
+// SetLv 设置当前日志打印级别
+func (l *Logger) SetLv(lv int8) {
+	l.lv.SetLevel(zapcore.Level(lv))
+}
+
+// NewFileOutput 新增日志输出文件配置
+func (l *Logger) NewFileOutput(opts ...FileOutputOpt) {
+	cfg := new(OutFileOptions)
+	for _, opt := range opts {
+		opt(cfg)
+	}
+	l.NewOutput(&lumberjack.Logger{
+		Filename:   cfg.filename,
+		MaxSize:    cfg.maxSize,
+		MaxAge:     cfg.maxAge,
+		MaxBackups: cfg.maxBackups,
+		LocalTime:  cfg.localTime,
+		Compress:   cfg.compress,
+	})
+}
+
+// NewOutput 新增日志输出位置
+func (l *Logger) NewOutput(writer io.Writer) {
+	cfg := zap.NewProductionEncoderConfig()
+	cfg.EncodeTime = zapcore.TimeEncoderOfLayout("2006-01-02 15:04:05")
+	l.writers = append(l.writers, writer)
+	l.AddCore(zapcore.NewCore(zapcore.NewJSONEncoder(cfg), zapcore.AddSync(writer), l.lv))
+}
+
+// AddCore 添加Core
+func (l *Logger) AddCore(core ...zapcore.Core) {
+	l.addCoreOnly(core...)
+	l.flushLogger()
+}
+
+// Flush 将缓冲区日志刷新至目标
+func (l *Logger) Flush() {
+	_ = l.Logger.Sync()
+}
+
+// 保存内部core(不负责刷新 logger)
+func (l *Logger) addCoreOnly(core ...zapcore.Core) {
+	l.lock.Lock()
+	defer l.lock.Unlock()
+	l.cores = append(l.cores, core...)
+}
+
+func (l *Logger) clone() *Logger {
+	newL := &Logger{cores: l.cores, lv: l.lv, Options: l.Options}
+	newL.flushLogger()
+	return newL
+}
+
+// 刷新内部 logger
+// 刷新互斥,触发刷新后,刷新完成前依旧按照旧的配置执行
+func (l *Logger) flushLogger() {
+	l.lock.Lock()
+	defer l.lock.Unlock()
+	l.Logger = *zap.
+		New(
+			zapcore.NewTee(l.cores...),
+			zap.AddCaller(),
+		).
+		Sugar().
+		WithOptions(
+			zap.AddCallerSkip(l.skipCaller),
+			zap.WithCaller(true),
+		)
+}

+ 73 - 0
logger/opt_file.go

@@ -0,0 +1,73 @@
+package logger
+
+type OutFileOptions struct {
+	filename   string
+	maxSize    int
+	maxAge     int
+	maxBackups int
+	localTime  bool
+	compress   bool
+}
+
+func (options OutFileOptions) Filename() string {
+	return options.filename
+}
+
+func (options OutFileOptions) MaxSize() int {
+	return options.maxSize
+}
+
+func (options OutFileOptions) MaxAge() int {
+	return options.maxAge
+}
+
+func (options OutFileOptions) MaxBackups() int {
+	return options.maxBackups
+}
+
+func (options OutFileOptions) LocalTime() bool {
+	return options.localTime
+}
+
+func (options OutFileOptions) Compress() bool {
+	return options.compress
+}
+
+// FileOutputOpt 文件输出选项
+type FileOutputOpt func(*OutFileOptions)
+
+func WithFilename(filename string) FileOutputOpt {
+	return func(c *OutFileOptions) {
+		c.filename = filename
+	}
+}
+
+func WithMaxSize(maxSize int) FileOutputOpt {
+	return func(c *OutFileOptions) {
+		c.maxSize = maxSize
+	}
+}
+
+func WithMaxAge(maxAge int) FileOutputOpt {
+	return func(c *OutFileOptions) {
+		c.maxAge = maxAge
+	}
+}
+
+func WithMaxBackups(maxBackups int) FileOutputOpt {
+	return func(c *OutFileOptions) {
+		c.maxBackups = maxBackups
+	}
+}
+
+func WithLocalTime(localTime bool) FileOutputOpt {
+	return func(c *OutFileOptions) {
+		c.localTime = localTime
+	}
+}
+
+func WithCompress(compress bool) FileOutputOpt {
+	return func(c *OutFileOptions) {
+		c.compress = compress
+	}
+}

+ 17 - 0
logger/opt_logger.go

@@ -0,0 +1,17 @@
+package logger
+
+type Options struct {
+	skipCaller int
+}
+
+func (options Options) SkipCaller() int {
+	return options.skipCaller
+}
+
+type Option func(*Options)
+
+func WithSkipCaller(skip int) Option {
+	return func(config *Options) {
+		config.skipCaller = skip
+	}
+}