Files
iiko-connector/src/main/java/su/xserver/iikocon/MainVerticle.java
Danil-Bodry 58c43fddc5 up docker-compose.yml
+ node js in gradle
2026-04-17 15:03:49 +03:00

210 lines
6.9 KiB
Java

package su.xserver.iikocon;
import io.vertx.config.ConfigRetriever;
import io.vertx.config.ConfigRetrieverOptions;
import io.vertx.config.ConfigStoreOptions;
import io.vertx.core.AbstractVerticle;
import io.vertx.core.Promise;
import io.vertx.core.http.HttpServer;
import io.vertx.core.json.JsonObject;
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.LocalSessionStore;
import io.vertx.ext.web.sstore.SessionStore;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import su.xserver.iikocon.config.AppConfig;
import su.xserver.iikocon.service.DataBaseService;
import su.xserver.iikocon.service.HealthCheckService;
import su.xserver.iikocon.service.RedisService;
public class MainVerticle extends AbstractVerticle {
private final Logger log = LoggerFactory.getLogger("[MainVerticle]");
private DataBaseService db;
private RedisService redis;
private HttpServer httpServer;
private AppConfig config;
private UserService userService;
@Override
public void start(Promise<Void> startPromise) {
ConfigStoreOptions classpathStore = new ConfigStoreOptions()
.setType("file")
.setFormat("json")
.setConfig(new JsonObject().put("path", "config.json").put("hierarchical", true))
.setOptional(false);
ConfigRetrieverOptions options = new ConfigRetrieverOptions()
.addStore(classpathStore);
ConfigRetriever retriever = ConfigRetriever.create(vertx, options);
retriever.getConfig()
.onSuccess(cfg -> {
config = AppConfig.from(cfg);
db = new DataBaseService(vertx, config.database);
redis = new RedisService(vertx, config.redis);
// Инициализация сервисов
userService = new UserService(db.getPool());
// Инициализация БД (создание таблицы users)
userService.initDatabase()
.onSuccess(v -> log.info("Database initialized successfully"))
.onFailure(err -> {
log.error("Failed to initialize database", err);
startPromise.fail(err);
});
Router router = initRouter();
startHttp(router, startPromise);
})
.onFailure(startPromise::fail);
}
private Router initRouter() {
// Настройка сессий (используем LocalSessionStore для простоты)
SessionStore sessionStore = LocalSessionStore.create(vertx);
SessionHandler sessionHandler = SessionHandler.create(sessionStore)
.setSessionCookieName("admin.session")
.setCookieHttpOnlyFlag(true)
.setCookieSecureFlag(false)
.setSessionTimeout(3600000); // 1 час
// Роутер
Router router = Router.router(vertx);
router.route().handler(BodyHandler.create());
router.route().handler(sessionHandler);
// CORS для разработки
router.route().handler(ctx -> {
ctx.response()
.putHeader("Access-Control-Allow-Origin", "*")
.putHeader("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS")
.putHeader("Access-Control-Allow-Headers", "Content-Type, Authorization")
.putHeader("Access-Control-Allow-Credentials", "true");
if (ctx.request().method().name().equals("OPTIONS")) {
ctx.response().end();
} else {
ctx.next();
}
});
// ------ Раздаём Vue статику ------
router.route("/assets/*").handler(StaticHandler.create("webroot/assets"));
router.route("/favicon.ico").handler(ctx -> ctx.response().sendFile("webroot/favicon.ico"));
// ------ SPA fallback: отдаём index.html на все не-API запросы ------
router.route().handler(ctx -> {
if (ctx.request().path().startsWith("/api")) {
ctx.next();
} else {
ctx.response()
.putHeader("Content-Type", "text/html")
.sendFile("webroot/index.html");
}
});
// Health Checks
HealthCheckService healthCheckService = new HealthCheckService(vertx, redis, db);
healthCheckService.registerHealthCheck(router);
// API маршруты
AuthHandler authHandler = new AuthHandler(userService);
SetupHandler setupHandler = new SetupHandler(userService);
// Проверка статуса (нужна ли инициализация)
router.get("/api/status").handler(setupHandler::checkStatus);
// Регистрация первого администратора
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()
.putHeader("Content-Type", "application/json")
.end(ar.result().encode());
} else {
rc.response().setStatusCode(500).end(ar.cause().getMessage());
}
}));
// Получение текущего пользователя
router.get("/api/admin/me").handler(rc -> {
Integer userId = rc.session().get("userId");
if (userId != null) {
rc.response()
.putHeader("Content-Type", "application/json")
.end(new JsonObject()
.put("id", userId)
.put("login", rc.session().get("login"))
.encode());
} else {
rc.response().setStatusCode(401).end();
}
});
// Статическая раздача фронтенда
// router.route("/*").handler(StaticHandler.create("webroot")
// .setCachingEnabled(false)
// .setIndexPage("index.html"));
return router;
}
private void startHttp(Router router, Promise<Void> startPromise) {
// Запуск HTTP-сервера
httpServer = vertx.createHttpServer();
httpServer.requestHandler(router).listen(config.server.port, config.server.host)
.onSuccess(server -> {
log.info("HTTP server started on port {}", server.actualPort());
startPromise.complete();
})
.onFailure(throwable -> {
log.error(throwable.getMessage());
startPromise.fail(throwable);
});
}
@Override
public void stop(Promise<Void> stopPromise) {
this.httpServer.close()
.onSuccess(server -> {
log.info("Stop HTTP server");
this.db.disconnect();
this.redis.disconnect();
this.vertx.close()
.onSuccess(vertx -> {
log.info("Stop Vert.x");
stopPromise.complete();
})
.onFailure(throwable -> log.info(throwable.getMessage()));
})
.onFailure(throwable -> log.info(throwable.getMessage()));
}
}