package cn.qaiu.lz;
import cn.qaiu.WebClientVertxInit;
import cn.qaiu.db.pool.JDBCPoolInit;
import cn.qaiu.lz.common.cache.CacheConfigLoader;
import cn.qaiu.lz.common.cache.CacheManager;
import cn.qaiu.lz.common.interceptorImpl.RateLimiter;
import cn.qaiu.lz.web.config.PlaygroundConfig;
import cn.qaiu.lz.web.service.DbService;
import cn.qaiu.lz.web.service.impl.ShoutServiceImpl;
import cn.qaiu.parser.custom.CustomParserConfig;
import cn.qaiu.parser.custom.CustomParserRegistry;
import cn.qaiu.parser.customjs.JsScriptMetadataParser;
import cn.qaiu.vx.core.Deploy;
import cn.qaiu.vx.core.util.AsyncServiceUtil;
import cn.qaiu.vx.core.util.ConfigConstant;
import cn.qaiu.vx.core.util.VertxHolder;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
import io.vertx.core.json.JsonArray;
import io.vertx.core.json.JsonObject;
import io.vertx.core.json.jackson.DatabindCodec;
import io.vertx.core.shareddata.LocalMap;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.time.DateFormatUtils;
import org.slf4j.LoggerFactory;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Date;
import static cn.qaiu.vx.core.util.ConfigConstant.LOCAL;
/**
* vertx程序入口
*
*
Create date 2021-05-08 13:00:01
* @author qaiu yyzy
*/
@Slf4j
public class AppMain {
public static void main(String[] args) {
applyRuntimeLogLevelOverride();
Deploy deploy = Deploy.instance();
// 先阻断应用级定时任务,再让 Vert.x 停入口和 verticle。
deploy.addPreShutdownTask(CacheManager::cancelPeriodicCleanup);
deploy.addPreShutdownTask(ShoutServiceImpl::cancelCleanup);
// Vert.x 停完后再关数据库和解析器共享资源,避免请求还在路上就先关底层 client。
deploy.addPostShutdownTask(() -> JDBCPoolInit.instance().close());
deploy.addPostShutdownTask(cn.qaiu.parser.customjs.JsParserExecutor::shutdownExecutor);
deploy.addPostShutdownTask(cn.qaiu.parser.customjs.JsPlaygroundExecutor::shutdownPools);
deploy.addPostShutdownTask(cn.qaiu.parser.customjs.JsHttpClient::shutdownSharedClient);
deploy.addPostShutdownTask(cn.qaiu.parser.PanBase::shutdownSharedClients);
deploy.addPostShutdownTask(cn.qaiu.parser.IPanTool::shutdownCloseAfterScheduler);
deploy.addPostShutdownTask(cn.qaiu.parser.impl.PodTool::shutdownWorkerExecutor);
// start
deploy.start(args, AppMain::exec);
}
private static void applyRuntimeLogLevelOverride() {
String levelName = System.getProperty("NFD_LOG_LEVEL");
if (levelName == null || levelName.isBlank()) {
levelName = System.getenv("NFD_LOG_LEVEL");
}
if (levelName == null || levelName.isBlank()) {
return;
}
try {
var level = ch.qos.logback.classic.Level.toLevel(levelName, null);
if (level == null) {
log.warn("忽略无效的 NFD_LOG_LEVEL: {}", levelName);
return;
}
var logger = LoggerFactory.getLogger("cn.qaiu");
if (logger instanceof ch.qos.logback.classic.Logger logbackLogger) {
logbackLogger.setLevel(level);
log.info("cn.qaiu 日志级别已覆盖为 {}", level);
}
} catch (Exception e) {
log.warn("覆盖 cn.qaiu 日志级别失败: {}", e.getMessage());
}
}
/**
* 框架回调方法
* 初始化数据库/缓存等
*
* @param jsonObject 配置
*/
private static void exec(JsonObject jsonObject) {
WebClientVertxInit.init(VertxHolder.getVertxInstance());
DatabindCodec.mapper().registerModule(new JavaTimeModule());
// 演练场配置要先加载,后续启动流程才能按开关决定是否注册动态解析器。
PlaygroundConfig.loadFromJson(jsonObject);
// 限流
if (jsonObject.containsKey("rateLimit")) {
JsonObject rateLimit = jsonObject.getJsonObject("rateLimit");
RateLimiter.init(rateLimit);
}
// 数据库
if (jsonObject.getJsonObject(ConfigConstant.SERVER).getBoolean("enableDatabase")) {
JDBCPoolInit.builder().config(jsonObject.getJsonObject("dataSource"))
.build()
.initPool().onSuccess(PreparedStatement -> {
VertxHolder.getVertxInstance().setTimer(1000, id -> {
System.out.println(DateFormatUtils.format(new Date(), "yyyy-MM-dd HH:mm:ss.SSS"));
System.out.println("数据库连接成功");
// 加载演练场解析器
String addr = jsonObject.getJsonObject(ConfigConstant.SERVER).getString("domainName");
if (addr == null || addr.isBlank()) {
addr = "http://127.0.0.1:" + jsonObject.getJsonObject(ConfigConstant.SERVER).getInteger("port", 6400);
}
// 读取代理配置获取前端页面端口(同步读取小文件,仅启动时执行一次)
String proxyConfName = jsonObject.getString("proxyConf", "server-proxy");
String pageAddr = addr;
try {
String configFile = proxyConfName + ".yml";
// 与 Deploy 保持一致:优先当前目录,其次 resources/
Path configPath = Path.of(configFile);
if (!Files.exists(configPath)) {
configPath = Path.of("resources", configFile);
}
if (Files.exists(configPath)) {
String yamlContent = Files.readString(configPath);
// 匹配项目约定的 YAML 格式: "- listen: 8080"
java.util.regex.Matcher m = java.util.regex.Pattern
.compile("^\\s*-\\s+listen:\\s*(\\d+)", java.util.regex.Pattern.MULTILINE)
.matcher(yamlContent);
if (m.find()) {
int pagePort = Integer.parseInt(m.group(1));
pageAddr = "http://127.0.0.1:" + pagePort;
}
}
} catch (Exception e) {
log.warn("读取代理配置失败,使用默认页面地址: {}", e.getMessage());
}
if (PlaygroundConfig.getInstance().isEnabled()) {
loadPlaygroundParsers(pageAddr);
} else {
log.info("演练场功能已禁用,跳过加载演练场解析器");
log.info("服务已启动,可通过 {} 访问页面", pageAddr);
}
System.out.println("启动成功: \n本地服务地址: " + addr);
});
});
}
// 缓存
if (jsonObject.containsKey(ConfigConstant.CACHE)) {
CacheConfigLoader.init(jsonObject.getJsonObject(ConfigConstant.CACHE));
}
LocalMap