Compare commits

..

No commits in common. "master" and "1.3.4" have entirely different histories.

7 changed files with 130 additions and 240 deletions

View File

@ -1,23 +1,18 @@
[![RediSkript Build](https://github.com/Limework/RediSkript/actions/workflows/maven.yml/badge.svg?branch=master)](https://github.com/Limework/RediSkript/actions/workflows/maven.yml)
RediSkript allows you to communicate between your servers with use of Redis, it's very fast and easy to use.
## RediSkript
Allows you to communicate between your Minecraft servers with use of Redis and Skript, it's very fast and easy to use.
Skript: https://github.com/SkriptLang/Skript Skript: https://github.com/SkriptLang/Skript
RediSkript spigot page: https://www.spigotmc.org/resources/rediskript-communicate-between-servers-with-ease.85067/s RediSkript spigot page: https://www.spigotmc.org/resources/rediskript-communicate-between-servers-with-ease.85067/
Minecraft server version supported: **1.8.8+** (On 1.8, may need to use a fork of Skript) Jedis: https://github.com/redis/jedis
You can transfer any data in the form of text between your servers, you can program it to execute a set of instructions on the server depending on the redis message, etc. This can be used for making scripts that sync data between all servers and much more! You can transfer any data in the form of text between your servers, you can program it to execute a set of instructions on the server depending on the redis message, etc. This can be used for making scripts that sync data between all servers and much more!
It is developed and maintained by Govindas & the team of Govindas Limework developers. It is developed and maintained by Govindas & the team of Govindas Limework developers.
There is only one command: **/reloadredis** it fully reloads the configuration, you can reload IP, password, channels and everything else. Redis Message:
You only need to have matching configuration in every server for communication and a Redis server to connect to. I recommend using a VPS for hosting redis server, but there also are free redis hosting options available.
### Redis Message
``` ```
on redis message: on redis message:
if redis channel is "world": if redis channel is "world":
@ -29,14 +24,12 @@ command /sendredis <text> <text>:
send redis message arg 1 to channel arg 2 send redis message arg 1 to channel arg 2
send redis message "hello world!" to channel "world" send redis message "hello world!" to channel "world"
``` ```
### Managing variables Managing variables:
``` ```
set variables "test::1", "test::2", "test::3" in channel "global" to 100 set variables "test::1", "test::2", "test::3" in channel "global" to 100
#then use this in any server that listens to "global" redis channel and was online when the above line was executed: #then use this in any server that listens to "global" redis channel and was online when the above line was executed:
send "%{test::*}%" #outputs 100, 100 and 100 send "%{test::*}%" #outputs 100, 100 and 100
add 100 to variables "test::1" and "test::2" in channel "global"
remove 10 from variable "test::1" in channel "global"
delete variables "test::*" in channel "global" delete variables "test::*" in channel "global"
set variable "test::%uuid of player%" in channel "playerdata" to tool of player set variable "test::%uuid of player%" in channel "playerdata" to tool of player
@ -48,8 +41,11 @@ Syntax:
variable[s] %strings% in [redis] [channel] %string% variable[s] %strings% in [redis] [channel] %string%
``` ```
### Configuration There is only one command: **/reloadredis** it fully reloads the configuration, you can reload IP, password, channels and everything else.
plugins/RediSkript/config.yml
You only need to have matching configuration in every server for communication and a Redis server to connect to. I recommend using VPS for hosting redis server, I personally use VPS from humbleservers.com.
Configuration:
``` ```
Redis: Redis:
#a secure password that cannot be cracked, please change it! #a secure password that cannot be cracked, please change it!
@ -76,12 +72,13 @@ Redis:
#the encryption configuration must be the same across all servers in order to communicate #the encryption configuration must be the same across all servers in order to communicate
#use 16 characters long key for AES-128 encryption #use 16 characters long key for AES-128 encryption
#32 characters long key for AES-256 encryption (recommended) #32 characters long key for AES-256 encryption
#AES-128 is faster, but less secure (but it is not crackable by today's technology as of 2020, may be crackable by quantum computers)
#the AES implementation used in RediSkript uses SIV mode, which makes the same key resistant to cracking for a big count of messages without the need of changing the key very often #the AES implementation used in RediSkript uses SIV mode, which makes the same key resistant to cracking for a big count of messages without the need of changing the key very often
EncryptMessages: true EncryptMessages: true
#EncryptionKey and MacKey must be different #EncryptionKey and MacKey must be different
EncryptionKey: "32CHARACTERS KEY" EncryptionKey: "16CHARACTERS KEY"
MacKey: "32CHARACTERS KEY" MacKey: "16CHARACTERS KEY"
#the channels from which this server can receive messages #the channels from which this server can receive messages

View File

@ -5,7 +5,7 @@
<parent> <parent>
<artifactId>RediSkript</artifactId> <artifactId>RediSkript</artifactId>
<groupId>net.limework</groupId> <groupId>net.limework</groupId>
<version>1.3.7-SNAPSHOT</version> <version>1.3.4</version>
</parent> </parent>
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
@ -57,7 +57,7 @@
<dependency> <dependency>
<groupId>com.github.SkriptLang</groupId> <groupId>com.github.SkriptLang</groupId>
<artifactId>Skript</artifactId> <artifactId>Skript</artifactId>
<version>2.6.1</version> <version>2.5.3</version>
<type>jar</type> <type>jar</type>
<exclusions> <exclusions>
<exclusion> <exclusion>
@ -73,6 +73,11 @@
<version>1.16.5-R0.1-SNAPSHOT</version> <version>1.16.5-R0.1-SNAPSHOT</version>
<scope>provided</scope> <scope>provided</scope>
</dependency> </dependency>
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>3.7.0</version>
</dependency>
<dependency> <dependency>
<groupId>net.limework</groupId> <groupId>net.limework</groupId>
<artifactId>RediSkript-core</artifactId> <artifactId>RediSkript-core</artifactId>

View File

@ -19,7 +19,6 @@ import java.nio.charset.StandardCharsets;
import java.util.Base64; import java.util.Base64;
import java.util.List; import java.util.List;
import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicBoolean;
import java.util.logging.Level;
public class RedisController extends BinaryJedisPubSub implements Runnable { public class RedisController extends BinaryJedisPubSub implements Runnable {
@ -47,30 +46,18 @@ public class RedisController extends BinaryJedisPubSub implements Runnable {
int maxConnections = config.getInt("Redis.MaxConnections"); int maxConnections = config.getInt("Redis.MaxConnections");
//do not allow less than 2 max connections as that causes issues //do not allow less than 2 max connections as that causes issues
if (maxConnections < 2) { if (maxConnections < 2) { maxConnections = 2; }
maxConnections = 2;
}
JConfig.setMaxTotal(maxConnections); JConfig.setMaxTotal(maxConnections);
JConfig.setMaxIdle(maxConnections); JConfig.setMaxIdle(maxConnections);
JConfig.setMinIdle(1); JConfig.setMinIdle(1);
JConfig.setBlockWhenExhausted(true); JConfig.setBlockWhenExhausted(true);
final String password = config.getString("Redis.Password", ""); this.jedisPool = new JedisPool(JConfig,
if (password.isEmpty()) { config.getString("Redis.Host"),
this.jedisPool = new JedisPool(JConfig, config.getInt("Redis.Port"),
config.getString("Redis.Host", "127.0.0.1"), config.getInt("Redis.TimeOut"),
config.getInt("Redis.Port", 6379), config.getString("Redis.Password"),
config.getInt("Redis.TimeOut", 9000), config.getBoolean("Redis.useTLS"));
config.getBoolean("Redis.useTLS", false));
} else {
this.jedisPool = new JedisPool(JConfig,
config.getString("Redis.Host", "127.0.0.1"),
config.getInt("Redis.Port", 6379),
config.getInt("Redis.TimeOut", 9000),
password,
config.getBoolean("Redis.useTLS", false));
}
encryption = new Encryption(config.getBoolean("Redis.EncryptMessages"), encryption = new Encryption(config.getBoolean("Redis.EncryptMessages"),
config.getString("Redis.EncryptionKey"), config.getString("Redis.EncryptionKey"),
config.getString("Redis.MacKey")); config.getString("Redis.MacKey"));
@ -114,201 +101,96 @@ public class RedisController extends BinaryJedisPubSub implements Runnable {
} }
@Override @Override
public void onMessage(byte[] channel, byte[] message) { public void onMessage(byte[] channel, byte[] message){
String channelString = new String(channel, StandardCharsets.UTF_8); String channelString = new String(channel, StandardCharsets.UTF_8);
String receivedMessage = null; String receivedMessage = null;
try { try {
//if encryption is enabled, decrypt the message, else just convert binary to string //if encryption is enabled, decrypt the message, else just convert binary to string
if (this.encryption.isEncryptionEnabled()) { if (this.encryption.isEncryptionEnabled()) {
try { try {
receivedMessage = encryption.decrypt(message); receivedMessage = encryption.decrypt(message);
} catch (UnauthenticCiphertextException | IllegalBlockSizeException e) { } catch (UnauthenticCiphertextException | IllegalBlockSizeException e) {
e.printStackTrace(); e.printStackTrace();
}
} else {
//encryption is disabled, so let's just get the string
receivedMessage = new String(message, StandardCharsets.UTF_8);
} }
if (receivedMessage != null) {
} else { JSONObject j = new JSONObject(receivedMessage);
//encryption is disabled, so let's just get the string if (j.get("Type").equals("Skript")) {
receivedMessage = new String(message, StandardCharsets.UTF_8); JSONArray messages = j.getJSONArray("Messages");
} RedisMessageEvent event;
if (receivedMessage != null) { for (int i = 0; i < messages.length(); i++) {
JSONObject j = new JSONObject(receivedMessage); event = new RedisMessageEvent(channelString, messages.get(i).toString(), j.getLong("Date"));
if (j.get("Type").equals("Skript")) { //if plugin is disabling, don't call events anymore
JSONArray messages = j.getJSONArray("Messages"); if (plugin.isEnabled()) {
RedisMessageEvent event; RedisMessageEvent finalEvent = event;
for (int i = 0; i < messages.length(); i++) { Bukkit.getScheduler().runTask(plugin, () -> plugin.getServer().getPluginManager().callEvent(finalEvent));
event = new RedisMessageEvent(channelString, messages.get(i).toString(), j.getLong("Date")); }
//if plugin is disabling, don't call events anymore
if (plugin.isEnabled()) {
RedisMessageEvent finalEvent = event;
Bukkit.getScheduler().runTask(plugin, () -> plugin.getServer().getPluginManager().callEvent(finalEvent));
} }
} } else if (j.get("Type").equals("SkriptVariables")) {
} else if (j.get("Type").equals("SkriptVariables")) {
//Transfer variables between servers //Transfer variables between servers
JSONArray varNames = j.getJSONArray("Names"); JSONArray variableNames = j.getJSONArray("Names");
Object inputValue; Object inputValue;
String changeValue = null; String changeValue = null;
JSONArray varValues = null; JSONArray variableValues = null;
if (!j.isNull("Values")) { if (!j.isNull("Values")) {
varValues = j.getJSONArray("Values"); variableValues = j.getJSONArray("Values");
} }
for (int i = 0; i < varNames.length(); i++) { for (int i = 0; i < variableNames.length(); i++) {
String varName = varNames.get(i).toString();
if (j.isNull("Values")) {
// only check for SET here, because null has to be ignored in all other cases if (j.isNull("Values")) {
if (j.getString("Operation").equals("SET")) { //only check for SET here, because null has to be ignored in all other cases
Variables.setVariable(varName, null, null, false);
}
} else { if (j.getString("Operation").equals("SET")) {
if (!varValues.isNull(i)) { Variables.setVariable(variableNames.get(i).toString(), null, null, false);
changeValue = varValues.get(i).toString(); }
}
String[] inputs = changeValue.split("\\^", 2);
inputValue = Classes.deserialize(inputs[0], Base64.getDecoder().decode(inputs[1]));
switch (j.getString("Operation")) {
case "ADD":
if (varName.charAt(varName.length() - 1) == '*') {
plugin.getLogger().log(Level.WARNING, "Adding to {::*} variables in RediSkript is not supported. Variable name: " + varName);
continue;
}
Object variable = Variables.getVariable(varName, null, false);
if (variable == null) {
Variables.setVariable(varName, inputValue, null, false);
} else if (variable instanceof Long) {
if (inputValue instanceof Integer) {
inputValue = Long.valueOf((Integer) inputValue);
}
if (inputValue instanceof Long) {
Variables.setVariable(varName, (Long) variable + (Long) inputValue, null, false);
} else if (inputValue instanceof Double) {
// convert Long variable to Double } else {
variable = Double.valueOf((Long) variable); if (!variableValues.isNull(i)) {
Variables.setVariable(varName, (Double) variable + (Double) inputValue, null, false); changeValue = variableValues.get(i).toString();
} else { }
// Not supported input type String[] inputs = changeValue.split("\\^", 2);
plugin.getLogger().log(Level.WARNING, "Unsupported add action of data type (" + inputValue.getClass().getName() + ") on variable: " + varName); inputValue = Classes.deserialize(inputs[0], Base64.getDecoder().decode(inputs[1]));
continue; switch (j.getString("Operation")) {
} case "ADD":
} else if (variable instanceof Double) { //I will add this once someone tells me how to remove from Skript variable
if (inputValue instanceof Integer) { //because using SET operation has issues with inconvertible types (Double and Long)
inputValue = Double.valueOf((Integer) inputValue); //variable = (Variable) Variables.getVariable(variableNames.get(i).toString(), null, false);
} // variable.change(null, (Object[]) inputValue, Changer.ChangeMode.REMOVE);
if (inputValue instanceof Double) { case "REMOVE":
Variables.setVariable(varName, (Double) variable + (Double) inputValue, null, false); //I will add this once someone tells me how to remove from Skript variable
} else if (inputValue instanceof Long) { //because using SET operation has issues with inconvertible types (Double and Long)
Variables.setVariable(varName, (Double) variable + ((Long) inputValue).doubleValue(), null, false); //variable = (Variable) Variables.getVariable(variableNames.get(i).toString(), null, false);
} else { // variable.change(null, (Object[]) inputValue, Changer.ChangeMode.REMOVE);
// Not supported input type break;
plugin.getLogger().log(Level.WARNING, "Unsupported add action of data type (" + inputValue.getClass().getName() + ") on variable: " + varName); case "SET":
continue; String variableName = variableNames.get(i).toString();
}
} else if (variable instanceof Integer) { //this is needed, because setting a {variable::*} causes weird behavior, like
if (inputValue instanceof Integer) { //1st set operation is no data, 2nd has data, etc.
Variables.setVariable(varName, (Integer) variable + (Integer) inputValue, null, false); //if you set it to null before action, it works correctly
} else if (inputValue instanceof Double) {
// convert Integer variable to Double
variable = Double.valueOf((Integer) variable);
Variables.setVariable(varName, (Double) variable + (Double) inputValue, null, false);
} else if (inputValue instanceof Long) {
// convert Integer variable to Long
variable = Long.valueOf((Integer) variable);
Variables.setVariable(varName, (Long) variable + (Long) inputValue, null, false);
}
} else {
// Not supported input type
plugin.getLogger().log(Level.WARNING, "Unsupported variable type in add action (" + variable.getClass().getName() + ") on variable: " + varName);
continue;
}
break;
case "REMOVE":
if (varName.charAt(varName.length() - 1) == '*') {
plugin.getLogger().log(Level.WARNING, "Removing from {::*} variables in RediSkript is not supported. Variable name: " + varName);
continue;
}
variable = Variables.getVariable(varName, null, false);
if (variable == null) {
if (inputValue instanceof Long) {
Variables.setVariable(varName, -(Long) inputValue, null, false);
} else if (inputValue instanceof Double) {
Variables.setVariable(varName, -(Double) inputValue, null, false);
} else if (inputValue instanceof Integer) {
Variables.setVariable(varName, -(Integer) inputValue, null, false);
} else {
// Not supported input type
plugin.getLogger().log(Level.WARNING, "Unsupported remove action of data type (" + inputValue.getClass().getName() + ") on variable: " + varName);
continue;
}
} else if (variable instanceof Long) {
if (inputValue instanceof Integer) {
inputValue = Long.valueOf((Integer) inputValue);
}
if (inputValue instanceof Long) {
Variables.setVariable(varName, (Long) variable - (Long) inputValue, null, false);
} else if (inputValue instanceof Double) {
// convert Long variable to Double if (variableName.charAt(variableName.length()-1) == '*') {
variable = Double.valueOf((Long) variable); Variables.setVariable(variableName, null, null, false);
Variables.setVariable(varName, (Double) variable - (Double) inputValue, null, false); }
} else { Variables.setVariable(variableNames.get(i).toString(), inputValue, null, false);
// Not supported input type
plugin.getLogger().log(Level.WARNING, "Unsupported remove action of data type (" + inputValue.getClass().getName() + ") on variable: " + varName);
continue;
}
} else if (variable instanceof Double) {
if (inputValue instanceof Integer) {
inputValue = Double.valueOf((Integer) inputValue);
}
if (inputValue instanceof Double) {
Variables.setVariable(varName, (Double) variable - (Double) inputValue, null, false);
} else if (inputValue instanceof Long) {
Variables.setVariable(varName, (Double) variable - ((Long) inputValue).doubleValue(), null, false);
}
} else if (variable instanceof Integer) {
if (inputValue instanceof Integer) {
Variables.setVariable(varName, (Integer) variable - (Integer) inputValue, null, false);
} else if (inputValue instanceof Long) {
// convert Integer variable to Long
variable = Long.valueOf((Integer) variable);
Variables.setVariable(varName, (Long) variable - (Long) inputValue, null, false);
} else if (inputValue instanceof Double) {
// convert Integer variable to Double
variable = Double.valueOf((Integer) variable);
Variables.setVariable(varName, (Double) variable - (Double) inputValue, null, false);
}
} else {
// Not supported input type
plugin.getLogger().log(Level.WARNING, "Unsupported variable type in remove action (" + variable.getClass().getName() + ") on variable: " + varName);
continue;
}
break;
case "SET":
//this is needed, because setting a {variable::*} causes weird behavior, like
//1st set operation is no data, 2nd has data, etc.
//if you set it to null before action, it works correctly
if (varName.charAt(varName.length() - 1) == '*') {
Variables.setVariable(varName, null, null, false);
}
Variables.setVariable(varNames.get(i).toString(), inputValue, null, false);
break;
}
} }
} }
} }
} }
} catch (Exception e) {
plugin.sendErrorLogs("&cI got a message that was empty from channel " + channelString + " please check your code that you used to send the message. Message content:");
plugin.sendErrorLogs(receivedMessage);
e.printStackTrace();
} }
} catch (Exception e) {
plugin.sendErrorLogs("&cI got a message that was empty from channel " + channelString + " please check your code that you used to send the message. Message content:");
plugin.sendErrorLogs(receivedMessage);
e.printStackTrace();
} }
}
public void sendMessage(String[] message, String channel) { public void sendMessage(String[] message, String channel) {
JSONObject json = new JSONObject(); JSONObject json = new JSONObject();
@ -344,17 +226,17 @@ public class RedisController extends BinaryJedisPubSub implements Runnable {
//so to avoid issues, it's best to do it always on separate thread //so to avoid issues, it's best to do it always on separate thread
if (plugin.isEnabled()) { if (plugin.isEnabled()) {
plugin.getServer().getScheduler().runTaskAsynchronously(plugin, () -> { plugin.getServer().getScheduler().runTaskAsynchronously(plugin, () -> {
try (Jedis j = jedisPool.getResource()) { try (BinaryJedis j = jedisPool.getResource()) {
j.publish(channel.getBytes(StandardCharsets.UTF_8), message); j.publish(channel.getBytes(StandardCharsets.UTF_8), message);
} catch (Exception e) { } catch (Exception e) {
plugin.sendErrorLogs("Error sending redis message!"); plugin.sendErrorLogs("Error sending redis message!");
e.printStackTrace(); e.printStackTrace();
} }
}); });
} else { } else {
//execute sending of redis message on the main thread if plugin is disabling //execute sending of redis message on the main thread if plugin is disabling
//so it can still process the sending //so it can still process the sending
try (Jedis j = jedisPool.getResource()) { try (BinaryJedis j = jedisPool.getResource()) {
j.publish(channel.getBytes(StandardCharsets.UTF_8), message); j.publish(channel.getBytes(StandardCharsets.UTF_8), message);
} catch (Exception e) { } catch (Exception e) {
e.printStackTrace(); e.printStackTrace();

View File

@ -73,7 +73,7 @@ public class ExprVariableInChannel extends SimpleExpression<Object> {
@Override @Override
public Class<?>[] acceptChange(Changer.ChangeMode mode) { public Class<?>[] acceptChange(Changer.ChangeMode mode) {
//if (mode == Changer.ChangeMode.DELETE || mode == Changer.ChangeMode.SET || mode == Changer.ChangeMode.ADD || mode == Changer.ChangeMode.REMOVE) //if (mode == Changer.ChangeMode.DELETE || mode == Changer.ChangeMode.SET || mode == Changer.ChangeMode.ADD || mode == Changer.ChangeMode.REMOVE)
if (mode == Changer.ChangeMode.DELETE || mode == Changer.ChangeMode.SET || mode == Changer.ChangeMode.ADD || mode == Changer.ChangeMode.REMOVE) if (mode == Changer.ChangeMode.DELETE || mode == Changer.ChangeMode.SET)
return CollectionUtils.array(Object.class); return CollectionUtils.array(Object.class);
return null; return null;

View File

@ -23,13 +23,13 @@ Redis:
#the encryption configuration must be the same across all servers in order to communicate #the encryption configuration must be the same across all servers in order to communicate
#use 16 characters long key for AES-128 encryption #use 16 characters long key for AES-128 encryption
#32 characters long key for AES-256 encryption (recommended) #32 characters long key for AES-256 encryption
#AES-128 is faster, but less secure (but it is not crackable by today's technology as of 2020, may be crackable by quantum computers) #AES-128 is faster, but less secure (but it is not crackable by today's technology as of 2020, may be crackable by quantum computers)
#the AES implementation used in RediSkript uses SIV mode, which makes the same key resistant to cracking for a big count of messages without the need of changing the key very often #the AES implementation used in RediSkript uses SIV mode, which makes the same key resistant to cracking for a big count of messages without the need of changing the key very often
EncryptMessages: true EncryptMessages: true
#EncryptionKey and MacKey must be different #EncryptionKey and MacKey must be different
EncryptionKey: "32CHARACTERS KEY" EncryptionKey: "16CHARACTERS KEY"
MacKey: "32CHARACTERS KEY" MacKey: "16CHARACTERS KEY"
#the channels from which this server can receive messages #the channels from which this server can receive messages
@ -41,4 +41,4 @@ Redis:
Channels: Channels:
- "global" - "global"
- "servername" - "servername"
- "Channel3" - "Channel3"

View File

@ -5,10 +5,21 @@
<parent> <parent>
<artifactId>RediSkript</artifactId> <artifactId>RediSkript</artifactId>
<groupId>net.limework</groupId> <groupId>net.limework</groupId>
<version>1.3.7-SNAPSHOT</version> <version>1.3.4</version>
</parent> </parent>
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
<artifactId>RediSkript-core</artifactId> <artifactId>RediSkript-core</artifactId>
</project>
<dependencies>
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>3.5.1</version>
<scope>provided</scope>
</dependency>
</dependencies>
</project>

15
pom.xml
View File

@ -6,7 +6,7 @@
<groupId>net.limework</groupId> <groupId>net.limework</groupId>
<artifactId>RediSkript</artifactId> <artifactId>RediSkript</artifactId>
<version>1.3.7-SNAPSHOT</version> <version>1.3.4</version>
<modules> <modules>
<module>RediSkript-core</module> <module>RediSkript-core</module>
<module>RediSkript-bukkit</module> <module>RediSkript-bukkit</module>
@ -43,23 +43,18 @@
<dependency> <dependency>
<groupId>org.json</groupId> <groupId>org.json</groupId>
<artifactId>json</artifactId> <artifactId>json</artifactId>
<version>20220320</version> <version>20210307</version>
</dependency> </dependency>
<dependency> <dependency>
<groupId>org.cryptomator</groupId> <groupId>org.cryptomator</groupId>
<artifactId>siv-mode</artifactId> <artifactId>siv-mode</artifactId>
<version>1.5.0</version> <version>1.4.1</version>
</dependency> </dependency>
<!-- https://mvnrepository.com/artifact/org.apache.commons/commons-pool2 --> <!-- https://mvnrepository.com/artifact/org.apache.commons/commons-pool2 -->
<dependency> <dependency>
<groupId>org.apache.commons</groupId> <groupId>org.apache.commons</groupId>
<artifactId>commons-pool2</artifactId> <artifactId>commons-pool2</artifactId>
<version>2.11.1</version> <version>2.6.2</version>
</dependency>
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>4.4.6</version>
</dependency> </dependency>
</dependencies> </dependencies>
</project> </project>