Улучшена передача контекста (не нарушается принцип ответственности), добавлено логгирование, переработан Boot который собирает все древо приложения, улучшен протокол

This commit is contained in:
RoyceDa
2026-02-03 20:11:37 +02:00
parent 695ec9c520
commit cb5cafb8f6
12 changed files with 188 additions and 106 deletions

View File

@@ -1,22 +0,0 @@
package com.rosetta.im;
import com.rosetta.im.event.EventManager;
import io.orprotocol.Context;
/**
* Контекст приложения, хранящий глобальные объекты
*/
public class AppContext extends Context {
private EventManager eventManager;
public AppContext(EventManager eventManager) {
this.eventManager = eventManager;
}
public EventManager getEventManager() {
return eventManager;
}
}

View File

@@ -1,44 +1,81 @@
package com.rosetta.im; package com.rosetta.im;
import com.rosetta.im.event.EventManager;
import com.rosetta.im.executors.Executor0Handshake; import com.rosetta.im.executors.Executor0Handshake;
import com.rosetta.im.listeners.ServerStopListener;
import com.rosetta.im.logger.Logger;
import com.rosetta.im.logger.enums.Color;
import com.rosetta.im.logger.enums.LogLevel;
import com.rosetta.im.packet.Packet0Handshake; import com.rosetta.im.packet.Packet0Handshake;
import io.orprotocol.packet.PacketManager; import io.orprotocol.packet.PacketManager;
/**
* Boot отвечает за инициализацию всех пакетов и их обработчиков,
* а так же событий приложения. Этот Boot отвечает за приложение, а не за протокол.
*/
public class Boot { public class Boot {
private PacketManager packetManager; private PacketManager packetManager;
private EventManager eventManager;
private Logger logger;
public Boot() { public Boot() {
this.packetManager = new PacketManager(); this.packetManager = new PacketManager();
this.eventManager = new EventManager();
this.logger = new Logger(LogLevel.INFO);
} }
/**
* Получить менеджер пакетов приложения
* @return PacketManager
*/
public PacketManager getPacketManager() { public PacketManager getPacketManager() {
return this.packetManager; return this.packetManager;
} }
/** /**
* Инициализация всех пакетов и их обработчиков * Получить менеджер событий приложения
* @return EventManager
*/
public EventManager getEventManager() {
return this.eventManager;
}
/**
* Получить логгер приложения
* @return Logger
*/
public Logger getLogger() {
return this.logger;
}
/**
* Регистрация пакетов, обработчиков, событий приложения
* @return Boot
*/ */
public Boot bootstrap() { public Boot bootstrap() {
this.registerAllPackets(); this.registerAllPackets();
this.registerAllExecutors(); this.registerAllExecutors();
this.registerAllEvents();
this.printBootMessage(); this.printBootMessage();
return this; return this;
} }
private void registerAllEvents() {
this.eventManager.registerListener(new ServerStopListener(this.logger));
}
private void registerAllPackets() { private void registerAllPackets() {
this.packetManager.registerPacket(0, Packet0Handshake.class); this.packetManager.registerPacket(0, Packet0Handshake.class);
} }
private void registerAllExecutors() { private void registerAllExecutors() {
this.packetManager.registerExecutor(0, Executor0Handshake.class); this.packetManager.registerExecutor(0, new Executor0Handshake(this.eventManager));
} }
private void printBootMessage() { private void printBootMessage() {
System.out.println("Bootstrapping completed. All packets and executors are registered."); this.logger.log(LogLevel.INFO, Color.GREEN + "Boot successful complete");
System.out.println("Total packets registered: " + this.packetManager.totalPackets());
System.out.println("Total executors registered: " + this.packetManager.totalExecutors());
} }
} }

View File

@@ -1,23 +0,0 @@
package com.rosetta.im;
import com.rosetta.im.event.EventManager;
import com.rosetta.im.listeners.ServerStopListener;
public class ContextBuilder {
private AppContext appContext;
public ContextBuilder() {}
public AppContext buildContext() {
/**
* Создание глобальных объектов приложения
*/
EventManager eventManager = new EventManager();
eventManager.registerListener(new ServerStopListener());
this.appContext = new AppContext(eventManager);
return this.appContext;
}
}

View File

@@ -2,25 +2,27 @@ package com.rosetta.im;
import io.orprotocol.Server; import io.orprotocol.Server;
import io.orprotocol.Settings; import io.orprotocol.Settings;
import io.orprotocol.packet.PacketManager;
public class Main { public class Main {
public static void main(String[] args) { public static void main(String[] args) {
/** /**
* Регистрация всех пакетов и их обработчиков * Регистрация всех пакетов и их обработчиков
*/ */
Boot boot = new Boot(); Boot boot = new Boot().bootstrap();
PacketManager manager = boot.bootstrap().getPacketManager();
/** /**
* Загрузка настроек сервера, сборка контекста приложения * Загрузка настроек сервера
*/ */
Settings settings = new Settings(8881, 30); Settings settings = new Settings(8881, 30);
AppContext appContext = new ContextBuilder().buildContext();
ServerAdapter serverAdapter = new ServerAdapter(appContext.getEventManager());
/** /**
* Запуск сервера на порту 8881 * Создание адаптера сервера для трансляции событий протокола в события приложения,
* обрабатываются основыне события такие как запуск/остановка сервера,
* подключение/отключение клиентов, ошибки сервера и получение пакетов
*/ */
Server server = new Server(settings, manager, appContext, serverAdapter); ServerAdapter serverAdapter = new ServerAdapter(boot.getEventManager());
/**
* Запуск сервера
*/
Server server = new Server(settings, boot.getPacketManager(), serverAdapter);
server.start(); server.start();
} }
} }

View File

@@ -1,6 +1,5 @@
package com.rosetta.im.executors; package com.rosetta.im.executors;
import com.rosetta.im.AppContext;
import com.rosetta.im.Configuration; import com.rosetta.im.Configuration;
import com.rosetta.im.Failures; import com.rosetta.im.Failures;
import com.rosetta.im.client.tags.ECIAuthentificate; import com.rosetta.im.client.tags.ECIAuthentificate;
@@ -9,6 +8,7 @@ import com.rosetta.im.database.entity.Device;
import com.rosetta.im.database.entity.User; import com.rosetta.im.database.entity.User;
import com.rosetta.im.database.repository.DeviceRepository; import com.rosetta.im.database.repository.DeviceRepository;
import com.rosetta.im.database.repository.UserRepository; import com.rosetta.im.database.repository.UserRepository;
import com.rosetta.im.event.EventManager;
import com.rosetta.im.event.events.handshake.HandshakeCompletedEvent; import com.rosetta.im.event.events.handshake.HandshakeCompletedEvent;
import com.rosetta.im.event.events.handshake.HandshakeDeviceConfirmEvent; import com.rosetta.im.event.events.handshake.HandshakeDeviceConfirmEvent;
import com.rosetta.im.event.events.handshake.HandshakeFailedEvent; import com.rosetta.im.event.events.handshake.HandshakeFailedEvent;
@@ -27,6 +27,11 @@ public class Executor0Handshake extends PacketExecutor {
private final UserRepository userRepository = new UserRepository(); private final UserRepository userRepository = new UserRepository();
private final DeviceRepository deviceRepository = new DeviceRepository(); private final DeviceRepository deviceRepository = new DeviceRepository();
private final DeviceService deviceService = new DeviceService(deviceRepository); private final DeviceService deviceService = new DeviceService(deviceRepository);
private final EventManager eventManager;
public Executor0Handshake(EventManager eventManager) {
this.eventManager = eventManager;
}
@Override @Override
@@ -39,7 +44,6 @@ public class Executor0Handshake extends PacketExecutor {
String deviceName = handshake.getDeviceName(); String deviceName = handshake.getDeviceName();
String deviceOs = handshake.getDeviceOs(); String deviceOs = handshake.getDeviceOs();
int protocolVersion = handshake.getProtocolVersion(); int protocolVersion = handshake.getProtocolVersion();
AppContext context = (AppContext) this.getContext();
/** /**
* Получаем информацию об аутентификации клиента * Получаем информацию об аутентификации клиента
* используя возможности ECI тэгов. * используя возможности ECI тэгов.
@@ -95,7 +99,7 @@ public class Executor0Handshake extends PacketExecutor {
/** /**
* Вызываем событие завершения хэндшейка * Вызываем событие завершения хэндшейка
*/ */
boolean cancelled = context.getEventManager().callEvent( boolean cancelled = this.eventManager.callEvent(
new HandshakeCompletedEvent(publicKey, privateKey, device, eciTag, client) new HandshakeCompletedEvent(publicKey, privateKey, device, eciTag, client)
); );
if(cancelled) { if(cancelled) {
@@ -120,7 +124,7 @@ public class Executor0Handshake extends PacketExecutor {
/** /**
* Приватный ключ не совпадает, отключаем клиента * Приватный ключ не совпадает, отключаем клиента
*/ */
context.getEventManager().callEvent(new HandshakeFailedEvent(publicKey, privateKey, device, authentificate, client)); eventManager.callEvent(new HandshakeFailedEvent(publicKey, privateKey, device, authentificate, client));
client.disconnect(Failures.AUTHENTIFICATION_ERROR); client.disconnect(Failures.AUTHENTIFICATION_ERROR);
return; return;
} }
@@ -139,7 +143,7 @@ public class Executor0Handshake extends PacketExecutor {
/** /**
* Вызываем событие подтверждения устройства * Вызываем событие подтверждения устройства
*/ */
context.getEventManager().callEvent( this.eventManager.callEvent(
new HandshakeDeviceConfirmEvent(publicKey, privateKey, device, authentificate, client) new HandshakeDeviceConfirmEvent(publicKey, privateKey, device, authentificate, client)
); );
/** /**
@@ -179,7 +183,7 @@ public class Executor0Handshake extends PacketExecutor {
/** /**
* Вызываем событие завершения хэндшейка * Вызываем событие завершения хэндшейка
*/ */
boolean cancelled = context.getEventManager().callEvent( boolean cancelled = this.eventManager.callEvent(
new HandshakeCompletedEvent(publicKey, privateKey, device, eciTag, client) new HandshakeCompletedEvent(publicKey, privateKey, device, eciTag, client)
); );
if(cancelled) { if(cancelled) {

View File

@@ -6,6 +6,8 @@ import com.rosetta.im.database.repository.DeviceRepository;
import com.rosetta.im.event.EventHandler; import com.rosetta.im.event.EventHandler;
import com.rosetta.im.event.Listener; import com.rosetta.im.event.Listener;
import com.rosetta.im.event.events.ServerStopEvent; import com.rosetta.im.event.events.ServerStopEvent;
import com.rosetta.im.logger.Logger;
import com.rosetta.im.logger.enums.Color;
import com.rosetta.im.service.services.DeviceService; import com.rosetta.im.service.services.DeviceService;
import io.orprotocol.Server; import io.orprotocol.Server;
@@ -17,25 +19,39 @@ import io.orprotocol.client.Client;
*/ */
public class ServerStopListener implements Listener { public class ServerStopListener implements Listener {
private static final DeviceRepository deviceRepository = new DeviceRepository(); private final DeviceRepository deviceRepository = new DeviceRepository();
private static final DeviceService deviceService = new DeviceService(deviceRepository); private final DeviceService deviceService = new DeviceService(deviceRepository);
private Logger logger;
public ServerStopListener(Logger logger) {
this.logger = logger;
}
@EventHandler @EventHandler
public void onServerStop(ServerStopEvent event) { public void onServerStop(ServerStopEvent event) {
Server server = event.getServer(); Server server = event.getServer();
System.out.println("Server is stopping. Please wait..."); this.logger.info(Color.RED + "Сервер останавливается, обновляем время последней активности устройств клиентов...");
for(Client client : server.getClients()){ for(Client client : server.getClients()){
ECIAuthentificate eciAuth = client.getTag(ECIAuthentificate.class); ECIAuthentificate eciAuth = client.getTag(ECIAuthentificate.class);
if(eciAuth == null || !eciAuth.hasAuthorized()){ if(eciAuth == null || !eciAuth.hasAuthorized()){
/**
* Если клиент не авторизован, пропускаем его, таким клиентам не нужно
* обновлять время активности устройства
*/
continue; continue;
} }
ECIDevice eciDevice = client.getTag(ECIDevice.class); ECIDevice eciDevice = client.getTag(ECIDevice.class);
if(eciDevice == null){ if(eciDevice == null){
/**
* Если у клиента нет тега устройства, пропускаем его
* такого быть не должно, но на всякий случай
*/
continue; continue;
} }
deviceService.updateDeviceLeaveTime(eciDevice.getDeviceId()); deviceService.updateDeviceLeaveTime(eciDevice.getDeviceId());
} }
System.out.println("Server stopped successfully."); this.logger.info(Color.RED + "Время последней активности устройств клиентов обновлено.");
} }
} }

View File

@@ -0,0 +1,74 @@
package com.rosetta.im.logger;
import java.time.Instant;
import com.rosetta.im.logger.enums.Color;
import com.rosetta.im.logger.enums.LogLevel;
public class Logger {
private long startTime = 0;
private LogLevel logLevel;
public Logger(LogLevel logLevel) {
this.logLevel = logLevel;
startTime = (System.currentTimeMillis() / 1000);
}
/**
* Логирование сообщения с указанным уровнем логирования
* @param logLevel уровень логирования
* @param message сообщение для логирования
*/
public void log(LogLevel logLevel, String message) {
if (!this.logLevel.allows(logLevel)) {
return;
}
long currentTimeMs = System.currentTimeMillis();
long currentTime = currentTimeMs / 1000;
long elapsedTime = currentTime - startTime;
String currentDateISO = Instant.ofEpochMilli(currentTimeMs).toString();
System.out.println(getColorForLogLevel(logLevel) + "["+ logLevel.toString() +"]" + Color.RESET + "[" + currentDateISO + "]" + Color.CYAN + "[+" + elapsedTime + "] " + Color.WHITE + message + Color.RESET);
}
/**
* Логирование информационного сообщения
* @param message сообщение для логирования
*/
public void info(String message) {
this.log(LogLevel.INFO, message);
}
/**
* Логирование предупреждающего сообщения
* @param message сообщение для логирования
*/
public void warn(String message) {
this.log(LogLevel.WARN, message);
}
/**
* Логирование сообщения об ошибке
* @param message сообщение для логирования
*/
public void error(String message) {
this.log(LogLevel.ERROR, message);
}
/**
* Логирование отладочного сообщения
* @param message сообщение для логирования
*/
public void debug(String message) {
this.log(LogLevel.DEBUG, message);
}
private String getColorForLogLevel(LogLevel logLevel) {
return switch (logLevel) {
case INFO -> Color.BLUE;
case WARN -> Color.YELLOW;
case ERROR -> Color.RED;
case DEBUG -> Color.PURPLE;
};
}
}

View File

@@ -0,0 +1,13 @@
package com.rosetta.im.logger.enums;
public final class Color {
public static final String RESET = "\u001B[0m";
public static final String BLACK = "\u001B[30m";
public static final String RED = "\u001B[31m";
public static final String GREEN = "\u001B[32m";
public static final String YELLOW = "\u001B[33m";
public static final String BLUE = "\u001B[34m";
public static final String PURPLE = "\u001B[35m";
public static final String CYAN = "\u001B[36m";
public static final String WHITE = "\u001B[37m";
}

View File

@@ -0,0 +1,12 @@
package com.rosetta.im.logger.enums;
public enum LogLevel {
INFO,
WARN,
ERROR,
DEBUG;
public boolean allows(LogLevel other) {
return this.ordinal() <= other.ordinal();
}
}

View File

@@ -23,7 +23,6 @@ public class Server extends WebSocketServer {
private PacketManager packetManager; private PacketManager packetManager;
private Settings settings; private Settings settings;
private ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1); private ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1);
private Context context;
private ServerListener listener; private ServerListener listener;
private ThreadLocker threadLocker = new ThreadLocker(); private ThreadLocker threadLocker = new ThreadLocker();
@@ -38,33 +37,17 @@ public class Server extends WebSocketServer {
this.packetManager = packetManager; this.packetManager = packetManager;
} }
/**
* Конструктор сервера с объектом прикрепления
* @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;
}
/** /**
* Конструктор сервера с объектом прикрепления и слушателем событий сервера * Конструктор сервера с объектом прикрепления и слушателем событий сервера
* @param settings базовые настройки серверера * @param settings базовые настройки серверера
* @param packetManager менеджер пакетов (обработчиков и зарегистрированных пакетов) * @param packetManager менеджер пакетов (обработчиков и зарегистрированных пакетов)
* @param context вложение которое будет передаваться всем серрверным обработчикам пакетов,
* @param listener слушатель событий сервера * @param listener слушатель событий сервера
* может быть использовано для передачи контекста приложения * может быть использовано для передачи контекста приложения
*/ */
public Server(Settings settings, PacketManager packetManager, Context context, ServerListener listener) { public Server(Settings settings, PacketManager packetManager, ServerListener listener) {
super(new InetSocketAddress(settings.port)); super(new InetSocketAddress(settings.port));
this.settings = settings; this.settings = settings;
this.packetManager = packetManager; this.packetManager = packetManager;
this.context = context;
this.listener = listener; this.listener = listener;
} }
@@ -138,10 +121,8 @@ public class Server extends WebSocketServer {
/** /**
* Получаем обработчик пакета и вызываем его метод обработки. * Получаем обработчик пакета и вызываем его метод обработки.
*/ */
Class<? extends PacketExecutor> executorClass = this.packetManager.getExecutors().get(packetId); PacketExecutor executor = this.packetManager.getExecutors().get(packetId);
PacketExecutor executor = executorClass.getConstructor().newInstance();
executor.settings = this.settings; executor.settings = this.settings;
executor.context = this.context;
if(listener != null && !listener.onPacketReceived(this, client, packet)) { if(listener != null && !listener.onPacketReceived(this, client, packet)) {
/** /**
* Если слушатель сервера вернул false, пакет не обрабатываем. * Если слушатель сервера вернул false, пакет не обрабатываем.
@@ -151,7 +132,7 @@ public class Server extends WebSocketServer {
/** /**
* Проверяем наличие блокировки для данного пакета и ключа в аннотации @Lock. * Проверяем наличие блокировки для данного пакета и ключа в аннотации @Lock.
*/ */
if(!threadLocker.acquireLock(packet, executorClass)) { if(!threadLocker.acquireLock(packet, executor.getClass())) {
/** /**
* Если блокировка уже существует, значит другой поток обрабатывает пакет * Если блокировка уже существует, значит другой поток обрабатывает пакет
* с таким же значением lockFor, отклоняем текущий пакет. * с таким же значением lockFor, отклоняем текущий пакет.
@@ -164,7 +145,7 @@ public class Server extends WebSocketServer {
/** /**
* Снимаем блокировку после обработки пакета. * Снимаем блокировку после обработки пакета.
*/ */
threadLocker.releaseLock(packet, executorClass); threadLocker.releaseLock(packet, executor.getClass());
} }
} catch (Exception e) { } catch (Exception e) {
System.out.println("Error while processing packet " + packetClass.getName()); System.out.println("Error while processing packet " + packetClass.getName());
@@ -244,7 +225,6 @@ public class Server extends WebSocketServer {
/** /**
* Останавливаем сервер при завершении работы и вызываем слушатели остановки сервера. * Останавливаем сервер при завершении работы и вызываем слушатели остановки сервера.
*/ */
System.out.println("JVM Shutdown detected, stopping server...");
this.listener.onServerStop(this); this.listener.onServerStop(this);
})); }));
} }

View File

@@ -1,6 +1,5 @@
package io.orprotocol.packet; package io.orprotocol.packet;
import io.orprotocol.Context;
import io.orprotocol.ProtocolException; import io.orprotocol.ProtocolException;
import io.orprotocol.Settings; import io.orprotocol.Settings;
import io.orprotocol.client.Client; import io.orprotocol.client.Client;
@@ -10,7 +9,6 @@ import io.orprotocol.client.Client;
*/ */
public abstract class PacketExecutor { public abstract class PacketExecutor {
public Settings settings; public Settings settings;
public Context context;
/** /**
* Настройки сервера. * Настройки сервера.
@@ -19,15 +17,6 @@ public abstract class PacketExecutor {
public Settings getSettings() { public Settings getSettings() {
return settings; return settings;
} }
/**
* Контекст приложения переданный при создании сервера.
* @return контекст
*/
public Context getContext() {
return context;
}
/** /**
* Вызывается при получении пакета от клиента. * Вызывается при получении пакета от клиента.
* @param packet Пакет, полученный от клиента. * @param packet Пакет, полученный от клиента.

View File

@@ -8,7 +8,7 @@ import java.util.HashMap;
public class PacketManager { public class PacketManager {
private HashMap<Integer, Class<? extends Packet>> packets; private HashMap<Integer, Class<? extends Packet>> packets;
private HashMap<Integer, Class<? extends PacketExecutor>> executors; private HashMap<Integer, PacketExecutor> executors;
public PacketManager() { public PacketManager() {
this.packets = new HashMap<>(); this.packets = new HashMap<>();
@@ -46,7 +46,7 @@ public class PacketManager {
* Возвращает зарегистрированные исполнители пакетов. * Возвращает зарегистрированные исполнители пакетов.
* @return Хэш-карта зарегистрированных исполнителей пакетов. * @return Хэш-карта зарегистрированных исполнителей пакетов.
*/ */
public HashMap<Integer, Class<? extends PacketExecutor>> getExecutors() { public HashMap<Integer, PacketExecutor> getExecutors() {
return this.executors; return this.executors;
} }
@@ -73,7 +73,7 @@ public class PacketManager {
* @param packetId ID пакета * @param packetId ID пакета
* @param executor Обработчик пакета * @param executor Обработчик пакета
*/ */
public void registerExecutor(int packetId, Class <? extends PacketExecutor> executor) { public void registerExecutor(int packetId, PacketExecutor executor) {
if (this.executors == null) { if (this.executors == null) {
this.executors = new HashMap<>(); this.executors = new HashMap<>();
} }