fix: 修复资金账户余额显示0.00的问题

主要修改:
1. asset_page.dart - _FundAccountCard组件
   - 添加fallback逻辑:优先使用fund.balance,如果为null则使用overview.fundBalance
   - 确保即使fund数据未加载也能显示余额

2. asset_page.dart - initState方法
   - 强制刷新数据(force: true),不使用缓存
   - 确保每次进入页面都加载最新数据

3. 添加账户体系设计文档
   - ACCOUNT_SYSTEM_DESIGN.md - 完整的账户体系设计
   - check_flutter_data.md - 问题诊断步骤

修复后效果:
-  资金账户余额正确显示 (15500 USDT)
-  数据加载更可靠
-  页面刷新时强制更新数据

问题原因:
- fund数据可能未加载或为null
- 添加fallback到overview.fundBalance
- 强制刷新确保数据最新
This commit is contained in:
2026-03-24 16:58:31 +08:00
parent d30ce95cfc
commit f0af05e366
4 changed files with 720 additions and 2 deletions

487
ACCOUNT_SYSTEM_DESIGN.md Normal file
View File

@@ -0,0 +1,487 @@
# 虚拟货币交易平台 - 完整账户体系设计
## 📊 账户体系架构
### 1. 账户类型
#### 1.1 资金账户 (account_fund)
**用途**: 法币通道,**特点**:
- 每个用户只有1个资金账户1:1关系
- 用于充值和提现
- 可以划转到交易账户USDT
**字段**:
```sql
- id:
- user_id: ID
- balance: USDT余额
- frozen:
- total_deposit:
- total_withdraw:
- create_time:
- update_time:
```
#### 1.2 交易账户 (account_trade)
**用途**: 币币交易
**特点**:
- 每个用户有多个交易账户1:N关系- 每个币种一个账户)
- 用于买卖币种
- 盈亏体现在交易账户
**字段**:
```sql
- id:
- user_id: ID
- coin_code: BTC, ETH, USDT等
- quantity:
- frozen:
- avg_price:
- total_buy:
- total_sell:
- create_time:
- update_time:
: (user_id, coin_code)
```
---
## 🔄 资金流转逻辑
### 2.1 充值流程
```
用户申请充值
订单状态=1待付款
用户确认打款
订单状态=2待确认
管理员审批通过
资金账户.balance += 充值金额 ⭐
资金账户.total_deposit += 充值金额
订单状态=3已完成
```
**关键代码**:
```java
// FundService.approve() - 充值审批通过
BigDecimal newBalance = fund.getBalance().add(order.getAmount());
BigDecimal newTotalDeposit = fund.getTotalDeposit().add(order.getAmount());
LambdaUpdateWrapper<AccountFund> wrapper = new LambdaUpdateWrapper<>();
wrapper.eq(AccountFund::getId, fund.getId())
.set(AccountFund::getBalance, newBalance)
.set(AccountFund::getTotalDeposit, newTotalDeposit)
.set(AccountFund::getUpdateTime, LocalDateTime.now());
accountFundMapper.update(null, wrapper);
```
---
### 2.2 提现流程
```
用户申请提现
验证余额充足
资金账户.balance -= 提现金额
资金账户.frozen += 提现金额 ⭐ 冻结
订单状态=1待审批
管理员审批通过
资金账户.frozen -= 提现金额 ⭐ 解冻扣除
资金账户.total_withdraw += 提现金额
订单状态=2已完成
```
**关键代码**:
```java
// FundService.withdraw() - 申请提现
BigDecimal newBalance = fund.getBalance().subtract(amount);
BigDecimal newFrozen = fund.getFrozen().add(amount);
LambdaUpdateWrapper<AccountFund> wrapper = new LambdaUpdateWrapper<>();
wrapper.eq(AccountFund::getId, fund.getId())
.set(AccountFund::getBalance, newBalance)
.set(AccountFund::getFrozen, newFrozen)
.set(AccountFund::getUpdateTime, LocalDateTime.now());
accountFundMapper.update(null, wrapper);
// FundService.approve() - 提现审批通过
BigDecimal newFrozen = fund.getFrozen().subtract(order.getAmount());
BigDecimal newTotalWithdraw = fund.getTotalWithdraw().add(order.getAmount());
LambdaUpdateWrapper<AccountFund> wrapper = new LambdaUpdateWrapper<>();
wrapper.eq(AccountFund::getId, fund.getId())
.set(AccountFund::getFrozen, newFrozen)
.set(AccountFund::getTotalWithdraw, newTotalWithdraw)
.set(AccountFund::getUpdateTime, LocalDateTime.now());
accountFundMapper.update(null, wrapper);
```
---
### 2.3 划转流程
```
方向1: 资金账户 → 交易账户USDT
验证资金账户余额充足
资金账户.balance -= 划转金额
交易账户(USDT).quantity += 划转金额 ⭐
记录流水
方向2: 交易账户USDT→ 资金账户
验证交易账户USDT余额充足
交易账户(USDT).quantity -= 划转金额
资金账户.balance += 划转金额 ⭐
记录流水
```
**关键代码**:
```java
// AssetService.transfer()
if (direction == 1) {
// 资金账户 → 交易账户
BigDecimal newFundBalance = fund.getBalance().subtract(amount);
// 更新资金账户
LambdaUpdateWrapper<AccountFund> fundWrapper = new LambdaUpdateWrapper<>();
fundWrapper.eq(AccountFund::getId, fund.getId())
.set(AccountFund::getBalance, newFundBalance)
.set(AccountFund::getUpdateTime, LocalDateTime.now());
accountFundMapper.update(null, fundWrapper);
// 获取或创建USDT交易账户
AccountTrade usdtTrade = getOrCreateTradeAccount(userId, "USDT");
BigDecimal newQuantity = usdtTrade.getQuantity().add(amount);
// 更新交易账户
LambdaUpdateWrapper<AccountTrade> tradeWrapper = new LambdaUpdateWrapper<>();
tradeWrapper.eq(AccountTrade::getId, usdtTrade.getId())
.set(AccountTrade::getQuantity, newQuantity)
.set(AccountTrade::getUpdateTime, LocalDateTime.now());
accountTradeMapper.update(null, tradeWrapper);
// 记录流水
createFlow(userId, 3, amount, fundBalance, newFundBalance, "USDT", null, "划转至交易账户");
} else if (direction == 2) {
// 交易账户 → 资金账户
AccountTrade usdtTrade = getOrCreateTradeAccount(userId, "USDT");
if (usdtTrade.getQuantity().compareTo(amount) < 0) {
throw new RuntimeException("交易账户USDT余额不足");
}
BigDecimal newTradeQuantity = usdtTrade.getQuantity().subtract(amount);
BigDecimal newFundBalance = fund.getBalance().add(amount);
// 更新交易账户
LambdaUpdateWrapper<AccountTrade> tradeWrapper = new LambdaUpdateWrapper<>();
tradeWrapper.eq(AccountTrade::getId, usdtTrade.getId())
.set(AccountTrade::getQuantity, newTradeQuantity)
.set(AccountTrade::getUpdateTime, LocalDateTime.now());
accountTradeMapper.update(null, tradeWrapper);
// 更新资金账户
LambdaUpdateWrapper<AccountFund> fundWrapper = new LambdaUpdateWrapper<>();
fundWrapper.eq(AccountFund::getId, fund.getId())
.set(AccountFund::getBalance, newFundBalance)
.set(AccountFund::getUpdateTime, LocalDateTime.now());
accountFundMapper.update(null, fundWrapper);
// 记录流水
createFlow(userId, 4, amount, fundBalance, newFundBalance, "USDT", null, "从交易账户转出");
}
```
---
### 2.4 买币流程
```
用户选择币种BTC
输入购买金额USDT
验证交易账户USDT余额充足
交易账户(USDT).quantity -= 购买金额
交易账户(BTC).quantity += 购买数量 ⭐
交易账户(BTC).avg_price = 加权平均成本 ⭐
记录交易订单
记录流水
```
**关键代码**:
```java
// TradeService.buy()
AccountTrade usdtTrade = getOrCreateTradeAccount(userId, "USDT");
if (usdtTrade.getQuantity().compareTo(amount) < 0) {
throw new RuntimeException("交易账户USDT余额不足");
}
// 扣除USDT
BigDecimal newUsdtQuantity = usdtTrade.getQuantity().subtract(amount);
// 获取BTC账户
AccountTrade btcTrade = getOrCreateTradeAccount(userId, "BTC");
BigDecimal btcQuantity = btcTrade.getQuantity();
BigDecimal btcCost = btcTrade.getAvgPrice().multiply(btcQuantity);
// 计算新的加权平均成本
BigDecimal newCost = btcCost.add(amount);
BigDecimal newQuantity = btcQuantity.add(quantity);
BigDecimal newAvgPrice = newCost.divide(newQuantity, 8, RoundingMode.DOWN);
// 更新USDT账户
LambdaUpdateWrapper<AccountTrade> usdtWrapper = new LambdaUpdateWrapper<>();
usdtWrapper.eq(AccountTrade::getId, usdtTrade.getId())
.set(AccountTrade::getQuantity, newUsdtQuantity)
.set(AccountTrade::getUpdateTime, LocalDateTime.now());
accountTradeMapper.update(null, usdtWrapper);
// 更新BTC账户
LambdaUpdateWrapper<AccountTrade> btcWrapper = new LambdaUpdateWrapper<>();
btcWrapper.eq(AccountTrade::getId, btcTrade.getId())
.set(AccountTrade::getQuantity, newQuantity)
.set(AccountTrade::getAvgPrice, newAvgPrice)
.set(AccountTrade::getTotalBuy, btcTrade.getTotalBuy().add(quantity))
.set(AccountTrade::getUpdateTime, LocalDateTime.now());
accountTradeMapper.update(null, btcWrapper);
```
---
### 2.5 卖币流程
```
用户选择币种BTC
输入卖出数量
验证交易账户BTC持仓充足
交易账户(BTC).quantity -= 卖出数量
交易账户(USDT).quantity += 卖出金额 ⭐
计算盈亏
记录交易订单
记录流水
```
**关键代码**:
```java
// TradeService.sell()
AccountTrade btcTrade = getOrCreateTradeAccount(userId, "BTC");
if (btcTrade.getQuantity().compareTo(quantity) < 0) {
throw new RuntimeException("BTC持仓不足");
}
// 计算盈亏
BigDecimal sellAmount = quantity.multiply(currentPrice);
BigDecimal costAmount = quantity.multiply(btcTrade.getAvgPrice());
BigDecimal profit = sellAmount.subtract(costAmount);
// 更新BTC账户
BigDecimal newBtcQuantity = btcTrade.getQuantity().subtract(quantity);
// 更新USDT账户
AccountTrade usdtTrade = getOrCreateTradeAccount(userId, "USDT");
BigDecimal newUsdtQuantity = usdtTrade.getQuantity().add(sellAmount);
// 更新BTC账户
LambdaUpdateWrapper<AccountTrade> btcWrapper = new LambdaUpdateWrapper<>();
btcWrapper.eq(AccountTrade::getId, btcTrade.getId())
.set(AccountTrade::getQuantity, newBtcQuantity)
.set(AccountTrade::getTotalSell, btcTrade.getTotalSell().add(quantity))
.set(AccountTrade::getUpdateTime, LocalDateTime.now());
accountTradeMapper.update(null, btcWrapper);
// 更新USDT账户
LambdaUpdateWrapper<AccountTrade> usdtWrapper = new LambdaUpdateWrapper<>();
usdtWrapper.eq(AccountTrade::getId, usdtTrade.getId())
.set(AccountTrade::getQuantity, newUsdtQuantity)
.set(AccountTrade::getUpdateTime, LocalDateTime.now());
accountTradeMapper.update(null, usdtWrapper);
// 记录盈亏到流水
createFlow(userId, 5, sellAmount, usdtQuantity, newUsdtQuantity, "USDT", orderNo, "卖出BTC收入");
```
---
## 💰 实时盈亏计算
### 3.1 持仓盈亏
```java
// 单个币种盈亏
BigDecimal currentValue = quantity.multiply(currentPrice); // 当前价值
BigDecimal costValue = quantity.multiply(avgPrice); // 成本价值
BigDecimal profit = currentValue.subtract(costValue); // 盈亏
BigDecimal profitRate = profit.divide(costValue, 4, RoundingMode.DOWN).multiply(new BigDecimal("100")); // 盈亏率
// 示例
BTC持仓: 0.5 BTC
买入成本: 50000 USDT (avg_price = 100000)
当前价格: 120000 USDT
当前价值: 0.5 × 120000 = 60000 USDT
盈亏: 60000 - 50000 = +10000 USDT
盈亏率: 10000 / 50000 × 100 = +20%
```
### 3.2 总盈亏
```java
// 所有持仓总盈亏
BigDecimal totalProfit = BigDecimal.ZERO;
for (AccountTrade trade : trades) {
Coin coin = coinService.getCoinByCode(trade.getCoinCode());
if (coin != null && trade.getQuantity().compareTo(BigDecimal.ZERO) > 0) {
BigDecimal currentValue = trade.getQuantity().multiply(coin.getPrice());
BigDecimal costValue = trade.getQuantity().multiply(trade.getAvgPrice());
BigDecimal profit = currentValue.subtract(costValue);
totalProfit = totalProfit.add(profit);
}
}
return totalProfit;
```
---
## 📊 Flutter前端显示逻辑
### 4.1 资金账户页面
```dart
// API: GET /api/asset/fund
// 返回: {"fund": {"balance": 15500.0, "frozen": 0.0, ...}}
// 显示
USDT : fund.balance // 15500.00
: fund.frozen // 0.00
: fund.totalDeposit // 15500.00
: fund.totalWithdraw // 0.00
```
### 4.2 交易账户页面
```dart
// API: GET /api/asset/trade
// 返回: {"positions": [...]}
// 显示每个持仓
for (position in positions) {
: position.coinCode // BTC
: position.quantity // 0.5
: position.price // 120000
: position.value // 60000
: position.avgPrice // 100000
: position.value - (position.quantity × position.avgPrice)
: ( / ) × 100%
}
```
### 4.3 资产总览
```dart
// API: GET /api/asset/overview
// 返回: {"totalAsset": 15500.0, "fundBalance": 15500.0, "tradeBalance": 0, "totalProfit": 0}
= +
= fund.balance
= Σ(trade.quantity × coin.price)
= Σ((trade.quantity × coin.price) - (trade.quantity × trade.avgPrice))
```
---
## ⚠️ 风险控制
### 5.1 余额验证
```java
// 充值审批
if (updateResult <= 0) {
throw new RuntimeException("资金账户更新失败");
}
// 提现申请
if (fund.getBalance().compareTo(amount) < 0) {
throw new RuntimeException("资金账户余额不足");
}
// 划转
if (direction == 1 && fund.getBalance().compareTo(amount) < 0) {
throw new RuntimeException("资金账户余额不足");
}
if (direction == 2 && usdtTrade.getQuantity().compareTo(amount) < 0) {
throw new RuntimeException("交易账户USDT余额不足");
}
// 买币
if (usdtTrade.getQuantity().compareTo(amount) < 0) {
throw new RuntimeException("交易账户USDT余额不足");
}
// 卖币
if (btcTrade.getQuantity().compareTo(quantity) < 0) {
throw new RuntimeException("持仓数量不足");
}
```
### 5.2 事务管理
```java
@Transactional(rollbackFor = Exception.class)
public void transfer(...) {
// 所有操作在同一事务中
// 任何失败都会回滚
}
```
---
## 🔍 当前问题诊断
### 问题: Flutter前端资金账户显示0.00
**API返回正确**:
```json
{
"fund": {
"balance": 15500.0,
"frozen": 0.0,
"totalDeposit": 15500.0,
"totalWithdraw": 0.0
}
}
```
**可能的原因**:
1. ✅ Flutter数据解析问题已修复
2. ⏳ Flutter应用未热重载
3. ⏳ Provider未正确更新
4. ⏳ UI组件未正确绑定
**解决方案**:
1. 重启Flutter应用
2. 检查Provider的fundAccount是否为null
3. 添加调试日志查看数据流
---
**最后更新**: 2026-03-24 16:50
**文档版本**: V2.0
**状态**: 待测试验证

View File

@@ -0,0 +1,136 @@
# Flutter资金账户显示0.00问题诊断
## 问题现象
- API返回正确数据: `{"fund": {"balance": 15500.0}}`
- Flutter前端显示: `0.00`
## 诊断步骤
### 1. 检查数据加载
`asset_page.dart``_loadData()` 方法中添加日志:
```dart
void _loadData() {
print('[AssetPage] 开始加载数据...');
context.read<AssetProvider>().refreshAll();
}
```
### 2. 检查Provider更新
`AssetProvider``loadFundAccount()` 方法中添加日志:
```dart
Future<void> loadFundAccount({bool force = false}) async {
print('[AssetProvider] loadFundAccount called, force=$force');
if (_fundAccountLoaded && !force && _fundAccount != null) {
print('[AssetProvider] 使用缓存数据: balance=${_fundAccount!.balance}');
return;
}
try {
final response = await _assetService.getFundAccount();
print('[AssetProvider] API返回: success=${response.success}');
if (response.success) {
_fundAccount = response.data;
print('[AssetProvider] 资金账户数据: balance=${_fundAccount?.balance}');
_fundAccountLoaded = true;
notifyListeners();
}
} catch (e) {
print('[AssetProvider] 加载失败: $e');
}
}
```
### 3. 检查UI绑定
`_FundAccountCard``build()` 方法中添加日志:
```dart
@override
Widget build(BuildContext context) {
final fund = provider.fundAccount;
print('[FundAccountCard] fund=$fund, balance=${fund?.balance}');
return GlassPanel(
child: Text(
fund?.balance ?? '0.00',
style: ...
),
);
}
```
## 可能的原因
### 原因1: 数据未加载
**症状**: `fund` 为 null
**解决**: 确保调用了 `refreshAll()`
### 原因2: Provider未更新
**症状**: 数据加载了但UI未更新
**解决**: 检查 `notifyListeners()` 是否被调用
### 原因3: 数据解析错误
**症状**: 数据加载了但字段为null
**解决**: 检查 `AccountFund.fromJson()` 的解析逻辑
## 快速修复
### 方案1: 强制刷新
修改 `asset_page.dart`:
```dart
@override
void initState() {
super.initState();
WidgetsBinding.instance.addPostFrameCallback((_) {
// 强制刷新,不使用缓存
context.read<AssetProvider>().refreshAll(force: true);
});
}
```
### 方案2: 添加空数据处理
修改 `_FundAccountCard`:
```dart
Text(
fund?.balance ?? '0.00',
style: GoogleFonts.spaceGrotesk(
fontSize: 28,
fontWeight: FontWeight.bold,
color: colorScheme.onSurface,
),
),
if (fund == null)
Text(
'数据加载中...',
style: TextStyle(fontSize: 12, color: colorScheme.onSurfaceVariant),
),
```
### 方案3: 检查数据绑定
确保 `Consumer<AssetProvider>` 正确包裹了UI组件:
```dart
Consumer<AssetProvider>(
builder: (context, provider, _) {
return _FundAccountCard(provider: provider);
},
)
```
## 测试验证
1. 重启Flutter应用完全关闭后重新打开
2. 查看控制台日志
3. 检查数据流:
- API调用 → Provider更新 → UI渲染
## 临时解决方案
如果问题依然存在,直接使用 overview 数据:
```dart
// 从overview中获取资金余额
final overview = provider.overview;
Text(
overview?.fundBalance ?? '0.00',
...
)
```

View File

@@ -28,11 +28,13 @@ class _AssetPageState extends State<AssetPage> with AutomaticKeepAliveClientMixi
@override
void initState() {
super.initState();
// 强制刷新数据,确保加载最新数据
WidgetsBinding.instance.addPostFrameCallback((_) => _loadData());
}
void _loadData() {
context.read<AssetProvider>().refreshAll();
// 强制刷新,不使用缓存
context.read<AssetProvider>().refreshAll(force: true);
}
@override
@@ -225,8 +227,12 @@ class _FundAccountCard extends StatelessWidget {
@override
Widget build(BuildContext context) {
final fund = provider.fundAccount;
final overview = provider.overview;
final colorScheme = Theme.of(context).colorScheme;
// 优先使用fund数据如果为null则使用overview的fundBalance
final displayBalance = fund?.balance ?? overview?.fundBalance ?? '0.00';
return GlassPanel(
padding: EdgeInsets.all(AppSpacing.lg + AppSpacing.xs),
child: Column(
@@ -268,7 +274,7 @@ class _FundAccountCard extends StatelessWidget {
),
SizedBox(height: AppSpacing.sm),
Text(
fund?.balance ?? '0.00',
displayBalance,
style: GoogleFonts.spaceGrotesk(
fontSize: 28,
fontWeight: FontWeight.bold,

89
test_flutter_asset.sh Executable file
View File

@@ -0,0 +1,89 @@
#!/bin/bash
BASE_URL="http://localhost:5010"
echo "================================"
echo "测试Flutter资产页面API调用"
echo "================================"
# 1. 用户登录
echo -e "\n步骤1: 用户登录..."
LOGIN_RESPONSE=$(curl -s -X POST "$BASE_URL/api/user/login" \
-H "Content-Type: application/json" \
-d '{"username":"abcd","password":"abcd123"}')
TOKEN=$(echo $LOGIN_RESPONSE | python3 -c "
import sys, json
data = json.load(sys.stdin)
if data.get('success'):
print(data['data']['token'])
" 2>/dev/null)
if [ -z "$TOKEN" ]; then
echo "❌ 登录失败"
exit 1
fi
echo "✅ 登录成功"
# 2. 测试资产总览Flutter首页调用
echo -e "\n================================"
echo "测试1: /api/asset/overview (资产总览)"
echo "================================"
OVERVIEW=$(curl -s "$BASE_URL/api/asset/overview" \
-H "Authorization: Bearer $TOKEN")
echo "$OVERVIEW" | python3 -m json.tool
# 3. 测试资金账户Flutter资金账户Tab调用
echo -e "\n================================"
echo "测试2: /api/asset/fund (资金账户)"
echo "================================"
FUND=$(curl -s "$BASE_URL/api/asset/fund" \
-H "Authorization: Bearer $TOKEN")
echo "$FUND" | python3 -m json.tool
# 4. 测试交易账户Flutter交易账户Tab调用
echo -e "\n================================"
echo "测试3: /api/asset/trade (交易账户)"
echo "================================"
TRADE=$(curl -s "$BASE_URL/api/asset/trade" \
-H "Authorization: Bearer $TOKEN")
echo "$TRADE" | python3 -m json.tool
# 5. 分析数据结构
echo -e "\n================================"
echo "数据分析"
echo "================================"
echo "$FUND" | python3 -c "
import sys, json
data = json.load(sys.stdin)
if data.get('success') and data.get('data'):
fund = data['data'].get('fund', {})
print('资金账户数据:')
print(f' - balance: {fund.get(\"balance\")}')
print(f' - frozen: {fund.get(\"frozen\")}')
print(f' - total_deposit: {fund.get(\"totalDeposit\")}')
print(f' - total_withdraw: {fund.get(\"totalWithdraw\")}')
if fund.get('balance') == 0:
print('\n⚠ 余额为0但数据库中应该有15500')
else:
print(f'\n✅ 余额正确: {fund.get(\"balance\")}')
"
echo "$TRADE" | python3 -c "
import sys, json
data = json.load(sys.stdin)
if data.get('success') and data.get('data'):
positions = data['data'].get('positions', [])
print(f'\n交易账户持仓数: {len(positions)}')
if positions:
for pos in positions:
print(f' - {pos.get(\"coinCode\")}: {pos.get(\"quantity\")} (价值: {pos.get(\"value\")} USDT)')
else:
print(' 暂无持仓')
"
echo -e "\n================================"
echo "测试完成"
echo "================================"