diff --git a/go.mod b/go.mod index 09bfea7..83046ec 100644 --- a/go.mod +++ b/go.mod @@ -42,6 +42,7 @@ require ( ) require ( + github.com/doug-martin/goqu/v9 v9.18.0 github.com/fsnotify/fsnotify v1.5.4 // indirect github.com/hashicorp/hcl v1.0.0 // indirect github.com/json-iterator/go v1.1.12 // indirect diff --git a/go.sum b/go.sum index f0bfce6..f733226 100644 --- a/go.sum +++ b/go.sum @@ -39,6 +39,7 @@ dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7 github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= +github.com/DATA-DOG/go-sqlmock v1.5.0/go.mod h1:f/Ixk793poVmq4qj/V1dPUg2JEAKC73Q5eFN3EC/SaM= github.com/andybalholm/brotli v1.0.4 h1:V7DdXeJtZscaqfNuAdSRuRFzuiKlHSC/Zh3zl9qY3JY= github.com/andybalholm/brotli v1.0.4/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig= github.com/benbjohnson/clock v1.1.0 h1:Q92kusRqC1XV2MjkWETPvjJVqKetz1OzxZB7mHJLju8= @@ -55,6 +56,9 @@ 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/deckarep/golang-set/v2 v2.1.0 h1:g47V4Or+DUdzbs8FxCCmgb6VYd+ptPAngjM6dtGktsI= github.com/deckarep/golang-set/v2 v2.1.0/go.mod h1:VAky9rY/yGXJOLEDv3OMci+7wtDpOF4IN+y82NBOac4= +github.com/denisenkom/go-mssqldb v0.10.0/go.mod h1:xbL0rPBG9cCiLr28tMa8zpbdarY27NDyej4t/EjAShU= +github.com/doug-martin/goqu/v9 v9.18.0 h1:/6bcuEtAe6nsSMVK/M+fOiXUNfyFF3yYtE07DBPFMYY= +github.com/doug-martin/goqu/v9 v9.18.0/go.mod h1:nf0Wc2/hV3gYK9LiyqIrzBEVGlI8qW3GuDCEobC4wBQ= github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= @@ -74,6 +78,7 @@ github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2 github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= github.com/gofiber/fiber/v2 v2.38.1 h1:GEQ/Yt3Wsf2a30iTqtLXlBYJZso0JXPovt/tmj5H9jU= github.com/gofiber/fiber/v2 v2.38.1/go.mod h1:t0NlbaXzuGH7I+7M4paE848fNWInZ7mfxI/Er1fTth8= +github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe/go.mod h1:8vg3r2VgvsThLBIFL93Qb5yWzgyZWhEmBwUJWevAkK0= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= @@ -170,9 +175,11 @@ github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/liamylian/jsontime/v2 v2.0.0 h1:3if2kDW/boymUdO+4Qj/m4uaXMBSF6np9KEgg90cwH0= github.com/liamylian/jsontime/v2 v2.0.0/go.mod h1:UHp1oAPqCBfspokvGmaGe0IAl2IgOpgOgDaKPcvcGGY= github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= +github.com/lib/pq v1.10.1/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= github.com/magiconair/properties v1.8.6 h1:5ibWZ6iY0NctNGWo87LalDlEZ6R41TqbbDamhfG/Qzo= github.com/magiconair/properties v1.8.6/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60= github.com/mattn/go-sqlite3 v1.14.6/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU= +github.com/mattn/go-sqlite3 v1.14.7/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU= github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= @@ -280,6 +287,7 @@ go.uber.org/multierr v1.8.0/go.mod h1:7EAYxJLBy9rStEaz58O2t4Uvip6FSURkq8/ppBp95a go.uber.org/zap v1.23.0 h1:OjGQ5KQDEUawVHxNwQgPpiypGHOxo2mNZsOqTak4fFY= go.uber.org/zap v1.23.0/go.mod h1:D+nX8jyLsMHMYrln8A0rJjFt/T/9/bGgIhAqxv5URuY= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20190325154230-a5d413f7728c/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= diff --git a/model/user.go b/model/user.go index 391df8a..4895a82 100644 --- a/model/user.go +++ b/model/user.go @@ -1,7 +1,72 @@ package model +import ( + "time" + + "github.com/shopspring/decimal" +) + const ( - USER_TYPE_ENT int8 = iota + USER_TYPE_ENT int16 = iota USER_TYPE_SUP USER_TYPE_OPS ) + +type ManagementAccountCreationForm struct { + Id *string + Username string + Name string + Contact *string + Phone *string + Type int16 `json:"type"` + Enabled bool + Expires Date +} + +type User struct { + Id string + Username string + Password string + ResetNeeded bool + UserType int16 `db:"type"` + Enabled bool + CreatedAt *time.Time +} + +type UserDetail struct { + Id string + Name *string + Abbr *string + Region *string + Address *string + Contact *string + Phone *string + UnitServiceFee decimal.Decimal `db:"unit_service_fee"` + ServiceExpiration Date + CreatedAt time.Time + CreatedBy *string + LastModifiedAt time.Time + LastModifiedBy *string + DeletedAt *time.Time + DeletedBy *string +} + +type UserWithDetail struct { + Id string + Username string + ResetNeeded bool + UserType int16 `db:"type"` + Enabled bool + Name *string + Abbr *string + Region *string + Address *string + Contact *string + Phone *string + UnitServiceFee decimal.Decimal `db:"unit_service_fee"` + ServiceExpiration Date + CreatedAt time.Time + CreatedBy *string + LastModifiedAt time.Time + LastModifiedBy *string +} diff --git a/repository/user.go b/repository/user.go new file mode 100644 index 0000000..c304e93 --- /dev/null +++ b/repository/user.go @@ -0,0 +1,138 @@ +package repository + +import ( + "electricity_bill_calc/cache" + "electricity_bill_calc/global" + "electricity_bill_calc/logger" + "electricity_bill_calc/model" + "fmt" + + "github.com/doug-martin/goqu/v9" + _ "github.com/doug-martin/goqu/v9/dialect/postgres" + "github.com/georgysavva/scany/v2/pgxscan" + "go.uber.org/zap" +) + +type _UserRepository struct { + log *zap.Logger +} + +var UserRepository = _UserRepository{ + log: logger.Named("Repository", "User"), +} + +// 使用用户名查询指定用户的基本信息 +func (ur _UserRepository) FindUserByUsername(username string) (*model.User, error) { + ur.log.Info("根据用户名查询指定用户的基本信息。", zap.String("username", username)) + if cachedUser, _ := cache.RetreiveEntity[model.User]("user", username); cachedUser != nil { + ur.log.Info("已经从缓存获取到了符合指定用户名条件的用户基本信息。", zap.String("username", username)) + return cachedUser, nil + } + ctx, cancel := global.TimeoutContext() + defer cancel() + + var user = new(model.User) + sql, params, _ := goqu.From("user").Where(goqu.C("username").Eq(username)).Prepared(true).ToSQL() + if err := pgxscan.Get(ctx, global.DB, &user, sql, params...); err != nil { + ur.log.Error("从数据库查询指定用户名的用户基本信息失败。", zap.String("username", username), zap.Error(err)) + return nil, err + } + cache.CacheEntity(user, []string{"user", fmt.Sprintf("user:%s", username)}, "user", username) + return user, nil +} + +// 使用用户唯一编号查询指定用户的基本信息 +func (ur _UserRepository) FindUserById(uid string) (*model.User, error) { + ur.log.Info("根据用户唯一编号查询指定用户的基本信息。", zap.String("user id", uid)) + if cachedUser, _ := cache.RetreiveEntity[model.User]("user", uid); cachedUser != nil { + ur.log.Info("已经从缓存获取到了符合指定用户唯一编号的用户基本信息。") + return cachedUser, nil + } + ctx, cancel := global.TimeoutContext() + defer cancel() + + var user = new(model.User) + sql, params, _ := goqu.From("user").Where(goqu.C("id").Eq(uid)).Prepared(true).ToSQL() + if err := pgxscan.Get(ctx, global.DB, &user, sql, params...); err != nil { + ur.log.Error("从数据库查询指定用户唯一编号的用户基本信息失败。", zap.String("user id", uid), zap.Error(err)) + return nil, err + } + cache.CacheEntity(user, []string{"user", fmt.Sprintf("user:%s", uid)}, "user", uid) + return user, nil +} + +// 使用用户的唯一编号获取用户的详细信息 +func (ur _UserRepository) FindUserDetailById(uid string) (*model.UserDetail, error) { + ur.log.Info("根据用户唯一编号查询指定用户的详细信息。", zap.String("user id", uid)) + if cachedUser, _ := cache.RetreiveEntity[model.UserDetail]("user_detail", uid); cachedUser != nil { + ur.log.Info("已经从缓存获取到了符合指定用户唯一编号的用户详细信息。") + return cachedUser, nil + } + ctx, cancel := global.TimeoutContext() + defer cancel() + + var user = new(model.UserDetail) + sql, params, _ := goqu.From("user_detail").Where(goqu.C("id").Eq(uid)).Prepared(true).ToSQL() + if err := pgxscan.Get(ctx, global.DB, &user, sql, params...); err != nil { + ur.log.Error("从数据库查询指定用户唯一编号的用户详细信息失败。", zap.String("user id", uid), zap.Error(err)) + return nil, err + } + cache.CacheEntity(user, []string{"user", fmt.Sprintf("user:%s", uid)}, "user_detail", uid) + return user, nil +} + +// 使用用户唯一编号获取用户的综合详细信息 +func (ur _UserRepository) FindUserInformation(uid string) (*model.UserWithDetail, error) { + ur.log.Info("根据用户唯一编号查询用户的综合详细信息", zap.String("user id", uid)) + if cachedUser, _ := cache.RetreiveEntity[model.UserWithDetail]("user_information", uid); cachedUser != nil { + ur.log.Info("已经从缓存获取到了符合指定用户唯一编号的用户综合详细信息。") + return cachedUser, nil + } + ctx, cancel := global.TimeoutContext() + defer cancel() + + var user = new(model.UserWithDetail) + sql, params, _ := goqu. + From("user").As("u"). + Join( + goqu.T("user_detail").As("ud"), + goqu.On(goqu.Ex{ + "ud.id": goqu.I("u.id"), + })). + Select( + "u.id", "u.username", "u.reset_needed", "u.type", "u.enabled", + "ud.name", "ud.abbr", "ud.region", "ud.address", "ud.contact", "ud.phone", + "ud.unit_service_fee", "ud.service_expiration", + "ud.created_at", "ud.created_by", "ud.last_modified_at", "ud.last_modified_by"). + Where(goqu.C("u.id").Eq(uid)). + Prepared(true).ToSQL() + if err := pgxscan.Get( + ctx, global.DB, &user, sql, params...); err != nil { + ur.log.Error("从数据库查询指定用户唯一编号的用户详细信息失败。", zap.String("user id", uid), zap.Error(err)) + return nil, err + } + cache.CacheEntity(user, []string{"user", fmt.Sprintf("user:%s", uid)}, "user_information", uid) + return user, nil +} + +// 检查指定用户唯一编号是否存在对应的用户 +func (ur _UserRepository) IsUserExists(uid string) (bool, error) { + ur.log.Info("检查指定用户唯一编号是否存在对应的用户。", zap.String("user id", uid)) + if exists, _ := cache.CheckExists("user", uid); exists { + ur.log.Info("已经从缓存获取到了符合指定用户唯一编号的用户基本信息。") + return exists, nil + } + ctx, cancel := global.TimeoutContext() + defer cancel() + + var userCount int + sql, params, _ := goqu.From("user").Select(goqu.COUNT("*")).Where(goqu.C("id").Eq(uid)).Prepared(true).ToSQL() + if err := pgxscan.Get(ctx, global.DB, &userCount, sql, params...); err != nil { + ur.log.Error("从数据库查询指定用户唯一编号的用户基本信息失败。", zap.String("user id", uid), zap.Error(err)) + return false, err + } + if userCount > 0 { + cache.CacheExists([]string{"user", fmt.Sprintf("user:%s", uid)}, "user", uid) + } + return userCount > 0, nil +}