- 实现账号增删改查接口和逻辑 - 支持账号状态更新及状态历史记录功能 - 提供账号列表、历史和统计信息查询API - 实现账号轮询机制,支持按使用时间轮询获取账号 - 增加账号登录流程及批量登录功能,集成接码平台和平台API - 管理账号订单容量,支持容量检查与账号登录触发 - 提供账号池状态统计接口 - 账号历史记录查询支持多种变更类型文本展示 - 密码等敏感信息采用脱敏展示 - 完善日志记录和错误处理机制,保证业务稳定运行
338 lines
11 KiB
Go
338 lines
11 KiB
Go
package card_apple_account
|
||
|
||
import (
|
||
"context"
|
||
"fmt"
|
||
"github.com/gogf/gf/v2/os/gmlock"
|
||
"kami/utility/cache"
|
||
"slices"
|
||
"time"
|
||
|
||
"github.com/duke-git/lancet/v2/pointer"
|
||
"github.com/duke-git/lancet/v2/slice"
|
||
"github.com/gogf/gf/v2/os/gtime"
|
||
"github.com/gogf/gf/v2/text/gstr"
|
||
"github.com/gogf/gf/v2/util/gconv"
|
||
|
||
"kami/internal/consts"
|
||
"kami/internal/dao"
|
||
"kami/internal/model"
|
||
"kami/internal/model/entity"
|
||
"kami/internal/service"
|
||
"kami/utility/config"
|
||
"kami/utility/utils"
|
||
|
||
"github.com/shopspring/decimal"
|
||
)
|
||
|
||
// GetAccordingAccountV3 账户分配算法,适合多线程
|
||
func (a *sAppleAccount) GetAccordingAccountV3(ctx context.Context, machineId string, amount decimal.Decimal) (data *entity.V1CardAppleAccountInfo, err error) {
|
||
gmlock.Lock("sAppleAccount_GetAccordingAccount")
|
||
defer gmlock.Unlock("sAppleAccount_GetAccordingAccount")
|
||
|
||
//获取所有的可用用户
|
||
users, err := service.SysUser().GetUsersAll(ctx)
|
||
if err != nil {
|
||
return
|
||
}
|
||
// 把管理员临时添加进来
|
||
users = append(users, &model.SysUserSimpleOutput{Id: ""})
|
||
|
||
// 检索当前用户在所有用户的位置
|
||
currentAccountInfo, err := a.GetCurrentTargetAccount(ctx, machineId)
|
||
if err != nil {
|
||
return
|
||
}
|
||
|
||
/*
|
||
1. 当前账号1分钟内调度过5次以上的要排除
|
||
2. 其他节点90s内调度过的要排除
|
||
*/
|
||
|
||
//获取当前用户需要排除的账号
|
||
accountInfos, err := a.GetExcludeAccounts(ctx, machineId)
|
||
|
||
currentUserIndex := slices.IndexFunc(users, func(item *model.SysUserSimpleOutput) bool {
|
||
return item.Id == currentAccountInfo.UserId
|
||
})
|
||
//如果没有当前账户,则从第一个用户开始
|
||
if currentUserIndex == -1 {
|
||
currentUserIndex = 0
|
||
}
|
||
|
||
isEnough := false
|
||
excludeAccountList := a.getAllCheckedAccountByFirstAccount(ctx)
|
||
//排除掉其他节点调度的账号
|
||
slice.ForEach(accountInfos, func(index int, item *model.AccountIdInfo) {
|
||
excludeAccountList = append(excludeAccountList, item.AccountId)
|
||
})
|
||
|
||
for i := 0; i < len(users)+1; i++ {
|
||
|
||
data = &entity.V1CardAppleAccountInfo{}
|
||
|
||
isEnough = false
|
||
// 从当前用户开始,循环遍历所有用户
|
||
currentUser := users[(currentUserIndex+i)%len(users)]
|
||
m := dao.V1CardAppleAccountInfo.Ctx(ctx).DB(config.GetDatabaseV1())
|
||
|
||
if currentUser.Id != "" {
|
||
m = m.Where(dao.V1CardAppleAccountInfo.Columns().CreatedUserId, currentUser.Id)
|
||
} else {
|
||
//查找管理员上传的id
|
||
m = m.Where(fmt.Sprintf("`%s` = ? OR `%s` is NULL", dao.V1CardAppleAccountInfo.Columns().CreatedUserId, dao.V1CardAppleAccountInfo.Columns().CreatedUserId), currentUser.Id)
|
||
}
|
||
|
||
mt := m.OrderAsc(dao.V1CardAppleAccountInfo.Columns().CreatedAt).
|
||
Where(dao.V1CardAppleAccountInfo.Columns().Status, consts.AppleAccountNormal)
|
||
//排除id
|
||
if len(excludeAccountList) > 0 {
|
||
mt = mt.WhereNotIn(dao.V1CardAppleAccountInfo.Columns().Account, excludeAccountList)
|
||
}
|
||
|
||
// 查找当前用户的所有订单
|
||
err = mt.Scan(&data)
|
||
err = utils.HandleNoRowsError(err)
|
||
// 管理员
|
||
if data.Id != "" {
|
||
// 当前用户是正在使用账户,但当前账户不是正在使用的情况,并且不是循环完一圈的情况
|
||
if data.CreatedUserId == currentAccountInfo.UserId &&
|
||
data.Id != currentAccountInfo.AccountId &&
|
||
i != len(users) {
|
||
continue
|
||
}
|
||
if currentUser.Id == "" {
|
||
isEnough = true
|
||
break
|
||
}
|
||
|
||
if isNormal, err2 := service.SysUser().CheckUserNormal(ctx, data.CreatedUserId); err2 != nil || !isNormal {
|
||
continue
|
||
}
|
||
|
||
isEnough, _ = service.SysUserPayment().CheckBalanceEnough(ctx, &model.SysUserPaymentCheckBalanceInput{
|
||
UserId: data.CreatedUserId,
|
||
Amount: amount,
|
||
})
|
||
if isEnough {
|
||
break
|
||
}
|
||
}
|
||
}
|
||
|
||
if !isEnough {
|
||
data = &entity.V1CardAppleAccountInfo{}
|
||
_ = a.ClearCurrentTargetAccount(ctx, machineId)
|
||
return
|
||
}
|
||
|
||
// 如果切换账号或者切换id后,需要重新切换账户
|
||
if data.CreatedUserId != currentAccountInfo.UserId ||
|
||
data.Id != currentAccountInfo.AccountId {
|
||
err = a.SetCurrentTargetAccount(ctx, machineId, &model.AccountIdInfo{
|
||
UserId: data.CreatedUserId,
|
||
AccountId: data.Id,
|
||
})
|
||
}
|
||
//账户过期时间
|
||
_ = cache.NewCache().Set(ctx, cache.PrefixRedeemAppleAccountLimitedType.Key(data.Account+":"+utils.GenerateRandomUUID()), time.Now().Unix(), gtime.S*90)
|
||
return
|
||
}
|
||
|
||
// GetAccordingAccount 账户分配算法,适合多线程
|
||
func (a *sAppleAccount) GetAccordingAccount(ctx context.Context, amount decimal.Decimal, excludeAccountIds []string) (data *entity.V1CardAppleAccountInfo, err error) {
|
||
gmlock.Lock("sAppleAccount_GetAccordingAccount")
|
||
defer gmlock.Unlock("sAppleAccount_GetAccordingAccount")
|
||
|
||
//获取所有的可用用户
|
||
users, err := service.SysUser().GetUsersAll(ctx)
|
||
if err != nil {
|
||
return
|
||
}
|
||
// 把管理员临时添加进来
|
||
users = append(users, &model.SysUserSimpleOutput{Id: ""})
|
||
|
||
// 检索当前用户在所有用户的位置
|
||
currentAccountInfo, err := a.GetCurrentTargetAccount(ctx, "tip")
|
||
if err != nil {
|
||
return
|
||
}
|
||
|
||
/*
|
||
1. 当前账号1分钟内调度过5次以上的要排除
|
||
2. 其他节点90s内调度过的要排除
|
||
*/
|
||
|
||
//获取当前用户需要排除的账号
|
||
accountInfos, err := a.GetExcludeAccounts(ctx, "tip")
|
||
|
||
currentUserIndex := slices.IndexFunc(users, func(item *model.SysUserSimpleOutput) bool {
|
||
return item.Id == currentAccountInfo.UserId
|
||
})
|
||
//如果没有当前账户,则从第一个用户开始
|
||
if currentUserIndex == -1 {
|
||
currentUserIndex = 0
|
||
}
|
||
|
||
isEnough := false
|
||
excludeAccountList := a.getAllCheckedAccountByFirstAccount(ctx)
|
||
//排除掉其他节点调度的账号
|
||
slice.ForEach(accountInfos, func(index int, item *model.AccountIdInfo) {
|
||
excludeAccountList = append(excludeAccountList, item.AccountId)
|
||
})
|
||
|
||
for i := 0; i < len(users)+1; i++ {
|
||
|
||
data = &entity.V1CardAppleAccountInfo{}
|
||
|
||
isEnough = false
|
||
// 从当前用户开始,循环遍历所有用户
|
||
currentUser := users[(currentUserIndex+i)%len(users)]
|
||
m := dao.V1CardAppleAccountInfo.Ctx(ctx).DB(config.GetDatabaseV1())
|
||
|
||
if currentUser.Id != "" {
|
||
m = m.Where(dao.V1CardAppleAccountInfo.Columns().CreatedUserId, currentUser.Id)
|
||
} else {
|
||
//查找管理员上传的id
|
||
m = m.Where(dao.V1CardAppleAccountInfo.Columns().CreatedUserId, currentUser.Id).
|
||
WhereOr(dao.V1CardAppleAccountInfo.Columns().CreatedUserId)
|
||
}
|
||
if len(excludeAccountIds) > 0 {
|
||
m = m.WhereNotIn(dao.V1CardAppleAccountInfo.Columns().Id, excludeAccountIds)
|
||
}
|
||
mt := m.OrderAsc(dao.V1CardAppleAccountInfo.Columns().CreatedAt).
|
||
Where(dao.V1CardAppleAccountInfo.Columns().Status, consts.AppleAccountNormal)
|
||
//排除id
|
||
if len(excludeAccountList) > 0 {
|
||
mt = mt.WhereNotIn(dao.V1CardAppleAccountInfo.Columns().Account, excludeAccountList)
|
||
}
|
||
// 查找当前用户的所有订单
|
||
err = mt.Scan(&data)
|
||
err = utils.HandleNoRowsError(err)
|
||
// 管理员
|
||
if data.Id != "" {
|
||
// 当前用户是正在使用账户,但当前账户不是正在使用的情况,并且不是循环完一圈的情况
|
||
if data.CreatedUserId == currentAccountInfo.UserId &&
|
||
data.Id != currentAccountInfo.AccountId &&
|
||
i != len(users) {
|
||
continue
|
||
}
|
||
if currentUser.Id == "" {
|
||
isEnough = true
|
||
break
|
||
}
|
||
|
||
if isNormal, err2 := service.SysUser().CheckUserNormal(ctx, data.CreatedUserId); err2 != nil || !isNormal {
|
||
continue
|
||
}
|
||
|
||
isEnough, _ = service.SysUserPayment().CheckBalanceEnough(ctx, &model.SysUserPaymentCheckBalanceInput{
|
||
UserId: data.CreatedUserId,
|
||
Amount: amount,
|
||
})
|
||
if isEnough {
|
||
break
|
||
}
|
||
}
|
||
}
|
||
|
||
if !isEnough {
|
||
data = &entity.V1CardAppleAccountInfo{}
|
||
_ = a.ClearCurrentTargetAccount(ctx, "tip")
|
||
return
|
||
}
|
||
|
||
// 如果切换账号或者切换id后,需要重新切换账户
|
||
if data.CreatedUserId != currentAccountInfo.UserId ||
|
||
data.Id != currentAccountInfo.AccountId {
|
||
err = a.SetCurrentTargetAccount(ctx, "tip", &model.AccountIdInfo{
|
||
UserId: data.CreatedUserId,
|
||
AccountId: data.Id,
|
||
})
|
||
}
|
||
//账户过期时间
|
||
_ = cache.NewCache().Set(ctx, cache.PrefixRedeemAppleAccountLimitedType.Key(data.Account+":"+utils.GenerateRandomUUID()), time.Now().Unix(), gtime.S*90)
|
||
return
|
||
}
|
||
|
||
func (a *sAppleAccount) checkAccountLimit(ctx context.Context, accountName string) (isLimited bool) {
|
||
isLimited = false
|
||
keys, err := cache.NewCache().KeyStrings(ctx)
|
||
if err != nil {
|
||
return
|
||
}
|
||
return len(slice.Filter(keys, func(index int, item string) bool {
|
||
tmpKeyList := gstr.Split(item, ":")
|
||
if len(tmpKeyList) == 3 && tmpKeyList[1] == accountName && tmpKeyList[0] == gconv.String(cache.PrefixRedeemAppleAccountLimitedType) {
|
||
return true
|
||
}
|
||
return false
|
||
})) >= 5
|
||
}
|
||
|
||
func (a *sAppleAccount) getAllCheckedAccountByFirstAccount(ctx context.Context) []string {
|
||
targetKeys := a.getAllCheckedAccountByFirstAccountUnUnique(ctx)
|
||
targetKeys = slice.Unique(slice.Filter(targetKeys, func(index int, item string) bool {
|
||
return slice.Count(targetKeys, item) >= 5
|
||
}))
|
||
return targetKeys
|
||
}
|
||
|
||
func (a *sAppleAccount) getAllCheckedAccountByFirstAccountUnUnique(ctx context.Context) []string {
|
||
keys, err := cache.NewCache().KeyStrings(ctx)
|
||
if err != nil {
|
||
return make([]string, 0)
|
||
}
|
||
targetKeys := slice.Filter(keys, func(index int, item string) bool {
|
||
tmpKeyList := gstr.Split(item, ":")
|
||
if len(tmpKeyList) == 3 && tmpKeyList[0] == gconv.String(cache.PrefixRedeemAppleAccountLimitedType) {
|
||
return true
|
||
}
|
||
return false
|
||
})
|
||
targetKeys = slice.Map(targetKeys, func(index int, item string) string {
|
||
return gstr.Split(item, ":")[1]
|
||
})
|
||
return targetKeys
|
||
}
|
||
|
||
func (a *sAppleAccount) getAllCheckedAccountByLastAccount(ctx context.Context) []string {
|
||
keys, err := cache.NewCache().KeyStrings(ctx)
|
||
if err != nil {
|
||
return make([]string, 0)
|
||
}
|
||
//筛选掉其他无用的键值
|
||
targetKeys := slice.Filter(keys, func(index int, item string) bool {
|
||
tmpKeyList := gstr.Split(item, ":")
|
||
if len(tmpKeyList) == 3 && tmpKeyList[0] == gconv.String(cache.PrefixRedeemAppleAccountLimitedType) {
|
||
return true
|
||
}
|
||
return false
|
||
})
|
||
slice.ForEach(keys, func(index int, item string) {
|
||
targetKeys[index] = gstr.Split(item, ":")[1]
|
||
})
|
||
targetKeys = slice.Filter(slice.Unique(targetKeys), func(index int, item string) bool {
|
||
allKeys := slice.Filter(keys, func(index int, itemFilter string) bool {
|
||
return gstr.HasPrefix(itemFilter, cache.PrefixRedeemAppleAccountLimitedType.Key(item)+":")
|
||
})
|
||
return slice.Some(allKeys, func(index int, itemEvery string) bool {
|
||
varTime, _ := cache.NewCache().Get(ctx, itemEvery)
|
||
return pointer.IsNil(varTime) && varTime.Int64() > time.Now().Unix()-60 && slice.Count(allKeys, itemEvery) >= 5
|
||
})
|
||
})
|
||
//清除过期数据
|
||
slice.ForEach(slice.Unique(targetKeys), func(index int, item string) {
|
||
allKeys := slice.Filter(keys, func(index int, itemFilter string) bool {
|
||
return gstr.HasPrefix(itemFilter, cache.PrefixRedeemAppleAccountLimitedType.Key(item)+":")
|
||
})
|
||
if slice.Every(allKeys, func(index int, itemEvery string) bool {
|
||
varTime, _ := cache.NewCache().Get(ctx, itemEvery)
|
||
return pointer.IsNil(varTime) && varTime.Int64() < time.Now().Unix()-60 && slice.Count(allKeys, itemEvery) >= 5
|
||
}) {
|
||
_ = cache.NewCache().Removes(ctx, gconv.Interfaces(allKeys))
|
||
}
|
||
})
|
||
return targetKeys
|
||
}
|