From bb01add10411da57e9ba621e193dd2fc0a771986 Mon Sep 17 00:00:00 2001 From: RoyceDa Date: Mon, 2 Feb 2026 22:38:46 +0200 Subject: [PATCH] =?UTF-8?q?=D0=A1=D0=B8=D1=81=D1=82=D0=B5=D0=BC=D0=B0=20?= =?UTF-8?q?=D1=81=D0=BE=D0=B1=D1=8B=D1=82=D0=B8=D0=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/com/rosetta/im/Main.java | 2 +- .../java/com/rosetta/im/event/Cancelable.java | 17 +++ src/main/java/com/rosetta/im/event/Event.java | 18 +++ .../com/rosetta/im/event/EventException.java | 13 ++ .../com/rosetta/im/event/EventHandler.java | 12 ++ .../com/rosetta/im/event/EventManager.java | 120 ++++++++++++++++++ .../com/rosetta/im/event/EventPriority.java | 25 ++++ .../java/com/rosetta/im/event/Listener.java | 10 ++ src/main/java/io/orprotocol/Context.java | 3 + src/main/java/io/orprotocol/Server.java | 70 +++++++++- .../java/io/orprotocol/ServerFailures.java | 5 + .../java/io/orprotocol/ServerListener.java | 45 +++++++ .../io/orprotocol/packet/PacketExecutor.java | 11 +- 13 files changed, 338 insertions(+), 13 deletions(-) create mode 100644 src/main/java/com/rosetta/im/event/Cancelable.java create mode 100644 src/main/java/com/rosetta/im/event/Event.java create mode 100644 src/main/java/com/rosetta/im/event/EventException.java create mode 100644 src/main/java/com/rosetta/im/event/EventHandler.java create mode 100644 src/main/java/com/rosetta/im/event/EventManager.java create mode 100644 src/main/java/com/rosetta/im/event/EventPriority.java create mode 100644 src/main/java/com/rosetta/im/event/Listener.java create mode 100644 src/main/java/io/orprotocol/Context.java create mode 100644 src/main/java/io/orprotocol/ServerListener.java diff --git a/src/main/java/com/rosetta/im/Main.java b/src/main/java/com/rosetta/im/Main.java index ca389b9..aa86007 100644 --- a/src/main/java/com/rosetta/im/Main.java +++ b/src/main/java/com/rosetta/im/Main.java @@ -22,7 +22,7 @@ public class Main { /** * Запуск сервера на порту 8881 */ - Server server = new Server(settings, manager, Configuration.class); + Server server = new Server(settings, manager); server.start(); } } \ No newline at end of file diff --git a/src/main/java/com/rosetta/im/event/Cancelable.java b/src/main/java/com/rosetta/im/event/Cancelable.java new file mode 100644 index 0000000..298ee20 --- /dev/null +++ b/src/main/java/com/rosetta/im/event/Cancelable.java @@ -0,0 +1,17 @@ +package com.rosetta.im.event; + +public interface Cancelable { + + /** + * Отменено ли событие + * @return true, если событие отменено + */ + boolean isCanceled(); + + /** + * Установить отмену события + * @param canceled true, если событие должно быть отменено + */ + void setCanceled(boolean canceled); + +} diff --git a/src/main/java/com/rosetta/im/event/Event.java b/src/main/java/com/rosetta/im/event/Event.java new file mode 100644 index 0000000..205b7f5 --- /dev/null +++ b/src/main/java/com/rosetta/im/event/Event.java @@ -0,0 +1,18 @@ +package com.rosetta.im.event; + +public class Event { + + private String name; + + public Event() {} + + public String getEventName() { + if(this.name == null) { + this.name = this.getClass().getSimpleName(); + } + return this.name; + } + + + +} diff --git a/src/main/java/com/rosetta/im/event/EventException.java b/src/main/java/com/rosetta/im/event/EventException.java new file mode 100644 index 0000000..be5dbd9 --- /dev/null +++ b/src/main/java/com/rosetta/im/event/EventException.java @@ -0,0 +1,13 @@ +package com.rosetta.im.event; + +public class EventException extends Exception { + + public EventException(String message) { + super(message); + } + + public EventException(String message, Throwable cause) { + super(message, cause); + } + +} diff --git a/src/main/java/com/rosetta/im/event/EventHandler.java b/src/main/java/com/rosetta/im/event/EventHandler.java new file mode 100644 index 0000000..6ebb89f --- /dev/null +++ b/src/main/java/com/rosetta/im/event/EventHandler.java @@ -0,0 +1,12 @@ +package com.rosetta.im.event; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Target(ElementType.METHOD) +@Retention(RetentionPolicy.RUNTIME) +public @interface EventHandler { + +} diff --git a/src/main/java/com/rosetta/im/event/EventManager.java b/src/main/java/com/rosetta/im/event/EventManager.java new file mode 100644 index 0000000..f92517c --- /dev/null +++ b/src/main/java/com/rosetta/im/event/EventManager.java @@ -0,0 +1,120 @@ +package com.rosetta.im.event; + +import java.lang.annotation.Annotation; +import java.lang.reflect.Method; +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +/** + * Менеджер событий + */ +public class EventManager { + + private Set listeners; + + public EventManager() { + this.listeners = new HashSet<>(); + } + + /** + * Регистрация слушателя событий + * @param listener Слушатель событий + */ + public void registerListener(Listener listener) { + this.listeners.add(listener); + } + + /** + * Удаление слушателя событий + * @param listener Слушатель событий + */ + public void unregisterListener(Listener listener) { + this.listeners.remove(listener); + } + + /** + * Получить все зарегистрированные слушатели событий + * @return Множество слушателей событий + */ + public Set getListeners() { + return this.listeners; + } + + /** + * Вызывает событие и обрабатывает исключения + * @param event Событие для вызова + * @return true, если событие отменено + */ + public boolean callEvent(Event event) { + try { + return fireEvent(event); + } catch (EventException e) { + e.printStackTrace(); + return false; + } catch (Exception e) { + e.printStackTrace(); + return false; + } + } + + /** + * Запускает событие, уведомляя всех зарегистрированных слушателей, возвращает true если событие отменено + * @param event Событие для запуска + * @return true, если событие отменено + * @throws EventException Ошибка при обработке события + * @throws Exception Общая ошибка при вызове метода + */ + private boolean fireEvent(Event event) throws EventException, Exception { + for(Listener listener : this.listeners) { + /** + * Получаем все методы в Listener с аннотацией @EventHandler + */ + List methods = getMethodsWithAnnotation(listener.getClass(), EventHandler.class); + for(Method method : methods) { + /** + * Проверяем, что метод принимает один параметр того же типа, что и событие + */ + if(method.getParameterCount() == 1 + && method.getParameterTypes()[0].isAssignableFrom(event.getClass())) { + /** + * Если параметры совпадают и они одного типа - вызываем событие + */ + method.setAccessible(true); + method.invoke(listener, event); + /** + * Если событие отменяемое - проверяем его статус + */ + if(event instanceof Cancelable) { + Cancelable cancelableEvent = (Cancelable) event; + if(cancelableEvent.isCanceled()) { + return true; + } + } + continue; + } + /** + * Если метод обрабатывающий событие + * реализован неправильно - выбрасываем исключение + */ + throw new EventException("Invalid event params"); + } + } + return false; + } + + + private List getMethodsWithAnnotation(Class clazz, Class annotation) { + List methods = new ArrayList<>(); + for (Method method : clazz.getDeclaredMethods()) { + if (method.isAnnotationPresent(annotation)) { + methods.add(method); + } + } + return methods; + } + + + +} diff --git a/src/main/java/com/rosetta/im/event/EventPriority.java b/src/main/java/com/rosetta/im/event/EventPriority.java new file mode 100644 index 0000000..94edd60 --- /dev/null +++ b/src/main/java/com/rosetta/im/event/EventPriority.java @@ -0,0 +1,25 @@ +package com.rosetta.im.event; + +/** + * Приоритет события + * Указывает на то, в каком порядке обработаются два одинаковых события + */ +public enum EventPriority { + LOW(0), + MEDIUM(1), + HIGH(2); + + private final int priority; + + EventPriority(int priority) { + this.priority = priority; + } + + /** + * Получить приоритет + * @return Приоритет события + */ + public int getPriority() { + return priority; + } +} diff --git a/src/main/java/com/rosetta/im/event/Listener.java b/src/main/java/com/rosetta/im/event/Listener.java new file mode 100644 index 0000000..75eaa6c --- /dev/null +++ b/src/main/java/com/rosetta/im/event/Listener.java @@ -0,0 +1,10 @@ +package com.rosetta.im.event; + +/** + * Слушатель событий + */ +public interface Listener { + /** + * Пустой интерфейс для обозначения слушателя событий + */ +} diff --git a/src/main/java/io/orprotocol/Context.java b/src/main/java/io/orprotocol/Context.java new file mode 100644 index 0000000..075a3b7 --- /dev/null +++ b/src/main/java/io/orprotocol/Context.java @@ -0,0 +1,3 @@ +package io.orprotocol; + +public class Context extends Object {} diff --git a/src/main/java/io/orprotocol/Server.java b/src/main/java/io/orprotocol/Server.java index d81a9af..25122ac 100644 --- a/src/main/java/io/orprotocol/Server.java +++ b/src/main/java/io/orprotocol/Server.java @@ -20,23 +20,56 @@ public class Server extends WebSocketServer { private PacketManager packetManager; private Settings settings; private ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1); - private Object attachment; + private Context context; + private ServerListener listener; - public Server(Settings settings, PacketManager packetManager, Object attachment) { + /** + * Конструктор сервера + * @param settings базовые настройки серверера + * @param packetManager менеджер пакетов (обработчиков и зарегистрированных пакетов) + */ + public Server(Settings settings, PacketManager packetManager) { super(new InetSocketAddress(settings.port)); this.settings = settings; this.packetManager = packetManager; - this.attachment = attachment; + } + + /** + * Конструктор сервера с объектом прикрепления + * @param settings базовые настройки серверера + * @param packetManager менеджер пакетов (обработчиков и зарегистрированных пакетов) + * @param context вложение которое будет передаваться всем серрверным обработчикам пакетов, + * может быть использовано для передачи контекста приложения + */ + public Server(Settings settings, PacketManager packetManager, Context context) { + super(new InetSocketAddress(settings.port)); + this.settings = settings; + this.packetManager = packetManager; + this.context = context; + } + + public Server(Settings settings, PacketManager packetManager, Context context, ServerListener listener) { + super(new InetSocketAddress(settings.port)); + this.settings = settings; + this.packetManager = packetManager; + this.context = context; + this.listener = listener; } @Override - public void onClose(WebSocket arg0, int arg1, String arg2, boolean arg3) { - + public void onClose(WebSocket socket, int arg1, String arg2, boolean arg3) { + if(this.listener == null){ + return; + } + this.listener.onClientDisconnect(this, socket.getAttachment()); } @Override public void onError(WebSocket arg0, Exception arg1) { - + if(this.listener == null){ + return; + } + this.listener.onError(this, arg1); } @Override @@ -47,6 +80,12 @@ public class Server extends WebSocketServer { */ Client client = socket.getAttachment(); client.updateHeartbeat(); + if(this.listener != null) { + /** + * Сообщение от клиента, но пакет не сформирован, передаем null + */ + this.listener.onPacketReceived(this, client, null); + } } @Override @@ -90,7 +129,13 @@ public class Server extends WebSocketServer { Class executorClass = this.packetManager.getExecutors().get(packetId); PacketExecutor executor = executorClass.getConstructor().newInstance(); executor.settings = this.settings; - executor.attachment = this.attachment; + executor.context = this.context; + if(listener != null && listener.onPacketReceived(this, client, packet)) { + /** + * Если слушатель сервера вернул true, пакет не обрабатываем. + */ + return; + } executor.onPacketReceived(packet, client); } catch (Exception e) { System.out.println("Error while processing packet " + packetClass.getName()); @@ -107,6 +152,13 @@ public class Server extends WebSocketServer { */ Client client = new Client(socket, this.settings.heartbeatInterval); socket.setAttachment(client); + if(this.listener == null){ + return; + } + if(this.listener.onClientConnect(this, client)) { + client.disconnect(ServerFailures.SERVER_NOT_ACCEPT_CLIENT); + return; + } } @Override @@ -118,6 +170,10 @@ public class Server extends WebSocketServer { int port = this.getPort(); System.out.println("\u001B[32mServer started at x.x.x.x:" + port + "\u001B[0m"); + if(this.listener == null){ + return; + } + this.listener.onServerStart(this); } /** diff --git a/src/main/java/io/orprotocol/ServerFailures.java b/src/main/java/io/orprotocol/ServerFailures.java index ae01fa2..ff4af62 100644 --- a/src/main/java/io/orprotocol/ServerFailures.java +++ b/src/main/java/io/orprotocol/ServerFailures.java @@ -24,6 +24,11 @@ public enum ServerFailures implements BaseFailures { * Код ошибки, указывающий на тайм-аут бездействия. */ INACTIVITY_TIMEOUT(3004), + /** + * Сервер по какой-то причине не принимает подключение клиента + * Выбрасывается в методе onClientConnect при отмене события например защитой + */ + SERVER_NOT_ACCEPT_CLIENT(3005), /** * Код ошибки, указывающий на неизвестный тип пакета. */ diff --git a/src/main/java/io/orprotocol/ServerListener.java b/src/main/java/io/orprotocol/ServerListener.java new file mode 100644 index 0000000..bff82c4 --- /dev/null +++ b/src/main/java/io/orprotocol/ServerListener.java @@ -0,0 +1,45 @@ +package io.orprotocol; + +import io.orprotocol.client.Client; +import io.orprotocol.packet.Packet; + +public interface ServerListener { + /** + * Сервер запущен + * @param server + */ + void onServerStart(Server server); + /** + * Сервер остановлен + * @param server + */ + void onServerStop(Server server); + /** + * Клиент подключился + * @param server + * @param client + * @return если возвращено true значит клиент не будет подключен к серверу выбросится + * ошибка SERVER_NOT_ACCEPT_CLIENT + */ + boolean onClientConnect(Server server, Client client); + /** + * Клиент отключился + * @param server + * @param client + */ + void onClientDisconnect(Server server, Client client); + /** + * Произошла ошибка сервера + * @param server + * @param exception + */ + void onError(Server server, Exception exception); + /** + * Пакет получен от клиента + * @param server + * @param client + * @param packet + * @return если возвращено true значит пакет не будет обработан дальше + */ + boolean onPacketReceived(Server server, Client client, Packet packet); +} diff --git a/src/main/java/io/orprotocol/packet/PacketExecutor.java b/src/main/java/io/orprotocol/packet/PacketExecutor.java index cfab517..373dec0 100644 --- a/src/main/java/io/orprotocol/packet/PacketExecutor.java +++ b/src/main/java/io/orprotocol/packet/PacketExecutor.java @@ -1,5 +1,6 @@ package io.orprotocol.packet; +import io.orprotocol.Context; import io.orprotocol.Settings; import io.orprotocol.client.Client; @@ -8,7 +9,7 @@ import io.orprotocol.client.Client; */ public abstract class PacketExecutor { public Settings settings; - public Object attachment; + public Context context; /** * Настройки сервера. @@ -19,11 +20,11 @@ public abstract class PacketExecutor { } /** - * Вложенный обьект, который был передан при создании сервера. - * @return вложенный обьект + * Контекст приложения переданный при создании сервера. + * @return контекст */ - public Object getAttachment() { - return attachment; + public Context getContext() { + return context; } /**