中心化交易所现货与杠杆业务
加密货币交易所是高性能金融系统的典型代表,撮合引擎、订单簿、风控系统都是面试高频考点
🎯 面试重点
- 撮合引擎的核心原理与性能优化
- 订单簿(Order Book)的数据结构与实现
- 现货交易与杠杆交易的区别与联系
- 保证金系统与强平机制
- 冷热钱包架构与资金安全
- 高并发下的数据一致性保障
📖 一、现货交易(Spot Trading)
1.1 现货交易基础
定义:用户用自有资金买卖加密货币,即时交割,无杠杆。
核心流程:
用户下单 → 订单校验 → 进入订单簿 → 撮合引擎匹配 → 成交 → 更新资产 → 清算结算
关键概念:
- Maker/Taker:Maker 提供流动性(挂单),Taker 消耗流动性(吃单)
- 买卖盘:Bid(买盘,买家出价),Ask(卖盘,卖家要价)
- Spread:买一和卖一的价差
- 深度:订单簿中不同价位的挂单量
1.2 订单类型
| 订单类型 | 说明 | 适用场景 |
|---|---|---|
| 限价单(Limit) | 指定价格成交,不保证成交 | 控制成本,做 Maker 赚手续费返佣 |
| 市价单(Market) | 以当前最优价立即成交 | 急需成交,追求速度 |
| 止损单(Stop-Loss) | 价格触发后转为市价单 | 风险控制,自动止损 |
| 止盈止损(TP/SL) | 同时设置止盈和止损价格 | 自动化交易策略 |
| 冰山单(Iceberg) | 大单拆分为多个小单逐步成交 | 隐藏真实交易量,减少市场冲击 |
| FOK(Fill or Kill) | 全部成交或全部取消 | 要求完全成交 |
| IOC(Immediate or Cancel) | 立即成交剩余取消 | 部分成交也可接受 |
1.3 撮合引擎(Matching Engine)
核心职责:将买单和卖单按价格优先、时间优先原则进行匹配。
价格优先、时间优先原则:
买单:价格高的优先(买得起的优先)
卖单:价格低的优先(卖得便宜的优先)
同价格:先下单的优先
示例:
买盘(Bids): 卖盘(Asks):
100.5 1000 100.6 500
100.4 2000 100.7 1000
100.3 1500 100.8 2000
新买单:价格 100.6,数量 800
→ 与卖一 100.6 匹配成交 500
→ 剩余 300 成为新的买一(100.6)
撮合引擎架构:
public class MatchingEngine {
// 订单簿:买盘的优先级队列(价格降序,同价格时间升序)
private PriorityQueue<Order> bidBook = new PriorityQueue<>((a, b) -> {
int priceCompare = b.getPrice().compareTo(a.getPrice());
return priceCompare != 0 ? priceCompare :
Long.compare(a.getTimestamp(), b.getTimestamp());
});
// 订单簿:卖盘的优先级队列(价格升序,同价格时间升序)
private PriorityQueue<Order> askBook = new PriorityQueue<>((a, b) -> {
int priceCompare = a.getPrice().compareTo(b.getPrice());
return priceCompare != 0 ? priceCompare :
Long.compare(a.getTimestamp(), b.getTimestamp());
});
// 撮合入口
public List<Trade> match(Order newOrder) {
List<Trade> trades = new ArrayList<>();
if (newOrder.getSide() == Side.BUY) {
// 买单与卖盘撮合
trades = matchWithAsks(newOrder);
} else {
// 卖单与买盘撮合
trades = matchWithBids(newOrder);
}
return trades;
}
private List<Trade> matchWithAsks(Order buyOrder) {
List<Trade> trades = new ArrayList<>();
while (!buyOrder.isFilled() && !askBook.isEmpty()) {
Order bestAsk = askBook.peek();
// 价格是否匹配(买价 >= 卖价)
if (buyOrder.getPrice().compareTo(bestAsk.getPrice()) < 0) {
break; // 无法成交
}
// 撮合
BigDecimal tradeQty = buyOrder.getRemainingQty()
.min(bestAsk.getRemainingQty());
BigDecimal tradePrice = bestAsk.getPrice(); // 以卖价成交
Trade trade = new Trade(buyOrder, bestAsk, tradeQty, tradePrice);
trades.add(trade);
// 更新订单状态
buyOrder.fill(tradeQty);
bestAsk.fill(tradeQty);
// 完全成交的订单从订单簿移除
if (bestAsk.isFilled()) {
askBook.poll();
}
}
// 未完全成交的限价单进入订单簿
if (!buyOrder.isFilled() && buyOrder.getType() == OrderType.LIMIT) {
bidBook.offer(buyOrder);
}
return trades;
}
}
1.4 订单簿(Order Book)实现
核心挑战:
- 高频查询:每秒数万次查询最优价、深度
- 高频更新:每秒数千次挂单、撤单、成交
- 内存效率:需要存储大量订单(百万级)
数据结构选择:
| 方案 | 查询最优价 | 插入/删除 | 内存占用 | 适用场景 |
|---|---|---|---|---|
| 红黑树(TreeMap) | O(log n) | O(log n) | 高 | 通用,Java 常用 |
| 跳表(SkipList) | O(log n) | O(log n) | 中 | 并发性能好 |
| 数组 + 哈希表 | O(1) | O(1) | 低 | 价格范围有限时 |
高性能实现(价格分桶):
// 假设价格精度为 0.01,价格范围 0-100000
// 使用数组索引直接定位,O(1) 复杂度
public class PriceLadder {
private static final int PRICE_PRECISION = 2; // 小数位
private static final BigDecimal PRICE_TICK = new BigDecimal("0.01");
private static final int MAX_PRICE_INDEX = 10_000_000; // 支持到 100000.00
// 每个价格档的订单链表
private PriceLevel[] bidLevels = new PriceLevel[MAX_PRICE_INDEX];
private PriceLevel[] askLevels = new PriceLevel[MAX_PRICE_INDEX];
// 当前最优价索引
private int bestBidIndex = 0;
private int bestAskIndex = MAX_PRICE_INDEX - 1;
// 价格 → 索引转换
private int priceToIndex(BigDecimal price) {
return price.movePointRight(PRICE_PRECISION).intValue();
}
// O(1) 插入订单
public void addOrder(Order order) {
int index = priceToIndex(order.getPrice());
PriceLevel level = order.getSide() == Side.BUY ?
bidLevels[index] : askLevels[index];
if (level == null) {
level = new PriceLevel(order.getPrice());
if (order.getSide() == Side.BUY) {
bidLevels[index] = level;
bestBidIndex = Math.max(bestBidIndex, index);
} else {
askLevels[index] = level;
bestAskIndex = Math.min(bestAskIndex, index);
}
}
level.addOrder(order);
}
// O(1) 获取最优价
public BigDecimal getBestBid() {
return new BigDecimal(bestBidIndex).movePointLeft(PRICE_PRECISION);
}
}
1.5 资产系统(Wallet)
账户模型:
用户账户
├── 现货账户(Spot)
│ ├── BTC:1.5
│ ├── ETH:10.0
│ └── USDT:50000.0
├── 杠杆账户(Margin)
│ ├── 已借 BTC:0.5
│ ├── 已借 USDT:10000
│ └── 保证金:20000 USDT
└── 合约账户(Futures)
└── ...
资产操作原子性:
// 成交后的资产划转必须原子执行
@Transactional
public void settleTrade(Trade trade) {
String buyerId = trade.getBuyerId();
String sellerId = trade.getSellerId();
String baseCurrency = trade.getBaseCurrency(); // BTC
String quoteCurrency = trade.getQuoteCurrency(); // USDT
BigDecimal qty = trade.getQuantity();
BigDecimal price = trade.getPrice();
BigDecimal amount = qty.multiply(price);
// 买方:减少 USDT,增加 BTC
walletService.deductAvailable(buyerId, quoteCurrency, amount);
walletService.addAvailable(buyerId, baseCurrency, qty);
// 卖方:减少 BTC,增加 USDT
walletService.deductAvailable(sellerId, baseCurrency, qty);
walletService.addAvailable(sellerId, quoteCurrency, amount);
// 记录成交明细
tradeHistoryService.save(trade);
}
📖 二、杠杆交易(Margin Trading)
2.1 杠杆交易基础
定义:用户借入资金进行交易,放大收益和风险。
核心概念:
- 本金(Principal):用户自有资金
- 杠杆倍数(Leverage):2x、3x、5x、10x、20x、100x 等
- 保证金(Margin):用于担保借款的资金
- 维持保证金(Maintenance Margin):最低保证金比例,低于则触发强平
杠杆计算示例:
用户本金:10000 USDT
杠杆倍数:5x
可借资金:40000 USDT(总仓位 50000 USDT)
情况1:BTC 上涨 20%
仓位价值:50000 × 1.2 = 60000 USDT
还款后:60000 - 40000 = 20000 USDT
收益率:(20000 - 10000) / 10000 = 100%(放大5倍)
情况2:BTC 下跌 20%
仓位价值:50000 × 0.8 = 40000 USDT
还款后:40000 - 40000 = 0 USDT
收益率:-100%(爆仓,本金亏光)
2.2 保证金系统
全仓保证金(Cross Margin):
- 所有仓位共享一个保证金池
- 盈亏互抵,风险分散
- 适合多币种对冲策略
逐仓保证金(Isolated Margin):
- 每个仓位独立保证金
- 单个仓位爆仓不影响其他仓位
- 风险隔离,适合高风险单笔交易
保证金计算公式:
// 全仓模式
public class CrossMarginCalculator {
// 计算账户权益(总资产 - 总负债)
public BigDecimal calculateEquity(Account account) {
BigDecimal totalAsset = account.getBalances().values().stream()
.map(Balance::getTotal) // 可用 + 冻结
.reduce(BigDecimal.ZERO, BigDecimal::add);
BigDecimal totalDebt = account.getDebts().values().stream()
.map(Debt::getPrincipal)
.reduce(BigDecimal.ZERO, BigDecimal::add);
return totalAsset.subtract(totalDebt);
}
// 计算保证金率
public BigDecimal calculateMarginRatio(Account account) {
BigDecimal equity = calculateEquity(account);
BigDecimal totalPositionValue = calculateTotalPositionValue(account);
if (totalPositionValue.compareTo(BigDecimal.ZERO) == 0) {
return BigDecimal.valueOf(Long.MAX_VALUE); // 无仓位
}
return equity.divide(totalPositionValue, 4, RoundingMode.DOWN);
}
// 检查是否触发强平
public boolean isLiquidationTriggered(Account account) {
BigDecimal marginRatio = calculateMarginRatio(account);
BigDecimal maintenanceMarginRate = account.getMaintenanceMarginRate(); // 如 0.05 (5%)
return marginRatio.compareTo(maintenanceMarginRate) < 0;
}
}
2.3 强平(Liquidation)机制
强平触发条件:
保证金率 = 账户权益 / 持仓总价值
当 保证金率 < 维持保证金率(如 5%)时,触发强平
示例:
本金:10000 USDT
借入:40000 USDT
持仓:50000 USDT 的 BTC
BTC 下跌 19%:
持仓价值:50000 × 0.81 = 40500 USDT
账户权益:40500 - 40000 = 500 USDT
保证金率:500 / 40500 = 1.23% < 5%
→ 触发强平
强平流程:
@Service
public class LiquidationService {
@Autowired
private RiskEngine riskEngine;
@Autowired
private MatchingEngine matchingEngine;
// 定时扫描或事件驱动检查
@Scheduled(fixedDelay = 100) // 每100ms检查一次
public void checkLiquidation() {
List<Account> highRiskAccounts = riskEngine.getHighRiskAccounts();
for (Account account : highRiskAccounts) {
if (shouldLiquidate(account)) {
executeLiquidation(account);
}
}
}
private void executeLiquidation(Account account) {
try {
// 1. 冻结账户,禁止新下单
account.freeze();
// 2. 取消所有未成交订单
orderService.cancelAllOrders(account.getId());
// 3. 市价平仓(以当前最优价立即卖出)
List<Position> positions = account.getPositions();
for (Position position : positions) {
Order liquidationOrder = Order.builder()
.accountId(account.getId())
.symbol(position.getSymbol())
.side(position.getSide() == Side.BUY ? Side.SELL : Side.BUY)
.type(OrderType.MARKET) // 市价单
.quantity(position.getQuantity())
.build();
// 发送到撮合引擎
matchingEngine.submitOrder(liquidationOrder);
}
// 4. 归还借款
settleDebt(account);
// 5. 剩余资金返还用户
BigDecimal remaining = account.getEquity();
if (remaining.compareTo(BigDecimal.ZERO) > 0) {
walletService.transferToSpot(account.getId(), remaining);
}
// 6. 记录强平日志
liquidationLogService.save(new LiquidationLog(account, remaining));
} catch (Exception e) {
// 强平失败,触发保险基金或自动减仓
insuranceFundService.handleFailedLiquidation(account);
}
}
}
2.4 风险限额(Risk Limit)
目的:限制用户持仓规模,降低系统性风险。
分级制度:
Level 1: 持仓 ≤ 100 BTC,维持保证金率 2.5%,最高杠杆 125x
Level 2: 持仓 ≤ 500 BTC,维持保证金率 5%,最高杠杆 100x
Level 3: 持仓 ≤ 1000 BTC,维持保证金率 10%,最高杠杆 50x
...
风险限额检查:
public class RiskLimitService {
public void checkRiskLimit(String accountId, String symbol,
BigDecimal newPosition) {
RiskLimit limit = riskLimitRepository.findByAccountId(accountId);
// 检查持仓限额
if (newPosition.compareTo(limit.getPositionLimit()) > 0) {
throw new RiskLimitExceededException(
"持仓超过风险限额: " + limit.getPositionLimit());
}
// 检查杠杆倍数
BigDecimal leverage = calculateLeverage(accountId);
if (leverage.compareTo(limit.getMaxLeverage()) > 0) {
throw new RiskLimitExceededException(
"杠杆超过限制: " + limit.getMaxLeverage());
}
}
}
📖 三、交易所核心系统架构
3.1 整体架构
┌─────────────────────────────────────────────────────────────┐
│ 接入层 │
│ ┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐ │
│ │ Web │ │ App │ │ API │ │ WS │ │
│ │ 前端 │ │ 客户端 │ │ 接口 │ │ 推送 │ │
│ └────┬────┘ └────┬────┘ └────┬────┘ └────┬────┘ │
└───────┼────────────┼────────────┼────────────┼──────────────┘
│ │ │ │
└────────────┴────────────┴────────────┘
│
┌─────────────────────────▼───────────────────────────────────┐
│ 网关层 │
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────────────┐ │
│ │ 负载均衡 │ │ 限流熔断 │ │ 认证鉴权 │ │
│ │ (Nginx/ALB) │ │ (Sentinel) │ │ (JWT/API Key) │ │
│ └─────────────┘ └─────────────┘ └─────────────────────┘ │
└─────────────────────────┬───────────────────────────────────┘
│
┌─────────────────────────▼───────────────────────────────────┐
│ 业务层 │
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────────────┐ │
│ │ 订单服务 │ │ 撮合引擎 │ │ 资产服务 │ │
│ │ (Order) │ │ (Matching) │ │ (Wallet) │ │
│ └─────────────┘ └─────────────┘ └─────────────────────┘ │
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────────────┐ │
│ │ 杠杆服务 │ │ 风控服务 │ │ 清算服务 │ │
│ │ (Margin) │ │ (Risk) │ │ (Settlement) │ │
│ └─────────────┘ └─────────────┘ └─────────────────────┘ │
└─────────────────────────┬───────────────────────────────────┘
│
┌─────────────────────────▼───────────────────────────────────┐
│ 数据层 │
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────────────┐ │
│ │ 订单簿 │ │ 资产数据 │ │ 行情数据 │ │
│ │ (Redis) │ │ (MySQL) │ │ (Kafka + TSDB) │ │
│ └─────────────┘ └─────────────┘ └─────────────────────┘ │
└─────────────────────────────────────────────────────────────┘
3.2 撮合引擎性能优化
挑战:
- 每秒数万笔订单(10万+ TPS)
- 延迟要求:< 1ms(从接收到成交)
- 数据一致性:不能丢单、不能重复成交
优化策略:
- 内存计算:
// 订单簿完全在内存中,避免数据库访问 public class InMemoryOrderBook { // 使用 Disruptor 无锁队列处理订单 private Disruptor<OrderEvent> disruptor; // 订单簿数据结构 private ConcurrentSkipListMap<BigDecimal, PriceLevel> bids; private ConcurrentSkipListMap<BigDecimal, PriceLevel> asks; // 撮合核心:单线程处理,避免锁竞争 public void onEvent(OrderEvent event, long sequence, boolean endOfBatch) { Order order = event.getOrder(); List<Trade> trades = match(order); // 批量发送成交结果 if (endOfBatch) { tradeProducer.sendBatch(trades); } } } - 异步持久化:
// 撮合结果先写内存,异步批量写入数据库 @Service public class AsyncSettlementService { private BlockingQueue<Trade> tradeQueue = new LinkedBlockingQueue<>(); // 撮合线程只负责撮合,不落库 public void onTrade(Trade trade) { tradeQueue.offer(trade); // 立即返回,不等待数据库 } // 独立线程批量落库 @Scheduled(fixedDelay = 10) public void batchPersist() { List<Trade> batch = new ArrayList<>(); tradeQueue.drainTo(batch, 1000); if (!batch.isEmpty()) { tradeRepository.batchInsert(batch); } } } - 读写分离:
写入路径:订单 → 撮合引擎 → 内存订单簿 → 异步落库 读取路径:行情查询 → 内存订单簿(无锁读取)
3.3 冷热钱包架构
资金分层:
用户充值
↓
热钱包(在线)─── 日常提现、交易结算
│ 占比:5% 资金
│ 存储:HSM 加密机
↓
温钱包(半离线)── 定期归集、大额提现审批
│ 占比:15% 资金
│ 存储:离线签名机
↓
冷钱包(完全离线)── 长期存储、多签管理
占比:80% 资金
存储:纸钱包、硬件钱包、银行保险柜
安全机制:
- 多签(Multi-Sig):3-5 个私钥,至少 3 个签名才能转账
- 阈值控制:热钱包余额超过阈值自动归集到冷钱包
- 异常监控:大额转出触发人工审核
- 定期对账:链上余额与系统余额每日对账
📖 四、面试真题
Q1: 撮合引擎的核心设计原则是什么?
答: 撮合引擎是交易所的核心,设计原则包括:
-
正确性:价格优先、时间优先原则必须严格遵守,不能出现错配、漏配。
- 高性能:
- 内存计算:订单簿完全在内存中
- 无锁设计:单线程处理或使用无锁数据结构
- 批量处理:批量落库、批量推送行情
- 低延迟:
- 目标:< 1ms 从订单接收到成交
- 手段:CPU 亲和性绑定、禁用 GC、使用 Disruptor
- 高可用:
- 主备架构:主撮合故障时秒级切换
- 状态复制:订单簿状态实时同步到备机
- 数据一致性:
- 撮合结果必须持久化,不能丢单
- 使用 WAL(Write Ahead Log)保证
Q2: 如何实现订单簿的快速查询和更新?
答: 订单簿需要支持高频查询(最优价、深度)和更新(挂单、撤单、成交)。
方案对比:
| 数据结构 | 最优价查询 | 插入/删除 | 内存占用 | 适用场景 |
|---|---|---|---|---|
| TreeMap/红黑树 | O(log n) | O(log n) | 高 | 通用实现 |
| SkipList | O(log n) | O(log n) | 中 | 高并发 |
| 数组索引 | O(1) | O(1) | 低 | 价格范围有限 |
最佳实践(数组索引 + 链表):
// 价格精度 0.01,范围 0-100000,共 1000 万个价格档
// 使用 int[] 索引,O(1) 定位
public class FastOrderBook {
// 每个价格档是一个链表(同价格按时间排序)
private Order[] bidLevels = new Order[10_000_000];
private Order[] askLevels = new Order[10_000_000];
// 当前最优价索引
private int bestBid = 0;
private int bestAsk = 9_999_999;
// O(1) 插入
public void addOrder(Order order) {
int index = priceToIndex(order.getPrice());
Order[] levels = order.isBuy() ? bidLevels : askLevels;
// 头插法(时间最新在最前)
order.next = levels[index];
levels[index] = order;
// 更新最优价
if (order.isBuy() && index > bestBid) bestBid = index;
if (!order.isBuy() && index < bestAsk) bestAsk = index;
}
// O(1) 获取最优价
public BigDecimal getBestBid() {
return indexToPrice(bestBid);
}
}
Q3: 杠杆交易的强平机制是如何设计的?
答: 强平机制是杠杆系统的风险控制核心。
触发条件:
保证金率 = 账户权益 / 持仓总价值 < 维持保证金率(如 5%)
强平流程:
- 冻结账户:禁止新下单,防止风险扩大
- 取消挂单:撤销所有未成交订单,释放保证金
- 市价平仓:以当前最优价立即卖出持仓
- 归还借款:优先归还借入的资金
- 剩余返还:如有剩余资金,返还到用户现货账户
风险控制:
- 保险基金:强平产生的穿仓损失由保险基金承担
- 自动减仓(ADL):保险基金不足时,按盈利和杠杆比例强制平仓盈利用户
- 风险限额:根据持仓大小限制杠杆倍数
Q4: 如何保证撮合引擎的高可用和数据一致性?
答: 撮合引擎的高可用和数据一致性需要多层面保障。
高可用架构:
主撮合引擎 ── 实时复制状态 ── 备撮合引擎
│ │
└────── 共享存储(WAL)──────┘
数据一致性保障:
- WAL(Write Ahead Log):
- 撮合前先写日志
- 日志顺序写入,性能高
- 故障恢复时重放日志
- 状态快照:
- 定期对订单簿打快照
- 故障时从快照恢复 + 重放增量日志
- 主备切换:
- 主故障时,备机从共享存储读取最新状态
- 秒级切换,用户无感知
- 对账机制:
- 每日对账:订单簿状态 vs 数据库状态
- 发现不一致时告警并修复
Q5: 交易所如何防止女巫攻击和洗钱?
答: 女巫攻击(Sybil Attack)和洗钱(Money Laundering)是交易所面临的安全挑战。
防御措施:
- KYC(Know Your Customer):
- 实名认证:身份证、人脸识别
- 地址证明:水电账单、银行对账单
- 风险评级:根据用户行为和资金来源评级
- 链上监控:
- 黑名单地址:与已知黑客地址、混币器地址关联
- 资金溯源:追踪资金来源,识别可疑交易
- 大额告警:超过阈值的交易触发人工审核
- 行为分析:
- 异常交易模式:如频繁小额转账(分层)
- 多账户关联:同一 IP、设备、身份关联多个账户
- 交易对手分析:识别高风险对手方
- 合规合作:
- 与 Chainalysis、Elliptic 等链上分析公司合作
- 与监管机构共享可疑交易信息
Q6: 冷热钱包的资金流转是如何设计的?
答: 冷热钱包的资金流转需要兼顾安全性和效率。
归集流程(热 → 冷):
1. 监控热钱包余额
2. 超过阈值(如 100 BTC)触发归集
3. 生成冷钱包地址(离线)
4. 热钱包发起转账(多签)
5. 等待链上确认(6个区块)
6. 更新系统余额
提现流程(冷 → 热):
1. 用户发起提现申请
2. 风控审核(大额人工审核)
3. 从冷钱包转账到热钱包(离线签名)
4. 热钱包发起链上转账给用户
5. 等待确认,标记提现完成
安全机制:
- 多签控制:冷钱包转账需要 3/5 签名
- 离线签名:私钥永不触网
- 阈值控制:热钱包余额有上限,超出自动归集
- 定期审计:第三方审计公司定期审计钱包余额
Q7: 交易所的费率模型是如何设计的?
答: 费率模型影响交易所的收入和流动性。
费率结构:
Maker 费率:0.02% - 0.1%(挂单,提供流动性,费率低)
Taker 费率:0.04% - 0.2%(吃单,消耗流动性,费率高)
费率优惠:
- VIP 等级:根据 30 天交易量或持仓量分级
- VIP 0:基础费率
- VIP 1-9:费率递减,最高 50% 折扣
- 平台币抵扣:使用平台币支付手续费,额外折扣
- 邀请返佣:邀请人获得被邀请人手续费返佣
费率计算示例:
public class FeeCalculator {
public Fee calculateFee(Order order, User user) {
// 基础费率
BigDecimal baseRate = order.isMaker() ?
new BigDecimal("0.0002") : new BigDecimal("0.0004");
// VIP 折扣
BigDecimal vipDiscount = getVipDiscount(user.getVipLevel());
// 平台币抵扣折扣
BigDecimal tokenDiscount = user.isUseTokenFee() ?
new BigDecimal("0.25") : BigDecimal.ZERO;
// 最终费率
BigDecimal finalRate = baseRate
.multiply(BigDecimal.ONE.subtract(vipDiscount))
.multiply(BigDecimal.ONE.subtract(tokenDiscount));
// 计算手续费
BigDecimal fee = order.getAmount().multiply(finalRate);
return new Fee(fee, finalRate);
}
}