1.引入Mavan 依赖:
2.
@Data public class MsgTxtBo implements Serializable { private long orderNo; private int productNo; private String msgId; }
2.mq 消费者:
MqConsumer
@Component @Slf4j public class MqConsumer { /**队列名称*/ public static final String ORDER_TO_PRODUCT_QUEUE_NAME = "order-to-product.queue"; public static final String LOCK_KEY="LOCK_KEY"; @Autowired private IProductService productService; @Autowired private MsgContentMapper msgContentMapper; @Autowired private RedisTemplate redisTemplate; /** * 没有加分布式锁的版本,可能存在重复消费问题 * @param message * @param channel * @throws IOException @RabbitListener(queues = {ORDER_TO_PRODUCT_QUEUE_NAME}) @RabbitHandler public void consumerMsg(Message message, Channel channel) throws IOException { ObjectMapper objectMapper = new ObjectMapper(); MsgTxtBo msgTxtBo = objectMapper.readValue(message.getBody(),MsgTxtBo.class); log.info("消费消息:{}",msgTxtBo); Long deliveryTag = (Long) message.getMessageProperties().getDeliveryTag(); try { //更新消息表也业务表 productService.updateProductStore(msgTxtBo); System.out.println(1/0); //消息签收 channel.basicAck(deliveryTag,false); }catch (Exception e) { //更新msg表为消费失败 //更新消息表状态 MessageContent messageContent = new MessageContent(); messageContent.setMsgId(msgTxtBo.getMsgId()); messageContent.setUpdateTime(new Date()); messageContent.setMsgStatus(MsgStatusEnum.CONSUMER_FAIL.getCode()); msgContentMapper.updateMsgStatus(messageContent); channel.basicReject(deliveryTag,false); } } */ @RabbitListener(queues = {ORDER_TO_PRODUCT_QUEUE_NAME}) @RabbitHandler public void consumerMsgWithLock(Message message, Channel channel) throws IOException { ObjectMapper objectMapper = new ObjectMapper(); MsgTxtBo msgTxtBo = objectMapper.readValue(message.getBody(), MsgTxtBo.class); Long deliveryTag = (Long) message.getMessageProperties().getDeliveryTag(); if (redisTemplate.opsForValue().setIfAbsent(LOCK_KEY + msgTxtBo.getMsgId(), msgTxtBo.getMsgId())) { log.info("消费消息:{}", msgTxtBo); try { //更新消息表也业务表 productService.updateProductStore(msgTxtBo); //消息签收 System.out.println(1/0); channel.basicAck(deliveryTag, false); } catch (Exception e) { /** * 更新数据库异常说明业务没有操作成功需要删除分布式锁 */ if (e instanceof BizExp) { BizExp bizExp = (BizExp) e; log.info("数据业务异常:{},即将删除分布式锁", bizExp.getErrMsg()); //删除分布式锁 redisTemplate.delete(LOCK_KEY); } //更新消息表状态 MessageContent messageContent = new MessageContent(); messageContent.setMsgStatus(MsgStatusEnum.CONSUMER_FAIL.getCode()); messageContent.setUpdateTime(new Date()); messageContent.setErrCause(e.getMessage()); messageContent.setMsgId(msgTxtBo.getMsgId()); msgContentMapper.updateMsgStatus(messageContent); channel.basicReject(deliveryTag,false); } } else { log.warn("请不要重复消费消息{}", msgTxtBo); channel.basicReject(deliveryTag,false); } } }
config 文件配置:
@Slf4j @Configuration @MapperScan(basePackages = {"com.tuling.mapper"}) @EnableConfigurationProperties(value = DruidDataSourceProperties.class) public class SpringMyBatisConfig { @Autowired private DruidDataSourceProperties druidDataSourceProperties; @Bean public DataSource dataSource() throws SQLException { System.out.println(druidDataSourceProperties); DruidDataSource druidDataSource = new DruidDataSource(); druidDataSource.setUsername(druidDataSourceProperties.getUsername()); druidDataSource.setPassword(druidDataSourceProperties.getPassword()); druidDataSource.setUrl(druidDataSourceProperties.getJdbcUrl()); druidDataSource.setDriverClassName(druidDataSourceProperties.getDriverClassName()); druidDataSource.setInitialSize(druidDataSourceProperties.getInitialSize()); druidDataSource.setMinIdle(druidDataSourceProperties.getMinIdle()); druidDataSource.setMaxActive(druidDataSourceProperties.getMaxActive()); druidDataSource.setMaxWait(druidDataSourceProperties.getMaxWait()); druidDataSource.setTestOnBorrow(true); druidDataSource.setTestOnReturn(true); druidDataSource.setFilters(druidDataSourceProperties.getFilters()); druidDataSourceProperties.setPoolPreparedStatements(druidDataSourceProperties.isPoolPreparedStatements()); return druidDataSource; } @Bean public PlatformTransactionManager transactionManager() throws SQLException { PlatformTransactionManager transactionManager = new DataSourceTransactionManager(dataSource()); return transactionManager; } }: 实体:
Message:
@Data public class MessageContent { private String msgId; private long orderNo; private Date createTime; private Date updateTime; private Integer msgStatus; private String exchange; private String routingKey; private String errCause; private Integer maxRetry; private Integer currentRetry=0; private Integer productNo; }
@Data public class ProductInfo { private Integer productNo; private String productName; private String productNum; }
enumneration:
@Getter public enum MsgStatusEnum { SENDING(0,"发送中"), SENDING_SUCCESS(1,"消息发送成功"), SENDING_FAIL(2,"消息发送失败"), CONSUMER_SUCCESS(3,"消费成功"), CONSUMER_FAIL(4,"消费失败"); private Integer code; private String msgStatus; MsgStatusEnum(Integer code, String msgStatus) { this.code = code; this.msgStatus = msgStatus; } }
exception:
@Data public class BizExp extends RuntimeException { private Integer code; private String errMsg; public BizExp(Integer code,String errMsg) { super(); this.code = code; this.errMsg = errMsg; } }
map 文件:
/** * @vlog: 高于生活,源于生活 * @desc: 类的描述:消息内容mapper * @author: smlz * @createDate: 2019/10/11 16:14 * @version: 1.0 */ public interface MsgContentMapper { /** * 方法实现说明:保存消息 * @author:smlz * @param messageContent:消息对象 * @return: * @date:2019/10/11 16:16 */ int saveMsgContent(MessageContent messageContent); int updateMsgStatus(MessageContent messageContent); List<MessageContent> qryNeedRetryMsg(@Param("msgStatus") Integer status, @Param("timeDiff") Integer timeDiff); void updateMsgRetryCount(String msgId); }
public interface ProductInfoMapper { int updateProductStoreById(Integer productId); }
@Service @Slf4j public class ProductServiceImpl implements IProductService { @Autowired private ProductInfoMapper productInfoMapper; @Autowired private MsgContentMapper msgContentMapper; @Transactional @Override public boolean updateProductStore(MsgTxtBo msgTxtBo) { boolean updateFlag = true; try{ //更新库存 productInfoMapper.updateProductStoreById(msgTxtBo.getProductNo()); //更新消息表状态 MessageContent messageContent = new MessageContent(); messageContent.setMsgId(msgTxtBo.getMsgId()); messageContent.setUpdateTime(new Date()); messageContent.setMsgStatus(MsgStatusEnum.CONSUMER_SUCCESS.getCode()); msgContentMapper.updateMsgStatus(messageContent); //System.out.println(1/0); }catch (Exception e) { log.error("更新数据库失败:{}",e); throw new BizExp(0,"更新数据库异常"); } return updateFlag; } }
support:
package com.tuling.support; import org.springframework.boot.context.properties.ConfigurationProperties; /** * Created by smlz on 2019/3/22. */ @ConfigurationProperties(prefix = "spring.datasource.druid") public class DruidDataSourceProperties { private String username; private String password; public String getFilters() { return filters; } public void setFilters(String filters) { this.filters = filters; } public String filters; public String getUsername() { return username; } public void setUsername(String username) { this.username = username; } public String getPassword() { return password; } public void setPassword(String password) { this.password = password; } public String getJdbcUrl() { return jdbcUrl; } public void setJdbcUrl(String jdbcUrl) { this.jdbcUrl = jdbcUrl; } public String getDriverClassName() { return driverClassName; } public void setDriverClassName(String driverClassName) { this.driverClassName = driverClassName; } public Integer getInitialSize() { return initialSize; } public void setInitialSize(Integer initialSize) { this.initialSize = initialSize; } public Integer getMaxActive() { return maxActive; } public void setMaxActive(Integer maxActive) { this.maxActive = maxActive; } public Integer getMinIdle() { return minIdle; } public void setMinIdle(Integer minIdle) { this.minIdle = minIdle; } public long getMaxWait() { return maxWait; } public void setMaxWait(long maxWait) { this.maxWait = maxWait; } public boolean isPoolPreparedStatements() { return poolPreparedStatements; } public void setPoolPreparedStatements(boolean poolPreparedStatements) { this.poolPreparedStatements = poolPreparedStatements; } private String jdbcUrl; private String driverClassName; private Integer initialSize; private Integer maxActive; private Integer minIdle; private long maxWait; private boolean poolPreparedStatements; }