Files
junhong_cmp_fiber/internal/service/package/service.go
huang 907e500ffb
All checks were successful
构建并部署到测试环境(无 SSH) / build-and-deploy (push) Successful in 6m58s
修复列表没有正确返回新增字段问题
2026-03-16 10:51:15 +08:00

688 lines
22 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 packagepkg
import (
"context"
"fmt"
"time"
"gorm.io/gorm"
"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"
"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"
)
type Service struct {
packageStore *postgres.PackageStore
packageSeriesStore *postgres.PackageSeriesStore
packageAllocationStore *postgres.ShopPackageAllocationStore
shopSeriesAllocationStore *postgres.ShopSeriesAllocationStore
}
func New(
packageStore *postgres.PackageStore,
packageSeriesStore *postgres.PackageSeriesStore,
packageAllocationStore *postgres.ShopPackageAllocationStore,
shopSeriesAllocationStore *postgres.ShopSeriesAllocationStore,
) *Service {
return &Service{
packageStore: packageStore,
packageSeriesStore: packageSeriesStore,
packageAllocationStore: packageAllocationStore,
shopSeriesAllocationStore: shopSeriesAllocationStore,
}
}
func (s *Service) Create(ctx context.Context, req *dto.CreatePackageRequest) (*dto.PackageResponse, error) {
currentUserID := middleware.GetUserIDFromContext(ctx)
if currentUserID == 0 {
return nil, errors.New(errors.CodeUnauthorized, "未授权访问")
}
existing, _ := s.packageStore.GetByCode(ctx, req.PackageCode)
if existing != nil {
return nil, errors.New(errors.CodeConflict, "套餐编码已存在")
}
// 校验虚流量配置:启用时虚流量必须 > 0 且 ≤ 真流量
if req.EnableVirtualData {
if req.VirtualDataMB == nil || *req.VirtualDataMB <= 0 {
return nil, errors.New(errors.CodeInvalidParam, "启用虚流量时虚流量额度必须大于0")
}
realDataMB := int64(0)
if req.RealDataMB != nil {
realDataMB = *req.RealDataMB
}
if *req.VirtualDataMB > realDataMB {
return nil, errors.New(errors.CodeInvalidParam, "虚流量额度不能大于真流量额度")
}
}
// 校验套餐周期类型和时长配置
calendarType := constants.PackageCalendarTypeByDay // 默认按天
if req.CalendarType != nil {
calendarType = *req.CalendarType
}
if calendarType == constants.PackageCalendarTypeNaturalMonth {
// 自然月套餐:必须提供 duration_months
if req.DurationMonths <= 0 {
return nil, errors.New(errors.CodeInvalidParam, "自然月套餐必须提供有效的duration_months")
}
} else if calendarType == constants.PackageCalendarTypeByDay {
// 按天套餐:必须提供 duration_days
if req.DurationDays == nil || *req.DurationDays <= 0 {
return nil, errors.New(errors.CodeInvalidParam, "按天套餐必须提供有效的duration_days")
}
}
var seriesName *string
if req.SeriesID != nil && *req.SeriesID > 0 {
series, err := s.packageSeriesStore.GetByID(ctx, *req.SeriesID)
if err != nil {
if err == gorm.ErrRecordNotFound {
return nil, errors.New(errors.CodeNotFound, "套餐系列不存在")
}
return nil, errors.Wrap(errors.CodeInternalError, err, "获取套餐系列失败")
}
seriesName = &series.SeriesName
}
pkg := &model.Package{
PackageCode: req.PackageCode,
PackageName: req.PackageName,
PackageType: req.PackageType,
DurationMonths: req.DurationMonths,
CostPrice: req.CostPrice,
EnableVirtualData: req.EnableVirtualData,
CalendarType: calendarType,
Status: constants.StatusEnabled,
ShelfStatus: 2,
}
if req.SeriesID != nil {
pkg.SeriesID = *req.SeriesID
}
if req.RealDataMB != nil {
pkg.RealDataMB = *req.RealDataMB
}
if req.VirtualDataMB != nil {
pkg.VirtualDataMB = *req.VirtualDataMB
}
if req.SuggestedRetailPrice != nil {
pkg.SuggestedRetailPrice = *req.SuggestedRetailPrice
}
if req.DurationDays != nil {
pkg.DurationDays = *req.DurationDays
}
if req.DataResetCycle != nil {
pkg.DataResetCycle = *req.DataResetCycle
} else {
// 默认月重置
pkg.DataResetCycle = constants.PackageDataResetMonthly
}
if req.EnableRealnameActivation != nil {
pkg.EnableRealnameActivation = *req.EnableRealnameActivation
} else {
pkg.EnableRealnameActivation = true
}
pkg.VirtualRatio = calculateVirtualRatio(pkg.EnableVirtualData, pkg.RealDataMB, pkg.VirtualDataMB)
pkg.Creator = currentUserID
if err := s.packageStore.Create(ctx, pkg); err != nil {
return nil, errors.Wrap(errors.CodeInternalError, err, "创建套餐失败")
}
resp := s.toResponse(ctx, pkg)
resp.SeriesName = seriesName
return resp, nil
}
func (s *Service) Get(ctx context.Context, id uint) (*dto.PackageResponse, error) {
pkg, err := s.packageStore.GetByID(ctx, id)
if err != nil {
if err == gorm.ErrRecordNotFound {
return nil, errors.New(errors.CodeNotFound, "套餐不存在")
}
return nil, errors.Wrap(errors.CodeInternalError, err, "获取套餐失败")
}
resp := s.toResponse(ctx, pkg)
// 查询系列名称
if pkg.SeriesID > 0 {
series, err := s.packageSeriesStore.GetByID(ctx, pkg.SeriesID)
if err == nil {
resp.SeriesName = &series.SeriesName
}
}
return resp, nil
}
func (s *Service) Update(ctx context.Context, id uint, req *dto.UpdatePackageRequest) (*dto.PackageResponse, error) {
currentUserID := middleware.GetUserIDFromContext(ctx)
if currentUserID == 0 {
return nil, errors.New(errors.CodeUnauthorized, "未授权访问")
}
pkg, err := s.packageStore.GetByID(ctx, id)
if err != nil {
if err == gorm.ErrRecordNotFound {
return nil, errors.New(errors.CodeNotFound, "套餐不存在")
}
return nil, errors.Wrap(errors.CodeInternalError, err, "获取套餐失败")
}
var seriesName *string
if req.SeriesID != nil && *req.SeriesID > 0 {
series, err := s.packageSeriesStore.GetByID(ctx, *req.SeriesID)
if err != nil {
if err == gorm.ErrRecordNotFound {
return nil, errors.New(errors.CodeNotFound, "套餐系列不存在")
}
return nil, errors.Wrap(errors.CodeInternalError, err, "获取套餐系列失败")
}
pkg.SeriesID = *req.SeriesID
seriesName = &series.SeriesName
} else if pkg.SeriesID > 0 {
series, err := s.packageSeriesStore.GetByID(ctx, pkg.SeriesID)
if err == nil {
seriesName = &series.SeriesName
}
}
if req.PackageName != nil {
pkg.PackageName = *req.PackageName
}
if req.PackageType != nil {
pkg.PackageType = *req.PackageType
}
if req.DurationMonths != nil {
pkg.DurationMonths = *req.DurationMonths
}
if req.RealDataMB != nil {
pkg.RealDataMB = *req.RealDataMB
}
if req.VirtualDataMB != nil {
pkg.VirtualDataMB = *req.VirtualDataMB
}
if req.EnableVirtualData != nil {
pkg.EnableVirtualData = *req.EnableVirtualData
}
if req.CostPrice != nil {
pkg.CostPrice = *req.CostPrice
}
if req.SuggestedRetailPrice != nil {
pkg.SuggestedRetailPrice = *req.SuggestedRetailPrice
}
if req.CalendarType != nil {
pkg.CalendarType = *req.CalendarType
}
if req.DurationDays != nil {
pkg.DurationDays = *req.DurationDays
}
if req.DataResetCycle != nil {
pkg.DataResetCycle = *req.DataResetCycle
}
if req.EnableRealnameActivation != nil {
pkg.EnableRealnameActivation = *req.EnableRealnameActivation
}
// 校验套餐周期类型和时长配置
if pkg.CalendarType == constants.PackageCalendarTypeNaturalMonth {
if pkg.DurationMonths <= 0 {
return nil, errors.New(errors.CodeInvalidParam, "自然月套餐必须提供有效的duration_months")
}
} else if pkg.CalendarType == constants.PackageCalendarTypeByDay {
if pkg.DurationDays <= 0 {
return nil, errors.New(errors.CodeInvalidParam, "按天套餐必须提供有效的duration_days")
}
}
// 校验虚流量配置
if pkg.EnableVirtualData {
if pkg.VirtualDataMB <= 0 {
return nil, errors.New(errors.CodeInvalidParam, "启用虚流量时虚流量额度必须大于0")
}
if pkg.VirtualDataMB > pkg.RealDataMB {
return nil, errors.New(errors.CodeInvalidParam, "虚流量额度不能大于真流量额度")
}
}
pkg.VirtualRatio = calculateVirtualRatio(pkg.EnableVirtualData, pkg.RealDataMB, pkg.VirtualDataMB)
pkg.Updater = currentUserID
if err := s.packageStore.Update(ctx, pkg); err != nil {
return nil, errors.Wrap(errors.CodeInternalError, err, "更新套餐失败")
}
resp := s.toResponse(ctx, pkg)
resp.SeriesName = seriesName
return resp, nil
}
func (s *Service) Delete(ctx context.Context, id uint) error {
_, err := s.packageStore.GetByID(ctx, id)
if err != nil {
if err == gorm.ErrRecordNotFound {
return errors.New(errors.CodeNotFound, "套餐不存在")
}
return errors.Wrap(errors.CodeInternalError, err, "获取套餐失败")
}
if err := s.packageStore.Delete(ctx, id); err != nil {
return errors.Wrap(errors.CodeInternalError, err, "删除套餐失败")
}
return nil
}
func (s *Service) List(ctx context.Context, req *dto.PackageListRequest) ([]*dto.PackageResponse, int64, error) {
opts := &store.QueryOptions{
Page: req.Page,
PageSize: req.PageSize,
OrderBy: "id DESC",
}
if opts.Page == 0 {
opts.Page = 1
}
if opts.PageSize == 0 {
opts.PageSize = constants.DefaultPageSize
}
filters := make(map[string]interface{})
if req.PackageName != nil {
filters["package_name"] = *req.PackageName
}
if req.SeriesID != nil {
filters["series_id"] = *req.SeriesID
}
if req.Status != nil {
filters["status"] = *req.Status
}
if req.ShelfStatus != nil {
filters["shelf_status"] = *req.ShelfStatus
}
if req.PackageType != nil {
filters["package_type"] = *req.PackageType
}
packages, total, err := s.packageStore.List(ctx, opts, filters)
if err != nil {
return nil, 0, errors.Wrap(errors.CodeInternalError, err, "查询套餐列表失败")
}
// 收集所有唯一的 series_id 和 package_id
seriesIDMap := make(map[uint]bool)
packageIDs := make([]uint, len(packages))
for i, pkg := range packages {
packageIDs[i] = pkg.ID
if pkg.SeriesID > 0 {
seriesIDMap[pkg.SeriesID] = true
}
}
// 批量查询套餐系列(名称和配置)
seriesMap := make(map[uint]string)
seriesConfigMap := make(map[uint]*model.OneTimeCommissionConfig)
if len(seriesIDMap) > 0 {
seriesIDs := make([]uint, 0, len(seriesIDMap))
for id := range seriesIDMap {
seriesIDs = append(seriesIDs, id)
}
seriesList, err := s.packageSeriesStore.GetByIDs(ctx, seriesIDs)
if err != nil {
return nil, 0, errors.Wrap(errors.CodeInternalError, err, "批量查询套餐系列失败")
}
for _, series := range seriesList {
seriesMap[series.ID] = series.SeriesName
// 解析一次性佣金配置
if series.EnableOneTimeCommission {
config, _ := series.GetOneTimeCommissionConfig()
if config != nil {
seriesConfigMap[series.ID] = config
}
}
}
}
userType := middleware.GetUserTypeFromContext(ctx)
shopID := middleware.GetShopIDFromContext(ctx)
var allocationMap map[uint]*model.ShopPackageAllocation
var seriesAllocationMap map[uint]*model.ShopSeriesAllocation
if userType == constants.UserTypeAgent && shopID > 0 && len(packageIDs) > 0 {
allocationMap = s.batchGetAllocationsForShop(ctx, shopID, packageIDs)
// 批量获取店铺的系列分配
seriesAllocationMap = s.batchGetSeriesAllocationsForShop(ctx, shopID, seriesIDMap)
}
responses := make([]*dto.PackageResponse, len(packages))
for i, pkg := range packages {
resp := s.toResponseWithAllocation(ctx, pkg, allocationMap, seriesAllocationMap, seriesConfigMap)
if pkg.SeriesID > 0 {
if seriesName, ok := seriesMap[pkg.SeriesID]; ok {
resp.SeriesName = &seriesName
}
}
responses[i] = resp
}
return responses, total, nil
}
// batchGetSeriesAllocationsForShop 批量获取店铺的系列分配
func (s *Service) batchGetSeriesAllocationsForShop(ctx context.Context, shopID uint, seriesIDMap map[uint]bool) map[uint]*model.ShopSeriesAllocation {
result := make(map[uint]*model.ShopSeriesAllocation)
if len(seriesIDMap) == 0 {
return result
}
allocations, err := s.shopSeriesAllocationStore.GetByShopID(ctx, shopID)
if err != nil || len(allocations) == 0 {
return result
}
for _, alloc := range allocations {
if seriesIDMap[alloc.SeriesID] {
result[alloc.SeriesID] = alloc
}
}
return result
}
func (s *Service) UpdateStatus(ctx context.Context, id uint, status int) error {
currentUserID := middleware.GetUserIDFromContext(ctx)
if currentUserID == 0 {
return errors.New(errors.CodeUnauthorized, "未授权访问")
}
pkg, err := s.packageStore.GetByID(ctx, id)
if err != nil {
if err == gorm.ErrRecordNotFound {
return errors.New(errors.CodeNotFound, "套餐不存在")
}
return errors.Wrap(errors.CodeInternalError, err, "获取套餐失败")
}
pkg.Status = status
pkg.Updater = currentUserID
if status == constants.StatusDisabled {
pkg.ShelfStatus = 2
}
if err := s.packageStore.Update(ctx, pkg); err != nil {
return errors.Wrap(errors.CodeInternalError, err, "更新套餐状态失败")
}
return nil
}
func (s *Service) UpdateShelfStatus(ctx context.Context, id uint, shelfStatus int) error {
currentUserID := middleware.GetUserIDFromContext(ctx)
if currentUserID == 0 {
return errors.New(errors.CodeUnauthorized, "未授权访问")
}
userType := middleware.GetUserTypeFromContext(ctx)
// 代理用户:修改自己分配记录的 shelf_status不影响平台全局套餐状态
if userType == constants.UserTypeAgent {
return s.updateAgentShelfStatus(ctx, id, shelfStatus, currentUserID)
}
// 平台/超管:修改套餐全局 shelf_status
pkg, err := s.packageStore.GetByID(ctx, id)
if err != nil {
if err == gorm.ErrRecordNotFound {
return errors.New(errors.CodeNotFound, "套餐不存在")
}
return errors.Wrap(errors.CodeInternalError, err, "获取套餐失败")
}
if shelfStatus == constants.ShelfStatusOn && pkg.Status == constants.StatusDisabled {
return errors.New(errors.CodeInvalidStatus, "禁用的套餐不能上架,请先启用")
}
pkg.ShelfStatus = shelfStatus
pkg.Updater = currentUserID
if err := s.packageStore.Update(ctx, pkg); err != nil {
return errors.Wrap(errors.CodeInternalError, err, "更新套餐上架状态失败")
}
return nil
}
// updateAgentShelfStatus 代理上下架路径:更新分配记录的 shelf_status
func (s *Service) updateAgentShelfStatus(ctx context.Context, packageID uint, shelfStatus int, updaterID uint) error {
shopID := middleware.GetShopIDFromContext(ctx)
if shopID == 0 {
return errors.New(errors.CodeUnauthorized, "当前用户不属于任何店铺")
}
// 查找代理对该套餐的分配记录
allocation, err := s.packageAllocationStore.GetByShopAndPackage(ctx, shopID, packageID)
if err != nil {
if err == gorm.ErrRecordNotFound {
return errors.New(errors.CodeNotFound, "该套餐未分配给您,无法操作上下架")
}
return errors.Wrap(errors.CodeInternalError, err, "获取分配记录失败")
}
// 上架时检查套餐全局禁用状态
if shelfStatus == constants.ShelfStatusOn {
pkg, err := s.packageStore.GetByID(ctx, packageID)
if err != nil {
if err == gorm.ErrRecordNotFound {
return errors.New(errors.CodeNotFound, "套餐不存在")
}
return errors.Wrap(errors.CodeInternalError, err, "获取套餐失败")
}
if pkg.Status == constants.StatusDisabled {
return errors.New(errors.CodeInvalidStatus, "套餐已禁用,无法上架")
}
}
if err := s.packageAllocationStore.UpdateShelfStatus(ctx, allocation.ID, shelfStatus, updaterID); err != nil {
return errors.Wrap(errors.CodeInternalError, err, "更新上下架状态失败")
}
return nil
}
func (s *Service) toResponse(ctx context.Context, pkg *model.Package) *dto.PackageResponse {
var seriesID *uint
if pkg.SeriesID > 0 {
seriesID = &pkg.SeriesID
}
var durationDays *int
if pkg.CalendarType == constants.PackageCalendarTypeByDay && pkg.DurationDays > 0 {
durationDays = &pkg.DurationDays
}
resp := &dto.PackageResponse{
ID: pkg.ID,
PackageCode: pkg.PackageCode,
PackageName: pkg.PackageName,
SeriesID: seriesID,
PackageType: pkg.PackageType,
DurationMonths: pkg.DurationMonths,
RealDataMB: pkg.RealDataMB,
VirtualDataMB: pkg.VirtualDataMB,
EnableVirtualData: pkg.EnableVirtualData,
VirtualRatio: calculateVirtualRatio(pkg.EnableVirtualData, pkg.RealDataMB, pkg.VirtualDataMB),
CostPrice: pkg.CostPrice,
SuggestedRetailPrice: pkg.SuggestedRetailPrice,
CalendarType: pkg.CalendarType,
DurationDays: durationDays,
DataResetCycle: pkg.DataResetCycle,
EnableRealnameActivation: pkg.EnableRealnameActivation,
Status: pkg.Status,
ShelfStatus: pkg.ShelfStatus,
CreatedAt: pkg.CreatedAt.Format(time.RFC3339),
UpdatedAt: pkg.UpdatedAt.Format(time.RFC3339),
}
userType := middleware.GetUserTypeFromContext(ctx)
shopID := middleware.GetShopIDFromContext(ctx)
if userType == constants.UserTypeAgent && shopID > 0 {
allocation, err := s.packageAllocationStore.GetByShopAndPackage(ctx, shopID, pkg.ID)
if err == nil && allocation != nil {
resp.CostPrice = allocation.CostPrice
profitMargin := pkg.SuggestedRetailPrice - allocation.CostPrice
resp.ProfitMargin = &profitMargin
// 代理查询时shelf_status 返回自己分配记录的值,而非平台全局值
resp.ShelfStatus = allocation.ShelfStatus
}
}
return resp
}
func (s *Service) batchGetAllocationsForShop(ctx context.Context, shopID uint, packageIDs []uint) map[uint]*model.ShopPackageAllocation {
allocationMap := make(map[uint]*model.ShopPackageAllocation)
allocations, err := s.packageAllocationStore.GetByShopAndPackages(ctx, shopID, packageIDs)
if err != nil || len(allocations) == 0 {
return allocationMap
}
for _, alloc := range allocations {
allocationMap[alloc.PackageID] = alloc
}
return allocationMap
}
func (s *Service) toResponseWithAllocation(_ context.Context, pkg *model.Package, allocationMap map[uint]*model.ShopPackageAllocation, seriesAllocationMap map[uint]*model.ShopSeriesAllocation, seriesConfigMap map[uint]*model.OneTimeCommissionConfig) *dto.PackageResponse {
var seriesID *uint
if pkg.SeriesID > 0 {
seriesID = &pkg.SeriesID
}
var durationDays *int
if pkg.CalendarType == constants.PackageCalendarTypeByDay && pkg.DurationDays > 0 {
durationDays = &pkg.DurationDays
}
resp := &dto.PackageResponse{
ID: pkg.ID,
PackageCode: pkg.PackageCode,
PackageName: pkg.PackageName,
SeriesID: seriesID,
PackageType: pkg.PackageType,
DurationMonths: pkg.DurationMonths,
RealDataMB: pkg.RealDataMB,
VirtualDataMB: pkg.VirtualDataMB,
EnableVirtualData: pkg.EnableVirtualData,
VirtualRatio: calculateVirtualRatio(pkg.EnableVirtualData, pkg.RealDataMB, pkg.VirtualDataMB),
CostPrice: pkg.CostPrice,
SuggestedRetailPrice: pkg.SuggestedRetailPrice,
CalendarType: pkg.CalendarType,
DurationDays: durationDays,
DataResetCycle: pkg.DataResetCycle,
EnableRealnameActivation: pkg.EnableRealnameActivation,
Status: pkg.Status,
ShelfStatus: pkg.ShelfStatus,
CreatedAt: pkg.CreatedAt.Format(time.RFC3339),
UpdatedAt: pkg.UpdatedAt.Format(time.RFC3339),
}
if allocationMap != nil {
if allocation, ok := allocationMap[pkg.ID]; ok {
resp.CostPrice = allocation.CostPrice
profitMargin := pkg.SuggestedRetailPrice - allocation.CostPrice
resp.ProfitMargin = &profitMargin
// 代理查询时shelf_status 返回自己分配记录的值,而非平台全局值
resp.ShelfStatus = allocation.ShelfStatus
}
}
// 填充返佣信息(仅代理用户可见)
if pkg.SeriesID > 0 && seriesAllocationMap != nil && seriesConfigMap != nil {
s.fillCommissionInfo(resp, pkg.SeriesID, seriesAllocationMap, seriesConfigMap)
}
return resp
}
// fillCommissionInfo 填充返佣信息到响应中
func (s *Service) fillCommissionInfo(resp *dto.PackageResponse, seriesID uint, seriesAllocationMap map[uint]*model.ShopSeriesAllocation, seriesConfigMap map[uint]*model.OneTimeCommissionConfig) {
seriesAllocation, hasAllocation := seriesAllocationMap[seriesID]
config, hasConfig := seriesConfigMap[seriesID]
if !hasAllocation || !hasConfig {
return
}
// 一次性佣金是否启用由 PackageSeries.enable_one_time_commission 控制
if !config.Enable {
return
}
// 设置一次性佣金金额
oneTimeAmount := seriesAllocation.OneTimeCommissionAmount
resp.OneTimeCommissionAmount = &oneTimeAmount
// 设置当前返佣比例(格式化为可读字符串)
if config.CommissionType == "fixed" {
// 固定金额模式:显示代理能拿到的金额
resp.CurrentCommissionRate = formatAmount(seriesAllocation.OneTimeCommissionAmount)
} else if config.CommissionType == "tiered" && len(config.Tiers) > 0 {
// 梯度模式:显示基础金额,并设置梯度信息
resp.CurrentCommissionRate = formatAmount(seriesAllocation.OneTimeCommissionAmount)
// 构建梯度信息
tierInfo := s.buildTierInfo(config.Tiers, seriesAllocation.OneTimeCommissionAmount)
if tierInfo != nil {
resp.TierInfo = tierInfo
}
}
}
// buildTierInfo 构建梯度返佣信息
func (s *Service) buildTierInfo(tiers []model.OneTimeCommissionTier, currentAmount int64) *dto.CommissionTierInfo {
if len(tiers) == 0 {
return nil
}
tierInfo := &dto.CommissionTierInfo{
CurrentRate: formatAmount(currentAmount),
}
// 找到下一个可达到的梯度
// 梯度按 threshold 升序排列,找到第一个 amount > currentAmount 的梯度
for _, tier := range tiers {
if tier.Amount > currentAmount {
tierInfo.NextThreshold = &tier.Threshold
nextRate := formatAmount(tier.Amount)
tierInfo.NextRate = nextRate
break
}
}
return tierInfo
}
// formatAmount 格式化金额为可读字符串(分转元)
func formatAmount(amountFen int64) string {
yuan := float64(amountFen) / 100
if yuan == float64(int64(yuan)) {
return fmt.Sprintf("%.0f元/张", yuan)
}
return fmt.Sprintf("%.2f元/张", yuan)
}
// calculateVirtualRatio 计算虚流量比例
// enable_virtual_data=true 且 virtual_data_mb>0 时 = real_data_mb/virtual_data_mb否则 = 1.0
func calculateVirtualRatio(enableVirtualData bool, realDataMB, virtualDataMB int64) float64 {
if enableVirtualData && virtualDataMB > 0 {
return float64(realDataMB) / float64(virtualDataMB)
}
return 1.0
}