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

14 KiB
Raw Blame History

虚拟货币交易平台 - 完整账户体系设计

📊 账户体系架构

1. 账户类型

1.1 资金账户 (account_fund)

用途: 法币通道,特点:

  • 每个用户只有1个资金账户1:1关系
  • 用于充值和提现
  • 可以划转到交易账户USDT

字段:

- id: 主键
- user_id: 用户ID(唯一)
- balance: USDT余额 
- frozen: 冻结金额(提现申请时冻结)
- total_deposit: 累计充值
- total_withdraw: 累计提现
- create_time: 创建时间
- update_time: 更新时间

1.2 交易账户 (account_trade)

用途: 币币交易 特点:

  • 每个用户有多个交易账户1:N关系- 每个币种一个账户)
  • 用于买卖币种
  • 盈亏体现在交易账户

字段:

- 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已完成

关键代码:

// 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已完成

关键代码:

// 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 += 划转金额 ⭐
    ↓
记录流水

关键代码:

// 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 = 加权平均成本 ⭐
    ↓
记录交易订单
记录流水

关键代码:

// 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 += 卖出金额 ⭐
    ↓
计算盈亏
记录交易订单
记录流水

关键代码:

// 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 持仓盈亏

// 单个币种盈亏
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 总盈亏

// 所有持仓总盈亏
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 资金账户页面

// 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 交易账户页面

// 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 资产总览

// 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 余额验证

// 充值审批
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 事务管理

@Transactional(rollbackFor = Exception.class)
public void transfer(...) {
    // 所有操作在同一事务中
    // 任何失败都会回滚
}

🔍 当前问题诊断

问题: Flutter前端资金账户显示0.00

API返回正确:

{
  "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 状态: 待测试验证