242 lines
7.8 KiB
Go
242 lines
7.8 KiB
Go
package business
|
|
|
|
import (
|
|
"context"
|
|
"strconv"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/go-kratos/kratos/v3/errors"
|
|
"golang.org/x/crypto/bcrypt"
|
|
"kra/internal/biz/system"
|
|
)
|
|
|
|
var (
|
|
ErrAccountNotFound = errors.NotFound("SW_ACCOUNT_NOT_FOUND", "商务账号不存在")
|
|
ErrAccountScope = errors.Forbidden("SW_ACCOUNT_SCOPE", "没有权限访问用户数据")
|
|
ErrChildLevel = errors.BadRequest("SW_CHILD_LEVEL", "三级商务不可再创建下级,请使用绑定游戏商务")
|
|
ErrAccountInput = errors.BadRequest("SW_ACCOUNT_INPUT", "商务账号参数不正确")
|
|
ErrAccountExists = errors.BadRequest("SW_ACCOUNT_EXISTS", "登录账号已存在")
|
|
ErrPhoneExists = errors.BadRequest("SW_PHONE_EXISTS", "手机号码已存在")
|
|
ErrEmailExists = errors.BadRequest("SW_EMAIL_EXISTS", "邮箱账号已存在")
|
|
ErrBusinessRole = errors.BadRequest("SW_BUSINESS_ROLE", "未找到对应层级的商务角色,请核对角色迁移映射")
|
|
)
|
|
|
|
type Account struct {
|
|
HireDate, Employment string
|
|
ID uint
|
|
Username, NickName, Password, RealName, Phone, Email, QQNumber string
|
|
Level int
|
|
ParentID uint
|
|
IDPath, ParentUserName string
|
|
Status int
|
|
RejectReason string
|
|
AuthorityID uint
|
|
CreatedAt, UpdatedAt time.Time
|
|
PromoteCount *int64
|
|
BindCount int64
|
|
}
|
|
|
|
type AccountCounts struct {
|
|
// 无渠道表时为 nil,避免将未接入数据误报为真实零渠道。
|
|
PromoteCount *int64
|
|
BindCount int64
|
|
}
|
|
|
|
// UserScope 表示商务树可见范围,与框架部门权限相互独立。
|
|
type UserScope struct {
|
|
All, Empty, IncludeSelf, OnlySelf bool
|
|
SelfID uint
|
|
DescendantPath string
|
|
}
|
|
|
|
type AccountFilter struct {
|
|
UserID uint
|
|
Username, NickName, RealName, Phone string
|
|
Level *int
|
|
Status *int
|
|
BeginTime, EndTime *time.Time
|
|
}
|
|
|
|
type AccountUpdate struct {
|
|
ID uint
|
|
Username, NickName, RealName, Phone, Email, QQNumber, RejectReason *string
|
|
Status *int
|
|
}
|
|
|
|
type ListOptions struct {
|
|
Filter AccountFilter
|
|
Offset, Limit int
|
|
OrderBy string
|
|
Desc bool
|
|
}
|
|
type ListOption func(*ListOptions)
|
|
|
|
func ListFilter(filter AccountFilter) ListOption { return func(o *ListOptions) { o.Filter = filter } }
|
|
func ListOffset(n int) ListOption { return func(o *ListOptions) { o.Offset = n } }
|
|
func ListLimit(n int) ListOption { return func(o *ListOptions) { o.Limit = n } }
|
|
func ListOrderBy(key string, desc bool) ListOption {
|
|
return func(o *ListOptions) { o.OrderBy, o.Desc = key, desc }
|
|
}
|
|
func NewListOptions(options ...ListOption) ListOptions {
|
|
o := ListOptions{Limit: 10, OrderBy: "userId"}
|
|
for _, option := range options {
|
|
option(&o)
|
|
}
|
|
return o
|
|
}
|
|
|
|
type AccountRepo interface {
|
|
Find(context.Context, uint) (*Account, error)
|
|
List(context.Context, UserScope, ...ListOption) ([]*Account, int64, error)
|
|
Create(context.Context, *Account) error
|
|
Update(context.Context, *Account) error
|
|
AuthorityForLevel(context.Context, int) (uint, error)
|
|
DirectCounts(context.Context, []uint) (map[uint]AccountCounts, error)
|
|
}
|
|
|
|
type AccountUsecase struct{ repo AccountRepo }
|
|
|
|
func NewAccountUsecase(repo AccountRepo) *AccountUsecase { return &AccountUsecase{repo: repo} }
|
|
|
|
func (uc *AccountUsecase) actor(ctx context.Context) (system.Actor, *Account, error) {
|
|
actor, ok := system.ActorFromContext(ctx)
|
|
if !ok || actor.UserID == 0 {
|
|
return actor, nil, ErrUnauthenticated
|
|
}
|
|
account, err := uc.repo.Find(ctx, actor.UserID)
|
|
return actor, account, err
|
|
}
|
|
|
|
func descendantPath(account *Account) string {
|
|
path := strings.TrimSpace(account.IDPath)
|
|
if path == "" {
|
|
path = "0"
|
|
}
|
|
return path + "," + strconv.FormatUint(uint64(account.ID), 10)
|
|
}
|
|
|
|
func descendantOf(owner, target *Account) bool {
|
|
if owner == nil || target == nil || owner.ID == target.ID {
|
|
return false
|
|
}
|
|
path := descendantPath(owner)
|
|
return target.ParentID == owner.ID || target.IDPath == path || strings.HasPrefix(target.IDPath, path+",")
|
|
}
|
|
|
|
func (uc *AccountUsecase) List(ctx context.Context, filter AccountFilter, options ...ListOption) ([]*Account, int64, error) {
|
|
actor, current, err := uc.actor(ctx)
|
|
if err != nil {
|
|
return nil, 0, err
|
|
}
|
|
scope := UserScope{All: actor.AuthorityID == 888}
|
|
if !scope.All {
|
|
if current.Level >= 3 {
|
|
return []*Account{}, 0, nil
|
|
}
|
|
scope.SelfID, scope.DescendantPath = current.ID, descendantPath(current)
|
|
}
|
|
rows, total, err := uc.repo.List(ctx, scope, append(options, ListFilter(filter))...)
|
|
if err != nil || len(rows) == 0 {
|
|
return rows, total, err
|
|
}
|
|
ids := make([]uint, 0, len(rows))
|
|
for _, row := range rows {
|
|
ids = append(ids, row.ID)
|
|
}
|
|
counts, err := uc.repo.DirectCounts(ctx, ids)
|
|
if err != nil {
|
|
return nil, 0, err
|
|
}
|
|
for _, row := range rows {
|
|
row.PromoteCount, row.BindCount = counts[row.ID].PromoteCount, counts[row.ID].BindCount
|
|
}
|
|
return rows, total, nil
|
|
}
|
|
|
|
func (uc *AccountUsecase) Find(ctx context.Context, id uint) (*Account, error) {
|
|
actor, current, err := uc.actor(ctx)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
account, err := uc.repo.Find(ctx, id)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if actor.AuthorityID != 888 && id != current.ID && !descendantOf(current, account) {
|
|
return nil, ErrAccountScope
|
|
}
|
|
return account, nil
|
|
}
|
|
|
|
func (uc *AccountUsecase) CreateChild(ctx context.Context, input *Account) (*Account, error) {
|
|
if input == nil {
|
|
return nil, ErrAccountInput
|
|
}
|
|
actor, parent, err := uc.actor(ctx)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
account := *input
|
|
account.ID, account.ParentID, account.IDPath, account.Level = 0, 0, "0", 1
|
|
if actor.AuthorityID != 888 {
|
|
if parent.Level >= 3 {
|
|
return nil, ErrChildLevel
|
|
}
|
|
level := parent.Level
|
|
if level < 1 {
|
|
level = 1
|
|
}
|
|
account.Level, account.ParentID, account.IDPath = level+1, parent.ID, descendantPath(parent)
|
|
}
|
|
account.AuthorityID, err = uc.repo.AuthorityForLevel(ctx, account.Level)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if account.NickName == "" {
|
|
account.NickName = account.RealName
|
|
}
|
|
hash, err := bcrypt.GenerateFromPassword([]byte(account.Password), bcrypt.DefaultCost)
|
|
if err != nil {
|
|
return nil, ErrAccountInput
|
|
}
|
|
account.Password = string(hash)
|
|
if err = uc.repo.Create(ctx, &account); err != nil {
|
|
return nil, err
|
|
}
|
|
account.Password = ""
|
|
return &account, nil
|
|
}
|
|
|
|
func (uc *AccountUsecase) Update(ctx context.Context, input *AccountUpdate) error {
|
|
if input == nil || input.ID == 0 {
|
|
return ErrAccountInput
|
|
}
|
|
account, err := uc.Find(ctx, input.ID)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if account.AuthorityID == 888 {
|
|
return ErrAccountScope
|
|
}
|
|
if input.Username != nil && *input.Username != account.Username || input.NickName != nil && *input.NickName != account.NickName {
|
|
return ErrAccountInput
|
|
}
|
|
// 编辑资料和审核不会改变商务层级、归属、角色或密码。
|
|
for _, field := range []struct {
|
|
dst *string
|
|
src *string
|
|
}{
|
|
{&account.Username, input.Username}, {&account.NickName, input.NickName}, {&account.RealName, input.RealName},
|
|
{&account.Phone, input.Phone}, {&account.Email, input.Email}, {&account.QQNumber, input.QQNumber}, {&account.RejectReason, input.RejectReason},
|
|
} {
|
|
if field.src != nil {
|
|
*field.dst = *field.src
|
|
}
|
|
}
|
|
if input.Status != nil {
|
|
account.Status = *input.Status
|
|
}
|
|
return uc.repo.Update(ctx, account)
|
|
}
|