一、本节诉求
按照用户行为返利的需求设计,创建相应的库表,开发 rebate 返利领域,提供返利订单创建接口。并在写入订单后发送 MQ 消息。后续则处理奖励入账。
用户的行为动作返利,是一种日常的活动类型。比如;你在某个平台创建了新账号,就会给你发一堆的开户优惠券,这些都是日常的返利活动。
日常的返利会根据用户所完成的行为动作来触达,这包括;打卡、签到、连签、支付、开户、交易、信贷、拉新等各类的动作。我们本节中所涉及的,主要是日常的日历签到行为,并也从功能实现上扩展出各类型的任务,以便于后续扩展。

一个用户行为可能会给多种奖励,所以在接收到用户信息后,会根据配置组装聚合对象。【聚合的目的就是为了做一个统一的事务】
一个聚合对象中包含了返利的订单实体对象,写入task的实体对象。它们是一个事务入库。
另外是发送MQ消息,在完成入口动作后,会直接发送MQ消息,并且如果发送失败,会有任务兜底。【这样是面试中经常问到的点,如果MQ消息发送失败了,你是怎么处理的。】
二、代码实现
本节的流程比较简单,就是把用户的行为动作,根据配置,创建出聚合对象,之后写账入库即可。
注意;仓储的使用、MQ消息事件的定义、聚合对象的创建。
@OverRide
public void saveUserRebateRecord(String userId, List behaviorRebateAggregates) {
try {
dbRouter.doRouter(userId);
transactionTemplate.execute(status -> {
try {
for (BehaviorRebateAggregate behaviorRebateAggregate : behaviorRebateAggregates) {
BehaviorRebateOrderEntity behaviorRebateOrderEntity = behaviorRebateAggregate.getBehaviorRebateOrderEntity();
// 用户行为返利订单对象
UserBehaviorRebateOrder userBehaviorRebateOrder = new UserBehaviorRebateOrder();
userBehaviorRebateOrder.setUserId(behaviorRebateOrderEntity.getUserId());
userBehaviorRebateOrder.setOrderId(behaviorRebateOrderEntity.getOrderId());
userBehaviorRebateOrder.setBehaviorType(behaviorRebateOrderEntity.getBehaviorType());
userBehaviorRebateOrder.setRebateDesc(behaviorRebateOrderEntity.getRebateDesc());
userBehaviorRebateOrder.setRebateType(behaviorRebateOrderEntity.getRebateType());
userBehaviorRebateOrder.setRebateConfig(behaviorRebateOrderEntity.getRebateConfig());
userBehaviorRebateOrder.setBizId(behaviorRebateOrderEntity.getBizId());
userBehaviorRebateOrderDao.insert(userBehaviorRebateOrder);
// 任务对象
TaskEntity taskEntity = behaviorRebateAggregate.getTaskEntity();
Task task = new Task();
task.setUserId(taskEntity.getUserId());
task.setTopic(taskEntity.getTopic());
task.setMessageId(taskEntity.getMessageId());
task.setMessage(JSON.toJSONString(taskEntity.getMessage()));
task.setState(taskEntity.getState().getCode());
taskDao.insert(task);
}
return 1;
} catch (DuplicateKeyException e) {
status.setRollbackOnly();
log.error("写入返利记录,唯一索引冲突 userId: {}", userId, e);
throw new AppException(ResponseCode.INDEX_DUP.getCode(), e);
}
});
} finally {
dbRouter.clear();
}
// 同步发送MQ消息
for (BehaviorRebateAggregate behaviorRebateAggregate : behaviorRebateAggregates) {
TaskEntity taskEntity = behaviorRebateAggregate.getTaskEntity();
Task task = new Task();
task.setUserId(taskEntity.getUserId());
task.setMessageId(taskEntity.getMessageId());
try {
// 发送消息【在事务外执行,如果失败还有任务补偿】
eventPublisher.publish(taskEntity.getTopic(), taskEntity.getMessage());
// 更新数据库记录,task 任务表
taskDao.updateTaskSendMessageCompleted(task);
} catch (Exception e) {
log.error("写入返利记录,发送MQ消息失败 userId: {} topic: {}", userId, task.getTopic());
taskDao.updateTaskSendMessageFail(task);
}
}
}
设置路由 dbRouter.doRouter(userId); 在这个操作内,所有的流程都走一个路由结果,并且在同一个数据库链接。
transactionTemplate.execute 是手动开启数据库事务的操作。
status.setRollbackOnly(); 是手动回滚事务的操作,如果有唯一索引冲突则回滚事务。
最后同步发送MQ,避免强依赖 task,让 task 只做补偿,提高 MQ 发送效率。
一、本节诉求
按照用户行为返利的需求设计,创建相应的库表,开发 rebate 返利领域,提供返利订单创建接口。并在写入订单后发送 MQ 消息。后续则处理奖励入账。
用户的行为动作返利,是一种日常的活动类型。比如;你在某个平台创建了新账号,就会给你发一堆的开户优惠券,这些都是日常的返利活动。
日常的返利会根据用户所完成的行为动作来触达,这包括;打卡、签到、连签、支付、开户、交易、信贷、拉新等各类的动作。我们本节中所涉及的,主要是日常的日历签到行为,并也从功能实现上扩展出各类型的任务,以便于后续扩展。
一个用户行为可能会给多种奖励,所以在接收到用户信息后,会根据配置组装聚合对象。【聚合的目的就是为了做一个统一的事务】
一个聚合对象中包含了返利的订单实体对象,写入task的实体对象。它们是一个事务入库。
另外是发送MQ消息,在完成入口动作后,会直接发送MQ消息,并且如果发送失败,会有任务兜底。【这样是面试中经常问到的点,如果MQ消息发送失败了,你是怎么处理的。】
二、代码实现
本节的流程比较简单,就是把用户的行为动作,根据配置,创建出聚合对象,之后写账入库即可。
注意;仓储的使用、MQ消息事件的定义、聚合对象的创建。
@OverRide
public void saveUserRebateRecord(String userId, List behaviorRebateAggregates) {
try {
dbRouter.doRouter(userId);
transactionTemplate.execute(status -> {
try {
for (BehaviorRebateAggregate behaviorRebateAggregate : behaviorRebateAggregates) {
BehaviorRebateOrderEntity behaviorRebateOrderEntity = behaviorRebateAggregate.getBehaviorRebateOrderEntity();
// 用户行为返利订单对象
UserBehaviorRebateOrder userBehaviorRebateOrder = new UserBehaviorRebateOrder();
userBehaviorRebateOrder.setUserId(behaviorRebateOrderEntity.getUserId());
userBehaviorRebateOrder.setOrderId(behaviorRebateOrderEntity.getOrderId());
userBehaviorRebateOrder.setBehaviorType(behaviorRebateOrderEntity.getBehaviorType());
userBehaviorRebateOrder.setRebateDesc(behaviorRebateOrderEntity.getRebateDesc());
userBehaviorRebateOrder.setRebateType(behaviorRebateOrderEntity.getRebateType());
userBehaviorRebateOrder.setRebateConfig(behaviorRebateOrderEntity.getRebateConfig());
userBehaviorRebateOrder.setBizId(behaviorRebateOrderEntity.getBizId());
userBehaviorRebateOrderDao.insert(userBehaviorRebateOrder);
// 任务对象
TaskEntity taskEntity = behaviorRebateAggregate.getTaskEntity();
Task task = new Task();
task.setUserId(taskEntity.getUserId());
task.setTopic(taskEntity.getTopic());
task.setMessageId(taskEntity.getMessageId());
task.setMessage(JSON.toJSONString(taskEntity.getMessage()));
task.setState(taskEntity.getState().getCode());
taskDao.insert(task);
}
return 1;
} catch (DuplicateKeyException e) {
status.setRollbackOnly();
log.error("写入返利记录,唯一索引冲突 userId: {}", userId, e);
throw new AppException(ResponseCode.INDEX_DUP.getCode(), e);
}
});
} finally {
dbRouter.clear();
}
}
设置路由 dbRouter.doRouter(userId); 在这个操作内,所有的流程都走一个路由结果,并且在同一个数据库链接。
transactionTemplate.execute 是手动开启数据库事务的操作。
status.setRollbackOnly(); 是手动回滚事务的操作,如果有唯一索引冲突则回滚事务。
最后同步发送MQ,避免强依赖 task,让 task 只做补偿,提高 MQ 发送效率。