fix: 修复K线图NaN错误并添加mining-service划转端点
- 修复K线图当价格范围为0时导致的NaN Offset错误 - 在mining-service添加transfer-out和transfer-in端点 - 划转操作会在mining_transactions表中记录明细 Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
4e181354f4
commit
2154d5752f
|
|
@ -1,9 +1,20 @@
|
|||
import { Controller, Get, Param, Query, NotFoundException } from '@nestjs/common';
|
||||
import { Controller, Get, Post, Param, Query, Body, NotFoundException, BadRequestException } from '@nestjs/common';
|
||||
import { ApiTags, ApiOperation, ApiResponse, ApiParam, ApiQuery } from '@nestjs/swagger';
|
||||
import { IsString, IsOptional } from 'class-validator';
|
||||
import { GetMiningAccountQuery } from '../../application/queries/get-mining-account.query';
|
||||
import { GetMiningStatsQuery } from '../../application/queries/get-mining-stats.query';
|
||||
import { Public } from '../../shared/guards/jwt-auth.guard';
|
||||
import { PrismaService } from '../../infrastructure/persistence/prisma/prisma.service';
|
||||
import { Decimal } from '@prisma/client/runtime/library';
|
||||
|
||||
class TransferDto {
|
||||
@IsString()
|
||||
amount: string;
|
||||
|
||||
@IsString()
|
||||
@IsOptional()
|
||||
transferNo?: string;
|
||||
}
|
||||
|
||||
@ApiTags('Mining')
|
||||
@Controller('mining')
|
||||
|
|
@ -145,4 +156,129 @@ export class MiningController {
|
|||
pageSize ?? 50,
|
||||
);
|
||||
}
|
||||
|
||||
@Post('accounts/:accountSequence/transfer-out')
|
||||
@ApiOperation({ summary: '从挖矿账户划出积分股(到交易账户)' })
|
||||
@ApiParam({ name: 'accountSequence', description: '账户序号' })
|
||||
@ApiResponse({ status: 200, description: '划出成功' })
|
||||
@ApiResponse({ status: 400, description: '余额不足或参数错误' })
|
||||
async transferOut(
|
||||
@Param('accountSequence') accountSequence: string,
|
||||
@Body() dto: TransferDto,
|
||||
) {
|
||||
const amount = new Decimal(dto.amount);
|
||||
if (amount.lessThanOrEqualTo(0)) {
|
||||
throw new BadRequestException('Amount must be positive');
|
||||
}
|
||||
|
||||
// 查找账户
|
||||
const account = await this.prisma.miningAccount.findUnique({
|
||||
where: { accountSequence },
|
||||
});
|
||||
|
||||
if (!account) {
|
||||
throw new NotFoundException(`Account ${accountSequence} not found`);
|
||||
}
|
||||
|
||||
// 检查余额
|
||||
if (new Decimal(account.availableBalance).lessThan(amount)) {
|
||||
throw new BadRequestException('Insufficient balance');
|
||||
}
|
||||
|
||||
// 执行划转(使用事务)
|
||||
const txId = `TX${Date.now().toString(36)}${Math.random().toString(36).substring(2, 8)}`.toUpperCase();
|
||||
|
||||
await this.prisma.$transaction(async (tx) => {
|
||||
// 扣减余额
|
||||
const newBalance = new Decimal(account.availableBalance).minus(amount);
|
||||
await tx.miningAccount.update({
|
||||
where: { accountSequence },
|
||||
data: { availableBalance: newBalance },
|
||||
});
|
||||
|
||||
// 创建交易记录
|
||||
await tx.miningTransaction.create({
|
||||
data: {
|
||||
accountSequence,
|
||||
type: 'TRANSFER_OUT',
|
||||
amount: amount.negated(),
|
||||
balanceBefore: account.availableBalance,
|
||||
balanceAfter: newBalance,
|
||||
referenceId: dto.transferNo || txId,
|
||||
referenceType: 'TRADING_TRANSFER',
|
||||
memo: `划转到交易账户,金额: ${amount.toString()}`,
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
return {
|
||||
success: true,
|
||||
txId,
|
||||
message: 'Transfer out successful',
|
||||
};
|
||||
}
|
||||
|
||||
@Post('accounts/:accountSequence/transfer-in')
|
||||
@ApiOperation({ summary: '划入积分股到挖矿账户(从交易账户)' })
|
||||
@ApiParam({ name: 'accountSequence', description: '账户序号' })
|
||||
@ApiResponse({ status: 200, description: '划入成功' })
|
||||
@ApiResponse({ status: 400, description: '参数错误' })
|
||||
async transferIn(
|
||||
@Param('accountSequence') accountSequence: string,
|
||||
@Body() dto: TransferDto,
|
||||
) {
|
||||
const amount = new Decimal(dto.amount);
|
||||
if (amount.lessThanOrEqualTo(0)) {
|
||||
throw new BadRequestException('Amount must be positive');
|
||||
}
|
||||
|
||||
const txId = `TX${Date.now().toString(36)}${Math.random().toString(36).substring(2, 8)}`.toUpperCase();
|
||||
|
||||
await this.prisma.$transaction(async (tx) => {
|
||||
// 查找或创建账户
|
||||
let account = await tx.miningAccount.findUnique({
|
||||
where: { accountSequence },
|
||||
});
|
||||
|
||||
if (!account) {
|
||||
// 创建账户
|
||||
account = await tx.miningAccount.create({
|
||||
data: {
|
||||
accountSequence,
|
||||
totalMined: new Decimal(0),
|
||||
availableBalance: new Decimal(0),
|
||||
frozenBalance: new Decimal(0),
|
||||
totalContribution: new Decimal(0),
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
// 增加余额
|
||||
const newBalance = new Decimal(account.availableBalance).plus(amount);
|
||||
await tx.miningAccount.update({
|
||||
where: { accountSequence },
|
||||
data: { availableBalance: newBalance },
|
||||
});
|
||||
|
||||
// 创建交易记录
|
||||
await tx.miningTransaction.create({
|
||||
data: {
|
||||
accountSequence,
|
||||
type: 'TRANSFER_IN',
|
||||
amount,
|
||||
balanceBefore: account.availableBalance,
|
||||
balanceAfter: newBalance,
|
||||
referenceId: dto.transferNo || txId,
|
||||
referenceType: 'TRADING_TRANSFER',
|
||||
memo: `从交易账户划入,金额: ${amount.toString()}`,
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
return {
|
||||
success: true,
|
||||
txId,
|
||||
message: 'Transfer in successful',
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -703,11 +703,16 @@ class _KlineChartWidgetState extends State<KlineChartWidget> {
|
|||
}
|
||||
|
||||
final range = maxPrice - minPrice;
|
||||
final padding = range * 0.1;
|
||||
// 防止 range 为 0 导致 NaN
|
||||
final safeRange = range > 0 ? range : (maxPrice > 0 ? maxPrice * 0.1 : 1.0);
|
||||
final padding = safeRange * 0.1;
|
||||
minPrice -= padding;
|
||||
maxPrice += padding;
|
||||
|
||||
final y = 10 + ((maxPrice - price) / (maxPrice - minPrice)) * (chartHeight - 20);
|
||||
final adjustedRange = maxPrice - minPrice;
|
||||
if (adjustedRange <= 0) return chartHeight / 2;
|
||||
|
||||
final y = 10 + ((maxPrice - price) / adjustedRange) * (chartHeight - 20);
|
||||
return y.clamp(0.0, chartHeight - 20);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -100,13 +100,16 @@ class KlinePainter extends CustomPainter {
|
|||
|
||||
// 添加上下留白
|
||||
final priceRange = maxPrice - minPrice;
|
||||
final padding = priceRange * 0.1;
|
||||
// 避免 priceRange 为 0 导致 NaN
|
||||
final safePriceRange = priceRange > 0 ? priceRange : (maxPrice > 0 ? maxPrice * 0.1 : 1.0);
|
||||
final padding = safePriceRange * 0.1;
|
||||
minPrice -= padding;
|
||||
maxPrice += padding;
|
||||
final adjustedRange = maxPrice - minPrice;
|
||||
|
||||
// Y坐标转换函数
|
||||
double priceToY(double price) {
|
||||
if (adjustedRange <= 0) return topPadding + chartHeight / 2;
|
||||
return topPadding + ((maxPrice - price) / adjustedRange) * chartHeight;
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue