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 { Injectable, Logger } from '@nestjs/common';
|
||||||
|
import { ConfigService } from '@nestjs/config';
|
||||||
import { TradingCalculatorService } from '../../domain/services/trading-calculator.service';
|
import { TradingCalculatorService } from '../../domain/services/trading-calculator.service';
|
||||||
import { TradingAccountRepository } from '../../infrastructure/persistence/repositories/trading-account.repository';
|
import { TradingAccountRepository } from '../../infrastructure/persistence/repositories/trading-account.repository';
|
||||||
import { BlackHoleRepository } from '../../infrastructure/persistence/repositories/black-hole.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 { Money } from '../../domain/value-objects/money.vo';
|
||||||
import Decimal from 'decimal.js';
|
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 {
|
export interface AssetDisplay {
|
||||||
// 账户积分股余额
|
// 挖矿账户积分股余额
|
||||||
|
miningShareBalance: string;
|
||||||
|
// 交易账户积分股余额
|
||||||
|
tradingShareBalance: string;
|
||||||
|
// 总积分股余额(挖矿 + 交易)
|
||||||
shareBalance: string;
|
shareBalance: string;
|
||||||
// 账户现金余额
|
// 账户现金余额(交易账户)
|
||||||
cashBalance: string;
|
cashBalance: string;
|
||||||
// 冻结积分股
|
// 冻结积分股(挖矿冻结 + 交易冻结)
|
||||||
frozenShares: string;
|
frozenShares: string;
|
||||||
// 冻结现金
|
// 冻结现金
|
||||||
frozenCash: string;
|
frozenCash: string;
|
||||||
// 可用积分股
|
// 可用积分股(挖矿可用 + 交易可用)
|
||||||
availableShares: string;
|
availableShares: string;
|
||||||
// 可用现金
|
// 可用现金
|
||||||
availableCash: string;
|
availableCash: string;
|
||||||
|
|
@ -41,6 +57,7 @@ export interface AssetDisplay {
|
||||||
export class AssetService {
|
export class AssetService {
|
||||||
private readonly logger = new Logger(AssetService.name);
|
private readonly logger = new Logger(AssetService.name);
|
||||||
private readonly calculator = new TradingCalculatorService();
|
private readonly calculator = new TradingCalculatorService();
|
||||||
|
private readonly miningServiceUrl: string;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private readonly tradingAccountRepository: TradingAccountRepository,
|
private readonly tradingAccountRepository: TradingAccountRepository,
|
||||||
|
|
@ -48,11 +65,17 @@ export class AssetService {
|
||||||
private readonly circulationPoolRepository: CirculationPoolRepository,
|
private readonly circulationPoolRepository: CirculationPoolRepository,
|
||||||
private readonly sharePoolRepository: SharePoolRepository,
|
private readonly sharePoolRepository: SharePoolRepository,
|
||||||
private readonly priceService: PriceService,
|
private readonly priceService: PriceService,
|
||||||
) {}
|
private readonly configService: ConfigService,
|
||||||
|
) {
|
||||||
|
this.miningServiceUrl = this.configService.get<string>(
|
||||||
|
'MINING_SERVICE_URL',
|
||||||
|
'http://localhost:3021',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取用户资产显示
|
* 获取用户资产显示(汇总挖矿账户 + 交易账户)
|
||||||
* 资产显示 = (账户积分股 + 账户积分股 × 倍数) × 积分股价
|
* 资产显示 = (总积分股 + 总积分股 × 倍数) × 积分股价
|
||||||
*
|
*
|
||||||
* @param accountSequence 账户序号
|
* @param accountSequence 账户序号
|
||||||
* @param dailyAllocation 用户每天分配的积分股(可选,用于计算每秒增长)
|
* @param dailyAllocation 用户每天分配的积分股(可选,用于计算每秒增长)
|
||||||
|
|
@ -61,8 +84,14 @@ export class AssetService {
|
||||||
accountSequence: string,
|
accountSequence: string,
|
||||||
dailyAllocation?: string,
|
dailyAllocation?: string,
|
||||||
): Promise<AssetDisplay | null> {
|
): 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;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -71,13 +100,32 @@ export class AssetService {
|
||||||
const price = new Money(priceInfo.price);
|
const price = new Money(priceInfo.price);
|
||||||
const burnMultiplier = new Decimal(priceInfo.burnMultiplier);
|
const burnMultiplier = new Decimal(priceInfo.burnMultiplier);
|
||||||
|
|
||||||
// 计算有效积分股 = 余额 × (1 + 倍数)
|
// 挖矿账户余额
|
||||||
const multiplierFactor = new Decimal(1).plus(burnMultiplier);
|
const miningShareBalance = new Money(miningAccount?.availableBalance || '0');
|
||||||
const effectiveShares = account.shareBalance.value.times(multiplierFactor);
|
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(
|
const displayAssetValue = this.calculator.calculateDisplayAssetValue(
|
||||||
account.shareBalance,
|
totalShareBalance,
|
||||||
burnMultiplier,
|
burnMultiplier,
|
||||||
price,
|
price,
|
||||||
);
|
);
|
||||||
|
|
@ -90,22 +138,51 @@ export class AssetService {
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
shareBalance: account.shareBalance.toFixed(8),
|
miningShareBalance: miningShareBalance.toFixed(8),
|
||||||
cashBalance: account.cashBalance.toFixed(8),
|
tradingShareBalance: tradingShareBalance.toFixed(8),
|
||||||
frozenShares: account.frozenShares.toFixed(8),
|
shareBalance: totalShareBalance.toFixed(8),
|
||||||
frozenCash: account.frozenCash.toFixed(8),
|
cashBalance: cashBalance.toFixed(8),
|
||||||
availableShares: account.availableShares.toFixed(8),
|
frozenShares: totalFrozenShares.toFixed(8),
|
||||||
availableCash: account.availableCash.toFixed(8),
|
frozenCash: frozenCash.toFixed(8),
|
||||||
|
availableShares: totalAvailableShares.toFixed(8),
|
||||||
|
availableCash: (tradingAccount?.availableCash || Money.zero()).toFixed(8),
|
||||||
currentPrice: price.toFixed(18),
|
currentPrice: price.toFixed(18),
|
||||||
burnMultiplier: burnMultiplier.toFixed(18),
|
burnMultiplier: burnMultiplier.toFixed(18),
|
||||||
effectiveShares: new Money(effectiveShares).toFixed(8),
|
effectiveShares: new Money(effectiveShares).toFixed(8),
|
||||||
displayAssetValue: displayAssetValue.toFixed(8),
|
displayAssetValue: displayAssetValue.toFixed(8),
|
||||||
assetGrowthPerSecond: assetGrowthPerSecond.toFixed(18),
|
assetGrowthPerSecond: assetGrowthPerSecond.toFixed(18),
|
||||||
totalBought: account.totalBought.toFixed(8),
|
totalBought: totalBought.toFixed(8),
|
||||||
totalSold: account.totalSold.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
|
* 资产每秒增长量 = 用户每天分配的积分股 ÷ 24 ÷ 60 ÷ 60
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,8 @@ import '../../domain/entities/asset_display.dart';
|
||||||
|
|
||||||
class AssetDisplayModel extends AssetDisplay {
|
class AssetDisplayModel extends AssetDisplay {
|
||||||
const AssetDisplayModel({
|
const AssetDisplayModel({
|
||||||
|
required super.miningShareBalance,
|
||||||
|
required super.tradingShareBalance,
|
||||||
required super.shareBalance,
|
required super.shareBalance,
|
||||||
required super.cashBalance,
|
required super.cashBalance,
|
||||||
required super.frozenShares,
|
required super.frozenShares,
|
||||||
|
|
@ -19,6 +21,8 @@ class AssetDisplayModel extends AssetDisplay {
|
||||||
|
|
||||||
factory AssetDisplayModel.fromJson(Map<String, dynamic> json) {
|
factory AssetDisplayModel.fromJson(Map<String, dynamic> json) {
|
||||||
return AssetDisplayModel(
|
return AssetDisplayModel(
|
||||||
|
miningShareBalance: json['miningShareBalance']?.toString() ?? '0',
|
||||||
|
tradingShareBalance: json['tradingShareBalance']?.toString() ?? '0',
|
||||||
shareBalance: json['shareBalance']?.toString() ?? '0',
|
shareBalance: json['shareBalance']?.toString() ?? '0',
|
||||||
cashBalance: json['cashBalance']?.toString() ?? '0',
|
cashBalance: json['cashBalance']?.toString() ?? '0',
|
||||||
frozenShares: json['frozenShares']?.toString() ?? '0',
|
frozenShares: json['frozenShares']?.toString() ?? '0',
|
||||||
|
|
@ -37,6 +41,8 @@ class AssetDisplayModel extends AssetDisplay {
|
||||||
|
|
||||||
Map<String, dynamic> toJson() {
|
Map<String, dynamic> toJson() {
|
||||||
return {
|
return {
|
||||||
|
'miningShareBalance': miningShareBalance,
|
||||||
|
'tradingShareBalance': tradingShareBalance,
|
||||||
'shareBalance': shareBalance,
|
'shareBalance': shareBalance,
|
||||||
'cashBalance': cashBalance,
|
'cashBalance': cashBalance,
|
||||||
'frozenShares': frozenShares,
|
'frozenShares': frozenShares,
|
||||||
|
|
|
||||||
|
|
@ -2,11 +2,18 @@ import 'package:equatable/equatable.dart';
|
||||||
|
|
||||||
/// 用户资产显示信息
|
/// 用户资产显示信息
|
||||||
/// 来自后端 trading-service 的 /asset/my 或 /asset/account/:accountSequence 接口
|
/// 来自后端 trading-service 的 /asset/my 或 /asset/account/:accountSequence 接口
|
||||||
|
/// 汇总了挖矿账户和交易账户的数据
|
||||||
class AssetDisplay extends Equatable {
|
class AssetDisplay extends Equatable {
|
||||||
/// 账户积分股余额
|
/// 挖矿账户积分股余额
|
||||||
|
final String miningShareBalance;
|
||||||
|
|
||||||
|
/// 交易账户积分股余额
|
||||||
|
final String tradingShareBalance;
|
||||||
|
|
||||||
|
/// 总积分股余额(挖矿 + 交易)
|
||||||
final String shareBalance;
|
final String shareBalance;
|
||||||
|
|
||||||
/// 账户现金余额
|
/// 账户现金余额(交易账户)
|
||||||
final String cashBalance;
|
final String cashBalance;
|
||||||
|
|
||||||
/// 冻结积分股
|
/// 冻结积分股
|
||||||
|
|
@ -43,6 +50,8 @@ class AssetDisplay extends Equatable {
|
||||||
final String totalSold;
|
final String totalSold;
|
||||||
|
|
||||||
const AssetDisplay({
|
const AssetDisplay({
|
||||||
|
required this.miningShareBalance,
|
||||||
|
required this.tradingShareBalance,
|
||||||
required this.shareBalance,
|
required this.shareBalance,
|
||||||
required this.cashBalance,
|
required this.cashBalance,
|
||||||
required this.frozenShares,
|
required this.frozenShares,
|
||||||
|
|
@ -74,6 +83,8 @@ class AssetDisplay extends Equatable {
|
||||||
|
|
||||||
@override
|
@override
|
||||||
List<Object?> get props => [
|
List<Object?> get props => [
|
||||||
|
miningShareBalance,
|
||||||
|
tradingShareBalance,
|
||||||
shareBalance,
|
shareBalance,
|
||||||
cashBalance,
|
cashBalance,
|
||||||
frozenShares,
|
frozenShares,
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue