- 增加骆驼模块设置接口支持获取和更新配置 - 使用Redis缓存设置数据,实现模块配置的持久化管理 - 引入预拉取订单日志功能,支持日志的保存和按时间范围查询 - 预拉取订单请求响应数据记录到Redis,方便问题追踪 - 根据模块设置动态调整账号登录、预拉取订单并发数量 - 调整账号登录逻辑以支持配置的并发控制 - 优化预拉取订单补充流程,支持多面额库存管理 - 修正集成API请求函数名及调用,记录详细调用日志数据 - 调整定时任务调度频率,增加预拉取订单补充任务的执行频率 - 升级golang版本到1.25.5,保持开发环境最新状态
402 lines
13 KiB
Go
402 lines
13 KiB
Go
package camel_oil
|
||
|
||
import (
|
||
"context"
|
||
"fmt"
|
||
"github.com/gogf/gf/v2/errors/gerror"
|
||
"time"
|
||
|
||
"kami/internal/consts"
|
||
"kami/internal/dao"
|
||
"kami/internal/model/do"
|
||
"kami/internal/model/entity"
|
||
"kami/utility/config"
|
||
"kami/utility/integration/camel_oil_api"
|
||
"kami/utility/integration/pig"
|
||
|
||
"github.com/gogf/gf/v2/os/glog"
|
||
"github.com/gogf/gf/v2/os/gtime"
|
||
)
|
||
|
||
// CronAccountPrefetchTask 账户预拉取定时任务 - 由cron调度器定期调用
|
||
// 流程:并发拉取账户到指定数量
|
||
func (s *sCamelOil) CronAccountPrefetchTask(ctx context.Context) error {
|
||
//判断是否在 8 点到下午 20点之间
|
||
if gtime.Now().Hour() < 9 || gtime.Now().Hour() >= 20 {
|
||
return nil
|
||
}
|
||
|
||
// 获取设置
|
||
settings, err := GetCamelOilSettings(ctx)
|
||
if err != nil {
|
||
glog.Errorf(ctx, "获取骆驼模块设置失败: %v", err)
|
||
return err
|
||
}
|
||
|
||
// 1. 获取当前在线账号数量
|
||
m := dao.V1CamelOilAccount.Ctx(ctx).DB(config.GetDatabaseV1())
|
||
onlineCount, err := m.Where(dao.V1CamelOilAccount.Columns().Status, consts.CamelOilAccountStatusOnline).
|
||
WhereOr(dao.V1CamelOilAccount.Columns().Status, consts.CamelOilAccountStatusSendCode).
|
||
Count()
|
||
if err != nil {
|
||
glog.Errorf(ctx, "获取在线账号数量失败: %v", err)
|
||
onlineCount = 0
|
||
}
|
||
|
||
targetOnlineAccounts := settings.LoginAccountCount
|
||
glog.Infof(ctx, "当前在线账号数量: %d, 目标数量: %d", onlineCount, targetOnlineAccounts)
|
||
|
||
// 2. 如果在线账号少于目标数,触发并发登录
|
||
if onlineCount < targetOnlineAccounts {
|
||
needCount := targetOnlineAccounts - onlineCount
|
||
glog.Infof(ctx, "在线账号不足,需要登录 %d 个账号", needCount)
|
||
|
||
// 使用并发登录提高效率
|
||
successCount, err2 := s.BatchLoginAccounts(ctx, int64(needCount))
|
||
if err2 != nil {
|
||
glog.Errorf(ctx, "批量登录账号失败: %v", err2)
|
||
// 不返回错误,继续执行
|
||
} else {
|
||
glog.Infof(ctx, "成功登录 %d 个账号", successCount)
|
||
}
|
||
}
|
||
return nil
|
||
}
|
||
|
||
// CronPrefetchOrderSupplementTask 预拉取订单补充定时任务 - 由cron调度器定期调用
|
||
// 流程:检查预拉取订单库存,不足时补充
|
||
func (s *sCamelOil) CronPrefetchOrderSupplementTask(ctx context.Context) error {
|
||
// 2. 如果库存不足则补充
|
||
supplementedCount, err := s.SupplementPrefetchOrders(ctx)
|
||
if err != nil {
|
||
glog.Errorf(ctx, "预拉取订单补充失败: %v", err)
|
||
return err
|
||
}
|
||
glog.Infof(ctx, "预拉取订单补充完成,补充数量: %d", supplementedCount)
|
||
|
||
return nil
|
||
}
|
||
|
||
// CronOrderPaymentCheckTask 订单支付状态检测任务 - 由cron调度器定期调用
|
||
func (s *sCamelOil) CronOrderPaymentCheckTask(ctx context.Context) error {
|
||
|
||
// 查询待支付订单(创建时间在24小时内)
|
||
var orders []*entity.V1CamelOilOrder
|
||
err := dao.V1CamelOilOrder.Ctx(ctx).DB(config.GetDatabaseV1()).
|
||
Where(dao.V1CamelOilOrder.Columns().PayStatus, consts.CamelOilPaymentStatusUnpaid).
|
||
WhereGTE(dao.V1CamelOilOrder.Columns().CreatedAt, gtime.Now().Add(time.Hour*6)).
|
||
Scan(&orders)
|
||
|
||
if err != nil {
|
||
glog.Error(ctx, "查询待支付订单失败:", err)
|
||
return err
|
||
}
|
||
|
||
if len(orders) == 0 {
|
||
glog.Debug(ctx, "无待支付订单")
|
||
return nil
|
||
}
|
||
|
||
glog.Infof(ctx, "查询到 %d 个待支付订单", len(orders))
|
||
|
||
paidCount := 0
|
||
timeoutCount := 0
|
||
|
||
for _, order := range orders {
|
||
accountInfo, err2 := s.GetAccountInfo(ctx, order.AccountId)
|
||
if err2 != nil {
|
||
glog.Error(ctx, "获取账号信息失败:", err2)
|
||
// 记录该订单检查失败
|
||
_ = s.UpdateOrderStatus(ctx, order.Id, consts.CamelOilOrderStatusFailed, consts.CamelOilOrderChangeTypeFail, "", fmt.Sprintf("查询账户失败: %v", err2))
|
||
continue
|
||
}
|
||
|
||
// 查询订单状态
|
||
queryResult, err2 := camel_oil_api.NewClient().QueryOrder(ctx, accountInfo.Phone, accountInfo.Token, order.PlatformOrderNo)
|
||
if err2 != nil {
|
||
glog.Error(ctx, "查询订单状态失败:", err2)
|
||
_ = s.RecordOrderHistory(ctx, order.OrderNo, "query_failed", "", fmt.Sprintf("查询订单失败: %v", err))
|
||
continue
|
||
}
|
||
|
||
// 订单已支付
|
||
if queryResult != nil {
|
||
if order.PayStatus != int(consts.CamelOilPaymentStatusPaid) {
|
||
glog.Infof(ctx, "订单%s已支付,金额: %.2f", order.OrderNo, queryResult.Balance)
|
||
_ = s.fillOrderCard(ctx, order.OrderNo, queryResult.CardNumber, queryResult.CardPassword)
|
||
// 增加账户订单计数
|
||
_ = s.IncrementAccountOrderCount(ctx, order.AccountId)
|
||
paidCount++
|
||
}
|
||
continue
|
||
}
|
||
|
||
// 订单未支付,检查是否超时(24小时)
|
||
if gtime.Now().Sub(order.CreatedAt) >= consts.CamelOilOrderExpireDuration {
|
||
glog.Warningf(ctx, "订单%s支付超时,创建时间: %v", order.OrderNo, order.CreatedAt)
|
||
_, _ = dao.V1CamelOilOrder.Ctx(ctx).DB(config.GetDatabaseV1()).
|
||
Where(dao.V1CamelOilOrder.Columns().Id, order.Id).
|
||
Update(&do.V1CamelOilOrder{
|
||
Status: consts.CamelOilOrderStatusFailed,
|
||
PayStatus: consts.CamelOilPaymentStatusTimeout,
|
||
FailureReason: "支付时间超过24小时,支付超时",
|
||
})
|
||
_ = s.RecordOrderHistory(ctx, order.OrderNo, consts.CamelOilOrderChangeTypeTimeout, "", "订单支付超时")
|
||
timeoutCount++
|
||
}
|
||
}
|
||
|
||
glog.Infof(ctx, "订单支付状态检测完成: 已支付=%d, 超时=%d", paidCount, timeoutCount)
|
||
return nil
|
||
}
|
||
|
||
// CronAccountDailyResetTask 账号日重置任务 - 由cron调度器在每日00:00调用
|
||
func (s *sCamelOil) CronAccountDailyResetTask(ctx context.Context) error {
|
||
glog.Info(ctx, "开始执行账号日重置任务")
|
||
|
||
_, _ = dao.V1CamelOilAccount.Ctx(ctx).DB(config.GetDatabaseV1()).
|
||
Where(dao.V1CamelOilAccount.Columns().Status, consts.CamelOilAccountStatusOnline).
|
||
WhereLTE(dao.V1CamelOilAccount.Columns().DailyOrderCount, consts.CamelOilAccountDailyOrderLimit).
|
||
Update(&do.V1CamelOilAccount{
|
||
DailyOrderCount: 0,
|
||
DailyOrderDate: gtime.Now(),
|
||
})
|
||
|
||
//// 查询所有暂停的账号
|
||
//var accounts []*entity.V1CamelOilAccount
|
||
//err := dao.V1CamelOilAccount.Ctx(ctx).DB(config.GetDatabaseV1()).
|
||
// Where(dao.V1CamelOilAccount.Columns().Status, consts.CamelOilAccountStatusPaused).
|
||
// Scan(&accounts)
|
||
//
|
||
//if err != nil {
|
||
// glog.Error(ctx, "查询暂停账号失败:", err)
|
||
// return err
|
||
//}
|
||
//
|
||
//glog.Infof(ctx, "查询到 %d 个暂停账号", len(accounts))
|
||
//
|
||
//resumedCount := 0
|
||
//invalidCount := 0
|
||
//
|
||
//for _, account := range accounts {
|
||
// // 检查是否是昨日的记录
|
||
// if account.DailyOrderDate == nil || account.DailyOrderDate.Format("Y-m-d") != yesterday.Format("Y-m-d") {
|
||
// continue
|
||
// }
|
||
//
|
||
// if account.DailyOrderCount >= consts.CamelOilAccountDailyOrderLimit {
|
||
// // 正常完成,重置为在线
|
||
// _ = s.UpdateAccountStatus(ctx, account.Id, consts.CamelOilAccountStatusOnline,
|
||
// consts.CamelOilAccountChangeTypeResume, "次日重置,恢复使用")
|
||
//
|
||
// // 同时重置日算信息
|
||
// _, _ = dao.V1CamelOilAccount.Ctx(ctx).DB(config.GetDatabaseV1()).
|
||
// Where(dao.V1CamelOilAccount.Columns().Id, account.Id).
|
||
// Update(&do.V1CamelOilAccount{
|
||
// DailyOrderCount: 0,
|
||
// DailyOrderDate: gtime.Now(),
|
||
// })
|
||
//
|
||
// resumedCount++
|
||
// } else {
|
||
// // 单日下单不足10个,标记为失效
|
||
// _ = s.UpdateAccountStatus(ctx, account.Id, consts.CamelOilAccountStatusInvalid,
|
||
// consts.CamelOilAccountChangeTypeInvalidate, "单日下单不足10个,账号失效")
|
||
//
|
||
// invalidCount++
|
||
// }
|
||
//}
|
||
//
|
||
//glog.Infof(ctx, "账号日重置任务完成: 恢复=%d, 失效=%d", resumedCount, invalidCount)
|
||
return nil
|
||
}
|
||
|
||
func (s *sCamelOil) CronVerifyCodeCheckTask(ctx context.Context) error {
|
||
var accounts []*entity.V1CamelOilAccount
|
||
err := dao.V1CamelOilAccount.Ctx(ctx).DB(config.GetDatabaseV1()).
|
||
Where(dao.V1CamelOilAccount.Columns().Status, consts.CamelOilAccountStatusSendCode).
|
||
Scan(&accounts)
|
||
|
||
if err != nil {
|
||
glog.Error(ctx, "查询待验证码账号失败:", err)
|
||
return err
|
||
}
|
||
|
||
glog.Infof(ctx, "查询到 %d 个待验证码账号", len(accounts))
|
||
|
||
successCount := 0
|
||
failCount := 0
|
||
|
||
pigClient := pig.NewClient()
|
||
camelClient := camel_oil_api.NewClient()
|
||
|
||
for _, account := range accounts {
|
||
//如果时间超过 1 分钟,就是过期
|
||
if gtime.Now().Sub(account.CreatedAt).Minutes() > 1 {
|
||
glog.Warningf(ctx, "验证码已过期,账号ID: %d, 手机号: %s", account.Id, account.Phone)
|
||
_ = s.UpdateAccountStatus(ctx, account.Id, consts.CamelOilAccountStatusInvalid,
|
||
consts.CamelOilAccountChangeTypeLoginFail, "验证码已过期")
|
||
failCount++
|
||
continue
|
||
}
|
||
|
||
// 从野猪平台检测验证码是否已接收
|
||
verifyCode, received, err := pigClient.CheckVerifyCode(ctx, account.Phone)
|
||
if err != nil {
|
||
glog.Warningf(ctx, "检测验证码失败,账号ID: %d, 手机号: %s, 错误: %v", account.Id, account.Phone, err)
|
||
failCount++
|
||
continue
|
||
}
|
||
|
||
// 验证码未接收,继续等待
|
||
if !received {
|
||
continue
|
||
}
|
||
|
||
// 验证码已接收,执行登录
|
||
glog.Infof(ctx, "验证码已接收,开始执行登录,账号ID: %d, 手机号: %s", account.Id, account.Phone)
|
||
|
||
// 调用骆驼加油平台执行登录
|
||
token, err := camelClient.LoginWithCaptcha(ctx, account.Phone, verifyCode)
|
||
if err != nil {
|
||
glog.Errorf(ctx, "登录失败,账号ID: %d, 手机号: %s, 错误: %v", account.Id, account.Phone, err)
|
||
// 标记账号失效
|
||
_ = s.UpdateAccountStatus(ctx, account.Id, consts.CamelOilAccountStatusInvalid,
|
||
consts.CamelOilAccountChangeTypeLoginFail, fmt.Sprintf("登录失败: %v", err))
|
||
failCount++
|
||
continue
|
||
}
|
||
|
||
// 保存 token
|
||
_, err = dao.V1CamelOilAccount.Ctx(ctx).DB(config.GetDatabaseV1()).
|
||
Where(dao.V1CamelOilAccount.Columns().Id, account.Id).
|
||
Update(&do.V1CamelOilAccount{
|
||
Token: token,
|
||
LastLoginAt: gtime.Now(),
|
||
})
|
||
|
||
if err != nil {
|
||
glog.Errorf(ctx, "保存 token 失败,账号ID: %d, 错误: %v", account.Id, err)
|
||
failCount++
|
||
continue
|
||
}
|
||
|
||
// 调用 UpdateAccountStatus 更新账号状态为在线
|
||
_ = s.UpdateAccountStatus(ctx, account.Id, consts.CamelOilAccountStatusOnline,
|
||
consts.CamelOilAccountChangeTypeLogin, fmt.Sprintf("登录成功,手机号: %s", account.Phone))
|
||
|
||
glog.Infof(ctx, "账号登录成功,ID: %d, 手机号: %s, Token: %s", account.Id, account.Phone, token)
|
||
|
||
successCount++
|
||
}
|
||
|
||
glog.Infof(ctx, "验证码检测任务完成: 成功=%d, 失败=%d", successCount, failCount)
|
||
return nil
|
||
}
|
||
|
||
// CronCardBindingTask 卡密绑定定时任务 - 由cron调度器定期调用
|
||
// 流程:处理已支付但未绑定 Token 的订单,进行卡密绑定
|
||
func (s *sCamelOil) CronCardBindingTask(ctx context.Context) error {
|
||
glog.Info(ctx, "开始执行卡密绑定任务")
|
||
|
||
// 查询已支付但未绑定 Token 的订单
|
||
var orders []*entity.V1CamelOilOrder
|
||
err := dao.V1CamelOilOrder.Ctx(ctx).DB(config.GetDatabaseV1()).
|
||
Where(dao.V1CamelOilOrder.Columns().PayStatus, consts.CamelOilPaymentStatusPaid).
|
||
WhereNotIn(dao.V1CamelOilOrder.Columns().Id,
|
||
dao.V1CamelOilCardBinding.Ctx(ctx).DB(config.GetDatabaseV1()).
|
||
Fields(dao.V1CamelOilCardBinding.Columns().OrderId)).
|
||
Limit(50).
|
||
Scan(&orders)
|
||
|
||
if err != nil {
|
||
glog.Error(ctx, "查询待绑定订单失败", err)
|
||
return err
|
||
}
|
||
|
||
if len(orders) == 0 {
|
||
glog.Debug(ctx, "无待绑定订单")
|
||
return nil
|
||
}
|
||
|
||
glog.Infof(ctx, "查询到 %d 个待绑定订单", len(orders))
|
||
|
||
successCount := 0
|
||
failCount := 0
|
||
|
||
for _, order := range orders {
|
||
// 检查是否有可用的 Token
|
||
availableTokenCount, err := dao.V1CamelOilToken.Ctx(ctx).DB(config.GetDatabaseV1()).
|
||
Where(dao.V1CamelOilToken.Columns().Status, consts.CamelOilTokenStatusAvailable).
|
||
Count()
|
||
|
||
if err != nil || availableTokenCount == 0 {
|
||
glog.Warningf(ctx, "无可用 Token,订单 ID: %d 无法绑定", order.Id)
|
||
failCount++
|
||
continue
|
||
}
|
||
|
||
// 检查卡号和卡密是否存在
|
||
if order.CardNumber == "" || order.CardPassword == "" {
|
||
glog.Warningf(ctx, "订单 %d 卡号或卡密未填写,无法绑定", order.Id)
|
||
failCount++
|
||
continue
|
||
}
|
||
|
||
// 尝试绑定卡密到 Token
|
||
_, err = s.BindCardToToken(ctx, order.Id, order.CardNumber, order.CardPassword, order.Amount)
|
||
if err != nil {
|
||
glog.Errorf(ctx, "绑定卡密到 Token 失败,订单 ID: %d, 错误: %v", order.Id, err)
|
||
failCount++
|
||
continue
|
||
}
|
||
|
||
glog.Infof(ctx, "订单 %d 卡密绑定成功", order.Id)
|
||
successCount++
|
||
}
|
||
|
||
glog.Infof(ctx, "卡密绑定任务完成: 成功=%d, 失败=%d", successCount, failCount)
|
||
return nil
|
||
}
|
||
|
||
// CronCleanExpiredPrefetchOrders 清理过期的预拉取订单
|
||
func (s *sCamelOil) CronCleanExpiredPrefetchOrders(ctx context.Context) (cleanedCount int, err error) {
|
||
m := dao.V1CamelOilPrefetchOrder.Ctx(ctx).DB(config.GetDatabaseV1())
|
||
|
||
// 查询已过期的待匹配订单
|
||
var expiredOrders []*entity.V1CamelOilPrefetchOrder
|
||
err = m.Where(dao.V1CamelOilPrefetchOrder.Columns().Status, consts.CamelOilPrefetchOrderStatusPending).
|
||
WhereLTE(dao.V1CamelOilPrefetchOrder.Columns().ExpireAt, gtime.Now()).
|
||
Scan(&expiredOrders)
|
||
|
||
if err != nil {
|
||
return 0, gerror.Wrap(err, "查询过期订单失败")
|
||
}
|
||
|
||
if len(expiredOrders) == 0 {
|
||
return 0, nil
|
||
}
|
||
|
||
// 标记为已过期
|
||
for _, order := range expiredOrders {
|
||
_, err = m.Where(dao.V1CamelOilPrefetchOrder.Columns().Id, order.Id).
|
||
Update(&do.V1CamelOilPrefetchOrder{
|
||
Status: int(consts.CamelOilPrefetchOrderStatusExpired),
|
||
})
|
||
|
||
if err != nil {
|
||
glog.Warningf(ctx, "标记预拉取订单为过期失败,ID=%d: %v", order.Id, err)
|
||
continue
|
||
}
|
||
|
||
// 记录历史
|
||
_ = s.recordPrefetchOrderHistory(ctx, order.Id, consts.CamelOilPrefetchOrderChangeTypeExpire,
|
||
order.AccountId, order.AccountName, "订单已过期,自动标记失效", nil)
|
||
|
||
cleanedCount++
|
||
}
|
||
|
||
glog.Infof(ctx, "清理过期预拉取订单完成,清理数量: %d", cleanedCount)
|
||
return cleanedCount, nil
|
||
}
|