我们之前在虚拟主机中,提前定义了俩个类,一个数消费者核心类(已完成),一个是转发规则类,接下来我们完成转发规则类的设计
BindKey 与 routingkey都是有字母,数字,下换线组成的 ,使用. 号分割。
有俩种通配符, 一种是 * 号代表匹配任意一个部分, 一种是 #号代表匹配任意多个部分 , 通配符只能作为单独的字符串存在
具体情况如下
package com.example.demo.mqServer.core;
import com.example.demo.Common.MqException;
/*
* 使用这个类, 来实现交换机的转发规则.
* 同时也借助这个类验证 bindingKey 是否合法.
*/
public class Router {
// bindingKey 的构造规则:
// 1. 数字, 字母, 下划线
// 2. 使用 . 分割成若干部分
// 3. 允许存在 * 和 # 作为通配符. 但是通配符只能作为独立的分段.
public boolean checkBindingKey(String bindingKey) {
if (bindingKey == null || bindingKey.length() == 0){
return true;
}
for (char ch: bindingKey.toCharArray()) {
if ( ch >= 'A' && ch<='Z' ){
continue;
}
if (ch>= '0' && ch <= '9'){
continue;
}
if (ch >= 'a' && ch <='z'){
continue;
}
if (ch == '_' || ch =='.'||ch=='#'||ch=='*'){
continue;
}
return false;
}
// 检查 * 或者 # 是否是独立的部分.
String[] words = bindingKey.split("\\.");
//该处使用转义字符, 第一个 \是java转义 第二个 \ 是正则表达式
for (String word : words) {
if (word.length() >1 && (word.contains("*") || word.contains("#"))){
return false;
}
}
for (int i = 0; i < words.length - 1; 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;
}
// routingKey 的构造规则:
// 1. 数字, 字母, 下划线
// 2. 使用 . 分割成若干部分
public boolean checkRoutingKey(String routingKey) {
if (routingKey.length() == 0) {
// 空字符串. 合法的情况. 比如在使用 fanout 交换机的时候, routingKey 用不上, 就可以设为 ""
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;
}
// 该字符, 不是上述任何一种合法情况, 就直接返回 false
return false;
}
// 把每个字符都检查过, 没有遇到非法情况. 此时直接返回 true
return true;
}
//用来判定该消息是否可以转发给绑定对应的队列
public boolean route(ExchangeType exchangeType,Binding binding,Message message) throws MqException {
if (exchangeType == ExchangeType.fanout){
// 无脑返回一个TRUE
return true;
}else if (exchangeType == ExchangeType.topic){
return routeTopic(binding,message);
}else {
// 如果有其他情况说明出现了异常了
throw new MqException("[router] 交换机类型异常 exchangeType"+exchangeType);
}
}
private boolean routeTopic(Binding binding, Message message) {
// 先把这两个 key 进行切分
String[] bindingTokens = binding.getBindingKey().split("\\.");
String[] routingTokens = message.getRoutingKey().split("\\.");
// 引入两个下标, 指向上述两个数组. 初始情况下都为 0
int bindingIndex = 0;
int routingIndex = 0;
// 此处使用 while 更合适, 每次循环, 下标不一定就是 + 1, 不适合使用 for
while (bindingIndex < bindingTokens.length && routingIndex < routingTokens.length) {
if (bindingTokens[bindingIndex].equals("*")) {
// [情况二] 如果遇到 * , 直接进入下一轮. * 可以匹配到任意一个部分!!
bindingIndex++;
routingIndex++;
continue;
} else if (bindingTokens[bindingIndex].equals("#")) {
// 如果遇到 #, 需要先看看有没有下一个位置.
bindingIndex++;
if (bindingIndex == bindingTokens.length) {
// [情况三] 该 # 后面没东西了, 说明此时一定能匹配成功了!
return true;
}
// [情况四] # 后面还有东西, 拿着这个内容, 去 routingKey 中往后找, 找到对应的位置.
// findNextMatch 这个方法用来查找该部分在 routingKey 的位置. 返回该下标. 没找到, 就返回 -1
routingIndex = findNextMatch(routingTokens, routingIndex, bindingTokens[bindingIndex]);
if (routingIndex == -1) {
// 没找到匹配的结果. 匹配失败
return false;
}
// 找到的匹配的情况, 继续往后匹配.
bindingIndex++;
routingIndex++;
} else {
// [情况一] 如果遇到普通字符串, 要求两边的内容是一样的.
if (!bindingTokens[bindingIndex].equals(routingTokens[routingIndex])) {
return false;
}
bindingIndex++;
routingIndex++;
}
}
// [情况五] 判定是否是双方同时到达末尾
if (bindingIndex == bindingTokens.length && routingIndex == routingTokens.length) {
return true;
}
return false;
}
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;
}
}
package com.example.demo.mqServer.dataCenter;
import com.example.demo.Common.MqException;
import com.example.demo.mqServer.core.Binding;
import com.example.demo.mqServer.core.ExchangeType;
import com.example.demo.mqServer.core.Message;
import com.example.demo.mqServer.core.Router;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
@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
// aaa aaa true
// aaa.bbb aaa.bbb true
// aaa.bbb aaa.bbb.ccc false
// aaa.bbb aaa.ccc false
// aaa.bbb.ccc aaa.bbb.ccc true
// aaa.* aaa.bbb true
// aaa.*.bbb aaa.bbb.ccc false
// *.aaa.bbb aaa.bbb false
// # aaa.bbb.ccc true
// aaa.# aaa.bbb true
// aaa.# aaa.bbb.ccc true
// aaa.#.ccc aaa.ccc true
// aaa.#.ccc aaa.bbb.ccc true
// aaa.#.ccc aaa.aaa.bbb.ccc true
// #.ccc ccc true
// #.ccc aaa.bbb.ccc true
@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.assertFalse(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));
}
}