package dao import ( "errors" "fmt" "time" "gorm.io/gorm" "micro-bundle/internal/model" "micro-bundle/pkg/app" ) // RunInitialTaskBalanceSync 一次性将 BundleBalance 同步到 TaskBalance // 仅在未执行过且任务余额表为空时运行;执行成功后写入标记,避免再次执行 func RunInitialTaskBalanceSync() error { // 确保标记表存在 _ = app.ModuleClients.TaskBenchDB.AutoMigrate(&model.TaskSyncStatus{}) // 已执行标记检查 var markerCount int64 if err := app.ModuleClients.TaskBenchDB.Model(&model.TaskSyncStatus{}). Where("sync_key = ?", model.InitialSyncKey).Count(&markerCount).Error; err != nil { return err } if markerCount > 0 { return nil } // 安全检查:如果任务余额表已存在数据,则不再执行,同样写入标记 var existing int64 if err := app.ModuleClients.TaskBenchDB.Model(&model.TaskBalance{}).Count(&existing).Error; err != nil { return err } if existing > 0 { _ = app.ModuleClients.TaskBenchDB.Create(&model.TaskSyncStatus{ SyncKey: model.InitialSyncKey, ExecutedAt: time.Now(), Remark: "skipped: task_balance already has data", }).Error return nil } // 获取当前有效(未过期且已支付)的艺人及其最新订单 validArtists, err := GetValidArtistList() if err != nil { return err } fmt.Println(validArtists) if len(validArtists) == 0 { // 不写入已执行标记,留待后续有数据时再次执行 fmt.Println("无数据更新") return nil } // 构造待插入的 TaskBalance 列表 tasks := make([]model.TaskBalance, 0, len(validArtists)) for _, a := range validArtists { // 根据 user_id + order_uuid 获取 BundleBalance 明细 var bb model.BundleBalance if err := app.ModuleClients.BundleDB.Where("user_id = ? AND order_uuid = ?", a.UserID, a.OrderUUID).First(&bb).Error; err != nil { // 若未查到则跳过该条 if err == gorm.ErrRecordNotFound { continue } return err } subNum, telNum, err := fetchIdentityForBundle(&bb) if err != nil { // 无法获取身份信息则跳过该条 continue } tb := model.TaskBalance{ SubNum: subNum, TelNum: telNum, Month: bb.Month, StartAt: bb.StartAt, ExpiredAt: bb.ExpiredAt, CreatedAt: time.Now(), UpdatedAt: time.Now(), } copyBundleToTaskBalance(&tb, &bb) tasks = append(tasks, tb) } // 原子写入:插入 TaskBalance + 插入标记(确保有插入才写标记) tx := app.ModuleClients.TaskBenchDB.Begin() defer func() { if r := recover(); r != nil { tx.Rollback() } }() if len(tasks) == 0 { // 没有可插入的数据,不写标记,直接返回 tx.Rollback() return nil } if err := tx.Create(&tasks).Error; err != nil { tx.Rollback() return err } if err := tx.Create(&model.TaskSyncStatus{ SyncKey: model.InitialSyncKey, ExecutedAt: time.Now(), Remark: "initial sync executed", }).Error; err != nil { tx.Rollback() return err } if err := tx.Commit().Error; err != nil { return err } return nil } // 用户新买套餐时使用 // SyncTaskBalanceFromBundleBalance 增量/每月:根据单条 BundleBalance 同步或更新 TaskBalance(按 sub_num + tel_num + month 唯一) func SyncTaskBalanceFromBundleBalance(bb model.BundleBalance) error { // 获取身份信息(sub_num, tel_num) subNum, telNum, err := fetchIdentityForBundle(&bb) if err != nil { return err } // 组装 TaskBalance tb := model.TaskBalance{ SubNum: subNum, TelNum: telNum, Month: bb.Month, ExpiredAt: bb.ExpiredAt, StartAt: bb.StartAt, UpdatedAt: time.Now(), CreatedAt: time.Now(), } copyBundleToTaskBalance(&tb, &bb) // 查询是否已存在(唯一:sub_num + tel_num + month) var existing model.TaskBalance err = app.ModuleClients.TaskBenchDB. Where("sub_num = ? AND tel_num = ? AND month = ?", subNum, telNum, bb.Month). First(&existing).Error if err != nil { if err == gorm.ErrRecordNotFound { // 不存在则创建 return app.ModuleClients.TaskBenchDB.Create(&tb).Error } return err } // 已存在则更新所有映射字段与时间 tb.ID = existing.ID return app.ModuleClients.TaskBenchDB.Save(&tb).Error } // fetchIdentityForBundle 根据 BundleBalance 拿到 sub_num 与 tel_num func fetchIdentityForBundle(bb *model.BundleBalance) (string, string, error) { // tel_num 来自 micro-account.user type userRow struct { Tel string } var ur userRow if err := app.ModuleClients.BundleDB.Table("`micro-account`.`user`").Unscoped(). Select("tel_num AS tel").Where("id = ?", bb.UserId).Limit(1).Scan(&ur).Error; err != nil { return "", "", err } // customer_num 来自 bundle_order_records(按 order_uuid) type orderRow struct { Customer string } var or orderRow if bb.OrderUUID == "" { return "", "", errors.New("bundle order_uuid missing") } if err := app.ModuleClients.BundleDB.Table("bundle_order_records"). Select("customer_num AS customer").Where("uuid = ?", bb.OrderUUID).Limit(1).Scan(&or).Error; err != nil { return "", "", err } return or.Customer, ur.Tel, nil } // UpdateTaskBalance 每月批量更新任务余额 // 类似于 UpdateBundleBalance 的逻辑,但针对任务余额表 func UpdateTaskBalanceEveryMon() error { // 查询需要更新的任务余额记录(最新月份且未过期的记录) tl := []model.TaskBalance{} if err := app.ModuleClients.TaskBenchDB.Raw(`select * from task_balance tb inner join ( select max(tb.month) as month , sub_num, tel_num from task_balance tb group by tb.sub_num, tb.tel_num ) newest on newest.month = tb.month and (tb.sub_num = newest.sub_num OR tb.tel_num = newest.tel_num) and tb.expired_at > now()`).Find(&tl).Error; err != nil { return err } now := time.Now() month := time.Now().Format("2006-01") for _, v := range tl { if v.Month == month { continue } cal := func(total, limit int) int { // 计算本月发放的限制类型数量 var released int // 已释放的次数 if v.StartAt.Month() == now.Month() && v.StartAt.Year() == now.Year() { } else if v.StartAt.Day() >= 16 { //第一个月释放的 released += (limit + 1) / 2 } else { released += limit } interval := now.Year()*12 + int(now.Month()) - (v.StartAt.Year()*12 + int(v.StartAt.Month())) // 释放了多少个月 released += max(interval-1, 0) * limit // 后续月份释放的 remaining := max(total-released, 0) // 还剩余多少次没有发放 if v.StartAt.Month() == now.Month() && v.StartAt.Year() == now.Year() && v.StartAt.Day() >= 16 { // 本月为第一个月并且16号后购买只给一半(向上取整) return min((limit+1)/2, remaining) } if v.ExpiredAt.Month() == now.Month() && v.ExpiredAt.Year() == now.Year() && v.ExpiredAt.Day() < 16 { // 本月为最后一个月并且16号前到期只给一半(向下取整) return min(limit/2, remaining) } return min(limit, remaining) } v.MonthlyInvalidBundleVideoNumber = v.MonthlyBundleLimitExpiredVideoNumber - v.MonthlyBundleLimitExpiredVideoConsumptionNumber // 当月过期的视频数 v.InvalidBundleVideoNumber += v.MonthlyInvalidBundleVideoNumber v.MonthlyInvalidBundleImageNumber = v.MonthlyBundleLimitExpiredImageNumber - v.MonthlyBundleLimitExpiredImageConsumptionNumber // 当月过期的图片数 v.InvalidBundleImageNumber += v.MonthlyInvalidBundleImageNumber v.MonthlyInvalidBundleDataAnalysisNumber = v.MonthlyBundleLimitExpiredDataAnalysisNumber - v.MonthlyBundleLimitExpiredDataAnalysisConsumptionNumber // 当月过期的数据分析数 v.InvalidBundleDataAnalysisNumber += v.MonthlyInvalidBundleDataAnalysisNumber // 当月可用的限制类型数等于本月方法的套餐和增值两种类型的总和 v.MonthlyBundleLimitExpiredVideoNumber = cal(v.BundleLimitVideoExpiredNumber, v.MonthlyLimitVideoQuotaNumber) v.MonthlyIncreaseLimitExpiredVideoNumber = cal(v.IncreaseLimitVideoExpiredNumber, v.MonthlyLimitVideoQuotaNumber) v.MonthlyBundleLimitVideoNumber = v.MonthlyBundleLimitVideoNumber - v.MonthlyBundleLimitVideoConsumptionNumber + cal(v.BundleLimitVideoNumber, v.MonthlyLimitVideoQuotaNumber) v.MonthlyIncreaseLimitVideoNumber = v.MonthlyIncreaseLimitVideoNumber - v.MonthlyIncreaseLimitVideoConsumptionNumber + cal(v.IncreaseLimitVideoNumber, v.MonthlyLimitVideoQuotaNumber) v.MonthlyBundleLimitExpiredImageNumber = cal(v.BundleLimitImageExpiredNumber, v.MonthlyLimitImageQuotaNumber) v.MonthlyIncreaseLimitExpiredImageNumber = cal(v.IncreaseLimitImageExpiredNumber, v.MonthlyLimitImageQuotaNumber) v.MonthlyBundleLimitImageNumber = v.MonthlyBundleLimitImageNumber - v.MonthlyBundleLimitImageConsumptionNumber + cal(v.BundleLimitImageNumber, v.MonthlyLimitImageQuotaNumber) v.MonthlyIncreaseLimitImageNumber = v.MonthlyIncreaseLimitImageNumber - v.MonthlyIncreaseLimitImageConsumptionNumber + cal(v.IncreaseLimitImageNumber, v.MonthlyLimitImageQuotaNumber) v.MonthlyBundleLimitExpiredDataAnalysisNumber = cal(v.BundleLimitDataAnalysisExpiredNumber, v.MonthlyLimitDataAnalysisQuotaNumber) v.MonthlyIncreaseLimitExpiredDataAnalysisNumber = cal(v.IncreaseLimitDataAnalysisExpiredNumber, v.MonthlyLimitDataAnalysisQuotaNumber) v.MonthlyBundleLimitDataAnalysisNumber = v.MonthlyBundleLimitDataAnalysisNumber - v.MonthlyBundleLimitDataAnalysisConsumptionNumber + cal(v.BundleLimitImageNumber, v.MonthlyLimitImageQuotaNumber) v.MonthlyIncreaseLimitDataAnalysisNumber = v.MonthlyIncreaseLimitDataAnalysisNumber - v.MonthlyIncreaseLimitDataAnalysisConsumptionNumber + cal(v.IncreaseLimitImageNumber, v.MonthlyLimitImageQuotaNumber) // 重置单月消耗数量 v.MonthlyBundleVideoConsumptionNumber = 0 v.MonthlyIncreaseVideoConsumptionNumber = 0 v.MonthlyBundleLimitVideoConsumptionNumber = 0 v.MonthlyIncreaseLimitVideoConsumptionNumber = 0 v.MonthlyBundleLimitExpiredVideoConsumptionNumber = 0 v.MonthlyIncreaseLimitExpiredVideoConsumptionNumber = 0 v.MonthlyNewManualVideoNumber = 0 v.MonthlyManualVideoConsumptionNumber = 0 v.MonthlyBundleImageConsumptionNumber = 0 v.MonthlyIncreaseImageConsumptionNumber = 0 v.MonthlyBundleLimitImageConsumptionNumber = 0 v.MonthlyIncreaseLimitImageConsumptionNumber = 0 v.MonthlyBundleLimitExpiredImageConsumptionNumber = 0 v.MonthlyIncreaseLimitExpiredImageConsumptionNumber = 0 v.MonthlyNewManualImageNumber = 0 v.MonthlyManualImageConsumptionNumber = 0 v.MonthlyBundleDataAnalysisConsumptionNumber = 0 v.MonthlyIncreaseDataAnalysisConsumptionNumber = 0 v.MonthlyBundleLimitDataAnalysisConsumptionNumber = 0 v.MonthlyIncreaseLimitDataAnalysisConsumptionNumber = 0 v.MonthlyBundleLimitExpiredDataAnalysisConsumptionNumber = 0 v.MonthlyIncreaseLimitExpiredDataAnalysisConsumptionNumber = 0 v.MonthlyNewManualDataAnalysisNumber = 0 v.MonthlyManualDataAnalysisConsumptionNumber = 0 // 设置新月份和重置ID v.Month = month v.ID = 0 // 创建新的任务余额记录 if err := app.ModuleClients.TaskBenchDB.Create(&v).Error; err != nil { return err } } return nil } // updateTaskBalanceExpiredAt 更新任务余额表的ExpiredAt字段 func updateTaskBalanceExpiredAt(subNum, telNum string, durationNumber int) error { return app.ModuleClients.TaskBenchDB.Transaction(func(tx *gorm.DB) error { var taskBalance model.TaskBalance query := tx.Model(&model.TaskBalance{}) // 构建查询条件,优先使用 subNum if subNum != "" { query = query.Where("sub_num = ?", subNum) } else { query = query.Where("tel_num = ?", telNum) } // 查询当前有效的任务余额记录,按最新的开始时间排序 now := time.Now() err := query.Where("start_at <= ? AND expired_at >= ?", now, now).Order("start_at DESC").First(&taskBalance).Error if err != nil { return err } // 增加过期时间 taskBalance.ExpiredAt = taskBalance.ExpiredAt.Add(time.Hour * 24 * time.Duration(durationNumber)) return tx.Save(&taskBalance).Error }) } // copyBundleToTaskBalance 将 BundleBalance 的图片、视频、数据分析相关字段映射到 TaskBalance func copyBundleToTaskBalance(tb *model.TaskBalance, bb *model.BundleBalance) { // ===== 视频类 ===== tb.BundleVideoNumber = bb.BundleVideoNumber tb.IncreaseVideoNumber = bb.IncreaseVideoNumber tb.BundleLimitVideoNumber = bb.BundleLimitVideoNumber tb.IncreaseLimitVideoNumber = bb.IncreaseLimitVideoNumber tb.BundleLimitVideoExpiredNumber = bb.BundleLimitVideoExpiredNumber tb.IncreaseLimitVideoExpiredNumber = bb.IncreaseLimitVideoExpiredNumber tb.MonthlyInvalidBundleVideoNumber = bb.MonthlyInvalidBundleVideoNumber tb.InvalidBundleVideoNumber = bb.InvalidBundleVideoNumber tb.MonthlyInvalidIncreaseVideoNumber = bb.MonthlyInvalidIncreaseVideoNumber tb.InvalidIncreaseVideoNumber = bb.InvalidIncreaseVideoNumber tb.BundleVideoConsumptionNumber = bb.BundleVideoConsumptionNumber tb.IncreaseVideoConsumptionNumber = bb.IncreaseVideoConsumptionNumber tb.BundleLimitVideoConsumptionNumber = bb.BundleLimitVideoConsumptionNumber tb.IncreaseLimitVideoConsumptionNumber = bb.IncreaseLimitVideoConsumptionNumber tb.BundleLimitVideoExpiredConsumptionNumber = bb.BundleLimitVideoExpiredConsumptionNumber tb.IncreaseLimitVideoExpiredConsumptionNumber = bb.IncreaseLimitVideoExpiredConsumptionNumber tb.MonthlyBundleVideoConsumptionNumber = bb.MonthlyBundleVideoConsumptionNumber tb.MonthlyIncreaseVideoConsumptionNumber = bb.MonthlyIncreaseVideoConsumptionNumber tb.MonthlyBundleLimitVideoNumber = bb.MonthlyBundleLimitVideoNumber tb.MonthlyIncreaseLimitVideoNumber = bb.MonthlyIncreaseLimitVideoNumber tb.MonthlyBundleLimitVideoConsumptionNumber = bb.MonthlyBundleLimitVideoConsumptionNumber tb.MonthlyIncreaseLimitVideoConsumptionNumber = bb.MonthlyIncreaseLimitVideoConsumptionNumber tb.MonthlyBundleLimitExpiredVideoNumber = bb.MonthlyBundleLimitExpiredVideoNumber tb.MonthlyIncreaseLimitExpiredVideoNumber = bb.MonthlyIncreaseLimitExpiredVideoNumber tb.MonthlyBundleLimitExpiredVideoConsumptionNumber = bb.MonthlyBundleLimitExpiredVideoConsumptionNumber tb.MonthlyIncreaseLimitExpiredVideoConsumptionNumber = bb.MonthlyIncreaseLimitExpiredVideoConsumptionNumber tb.MonthlyLimitVideoQuotaNumber = bb.MonthlyLimitVideoQuotaNumber // 手动扩展(视频) tb.ManualVideoNumber = bb.ManualVideoNumber tb.ManualVideoConsumptionNumber = bb.ManualVideoConsumptionNumber tb.MonthlyNewManualVideoNumber = bb.MonthlyNewManualVideoNumber tb.MonthlyManualVideoConsumptionNumber = bb.MonthlyManualVideoConsumptionNumber // ===== 图片类 ===== tb.BundleImageNumber = bb.BundleImageNumber tb.IncreaseImageNumber = bb.IncreaseImageNumber tb.BundleLimitImageNumber = bb.BundleLimitImageNumber tb.IncreaseLimitImageNumber = bb.IncreaseLimitImageNumber tb.BundleLimitImageExpiredNumber = bb.BundleLimitImageExpiredNumber tb.IncreaseLimitImageExpiredNumber = bb.IncreaseLimitImageExpiredNumber tb.MonthlyInvalidBundleImageNumber = bb.MonthlyInvalidBundleImageNumber tb.InvalidBundleImageNumber = bb.InvalidBundleImageNumber tb.MonthlyInvalidIncreaseImageNumber = bb.MonthlyInvalidIncreaseImageNumber tb.InvalidIncreaseImageNumber = bb.InvalidIncreaseImageNumber tb.BundleImageConsumptionNumber = bb.BundleImageConsumptionNumber tb.IncreaseImageConsumptionNumber = bb.IncreaseImageConsumptionNumber tb.BundleLimitImageConsumptionNumber = bb.BundleLimitImageConsumptionNumber tb.IncreaseLimitImageConsumptionNumber = bb.IncreaseLimitImageConsumptionNumber tb.BundleLimitImageExpiredConsumptionNumber = bb.BundleLimitImageExpiredConsumptionNumber tb.IncreaseLimitImageExpiredConsumptionNumber = bb.IncreaseLimitImageExpiredConsumptionNumber tb.MonthlyBundleImageConsumptionNumber = bb.MonthlyBundleImageConsumptionNumber tb.MonthlyIncreaseImageConsumptionNumber = bb.MonthlyIncreaseImageConsumptionNumber tb.MonthlyBundleLimitImageNumber = bb.MonthlyBundleLimitImageNumber tb.MonthlyIncreaseLimitImageNumber = bb.MonthlyIncreaseLimitImageNumber tb.MonthlyBundleLimitImageConsumptionNumber = bb.MonthlyBundleLimitImageConsumptionNumber tb.MonthlyIncreaseLimitImageConsumptionNumber = bb.MonthlyIncreaseLimitImageConsumptionNumber tb.MonthlyBundleLimitExpiredImageNumber = bb.MonthlyBundleLimitExpiredImageNumber tb.MonthlyIncreaseLimitExpiredImageNumber = bb.MonthlyIncreaseLimitExpiredImageNumber tb.MonthlyBundleLimitExpiredImageConsumptionNumber = bb.MonthlyBundleLimitExpiredImageConsumptionNumber tb.MonthlyIncreaseLimitExpiredImageConsumptionNumber = bb.MonthlyIncreaseLimitExpiredImageConsumptionNumber tb.MonthlyLimitImageQuotaNumber = bb.MonthlyLimitImageQuotaNumber // 手动扩展(图片) tb.ManualImageNumber = bb.ManualImageNumber tb.ManualImageConsumptionNumber = bb.ManualImageConsumptionNumber tb.MonthlyNewManualImageNumber = bb.MonthlyNewManualImageNumber tb.MonthlyManualImageConsumptionNumber = bb.MonthlyManualImageConsumptionNumber // ===== 数据分析类 ===== tb.BundleDataAnalysisNumber = bb.BundleDataAnalysisNumber tb.IncreaseDataAnalysisNumber = bb.IncreaseDataAnalysisNumber tb.BundleLimitDataAnalysisNumber = bb.BundleLimitDataAnalysisNumber tb.IncreaseLimitDataAnalysisNumber = bb.IncreaseLimitDataAnalysisNumber tb.BundleLimitDataAnalysisExpiredNumber = bb.BundleLimitDataAnalysisExpiredNumber tb.IncreaseLimitDataAnalysisExpiredNumber = bb.IncreaseLimitDataAnalysisExpiredNumber tb.MonthlyInvalidBundleDataAnalysisNumber = bb.MonthlyInvalidBundleDataAnalysisNumber tb.InvalidBundleDataAnalysisNumber = bb.InvalidBundleDataAnalysisNumber tb.MonthlyInvalidIncreaseDataAnalysisNumber = bb.MonthlyInvalidIncreaseDataAnalysisNumber tb.InvalidIncreaseDataAnalysisNumber = bb.InvalidIncreaseDataAnalysisNumber tb.BundleDataAnalysisConsumptionNumber = bb.BundleDataAnalysisConsumptionNumber tb.IncreaseDataAnalysisConsumptionNumber = bb.IncreaseDataAnalysisConsumptionNumber tb.BundleLimitDataAnalysisConsumptionNumber = bb.BundleLimitDataAnalysisConsumptionNumber tb.IncreaseLimitDataAnalysisConsumptionNumber = bb.IncreaseLimitDataAnalysisConsumptionNumber tb.BundleLimitDataAnalysisExpiredConsumptionNumber = bb.BundleLimitDataAnalysisExpiredConsumptionNumber tb.IncreaseLimitDataAnalysisExpiredConsumptionNumber = bb.IncreaseLimitDataAnalysisExpiredConsumptionNumber tb.MonthlyBundleDataAnalysisConsumptionNumber = bb.MonthlyBundleDataAnalysisConsumptionNumber tb.MonthlyIncreaseDataAnalysisConsumptionNumber = bb.MonthlyIncreaseDataAnalysisConsumptionNumber tb.MonthlyBundleLimitDataAnalysisNumber = bb.MonthlyBundleLimitDataAnalysisNumber tb.MonthlyIncreaseLimitDataAnalysisNumber = bb.MonthlyIncreaseLimitDataAnalysisNumber tb.MonthlyBundleLimitDataAnalysisConsumptionNumber = bb.MonthlyBundleLimitDataAnalysisConsumptionNumber tb.MonthlyIncreaseLimitDataAnalysisConsumptionNumber = bb.MonthlyIncreaseLimitDataAnalysisConsumptionNumber tb.MonthlyBundleLimitExpiredDataAnalysisNumber = bb.MonthlyBundleLimitExpiredDataAnalysisNumber tb.MonthlyIncreaseLimitExpiredDataAnalysisNumber = bb.MonthlyIncreaseLimitExpiredDataAnalysisNumber tb.MonthlyBundleLimitExpiredDataAnalysisConsumptionNumber = bb.MonthlyBundleLimitExpiredDataAnalysisConsumptionNumber tb.MonthlyIncreaseLimitExpiredDataAnalysisConsumptionNumber = bb.MonthlyIncreaseLimitExpiredDataAnalysisConsumptionNumber tb.MonthlyLimitDataAnalysisQuotaNumber = bb.MonthlyLimitDataAnalysisQuotaNumber // 手动扩展(数据分析) tb.ManualDataAnalysisNumber = bb.ManualDataAnalysisNumber tb.ManualDataAnalysisConsumptionNumber = bb.ManualDataAnalysisConsumptionNumber tb.MonthlyNewManualDataAnalysisNumber = bb.MonthlyNewManualDataAnalysisNumber tb.MonthlyManualDataAnalysisConsumptionNumber = bb.MonthlyManualDataAnalysisConsumptionNumber // 其他字段 tb.MonthlyNewDurationNumber = bb.MonthlyNewDurationNumber tb.ExpansionPacksNumber = bb.ExpansionPacksNumber } func ExtendTaskBalanceByUserId(userId int, imageNumber int, dataAnalysisNumber int, videoNumber int, durationNumber int) error { // 根据用户ID获取其最新套餐记录,进而获取 sub_num、tel_num oldBundle := model.BundleBalance{} if err := app.ModuleClients.BundleDB.Model(&model.BundleBalance{}). Where("user_id = ?", userId). Order("created_at desc"). First(&oldBundle).Error; err != nil { return errors.New("用户还没有套餐信息") } subNum, telNum, err := fetchIdentityForBundle(&oldBundle) if err != nil { return err } // 事务更新当前有效的任务余额记录(按 start_at 最近的一条) err = app.ModuleClients.TaskBenchDB.Transaction(func(tx *gorm.DB) error { var tb model.TaskBalance now := time.Now() query := tx.Model(&model.TaskBalance{}). Where("sub_num = ? AND tel_num = ? AND start_at <= ? AND expired_at >= ?", subNum, telNum, now, now). Order("start_at DESC") if err := query.First(&tb).Error; err != nil { if err == gorm.ErrRecordNotFound { return errors.New("用户还没有任务余额信息") } return err } // 手动扩展额度与当月新增记录 tb.ManualImageNumber += imageNumber tb.MonthlyNewManualImageNumber += imageNumber tb.ManualDataAnalysisNumber += dataAnalysisNumber tb.MonthlyNewManualDataAnalysisNumber += dataAnalysisNumber tb.ManualVideoNumber += videoNumber tb.MonthlyNewManualVideoNumber += videoNumber tb.MonthlyNewDurationNumber += durationNumber return tx.Model(&model.TaskBalance{}).Where("id = ?", tb.ID).Save(&tb).Error }) if err != nil { return err } // 增加过期时间(按天) if durationNumber > 0 { if err := updateTaskBalanceExpiredAt(subNum, telNum, durationNumber); err != nil { return err } } return nil }