From 1e2d8d1df7be5afa0a85fbb903668571b4b76ef1 Mon Sep 17 00:00:00 2001 From: hailin Date: Thu, 15 Jan 2026 19:53:31 -0800 Subject: [PATCH] feat(asset): aggregate mining and trading account balances in asset display - Modify AssetService to fetch mining account balance from mining-service - Sum mining balance + trading balance for total share display - Add miningShareBalance and tradingShareBalance fields to AssetDisplay - Update frontend entity and model to support new fields Co-Authored-By: Claude Opus 4.5 --- .../src/application/services/asset.service.ts | 121 ++++++++++++++---- .../lib/data/models/asset_display_model.dart | 6 + .../lib/domain/entities/asset_display.dart | 15 ++- 3 files changed, 118 insertions(+), 24 deletions(-) diff --git a/backend/services/trading-service/src/application/services/asset.service.ts b/backend/services/trading-service/src/application/services/asset.service.ts index d25d5ed2..f2a7ac6d 100644 --- a/backend/services/trading-service/src/application/services/asset.service.ts +++ b/backend/services/trading-service/src/application/services/asset.service.ts @@ -1,4 +1,5 @@ import { Injectable, Logger } from '@nestjs/common'; +import { ConfigService } from '@nestjs/config'; import { TradingCalculatorService } from '../../domain/services/trading-calculator.service'; import { TradingAccountRepository } from '../../infrastructure/persistence/repositories/trading-account.repository'; import { BlackHoleRepository } from '../../infrastructure/persistence/repositories/black-hole.repository'; @@ -8,16 +9,31 @@ import { PriceService } from './price.service'; import { Money } from '../../domain/value-objects/money.vo'; import Decimal from 'decimal.js'; +// mining-service 返回的账户数据 +interface MiningAccountDto { + accountSequence: string; + totalMined: string; + availableBalance: string; + frozenBalance: string; + totalBalance: string; + totalContribution: string; + lastSyncedAt: string | null; +} + export interface AssetDisplay { - // 账户积分股余额 + // 挖矿账户积分股余额 + miningShareBalance: string; + // 交易账户积分股余额 + tradingShareBalance: string; + // 总积分股余额(挖矿 + 交易) shareBalance: string; - // 账户现金余额 + // 账户现金余额(交易账户) cashBalance: string; - // 冻结积分股 + // 冻结积分股(挖矿冻结 + 交易冻结) frozenShares: string; // 冻结现金 frozenCash: string; - // 可用积分股 + // 可用积分股(挖矿可用 + 交易可用) availableShares: string; // 可用现金 availableCash: string; @@ -41,6 +57,7 @@ export interface AssetDisplay { export class AssetService { private readonly logger = new Logger(AssetService.name); private readonly calculator = new TradingCalculatorService(); + private readonly miningServiceUrl: string; constructor( private readonly tradingAccountRepository: TradingAccountRepository, @@ -48,11 +65,17 @@ export class AssetService { private readonly circulationPoolRepository: CirculationPoolRepository, private readonly sharePoolRepository: SharePoolRepository, private readonly priceService: PriceService, - ) {} + private readonly configService: ConfigService, + ) { + this.miningServiceUrl = this.configService.get( + 'MINING_SERVICE_URL', + 'http://localhost:3021', + ); + } /** - * 获取用户资产显示 - * 资产显示 = (账户积分股 + 账户积分股 × 倍数) × 积分股价 + * 获取用户资产显示(汇总挖矿账户 + 交易账户) + * 资产显示 = (总积分股 + 总积分股 × 倍数) × 积分股价 * * @param accountSequence 账户序号 * @param dailyAllocation 用户每天分配的积分股(可选,用于计算每秒增长) @@ -61,8 +84,14 @@ export class AssetService { accountSequence: string, dailyAllocation?: string, ): Promise { - const account = await this.tradingAccountRepository.findByAccountSequence(accountSequence); - if (!account) { + // 并行获取交易账户和挖矿账户数据 + const [tradingAccount, miningAccount] = await Promise.all([ + this.tradingAccountRepository.findByAccountSequence(accountSequence), + this.getMiningAccountBalance(accountSequence), + ]); + + // 如果两个账户都不存在,返回 null + if (!tradingAccount && !miningAccount) { return null; } @@ -71,13 +100,32 @@ export class AssetService { const price = new Money(priceInfo.price); const burnMultiplier = new Decimal(priceInfo.burnMultiplier); - // 计算有效积分股 = 余额 × (1 + 倍数) - const multiplierFactor = new Decimal(1).plus(burnMultiplier); - const effectiveShares = account.shareBalance.value.times(multiplierFactor); + // 挖矿账户余额 + const miningShareBalance = new Money(miningAccount?.availableBalance || '0'); + const miningFrozenShares = new Money(miningAccount?.frozenBalance || '0'); - // 计算资产显示值 + // 交易账户余额 + const tradingShareBalance = tradingAccount?.shareBalance || Money.zero(); + const tradingFrozenShares = tradingAccount?.frozenShares || Money.zero(); + const cashBalance = tradingAccount?.cashBalance || Money.zero(); + const frozenCash = tradingAccount?.frozenCash || Money.zero(); + const totalBought = tradingAccount?.totalBought || Money.zero(); + const totalSold = tradingAccount?.totalSold || Money.zero(); + + // 汇总余额 + const totalShareBalance = miningShareBalance.add(tradingShareBalance); + const totalFrozenShares = miningFrozenShares.add(tradingFrozenShares); + const totalAvailableShares = miningShareBalance.add( + tradingAccount?.availableShares || Money.zero(), + ); + + // 计算有效积分股 = 总余额 × (1 + 倍数) + const multiplierFactor = new Decimal(1).plus(burnMultiplier); + const effectiveShares = totalShareBalance.value.times(multiplierFactor); + + // 计算资产显示值(使用汇总后的余额) const displayAssetValue = this.calculator.calculateDisplayAssetValue( - account.shareBalance, + totalShareBalance, burnMultiplier, price, ); @@ -90,22 +138,51 @@ export class AssetService { } return { - shareBalance: account.shareBalance.toFixed(8), - cashBalance: account.cashBalance.toFixed(8), - frozenShares: account.frozenShares.toFixed(8), - frozenCash: account.frozenCash.toFixed(8), - availableShares: account.availableShares.toFixed(8), - availableCash: account.availableCash.toFixed(8), + miningShareBalance: miningShareBalance.toFixed(8), + tradingShareBalance: tradingShareBalance.toFixed(8), + shareBalance: totalShareBalance.toFixed(8), + cashBalance: cashBalance.toFixed(8), + frozenShares: totalFrozenShares.toFixed(8), + frozenCash: frozenCash.toFixed(8), + availableShares: totalAvailableShares.toFixed(8), + availableCash: (tradingAccount?.availableCash || Money.zero()).toFixed(8), currentPrice: price.toFixed(18), burnMultiplier: burnMultiplier.toFixed(18), effectiveShares: new Money(effectiveShares).toFixed(8), displayAssetValue: displayAssetValue.toFixed(8), assetGrowthPerSecond: assetGrowthPerSecond.toFixed(18), - totalBought: account.totalBought.toFixed(8), - totalSold: account.totalSold.toFixed(8), + totalBought: totalBought.toFixed(8), + totalSold: totalSold.toFixed(8), }; } + /** + * 调用 mining-service 获取挖矿账户余额 + */ + private async getMiningAccountBalance( + accountSequence: string, + ): Promise { + try { + const response = await fetch( + `${this.miningServiceUrl}/api/v2/mining/accounts/${accountSequence}`, + ); + + if (!response.ok) { + return null; + } + + const result = await response.json(); + if (!result.success || !result.data) { + return null; + } + + return result.data as MiningAccountDto; + } catch (error) { + this.logger.warn(`Failed to fetch mining account ${accountSequence}: ${error}`); + return null; + } + } + /** * 计算资产每秒增长量 * 资产每秒增长量 = 用户每天分配的积分股 ÷ 24 ÷ 60 ÷ 60 diff --git a/frontend/mining-app/lib/data/models/asset_display_model.dart b/frontend/mining-app/lib/data/models/asset_display_model.dart index 578f7281..996f5d10 100644 --- a/frontend/mining-app/lib/data/models/asset_display_model.dart +++ b/frontend/mining-app/lib/data/models/asset_display_model.dart @@ -2,6 +2,8 @@ import '../../domain/entities/asset_display.dart'; class AssetDisplayModel extends AssetDisplay { const AssetDisplayModel({ + required super.miningShareBalance, + required super.tradingShareBalance, required super.shareBalance, required super.cashBalance, required super.frozenShares, @@ -19,6 +21,8 @@ class AssetDisplayModel extends AssetDisplay { factory AssetDisplayModel.fromJson(Map json) { return AssetDisplayModel( + miningShareBalance: json['miningShareBalance']?.toString() ?? '0', + tradingShareBalance: json['tradingShareBalance']?.toString() ?? '0', shareBalance: json['shareBalance']?.toString() ?? '0', cashBalance: json['cashBalance']?.toString() ?? '0', frozenShares: json['frozenShares']?.toString() ?? '0', @@ -37,6 +41,8 @@ class AssetDisplayModel extends AssetDisplay { Map toJson() { return { + 'miningShareBalance': miningShareBalance, + 'tradingShareBalance': tradingShareBalance, 'shareBalance': shareBalance, 'cashBalance': cashBalance, 'frozenShares': frozenShares, diff --git a/frontend/mining-app/lib/domain/entities/asset_display.dart b/frontend/mining-app/lib/domain/entities/asset_display.dart index 20f0856d..503d5486 100644 --- a/frontend/mining-app/lib/domain/entities/asset_display.dart +++ b/frontend/mining-app/lib/domain/entities/asset_display.dart @@ -2,11 +2,18 @@ import 'package:equatable/equatable.dart'; /// 用户资产显示信息 /// 来自后端 trading-service 的 /asset/my 或 /asset/account/:accountSequence 接口 +/// 汇总了挖矿账户和交易账户的数据 class AssetDisplay extends Equatable { - /// 账户积分股余额 + /// 挖矿账户积分股余额 + final String miningShareBalance; + + /// 交易账户积分股余额 + final String tradingShareBalance; + + /// 总积分股余额(挖矿 + 交易) final String shareBalance; - /// 账户现金余额 + /// 账户现金余额(交易账户) final String cashBalance; /// 冻结积分股 @@ -43,6 +50,8 @@ class AssetDisplay extends Equatable { final String totalSold; const AssetDisplay({ + required this.miningShareBalance, + required this.tradingShareBalance, required this.shareBalance, required this.cashBalance, required this.frozenShares, @@ -74,6 +83,8 @@ class AssetDisplay extends Equatable { @override List get props => [ + miningShareBalance, + tradingShareBalance, shareBalance, cashBalance, frozenShares,