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

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)
}