diff --git a/model/user.go b/model/user.go index 8ba6787..b0d836d 100644 --- a/model/user.go +++ b/model/user.go @@ -55,6 +55,15 @@ func (m ManagementAccountCreationForm) IntoUserDetail() *UserDetail { } } +type UserModificationForm struct { + Name string + Region *string + Address *string + Contact *string + Phone *string + UnitServiceFee *decimal.Decimal +} + type User struct { Id string Username string diff --git a/repository/user.go b/repository/user.go index 651b61c..d3588dc 100644 --- a/repository/user.go +++ b/repository/user.go @@ -9,9 +9,12 @@ import ( "electricity_bill_calc/tools" "electricity_bill_calc/tools/time" "fmt" + "strings" + stdTime "time" "github.com/doug-martin/goqu/v9" _ "github.com/doug-martin/goqu/v9/dialect/postgres" + "github.com/fufuok/utils" "github.com/georgysavva/scany/v2/pgxscan" "go.uber.org/zap" ) @@ -223,9 +226,9 @@ func (ur _UserRepository) CreateUser(user model.User, detail model.UserDetail, o } // 根据给定的条件检索用户 -func (ur _UserRepository) FindUser(keyword *string, userType int16, state *bool, page uint) (*[]model.UserWithDetail, int64, error) { +func (ur _UserRepository) FindUser(keyword *string, userType int16, state *bool, page uint) ([]model.UserWithDetail, int64, error) { ur.log.Info("根据给定的条件检索用户。", zap.Uint("page", page), zap.Stringp("keyword", keyword), zap.Int16("user type", userType), zap.Boolp("state", state)) - if users, total, err := cache.RetrievePagedSearch[[]model.UserWithDetail]("user_with_detail", []string{ + cacheConditions := []string{ fmt.Sprintf("%d", page), tools.CondFn( func(v int16) bool { @@ -237,8 +240,9 @@ func (ur _UserRepository) FindUser(keyword *string, userType int16, state *bool, ), tools.DefaultStrTo("%s", state, "UNDEF"), tools.DefaultTo(keyword, ""), - }...); err == nil && users != nil && total != -1 { - return users, total, nil + } + if users, total, err := cache.RetrievePagedSearch[[]model.UserWithDetail]("user_with_detail", cacheConditions...); err == nil && users != nil && total != -1 { + return *users, total, nil } ctx, cancel := global.TimeoutContext() @@ -299,30 +303,197 @@ func (ur _UserRepository) FindUser(keyword *string, userType int16, state *bool, countSql, countParams, _ := countQuery.Prepared(true).ToSQL() if err := pgxscan.Select(ctx, global.DB, &userWithDetails, userSql, userParams...); err != nil { ur.log.Error("从数据库查询用户列表失败。", zap.Error(err)) - return nil, 0, err + return make([]model.UserWithDetail, 0), 0, err } if err := pgxscan.Get(ctx, global.DB, &userCount, countSql, countParams...); err != nil { ur.log.Error("从数据库查询用户列表总数失败。", zap.Error(err)) - return nil, 0, err + return make([]model.UserWithDetail, 0), 0, err } cache.CachePagedSearch( userWithDetails, userCount, []string{"user"}, "user_with_detail", - []string{ - fmt.Sprintf("%d", page), - tools.CondFn( - func(v int16) bool { - return v != -1 - }, - userType, - fmt.Sprintf("%d", userType), - "UNDEF", - ), - tools.DefaultStrTo("%s", state, "UNDEF"), - tools.DefaultTo(keyword, ""), - }..., + cacheConditions..., ) - return &userWithDetails, userCount, nil + return userWithDetails, userCount, nil +} + +// 更新指定用户的详细信息 +func (ur _UserRepository) UpdateDetail(uid string, userDetail model.UserModificationForm, operator *string) (bool, error) { + ur.log.Info("更新指定用户的详细信息。", zap.String("user id", uid)) + ctx, cancel := global.TimeoutContext() + defer cancel() + + userDetailUpdateQuery := ur.ds. + Update("user_detail"). + Set(goqu.Record{ + "name": userDetail.Name, "abbr": tools.PinyinAbbr(userDetail.Name), "region": userDetail.Region, + "address": userDetail.Address, "contact": userDetail.Contact, "phone": userDetail.Phone, + "last_modified_at": time.Now(), "last_modified_by": operator, + }). + Where(goqu.Ex{"id": uid}) + + if userDetail.UnitServiceFee != nil { + userDetailUpdateQuery = userDetailUpdateQuery.Set(goqu.Record{"unit_service_fee": userDetail.UnitServiceFee}) + } + + userDetailSql, userDetailParams, _ := userDetailUpdateQuery. + Prepared(true).ToSQL() + + if res, err := global.DB.Exec(ctx, userDetailSql, userDetailParams...); err != nil { + ur.log.Error("向数据库更新指定用户的详细信息失败。", zap.String("user id", uid), zap.Error(err)) + return false, err + } else { + cache.AbolishRelation(fmt.Sprintf("user:%s", uid)) + return res.RowsAffected() > 0, nil + } +} + +// 更新指定用户的登录凭据 +func (ur _UserRepository) UpdatePassword(uid, newCredential string, needReset bool) (bool, error) { + ur.log.Info("更新指定用户的登录凭据。", zap.String("user id", uid)) + ctx, cancel := global.TimeoutContext() + defer cancel() + + userUpdateQuery := ur.ds. + Update("user"). + Set(goqu.Record{"password": utils.Sha512Hex(newCredential), "reset_needed": needReset}). + Where(goqu.Ex{"id": uid}) + + userSql, userParams, _ := userUpdateQuery. + Prepared(true).ToSQL() + + if res, err := global.DB.Exec(ctx, userSql, userParams...); err != nil { + ur.log.Error("向数据库更新指定用户的登录凭据失败。", zap.String("user id", uid), zap.Error(err)) + return false, err + } else { + cache.AbolishRelation(fmt.Sprintf("user:%s", uid)) + return res.RowsAffected() > 0, nil + } +} + +// 更新指定用户的可用性状态 +func (ur _UserRepository) ChangeState(uid string, state bool) (bool, error) { + ur.log.Info("更新指定用户的可用性状态。", zap.String("user id", uid)) + ctx, cancel := global.TimeoutContext() + defer cancel() + + userUpdateQuery := ur.ds. + Update("user"). + Set(goqu.Record{"enabled": state}). + Where(goqu.Ex{"id": uid}) + + userSql, userParams, _ := userUpdateQuery. + Prepared(true).ToSQL() + + if res, err := global.DB.Exec(ctx, userSql, userParams...); err != nil { + ur.log.Error("向数据库更新指定用户的可用性状态失败。", zap.String("user id", uid), zap.Error(err)) + return false, err + } else { + cache.AbolishRelation(fmt.Sprintf("user:%s", uid)) + return res.RowsAffected() > 0, nil + } +} + +// 检索条目数量有限的用户详细信息 +func (ur _UserRepository) SearchUsersWithLimit(userType *int16, keyword *string, limit uint) (*[]model.UserDetail, error) { + ur.log.Info("检索条目数量有限的用户详细信息。", zap.Int16p("user type", userType), zap.Uint("limit", limit), zap.Stringp("keyword", keyword)) + actualUserType := tools.DefaultTo(userType, model.USER_TYPE_ENT) + cacheConditions := []string{ + fmt.Sprintf("%d", actualUserType), + tools.DefaultTo(keyword, ""), + fmt.Sprintf("%d", limit), + } + if users, err := cache.RetrieveSearch[[]model.UserDetail]("user_with_detail_limited", cacheConditions...); err == nil && users != nil { + return users, nil + } + ctx, cancel := global.TimeoutContext() + defer cancel() + + var users []model.UserDetail + userQuery := ur.ds. + From(goqu.T("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", + ) + + if keyword != nil && len(*keyword) > 0 { + pattern := fmt.Sprintf("%%%s%%", *keyword) + userQuery = userQuery.Where( + goqu.Or( + goqu.Ex{"u.username": goqu.Op{"like": pattern}}, + goqu.Ex{"ud.name": goqu.Op{"like": pattern}}, + goqu.Ex{"ud.abbr": goqu.Op{"like": pattern}}, + ), + ) + } + + userQuery = userQuery.Where(goqu.Ex{"u.type": actualUserType}) + + userQuery.Order(goqu.I("u.created_at").Desc()).Limit(limit) + + userSql, userParams, _ := userQuery.Prepared(true).ToSQL() + if err := pgxscan.Select(ctx, global.DB, &users, userSql, userParams...); err != nil { + ur.log.Error("从数据库查询用户列表失败。", zap.Error(err)) + return nil, err + } + cache.CacheSearch(users, []string{"user"}, "user_with_detail_limited", cacheConditions...) + return &users, nil +} + +// 更新指定用户的服务有效期限 +func (ur _UserRepository) UpdateServiceExpiration(uid string, expiration stdTime.Time) (bool, error) { + ur.log.Info("更新指定用户的服务有效期限。", zap.String("user id", uid)) + ctx, cancel := global.TimeoutContext() + defer cancel() + + userDetailUpdateQuery := ur.ds. + Update("user_detail"). + Set(goqu.Record{"service_expiration": expiration}). + Where(goqu.Ex{"id": uid}) + + userDetailSql, userDetailParams, _ := userDetailUpdateQuery. + Prepared(true).ToSQL() + + if res, err := global.DB.Exec(ctx, userDetailSql, userDetailParams...); err != nil { + ur.log.Error("向数据库更新指定用户的服务有效期限失败。", zap.String("user id", uid), zap.Error(err)) + return false, err + } else { + cache.AbolishRelation(fmt.Sprintf("user:%s", uid)) + return res.RowsAffected() > 0, nil + } +} + +// 检索指定用户列表的详细信息 +func (ur _UserRepository) RetrieveUsersDetail(uids []string) ([]model.UserDetail, error) { + ur.log.Info("检索指定用户列表的详细信息。", zap.Strings("user ids", uids)) + if len(uids) == 0 { + return make([]model.UserDetail, 0), nil + } + cacheConditions := []string{ + strings.Join(uids, ","), + } + if users, err := cache.RetrieveSearch[[]model.UserDetail]("user_detail", cacheConditions...); err == nil && users != nil { + return *users, nil + } + ctx, cancel := global.TimeoutContext() + defer cancel() + + var users []model.UserDetail + userQuery := ur.ds. + From("user_detail"). + Where(goqu.Ex{"id": goqu.Any(uids)}) + + userSql, userParams, _ := userQuery.Prepared(true).ToSQL() + if err := pgxscan.Select(ctx, global.DB, &users, userSql, userParams...); err != nil { + ur.log.Error("从数据库查询用户列表失败。", zap.Error(err)) + return make([]model.UserDetail, 0), err + } + cache.CacheSearch(users, []string{"user", "user_detail"}, "user", cacheConditions...) + return users, nil }