Files
monisuo/ACCOUNT_SYSTEM_DESIGN.md
sion f0af05e366 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
- 强制刷新确保数据最新
2026-03-24 16:58:31 +08:00

488 lines
14 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 虚拟货币交易平台 - 完整账户体系设计
## 📊 账户体系架构
### 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
**状态**: 待测试验证