diff --git a/build.gradle b/build.gradle index 71ee531..5920432 100644 --- a/build.gradle +++ b/build.gradle @@ -16,9 +16,10 @@ repositories { } dependencies { - implementation 'org.eclipse.jetty:jetty-server:11.0.20' + implementation 'org.eclipse.jetty:jetty-server:11.0.20' // WS сервер implementation 'org.eclipse.jetty:jetty-servlet:11.0.20' implementation 'org.eclipse.jetty.websocket:websocket-jetty-server:11.0.20' + implementation 'org.bouncycastle:bcprov-jdk18on:1.78.1' // шифрование implementation 'com.fasterxml.jackson.core:jackson-databind:2.17.1' // json diff --git a/shine-server-geo/src/main/java/shine.geo/GeoLookupService.java b/shine-server-geo/src/main/java/shine.geo/GeoLookupService.java index 34ef1fd..6aede55 100644 --- a/shine-server-geo/src/main/java/shine.geo/GeoLookupService.java +++ b/shine-server-geo/src/main/java/shine.geo/GeoLookupService.java @@ -11,7 +11,7 @@ import java.net.http.HttpResponse; /** * Сервис для геолокации по IP. - * + *. * Основной метод: * resolveCountryCityOrIp(ip) -> "Country, City" или исходный ip, если не удалось. */ diff --git a/shine-server-geo/src/main/java/shine.geo/GeoLookupTestMain.java b/shine-server-geo/src/main/java/shine.geo/GeoLookupTestMain.java index 230ee63..b447b36 100644 --- a/shine-server-geo/src/main/java/shine.geo/GeoLookupTestMain.java +++ b/shine-server-geo/src/main/java/shine.geo/GeoLookupTestMain.java @@ -2,7 +2,7 @@ package shine.geo; /** * Тестовый запуск геолокации. - * + *. * Логика: * 1) Если в args[0] передан IP — используем его. * 2) Иначе пробуем узнать внешний IP текущей машины. diff --git a/shine-server-net-protocol/build.gradle b/shine-server-net-protocol/build.gradle index 09693cf..e867644 100644 --- a/shine-server-net-protocol/build.gradle +++ b/shine-server-net-protocol/build.gradle @@ -16,6 +16,10 @@ repositories { } dependencies { + implementation 'org.eclipse.jetty:jetty-server:11.0.20' // WS сервер + implementation 'org.eclipse.jetty:jetty-servlet:11.0.20' + implementation 'org.eclipse.jetty.websocket:websocket-jetty-server:11.0.20' + implementation 'com.fasterxml.jackson.core:jackson-databind:2.17.1' // json implementation 'org.slf4j:slf4j-api:2.0.9' diff --git a/shine-server-net-protocol/src/main/java/server/logic/ws_protocol/JSON/ActiveConnectionsRegistry.java b/shine-server-net-protocol/src/main/java/server/logic/ws_protocol/JSON/ActiveConnectionsRegistry.java new file mode 100644 index 0000000..d8885c5 --- /dev/null +++ b/shine-server-net-protocol/src/main/java/server/logic/ws_protocol/JSON/ActiveConnectionsRegistry.java @@ -0,0 +1,123 @@ +package server.logic.ws_protocol.JSON; + +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.CopyOnWriteArraySet; + +/** + * Реестр активных подключений (только авторизованные). + *. + * Позволяет: + * - получить ConnectionContext по sessionId; + * - получить все активные подключения пользователя по loginId; + * - удалить подключение при закрытии WebSocket. + *. + * найти все подключения пользователя: + * var set = ActiveConnectionsRegistry.getInstance().getByLoginId(loginId); + *. + * найти конкретное подключение по sessionId: + * ConnectionContext ctx = ActiveConnectionsRegistry.getInstance().getBySessionId(sessionId); + * Session ws = ctx != null ? ctx.getWsSession() : null; + */ + +public final class ActiveConnectionsRegistry { + + private static final ActiveConnectionsRegistry INSTANCE = new ActiveConnectionsRegistry(); + + public static ActiveConnectionsRegistry getInstance() { + return INSTANCE; + } + + private ActiveConnectionsRegistry() { + // singleton + } + + // sessionId -> ConnectionContext + private final ConcurrentHashMap bySessionId = new ConcurrentHashMap<>(); + + // loginId -> множество ConnectionContext для этого пользователя + private final ConcurrentHashMap> byLoginId = new ConcurrentHashMap<>(); + + /** + * Зарегистрировать авторизованное подключение. + * Ожидается, что в ctx уже выставлены loginId и sessionId. + */ + public void register(ConnectionContext ctx) { + if (ctx == null) return; + + Long sessionId = ctx.getSessionId(); + Long loginId = ctx.getLoginId(); + + if (sessionId == null || loginId == null) { + return; + } + + bySessionId.put(sessionId, ctx); + + byLoginId + .computeIfAbsent(loginId, id -> new CopyOnWriteArraySet<>()) + .add(ctx); + } + + /** + * Удалить подключение по контексту (например, при onClose). + */ + public void remove(ConnectionContext ctx) { + if (ctx == null) return; + + Long sessionId = ctx.getSessionId(); + Long loginId = ctx.getLoginId(); + + if (sessionId != null) { + bySessionId.remove(sessionId); + } + + if (loginId != null) { + Set set = byLoginId.get(loginId); + if (set != null) { + set.remove(ctx); + if (set.isEmpty()) { + byLoginId.remove(loginId); + } + } + } + } + + /** + * Удалить подключение по sessionId. + */ + public void removeBySessionId(long sessionId) { + ConnectionContext ctx = bySessionId.remove(sessionId); + if (ctx != null) { + Long loginId = ctx.getLoginId(); + if (loginId != null) { + Set set = byLoginId.get(loginId); + if (set != null) { + set.remove(ctx); + if (set.isEmpty()) { + byLoginId.remove(loginId); + } + } + } + } + } + + /** + * Получить контекст по sessionId. + */ + public ConnectionContext getBySessionId(long sessionId) { + return bySessionId.get(sessionId); + } + + /** + * Получить все активные подключения пользователя по loginId. + */ + public Set getByLoginId(long loginId) { + Set set = byLoginId.get(loginId); + if (set == null) { + return Set.of(); + } + // CopyOnWriteArraySet безопасно отдавать как есть + return set; + } +} diff --git a/shine-server-net-protocol/src/main/java/server/logic/ws_protocol/JSON/ConnectionContext.java b/shine-server-net-protocol/src/main/java/server/logic/ws_protocol/JSON/ConnectionContext.java index a8507da..e38bb57 100644 --- a/shine-server-net-protocol/src/main/java/server/logic/ws_protocol/JSON/ConnectionContext.java +++ b/shine-server-net-protocol/src/main/java/server/logic/ws_protocol/JSON/ConnectionContext.java @@ -1,5 +1,6 @@ package server.logic.ws_protocol.JSON; +import org.eclipse.jetty.websocket.api.Session; import shine.db.entities.SolanaUser; import shine.db.entities.ActiveSession; @@ -24,6 +25,22 @@ public class ConnectionContext { private int authenticationStatus = AUTH_STATUS_NONE; + /** + * WebSocket-сессия Jetty для данного подключения. + * Нужна, чтобы через ConnectionContext можно было отправлять сообщения клиенту. + */ + private Session wsSession; + + // --- WebSocket Session --- + + public Session getWsSession() { + return wsSession; + } + + public void setWsSession(Session wsSession) { + this.wsSession = wsSession; + } + // --- SolanaUser / ActiveSession --- public SolanaUser getSolanaUser() { @@ -96,6 +113,7 @@ public class ConnectionContext { sessionPwd = null; authenticationStatus = AUTH_STATUS_NONE; + wsSession = null; } @Override diff --git a/shine-server-net-protocol/src/main/java/server/logic/ws_protocol/JSON/JsonHandlerRegistry.java b/shine-server-net-protocol/src/main/java/server/logic/ws_protocol/JSON/JsonHandlerRegistry.java index 7052bdb..214b68d 100644 --- a/shine-server-net-protocol/src/main/java/server/logic/ws_protocol/JSON/JsonHandlerRegistry.java +++ b/shine-server-net-protocol/src/main/java/server/logic/ws_protocol/JSON/JsonHandlerRegistry.java @@ -16,7 +16,7 @@ import java.util.Map; /** * JsonHandlerRegistry — единое место, где руками регистрируются * JSON-операции: op → handler и op → requestClass. - * + *. * Если нужно добавить новый запрос: * 1) создаёшь класс NetXXXRequest / NetXXXResponse, * 2) создаёшь JsonMessageHandler (NetXXXHandler), diff --git a/shine-server-net-protocol/src/main/java/server/logic/ws_protocol/JSON/JsonInboundProcessor.java b/shine-server-net-protocol/src/main/java/server/logic/ws_protocol/JSON/JsonInboundProcessor.java index 3788172..6048325 100644 --- a/shine-server-net-protocol/src/main/java/server/logic/ws_protocol/JSON/JsonInboundProcessor.java +++ b/shine-server-net-protocol/src/main/java/server/logic/ws_protocol/JSON/JsonInboundProcessor.java @@ -17,7 +17,7 @@ import java.util.Map; /** * JsonInboundProcessor — обработка JSON-сообщений. - * + *. * 1) Парсит общий пакет (op, requestId,...). * 2) По op выбирает класс запроса и хэндлер. * 3) Маппит JSON → NetRequest через ObjectMapper. diff --git a/shine-server-net-protocol/src/main/java/server/logic/ws_protocol/JSON/entyties/Auth/NetAuthSessionNewStep2Request.java b/shine-server-net-protocol/src/main/java/server/logic/ws_protocol/JSON/entyties/Auth/NetAuthSessionNewStep2Request.java index 41ef7a5..bf1d9d2 100644 --- a/shine-server-net-protocol/src/main/java/server/logic/ws_protocol/JSON/entyties/Auth/NetAuthSessionNewStep2Request.java +++ b/shine-server-net-protocol/src/main/java/server/logic/ws_protocol/JSON/entyties/Auth/NetAuthSessionNewStep2Request.java @@ -4,7 +4,7 @@ import server.logic.ws_protocol.JSON.entyties.NetRequest; /** * Шаг 2 авторизации: клиент подтверждает владение ключом. - * + *. * JSON: * { * "op": "AuthSessionNewStep2", diff --git a/shine-server-net-protocol/src/main/java/server/logic/ws_protocol/JSON/entyties/Auth/NetAuthSessionNewStep2Response.java b/shine-server-net-protocol/src/main/java/server/logic/ws_protocol/JSON/entyties/Auth/NetAuthSessionNewStep2Response.java index 5cda6a5..2d52b6c 100644 --- a/shine-server-net-protocol/src/main/java/server/logic/ws_protocol/JSON/entyties/Auth/NetAuthSessionNewStep2Response.java +++ b/shine-server-net-protocol/src/main/java/server/logic/ws_protocol/JSON/entyties/Auth/NetAuthSessionNewStep2Response.java @@ -4,7 +4,7 @@ import server.logic.ws_protocol.JSON.entyties.NetResponse; /** * Ответ на AuthSessionNewStep2. - * + *. * Успешный JSON: * { * "op": "AuthSessionNewStep2", diff --git a/shine-server-net-protocol/src/main/java/server/logic/ws_protocol/JSON/entyties/Auth/NetSessionRefreshRequest.java b/shine-server-net-protocol/src/main/java/server/logic/ws_protocol/JSON/entyties/Auth/NetSessionRefreshRequest.java index 81f6d4f..3ed2fa9 100644 --- a/shine-server-net-protocol/src/main/java/server/logic/ws_protocol/JSON/entyties/Auth/NetSessionRefreshRequest.java +++ b/shine-server-net-protocol/src/main/java/server/logic/ws_protocol/JSON/entyties/Auth/NetSessionRefreshRequest.java @@ -4,7 +4,7 @@ import server.logic.ws_protocol.JSON.entyties.NetRequest; /** * Запрос SessionRefresh. - * + *. * JSON (payload): * { * "sessionId": 123, diff --git a/shine-server-net-protocol/src/main/java/server/logic/ws_protocol/JSON/entyties/Auth/NetSessionRefreshResponse.java b/shine-server-net-protocol/src/main/java/server/logic/ws_protocol/JSON/entyties/Auth/NetSessionRefreshResponse.java index 2e45a26..2f8db44 100644 --- a/shine-server-net-protocol/src/main/java/server/logic/ws_protocol/JSON/entyties/Auth/NetSessionRefreshResponse.java +++ b/shine-server-net-protocol/src/main/java/server/logic/ws_protocol/JSON/entyties/Auth/NetSessionRefreshResponse.java @@ -4,7 +4,7 @@ import server.logic.ws_protocol.JSON.entyties.NetResponse; /** * Успешный ответ на SessionRefresh. - * + *. * Дополнительных полей нет, достаточно status=200 и (опционально) пустого payload. */ public class NetSessionRefreshResponse extends NetResponse { diff --git a/shine-server-net-protocol/src/main/java/server/logic/ws_protocol/JSON/entyties/NetEvent.java b/shine-server-net-protocol/src/main/java/server/logic/ws_protocol/JSON/entyties/NetEvent.java index b5d53ca..be9e845 100644 --- a/shine-server-net-protocol/src/main/java/server/logic/ws_protocol/JSON/entyties/NetEvent.java +++ b/shine-server-net-protocol/src/main/java/server/logic/ws_protocol/JSON/entyties/NetEvent.java @@ -3,7 +3,7 @@ package server.logic.ws_protocol.JSON.entyties; /** * Базовый класс для всех событий (event). * Общие поля: op и payload. - * + *. * Формат JSON (event): * { * "op": "...", diff --git a/shine-server-net-protocol/src/main/java/server/logic/ws_protocol/JSON/entyties/NetExceptionResponse.java b/shine-server-net-protocol/src/main/java/server/logic/ws_protocol/JSON/entyties/NetExceptionResponse.java index 88c0635..1703c49 100644 --- a/shine-server-net-protocol/src/main/java/server/logic/ws_protocol/JSON/entyties/NetExceptionResponse.java +++ b/shine-server-net-protocol/src/main/java/server/logic/ws_protocol/JSON/entyties/NetExceptionResponse.java @@ -2,7 +2,7 @@ package server.logic.ws_protocol.JSON.entyties; /** * Ответ с ошибкой (любой отказ). - * + *. * В payload будет: * { * "code": "...", diff --git a/shine-server-net-protocol/src/main/java/server/logic/ws_protocol/JSON/entyties/NetRequest.java b/shine-server-net-protocol/src/main/java/server/logic/ws_protocol/JSON/entyties/NetRequest.java index 432c4e5..7a7c836 100644 --- a/shine-server-net-protocol/src/main/java/server/logic/ws_protocol/JSON/entyties/NetRequest.java +++ b/shine-server-net-protocol/src/main/java/server/logic/ws_protocol/JSON/entyties/NetRequest.java @@ -2,9 +2,9 @@ package server.logic.ws_protocol.JSON.entyties; /** * Базовый класс для всех запросов (client → server). - * + *. * Наследуется от NetEvent и добавляет requestId. - * + *. * Формат JSON (request): * { * "op": "...", diff --git a/shine-server-net-protocol/src/main/java/server/logic/ws_protocol/JSON/entyties/NetResponse.java b/shine-server-net-protocol/src/main/java/server/logic/ws_protocol/JSON/entyties/NetResponse.java index 0120e8d..e6852ff 100644 --- a/shine-server-net-protocol/src/main/java/server/logic/ws_protocol/JSON/entyties/NetResponse.java +++ b/shine-server-net-protocol/src/main/java/server/logic/ws_protocol/JSON/entyties/NetResponse.java @@ -2,9 +2,9 @@ package server.logic.ws_protocol.JSON.entyties; /** * Базовый класс для всех ответов (server → client). - * + *. * Наследуется от NetRequest и добавляет status. - * + *. * Формат JSON (response): * { * "op": "...", diff --git a/shine-server-net-protocol/src/main/java/server/logic/ws_protocol/JSON/entyties/tempToTest/NetAddUserRequest.java b/shine-server-net-protocol/src/main/java/server/logic/ws_protocol/JSON/entyties/tempToTest/NetAddUserRequest.java index 1bd25c2..b6d2305 100644 --- a/shine-server-net-protocol/src/main/java/server/logic/ws_protocol/JSON/entyties/tempToTest/NetAddUserRequest.java +++ b/shine-server-net-protocol/src/main/java/server/logic/ws_protocol/JSON/entyties/tempToTest/NetAddUserRequest.java @@ -4,7 +4,7 @@ import server.logic.ws_protocol.JSON.entyties.NetRequest; /** * Запрос AddUser. - * + *. * Ожидаемый JSON: * { * "op": "AddUser", diff --git a/shine-server-net-protocol/src/main/java/server/logic/ws_protocol/JSON/handlers/auth/NetAuthSessionNewStep2Handler.java b/shine-server-net-protocol/src/main/java/server/logic/ws_protocol/JSON/handlers/auth/NetAuthSessionNewStep2Handler.java index 7430bba..46917f1 100644 --- a/shine-server-net-protocol/src/main/java/server/logic/ws_protocol/JSON/handlers/auth/NetAuthSessionNewStep2Handler.java +++ b/shine-server-net-protocol/src/main/java/server/logic/ws_protocol/JSON/handlers/auth/NetAuthSessionNewStep2Handler.java @@ -2,6 +2,7 @@ package server.logic.ws_protocol.JSON.handlers.auth; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import server.logic.ws_protocol.JSON.ActiveConnectionsRegistry; import server.logic.ws_protocol.JSON.ConnectionContext; import server.logic.ws_protocol.JSON.entyties.NetRequest; import server.logic.ws_protocol.JSON.entyties.NetResponse; @@ -22,7 +23,7 @@ import java.util.concurrent.ThreadLocalRandom; /** * Шаг 2 авторизации: проверка подписи и создание сессии. - * + *. * Клиент присылает: * - loginId * - sigNum (0 или 1) @@ -165,6 +166,9 @@ public class NetAuthSessionNewStep2Handler implements JsonMessageHandler { ctx.setSessionId(sessionId); ctx.setAuthenticationStatus(ConnectionContext.AUTH_STATUS_USER); + // Регистрируем это подключение в глобальном реестре активных соединений + ActiveConnectionsRegistry.getInstance().register(ctx); + // --- формируем ответ --- NetAuthSessionNewStep2Response resp = new NetAuthSessionNewStep2Response(); resp.setOp(req.getOp()); diff --git a/shine-server-net-protocol/src/main/java/server/logic/ws_protocol/JSON/handlers/auth/NetSessionRefreshHandler.java b/shine-server-net-protocol/src/main/java/server/logic/ws_protocol/JSON/handlers/auth/NetSessionRefreshHandler.java index e490bb4..57a34fd 100644 --- a/shine-server-net-protocol/src/main/java/server/logic/ws_protocol/JSON/handlers/auth/NetSessionRefreshHandler.java +++ b/shine-server-net-protocol/src/main/java/server/logic/ws_protocol/JSON/handlers/auth/NetSessionRefreshHandler.java @@ -2,6 +2,7 @@ package server.logic.ws_protocol.JSON.handlers.auth; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import server.logic.ws_protocol.JSON.ActiveConnectionsRegistry; import server.logic.ws_protocol.JSON.ConnectionContext; import server.logic.ws_protocol.JSON.entyties.NetRequest; import server.logic.ws_protocol.JSON.entyties.NetResponse; @@ -108,6 +109,9 @@ public class NetSessionRefreshHandler implements JsonMessageHandler { ctx.setSessionId(sessionId); ctx.setSessionPwd(sessionPwd); ctx.setAuthenticationStatus(ConnectionContext.AUTH_STATUS_USER); + + // Регистрируем это подключение в глобальном реестре активных соединений + ActiveConnectionsRegistry.getInstance().register(ctx); } // И возвращаем OK без доп. полей (payload будет {}). diff --git a/shine-server-net-protocol/src/main/java/server/logic/ws_protocol/WireCodes.java b/shine-server-net-protocol/src/main/java/server/logic/ws_protocol/WireCodes.java index dd743e4..782f736 100644 --- a/shine-server-net-protocol/src/main/java/server/logic/ws_protocol/WireCodes.java +++ b/shine-server-net-protocol/src/main/java/server/logic/ws_protocol/WireCodes.java @@ -2,11 +2,11 @@ package server.logic.ws_protocol; /** * WireCodes — константы бинарного протокола поверх WebSocket. - * + *. * Формат входящего сообщения: * [4] int opCode (big-endian) * [*] payload - * + *. * Ответ сервера: * ровно [4] int statusCode (big-endian) */ diff --git a/src/main/docs/Формат блоков/структура блока/Запись в блокчейн.txt b/src/main/docs/Формат блоков/структура блока/Запись в блокчейн.txt index 0457c92..c61466b 100644 --- a/src/main/docs/Формат блоков/структура блока/Запись в блокчейн.txt +++ b/src/main/docs/Формат блоков/структура блока/Запись в блокчейн.txt @@ -1,20 +1,20 @@ * ============================================================================ * BchBlockEntry — универсальная запись блокчейна SHiNE (.bch) * ============================================================================ - * + *. * 🧩 Формат файла .bch: * Каждый блок хранится последовательно, без промежутков. * Один блок = «заголовок» (RAW) + подпись (64) + хэш (32). - * + *. * FULL = RAW + signature(64) + hash(32) - * + *. * --------------------------------------------------------------------------- * 🔹 Структура RAW-части блока (без подписи и хэша) * --------------------------------------------------------------------------- * Размеры и порядок строго фиксированы (BigEndian). - * + *. * Порядок байтов (сверху вниз, смещения от начала RAW): - * + *. * ┌────────────────────────────┬────────┬───────────────────────────────┐ * │ Поле │ Размер │ Описание │ * ├────────────────────────────┼────────┼───────────────────────────────┤ @@ -30,22 +30,22 @@ * │ recordTypeVersion │ 2 байта│ версия структуры данного типа │ * │ body │ M байт │ бинарное тело записи │ * └────────────────────────────┴────────┴───────────────────────────────┘ - * + *. * ⇒ RAW_HEADER_SIZE = 4 + 4 + 8 + 2 + 2 = 20 байт. * ⇒ recordSize = RAW_HEADER_SIZE + body.length - * + *. * --------------------------------------------------------------------------- * 🔹 Структура FULL-блока * --------------------------------------------------------------------------- - * + *. * ┌────────────────────────────┬─────────┬──────────────────────────────┐ * │ RAW │ M+20 │ тело блока без подписи │ * │ signature64 │ 64 │ подпись Ed25519(preimage) │ * │ hash32 │ 32 │ SHA-256(preimage) │ * └────────────────────────────┴─────────┴──────────────────────────────┘ - * + *. * ⇒ Общая длина FULL = recordSize + 96 байт. - * + *. * --------------------------------------------------------------------------- * 🔹 Канонический preimage для подписи/хэша * --------------------------------------------------------------------------- @@ -58,9 +58,9 @@ * можно номер блока? * prevHash32(32B) + * rawBytes (M+20B) - * + *. * hash32 = SHA-256(preimage) * signature64= Ed25519.sign(preimage, privateKey) - * + *. * Проверка осуществляется через {@link utils.crypto.BchCryptoVerifier}. diff --git a/src/main/java/server/logic/ws_protocol/binary/handlers/AddBlockHandler.java b/src/main/java/server/logic/ws_protocol/binary/handlers/AddBlockHandler.java index bb85d6c..87b2c20 100644 --- a/src/main/java/server/logic/ws_protocol/binary/handlers/AddBlockHandler.java +++ b/src/main/java/server/logic/ws_protocol/binary/handlers/AddBlockHandler.java @@ -20,7 +20,7 @@ import java.util.Arrays; * AddBlockHandler — обработчик команды "добавить блок" (ADD_BLOCK) * --------------------------------------------------------------- * Принимает бинарное сообщение от клиента и добавляет новый блок в цепочку. - * + *. * Формат входного сообщения (msg): * [0..3] — 4 байта: код операции (WireCodes.ADD_BLOCK) * [4..11] — 8 байт: blockchainId (уникальный идентификатор цепочки) @@ -33,13 +33,13 @@ import java.util.Arrays; * ├── M байт body (содержимое блока) * ├── 64 байта signature (Ed25519) * └── 32 байта hash (SHA-256) - * + *. * --------------------------------------------------------------- * Алгоритм работы: - * + *. * 1️⃣ Распаковать BchBlockEntry из msg (т.е. выделить тело блока и подписи). * 2️⃣ Найти описание цепочки (BchInfoEntry) по blockchainId. - * + *. * ─ Если описания нет (цепочка ещё не существует): * • принимаем только блок типа 0 (HeaderBody) и номера 0; * • парсим его, создаём новый BchInfoEntry на основе данных заголовка; @@ -48,16 +48,16 @@ import java.util.Arrays; * • сохраняем блок и создаём новый blockchain-файл; * • добавляем цепочку в менеджер BchInfoManager. * (💡 временное решение: создание цепочки допустимо только через HeaderBody) - * + *. * ─ Если цепочка уже существует: * • проверяем, что номер блока равен (lastBlockNumber + 1); * • проверяем подпись и хэш; * • проверяем тело блока (check); * • добавляем блок в файл цепочки; * • обновляем состояние BchInfoEntry (номер, хэш, размер). - * + *. * 3️⃣ Если все проверки пройдены — возвращаем статус OK. - * + *. * Таким образом, единственное различие между первым блоком и последующими — * момент инициализации описания цепочки (BchInfoEntry). * Всё остальное (валидация, подпись, добавление, обновление) выполняется одинаково. diff --git a/src/main/java/server/ws/BlockchainWsEndpoint.java b/src/main/java/server/ws/BlockchainWsEndpoint.java index 2d5ae65..affdf19 100644 --- a/src/main/java/server/ws/BlockchainWsEndpoint.java +++ b/src/main/java/server/ws/BlockchainWsEndpoint.java @@ -6,6 +6,7 @@ import org.eclipse.jetty.websocket.api.annotations.*; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import server.logic.InboundMessageProcessor; +import server.logic.ws_protocol.JSON.ActiveConnectionsRegistry; import server.logic.ws_protocol.JSON.ConnectionContext; import server.logic.ws_protocol.JSON.JsonInboundProcessor; @@ -24,6 +25,8 @@ public class BlockchainWsEndpoint { @OnWebSocketConnect public void onConnect(Session session) { this.session = session; + // Привязываем WebSocket-сессию к ConnectionContext + connectionContext.setWsSession(session); log.info("WS connected: {}", session.getRemoteAddress()); } @@ -77,6 +80,8 @@ public class BlockchainWsEndpoint { @OnWebSocketClose public void onClose(int statusCode, String reason) { log.info("WS closed: {} {}", statusCode, reason); + // Удаляем это подключение из реестра активных соединений + ActiveConnectionsRegistry.getInstance().remove(connectionContext); // На всякий случай очищаем контекст connectionContext.reset(); }