Хэндшейк, улучшенный граф, новые методы в репозитории в DB

This commit is contained in:
RoyceDa
2026-02-04 06:08:24 +02:00
parent cd1e6e6b14
commit f74f4e7af7
16 changed files with 386 additions and 31 deletions

View File

@@ -1,13 +1,20 @@
package com.rosetta.im; package com.rosetta.im;
import com.rosetta.im.client.ClientManager;
import com.rosetta.im.event.EventManager; import com.rosetta.im.event.EventManager;
import com.rosetta.im.executors.Executor0Handshake; import com.rosetta.im.executors.Executor0Handshake;
import com.rosetta.im.listeners.HandshakeCompleteListener;
import com.rosetta.im.listeners.ServerStopListener; import com.rosetta.im.listeners.ServerStopListener;
import com.rosetta.im.logger.Logger; import com.rosetta.im.logger.Logger;
import com.rosetta.im.logger.enums.Color; import com.rosetta.im.logger.enums.Color;
import com.rosetta.im.logger.enums.LogLevel; import com.rosetta.im.logger.enums.LogLevel;
import com.rosetta.im.packet.Packet0Handshake; 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; import io.orprotocol.packet.PacketManager;
/** /**
@@ -19,11 +26,20 @@ public class Boot {
private PacketManager packetManager; private PacketManager packetManager;
private EventManager eventManager; private EventManager eventManager;
private Logger logger; private Logger logger;
private Server server;
private ServerAdapter serverAdapter;
private ClientManager clientManager;
public Boot() { public Boot() {
this.packetManager = new PacketManager(); this.packetManager = new PacketManager();
this.eventManager = new EventManager(); this.eventManager = new EventManager();
this.logger = new Logger(LogLevel.INFO); 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 this.eventManager;
} }
/**
* Получить менеджера клиентов, нужно для того чтобы отправить пакет списку клиентов например
* @return менеджер клиентов
*/
public ClientManager getClientManager() {
return this.clientManager;
}
/** /**
* Получить логгер приложения * Получить логгер приложения
* @return Logger * @return Logger
@@ -64,10 +88,14 @@ public class Boot {
private void registerAllEvents() { private void registerAllEvents() {
this.eventManager.registerListener(new ServerStopListener(this.logger)); this.eventManager.registerListener(new ServerStopListener(this.logger));
this.eventManager.registerListener(new HandshakeCompleteListener());
} }
private void registerAllPackets() { private void registerAllPackets() {
this.packetManager.registerPacket(0, Packet0Handshake.class); 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() { private void registerAllExecutors() {

View File

@@ -8,6 +8,10 @@ public enum Failures implements BaseFailures {
*/ */
AUTHENTIFICATION_ERROR(3001), AUTHENTIFICATION_ERROR(3001),
DATA_MISSMATCH(3001), DATA_MISSMATCH(3001),
/**
* Handshake не завершен
*/
HANDSHAKE_NOT_COMPLETED(3002),
/** /**
* Неподдерживаемый протокол * Неподдерживаемый протокол
*/ */

View File

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

View File

@@ -1,13 +1,15 @@
package com.rosetta.im.client.tags; 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; import io.orprotocol.client.ECITag;
/** /**
* Это вложенный обьект для клиента, содержащий информацию об аутентификации. * Это вложенный обьект для клиента, содержащий информацию об аутентификации.
*/ */
public class ECIAuthentificate extends ECITag { public class ECIAuthentificate implements ECITag {
public String publicKey; public String publicKey;
public String privateKey; public String privateKey;
@@ -51,4 +53,12 @@ public class ECIAuthentificate extends ECITag {
return this.handshakeStage == HandshakeStage.COMPLETED; return this.handshakeStage == HandshakeStage.COMPLETED;
} }
/**
* Создаем индекс для быстрого поиска клиентов (индексацию реализует ORProtocol)
*/
@Override
public Map<String, Object> getIndex() {
return null;
}
} }

View File

@@ -2,7 +2,7 @@ package com.rosetta.im.client.tags;
import io.orprotocol.client.ECITag; import io.orprotocol.client.ECITag;
public class ECIDevice extends ECITag { public class ECIDevice implements ECITag {
public String deviceId; public String deviceId;
public String deviceName; public String deviceName;

View File

@@ -67,6 +67,36 @@ public abstract class Repository<T> {
}); });
} }
/**
* Поиск сущности по значению одного поля с использованием оператора 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<T> 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 карта полей и их значений * @param fields карта полей и их значений

View File

@@ -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.HandshakeDeviceConfirmEvent;
import com.rosetta.im.event.events.handshake.HandshakeFailedEvent; import com.rosetta.im.event.events.handshake.HandshakeFailedEvent;
import com.rosetta.im.packet.Packet0Handshake; 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 com.rosetta.im.service.services.DeviceService;
import io.orprotocol.ProtocolException; import io.orprotocol.ProtocolException;
import io.orprotocol.client.Client; import io.orprotocol.client.Client;
import io.orprotocol.lock.Lock; import io.orprotocol.lock.Lock;
import io.orprotocol.packet.Packet;
import io.orprotocol.packet.PacketExecutor; import io.orprotocol.packet.PacketExecutor;
public class Executor0Handshake extends PacketExecutor { public class Executor0Handshake extends PacketExecutor<Packet0Handshake> {
private final UserRepository userRepository = new UserRepository(); private final UserRepository userRepository = new UserRepository();
private final DeviceRepository deviceRepository = new DeviceRepository(); private final DeviceRepository deviceRepository = new DeviceRepository();
@@ -36,8 +35,7 @@ public class Executor0Handshake extends PacketExecutor {
@Override @Override
@Lock(lockFor = "publicKey") @Lock(lockFor = "publicKey")
public void onPacketReceived(Packet packet, Client client) throws ProtocolException { public void onPacketReceived(Packet0Handshake handshake, Client client) throws ProtocolException {
Packet0Handshake handshake = (Packet0Handshake) packet;
String publicKey = handshake.getPublicKey(); String publicKey = handshake.getPublicKey();
String privateKey = handshake.getPrivateKey(); String privateKey = handshake.getPrivateKey();
String deviceId = handshake.getDeviceId(); String deviceId = handshake.getDeviceId();
@@ -67,7 +65,7 @@ public class Executor0Handshake extends PacketExecutor {
* Создаем минимальную информацию об устройстве клиента * Создаем минимальную информацию об устройстве клиента
*/ */
ECIDevice device = new ECIDevice(deviceId, deviceName, deviceOs); 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 ECIAuthentificate eciTag = new ECIAuthentificate
(publicKey, privateKey, HandshakeStage.COMPLETED); (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 ECIAuthentificate eciTag = new ECIAuthentificate
(publicKey, privateKey, HandshakeStage.NEED_DEVICE_VERIFICATION); (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 ECIAuthentificate eciTag = new ECIAuthentificate
(publicKey, privateKey, HandshakeStage.COMPLETED); (publicKey, privateKey, HandshakeStage.COMPLETED);
client.addTag(eciTag); client.addTag(ECIAuthentificate.class, eciTag);
/** /**
* Вызываем событие завершения хэндшейка * Вызываем событие завершения хэндшейка
*/ */

View File

@@ -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<Packet3Search> {
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;
}
}
}

View File

@@ -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 и синхронизация недоставленных сообщений (переписок)
}
}

View File

@@ -1,6 +1,6 @@
package com.rosetta.im.packet; 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.Stream;
import io.orprotocol.packet.Packet; import io.orprotocol.packet.Packet;

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View File

@@ -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> 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;
}
}

View File

@@ -1,4 +1,4 @@
package com.rosetta.im.packet.enums; package com.rosetta.im.packet.runtime;
/** /**
* Этапы хэндшейка между клиентом и сервером. * Этапы хэндшейка между клиентом и сервером.

View File

@@ -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);
}
}

View File

@@ -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<UserRepository> {
public UserService(UserRepository repository) {
super(repository);
}
/**
* Поиск пользователей по части имени пользователя.
* @param query часть имени пользователя
* @return список пользователей, соответствующих запросу
*/
public List<User> searchUsers(String query) {
return getRepository().likeSearchAll("username", query);
}
}