From 8befc9bbf448a5c1e696b390190d4537146ca85d Mon Sep 17 00:00:00 2001
From: tangmingyou <234767776@qq.com>
Date: Fri, 29 Apr 2022 17:08:27 +0800
Subject: [PATCH] ConsistentHashRouteLoadBalance
---
im-client/pom.xml | 7 +
.../soim/client/cmd/console/ConsoleBox.java | 309 ++++++++++++++++++
.../cmd/console/ConsoleBoxKeyHandler.java | 24 ++
.../handlers/ConsoleBoxPasswordHandler.java | 41 +++
.../cmd/console/handlers/JvmKeyHandler.java | 72 ++++
.../handler/msg/ResOnlineUserListHandler.java | 11 +-
.../net/sopod/soim/client/logger/Logger.java | 47 ++-
.../client/prettyconsole/ConsoleBoxTest.java | 241 ++++++++++++++
.../net/sopod/soim/entry/worker/Worker.java | 18 +-
im-service-api/im-router-api/pom.xml | 6 +
.../router/api/route/ConsistentHashRoute.java | 26 ++
.../src/main/resources/application.yml | 6 +-
12 files changed, 788 insertions(+), 20 deletions(-)
create mode 100644 im-client/src/main/java/net/sopod/soim/client/cmd/console/ConsoleBox.java
create mode 100644 im-client/src/main/java/net/sopod/soim/client/cmd/console/ConsoleBoxKeyHandler.java
create mode 100644 im-client/src/main/java/net/sopod/soim/client/cmd/console/handlers/ConsoleBoxPasswordHandler.java
create mode 100644 im-client/src/main/java/net/sopod/soim/client/cmd/console/handlers/JvmKeyHandler.java
create mode 100644 im-client/src/test/java/net/sopod/soim/client/prettyconsole/ConsoleBoxTest.java
create mode 100644 im-service-api/im-router-api/src/main/java/net/sopod/soim/router/api/route/ConsistentHashRoute.java
diff --git a/im-client/pom.xml b/im-client/pom.xml
index 8fcbe95..a33fac9 100644
--- a/im-client/pom.xml
+++ b/im-client/pom.xml
@@ -47,6 +47,13 @@
unirest-java
3.13.6
+
+
+ junit
+ junit
+ 3.8.1
+ test
+
\ No newline at end of file
diff --git a/im-client/src/main/java/net/sopod/soim/client/cmd/console/ConsoleBox.java b/im-client/src/main/java/net/sopod/soim/client/cmd/console/ConsoleBox.java
new file mode 100644
index 0000000..3d46851
--- /dev/null
+++ b/im-client/src/main/java/net/sopod/soim/client/cmd/console/ConsoleBox.java
@@ -0,0 +1,309 @@
+package net.sopod.soim.client.cmd.console;
+
+import com.google.common.base.*;
+import com.google.common.collect.Iterables;
+import net.sopod.soim.client.cmd.console.handlers.ConsoleBoxPasswordHandler;
+
+import java.awt.*;
+import java.awt.image.BufferedImage;
+import java.io.PrintStream;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.regex.Pattern;
+
+/**
+ * represents a console display box in the system console (or any PrintStream).
+ * used to display friendly technical information
+ * @author Willie Scholtz
+ */
+public final class ConsoleBox {
+
+ /**
+ * the character to use in box corners
+ */
+ private String boxChar = " + ";
+
+ /**
+ * the character to use to pad strings with
+ */
+ private char padChar = ' ';
+
+ /**
+ * the character used for the box's right and left sides
+ */
+ private String endChar = " | ";
+
+ /**
+ * the character used for the box's top, title and bottom sides
+ */
+ private String borderChar = "-";
+
+ /**
+ * the character used for representing black in a color
+ */
+ private String blackChar = " ";
+
+ /**
+ * the character used for representing white in a color
+ */
+ private String whiteChar = "#";
+
+ /**
+ * the character used for representing aliasing of fonts.
+ * actually other colors, but since our image is only black and white
+ * it will represent a shade, which in this case is aliasing
+ */
+ private String aliasChar = " ";
+
+ /**
+ * the key value separator for names and values
+ */
+ private String keyValueSeparator = " : ";
+
+ private final List handlers;
+ private final StringBuilder builder;
+ private boolean content;
+ private final int width;
+
+ /**
+ * creates a new instance of a ConsoleBox
+ * @param boxWidth the width of the box (in character count)
+ * @param title the initial title of the box, leave blank for none
+ */
+ public ConsoleBox(int boxWidth, String title) {
+ this.width = boxWidth;
+ this.builder = new StringBuilder();
+ this.handlers = new ArrayList();
+
+ // add default password handler
+ this.handlers.add(new ConsoleBoxPasswordHandler());
+
+ if (!Strings.isNullOrEmpty(title)) {
+ this.title(title);
+ }
+ }
+
+ /*
+ * creates a new instance of a ConsoleBox with no title
+ * @param boxWidth the width of the box (in character count)
+ */
+ public ConsoleBox(int boxWidth) {
+ this(boxWidth, null);
+ }
+
+ /**
+ * pads a string from both sides
+ * @param string the string to pad
+ * @param pad the padding character
+ * @param length the length to pad
+ * @return the padded string
+ */
+ private String padBoth(String string, String pad, int length) {
+ int right = (length - string.length()) / 2 + string.length();
+ String result = Strings.padEnd(string, right, pad.toCharArray()[0]);
+ return Strings.padStart(result, length, pad.toCharArray()[0]);
+ }
+
+ /**
+ * configures the box characters, note that sensible defaults are already set.
+ * @param cornerChar the character to use in box corners
+ * @param padChar the character to use to pad strings with
+ * @param sideChar the character used for the box's right and left sides
+ * @param borderChar the character used for the box's top, title and bottom sides
+ * @return the current instance
+ */
+ public ConsoleBox setBoxCharacters(String cornerChar, char padChar, String sideChar, String borderChar) {
+ this.boxChar = Preconditions.checkNotNull(cornerChar);
+ this.padChar = Preconditions.checkNotNull(padChar);
+ this.endChar = Preconditions.checkNotNull(sideChar);
+ this.borderChar = Preconditions.checkNotNull(borderChar);
+ return this;
+ }
+
+ /**
+ * configures the box's ASCII characters, note that sensible defaults are already set.
+ * @param blackChar the character used for representing black in a color
+ * @param whiteChar the character used for representing white in a color
+ * @param aliasChar the character used for representing aliasing of fonts.
+ * actually other colors, but since our image is only black and white
+ * it will represent a shade, which in this case is aliasing.
+ * @return the current instance
+ */
+ public ConsoleBox setAsciiCharacters(String blackChar, String whiteChar, String aliasChar) {
+ this.blackChar = Preconditions.checkNotNull(blackChar);
+ this.whiteChar = Preconditions.checkNotNull(whiteChar);
+ this.aliasChar = Preconditions.checkNotNull(aliasChar);
+
+ return this;
+ }
+
+ /**
+ * configures the box's key-value separator.
+ * @param keyValueSeparator the character used for representing black in a color
+ * actually other colors, but since our image is only black and white
+ * it will represent a shade, which in this case is aliasing.
+ * @return the current instance
+ */
+ public ConsoleBox setKeyValueSeparator(String keyValueSeparator) {
+ this.keyValueSeparator = Preconditions.checkNotNull(keyValueSeparator);
+ return this;
+ }
+
+ /**
+ * adds a new key-value handler for this ConsoleBox
+ * @param handler the handler to use
+ * @return the current instance
+ */
+ public ConsoleBox handler(ConsoleBoxKeyHandler handler) {
+ this.handlers.add(handler);
+ return this;
+ }
+
+ /**
+ * builds and writes this box to the specified output stream
+ * @param output
+ */
+ public void build(PrintStream output) {
+ this.title("");
+ output.println(this.builder.toString());
+ }
+
+ /**
+ * adds a title section to the console box
+ * @param title the title to use
+ * @return the current box
+ */
+ public ConsoleBox title(String title) {
+ this.builder.append("\n").append(this.boxChar).append(padBoth(title,
+ this.borderChar, this.width)).append(this.boxChar);
+
+ return this;
+ }
+
+ /**
+ * adds an empty line section to the console box
+ * @return the current box
+ */
+ public ConsoleBox empty() {
+ this.builder.append("\n").append(this.boxChar).append(
+ padBoth("", " ", this.width)).append(this.boxChar);
+
+ return this;
+ }
+
+ /**
+ * generates and writes the specified text as an ASCII image into this box
+ * @param text the text to write as ASCII
+ * @param invert should the ASCII colors be inverted?
+ * @return the current box
+ */
+ public ConsoleBox ascii(String text, boolean invert) {
+ final BufferedImage image = new BufferedImage(this.width,
+ 32, BufferedImage.TYPE_INT_RGB);
+
+ final Graphics graphics = image.getGraphics();
+ final Graphics2D g2d = (Graphics2D) graphics;
+
+ g2d.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING,
+ RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
+
+ Font textFont = new Font("Dialog", Font.BOLD, 22);
+ FontMetrics textMetrics = g2d.getFontMetrics(textFont);
+ g2d.setFont(textFont);
+
+ int tX = (image.getWidth() / 2) - (textMetrics.stringWidth(text) / 2);
+ int tY = (image.getHeight() / 2) + (textMetrics.getHeight() / 2) - 5;
+
+ g2d.drawString(text, tX, tY);
+ g2d.drawRenderedImage(image, null);
+ g2d.dispose();
+
+ final int iHeight = image.getHeight();
+ final int iWidth = image.getWidth();
+
+ final String bChar = invert ? this.whiteChar : this.blackChar;
+ final String wChar = invert ? this.blackChar : this.whiteChar;
+
+ for (int y = 0; y < iHeight; y++) {
+ final StringBuilder sb = new StringBuilder();
+ for (int x = 0; x < iWidth; x++) {
+ final int rgbColor = image.getRGB(x, y);
+ sb.append(rgbColor == -16777216 ? bChar : rgbColor == -1 ? wChar : aliasChar);
+ }
+
+ if (sb.toString().trim().isEmpty()) {
+ continue;
+ }
+
+ this.builder.append("\n").append(this.endChar)
+ .append(sb).append(this.endChar);
+ }
+
+ return this;
+ }
+
+ /**
+ * adds a informational line to the console box,
+ * automatically splitting large values
+ * @param key the name of the value to display
+ * @param value the value of this line
+ * @return the current box
+ */
+ public ConsoleBox line(String key, String value) {
+ key = Strings.isNullOrEmpty(key) ? "null" : key;
+ value = Strings.isNullOrEmpty(value) ? "" : value;
+
+ // get the key length
+ final int kL = key.length();
+ final int kSl = this.keyValueSeparator.length();
+
+ // calculate remaining box space for the value
+ final int ths = (this.width - kL - kSl);
+ Preconditions.checkState(ths > -1, "key[" + key + "] is to long "
+ + "for box with a " + width + " width!");
+
+ // \n | the_key_length_in_spaces
+ final String joinOn = ("\n" + this.endChar + Strings.padEnd("",
+ kL + kSl, this.padChar));
+
+ // get key handlers and modify if neccessary
+ for (ConsoleBoxKeyHandler handler : this.handlers) {
+ if (handler.shouldHandle(key)) {
+ value = handler.handleValue(key, value);
+ // don't break, possibilitty of multiple handlers
+ }
+ }
+
+ // if a key handler returns null, a key should be skipped
+ if (value != null) {
+ // split the string on either length or new lines
+ Iterable splitted = Splitter.on(Pattern
+ .compile("(?<=\\G.{" + ths + "})|\\n")).split(value);
+
+ // add the value + end characters (multiple lines)
+ String formatted = Joiner.on(joinOn).join(
+ Iterables.transform(splitted, new Function() {
+ @Override
+ public String apply(String input) {
+ return Strings.padEnd(input, ths, ' ') + endChar;
+ }
+ }));
+
+ // write completed line to builder
+ this.builder.append("\n").append(this.endChar).append(key)
+ .append(this.keyValueSeparator).append(formatted);
+
+ this.content = true;
+ }
+
+ return this;
+ }
+
+ /**
+ * @return true if {@link #line(java.lang.String, java.lang.String)}
+ * has been called at least once
+ */
+ public boolean hasContent() {
+ return content;
+ }
+}
\ No newline at end of file
diff --git a/im-client/src/main/java/net/sopod/soim/client/cmd/console/ConsoleBoxKeyHandler.java b/im-client/src/main/java/net/sopod/soim/client/cmd/console/ConsoleBoxKeyHandler.java
new file mode 100644
index 0000000..d46a750
--- /dev/null
+++ b/im-client/src/main/java/net/sopod/soim/client/cmd/console/ConsoleBoxKeyHandler.java
@@ -0,0 +1,24 @@
+package net.sopod.soim.client.cmd.console;
+
+/**
+ * interface for defining handlers for certain console box keys
+ * @author Willie Scholtz
+ */
+public interface ConsoleBoxKeyHandler {
+
+ /**
+ * determines if the specified key should be handled by this key handler
+ * @param key the key to check
+ * @return true if this key should be handled by this key handler
+ */
+ public boolean shouldHandle(final String key);
+
+ /**
+ * if this handler is set to handle the specified key it, will execute this method to handle it's value
+ * @param key the key being handled
+ * @param value the original value to handle, note that console box automatically handles new lines and long lines
+ * @return the formatted value for ConsoleBox, or null if the whole line should be skipped
+ */
+ public String handleValue(final String key, final String value);
+
+}
\ No newline at end of file
diff --git a/im-client/src/main/java/net/sopod/soim/client/cmd/console/handlers/ConsoleBoxPasswordHandler.java b/im-client/src/main/java/net/sopod/soim/client/cmd/console/handlers/ConsoleBoxPasswordHandler.java
new file mode 100644
index 0000000..4c2f042
--- /dev/null
+++ b/im-client/src/main/java/net/sopod/soim/client/cmd/console/handlers/ConsoleBoxPasswordHandler.java
@@ -0,0 +1,41 @@
+package net.sopod.soim.client.cmd.console.handlers;
+
+import net.sopod.soim.client.cmd.console.ConsoleBoxKeyHandler;
+
+import java.util.regex.Pattern;
+
+/**
+ * ensures no passwords are present in the ConsoleBox
+ * @author Willie Scholtz
+ */
+public class ConsoleBoxPasswordHandler implements ConsoleBoxKeyHandler {
+ /**
+ * pattern defining which attributes should be blurred
+ */
+ private static final Pattern IGNORE_ATT_PATT = Pattern
+ .compile("pass(word)?", Pattern.CASE_INSENSITIVE);
+
+ /**
+ * replacement value for sensitive data
+ */
+ private static final String SAFE_REPLACEMENT = "*****";
+
+ /**
+ * determines if a certain parameter is safe to transmit over the wire
+ * @param key the name of the parameter that will be sent
+ * @return true if the parameter may be sent
+ */
+ private static boolean safeToTransmit(final String key) {
+ return !IGNORE_ATT_PATT.matcher(key).find();
+ }
+
+ @Override
+ public boolean shouldHandle(String key) {
+ return !safeToTransmit(key);
+ }
+
+ @Override
+ public String handleValue(String key, String value) {
+ return SAFE_REPLACEMENT;
+ }
+}
\ No newline at end of file
diff --git a/im-client/src/main/java/net/sopod/soim/client/cmd/console/handlers/JvmKeyHandler.java b/im-client/src/main/java/net/sopod/soim/client/cmd/console/handlers/JvmKeyHandler.java
new file mode 100644
index 0000000..34156c3
--- /dev/null
+++ b/im-client/src/main/java/net/sopod/soim/client/cmd/console/handlers/JvmKeyHandler.java
@@ -0,0 +1,72 @@
+
+package net.sopod.soim.client.cmd.console.handlers;
+
+import com.google.common.base.Function;
+import com.google.common.base.Joiner;
+import com.google.common.base.Splitter;
+import com.google.common.collect.Iterables;
+import net.sopod.soim.client.cmd.console.ConsoleBoxKeyHandler;
+
+/**
+ * ensures that JVM system properties are handled correctly by consolebox
+ * @author Willie Scholtz
+ */
+public class JvmKeyHandler implements ConsoleBoxKeyHandler {
+
+ private static final boolean WINDOWS = System.getProperty("os.name").indexOf("Windows") > -1;
+
+ /**
+ * keys that should be converted to new lines
+ */
+ private static final String[] CLASSPATHS = new String[] {
+ "java.library.path", "java.class.path", "sun.boot.class.path"
+ };
+
+ /**
+ * keys that should be skipped
+ */
+ private static final String[] SKIPS = new String[] {
+ "line.seperator"
+ };
+
+ @Override
+ public boolean shouldHandle(String key) {
+ for (String string : CLASSPATHS) {
+ if (string.equals(key)) {
+ return true;
+ }
+ }
+
+ for (String string : SKIPS) {
+ if (string.equals(key)) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ @Override
+ public String handleValue(String key, String value) {
+ for (String string : CLASSPATHS) {
+ if (string.equals(key)) {
+ value = Joiner.on("\n").join(Iterables.transform(Splitter.on(WINDOWS ? ";" : ":").split(value),
+ new Function() {
+ int item = 0;
+ @Override
+ public String apply(String input) {
+ return "[" + ++item + "] " + input;
+ }
+ }));
+ }
+ }
+
+ for (String string : SKIPS) {
+ if (string.equals(key)) {
+ return null;
+ }
+ }
+
+ return value;
+ }
+}
\ No newline at end of file
diff --git a/im-client/src/main/java/net/sopod/soim/client/handler/msg/ResOnlineUserListHandler.java b/im-client/src/main/java/net/sopod/soim/client/handler/msg/ResOnlineUserListHandler.java
index 366f89f..1227852 100644
--- a/im-client/src/main/java/net/sopod/soim/client/handler/msg/ResOnlineUserListHandler.java
+++ b/im-client/src/main/java/net/sopod/soim/client/handler/msg/ResOnlineUserListHandler.java
@@ -5,6 +5,9 @@ import net.sopod.soim.client.logger.Logger;
import net.sopod.soim.client.session.MessageHandler;
import net.sopod.soim.data.msg.user.UserGroup;
+import java.util.List;
+import java.util.stream.Collectors;
+
/**
* ResOnlineUserListHandler
*
@@ -16,10 +19,10 @@ public class ResOnlineUserListHandler implements MessageHandler userLines = msg.getUsersList().stream()
+ .map(user -> user.getUid() + " | " + user.getAccount())
+ .collect(Collectors.toList());
+ Logger.logList("在线用户", userLines);
}
}
diff --git a/im-client/src/main/java/net/sopod/soim/client/logger/Logger.java b/im-client/src/main/java/net/sopod/soim/client/logger/Logger.java
index 8c75c6f..22c4c6e 100644
--- a/im-client/src/main/java/net/sopod/soim/client/logger/Logger.java
+++ b/im-client/src/main/java/net/sopod/soim/client/logger/Logger.java
@@ -1,6 +1,12 @@
package net.sopod.soim.client.logger;
+import com.google.common.base.Joiner;
import net.sopod.soim.client.cmd.CmdStarter;
+import net.sopod.soim.client.cmd.console.ConsoleBox;
+
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
/**
* Logger
@@ -10,30 +16,51 @@ import net.sopod.soim.client.cmd.CmdStarter;
*/
public class Logger {
+ private static int consoleWidth = 60;
+
public static void info(String msg, Object...args) {
- log("[info] " + msg, args);
+ log("info ", msg, args);
}
public static void error(String msg, Object...args) {
- log("[error] " + msg, args);
+ log("error ", msg, args);
}
- public static void log(String msg, Object...args) {
+ /**
+ * TODO ConsoleBox 中文尺寸处理
+ */
+ public static void log(String key, String msg, Object...args) {
msg = msg.replaceAll("\\{}", "%s");
for (int i = 0; i < args.length; i++) {
args[i] = String.valueOf(args[i]);
}
- if ("main".equals(Thread.currentThread().getName())) {
- System.out.println(String.format(msg, args));
- } else {
- // 其他线程异步打印
- System.out.println(System.lineSeparator() + String.format(msg, args));
- CmdStarter.printPre();
- }
+// if ("main".equals(Thread.currentThread().getName())) {
+// System.out.println(String.format(msg, args));
+// } else {
+// // 其他线程异步打印
+// System.out.println(System.lineSeparator() + String.format(msg, args));
+// CmdStarter.printPre();
+// }
+ ConsoleBox box = new ConsoleBox(consoleWidth);
+ box.title("提示");
+ box.line(key, String.format(msg, args));
+ box.build(System.out);
+ }
+
+ public static void logList(String key, List lines) {
+ ConsoleBox box = new ConsoleBox(consoleWidth);
+ box.title("");
+ String line = Joiner.on("\n").join(lines);
+ box.line(key, "\n" + line);
+ box.build(System.out);
}
public static void pre(String pre) {
System.out.print(pre);
}
+ public static void main(String[] args) {
+ System.out.println(Joiner.on(System.lineSeparator()).join(Arrays.asList("a", "b", "c")));
+ }
+
}
diff --git a/im-client/src/test/java/net/sopod/soim/client/prettyconsole/ConsoleBoxTest.java b/im-client/src/test/java/net/sopod/soim/client/prettyconsole/ConsoleBoxTest.java
new file mode 100644
index 0000000..e3cc238
--- /dev/null
+++ b/im-client/src/test/java/net/sopod/soim/client/prettyconsole/ConsoleBoxTest.java
@@ -0,0 +1,241 @@
+package net.sopod.soim.client.prettyconsole;
+
+import com.google.common.base.Charsets;
+import junit.framework.Test;
+import junit.framework.TestCase;
+import junit.framework.TestSuite;
+import net.sopod.soim.client.cmd.console.ConsoleBox;
+import net.sopod.soim.client.cmd.console.ConsoleBoxKeyHandler;
+
+import java.io.ByteArrayOutputStream;
+import java.io.PrintStream;
+import java.io.UnsupportedEncodingException;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+/**
+ * Unit test for testing console box
+ */
+public class ConsoleBoxTest extends TestCase {
+
+ /**
+ * Create the test case
+ * @param testName name of the test case
+ */
+ public ConsoleBoxTest(String testName) {
+ super(testName);
+ }
+
+ /**
+ * @return the suite of tests being tested
+ */
+ public static Test suite() {
+ return new TestSuite(ConsoleBoxTest.class);
+ }
+
+ private ConsoleBox basicBox(int width) {
+ ConsoleBox box = new ConsoleBox(width);
+ box.title("TEST");
+ return box;
+ }
+
+ private String getBox(ConsoleBox box) {
+ ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ box.build(new PrintStream(baos));
+
+ String actual = null;
+ try {
+ actual = baos.toString(Charsets.UTF_8.name());
+ } catch (UnsupportedEncodingException ex) {
+ Logger.getLogger(ConsoleBoxTest.class.getName()).log(
+ Level.SEVERE, "failed to get UTF-8", ex);
+ }
+
+ System.out.println(actual);
+ return actual;
+ }
+
+ /**
+ * @throws java.io.UnsupportedEncodingException
+ */
+ public void testCreation() throws UnsupportedEncodingException {
+ ConsoleBox box = this.basicBox(10);
+ box.line("a", "1");
+
+ final String actual = this.getBox(box);
+ final String expected = "\n" +
+ " + ---TEST--- + \n" +
+ " | a : 1 | \n" +
+ " + ---------- + \r\n";
+
+ assertEquals("basic box build", expected, actual);
+ }
+
+ public void testOverflow() throws UnsupportedEncodingException {
+ ConsoleBox box = this.basicBox(10);
+ box.line("a", "123456789987654321");
+
+ final String actual = this.getBox(box);
+ final String expected = "\n" +
+ " + ---TEST--- + \n" +
+ " | a : 123456 | \n" +
+ " | 789987 | \n" +
+ " | 654321 | \n" +
+ " | | \n" +
+ " + ---------- + \r\n";
+
+ assertEquals("overflow in content", expected, actual);
+ }
+
+ public void testNewLineOverflow() throws UnsupportedEncodingException {
+ ConsoleBox box = this.basicBox(10);
+ box.line("a", "1\n2\n3\n4\n5\n6\n7\n8\n9\n12345679abcdefg");
+
+ final String actual = this.getBox(box);
+ final String expected = "\n" +
+ " + ---TEST--- + \n" +
+ " | a : 1 | \n" +
+ " | 2 | \n" +
+ " | 3 | \n" +
+ " | 4 | \n" +
+ " | 5 | \n" +
+ " | 6 | \n" +
+ " | 7 | \n" +
+ " | 8 | \n" +
+ " | 9 | \n" +
+ " | 123456 | \n" +
+ " | 79abcd | \n" +
+ " | efg | \n" +
+ " + ---------- + \r\n";
+
+ assertEquals("overflow in content with new lines", expected, actual);
+ }
+
+ public void testDefaultPasswordHandling() throws UnsupportedEncodingException {
+ ConsoleBox box = this.basicBox(20);
+ box.line("password", "supercoolpassword");
+ box.line("pass", "mysecret");
+
+ final String actual = this.getBox(box);
+ final String expected = "\n" +
+ " + --------TEST-------- + \n" +
+ " | password : ***** | \n" +
+ " | pass : ***** | \n" +
+ " + -------------------- + \r\n";
+
+
+ assertEquals("default password handling", expected, actual);
+ }
+
+ public void testLinesAndTitles() throws UnsupportedEncodingException {
+ ConsoleBox box = this.basicBox(25);
+ box.line("keyOne", "valueOne");
+ box.line("keyTwo", "valueTwo");
+ box.title("TITLE");
+ box.line("keyThree", "valueThree");
+ box.line("keyFour", "valueFour");
+
+ final String actual = this.getBox(box);
+ final String expected = "\n" +
+ " + -----------TEST---------- + \n" +
+ " | keyOne : valueOne | \n" +
+ " | keyTwo : valueTwo | \n" +
+ " + ----------TITLE---------- + \n" +
+ " | keyThree : valueThree | \n" +
+ " | keyFour : valueFour | \n" +
+ " + ------------------------- + \r\n";
+
+ assertEquals("with lines and titles", expected, actual);
+ }
+
+ public void testAscii() throws UnsupportedEncodingException {
+ ConsoleBox box = this.basicBox(60);
+ box.line("keyOne", "valueOne");
+ box.line("keyTwo", "valueTwo");
+ box.title("TITLE");
+ box.line("keyThree", "valueThree");
+ box.line("keyFour", "valueFour");
+ box.title("");
+ box.ascii("ASCII", false);
+
+ final String actual = this.getBox(box);
+ final String expected = "\n" +
+ " + ----------------------------TEST---------------------------- + \n" +
+ " | keyOne : valueOne | \n" +
+ " | keyTwo : valueTwo | \n" +
+ " + ----------------------------TITLE--------------------------- + \n" +
+ " | keyThree : valueThree | \n" +
+ " | keyFour : valueFour | \n" +
+ " + ------------------------------------------------------------ + \n" +
+ " | ### ### ### | \n" +
+ " | #### ######## ####### ### ### | \n" +
+ " | ##### ########## ########## ### ### | \n" +
+ " | ## ## ## ## ## ## ### ### | \n" +
+ " | ## # ## ## ## ### ### | \n" +
+ " | ## ## ## ## ### ### | \n" +
+ " | ## ## ### ## ### ### | \n" +
+ " | ## # ###### ## ### ### | \n" +
+ " | ## ## ### ## ### ### | \n" +
+ " | ######### ## ## ### ### | \n" +
+ " | ########### ## ## ## ### ### | \n" +
+ " | ########### ## ## ## ### ### | \n" +
+ " | ## ## ### ## ## ## ### ### | \n" +
+ " | ## ## ########## ######### ### ### | \n" +
+ " | ## ## ######## ####### ### ### | \n" +
+ " | ## ## # ### ### | \n" +
+ " + ------------------------------------------------------------ + \r\n";
+
+ assertEquals("with ascii text", expected, actual);
+ }
+
+ public void testCustomHandler() throws UnsupportedEncodingException {
+ ConsoleBox box = this.basicBox(25);
+ box.handler(new ConsoleBoxKeyHandler() {
+ public boolean shouldHandle(String key) {
+ return "a".equals(key) || "c".equals(key);
+ }
+
+ public String handleValue(String key, String value) {
+ if ("a".equals(key)) {
+ return "a_replace";
+ }
+
+ if ("c".equals(key)) {
+ return "c_replace";
+ }
+
+ return value;
+ }
+ });
+ box.line("a", "valueOne");
+ box.line("b", "valueTwo");
+ box.line("c", "valueThree");
+ box.line("d", "valueFour");
+
+ final String actual = this.getBox(box);
+ final String expected = "\n" +
+ " + -----------TEST---------- + \n" +
+ " | a : a_replace | \n" +
+ " | b : valueTwo | \n" +
+ " | c : c_replace | \n" +
+ " | d : valueFour | \n" +
+ " + ------------------------- + \r\n";
+
+ assertEquals("with a custom handler", expected, actual);
+ }
+
+ public void testEmpty() throws UnsupportedEncodingException {
+ ConsoleBox box = this.basicBox(5);
+ box.empty();
+ box.empty();
+
+ final String actual = this.getBox(box);
+ final String expected = "\n" +
+ " + -TEST + \n" +
+ " + + \n" +
+ " + + \n" +
+ " + ----- + \r\n";
+
+ assertEquals("with empty content", expected, actual);
+ }
+}
\ No newline at end of file
diff --git a/im-entry/src/main/java/net/sopod/soim/entry/worker/Worker.java b/im-entry/src/main/java/net/sopod/soim/entry/worker/Worker.java
index b0aceab..a930e39 100644
--- a/im-entry/src/main/java/net/sopod/soim/entry/worker/Worker.java
+++ b/im-entry/src/main/java/net/sopod/soim/entry/worker/Worker.java
@@ -5,6 +5,8 @@ import com.lmax.disruptor.dsl.Disruptor;
import com.lmax.disruptor.dsl.ProducerType;
import net.sopod.soim.common.util.ImClock;
import net.sopod.soim.entry.util.FastThreadLocalThreadFactory;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
import java.util.concurrent.*;
@@ -13,6 +15,8 @@ import java.util.concurrent.*;
*/
public class Worker implements EventHandler, EventFactory {
+ private static final Logger logger = LoggerFactory.getLogger(Worker.class);
+
private Disruptor disruptor;
private RingBuffer ringBuffer;
@@ -54,11 +58,15 @@ public class Worker implements EventHandler, EventFactory
@Override
public void onEvent(TaskEvent taskEvent, long sequence, boolean endOfBatch) throws Exception {
- long start = ImClock.millis();
- taskEvent.getTask().run();
- long time = ImClock.millis() - start;
- if (time > 100) {
- System.out.println("任务执行时间过长:" + time);
+ try {
+ long start = ImClock.millis();
+ taskEvent.getTask().run();
+ long time = ImClock.millis() - start;
+ if (time > 100) {
+ logger.info("任务执行时间过长: {}", time);
+ }
+ } catch (Exception e) {
+ logger.error("任务执行失败:", e);
}
// 事件对象不会释放,将数据置空
taskEvent.setTask(null);
diff --git a/im-service-api/im-router-api/pom.xml b/im-service-api/im-router-api/pom.xml
index 6f1f2c6..117f248 100644
--- a/im-service-api/im-router-api/pom.xml
+++ b/im-service-api/im-router-api/pom.xml
@@ -17,6 +17,12 @@
net.sopod
1.0.0
+
+ org.apache.dubbo
+ dubbo-cluster
+ ${dubbo.version}
+ provided
+
\ No newline at end of file
diff --git a/im-service-api/im-router-api/src/main/java/net/sopod/soim/router/api/route/ConsistentHashRoute.java b/im-service-api/im-router-api/src/main/java/net/sopod/soim/router/api/route/ConsistentHashRoute.java
new file mode 100644
index 0000000..b8b455d
--- /dev/null
+++ b/im-service-api/im-router-api/src/main/java/net/sopod/soim/router/api/route/ConsistentHashRoute.java
@@ -0,0 +1,26 @@
+package net.sopod.soim.router.api.route;
+
+import org.apache.dubbo.common.URL;
+import org.apache.dubbo.rpc.Invocation;
+import org.apache.dubbo.rpc.Invoker;
+import org.apache.dubbo.rpc.cluster.loadbalance.AbstractLoadBalance;
+
+import java.util.List;
+
+/**
+ * ConsistentHashRoute
+ * 一致性 hash 服务路由
+ *
+ *
+ * @author tmy
+ * @date 2022-04-29 14:41
+ */
+public class ConsistentHashRoute extends AbstractLoadBalance {
+
+ @Override
+ protected Invoker doSelect(List> invokers, URL url, Invocation invocation) {
+ Invoker invoker = invokers.get(0);
+ return null;
+ }
+
+}
diff --git a/im-service/im-router/src/main/resources/application.yml b/im-service/im-router/src/main/resources/application.yml
index 3fa7f6d..3e86641 100644
--- a/im-service/im-router/src/main/resources/application.yml
+++ b/im-service/im-router/src/main/resources/application.yml
@@ -20,4 +20,8 @@ dubbo:
group: so-im
protocol:
name: dubbo
- port: 3031
\ No newline at end of file
+ port: 3032
+ consumer:
+ check: false
+ provider:
+ loadbalance: consistenthash
\ No newline at end of file