Система вложенных тегов (ECI), связь с базой данных
This commit is contained in:
@@ -2,27 +2,15 @@ version: '3.8'
|
||||
|
||||
services:
|
||||
db:
|
||||
image: postgres:16-alpine
|
||||
image: postgres:latest
|
||||
environment:
|
||||
POSTGRES_DB: rosetta_db
|
||||
POSTGRES_USER: rosetta_user
|
||||
POSTGRES_PASSWORD: rosetta_password
|
||||
POSTGRES_DB: your_database
|
||||
POSTGRES_USER: your_user
|
||||
POSTGRES_PASSWORD: your_password
|
||||
ports:
|
||||
- "5432:5432"
|
||||
volumes:
|
||||
- postgres_data:/var/lib/postgresql/data
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "pg_isready -U rosetta_user"]
|
||||
interval: 10s
|
||||
timeout: 5s
|
||||
retries: 5
|
||||
|
||||
adminer:
|
||||
image: adminer
|
||||
ports:
|
||||
- "8080:8080"
|
||||
depends_on:
|
||||
- db
|
||||
|
||||
volumes:
|
||||
postgres_data:
|
||||
- "8080:8080"
|
||||
7
src/main/java/com/rosetta/im/Configuration.java
Normal file
7
src/main/java/com/rosetta/im/Configuration.java
Normal file
@@ -0,0 +1,7 @@
|
||||
package com.rosetta.im;
|
||||
|
||||
public class Configuration {
|
||||
|
||||
public static final int PROTOCOL_VERSION = 1;
|
||||
|
||||
}
|
||||
26
src/main/java/com/rosetta/im/Failures.java
Normal file
26
src/main/java/com/rosetta/im/Failures.java
Normal file
@@ -0,0 +1,26 @@
|
||||
package com.rosetta.im;
|
||||
|
||||
import com.rosetta.im.protocol.BaseFailures;
|
||||
|
||||
public enum Failures implements BaseFailures {
|
||||
/**
|
||||
* Код ошибки, указывающий на неизвестную ошибку.
|
||||
*/
|
||||
UNSUPPORTED_PROTOCOL(3008);
|
||||
|
||||
private final int code;
|
||||
|
||||
Failures(int code) {
|
||||
this.code = code;
|
||||
}
|
||||
|
||||
/**
|
||||
* Получает код ошибки.
|
||||
* @return Код ошибки.
|
||||
*/
|
||||
public int getCode() {
|
||||
return code;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -22,7 +22,7 @@ public class Main {
|
||||
/**
|
||||
* Запуск сервера на порту 8881
|
||||
*/
|
||||
Server server = new Server(settings, manager);
|
||||
Server server = new Server(settings, manager, Configuration.class);
|
||||
server.start();
|
||||
}
|
||||
}
|
||||
68
src/main/java/com/rosetta/im/client/tags/Authentificate.java
Normal file
68
src/main/java/com/rosetta/im/client/tags/Authentificate.java
Normal file
@@ -0,0 +1,68 @@
|
||||
package com.rosetta.im.client.tags;
|
||||
|
||||
import com.rosetta.im.packet.enums.HandshakeStage;
|
||||
import com.rosetta.im.protocol.ECITag;
|
||||
|
||||
/**
|
||||
* Это вложенный обьект для клиента, содержащий информацию об аутентификации.
|
||||
*/
|
||||
public class Authentificate extends ECITag {
|
||||
|
||||
public Device device;
|
||||
public String publicKey;
|
||||
public String privateKey;
|
||||
public HandshakeStage handshakeStage;
|
||||
|
||||
public Authentificate() {
|
||||
super("authentificate");
|
||||
}
|
||||
|
||||
public Authentificate(Device device, String publicKey, String privateKey, HandshakeStage handshakeStage) {
|
||||
super("authentificate");
|
||||
this.device = device;
|
||||
this.publicKey = publicKey;
|
||||
this.privateKey = privateKey;
|
||||
this.handshakeStage = handshakeStage;
|
||||
}
|
||||
|
||||
public Device getDevice() {
|
||||
return device;
|
||||
}
|
||||
|
||||
public String getPublicKey() {
|
||||
return publicKey;
|
||||
}
|
||||
|
||||
public String getPrivateKey() {
|
||||
return privateKey;
|
||||
}
|
||||
|
||||
public HandshakeStage getHandshakeStage() {
|
||||
return handshakeStage;
|
||||
}
|
||||
|
||||
public void setDevice(Device device) {
|
||||
this.device = device;
|
||||
}
|
||||
|
||||
public void setPublicKey(String publicKey) {
|
||||
this.publicKey = publicKey;
|
||||
}
|
||||
|
||||
public void setPrivateKey(String privateKey) {
|
||||
this.privateKey = privateKey;
|
||||
}
|
||||
|
||||
public void setHandshakeStage(HandshakeStage handshakeStage) {
|
||||
this.handshakeStage = handshakeStage;
|
||||
}
|
||||
|
||||
/**
|
||||
* Проверяет, прошел ли клиент аутентификацию. В том числе подтвердил ли устройство.
|
||||
* @return true если аутентификация пройдена, иначе false.
|
||||
*/
|
||||
public boolean hasAuthorized() {
|
||||
return this.handshakeStage == HandshakeStage.COMPLETED;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
package com.rosetta.im.device;
|
||||
package com.rosetta.im.client.tags;
|
||||
|
||||
public class Device {
|
||||
|
||||
@@ -3,6 +3,7 @@ package com.rosetta.im.database;
|
||||
import org.hibernate.Session;
|
||||
import org.hibernate.Transaction;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
|
||||
public class DatabaseManager {
|
||||
@@ -61,6 +62,28 @@ public class DatabaseManager {
|
||||
}
|
||||
}
|
||||
|
||||
public static <T> T findBy(Class<T> entity, HashMap<String, Object> params) {
|
||||
Session session = HibernateUtil.openSession();
|
||||
try {
|
||||
StringBuilder queryString = new StringBuilder("FROM " + entity.getSimpleName() + " WHERE ");
|
||||
int index = 0;
|
||||
for (String key : params.keySet()) {
|
||||
if (index > 0) {
|
||||
queryString.append(" AND ");
|
||||
}
|
||||
queryString.append(key).append(" = :").append(key);
|
||||
index++;
|
||||
}
|
||||
var query = session.createQuery(queryString.toString(), entity);
|
||||
for (var entry : params.entrySet()) {
|
||||
query.setParameter(entry.getKey(), entry.getValue());
|
||||
}
|
||||
return query.uniqueResult();
|
||||
} finally {
|
||||
session.close();
|
||||
}
|
||||
}
|
||||
|
||||
public static <T> void delete(T entity) {
|
||||
Session session = HibernateUtil.openSession();
|
||||
Transaction transaction = null;
|
||||
|
||||
@@ -43,9 +43,6 @@ public class User extends BaseEntity {
|
||||
@Column(name = "publicKey", nullable = false, unique = true)
|
||||
private String publicKey;
|
||||
|
||||
@Column(name = "timestamp", nullable = false)
|
||||
private long timestamp;
|
||||
|
||||
@Column(name = "createdTime", nullable = false, updatable = false)
|
||||
private Date createdTime;
|
||||
|
||||
@@ -104,14 +101,6 @@ public class User extends BaseEntity {
|
||||
this.publicKey = publicKey;
|
||||
}
|
||||
|
||||
public long getTimestamp() {
|
||||
return timestamp;
|
||||
}
|
||||
|
||||
public void setTimestamp(long timestamp) {
|
||||
this.timestamp = timestamp;
|
||||
}
|
||||
|
||||
public Date getCreatedTime() {
|
||||
return createdTime;
|
||||
}
|
||||
|
||||
@@ -1,18 +1,90 @@
|
||||
package com.rosetta.im.executors;
|
||||
|
||||
import java.util.HashMap;
|
||||
|
||||
import com.rosetta.im.Configuration;
|
||||
import com.rosetta.im.Failures;
|
||||
import com.rosetta.im.client.tags.Authentificate;
|
||||
import com.rosetta.im.client.tags.Device;
|
||||
import com.rosetta.im.database.DatabaseManager;
|
||||
import com.rosetta.im.database.entity.User;
|
||||
import com.rosetta.im.packet.Packet0Handshake;
|
||||
import com.rosetta.im.packet.enums.HandshakeStage;
|
||||
import com.rosetta.im.protocol.Client;
|
||||
import com.rosetta.im.protocol.packet.Packet;
|
||||
import com.rosetta.im.protocol.packet.PacketExecutor;
|
||||
|
||||
public class Executor0Handshake implements PacketExecutor {
|
||||
public class Executor0Handshake extends PacketExecutor {
|
||||
|
||||
@Override
|
||||
public void onPacketReceived(Packet packet, Client client) {
|
||||
Packet0Handshake handshake = (Packet0Handshake) packet;
|
||||
String publicKey = handshake.getPublicKey();
|
||||
String privateKey = handshake.getPrivateKey();
|
||||
int b = 0;
|
||||
Device device = handshake.getDevice();
|
||||
int protocolVersion = handshake.getProtocolVersion();
|
||||
|
||||
/**
|
||||
* Получаем информацию об аутентификации клиента
|
||||
* используя возможности ECI тэгов.
|
||||
*/
|
||||
Authentificate authentificate = client.getTag(new Authentificate());
|
||||
if(authentificate != null && authentificate.hasAuthorized()) {
|
||||
/**
|
||||
* Клиент уже авторизован, повторный хэндшейк не допускается
|
||||
*/
|
||||
return;
|
||||
}
|
||||
|
||||
/**
|
||||
* Проверяем корректность версии протокола
|
||||
*/
|
||||
if(protocolVersion != Configuration.PROTOCOL_VERSION) {
|
||||
client.disconnect(Failures.UNSUPPORTED_PROTOCOL);
|
||||
return;
|
||||
}
|
||||
|
||||
/**
|
||||
* Проверяем есть ли такой пользователь
|
||||
*/
|
||||
User user = DatabaseManager.findBy(User.class, new HashMap<>() {{
|
||||
put("publicKey", publicKey);
|
||||
}});
|
||||
|
||||
if(user == null) {
|
||||
/**
|
||||
* Пользователь не найден, создаем нового
|
||||
*/
|
||||
user = new User();
|
||||
user.setPrivateKey(privateKey);
|
||||
user.setPublicKey(publicKey);
|
||||
user.setUsername("");
|
||||
user.setTitle(publicKey.substring(0, 7));
|
||||
/**
|
||||
* Новый пользователь не верифицирован
|
||||
*/
|
||||
user.setVerified(0);
|
||||
|
||||
DatabaseManager.save(user);
|
||||
|
||||
Authentificate eciTag = new Authentificate(device, publicKey, privateKey, HandshakeStage.COMPLETED);
|
||||
client.addTag(eciTag);
|
||||
|
||||
|
||||
System.out.println("New user created: " + user.getId());
|
||||
|
||||
Authentificate authTag = client.getTag(new Authentificate());
|
||||
|
||||
System.out.println("eciTag" + authTag);
|
||||
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
package com.rosetta.im.packet;
|
||||
|
||||
import com.rosetta.im.device.Device;
|
||||
import com.rosetta.im.client.tags.Device;
|
||||
import com.rosetta.im.packet.enums.HandshakeStage;
|
||||
import com.rosetta.im.protocol.Stream;
|
||||
import com.rosetta.im.protocol.packet.Packet;
|
||||
|
||||
9
src/main/java/com/rosetta/im/protocol/BaseFailures.java
Normal file
9
src/main/java/com/rosetta/im/protocol/BaseFailures.java
Normal file
@@ -0,0 +1,9 @@
|
||||
package com.rosetta.im.protocol;
|
||||
|
||||
public interface BaseFailures {
|
||||
/**
|
||||
* Получает код ошибки.
|
||||
* @return
|
||||
*/
|
||||
int getCode();
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
package com.rosetta.im.protocol;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
import org.java_websocket.WebSocket;
|
||||
|
||||
@@ -14,7 +14,10 @@ public class Client {
|
||||
|
||||
public WebSocket socket;
|
||||
public String clientId;
|
||||
public Map<String, Object> clientData;
|
||||
/**
|
||||
* Любые данные, связанные с клиентом.
|
||||
*/
|
||||
public Set<ECITag> eciTags;
|
||||
/**
|
||||
* Интервал отправки heartbeat пакетов в миллисекундах.
|
||||
*/
|
||||
@@ -33,7 +36,7 @@ public class Client {
|
||||
public Client(WebSocket socket, long heartbeatInterval) {
|
||||
this.socket = socket;
|
||||
this.clientId = StringUtil.randomString(32);
|
||||
this.clientData = new HashMap<>();
|
||||
this.eciTags = new HashSet<ECITag>();
|
||||
this.heartbeatInterval = heartbeatInterval;
|
||||
this.lastHeartbeatTime = System.currentTimeMillis();
|
||||
}
|
||||
@@ -68,8 +71,8 @@ public class Client {
|
||||
* Получает данные, связанные с клиентом.
|
||||
* @return Данные клиента.
|
||||
*/
|
||||
public Map<String, Object> getClientData() {
|
||||
return clientData;
|
||||
public Set<ECITag> getEciTags() {
|
||||
return this.eciTags;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -85,16 +88,16 @@ public class Client {
|
||||
* @param key Ключ данных.
|
||||
* @param value Значение данных.
|
||||
*/
|
||||
public void setData(String key, Object value) {
|
||||
this.clientData.put(key, value);
|
||||
public <T extends ECITag> void addTag(T eciTag) {
|
||||
this.eciTags.add(eciTag);
|
||||
}
|
||||
|
||||
/**
|
||||
* Устанавливает данные клиента.
|
||||
* @param data Данные клиента.
|
||||
*/
|
||||
public void setData(Map<String, Object> data) {
|
||||
this.clientData = data;
|
||||
public void setTags(Set<ECITag> eciTags) {
|
||||
this.eciTags = eciTags;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -102,8 +105,27 @@ public class Client {
|
||||
* @param key Ключ данных.
|
||||
* @return Значение данных.
|
||||
*/
|
||||
public Object getData(String key) {
|
||||
return this.clientData.get(key);
|
||||
public <T extends ECITag> T getTag(ECITag eciTag) {
|
||||
for (ECITag tag : this.eciTags) {
|
||||
if (tag.getKey().equals(eciTag.getKey())) {
|
||||
return (T) tag;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Проверяет наличие данных клиента по указанному ключу.
|
||||
* @param key Ключ данных.
|
||||
* @return true если данные существуют, иначе false.
|
||||
*/
|
||||
public boolean hasTag(ECITag eciTag) {
|
||||
for (ECITag tag : this.eciTags) {
|
||||
if (tag.getKey().equals(eciTag.getKey())) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -125,7 +147,7 @@ public class Client {
|
||||
* Отключает клиента с указанным кодом отказа.
|
||||
* @param code Код отказа.
|
||||
*/
|
||||
public void disconnect(Failures code) {
|
||||
public void disconnect(BaseFailures code) {
|
||||
this.disconnect(code.getCode());
|
||||
}
|
||||
|
||||
@@ -133,7 +155,7 @@ public class Client {
|
||||
* Отключает клиента с неизвестной причиной.
|
||||
*/
|
||||
public void disconnect() {
|
||||
this.disconnect(Failures.UNKNOWN_FAILURE);
|
||||
this.disconnect(ServerFailures.UNKNOWN_FAILURE);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
31
src/main/java/com/rosetta/im/protocol/ECITag.java
Normal file
31
src/main/java/com/rosetta/im/protocol/ECITag.java
Normal file
@@ -0,0 +1,31 @@
|
||||
package com.rosetta.im.protocol;
|
||||
|
||||
/**
|
||||
* Embedded Client Information Tag.
|
||||
*
|
||||
* Используется для хранения дополнительной информации о клиенте.
|
||||
*/
|
||||
public abstract class ECITag {
|
||||
|
||||
/**
|
||||
* Ключ тега.
|
||||
*/
|
||||
public String key;
|
||||
|
||||
/**
|
||||
* Создает новый тег с указанным ключом и значением.
|
||||
* @param key Ключ тега.
|
||||
*/
|
||||
public ECITag(String key) {
|
||||
this.key = key;
|
||||
}
|
||||
|
||||
/**
|
||||
* Получает ключ тега.
|
||||
* @return Ключ тега.
|
||||
*/
|
||||
public String getKey() {
|
||||
return this.key;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -19,11 +19,13 @@ public class Server extends WebSocketServer {
|
||||
private PacketManager packetManager;
|
||||
private Settings settings;
|
||||
private ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1);
|
||||
private Object attachment;
|
||||
|
||||
public Server(Settings settings, PacketManager packetManager) {
|
||||
public Server(Settings settings, PacketManager packetManager, Object attachment) {
|
||||
super(new InetSocketAddress(settings.port));
|
||||
this.settings = settings;
|
||||
this.packetManager = packetManager;
|
||||
this.attachment = attachment;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -62,14 +64,14 @@ public class Server extends WebSocketServer {
|
||||
/**
|
||||
* Если пакет не поддерживается, отключаем клиента с соответствующим кодом ошибки.
|
||||
*/
|
||||
client.disconnect(Failures.UNSUPPORTED_PACKET);
|
||||
client.disconnect(ServerFailures.UNSUPPORTED_PACKET);
|
||||
return;
|
||||
}
|
||||
if(!this.packetManager.hasExecutorDelegated(packetId)){
|
||||
/**
|
||||
* Если для пакета не назначен обработчик, отключаем клиента с соответствующим кодом ошибки.
|
||||
*/
|
||||
client.disconnect(Failures.UNSUPPORTED_PACKET);
|
||||
client.disconnect(ServerFailures.UNSUPPORTED_PACKET);
|
||||
return;
|
||||
}
|
||||
Class<? extends Packet> packetClass = this.packetManager.getPacketClass(packetId);
|
||||
@@ -86,6 +88,8 @@ public class Server extends WebSocketServer {
|
||||
*/
|
||||
Class<? extends PacketExecutor> executorClass = this.packetManager.getExecutors().get(packetId);
|
||||
PacketExecutor executor = executorClass.getConstructor().newInstance();
|
||||
executor.settings = this.settings;
|
||||
executor.attachment = this.attachment;
|
||||
executor.onPacketReceived(packet, client);
|
||||
} catch (Exception e) {
|
||||
System.out.println("Error while processing packet " + packetClass.getName());
|
||||
@@ -124,7 +128,7 @@ public class Server extends WebSocketServer {
|
||||
for(WebSocket socket : this.getConnections()) {
|
||||
Client client = socket.getAttachment();
|
||||
if(!client.isAlive()) {
|
||||
client.disconnect(Failures.INACTIVITY_TIMEOUT);
|
||||
client.disconnect(ServerFailures.INACTIVITY_TIMEOUT);
|
||||
}
|
||||
}
|
||||
}, this.settings.heartbeatInterval, this.settings.heartbeatInterval, TimeUnit.MILLISECONDS);
|
||||
|
||||
@@ -3,7 +3,7 @@ package com.rosetta.im.protocol;
|
||||
/**
|
||||
* Перечисление кодов ошибок, используемых в протоколе.
|
||||
*/
|
||||
public enum Failures {
|
||||
public enum ServerFailures implements BaseFailures {
|
||||
/**
|
||||
* Код ошибки, указывающий на несоответствие данных.
|
||||
*/
|
||||
@@ -40,7 +40,7 @@ public enum Failures {
|
||||
|
||||
private final int code;
|
||||
|
||||
Failures(int code) {
|
||||
ServerFailures(int code) {
|
||||
this.code = code;
|
||||
}
|
||||
|
||||
@@ -1,7 +1,35 @@
|
||||
package com.rosetta.im.protocol.packet;
|
||||
|
||||
import com.rosetta.im.protocol.Client;
|
||||
import com.rosetta.im.protocol.Settings;
|
||||
|
||||
public interface PacketExecutor {
|
||||
public void onPacketReceived(Packet packet, Client client);
|
||||
/**
|
||||
* Базовый класс для обработчиков пакетов.
|
||||
*/
|
||||
public abstract class PacketExecutor {
|
||||
public Settings settings;
|
||||
public Object attachment;
|
||||
|
||||
/**
|
||||
* Настройки сервера.
|
||||
* @return
|
||||
*/
|
||||
public Settings getSettings() {
|
||||
return settings;
|
||||
}
|
||||
|
||||
/**
|
||||
* Вложенный обьект, который был передан при создании сервера.
|
||||
* @return вложенный обьект
|
||||
*/
|
||||
public Object getAttachment() {
|
||||
return attachment;
|
||||
}
|
||||
|
||||
/**
|
||||
* Вызывается при получении пакета от клиента.
|
||||
* @param packet Пакет, полученный от клиента.
|
||||
* @param client Клиент, отправивший пакет.
|
||||
*/
|
||||
public abstract void onPacketReceived(Packet packet, Client client);
|
||||
}
|
||||
|
||||
@@ -3,10 +3,10 @@
|
||||
<hibernate-configuration>
|
||||
<session-factory>
|
||||
<property name="hibernate.connection.driver_class">org.postgresql.Driver</property>
|
||||
<property name="hibernate.connection.url">jdbc:postgresql://localhost:5432/rosetta_db</property>
|
||||
<property name="hibernate.connection.username">rosetta_user</property>
|
||||
<property name="hibernate.connection.password">rosetta_password</property>
|
||||
<property name="hibernate.dialect">org.hibernate.dialect.PostgreSQLDialect</property>
|
||||
<property name="hibernate.connection.url">jdbc:postgresql://localhost:5432/your_database</property>
|
||||
<property name="hibernate.connection.username">your_user</property>
|
||||
<property name="hibernate.connection.password">your_password</property>
|
||||
|
||||
<property name="hibernate.hbm2ddl.auto">update</property>
|
||||
<property name="hibernate.show_sql">true</property>
|
||||
<property name="hibernate.format_sql">true</property>
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Reference in New Issue
Block a user