626 lines
21 KiB
Go
626 lines
21 KiB
Go
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
|
||
}
|
||
|
||
// 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,
|
||
) *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,
|
||
}
|
||
}
|
||
|
||
// GetCardTraffic 查询开放接口单卡流量
|
||
func (s *Service) GetCardTraffic(ctx context.Context, req *dto.AgentOpenAPICardQueryRequest) (*dto.AgentOpenAPICardTrafficResponse, error) {
|
||
card, err := s.resolveOpenAPICard(ctx, req.CardNo)
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
|
||
activeStatus := constants.PackageUsageStatusActive
|
||
pendingStatus := constants.PackageUsageStatusPending
|
||
activeUsages, err := s.packageUsageStore.ListByCarrier(ctx, "iot_card", card.ID, &activeStatus)
|
||
if err != nil {
|
||
return nil, errors.Wrap(errors.CodeInternalError, err, "查询生效套餐失败")
|
||
}
|
||
pendingUsages, err := s.packageUsageStore.ListByCarrier(ctx, "iot_card", card.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.AgentOpenAPICardTrafficResponse{
|
||
CardNo: req.CardNo,
|
||
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
|
||
}
|
||
card, err := s.resolveOpenAPICard(ctx, cardNo)
|
||
if err != nil {
|
||
resp.FailedCards = append(resp.FailedCards, buildFailedCard(cardNo, err))
|
||
continue
|
||
}
|
||
|
||
orderResp, err := s.orderService.CreateAdminOrder(ctx, &dto.CreateAdminOrderRequest{
|
||
Identifier: card.ICCID,
|
||
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)
|
||
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))
|
||
}
|