From b926a8fb80773931731fea11f37225c26a0d5148 Mon Sep 17 00:00:00 2001 From: loveuer Date: Fri, 11 Jul 2025 22:12:37 +0800 Subject: [PATCH] wip: user stuff --- go.mod | 2 + go.sum | 4 + internal/api/api.go | 29 +++++++ internal/cmd/svc.go | 4 +- internal/handler/login.go | 14 ++++ internal/model/{model.go => init.go} | 2 + internal/model/object.go | 10 +++ internal/model/todo.go | 2 +- internal/model/user.go | 14 ++++ pkg/api/api.go | 5 +- pkg/logger/ctx.go | 56 +++++++++++++ pkg/logger/default.go | 97 ++++++++++++++++++++++ pkg/logger/logger.go | 116 +++++++++++++++++++++++++++ pkg/logger/new.go | 21 +++++ pkg/middleware/logger/logger.go | 9 +++ pkg/middleware/trace/trace.go | 65 +++++++++++++++ 16 files changed, 447 insertions(+), 3 deletions(-) create mode 100644 internal/api/api.go create mode 100644 internal/handler/login.go rename internal/model/{model.go => init.go} (85%) create mode 100644 internal/model/object.go create mode 100644 internal/model/user.go create mode 100644 pkg/logger/ctx.go create mode 100644 pkg/logger/default.go create mode 100644 pkg/logger/logger.go create mode 100644 pkg/logger/new.go create mode 100644 pkg/middleware/logger/logger.go create mode 100644 pkg/middleware/trace/trace.go diff --git a/go.mod b/go.mod index 39f3287..855fe9b 100644 --- a/go.mod +++ b/go.mod @@ -62,6 +62,8 @@ require ( github.com/valyala/fasthttp v1.58.0 // indirect github.com/valyala/tcplisten v1.0.0 // indirect github.com/x448/float16 v0.8.4 // indirect + go.uber.org/multierr v1.11.0 // indirect + go.uber.org/zap v1.27.0 // indirect golang.org/x/arch v0.8.0 // indirect golang.org/x/crypto v0.31.0 // indirect golang.org/x/net v0.31.0 // indirect diff --git a/go.sum b/go.sum index 197641c..340d7f5 100644 --- a/go.sum +++ b/go.sum @@ -148,6 +148,10 @@ github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM= github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg= github.com/xyproto/randomstring v1.0.5 h1:YtlWPoRdgMu3NZtP45drfy1GKoojuR7hmRcnhZqKjWU= github.com/xyproto/randomstring v1.0.5/go.mod h1:rgmS5DeNXLivK7YprL0pY+lTuhNQW3iGxZ18UQApw/E= +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= diff --git a/internal/api/api.go b/internal/api/api.go new file mode 100644 index 0000000..e7a5850 --- /dev/null +++ b/internal/api/api.go @@ -0,0 +1,29 @@ +package api + +import ( + "context" + "loveuer/utodo/internal/handler" + "loveuer/utodo/internal/opt" + g_handler "loveuer/utodo/pkg/handler" + "loveuer/utodo/pkg/middleware/trace" + + "github.com/gofiber/fiber/v3" + l3 "github.com/gofiber/fiber/v3/middleware/logger" + r3 "github.com/gofiber/fiber/v3/middleware/recover" +) + +func New(ctx context.Context) *fiber.App { + app := fiber.New() + app.Use(trace.New()) + app.Use(l3.New()) + app.Use(r3.New()) + + app.Get("/healthz", g_handler.Healthz(opt.Cfg.Name, opt.Cfg.Version)) + + { + api := app.Group("/api/v1/auth") + api.Post("/login", handler.Login()) + } + + return app +} diff --git a/internal/cmd/svc.go b/internal/cmd/svc.go index 17c1eed..235a820 100644 --- a/internal/cmd/svc.go +++ b/internal/cmd/svc.go @@ -2,6 +2,7 @@ package cmd import ( "context" + "loveuer/utodo/internal/api" "loveuer/utodo/internal/model" "loveuer/utodo/internal/opt" g_api "loveuer/utodo/pkg/api" @@ -34,7 +35,7 @@ func svcRun(cmd *cobra.Command, args []string) error { return err } - if err = model.Init(cmd.Context(), db.Default.Session(tool.Timeout(60))); err != nil { + if err = model.Init(cmd.Context(), db.Default.Session(tool.Timeout(60), db.Config{Debug: opt.Cfg.Debug})); err != nil { return err } @@ -43,6 +44,7 @@ func svcRun(cmd *cobra.Command, args []string) error { g_api.WithAddress(opt.Cfg.Svc.Address), g_api.WithVersion(opt.Cfg.Version), g_api.WithName(opt.Cfg.Name), + g_api.WithApp(api.New(cmd.Context())), ); err != nil { return err } diff --git a/internal/handler/login.go b/internal/handler/login.go new file mode 100644 index 0000000..43f4ba9 --- /dev/null +++ b/internal/handler/login.go @@ -0,0 +1,14 @@ +package handler + +import ( + "loveuer/utodo/pkg/logger" + + "github.com/gofiber/fiber/v3" +) + +func Login() fiber.Handler { + return func(c fiber.Ctx) error { + logger.InfoCtx(c.Context(), "login") + return c.SendString("Hello, World!") + } +} diff --git a/internal/model/model.go b/internal/model/init.go similarity index 85% rename from internal/model/model.go rename to internal/model/init.go index dbbd297..402f1ec 100644 --- a/internal/model/model.go +++ b/internal/model/init.go @@ -9,5 +9,7 @@ import ( func Init(ctx context.Context, tx *gorm.DB) error { return tx.AutoMigrate( &Todo{}, + &User{}, + &Object{}, ) } diff --git a/internal/model/object.go b/internal/model/object.go new file mode 100644 index 0000000..d595042 --- /dev/null +++ b/internal/model/object.go @@ -0,0 +1,10 @@ +package model + +type Object struct { + Id int64 `json:"id" gorm:"column:id,primaryKey"` + CreatedAt int64 `json:"created_at" gorm:"column:created_at,autoCreateTime:milli"` + Key string `json:"key" gorm:"column:key;type:varchar(32);not null;unique"` + ContentType string `json:"content_type" gorm:"column:content_type;type:varchar(255);not null"` + Size int64 `json:"size" gorm:"column:size"` + Blob []byte `json:"-" gorm:"column:blob;type:blob"` +} diff --git a/internal/model/todo.go b/internal/model/todo.go index e461bdf..2fb387e 100644 --- a/internal/model/todo.go +++ b/internal/model/todo.go @@ -27,7 +27,7 @@ func (t TodoStatus) MarshalJSON() ([]byte, error) { } type Todo struct { - Id int64 `json:"id" gorm:"column:id,primaryKey;autoIncrement"` + Id int64 `json:"id" gorm:"column:id,primaryKey"` CreatedAt int64 `json:"created_at" gorm:"column:created_at,autoCreateTime:milli"` UpdatedAt int64 `json:"updated_at" gorm:"column:updated_at,autoUpdateTime:milli"` DeletedAt int64 `json:"deleted_at" gorm:"column:deleted_at"` diff --git a/internal/model/user.go b/internal/model/user.go new file mode 100644 index 0000000..4020e6b --- /dev/null +++ b/internal/model/user.go @@ -0,0 +1,14 @@ +package model + +type User struct { + Id int64 `json:"id" gorm:"column:id,primaryKey"` + CreatedAt int64 `json:"created_at" gorm:"column:created_at,autoCreateTime:milli"` + UpdatedAt int64 `json:"updated_at" gorm:"column:updated_at,autoUpdateTime:milli"` + DeletedAt int64 `json:"deleted_at" gorm:"column:deleted_at"` + Username string `json:"username" gorm:"column:username;type:varchar(255);not null"` + Nickname string `json:"nickname" gorm:"column:nickname;type:varchar(255);not null"` + Password string `json:"-" gorm:"column:password;type:varchar(255);not null"` + Avatar string `json:"avatar" gorm:"column:avatar;type:varchar(32);not null"` + Phone string `json:"phone" gorm:"column:phone;type:varchar(16);not null"` + Email string `json:"email" gorm:"column:email;type:varchar(255);not null"` +} diff --git a/pkg/api/api.go b/pkg/api/api.go index 024f82d..ff7423d 100644 --- a/pkg/api/api.go +++ b/pkg/api/api.go @@ -5,9 +5,9 @@ import ( "crypto/tls" "fmt" "loveuer/utodo/pkg/handler" + "loveuer/utodo/pkg/logger" "net" - "gitea.loveuer.com/yizhisec/packages/logger" "github.com/gofiber/fiber/v3" l3 "github.com/gofiber/fiber/v3/middleware/logger" r3 "github.com/gofiber/fiber/v3/middleware/recover" @@ -80,6 +80,9 @@ func Start(ctx context.Context, opts ...Option) (func(context.Context) error, er } if opt.app == nil { + logger.WarnCtx(ctx, "app is nil, use default app!!!") + logger.WarnCtx(ctx, "app is nil, use default app!!!") + logger.WarnCtx(ctx, "app is nil, use default app!!!") opt.app = fiber.New(fiber.Config{BodyLimit: 1024 * 1024 * 5}) opt.app.Use(l3.New()) opt.app.Use(r3.New()) diff --git a/pkg/logger/ctx.go b/pkg/logger/ctx.go new file mode 100644 index 0000000..fe9dad1 --- /dev/null +++ b/pkg/logger/ctx.go @@ -0,0 +1,56 @@ +package logger + +import ( + "context" + + uuid2 "github.com/google/uuid" +) + +type ctxKey struct{} + +var ( + CtxKey = ctxKey{} +) + +func traceId(ctx context.Context) string { + if ctx == nil { + uuid, _ := uuid2.NewV7() + return uuid.String() + } + + if id, _ := ctx.Value(CtxKey).(string); id != "" { + return id + } + + uuid, _ := uuid2.NewV7() + return uuid.String() +} + +func DebugCtx(ctx context.Context, msg string, data ...any) { + msg = traceId(ctx) + " | " + msg + DefaultLogger.Debug(msg, data...) +} +func InfoCtx(ctx context.Context, msg string, data ...any) { + msg = traceId(ctx) + " | " + msg + DefaultLogger.Info(msg, data...) +} + +func WarnCtx(ctx context.Context, msg string, data ...any) { + msg = traceId(ctx) + " | " + msg + DefaultLogger.Warn(msg, data...) +} + +func ErrorCtx(ctx context.Context, msg string, data ...any) { + msg = traceId(ctx) + " | " + msg + DefaultLogger.Error(msg, data...) +} + +func PanicCtx(ctx context.Context, msg string, data ...any) { + msg = traceId(ctx) + " | " + msg + DefaultLogger.Panic(msg, data...) +} + +func FatalCtx(ctx context.Context, msg string, data ...any) { + msg = traceId(ctx) + " | " + msg + DefaultLogger.Fatal(msg, data...) +} diff --git a/pkg/logger/default.go b/pkg/logger/default.go new file mode 100644 index 0000000..4caa63c --- /dev/null +++ b/pkg/logger/default.go @@ -0,0 +1,97 @@ +package logger + +import ( + "fmt" + "os" + "strings" + "sync" +) + +var ( + pool = &sync.Pool{ + New: func() any { + return &strings.Builder{} + }, + } + nilLogger = func(prefix, timestamp, msg string, data ...any) {} + normalLogger = func(prefix, timestamp, msg string, data ...any) { + buf := pool.Get().(*strings.Builder) + buf.Reset() + buf.WriteString(prefix) + buf.WriteString(" | ") + buf.WriteString(timestamp) + buf.WriteString(" | ") + buf.WriteString(msg) + buf.WriteString("\n") + fmt.Printf(buf.String(), data...) + } + + panicLogger = func(prefix, timestamp, msg string, data ...any) { + buf := pool.Get().(*strings.Builder) + buf.Reset() + buf.WriteString(prefix) + buf.WriteString(" | ") + buf.WriteString(timestamp) + buf.WriteString(" | ") + buf.WriteString(msg) + buf.WriteString("\n") + panic(buf.String()) + } + + fatalLogger = func(prefix, timestamp, msg string, data ...any) { + buf := pool.Get().(*strings.Builder) + buf.Reset() + buf.WriteString(prefix) + buf.WriteString(" | ") + buf.WriteString(timestamp) + buf.WriteString(" | ") + buf.WriteString(msg) + buf.WriteString("\n") + fmt.Printf(buf.String(), data...) + os.Exit(1) + } + + DefaultLogger = &logger{ + Mutex: sync.Mutex{}, + timeFormat: "2006-01-02T15:04:05", + writer: os.Stdout, + level: LogLevelInfo, + debug: nilLogger, + info: normalLogger, + warn: normalLogger, + error: normalLogger, + panic: panicLogger, + fatal: fatalLogger, + } +) + +func SetTimeFormat(format string) { + DefaultLogger.SetTimeFormat(format) +} + +func SetLogLevel(level LogLevel) { + DefaultLogger.SetLogLevel(level) +} + +func Debug(msg string, data ...any) { + DefaultLogger.Debug(msg, data...) +} +func Info(msg string, data ...any) { + DefaultLogger.Info(msg, data...) +} + +func Warn(msg string, data ...any) { + DefaultLogger.Warn(msg, data...) +} + +func Error(msg string, data ...any) { + DefaultLogger.Error(msg, data...) +} + +func Panic(msg string, data ...any) { + DefaultLogger.Panic(msg, data...) +} + +func Fatal(msg string, data ...any) { + DefaultLogger.Fatal(msg, data...) +} diff --git a/pkg/logger/logger.go b/pkg/logger/logger.go new file mode 100644 index 0000000..af51990 --- /dev/null +++ b/pkg/logger/logger.go @@ -0,0 +1,116 @@ +package logger + +import ( + "io" + "sync" + "time" + + "github.com/fatih/color" +) + +type LogLevel uint32 + +const ( + LogLevelDebug = iota + LogLevelInfo + LogLevelWarn + LogLevelError + LogLevelPanic + LogLevelFatal +) + +type logger struct { + sync.Mutex + timeFormat string + writer io.Writer + level LogLevel + debug func(prefix, timestamp, msg string, data ...any) + info func(prefix, timestamp, msg string, data ...any) + warn func(prefix, timestamp, msg string, data ...any) + error func(prefix, timestamp, msg string, data ...any) + panic func(prefix, timestamp, msg string, data ...any) + fatal func(prefix, timestamp, msg string, data ...any) +} + +var ( + red = color.New(color.FgRed) + hired = color.New(color.FgHiRed) + green = color.New(color.FgGreen) + yellow = color.New(color.FgYellow) + white = color.New(color.FgWhite) +) + +func (l *logger) SetTimeFormat(format string) { + l.Lock() + defer l.Unlock() + l.timeFormat = format +} + +func (l *logger) SetLogLevel(level LogLevel) { + l.Lock() + defer l.Unlock() + + if level > LogLevelDebug { + l.debug = nilLogger + } else { + l.debug = normalLogger + } + + if level > LogLevelInfo { + l.info = nilLogger + } else { + l.info = normalLogger + } + + if level > LogLevelWarn { + l.warn = nilLogger + } else { + l.warn = normalLogger + } + + if level > LogLevelError { + l.error = nilLogger + } else { + l.error = normalLogger + } + + if level > LogLevelPanic { + l.panic = nilLogger + } else { + l.panic = panicLogger + } + + if level > LogLevelFatal { + l.fatal = nilLogger + } else { + l.fatal = fatalLogger + } +} + +func (l *logger) Debug(msg string, data ...any) { + l.debug(white.Sprint("Debug "), time.Now().Format(l.timeFormat), msg, data...) +} + +func (l *logger) Info(msg string, data ...any) { + l.info(green.Sprint("Info "), time.Now().Format(l.timeFormat), msg, data...) +} + +func (l *logger) Warn(msg string, data ...any) { + l.warn(yellow.Sprint("Warn "), time.Now().Format(l.timeFormat), msg, data...) +} + +func (l *logger) Error(msg string, data ...any) { + l.error(red.Sprint("Error "), time.Now().Format(l.timeFormat), msg, data...) +} + +func (l *logger) Panic(msg string, data ...any) { + l.panic(hired.Sprint("Panic "), time.Now().Format(l.timeFormat), msg, data...) +} + +func (l *logger) Fatal(msg string, data ...any) { + l.fatal(hired.Sprint("Fatal "), time.Now().Format(l.timeFormat), msg, data...) +} + +type WroteLogger interface { + Info(msg string, data ...any) +} diff --git a/pkg/logger/new.go b/pkg/logger/new.go new file mode 100644 index 0000000..e684914 --- /dev/null +++ b/pkg/logger/new.go @@ -0,0 +1,21 @@ +package logger + +import ( + "os" + "sync" +) + +func New() *logger { + return &logger{ + Mutex: sync.Mutex{}, + timeFormat: "2006-01-02T15:04:05", + writer: os.Stdout, + level: LogLevelInfo, + debug: nilLogger, + info: normalLogger, + warn: normalLogger, + error: normalLogger, + panic: panicLogger, + fatal: fatalLogger, + } +} diff --git a/pkg/middleware/logger/logger.go b/pkg/middleware/logger/logger.go new file mode 100644 index 0000000..c0df11d --- /dev/null +++ b/pkg/middleware/logger/logger.go @@ -0,0 +1,9 @@ +package logger + +import "github.com/gofiber/fiber/v3" + +func New() fiber.Handler { + return func(c fiber.Ctx) error { + return c.Next() + } +} diff --git a/pkg/middleware/trace/trace.go b/pkg/middleware/trace/trace.go new file mode 100644 index 0000000..bb966e7 --- /dev/null +++ b/pkg/middleware/trace/trace.go @@ -0,0 +1,65 @@ +package trace + +import ( + "context" + "loveuer/utodo/pkg/logger" + "strconv" + "time" + + "github.com/gofiber/fiber/v3" + "github.com/google/uuid" +) + +type Option func(*option) + +type option struct { + traceKey string + traceFn func(c fiber.Ctx) string +} + +func WithTraceKey(key string) Option { + return func(o *option) { + if key != "" { + o.traceKey = key + } + } +} + +func WithTraceFn(fn func(c fiber.Ctx) string) Option { + return func(o *option) { + if fn != nil { + o.traceFn = fn + } + } +} + +func New(opts ...Option) fiber.Handler { + opt := &option{ + traceKey: "X-Trace-Id", + traceFn: func(c fiber.Ctx) string { + uid, err := uuid.NewV7() + if err != nil { + return strconv.FormatInt(time.Now().UnixNano(), 10) + } + + return uid.String() + }, + } + + for _, o := range opts { + o(opt) + } + + return func(c fiber.Ctx) error { + traceId := c.Get(opt.traceKey) + if traceId == "" { + traceId = opt.traceFn(c) + c.Request().Header.Set(opt.traceKey, traceId) + } + + c.SetContext(context.WithValue(c.Context(), logger.CtxKey, traceId)) + c.Set(opt.traceKey, traceId) + + return c.Next() + } +}