SHA256
feat(update): server push-команда на массовое обновление UI и формат версий
This commit is contained in:
@@ -30,6 +30,7 @@ public final class DebugApiConfigurator {
|
||||
addServlet(context, new DebugClientsServlet(tokenProvider), "/debug/ws/clients");
|
||||
addServlet(context, new DebugConnectServlet(tokenProvider), "/debug/ws/connect");
|
||||
addServlet(context, new DebugLogsServlet(tokenProvider), "/debug/ws/logs");
|
||||
addServlet(context, new DebugUiReloadAllServlet(tokenProvider), "/debug/ws/ui-reload-all");
|
||||
log.info("✅ Debug API включены настройкой {}=true", CFG_DEBUG_API_ENABLED);
|
||||
}
|
||||
|
||||
|
||||
@@ -51,4 +51,10 @@ public final class DebugTokenProvider {
|
||||
String actual = authorizationHeader.substring("Bearer ".length()).trim();
|
||||
return token.equals(actual);
|
||||
}
|
||||
|
||||
public boolean matchesRawToken(String rawToken) {
|
||||
if (!isEnabled()) return false;
|
||||
if (rawToken == null || rawToken.isBlank()) return false;
|
||||
return token.equals(rawToken.trim());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,133 @@
|
||||
package server.debug;
|
||||
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.fasterxml.jackson.databind.node.ObjectNode;
|
||||
import jakarta.servlet.http.HttpServlet;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import server.logic.ws_protocol.JSON.ActiveConnectionsRegistry;
|
||||
import server.logic.ws_protocol.JSON.ConnectionContext;
|
||||
import server.logic.ws_protocol.JSON.push.WsEventSender;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* POST /debug/ws/ui-reload-all
|
||||
*
|
||||
* Заголовки:
|
||||
* - X-Debug-Token: <token> (рекомендуется)
|
||||
* - или Authorization: Bearer <token>
|
||||
*
|
||||
* Тело (опционально):
|
||||
* {
|
||||
* "reason": "deploy_ui",
|
||||
* "reloadAfterMs": 800
|
||||
* }
|
||||
*/
|
||||
public class DebugUiReloadAllServlet extends HttpServlet {
|
||||
private static final ObjectMapper MAPPER = new ObjectMapper();
|
||||
|
||||
private final DebugTokenProvider tokenProvider;
|
||||
|
||||
public DebugUiReloadAllServlet(DebugTokenProvider tokenProvider) {
|
||||
this.tokenProvider = tokenProvider;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws IOException {
|
||||
if (!tokenProvider.isEnabled()) {
|
||||
writeError(resp, 503, "DEBUG_DISABLED", "Debug API отключен: .debug-token не найден или пуст");
|
||||
return;
|
||||
}
|
||||
|
||||
String headerToken = req.getHeader("X-Debug-Token");
|
||||
String bearerHeader = req.getHeader("Authorization");
|
||||
boolean authorized = tokenProvider.matchesRawToken(headerToken) || tokenProvider.matchesBearerHeader(bearerHeader);
|
||||
if (!authorized) {
|
||||
writeError(resp, 401, "UNAUTHORIZED", "Неверный debug token");
|
||||
return;
|
||||
}
|
||||
|
||||
JsonNode body = null;
|
||||
try {
|
||||
if (req.getContentLengthLong() != 0) {
|
||||
body = MAPPER.readTree(req.getInputStream());
|
||||
}
|
||||
} catch (Exception e) {
|
||||
writeError(resp, 400, "BAD_JSON", "Тело запроса должно быть JSON");
|
||||
return;
|
||||
}
|
||||
|
||||
String reason = safe(text(body, "reason"));
|
||||
if (reason.isBlank()) reason = "manual_debug_api";
|
||||
int reloadAfterMs = clampInt(body == null ? 700 : body.path("reloadAfterMs").asInt(700), 100, 15_000);
|
||||
|
||||
long issuedAtMs = System.currentTimeMillis();
|
||||
Set<ConnectionContext> all = ActiveConnectionsRegistry.getInstance().getAllConnectionsSnapshot();
|
||||
int totalConnections = 0;
|
||||
int sent = 0;
|
||||
|
||||
for (ConnectionContext ctx : all) {
|
||||
if (ctx == null || ctx.getWsSession() == null || !ctx.getWsSession().isOpen()) continue;
|
||||
totalConnections += 1;
|
||||
|
||||
ObjectNode payload = MAPPER.createObjectNode();
|
||||
payload.put("reason", reason);
|
||||
payload.put("issuedAtMs", issuedAtMs);
|
||||
payload.put("reloadAfterMs", reloadAfterMs);
|
||||
|
||||
String sessionId = safe(ctx.getSessionId());
|
||||
String eventId = "evt-ui-reload-" + issuedAtMs + "-" + (sessionId.isBlank() ? totalConnections : sessionId);
|
||||
if (WsEventSender.sendEvent(ctx, "ForceUiReload", eventId, payload)) {
|
||||
sent += 1;
|
||||
}
|
||||
}
|
||||
|
||||
ObjectNode payload = MAPPER.createObjectNode();
|
||||
payload.put("accepted", sent > 0);
|
||||
payload.put("reason", reason);
|
||||
payload.put("reloadAfterMs", reloadAfterMs);
|
||||
payload.put("issuedAtMs", issuedAtMs);
|
||||
payload.put("totalConnections", totalConnections);
|
||||
payload.put("sentCount", sent);
|
||||
payload.put("skippedCount", Math.max(0, totalConnections - sent));
|
||||
writeOk(resp, payload);
|
||||
}
|
||||
|
||||
private static int clampInt(int value, int min, int max) {
|
||||
return Math.min(max, Math.max(min, value));
|
||||
}
|
||||
|
||||
private static String text(JsonNode node, String field) {
|
||||
if (node == null) return "";
|
||||
return String.valueOf(node.path(field).asText(""));
|
||||
}
|
||||
|
||||
private static String safe(String value) {
|
||||
return value == null ? "" : value.trim();
|
||||
}
|
||||
|
||||
private static void writeOk(HttpServletResponse resp, ObjectNode payload) throws IOException {
|
||||
ObjectNode root = MAPPER.createObjectNode();
|
||||
root.put("ok", true);
|
||||
root.set("payload", payload == null ? MAPPER.createObjectNode() : payload);
|
||||
writeJson(resp, 200, root);
|
||||
}
|
||||
|
||||
private static void writeError(HttpServletResponse resp, int status, String code, String message) throws IOException {
|
||||
ObjectNode root = MAPPER.createObjectNode();
|
||||
root.put("ok", false);
|
||||
root.put("code", code);
|
||||
root.put("message", message);
|
||||
writeJson(resp, status, root);
|
||||
}
|
||||
|
||||
private static void writeJson(HttpServletResponse resp, int status, ObjectNode root) throws IOException {
|
||||
resp.setStatus(status);
|
||||
resp.setCharacterEncoding("UTF-8");
|
||||
resp.setContentType("application/json; charset=UTF-8");
|
||||
resp.getWriter().write(MAPPER.writeValueAsString(root));
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user