MQ 最终一致性实现电商账户余额提现

一、业务场景:电商商户余额提现需求

在电商平台中,商户通过销售商品产生的收入会暂存于「平台账户余额」中,核心需求是:支持商户将账户余额提现至微信钱包

这一场景的核心痛点是「分布式事务一致性」—— 既要确保商户账户余额准确扣减,又要保证微信钱包能成功到账,避免 “扣了钱没到账” 或 “没扣钱却到账” 的资金异常问题,是分布式系统中极具代表性的实战案例。

二、技术方案:MQ 最终一致性方案

采用「MQ 事务消息 + 最终一致性」方案,通过消息队列的可靠性投递与消费,确保跨系统(电商账户系统、微信支付系统)的数据一致性,整体流程如下:

关键设计:事务消息确保 “业务操作(扣余额、创记录)成功” 与 “消息投递成功” 强绑定,避免消息丢失;消费者失败重试机制确保微信打款最终能执行成功。

三、实战落地:代码与效果演示

1. 前置准备:数据库表设计

需 2 张核心表存储账户与提现数据,SQL 如下:

-- 1. 账户表:存储商户账户信息与余额

drop table if exists t_account;

create table if not exists t_account

(

id varchar(32) not null primary key comment '用户ID',

name varchar(50) not null comment '商户名称',

balance decimal(12, 2) not null comment '账户余额(单位:元)'

) comment '电商平台账户表';

-- 初始化测试数据:商户ID=1,余额1000元

insert ignore into t_account_lesson037 value ('1','路人1','1000.00');

-- 2. 提现记录表:跟踪每笔提现的状态(核心:分布式事务对账依据)

drop table if exists t_cash_out;

create table if not exists t_cash_out

(

id varchar(32) not null primary key comment '提现记录ID',

account_id varchar(32) not null comment '关联账户ID',

price decimal(12, 2) not null comment '提现金额',

status smallint not null comment '状态(0:待处理,100:提现成功)',

create_time datetime not null comment '创建时间',

update_time datetime comment '最后更新时间'

) comment '账户提现记录表';

2. 核心代码实现

(1)提现接口:处理业务逻辑 + 发送事务消息

接口路径:com.itsoku.lesson037.controller.AccountController#cashOut

核心职责(3 件事):

  1. 校验账户余额是否充足;
  2. 扣减账户余额 + 创建「待处理」状态的提现记录(同一数据库事务,确保原子性);
  3. 发送 MQ 事务消息(确保业务操作成功后,消息必投递)。
@PostMapping("/account/cashOut")

@Transactional(rollbackFor = Exception.class)

public ResultDTO cashOut(@RequestBody CashOutDTO cashOutDTO) {

// 1. 获取当前商户账户(实际场景需从登录态获取accountId)

String accountId = "1";

AccountDO account = accountMapper.selectById(accountId);

if (account == null) {

return ResultDTO.fail("账户不存在");

}

// 2. 校验余额是否充足

BigDecimal cashOutAmount = new BigDecimal(cashOutDTO.getPrice());

if (account.getBalance().compareTo(cashOutAmount) < 0) {

return ResultDTO.fail("余额不足");

}

// 3. 扣减账户余额

account.setBalance(account.getBalance().subtract(cashOutAmount));

accountMapper.updateById(account);

// 4. 创建提现记录(状态:待处理)

CashOutDO cashOutDO = new CashOutDO();

cashOutDO.setId(UUID.randomUUID().toString());

cashOutDO.setAccountId(accountId);

cashOutDO.setPrice(cashOutAmount);

cashOutDO.setStatus(0); // 0=待处理

cashOutDO.setCreateTime(new Date());

cashOutMapper.insert(cashOutDO);

// 5. 发送MQ事务消息(确保消息投递成功)

mqProducer.sendCashOutTransactionMsg(cashOutDO.getId(), accountId, cashOutAmount);

return ResultDTO.success("提现请求已受理");

}
(2)提现消息消费者:调用微信接口 + 更新状态

核心职责(2 件事):

  1. 调用微信支付接口完成打款(依赖微信接口的幂等性,避免重复打款);
  2. 更新提现记录状态为「成功」。

特性:继承 AbstractRetryConsumer 抽象类,自动拥有「失败衰减式重试」能力(如:失败后隔 10s、30s、1min 重试,避免瞬时故障导致的失败)。

@Component

public class CashOutMsgConsumer extends AbstractRetryConsumer<CashOutMsg> {

@Autowired

private WeChatPayService weChatPayService;

@Autowired

private CashOutMapper cashOutMapper;

@Override

public boolean consume(CashOutMsg msg) {

try {

// 1. 调用微信支付接口:给商户微信钱包打款

// 微信接口天然支持幂等(通过提现记录ID作为唯一标识)

boolean paySuccess = weChatPayService.transferToWeChat(

msg.getAccountId(), // 商户微信关联ID(实际需存储)

msg.getCashOutId(), // 唯一标识(幂等键)

msg.getPrice() // 打款金额

);

if (!paySuccess) {

log.error("微信打款失败,cashOutId:{}", msg.getCashOutId());

return false; // 返回false触发重试

}

// 2. 更新提现记录状态为「成功」

CashOutDO updateDO = new CashOutDO();

updateDO.setId(msg.getCashOutId());

updateDO.setStatus(100); // 100=提现成功

updateDO.setUpdateTime(new Date());

cashOutMapper.updateById(updateDO);

return true; // 消费成功

} catch (Exception e) {

log.error("提现消费异常,cashOutId:{}", msg.getCashOutId(), e);

return false; // 异常时触发重试

}

}

}

3. 效果验证步骤

步骤 1:启动应用
步骤 2:查看初始账户余额

执行 SQL 查询账户初始状态:

select * from t_account;

初始结果:id=1,name = chen 1,balance=1000.00(元)。

步骤 3:调用提现接口

使用 HTTP 工具(如 Postman、IDEA HTTP Client)发起请求:

### 商户提现请求

POST http://localhost:8080/account/cashOut

Accept: application/json

Content-Type: application/json

{

"price": "10.00" // 提现10元

}

步骤 4:验证最终结果

账户余额验证

select * from t_account;

结果:balance=990.00(元),余额成功扣减 10 元。

提现记录验证

select * from t_cash_out;

结果:status=100(提现成功),update_time 有值,说明微信打款与状态更新均完成。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

小陈永不服输

你的鼓励将是我最大的动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值