diff --git a/config.go b/config.go index 99669b7..f9f407a 100644 --- a/config.go +++ b/config.go @@ -3,5 +3,6 @@ package upp import "context" type Config struct { - Ctx context.Context + Ctx context.Context + Debug bool } diff --git a/flag.go b/flag.go new file mode 100644 index 0000000..073b1e8 --- /dev/null +++ b/flag.go @@ -0,0 +1,24 @@ +package upp + +import ( + "flag" + "time" +) + +type __flag struct { + debug bool + listen struct { + http string + } +} + +var _flag = &__flag{} + +func init() { + time.Local = time.FixedZone("CST", 8*3600) + + flag.BoolVar(&_flag.debug, "debug", false, "debug mode") + flag.StringVar(&_flag.listen.http, "listen.http", "localhost:8080", "") + + flag.Parse() +} diff --git a/go.mod b/go.mod index 59d43b1..ed88d1d 100644 --- a/go.mod +++ b/go.mod @@ -14,6 +14,7 @@ require ( github.com/loveuer/nf v0.3.1 github.com/samber/lo v1.47.0 github.com/spf13/cast v1.7.1 + github.com/spf13/cobra v1.8.1 golang.org/x/crypto v0.25.0 gorm.io/driver/mysql v1.5.7 gorm.io/driver/postgres v1.5.11 @@ -27,6 +28,7 @@ require ( github.com/dustin/go-humanize v1.0.1 // indirect github.com/glebarez/go-sqlite v1.21.2 // indirect github.com/go-sql-driver/mysql v1.8.1 // indirect + github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/jackc/pgpassfile v1.0.0 // indirect github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a // indirect github.com/jackc/pgx/v5 v5.5.5 // indirect @@ -38,6 +40,7 @@ require ( github.com/mattn/go-runewidth v0.0.15 // indirect github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect github.com/rivo/uniseg v0.2.0 // indirect + github.com/spf13/pflag v1.0.5 // indirect golang.org/x/sync v0.10.0 // indirect golang.org/x/sys v0.25.0 // indirect golang.org/x/text v0.21.0 // indirect diff --git a/go.sum b/go.sum index ecb862f..37ed91f 100644 --- a/go.sum +++ b/go.sum @@ -4,6 +4,7 @@ gitea.com/loveuer/gredis v1.0.0 h1:fbRS8YZObcp1KV1KGj8pDpIj1WrI0W8pwU9Ny/2fJys= gitea.com/loveuer/gredis v1.0.0/go.mod h1:TQlubgDiyNTRXqASd/XIUrqPBLj9NZRR2DmV3V2ZyMY= github.com/cespare/xxhash/v2 v2.1.2 h1:YRXhKfTDauu4ajMg1TPgFO5jnlC2HCbmLXMcTG5cbYE= github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= 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/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78= @@ -31,6 +32,8 @@ github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs4luLUK2k= github.com/hashicorp/golang-lru/v2 v2.0.7/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM= +github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= +github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM= github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg= github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a h1:bbPeKD0xmW/Y25WS6cokEszi5g+S0QxI/d45PkRi7Nk= @@ -67,10 +70,15 @@ github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qq github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY= github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8= +github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/samber/lo v1.47.0 h1:z7RynLwP5nbyRscyvcD043DWYoOcYRv3mV8lBeqOCLc= github.com/samber/lo v1.47.0/go.mod h1:RmDH9Ct32Qy3gduHQuKJ3gW1fMHAnE/fAzQuf6He5cU= github.com/spf13/cast v1.7.1 h1:cuNEagBQEHWN1FnbGEjCXL2szYEXqfJPbP2HNUaca9Y= github.com/spf13/cast v1.7.1/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo= +github.com/spf13/cobra v1.8.1 h1:e5/vxKd/rZsfSJMUX1agtjeTDf+qv1/JdBF8gg5k9ZM= +github.com/spf13/cobra v1.8.1/go.mod h1:wHxEcudfqmLYa8iTfL+OuZPbBZkmvliBWKIezN3kD9Y= +github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= +github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 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= @@ -91,6 +99,7 @@ gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkep gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= 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= gorm.io/driver/mysql v1.5.7 h1:MndhOPYOfEp2rHKgkZIhJ16eVUIRf2HmzgoPmh7FCWo= gorm.io/driver/mysql v1.5.7/go.mod h1:sEtPWMiqiN1N1cMXoXmBbd8C6/l+TESwriotuRRpkDM= gorm.io/driver/postgres v1.5.11 h1:ubBVAfbKEUld/twyKZ0IYn9rSQh448EdelLYk9Mv314= diff --git a/pkg/cache/cache.go b/pkg/cache/cache.go index f6d90b4..cd1b84c 100644 --- a/pkg/cache/cache.go +++ b/pkg/cache/cache.go @@ -19,6 +19,7 @@ type Cache interface { // SetEx value 会被序列化, 优先使用 MarshalBinary 方法, 没有则执行 json.Marshal SetEx(ctx context.Context, key string, value any, duration time.Duration) error Del(ctx context.Context, keys ...string) error + Close() error } type Scanner interface { diff --git a/pkg/cache/cache.lru.go b/pkg/cache/cache.lru.go index 72d177b..f3f76f5 100644 --- a/pkg/cache/cache.lru.go +++ b/pkg/cache/cache.lru.go @@ -134,6 +134,11 @@ func (l *_lru) Del(ctx context.Context, keys ...string) error { return nil } +func (l *_lru) Close() error { + l.client = nil + return nil +} + func newLRUCache() (Cache, error) { client := expirable.NewLRU[string, *_lru_value](1024*1024, nil, 0) diff --git a/pkg/cache/cache.memory.go b/pkg/cache/cache.memory.go index f759a1b..fb5ec1b 100644 --- a/pkg/cache/cache.memory.go +++ b/pkg/cache/cache.memory.go @@ -103,3 +103,9 @@ func (m *_mem) Del(ctx context.Context, keys ...string) error { m.client.Delete(keys...) return nil } + +func (m *_mem) Close() error { + m.client = nil + + return nil +} diff --git a/pkg/cache/cache.redis.go b/pkg/cache/cache.redis.go index 6d6d207..ccca10f 100644 --- a/pkg/cache/cache.redis.go +++ b/pkg/cache/cache.redis.go @@ -104,3 +104,7 @@ func (r *_redis) SetEx(ctx context.Context, key string, value any, duration time func (r *_redis) Del(ctx context.Context, keys ...string) error { return r.client.Del(ctx, keys...).Err() } + +func (r *_redis) Close() error { + return r.client.Close() +} diff --git a/pkg/interfaces/upp.go b/pkg/interfaces/upp.go index 8f626eb..f97dbcf 100644 --- a/pkg/interfaces/upp.go +++ b/pkg/interfaces/upp.go @@ -9,6 +9,7 @@ import ( ) type Upp interface { + Debug() bool UseCtx() context.Context UseDB(ctx ...context.Context) *gorm.DB UseCache() cache.Cache diff --git a/readme.md b/readme.md index 139c81b..b95e015 100644 --- a/readme.md +++ b/readme.md @@ -2,10 +2,42 @@ ### Usage +> usage present ```go app := upp.New() app.With(db, es, api) app.Run(ctx) +``` + +> simple example +```go +type Record struct { + Id uint64 `json:"id" gorm:"primaryKey;column:id"` + CreatedAt int64 `json:"created_at" gorm:"column:created_at;autoCreateTime:milli"` + Name string `json:"name" gorm:"column:name"` +} + +func main() { + app := upp.New() + + app.With(upp.InitDB("sqlite://data.db", &Record{})) + app.With(upp.InitApi(api.New())) + + app.GET("/hello/:name", func(c *api.Ctx) error { + name := c.Param("name") + c.UseLogger().Info("[hello] got name = %s", name) + record := &Record{Name: name} + err := c.UseDB().Create(record).Error + return c.JSON(map[string]any{"record": record, "err": err}) + }) + + app.RunSignal() +} + +``` +> run with flags +```sh +go run . --debug --listen.http '0.0.0.0:8080' ``` \ No newline at end of file diff --git a/run.go b/run.go new file mode 100644 index 0000000..c563e5f --- /dev/null +++ b/run.go @@ -0,0 +1,88 @@ +package upp + +import ( + "context" + "os/signal" + "syscall" + + "github.com/loveuer/upp/pkg/interfaces" + "github.com/loveuer/upp/pkg/tool" +) + +func (u *upp) StartAPI(ctx context.Context) { + address := _flag.listen.http + if address == "" { + address = u.api.config.Address + } + + u.UseLogger().Info("UPP | run api at %s", address) + go u.api.engine.Run(address) + go func() { + <-ctx.Done() + u.api.engine.Shutdown(tool.Timeout(2)) + }() +} + +func (u *upp) StartTask(ctx context.Context) { + for _, _ch := range u.taskCh { + go func(ch <-chan func(interfaces.Upp) error) { + var err error + for { + select { + case <-ctx.Done(): + case task, ok := <-ch: + if !ok { + return + } + + if err = task(u); err != nil { + u.UseLogger(ctx).Error(err.Error()) + } + } + } + }(_ch) + } +} + +func (u *upp) Run(ctx context.Context) { + u.RunSignal(ctx) +} + +func (u *upp) RunInitFns(ctx context.Context) { + for _, fn := range u.initFns { + fn(u) + } +} + +func (u *upp) RunSignal(ctxs ...context.Context) { + c := context.Background() + if len(ctxs) > 0 { + c = ctxs[0] + } + + ctx, cancel := signal.NotifyContext(c, syscall.SIGINT, syscall.SIGTERM, syscall.SIGQUIT) + defer cancel() + + u.ctx = ctx + + if len(u.initFns) > 0 { + u.RunInitFns(ctx) + } + + if u.api != nil { + u.StartAPI(ctx) + } + + if len(u.taskCh) > 0 { + u.StartTask(ctx) + } + + <-ctx.Done() + + u.UseLogger().Warn(" UPP | quit by signal...") + if u.cache != nil { + u.cache.Close() + } + + <-tool.Timeout(2).Done() +} diff --git a/upp.go b/upp.go index 966569f..8742868 100644 --- a/upp.go +++ b/upp.go @@ -2,15 +2,13 @@ package upp import ( "context" - "os/signal" "sync" - "syscall" "github.com/elastic/go-elasticsearch/v7" "github.com/loveuer/upp/pkg/api" "github.com/loveuer/upp/pkg/cache" "github.com/loveuer/upp/pkg/interfaces" - "github.com/loveuer/upp/pkg/tool" + "github.com/loveuer/upp/pkg/log" "gorm.io/gorm" ) @@ -37,79 +35,20 @@ func (u *upp) With(modules ...module) { } } -func (u *upp) Run(ctx context.Context) { - u.RunSignal(ctx) -} - -func (u *upp) RunInitFns(ctx context.Context) { - for _, fn := range u.initFns { - fn(u) - } -} - -func (u *upp) RunSignal(ctxs ...context.Context) { - c := context.Background() - if len(ctxs) > 0 { - c = ctxs[0] - } - - ctx, cancel := signal.NotifyContext(c, syscall.SIGHUP, syscall.SIGINT, syscall.SIGTERM, syscall.SIGQUIT) - defer cancel() - - u.ctx = ctx - - if len(u.initFns) > 0 { - u.RunInitFns(ctx) - } - - if u.api != nil { - u.StartAPI(ctx) - } - - if len(u.taskCh) > 0 { - u.StartTask(ctx) - } - - <-ctx.Done() - - u.UseLogger().Warn(" UPP | quit by signal...") - - <-tool.Timeout(2).Done() -} - -func (u *upp) StartAPI(ctx context.Context) { - u.UseLogger().Info("UPP | run api at %s", u.api.config.Address) - go u.api.engine.Run(u.api.config.Address) - go func() { - <-ctx.Done() - u.api.engine.Shutdown(tool.Timeout(2)) - }() -} - -func (u *upp) StartTask(ctx context.Context) { - for _, _ch := range u.taskCh { - go func(ch <-chan func(interfaces.Upp) error) { - var err error - for { - select { - case <-ctx.Done(): - case task, ok := <-ch: - if !ok { - return - } - - if err = task(u); err != nil { - u.UseLogger(ctx).Error(err.Error()) - } - } - } - }(_ch) - } -} - func New(configs ...Config) *upp { + config := Config{} + + if len(configs) > 0 { + config = configs[0] + } + + if config.Debug || _flag.debug { + log.SetLogLevel(log.LogLevelDebug) + } + app := &upp{ logger: upp_logger_pool, + debug: config.Debug, } return app diff --git a/implement.go b/use.go similarity index 92% rename from implement.go rename to use.go index 057cfc5..15030e9 100644 --- a/implement.go +++ b/use.go @@ -10,6 +10,10 @@ import ( "gorm.io/gorm" ) +func (u *upp) Debug() bool { + return u.debug +} + func (u *upp) UseCtx() context.Context { return u.ctx } @@ -27,7 +31,7 @@ func (u *upp) UseDB(ctx ...context.Context) *gorm.DB { Context: c, }) - if u.debug { + if u.Debug() { tx = tx.Debug() }