package packagepkg import ( "context" "github.com/break/junhong_cmp_fiber/internal/model" "github.com/break/junhong_cmp_fiber/internal/model/dto" "github.com/break/junhong_cmp_fiber/internal/store/postgres" "github.com/break/junhong_cmp_fiber/pkg/constants" "github.com/break/junhong_cmp_fiber/pkg/errors" "github.com/redis/go-redis/v9" "go.uber.org/zap" "gorm.io/gorm" ) type CustomerViewService struct { db *gorm.DB redis *redis.Client packageUsageStore *postgres.PackageUsageStore logger *zap.Logger } func NewCustomerViewService( db *gorm.DB, redis *redis.Client, packageUsageStore *postgres.PackageUsageStore, logger *zap.Logger, ) *CustomerViewService { return &CustomerViewService{ db: db, redis: redis, packageUsageStore: packageUsageStore, logger: logger, } } // GetMyUsage 任务 12.2-12.5: 获取客户套餐使用情况 // 根据载体ID和类型查询生效中的套餐,计算总流量使用情况 func (s *CustomerViewService) GetMyUsage(ctx context.Context, carrierType string, carrierID uint) (*dto.PackageUsageCustomerViewResponse, error) { // 任务 12.3: 查询生效套餐(status IN (1,2)) var packages []*model.PackageUsage query := s.db.WithContext(ctx). Where("status IN ?", []int{constants.PackageUsageStatusActive, constants.PackageUsageStatusDepleted}) if carrierType == "iot_card" { query = query.Where("iot_card_id = ?", carrierID) } else if carrierType == "device" { query = query.Where("device_id = ?", carrierID) } else { return nil, errors.New(errors.CodeInvalidParam, "无效的载体类型") } // 按优先级排序:主套餐在前,加油包在后 if err := query.Order("CASE WHEN master_usage_id IS NULL THEN 0 ELSE 1 END, priority ASC"). Find(&packages).Error; err != nil { return nil, errors.Wrap(errors.CodeDatabaseError, err, "查询套餐使用记录失败") } if len(packages) == 0 { return nil, errors.New(errors.CodeNotFound, "未找到套餐使用记录") } // 任务 12.4: 区分主套餐和加油包,计算总流量 var mainPackage *dto.PackageUsageItemResponse var addonPackages []dto.PackageUsageItemResponse var totalUsedMB int64 var totalLimitMB int64 for _, pkg := range packages { // 查询套餐信息 var packageInfo model.Package if err := s.db.First(&packageInfo, pkg.PackageID).Error; err != nil { s.logger.Warn("查询套餐信息失败", zap.Uint("package_id", pkg.PackageID), zap.Error(err)) continue } // 格式化状态文本 statusText := getStatusText(pkg.Status) // 格式化时间 activatedAtStr := "" if pkg.ActivatedAt.Year() > 1 { activatedAtStr = pkg.ActivatedAt.Format("2006-01-02 15:04:05") } expiresAtStr := "" if pkg.ExpiresAt.Year() > 1 { expiresAtStr = pkg.ExpiresAt.Format("2006-01-02 15:04:05") } item := dto.PackageUsageItemResponse{ PackageUsageID: pkg.ID, PackageID: pkg.PackageID, PackageName: packageInfo.PackageName, UsedMB: pkg.DataUsageMB, TotalMB: pkg.DataLimitMB, Status: pkg.Status, StatusText: statusText, ActivatedAt: activatedAtStr, ExpiresAt: expiresAtStr, Priority: pkg.Priority, } // 累计总流量 totalUsedMB += pkg.DataUsageMB totalLimitMB += pkg.DataLimitMB // 区分主套餐和加油包 if pkg.MasterUsageID == nil { mainPackage = &item } else { addonPackages = append(addonPackages, item) } } // 任务 12.5: 组装响应 DTO response := &dto.PackageUsageCustomerViewResponse{ MainPackage: mainPackage, AddonPackages: addonPackages, Total: dto.PackageUsageTotalInfo{ UsedMB: totalUsedMB, TotalMB: totalLimitMB, }, } return response, nil } // getStatusText 获取状态文本 func getStatusText(status int) string { switch status { case constants.PackageUsageStatusPending: return "待生效" case constants.PackageUsageStatusActive: return "生效中" case constants.PackageUsageStatusDepleted: return "已用完" case constants.PackageUsageStatusExpired: return "已过期" case constants.PackageUsageStatusInvalidated: return "已失效" default: return "未知" } }