package agent_open_api import ( "context" "fmt" "math/rand" "strings" "time" "github.com/break/junhong_cmp_fiber/internal/model" "github.com/break/junhong_cmp_fiber/internal/model/dto" assetSvc "github.com/break/junhong_cmp_fiber/internal/service/asset" deviceSvc "github.com/break/junhong_cmp_fiber/internal/service/device" iotCardSvc "github.com/break/junhong_cmp_fiber/internal/service/iot_card" orderSvc "github.com/break/junhong_cmp_fiber/internal/service/order" packageSvc "github.com/break/junhong_cmp_fiber/internal/service/package" shopCommissionSvc "github.com/break/junhong_cmp_fiber/internal/service/shop_commission" "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/break/junhong_cmp_fiber/pkg/middleware" "gorm.io/gorm" ) // Service 代理开放接口业务编排服务 type Service struct { assetService *assetSvc.Service packageService *packageSvc.Service orderService *orderSvc.Service shopCommissionService *shopCommissionSvc.Service stopResumeService *iotCardSvc.StopResumeService deviceService *deviceSvc.Service iotCardStore *postgres.IotCardStore packageUsageStore *postgres.PackageUsageStore packageStore *postgres.PackageStore packageSeriesStore *postgres.PackageSeriesStore agentWalletStore *postgres.AgentWalletStore deviceSimBindingStore *postgres.DeviceSimBindingStore deviceStore *postgres.DeviceStore } // New 创建代理开放接口业务编排服务 func New( assetService *assetSvc.Service, packageService *packageSvc.Service, orderService *orderSvc.Service, shopCommissionService *shopCommissionSvc.Service, stopResumeService *iotCardSvc.StopResumeService, deviceService *deviceSvc.Service, iotCardStore *postgres.IotCardStore, packageUsageStore *postgres.PackageUsageStore, packageStore *postgres.PackageStore, packageSeriesStore *postgres.PackageSeriesStore, agentWalletStore *postgres.AgentWalletStore, deviceSimBindingStore *postgres.DeviceSimBindingStore, deviceStore *postgres.DeviceStore, ) *Service { return &Service{ assetService: assetService, packageService: packageService, orderService: orderService, shopCommissionService: shopCommissionService, stopResumeService: stopResumeService, deviceService: deviceService, iotCardStore: iotCardStore, packageUsageStore: packageUsageStore, packageStore: packageStore, packageSeriesStore: packageSeriesStore, agentWalletStore: agentWalletStore, deviceSimBindingStore: deviceSimBindingStore, deviceStore: deviceStore, } } // GetCardTraffic 查询开放接口流量 // 支持三种解析路径: // 1. card_no 解析为独立卡 → 查卡维度流量 // 2. card_no 解析为已绑定设备的卡 → 反查绑定设备 → 查设备维度流量 // 3. card_no 无法解析为卡 → 尝试作为设备标识(IMEI/虚拟号)→ 查设备维度流量 func (s *Service) GetCardTraffic(ctx context.Context, req *dto.AgentOpenAPICardQueryRequest) (*dto.AgentOpenAPICardTrafficResponse, error) { card, cardErr := s.resolveOpenAPICard(ctx, req.CardNo) if cardErr == nil { // 独立卡:直接查卡维度流量 if card.IsStandalone { return s.buildCardTrafficResponse(ctx, req.CardNo, "iot_card", card.ID, "") } // 已绑定设备的卡:反查设备后查设备维度流量 binding, err := s.deviceSimBindingStore.GetActiveBindingByCardID(ctx, card.ID) if err != nil { return nil, errors.Wrap(errors.CodeInternalError, err, "查询卡绑定设备失败") } device, err := s.deviceStore.GetByID(ctx, binding.DeviceID) if err != nil { return nil, errors.Wrap(errors.CodeInternalError, err, "查询绑定设备信息失败") } return s.buildCardTrafficResponse(ctx, req.CardNo, "device", device.ID, device.VirtualNo) } // 兜底:尝试解析为设备标识(支持 IMEI/虚拟号) device, err := s.resolveOpenAPIDevice(ctx, req.CardNo) if err != nil { // 两种解析都失败,返回原始卡解析错误(语义更贴近入参) return nil, cardErr } return s.buildCardTrafficResponse(ctx, req.CardNo, "device", device.ID, device.VirtualNo) } // buildCardTrafficResponse 统一构造卡流量响应,支持卡和设备两种载体维度 func (s *Service) buildCardTrafficResponse(ctx context.Context, cardNo, carrierType string, carrierID uint, deviceNo string) (*dto.AgentOpenAPICardTrafficResponse, error) { activeStatus := constants.PackageUsageStatusActive depletedStatus := constants.PackageUsageStatusDepleted pendingStatus := constants.PackageUsageStatusPending activeUsages, err := s.packageUsageStore.ListByCarrier(ctx, carrierType, carrierID, &activeStatus) if err != nil { return nil, errors.Wrap(errors.CodeInternalError, err, "查询生效套餐失败") } // 已用完的套餐仍在有效期内,与生效中套餐一并展示 depletedUsages, err := s.packageUsageStore.ListByCarrier(ctx, carrierType, carrierID, &depletedStatus) if err != nil { return nil, errors.Wrap(errors.CodeInternalError, err, "查询已用完套餐失败") } activeUsages = append(activeUsages, depletedUsages...) pendingUsages, err := s.packageUsageStore.ListByCarrier(ctx, carrierType, carrierID, &pendingStatus) if err != nil { return nil, errors.Wrap(errors.CodeInternalError, err, "查询待生效套餐失败") } packageMap, seriesMap, err := s.loadUsagePackageContext(ctx, append(activeUsages, pendingUsages...)) if err != nil { return nil, err } resp := &dto.AgentOpenAPICardTrafficResponse{ CardNo: cardNo, DeviceNo: deviceNo, ActivePackages: make([]dto.AgentOpenAPIPackageTrafficItem, 0, len(activeUsages)), PendingPackages: make([]dto.AgentOpenAPIPackageTrafficItem, 0, len(pendingUsages)), } for _, usage := range activeUsages { item := s.buildTrafficItem(usage, packageMap, seriesMap, true) resp.ActivePackages = append(resp.ActivePackages, item) resp.ActiveTotalFlowMB += item.TotalFlowMB resp.ActiveUsedFlowMB += item.UsedFlowMB resp.ActiveRemainingFlowMB += item.RemainingFlowMB if item.ExpiresAt != nil && (resp.ActiveExpiresAt == nil || item.ExpiresAt.After(*resp.ActiveExpiresAt)) { resp.ActiveExpiresAt = item.ExpiresAt } } for _, usage := range pendingUsages { resp.PendingPackages = append(resp.PendingPackages, s.buildTrafficItem(usage, packageMap, seriesMap, false)) } return resp, nil } // GetCardStatus 查询开放接口单卡状态 func (s *Service) GetCardStatus(ctx context.Context, req *dto.AgentOpenAPICardQueryRequest) (*dto.AgentOpenAPICardStatusResponse, error) { card, err := s.resolveOpenAPICard(ctx, req.CardNo) if err != nil { return nil, err } cardStatus := constants.AgentOpenAPICardStatusStopped stopReason := card.StopReason if card.NetworkStatus == constants.NetworkStatusOnline { cardStatus = constants.AgentOpenAPICardStatusNormal stopReason = "" } return &dto.AgentOpenAPICardStatusResponse{ CardNo: req.CardNo, CardStatus: cardStatus, CardStatusName: constants.AgentOpenAPICardStatusName(cardStatus), GatewayExtend: card.GatewayExtend, StopReason: stopReason, StopReasonName: constants.AgentOpenAPIStopReasonName(stopReason), }, nil } // GetRealnameStatus 查询开放接口单卡实名状态 func (s *Service) GetRealnameStatus(ctx context.Context, req *dto.AgentOpenAPICardQueryRequest) (*dto.AgentOpenAPIRealnameStatusResponse, error) { card, err := s.resolveOpenAPICard(ctx, req.CardNo) if err != nil { return nil, err } return &dto.AgentOpenAPIRealnameStatusResponse{ CardNo: req.CardNo, IsRealnamed: card.RealNameStatus == constants.RealNameStatusVerified, }, nil } // ResumeCard 调用上游复机机卡分离停机卡 func (s *Service) ResumeCard(ctx context.Context, req *dto.AgentOpenAPICardResumeRequest) (*dto.AgentOpenAPICardResumeResponse, error) { card, err := s.resolveOpenAPICard(ctx, req.CardNo) if err != nil { return nil, err } if s.stopResumeService == nil { return nil, errors.New(errors.CodeInternalError, "开放接口复机依赖未配置") } if err := s.stopResumeService.StartMachineSeparatedCard(ctx, card); err != nil { return nil, err } return &dto.AgentOpenAPICardResumeResponse{ CardNo: req.CardNo, CardStatus: constants.AgentOpenAPICardStatusNormal, CardStatusName: constants.AgentOpenAPICardStatusName(constants.AgentOpenAPICardStatusNormal), GatewayExtend: "", }, nil } // ListPackages 查询开放接口代理可购买套餐列表 func (s *Service) ListPackages(ctx context.Context, req *dto.AgentOpenAPIPackageListRequest) ([]*dto.AgentOpenAPIPackageItem, int64, int, int, error) { status := constants.StatusEnabled shelfStatus := constants.ShelfStatusOn packageReq := &dto.PackageListRequest{ Page: req.Page, PageSize: req.PageSize, PackageName: req.PackageName, SeriesID: req.SeriesID, Status: &status, ShelfStatus: &shelfStatus, PackageType: req.PackageType, } packages, total, err := s.packageService.List(ctx, packageReq) if err != nil { return nil, 0, 0, 0, err } items := make([]*dto.AgentOpenAPIPackageItem, 0, len(packages)) for _, pkg := range packages { items = append(items, &dto.AgentOpenAPIPackageItem{ PackageID: pkg.ID, PackageCode: pkg.PackageCode, PackageName: pkg.PackageName, SeriesName: stringValue(pkg.SeriesName), PackageType: pkg.PackageType, PackageTypeName: constants.AgentOpenAPIPackageTypeName(pkg.PackageType), CostPrice: pkg.CostPrice, RetailPrice: int64Value(pkg.EffectiveRetailPrice), TotalFlowMB: pkg.RealDataMB, ValidDays: packageValidDays(pkg.DurationDays, pkg.DurationMonths), }) } page, pageSize := normalizePage(req.Page, req.PageSize) return items, total, page, pageSize, nil } // GetWalletBalance 查询开放接口预充值主钱包余额 func (s *Service) GetWalletBalance(ctx context.Context) (*dto.AgentOpenAPIWalletBalanceResponse, error) { shopID := middleware.GetShopIDFromContext(ctx) if shopID == 0 { return nil, errors.New(errors.CodeUnauthorized) } wallet, err := s.agentWalletStore.GetMainWallet(ctx, shopID) if err != nil { if err == gorm.ErrRecordNotFound { return &dto.AgentOpenAPIWalletBalanceResponse{ Balance: 0, FrozenBalance: 0, AvailableBalance: 0, Currency: constants.AgentOpenAPIWalletCurrencyCNY, }, nil } return nil, errors.Wrap(errors.CodeInternalError, err, "查询主钱包余额失败") } return &dto.AgentOpenAPIWalletBalanceResponse{ Balance: wallet.Balance, FrozenBalance: wallet.FrozenBalance, AvailableBalance: wallet.GetAvailableBalance(), Currency: walletCurrency(wallet.Currency), }, nil } // ListWalletTransactions 查询开放接口预充值主钱包流水 func (s *Service) ListWalletTransactions(ctx context.Context, req *dto.AgentOpenAPIWalletTransactionListRequest) (*dto.MainWalletTransactionListResponse, error) { shopID := middleware.GetShopIDFromContext(ctx) if shopID == 0 { return nil, errors.New(errors.CodeUnauthorized) } return s.shopCommissionService.ListMainWalletTransactions(ctx, shopID, &dto.MainWalletTransactionListRequest{ Page: req.Page, PageSize: req.PageSize, TransactionType: req.TransactionType, StartDate: req.StartDate, EndDate: req.EndDate, }) } // CreateWalletPackageOrders 使用代理预充值钱包按卡购买套餐 func (s *Service) CreateWalletPackageOrders(ctx context.Context, req *dto.AgentOpenAPIWalletPackageOrderRequest) (*dto.AgentOpenAPIWalletPackageOrderResponse, error) { shopID := middleware.GetShopIDFromContext(ctx) if shopID == 0 { return nil, errors.New(errors.CodeUnauthorized) } pkg, err := s.packageStore.GetByCode(ctx, strings.TrimSpace(req.PackageCode)) if err != nil { if err == gorm.ErrRecordNotFound { return nil, errors.New(errors.CodeNotFound, "套餐不存在") } return nil, errors.Wrap(errors.CodeInternalError, err, "查询套餐失败") } resp := &dto.AgentOpenAPIWalletPackageOrderResponse{ BatchNo: generateAgentOpenAPIBatchNo(), PackageCode: pkg.PackageCode, Orders: []dto.AgentOpenAPIWalletPackageOrderItem{}, FailedCards: []dto.AgentOpenAPIWalletPackageOrderFailure{}, } for _, rawCardNo := range req.CardNos { cardNo := strings.TrimSpace(rawCardNo) if cardNo == "" { resp.FailedCards = append(resp.FailedCards, buildFailedCard(rawCardNo, errors.New(errors.CodeInvalidParam, "卡标识不能为空"))) continue } var identifier string card, cardErr := s.resolveOpenAPICard(ctx, cardNo) if cardErr != nil { // 兜底:尝试解析为设备标识(支持 IMEI/虚拟号) device, devErr := s.resolveOpenAPIDevice(ctx, cardNo) if devErr != nil { resp.FailedCards = append(resp.FailedCards, buildFailedCard(cardNo, cardErr)) continue } identifier = device.VirtualNo } else if !card.IsStandalone { // 卡已绑定设备时,自动转为设备维度购买 binding, bindErr := s.deviceSimBindingStore.GetActiveBindingByCardID(ctx, card.ID) if bindErr != nil { resp.FailedCards = append(resp.FailedCards, buildFailedCard(cardNo, errors.Wrap(errors.CodeInternalError, bindErr, "查询卡绑定设备失败"))) continue } device, devErr := s.deviceStore.GetByID(ctx, binding.DeviceID) if devErr != nil { resp.FailedCards = append(resp.FailedCards, buildFailedCard(cardNo, errors.Wrap(errors.CodeInternalError, devErr, "查询绑定设备信息失败"))) continue } identifier = device.VirtualNo } else { identifier = card.ICCID } orderResp, err := s.orderService.CreateAdminOrder(ctx, &dto.CreateAdminOrderRequest{ Identifier: identifier, PackageIDs: []uint{pkg.ID}, PaymentMethod: model.PaymentMethodWallet, }, model.BuyerTypeAgent, shopID) if err != nil { resp.FailedCards = append(resp.FailedCards, buildFailedCard(cardNo, err)) continue } resp.Orders = append(resp.Orders, dto.AgentOpenAPIWalletPackageOrderItem{ CardNo: cardNo, OrderNo: orderResp.OrderNo, }) } resp.SuccessCount = len(resp.Orders) resp.FailedCount = len(resp.FailedCards) return resp, nil } // resolveOpenAPIDevice 将开放接口设备标识解析为当前代理可见的设备 // 设备不存在或不属于代理管辖店铺时统一返回 CodeForbidden,避免信息泄露 func (s *Service) resolveOpenAPIDevice(ctx context.Context, deviceNo string) (*model.Device, error) { identifier := strings.TrimSpace(deviceNo) if identifier == "" { return nil, errors.New(errors.CodeInvalidParam) } if s.deviceService == nil { return nil, errors.New(errors.CodeInternalError, "开放接口设备解析依赖未配置") } device, err := s.deviceService.GetDeviceByIdentifier(ctx, identifier) if err != nil { return nil, errors.New(errors.CodeForbidden, "无权限操作该资源或资源不存在") } if device.ShopID == nil { return nil, errors.New(errors.CodeForbidden, "无权限操作该资源或资源不存在") } subordinateIDs := middleware.GetSubordinateShopIDs(ctx) found := false for _, id := range subordinateIDs { if id == *device.ShopID { found = true break } } if !found { return nil, errors.New(errors.CodeForbidden, "无权限操作该资源或资源不存在") } return device, nil } // GetDeviceTraffic 查询开放接口设备套餐内流量 func (s *Service) GetDeviceTraffic(ctx context.Context, req *dto.AgentOpenAPIDeviceQueryRequest) (*dto.AgentOpenAPIDeviceTrafficResponse, error) { device, err := s.resolveOpenAPIDevice(ctx, req.DeviceNo) if err != nil { return nil, err } activeStatus := constants.PackageUsageStatusActive pendingStatus := constants.PackageUsageStatusPending activeUsages, err := s.packageUsageStore.ListByCarrier(ctx, "device", device.ID, &activeStatus) if err != nil { return nil, errors.Wrap(errors.CodeInternalError, err, "查询生效套餐失败") } pendingUsages, err := s.packageUsageStore.ListByCarrier(ctx, "device", device.ID, &pendingStatus) if err != nil { return nil, errors.Wrap(errors.CodeInternalError, err, "查询待生效套餐失败") } packageMap, seriesMap, err := s.loadUsagePackageContext(ctx, append(activeUsages, pendingUsages...)) if err != nil { return nil, err } resp := &dto.AgentOpenAPIDeviceTrafficResponse{ DeviceNo: req.DeviceNo, ActivePackages: make([]dto.AgentOpenAPIPackageTrafficItem, 0, len(activeUsages)), PendingPackages: make([]dto.AgentOpenAPIPackageTrafficItem, 0, len(pendingUsages)), } for _, usage := range activeUsages { item := s.buildTrafficItem(usage, packageMap, seriesMap, true) resp.ActivePackages = append(resp.ActivePackages, item) resp.ActiveTotalFlowMB += item.TotalFlowMB resp.ActiveUsedFlowMB += item.UsedFlowMB resp.ActiveRemainingFlowMB += item.RemainingFlowMB if item.ExpiresAt != nil && (resp.ActiveExpiresAt == nil || item.ExpiresAt.After(*resp.ActiveExpiresAt)) { resp.ActiveExpiresAt = item.ExpiresAt } } for _, usage := range pendingUsages { resp.PendingPackages = append(resp.PendingPackages, s.buildTrafficItem(usage, packageMap, seriesMap, false)) } return resp, nil } // SwitchDeviceCard 开放接口切网(多卡设备切换到指定 ICCID) func (s *Service) SwitchDeviceCard(ctx context.Context, req *dto.AgentOpenAPIDeviceSwitchCardRequest) error { if _, err := s.resolveOpenAPIDevice(ctx, req.DeviceNo); err != nil { return err } return s.deviceService.GatewaySwitchCard(ctx, req.DeviceNo, &dto.SwitchCardRequest{ TargetICCID: req.ICCID, }) } // RebootDevice 开放接口重启设备 func (s *Service) RebootDevice(ctx context.Context, req *dto.AgentOpenAPIDeviceOperationRequest) error { if _, err := s.resolveOpenAPIDevice(ctx, req.DeviceNo); err != nil { return err } return s.deviceService.GatewayRebootDevice(ctx, req.DeviceNo) } // ResetDevice 开放接口恢复出厂设置 func (s *Service) ResetDevice(ctx context.Context, req *dto.AgentOpenAPIDeviceOperationRequest) error { if _, err := s.resolveOpenAPIDevice(ctx, req.DeviceNo); err != nil { return err } return s.deviceService.GatewayResetDevice(ctx, req.DeviceNo) } // resolveOpenAPICard 将开放接口卡标识解析为当前代理可见的 IoT 卡 func (s *Service) resolveOpenAPICard(ctx context.Context, cardNo string) (*model.IotCard, error) { identifier := strings.TrimSpace(cardNo) if identifier == "" { return nil, errors.New(errors.CodeInvalidParam) } if s.assetService == nil || s.iotCardStore == nil { return nil, errors.New(errors.CodeInternalError, "开放接口资产解析依赖未配置") } resolved, err := s.assetService.Resolve(ctx, identifier, false) if err != nil { if appErr, ok := err.(*errors.AppError); ok && appErr.Code == errors.CodeNotFound { return s.resolveOpenAPICardByIdentifier(ctx, identifier) } return nil, err } if resolved.AssetType != "card" { return nil, errors.New(errors.CodeInvalidParam, "开放接口只支持单卡标识") } card, err := s.iotCardStore.GetByID(ctx, resolved.AssetID) if err != nil { if err == gorm.ErrRecordNotFound { return nil, errors.New(errors.CodeForbidden, "无权限操作该资源或资源不存在") } return nil, errors.Wrap(errors.CodeInternalError, err, "查询卡信息失败") } return card, nil } // resolveOpenAPICardByIdentifier 兜底支持 ICCID 双列、虚拟号和 MSISDN 精确解析 func (s *Service) resolveOpenAPICardByIdentifier(ctx context.Context, identifier string) (*model.IotCard, error) { card, err := s.iotCardStore.GetByIdentifier(ctx, identifier) if err != nil { if err == gorm.ErrRecordNotFound { return nil, errors.New(errors.CodeForbidden, "无权限操作该资源或资源不存在") } return nil, errors.Wrap(errors.CodeInternalError, err, "查询卡信息失败") } return card, nil } // loadUsagePackageContext 批量加载套餐和系列信息,避免单卡流量响应出现 N+1 查询 func (s *Service) loadUsagePackageContext(ctx context.Context, usages []*model.PackageUsage) (map[uint]*model.Package, map[uint]string, error) { packageIDs := make([]uint, 0) packageIDSeen := make(map[uint]struct{}) for _, usage := range usages { if usage.PackageID == 0 { continue } if _, ok := packageIDSeen[usage.PackageID]; !ok { packageIDSeen[usage.PackageID] = struct{}{} packageIDs = append(packageIDs, usage.PackageID) } } packages, err := s.packageStore.GetByIDsUnscoped(ctx, packageIDs) if err != nil { return nil, nil, errors.Wrap(errors.CodeInternalError, err, "批量查询套餐失败") } packageMap := make(map[uint]*model.Package, len(packages)) seriesIDSeen := make(map[uint]struct{}) seriesIDs := make([]uint, 0) for _, pkg := range packages { packageMap[pkg.ID] = pkg if pkg.SeriesID > 0 { if _, ok := seriesIDSeen[pkg.SeriesID]; !ok { seriesIDSeen[pkg.SeriesID] = struct{}{} seriesIDs = append(seriesIDs, pkg.SeriesID) } } } seriesList, err := s.packageSeriesStore.GetByIDs(ctx, seriesIDs) if err != nil { return nil, nil, errors.Wrap(errors.CodeInternalError, err, "批量查询套餐系列失败") } seriesMap := make(map[uint]string, len(seriesList)) for _, series := range seriesList { seriesMap[series.ID] = series.SeriesName } return packageMap, seriesMap, nil } // buildTrafficItem 按开放接口口径构造套餐流量项,主动隐藏虚流量内部字段 func (s *Service) buildTrafficItem(usage *model.PackageUsage, packageMap map[uint]*model.Package, seriesMap map[uint]string, active bool) dto.AgentOpenAPIPackageTrafficItem { pkg := packageMap[usage.PackageID] packageCode := "" packageName := usage.PackageName packageType := "" validDays := 0 seriesName := "" if pkg != nil { packageCode = pkg.PackageCode if packageName == "" { packageName = pkg.PackageName } packageType = pkg.PackageType validDays = packageValidDays(nil, pkg.DurationMonths) if pkg.DurationDays > 0 { validDays = pkg.DurationDays } seriesName = seriesMap[pkg.SeriesID] } usedFlow := int64(0) remainingFlow := int64(0) if active { usedFlow = visibleUsedFlowMB(usage) remainingFlow = usage.DataLimitMB - usedFlow if remainingFlow < 0 { remainingFlow = 0 } } return dto.AgentOpenAPIPackageTrafficItem{ PackageCode: packageCode, PackageName: packageName, SeriesName: seriesName, PackageType: packageType, PackageTypeName: constants.AgentOpenAPIPackageTypeName(packageType), TotalFlowMB: usage.DataLimitMB, UsedFlowMB: usedFlow, RemainingFlowMB: remainingFlow, StartAt: usage.ActivatedAt, ExpiresAt: usage.ExpiresAt, ValidDays: validDays, Priority: usage.Priority, } } // visibleUsedFlowMB 计算对代理展示的已用流量,并限制不超过真实总量 func visibleUsedFlowMB(usage *model.PackageUsage) int64 { ratio := usage.DisplayGainRatioSnapshot if ratio <= 0 { ratio = 1 } used := int64(float64(usage.DataUsageMB) * ratio) if used > usage.DataLimitMB { return usage.DataLimitMB } if used < 0 { return 0 } return used } // packageValidDays 将套餐有效期统一折算为开放接口展示天数 func packageValidDays(durationDays *int, durationMonths int) int { if durationDays != nil && *durationDays > 0 { return *durationDays } if durationMonths > 0 { return durationMonths * 30 } return 0 } func normalizePage(page, pageSize int) (int, int) { if page < 1 { page = 1 } if pageSize < 1 { pageSize = constants.DefaultPageSize } if pageSize > constants.MaxPageSize { pageSize = constants.MaxPageSize } return page, pageSize } func stringValue(value *string) string { if value == nil { return "" } return *value } func int64Value(value *int64) int64 { if value == nil { return 0 } return *value } func walletCurrency(currency string) string { if currency == "" { return constants.AgentOpenAPIWalletCurrencyCNY } return currency } func buildFailedCard(cardNo string, err error) dto.AgentOpenAPIWalletPackageOrderFailure { code := errors.CodeInternalError message := errors.GetMessage(code, "zh-CN") if appErr, ok := err.(*errors.AppError); ok { code = appErr.Code message = appErr.Message } return dto.AgentOpenAPIWalletPackageOrderFailure{ CardNo: cardNo, Code: code, Message: message, } } func generateAgentOpenAPIBatchNo() string { return fmt.Sprintf("%s%s%06d", constants.AgentOpenAPIBatchNoPrefix, time.Now().Format("20060102150405"), rand.Intn(1000000)) }