From f74f4e7af779dc7144f7d742e2fe8264792c84ee Mon Sep 17 00:00:00 2001 From: RoyceDa Date: Wed, 4 Feb 2026 06:08:24 +0200 Subject: [PATCH] =?UTF-8?q?=D0=A5=D1=8D=D0=BD=D0=B4=D1=88=D0=B5=D0=B9?= =?UTF-8?q?=D0=BA,=20=D1=83=D0=BB=D1=83=D1=87=D1=88=D0=B5=D0=BD=D0=BD?= =?UTF-8?q?=D1=8B=D0=B9=20=D0=B3=D1=80=D0=B0=D1=84,=20=D0=BD=D0=BE=D0=B2?= =?UTF-8?q?=D1=8B=D0=B5=20=D0=BC=D0=B5=D1=82=D0=BE=D0=B4=D1=8B=20=D0=B2=20?= =?UTF-8?q?=D1=80=D0=B5=D0=BF=D0=BE=D0=B7=D0=B8=D1=82=D0=BE=D1=80=D0=B8?= =?UTF-8?q?=D0=B8=20=D0=B2=20DB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/com/rosetta/im/Boot.java | 28 ++++++ src/main/java/com/rosetta/im/Failures.java | 4 + src/main/java/com/rosetta/im/Main.java | 20 +---- .../im/client/tags/ECIAuthentificate.java | 14 ++- .../com/rosetta/im/client/tags/ECIDevice.java | 2 +- .../com/rosetta/im/database/Repository.java | 30 +++++++ .../im/executors/Executor0Handshake.java | 16 ++-- .../rosetta/im/executors/Executor3Search.java | 32 +++++++ .../listeners/HandshakeCompleteListener.java | 12 +++ .../rosetta/im/packet/Packet0Handshake.java | 2 +- .../rosetta/im/packet/Packet1UserInfo.java | 85 +++++++++++++++++++ .../com/rosetta/im/packet/Packet2Result.java | 40 +++++++++ .../com/rosetta/im/packet/Packet3Search.java | 81 ++++++++++++++++++ .../{enums => runtime}/HandshakeStage.java | 2 +- .../im/packet/runtime/NetworkStatus.java | 25 ++++++ .../im/service/services/UserService.java | 24 ++++++ 16 files changed, 386 insertions(+), 31 deletions(-) create mode 100644 src/main/java/com/rosetta/im/executors/Executor3Search.java create mode 100644 src/main/java/com/rosetta/im/listeners/HandshakeCompleteListener.java create mode 100644 src/main/java/com/rosetta/im/packet/Packet1UserInfo.java create mode 100644 src/main/java/com/rosetta/im/packet/Packet2Result.java create mode 100644 src/main/java/com/rosetta/im/packet/Packet3Search.java rename src/main/java/com/rosetta/im/packet/{enums => runtime}/HandshakeStage.java (96%) create mode 100644 src/main/java/com/rosetta/im/packet/runtime/NetworkStatus.java create mode 100644 src/main/java/com/rosetta/im/service/services/UserService.java diff --git a/src/main/java/com/rosetta/im/Boot.java b/src/main/java/com/rosetta/im/Boot.java index 6d426c6..728507a 100644 --- a/src/main/java/com/rosetta/im/Boot.java +++ b/src/main/java/com/rosetta/im/Boot.java @@ -1,13 +1,20 @@ package com.rosetta.im; +import com.rosetta.im.client.ClientManager; import com.rosetta.im.event.EventManager; import com.rosetta.im.executors.Executor0Handshake; +import com.rosetta.im.listeners.HandshakeCompleteListener; 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.Packet1UserInfo; +import com.rosetta.im.packet.Packet2Result; +import com.rosetta.im.packet.Packet3Search; +import io.orprotocol.Server; +import io.orprotocol.Settings; import io.orprotocol.packet.PacketManager; /** @@ -19,11 +26,20 @@ public class Boot { private PacketManager packetManager; private EventManager eventManager; private Logger logger; + private Server server; + private ServerAdapter serverAdapter; + private ClientManager clientManager; public Boot() { this.packetManager = new PacketManager(); this.eventManager = new EventManager(); this.logger = new Logger(LogLevel.INFO); + this.serverAdapter = new ServerAdapter(this.eventManager); + this.server = new Server(new Settings( + 8881, + 30 + ), packetManager, this.serverAdapter); + this.clientManager = new ClientManager(server); } /** @@ -42,6 +58,14 @@ public class Boot { return this.eventManager; } + /** + * Получить менеджера клиентов, нужно для того чтобы отправить пакет списку клиентов например + * @return менеджер клиентов + */ + public ClientManager getClientManager() { + return this.clientManager; + } + /** * Получить логгер приложения * @return Logger @@ -64,10 +88,14 @@ public class Boot { private void registerAllEvents() { this.eventManager.registerListener(new ServerStopListener(this.logger)); + this.eventManager.registerListener(new HandshakeCompleteListener()); } private void registerAllPackets() { this.packetManager.registerPacket(0, Packet0Handshake.class); + this.packetManager.registerPacket(1, Packet1UserInfo.class); + this.packetManager.registerPacket(2, Packet2Result.class); + this.packetManager.registerPacket(3, Packet3Search.class); } private void registerAllExecutors() { diff --git a/src/main/java/com/rosetta/im/Failures.java b/src/main/java/com/rosetta/im/Failures.java index 4b422f0..7569b5a 100644 --- a/src/main/java/com/rosetta/im/Failures.java +++ b/src/main/java/com/rosetta/im/Failures.java @@ -8,6 +8,10 @@ public enum Failures implements BaseFailures { */ AUTHENTIFICATION_ERROR(3001), DATA_MISSMATCH(3001), + /** + * Handshake не завершен + */ + HANDSHAKE_NOT_COMPLETED(3002), /** * Неподдерживаемый протокол */ diff --git a/src/main/java/com/rosetta/im/Main.java b/src/main/java/com/rosetta/im/Main.java index d9f8547..a2e0b6f 100644 --- a/src/main/java/com/rosetta/im/Main.java +++ b/src/main/java/com/rosetta/im/Main.java @@ -1,28 +1,14 @@ package com.rosetta.im; -import io.orprotocol.Server; -import io.orprotocol.Settings; - public class Main { public static void main(String[] args) { /** * Регистрация всех пакетов и их обработчиков */ - Boot boot = new Boot().bootstrap(); + Boot boot = new Boot(); /** - * Загрузка настроек сервера + * Стартуем сервер */ - Settings settings = new Settings(8881, 30); - /** - * Создание адаптера сервера для трансляции событий протокола в события приложения, - * обрабатываются основыне события такие как запуск/остановка сервера, - * подключение/отключение клиентов, ошибки сервера и получение пакетов - */ - ServerAdapter serverAdapter = new ServerAdapter(boot.getEventManager()); - /** - * Запуск сервера - */ - Server server = new Server(settings, boot.getPacketManager(), serverAdapter); - server.start(); + boot.bootstrap(); } } \ No newline at end of file diff --git a/src/main/java/com/rosetta/im/client/tags/ECIAuthentificate.java b/src/main/java/com/rosetta/im/client/tags/ECIAuthentificate.java index 01f0116..bc08482 100644 --- a/src/main/java/com/rosetta/im/client/tags/ECIAuthentificate.java +++ b/src/main/java/com/rosetta/im/client/tags/ECIAuthentificate.java @@ -1,13 +1,15 @@ package com.rosetta.im.client.tags; -import com.rosetta.im.packet.enums.HandshakeStage; +import java.util.Map; + +import com.rosetta.im.packet.runtime.HandshakeStage; import io.orprotocol.client.ECITag; /** * Это вложенный обьект для клиента, содержащий информацию об аутентификации. */ -public class ECIAuthentificate extends ECITag { +public class ECIAuthentificate implements ECITag { public String publicKey; public String privateKey; @@ -51,4 +53,12 @@ public class ECIAuthentificate extends ECITag { return this.handshakeStage == HandshakeStage.COMPLETED; } + /** + * Создаем индекс для быстрого поиска клиентов (индексацию реализует ORProtocol) + */ + @Override + public Map getIndex() { + return null; + } + } diff --git a/src/main/java/com/rosetta/im/client/tags/ECIDevice.java b/src/main/java/com/rosetta/im/client/tags/ECIDevice.java index f24918e..0994835 100644 --- a/src/main/java/com/rosetta/im/client/tags/ECIDevice.java +++ b/src/main/java/com/rosetta/im/client/tags/ECIDevice.java @@ -2,7 +2,7 @@ package com.rosetta.im.client.tags; import io.orprotocol.client.ECITag; -public class ECIDevice extends ECITag { +public class ECIDevice implements ECITag { public String deviceId; public String deviceName; diff --git a/src/main/java/com/rosetta/im/database/Repository.java b/src/main/java/com/rosetta/im/database/Repository.java index d0e15cb..2b2398d 100644 --- a/src/main/java/com/rosetta/im/database/Repository.java +++ b/src/main/java/com/rosetta/im/database/Repository.java @@ -67,6 +67,36 @@ public abstract class Repository { }); } + /** + * Поиск сущности по значению одного поля с использованием оператора LIKE + * @param fieldName поле + * @param value значение + * @return найденная сущность или null + */ + public T likeSearch(String fieldName, String value) { + return executeInSession(session -> { + String queryString = "FROM " + entityClass.getSimpleName() + " WHERE " + fieldName + " LIKE :value"; + return session.createQuery(queryString, entityClass) + .setParameter("value", "%" + value + "%") + .uniqueResult(); + }); + } + + /** + * Поиск сущности по значению одного поля с использованием оператора LIKE + * @param fieldName поле + * @param value значение + * @return найденная сущность или null + */ + public List likeSearchAll(String fieldName, String value) { + return executeInSession(session -> { + String queryString = "FROM " + entityClass.getSimpleName() + " WHERE " + fieldName + " LIKE :value"; + return session.createQuery(queryString, entityClass) + .setParameter("value", "%" + value + "%") + .list(); + }); + } + /** * Поиск сущности по набору полей * @param fields карта полей и их значений diff --git a/src/main/java/com/rosetta/im/executors/Executor0Handshake.java b/src/main/java/com/rosetta/im/executors/Executor0Handshake.java index b3939ee..3dfbff7 100644 --- a/src/main/java/com/rosetta/im/executors/Executor0Handshake.java +++ b/src/main/java/com/rosetta/im/executors/Executor0Handshake.java @@ -13,16 +13,15 @@ import com.rosetta.im.event.events.handshake.HandshakeCompletedEvent; import com.rosetta.im.event.events.handshake.HandshakeDeviceConfirmEvent; import com.rosetta.im.event.events.handshake.HandshakeFailedEvent; import com.rosetta.im.packet.Packet0Handshake; -import com.rosetta.im.packet.enums.HandshakeStage; +import com.rosetta.im.packet.runtime.HandshakeStage; import com.rosetta.im.service.services.DeviceService; import io.orprotocol.ProtocolException; import io.orprotocol.client.Client; import io.orprotocol.lock.Lock; -import io.orprotocol.packet.Packet; import io.orprotocol.packet.PacketExecutor; -public class Executor0Handshake extends PacketExecutor { +public class Executor0Handshake extends PacketExecutor { private final UserRepository userRepository = new UserRepository(); private final DeviceRepository deviceRepository = new DeviceRepository(); @@ -36,8 +35,7 @@ public class Executor0Handshake extends PacketExecutor { @Override @Lock(lockFor = "publicKey") - public void onPacketReceived(Packet packet, Client client) throws ProtocolException { - Packet0Handshake handshake = (Packet0Handshake) packet; + public void onPacketReceived(Packet0Handshake handshake, Client client) throws ProtocolException { String publicKey = handshake.getPublicKey(); String privateKey = handshake.getPrivateKey(); String deviceId = handshake.getDeviceId(); @@ -67,7 +65,7 @@ public class Executor0Handshake extends PacketExecutor { * Создаем минимальную информацию об устройстве клиента */ ECIDevice device = new ECIDevice(deviceId, deviceName, deviceOs); - client.addTag(device); + client.addTag(ECIDevice.class, device); /** * Проверяем есть ли такой пользователь @@ -95,7 +93,7 @@ public class Executor0Handshake extends PacketExecutor { */ ECIAuthentificate eciTag = new ECIAuthentificate (publicKey, privateKey, HandshakeStage.COMPLETED); - client.addTag(eciTag); + client.addTag(ECIAuthentificate.class, eciTag); /** * Вызываем событие завершения хэндшейка */ @@ -151,7 +149,7 @@ public class Executor0Handshake extends PacketExecutor { */ ECIAuthentificate eciTag = new ECIAuthentificate (publicKey, privateKey, HandshakeStage.NEED_DEVICE_VERIFICATION); - client.addTag(eciTag); + client.addTag(ECIAuthentificate.class, eciTag); /** * Отправляем клиенту информацию о необходимости * подтверждения устройства @@ -179,7 +177,7 @@ public class Executor0Handshake extends PacketExecutor { */ ECIAuthentificate eciTag = new ECIAuthentificate (publicKey, privateKey, HandshakeStage.COMPLETED); - client.addTag(eciTag); + client.addTag(ECIAuthentificate.class, eciTag); /** * Вызываем событие завершения хэндшейка */ diff --git a/src/main/java/com/rosetta/im/executors/Executor3Search.java b/src/main/java/com/rosetta/im/executors/Executor3Search.java new file mode 100644 index 0000000..4f07380 --- /dev/null +++ b/src/main/java/com/rosetta/im/executors/Executor3Search.java @@ -0,0 +1,32 @@ +package com.rosetta.im.executors; + +import com.rosetta.im.Failures; +import com.rosetta.im.client.tags.ECIAuthentificate; +import com.rosetta.im.database.repository.UserRepository; +import com.rosetta.im.packet.Packet3Search; + +import io.orprotocol.ProtocolException; +import io.orprotocol.client.Client; +import io.orprotocol.packet.PacketExecutor; + +public class Executor3Search extends PacketExecutor { + + private final UserRepository userRepository = new UserRepository(); + + @Override + public void onPacketReceived(Packet3Search packet, Client client) throws Exception, ProtocolException { + String search = packet.getSearch(); + + ECIAuthentificate eciAuthentificate = client.getTag(ECIAuthentificate.class); + if(eciAuthentificate == null || !eciAuthentificate.hasAuthorized()) { + /** + * Клиент не авторизован, не разрешаем ему выполнять поиск + */ + client.disconnect(Failures.HANDSHAKE_NOT_COMPLETED); + return; + } + + + } + +} diff --git a/src/main/java/com/rosetta/im/listeners/HandshakeCompleteListener.java b/src/main/java/com/rosetta/im/listeners/HandshakeCompleteListener.java new file mode 100644 index 0000000..39e1658 --- /dev/null +++ b/src/main/java/com/rosetta/im/listeners/HandshakeCompleteListener.java @@ -0,0 +1,12 @@ +package com.rosetta.im.listeners; + +import com.rosetta.im.event.Listener; +import com.rosetta.im.event.events.handshake.HandshakeCompletedEvent; + +public class HandshakeCompleteListener implements Listener { + + public void onHandshakeComplete(HandshakeCompletedEvent event) { + //TODO: Обработка завершения Handshake и синхронизация недоставленных сообщений (переписок) + } + +} diff --git a/src/main/java/com/rosetta/im/packet/Packet0Handshake.java b/src/main/java/com/rosetta/im/packet/Packet0Handshake.java index 44f7506..44f415c 100644 --- a/src/main/java/com/rosetta/im/packet/Packet0Handshake.java +++ b/src/main/java/com/rosetta/im/packet/Packet0Handshake.java @@ -1,6 +1,6 @@ package com.rosetta.im.packet; -import com.rosetta.im.packet.enums.HandshakeStage; +import com.rosetta.im.packet.runtime.HandshakeStage; import io.orprotocol.Stream; import io.orprotocol.packet.Packet; diff --git a/src/main/java/com/rosetta/im/packet/Packet1UserInfo.java b/src/main/java/com/rosetta/im/packet/Packet1UserInfo.java new file mode 100644 index 0000000..a6022da --- /dev/null +++ b/src/main/java/com/rosetta/im/packet/Packet1UserInfo.java @@ -0,0 +1,85 @@ +package com.rosetta.im.packet; + +import io.orprotocol.Stream; +import io.orprotocol.packet.Packet; + +public class Packet1UserInfo extends Packet { + + @Deprecated(since = "1.1", forRemoval = true) + private String privateKey; + + private String username; + private String title; + + @Override + public void read(Stream stream) { + this.packetId = stream.readInt16(); + this.username = stream.readString(); + this.title = stream.readString(); + this.privateKey = stream.readString(); + } + + @Override + public Stream write() { + Stream steram = new Stream(); + steram.writeInt16(this.packetId); + steram.writeString(this.username); + steram.writeString(this.title); + steram.writeString(this.privateKey); + return steram; + } + + + /** + * Получает приватный ключ пользователя + * @return приватный ключ + * @deprecated с версии сервера 1.1 использование приватных ключей + * в протоколе устарело, так как теперь сервер использует Handshake для аутентификации пользователей. + */ + @Deprecated(since = "1.1", forRemoval = true) + public String getPrivateKey() { + return this.privateKey; + } + + /** + * Устанавливает приватный ключ пользователя + * @param privateKey приватный ключ + * @deprecated с версии сервера 1.1 использование приватных ключей + * в протоколе устарело, так как теперь сервер использует Handshake для аутентификации пользователей. + */ + public void setPrivateKey(String privateKey) { + this.privateKey = privateKey; + } + + /** + * Возвращает имя пользователя + * @return имя пользователя + */ + public String getUsername() { + return this.username; + } + + /** + * Возвращает заголовок (титул) пользователя + * @return заголовок пользователя + */ + public String getTitle() { + return this.title; + } + + /** + * Устанавливает имя пользователя + * @param username имя пользователя + */ + public void setUsername(String username) { + this.username = username; + } + + /** + * Устанавливает заголовок (титул) пользователя + * @param title заголовок пользователя + */ + public void setTitle(String title) { + this.title = title; + } +} diff --git a/src/main/java/com/rosetta/im/packet/Packet2Result.java b/src/main/java/com/rosetta/im/packet/Packet2Result.java new file mode 100644 index 0000000..d5c4056 --- /dev/null +++ b/src/main/java/com/rosetta/im/packet/Packet2Result.java @@ -0,0 +1,40 @@ +package com.rosetta.im.packet; + +import io.orprotocol.Stream; +import io.orprotocol.packet.Packet; + +public class Packet2Result extends Packet { + + private int resultCode; + + @Override + public void read(Stream stream) { + this.resultCode = stream.readInt8(); + } + + @Override + public Stream write() { + Stream stream = new Stream(); + stream.writeInt16(this.packetId); + stream.writeInt8(this.resultCode); + return stream; + } + + /** + * Получает код результата операции + * @return код результата + */ + public int getResultCode() { + return this.resultCode; + } + + /** + * Устанавливает код результата операции + * @param resultCode код результата + */ + public void setResultCode(int resultCode) { + this.resultCode = resultCode; + } + + +} diff --git a/src/main/java/com/rosetta/im/packet/Packet3Search.java b/src/main/java/com/rosetta/im/packet/Packet3Search.java new file mode 100644 index 0000000..cd18c5b --- /dev/null +++ b/src/main/java/com/rosetta/im/packet/Packet3Search.java @@ -0,0 +1,81 @@ +package com.rosetta.im.packet; + +import java.util.ArrayList; +import java.util.List; + +import com.rosetta.im.packet.runtime.SearchInfo; + +import io.orprotocol.Stream; +import io.orprotocol.packet.Packet; + +public class Packet3Search extends Packet { + + @Deprecated(since = "1.1", forRemoval = true) + private String privateKey; + private String search; + private List searchInfo; + + @Override + public void read(Stream stream) { + this.privateKey = stream.readString(); + this.search = stream.readString(); + int searchInfoCount = stream.readInt16(); + this.searchInfo = new ArrayList<>(); + for (int i = 0; i < searchInfoCount; i++) { + SearchInfo info = new SearchInfo(); + info.readFromStream(stream); + this.searchInfo.add(info); + } + } + + @Override + public Stream write() { + Stream stream = new Stream(); + stream.writeInt16(this.packetId); + stream.writeString(this.privateKey); + stream.writeString(this.search); + stream.writeInt16(this.searchInfo.size()); + for (SearchInfo info : this.searchInfo) { + info.writeToStream(stream); + } + return stream; + } + + /** + * Получает приватный ключ пользователя + * @return приватный ключ + * @deprecated с версии сервера 1.1 использование приватных ключей + * в протоколе устарело, так как теперь сервер использует Handshake для аутентификации пользователей. + */ + @Deprecated(since = "1.1", forRemoval = true) + public String getPrivateKey() { + return this.privateKey; + } + + /** + * Устанавливает приватный ключ пользователя + * @param privateKey приватный ключ + * @deprecated с версии сервера 1.1 использование приватных ключей + * в протоколе устарело, так как теперь сервер использует Handshake для аутентификации пользователей. + */ + public void setPrivateKey(String privateKey) { + this.privateKey = privateKey; + } + + /** + * Получает строку поиска + * @return строка поиска + */ + public String getSearch() { + return this.search; + } + + /** + * Устанавливает строку поиска + * @param search строка поиска + */ + public void setSearch(String search) { + this.search = search; + } + +} diff --git a/src/main/java/com/rosetta/im/packet/enums/HandshakeStage.java b/src/main/java/com/rosetta/im/packet/runtime/HandshakeStage.java similarity index 96% rename from src/main/java/com/rosetta/im/packet/enums/HandshakeStage.java rename to src/main/java/com/rosetta/im/packet/runtime/HandshakeStage.java index a581057..74fc01b 100644 --- a/src/main/java/com/rosetta/im/packet/enums/HandshakeStage.java +++ b/src/main/java/com/rosetta/im/packet/runtime/HandshakeStage.java @@ -1,4 +1,4 @@ -package com.rosetta.im.packet.enums; +package com.rosetta.im.packet.runtime; /** * Этапы хэндшейка между клиентом и сервером. diff --git a/src/main/java/com/rosetta/im/packet/runtime/NetworkStatus.java b/src/main/java/com/rosetta/im/packet/runtime/NetworkStatus.java new file mode 100644 index 0000000..1ed68fe --- /dev/null +++ b/src/main/java/com/rosetta/im/packet/runtime/NetworkStatus.java @@ -0,0 +1,25 @@ +package com.rosetta.im.packet.runtime; + +public enum NetworkStatus { + ONLINE(0), + OFFILE(1); + + private final int code; + + NetworkStatus(int code) { + this.code = code; + } + + public int getCode() { + return code; + } + + public static NetworkStatus fromCode(int code) { + for (NetworkStatus status : NetworkStatus.values()) { + if (status.getCode() == code) { + return status; + } + } + throw new IllegalArgumentException("Invalid NetworkStatus code: " + code); + } +} diff --git a/src/main/java/com/rosetta/im/service/services/UserService.java b/src/main/java/com/rosetta/im/service/services/UserService.java new file mode 100644 index 0000000..5ea2bbe --- /dev/null +++ b/src/main/java/com/rosetta/im/service/services/UserService.java @@ -0,0 +1,24 @@ +package com.rosetta.im.service.services; + +import java.util.List; + +import com.rosetta.im.database.entity.User; +import com.rosetta.im.database.repository.UserRepository; +import com.rosetta.im.service.Service; + +public class UserService extends Service { + + public UserService(UserRepository repository) { + super(repository); + } + + /** + * Поиск пользователей по части имени пользователя. + * @param query часть имени пользователя + * @return список пользователей, соответствующих запросу + */ + public List searchUsers(String query) { + return getRepository().likeSearchAll("username", query); + } + +}