Хэндшейк, сервисы, аннотационные блокировки в протоколе, репозитории
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
package com.rosetta.im.database;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
|
||||
import org.hibernate.Session;
|
||||
import org.hibernate.Transaction;
|
||||
@@ -16,6 +17,11 @@ public abstract class Repository<T> {
|
||||
this.entityClass = entityClass;
|
||||
}
|
||||
|
||||
/**
|
||||
* Сохранение сущности в базе данных
|
||||
* @param entity сущность для сохранения
|
||||
* @return сохраненная сущность
|
||||
*/
|
||||
public T save(T entity) {
|
||||
return executeInTransaction(session -> {
|
||||
session.persist(entity);
|
||||
@@ -23,6 +29,11 @@ public abstract class Repository<T> {
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Обновление сущности в базе данных
|
||||
* @param entity сущность для обновления
|
||||
* @return обновленная сущность
|
||||
*/
|
||||
public T update(T entity) {
|
||||
return executeInTransaction(session -> {
|
||||
session.merge(entity);
|
||||
@@ -30,6 +41,10 @@ public abstract class Repository<T> {
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Удаление сущности из базы данных
|
||||
* @param entity сущность для удаления
|
||||
*/
|
||||
public void delete(T entity) {
|
||||
executeInTransaction(session -> {
|
||||
session.remove(entity);
|
||||
@@ -76,6 +91,126 @@ public abstract class Repository<T> {
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Удаление сущностей по значению одного поля
|
||||
* @param fieldName поле
|
||||
* @param value значение
|
||||
*/
|
||||
public void deleteByField(String fieldName, Object value) {
|
||||
executeInTransaction(session -> {
|
||||
String queryString = "DELETE FROM " + entityClass.getSimpleName() + " WHERE " + fieldName + " = :value";
|
||||
session.createQuery(queryString, entityClass)
|
||||
.setParameter("value", value)
|
||||
.executeUpdate();
|
||||
return null;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Поиск всех сущностей по значению одного поля
|
||||
* @param fieldName поле
|
||||
* @param value значение
|
||||
* @return список найденных сущностей
|
||||
*/
|
||||
public List<T> findAllByField(String fieldName, Object value) {
|
||||
return executeInSession(session -> {
|
||||
String queryString = "FROM " + entityClass.getSimpleName() + " WHERE " + fieldName + " = :value";
|
||||
return session.createQuery(queryString, entityClass)
|
||||
.setParameter("value", value)
|
||||
.list();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Поиск всех сущностей по значению набора полей
|
||||
* @param fields карта полей и их значений
|
||||
* @return список найденных сущностей
|
||||
*/
|
||||
public List<T> findAllByField(HashMap<String, Object> fields) {
|
||||
return executeInSession(session -> {
|
||||
StringBuilder queryString = new StringBuilder("FROM " + entityClass.getSimpleName() + " WHERE ");
|
||||
int index = 0;
|
||||
for (String fieldName : fields.keySet()) {
|
||||
if (index > 0) {
|
||||
queryString.append(" AND ");
|
||||
}
|
||||
queryString.append(fieldName).append(" = :").append(fieldName);
|
||||
index++;
|
||||
}
|
||||
var query = session.createQuery(queryString.toString(), this.entityClass);
|
||||
for (var entry : fields.entrySet()) {
|
||||
query.setParameter(entry.getKey(), entry.getValue());
|
||||
}
|
||||
return query.list();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Поиск всех сущностей, тяжелый метод, лучше не выполнять без необходимости
|
||||
* @return список всех сущностей
|
||||
*/
|
||||
public List<T> findAll() {
|
||||
return executeInSession(session -> {
|
||||
String queryString = "FROM " + entityClass.getSimpleName();
|
||||
return session.createQuery(queryString, entityClass).list();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Подсчет всех сущностей в таблице
|
||||
* @return количество сущностей
|
||||
*/
|
||||
public long countAll() {
|
||||
return executeInSession(session -> {
|
||||
String queryString = "SELECT COUNT(*) FROM " + entityClass.getSimpleName();
|
||||
return session.createQuery(queryString, Long.class).uniqueResult();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Подсчет сущностей по значению одного поля
|
||||
* @param fieldName поле
|
||||
* @param value значение
|
||||
* @return количество сущностей
|
||||
*/
|
||||
public long countByField(String fieldName, Object value) {
|
||||
return executeInSession(session -> {
|
||||
String queryString = "SELECT COUNT(*) FROM " + entityClass.getSimpleName() + " WHERE " + fieldName + " = :value";
|
||||
return session.createQuery(queryString, Long.class)
|
||||
.setParameter("value", value)
|
||||
.uniqueResult();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Подсчет сущностей по набору полей
|
||||
* @param fields карта полей и их значений
|
||||
* @return количество сущностей
|
||||
*/
|
||||
public long countByField(HashMap<String, Object> fields) {
|
||||
return executeInSession(session -> {
|
||||
StringBuilder queryString = new StringBuilder("SELECT COUNT(*) FROM " + entityClass.getSimpleName() + " WHERE ");
|
||||
int index = 0;
|
||||
for (String fieldName : fields.keySet()) {
|
||||
if (index > 0) {
|
||||
queryString.append(" AND ");
|
||||
}
|
||||
queryString.append(fieldName).append(" = :").append(fieldName);
|
||||
index++;
|
||||
}
|
||||
var query = session.createQuery(queryString.toString(), Long.class);
|
||||
for (var entry : fields.entrySet()) {
|
||||
query.setParameter(entry.getKey(), entry.getValue());
|
||||
}
|
||||
return query.uniqueResult();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Обновление полей сущности по заданным условиям
|
||||
* @param fieldsToUpdate поля для обновления
|
||||
* @param whereFields условия для выбора сущностей
|
||||
*/
|
||||
public void update(HashMap<String, Object> fieldsToUpdate, HashMap<String, Object> whereFields) {
|
||||
executeInTransaction(session -> {
|
||||
StringBuilder queryString = new StringBuilder("UPDATE " + entityClass.getSimpleName() + " SET ");
|
||||
|
||||
@@ -7,7 +7,6 @@ import jakarta.persistence.Entity;
|
||||
import jakarta.persistence.GeneratedValue;
|
||||
import jakarta.persistence.GenerationType;
|
||||
import jakarta.persistence.Id;
|
||||
import jakarta.persistence.PrePersist;
|
||||
import jakarta.persistence.Table;
|
||||
|
||||
@Entity
|
||||
@@ -29,14 +28,9 @@ public class Device extends CreateUpdateEntity {
|
||||
/**
|
||||
* Время завершения сессии устройства
|
||||
*/
|
||||
@Column(name = "leaveTime", nullable = true)
|
||||
@Column(name = "leaveTime", nullable = true, columnDefinition = "bigint default 0")
|
||||
private Long leaveTime;
|
||||
|
||||
@PrePersist
|
||||
protected void onCreate() {
|
||||
this.leaveTime = 0L;
|
||||
}
|
||||
|
||||
public Long getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
@@ -1,5 +1,25 @@
|
||||
package com.rosetta.im.database.repository;
|
||||
|
||||
public class DeviceRepository {
|
||||
import java.util.List;
|
||||
|
||||
import com.rosetta.im.database.Repository;
|
||||
import com.rosetta.im.database.entity.Device;
|
||||
import com.rosetta.im.database.entity.User;
|
||||
|
||||
public class DeviceRepository extends Repository<Device> {
|
||||
|
||||
public DeviceRepository() {
|
||||
super(Device.class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Найти все устройства пользователя
|
||||
* @param user пользователь
|
||||
* @return список устройств
|
||||
*/
|
||||
public List<Device> findAll(User user) {
|
||||
return this.findAllByField("publicKey", user.getPublicKey());
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
package com.rosetta.im.event.events.handshake;
|
||||
|
||||
import com.rosetta.im.client.tags.ECIAuthentificate;
|
||||
import com.rosetta.im.client.tags.ECIDevice;
|
||||
|
||||
import io.orprotocol.client.Client;
|
||||
|
||||
/**
|
||||
* Вызывается когда устройство клиента нуждается в подтверждении
|
||||
* пользователем с другого устрйоства для завершения хэндшейка.
|
||||
*/
|
||||
public class HandshakeDeviceConfirmEvent extends BaseHandshakeEvent {
|
||||
|
||||
public HandshakeDeviceConfirmEvent(String publicKey, String privateKey, ECIDevice device, ECIAuthentificate eciAuthentificate, Client client) {
|
||||
super(publicKey, privateKey, device, eciAuthentificate, client);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -5,23 +5,32 @@ import com.rosetta.im.Configuration;
|
||||
import com.rosetta.im.Failures;
|
||||
import com.rosetta.im.client.tags.ECIAuthentificate;
|
||||
import com.rosetta.im.client.tags.ECIDevice;
|
||||
import com.rosetta.im.database.entity.Device;
|
||||
import com.rosetta.im.database.entity.User;
|
||||
import com.rosetta.im.database.repository.DeviceRepository;
|
||||
import com.rosetta.im.database.repository.UserRepository;
|
||||
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.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 {
|
||||
|
||||
private final UserRepository userRepository = new UserRepository();
|
||||
private final DeviceRepository deviceRepository = new DeviceRepository();
|
||||
private final DeviceService deviceService = new DeviceService(deviceRepository);
|
||||
|
||||
|
||||
@Override
|
||||
@Lock(lockFor = "publicKey")
|
||||
public void onPacketReceived(Packet packet, Client client) throws ProtocolException {
|
||||
Packet0Handshake handshake = (Packet0Handshake) packet;
|
||||
String publicKey = handshake.getPublicKey();
|
||||
@@ -31,7 +40,6 @@ public class Executor0Handshake extends PacketExecutor {
|
||||
String deviceOs = handshake.getDeviceOs();
|
||||
int protocolVersion = handshake.getProtocolVersion();
|
||||
AppContext context = (AppContext) this.getContext();
|
||||
|
||||
/**
|
||||
* Получаем информацию об аутентификации клиента
|
||||
* используя возможности ECI тэгов.
|
||||
@@ -43,7 +51,6 @@ public class Executor0Handshake extends PacketExecutor {
|
||||
*/
|
||||
return;
|
||||
}
|
||||
|
||||
/**
|
||||
* Проверяем корректность версии протокола
|
||||
*/
|
||||
@@ -56,6 +63,7 @@ public class Executor0Handshake extends PacketExecutor {
|
||||
* Создаем минимальную информацию об устройстве клиента
|
||||
*/
|
||||
ECIDevice device = new ECIDevice(deviceId, deviceName, deviceOs);
|
||||
client.addTag(device);
|
||||
|
||||
/**
|
||||
* Проверяем есть ли такой пользователь
|
||||
@@ -100,10 +108,9 @@ public class Executor0Handshake extends PacketExecutor {
|
||||
/**
|
||||
* Отправляем клиенту подтверждение успешного хэндшейка
|
||||
*/
|
||||
Packet0Handshake response = new Packet0Handshake();
|
||||
response.setHandshakeStage(HandshakeStage.COMPLETED);
|
||||
response.setHeartbeatInterval(this.settings.heartbeatInterval);
|
||||
client.send(response);
|
||||
handshake.setHandshakeStage(HandshakeStage.COMPLETED);
|
||||
handshake.setHeartbeatInterval(this.settings.heartbeatInterval);
|
||||
client.send(handshake);
|
||||
return;
|
||||
}
|
||||
/**
|
||||
@@ -117,10 +124,77 @@ public class Executor0Handshake extends PacketExecutor {
|
||||
client.disconnect(Failures.AUTHENTIFICATION_ERROR);
|
||||
return;
|
||||
}
|
||||
long userDevicesCount = deviceService.countUserDevices(user);
|
||||
|
||||
/**
|
||||
* Проверяем верифицировано ли устройство
|
||||
*/
|
||||
if(userDevicesCount > 0 && !deviceService.isDeviceVerifiedByUser(deviceId, user)) {
|
||||
/**
|
||||
* Устройство не верифицировано, нужно отправить клиента
|
||||
* на подтверждение устройства
|
||||
*/
|
||||
handshake.setHandshakeStage(HandshakeStage.NEED_DEVICE_VERIFICATION);
|
||||
handshake.setHeartbeatInterval(this.settings.heartbeatInterval);
|
||||
/**
|
||||
* Вызываем событие подтверждения устройства
|
||||
*/
|
||||
context.getEventManager().callEvent(
|
||||
new HandshakeDeviceConfirmEvent(publicKey, privateKey, device, authentificate, client)
|
||||
);
|
||||
/**
|
||||
* Ставим метку аутентификации на клиента
|
||||
*/
|
||||
ECIAuthentificate eciTag = new ECIAuthentificate
|
||||
(publicKey, privateKey, HandshakeStage.NEED_DEVICE_VERIFICATION);
|
||||
client.addTag(eciTag);
|
||||
/**
|
||||
* Отправляем клиенту информацию о необходимости
|
||||
* подтверждения устройства
|
||||
*/
|
||||
client.send(handshake);
|
||||
return;
|
||||
}
|
||||
|
||||
if(userDevicesCount == 0) {
|
||||
/**
|
||||
* Это первое устройство пользователя, сохраняем его
|
||||
* как верифицированное
|
||||
*/
|
||||
Device newDevice = new Device();
|
||||
newDevice.setDeviceId(deviceId);
|
||||
newDevice.setDeviceName(deviceName);
|
||||
newDevice.setDeviceOs(deviceOs);
|
||||
newDevice.setPublicKey(publicKey);
|
||||
newDevice.setLeaveTime(System.currentTimeMillis());
|
||||
deviceRepository.save(newDevice);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Ставим метку аутентификации на клиента
|
||||
*/
|
||||
ECIAuthentificate eciTag = new ECIAuthentificate
|
||||
(publicKey, privateKey, HandshakeStage.COMPLETED);
|
||||
client.addTag(eciTag);
|
||||
/**
|
||||
* Вызываем событие завершения хэндшейка
|
||||
*/
|
||||
boolean cancelled = context.getEventManager().callEvent(
|
||||
new HandshakeCompletedEvent(publicKey, privateKey, device, eciTag, client)
|
||||
);
|
||||
if(cancelled) {
|
||||
/**
|
||||
* Событие было отменено, не даем завершить хэндшейк
|
||||
*/
|
||||
client.disconnect(Failures.DATA_MISSMATCH);
|
||||
return;
|
||||
}
|
||||
/**
|
||||
* Отправляем клиенту подтверждение успешного хэндшейка
|
||||
*/
|
||||
handshake.setHandshakeStage(HandshakeStage.COMPLETED);
|
||||
handshake.setHeartbeatInterval(this.settings.heartbeatInterval);
|
||||
client.send(handshake);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
23
src/main/java/com/rosetta/im/service/Service.java
Normal file
23
src/main/java/com/rosetta/im/service/Service.java
Normal file
@@ -0,0 +1,23 @@
|
||||
package com.rosetta.im.service;
|
||||
|
||||
/**
|
||||
* Базовый класс для всех сервисов. Нужно чтобы унифицировать доступ к репозиториям,
|
||||
* а так же не раздувать логику в executor'ах. Так код в executor'ах будет чище и
|
||||
* проще для понимания. Для атомарных операций с сущностями сервисы не используются, они используются только для
|
||||
* более сложной логики, требующей взаимодействия с несколькими репозиториями или
|
||||
* иной бизнес-логики.
|
||||
* @param <T> тип репозитория
|
||||
*/
|
||||
public abstract class Service<T> {
|
||||
|
||||
private T repository;
|
||||
|
||||
public Service(T repository) {
|
||||
this.repository = repository;
|
||||
}
|
||||
|
||||
public T getRepository() {
|
||||
return repository;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
package com.rosetta.im.service.services;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import com.rosetta.im.database.entity.Device;
|
||||
import com.rosetta.im.database.entity.User;
|
||||
import com.rosetta.im.database.repository.DeviceRepository;
|
||||
import com.rosetta.im.service.Service;
|
||||
|
||||
public class DeviceService extends Service<DeviceRepository> {
|
||||
|
||||
public DeviceService(DeviceRepository repository) {
|
||||
super(repository);
|
||||
}
|
||||
|
||||
/**
|
||||
* Проверяет, верифицировано ли устройство с deviceId для пользователя user
|
||||
* @param deviceId ID устройства
|
||||
* @param user пользователь
|
||||
* @return true если устройство верифицировано, иначе false
|
||||
*/
|
||||
public boolean isDeviceVerifiedByUser(String deviceId, User user) {
|
||||
List<Device> devices = this.getRepository().findAll(user);
|
||||
if(devices.size() == 0) {
|
||||
/**
|
||||
* Если у пользователя нет устройств, значит текущее устройство верифицировано
|
||||
* такого быть не может, это избыточная проверка
|
||||
*/
|
||||
return true;
|
||||
}
|
||||
for(Device device : devices) {
|
||||
if(device.getDeviceId().equals(deviceId)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Считает количество устройств пользователя
|
||||
* @param user пользователь
|
||||
* @return количество устройств
|
||||
*/
|
||||
public long countUserDevices(User user) {
|
||||
return this.getRepository().countByField("publicKey", user.getPublicKey());
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user