package liveclient import ( "context" "strings" "time" kratoserrors "github.com/go-kratos/kratos/v3/errors" "golang.org/x/crypto/bcrypt" "kra/internal/biz/system" ) const ( ClientAuthorityID uint = 1000 InitialDiamonds int64 = 100 ) var ( ErrInvalidAccount = kratoserrors.BadRequest("LIVE_CLIENT_INVALID", "invalid client account data") ErrUsernameExists = kratoserrors.BadRequest("LIVE_CLIENT_USERNAME_EXISTS", "username already exists") ErrAccountNotFound = kratoserrors.NotFound("LIVE_CLIENT_NOT_FOUND", "client account not found") ) type Account struct { UserID uint UUID string Username string Nickname string Avatar string AuthorityID uint Enabled bool DiamondBalance int64 CreatedAt time.Time PasswordUpdatedAt *time.Time } type RegisterInput struct { Username, Nickname, Password, ConfirmPassword string } type LoginInput struct { Username, Password, IP, Agent string } type ProfileInput struct { UserID uint Nickname, Avatar string } type PasswordInput struct { UserID uint OldPassword, NewPassword, ConfirmPassword string IP, Agent string } type AuthResult struct { Account *Account Auth *system.AuthenticationResult } type Repo interface { Register(context.Context, *Account, string, int64) (*Account, error) Get(context.Context, uint) (*Account, error) UpdateProfile(context.Context, *ProfileInput) (*Account, error) } type Usecase struct { repo Repo security *system.SecurityUsecase auth *system.AuthenticationUsecase users *system.UserUsecase } func NewUsecase(repo Repo, security *system.SecurityUsecase, auth *system.AuthenticationUsecase, users *system.UserUsecase) *Usecase { return &Usecase{repo: repo, security: security, auth: auth, users: users} } func (uc *Usecase) Register(ctx context.Context, in *RegisterInput, ip, agent string) (*AuthResult, error) { if in == nil { return nil, ErrInvalidAccount } in.Username = strings.TrimSpace(in.Username) if n := len([]rune(in.Username)); n < 3 || n > 32 || in.Password == "" || in.Password != in.ConfirmPassword { return nil, ErrInvalidAccount } in.Nickname = strings.TrimSpace(in.Nickname) if in.Nickname == "" { in.Nickname = in.Username } else if n := len([]rune(in.Nickname)); n < 2 || n > 16 { return nil, ErrInvalidAccount } config, err := uc.security.Current(ctx) if err != nil { return nil, err } if err = uc.security.ValidatePassword(config, in.Password); err != nil { return nil, err } hash, err := bcrypt.GenerateFromPassword([]byte(in.Password), bcrypt.DefaultCost) if err != nil { return nil, err } account, err := uc.repo.Register(ctx, &Account{Username: in.Username, Nickname: in.Nickname, AuthorityID: ClientAuthorityID, Enabled: true}, string(hash), InitialDiamonds) if err != nil { return nil, err } auth, err := uc.auth.AuthenticateRegisteredUser(ctx, accountUser(account), ip, agent) if err != nil { return nil, err } return &AuthResult{Account: account, Auth: auth}, nil } func (uc *Usecase) Login(ctx context.Context, in *LoginInput) (*AuthResult, error) { if in == nil || strings.TrimSpace(in.Username) == "" || in.Password == "" { return nil, ErrInvalidAccount } auth, err := uc.auth.LoginClient(ctx, &system.LoginAttempt{Username: strings.TrimSpace(in.Username), Password: in.Password, IP: in.IP, Agent: in.Agent}) if err != nil { return nil, err } account, err := uc.repo.Get(ctx, auth.User.ID) if err != nil { return nil, err } return &AuthResult{Account: account, Auth: auth}, nil } func (uc *Usecase) Get(ctx context.Context, userID uint) (*Account, error) { if userID == 0 { return nil, ErrAccountNotFound } return uc.repo.Get(ctx, userID) } func (uc *Usecase) UpdateProfile(ctx context.Context, in *ProfileInput) (*Account, error) { if in == nil || in.UserID == 0 { return nil, ErrInvalidAccount } in.Nickname = strings.TrimSpace(in.Nickname) in.Avatar = strings.TrimSpace(in.Avatar) if in.Nickname != "" { if n := len([]rune(in.Nickname)); n < 2 || n > 16 { return nil, ErrInvalidAccount } } if len([]rune(in.Avatar)) > 1000 { return nil, ErrInvalidAccount } return uc.repo.UpdateProfile(ctx, in) } func (uc *Usecase) ChangePassword(ctx context.Context, in *PasswordInput) (*AuthResult, error) { if in == nil || in.UserID == 0 || in.OldPassword == "" || in.NewPassword == "" || in.NewPassword != in.ConfirmPassword { return nil, ErrInvalidAccount } config, err := uc.security.Current(ctx) if err != nil { return nil, err } if err = uc.security.ValidatePassword(config, in.NewPassword); err != nil { return nil, err } if err = uc.users.ChangePassword(ctx, in.UserID, in.OldPassword, in.NewPassword); err != nil { return nil, err } account, err := uc.repo.Get(ctx, in.UserID) if err != nil { return nil, err } auth, err := uc.auth.AuthenticateRegisteredUser(ctx, accountUser(account), in.IP, in.Agent) if err != nil { return nil, err } return &AuthResult{Account: account, Auth: auth}, nil } func accountUser(account *Account) *system.User { return &system.User{ID: account.UserID, UUID: account.UUID, Username: account.Username, NickName: account.Nickname, HeaderImg: account.Avatar, AuthorityID: account.AuthorityID, Enable: 1, PasswordUpdatedAt: account.PasswordUpdatedAt} }