mirror of
https://github.com/qaiu/netdisk-fast-download.git
synced 2025-12-17 12:53:02 +00:00
js演练场
This commit is contained in:
@@ -0,0 +1,323 @@
|
||||
package cn.qaiu.parser.customjs;
|
||||
|
||||
import cn.qaiu.WebClientVertxInit;
|
||||
import cn.qaiu.entity.FileInfo;
|
||||
import cn.qaiu.entity.ShareLinkInfo;
|
||||
import io.vertx.core.Future;
|
||||
import io.vertx.core.WorkerExecutor;
|
||||
import io.vertx.core.json.JsonObject;
|
||||
import org.openjdk.nashorn.api.scripting.NashornScriptEngineFactory;
|
||||
import org.openjdk.nashorn.api.scripting.ScriptObjectMirror;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import javax.script.ScriptEngine;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* JavaScript演练场执行器
|
||||
* 用于临时执行JavaScript代码,不注册到解析器注册表
|
||||
*
|
||||
* @author <a href="https://qaiu.top">QAIU</a>
|
||||
*/
|
||||
public class JsPlaygroundExecutor {
|
||||
|
||||
private static final Logger log = LoggerFactory.getLogger(JsPlaygroundExecutor.class);
|
||||
|
||||
private static final WorkerExecutor EXECUTOR = WebClientVertxInit.get().createSharedWorkerExecutor("playground-executor", 16);
|
||||
|
||||
private final ShareLinkInfo shareLinkInfo;
|
||||
private final String jsCode;
|
||||
private final ScriptEngine engine;
|
||||
private final JsHttpClient httpClient;
|
||||
private final JsPlaygroundLogger playgroundLogger;
|
||||
private final JsShareLinkInfoWrapper shareLinkInfoWrapper;
|
||||
|
||||
/**
|
||||
* 创建演练场执行器
|
||||
*
|
||||
* @param shareLinkInfo 分享链接信息
|
||||
* @param jsCode JavaScript代码
|
||||
*/
|
||||
public JsPlaygroundExecutor(ShareLinkInfo shareLinkInfo, String jsCode) {
|
||||
this.shareLinkInfo = shareLinkInfo;
|
||||
this.jsCode = jsCode;
|
||||
|
||||
// 检查是否有代理配置
|
||||
JsonObject proxyConfig = null;
|
||||
if (shareLinkInfo.getOtherParam().containsKey("proxy")) {
|
||||
proxyConfig = (JsonObject) shareLinkInfo.getOtherParam().get("proxy");
|
||||
}
|
||||
|
||||
this.httpClient = new JsHttpClient(proxyConfig);
|
||||
this.playgroundLogger = new JsPlaygroundLogger();
|
||||
this.shareLinkInfoWrapper = new JsShareLinkInfoWrapper(shareLinkInfo);
|
||||
this.engine = initEngine();
|
||||
}
|
||||
|
||||
/**
|
||||
* 初始化JavaScript引擎(带安全限制)
|
||||
*/
|
||||
private ScriptEngine initEngine() {
|
||||
try {
|
||||
// 使用安全的ClassFilter创建Nashorn引擎
|
||||
NashornScriptEngineFactory factory = new NashornScriptEngineFactory();
|
||||
|
||||
// 正确的方法签名: getScriptEngine(String[] args, ClassLoader appLoader, ClassFilter classFilter)
|
||||
ScriptEngine engine = factory.getScriptEngine(new String[0], null, new SecurityClassFilter());
|
||||
|
||||
if (engine == null) {
|
||||
throw new RuntimeException("无法创建JavaScript引擎,请确保Nashorn可用");
|
||||
}
|
||||
|
||||
// 注入Java对象到JavaScript环境
|
||||
engine.put("http", httpClient);
|
||||
engine.put("logger", playgroundLogger);
|
||||
engine.put("shareLinkInfo", shareLinkInfoWrapper);
|
||||
|
||||
// 禁用Java对象访问
|
||||
engine.eval("var Java = undefined;");
|
||||
engine.eval("var JavaImporter = undefined;");
|
||||
engine.eval("var Packages = undefined;");
|
||||
engine.eval("var javax = undefined;");
|
||||
engine.eval("var org = undefined;");
|
||||
engine.eval("var com = undefined;");
|
||||
|
||||
playgroundLogger.infoJava("🔒 安全的JavaScript引擎初始化成功(演练场)");
|
||||
|
||||
// 执行JavaScript代码
|
||||
engine.eval(jsCode);
|
||||
|
||||
log.debug("JavaScript引擎初始化成功(演练场)");
|
||||
return engine;
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("JavaScript引擎初始化失败(演练场)", e);
|
||||
throw new RuntimeException("JavaScript引擎初始化失败: " + e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 执行parse方法(异步)
|
||||
*
|
||||
* @return Future包装的执行结果
|
||||
*/
|
||||
public Future<String> executeParseAsync() {
|
||||
// 在worker线程中执行,避免阻塞事件循环
|
||||
return EXECUTOR.executeBlocking(() -> {
|
||||
playgroundLogger.infoJava("开始执行parse方法");
|
||||
try {
|
||||
Object parseFunction = engine.get("parse");
|
||||
if (parseFunction == null) {
|
||||
playgroundLogger.errorJava("JavaScript代码中未找到parse函数");
|
||||
throw new RuntimeException("JavaScript代码中未找到parse函数");
|
||||
}
|
||||
|
||||
if (parseFunction instanceof ScriptObjectMirror parseMirror) {
|
||||
playgroundLogger.debugJava("调用parse函数");
|
||||
log.debug("[JsPlaygroundExecutor] 调用parse函数,当前日志数量: {}", playgroundLogger.size());
|
||||
Object result = parseMirror.call(null, shareLinkInfoWrapper, httpClient, playgroundLogger);
|
||||
log.debug("[JsPlaygroundExecutor] parse函数执行完成,当前日志数量: {}", playgroundLogger.size());
|
||||
|
||||
if (result instanceof String) {
|
||||
playgroundLogger.infoJava("解析成功,返回结果: " + result);
|
||||
return (String) result;
|
||||
} else {
|
||||
String errorMsg = "parse方法返回值类型错误,期望String,实际: " +
|
||||
(result != null ? result.getClass().getSimpleName() : "null");
|
||||
playgroundLogger.errorJava(errorMsg);
|
||||
throw new RuntimeException(errorMsg);
|
||||
}
|
||||
} else {
|
||||
playgroundLogger.errorJava("parse函数类型错误");
|
||||
throw new RuntimeException("parse函数类型错误");
|
||||
}
|
||||
} catch (Exception e) {
|
||||
playgroundLogger.errorJava("执行parse方法失败: " + e.getMessage(), e);
|
||||
throw e;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 执行parseFileList方法(异步)
|
||||
*
|
||||
* @return Future包装的文件列表
|
||||
*/
|
||||
public Future<List<FileInfo>> executeParseFileListAsync() {
|
||||
// 在worker线程中执行,避免阻塞事件循环
|
||||
return EXECUTOR.executeBlocking(() -> {
|
||||
playgroundLogger.infoJava("开始执行parseFileList方法");
|
||||
try {
|
||||
Object parseFileListFunction = engine.get("parseFileList");
|
||||
if (parseFileListFunction == null) {
|
||||
playgroundLogger.errorJava("JavaScript代码中未找到parseFileList函数");
|
||||
throw new RuntimeException("JavaScript代码中未找到parseFileList函数");
|
||||
}
|
||||
|
||||
if (parseFileListFunction instanceof ScriptObjectMirror parseFileListMirror) {
|
||||
playgroundLogger.debugJava("调用parseFileList函数");
|
||||
Object result = parseFileListMirror.call(null, shareLinkInfoWrapper, httpClient, playgroundLogger);
|
||||
|
||||
if (result instanceof ScriptObjectMirror resultMirror) {
|
||||
List<FileInfo> fileList = convertToFileInfoList(resultMirror);
|
||||
playgroundLogger.infoJava("文件列表解析成功,共 " + fileList.size() + " 个文件");
|
||||
return fileList;
|
||||
} else {
|
||||
String errorMsg = "parseFileList方法返回值类型错误,期望数组,实际: " +
|
||||
(result != null ? result.getClass().getSimpleName() : "null");
|
||||
playgroundLogger.errorJava(errorMsg);
|
||||
throw new RuntimeException(errorMsg);
|
||||
}
|
||||
} else {
|
||||
playgroundLogger.errorJava("parseFileList函数类型错误");
|
||||
throw new RuntimeException("parseFileList函数类型错误");
|
||||
}
|
||||
} catch (Exception e) {
|
||||
playgroundLogger.errorJava("执行parseFileList方法失败: " + e.getMessage(), e);
|
||||
throw e;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 执行parseById方法(异步)
|
||||
*
|
||||
* @return Future包装的执行结果
|
||||
*/
|
||||
public Future<String> executeParseByIdAsync() {
|
||||
// 在worker线程中执行,避免阻塞事件循环
|
||||
return EXECUTOR.executeBlocking(() -> {
|
||||
playgroundLogger.infoJava("开始执行parseById方法");
|
||||
try {
|
||||
Object parseByIdFunction = engine.get("parseById");
|
||||
if (parseByIdFunction == null) {
|
||||
playgroundLogger.errorJava("JavaScript代码中未找到parseById函数");
|
||||
throw new RuntimeException("JavaScript代码中未找到parseById函数");
|
||||
}
|
||||
|
||||
if (parseByIdFunction instanceof ScriptObjectMirror parseByIdMirror) {
|
||||
playgroundLogger.debugJava("调用parseById函数");
|
||||
Object result = parseByIdMirror.call(null, shareLinkInfoWrapper, httpClient, playgroundLogger);
|
||||
|
||||
if (result instanceof String) {
|
||||
playgroundLogger.infoJava("按ID解析成功: " + result);
|
||||
return (String) result;
|
||||
} else {
|
||||
String errorMsg = "parseById方法返回值类型错误,期望String,实际: " +
|
||||
(result != null ? result.getClass().getSimpleName() : "null");
|
||||
playgroundLogger.errorJava(errorMsg);
|
||||
throw new RuntimeException(errorMsg);
|
||||
}
|
||||
} else {
|
||||
playgroundLogger.errorJava("parseById函数类型错误");
|
||||
throw new RuntimeException("parseById函数类型错误");
|
||||
}
|
||||
} catch (Exception e) {
|
||||
playgroundLogger.errorJava("执行parseById方法失败: " + e.getMessage(), e);
|
||||
throw e;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取日志列表
|
||||
*/
|
||||
public List<JsPlaygroundLogger.LogEntry> getLogs() {
|
||||
List<JsPlaygroundLogger.LogEntry> logs = playgroundLogger.getLogs();
|
||||
System.out.println("[JsPlaygroundExecutor] 获取日志,数量: " + logs.size());
|
||||
return logs;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取ShareLinkInfo对象
|
||||
*/
|
||||
public ShareLinkInfo getShareLinkInfo() {
|
||||
return shareLinkInfo;
|
||||
}
|
||||
|
||||
/**
|
||||
* 将JavaScript对象数组转换为FileInfo列表
|
||||
*/
|
||||
private List<FileInfo> convertToFileInfoList(ScriptObjectMirror resultMirror) {
|
||||
List<FileInfo> fileList = new ArrayList<>();
|
||||
|
||||
if (resultMirror.isArray()) {
|
||||
for (int i = 0; i < resultMirror.size(); i++) {
|
||||
Object item = resultMirror.get(String.valueOf(i));
|
||||
if (item instanceof ScriptObjectMirror) {
|
||||
FileInfo fileInfo = convertToFileInfo((ScriptObjectMirror) item);
|
||||
if (fileInfo != null) {
|
||||
fileList.add(fileInfo);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return fileList;
|
||||
}
|
||||
|
||||
/**
|
||||
* 将JavaScript对象转换为FileInfo
|
||||
*/
|
||||
private FileInfo convertToFileInfo(ScriptObjectMirror itemMirror) {
|
||||
try {
|
||||
FileInfo fileInfo = new FileInfo();
|
||||
|
||||
// 设置基本字段
|
||||
if (itemMirror.hasMember("fileName")) {
|
||||
fileInfo.setFileName(itemMirror.getMember("fileName").toString());
|
||||
}
|
||||
if (itemMirror.hasMember("fileId")) {
|
||||
fileInfo.setFileId(itemMirror.getMember("fileId").toString());
|
||||
}
|
||||
if (itemMirror.hasMember("fileType")) {
|
||||
fileInfo.setFileType(itemMirror.getMember("fileType").toString());
|
||||
}
|
||||
if (itemMirror.hasMember("size")) {
|
||||
Object size = itemMirror.getMember("size");
|
||||
if (size instanceof Number) {
|
||||
fileInfo.setSize(((Number) size).longValue());
|
||||
}
|
||||
}
|
||||
if (itemMirror.hasMember("sizeStr")) {
|
||||
fileInfo.setSizeStr(itemMirror.getMember("sizeStr").toString());
|
||||
}
|
||||
if (itemMirror.hasMember("createTime")) {
|
||||
fileInfo.setCreateTime(itemMirror.getMember("createTime").toString());
|
||||
}
|
||||
if (itemMirror.hasMember("updateTime")) {
|
||||
fileInfo.setUpdateTime(itemMirror.getMember("updateTime").toString());
|
||||
}
|
||||
if (itemMirror.hasMember("createBy")) {
|
||||
fileInfo.setCreateBy(itemMirror.getMember("createBy").toString());
|
||||
}
|
||||
if (itemMirror.hasMember("downloadCount")) {
|
||||
Object downloadCount = itemMirror.getMember("downloadCount");
|
||||
if (downloadCount instanceof Number) {
|
||||
fileInfo.setDownloadCount(((Number) downloadCount).intValue());
|
||||
}
|
||||
}
|
||||
if (itemMirror.hasMember("fileIcon")) {
|
||||
fileInfo.setFileIcon(itemMirror.getMember("fileIcon").toString());
|
||||
}
|
||||
if (itemMirror.hasMember("panType")) {
|
||||
fileInfo.setPanType(itemMirror.getMember("panType").toString());
|
||||
}
|
||||
if (itemMirror.hasMember("parserUrl")) {
|
||||
fileInfo.setParserUrl(itemMirror.getMember("parserUrl").toString());
|
||||
}
|
||||
if (itemMirror.hasMember("previewUrl")) {
|
||||
fileInfo.setPreviewUrl(itemMirror.getMember("previewUrl").toString());
|
||||
}
|
||||
|
||||
return fileInfo;
|
||||
|
||||
} catch (Exception e) {
|
||||
playgroundLogger.errorJava("转换FileInfo对象失败", e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,182 @@
|
||||
package cn.qaiu.parser.customjs;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 演练场日志收集器
|
||||
* 收集JavaScript执行过程中的日志信息
|
||||
* 注意:为避免Nashorn对Java重载方法的选择问题,所有日志方法都使用Object参数
|
||||
*
|
||||
* @author <a href="https://qaiu.top">QAIU</a>
|
||||
*/
|
||||
public class JsPlaygroundLogger {
|
||||
|
||||
// 使用线程安全的列表
|
||||
private final List<LogEntry> logs = Collections.synchronizedList(new ArrayList<>());
|
||||
|
||||
/**
|
||||
* 日志条目
|
||||
*/
|
||||
public static class LogEntry {
|
||||
private final String level;
|
||||
private final String message;
|
||||
private final long timestamp;
|
||||
private final String source; // "JS" 或 "JAVA"
|
||||
|
||||
public LogEntry(String level, String message, String source) {
|
||||
this.level = level;
|
||||
this.message = message;
|
||||
this.timestamp = System.currentTimeMillis();
|
||||
this.source = source;
|
||||
}
|
||||
|
||||
public String getLevel() {
|
||||
return level;
|
||||
}
|
||||
|
||||
public String getMessage() {
|
||||
return message;
|
||||
}
|
||||
|
||||
public long getTimestamp() {
|
||||
return timestamp;
|
||||
}
|
||||
|
||||
public String getSource() {
|
||||
return source;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 将任意对象转为字符串
|
||||
*/
|
||||
private String toString(Object obj) {
|
||||
if (obj == null) {
|
||||
return "null";
|
||||
}
|
||||
return obj.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* 记录日志(内部方法)
|
||||
* @param level 日志级别
|
||||
* @param message 日志消息
|
||||
* @param source 日志来源:"JS" 或 "JAVA"
|
||||
*/
|
||||
private void log(String level, Object message, String source) {
|
||||
String msg = toString(message);
|
||||
logs.add(new LogEntry(level, msg, source));
|
||||
System.out.println("[" + source + "PlaygroundLogger] " + level + ": " + msg);
|
||||
}
|
||||
|
||||
/**
|
||||
* 调试日志(供JavaScript调用)
|
||||
* 使用Object参数避免Nashorn重载选择问题
|
||||
*/
|
||||
public void debug(Object message) {
|
||||
log("DEBUG", message, "JS");
|
||||
}
|
||||
|
||||
/**
|
||||
* 信息日志(供JavaScript调用)
|
||||
* 使用Object参数避免Nashorn重载选择问题
|
||||
*/
|
||||
public void info(Object message) {
|
||||
log("INFO", message, "JS");
|
||||
}
|
||||
|
||||
/**
|
||||
* 警告日志(供JavaScript调用)
|
||||
* 使用Object参数避免Nashorn重载选择问题
|
||||
*/
|
||||
public void warn(Object message) {
|
||||
log("WARN", message, "JS");
|
||||
}
|
||||
|
||||
/**
|
||||
* 错误日志(供JavaScript调用)
|
||||
* 使用Object参数避免Nashorn重载选择问题
|
||||
*/
|
||||
public void error(Object message) {
|
||||
log("ERROR", message, "JS");
|
||||
}
|
||||
|
||||
/**
|
||||
* 错误日志(带异常,供JavaScript调用)
|
||||
*/
|
||||
public void error(Object message, Throwable throwable) {
|
||||
String msg = toString(message);
|
||||
if (throwable != null) {
|
||||
msg = msg + ": " + throwable.getMessage();
|
||||
}
|
||||
logs.add(new LogEntry("ERROR", msg, "JS"));
|
||||
System.out.println("[JSPlaygroundLogger] ERROR: " + msg);
|
||||
}
|
||||
|
||||
// ===== 以下是供Java层调用的内部方法 =====
|
||||
|
||||
/**
|
||||
* 调试日志(供Java层调用)
|
||||
*/
|
||||
public void debugJava(String message) {
|
||||
log("DEBUG", message, "JAVA");
|
||||
}
|
||||
|
||||
/**
|
||||
* 信息日志(供Java层调用)
|
||||
*/
|
||||
public void infoJava(String message) {
|
||||
log("INFO", message, "JAVA");
|
||||
}
|
||||
|
||||
/**
|
||||
* 警告日志(供Java层调用)
|
||||
*/
|
||||
public void warnJava(String message) {
|
||||
log("WARN", message, "JAVA");
|
||||
}
|
||||
|
||||
/**
|
||||
* 错误日志(供Java层调用)
|
||||
*/
|
||||
public void errorJava(String message) {
|
||||
log("ERROR", message, "JAVA");
|
||||
}
|
||||
|
||||
/**
|
||||
* 错误日志(带异常,供Java层调用)
|
||||
*/
|
||||
public void errorJava(String message, Throwable throwable) {
|
||||
String msg = message;
|
||||
if (throwable != null) {
|
||||
msg = msg + ": " + throwable.getMessage();
|
||||
}
|
||||
logs.add(new LogEntry("ERROR", msg, "JAVA"));
|
||||
System.out.println("[JAVAPlaygroundLogger] ERROR: " + msg);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取所有日志
|
||||
*/
|
||||
public List<LogEntry> getLogs() {
|
||||
synchronized (logs) {
|
||||
return new ArrayList<>(logs);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取日志数量
|
||||
*/
|
||||
public int size() {
|
||||
return logs.size();
|
||||
}
|
||||
|
||||
/**
|
||||
* 清空日志
|
||||
*/
|
||||
public void clear() {
|
||||
logs.clear();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user