package packagepkg import ( "context" "sync/atomic" packagedomain "github.com/break/junhong_cmp_fiber/internal/domain/package" "github.com/break/junhong_cmp_fiber/internal/model" "github.com/break/junhong_cmp_fiber/internal/store/postgres" "github.com/break/junhong_cmp_fiber/pkg/errors" "go.uber.org/zap" "gorm.io/gorm" ) var historicalTermsFallbackCount atomic.Uint64 // ResolveUsageTerms 优先读取使用记录快照,仅对完整空快照的历史记录显式回退。 func ResolveUsageTerms(usage *model.PackageUsage, pkg *model.Package, logger *zap.Logger) (packagedomain.TermsSnapshot, error) { terms := packagedomain.TermsSnapshotFromUsage(usage) if terms.IsValid() { return terms, nil } if !isEmptyHistoricalTerms(usage) { if logger != nil { logger.Error("套餐使用记录计时快照异常", zap.Uint("package_usage_id", usage.ID), zap.Uint("package_id", usage.PackageID)) } return packagedomain.TermsSnapshot{}, errors.New(errors.CodeInternalError, "套餐使用记录计时快照异常") } fallback, err := packagedomain.ResolveTermsSnapshot(pkg, nil) if err != nil { return packagedomain.TermsSnapshot{}, err } historicalTermsFallbackCount.Add(1) if logger != nil { logger.Warn("历史套餐使用记录缺少计时快照,回退套餐当前配置", zap.Uint("package_usage_id", usage.ID), zap.Uint("package_id", usage.PackageID), zap.Uint64("historical_terms_fallback_count", historicalTermsFallbackCount.Load())) } return fallback, nil } // HistoricalTermsFallbackCount 返回历史计时条款回退累计次数。 func HistoricalTermsFallbackCount() uint64 { return historicalTermsFallbackCount.Load() } // ResolveTermsFromTx 在给定事务内查询分配覆盖并解析计时条款快照。 // 必须在事务内调用,确保分配读取与 PackageUsage 写入在同一连接,避免快照与提交值不一致。 func ResolveTermsFromTx(ctx context.Context, tx *gorm.DB, pkg *model.Package, sellerShopID *uint) (packagedomain.TermsSnapshot, error) { var allocation *model.ShopPackageAllocation if sellerShopID != nil && *sellerShopID > 0 { store := postgres.NewShopPackageAllocationStore(tx) found, err := store.GetByShopAndPackageForSystem(ctx, *sellerShopID, pkg.ID) if err != nil && err != gorm.ErrRecordNotFound { return packagedomain.TermsSnapshot{}, errors.Wrap(errors.CodeDatabaseError, err, "查询套餐分配失败") } if err == nil { allocation = found } } return packagedomain.ResolveTermsSnapshot(pkg, allocation) } func isEmptyHistoricalTerms(usage *model.PackageUsage) bool { return usage.ExpiryBaseSnapshot == "" && usage.CalendarTypeSnapshot == "" && usage.DurationMonthsSnapshot == 0 && usage.DurationDaysSnapshot == 0 }