обновляю сетевые хэндлеры

This commit is contained in:
AidarKC 2025-12-04 13:34:04 +03:00
parent 6276f3868b
commit 5d8dd86c96
7 changed files with 240 additions and 184 deletions

View File

@ -5,7 +5,7 @@ import server.logic.ws_protocol.JSON.entyties.Auth.NetAuthSessionNewStep1Request
import server.logic.ws_protocol.JSON.entyties.Auth.NetSessionRefreshRequest; import server.logic.ws_protocol.JSON.entyties.Auth.NetSessionRefreshRequest;
import server.logic.ws_protocol.JSON.handlers.*; import server.logic.ws_protocol.JSON.handlers.*;
import server.logic.ws_protocol.JSON.entyties.tempToTest.NetAddUserRequest; import server.logic.ws_protocol.JSON.entyties.tempToTest.NetAddUserRequest;
import server.logic.ws_protocol.JSON.handlers.auth.NetAddUserHandler; import server.logic.ws_protocol.JSON.handlers.tempToTest.NetAddUserHandler;
import server.logic.ws_protocol.JSON.handlers.auth.NetAuthSessionNewStep1Handler; import server.logic.ws_protocol.JSON.handlers.auth.NetAuthSessionNewStep1Handler;
import server.logic.ws_protocol.JSON.handlers.auth.NetSessionRefreshHandler; import server.logic.ws_protocol.JSON.handlers.auth.NetSessionRefreshHandler;

View File

@ -55,10 +55,10 @@ public final class JsonInboundProcessor {
"EMPTY_JSON", "Пустое JSON-сообщение"); "EMPTY_JSON", "Пустое JSON-сообщение");
} }
// 1. Парсим общий пакет как дерево // 1. Парсим общий пакет
JsonNode root = JSON_MAPPER.readTree(json); JsonNode root = JSON_MAPPER.readTree(json);
// 2. Берём op и requestId // 2. op и requestId
String op = getTextOrNull(root, "op"); String op = getTextOrNull(root, "op");
if (op == null || op.isEmpty()) { if (op == null || op.isEmpty()) {
return buildErrorJson(null, null, WireCodes.Status.BAD_REQUEST, return buildErrorJson(null, null, WireCodes.Status.BAD_REQUEST,
@ -75,21 +75,27 @@ public final class JsonInboundProcessor {
"UNKNOWN_OP", "Неизвестная операция: " + op); "UNKNOWN_OP", "Неизвестная операция: " + op);
} }
// 3. Маппим весь JSON в конкретный класс запроса // 3. Маппим JSON нужный NetRequest
NetRequest request = JSON_MAPPER.treeToValue(root, reqClass); NetRequest request = JSON_MAPPER.treeToValue(root, reqClass);
// 4. Вызываем хэндлер, передавая контекст NetResponse response;
NetResponse response = handler.handle(request, ctx);
// На всякий случай: если хэндлер не выставил op/requestId // 4. Трай-кэтч вокруг хэндлера (важно!)
if (response.getOp() == null) { try {
response.setOp(op); response = handler.handle(request, ctx);
} } catch (Exception handlerError) {
if (response.getRequestId() == null) { log.error("💥 Ошибка внутри хэндлера '{}'", op, handlerError);
response.setRequestId(requestId); return buildErrorJson(op, requestId,
WireCodes.Status.INTERNAL_ERROR,
"INTERNAL_HANDLER_ERROR",
"Неожиданная ошибка при обработке операции: " + op);
} }
// 5. Собираем JSON-ответ // Если хэндлер не выставил op/requestId
if (response.getOp() == null) response.setOp(op);
if (response.getRequestId() == null) response.setRequestId(requestId);
// 5. Формируем JSON
ObjectNode out = JSON_MAPPER.createObjectNode(); ObjectNode out = JSON_MAPPER.createObjectNode();
out.put("op", response.getOp()); out.put("op", response.getOp());
out.put("requestId", response.getRequestId()); out.put("requestId", response.getRequestId());
@ -118,16 +124,7 @@ public final class JsonInboundProcessor {
} }
/** /**
* Генерация JSON-ошибки в формате ответа: * Генерация JSON-ошибки
* {
* "op": op,
* "requestId": requestId,
* "status": status,
* "payload": {
* "code": errorCode,
* "message": errorMessage
* }
* }
*/ */
private static String buildErrorJson(String op, private static String buildErrorJson(String op,
String requestId, String requestId,

View File

@ -5,6 +5,7 @@ import server.logic.ws_protocol.JSON.entyties.*;
import server.logic.ws_protocol.JSON.entyties.Auth.NetAuthSessionNewStep1Request; import server.logic.ws_protocol.JSON.entyties.Auth.NetAuthSessionNewStep1Request;
import server.logic.ws_protocol.JSON.entyties.Auth.NetAuthSessionNewStep1Response; import server.logic.ws_protocol.JSON.entyties.Auth.NetAuthSessionNewStep1Response;
import server.logic.ws_protocol.JSON.handlers.JsonMessageHandler; import server.logic.ws_protocol.JSON.handlers.JsonMessageHandler;
import server.logic.ws_protocol.JSON.utils.NetExceptionResponseFactory;
import server.logic.ws_protocol.WireCodes; import server.logic.ws_protocol.WireCodes;
import shine.db.dao.SolanaUsersDAO; import shine.db.dao.SolanaUsersDAO;
import shine.db.entities.SolanaUser; import shine.db.entities.SolanaUser;
@ -23,15 +24,22 @@ public class NetAuthSessionNewStep1Handler implements JsonMessageHandler {
String login = req.getLogin(); String login = req.getLogin();
if (login == null || login.isBlank()) { if (login == null || login.isBlank()) {
return error(req, WireCodes.Status.BAD_REQUEST, return NetExceptionResponseFactory.error(
"EMPTY_LOGIN", "Пустой логин"); req,
WireCodes.Status.BAD_REQUEST,
"EMPTY_LOGIN",
"Пустой логин"
);
} }
// 1) Проверка: в контексте никто не авторизован // 1) Проверка: в контексте никто не авторизован
if (ctx.getLogin() != null) { if (ctx.getLogin() != null) {
return error(req, WireCodes.Status.BAD_REQUEST, return NetExceptionResponseFactory.error(
req,
WireCodes.Status.BAD_REQUEST,
"ALREADY_AUTHED", "ALREADY_AUTHED",
"Попытка повторной авторификации для уже заданного login=" + ctx.getLogin()); "Попытка повторной авторификации для уже заданного login=" + ctx.getLogin()
);
} }
// 2) Ищем пользователя в локальной БД // 2) Ищем пользователя в локальной БД
@ -39,8 +47,12 @@ public class NetAuthSessionNewStep1Handler implements JsonMessageHandler {
if (solanaUser == null) { if (solanaUser == null) {
// TODO позже запрос в Solana, если не нашли локально // TODO позже запрос в Solana, если не нашли локально
return error(req, WireCodes.Status.UNVERIFIED, return NetExceptionResponseFactory.error(
"UNKNOWN_USER", "Пользователь с таким логином не найден"); req,
WireCodes.Status.UNVERIFIED,
"UNKNOWN_USER",
"Пользователь с таким логином не найден"
);
} }
// 3) Заполняем контекст полями пользователя // 3) Заполняем контекст полями пользователя
@ -67,13 +79,4 @@ public class NetAuthSessionNewStep1Handler implements JsonMessageHandler {
return resp; return resp;
} }
private NetExceptionResponse error(NetRequest req, int status, String code, String msg) {
NetExceptionResponse resp = new NetExceptionResponse();
resp.setOp(req.getOp());
resp.setRequestId(req.getRequestId());
resp.setStatus(status);
resp.setPayload(Map.of("code", code, "message", msg));
return resp;
}
} }

View File

@ -1,18 +1,17 @@
package server.logic.ws_protocol.JSON.handlers.auth; package server.logic.ws_protocol.JSON.handlers.auth;
import server.logic.ws_protocol.JSON.ConnectionContext; import server.logic.ws_protocol.JSON.ConnectionContext;
import server.logic.ws_protocol.JSON.entyties.NetExceptionResponse;
import server.logic.ws_protocol.JSON.entyties.NetRequest; import server.logic.ws_protocol.JSON.entyties.NetRequest;
import server.logic.ws_protocol.JSON.entyties.NetResponse; import server.logic.ws_protocol.JSON.entyties.NetResponse;
import server.logic.ws_protocol.JSON.entyties.Auth.NetSessionRefreshRequest; import server.logic.ws_protocol.JSON.entyties.Auth.NetSessionRefreshRequest;
import server.logic.ws_protocol.JSON.entyties.Auth.NetSessionRefreshResponse; import server.logic.ws_protocol.JSON.entyties.Auth.NetSessionRefreshResponse;
import server.logic.ws_protocol.JSON.handlers.JsonMessageHandler; import server.logic.ws_protocol.JSON.handlers.JsonMessageHandler;
import server.logic.ws_protocol.JSON.utils.NetExceptionResponseFactory;
import server.logic.ws_protocol.WireCodes; import server.logic.ws_protocol.WireCodes;
import shine.db.dao.ActiveSessionsDAO; import shine.db.dao.ActiveSessionsDAO;
import shine.db.entities.ActiveSession; import shine.db.entities.ActiveSession;
import java.sql.SQLException; import java.sql.SQLException;
import java.util.Map;
/** /**
* Хэндлер SessionRefresh. * Хэндлер SessionRefresh.
@ -35,8 +34,12 @@ public class NetSessionRefreshHandler implements JsonMessageHandler {
String sessionPwd = req.getSessionPwd(); String sessionPwd = req.getSessionPwd();
if (sessionPwd == null || sessionPwd.isEmpty()) { if (sessionPwd == null || sessionPwd.isEmpty()) {
return buildError(req, WireCodes.Status.BAD_REQUEST, return NetExceptionResponseFactory.error(
"BAD_SESSION_PWD", "Пустой пароль сессии"); req,
WireCodes.Status.BAD_REQUEST,
"BAD_SESSION_PWD",
"Пустой пароль сессии"
);
} }
ActiveSessionsDAO dao = ActiveSessionsDAO.getInstance(); ActiveSessionsDAO dao = ActiveSessionsDAO.getInstance();
@ -45,19 +48,31 @@ public class NetSessionRefreshHandler implements JsonMessageHandler {
session = dao.getBySessionId(sessionId); session = dao.getBySessionId(sessionId);
} catch (SQLException e) { } catch (SQLException e) {
// Ошибка БД внутренняя ошибка сервера // Ошибка БД внутренняя ошибка сервера
return buildError(req, WireCodes.Status.SERVER_DATA_ERROR, return NetExceptionResponseFactory.error(
"DB_ERROR", "Ошибка доступа к базе данных"); req,
WireCodes.Status.SERVER_DATA_ERROR,
"DB_ERROR",
"Ошибка доступа к базе данных"
);
} }
if (session == null) { if (session == null) {
return buildError(req, WireCodes.Status.UNVERIFIED, return NetExceptionResponseFactory.error(
"SESSION_NOT_FOUND", "Сессия не найдена"); req,
WireCodes.Status.UNVERIFIED,
"SESSION_NOT_FOUND",
"Сессия не найдена"
);
} }
String dbPwd = session.getSessionPwd(); String dbPwd = session.getSessionPwd();
if (dbPwd == null || !dbPwd.equals(sessionPwd)) { if (dbPwd == null || !dbPwd.equals(sessionPwd)) {
return buildError(req, WireCodes.Status.UNVERIFIED, return NetExceptionResponseFactory.error(
"SESSION_PWD_MISMATCH", "Неверный пароль сессии"); req,
WireCodes.Status.UNVERIFIED,
"SESSION_PWD_MISMATCH",
"Неверный пароль сессии"
);
} }
// Всё хорошо обновляем контекст соединения // Всё хорошо обновляем контекст соединения
@ -76,19 +91,4 @@ public class NetSessionRefreshHandler implements JsonMessageHandler {
resp.setPayload(null); // или Map.of("ok", true) resp.setPayload(null); // или Map.of("ok", true)
return resp; return resp;
} }
private NetExceptionResponse buildError(NetRequest req,
int status,
String code,
String message) {
NetExceptionResponse resp = new NetExceptionResponse();
resp.setOp(req.getOp());
resp.setRequestId(req.getRequestId());
resp.setStatus(status);
resp.setPayload(Map.of(
"code", code,
"message", message
));
return resp;
}
} }

View File

@ -1,20 +1,19 @@
package server.logic.ws_protocol.JSON.handlers.auth; package server.logic.ws_protocol.JSON.handlers.tempToTest;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import server.logic.ws_protocol.JSON.ConnectionContext; import server.logic.ws_protocol.JSON.ConnectionContext;
import server.logic.ws_protocol.JSON.entyties.tempToTest.NetAddUserRequest;
import server.logic.ws_protocol.JSON.entyties.tempToTest.NetAddUserResponse;
import server.logic.ws_protocol.JSON.entyties.NetExceptionResponse;
import server.logic.ws_protocol.JSON.entyties.NetRequest; import server.logic.ws_protocol.JSON.entyties.NetRequest;
import server.logic.ws_protocol.JSON.entyties.NetResponse; import server.logic.ws_protocol.JSON.entyties.NetResponse;
import server.logic.ws_protocol.JSON.entyties.tempToTest.NetAddUserRequest;
import server.logic.ws_protocol.JSON.entyties.tempToTest.NetAddUserResponse;
import server.logic.ws_protocol.JSON.handlers.JsonMessageHandler; import server.logic.ws_protocol.JSON.handlers.JsonMessageHandler;
import server.logic.ws_protocol.JSON.utils.NetExceptionResponseFactory;
import server.logic.ws_protocol.WireCodes; import server.logic.ws_protocol.WireCodes;
import shine.db.dao.SolanaUsersDAO; import shine.db.dao.SolanaUsersDAO;
import shine.db.entities.SolanaUser; import shine.db.entities.SolanaUser;
import java.sql.SQLException; import java.sql.SQLException;
import java.util.Map;
/** /**
* Временный Хэндлер AddUser. Используется для тестовой регистрации!!!!!!!! * Временный Хэндлер AddUser. Используется для тестовой регистрации!!!!!!!!
@ -33,19 +32,18 @@ public class NetAddUserHandler implements JsonMessageHandler {
public NetResponse handle(NetRequest baseRequest, ConnectionContext ctx) throws Exception { public NetResponse handle(NetRequest baseRequest, ConnectionContext ctx) throws Exception {
NetAddUserRequest req = (NetAddUserRequest) baseRequest; NetAddUserRequest req = (NetAddUserRequest) baseRequest;
// Минимальная валидация входных данных // Одна общая проверка всех ключевых полей
if (req.getLogin() == null || req.getLogin().isBlank()) { if (req.getLogin() == null || req.getLogin().isBlank()
return buildError(req, WireCodes.Status.BAD_REQUEST, || req.getPubkey0() == null || req.getPubkey0().isBlank()
"BAD_LOGIN", "Пустой логин"); || req.getPubkey1() == null || req.getPubkey1().isBlank()
} || req.getBchLimit() == null) {
if (req.getPubkey0() == null || req.getPubkey0().isBlank()
|| req.getPubkey1() == null || req.getPubkey1().isBlank()) { return NetExceptionResponseFactory.error(
return buildError(req, WireCodes.Status.BAD_REQUEST, req,
"BAD_PUBKEY", "Публичные ключи не указаны"); WireCodes.Status.BAD_REQUEST,
} "BAD_FIELDS",
if (req.getBchLimit() == null) { "Некорректные или пустые поля: login, pubkey0, pubkey1, bchLimit"
return buildError(req, WireCodes.Status.BAD_REQUEST, );
"BAD_BCH_LIMIT", "Не указан лимит блокчейна");
} }
try { try {
@ -72,27 +70,20 @@ public class NetAddUserHandler implements JsonMessageHandler {
} catch (SQLException e) { } catch (SQLException e) {
log.error("❌ Ошибка при вставке пользователя в БД", e); log.error("❌ Ошибка при вставке пользователя в БД", e);
return buildError(req, WireCodes.Status.SERVER_DATA_ERROR, return NetExceptionResponseFactory.error(
"DB_ERROR", "Ошибка доступа к базе данных"); req,
WireCodes.Status.SERVER_DATA_ERROR,
"DB_ERROR",
"Ошибка доступа к базе данных"
);
} catch (Exception e) { } catch (Exception e) {
log.error("❌ Неожиданная ошибка в AddUser", e); log.error("❌ Неожиданная ошибка в AddUser", e);
return buildError(req, WireCodes.Status.INTERNAL_ERROR, return NetExceptionResponseFactory.error(
"INTERNAL_ERROR", "Внутренняя ошибка сервера"); req,
WireCodes.Status.INTERNAL_ERROR,
"INTERNAL_ERROR",
"Внутренняя ошибка сервера"
);
} }
} }
private NetExceptionResponse buildError(NetRequest req,
int status,
String code,
String message) {
NetExceptionResponse resp = new NetExceptionResponse();
resp.setOp(req.getOp());
resp.setRequestId(req.getRequestId());
resp.setStatus(status);
resp.setPayload(Map.of(
"code", code,
"message", message
));
return resp;
}
} }

View File

@ -0,0 +1,36 @@
package server.logic.ws_protocol.JSON.utils;
import server.logic.ws_protocol.JSON.entyties.NetExceptionResponse;
import server.logic.ws_protocol.JSON.entyties.NetRequest;
import java.util.Map;
/**
* Фабрика ошибок для JSON-протокола.
* Создаёт единообразные NetExceptionResponse.
*/
public final class NetExceptionResponseFactory {
private NetExceptionResponseFactory() {
// запрет на создание объектов
}
public static NetExceptionResponse error(NetRequest req,
int status,
String code,
String message) {
NetExceptionResponse resp = new NetExceptionResponse();
resp.setOp(req.getOp());
resp.setRequestId(req.getRequestId());
resp.setStatus(status);
resp.setPayload(Map.of(
"code", code,
"message", message
));
return resp;
}
}

View File

@ -2,18 +2,17 @@ import java.net.URI;
import java.net.http.HttpClient; import java.net.http.HttpClient;
import java.net.http.WebSocket; import java.net.http.WebSocket;
import java.net.http.WebSocket.Listener; import java.net.http.WebSocket.Listener;
import java.nio.charset.StandardCharsets;
import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CompletionStage; import java.util.concurrent.CompletionStage;
import java.util.concurrent.CountDownLatch; import java.util.concurrent.CountDownLatch;
public class TestJsonWsClient { public class TestJsonWsClient {
public static void main(String[] args) throws Exception { // Адрес сервера
String uri = "ws://localhost:7070/ws"; private static final String WS_URI = "ws://localhost:7070/ws";
// Отдельные запросы
String jsonRequestSessionRefresh = """ private static final String JSON_REQUEST_SESSION_REFRESH = """
{ {
"op": "SessionRefresh", "op": "SessionRefresh",
"requestId": "test-1", "requestId": "test-1",
@ -22,7 +21,7 @@ public class TestJsonWsClient {
} }
"""; """;
String jsonRequestAddUser = """ private static final String JSON_REQUEST_ADD_USER = """
{ {
"op": "AddUser", "op": "AddUser",
"requestId": "test-add-1", "requestId": "test-add-1",
@ -35,7 +34,7 @@ public class TestJsonWsClient {
} }
"""; """;
String jsonRequestAuthSessionNewStep1 = """ private static final String JSON_REQUEST_AUTH_SESSION_NEW_STEP1 = """
{ {
"op": "AuthSessionNewStep1", "op": "AuthSessionNewStep1",
"requestId": "test-auth-1", "requestId": "test-auth-1",
@ -43,42 +42,78 @@ public class TestJsonWsClient {
} }
"""; """;
// МАССИВ КОНСТАНТА с запросами добавляешь сюда любые свои JSON
private static final String[] JSON_REQUESTS = {
JSON_REQUEST_SESSION_REFRESH,
JSON_REQUEST_ADD_USER,
JSON_REQUEST_AUTH_SESSION_NEW_STEP1
};
// Тестовый JSON-пакет SessionRefresh public static void main(String[] args) throws Exception {
String jsonRequest = jsonRequestAuthSessionNewStep1; System.out.println("Подключаемся к " + WS_URI);
System.out.println("Подключаемся к " + uri);
CountDownLatch latch = new CountDownLatch(1); CountDownLatch latch = new CountDownLatch(1);
HttpClient client = HttpClient.newHttpClient(); HttpClient client = HttpClient.newHttpClient();
WebSocket webSocket = client.newWebSocketBuilder() ClientListener listener = new ClientListener(JSON_REQUESTS, latch);
.buildAsync(URI.create(uri), new Listener() {
client.newWebSocketBuilder()
.buildAsync(URI.create(WS_URI), listener)
.join();
// Ждём, пока всё не завершится (успех/ошибка/закрытие)
latch.await();
System.out.println("Тест завершён, выходим.");
}
// Внутренний Listener, который сам по очереди шлёт запросы и печатает ответы
private static class ClientListener implements Listener {
private final String[] requests;
private final CountDownLatch latch;
private int index = 0; // какой запрос сейчас отправляем/ждём ответ
ClientListener(String[] requests, CountDownLatch latch) {
this.requests = requests;
this.latch = latch;
}
@Override @Override
public void onOpen(WebSocket webSocket) { public void onOpen(WebSocket webSocket) {
System.out.println("✅ WebSocket подключен"); System.out.println("✅ WebSocket подключен");
sendNextRequest(webSocket);
// Отправляем JSON сразу после подключения
System.out.println("📤 Отправляем JSON-запрос:");
System.out.println(jsonRequest);
webSocket.sendText(jsonRequest, true);
Listener.super.onOpen(webSocket); Listener.super.onOpen(webSocket);
} }
// Отправка следующего запроса из массива
private void sendNextRequest(WebSocket webSocket) {
if (index < requests.length) {
String json = requests[index];
System.out.println();
System.out.println("📤 Отправляем запрос " + (index + 1) + " из " + requests.length + ":");
System.out.println(json);
webSocket.sendText(json, true);
} else {
System.out.println("Все запросы отправлены, закрываем соединение");
webSocket.sendClose(WebSocket.NORMAL_CLOSURE, "all tests done");
}
}
@Override @Override
public CompletionStage<?> onText(WebSocket webSocket, public CompletionStage<?> onText(WebSocket webSocket,
CharSequence data, CharSequence data,
boolean last) { boolean last) {
String message = data.toString(); // Ответ на текущий запрос (с индексом index)
System.out.println("📥 Получен TEXT-ответ от сервера:"); System.out.println("📥 Ответ на запрос " + (index + 1) + ":");
System.out.println(message); System.out.println(data.toString());
System.out.println("-----------------------------------------------------");
// После получения первого ответа закрываем соединение // Переходим к следующему запросу
webSocket.sendClose(WebSocket.NORMAL_CLOSURE, "test done"); index++;
latch.countDown(); sendNextRequest(webSocket);
return Listener.super.onText(webSocket, data, last);
return CompletableFuture.completedFuture(null);
} }
@Override @Override
@ -88,19 +123,13 @@ public class TestJsonWsClient {
latch.countDown(); latch.countDown();
} }
@Override @Override
public CompletionStage<?> onClose(WebSocket webSocket, public CompletionStage<?> onClose(WebSocket webSocket,
int statusCode, int statusCode,
String reason) { String reason) {
System.out.println("🔚 Соединение закрыто. Код=" + statusCode + ", причина=" + reason); System.out.println("🔚 Соединение закрыто. Код=" + statusCode + ", причина=" + reason);
latch.countDown(); latch.countDown();
return Listener.super.onClose(webSocket, statusCode, reason); return CompletableFuture.completedFuture(null);
} }
}).join();
// Ждём, пока получим ответ/ошибку/закрытие
latch.await();
System.out.println("Тест завершён, выходим.");
} }
} }