159 lines
6.2 KiB
Java
159 lines
6.2 KiB
Java
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<Void> 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();
|
||
}
|
||
}
|