mq
1.RmqMessageListener 主要是 onMessage 方法的策略模式
import cn.hutool.core.date.DateUtil; import cn.hutool.core.net.NetUtil; import cn.hutool.json.JSONUtil; import com.cloud.cang.cache.redis.ICached; import com.cloud.cang.core.mq.service.MessageService; import com.cloud.cang.core.rmq.interfaces.HandlerEnum; import com.cloud.cang.core.rmq.interfaces.RmqMessageHandler; import com.cloud.cang.core.rmq.message.RmqMessage; import com.cloud.cang.core.utils.CoreUtils; import com.cloud.cang.core.utils.DingTalkUtils; import com.cloud.cang.lock.JedisLock; import com.rabbitmq.client.Channel; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.amqp.core.Message; import org.springframework.amqp.rabbit.listener.api.ChannelAwareMessageListener; import org.springframework.context.ApplicationContext; import redis.clients.jedis.JedisCluster; import java.io.IOException; import java.lang.reflect.ParameterizedType; import java.lang.reflect.Type; import java.util.HashMap; import java.util.Map; /** * @program: 37cang-云平台 * @description: * @author: qzg * @create: 2019-11-13 16:09 **/ public class RmqMessageListener implements ChannelAwareMessageListener { public static final String CHART_SET = "UTF-8"; private static final String MQ_RECEIVE_FAIL_ID = "mq_receive_fail_id_"; private static final Logger logger = LoggerFactory.getLogger(RmqMessageListener.class); ICached iCached; MessageService messageService; ApplicationContext applicationContext; Map<String,Class<?>> handlerMap = new HashMap<>(); // mq消费失败次数锁 JedisLock failCountLock = null; public RmqMessageListener(ApplicationContext applicationContext){ this.applicationContext = applicationContext; this.iCached = applicationContext.getBean(ICached.class); this.messageService = applicationContext.getBean(MessageService.class); failCountLock = new JedisLock( applicationContext.getBean(JedisCluster.class), MQ_RECEIVE_FAIL_ID); } @Override public void onMessage(Message message, Channel channel){ String body = ""; RmqMessage rmqMessage = null; try { body = new String(message.getBody(), CHART_SET); logger.info("<== 接收MQ消息:{}", body); rmqMessage = JSONUtil.toBean(body, RmqMessage.class); } catch (Exception e) { e.printStackTrace(); logger.error("MQ非法消息, 直接移除队列:{}",body); this.basicACK(message,channel); return; } try { Map<String, RmqMessageHandler> beansOfType = applicationContext.getBeansOfType(RmqMessageHandler.class); for(RmqMessageHandler handler: beansOfType.values()){ // 1, 判断是哪个Handler处理 if(!handler.matchHandler(rmqMessage.getQueueName())) { continue; } // 2, RmqMessage的message确定类型 this.convertMessage(handler,rmqMessage); // 3, 执行RmqMessageHandler.handler() HandlerEnum ack = handler.handler(rmqMessage); // 4, ACK this.confirmAck(ack,message,rmqMessage,channel); } } catch (Exception e) { logger.error("Handler处理消息异常,{}", e); this.confirmAck(HandlerEnum.ACK_BIZ_FAIL,message,rmqMessage,channel); } } @Override public void onMessage(Message message) {} private void convertMessage(RmqMessageHandler handler, RmqMessage rmqMessage){ try { Class clazz = this.dectectedParamType(handler); rmqMessage.setMessage(JSONUtil.toBean(JSONUtil.toJsonStr(rmqMessage.getMessage()),clazz)); } catch (Exception e) { e.printStackTrace(); } } private void confirmAck(HandlerEnum ack, Message message, RmqMessage rmqMessage, Channel channel){ try { // 正常消费,MQ移除队列 if(ack == HandlerEnum.ACK) { this.basicACK(message,channel); this.updateMessageSuccess(rmqMessage); return; } // 更新MQ_MESSAGE消费成功, 业务处理失败 if(ack == HandlerEnum.ACK_BIZ_FAIL) { this.basicACK(message,channel); this.updateMessageSuccess_BizFail(rmqMessage); return; } // 消费三次失败后, MQ移除队列, 放入死信队列 if(ack == HandlerEnum.REJECT){ try { if(failCountLock.acquire()){ String failKey = MQ_RECEIVE_FAIL_ID + rmqMessage.getId(); int count = iCached.get(failKey) == null ? 0: (int)iCached.get(failKey); if(count > 3){ logger.error("MQ消息已达消费上限次数,放入死信队列, {}",JSONUtil.toJsonStr(rmqMessage)); // this.basicReject(message,channel); // 删除缓存 iCached.remove(failKey); // 更新MQ_MESSAGE消费失败 this.updateMessageFail_dead(rmqMessage); //发送钉钉预警 DingTalkUtils.sendText("消费队列次数已达上限:"+rmqMessage.getQueueName()); }else { count = count + 1; iCached.put(failKey, count); this.basicNACK(message,channel); } } } catch (Exception e) { logger.error("",e); } finally { failCountLock.release(); } return; } // MQ重回队列 if(ack == HandlerEnum.NACK){ this.basicNACK(message,channel); } } catch (Exception e) { e.printStackTrace(); } } /** * 正常消费掉后通知mq服务器移除此条mq */ private void basicACK(Message message,Channel channel){ try{ channel.basicAck(message.getMessageProperties().getDeliveryTag(),false); }catch(IOException e){ logger.error("通知服务器移除MQ时异常,异常信息:"+e); } } /** * 处理异常,mq重回队列 */ private void basicNACK(Message message,Channel channel){ try{ channel.basicNack(message.getMessageProperties().getDeliveryTag(),false,true); }catch(IOException e){ logger.error("MQ重新进入服务器时出现异常,异常信息:"+e); } } /** * 处理异常,放入死信队列 */ private void basicReject(Message message,Channel channel){ try{ channel.basicReject(message.getMessageProperties().getDeliveryTag(),false); }catch(IOException e){ logger.error("MQ重新进入服务器时出现异常,异常信息:"+e); } } /** * 确定RmqMessageHandler<?> 泛型类型 * @param handler * @return */ private Class<?> dectectedParamType(RmqMessageHandler handler){ String className = handler.getClass().getName(); if(handlerMap.containsKey(className)){ return handlerMap.get(className); } Type[] types = handler.getClass().getGenericInterfaces(); ParameterizedType parameterized = (ParameterizedType) types[0]; Class<?> clazz = (Class<?>) parameterized.getActualTypeArguments()[0]; handlerMap.put(className,clazz); return clazz; } private void updateMessageSuccess(final RmqMessage rmqMessage){ // 不需要更新 if(!rmqMessage.isFlagDB()){ return; } com.cloud.cang.model.mq.Message message = new com.cloud.cang.model.mq.Message(); message.setIstatus(com.cloud.cang.model.mq.Message.StatusEnum.CONSUMER_SUC.getCode()); message.setStoIp(NetUtil.getLocalhost().getHostAddress() + ":" + CoreUtils.getPortByMBean()); message.setId(rmqMessage.getId()); message.setTupdateTime(DateUtil.date()); messageService.updateBySelective(message); } private void updateMessageSuccess_BizFail(final RmqMessage rmqMessage){ // 不需要更新 if(!rmqMessage.isFlagDB()){ return; } com.cloud.cang.model.mq.Message message = new com.cloud.cang.model.mq.Message(); message.setIstatus(com.cloud.cang.model.mq.Message.StatusEnum.CONSUMER_SUC_BIZ_FAIL.getCode()); message.setStoIp(NetUtil.getLocalhost().getHostAddress() + ":" + CoreUtils.getPortByMBean()); message.setId(rmqMessage.getId()); message.setTupdateTime(DateUtil.date()); messageService.updateBySelective(message); } private void updateMessageFail_dead(final RmqMessage rmqMessage){ // 不需要更新 if(!rmqMessage.isFlagDB()){ return; } com.cloud.cang.model.mq.Message message = new com.cloud.cang.model.mq.Message(); message.setIstatus(com.cloud.cang.model.mq.Message.StatusEnum.CONSUMER_FAIL_DEAD.getCode()); message.setStoIp(NetUtil.getLocalhost().getHostAddress() + ":" + CoreUtils.getPortByMBean()); message.setId(rmqMessage.getId()); message.setTupdateTime(DateUtil.date()); message.setSremark(com.cloud.cang.model.mq.Message.StatusEnum.CONSUMER_FAIL_DEAD.getDesc()); messageService.updateBySelective(message); } }
2.RmqMessageHandler 策略handler
import com.cloud.cang.core.rmq.message.RmqMessage; public interface RmqMessageHandler<T> { /** * 业务处理, 当matchHandler()返回true,才调用handler() * @param rmqMessage * @return HandlerEnum * ACK(10," 正常消费,MQ移除队列"), * NACK(20,"重回队列"), * REJECT(30," 消息放入死信队列"); */ HandlerEnum handler(RmqMessage<T> rmqMessage); /** * 返回true表示:匹配到消息队列名称为‘queueName’的队列,由此handle()处理, * 返回false表示:未匹配到消息队列名称为‘queueName’的队列,handle()不处理 * @param queueName * @return */ boolean matchHandler(String queueName); }
3.ImgRecZlqfMessageHandler 策略类的某个具体实现
import cn.hutool.core.collection.CollUtil; import cn.hutool.core.map.MapUtil; import cn.hutool.core.text.StrFormatter; import cn.hutool.json.JSONUtil; import com.alibaba.fastjson.JSONObject; import com.cloud.cang.cache.redis.ICached; import com.cloud.cang.concurrent.ExecutorManager; import com.cloud.cang.core.common.BizTypeDefinitionEnum; import com.cloud.cang.core.rmq.RmqProducer; import com.cloud.cang.core.rmq.interfaces.HandlerEnum; import com.cloud.cang.core.rmq.interfaces.RmqMessageHandler; import com.cloud.cang.core.rmq.message.RmqMessage; import com.cloud.cang.core.utils.DingTalkUtils; import com.cloud.cang.model.op.AppManage; import com.cloud.cang.model.op.InterfaceAccount; import com.cloud.cang.model.op.InterfaceInfo; import com.cloud.cang.mq.QueueEnum; import com.cloud.cang.mq.message.Mq_ImgResult; import com.cloud.cang.mq.message.Mq_Zlqf_ImgResult; import com.cloud.cang.open.api.common.APIConstant; import com.cloud.cang.open.api.common.SubCodeEnum; import com.cloud.cang.open.api.op.service.InterfaceAcceptService; import com.cloud.cang.open.api.op.service.InterfaceAccountService; import com.cloud.cang.open.api.op.service.TransferLogService; import com.cloud.cang.open.sdk.exception.CloudCangException; import com.cloud.cang.open.sdk.model.request.ImgRecognition; import com.cloud.cang.open.sdk.model.response.GoodDetail; import com.cloud.cang.open.sdk.model.response.ImgResultDetail; import com.cloud.cang.open.sdk.util.BaseSignature; import com.cloud.cang.open.sdk.utils.EncryptUtils; import com.cloud.cang.open.sdk.utils.SortUtils; import com.cloud.cang.openapi.CommonParam; import com.cloud.cang.openapi.RecongitionVo; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import java.util.Date; import java.util.HashMap; import java.util.List; import java.util.Map; /** * @program: 37cang-云平台 * @description: 消息处理类:处理图像识别结果消息 * @author: qzg * @create: 2019-11-15 11:23 **/ @Component public class ImgRecZlqfMessageHandler implements RmqMessageHandler<Mq_Zlqf_ImgResult> { private static final Logger logger = LoggerFactory.getLogger(ImgRecZlqfMessageHandler.class); @Autowired private InterfaceAccountService interfaceAccountService; @Autowired private InterfaceAcceptService interfaceAcceptService; @Autowired private TransferLogService transferLogService; @Autowired private ICached iCached; @Autowired RmqProducer rmqProducer; @Override public HandlerEnum handler(RmqMessage<Mq_Zlqf_ImgResult> rmqMessage) { Mq_Zlqf_ImgResult imgResult = rmqMessage.getMessage(); try{ // 业务处理 this.processBiz(imgResult); return HandlerEnum.ACK; } catch (Exception e){ e.printStackTrace(); DingTalkUtils.sendText("第三方open-api处理识别结果失败, 消息ID:"+rmqMessage.getId()); } return HandlerEnum.ACK_BIZ_FAIL; } @Override public boolean matchHandler(String queueName) { if(queueName.contains(QueueEnum.IMG_RESULT_DARKNET.getName())) { return true; } return false; } /** * @param imgResult */ private void processBiz(Mq_Zlqf_ImgResult imgResult)throws Exception{ try { String backBody = this.buildBackBody(imgResult); logger.info("===========第三方(中林清风)接口同步识别结果处理成功=============="); iCached.put(com.cloud.cang.openapi.APIConstant.ZLQF_RECOGNITION_RESULT + imgResult.getImgCode(), backBody,60*60*2); } catch (Exception e) { e.printStackTrace(); } } /** * 构建backBody * @return */ private String buildBackBody(Mq_Zlqf_ImgResult imgResult) throws Exception { Map tempMap = MapUtil.builder() .put("status", "200") .put("msgCode", SubCodeEnum.SUCCESS.getCode()) .put("msgContent", SubCodeEnum.SUCCESS.getCode()) .put("imgCode", imgResult.getImgCode()) .put("imgResultDetail", imgResult.getGoods()) .map(); if( imgResult.getStatus() != 200){ tempMap.put("status", "-100"); tempMap.put("msgCode", SubCodeEnum.RECOGNITION_SERVER_RESULT_ERROR.getCode()); tempMap.put("msgContent", SubCodeEnum.RECOGNITION_SERVER_RESULT_ERROR.getReturnMsg()); tempMap.put("imgCode", imgResult.getImgCode()); tempMap.put("imgResultDetail", null); } // Map排序 tempMap = SortUtils.sortMapByKey(tempMap); String backBody = JSONObject.toJSONString(MapUtil.builder().put("body",tempMap).map()); return backBody; } }
4.QueueEnum
public enum QueueEnum { IMG_MODEL("img.model.","open-api发送给vis消息, 识别图片"), IMG_MODEL_CALLBACK("img.callback.model.","open-api发送给vis消息, 识别图片(第三方)"), IMG_MODEL_YOLOV4_CALLBACK("img.yolov4.callback.model.","open-api发送给vis消息, YOLOV4识别图片(第三方)"), IMG_MODEL_SYNCHRONIZE("img.synchronize.model.","open-api发送给vis消息, 识别图片同步方法(第三方)"), IMG_RESULT("img.result","vis把识别结果发送给open-api"), IMG_RESULT_CALLBACK("img.callback.result","vis把识别结果发送给open-api(第三方)"), IMG_RESULT_YOLOV4_CALLBACK("img.callback.yolov4.result","vis把YOLOV4识别结果发送给open-api(第三方)"), IMG_RESULT_SYNCHRONIZE("img.synchronize.result","vis把识别结果发送给open-api(第三方同步方法)"), IMG_OPENAPI_RESULT("img.openapi.result","open-api处理识别结果,发送给api,纯视觉"), IMG_OPENAPI_WEIGHT_RESULT("img.openapi.weight.result","open-api处理识别结果,发送给api,视觉+称重"), IMG_ERROR("img.error","识别错误,发送给api,纯视觉"), IMG_WEIGHT_ERROR("img.weight.error","识别错误,发送给api,视觉+称重"), IMG_MODEL_DARKNET("img.darknet.model.","第三方调用open-api发送给vis消息, 识别图片(V4模型)"), IMG_RESULT_DARKNET("img.darknet.result","vis把识别结果发送给open-api(V4模型)"), MODEL_UPDATE("model.update","更新模型"); private String name; private String desc; QueueEnum(String name, String desc) { this.name = name; this.desc = desc; } public String getName() { return name; } public void setName(String name) { this.name = name; } public String getDesc() { return desc; } public void setDesc(String desc) { this.desc = desc; } }
5.RmqMessage
import lombok.Builder; import lombok.Data; import java.io.Serializable; /** * @program: 37cang-云平台 * @description: * @author: qzg * @create: 2019-11-20 17:11 **/ @Data @Builder public class RmqMessage<T> implements Serializable { // 消息ID private String id; // 消息发送时间 private long time; // 消息队列的名称 private String queueName; // 是否保存、更新MQ_MESSAGE @Builder.Default private boolean flagDB = true; // 消息内容 private T message; }

浙公网安备 33010602011771号