package repository import ( "context" "electricity_bill_calc/cache" "electricity_bill_calc/config" "electricity_bill_calc/global" "electricity_bill_calc/logger" "electricity_bill_calc/model" "electricity_bill_calc/tools" "electricity_bill_calc/types" "fmt" "strings" "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" "github.com/jackc/pgx/v5" "github.com/samber/lo" "go.uber.org/zap" ) type _UserRepository struct { log *zap.Logger ds goqu.DialectWrapper } var UserRepository = _UserRepository{ log: logger.Named("Repository", "User"), ds: goqu.Dialect("postgres"), } // 使用用户名查询指定用户的基本信息 func (ur _UserRepository) FindUserByUsername(username string) (*model.User, error) { ur.log.Info("根据用户名查询指定用户的基本信息。", zap.String("username", username)) if cachedUser, _ := cache.RetrieveEntity[model.User]("user", username); cachedUser != nil { ur.log.Info("已经从缓存获取到了符合指定用户名条件的用户基本信息。", zap.String("username", username)) return cachedUser, nil } ctx, cancel := global.TimeoutContext() defer cancel() var user model.User sql, params, _ := ur.ds.From("user").Where(goqu.Ex{"username": 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), fmt.Sprintf("user:%s", user.Id)}, "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.RetrieveEntity[model.User]("user", uid); cachedUser != nil { ur.log.Info("已经从缓存获取到了符合指定用户唯一编号的用户基本信息。") return cachedUser, nil } ctx, cancel := global.TimeoutContext() defer cancel() var user model.User sql, params, _ := ur.ds.From("user").Where(goqu.Ex{"id": 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.RetrieveEntity[model.UserDetail]("user_detail", uid); cachedUser != nil { ur.log.Info("已经从缓存获取到了符合指定用户唯一编号的用户详细信息。") return cachedUser, nil } ctx, cancel := global.TimeoutContext() defer cancel() var user model.UserDetail sql, params, _ := ur.ds.From("user_detail").Where(goqu.Ex{"id": 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.RetrieveEntity[model.UserWithDetail]("user_information", uid); cachedUser != nil { ur.log.Info("已经从缓存获取到了符合指定用户唯一编号的用户综合详细信息。") return cachedUser, nil } ctx, cancel := global.TimeoutContext() defer cancel() var user model.UserWithDetail sql, params, _ := ur.ds. 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.Ex{"u.id": 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, _ := ur.ds.From("user").Select(goqu.COUNT("*")).Where(goqu.Ex{"id": 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 } // 检查指定用户名在数据库中是否已经存在 func (ur _UserRepository) IsUsernameExists(username string) (bool, error) { ur.log.Info("检查指定用户名在数据库中是否已经存在。", zap.String("username", username)) if exists, _ := cache.CheckExists("user", username); exists { ur.log.Info("已经从缓存获取到了符合指定用户名的用户基本信息。") return exists, nil } ctx, cancel := global.TimeoutContext() defer cancel() var userCount int sql, params, _ := ur.ds.From("user").Select(goqu.COUNT("*")).Where(goqu.Ex{"username": username}).Prepared(true).ToSQL() if err := pgxscan.Get(ctx, global.DB, &userCount, sql, params...); err != nil { ur.log.Error("从数据库查询指定用户名的用户基本信息失败。", zap.String("username", username), zap.Error(err)) return false, err } if userCount > 0 { cache.CacheExists([]string{"user", fmt.Sprintf("user:%s", username)}, "user", username) } return userCount > 0, nil } // 创建一个新用户 func (ur _UserRepository) CreateUser(user model.User, detail model.UserDetail, operator *string) (bool, error) { ur.log.Info("创建一个新用户。", zap.String("username", user.Username)) ctx, cancel := global.TimeoutContext() defer cancel() tx, err := global.DB.Begin(ctx) if err != nil { ur.log.Error("启动数据库事务失败。", zap.Error(err)) return false, err } createdTime := types.Now() userSql, userParams, _ := ur.ds. Insert("user"). Rows( goqu.Record{ "id": user.Id, "username": user.Username, "password": user.Password, "reset_needed": user.ResetNeeded, "type": user.UserType, "enabled": user.Enabled, "created_at": createdTime, }, ). Prepared(true).ToSQL() userResult, err := tx.Exec(ctx, userSql, userParams...) if err != nil { ur.log.Error("向数据库插入新用户基本信息失败。", zap.Error(err)) tx.Rollback(ctx) return false, err } userDetailSql, userDetailParams, _ := ur.ds. Insert("user_detail"). Rows( goqu.Record{ "id": user.Id, "name": detail.Name, "abbr": tools.PinyinAbbr(*detail.Name), "region": detail.Region, "address": detail.Address, "contact": detail.Contact, "phone": detail.Phone, "unit_service_fee": detail.UnitServiceFee, "service_expiration": detail.ServiceExpiration, "created_at": createdTime, "created_by": operator, "last_modified_at": createdTime, "last_modified_by": operator, }, ). Prepared(true).ToSQL() detailResult, err := tx.Exec(ctx, userDetailSql, userDetailParams...) if err != nil { ur.log.Error("向数据库插入新用户详细信息失败。", zap.Error(err)) tx.Rollback(ctx) return false, err } err = tx.Commit(ctx) if err != nil { ur.log.Error("提交数据库事务失败。", zap.Error(err)) tx.Rollback(ctx) return false, err } else { cache.AbolishRelation("user") } return userResult.RowsAffected() > 0 && detailResult.RowsAffected() > 0, nil } // 根据给定的条件检索用户 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)) cacheConditions := []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, ""), } 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() defer cancel() var ( userWithDetails []*model.UserWithDetail userCount int64 ) 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", ) countQuery := 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(goqu.COUNT("*")) if keyword != nil && len(*keyword) > 0 { pattern := fmt.Sprintf("%%%s%%", *keyword) userQuery = userQuery.Where( goqu.Or( goqu.I("u.username").ILike(pattern), goqu.I("ud.name").ILike(pattern), goqu.I("ud.abbr").ILike(pattern), ), ) countQuery = countQuery.Where( goqu.Or( goqu.I("u.username").ILike(pattern), goqu.I("ud.name").ILike(pattern), goqu.I("ud.abbr").ILike(pattern), ), ) } if userType != -1 { userQuery = userQuery.Where(goqu.Ex{"u.type": userType}) countQuery = countQuery.Where(goqu.Ex{"u.type": userType}) } if state != nil { userQuery = userQuery.Where(goqu.Ex{"u.enabled": state}) countQuery = countQuery.Where(goqu.Ex{"u.enabled": state}) } userQuery.Order(goqu.I("u.created_at").Desc()) currentPosition := (page - 1) * config.ServiceSettings.ItemsPageSize userQuery = userQuery.Offset(currentPosition).Limit(config.ServiceSettings.ItemsPageSize) userSql, userParams, _ := userQuery.Prepared(true).ToSQL() 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 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 make([]*model.UserWithDetail, 0), 0, err } cache.CachePagedSearch( userWithDetails, userCount, []string{"user"}, "user_with_detail", cacheConditions..., ) 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() updates := 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": types.Now(), "last_modified_by": operator, } if userDetail.UnitServiceFee != nil { updates = lo.Assign(updates, goqu.Record{"unit_service_fee": userDetail.UnitServiceFee}) } userDetailUpdateQuery := ur.ds. Update("user_detail"). Set(updates). 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) 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.I("u.username").ILike(pattern), goqu.I("ud.name").ILike(pattern), goqu.I("ud.abbr").ILike(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(tx pgx.Tx, ctx context.Context, uid string, expiration time.Time) (bool, error) { ur.log.Info("更新指定用户的服务有效期限。", zap.String("user id", uid)) 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 := tx.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 }