05 01 25
Запрос для работы с параметрами пользователя работают!! И тесты на них проходят!!
This commit is contained in:
parent
eb122456ab
commit
a6a5089379
@ -14,9 +14,14 @@ import java.util.List;
|
||||
* - методы с Connection НЕ закрывают соединение
|
||||
* - методы без Connection сами открывают и закрывают соединение
|
||||
*
|
||||
* ВАЖНО по логике времени:
|
||||
* - сам DAO делает "технический upsert"
|
||||
* - правила "не принимать более старый time_ms" должны проверяться в handler-е, в транзакции.
|
||||
* ЛОГИКА time_ms:
|
||||
* - БД принимает запись только если она "новее" (time_ms строго больше текущего).
|
||||
* - Реализовано атомарно одним SQL: UPSERT + WHERE users_params.time_ms < excluded.time_ms
|
||||
*
|
||||
* Возврат результата:
|
||||
* - upsertIfNewer(...) возвращает количество изменённых строк:
|
||||
* 1 = вставили/обновили
|
||||
* 0 = проигнорировали (запись уже новее или равная)
|
||||
*/
|
||||
public final class UserParamsDAO {
|
||||
|
||||
@ -34,10 +39,14 @@ public final class UserParamsDAO {
|
||||
return instance;
|
||||
}
|
||||
|
||||
// -------------------- UPSERT --------------------
|
||||
// -------------------- UPSERT (IF NEWER) --------------------
|
||||
|
||||
/** UPSERT с внешним соединением. Соединение НЕ закрывает. */
|
||||
public void upsert(Connection c, UserParamEntry e) throws SQLException {
|
||||
/**
|
||||
* Атомарный UPSERT "только если новее".
|
||||
*
|
||||
* @return 1 если вставили/обновили; 0 если запись не тронули (existing.time_ms >= incoming.time_ms).
|
||||
*/
|
||||
public int upsertIfNewer(Connection c, UserParamEntry e) throws SQLException {
|
||||
String sql = """
|
||||
INSERT INTO users_params (
|
||||
login,
|
||||
@ -53,6 +62,7 @@ public final class UserParamsDAO {
|
||||
value = excluded.value,
|
||||
device_key = excluded.device_key,
|
||||
signature = excluded.signature
|
||||
WHERE users_params.time_ms < excluded.time_ms
|
||||
""";
|
||||
|
||||
try (PreparedStatement ps = c.prepareStatement(sql)) {
|
||||
@ -67,14 +77,14 @@ public final class UserParamsDAO {
|
||||
if (e.getSignature() != null) ps.setString(6, e.getSignature());
|
||||
else ps.setNull(6, Types.VARCHAR);
|
||||
|
||||
ps.executeUpdate();
|
||||
return ps.executeUpdate(); // 1 или 0
|
||||
}
|
||||
}
|
||||
|
||||
/** UPSERT без внешнего соединения. Сам открывает/закрывает. */
|
||||
public void upsert(UserParamEntry e) throws SQLException {
|
||||
/** То же самое, но сам открывает/закрывает соединение. */
|
||||
public int upsertIfNewer(UserParamEntry e) throws SQLException {
|
||||
try (Connection c = db.getConnection()) {
|
||||
upsert(c, e);
|
||||
return upsertIfNewer(c, e);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -21,32 +21,22 @@ import utils.crypto.Ed25519Util;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.sql.Connection;
|
||||
import java.sql.SQLException;
|
||||
import java.sql.Statement;
|
||||
import java.util.Base64;
|
||||
|
||||
/**
|
||||
* Net_UpsertUserParam_Handler
|
||||
*
|
||||
* Делает:
|
||||
* 1) Проверяет, что пользователь существует и что device_key действительно его.
|
||||
* 2) Проверяет, что нет "более нового" значения этого param (time_ms монотонно растёт).
|
||||
* 3) Проверяет подпись Ed25519 по device_key.
|
||||
* 4) Пишет в БД только если time_ms строго больше текущего сохранённого.
|
||||
* Делает (MVP, без "сессий"):
|
||||
* 1) Проверка входных полей.
|
||||
* 2) Проверка подписи Ed25519 по device_key.
|
||||
* 3) Проверка, что пользователь существует и что device_key принадлежит этому login.
|
||||
* 4) Атомарная запись в БД "только если time_ms новее" (UPSERT + WHERE).
|
||||
*
|
||||
* БОЛЬШОЙ КОММЕНТ ПРО АВТОРИЗАЦИЮ НА БУДУЩЕЕ:
|
||||
* ---------------------------------------------------------------------------------
|
||||
* Сейчас (MVP) этот эндпоинт намеренно не делает полноценную "сессию/авторизацию",
|
||||
* потому что целостность обеспечивается криптографией: сервер проверяет подпись
|
||||
* и то, что device_key принадлежит login.
|
||||
*
|
||||
* В будущем, если понадобится "ограничить кто может писать параметры", можно добавить:
|
||||
* - проверку активной сессии (active_sessions) и соответствие login в сессии;
|
||||
* - rate-limit на пользователя;
|
||||
* - отдельные права на запись конкретных param.
|
||||
*
|
||||
* Но возможно это вообще не потребуется, если модель безопасности строится
|
||||
* строго на подписи и владении device_key (как сейчас).
|
||||
* ---------------------------------------------------------------------------------
|
||||
* ВАЖНО:
|
||||
* - НИКАКИХ ручных транзакций / BEGIN здесь нет.
|
||||
* - autoCommit=true, каждый statement завершённый сам по себе.
|
||||
* - Гонки не страшны: если за время проверок кто-то записал более новый time_ms,
|
||||
* наш финальный UPSERT просто вернёт 0 обновлённых строк.
|
||||
*/
|
||||
public class Net_UpsertUserParam_Handler implements JsonMessageHandler {
|
||||
|
||||
@ -79,6 +69,7 @@ public class Net_UpsertUserParam_Handler implements JsonMessageHandler {
|
||||
final String signatureB64 = req.getSignature().trim();
|
||||
|
||||
try {
|
||||
// ---------------- Base64 decode ----------------
|
||||
byte[] pubKey32;
|
||||
byte[] sig64;
|
||||
try {
|
||||
@ -110,6 +101,7 @@ public class Net_UpsertUserParam_Handler implements JsonMessageHandler {
|
||||
);
|
||||
}
|
||||
|
||||
// ---------------- Signature verify ----------------
|
||||
String signText = ShineSignatureConstants.USER_PARAMETER_PREFIX
|
||||
+ login
|
||||
+ param
|
||||
@ -128,22 +120,15 @@ public class Net_UpsertUserParam_Handler implements JsonMessageHandler {
|
||||
);
|
||||
}
|
||||
|
||||
// ---------------- DB checks + upsert ----------------
|
||||
SqliteDbController db = SqliteDbController.getInstance();
|
||||
SolanaUsersDAO usersDAO = SolanaUsersDAO.getInstance();
|
||||
UserParamsDAO paramsDAO = UserParamsDAO.getInstance();
|
||||
|
||||
try (Connection c = db.getConnection()) {
|
||||
boolean oldAuto = c.getAutoCommit();
|
||||
c.setAutoCommit(false);
|
||||
|
||||
try (Statement st = c.createStatement()) {
|
||||
st.execute("BEGIN IMMEDIATE");
|
||||
}
|
||||
|
||||
try {
|
||||
// 1) user exists
|
||||
SolanaUserEntry user = usersDAO.getByLogin(c, login);
|
||||
if (user == null) {
|
||||
c.rollback();
|
||||
return NetExceptionResponseFactory.error(
|
||||
req,
|
||||
404,
|
||||
@ -152,9 +137,9 @@ public class Net_UpsertUserParam_Handler implements JsonMessageHandler {
|
||||
);
|
||||
}
|
||||
|
||||
// 2) device key must match the user's stored deviceKey
|
||||
String userDeviceKey = user.getDeviceKey();
|
||||
if (userDeviceKey == null || userDeviceKey.isBlank()) {
|
||||
c.rollback();
|
||||
return NetExceptionResponseFactory.error(
|
||||
req,
|
||||
WireCodes.Status.SERVER_DATA_ERROR,
|
||||
@ -164,7 +149,6 @@ public class Net_UpsertUserParam_Handler implements JsonMessageHandler {
|
||||
}
|
||||
|
||||
if (!userDeviceKey.trim().equals(deviceKeyB64)) {
|
||||
c.rollback();
|
||||
return NetExceptionResponseFactory.error(
|
||||
req,
|
||||
403,
|
||||
@ -173,35 +157,7 @@ public class Net_UpsertUserParam_Handler implements JsonMessageHandler {
|
||||
);
|
||||
}
|
||||
|
||||
UserParamEntry existing = paramsDAO.getByLoginAndParam(c, login, param);
|
||||
|
||||
// если есть более новое — запрет
|
||||
if (existing != null && existing.getTimeMs() > timeMs) {
|
||||
c.rollback();
|
||||
return NetExceptionResponseFactory.error(
|
||||
req,
|
||||
409,
|
||||
"PARAM_NEWER_EXISTS",
|
||||
"Уже есть более новое значение этого параметра (time_ms больше)"
|
||||
);
|
||||
}
|
||||
|
||||
// если time_ms равен — ничего не делаем (твой кейс)
|
||||
if (existing != null && existing.getTimeMs() == timeMs) {
|
||||
c.commit();
|
||||
c.setAutoCommit(oldAuto);
|
||||
|
||||
Net_UpsertUserParam_Response resp = new Net_UpsertUserParam_Response();
|
||||
resp.setOp(req.getOp());
|
||||
resp.setRequestId(req.getRequestId());
|
||||
resp.setStatus(WireCodes.Status.OK);
|
||||
|
||||
log.info("ℹ️ UpsertUserParam noop (same time_ms): login={}, param={}, time_ms={}",
|
||||
login, param, timeMs);
|
||||
return resp;
|
||||
}
|
||||
|
||||
// иначе existing==null или existingTime < timeMs -> пишем
|
||||
// 3) atomic upsert-if-newer
|
||||
UserParamEntry e = new UserParamEntry(
|
||||
login,
|
||||
param,
|
||||
@ -211,25 +167,21 @@ public class Net_UpsertUserParam_Handler implements JsonMessageHandler {
|
||||
signatureB64
|
||||
);
|
||||
|
||||
paramsDAO.upsert(c, e);
|
||||
|
||||
c.commit();
|
||||
c.setAutoCommit(oldAuto);
|
||||
int changed = paramsDAO.upsertIfNewer(c, e);
|
||||
|
||||
Net_UpsertUserParam_Response resp = new Net_UpsertUserParam_Response();
|
||||
resp.setOp(req.getOp());
|
||||
resp.setRequestId(req.getRequestId());
|
||||
resp.setStatus(WireCodes.Status.OK);
|
||||
|
||||
log.info("✅ UpsertUserParam ok: login={}, param={}, time_ms={}", login, param, timeMs);
|
||||
return resp;
|
||||
|
||||
} catch (SQLException ex) {
|
||||
c.rollback();
|
||||
throw ex;
|
||||
} finally {
|
||||
c.setAutoCommit(oldAuto);
|
||||
if (changed == 1) {
|
||||
log.info("✅ UpsertUserParam applied: login={}, param={}, time_ms={}", login, param, timeMs);
|
||||
} else {
|
||||
// 0 строк — значит в БД уже есть time_ms >= incoming
|
||||
log.info("ℹ️ UpsertUserParam ignored (not newer): login={}, param={}, time_ms={}", login, param, timeMs);
|
||||
}
|
||||
|
||||
return resp;
|
||||
}
|
||||
|
||||
} catch (SQLException e) {
|
||||
|
||||
Loading…
Reference in New Issue
Block a user