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 <noreply@anthropic.com>
This commit is contained in:
parent
ed715111ae
commit
1e2d8d1df7
|
|
@ -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<string>(
|
||||
'MINING_SERVICE_URL',
|
||||
'http://localhost:3021',
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取用户资产显示
|
||||
* 资产显示 = (账户积分股 + 账户积分股 × 倍数) × 积分股价
|
||||
* 获取用户资产显示(汇总挖矿账户 + 交易账户)
|
||||
* 资产显示 = (总积分股 + 总积分股 × 倍数) × 积分股价
|
||||
*
|
||||
* @param accountSequence 账户序号
|
||||
* @param dailyAllocation 用户每天分配的积分股(可选,用于计算每秒增长)
|
||||
|
|
@ -61,8 +84,14 @@ export class AssetService {
|
|||
accountSequence: string,
|
||||
dailyAllocation?: string,
|
||||
): Promise<AssetDisplay | null> {
|
||||
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<MiningAccountDto | null> {
|
||||
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
|
||||
|
|
|
|||
|
|
@ -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<String, dynamic> 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<String, dynamic> toJson() {
|
||||
return {
|
||||
'miningShareBalance': miningShareBalance,
|
||||
'tradingShareBalance': tradingShareBalance,
|
||||
'shareBalance': shareBalance,
|
||||
'cashBalance': cashBalance,
|
||||
'frozenShares': frozenShares,
|
||||
|
|
|
|||
|
|
@ -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<Object?> get props => [
|
||||
miningShareBalance,
|
||||
tradingShareBalance,
|
||||
shareBalance,
|
||||
cashBalance,
|
||||
frozenShares,
|
||||
|
|
|
|||
Loading…
Reference in New Issue