Files
kra-new/internal/data/liveclient/account.go
T
2026-09-22 12:04:22 +08:00

173 lines
5.9 KiB
Go

package liveclient
import (
"context"
"errors"
"strconv"
"strings"
"time"
"github.com/google/uuid"
"gorm.io/gorm"
biz "kra/internal/biz/liveclient"
"kra/internal/data/ledger"
)
type Provider interface{ DB() *gorm.DB }
type repo struct{ data Provider }
func NewRepo(data Provider) biz.Repo { return &repo{data: data} }
type userPO struct {
ID uint `gorm:"primaryKey"`
CreatedAt time.Time
UpdatedAt time.Time
DeletedAt gorm.DeletedAt `gorm:"index"`
UUID string
Username string
Password string
NickName string `gorm:"column:nick_name"`
HeaderImg string `gorm:"column:header_img"`
AuthorityID uint `gorm:"column:authority_id"`
Enable int
PasswordUpdatedAt *time.Time
MustChangePassword bool
}
func (userPO) TableName() string { return "sys_users" }
type authorityPO struct {
AuthorityID uint `gorm:"primaryKey;column:authority_id"`
AuthorityName string
ParentID *uint
DataScope int
DefaultRouter string
}
func (authorityPO) TableName() string { return "sys_authorities" }
type userAuthorityPO struct {
UserID uint `gorm:"column:sys_user_id"`
AuthorityID uint `gorm:"column:sys_authority_authority_id"`
}
func (userAuthorityPO) TableName() string { return "sys_user_authority" }
type policyPO struct {
ID uint `gorm:"primaryKey;autoIncrement"`
Ptype string
V0, V1, V2 string
V3, V4, V5 string
}
func (policyPO) TableName() string { return "casbin_rule" }
func (r *repo) Register(ctx context.Context, account *biz.Account, passwordHash string, initialDiamonds int64) (*biz.Account, error) {
now := time.Now()
user := userPO{UUID: uuid.NewString(), Username: account.Username, Password: passwordHash, NickName: account.Nickname, HeaderImg: account.Avatar, AuthorityID: biz.ClientAuthorityID, Enable: 1, PasswordUpdatedAt: &now}
err := r.data.DB().WithContext(ctx).Transaction(func(tx *gorm.DB) error {
var count int64
if err := tx.Model(&userPO{}).Where("username = ?", account.Username).Count(&count).Error; err != nil {
return err
}
if count > 0 {
return biz.ErrUsernameExists
}
root := uint(0)
authority := authorityPO{AuthorityID: biz.ClientAuthorityID, AuthorityName: "直播用户", ParentID: &root, DataScope: 4, DefaultRouter: "404"}
if err := tx.Where("authority_id = ?", authority.AuthorityID).FirstOrCreate(&authority).Error; err != nil {
return err
}
for _, route := range clientRoutes() {
policy := policyPO{Ptype: "p", V0: strconv.FormatUint(uint64(biz.ClientAuthorityID), 10), V1: route.path, V2: route.method}
if err := tx.Where("ptype = ? AND v0 = ? AND v1 = ? AND v2 = ?", policy.Ptype, policy.V0, policy.V1, policy.V2).FirstOrCreate(&policy).Error; err != nil {
return err
}
}
if err := tx.Create(&user).Error; err != nil {
if duplicate(err) {
return biz.ErrUsernameExists
}
return err
}
if err := tx.Create(&userAuthorityPO{UserID: user.ID, AuthorityID: biz.ClientAuthorityID}).Error; err != nil {
return err
}
return tx.Create(&ledger.WalletPO{UserID: user.ID, DiamondBalance: initialDiamonds}).Error
})
if err != nil {
return nil, err
}
return r.Get(ctx, user.ID)
}
func clientRoutes() []struct{ method, path string } {
return []struct{ method, path string }{
{method: "POST", path: "/live/client/auth/logout"},
{method: "GET", path: "/live/client/me"},
{method: "GET", path: "/live/client/balance"},
{method: "PUT", path: "/live/client/me"},
{method: "PUT", path: "/live/client/me/password"},
{method: "POST", path: "/live/rooms/:id/gifts"},
{method: "GET", path: "/live/rooms/:id/follow"},
{method: "POST", path: "/live/rooms/:id/follow"},
{method: "DELETE", path: "/live/rooms/:id/follow"},
{method: "POST", path: "/live/recharge/orders"},
{method: "GET", path: "/live/recharge/orders/:orderNo"},
{method: "GET", path: "/live/recharge/orders/:orderNo/status"},
}
}
func (r *repo) Get(ctx context.Context, userID uint) (*biz.Account, error) {
var row struct {
ID uint
CreatedAt time.Time
UUID string
Username string
Nickname string
Avatar string
AuthorityID uint
Enable int
DiamondBalance int64
PasswordUpdatedAt *time.Time
}
err := r.data.DB().WithContext(ctx).Table("sys_users").
Select("sys_users.id, sys_users.created_at, sys_users.uuid, sys_users.username, sys_users.nick_name AS nickname, sys_users.header_img AS avatar, sys_users.authority_id, sys_users.enable, COALESCE(live_wallets.diamond_balance, 0) AS diamond_balance, sys_users.password_updated_at").
Joins("LEFT JOIN live_wallets ON live_wallets.user_id = sys_users.id").
Where("sys_users.id = ? AND sys_users.deleted_at IS NULL", userID).Scan(&row).Error
if err != nil {
return nil, err
}
if row.ID == 0 {
return nil, biz.ErrAccountNotFound
}
return &biz.Account{UserID: row.ID, UUID: row.UUID, Username: row.Username, Nickname: row.Nickname, Avatar: row.Avatar, AuthorityID: row.AuthorityID, Enabled: row.Enable == 1, DiamondBalance: row.DiamondBalance, CreatedAt: row.CreatedAt, PasswordUpdatedAt: row.PasswordUpdatedAt}, nil
}
func (r *repo) UpdateProfile(ctx context.Context, in *biz.ProfileInput) (*biz.Account, error) {
result := r.data.DB().WithContext(ctx).Model(&userPO{}).Where("id = ?", in.UserID).Updates(map[string]any{"nick_name": in.Nickname, "header_img": in.Avatar})
if result.Error != nil {
return nil, result.Error
}
if result.RowsAffected == 0 {
var count int64
if err := r.data.DB().WithContext(ctx).Model(&userPO{}).Where("id = ?", in.UserID).Count(&count).Error; err != nil {
return nil, err
}
if count == 0 {
return nil, biz.ErrAccountNotFound
}
}
return r.Get(ctx, in.UserID)
}
func duplicate(err error) bool {
if err == nil || errors.Is(err, biz.ErrUsernameExists) {
return false
}
text := strings.ToLower(err.Error())
return strings.Contains(text, "unique") || strings.Contains(text, "duplicate")
}