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;

}

 

posted @ 2021-10-08 23:50  mimimikasa  阅读(83)  评论(0)    收藏  举报