提交git

This commit is contained in:
2025-08-25 18:51:02 +08:00
commit 9dd33ed2b9
2171 changed files with 172166 additions and 0 deletions

34
uling-components/pom.xml Normal file
View File

@ -0,0 +1,34 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>uling</artifactId>
<groupId>cc.uling</groupId>
<version>1.0</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>uling-components</artifactId>
<name>uling-components</name>
<url>http://www.example.com</url>
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<dependency>
<groupId>com.rabbitmq</groupId>
<artifactId>amqp-client</artifactId>
<version>5.11.0</version>
</dependency>
</dependencies>
</project>

View File

@ -0,0 +1,6 @@
package cc.uling.components.mq;
public interface IMQReceiver {
void receive(String channel, Object payload);
}

View File

@ -0,0 +1,69 @@
package cc.uling.components.mq;
import io.jboot.components.mq.JbootmqMessageListener;
import io.jboot.components.mq.MessageContext;
import io.jboot.core.spi.JbootSpiLoader;
import io.jboot.utils.ClassUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
@Slf4j
public class MQReceiverFactory implements JbootmqMessageListener {
private static MQReceiverFactory mFactory;
private Map<String, IMQReceiver> mMQReceivers = new ConcurrentHashMap();
public MQReceiverFactory() {
}
public static MQReceiverFactory me() {
if (mFactory == null) {
mFactory = ClassUtil.singleton(MQReceiverFactory.class);
}
return mFactory;
}
@Override
public void onMessage(String channel, Object message, MessageContext context) {
// log.info("onMessage|"+channel);
JbootmqMessageListener.super.onMessage(channel, message, context);
try {
IMQReceiver receiver = getMQReceiver(channel);
if (null != receiver) {
receiver.receive(channel, message);
} else {
log.warn(String.format("can not found %s IMQReceiver", channel));
}
} catch (Exception e) {
e.printStackTrace();
}
}
public IMQReceiver getMQReceiver(String name) {
if (StringUtils.isEmpty(name)) {
return null;
}
IMQReceiver receiver = mMQReceivers.get(name);
if (receiver == null) {
synchronized (this) {
receiver = mMQReceivers.get(name);
if (receiver == null) {
try {
receiver = JbootSpiLoader.load(IMQReceiver.class, name);
if (receiver != null) {
this.mMQReceivers.put(name, receiver);
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
return receiver;
}
}

View File

@ -0,0 +1,178 @@
package cc.uling.components.mq.rabbitmq;
import io.jboot.app.config.annotation.ConfigModel;
@ConfigModel(
prefix = "jboot.mq.RabbitMQ"
)
public class RabbitMQConfig {
private String username;
private String password;
private String host = "127.0.0.1";
private int port = 5672;
private String virtualHost;
private String broadcastChannelPrefix = "broadcast-";
private String broadcastChannelRoutingKey = "";
//true表示自动应答false表示手动应答
private boolean autoAck = false;
private boolean queueEnable = true;
private boolean queueDeclareDurable = true;
private boolean queueDeclareExclusive = false;
private boolean queueDeclareAutoDelete = false;
private boolean broadcastEnable = true;
private String broadcastExchangeDeclareExchangeType = "fanout";
private boolean broadcastExchangeDeclareDurable = true;
private boolean broadcastQueueDeclareDurable = true;
private boolean broadcastQueueDeclareExclusive = false;
private boolean broadcastQueueDeclareAutoDelete = true;
public RabbitMQConfig() {
}
public String getUsername() {
return this.username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPassword() {
return this.password;
}
public void setPassword(String password) {
this.password = password;
}
public String getHost() {
return this.host;
}
public void setHost(String host) {
this.host = host;
}
public int getPort() {
return this.port;
}
public void setPort(int port) {
this.port = port;
}
public String getVirtualHost() {
return this.virtualHost;
}
public void setVirtualHost(String virtualHost) {
this.virtualHost = virtualHost;
}
public String getBroadcastChannelPrefix() {
return this.broadcastChannelPrefix;
}
public void setBroadcastChannelPrefix(String broadcastChannelPrefix) {
this.broadcastChannelPrefix = broadcastChannelPrefix;
}
public String getBroadcastChannelRoutingKey() {
return this.broadcastChannelRoutingKey;
}
public void setBroadcastChannelRoutingKey(String broadcastChannelRoutingKey) {
this.broadcastChannelRoutingKey = broadcastChannelRoutingKey;
}
public boolean isAutoAck() {
return this.autoAck;
}
public void setAutoAck(boolean autoAck) {
this.autoAck = autoAck;
}
public boolean isQueueDeclareDurable() {
return this.queueDeclareDurable;
}
public void setQueueDeclareDurable(boolean queueDeclareDurable) {
this.queueDeclareDurable = queueDeclareDurable;
}
public boolean isQueueDeclareExclusive() {
return this.queueDeclareExclusive;
}
public void setQueueDeclareExclusive(boolean queueDeclareExclusive) {
this.queueDeclareExclusive = queueDeclareExclusive;
}
public boolean isQueueDeclareAutoDelete() {
return this.queueDeclareAutoDelete;
}
public void setQueueDeclareAutoDelete(boolean queueDeclareAutoDelete) {
this.queueDeclareAutoDelete = queueDeclareAutoDelete;
}
public String getBroadcastExchangeDeclareExchangeType() {
return this.broadcastExchangeDeclareExchangeType;
}
public void setBroadcastExchangeDeclareExchangeType(String broadcastExchangeDeclareExchangeType) {
this.broadcastExchangeDeclareExchangeType = broadcastExchangeDeclareExchangeType;
}
public boolean isBroadcastExchangeDeclareDurable() {
return this.broadcastExchangeDeclareDurable;
}
public void setBroadcastExchangeDeclareDurable(boolean broadcastExchangeDeclareDurable) {
this.broadcastExchangeDeclareDurable = broadcastExchangeDeclareDurable;
}
public boolean isBroadcastQueueDeclareDurable() {
return this.broadcastQueueDeclareDurable;
}
public void setBroadcastQueueDeclareDurable(boolean broadcastQueueDeclareDurable) {
this.broadcastQueueDeclareDurable = broadcastQueueDeclareDurable;
}
public boolean isBroadcastQueueDeclareExclusive() {
return this.broadcastQueueDeclareExclusive;
}
public void setBroadcastQueueDeclareExclusive(boolean broadcastQueueDeclareExclusive) {
this.broadcastQueueDeclareExclusive = broadcastQueueDeclareExclusive;
}
public boolean isBroadcastQueueDeclareAutoDelete() {
return this.broadcastQueueDeclareAutoDelete;
}
public void setBroadcastQueueDeclareAutoDelete(boolean broadcastQueueDeclareAutoDelete) {
this.broadcastQueueDeclareAutoDelete = broadcastQueueDeclareAutoDelete;
}
public boolean isQueueEnable() {
return this.queueEnable;
}
public void setQueueEnable(boolean queueEnable) {
this.queueEnable = queueEnable;
}
public boolean isBroadcastEnable() {
return this.broadcastEnable;
}
public void setBroadcastEnable(boolean broadcastEnable) {
this.broadcastEnable = broadcastEnable;
}
}

View File

@ -0,0 +1,244 @@
package cc.uling.components.mq.rabbitmq;
import com.alibaba.fastjson.JSON;
import com.rabbitmq.client.*;
import io.jboot.Jboot;
import io.jboot.components.mq.Jbootmq;
import io.jboot.components.mq.JbootmqBase;
import io.jboot.components.mq.JbootmqConfig;
import io.jboot.components.mq.rabbitmq.RabbitmqMessageContext;
import io.jboot.core.spi.JbootSpi;
import java.io.IOException;
import java.util.*;
import io.jboot.exception.JbootException;
import io.jboot.exception.JbootIllegalConfigException;
import io.jboot.utils.ConfigUtil;
import io.jboot.utils.StrUtil;
import lombok.extern.slf4j.Slf4j;
import java.util.concurrent.ConcurrentHashMap;
@Slf4j
@JbootSpi("RabbitMQ")
public class RabbitMQImpl extends JbootmqBase implements Jbootmq {
private final static String DELAY_QUEUE_NAME = "delayed_queue";
private final static String DELAY_EXCHANGE_NAME = "delayed_exchange";
private Connection connection;
private RabbitMQConfig rabbitmqConfig;
private Map<String, Channel> channelMap = new ConcurrentHashMap();
public RabbitMQImpl(JbootmqConfig config) {
super(config);
String typeName = config.getTypeName();
if (StrUtil.isNotBlank(typeName)) {
Map<String, RabbitMQConfig> configModels = ConfigUtil.getConfigModels(RabbitMQConfig.class);
if (!configModels.containsKey(typeName)) {
throw new JbootIllegalConfigException("Please config \"jboot.mq.RabbitMQ." + typeName + ".host\" in your jboot.properties.");
}
this.rabbitmqConfig = configModels.get(typeName);
} else {
this.rabbitmqConfig = Jboot.config(RabbitMQConfig.class);
}
log.info("rabbitmqConfig {}", JSON.toJSONString(rabbitmqConfig));
try {
ConnectionFactory factory = new ConnectionFactory();
factory.setHost(this.rabbitmqConfig.getHost());
factory.setPort(this.rabbitmqConfig.getPort());
if (StrUtil.isNotBlank(this.rabbitmqConfig.getVirtualHost())) {
factory.setVirtualHost(this.rabbitmqConfig.getVirtualHost());
}
if (StrUtil.isNotBlank(this.rabbitmqConfig.getUsername())) {
factory.setUsername(this.rabbitmqConfig.getUsername());
}
if (StrUtil.isNotBlank(this.rabbitmqConfig.getPassword())) {
factory.setPassword(this.rabbitmqConfig.getPassword());
}
this.connection = factory.newConnection();
} catch (Exception var4) {
throw new JbootException("Can not connection rabbitmq server", var4);
}
}
@Override
public void enqueue(Object message, String toChannel) {
Channel channel = this.getChannel(toChannel, true, false);
try {
String exchangeName = "";
String routingKey = toChannel;
byte[] bytes = this.getSerializer().serialize(message);
channel.basicPublish(exchangeName, routingKey, MessageProperties.PERSISTENT_TEXT_PLAIN, bytes);
} catch (IOException var5) {
var5.printStackTrace();
}
}
@Override
public void publish(Object message, String toChannel) {
Channel channel = this.getChannel(toChannel, false, false);
try {
String routingKey = "";
String exchangeName = toChannel;
byte[] bytes = this.getSerializer().serialize(message);
channel.basicPublish(exchangeName, routingKey, MessageProperties.PERSISTENT_TEXT_PLAIN, bytes);
} catch (IOException var5) {
var5.printStackTrace();
}
}
public void send(Object message, String toChannel, int delay) {
Channel channel = this.getChannel(toChannel, false, true);
try {
String exchangeName = DELAY_EXCHANGE_NAME + "_" + toChannel;
String routingKey = this.rabbitmqConfig.getBroadcastChannelRoutingKey();
byte[] bytes = this.getSerializer().serialize(message);
AMQP.BasicProperties properties = new AMQP.BasicProperties.Builder()
.deliveryMode(2)//1-非持久化、2-持久化
.expiration(String.valueOf(delay * 1000))
.headers(Collections.singletonMap("x-delay", delay * 1000))
.build();
channel.basicPublish(exchangeName, routingKey, properties, bytes);
} catch (Exception var5) {
var5.printStackTrace();
}
}
protected void onStartListening() {
Iterator var1 = this.channels.iterator();
while (var1.hasNext()) {
String toChannel = (String) var1.next();
if (this.rabbitmqConfig.isBroadcastEnable()) {
String queueName = this.buildBroadcastChannelName(toChannel);
Channel queueChannel = this.getChannel(toChannel, false, false);
this.bindChannel(queueChannel, queueName, toChannel);
}
if (this.rabbitmqConfig.isQueueEnable()) {
Channel queueChannel = this.getChannel(toChannel, true, false);
this.bindChannel(queueChannel, toChannel, toChannel);
}
}
}
protected void onStopListening() {
this.connection.abort();
}
public void bindChannel(Channel channel, String queueName, String channelName) {
if (channel != null) {
try {
channel.basicConsume(queueName, this.rabbitmqConfig.isAutoAck(), new DefaultConsumer(channel) {
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
try {
Object o = RabbitMQImpl.this.getSerializer().deserialize(body);
RabbitMQImpl.this.notifyListeners(channelName, o, new RabbitmqMessageContext(RabbitMQImpl.this, channel, consumerTag, envelope, properties));
} catch (Exception e) {
e.printStackTrace();
}
channel.basicAck(envelope.getDeliveryTag(), false);
}
});
} catch (Exception var5) {
var5.printStackTrace();
}
}
}
public synchronized Channel getChannel(String toChannel, boolean queueMode, boolean delay) {
String channelKey = toChannel + "_" + queueMode + "_" + delay;
Channel channel = this.channelMap.get(channelKey);
if (channel == null) {
try {
channel = this.connection.createChannel();
if (queueMode) {
//队列名字
String queueName = toChannel;
//是否持久化
boolean durable = this.rabbitmqConfig.isQueueDeclareDurable();
//是否排外的有两个作用当连接关闭时connection.close()该队列是否会自动删除;
// 二该队列是否是私有的private如果不是排外的可以使用两个消费者都访问同一个队列没有任何问题
// 如果是排外的会对当前队列加锁其他通道channel是不能访问的如果强制访问会报异常
// 一般等于true的话用于一个队列只能有一个消费者来消费的场景
boolean exclusive = this.rabbitmqConfig.isQueueDeclareExclusive();
//是否自动删除,当最后一个消费者断开连接之后队列是否自动被删除
boolean autoDelete = this.rabbitmqConfig.isQueueDeclareAutoDelete();
// 声明队列
channel.queueDeclare(queueName, durable, exclusive, autoDelete, null);
} else {
// 交换机名称
String exchangeName = toChannel;
// 队列名称
String queueName = this.buildBroadcastChannelName(toChannel);
boolean durable = this.rabbitmqConfig.isBroadcastQueueDeclareDurable();
boolean exclusive = this.rabbitmqConfig.isBroadcastQueueDeclareExclusive();
boolean autoDelete = this.rabbitmqConfig.isBroadcastQueueDeclareAutoDelete();
String routingKey = this.rabbitmqConfig.getBroadcastChannelRoutingKey();
if (delay) {
String delayQueueName = DELAY_QUEUE_NAME + "_" + toChannel;
String delayExchangeName = DELAY_EXCHANGE_NAME + "_" + toChannel;
// log.info("正常队列设置死信交换机[" + delayQueueName + "][" + delayExchangeName + "]");
Map<String, Object> args = new HashMap<>();
//正常队列设置死信交换机
args.put("x-dead-letter-exchange", exchangeName);
channel.queueDeclare(delayQueueName, true, exclusive, autoDelete, args);
// 死信交换机
channel.exchangeDeclare(delayExchangeName, BuiltinExchangeType.DIRECT);
channel.queueBind(delayQueueName, delayExchangeName, routingKey);
} else {
// 声明队列
channel.queueDeclare(queueName, durable, exclusive, autoDelete, null);
// 交换机类型
BuiltinExchangeType exchangeType = BuiltinExchangeType.FANOUT;
BuiltinExchangeType[] types = BuiltinExchangeType.values();
for (int i = 0; i < types.length; ++i) {
BuiltinExchangeType type = types[i];
if (type.getType().equals(this.rabbitmqConfig.getBroadcastExchangeDeclareExchangeType())) {
exchangeType = type;
}
}
//交换机是否持久化
boolean exchangeDurable = this.rabbitmqConfig.isBroadcastExchangeDeclareDurable();
// 声明交换机
channel.exchangeDeclare(exchangeName, exchangeType, exchangeDurable);
// 队列与交换机绑定
channel.queueBind(queueName, exchangeName, routingKey);
}
}
channel.confirmSelect();//开启confirm模式以便进行异步确认
channel.addConfirmListener(new ConfirmListener() {
@Override
public void handleAck(long deliveryTag, boolean multiple) throws IOException {
//消息正确到达broker
// log.info(deliveryTag + "|消息已投递成功");
}
@Override
public void handleNack(long deliveryTag, boolean multiple) throws IOException {
//RabbitMQ因为自身内部错误导致消息丢失就会发送一条nack消息
log.info("未确认消息,标识:" + deliveryTag);
}
});
} catch (Exception var9) {
throw new JbootException("Can not create rabbit mq channel.", var9);
}
this.channelMap.put(channelKey, channel);
} else {
// log.info("已存在Channel[" + channelKey + "]");
}
return channel;
}
private String buildBroadcastChannelName(String channel) {
return this.rabbitmqConfig.getBroadcastChannelPrefix() + channel;
}
}

View File

@ -0,0 +1,83 @@
<?xml version="1.0" encoding="UTF-8"?>
<module org.jetbrains.idea.maven.project.MavenProjectsManager.isMavenModule="true" type="JAVA_MODULE" version="4">
<component name="NewModuleRootManager" LANGUAGE_LEVEL="JDK_1_8">
<output url="file://$MODULE_DIR$/target/classes" />
<output-test url="file://$MODULE_DIR$/target/test-classes" />
<content url="file://$MODULE_DIR$">
<sourceFolder url="file://$MODULE_DIR$/src/main/java" isTestSource="false" />
<excludeFolder url="file://$MODULE_DIR$/target" />
</content>
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
<orderEntry type="library" name="Maven: com.rabbitmq:amqp-client:5.11.0" level="project" />
<orderEntry type="library" name="Maven: io.jboot:jboot:3.15.7" level="project" />
<orderEntry type="library" name="Maven: com.jfinal:jfinal:5.0.1" level="project" />
<orderEntry type="library" name="Maven: com.jfinal:cos:2022.2" level="project" />
<orderEntry type="library" name="Maven: com.jfinal:jfinal-undertow:3.1" level="project" />
<orderEntry type="library" name="Maven: io.undertow:undertow-core:2.2.18.Final" level="project" />
<orderEntry type="library" name="Maven: org.jboss.xnio:xnio-api:3.8.7.Final" level="project" />
<orderEntry type="library" name="Maven: org.wildfly.common:wildfly-common:1.5.4.Final" level="project" />
<orderEntry type="library" name="Maven: org.wildfly.client:wildfly-client-config:1.0.1.Final" level="project" />
<orderEntry type="library" scope="RUNTIME" name="Maven: org.jboss.xnio:xnio-nio:3.8.7.Final" level="project" />
<orderEntry type="library" name="Maven: org.jboss.threads:jboss-threads:3.1.0.Final" level="project" />
<orderEntry type="library" name="Maven: io.undertow:undertow-servlet:2.2.18.Final" level="project" />
<orderEntry type="library" name="Maven: javax.servlet:javax.servlet-api:4.0.1" level="project" />
<orderEntry type="library" name="Maven: cglib:cglib:3.3.0" level="project" />
<orderEntry type="library" name="Maven: org.ow2.asm:asm:7.1" level="project" />
<orderEntry type="library" name="Maven: io.undertow:undertow-websockets-jsr:2.2.18.Final" level="project" />
<orderEntry type="library" name="Maven: org.jboss.spec.javax.websocket:jboss-websocket-api_1.1_spec:2.0.0.Final" level="project" />
<orderEntry type="library" name="Maven: com.zaxxer:HikariCP:4.0.3" level="project" />
<orderEntry type="library" name="Maven: com.fasterxml.jackson.core:jackson-core:2.13.3" level="project" />
<orderEntry type="library" name="Maven: com.fasterxml.jackson.core:jackson-annotations:2.13.3" level="project" />
<orderEntry type="library" name="Maven: com.fasterxml.jackson.core:jackson-databind:2.13.3" level="project" />
<orderEntry type="library" name="Maven: com.fasterxml.jackson.dataformat:jackson-dataformat-yaml:2.13.3" level="project" />
<orderEntry type="library" name="Maven: org.yaml:snakeyaml:1.30" level="project" />
<orderEntry type="library" name="Maven: mysql:mysql-connector-java:5.1.49" level="project" />
<orderEntry type="library" name="Maven: de.ruedigermoeller:fst:2.57" level="project" />
<orderEntry type="library" name="Maven: org.objenesis:objenesis:2.5.1" level="project" />
<orderEntry type="library" name="Maven: org.javassist:javassist:3.29.0-GA" level="project" />
<orderEntry type="library" name="Maven: com.alibaba:fastjson:1.2.83" level="project" />
<orderEntry type="library" name="Maven: com.google.guava:guava:31.1-jre" level="project" />
<orderEntry type="library" name="Maven: com.google.guava:failureaccess:1.0.1" level="project" />
<orderEntry type="library" name="Maven: com.google.guava:listenablefuture:9999.0-empty-to-avoid-conflict-with-guava" level="project" />
<orderEntry type="library" name="Maven: com.google.code.findbugs:jsr305:3.0.2" level="project" />
<orderEntry type="library" name="Maven: org.checkerframework:checker-qual:3.12.0" level="project" />
<orderEntry type="library" name="Maven: com.google.errorprone:error_prone_annotations:2.11.0" level="project" />
<orderEntry type="library" name="Maven: com.google.j2objc:j2objc-annotations:1.3" level="project" />
<orderEntry type="library" name="Maven: it.sauronsoftware.cron4j:cron4j:2.2.5" level="project" />
<orderEntry type="library" name="Maven: org.jsoup:jsoup:1.14.3" level="project" />
<orderEntry type="library" name="Maven: com.github.ben-manes.caffeine:caffeine:2.9.3" level="project" />
<orderEntry type="library" name="Maven: commons-io:commons-io:2.11.0" level="project" />
<orderEntry type="library" name="Maven: javax.annotation:javax.annotation-api:1.3.2" level="project" />
<orderEntry type="library" name="Maven: org.hibernate.validator:hibernate-validator:6.0.21.Final" level="project" />
<orderEntry type="library" name="Maven: org.jboss.logging:jboss-logging:3.3.2.Final" level="project" />
<orderEntry type="library" name="Maven: com.fasterxml:classmate:1.3.4" level="project" />
<orderEntry type="library" name="Maven: org.glassfish:javax.el:3.0.0" level="project" />
<orderEntry type="library" name="Maven: io.dropwizard.metrics:metrics-core:4.2.9" level="project" />
<orderEntry type="library" name="Maven: io.dropwizard.metrics:metrics-servlets:4.2.9" level="project" />
<orderEntry type="library" name="Maven: io.dropwizard.metrics:metrics-json:4.2.9" level="project" />
<orderEntry type="library" name="Maven: com.helger:profiler:1.1.1" level="project" />
<orderEntry type="library" name="Maven: io.dropwizard.metrics:metrics-healthchecks:4.2.9" level="project" />
<orderEntry type="library" name="Maven: io.dropwizard.metrics:metrics-jvm:4.2.9" level="project" />
<orderEntry type="library" name="Maven: io.jsonwebtoken:jjwt:0.9.1" level="project" />
<orderEntry type="library" name="Maven: c3p0:c3p0:0.9.1.2" level="project" />
<orderEntry type="library" name="Maven: com.alibaba:druid:1.0.21" level="project" />
<orderEntry type="library" name="Maven: org.apache.commons:commons-pool2:2.4.3" level="project" />
<orderEntry type="library" name="Maven: redis.clients:jedis:3.2.0" level="project" />
<orderEntry type="library" scope="PROVIDED" name="Maven: org.projectlombok:lombok:1.18.16" level="project" />
<orderEntry type="library" name="Maven: ch.qos.logback:logback-core:1.2.3" level="project" />
<orderEntry type="library" name="Maven: ch.qos.logback:logback-classic:1.2.3" level="project" />
<orderEntry type="library" name="Maven: org.slf4j:slf4j-api:1.7.30" level="project" />
<orderEntry type="library" name="Maven: log4j:log4j:1.2.17" level="project" />
<orderEntry type="library" name="Maven: org.apache.logging.log4j:log4j-core:2.23.1" level="project" />
<orderEntry type="library" name="Maven: org.apache.logging.log4j:log4j-api:2.23.1" level="project" />
<orderEntry type="library" name="Maven: javax.validation:validation-api:2.0.1.Final" level="project" />
<orderEntry type="library" name="Maven: commons-logging:commons-logging:1.2" level="project" />
<orderEntry type="library" name="Maven: org.apache.commons:commons-lang3:3.6" level="project" />
<orderEntry type="library" name="Maven: commons-codec:commons-codec:1.10" level="project" />
<orderEntry type="library" name="Maven: cn.hutool:hutool-all:5.8.29" level="project" />
<orderEntry type="library" name="Maven: pro.fessional:kaptcha:2.3.3" level="project" />
<orderEntry type="library" name="Maven: com.jhlabs:filters:2.0.235-1" level="project" />
<orderEntry type="library" name="Maven: eu.bitwalker:UserAgentUtils:1.19" level="project" />
</component>
</module>