SHA256
Старт
This commit is contained in:
@@ -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
|
||||
}
|
||||
|
||||
|
||||
|
||||
Executable
+16
@@ -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"
|
||||
|
||||
+137
@@ -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 +
|
||||
'}';
|
||||
}
|
||||
}
|
||||
+49
@@ -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;
|
||||
}
|
||||
}
|
||||
+158
@@ -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 + "\"}}";
|
||||
}
|
||||
}
|
||||
}
|
||||
+14
@@ -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;
|
||||
}
|
||||
}
|
||||
+14
@@ -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;
|
||||
}
|
||||
}
|
||||
+34
@@ -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;
|
||||
}
|
||||
}
|
||||
+12
@@ -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.
|
||||
}
|
||||
+41
@@ -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;
|
||||
}
|
||||
}
|
||||
+14
@@ -0,0 +1,14 @@
|
||||
package server.logic.ws_protocol.JSON.entyties;
|
||||
|
||||
/**
|
||||
* Ответ с ошибкой (любой отказ).
|
||||
*
|
||||
* В payload лежит:
|
||||
* {
|
||||
* "code": "...",
|
||||
* "message": "..."
|
||||
* }
|
||||
*/
|
||||
public class NetExceptionResponse extends NetResponse {
|
||||
// Ничего дополнительного: код/текст ошибки лежат в payload (Map или DTO).
|
||||
}
|
||||
+29
@@ -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;
|
||||
}
|
||||
}
|
||||
+34
@@ -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;
|
||||
}
|
||||
}
|
||||
+76
@@ -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;
|
||||
}
|
||||
}
|
||||
+11
@@ -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 {
|
||||
// Можно потом добавить какие-то данные, если понадобится.
|
||||
}
|
||||
+19
@@ -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;
|
||||
}
|
||||
+98
@@ -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;
|
||||
}
|
||||
}
|
||||
+79
@@ -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;
|
||||
}
|
||||
}
|
||||
+94
@@ -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;
|
||||
}
|
||||
}
|
||||
+52
@@ -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-ответ.
|
||||
Reference in New Issue
Block a user