2026-07-18 15:32:58 +08:00

130 lines
4.2 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 yumigiftchallenge
import (
"context"
"fmt"
"time"
"chatapp3-golang/internal/model"
)
const (
defaultSettleDueBatch = 20
maxSettleDueBatch = 100
settleDueItemTimeout = 30 * time.Second
)
// SettleDue 是 chatapp-cron 的单轮幂等 runner冻结到期周期并恢复 PENDING/过租期 PROCESSING。
// 本仓库不启动 ticker同一端点可被多个实例重复调用数据库门闩和 delivery item 唯一键负责并发幂等。
func (s *Service) SettleDue(ctx context.Context, batch int) (*SettleDueResult, error) {
batch = normalizeSettleDueBatch(batch)
result := &SettleDueResult{FailedKeys: []string{}}
var periods []model.YumiGiftChallengePeriodSettlement
if err := s.db.WithContext(ctx).
Where("status IN ? AND snapshot_due_time <= ?", []string{StatusNotStarted, StatusProcessing}, time.Now()).
Order("snapshot_due_time ASC, id ASC").Limit(batch).Find(&periods).Error; err != nil {
return nil, err
}
for _, period := range periods {
if err := ctx.Err(); err != nil {
return result, err
}
result.PeriodsScanned++
request := SettlementRequest{PeriodType: period.PeriodType}
if period.StatDate != nil {
request.StatDate = *period.StatDate
}
itemCtx, cancel := context.WithTimeout(ctx, settleDueItemTimeout)
target, err := s.resolveSettlementTarget(itemCtx, period.ActivityID, request)
if err == nil {
err = s.freezeSettlementTarget(itemCtx, target)
}
if err == nil {
// 空榜直接完成;有获奖 parent 时保持 PROCESSING后面的有界 delivery 恢复负责逐项发放。
err = s.refreshPeriodStatus(itemCtx, target.activity.ID, target.period, target.periodKey)
}
cancel()
if err != nil {
result.FailedKeys = append(result.FailedKeys, fmt.Sprintf("PERIOD:%d:%s:%s", period.ActivityID, period.PeriodType, period.PeriodKey))
continue
}
result.PeriodsFrozen++
}
if err := s.recoverTaskOwners(ctx, batch, result); err != nil {
return result, err
}
if err := s.recoverSettlementOwners(ctx, batch, result); err != nil {
return result, err
}
return result, nil
}
func (s *Service) recoverTaskOwners(ctx context.Context, batch int, result *SettleDueResult) error {
var rows []model.YumiGiftChallengeUserTaskDaily
if err := s.db.WithContext(ctx).Select("id").
Where("delivery_status = ? OR (delivery_status = ? AND update_time <= ?)", DeliveryPending, DeliveryProcessing, s.deliveryLeaseCutoff()).
Order("update_time ASC, id ASC").Limit(batch).Find(&rows).Error; err != nil {
return err
}
for _, row := range rows {
if err := ctx.Err(); err != nil {
return err
}
result.TaskOwnersScanned++
itemCtx, cancel := context.WithTimeout(ctx, settleDueItemTimeout)
err := s.deliverOwnerItems(itemCtx, OwnerTask, row.ID)
cancel()
if err != nil {
result.FailedKeys = append(result.FailedKeys, fmt.Sprintf("TASK:%d", row.ID))
continue
}
result.OwnersProcessed++
}
return nil
}
func (s *Service) recoverSettlementOwners(ctx context.Context, batch int, result *SettleDueResult) error {
var rows []model.YumiGiftChallengeSettlement
if err := s.db.WithContext(ctx).Select("id").
Where("delivery_status = ? OR (delivery_status = ? AND update_time <= ?)", DeliveryPending, DeliveryProcessing, s.deliveryLeaseCutoff()).
Order("update_time ASC, id ASC").Limit(batch).Find(&rows).Error; err != nil {
return err
}
for _, row := range rows {
if err := ctx.Err(); err != nil {
return err
}
result.SettlementOwnersScanned++
itemCtx, cancel := context.WithTimeout(ctx, settleDueItemTimeout)
err := s.ensureSettlementDeliveryItems(itemCtx, row.ID)
if err == nil {
err = s.deliverOwnerItems(itemCtx, OwnerSettlement, row.ID)
}
cancel()
if err != nil {
result.FailedKeys = append(result.FailedKeys, fmt.Sprintf("SETTLEMENT:%d", row.ID))
continue
}
result.OwnersProcessed++
}
return nil
}
func normalizeSettleDueBatch(batch int) int {
if batch <= 0 {
return defaultSettleDueBatch
}
if batch > maxSettleDueBatch {
return maxSettleDueBatch
}
return batch
}
func (s *Service) deliveryLeaseCutoff() time.Time {
leaseSeconds := s.cfg.YumiGiftChallenge.DeliveryProcessingLeaseSeconds
if leaseSeconds < 30 {
leaseSeconds = 300
}
return time.Now().Add(-time.Duration(leaseSeconds) * time.Second)
}