package su.xserver.iikocon; import io.vertx.core.AbstractVerticle; import io.vertx.core.Promise; import io.vertx.core.json.JsonObject; import io.vertx.ext.healthchecks.HealthChecks; import io.vertx.ext.healthchecks.Status; import io.vertx.ext.web.Router; import io.vertx.ext.web.handler.BodyHandler; import io.vertx.ext.web.handler.SessionHandler; import io.vertx.ext.web.handler.StaticHandler; import io.vertx.ext.web.sstore.SessionStore; import io.vertx.ext.web.sstore.redis.RedisSessionStore; import io.vertx.mysqlclient.MySQLConnectOptions; import io.vertx.redis.client.Redis; import io.vertx.redis.client.RedisAPI; import io.vertx.redis.client.RedisOptions; import io.vertx.sqlclient.Pool; import io.vertx.sqlclient.PoolOptions; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.util.Collections; public class MainVerticle extends AbstractVerticle { private static final Logger log = LoggerFactory.getLogger(MainVerticle.class); private Pool dbPool; private Redis redisClient; private UserService userService; @Override public void start(Promise startPromise) { // Конфигурация из переменных окружения JsonObject config = new JsonObject() .put("db_host", System.getenv().getOrDefault("DB_HOST", "localhost")) .put("db_port", Integer.parseInt(System.getenv().getOrDefault("DB_PORT", "3306"))) .put("db_name", System.getenv().getOrDefault("DB_NAME", "admin_db")) .put("db_user", System.getenv().getOrDefault("DB_USER", "admin_user")) .put("db_password", System.getenv().getOrDefault("DB_PASSWORD", "admin_pass")) .put("redis_host", System.getenv().getOrDefault("REDIS_HOST", "localhost")) .put("redis_port", Integer.parseInt(System.getenv().getOrDefault("REDIS_PORT", "6379"))) .put("http_port", Integer.parseInt(System.getenv().getOrDefault("HTTP_PORT", "8080"))); // Подключение к MariaDB MySQLConnectOptions connectOptions = new MySQLConnectOptions() .setHost(config.getString("db_host")) .setPort(config.getInteger("db_port")) .setDatabase(config.getString("db_name")) .setUser(config.getString("db_user")) .setPassword(config.getString("db_password")); PoolOptions poolOptions = new PoolOptions().setMaxSize(5); dbPool = Pool.pool(vertx, connectOptions, poolOptions); // Подключение к Redis RedisOptions redisOptions = new RedisOptions() .setConnectionString("redis://" + config.getString("redis_host") + ":" + config.getInteger("redis_port")); redisClient = Redis.createClient(vertx, redisOptions); // Инициализация сервисов userService = new UserService(dbPool); // Инициализация БД (создание таблицы users) userService.initDatabase().compose(v -> { // Настройка сессий с Redis SessionStore sessionStore = RedisSessionStore.create(vertx, redisClient); SessionHandler sessionHandler = SessionHandler.create(sessionStore) .setSessionCookieName("admin.session") .setCookieHttpOnlyFlag(true) .setCookieSecureFlag(false) // для разработки, в продакшене true + HTTPS .setSessionTimeout(3600000); // 1 час // Роутер Router router = Router.router(vertx); router.route().handler(BodyHandler.create()); router.route().handler(sessionHandler); // Health Checks HealthChecks hc = HealthChecks.create(vertx); hc.register("database", promise -> dbPool.getConnection().onComplete(ar -> { if (ar.succeeded()) { ar.result().close(); promise.complete(Status.OK()); } else { promise.fail(ar.cause()); } })); hc.register("redis", promise -> { RedisAPI.api(redisClient) .ping(Collections.singletonList("ping")) .onSuccess(response -> { if ("PONG".equals(response.toString())) { promise.complete(Status.OK()); } else { promise.fail("Unexpected ping response: " + response); } }) .onFailure(promise::fail); }); router.get("/health").handler(rc -> hc.checkStatus().onComplete(ar -> { if (ar.succeeded()) { rc.response().end(ar.result().toJson().encodePrettily()); } else { rc.response().setStatusCode(503).end(ar.cause().getMessage()); } })); // Статическая раздача фронтенда (из webroot) router.route("/*").handler(StaticHandler.create("webroot").setCachingEnabled(false).setIndexPage("index.html")); // API маршруты AuthHandler authHandler = new AuthHandler(userService); SetupHandler setupHandler = new SetupHandler(userService); // Регистрация первого администратора (если таблица пуста) router.post("/api/setup").handler(setupHandler::handleSetup); // Логин router.post("/api/login").handler(authHandler::handleLogin); // Выход router.post("/api/logout").handler(authHandler::handleLogout); // Защищённые маршруты (требуют сессии) router.route("/api/admin/*").handler(authHandler::requireAuth); // Пример защищённого эндпоинта - получение списка пользователей router.get("/api/admin/users").handler(rc -> { userService.getAllUsers().onComplete(ar -> { if (ar.succeeded()) { rc.response().end(ar.result().encode()); } else { rc.response().setStatusCode(500).end(ar.cause().getMessage()); } }); }); // Запуск HTTP сервера int port = config.getInteger("http_port"); return vertx.createHttpServer().requestHandler(router).listen(port); }).onComplete(ar -> { if (ar.succeeded()) { log.info("Server started on port {}", config.getInteger("http_port")); startPromise.complete(); } else { log.error("Failed to start", ar.cause()); startPromise.fail(ar.cause()); } }); } @Override public void stop() { if (dbPool != null) dbPool.close(); } }