持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第16天,点击查看活动详情
每日英语:
Don't judge each day by the harvest you reap but by the seeds you plant.
翻译:不要用你的收成来评价你的每一天,而要看你每天播种了多少。 ——罗伯特·史蒂文森
支付工程创建
1)Api
创建mall-pay-api并创建支付日志记录实体Bean
@Data
@AllArgsConstructor
@NoArgsConstructor
//MyBatisPlus表映射注解
@TableName(value = "pay_log")
public class PayLog {
@TableId(type = IdType.ASSIGN_UUID)
private String id;
private Integer status;
private String content;
private String payId;
private Date createTime;
}
2)Service
创建mall-pay-service,并且引入mall-pay-api依赖
pom.xml:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>mall-service</artifactId>
<groupId>com.xz.mall</groupId>
<version>0.0.1-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>mall-pay-service</artifactId>
<description>
支付微服务
</description>
<dependencies>
<!--支付Api-->
<dependency>
<groupId>com.xz.mall</groupId>
<artifactId>mall-pay-api</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>
</dependencies>
</project>
bootstrap.yml
server:
port: 8090
spring:
application:
name: mall-pay
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://192.168.xxx.xxx:3306/shop_pay?useUnicode=true&characterEncoding=UTF-8&serverTimezone=UTC
username: root
password: 123456
cloud:
nacos:
config:
file-extension: yaml
server-addr: 192.168.xxx.xxx:8848
discovery:
#Nacos的注册地址
server-addr: 192.168.xxx.xxx:8848
main:
allow-bean-definition-overriding: true
feign:
client:
config:
default:
connectTimeout: 10000
readTimeout: 600000
# ====================MybatisPlus====================
mybatis-plus:
mapper-locations: mapper/*.xml
type-aliases-package: com.xz.mall.*.model
configuration:
map-underscore-to-camel-case: true
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
#日志配置
logging:
pattern:
console: "%msg%n"
启动类com.xz.mall.MallPayApplication
@SpringBootApplication
@MapperScan(basePackages = "com.xz.mall.pay.mapper")
public class MallPayApplication {
public static void main(String[] args) {
SpringApplication.run(MallPayApplication.class,args);
}
}
事务消息发送
当接到微信支付服务器返回的支付结果后,将支付结果记录到数据库,同时将数据发送到RocketMQ中,并且以事务消息发送。
1)配置bootstrap.yml,添加RocketMQ配置
#producer
rocketmq:
name-server: 192.168.xxx.xxx:9876
producer:
group: pay-group
send-message-timeout: 300000
compress-message-body-threshold: 4096
max-message-size: 4194304
retry-times-when-send-async-failed: 0
retry-next-server: true
retry-times-when-send-failed: 2
注意消息操作,需要引入如下包:
<!--rocketmq-->
<dependency>
<groupId>org.apache.rocketmq</groupId>
<artifactId>rocketmq-spring-boot-starter</artifactId>
<version>2.0.2</version>
</dependency>
2)Dao
创建com.xz.mall.pay.mapper.PayLogMapper
public interface PayLogMapper extends BaseMapper<PayLog> {
}
3)Service
接口:创建com.xz.mall.pay.service.PayLogService并添加记录日志方法
public interface PayLogService extends IService<PayLog> {
void log(PayLog payLog);
}
实现类:创建com.xz.mall.pay.service.impl.PayLogServiceImpl,并实现添加日志
@Service
public class PayLogServiceImpl extends ServiceImpl<PayLogMapper,PayLog> implements PayLogService {
@Autowired
private PayLogMapper payLogMapper;
/***
* 记录日志
* @param payLog
*/
@Transactional(rollbackFor = Exception.class)
@Override
public void log(PayLog payLog) {
//本地操作
int count = payLogMapper.insert(payLog);
}
}
4)Controller
创建com.xz.mall.pay.controller.WeixinPayController,调用增加日志方法,并向RocketMQ发送事务消息
@RestController
@RequestMapping(value = "/wx")
public class WeixinPayController {
@Autowired
private PayLogService payLogService;
@Autowired
private RocketMQTemplate rocketMQTemplate;
/***
* 记录支付结果
* 执行事务消息发送
*/
@GetMapping(value = "/result")
public RespResult payLog(){
//记录日志
PayLog payLog = new PayLog("123",1,"test","No001",new Date());
Message message = MessageBuilder.withPayload(JSON.toJSONString(payLog)).build();
rocketMQTemplate.sendMessageInTransaction("rocket","log",message,null);
return RespResult.ok();
}
}
5)事务消息监听
创建com.xz.mall.pay.mq.TransactionListenerImpl实现事务操作
@Component
@RocketMQTransactionListener(txProducerGroup = "rocket")
public class TransactionListenerImpl implements RocketMQLocalTransactionListener {
@Autowired
private PayLogService payLogService;
/***
* 发送prepare消息成功后回调该方法用于执行本地事务
* @param message:回传的消息,利用transactionId即可获取到该消息的唯一Id
* @param o:调用send方法时传递的参数,当send时候若有额外的参数可以传递到send方法中,这里能获取到
* @return
*/
@Override
public RocketMQLocalTransactionState executeLocalTransaction(Message message, Object o) {
try {
//================本地事务操作开始=====================================
//将o转成PayLog
String result = new String((byte[]) message.getPayload(),"UTF-8");
PayLog payLog = JSON.parseObject(result,PayLog.class);
payLogService.log(payLog);
//================本地事务操作结束=====================================
} catch (Exception e) {
//异常,消息回滚
e.printStackTrace();
return RocketMQLocalTransactionState.ROLLBACK;
}
return RocketMQLocalTransactionState.COMMIT;
}
/***
* 消息回查
* @param message
* @return
*/
@Override
public RocketMQLocalTransactionState checkLocalTransaction(Message message) {
return RocketMQLocalTransactionState.COMMIT;
}
}
事务消息监听
我们只需要在mall-order-service中监听消息即可,注意消息操作,需要引入如下包:
<!--rocketmq-->
<dependency>
<groupId>org.apache.rocketmq</groupId>
<artifactId>rocketmq-spring-boot-starter</artifactId>
<version>2.0.2</version>
</dependency>
1)bootstrap.yml配置
#RocketMQ消费者配置
rocketmq:
name-server: 192.168.xxx.xxx:9876
2)消费监听
创建com.xz.mall.order.mq.OrderResultListener实现监听
@Component
@RocketMQMessageListener(topic = "log", consumerGroup = "order-group")
public class OrderResultListener implements RocketMQListener,RocketMQPushConsumerLifecycleListener {
/***
* 监听消息
* 实现RocketMQPushConsumerLifecycleListener监听器之后,此方法不调用
* @param message
*/
@Override
public void onMessage(Object message) {
}
/***
* 消息监听
* @param consumer
*/
@Override
public void prepareStart(DefaultMQPushConsumer consumer) {
consumer.registerMessageListener(new MessageListenerConcurrently() {
@Override
public ConsumeConcurrentlyStatus consumeMessage(List<MessageExt> msgs, ConsumeConcurrentlyContext context) {
try {
for (MessageExt msg : msgs) {
String result = new String(msg.getBody(),"UTF-8");
System.out.println("result:"+result);
}
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
//消费状态
return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;
}
});
}
}
我们发送数据测试,消费端打印结果如下:
result:{"content":"test","createTime":1665920546000,"id":"123","payId":"No001","status":1}
总结
本篇用具体代码实现了一下RocketMQ中TransactionProducer(事务),完成了数据最终一致性分布式事务。