Files
kami_backend/internal/logic/card_apple_account/schdule.go
danial 15e2426e85 feat(camel_oil): 新增骆驼加油账号管理模块
- 实现账号增删改查接口和逻辑
- 支持账号状态更新及状态历史记录功能
- 提供账号列表、历史和统计信息查询API
- 实现账号轮询机制,支持按使用时间轮询获取账号
- 增加账号登录流程及批量登录功能,集成接码平台和平台API
- 管理账号订单容量,支持容量检查与账号登录触发
- 提供账号池状态统计接口
- 账号历史记录查询支持多种变更类型文本展示
- 密码等敏感信息采用脱敏展示
- 完善日志记录和错误处理机制,保证业务稳定运行
2025-11-21 00:49:50 +08:00

338 lines
11 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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
}