RabbitMQ解决分布式事务实例

1.产生事务条件如下:

确认生产者将信息投递到MQ服务器中(采用MQ确认机制)
    生产者向MQ发送消息失败,采用重试机制
确认消费者能正确的消费消息,采用手动ACK模式(注意幂等性问题)
    消费者消费消息失败,生产者无需回滚 
生产者和消费者都成功,但是生产者后续步骤出现异常,数据库事务回滚
    生产者同时投递到两个队列,第二个队列判断生产者数据是否插入数据库,未插入则执行数据库插入逻辑


项目背景:umz-merchant-platform 商户平台项目  --用于创建订单 生成订单表
        umz-pay                支付项目     --跟据订单表中的订单号生成对应的流水表

 2.实例项目构建

 3.部分代码

umz-merchant-platform 商户平台项目

#########发起订单请求#######################

package com.microservice.soa.controller;

import java.util.Date;

import javax.annotation.Resource;

import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController;

import com.microservice.soa.dao.order.EquipOrderDao;
import com.microservice.soa.model.EquipOrder;
import com.microservice.soa.service.order.EquipOrderService;
import com.microservice.soa.service.order.impl.EquipOrderServiceImpl;
import com.microservice.soa.util.Utils;
import com.microservice.soa.vo.EquipOrderRequest;

@RestController
public class EquipOrderController {
	
	@Resource
	private EquipOrderServiceImpl equipOrderService;

	@RequestMapping("createOrder")
	@ResponseBody
	public String createOrder(@RequestBody EquipOrderRequest equipOrder) {
		String result = equipOrderService.createOrder_tx(equipOrder);
		return result;
		
	}
}
##########################生成订单 发送端 消息提供者 ###########################

package com.microservice.soa.service.order.impl;

import java.math.BigDecimal;
import java.util.Date;
import java.util.UUID;

import javax.annotation.Resource;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.amqp.AmqpException;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.core.MessageBuilder;
import org.springframework.amqp.core.MessageProperties;
import org.springframework.amqp.rabbit.connection.CorrelationData;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import com.microservice.soa.dao.order.EquipOrderDao;
import com.microservice.soa.model.EquipOrder;
import com.microservice.soa.util.JsonToBean;
import com.microservice.soa.util.Utils;
import com.microservice.soa.vo.EquipOrderRequest;

import net.sf.json.JSONObject;

/**
 *  订单业务实现
 *  分布式事务思路解决方案
 *  	1.如果消费者消费消息失败,生产者是不需要回滚事务
 *  		解决方法:消费采用手动ack应答方式,采用MQ进行补偿重试机制,注意MQ补偿幂等问题。
 *  	2.如何确认生产者一定要将数据投递到MQ服务器中Confirm机制(确认应答机制)
 *  		如查生产者发送消息到MQ服务器失败
 *  		解决办法:使用生产者重试机制进行发消息。
 * @author jiajie
 *
 */
@Service("equipOrderService")
public class EquipOrderServiceImpl implements RabbitTemplate.ConfirmCallback {
	private static Logger log = LoggerFactory.getLogger(EquipOrderServiceImpl.class);
	@Resource
	private EquipOrderDao equipOrderDao;
	
	@Resource
	RabbitTemplate template;

//	@Override
	@Transactional
	public int insert(EquipOrderRequest equipOrderRequest) {
		// TODO Auto-generated method stub
		EquipOrder equipOrder = new EquipOrder();
		equipOrder.setOrderId(Utils.createUtil());   	 //生成uuid
		equipOrder.setOrderNo(equipOrderRequest.getOrderNo()); 				//生成订单号
		equipOrder.setCreateTime(new Date());
		equipOrder.setDeliveryTime(new Date());
		equipOrder.setOrderName(equipOrderRequest.getOrderName());
		equipOrder.setConsigneeProvince(equipOrderRequest.getConsigneeProvince());
		equipOrder.setConsigneeCity(equipOrderRequest.getConsigneeCity());
		equipOrder.setConsigneeArea(equipOrderRequest.getConsigneeArea());
		equipOrder.setConsigneeSite(equipOrderRequest.getConsigneeSite());
		equipOrder.setConsigneeTel(equipOrderRequest.getConsigneeTel());
		equipOrder.setOrderAmt(Long.parseLong(equipOrderRequest.getOrderAmt()));
		equipOrder.setOrderStatus("1");
		equipOrder.setFreightPrice(new BigDecimal(equipOrderRequest.getFreightPrice()));
		equipOrder.setGoodsPrice(new BigDecimal(equipOrderRequest.getGoodsPrice()));
		equipOrder.setGoodsNum(Integer.parseInt(equipOrderRequest.getGoodsNum()));
		equipOrder.setOrderName(equipOrderRequest.getOrderName());
		//插入订单表
//		equipOrderDao.insert(equipOrder);
		//模拟补尝 
		 int result = 1/0;
		//插入订单表 
		return equipOrderDao.insert(equipOrder);
	}
	
	/**
	 * 创建订单
	 * @param equipOrder
	 * @return
	 */
	@Transactional
	public String createOrder_tx(EquipOrderRequest equipOrderRequest) {
		try {
			String orderNo = Utils.getOrderIdByTime();
			equipOrderRequest.setOrderNo(orderNo);
			
			JSONObject json = JSONObject.fromObject(equipOrderRequest);
			String jsonString = json.toString();
			System.out.println("jsonString:" + jsonString);
			
			EquipOrder equipOrder = new EquipOrder();
			equipOrder.setOrderId(Utils.createUtil());   	 //生成uuid
			equipOrder.setOrderNo(equipOrderRequest.getOrderNo()); 				//生成订单号
			equipOrder.setCreateTime(new Date());
			equipOrder.setDeliveryTime(new Date());
			equipOrder.setOrderName(equipOrderRequest.getOrderName());
			equipOrder.setConsigneeProvince(equipOrderRequest.getConsigneeProvince());
			equipOrder.setConsigneeCity(equipOrderRequest.getConsigneeCity());
			equipOrder.setConsigneeArea(equipOrderRequest.getConsigneeArea());
			equipOrder.setConsigneeSite(equipOrderRequest.getConsigneeSite());
			equipOrder.setConsigneeTel(equipOrderRequest.getConsigneeTel());
			equipOrder.setOrderAmt(Long.parseLong(equipOrderRequest.getOrderAmt()));
			equipOrder.setOrderStatus("1");
			equipOrder.setFreightPrice(new BigDecimal(equipOrderRequest.getFreightPrice()));
			equipOrder.setGoodsPrice(new BigDecimal(equipOrderRequest.getGoodsPrice()));
			equipOrder.setGoodsNum(Integer.parseInt(equipOrderRequest.getGoodsNum()));
			equipOrder.setOrderName(equipOrderRequest.getOrderName());
			//插入订单表
//			equipOrderDao.insert(equipOrder);
			
			//插入订单表 
			equipOrderDao.insert(equipOrder);
			
			
			// 生产者发送消息的时候需要设置消息id
			Message message = MessageBuilder.withBody(jsonString.getBytes())
					.setContentType(MessageProperties.CONTENT_TYPE_JSON).setContentEncoding("utf-8")
					.setMessageId(UUID.randomUUID() + "").build();
			//回调
			this.template.setMandatory(true);
			this.template.setConfirmCallback(this);
			//构建回调返回的数据(消息ID)
			CorrelationData correlationData = new CorrelationData(orderNo);
			correlationData.setReturnedMessage(message);
			template.convertAndSend("orderChange","order.bar.test",message,correlationData);
			//模拟补尝 
			 int result = 1/0;
		} catch (AmqpException e) {
			// TODO Auto-generated catch block
//			e.printStackTrace();
			log.info("订单创建失败:原因【"+e.toString()+"】");
			return  "error";
		}
		
		return "success";
	}

	/**
	 * confirm 生产者确认机制 生产者往服务器发送消息的时候,采用应答机制
	 */
	@Override
	public void confirm(CorrelationData correlationData, boolean ack, String cause) {
		// TODO Auto-generated method stub
		log.info("消息ID:"+correlationData.getId()+"消息内容:"+correlationData.getReturnedMessage());
		
		try {
			String msg = new String(correlationData.getReturnedMessage().getBody());
			JSONObject json = JSONObject.fromObject(msg);
			EquipOrderRequest equipOrderRequest  = JsonToBean.json2Bean(json, EquipOrderRequest.class);
			
			if (ack) {
				log.info("消息发送成功!");
//				insert(equipOrderRequest);
			}else {
				createOrder_tx(equipOrderRequest);
				log.info("消息发送失败!"+cause);
			}
		} catch (Exception e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}

}
#############################事物补尝 队列监听 ################################

package com.microservice.soa.listener;

import java.io.IOException;
import java.math.BigDecimal;
import java.util.Date;
import java.util.Map;

import javax.annotation.Resource;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.amqp.support.AmqpHeaders;
import org.springframework.amqp.support.converter.Jackson2JsonMessageConverter;
import org.springframework.messaging.handler.annotation.Headers;
import org.springframework.stereotype.Component;

import com.alibaba.druid.util.DaemonThreadFactory;
import com.alibaba.druid.util.StringUtils;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.microservice.soa.dao.order.EquipOrderDao;
import com.microservice.soa.model.EquipOrder;
import com.microservice.soa.util.JsonToBean;
import com.microservice.soa.util.Utils;
import com.microservice.soa.vo.EquipOrderRequest;
import com.netflix.ribbon.proxy.annotation.Http.Header;
import com.rabbitmq.client.Channel;

import net.sf.json.JSONObject;



/**
 *  @RabbitListener 底层 使用Aop进行拦截,如果程序没有抛出异常,自动提交事务,
 *  				如果Aop使用异常通知拦截  获取异常信息的话,自动实现补偿机制,该消息会缓存到rabbitmq服务器存放,
 *  				会一直重试到不抛出异常为主。解决方式 修改重试机制策略 默认间隔5秒重试一次
 * 
 * 
 * 
* @ClassName: EmailMessageListener  
* @Description: TODO(消息处理监听类 补尝队列订单)  
* @author MAOJIAJIE  
* @date 2019年4月12日  
*
 */
@Component
public class OderMessageCompensationListener{
	private static Logger log = LoggerFactory.getLogger(OderMessageCompensationListener.class);
	
	@Resource
	private EquipOrderDao equipOrderDao;
	
	@RabbitListener(queues = {"orderCompensationQueue"})
	public void onMessage(Message message,@Headers Map<String, Object> header,Channel channel) throws Exception {
		// TODO Auto-generated method stub
		log.info("*************************开始订单补尝监听消息*************************");
		String messageBody = new String(message.getBody());
		ObjectMapper mapper = new ObjectMapper();
		log.info("message:"+messageBody);
		
		/**
		 * rabbitmq 默认情况下,如果消费者程序出现异常的情况下,会自动实现补偿机制 (重试机制)
		 * 队列服务器发送补尝请求
		 */
		try {
//			int i = 1/0;
			JSONObject json = JSONObject.fromObject(messageBody);
			EquipOrderRequest equipOrderRequest  = JsonToBean.json2Bean(json, EquipOrderRequest.class);
			log.info("接收到邮件消息:【" + equipOrderRequest.toString() + "】");
			if (StringUtils.isEmpty(equipOrderRequest.getOrderNo())) {
				// 手动签收消息,通知mq服务器端删除该消息
				channel.basicAck(message.getMessageProperties().getDeliveryTag(), false);
				return;
			}
			EquipOrder order = equipOrderDao.queryByOrderNoInfo(equipOrderRequest.getOrderNo());
			if (order!=null) {
				//订单已经存在 手动签收消息,通知mq服务器端删除该消息
				channel.basicAck(message.getMessageProperties().getDeliveryTag(), false);
				return;
			}
			log.info("订单号:"+equipOrderRequest.getOrderNo()+"开始补尝操作!");
			EquipOrder equipOrder = new EquipOrder();
			equipOrder.setOrderId(Utils.createUtil());   	 //生成uuid
			equipOrder.setOrderNo(equipOrderRequest.getOrderNo()); 				//生成订单号
			equipOrder.setCreateTime(new Date());
			equipOrder.setDeliveryTime(new Date());
			equipOrder.setOrderName(equipOrderRequest.getOrderName());
			equipOrder.setConsigneeProvince(equipOrderRequest.getConsigneeProvince());
			equipOrder.setConsigneeCity(equipOrderRequest.getConsigneeCity());
			equipOrder.setConsigneeArea(equipOrderRequest.getConsigneeArea());
			equipOrder.setConsigneeSite(equipOrderRequest.getConsigneeSite());
			equipOrder.setConsigneeTel(equipOrderRequest.getConsigneeTel());
			equipOrder.setOrderAmt(Long.parseLong(equipOrderRequest.getOrderAmt()));
			equipOrder.setOrderStatus("1");
			equipOrder.setFreightPrice(new BigDecimal(equipOrderRequest.getFreightPrice()));
			equipOrder.setGoodsPrice(new BigDecimal(equipOrderRequest.getGoodsPrice()));
			equipOrder.setGoodsNum(Integer.parseInt(equipOrderRequest.getGoodsNum()));
			equipOrder.setOrderName(equipOrderRequest.getOrderName());
			//插入订单表
//			equipOrderDao.insert(equipOrder);
			//插入订单表
			int result = equipOrderDao.insert(equipOrder);
			if (result>=0) { //插入成功
				//订单插入成功,手动向通道发送签收消息,通知mq服务器端删除该消息
				channel.basicAck(message.getMessageProperties().getDeliveryTag(), false);
				return;
			}
			
		} catch (Exception e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
//			log.error("发送邮件失败!:【" + e.toString() + "】");
			//拒绝消费消息(丢失消息) 给死信队列
			channel.basicNack(message.getMessageProperties().getDeliveryTag(), false,false);
		}
	}

}
###########################相关工具类##############################
package com.microservice.soa.util;

import java.lang.reflect.Method;
import java.lang.reflect.ParameterizedType;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;

import net.sf.json.JSONArray;
import net.sf.json.JSONException;
import net.sf.json.JSONObject;

public class JsonToBean {
	public static <T> T json2Bean(JSONObject json, Class<T> clazz) throws Exception {
        T t = clazz.newInstance();
        Method[] methods = clazz.getDeclaredMethods();
        if (methods == null || methods.length == 0)
            return null;

        for (Method method : methods) {
            String methodName = method.getName();
            System.out.println(methodName);
            if (methodName.startsWith("set")) {
                StringBuffer fieldName = new StringBuffer();

                // 形参类型:String long Product boolean double short int String List
                Class<?> ParamClazz = method.getParameterTypes()[0];

                // 通过set的method拿到字段名称name time delFlag birth price price2 age
                fieldName.append(methodName.substring(3, 4).toLowerCase())//字段首字母小写
                        .append(methodName.substring(4));

                if (!json.has(fieldName.toString())) {
                    continue;
                }

                // 常用基本类型 不含short,Date(支持String类型的 日期)
                if (ParamClazz == boolean.class || ParamClazz == Boolean.class) {
                    method.invoke(t, new Object[] { json.optBoolean(fieldName.toString(), false) });
                } else if (ParamClazz == int.class || ParamClazz == Integer.class) {
                    method.invoke(t, new Object[] { json.optInt(fieldName.toString(), 0) });
                } else if (ParamClazz == long.class || ParamClazz == Long.class) {
                    method.invoke(t, new Object[] { json.optLong(fieldName.toString(), 0) });
                } else if (ParamClazz == double.class || ParamClazz == Double.class) {
                    method.invoke(t, new Object[] { json.optDouble(fieldName.toString(), 0) });
                } else if (ParamClazz == String.class) {
                		method.invoke(t, new Object[] { json.optString(fieldName.toString(), "") });
                }else if(ParamClazz == Date.class){
                    	method.invoke(t, new Object[] { json.optString(fieldName.toString(),"") });
                }else if (List.class.isAssignableFrom(ParamClazz)) {//解析jsonArray为list
                    JSONArray array = json.getJSONArray(fieldName.toString());
                    List list = new ArrayList<>();
                    for(int i =0;array!=null && i<array.size();i++){
                    	
                        JSONObject jsonobj = array.getJSONObject(i); 

                        Class<?> listType = (Class<?>) ((ParameterizedType) clazz   // 获取List的泛型类型
                        	    .getDeclaredField(fieldName.toString()).getGenericType())
                                .getActualTypeArguments()[0];

                        Object obj = json2Bean(jsonobj, listType);
                        list.add(obj);
                    }
                    method.invoke(t,list);

                } else {//jsonObject和无法解析字段的判断
                    try{
                        JSONObject getjsonObj = json.getJSONObject(fieldName.toString());
                        if (getjsonObj != null) {
                        	Object obj = json2Bean(getjsonObj, ParamClazz);
                            method.invoke(t, obj);
						}
                        
                    }catch(JSONException e){
                        e.printStackTrace();
                        continue;
                    }
                }
            }
        }
        return t;
    }
}

4.rabbitmq配置文件

package com.microservice.soa.conf;


import java.util.HashMap;
import java.util.Map;

import org.springframework.amqp.core.*;
import org.springframework.amqp.support.converter.Jackson2JsonMessageConverter;
import org.springframework.amqp.support.converter.MessageConverter;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class RabbitConfig {
    //为了方便演示我都使用的public
    //队列
    public static final String ORDER_QUEUE = "orderQueue";
    //死信队列
    public static final String ORDER_DEAD_QUEUE = "orderDeadQueue";
    //交换机
    public static final String EXCHANGE_TOPIC_NAME = "orderChange";
    //死信交换机
    public static final String EXCHANGE_DEAD_TOPIC_NAME = "orderDeadChange";
    //#路由规则 匹配 email后面 所有的键
    public static final String QUEUE_BING_ROUTINGKEY="order.#";
    //绑定死信路由(死信队列与死信交换机之前绑定关系)
    public static final String QUEUE_BING_EMAIL_DEAD_ROUTINGKEY="order.queue.dead.#";
    //绑定死信路由(消费队列与死储交换机绑定)
    public static final String QUEUE_BING_DEAD_ROUTING_ROUTINGKEY="order.deal.*";
    
    //补偿队列
    public static final String ORDER_COMPENSATION_QUEUE = "orderCompensationQueue";
    
   
    
    //# 提供者发送消息指定的 路由键(邮件)
    public static final String PRODUCER_ROUTINGKEY="order.bar.test";
    
    
    /**
     * 死信队列 交换机标识符
     */
    private static final String DEAD_LETTER_QUEUE_KEY = "x-dead-letter-exchange";
    /**
     * 死信队列交换机绑定键标识符
     */
    private static final String DEAD_LETTER_ROUTING_KEY = "x-dead-letter-routing-key";
    

    //声明邮件队列
    @Bean(ORDER_QUEUE)
    public Queue QUEUE_NEWS(){    //邮件队列
    	Map<String, Object> args = new HashMap<String, Object>(2);
//    	//消费队列---------------绑定死信交换机 (x-dead-letter-exchange    声明  死信交换机)
//    	args.put(DEAD_LETTER_QUEUE_KEY,EXCHANGE_DEAD_TOPIC_NAME);
//    	//消费队列---------------绑定死信路由  x-dead-letter-routing-key    声明 死信路由键
//    	args.put(DEAD_LETTER_ROUTING_KEY, QUEUE_BING_DEAD_ROUTING_ROUTINGKEY);
//    
//        return new Queue(ORDER_QUEUE, true, false, false,args);
    	return new Queue(ORDER_QUEUE);
    }
    
    //补尝队列
    @Bean(ORDER_COMPENSATION_QUEUE)
    public Queue ORDER_COMPENSATION_QUEUE() {
    	return new Queue(ORDER_COMPENSATION_QUEUE);
    }

    //声明交换机
    @Bean(EXCHANGE_TOPIC_NAME)
    public Exchange EXCHANGE_TOPIC_INFORM(){
        //声明了一个Topic类型的交换机,durable是持久化(重启rabbitmq这个交换机不会被自动删除)
        return ExchangeBuilder.topicExchange(EXCHANGE_TOPIC_NAME).durable(true).build();
    }

    //声明ORDER_QUEUE队列和交换机绑定关系,并且指定RoutingKey
    @Bean
    public Binding NEWS_BINDING_TOPIC(@Qualifier(ORDER_QUEUE) Queue queue,
                                      @Qualifier(EXCHANGE_TOPIC_NAME) Exchange exchange){
        return BindingBuilder.bind(queue).to(exchange).with(QUEUE_BING_ROUTINGKEY).noargs();
    }
    
    //声明ORDER_COMPENSATION_QUEUE补尝队列和交换机绑定关系,并且指定与ORDER_QUEUE队列同一个RoutingKey由路key
    @Bean
    public Binding NEWS_BINDING_COMPENSATION_TOPIC(@Qualifier(ORDER_COMPENSATION_QUEUE) Queue queue,
                                      @Qualifier(EXCHANGE_TOPIC_NAME) Exchange exchange){
        return BindingBuilder.bind(queue).to(exchange).with(QUEUE_BING_ROUTINGKEY).noargs();
    }
    
    
    
//    /**
//     * 创建死信队列
//     * @return
//     */
//    @Bean
//	public Queue deadQueue() {
//		Queue queue = new Queue(ORDER_DEAD_QUEUE, true);
//		return queue;
//	}
//
//    /**
//     * 创建死信交换机
//     * @return
//     */
//	@Bean
//	public DirectExchange deadExchange() {
//		return new DirectExchange(EXCHANGE_DEAD_TOPIC_NAME);
//	}
//
//	@Bean
//	public Binding bindingDeadExchange(Queue deadQueue, DirectExchange deadExchange) {
//		return BindingBuilder.bind(deadQueue).to(deadExchange).with(QUEUE_BING_DEAD_ROUTING_ROUTINGKEY);
//	} 
    
    /**
     * rabbit 数据传输方式JSON
     * @return
     */
    @Bean
    public MessageConverter messageConverter(){
        return new Jackson2JsonMessageConverter();
    }
   
}

5.application.yml文件

# native
#应用端口及应用名称 商户平台项目
server:
  port: 8010
  servlet:
    context-path: /umz-merchant-platform


#数据库配置
spring:
  datasource:
    url: jdbc:mysql://localhost:3306/umz_db?useUnicode=true&characterEncoding=utf-8
    driver-class-name: com.mysql.jdbc.Driver
    username: root
    password: 12345678
    type: com.alibaba.druid.pool.DruidDataSource
    tomcat:
      initial-size: 1
      max-wait: 60000
      min-idle: 3
      max-age: 60000
      time-between-eviction-runs-millis: 60000
      min-evictable-idle-time-millis: 300000
      validation-query: SELECT 1 FROM DUAL
      test-while-idle: true
      test-on-borrow: false
      test-on-return: false
  mvc:
    view:
      prefix: /WEB-INF/
      suffix: .jsp
  http:
    encoding:
      charset: UTF-8
      enabled: true
      force: true 
  #邮箱服务器
  mail:
    host: smtp.qq.com
    #发送邮箱用户名
    username: 2659217497@qq.com
    #此处为客户端授权密码
    password: kmwptnlpeznjebaj
  #编码集
    default-encoding: UTF-8
  #必须有!邮箱授权开启,不然报错
  properties:
    mail:
      smtp:
        auth: true
          #服务器地址校正
        localhost: smtp.qq.com

  
  application:
    name: umz-merchant-platform   #客户端服务名

  #消息队列
  rabbitmq:
    host: 127.0.0.1
    port: 5672
    username: admin
    password: admin
    ### 地址
    virtual-host: /
    ##开启消息确认机制 confirms
    publisher-confirms: true
    publisher-returns: true



#注册信息
eureka:
  instance:
    hostname: localhost  #eureka客户端主机实例名称
    instance-id: umz-merchant-platform:8763 #客户端实例名称
    prefer-ip-address: true #显示IP
    lease-renewal-interval-in-seconds: 1 #eureka 客户端向服务端发送心跳间隔时间  单位秒
    lease-expiration-duration-in-seconds: 2 #eureka服务端收到最后一次等笔
  client:
    service-url:
      defaultZone: http://localhost:8761/eureka/
      # 单机 defaultZone: http://localhost:2001/eureka   #把服务注册到eureka注册中心
      #defaultZone: http://eureka2001.java1234.com:2001/eureka/,http://eureka2002.java1234.com:2002/eureka/,http://eureka2003.java1234.com:2003/eureka/ # 集群

mybatis:
  mapper-locations:
  - classpath:com/microservice/soa/dao/*.xml
  config-location: classpath:com/microservice/mybatis-config.xml
  type-aliases-package: com.microservice.soa.model

6.pom文件

<?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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
	<modelVersion>4.0.0</modelVersion>
	<parent>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-parent</artifactId>
		<version>2.2.6.RELEASE</version>
		<relativePath/> <!-- lookup parent from repository -->
	</parent>
	<groupId>com.microservice</groupId>
	<artifactId>umz-merchant-platform</artifactId>
	<version>0.0.1-SNAPSHOT</version>
	<packaging>war</packaging>
	<name>umz-merchant-platform</name>
	<description>Demo project for Spring Boot</description>

	<properties>
		<java.version>1.8</java.version>
		<zkclient.version>0.10</zkclient.version>
		<spring-cloud.version>Hoxton.SR4</spring-cloud.version>
	</properties>

	<dependencies>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-web</artifactId>
		</dependency>

		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-tomcat</artifactId>
			<scope>provided</scope>
		</dependency>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-test</artifactId>
			<scope>test</scope>
			<exclusions>
				<exclusion>
					<groupId>org.junit.vintage</groupId>
					<artifactId>junit-vintage-engine</artifactId>
				</exclusion>
			</exclusions>
		</dependency>
		
		<dependency>
			<groupId>org.springframework.cloud</groupId>
			<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
		</dependency>
		
		<dependency>
		    <groupId>com.rabbitmq</groupId>
		    <artifactId>amqp-client</artifactId>
		    <version>5.5.1</version>
		</dependency>
		
		<dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-mail</artifactId>
        </dependency>
        
         <dependency>
		    <groupId>com.alibaba</groupId>
		    <artifactId>druid</artifactId>
		    <version>1.1.22</version>
		</dependency>  
		
		<dependency>
			<groupId>org.mybatis.spring.boot</groupId>
			<artifactId>mybatis-spring-boot-starter</artifactId>
			<version>2.1.2</version>
		</dependency>
		
		<dependency>
			<groupId>mysql</groupId>
			<artifactId>mysql-connector-java</artifactId>
			<scope>runtime</scope>
		</dependency>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-tomcat</artifactId>
			<scope>provided</scope>
		</dependency>
		
		<dependency>
			<groupId>net.sf.json-lib</groupId>
			<artifactId>json-lib</artifactId>
			<version>2.4</version>
			<classifier>jdk15</classifier>
		</dependency>
		
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-amqp</artifactId>
		</dependency>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-data-redis</artifactId>
		</dependency>
		
		<!--日志相关依赖-->                                                          
	   <dependency>                                                           
	       <groupId>org.springframework.boot</groupId>                        
	       <artifactId>spring-boot-starter-log4j2</artifactId>                
	   </dependency>                

		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-test</artifactId>
			<scope>test</scope>
			<exclusions>
				<exclusion>
					<groupId>org.junit.vintage</groupId>
					<artifactId>junit-vintage-engine</artifactId>
				</exclusion>
			</exclusions>
		</dependency>
		
		<dependency>
         <groupId>com.101tec</groupId>
         <artifactId>zkclient</artifactId>
         <version>${zkclient.version}</version>
     </dependency>
	</dependencies>

	<dependencyManagement>
		<dependencies>
			<dependency>
				<groupId>org.springframework.cloud</groupId>
				<artifactId>spring-cloud-dependencies</artifactId>
				<version>${spring-cloud.version}</version>
				<type>pom</type>
				<scope>import</scope>
			</dependency>
		</dependencies>
	</dependencyManagement>

	<build>
		<plugins>
			<plugin>
				<groupId>org.springframework.boot</groupId>
				<artifactId>spring-boot-maven-plugin</artifactId>
			</plugin>
		</plugins>
	</build>

</project>

7.umz-pay 支付项目

##################消费者 跟据订单表中的订单号 生成 支付流水表##########################
package com.microservice.soa.listener;

import java.io.IOException;
import java.util.Map;

import javax.annotation.Resource;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.amqp.support.AmqpHeaders;
import org.springframework.amqp.support.converter.Jackson2JsonMessageConverter;
import org.springframework.messaging.handler.annotation.Headers;
import org.springframework.stereotype.Component;

import com.alibaba.druid.util.DaemonThreadFactory;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.microservice.soa.dto.EquipOrderDto;
import com.microservice.soa.model.TradeWater;
import com.microservice.soa.service.trade.TradeWaterService;
import com.microservice.soa.util.JsonToBean;
import com.microservice.soa.util.Utils;
import com.netflix.ribbon.proxy.annotation.Http.Header;
import com.rabbitmq.client.Channel;

import net.sf.json.JSONObject;



/**
 *  @RabbitListener 底层 使用Aop进行拦截,如果程序没有抛出异常,自动提交事务,
 *  				如果Aop使用异常通知拦截  获取异常信息的话,自动实现补偿机制,该消息会缓存到rabbitmq服务器存放,
 *  				会一直重试到不抛出异常为主。解决方式 修改重试机制策略 默认间隔5秒重试一次
 * 
 * 
 * 
* @ClassName: EmailMessageListener  
* @Description: TODO(消息处理监听类 订单)  
* @author MAOJIAJIE  
* @date 2019年4月12日  
*
 */
@Component
public class OderToTradeMessageListener{
	private static Logger log = LoggerFactory.getLogger(OderToTradeMessageListener.class);
	
	@Resource
	private TradeWaterService tradeWaterService;
	
	@RabbitListener(queues = {"orderQueue"})
	public void onMessage(Message message,@Headers Map<String, Object> header,Channel channel) throws Exception {
		// TODO Auto-generated method stub
		log.info("*************************开始订单监听消息*************************");
		String messageBody = new String(message.getBody());
		ObjectMapper mapper = new ObjectMapper();
		System.out.println(messageBody);
		
		/**
		 * rabbitmq 默认情况下,如果消费者程序出现异常的情况下,会自动实现补偿机制 (重试机制)
		 * 队列服务器发送补尝请求
		 */
		try {
//			int i = 1/0;
			JSONObject json = JSONObject.fromObject(messageBody);
			EquipOrderDto orderInfoDto  = JsonToBean.json2Bean(json, EquipOrderDto.class);
//			 Jackson2JsonMessageConverter jackson2JsonMessageConverter =new Jackson2JsonMessageConverter();
//			 EquipOrderDto equipOrderDto = (EquipOrderDto)jackson2JsonMessageConverter.fromMessage(message);
//			Email email = mapper.readValue(messageBody, Email.class);
			log.info("接收到订单消息:【" + orderInfoDto.toString() + "】");
			//调用支付sdk
			
			//插入交易流水表
			TradeWater tradeWater = new TradeWater();
			tradeWater.setMtwId(Utils.createUtil());		   //uuid
			tradeWater.setOrdNo(orderInfoDto.getOrderNo());   //订单号
			tradeWater.setTradeAmt(tradeWater.getTradeAmt());  //交易金额
			tradeWater.setCreateTime(Utils.formatDateStr());   //创建时间 
			tradeWater.setTradeTime(Utils.formatDateStr());    //交易时间
			tradeWater.setTransType("2101");                   //消费类型
			tradeWater.setPayChannel("ALIPAY");                //交易码类型
			int result = tradeWaterService.insertTradeWater(tradeWater);
			if (result>=0) {
				// 手动签收消息,通知mq服务器端删除该消息
				channel.basicAck(message.getMessageProperties().getDeliveryTag(), false);
//				return;
			}
			
			
		} catch (Exception e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
//			log.error("发送邮件失败!:【" + e.toString() + "】");
			//拒绝消费消息(丢失消息) 给死信队列
			channel.basicNack(message.getMessageProperties().getDeliveryTag(), false,false);
		}
	}

}

8.application.yml配置文件

# native
#应用端口及应用名称 支付项目
server:
  port: 8011
  servlet:
    context-path: /umz-pay


#数据库配置
spring:
  datasource:
    url: jdbc:mysql://localhost:3306/umz_pay_db?useUnicode=true&characterEncoding=utf-8
    driver-class-name: com.mysql.jdbc.Driver
    username: root
    password: 12345678
    type: com.alibaba.druid.pool.DruidDataSource
    tomcat:
      initial-size: 1
      max-wait: 60000
      min-idle: 3
      max-age: 60000
      time-between-eviction-runs-millis: 60000
      min-evictable-idle-time-millis: 300000
      validation-query: SELECT 1 FROM DUAL
      test-while-idle: true
      test-on-borrow: false
      test-on-return: false
  mvc:
    view:
      prefix: /WEB-INF/
      suffix: .jsp
  http:
    encoding:
      charset: UTF-8
      enabled: true
      force: true 
  #邮箱服务器
  mail:
    host: smtp.qq.com
    #发送邮箱用户名
    username: 2659217497@qq.com
    #此处为客户端授权密码
    password: kmwptnlpeznjebaj
  #编码集
    default-encoding: UTF-8
  #必须有!邮箱授权开启,不然报错
  properties:
    mail:
      smtp:
        auth: true
          #服务器地址校正
        localhost: smtp.qq.com

  
  application:
    name: umz-pay   #客户端服务名

  #消息队列
  rabbitmq:
    host: 127.0.0.1
    port: 5672
    username: admin
    password: admin
    ### 地址
    virtual-host: /



#注册信息
eureka:
  instance:
    hostname: localhost  #eureka客户端主机实例名称
    instance-id: umz-pay:8764 #客户端实例名称
    prefer-ip-address: true #显示IP
    lease-renewal-interval-in-seconds: 1 #eureka 客户端向服务端发送心跳间隔时间  单位秒
    lease-expiration-duration-in-seconds: 2 #eureka服务端收到最后一次等笔
  client:
    service-url:
      defaultZone: http://localhost:8761/eureka/
      # 单机 defaultZone: http://localhost:2001/eureka   #把服务注册到eureka注册中心
      #defaultZone: http://eureka2001.java1234.com:2001/eureka/,http://eureka2002.java1234.com:2002/eureka/,http://eureka2003.java1234.com:2003/eureka/ # 集群

mybatis:
  mapper-locations:
  - classpath:com/microservice/soa/dao/*.xml
  config-location: classpath:com/microservice/mybatis-config.xml
  type-aliases-package: com.microservice.soa.model

9.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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
	<modelVersion>4.0.0</modelVersion>
	<parent>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-parent</artifactId>
		<version>2.2.6.RELEASE</version>
		<relativePath/> <!-- lookup parent from repository -->
	</parent>
	<groupId>com.microservice</groupId>
	<artifactId>umz-pay</artifactId>
	<version>0.0.1-SNAPSHOT</version>
	<packaging>war</packaging>
	<name>umz-pay</name>
	<description>Demo project for Spring Boot</description>

	<properties>
		<java.version>1.8</java.version>
		<zkclient.version>0.10</zkclient.version>
		<spring-cloud.version>Hoxton.SR4</spring-cloud.version>
	</properties>

	<dependencies>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-web</artifactId>
		</dependency>

		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-tomcat</artifactId>
			<scope>provided</scope>
		</dependency>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-test</artifactId>
			<scope>test</scope>
			<exclusions>
				<exclusion>
					<groupId>org.junit.vintage</groupId>
					<artifactId>junit-vintage-engine</artifactId>
				</exclusion>
			</exclusions>
		</dependency>
		
		<dependency>
			<groupId>org.springframework.cloud</groupId>
			<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
		</dependency>
		
		<dependency>
		    <groupId>com.rabbitmq</groupId>
		    <artifactId>amqp-client</artifactId>
		    <version>5.5.1</version>
		</dependency>
		
		<dependency>
			<groupId>net.sf.json-lib</groupId>
			<artifactId>json-lib</artifactId>
			<version>2.4</version>
			<classifier>jdk15</classifier>
		</dependency>
		
		<dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-mail</artifactId>
        </dependency>
        
         <dependency>
		    <groupId>com.alibaba</groupId>
		    <artifactId>druid</artifactId>
		    <version>1.1.22</version>
		</dependency>  
		
		<dependency>
			<groupId>org.mybatis.spring.boot</groupId>
			<artifactId>mybatis-spring-boot-starter</artifactId>
			<version>2.1.2</version>
		</dependency>
		
		<dependency>
			<groupId>mysql</groupId>
			<artifactId>mysql-connector-java</artifactId>
			<scope>runtime</scope>
		</dependency>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-tomcat</artifactId>
			<scope>provided</scope>
		</dependency>
		
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-amqp</artifactId>
		</dependency>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-data-redis</artifactId>
		</dependency>
		
		<!--日志相关依赖-->                                                          
	   <dependency>                                                           
	       <groupId>org.springframework.boot</groupId>                        
	       <artifactId>spring-boot-starter-log4j2</artifactId>                
	   </dependency>                

		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-test</artifactId>
			<scope>test</scope>
			<exclusions>
				<exclusion>
					<groupId>org.junit.vintage</groupId>
					<artifactId>junit-vintage-engine</artifactId>
				</exclusion>
			</exclusions>
		</dependency>
		
		<dependency>
         <groupId>com.101tec</groupId>
         <artifactId>zkclient</artifactId>
         <version>${zkclient.version}</version>
     </dependency>
	</dependencies>

	<dependencyManagement>
		<dependencies>
			<dependency>
				<groupId>org.springframework.cloud</groupId>
				<artifactId>spring-cloud-dependencies</artifactId>
				<version>${spring-cloud.version}</version>
				<type>pom</type>
				<scope>import</scope>
			</dependency>
		</dependencies>
	</dependencyManagement>

	<build>
		<plugins>
			<plugin>
				<groupId>org.springframework.boot</groupId>
				<artifactId>spring-boot-maven-plugin</artifactId>
			</plugin>
		</plugins>
	</build>

</project>

10.测试

 

订单插入异常时进入补尝队列 

支付项目执行监听订单信息

数据库

订单表

 交易流水表

项目下载 地址:https://download.csdn.net/download/qq_31987649/12394885

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值