# 虚拟货币交易平台 - 完整账户体系设计 ## 📊 账户体系架构 ### 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 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 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 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 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 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 tradeWrapper = new LambdaUpdateWrapper<>(); tradeWrapper.eq(AccountTrade::getId, usdtTrade.getId()) .set(AccountTrade::getQuantity, newTradeQuantity) .set(AccountTrade::getUpdateTime, LocalDateTime.now()); accountTradeMapper.update(null, tradeWrapper); // 更新资金账户 LambdaUpdateWrapper 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 usdtWrapper = new LambdaUpdateWrapper<>(); usdtWrapper.eq(AccountTrade::getId, usdtTrade.getId()) .set(AccountTrade::getQuantity, newUsdtQuantity) .set(AccountTrade::getUpdateTime, LocalDateTime.now()); accountTradeMapper.update(null, usdtWrapper); // 更新BTC账户 LambdaUpdateWrapper 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 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 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 **状态**: 待测试验证