Files
netdisk-fast-download/web-service/src/main/java/cn/qaiu/lz/AppMain.java
T

225 lines
11 KiB
Java

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程序入口
*
* <br>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<Object, Object> localMap = VertxHolder.getVertxInstance().sharedData().getLocalMap(LOCAL);
// 代理
if (jsonObject.containsKey(ConfigConstant.PROXY)) {
JsonArray proxyJsonArray = jsonObject.getJsonArray(ConfigConstant.PROXY);
if (proxyJsonArray != null) {
JsonObject jsonObject1 = new JsonObject();
proxyJsonArray.forEach(proxyJson -> {
String panTypes = ((JsonObject)proxyJson).getString("panTypes");
if (!panTypes.isEmpty()) {
for (String s : panTypes.split(",")) {
jsonObject1.put(s, proxyJson);
}
}
});
localMap.put("proxy", jsonObject1);
}
}
// 认证
if (jsonObject.containsKey(ConfigConstant.AUTHS)) {
JsonObject auths = jsonObject.getJsonObject(ConfigConstant.AUTHS);
localMap.put(ConfigConstant.AUTHS, auths);
}
}
/**
* 在启动时加载所有已发布的演练场解析器
*/
private static void loadPlaygroundParsers(String accessAddr) {
DbService dbService = AsyncServiceUtil.getAsyncServiceInstance(DbService.class);
dbService.getEnabledPlaygroundParsersForLoad().onSuccess(result -> {
JsonArray parsers = result.getJsonArray("data");
if (parsers != null) {
int loadedCount = 0;
for (int i = 0; i < parsers.size(); i++) {
JsonObject parser = parsers.getJsonObject(i);
try {
String jsCode = parser.getString("jsCode");
if (jsCode == null || jsCode.trim().isEmpty()) {
log.error("加载演练场解析器失败: {} - JavaScript代码为空", parser.getString("name"));
continue;
}
CustomParserConfig config = JsScriptMetadataParser.parseScript(jsCode);
CustomParserRegistry.register(config);
loadedCount++;
log.info("已加载演练场解析器: {} ({})",
config.getDisplayName(), config.getType());
} catch (Exception e) {
String parserName = parser.getString("name");
String errorMsg = e.getMessage();
log.error("加载演练场解析器失败: {} - {}", parserName, errorMsg, e);
// 如果是require相关错误,提供更详细的提示
if (errorMsg != null && errorMsg.contains("require")) {
log.error("提示:演练场解析器不支持CommonJS模块系统(require),请确保代码使用ES5.1语法");
}
}
}
log.info("演练场解析器加载完成,共加载 {} 个解析器", loadedCount);
} else {
log.info("未找到已发布的演练场解析器");
}
}).onFailure(e -> {
log.error("加载演练场解析器列表失败", e);
}).onComplete(ar -> {
log.info("服务已启动,可通过 {} 访问页面", accessAddr);
});
}
}