From f100427f8b712db14e91e7969a30bd8546bff4b3 Mon Sep 17 00:00:00 2001 From: kenneth <1185230223@qq.com> Date: Mon, 7 Apr 2025 11:21:43 +0800 Subject: [PATCH] 1 --- cmd/erp.go | 21 ++++- go.mod | 8 +- go.sum | 12 +++ internal/erpserver/biz/biz.go | 19 +++-- internal/erpserver/biz/v1/system/system.go | 19 +++-- internal/erpserver/biz/v1/system/user.go | 18 +++-- internal/erpserver/model/system/user.go | 40 ++++++++++ internal/erpserver/store/store.go | 69 ++++++++++++++++ internal/erpserver/store/system/user.go | 63 +++++++++++++++ internal/pkg/database/postgresql.go | 92 ++++++++++++++++++++++ main.go | 15 ++++ 11 files changed, 350 insertions(+), 26 deletions(-) create mode 100644 internal/erpserver/model/system/user.go create mode 100644 internal/erpserver/store/store.go create mode 100644 internal/erpserver/store/system/user.go create mode 100644 internal/pkg/database/postgresql.go diff --git a/cmd/erp.go b/cmd/erp.go index 6a26b08..406edb3 100644 --- a/cmd/erp.go +++ b/cmd/erp.go @@ -9,8 +9,10 @@ import ( "management/internal/erpserver" "management/internal/erpserver/biz" "management/internal/erpserver/handler" + "management/internal/erpserver/store" "management/internal/pkg/binding" "management/internal/pkg/config" + "management/internal/pkg/database" "management/internal/pkg/logger" "management/internal/pkg/middleware" "management/internal/pkg/redis" @@ -44,9 +46,22 @@ func runErp(ctx context.Context) error { logger.New(conf.App.Prod) - store, err := db.NewIStore(ctx, conf.DB) + sdb, err := db.NewIStore(ctx, conf.DB) checkError(err) + dbOptions := &database.PostgreSQLOptions{ + Addr: conf.DB.Host, + Username: conf.DB.Username, + Password: conf.DB.Password, + Database: conf.DB.DBName, + } + + // 创建并返回数据库连接 + gdb, err := database.NewPostgreSQL(dbOptions) + checkError(err) + + store := store.NewStore(gdb) + // 初始化数据 // dbinit.InitSeed() @@ -55,11 +70,11 @@ func runErp(ctx context.Context) error { redis, err := redis.New(conf.Redis) checkError(err) - session := session.New(store.Pool(), conf.App.Prod) + session := session.New(sdb.Pool(), conf.App.Prod) mustInit(snowflake.Init) - biz := biz.NewBiz(store, redis, session) + biz := biz.NewBiz(store, sdb, redis, session) middleware := middleware.New(biz.SystemV1(), session) diff --git a/go.mod b/go.mod index 2875fa2..63bfc8a 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,6 @@ module management -go 1.24.1 +go 1.24.2 require ( github.com/aead/chacha20poly1305 v0.0.0-20201124145622-1a5aba2a8b29 @@ -33,6 +33,7 @@ require ( github.com/sqids/sqids-go v0.4.1 github.com/zhang2092/browser v0.0.2 golang.org/x/crypto v0.36.0 + gorm.io/gorm v1.25.12 ) require ( @@ -45,6 +46,7 @@ require ( github.com/cespare/xxhash/v2 v2.3.0 // indirect github.com/cloudwego/base64x v0.1.5 // indirect github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect + github.com/drhin/logger v0.0.0-20250407005450-5bbde7825366 // indirect github.com/gabriel-vasile/mimetype v1.4.8 // indirect github.com/geoffgarside/ber v1.1.0 // indirect github.com/gin-contrib/sse v1.0.0 // indirect @@ -58,6 +60,8 @@ require ( github.com/jackc/pgproto3/v2 v2.3.3 // indirect github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect github.com/jackc/puddle/v2 v2.2.2 // indirect + github.com/jinzhu/inflection v1.0.0 // indirect + github.com/jinzhu/now v1.1.5 // indirect github.com/json-iterator/go v1.1.12 // indirect github.com/klauspost/cpuid/v2 v2.2.9 // indirect github.com/leodido/go-urn v1.4.0 // indirect @@ -76,6 +80,7 @@ require ( 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 + go.uber.org/zap v1.27.0 // indirect golang.org/x/arch v0.14.0 // indirect golang.org/x/image v0.25.0 // indirect golang.org/x/net v0.38.0 // indirect @@ -86,4 +91,5 @@ require ( gopkg.in/natefinch/lumberjack.v2 v2.2.1 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect + gorm.io/driver/postgres v1.5.11 // indirect ) diff --git a/go.sum b/go.sum index e9390b1..90be268 100644 --- a/go.sum +++ b/go.sum @@ -37,6 +37,8 @@ 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/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78= github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc= +github.com/drhin/logger v0.0.0-20250407005450-5bbde7825366 h1:HcCQ0kAGAiprUVYDw54vMcC9n9TSX3co0ro+l44TcrE= +github.com/drhin/logger v0.0.0-20250407005450-5bbde7825366/go.mod h1:XfunbMqKGMTCr3jsu5Vwe7kVUAINPOi/4z+e8qvVJDE= github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8= github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= github.com/fsnotify/fsnotify v1.8.0 h1:dAwr6QBTBZIkG8roQaJjGof0pp0EeF+tNV7YBP3F/8M= @@ -107,6 +109,10 @@ github.com/jackc/pgx/v5 v5.7.4/go.mod h1:ncY89UGWxg82EykZUwSpUKEfccBGGYq1xjrOpsb github.com/jackc/puddle/v2 v2.2.1/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4= github.com/jackc/puddle/v2 v2.2.2 h1:PR8nw+E/1w0GLuRFSmiioY6UooMp6KJv0/61nB7icHo= github.com/jackc/puddle/v2 v2.2.2/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4= +github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E= +github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc= +github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ= +github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8= 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= @@ -209,6 +215,8 @@ github.com/zhang2092/browser v0.0.2 h1:4jyWWkSCabGxqn74lCDvNnA4o9TlmsUXuwQHPZFsf github.com/zhang2092/browser v0.0.2/go.mod h1:k/HIdVgWmpi9WvGuIU8pu8aK4rMCI4vr0ICCsk5H8T8= 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.14.0 h1:z9JUEZWr8x4rR0OU6c4/4t6E6jOZ8/QBS2bBYBm4tx4= golang.org/x/arch v0.14.0/go.mod h1:FEVrYAQjsQXMVJ1nsMoVVXPZg6p2JE2mx8psSWTDQys= golang.org/x/crypto v0.0.0-20181025213731-e84da0312774/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= @@ -308,4 +316,8 @@ gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= 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/postgres v1.5.11 h1:ubBVAfbKEUld/twyKZ0IYn9rSQh448EdelLYk9Mv314= +gorm.io/driver/postgres v1.5.11/go.mod h1:DX3GReXH+3FPWGrrgffdvCk3DQ1dwDPdmbenSkweRGI= +gorm.io/gorm v1.25.12 h1:I0u8i2hWQItBq1WfE0o2+WuL9+8L21K9e2HHSTE/0f8= +gorm.io/gorm v1.25.12/go.mod h1:xh7N7RHfYlNc5EmcI/El95gXusucDrQnHXe0+CgWcLQ= nullprogram.com/x/optparse v1.0.0/go.mod h1:KdyPE+Igbe0jQUrVfMqDMeJQIJZEuyV7pjYmp6pbG50= diff --git a/internal/erpserver/biz/biz.go b/internal/erpserver/biz/biz.go index c3d20b3..aad3fab 100644 --- a/internal/erpserver/biz/biz.go +++ b/internal/erpserver/biz/biz.go @@ -9,6 +9,7 @@ import ( incomev1 "management/internal/erpserver/biz/v1/income" projectv1 "management/internal/erpserver/biz/v1/project" systemv1 "management/internal/erpserver/biz/v1/system" + "management/internal/erpserver/store" "management/internal/pkg/redis" "management/internal/pkg/session" ) @@ -33,20 +34,22 @@ type IBiz interface { // biz 是 IBiz 的一个具体实现. type biz struct { - store db.Store - redis redis.IRedis - session session.ISession + database store.IStore + store db.Store + redis redis.IRedis + session session.ISession } // 确保 biz 实现了 IBiz 接口. var _ IBiz = (*biz)(nil) // NewBiz 创建一个 IBiz 类型的实例. -func NewBiz(store db.Store, redis redis.IRedis, session session.ISession) *biz { +func NewBiz(database store.IStore, store db.Store, redis redis.IRedis, session session.ISession) *biz { return &biz{ - store: store, - redis: redis, - session: session, + database: database, + store: store, + redis: redis, + session: session, } } @@ -57,7 +60,7 @@ func (b *biz) CommonV1() commonv1.CommonBiz { // SystemV1 返回一个实现了 SystemBiz 接口的实例. func (b *biz) SystemV1() systemv1.SystemBiz { - return systemv1.New(b.store, b.redis, b.session) + return systemv1.New(b.database, b.store, b.redis, b.session) } func (b *biz) ProjectV1() projectv1.ProjectBiz { diff --git a/internal/erpserver/biz/v1/system/system.go b/internal/erpserver/biz/v1/system/system.go index caca6cf..7f868d0 100644 --- a/internal/erpserver/biz/v1/system/system.go +++ b/internal/erpserver/biz/v1/system/system.go @@ -2,6 +2,7 @@ package system import ( db "management/internal/db/sqlc" + "management/internal/erpserver/store" "management/internal/pkg/redis" "management/internal/pkg/session" ) @@ -18,23 +19,25 @@ type SystemBiz interface { } type systemBiz struct { - store db.Store - redis redis.IRedis - session session.ISession + database store.IStore + store db.Store + redis redis.IRedis + session session.ISession } var _ SystemBiz = (*systemBiz)(nil) -func New(store db.Store, redis redis.IRedis, session session.ISession) *systemBiz { +func New(database store.IStore, store db.Store, redis redis.IRedis, session session.ISession) *systemBiz { return &systemBiz{ - store: store, - redis: redis, - session: session, + database: database, + store: store, + redis: redis, + session: session, } } func (b *systemBiz) UserBiz() UserBiz { - return NewUser(b.store, b.session) + return NewUser(b.database, b.store, b.session) } func (b *systemBiz) MenuBiz() MenuBiz { diff --git a/internal/erpserver/biz/v1/system/user.go b/internal/erpserver/biz/v1/system/user.go index eaddce6..fb8d3c9 100644 --- a/internal/erpserver/biz/v1/system/user.go +++ b/internal/erpserver/biz/v1/system/user.go @@ -4,6 +4,7 @@ import ( "context" "encoding/json" "errors" + "fmt" "strconv" "time" @@ -11,6 +12,7 @@ import ( db "management/internal/db/sqlc" "management/internal/erpserver/model/form" "management/internal/erpserver/model/view" + "management/internal/erpserver/store" "management/internal/pkg/crypto" "management/internal/pkg/know" "management/internal/pkg/rand" @@ -39,17 +41,19 @@ type UserExpansion interface { // userBiz 是 UserBiz 接口的实现. type userBiz struct { - store db.Store - session session.ISession + database store.IStore + store db.Store + session session.ISession } // 确保 userBiz 实现了 UserBiz 接口. var _ UserBiz = (*userBiz)(nil) -func NewUser(store db.Store, session session.ISession) *userBiz { +func NewUser(database store.IStore, store db.Store, session session.ISession) *userBiz { return &userBiz{ - store: store, - session: session, + database: database, + store: store, + session: session, } } @@ -190,12 +194,14 @@ func (b *userBiz) Login(ctx context.Context, req *form.Login) error { Browser: req.Browser, } - user, err := b.store.GetSysUserByEmail(ctx, req.Email) + user, err := b.database.User().GetByEmail(ctx, req.Email) + // user, err = b.store.GetSysUserByEmail(ctx, req.Email) if err != nil { log.Message = err.Error() _ = b.store.CreateSysUserLoginLog(ctx, log) return err } + fmt.Printf("%+v", user) log.UserUuid = user.Uuid log.Username = user.Username diff --git a/internal/erpserver/model/system/user.go b/internal/erpserver/model/system/user.go new file mode 100644 index 0000000..60d01e2 --- /dev/null +++ b/internal/erpserver/model/system/user.go @@ -0,0 +1,40 @@ +package system + +import ( + "time" + + "github.com/google/uuid" +) + +type User struct { + ID int32 `json:"id" gorm:"primaryKey"` + Uuid uuid.UUID `json:"uuid"` + // 邮箱地址 + Email string `json:"email"` + // 用户名称 + Username string `json:"username"` + // 加密密码 + HashedPassword []byte `json:"hashed_password"` + // 密码盐值 + Salt string `json:"salt"` + // 头像 + Avatar string `json:"avatar"` + // 性别 + Gender int32 `json:"gender"` + // 部门 + DepartmentID int32 `json:"department_id"` + // 角色 + RoleID int32 `json:"role_id"` + // 状态 + Status int32 `json:"status"` + // 密码修改时间 + ChangePasswordAt time.Time `json:"change_password_at"` + // 创建时间 + CreatedAt time.Time `json:"created_at"` + // 更新时间 + UpdatedAt time.Time `json:"updated_at"` +} + +func (User) TableName() string { + return "sys_user" +} diff --git a/internal/erpserver/store/store.go b/internal/erpserver/store/store.go new file mode 100644 index 0000000..09ed57f --- /dev/null +++ b/internal/erpserver/store/store.go @@ -0,0 +1,69 @@ +package store + +import ( + "context" + "sync" + + "management/internal/erpserver/store/system" + + "gorm.io/gorm" +) + +var ( + once sync.Once + // 全局变量,方便其它包直接调用已初始化好的 datastore 实例. + engine *datastore +) + +// IStore 定义了 Store 层需要实现的方法. +type IStore interface { + DB(ctx context.Context) *gorm.DB + TX(ctx context.Context, fn func(ctx context.Context) error) error + + User() system.UserStore +} + +// transactionKey 用于在 context.Context 中存储事务上下文的键. +type transactionKey struct{} + +// datastore 是 IStore 的具体实现. +type datastore struct { + core *gorm.DB +} + +// 确保 datastore 实现了 IStore 接口. +var _ IStore = (*datastore)(nil) + +// NewStore 创建一个 IStore 类型的实例. +func NewStore(db *gorm.DB) *datastore { + // 确保 engine 只被初始化一次 + once.Do(func() { + engine = &datastore{db} + }) + + return engine +} + +func (store *datastore) DB(ctx context.Context) *gorm.DB { + db := store.core + // 从上下文中提取事务实例 + if tx, ok := ctx.Value(transactionKey{}).(*gorm.DB); ok { + db = tx + } + + return db +} + +func (store *datastore) TX(ctx context.Context, fn func(ctx context.Context) error) error { + return store.core.WithContext(ctx).Transaction( + func(tx *gorm.DB) error { + ctx = context.WithValue(ctx, transactionKey{}, tx) + return fn(ctx) + }, + ) +} + +// Users 返回一个实现了 UserStore 接口的实例. +func (store *datastore) User() system.UserStore { + return system.NewUserStore(store.core) +} diff --git a/internal/erpserver/store/system/user.go b/internal/erpserver/store/system/user.go new file mode 100644 index 0000000..9baa690 --- /dev/null +++ b/internal/erpserver/store/system/user.go @@ -0,0 +1,63 @@ +package system + +import ( + "context" + + "management/internal/erpserver/model/system" + + "gorm.io/gorm" +) + +// UserStore 定义了 user 模块在 store 层所实现的方法. +type UserStore interface { + Create(ctx context.Context, obj *system.User) error + Update(ctx context.Context, obj *system.User) error + Get(ctx context.Context, id int32) (*system.User, error) + GetByEmail(ctx context.Context, email string) (*system.User, error) + + UserExpansion +} + +// UserExpansion 定义了用户操作的附加方法. +type UserExpansion interface{} + +// userStore 是 UserStore 接口的实现. +type userStore struct { + db *gorm.DB +} + +// 确保 userStore 实现了 UserStore 接口. +var _ UserStore = (*userStore)(nil) + +// NewUserStore 创建 userStore 的实例. +func NewUserStore(db *gorm.DB) *userStore { + return &userStore{ + db: db, + } +} + +func (s *userStore) Create(ctx context.Context, obj *system.User) error { + return s.db.WithContext(ctx).Create(obj).Error +} + +func (s *userStore) Update(ctx context.Context, obj *system.User) error { + return s.db.WithContext(ctx).Save(obj).Error +} + +func (s *userStore) Get(ctx context.Context, id int32) (*system.User, error) { + var user system.User + err := s.db.WithContext(ctx).Where("id = ?", id).First(&user).Error + if err != nil { + return nil, err + } + return &user, nil +} + +func (s *userStore) GetByEmail(ctx context.Context, email string) (*system.User, error) { + var user system.User + err := s.db.WithContext(ctx).Where("email = ?", email).First(&user).Error + if err != nil { + return nil, err + } + return &user, nil +} diff --git a/internal/pkg/database/postgresql.go b/internal/pkg/database/postgresql.go new file mode 100644 index 0000000..c34cfde --- /dev/null +++ b/internal/pkg/database/postgresql.go @@ -0,0 +1,92 @@ +package database + +import ( + "fmt" + "strings" + "time" + + "gorm.io/driver/postgres" + "gorm.io/gorm" + "gorm.io/gorm/logger" +) + +// PostgreSQLOptions defines options for PostgreSQL database. +type PostgreSQLOptions struct { + Addr string + Username string + Password string + Database string + MaxIdleConnections int + MaxOpenConnections int + MaxConnectionLifeTime time.Duration + // +optional + Logger logger.Interface +} + +// DSN return DSN from PostgreSQLOptions. +func (o *PostgreSQLOptions) DSN() string { + splited := strings.Split(o.Addr, ":") + host, port := splited[0], "5432" + if len(splited) > 1 { + port = splited[1] + } + + return fmt.Sprintf(`user=%s password=%s host=%s port=%s dbname=%s sslmode=disable TimeZone=Asia/Shanghai`, + o.Username, + o.Password, + host, + port, + o.Database, + ) +} + +// NewPostgreSQL create a new gorm db instance with the given options. +func NewPostgreSQL(opts *PostgreSQLOptions) (*gorm.DB, error) { + // Set default values to ensure all fields in opts are available. + setPostgreSQLDefaults(opts) + + db, err := gorm.Open(postgres.Open(opts.DSN()), &gorm.Config{ + // PrepareStmt executes the given query in cached statement. + // This can improve performance. + PrepareStmt: true, + Logger: opts.Logger, + }) + if err != nil { + return nil, err + } + + sqlDB, err := db.DB() + if err != nil { + return nil, err + } + + // SetMaxOpenConns sets the maximum number of open connections to the database. + sqlDB.SetMaxOpenConns(opts.MaxOpenConnections) + + // SetConnMaxLifetime sets the maximum amount of time a connection may be reused. + sqlDB.SetConnMaxLifetime(opts.MaxConnectionLifeTime) + + // SetMaxIdleConns sets the maximum number of connections in the idle connection pool. + sqlDB.SetMaxIdleConns(opts.MaxIdleConnections) + + return db, nil +} + +// setPostgreSQLDefaults set available default values for some fields. +func setPostgreSQLDefaults(opts *PostgreSQLOptions) { + if opts.Addr == "" { + opts.Addr = "127.0.0.1:5432" + } + if opts.MaxIdleConnections == 0 { + opts.MaxIdleConnections = 100 + } + if opts.MaxOpenConnections == 0 { + opts.MaxOpenConnections = 100 + } + if opts.MaxConnectionLifeTime == 0 { + opts.MaxConnectionLifeTime = time.Duration(10) * time.Second + } + if opts.Logger == nil { + opts.Logger = logger.Default + } +} diff --git a/main.go b/main.go index 0b74cfe..ad388da 100644 --- a/main.go +++ b/main.go @@ -2,8 +2,23 @@ package main import ( "management/cmd" + + "github.com/drhin/logger" + "go.uber.org/zap" ) func main() { + log, err := logger.NewProduction() + if err != nil { + panic(err) + } + defer log.Sync() + + log.With( + zap.String("html", "

Hello World

"), + ).Info("Starting application") + + return + cmd.Execute() }