package auth import ( "context" "errors" "time" "hfb_sys/backend/internal/model" "gorm.io/gorm" "gorm.io/gorm/clause" ) type UserRepository struct { db *gorm.DB } func NewUserRepository(db *gorm.DB) *UserRepository { return &UserRepository{db: db} } func (r *UserRepository) FindByID(ctx context.Context, id uint64) (*model.User, error) { var user model.User if err := r.db.WithContext(ctx).First(&user, id).Error; err != nil { return nil, err } return &user, nil } // FindActiveForToken 校验用户仍可用且令牌版本未被撤销。 func (r *UserRepository) FindActiveForToken(ctx context.Context, id uint64, tokenVersion int64) (*model.User, error) { if r == nil || r.db == nil { return nil, ErrDependencyUnavailable } user, err := r.FindByID(ctx, id) if err != nil { return nil, err } if user.Status != "active" || user.TokenVersion <= 0 || user.TokenVersion != tokenVersion { return nil, ErrUserDisabled } return user, nil } func (r *UserRepository) UpdateProfile(ctx context.Context, id uint64, nickname string, avatarURL string) (*model.User, error) { if err := r.db.WithContext(ctx).Model(&model.User{}).Where("id = ?", id).Updates(map[string]any{ "nickname": nickname, "avatar_url": avatarURL, }).Error; err != nil { return nil, err } return r.FindByID(ctx, id) } func (r *UserRepository) FindOrCreateByPhone(ctx context.Context, phone string) (*model.User, error) { now := time.Now() user := model.User{ Phone: phone, Nickname: "用户" + phone[len(phone)-4:], RealnameStatus: "unverified", RiskStatus: "normal", CreditScore: 100, Status: "active", TokenVersion: 1, LastLoginAt: &now, } err := r.db.WithContext(ctx).Clauses(clause.OnConflict{ Columns: []clause.Column{{Name: "phone"}}, DoUpdates: clause.AssignmentColumns([]string{"last_login_at", "updated_at"}), }).Create(&user).Error if err != nil { return nil, err } var found model.User if err := r.db.WithContext(ctx).Where("phone = ?", phone).First(&found).Error; err != nil { return nil, err } return &found, nil } func (r *UserRepository) FindByPhone(ctx context.Context, phone string) (*model.User, error) { var user model.User if err := r.db.WithContext(ctx).Where("phone = ?", phone).First(&user).Error; err != nil { return nil, err } return &user, nil } func (r *UserRepository) SetPassword(ctx context.Context, userID uint64, hash string) error { return r.db.WithContext(ctx).Model(&model.User{}).Where("id = ?", userID).Updates(map[string]any{ "password_hash": hash, "token_version": gorm.Expr("token_version + 1"), }).Error } // RevokeTokens 使当前用户的所有 access/refresh token 立即失效。 func (r *UserRepository) RevokeTokens(ctx context.Context, userID uint64) error { return r.db.WithContext(ctx).Model(&model.User{}).Where("id = ?", userID). UpdateColumn("token_version", gorm.Expr("token_version + 1")).Error } func (r *UserRepository) RegisterWithPassword(ctx context.Context, phone string, hash string) (*model.User, error) { now := time.Now() user := model.User{ Phone: phone, PasswordHash: hash, Nickname: "用户" + phone[len(phone)-4:], RealnameStatus: "unverified", RiskStatus: "normal", CreditScore: 100, Status: "active", TokenVersion: 1, LastLoginAt: &now, } err := r.db.WithContext(ctx).Create(&user).Error if err != nil { return nil, err } return &user, nil } func IsNotFound(err error) bool { return errors.Is(err, gorm.ErrRecordNotFound) }