Старт

This commit is contained in:
AidarKC
2025-12-04 12:20:47 +03:00
commit ff9301eddb
79 changed files with 4655 additions and 0 deletions
+35
View File
@@ -0,0 +1,35 @@
plugins {
id 'java'
}
group = 'shine'
version = '1.0.0'
java {
toolchain {
languageVersion = JavaLanguageVersion.of(17)
}
}
repositories {
mavenCentral()
}
dependencies {
implementation 'com.fasterxml.jackson.core:jackson-databind:2.17.1' // json
implementation 'org.slf4j:slf4j-api:2.0.9'
implementation 'ch.qos.logback:logback-classic:1.5.6'
implementation project(':shine-server-config') // модуль с настройками
implementation project(':shine-server-db') // модуль для работы с БД содержит и сущности из БД и саму работу с БД
}
java {
sourceCompatibility = JavaVersion.VERSION_17
targetCompatibility = JavaVersion.VERSION_17
}
+16
View File
@@ -0,0 +1,16 @@
#!/usr/bin/env bash
set -euo pipefail
OUTFILE="all_files.txt"
# очищаем или создаём файл
: > "$OUTFILE"
# собрать только *.java файлы и вывести их содержимое в файл
find . -type f -name "*.java" | sort | while read -r f; do
cat "$f" >> "$OUTFILE"
echo >> "$OUTFILE" # пустая строка-разделитель
done
echo "Готово! Все .java файлы собраны в $OUTFILE"
@@ -0,0 +1,137 @@
package server.logic.ws_protocol.JSON;
/**
* ConnectionContext — контекст состояния одного WebSocket-соединения.
* Живёт ровно столько же, сколько живёт подключение.
*/
public class ConnectionContext {
// Статусы аутентификации
public static final int AUTH_STATUS_NONE = 0; // ананимный или не авторизованный пользователь
public static final int AUTH_STATUS_USER = 1; // авторизованный пользователь
// public static final int AUTH_STATUS_ANON = 2; // анонимный (зарезервировано на будущее)
private String login;
private Long loginId;
private Long sessionId;
private String sessionPwd;
// Данные пользователя / блокчейна
private Long bchId;
private String pubkey0;
private String pubkey1;
private Integer bchLimit;
private int authenticationStatus = AUTH_STATUS_NONE;
// --- getters / setters ---
public String getLogin() {
return login;
}
public void setLogin(String login) {
this.login = login;
}
public Long getLoginId() {
return loginId;
}
public void setLoginId(Long loginId) {
this.loginId = loginId;
}
public Long getSessionId() {
return sessionId;
}
public void setSessionId(Long sessionId) {
this.sessionId = sessionId;
}
public String getSessionPwd() {
return sessionPwd;
}
public void setSessionPwd(String sessionPwd) {
this.sessionPwd = sessionPwd;
}
public Long getBchId() {
return bchId;
}
public void setBchId(Long bchId) {
this.bchId = bchId;
}
public String getPubkey0() {
return pubkey0;
}
public void setPubkey0(String pubkey0) {
this.pubkey0 = pubkey0;
}
public String getPubkey1() {
return pubkey1;
}
public void setPubkey1(String pubkey1) {
this.pubkey1 = pubkey1;
}
public Integer getBchLimit() {
return bchLimit;
}
public void setBchLimit(Integer bchLimit) {
this.bchLimit = bchLimit;
}
public int getAuthenticationStatus() {
return authenticationStatus;
}
public void setAuthenticationStatus(int authenticationStatus) {
this.authenticationStatus = authenticationStatus;
}
public boolean isAuthenticatedUser() {
return authenticationStatus == AUTH_STATUS_USER;
}
public boolean isAnonymous() {
return authenticationStatus == AUTH_STATUS_NONE;
}
public void reset() {
login = null;
loginId = null;
sessionId = null;
sessionPwd = null;
bchId = null;
pubkey0 = null;
pubkey1 = null;
bchLimit = null;
authenticationStatus = AUTH_STATUS_NONE;
}
@Override
public String toString() {
return "ConnectionContext{" +
"login='" + login + '\'' +
", loginId=" + loginId +
", sessionId=" + sessionId +
", bchId=" + bchId +
", pubkey0='" + pubkey0 + '\'' +
", pubkey1='" + pubkey1 + '\'' +
", bchLimit=" + bchLimit +
", authenticationStatus=" + authenticationStatus +
'}';
}
}
@@ -0,0 +1,49 @@
package server.logic.ws_protocol.JSON;
import server.logic.ws_protocol.JSON.entyties.*;
import server.logic.ws_protocol.JSON.entyties.Auth.NetAuthSessionNewStep1Request;
import server.logic.ws_protocol.JSON.entyties.Auth.NetSessionRefreshRequest;
import server.logic.ws_protocol.JSON.handlers.*;
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.auth.NetAuthSessionNewStep1Handler;
import server.logic.ws_protocol.JSON.handlers.auth.NetSessionRefreshHandler;
import java.util.Map;
/**
* JsonHandlerRegistry — единое место, где руками регистрируются
* JSON-операции: op → handler и op → requestClass.
*
* Если нужно добавить новый запрос:
* 1) создаёшь класс NetXXXRequest / NetXXXResponse,
* 2) создаёшь JsonMessageHandler (NetXXXHandler),
* 3) добавляешь оп в HANDLERS и REQUEST_TYPES.
*/
public final class JsonHandlerRegistry {
private static final Map<String, JsonMessageHandler> HANDLERS = Map.of(
"SessionRefresh", new NetSessionRefreshHandler(),
"AddUser", new NetAddUserHandler(),
"AuthSessionNewStep1", new NetAuthSessionNewStep1Handler()
// сюда потом добавишь другие операции
);
private static final Map<String, Class<? extends NetRequest>> REQUEST_TYPES = Map.of(
"SessionRefresh", NetSessionRefreshRequest.class,
"AddUser", NetAddUserRequest.class,
"AuthSessionNewStep1", NetAuthSessionNewStep1Request.class
);
private JsonHandlerRegistry() {
// utility
}
public static Map<String, JsonMessageHandler> getHandlers() {
return HANDLERS;
}
public static Map<String, Class<? extends NetRequest>> getRequestTypes() {
return REQUEST_TYPES;
}
}
@@ -0,0 +1,158 @@
package server.logic.ws_protocol.JSON;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ObjectNode;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import server.logic.ws_protocol.JSON.entyties.NetRequest;
import server.logic.ws_protocol.JSON.entyties.NetResponse;
import server.logic.ws_protocol.JSON.handlers.JsonMessageHandler;
import server.logic.ws_protocol.WireCodes;
import java.util.Map;
/**
* JsonInboundProcessor — отдельный класс для обработки JSON-сообщений.
*
* 1) Парсит общий пакет (op, requestId, payload).
* 2) По op выбирает класс запроса и хэндлер.
* 3) Маппит JSON → NetRequest через ObjectMapper.
* 4) Вызывает хэндлер, получает NetResponse.
* 5) Собирает JSON-ответ и возвращает строкой.
*/
public final class JsonInboundProcessor {
private static final Logger log = LoggerFactory.getLogger(JsonInboundProcessor.class);
private static final ObjectMapper JSON_MAPPER = new ObjectMapper();
/**
* op → хэндлер.
* Регистрация вынесена в JsonHandlerRegistry.
*/
private static final Map<String, JsonMessageHandler> JSON_HANDLERS =
JsonHandlerRegistry.getHandlers();
/**
* op → класс запроса.
*/
private static final Map<String, Class<? extends NetRequest>> JSON_REQUEST_TYPES =
JsonHandlerRegistry.getRequestTypes();
private JsonInboundProcessor() {}
/**
* Обработка входящего JSON-сообщения.
*
* @param json исходная строка от клиента
* @param ctx контекст текущего WebSocket-соединения
* @return JSON-строка ответа
*/
public static String processJson(String json, ConnectionContext ctx) {
try {
if (json == null || json.isBlank()) {
return buildErrorJson(null, null, WireCodes.Status.BAD_REQUEST,
"EMPTY_JSON", "Пустое JSON-сообщение");
}
// 1. Парсим общий пакет как дерево
JsonNode root = JSON_MAPPER.readTree(json);
// 2. Берём op и requestId
String op = getTextOrNull(root, "op");
if (op == null || op.isEmpty()) {
return buildErrorJson(null, null, WireCodes.Status.BAD_REQUEST,
"NO_OP", "Поле 'op' отсутствует или пустое");
}
String requestId = getTextOrNull(root, "requestId");
JsonMessageHandler handler = JSON_HANDLERS.get(op);
Class<? extends NetRequest> reqClass = JSON_REQUEST_TYPES.get(op);
if (handler == null || reqClass == null) {
return buildErrorJson(op, requestId, WireCodes.Status.BAD_REQUEST,
"UNKNOWN_OP", "Неизвестная операция: " + op);
}
// 3. Маппим весь JSON в конкретный класс запроса
NetRequest request = JSON_MAPPER.treeToValue(root, reqClass);
// 4. Вызываем хэндлер, передавая контекст
NetResponse response = handler.handle(request, ctx);
// На всякий случай: если хэндлер не выставил op/requestId
if (response.getOp() == null) {
response.setOp(op);
}
if (response.getRequestId() == null) {
response.setRequestId(requestId);
}
// 5. Собираем JSON-ответ
ObjectNode out = JSON_MAPPER.createObjectNode();
out.put("op", response.getOp());
out.put("requestId", response.getRequestId());
out.put("status", response.getStatus());
if (response.getPayload() != null) {
out.set("payload", JSON_MAPPER.valueToTree(response.getPayload()));
} else {
out.putNull("payload");
}
return JSON_MAPPER.writeValueAsString(out);
} catch (Exception e) {
log.error("Ошибка при обработке JSON-сообщения", e);
return buildErrorJson("Unknown", null, WireCodes.Status.INTERNAL_ERROR,
"INTERNAL_ERROR", "Внутренняя ошибка сервера");
}
}
// --- helper'ы ---
private static String getTextOrNull(JsonNode node, String field) {
if (node == null || !node.has(field) || node.get(field).isNull()) return null;
return node.get(field).asText();
}
/**
* Генерация JSON-ошибки в формате ответа:
* {
* "op": op,
* "requestId": requestId,
* "status": status,
* "payload": {
* "code": errorCode,
* "message": errorMessage
* }
* }
*/
private static String buildErrorJson(String op,
String requestId,
int status,
String errorCode,
String errorMessage) {
try {
ObjectNode root = JSON_MAPPER.createObjectNode();
if (op != null) root.put("op", op); else root.putNull("op");
if (requestId != null) root.put("requestId", requestId); else root.putNull("requestId");
root.put("status", status);
ObjectNode payload = root.putObject("payload");
payload.put("code", errorCode);
payload.put("message", errorMessage);
return JSON_MAPPER.writeValueAsString(root);
} catch (Exception e) {
return "{\"op\":\"" + (op != null ? op : "") +
"\",\"requestId\":\"" + (requestId != null ? requestId : "") +
"\",\"status\":" + status +
",\"payload\":{\"code\":\"" + errorCode +
"\",\"message\":\"" + errorMessage + "\"}}";
}
}
}
@@ -0,0 +1,14 @@
package server.logic.ws_protocol.JSON.entyties.Auth;
import server.logic.ws_protocol.JSON.entyties.NetRequest;
public class NetAuthSessionNewStep1Request extends NetRequest {
private String login;
public String getLogin() {
return login;
}
public void setLogin(String login) {
this.login = login;
}
}
@@ -0,0 +1,14 @@
package server.logic.ws_protocol.JSON.entyties.Auth;
import server.logic.ws_protocol.JSON.entyties.NetResponse;
public class NetAuthSessionNewStep1Response extends NetResponse {
private String sessionPwd;
public String getSessionPwd() {
return sessionPwd;
}
public void setSessionPwd(String sessionPwd) {
this.sessionPwd = sessionPwd;
}
}
@@ -0,0 +1,34 @@
package server.logic.ws_protocol.JSON.entyties.Auth;
import server.logic.ws_protocol.JSON.entyties.NetRequest;
/**
* Запрос SessionRefresh.
*
* JSON (payload):
* {
* "sessionId": 123,
* "sessionPwd": "abcd..."
* }
*/
public class NetSessionRefreshRequest extends NetRequest {
private long sessionId;
private String sessionPwd;
public long getSessionId() {
return sessionId;
}
public void setSessionId(long sessionId) {
this.sessionId = sessionId;
}
public String getSessionPwd() {
return sessionPwd;
}
public void setSessionPwd(String sessionPwd) {
this.sessionPwd = sessionPwd;
}
}
@@ -0,0 +1,12 @@
package server.logic.ws_protocol.JSON.entyties.Auth;
import server.logic.ws_protocol.JSON.entyties.NetResponse;
/**
* Успешный ответ на SessionRefresh.
*
* Дополнительных полей нет, достаточно status=200 и (опционально) пустого payload.
*/
public class NetSessionRefreshResponse extends NetResponse {
// Ничего дополнительного, вся информация в status.
}
@@ -0,0 +1,41 @@
package server.logic.ws_protocol.JSON.entyties;
/**
* Базовый класс для всех событий (event).
* Общие поля: op и payload.
*
* Формат JSON (event):
* {
* "op": "...",
* "payload": { ... }
* }
*/
public abstract class NetEvent {
/** Имя операции / события (op). */
private String op;
/**
* Произвольные данные.
* В JSON это поле "payload".
*/
private Object payload;
// --- getters / setters ---
public String getOp() {
return op;
}
public void setOp(String op) {
this.op = op;
}
public Object getPayload() {
return payload;
}
public void setPayload(Object payload) {
this.payload = payload;
}
}
@@ -0,0 +1,14 @@
package server.logic.ws_protocol.JSON.entyties;
/**
* Ответ с ошибкой (любой отказ).
*
* В payload лежит:
* {
* "code": "...",
* "message": "..."
* }
*/
public class NetExceptionResponse extends NetResponse {
// Ничего дополнительного: код/текст ошибки лежат в payload (Map или DTO).
}
@@ -0,0 +1,29 @@
package server.logic.ws_protocol.JSON.entyties;
/**
* Базовый класс для всех запросов (client → server).
*
* Наследуется от NetEvent и добавляет requestId.
*
* Формат JSON (request):
* {
* "op": "...",
* "requestId": "...",
* "payload": { ... }
* }
*/
public abstract class NetRequest extends NetEvent {
/** Идентификатор запроса, чтобы связать запрос и ответ. */
private String requestId;
// --- getters / setters ---
public String getRequestId() {
return requestId;
}
public void setRequestId(String requestId) {
this.requestId = requestId;
}
}
@@ -0,0 +1,34 @@
package server.logic.ws_protocol.JSON.entyties;
/**
* Базовый класс для всех ответов (server → client).
*
* Наследуется от NetRequest и добавляет status.
*
* Формат JSON (response):
* {
* "op": "...",
* "requestId": "...",
* "status": 200,
* "payload": { ... } // и для успеха, и для ошибки
* }
*/
public abstract class NetResponse extends NetRequest {
/** Статус результата (200 — успех, любое другое значение — ошибка). */
private int status;
// --- getters / setters ---
public int getStatus() {
return status;
}
public void setStatus(int status) {
this.status = status;
}
public boolean isOk() {
return status == 200;
}
}
@@ -0,0 +1,76 @@
package server.logic.ws_protocol.JSON.entyties.tempToTest;
import server.logic.ws_protocol.JSON.entyties.NetRequest;
/**
* Запрос AddUser.
*
* Ожидаемый JSON:
* {
* "op": "AddUser",
* "requestId": "...",
* "login": "...",
* "loginId": 123,
* "bchId": 456,
* "pubkey0": "...",
* "pubkey1": "...",
* "bchLimit": 1000
* }
*/
public class NetAddUserRequest extends NetRequest {
private String login;
private long loginId;
private long bchId;
private String pubkey0;
private String pubkey1;
private Integer bchLimit;
public String getLogin() {
return login;
}
public void setLogin(String login) {
this.login = login;
}
public long getLoginId() {
return loginId;
}
public void setLoginId(long loginId) {
this.loginId = loginId;
}
public long getBchId() {
return bchId;
}
public void setBchId(long bchId) {
this.bchId = bchId;
}
public String getPubkey0() {
return pubkey0;
}
public void setPubkey0(String pubkey0) {
this.pubkey0 = pubkey0;
}
public String getPubkey1() {
return pubkey1;
}
public void setPubkey1(String pubkey1) {
this.pubkey1 = pubkey1;
}
public Integer getBchLimit() {
return bchLimit;
}
public void setBchLimit(Integer bchLimit) {
this.bchLimit = bchLimit;
}
}
@@ -0,0 +1,11 @@
package server.logic.ws_protocol.JSON.entyties.tempToTest;
import server.logic.ws_protocol.JSON.entyties.NetResponse;
/**
* Успешный ответ на AddUser.
* Дополнительных полей нет — достаточно status=200.
*/
public class NetAddUserResponse extends NetResponse {
// Можно потом добавить какие-то данные, если понадобится.
}
@@ -0,0 +1,19 @@
package server.logic.ws_protocol.JSON.handlers;
import server.logic.ws_protocol.JSON.ConnectionContext;
import server.logic.ws_protocol.JSON.entyties.NetRequest;
import server.logic.ws_protocol.JSON.entyties.NetResponse;
/**
* Общий интерфейс для всех JSON-хэндлеров.
*/
public interface JsonMessageHandler {
/**
* Обработать запрос и вернуть ответ.
*
* @param request распарсенный запрос
* @param ctx контекст текущего WebSocket-соединения
*/
NetResponse handle(NetRequest request, ConnectionContext ctx) throws Exception;
}
@@ -0,0 +1,98 @@
package server.logic.ws_protocol.JSON.handlers.auth;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
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.NetResponse;
import server.logic.ws_protocol.JSON.handlers.JsonMessageHandler;
import server.logic.ws_protocol.WireCodes;
import shine.db.dao.SolanaUsersDAO;
import shine.db.entities.SolanaUser;
import java.sql.SQLException;
import java.util.Map;
/**
* Временный Хэндлер AddUser. Используется для тестовой регистрации!!!!!!!!
*
* Логика:
* - берём login, loginId, bchId, pubkey0, pubkey1, bchLimit;
* - создаём SolanaUser и вставляем через SolanaUsersDAO;
* - если всё ОК → NetAddUserResponse со статусом 200;
* - если ошибка БД или некорректные данные → NetExceptionResponse.
*/
public class NetAddUserHandler implements JsonMessageHandler {
private static final Logger log = LoggerFactory.getLogger(NetAddUserHandler.class);
@Override
public NetResponse handle(NetRequest baseRequest, ConnectionContext ctx) throws Exception {
NetAddUserRequest req = (NetAddUserRequest) baseRequest;
// Минимальная валидация входных данных
if (req.getLogin() == null || req.getLogin().isBlank()) {
return buildError(req, WireCodes.Status.BAD_REQUEST,
"BAD_LOGIN", "Пустой логин");
}
if (req.getPubkey0() == null || req.getPubkey0().isBlank()
|| req.getPubkey1() == null || req.getPubkey1().isBlank()) {
return buildError(req, WireCodes.Status.BAD_REQUEST,
"BAD_PUBKEY", "Публичные ключи не указаны");
}
if (req.getBchLimit() == null) {
return buildError(req, WireCodes.Status.BAD_REQUEST,
"BAD_BCH_LIMIT", "Не указан лимит блокчейна");
}
try {
SolanaUsersDAO dao = SolanaUsersDAO.getInstance();
SolanaUser user = new SolanaUser(
req.getLoginId(),
req.getLogin(),
req.getBchId(),
req.getPubkey0(),
req.getPubkey1(),
req.getBchLimit()
);
dao.insert(user);
NetAddUserResponse resp = new NetAddUserResponse();
resp.setOp(req.getOp());
resp.setRequestId(req.getRequestId());
resp.setStatus(WireCodes.Status.OK);
resp.setPayload(null); // можно поставить Map.of("ok", true)
log.info("✅ Пользователь добавлен: login={}, loginId={}", req.getLogin(), req.getLoginId());
return resp;
} catch (SQLException e) {
log.error("❌ Ошибка при вставке пользователя в БД", e);
return buildError(req, WireCodes.Status.SERVER_DATA_ERROR,
"DB_ERROR", "Ошибка доступа к базе данных");
} catch (Exception e) {
log.error("❌ Неожиданная ошибка в AddUser", e);
return buildError(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;
}
}
@@ -0,0 +1,79 @@
package server.logic.ws_protocol.JSON.handlers.auth;
import server.logic.ws_protocol.JSON.ConnectionContext;
import server.logic.ws_protocol.JSON.entyties.*;
import server.logic.ws_protocol.JSON.entyties.Auth.NetAuthSessionNewStep1Request;
import server.logic.ws_protocol.JSON.entyties.Auth.NetAuthSessionNewStep1Response;
import server.logic.ws_protocol.JSON.handlers.JsonMessageHandler;
import server.logic.ws_protocol.WireCodes;
import shine.db.dao.SolanaUsersDAO;
import shine.db.entities.SolanaUser;
import java.security.SecureRandom;
import java.util.Map;
public class NetAuthSessionNewStep1Handler implements JsonMessageHandler {
private static final SecureRandom RANDOM = new SecureRandom();
@Override
public NetResponse handle(NetRequest baseReq, ConnectionContext ctx) throws Exception {
NetAuthSessionNewStep1Request req = (NetAuthSessionNewStep1Request) baseReq;
String login = req.getLogin();
if (login == null || login.isBlank()) {
return error(req, WireCodes.Status.BAD_REQUEST,
"EMPTY_LOGIN", "Пустой логин");
}
// 1) Проверка: в контексте никто не авторизован
if (ctx.getLogin() != null) {
return error(req, WireCodes.Status.BAD_REQUEST,
"ALREADY_AUTHED",
"Попытка повторной авторификации для уже заданного login=" + ctx.getLogin());
}
// 2) Ищем пользователя в локальной БД
SolanaUser solanaUser = SolanaUsersDAO.getInstance().getByLogin(login);
if (solanaUser == null) {
// TODO позже — запрос в Solana, если не нашли локально
return error(req, WireCodes.Status.UNVERIFIED,
"UNKNOWN_USER", "Пользователь с таким логином не найден");
}
// 3) Заполняем контекст полями пользователя
ctx.setLogin(solanaUser.getLogin());
ctx.setLoginId(solanaUser.getLoginId());
ctx.setBchId(solanaUser.getBchId());
ctx.setPubkey0(solanaUser.getPubkey0());
ctx.setPubkey1(solanaUser.getPubkey1());
ctx.setBchLimit(solanaUser.getBchLimit());
// 4) Генерируем надёжный sessionPwd
// SecureRandom + время → достаточно
String sessionPwd = Long.toHexString(System.nanoTime()) +
Long.toHexString(RANDOM.nextLong());
ctx.setSessionPwd(sessionPwd);
// 5) Формируем ответ
NetAuthSessionNewStep1Response resp = new NetAuthSessionNewStep1Response();
resp.setOp(req.getOp());
resp.setRequestId(req.getRequestId());
resp.setStatus(WireCodes.Status.OK);
resp.setPayload(Map.of("sessionPwd", sessionPwd));
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;
}
}
@@ -0,0 +1,94 @@
package server.logic.ws_protocol.JSON.handlers.auth;
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.NetResponse;
import server.logic.ws_protocol.JSON.entyties.Auth.NetSessionRefreshRequest;
import server.logic.ws_protocol.JSON.entyties.Auth.NetSessionRefreshResponse;
import server.logic.ws_protocol.JSON.handlers.JsonMessageHandler;
import server.logic.ws_protocol.WireCodes;
import shine.db.dao.ActiveSessionsDAO;
import shine.db.entities.ActiveSession;
import java.sql.SQLException;
import java.util.Map;
/**
* Хэндлер SessionRefresh.
*
* Логика:
* - берём sessionId и sessionPwd из запроса;
* - ищем сессию в БД;
* - если не нашли или пароль не совпал → NetExceptionResponse;
* - если всё ок:
* * обновляем ConnectionContext (sessionId, sessionPwd, статус USER);
* * возвращаем NetSessionRefreshResponse со статусом 200.
*/
public class NetSessionRefreshHandler implements JsonMessageHandler {
@Override
public NetResponse handle(NetRequest request, ConnectionContext ctx) throws Exception {
NetSessionRefreshRequest req = (NetSessionRefreshRequest) request;
long sessionId = req.getSessionId();
String sessionPwd = req.getSessionPwd();
if (sessionPwd == null || sessionPwd.isEmpty()) {
return buildError(req, WireCodes.Status.BAD_REQUEST,
"BAD_SESSION_PWD", "Пустой пароль сессии");
}
ActiveSessionsDAO dao = ActiveSessionsDAO.getInstance();
ActiveSession session;
try {
session = dao.getBySessionId(sessionId);
} catch (SQLException e) {
// Ошибка БД → внутренняя ошибка сервера
return buildError(req, WireCodes.Status.SERVER_DATA_ERROR,
"DB_ERROR", "Ошибка доступа к базе данных");
}
if (session == null) {
return buildError(req, WireCodes.Status.UNVERIFIED,
"SESSION_NOT_FOUND", "Сессия не найдена");
}
String dbPwd = session.getSessionPwd();
if (dbPwd == null || !dbPwd.equals(sessionPwd)) {
return buildError(req, WireCodes.Status.UNVERIFIED,
"SESSION_PWD_MISMATCH", "Неверный пароль сессии");
}
// Всё хорошо — обновляем контекст соединения
if (ctx != null) {
ctx.setSessionId(sessionId);
ctx.setSessionPwd(sessionPwd);
// Если потом добавишь в ActiveSession login / loginId — можно здесь и их проставлять
ctx.setAuthenticationStatus(ConnectionContext.AUTH_STATUS_USER);
}
// И возвращаем OK без доп. данных
NetSessionRefreshResponse resp = new NetSessionRefreshResponse();
resp.setOp(req.getOp());
resp.setRequestId(req.getRequestId());
resp.setStatus(WireCodes.Status.OK);
resp.setPayload(null); // или Map.of("ok", true)
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;
}
}
@@ -0,0 +1,52 @@
Протокол использует единый формат JSON-сообщений: клиент всегда отправляет запросы с полями op, requestId и payload, сервер отвечает тем же op и requestId, добавляя status (200 для успеха, любое другое значение — ошибка) и либо payload, либо error.
События от сервера приходят без requestId и status, содержат только op и payload и не являются ответами на запросы.
# Примеры JSON для SearchUsers (минимальная шпаргалка)
## 🔵 Запрос (client → server)
```json
{
"op": "SearchUsers",
"requestId": "req-1",
"payload": {
"query": "ai"
}
}
🟢 Успешный ответ (server → client)
{
"op": "SearchUsers",
"requestId": "req-1",
"status": 200,
"payload": {
"users": [
{ "login": "aidar" },
{ "login": "anya" }
]
}
}
🔴 Ошибочный ответ (server → client)
{
"op": "SearchUsers",
"requestId": "req-1",
"status": 403,
"error": {
"code": "SESSION_EXPIRED",
"message": "Сессия истекла"
}
}
🟣 Событие (server → client, не ответ)
{
"op": "NewBlockEvent",
"payload": {
"blockchainId": 42,
"blockNumber": 101
}
}
@@ -0,0 +1,102 @@
package server.logic.ws_protocol;
/**
* WireCodes — константы бинарного протокола поверх WebSocket.
*
* Формат входящего сообщения:
* [4] int opCode (big-endian)
* [*] payload
*
* Ответ сервера:
* ровно [4] int statusCode (big-endian)
*/
public final class WireCodes {
private WireCodes() {}
public static final class Op {
public static final int PING = 0;
public static final int ADD_BLOCK = 1;
public static final int GET_BLOCKCHAIN = 2;
public static final int SEARCH_USERS = 30;
public static final int GET_LAST_BLOCK_INFO = 31;
private Op() {}
}
public static final class Status {
public static final int PONG = 100; // ответ на PING
// public static final int OK = 200; // успех
public static final int ALREADY_EXISTS = 409; // пришёл блок < N+1
public static final int NON_SEQUENTIAL = 412; // пришёл блок > N+1
private Status() {}
// ============================================================
// 🟢 УСПЕШНЫЕ ОПЕРАЦИИ
// ============================================================
/** ✅ Блок успешно добавлен в цепочку. */
public static final int OK = 200;
/** 🌱 Создана новая цепочка (первый блок-заголовок принят). */
public static final int CHAIN_CREATED = 201;
/**
* 🔁 Такой блок уже существует.
* Клиент может считать это успешным ответом:
* - сервер возвращает 8 байт: [4] код (202) + [4] номер последнего блока (int)
* - клиент обновляет свой lastBlockNumber и не пересылает этот блок снова. */
public static final int BLOCK_ALREADY_EXISTS = 202; // плюс к кодуследом возвращается номер последнего блока на сервере
// ============================================================
// 🟡 ЛОГИЧЕСКИЕ / ПРОТОКОЛЬНЫЕ ОШИБКИ
// ============================================================
/** ⚠️ Нарушена последовательность — пришёл блок с номером > ожидаемого.
* Сервер вернёт 8 байт: [4] код (409) + [4] последний номер блока.
* Клиент должен дослать недостающие блоки. */
public static final int OUT_OF_SEQUENCE = 409; // плюс к кодуследом возвращается номер последнего блока на сервере
/** ❌ Некорректные или неполные данные в запросе. */
public static final int BAD_REQUEST = 400;
/** 🚫 Цепочка с указанным blockchainId не найдена. */
public static final int CHAIN_NOT_FOUND = 404;
/** 🧩 Несовпадение blockchainId между заголовком блока и телом. */
public static final int INVALID_BLOCKCHAIN_ID = 421;
/** ❌ Ошибка верификации блока — хэш или подпись не совпали.
* 🔐 Ошибка хэша: SHA-256(preimage) не совпал с переданным hash32.
* 🔏 Ошибка подписи Ed25519 — блок не прошёл криптографическую проверку. */
public static final int UNVERIFIED = 422;
/** 🙅 Некорректный логин (пустой, неверный формат, недопустимые символы). По сути вообще не может быть, тк логин проверяют при создании в другом блокчейне*/
public static final int BAD_LOGIN = 462;
// ============================================================
// 🔴 СИСТЕМНЫЕ ОШИБКИ / ОГРАНИЧЕНИЯ
// ============================================================
// ============================================================
// 🔴 СИСТЕМНЫЕ ОШИБКИ / ОГРАНИЧЕНИЯ
// ============================================================
/** 💾 Достигнут лимит размера блокчейна. */
public static final int BLOCKCHAIN_FULL = 507;
/** 🧱 Ошибка при сохранении или обновлении данных на сервере (файлы, JSON и т.п.). */
public static final int SERVER_DATA_ERROR = 501;
/** 💥 Общая внутренняя ошибка сервера (необработанное исключение). */
public static final int INTERNAL_ERROR = 500;
}
}
@@ -0,0 +1,29 @@
Мини-памятка по JSON-протоколу
** JsonInboundProcessor
Центральный вход для JSON.
Принимает текст → парсит → ищет op → создаёт нужный NetRequest → вызывает хэндлер → возвращает JSON-ответ.
** JsonHandlerRegistry
Словарь операций.
Связывает op → requestClass и op → handler.
Любая новая операция регистрируется здесь.
** NetEvent / NetRequest / NetResponse
Базовые структуры JSON-протокола - от которых есть реализации для всех сущностей запросов ответов
** ConnectionContext
Хранит состояние текущего WebSocket-соединения: логин, sessionId, статус пользователя.
Передаётся в любой JSON-хэндлер.
** JsonMessageHandler
Интерфейс одного хэндлера JSON-операции: - от которого наследуются все хэндлеры обработчики запросов
handle(request, context) → NetResponse.
** WebSocket endpoint
BlockchainWsEndpoint.onText(...) получает строку → передаёт в JsonInboundProcessor → отправляет клиенту готовый JSON-ответ.