This commit is contained in:
@@ -484,7 +484,7 @@ func (s *Service) resolveOpenAPICard(ctx context.Context, cardNo string) (*model
|
||||
return nil, errors.New(errors.CodeInternalError, "开放接口资产解析依赖未配置")
|
||||
}
|
||||
|
||||
resolved, err := s.assetService.Resolve(ctx, identifier)
|
||||
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)
|
||||
|
||||
@@ -91,7 +91,8 @@ func New(
|
||||
// Resolve 通过任意标识符解析资产
|
||||
// 主路径:查注册表(精确匹配 ICCID 或 VirtualNo)
|
||||
// Fallback:原有跨表 OR 查询(处理 IMEI/SN/MSISDN 等非注册标识符)
|
||||
func (s *Service) Resolve(ctx context.Context, identifier string) (*dto.AssetResolveResponse, error) {
|
||||
// includeUsageSummary 为 true 时额外填充当前世代流量汇总字段
|
||||
func (s *Service) Resolve(ctx context.Context, identifier string, includeUsageSummary bool) (*dto.AssetResolveResponse, error) {
|
||||
if s.assetIdentifierStore != nil {
|
||||
regRecord, regErr := s.assetIdentifierStore.FindByIdentifier(ctx, identifier)
|
||||
if regErr == nil && regRecord != nil {
|
||||
@@ -102,6 +103,9 @@ func (s *Service) Resolve(ctx context.Context, identifier string) (*dto.AssetRes
|
||||
resp, buildErr := s.buildDeviceResolveResponse(ctx, device)
|
||||
if buildErr == nil {
|
||||
resp.Identifier = identifier
|
||||
if includeUsageSummary {
|
||||
s.fillUsageSummary(ctx, resp, "device", device.ID)
|
||||
}
|
||||
}
|
||||
return resp, buildErr
|
||||
}
|
||||
@@ -111,6 +115,9 @@ func (s *Service) Resolve(ctx context.Context, identifier string) (*dto.AssetRes
|
||||
resp, buildErr := s.buildCardResolveResponse(ctx, card)
|
||||
if buildErr == nil {
|
||||
resp.Identifier = identifier
|
||||
if includeUsageSummary {
|
||||
s.fillUsageSummary(ctx, resp, "iot_card", card.ID)
|
||||
}
|
||||
}
|
||||
return resp, buildErr
|
||||
}
|
||||
@@ -123,6 +130,9 @@ func (s *Service) Resolve(ctx context.Context, identifier string) (*dto.AssetRes
|
||||
resp, buildErr := s.buildDeviceResolveResponse(ctx, device)
|
||||
if buildErr == nil {
|
||||
resp.Identifier = identifier
|
||||
if includeUsageSummary {
|
||||
s.fillUsageSummary(ctx, resp, "device", device.ID)
|
||||
}
|
||||
}
|
||||
return resp, buildErr
|
||||
}
|
||||
@@ -135,6 +145,9 @@ func (s *Service) Resolve(ctx context.Context, identifier string) (*dto.AssetRes
|
||||
resp, buildErr := s.buildCardResolveResponse(ctx, &card)
|
||||
if buildErr == nil {
|
||||
resp.Identifier = identifier
|
||||
if includeUsageSummary {
|
||||
s.fillUsageSummary(ctx, resp, "iot_card", card.ID)
|
||||
}
|
||||
}
|
||||
return resp, buildErr
|
||||
}
|
||||
@@ -336,6 +349,19 @@ func (s *Service) fillPackageInfo(ctx context.Context, resp *dto.AssetResolveRes
|
||||
resp.EnableVirtualData = usage.EnableVirtualDataSnapshot
|
||||
}
|
||||
|
||||
// fillUsageSummary 填充当前世代流量汇总字段。
|
||||
// 仅在 includeUsageSummary=true 时被调用,无套餐时返回 0.0(非 null)。
|
||||
func (s *Service) fillUsageSummary(ctx context.Context, resp *dto.AssetResolveResponse, carrierType string, carrierID uint) {
|
||||
usages, err := s.packageUsageStore.GetCurrentGenerationUsagesForSummary(ctx, carrierType, carrierID)
|
||||
if err != nil {
|
||||
logger.GetAppLogger().Error("查询流量汇总失败", zap.String("carrierType", carrierType), zap.Uint("carrierID", carrierID), zap.Error(err))
|
||||
return
|
||||
}
|
||||
totalUsed, totalRemaining := computeUsageSummary(usages)
|
||||
resp.TotalVirtualUsedMB = &totalUsed
|
||||
resp.TotalVirtualRemainingMB = &totalRemaining
|
||||
}
|
||||
|
||||
// fillShopName 填充店铺名称
|
||||
func (s *Service) fillShopName(ctx context.Context, resp *dto.AssetResolveResponse) {
|
||||
if resp.ShopID == nil || *resp.ShopID == 0 {
|
||||
@@ -971,7 +997,7 @@ func (s *Service) GetOrders(ctx context.Context, identifier string, page, pageSi
|
||||
pageSize = 100
|
||||
}
|
||||
|
||||
asset, err := s.Resolve(ctx, identifier)
|
||||
asset, err := s.Resolve(ctx, identifier, false)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
16
internal/service/asset/usage_summary.go
Normal file
16
internal/service/asset/usage_summary.go
Normal file
@@ -0,0 +1,16 @@
|
||||
package asset
|
||||
|
||||
import "github.com/break/junhong_cmp_fiber/internal/model"
|
||||
|
||||
// computeUsageSummary 汇总当前世代套餐的虚已用量和虚剩余量。
|
||||
// 对每条记录调用 BuildTrafficMetrics() 获取虚流量字段,累加后计算剩余。
|
||||
func computeUsageSummary(usages []*model.PackageUsage) (totalUsedMB float64, totalRemainingMB float64) {
|
||||
var totalVirtualTotal float64
|
||||
for _, u := range usages {
|
||||
m := u.BuildTrafficMetrics()
|
||||
totalUsedMB += m.VirtualUsedMB
|
||||
totalVirtualTotal += float64(m.VirtualTotalMB)
|
||||
}
|
||||
totalRemainingMB = totalVirtualTotal - totalUsedMB
|
||||
return
|
||||
}
|
||||
81
internal/service/asset/usage_summary_test.go
Normal file
81
internal/service/asset/usage_summary_test.go
Normal file
@@ -0,0 +1,81 @@
|
||||
package asset
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/break/junhong_cmp_fiber/internal/model"
|
||||
)
|
||||
|
||||
func TestComputeUsageSummary_EmptyList(t *testing.T) {
|
||||
totalUsed, totalRemaining := computeUsageSummary(nil)
|
||||
if totalUsed != 0.0 {
|
||||
t.Errorf("期望 totalUsed=0.0,实际=%v", totalUsed)
|
||||
}
|
||||
if totalRemaining != 0.0 {
|
||||
t.Errorf("期望 totalRemaining=0.0,实际=%v", totalRemaining)
|
||||
}
|
||||
}
|
||||
|
||||
func TestComputeUsageSummary_SingleVirtualPackage(t *testing.T) {
|
||||
// 启用虚流量:真实总量=100MB,虚拟总量=200MB,倍率=0.5
|
||||
// 真实已用=40MB → 虚拟已用=min(40*0.5, 100)=20MB
|
||||
// 虚拟剩余=200-20=180MB
|
||||
usage := &model.PackageUsage{
|
||||
DataLimitMB: 100,
|
||||
DataUsageMB: 40,
|
||||
VirtualTotalMBSnapshot: 200,
|
||||
DisplayGainRatioSnapshot: 0.5,
|
||||
EnableVirtualDataSnapshot: true,
|
||||
}
|
||||
totalUsed, totalRemaining := computeUsageSummary([]*model.PackageUsage{usage})
|
||||
if totalUsed != 20.0 {
|
||||
t.Errorf("期望 totalUsed=20.0,实际=%v", totalUsed)
|
||||
}
|
||||
if totalRemaining != 180.0 {
|
||||
t.Errorf("期望 totalRemaining=180.0,实际=%v", totalRemaining)
|
||||
}
|
||||
}
|
||||
|
||||
func TestComputeUsageSummary_SingleNonVirtualPackage(t *testing.T) {
|
||||
// 未启用虚流量:退化为真实值
|
||||
// 真实总量=100MB,真实已用=40MB → 虚拟已用=40MB,虚拟剩余=60MB
|
||||
usage := &model.PackageUsage{
|
||||
DataLimitMB: 100,
|
||||
DataUsageMB: 40,
|
||||
EnableVirtualDataSnapshot: false,
|
||||
}
|
||||
totalUsed, totalRemaining := computeUsageSummary([]*model.PackageUsage{usage})
|
||||
if totalUsed != 40.0 {
|
||||
t.Errorf("期望 totalUsed=40.0,实际=%v", totalUsed)
|
||||
}
|
||||
if totalRemaining != 60.0 {
|
||||
t.Errorf("期望 totalRemaining=60.0,实际=%v", totalRemaining)
|
||||
}
|
||||
}
|
||||
|
||||
func TestComputeUsageSummary_MultiplePackages(t *testing.T) {
|
||||
// 套餐1(启用虚流量):VirtualUsed=20, VirtualTotal=200
|
||||
// 套餐2(未启用虚流量):VirtualUsed=10, VirtualTotal=50
|
||||
// 汇总:totalUsed=30, totalRemaining=(200+50)-30=220
|
||||
usages := []*model.PackageUsage{
|
||||
{
|
||||
DataLimitMB: 100,
|
||||
DataUsageMB: 40,
|
||||
VirtualTotalMBSnapshot: 200,
|
||||
DisplayGainRatioSnapshot: 0.5,
|
||||
EnableVirtualDataSnapshot: true,
|
||||
},
|
||||
{
|
||||
DataLimitMB: 50,
|
||||
DataUsageMB: 10,
|
||||
EnableVirtualDataSnapshot: false,
|
||||
},
|
||||
}
|
||||
totalUsed, totalRemaining := computeUsageSummary(usages)
|
||||
if totalUsed != 30.0 {
|
||||
t.Errorf("期望 totalUsed=30.0,实际=%v", totalUsed)
|
||||
}
|
||||
if totalRemaining != 220.0 {
|
||||
t.Errorf("期望 totalRemaining=220.0,实际=%v", totalRemaining)
|
||||
}
|
||||
}
|
||||
@@ -128,7 +128,7 @@ func (s *Service) CreateOrder(ctx context.Context, customerID uint, req *dto.Cli
|
||||
}
|
||||
|
||||
skipCtx := context.WithValue(ctx, constants.ContextKeySubordinateShopIDs, []uint{})
|
||||
assetInfo, err := s.assetService.Resolve(skipCtx, strings.TrimSpace(req.Identifier))
|
||||
assetInfo, err := s.assetService.Resolve(skipCtx, strings.TrimSpace(req.Identifier), false)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -1060,7 +1060,7 @@ func (s *Service) createAlipayForceRechargeOrder(
|
||||
func (s *Service) ListOrders(ctx context.Context, customerID uint, req *dto.ClientOrderListRequest) ([]dto.ClientOrderListItem, int64, error) {
|
||||
skipCtx := context.WithValue(ctx, constants.ContextKeySubordinateShopIDs, []uint{})
|
||||
|
||||
assetInfo, err := s.assetService.Resolve(skipCtx, strings.TrimSpace(req.Identifier))
|
||||
assetInfo, err := s.assetService.Resolve(skipCtx, strings.TrimSpace(req.Identifier), false)
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
@@ -1231,7 +1231,7 @@ func (s *Service) PayOrder(ctx context.Context, customerID uint, orderID uint, r
|
||||
if err == nil && device != nil {
|
||||
assetRealnamePolicy = device.RealnamePolicy
|
||||
// 设备的 RealNameStatus 需要通过 Resolve 获取(从当前卡派生)
|
||||
resolved, resolveErr := s.assetService.Resolve(skipCtx, device.VirtualNo)
|
||||
resolved, resolveErr := s.assetService.Resolve(skipCtx, device.VirtualNo, false)
|
||||
if resolveErr == nil && resolved != nil {
|
||||
assetRealnameStatus = resolved.RealNameStatus
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user