项目实战 — 消息队列(7){虚拟主机设计(1)}

news2024/11/24 3:29:09

目录

一、什么是虚拟主机

二、编写虚拟主机代码

🍅 1、准备工作

🍅 2、实现exchange相关操作

🎄实现创建交换机exchangeDeclare

🎄 实现 删除交换机exchangeDelete

🍅  3、实现queue相关操作

🎄实现创建队列queueDeclare

🎄实现删除队列queueDelete

🍅 4、实现binding相关操作

🎄 实现交换机的转发规则

🎄 创建绑定queueBind

🎄 删除绑定queueUnbind

🍅 5、实现basicPublish

🎄实现basicPublish类

🎄 完善router类中的代码

三、测试routeTopic

🍅 1、准备工作和收尾工作

 🍅 2、编写测试方法


一、什么是虚拟主机

虚拟主机,就类似于MySQL的database,把交换机,队列,绑定,消息等进行逻辑上的隔离。

这里只实现单个虚拟主机,不仅要管理数据,还要提供一些核心API,供上层代码进行调用。

这里的核心API,主要就是要把之前写的内存中的数据管理和硬盘的数据管理穿起来。

   核心API:

        (1)创建交换机 exhcangeDeclare

        (2)删除交换机 exchangeDelete

        (3)创建队列 queueDeckare

        (4)删除队列 queueDelete

        (5)创建绑定 queueBind

        (6)删除绑定 queueUnbind 

        (7)发送消息 basicPublish

        (8)订阅消息 basicCosume

        (9)确认消息 basicAck

二、编写虚拟主机代码

🍅 1、准备工作

创建一个VirtualHost表示虚拟主机,其中每一个虚拟主机都管理着自己的交换机、队列、绑定、消息和数据,并且提供了一些api供上层使用。

/*
* 表示虚拟主机
* 每个虚拟主机都相当于一个消息队列,管理者自己的交换机、队列、绑定....
* 提供了api供上层调用
* */
@Data
public class VirtualHost {
    private String vitualHostName;
    private MemoryDataCenter memoryDataCenter = new MemoryDataCenter();
    private DiskDataCenter diskDataCenter = new DiskDataCenter();

    /*
    * 创建构造方法
    * */
    public VirtualHost(String name){

        this.vitualHostName = name;
//        MemoryDataCenter只需要new对象
//        DiskDataCenter需要进行初始化操作,建库建表和初始数据的设定
        diskDataCenter.init();

//        还需要针对硬盘的数据进行恢复到内存中
        try{
            memoryDataCenter.recovery(diskDataCenter);
        }catch (IOException | MqException | ClassNotFoundException e ){
            e.printStackTrace();
            System.out.println("[VirtualHost]恢复内存数据失败");
        }
    }
}

 注意:有关创建、删除等操作,无法避免的在多线程环境下面进行,所以后续为了保证线程安全,对一些操作还需要加锁。

这里创建一个统一的锁对象,在上面的代码中还新增几条成员变量:

//作为交换机的锁对象
    private final Object exchangeLocker = new Object();

    //针对队列的锁对象
    private final Object queueLocker = new Object();


🍅 2、实现exchange相关操作

表示交换机和虚拟主机之间的关系:使用虚拟主机的名字 + 交换机的真实名字

🎄实现创建交换机exchangeDeclare

public boolean exchangeDeclare(String exchangeName, ExchangeType exchangeType, boolean durable){
//        1、把交换机的名字,加上虚拟主机作为前缀
        exchangeName = virtualHostName + exchangeName;
        try{
            synchronized (exchangeLocker){
//                1.判定交换机是否存在,直接通过内存查询
                Exchange existsExchange = memoryDataCenter.getExchange(exchangeName);
                if (existsExchange != null){
//                该交换机已经存在
                    System.out.println("[VirtualHost]交换机已经存在!exchangeName = "+ exchangeName);
                    return true;
                }

//           2、创建交换机,先构造Exchange对象
                Exchange exchange = new Exchange();
                exchange.setName(exchangeName);
                exchange.setType(exchangeType);
                exchange.setDurable(durable);
 

//            3、把交换机对象写入硬盘
                if (durable){
                    diskDataCenter.insertExchange(exchange);
                }

//            4、把交换机对象写入内存
                memoryDataCenter.insertExchange(exchange);
                System.out.println("[VirtualHost] 交换机创建完成!exchangeName = " + exchangeName);
            }
          return true;
        }catch (Exception e) {
            System.out.println("[VirtualHost]交换机创建失败!exchangName = " + exchangeName );
            e.printStackTrace();
            return false;
        }
    }

🎄 实现 删除交换机exchangeDelete

public boolean exchangeDelete(String exchangeName){
        exchangeName = virtualHostName + exchangeName;
        try{
            synchronized (exchangeLocker){
                //             1.先找到对应的交换机
                Exchange toDelete = memoryDataCenter.getExchange(exchangeName);
                if (toDelete == null){
                    throw new MqException("[VirtualHost]交换机不存在无法删除");
                }
//            2、删除硬盘上的数据
                if (toDelete.isDurable()){
                    diskDataCenter.deleteExchange(exchangeName);
                }
//              3、删除内存中的交换数据
                memoryDataCenter.deleteExchange(exchangeName);
                System.out.println("[VirtualHost] 交换机删除成功!exchangeName = " + exchangeName);
            }
            return true;
        } catch (Exception e){
            System.out.println("[VirtualHost] 交换机删除失败!exchangeName = " + exchangeName);
            e.printStackTrace();
            return false;
        }
    }

🍅  3、实现queue相关操作

表示队列和虚拟主机之间的关系:使用虚拟主机的名字 + 队列的真实名字

🎄实现创建队列queueDeclare

//    创建队列
    public boolean queueDeclare(String queueName,boolean durable){
//        把队列的名字,拼接上虚拟主机的名字
        queueName = virtualHostName + queueName;
        try {
            synchronized (queueLocker){
                //1、判定队列是否存在
                MSGQueue exixtsQueue = memoryDataCenter.getQueue(queueName);
                if (exixtsQueue != null){
                    System.out.println("[VirtualHost]队列已经存在!queueName = " + queueName);
                    return true;
                }
//            2、创建队列对象
                MSGQueue queue = new MSGQueue();
                queue.setName(queueName);
                queue.setDurable(durable);

//          3、写硬盘
                if(durable){
                    diskDataCenter.insertQueue(queue);
                }
//            4、写内容
                memoryDataCenter.insertQueue(queue);
                System.out.println("[VirtualHost]队列创建成功!queueName = " + queueName);
            }
            return true;
        } catch (IOException e) {
            System.out.println("[VirtualHost]队列创建失败!queueName = " + queueName);
            e.printStackTrace();
            return false;
        }
    }

🎄实现删除队列queueDelete

public Boolean queueDelete(String queueName){
        queueName = virtualHostName + queueName;
        try{
            synchronized (queueLocker){
//            1、根据队列名字,查询当前队列对象
                MSGQueue queue = memoryDataCenter.getQueue(queueName);
                if (queue == null){
                    throw new MqException("[VirtualHost]队列不存在!无法删除,queueName = " + queueName);
                }
//            2、删除硬盘数据
                if (queue.isDurable()){
                    diskDataCenter.deleteQueue(queueName);
                }
//            3、删除内存数据
                memoryDataCenter.deleteQueue(queueName);
                System.out.println("[VirtualHost]删除队列成功!queueName = " + queueName);
            }
            return  true;
        } catch (Exception e) {
            System.out.println("[VirtualHost]删除队列失败!queueName = " + queueName);
            e.printStackTrace();
            return false;
        }
    }


🍅 4、实现binding相关操作

🎄 实现交换机的转发规则

创建 一个Router类,验证bindingKey是否合法,合法返回true没不合法返回false。

public class Router {
    public  boolean checkBindingKey(String bindingKey){
//        这里暂时不会写具体的步骤,等后面需要了再添加
        return true;
    }
}

 然后再VirtualHost里面新增一条成员变量:

private Router router = new Router();

🎄 创建绑定queueBind

 public boolean queueBind(String queueName,String exchangeName,String bindingKey){
        queueName = virtualHostName + queueName;
        exchangeName = virtualHostName + exchangeName;
        try {
            synchronized (exchangeLocker){
                synchronized (queueLocker){
//                    1、判定当前的绑定是否已经存在
                    Binding existsBinding = memoryDataCenter.getBinding(exchangeName,queueName);
                    if (existsBinding != null){
                        throw new MqException("[VirtualHost]binding已经存在!queueName = " + queueName + ",exchangeName = " + exchangeName);
                    }

//            2、验证bindingKey是否合法
                    if (!router.checkBindingKey(bindingKey)){
                        throw new MqException("[VirtualHost]bindingKey非法!bindingkey = " + bindingKey);
                    }

//            3.创建Binding对象
                    Binding binding = new Binding();
                    binding.setExchangeName(exchangeName);
                    binding.setQueueName(queueName);
                    binding.setBindingKey(bindingKey);

//            4、获取对应的交换机和队列,如果交换机或者队列不存在,这样的绑定也是无法创建的
                    MSGQueue queue = memoryDataCenter.getQueue(queueName);
                    if (queue == null){
                        throw new MqException("[VirtualHost]队列不存在!queueName = " + queueName);
                    }
                    Exchange exchange = memoryDataCenter.getExchange(exchangeName);
                    if (exchange == null){
                        throw new MqException("[VirtualHost]交换机不存在!exchangeName = " + exchangeName);
                    }

//            5、将binding写入写硬盘
                    if(queue.isDurable() && exchange.isDurable()){
                        diskDataCenter.insertBinding(binding);
                    }

//            6、将binding写入内存
                    memoryDataCenter.insertBinding(binding);
                }
            }
            System.out.println("[VirtualHost]绑定创建成功! exchangeName = " + exchangeName + "queueName = " + queueName);
            return true;
        }catch(Exception e){
            System.out.println("[VirtualHost]绑定创建失败! exchangeName = " + exchangeName + "queueName = " + queueName);
            e.printStackTrace();
            return false;
        }
    }

🎄 删除绑定queueUnbind

  注意点:删除绑定时,按照之前删除队列和交换机的设定一样,校验绑定的交换机和队列是否为空,为空就抛出异常,删除绑定失败。但是,如果在进行删除时,发现在删除绑定之前,就已经删了交换机或者队列了,但是绑定还在,此时前面那个逻辑就有问题了。

  所以这里,我们就不校验绑定的交换机或者队列是否存在,直接就尝试删除。

//    删除绑定
    public boolean queueUnbind(String queueName,String exchangeName) {
        queueName = virtualHostName + queueName;
        exchangeName = virtualHostName + exchangeName;
        try{
            synchronized (exchangeLocker){
                synchronized (queueLocker){
                    //            1、获取绑定看是否已经存在
                    Binding binding = memoryDataCenter.getBinding(exchangeName,queueName);
                    if (binding == null){
                        throw new MqException("[VirtualHost]删除绑定失败!绑定不存在!exchangeName = " + exchangeName + ",queueName = " + queueName);
                    }
//
//            2、删除硬盘上面的数据
                    diskDataCenter.deleteBinding(binding);

//            3、删除内存上的数据
                    memoryDataCenter.deleteBinding(binding);
                    System.out.println("[VirtualHost]删除绑定成功");
                }
            }
            return true;
        }catch(Exception e){
            System.out.println("[VirtualHost]删除绑定失败!exchangeName = " + exchangeName + ",queueName = " + queueName);
            e.printStackTrace();
            return false;
        }
    }


🍅 5、实现basicPublish

这一块比较复杂哈~

这个API主作用是发送消息到指定的的交换机中,然后再由交换机转发给队列。

关于交换机,这里有三种交换机:

        * Direct 直接交换机 (发送时指定队列名发送)

        * Fanout 扇出交换机(每个队列都发送)

        * Topic 主题交换机(指定bindingKey和RoutingKey)

需求分析里面也提到了这三种交换机,看到这里忘记了的小伙伴建议看看,参考博客项目实战 — 消息队列(1) {需求分析}_‍️藿香正气水的博客-CSDN博客

🎄实现basicPublish类

主要分以下几步:

        (1)转换交换机的名字:虚拟机名 + 交换机名

        (2)检查routingkey是否合法

        (3)根据交换机的名字查找交换机对象

        (4)判断交换机的类型,编写具体的转发规则

                🎊 以直接交换机(direct)的方式转发消息

                        a. 构造消息对象;

                        b. 查找该队列对应的对象,并判断队列是否为空

                        c. 队列存在就给队列写入消息

                🎊 以扇出交换机(fanout)和主题交换机(topic)的方式转发消息

                        a. 获取到绑定对象,判断对应的队列是否存在

                        b. 构造下消息对象

                        c. 判断消息是否能转发给队列

                        d. 转发消息给队列

首先我们再Router类中编写再几个方法,先搭个架子,不具体实现,避免basicPublish类报错

public class Router {
//    判断routingKey和BindingKey是否合法
    public  boolean checkBindingKey(String bindingKey){

        return true;
    }

    public boolean checkRoutingKey(String routingKey){

        return true;
    }

//    该方法用来判定该消息是否用来转发给绑定的队列
    public boolean route(ExchangeType exchangeType,Binding binding,Message message) throws MqException {

        return true;
    }

    private boolean routeTopic(Binding binding,Message message){
        return true;
    }
}

编写basicPublish 

//    发送消息到指定的交换机或者队列中
    public boolean basicPublish(String exchangeName,String routingKey,BasicProperties basicProperties,byte body[]){
        try {
//            1、转换交换机的名字
            exchangeName = virtualHostName + exchangeName;
//            2、检查这里的routingKey是否合法
            if (router.checkRoutingKey(routingKey)){
                throw new MqException("[VirtualHost]routingKey非法!routingKey = " + routingKey);
            }
//            3.根据交换机的名字查找到交换机对象
            Exchange exchange = memoryDataCenter.getExchange(exchangeName);
            if (exchange == null){
                throw new MqException("[VirtualHost]交换机不存在!exchangeName = " + exchangeName);
            }

//            4、判断交换机的类型
            if(exchange.getType() == ExchangeType.DIRECT){
//                按照直接交换机的方式转发消息
//                以routingKey作为队列的名字,直接把消息写入到指定的队列中
                String queueName = virtualHostName + routingKey;
//                5、构造消息对象
                Message message = Message.createMessageWithId(routingKey,basicProperties,body);
//                6、查找该队列对应的对象
                MSGQueue queue = memoryDataCenter.getQueue(queueName);
                if (queue == null) {
                    throw new MqException("[VirtualHost]队列不存在!queuename = " + queueName);
                }
//                7、队列存在,直接给队列中写入消息
                sendMessage(queue,message);
            }else {
//                按照fanout和topic的方式来转发
//                找到该交换机的所有绑定,并且遍历这些绑定消息
                ConcurrentHashMap<String ,Binding> bindingsMap = memoryDataCenter.getBindings(exchangeName);
                for (Map.Entry<String ,Binding> entry : bindingsMap.entrySet()){
//                    (1)获取到该绑定对象,判断对应的队列是否存在
                    Binding binding = entry.getValue();
                    MSGQueue queue = memoryDataCenter.getQueue(binding.getQueueName());
                    if (queue == null){
//                        存在多个队列,这里为了避免因为一个队列的失败影响到其他队列的消息传输
//                        这里就不抛异常
                        System.out.println("[VirtualHost]basicPublish发送消息时间,发现队列不存在!queueName = " + binding.getQueueName());
                        continue;
                    }
//                    (2)构造消息对象
                    Message message = Message.createMessageWithId(routingKey,basicProperties,body);
//                  (3)判定这个消息是否能转发给该队列
                        如果fanout,所有的绑定队列都要转发
                        如果式topic。还需要判定bindingKey和routingKey是不是匹配
                  if(!router.route(exchange.getType(),binding,message)){
                        continue;
                  }
//                  (4)转发消息给队列
                    sendMessage(queue,message);
                }
            }
            return true;
        }catch (Exception e){
            System.out.println("[VirtualHost]消息发送失败");
            e.printStackTrace();
            return false;
        }
    }


//    编写sendMessage
public void sendMessage(MSGQueue queue,Message message) throws IOException, MqException {
//        把消息写入到 硬盘 和 内存 中去
//        判定持久化
        int deliverMode = message.getDeliverMode();
//        deliverMode为1,不持久化,deliverMode 为2 表示持久化
        if(deliverMode == 2){
//            写入硬盘
            diskDataCenter.sendMessage(queue,message);
        }

//      写入内存
        memoryDataCenter.sendMessage(queue,message);

    }


🎄 完善router类中的代码

 首先编写route()方法,判断该消息是否需要用来转发给绑定的队列。

 public boolean route(ExchangeType exchangeType,Binding binding,Message message) throws MqException {
//        根据不同的exchangeType使用不同的判定转发规则
        if (exchangeType == ExchangeType.FANOUT){
//            如果是fanout类型,那么所有队列都需要转发
        }else if(exchangeType == ExchangeType.TOPIC){
//             如果是topic主题交换机
            return routeTopic(binding,message);
        }else {
            throw new MqException("[Router]交换机类型非法! exchangeType = " + exchangeType);
        }
        return true;
    }

然后编写有关topic中的一套转发规则。

首先检测routingKey和bindingKey是否合法:

有关routingKey和bindingKey的一套命名规则:

        🎊 routingKey

        (1)数字、字母、下划线

        (2)使用“.”点号,将routingKey分割程多个部分,形如aaa.bbb.ccc

        🎊 bindingKey

        (1)数字、字母、下划线

        (2)使用" . "点号,把整个bindingKey分成了多个部分

        (3)支持两种特殊的通配符:“ * ” 和“ # ” 。* 和 #必须是作为被分割出来的独立部分,           由" . "分割。形如aaa.*.bbb   

                “ * ”代表可以匹配任何一个独立的部分;

                “ # ”代表可以匹配任何0个或者多个独立的部分。

           第一种情况(bindingKey中没有 * 和 #):此时必须要求routingKey和bindingKey一           模一样,才能够匹配成功。这种就相当于直接交换机。

           第二种情况(bindingKey中有“ * ”):

               设定bindingKey:aaa.*.ccc,此时如果是aaa.bbb.ccc或者aaa.b.ccc这种形式的              routingKey都能匹配成功,但是,如果是aaa.b.ccc这种就会匹配失败

          第三种情况(bindingKey中有#):相当于fanout交换机。

                设定bindingKey:aaa.#.ccc,

                如果routingKey是以下的形式:

                        aaa.bbb.ccc(匹配成功)

                        aaa.b.b.ccc(匹配成功)

                        aaa.ccc(匹配成功)

                        aaa.b.b(匹配失败)

所以,综上所述,直接交换机和扇出交换机属于主题交换机的特例。

//    routingKey构造规则:数字\字母\下划线\使用 . 分割
    public boolean checkRoutingKey(String routingKey){
        if (routingKey.length() == 0){
//            空字符串,routingKey为0,可能就是使用的fanout交换机
            return true;
        }
        for (int i = 0; i < routingKey.length(); i++) {
            char ch = routingKey.charAt(i);
//            判定该字符是否是大写字母
            if (ch >= 'A' && ch <= 'Z'){
                continue;
            }
//            判定该字母是否是小写字母
            if (ch >= 'a' && ch <= 'z'){
                continue;
            }
//            判断字母是否是阿拉伯数字
            if (ch >= '0' && ch <= '9'){
                continue;
            }
//            判定是否是 _ 或者 .
            if(ch == '_' || ch == '.'){
                continue;
            }
//            上面的条件不符合
            return false;
        }
        return true;
    }

//    bindingKey构造规则:数字\字母\下划线\使用 . 分割\允许存在 * 和 # 作为通配符
    public  boolean checkBindingKey(String bindingKey){
        if (bindingKey.length() == 0){
//            合法,使用直接交换机和扇出交换机,可以为空,因为此时用不到bindingKey
            return true;
        }

//        检查是否存在不合法字符
        for (int i = 0; i < bindingKey.length(); i++) {
            char ch = bindingKey.charAt(i);
            if (ch >= 'A' && ch <= 'Z'){
                continue;
            }

            if (ch >= 'a' && ch <= 'z'){
                continue;
            }

            if (ch >= '0' && ch <= '9'){
                continue;
            }

            if (ch == '_' || ch == '.' || ch == '*' || ch == '#'){
                continue;
            }
            return false;
        }

//        检查*或者#的位置是否正确(被 . 进行分割)
//        为什么写作\\.  ,因为,在正则表达式种,"\."和"."都是特殊的字符,所以需要双\\转义
        String[] words = bindingKey.split("\\.");
        for (String word : words){
//            如果word为*或者#,那么长度不会大于1
            if (word.length() > 1 && (word.contains("*") || word.contains("#"))){
                return false;
            }
        }    
//           约定,通配符之间的相邻关系
//           1.aaa.#.#.bbb  => 非法
//           2.aaa.#.*.bbb  => 非法
//           3.aaa.*.#.bbb  => 非法
//           4.aaa.*.*.bbb  => 合法
        for (int i = 0; i < words.length; i++) {
//            #.#
            if(words[i].equals("#") && words[i+1].equals("#")){
                return false;
            }
//           #.*
            if (words[i].equals("#") && words[i+1].equals("*")){
                return false;
            }
//            *.#
            if (words[i].equals("*") && words[i+1].equals("#")){
                return false;
            }
        }
        return true;
    }

编写routeTopic()方法,考虑routingKey和bindingKey之间的匹配规则

采用双指针:

根据bindingKey的下标,判定当前下标指向的部分。

  (1)指向的是普通字符串,此时要求和routingKey对应的下标指向的内容完全一致

  (2)指向的是 * ,此时无论routingKey指向的是什么,指针都是前进

  (3)遇到了 # ,并且如果#后面没有其他内容了,匹配上了,直接返回true

  (4)遇到了#,#后面仍然有其他内容,然后拿着#后面的部分,去routingKey种找是否有相同的部分,没找到就返回fasle。如果找到了,就把routingkey的箭头指向该位置,指针继续往后走。按照前面的方式,走到末尾为止

  (5)移动过程种,如果同时到达末尾,就返回true;否则返回false。

private boolean routeTopic(Binding binding,Message message){
//        1.进行切分
        String[] bindingTokens = binding.getBindingKey().split("\\.");
        String[] routingTokens = message.getRoutingKey().split("\\.");

//        2.引入两个下标,指向两个数组的0下标
        int bindingIndex = 0;
        int routingIndex = 0;
//        3.进行循环
        while (bindingIndex < bindingTokens.length && routingIndex < routingTokens.length){
            if (bindingTokens[bindingIndex].equals("*")){
//                [1]如果遇见*,直接进入下一轮,*可以匹配到任何一个部分
                bindingIndex++;
                routingIndex++;
                continue;
            } else if (bindingTokens[bindingIndex].equals("#")){
//                如果遇到#,看还有没有下一个为止
                bindingIndex++;
                if (bindingIndex == bindingTokens.length){
//                    [3]直接到了末尾
                    return true;
                }
//               [4] #后面还有内容,继续向后
//                findNextMatch用来查找该部分在routingKey的位置,返回改下标,没找到就返回-1
                routingIndex = findNextMatch(routingTokens,routingIndex,bindingTokens[bindingIndex]);
                if(routingIndex == -1){
//                    没找到匹配的结果,匹配失败
                    return false;
                }
//                找到了匹配的结果,继续向后匹配
                bindingIndex++;
                routingIndex++;
            } else {
//                [1]如果遇见了普通的字符串(不含#和*),如果一样就返回true
                if (!bindingTokens[bindingIndex].equals(routingTokens[routingIndex])){
                    return false;
                }
                bindingIndex++;
                routingIndex++;
            }
        }
//      [5]判断是否双方同时到达末尾
        if(bindingIndex == bindingTokens.length && routingIndex == routingTokens.length){
            return true;
        }
        return true;
    }

    private int findNextMatch(String[] routingTokens, int routingIndex, String bindingToken) {
        for (int i = routingIndex; i < routingTokens.length ; i++) {
            if (routingTokens[i].equals(bindingToken)){
                return i;
            }
        }
        return -1;
    }

 


三、测试routeTopic

🍅 1、准备工作和收尾工作

创建测试类routerTests

@SpringBootTest
public class RouterTests {
    private Router router = new Router();
    private Binding binding = null;
    private Message message = null;

    @BeforeEach
    public void setUp(){
        binding = new Binding();
        message = new Message();
    }

    @AfterEach
    public void tearDown(){
        binding = null;
        message = null;
    }
}

以下是一些测试用例

    [测试用例]
     binding key          routing key         result
    1 aaa                  aaa                 true
    2 aaa.bbb              aaa.bbb             true
    3 aaa.bbb              aaa.bbb.ccc         false
    4 aaa.bbb              aaa.ccc             false
    5 aaa.bbb.ccc          aaa.bbb.ccc         true
    6 aaa.*                aaa.bbb             true
    7 aaa.*.bbb            aaa.bbb.ccc         false
    8 *.aaa.bbb            aaa.bbb             false
    9 #                    aaa.bbb.ccc         true
    10 aaa.#                aaa.bbb             true
    11 aaa.#                aaa.bbb.ccc         true
    12 aaa.#.ccc            aaa.ccc             true
    13 aaa.#.ccc            aaa.bbb.ccc         true
    14 aaa.#.ccc            aaa.aaa.bbb.ccc     true
    15 #.ccc                ccc                 true
    16 #.ccc                aaa.bbb.ccc         true

 🍅 2、编写测试方法

根据上面的测试用例编写16个测试方法

@Test
    public void test1() throws MqException {
        binding.setBindingKey("aaa");
        message.setRoutingKey("aaa");
        Assertions.assertTrue(router.route(ExchangeType.TOPIC,binding,message));
    }

    @Test
    public void test2() throws MqException {
        binding.setBindingKey("aaa.bbb");
        message.setRoutingKey("aaa.bbb");
        Assertions.assertTrue(router.route(ExchangeType.TOPIC,binding,message));
    }

    @Test
    public void test3() throws MqException {
        binding.setBindingKey("aaa.bbb");
        message.setRoutingKey("aaa.bbb.ccc");
        Assertions.assertTrue(router.route(ExchangeType.TOPIC,binding,message));
    }

    @Test
    public void test4() throws MqException {
        binding.setBindingKey("aaa.bbb");
        message.setRoutingKey("aaa.ccc");
        Assertions.assertFalse(router.route(ExchangeType.TOPIC,binding,message));
    }

    @Test
    public void test5() throws MqException {
        binding.setBindingKey("aaa.bbb.ccc");
        message.setRoutingKey("aaa.bbb.ccc");
        Assertions.assertTrue(router.route(ExchangeType.TOPIC,binding,message));
    }

    @Test
    public void test6() throws MqException {
        binding.setBindingKey("aaa.*");
        message.setRoutingKey("aaa.bbb");
        Assertions.assertTrue(router.route(ExchangeType.TOPIC,binding,message));
    }

    @Test
    public void test7() throws MqException {
        binding.setBindingKey("aaa.*.bbb");
        message.setRoutingKey("aaa.bbb.ccc");
        Assertions.assertFalse(router.route(ExchangeType.TOPIC,binding,message));
    }

    @Test
    public void test8() throws MqException {
        binding.setBindingKey("*.aaa.bbb");
        message.setRoutingKey("aaa.bbb");
        Assertions.assertFalse(router.route(ExchangeType.TOPIC,binding,message));
    }

    @Test
    public void test9() throws MqException {
        binding.setBindingKey("#");
        message.setRoutingKey("aaa.bbb.ccc");
        Assertions.assertTrue(router.route(ExchangeType.TOPIC,binding,message));
    }

    @Test
    public void test10() throws MqException {
        binding.setBindingKey("aaa.#");
        message.setRoutingKey("aaa.bbb");
        Assertions.assertTrue(router.route(ExchangeType.TOPIC,binding,message));
    }

    @Test
    public void test11() throws MqException {
        binding.setBindingKey("aaa.#");
        message.setRoutingKey("aaa.bbb.ccc");
        Assertions.assertTrue(router.route(ExchangeType.TOPIC,binding,message));
    }

    @Test
    public void test12() throws MqException {
        binding.setBindingKey("aaa.#.ccc");
        message.setRoutingKey("aaa.ccc");
        Assertions.assertTrue(router.route(ExchangeType.TOPIC,binding,message));
    }

    @Test
    public void test13() throws MqException {
        binding.setBindingKey("aaa.#.ccc");
        message.setRoutingKey("aaa.bbb.ccc");
        Assertions.assertTrue(router.route(ExchangeType.TOPIC,binding,message));
    }

    @Test
    public void test14() throws MqException {
        binding.setBindingKey("aaa.#.ccc");
        message.setRoutingKey("aaa.aaa.bbb.ccc");
        Assertions.assertTrue(router.route(ExchangeType.TOPIC,binding,message));
    }

    @Test
    public void test15() throws MqException {
        binding.setBindingKey("#.ccc");
        message.setRoutingKey("ccc");
        Assertions.assertTrue(router.route(ExchangeType.TOPIC,binding,message));
    }

    @Test
    public void test16() throws MqException {
        binding.setBindingKey("#.ccc");
        message.setRoutingKey("aaa.bbb.ccc");
        Assertions.assertTrue(router.route(ExchangeType.TOPIC,binding,message));
    }

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/861505.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【第二阶段】匿名函数隐式返回函数参数

1.匿名函数简单举例 fun main() {//第一种写法,count()获取字符长度val info"kotlin".count()println(info)//第二种写法&#xff0c;使用匿名函数val len"kotlin".count(){//it代表字符串中 k o t l i n 的字符itl}println(len) }执行结果 2.隐式返回 …

【LeetCode】236. 二叉树的最近公共祖先、 JZ36 二叉搜索树与双向链表

作者&#xff1a;小卢 专栏&#xff1a;《Leetcode》 喜欢的话&#xff1a;世间因为少年的挺身而出&#xff0c;而更加瑰丽。 ——《人民日报》 236. 二叉树的最近公共祖先 236. 二叉树的最近公共祖先 题目描述&#xff1a; 给定一个二叉树…

【人工智能前沿弄潮】——生成式AI系列:Diffusers学习(1)了解Pipeline 、模型和scheduler

Diffusers旨在成为一个用户友好且灵活的工具箱&#xff0c;用于构建针对您的用例量身定制的扩散系统。工具箱的核心是模型和scheduler。虽然DiffusionPipeline为了方便起见将这些组件捆绑在一起&#xff0c;但您也可以拆分管道并单独使用模型和scheduler来创建新的扩散系统。 …

MySQL_数据类型

数值类型 类型有符号(SIGNED)取值范围无符号(UNSIGNED)取值范围大小描述TINYINT(-128&#xff0c;127)(0&#xff0c;255)1byte小整数值SMALLINT(-32768&#xff0c;32767)(0&#xff0c;65535)2bytes大整数值INT/INTEGER(-2147483648&#xff0c;2147483647)(0&#xff0c;429…

Redis_持久化(AOF、RDB)

6. Redis AOF 6.1 简介 目前&#xff0c;redis的持久化主要应用AOF&#xff08;Append Only File&#xff09;和RDF两大机制&#xff0c;AOF以日志的形式来记录每个写操作&#xff08;增量保存&#xff09;&#xff0c;将redis执行过的所有指令全部安全记录下来&#xff08;读…

将本地项目上传至gitee的详细步骤

将本地项目上传至gitee的详细步骤 1.在gitee上创建以自己项目名称命名的空项目2.进入想上传的项目的文件夹&#xff0c;然后右键点击3. 初始化本地环境&#xff0c;把该项目变成可被git管理的仓库4.添加该项目下的所有文件5.使用如下命令将文件添加到仓库中去6.将本地代码库与远…

【Node.js】低代码平台源码

一、低代码简介 低代码管理系统是一种通过可视化界面和简化的开发工具&#xff0c;使非专业开发人员能够快速构建和管理应用程序的系统。它提供了一套预先定义的组件和模块&#xff0c;使用户可以通过拖放操作来设计应用程序的界面和逻辑。低代码管理系统还提供了自动化的工作…

IDEA每次启动indexing解决办法

每次启动indexing很浪费时间。 解决办法 setting中搜索index 设置如下&#xff1a; 这样设置以后&#xff0c;启动速度明显快多了。 参考 https://blog.csdn.net/qq_45162113/article/details/121128721

【云原生】Docker 详解(二):Docker 架构及工作原理

Docker 详解&#xff08;二&#xff09;&#xff1a;Docker 架构及工作原理 Docker 在运行时分为 Docker 引擎&#xff08;服务端守护进程&#xff09; 和 客户端工具&#xff0c;我们日常使用各种 docker 命令&#xff0c;其实就是在使用 客户端工具 与 Docker 引擎 进行交互。…

【LangChain概念】了解语言链️:第2部分

一、说明 在LangChain的帮助下创建LLM应用程序可以帮助我们轻松地链接所有内容。LangChain 是一个创新的框架&#xff0c;它正在彻底改变我们开发由语言模型驱动的应用程序的方式。通过结合先进的原则&#xff0c;LangChain正在重新定义通过传统API可以实现的极限。 在上一篇博…

统计学和机器学习之间的联系和区别

一、说明 老实说&#xff0c;我厌倦了几乎每天都在社交媒体和我的大学里听到这场辩论。通常&#xff0c;这伴随着一些模糊的陈述来解释这个问题。双方都为此感到内疚。我希望在本文结束时&#xff0c;您将对这些有些模糊的术语有更明智的立场。 二、论点 与普遍的看法相反&…

SpringCloud源码探析(九)- Sentinel概念及使用

1.概述 在微服务的依赖调用中&#xff0c;若被调用方出现故障&#xff0c;出于自我保护的目的&#xff0c;调用方会主动停止调用&#xff0c;并根据业务需要进行对应处理&#xff0c;这种方式叫做熔断&#xff0c;是微服务的一种保护方式。为了保证服务的高可用性&#xff0c;…

银河麒麟高级操作系统V10助力联通云建设打出组合拳

联通云基于“双引擎基座一云多芯”为不同行业场景提供可靠、高质量的应用上云服务。在核心代码进行了全面把控&#xff0c;定制多架构芯片应用适配模版&#xff0c;开发了计算、存储、网络、中间件等组件&#xff0c;全面适配自主化服务器和操作系统&#xff0c;提供云服务器、…

ffmpeg+intel核显实现硬解码

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 前言一、前言二、检查方法1.图形法2.nvidia-smi3.intel-gpu-tools 三、安装使用1.libva-dev2.libva-utils3.编译安装4.测试1.vainfo2.ffmpeg测试解码 总结 前言 之…

微信个人小程序申请 (AppID 和 AppSecret)

1. 登录微信公众平台 https://mp.weixin.qq.com/cgi-bin/loginpage?url%2Fcgi-bin%2Fhome%3Ft%3Dhome%2Findex%26lang%3Dzh_CN%26token%3D47421820 2. 右上角立即注册 3. 注册类型选择小程序 4. 账号信息 5. 邮箱激活 6. 小程序发布流程 7. 小程序信息 (前往填写) 8. 获取小程…

【JavaScript】jquery的导入方式有两种:本地导入和线上导入

前言 jQuery是一个用来代替JavaScript来快捷书写前端脚本语言的库&#xff0c;jQuery可以大大的简化复杂的js代码&#xff0c;使开发人员专注于实现页面的效果。 导入方式有两种 jQuery的导入方式有两种&#xff0c;一种是本地导入&#xff0c;一种是利用超链接导入。 方法…

AES加密(1):AES基础知识和计算过程

从产品代码的安全角度考虑&#xff0c;我们需要对代码、数据进行加密。加密的算法有很多种&#xff0c;基于速度考虑&#xff0c;我们一般使用对称加密算法&#xff0c;其中有一种常见的对称加密算法&#xff1a;AES(Advanced Encryption Standard)。在一些高端的MCU&#xff0…

智慧工地源码 智慧工地云平台源码 智慧工地APP源码

智慧工地的核心是数字化&#xff0c;它通过传感器、监控设备、智能终端等技术手段&#xff0c;实现对工地各个环节的实时数据采集和传输&#xff0c;如环境温度、湿度、噪音等数据信息&#xff0c;将数据汇集到云端进行处理和分析&#xff0c;生成各种报表、图表和预警信息&…

了解IL汇编循环

IL代码&#xff0c; .assembly extern mscorlib {}.assembly Test{.ver 1:0:1:0}.module test.exe.method static void main() cil managed{.maxstack 8.entrypoint.locals init (int32, int32)ldc.i4 4stloc.0 //Upper limit of the Loop, total 5 ldc.i4 0 stloc.…

【12】Git工具 协同工作平台使用教程 Gitee使用指南 腾讯工蜂使用指南【Gitee】【腾讯工蜂】【Git】

tips&#xff1a;少量的git安装和使用教程&#xff0c;更多讲快速使用上手Gitee和工蜂平台 一、准备工作 1、下载git Git - Downloads (git-scm.com) 找到对应操作系统&#xff0c;对应版本&#xff0c;对应的位数 下载后根据需求自己安装&#xff0c;然后用git --version验…