Files
kra-new/internal/biz/business/binding.go
T

190 lines
5.6 KiB
Go

package business
import (
"context"
"github.com/go-kratos/kratos/v3/errors"
"strings"
"time"
)
var (
ErrPlatform = errors.BadRequest("SW_PLATFORM_INVALID", "所属平台不正确")
ErrPlatformUnavailable = errors.BadRequest("SW_PLATFORM_UNAVAILABLE", "平台数据源未配置或暂不可用")
ErrPlatformPasswordConfig = errors.BadRequest("SW_PLATFORM_PASSWORD_CONFIG", "平台密码兼容配置未就绪")
ErrPlatformAccount = errors.BadRequest("SW_PLATFORM_ACCOUNT", "游戏商务账号不存在或已禁用")
ErrPlatformPassword = errors.BadRequest("SW_PLATFORM_PASSWORD", "游戏商务账号或密码错误")
ErrBindingLevel = errors.BadRequest("SW_BINDING_LEVEL", "仅三级商务可绑定游戏 SDK 商务")
ErrBindingOccupied = errors.BadRequest("SW_BINDING_OCCUPIED", "该游戏商务已被其他账号绑定或审核中")
ErrBindingExists = errors.BadRequest("SW_BINDING_EXISTS", "该游戏商务已绑定")
ErrBindingPending = errors.BadRequest("SW_BINDING_PENDING", "该游戏商务已提交,请等待 OA 审核")
ErrBindingNotFound = errors.NotFound("SW_BINDING_NOT_FOUND", "绑定记录不存在")
)
type PlatformKey struct {
SourceDB string
BusierID uint64
}
type PlatformAccount struct {
ID uint64
Account, Password string
Status int
}
type PlatformAccountRepo interface {
FindByAccount(context.Context, string, string) (*PlatformAccount, error)
}
type PlatformPasswordVerifier interface {
Verify(string, string) (bool, error)
}
func NormalizePlatform(value string) (string, error) {
value = strings.ToLower(strings.TrimSpace(value))
switch value {
case "lx", "hw", "yq":
return value, nil
default:
return "", ErrPlatform
}
}
func PlatformName(value string) string {
switch strings.ToLower(strings.TrimSpace(value)) {
case "lx":
return "龙翔"
case "hw":
return "汇玩"
case "yq":
return "游趣"
default:
return value
}
}
type Binding struct {
UserID uint
PlatformKey
Account, UserName string
Status string
RejectReason *string
CreatedAt time.Time
ReviewTime *time.Time
ReviewAccount *string
PromoteCount *int64
}
type BindingFilter struct {
UserID uint
SourceDBs []string
Account string
Status string
}
type BindingRepo interface {
List(context.Context, UserScope, BindingFilter, ...ListOption) ([]*Binding, int64, error)
WithPlatformLock(context.Context, PlatformKey, func(context.Context) error) error
ForPlatform(context.Context, PlatformKey) ([]*Binding, error)
Save(context.Context, *Binding) error
Delete(context.Context, uint, PlatformKey) error
}
type BindingUsecase struct {
repo BindingRepo
accounts *AccountUsecase
platforms PlatformAccountRepo
passwords PlatformPasswordVerifier
}
func NewBindingUsecase(repo BindingRepo, accounts *AccountUsecase, platforms PlatformAccountRepo, passwords PlatformPasswordVerifier) *BindingUsecase {
return &BindingUsecase{repo: repo, accounts: accounts, platforms: platforms, passwords: passwords}
}
func (uc *BindingUsecase) List(ctx context.Context, q BindingFilter, options ...ListOption) ([]*Binding, int64, error) {
actor, current, err := uc.accounts.actor(ctx)
if err != nil {
return nil, 0, err
}
scope := UserScope{All: actor.AuthorityID == 888}
if !scope.All {
scope.SelfID = current.ID
if current.Level >= 3 {
scope.OnlySelf = true
} else {
scope.DescendantPath = descendantPath(current)
}
}
return uc.repo.List(ctx, scope, q, options...)
}
func (uc *BindingUsecase) ForUser(ctx context.Context, id uint) ([]*Binding, error) {
if _, err := uc.accounts.Find(ctx, id); err != nil {
return nil, err
}
rows, _, err := uc.repo.List(ctx, UserScope{SelfID: id, OnlySelf: true}, BindingFilter{UserID: id, Status: "0"}, ListLimit(-1))
return rows, err
}
func (uc *BindingUsecase) Bind(ctx context.Context, source, account, password string) error {
_, current, err := uc.accounts.actor(ctx)
if err != nil {
return err
}
if current.Level != 3 {
return ErrBindingLevel
}
source, err = NormalizePlatform(source)
if err != nil {
return err
}
account = strings.TrimSpace(account)
platform, err := uc.platforms.FindByAccount(ctx, source, account)
if err != nil {
return err
}
if platform == nil || platform.Status != 1 {
return ErrPlatformAccount
}
valid, err := uc.passwords.Verify(password, platform.Password)
if err != nil {
return err
}
if !valid {
return ErrPlatformPassword
}
key := PlatformKey{SourceDB: source, BusierID: platform.ID}
// 平台验密完成后才开启主库事务,避免跨库事务切源。
return uc.repo.WithPlatformLock(ctx, key, func(txctx context.Context) error {
bindings, err := uc.repo.ForPlatform(txctx, key)
if err != nil {
return err
}
for _, b := range bindings {
if b.UserID != current.ID && (b.Status == "0" || b.Status == "3") {
return ErrBindingOccupied
}
}
for _, b := range bindings {
if b.UserID == current.ID {
if b.Status == "0" {
return ErrBindingExists
}
if b.Status == "3" {
return ErrBindingPending
}
}
}
return uc.repo.Save(txctx, &Binding{UserID: current.ID, PlatformKey: key, Account: account, Status: "3", CreatedAt: time.Now()})
})
}
func (uc *BindingUsecase) Unbind(ctx context.Context, key PlatformKey) error {
_, current, err := uc.accounts.actor(ctx)
if err != nil {
return err
}
key.SourceDB, err = NormalizePlatform(key.SourceDB)
if err != nil {
return err
}
if key.BusierID == 0 {
return ErrBindingNotFound
}
return uc.repo.WithPlatformLock(ctx, key, func(txctx context.Context) error { return uc.repo.Delete(txctx, current.ID, key) })
}