mirror of
https://github.com/qaiu/netdisk-fast-download.git
synced 2026-01-11 17:04:13 +00:00
Compare commits
7 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e17fb99de4 | ||
|
|
0f926a57ef | ||
|
|
4380bfe0d6 | ||
|
|
8127cd0758 | ||
|
|
71a220f42b | ||
|
|
d3b02676ec | ||
|
|
d8f0dc4f8e |
17
.github/dependabot.yml
vendored
Normal file
17
.github/dependabot.yml
vendored
Normal file
@@ -0,0 +1,17 @@
|
||||
version: 2
|
||||
updates:
|
||||
- package-ecosystem: "maven"
|
||||
directory: "/"
|
||||
open-pull-requests-limit: 10
|
||||
ignore:
|
||||
# 忽略通过 BOM 管理的 Vert.x 依赖
|
||||
# 这些依赖的版本通过 vertx-dependencies BOM 统一管理
|
||||
# 应该通过更新 pom.xml 中的 vertx.version 属性来更新这些依赖
|
||||
- dependency-name: "io.vertx:vertx-web"
|
||||
- dependency-name: "io.vertx:vertx-codegen"
|
||||
- dependency-name: "io.vertx:vertx-config"
|
||||
- dependency-name: "io.vertx:vertx-config-yaml"
|
||||
- dependency-name: "io.vertx:vertx-service-proxy"
|
||||
- dependency-name: "io.vertx:vertx-web-proxy"
|
||||
- dependency-name: "io.vertx:vertx-web-client"
|
||||
|
||||
2
.gitignore
vendored
2
.gitignore
vendored
@@ -29,6 +29,8 @@ target/
|
||||
/src/logs/
|
||||
*.zip
|
||||
sdkTest.log
|
||||
app.yml
|
||||
app-local.yml
|
||||
|
||||
|
||||
#some local files
|
||||
|
||||
@@ -44,7 +44,7 @@ https://nfd-parser.github.io/nfd-preview/preview.html?src=https%3A%2F%2Flz.qaiu.
|
||||
|
||||
## 预览地址
|
||||
[预览地址1](https://lz.qaiu.top)
|
||||
[预览地址2](https://lzzz.qaiu.top)
|
||||
[预览地址2](https://lz0.qaiu.top)
|
||||
[移动/联通/天翼云盘大文件试用版](https://189.qaiu.top)
|
||||
|
||||
main分支依赖JDK17, 提供了JDK11分支[main-jdk11](https://github.com/qaiu/netdisk-fast-download/tree/main-jdk11)
|
||||
|
||||
@@ -23,6 +23,8 @@ import io.vertx.ext.web.RoutingContext;
|
||||
import io.vertx.ext.web.handler.*;
|
||||
import io.vertx.ext.web.handler.sockjs.SockJSHandler;
|
||||
import io.vertx.ext.web.handler.sockjs.SockJSHandlerOptions;
|
||||
import io.vertx.ext.web.sstore.LocalSessionStore;
|
||||
import io.vertx.ext.web.sstore.SessionStore;
|
||||
import javassist.CtClass;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.apache.commons.lang3.tuple.Pair;
|
||||
@@ -98,6 +100,16 @@ public class RouterHandlerFactory implements BaseHttpApi {
|
||||
// 配置文件上传路径
|
||||
mainRouter.route().handler(BodyHandler.create().setUploadsDirectory("uploads"));
|
||||
|
||||
// 配置Session管理 - 用于演练场登录状态持久化
|
||||
// 30天过期时间(毫秒)
|
||||
SessionStore sessionStore = LocalSessionStore.create(VertxHolder.getVertxInstance());
|
||||
SessionHandler sessionHandler = SessionHandler.create(sessionStore)
|
||||
.setSessionTimeout(30L * 24 * 60 * 60 * 1000) // 30天
|
||||
.setSessionCookieName("SESSIONID") // Cookie名称
|
||||
.setCookieHttpOnlyFlag(true) // 防止XSS攻击
|
||||
.setCookieSecureFlag(false); // 非HTTPS环境设置为false
|
||||
mainRouter.route().handler(sessionHandler);
|
||||
|
||||
// 拦截器
|
||||
Set<Handler<RoutingContext>> interceptorSet = getInterceptorSet();
|
||||
Route route0 = mainRouter.route("/*");
|
||||
|
||||
@@ -128,7 +128,9 @@ public class ReverseProxyVerticle extends AbstractVerticle {
|
||||
}
|
||||
|
||||
private HttpServer getHttpsServer(JsonObject proxyConf) {
|
||||
HttpServerOptions httpServerOptions = new HttpServerOptions();
|
||||
HttpServerOptions httpServerOptions = new HttpServerOptions()
|
||||
.setCompressionSupported(true);
|
||||
|
||||
if (proxyConf.containsKey("ssl")) {
|
||||
JsonObject sslConfig = proxyConf.getJsonObject("ssl");
|
||||
|
||||
@@ -182,6 +184,7 @@ public class ReverseProxyVerticle extends AbstractVerticle {
|
||||
} else {
|
||||
staticHandler = StaticHandler.create();
|
||||
}
|
||||
|
||||
if (staticConf.containsKey("directory-listing")) {
|
||||
staticHandler.setDirectoryListing(staticConf.getBoolean("directory-listing"));
|
||||
} else if (staticConf.containsKey("index")) {
|
||||
|
||||
@@ -244,19 +244,17 @@ public class JsHttpClient {
|
||||
|
||||
if (data != null) {
|
||||
if (data instanceof String) {
|
||||
request.sendBuffer(Buffer.buffer((String) data));
|
||||
return request.sendBuffer(Buffer.buffer((String) data));
|
||||
} else if (data instanceof Map) {
|
||||
@SuppressWarnings("unchecked")
|
||||
Map<String, String> mapData = (Map<String, String>) data;
|
||||
request.sendForm(MultiMap.caseInsensitiveMultiMap().addAll(mapData));
|
||||
return request.sendForm(MultiMap.caseInsensitiveMultiMap().addAll(mapData));
|
||||
} else {
|
||||
request.sendJson(data);
|
||||
return request.sendJson(data);
|
||||
}
|
||||
} else {
|
||||
request.send();
|
||||
return request.send();
|
||||
}
|
||||
|
||||
return request.send();
|
||||
});
|
||||
}
|
||||
|
||||
@@ -276,19 +274,17 @@ public class JsHttpClient {
|
||||
|
||||
if (data != null) {
|
||||
if (data instanceof String) {
|
||||
request.sendBuffer(Buffer.buffer((String) data));
|
||||
return request.sendBuffer(Buffer.buffer((String) data));
|
||||
} else if (data instanceof Map) {
|
||||
@SuppressWarnings("unchecked")
|
||||
Map<String, String> mapData = (Map<String, String>) data;
|
||||
request.sendForm(MultiMap.caseInsensitiveMultiMap().addAll(mapData));
|
||||
return request.sendForm(MultiMap.caseInsensitiveMultiMap().addAll(mapData));
|
||||
} else {
|
||||
request.sendJson(data);
|
||||
return request.sendJson(data);
|
||||
}
|
||||
} else {
|
||||
request.send();
|
||||
return request.send();
|
||||
}
|
||||
|
||||
return request.send();
|
||||
});
|
||||
}
|
||||
|
||||
@@ -322,19 +318,17 @@ public class JsHttpClient {
|
||||
|
||||
if (data != null) {
|
||||
if (data instanceof String) {
|
||||
request.sendBuffer(Buffer.buffer((String) data));
|
||||
return request.sendBuffer(Buffer.buffer((String) data));
|
||||
} else if (data instanceof Map) {
|
||||
@SuppressWarnings("unchecked")
|
||||
Map<String, String> mapData = (Map<String, String>) data;
|
||||
request.sendForm(MultiMap.caseInsensitiveMultiMap().addAll(mapData));
|
||||
return request.sendForm(MultiMap.caseInsensitiveMultiMap().addAll(mapData));
|
||||
} else {
|
||||
request.sendJson(data);
|
||||
return request.sendJson(data);
|
||||
}
|
||||
} else {
|
||||
request.send();
|
||||
return request.send();
|
||||
}
|
||||
|
||||
return request.send();
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -111,7 +111,7 @@ public class JsPlaygroundExecutor {
|
||||
playgroundLogger.infoJava("✅ Fetch API和Promise polyfill注入成功");
|
||||
}
|
||||
|
||||
playgroundLogger.infoJava("🔒 安全的JavaScript引擎初始化成功(演练场)");
|
||||
playgroundLogger.infoJava("初始化成功");
|
||||
|
||||
// 执行JavaScript代码
|
||||
engine.eval(jsCode);
|
||||
@@ -183,20 +183,20 @@ public class JsPlaygroundExecutor {
|
||||
// 取消超时任务
|
||||
timeoutTask.cancel(false);
|
||||
|
||||
if (error != null) {
|
||||
if (error != null) {
|
||||
if (error instanceof CancellationException) {
|
||||
String timeoutMsg = "JavaScript执行超时(超过" + EXECUTION_TIMEOUT_SECONDS + "秒),已强制中断";
|
||||
playgroundLogger.errorJava(timeoutMsg);
|
||||
log.error(timeoutMsg);
|
||||
promise.fail(new RuntimeException(timeoutMsg));
|
||||
playgroundLogger.errorJava(timeoutMsg);
|
||||
log.error(timeoutMsg);
|
||||
promise.fail(new RuntimeException(timeoutMsg));
|
||||
} else {
|
||||
Throwable cause = error.getCause();
|
||||
promise.fail(cause != null ? cause : error);
|
||||
}
|
||||
} else {
|
||||
Throwable cause = error.getCause();
|
||||
promise.fail(cause != null ? cause : error);
|
||||
promise.complete(result);
|
||||
}
|
||||
} else {
|
||||
promise.complete(result);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
return promise.future();
|
||||
}
|
||||
@@ -258,20 +258,20 @@ public class JsPlaygroundExecutor {
|
||||
// 取消超时任务
|
||||
timeoutTask.cancel(false);
|
||||
|
||||
if (error != null) {
|
||||
if (error != null) {
|
||||
if (error instanceof CancellationException) {
|
||||
String timeoutMsg = "JavaScript执行超时(超过" + EXECUTION_TIMEOUT_SECONDS + "秒),已强制中断";
|
||||
playgroundLogger.errorJava(timeoutMsg);
|
||||
log.error(timeoutMsg);
|
||||
promise.fail(new RuntimeException(timeoutMsg));
|
||||
playgroundLogger.errorJava(timeoutMsg);
|
||||
log.error(timeoutMsg);
|
||||
promise.fail(new RuntimeException(timeoutMsg));
|
||||
} else {
|
||||
Throwable cause = error.getCause();
|
||||
promise.fail(cause != null ? cause : error);
|
||||
}
|
||||
} else {
|
||||
Throwable cause = error.getCause();
|
||||
promise.fail(cause != null ? cause : error);
|
||||
promise.complete(result);
|
||||
}
|
||||
} else {
|
||||
promise.complete(result);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
return promise.future();
|
||||
}
|
||||
@@ -332,20 +332,20 @@ public class JsPlaygroundExecutor {
|
||||
// 取消超时任务
|
||||
timeoutTask.cancel(false);
|
||||
|
||||
if (error != null) {
|
||||
if (error != null) {
|
||||
if (error instanceof CancellationException) {
|
||||
String timeoutMsg = "JavaScript执行超时(超过" + EXECUTION_TIMEOUT_SECONDS + "秒),已强制中断";
|
||||
playgroundLogger.errorJava(timeoutMsg);
|
||||
log.error(timeoutMsg);
|
||||
promise.fail(new RuntimeException(timeoutMsg));
|
||||
playgroundLogger.errorJava(timeoutMsg);
|
||||
log.error(timeoutMsg);
|
||||
promise.fail(new RuntimeException(timeoutMsg));
|
||||
} else {
|
||||
Throwable cause = error.getCause();
|
||||
promise.fail(cause != null ? cause : error);
|
||||
}
|
||||
} else {
|
||||
Throwable cause = error.getCause();
|
||||
promise.fail(cause != null ? cause : error);
|
||||
promise.complete(result);
|
||||
}
|
||||
} else {
|
||||
promise.complete(result);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
return promise.future();
|
||||
}
|
||||
|
||||
@@ -29,7 +29,7 @@ import java.util.regex.Pattern;
|
||||
*/
|
||||
public class LzTool extends PanBase {
|
||||
|
||||
public static final String SHARE_URL_PREFIX = "https://wwww.lanzoum.com";
|
||||
public static final String SHARE_URL_PREFIX = "https://wwwwp.lanzoup.com";
|
||||
MultiMap headers0 = HeaderUtils.parseHeaders("""
|
||||
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7
|
||||
Accept-Encoding: gzip, deflate
|
||||
|
||||
@@ -549,6 +549,176 @@ public class JsHttpClientTest {
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPostWithJsonString() {
|
||||
System.out.println("\n[测试16] POST请求(JSON字符串) - httpbin.org/post");
|
||||
System.out.println("测试修复:POST请求发送JSON字符串时请求体是否正确发送");
|
||||
|
||||
try {
|
||||
String url = "https://httpbin.org/post";
|
||||
System.out.println("请求URL: " + url);
|
||||
|
||||
// 模拟阿里云盘登录请求格式
|
||||
String jsonData = "{\"grant_type\":\"refresh_token\",\"refresh_token\":\"test_token_123\"}";
|
||||
System.out.println("POST数据(JSON字符串): " + jsonData);
|
||||
|
||||
// 设置Content-Type为application/json
|
||||
httpClient.putHeader("Content-Type", "application/json");
|
||||
System.out.println("设置Content-Type: application/json");
|
||||
System.out.println("开始请求...");
|
||||
|
||||
long startTime = System.currentTimeMillis();
|
||||
JsHttpClient.JsHttpResponse response = httpClient.post(url, jsonData);
|
||||
long endTime = System.currentTimeMillis();
|
||||
|
||||
System.out.println("请求完成,耗时: " + (endTime - startTime) + "ms");
|
||||
System.out.println("状态码: " + response.statusCode());
|
||||
|
||||
String body = response.body();
|
||||
System.out.println("响应体(前500字符): " + (body != null && body.length() > 500 ? body.substring(0, 500) + "..." : body));
|
||||
|
||||
// 验证结果
|
||||
assertNotNull("响应不能为null", response);
|
||||
assertEquals("状态码应该是200", 200, response.statusCode());
|
||||
assertNotNull("响应体不能为null", body);
|
||||
// 验证请求体是否正确发送(httpbin会回显请求数据)
|
||||
assertTrue("响应体应该包含发送的JSON数据",
|
||||
body.contains("grant_type") || body.contains("refresh_token"));
|
||||
|
||||
System.out.println("✓ 测试通过 - POST请求体已正确发送");
|
||||
|
||||
} catch (Exception e) {
|
||||
System.err.println("✗ 测试失败: " + e.getMessage());
|
||||
e.printStackTrace();
|
||||
fail("POST JSON字符串请求测试失败: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAlipanTokenApi() {
|
||||
System.out.println("\n[测试20] 阿里云盘Token接口测试 - auth.aliyundrive.com/v2/account/token");
|
||||
System.out.println("参考 alipan.js 中的登录逻辑,测试请求格式是否正确");
|
||||
|
||||
try {
|
||||
String tokenUrl = "https://auth.aliyundrive.com/v2/account/token";
|
||||
System.out.println("请求URL: " + tokenUrl);
|
||||
|
||||
// 参考 alipan.js 中的请求格式
|
||||
// setJsonHeaders(http) 设置 Content-Type: application/json 和 User-Agent
|
||||
httpClient.putHeader("Content-Type", "application/json");
|
||||
httpClient.putHeader("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36");
|
||||
|
||||
// 参考 alipan.js: JSON.stringify({grant_type: "refresh_token", refresh_token: REFRESH_TOKEN})
|
||||
String jsonData = "{\"grant_type\":\"refresh_token\",\"refresh_token\":\"\"}";
|
||||
System.out.println("POST数据(JSON字符串): " + jsonData);
|
||||
System.out.println("注意:使用无效token测试错误响应格式");
|
||||
System.out.println("开始请求...");
|
||||
|
||||
long startTime = System.currentTimeMillis();
|
||||
JsHttpClient.JsHttpResponse response = httpClient.post(tokenUrl, jsonData);
|
||||
long endTime = System.currentTimeMillis();
|
||||
|
||||
System.out.println("请求完成,耗时: " + (endTime - startTime) + "ms");
|
||||
System.out.println("状态码: " + response.statusCode());
|
||||
|
||||
String body = response.body();
|
||||
System.out.println("响应体: " + body);
|
||||
|
||||
// 验证结果
|
||||
assertNotNull("响应不能为null", response);
|
||||
// 使用无效token应该返回400或401等错误状态码,但请求格式应该是正确的
|
||||
assertTrue("状态码应该是4xx(无效token)或200(如果token有效)",
|
||||
response.statusCode() >= 200 && response.statusCode() < 500);
|
||||
assertNotNull("响应体不能为null", body);
|
||||
|
||||
// 验证响应格式(阿里云盘API通常返回JSON)
|
||||
try {
|
||||
Object jsonResponse = response.json();
|
||||
System.out.println("响应JSON解析成功: " + jsonResponse);
|
||||
assertNotNull("JSON响应不能为null", jsonResponse);
|
||||
} catch (Exception e) {
|
||||
System.out.println("警告:响应不是有效的JSON格式");
|
||||
}
|
||||
|
||||
// 验证请求头是否正确设置
|
||||
System.out.println("验证请求头设置...");
|
||||
Map<String, String> headers = httpClient.getHeaders();
|
||||
assertTrue("应该设置了Content-Type", headers.containsKey("Content-Type"));
|
||||
assertEquals("Content-Type应该是application/json",
|
||||
"application/json", headers.get("Content-Type"));
|
||||
|
||||
System.out.println("✓ 测试通过 - 请求格式正确,已成功发送到阿里云盘API");
|
||||
|
||||
} catch (Exception e) {
|
||||
System.err.println("✗ 测试失败: " + e.getMessage());
|
||||
e.printStackTrace();
|
||||
// 如果是超时或其他网络错误,说明请求格式可能有问题
|
||||
if (e.getMessage() != null && e.getMessage().contains("超时")) {
|
||||
fail("请求超时,可能是请求格式问题或网络问题: " + e.getMessage());
|
||||
} else {
|
||||
fail("阿里云盘Token接口测试失败: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAlipanTokenApiWithValidFormat() {
|
||||
System.out.println("\n[测试21] 阿里云盘Token接口格式验证 - 使用httpbin验证请求格式");
|
||||
System.out.println("通过httpbin回显验证请求格式是否与alipan.js中的格式一致");
|
||||
|
||||
try {
|
||||
// 使用httpbin来验证请求格式
|
||||
String testUrl = "https://httpbin.org/post";
|
||||
System.out.println("测试URL: " + testUrl);
|
||||
|
||||
// 参考 alipan.js 中的请求格式
|
||||
httpClient.clearHeaders(); // 清空之前的头
|
||||
httpClient.putHeader("Content-Type", "application/json");
|
||||
httpClient.putHeader("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36");
|
||||
|
||||
// 完全模拟 alipan.js 中的请求体格式
|
||||
String jsonData = "{\"grant_type\":\"refresh_token\",\"refresh_token\":\"test_refresh_token_12345\"}";
|
||||
System.out.println("POST数据(JSON字符串): " + jsonData);
|
||||
System.out.println("开始请求...");
|
||||
|
||||
long startTime = System.currentTimeMillis();
|
||||
JsHttpClient.JsHttpResponse response = httpClient.post(testUrl, jsonData);
|
||||
long endTime = System.currentTimeMillis();
|
||||
|
||||
System.out.println("请求完成,耗时: " + (endTime - startTime) + "ms");
|
||||
System.out.println("状态码: " + response.statusCode());
|
||||
|
||||
String body = response.body();
|
||||
System.out.println("响应体(前800字符): " + (body != null && body.length() > 800 ? body.substring(0, 800) + "..." : body));
|
||||
|
||||
// 验证结果
|
||||
assertNotNull("响应不能为null", response);
|
||||
assertEquals("状态码应该是200", 200, response.statusCode());
|
||||
assertNotNull("响应体不能为null", body);
|
||||
|
||||
// httpbin会回显请求数据,验证请求体是否正确发送
|
||||
assertTrue("响应体应该包含grant_type字段", body.contains("grant_type"));
|
||||
assertTrue("响应体应该包含refresh_token字段", body.contains("refresh_token"));
|
||||
assertTrue("响应体应该包含发送的refresh_token值", body.contains("test_refresh_token_12345"));
|
||||
|
||||
// 验证Content-Type是否正确
|
||||
assertTrue("响应体应该包含Content-Type信息", body.contains("application/json"));
|
||||
|
||||
// 验证User-Agent是否正确
|
||||
assertTrue("响应体应该包含User-Agent信息", body.contains("Mozilla"));
|
||||
|
||||
System.out.println("✓ 测试通过 - 请求格式与alipan.js中的格式完全一致");
|
||||
System.out.println(" - JSON请求体正确发送");
|
||||
System.out.println(" - Content-Type正确设置");
|
||||
System.out.println(" - User-Agent正确设置");
|
||||
|
||||
} catch (Exception e) {
|
||||
System.err.println("✗ 测试失败: " + e.getMessage());
|
||||
e.printStackTrace();
|
||||
fail("阿里云盘Token接口格式验证失败: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSetTimeout() {
|
||||
System.out.println("\n[测试15] 设置超时时间 - setTimeout方法");
|
||||
|
||||
@@ -3,6 +3,7 @@ module.exports = {
|
||||
'@vue/cli-plugin-babel/preset'
|
||||
],
|
||||
plugins: [
|
||||
'@vue/babel-plugin-transform-vue-jsx'
|
||||
'@vue/babel-plugin-transform-vue-jsx',
|
||||
'@babel/plugin-transform-class-static-block'
|
||||
]
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
|
||||
### 1. NPM包安装
|
||||
已在 `package.json` 中安装:
|
||||
- `monaco-editor`: ^0.45.0 - Monaco Editor核心包
|
||||
- `monaco-editor`: ^0.55.1 - Monaco Editor核心包
|
||||
- `@monaco-editor/loader`: ^1.4.0 - Monaco Editor加载器
|
||||
- `monaco-editor-webpack-plugin`: ^7.1.1 - Webpack打包插件(devDependencies)
|
||||
|
||||
@@ -172,3 +172,5 @@ Monaco Editor 打包后会增加构建产物大小(约2-3MB),但这是正
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -5,7 +5,8 @@
|
||||
"scripts": {
|
||||
"serve": "vue-cli-service serve",
|
||||
"dev": "vue-cli-service serve",
|
||||
"build": "vue-cli-service build",
|
||||
"build": "vue-cli-service build && node scripts/compress-vs.js",
|
||||
"build:no-compress": "vue-cli-service build",
|
||||
"lint": "vue-cli-service lint"
|
||||
},
|
||||
"dependencies": {
|
||||
@@ -16,7 +17,7 @@
|
||||
"clipboard": "^2.0.11",
|
||||
"core-js": "^3.8.3",
|
||||
"element-plus": "2.11.3",
|
||||
"monaco-editor": "^0.45.0",
|
||||
"monaco-editor": "^0.55.1",
|
||||
"qrcode": "^1.5.4",
|
||||
"splitpanes": "^4.0.4",
|
||||
"vue": "^3.5.12",
|
||||
@@ -28,6 +29,7 @@
|
||||
"@babel/core": "^7.26.0",
|
||||
"@babel/eslint-parser": "^7.25.9",
|
||||
"@babel/plugin-transform-class-properties": "^7.26.0",
|
||||
"@babel/plugin-transform-class-static-block": "^7.26.0",
|
||||
"@vue/babel-plugin-transform-vue-jsx": "^1.4.0",
|
||||
"@vue/cli-plugin-babel": "~5.0.8",
|
||||
"@vue/cli-plugin-eslint": "~5.0.8",
|
||||
@@ -35,7 +37,8 @@
|
||||
"compression-webpack-plugin": "^11.1.0",
|
||||
"eslint": "^9.0.0",
|
||||
"eslint-plugin-vue": "^9.30.0",
|
||||
"filemanager-webpack-plugin": "8.0.0"
|
||||
"filemanager-webpack-plugin": "8.0.0",
|
||||
"monaco-editor-webpack-plugin": "^7.1.1"
|
||||
},
|
||||
"eslintConfig": {
|
||||
"root": true,
|
||||
|
||||
@@ -9,8 +9,8 @@
|
||||
content="Netdisk fast download,网盘直链解析工具">
|
||||
<meta name="description"
|
||||
content="Netdisk fast download 网盘直链解析工具">
|
||||
<!-- Font Awesome 图标库 -->
|
||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
|
||||
<!-- Font Awesome 图标库 - 使用国内CDN -->
|
||||
<link rel="stylesheet" href="https://cdn.bootcdn.net/ajax/libs/font-awesome/6.4.0/css/all.min.css">
|
||||
<style>
|
||||
.page-loading-wrap {
|
||||
padding: 120px;
|
||||
@@ -154,11 +154,26 @@
|
||||
}
|
||||
</style>
|
||||
<script>
|
||||
const saved = localStorage.getItem('isDarkMode') === 'true'
|
||||
const systemDark = window.matchMedia('(prefers-color-scheme: dark)').matches
|
||||
if (saved || (!saved && systemDark)) {
|
||||
document.body.classList.add('dark-theme')
|
||||
}
|
||||
// 等待DOM加载完成后再操作
|
||||
(function() {
|
||||
function applyDarkTheme() {
|
||||
const body = document.body;
|
||||
if (body && body.classList) {
|
||||
// 只在用户明确选择暗色模式时才应用,不自动检测系统偏好
|
||||
if (localStorage.getItem('isDarkMode') === 'true') {
|
||||
body.classList.add('dark-theme')
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 如果DOM已加载,立即执行
|
||||
if (document.readyState === 'loading') {
|
||||
document.addEventListener('DOMContentLoaded', applyDarkTheme);
|
||||
} else {
|
||||
// DOM已加载,立即执行
|
||||
applyDarkTheme();
|
||||
}
|
||||
})();
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
124
web-front/scripts/compress-vs.js
Executable file
124
web-front/scripts/compress-vs.js
Executable file
@@ -0,0 +1,124 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
const path = require("path");
|
||||
const fs = require("fs");
|
||||
const zlib = require("zlib");
|
||||
const { promisify } = require("util");
|
||||
|
||||
const gzip = promisify(zlib.gzip);
|
||||
const readdir = promisify(fs.readdir);
|
||||
const stat = promisify(fs.stat);
|
||||
const readFile = promisify(fs.readFile);
|
||||
const writeFile = promisify(fs.writeFile);
|
||||
|
||||
// 递归压缩目录下的所有文件
|
||||
async function compressDirectory(dirPath, threshold = 1024) {
|
||||
if (!fs.existsSync(dirPath)) {
|
||||
console.warn(`目录不存在: ${dirPath}`);
|
||||
return;
|
||||
}
|
||||
|
||||
const files = await readdir(dirPath, { withFileTypes: true });
|
||||
let compressedCount = 0;
|
||||
let totalOriginalSize = 0;
|
||||
let totalCompressedSize = 0;
|
||||
|
||||
for (const file of files) {
|
||||
const filePath = path.join(dirPath, file.name);
|
||||
|
||||
if (file.isDirectory()) {
|
||||
await compressDirectory(filePath, threshold);
|
||||
} else if (file.isFile()) {
|
||||
const stats = await stat(filePath);
|
||||
// 只压缩超过阈值且不是已压缩的文件
|
||||
if (stats.size > threshold && !filePath.endsWith('.gz') && !filePath.endsWith('.map')) {
|
||||
try {
|
||||
const content = await readFile(filePath);
|
||||
const compressed = await gzip(content);
|
||||
await writeFile(filePath + '.gz', compressed);
|
||||
compressedCount++;
|
||||
totalOriginalSize += stats.size;
|
||||
totalCompressedSize += compressed.length;
|
||||
console.log(`✓ ${file.name} (${(stats.size / 1024).toFixed(2)}KB -> ${(compressed.length / 1024).toFixed(2)}KB)`);
|
||||
} catch (error) {
|
||||
console.warn(`⚠ 压缩失败: ${filePath}`, error.message);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (compressedCount > 0) {
|
||||
console.log(`\n压缩完成: ${compressedCount} 个文件`);
|
||||
console.log(`原始大小: ${(totalOriginalSize / 1024 / 1024).toFixed(2)}MB`);
|
||||
console.log(`压缩后大小: ${(totalCompressedSize / 1024 / 1024).toFixed(2)}MB`);
|
||||
console.log(`压缩率: ${((1 - totalCompressedSize / totalOriginalSize) * 100).toFixed(1)}%`);
|
||||
}
|
||||
}
|
||||
|
||||
// 删除未使用的 worker 文件
|
||||
function deleteUnusedWorkers() {
|
||||
const jsDir = path.join(__dirname, '../nfd-front/js');
|
||||
const workers = ['editor.worker.js', 'editor.worker.js.gz', 'json.worker.js', 'json.worker.js.gz', 'ts.worker.js', 'ts.worker.js.gz'];
|
||||
|
||||
let deletedCount = 0;
|
||||
for (const worker of workers) {
|
||||
const filePath = path.join(jsDir, worker);
|
||||
if (fs.existsSync(filePath)) {
|
||||
try {
|
||||
fs.unlinkSync(filePath);
|
||||
deletedCount++;
|
||||
console.log(`✓ 已删除未使用的文件: ${worker}`);
|
||||
} catch (error) {
|
||||
console.warn(`⚠ 删除失败: ${worker}`, error.message);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (deletedCount > 0) {
|
||||
console.log(`\n已删除 ${deletedCount} 个未使用的 worker 文件\n`);
|
||||
}
|
||||
}
|
||||
|
||||
// 复制到 webroot
|
||||
function copyToWebroot() {
|
||||
const source = path.join(__dirname, '../nfd-front');
|
||||
const dest = path.join(__dirname, '../../webroot/nfd-front');
|
||||
|
||||
// 使用 FileManagerPlugin 的方式,这里用简单的复制
|
||||
const { execSync } = require('child_process');
|
||||
try {
|
||||
// 删除目标目录
|
||||
if (fs.existsSync(dest)) {
|
||||
execSync(`rm -rf "${dest}"`, { stdio: 'inherit' });
|
||||
}
|
||||
// 复制整个目录
|
||||
execSync(`cp -R "${source}" "${dest}"`, { stdio: 'inherit' });
|
||||
console.log('\n✓ 已复制到 webroot');
|
||||
} catch (error) {
|
||||
console.error('\n✗ 复制到 webroot 失败:', error.message);
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
// 主函数
|
||||
async function main() {
|
||||
// 先删除未使用的 worker 文件
|
||||
deleteUnusedWorkers();
|
||||
|
||||
// 然后压缩 vs 目录
|
||||
const vsPath = path.join(__dirname, '../nfd-front/js/vs');
|
||||
console.log('开始压缩 vs 目录下的文件...\n');
|
||||
try {
|
||||
await compressDirectory(vsPath, 1024); // 只压缩超过1KB的文件
|
||||
console.log('\n✓ vs 目录压缩完成');
|
||||
} catch (error) {
|
||||
console.error('\n✗ vs 目录压缩失败:', error);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
// 最后复制到 webroot
|
||||
copyToWebroot();
|
||||
}
|
||||
|
||||
main();
|
||||
|
||||
@@ -43,12 +43,16 @@ watch(darkMode, (newValue) => {
|
||||
emit('theme-change', newValue)
|
||||
|
||||
// 应用主题到body
|
||||
if (newValue) {
|
||||
document.body.classList.add('dark-theme')
|
||||
document.documentElement.classList.add('dark-theme')
|
||||
} else {
|
||||
document.body.classList.remove('dark-theme')
|
||||
document.documentElement.classList.remove('dark-theme')
|
||||
const html = document.documentElement;
|
||||
const body = document.body;
|
||||
if (html && body && html.classList && body.classList) {
|
||||
if (newValue) {
|
||||
body.classList.add('dark-theme')
|
||||
html.classList.add('dark-theme')
|
||||
} else {
|
||||
body.classList.remove('dark-theme')
|
||||
html.classList.remove('dark-theme')
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
@@ -57,9 +61,11 @@ onMounted(() => {
|
||||
emit('theme-change', darkMode.value)
|
||||
|
||||
// 应用初始主题
|
||||
if (darkMode.value) {
|
||||
document.body.classList.add('dark-theme')
|
||||
document.documentElement.classList.add('dark-theme')
|
||||
const html = document.documentElement;
|
||||
const body = document.body;
|
||||
if (html && body && html.classList && body.classList && darkMode.value) {
|
||||
body.classList.add('dark-theme')
|
||||
html.classList.add('dark-theme')
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
@@ -388,8 +388,14 @@ export default {
|
||||
return date.toLocaleString('zh-CN')
|
||||
},
|
||||
checkTheme() {
|
||||
this.isDarkTheme = document.body.classList.contains('dark-theme') ||
|
||||
document.documentElement.classList.contains('dark-theme')
|
||||
const html = document.documentElement;
|
||||
const body = document.body;
|
||||
if (html && body && html.classList && body.classList) {
|
||||
this.isDarkTheme = body.classList.contains('dark-theme') ||
|
||||
html.classList.contains('dark-theme')
|
||||
} else {
|
||||
this.isDarkTheme = false;
|
||||
}
|
||||
},
|
||||
renderContent(h, { node, data, store }) {
|
||||
const isFolder = data.fileType === 'folder'
|
||||
|
||||
@@ -34,6 +34,7 @@ export default {
|
||||
const editorContainer = ref(null);
|
||||
let editor = null;
|
||||
let monaco = null;
|
||||
let touchHandlers = { start: null, move: null };
|
||||
|
||||
const defaultOptions = {
|
||||
value: props.modelValue,
|
||||
@@ -94,6 +95,19 @@ export default {
|
||||
return;
|
||||
}
|
||||
|
||||
// 配置Monaco Editor使用本地打包的文件,而不是CDN
|
||||
if (loader.config) {
|
||||
const vsPath = process.env.NODE_ENV === 'production'
|
||||
? './js/vs' // 生产环境使用相对路径
|
||||
: '/js/vs'; // 开发环境使用绝对路径
|
||||
|
||||
loader.config({
|
||||
paths: {
|
||||
vs: vsPath
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// 初始化Monaco Editor
|
||||
monaco = await loader.init();
|
||||
|
||||
@@ -123,6 +137,46 @@ export default {
|
||||
if (editorContainer.value) {
|
||||
editorContainer.value.style.height = props.height;
|
||||
}
|
||||
|
||||
// 移动端:添加触摸缩放来调整字体大小
|
||||
if (window.innerWidth <= 768 && editorContainer.value) {
|
||||
let initialDistance = 0;
|
||||
let initialFontSize = defaultOptions.fontSize || 14;
|
||||
const minFontSize = 8;
|
||||
const maxFontSize = 24;
|
||||
|
||||
const getTouchDistance = (touch1, touch2) => {
|
||||
const dx = touch1.clientX - touch2.clientX;
|
||||
const dy = touch1.clientY - touch2.clientY;
|
||||
return Math.sqrt(dx * dx + dy * dy);
|
||||
};
|
||||
|
||||
touchHandlers.start = (e) => {
|
||||
if (e.touches.length === 2 && editor) {
|
||||
initialDistance = getTouchDistance(e.touches[0], e.touches[1]);
|
||||
initialFontSize = editor.getOption(monaco.editor.EditorOption.fontSize);
|
||||
}
|
||||
};
|
||||
|
||||
touchHandlers.move = (e) => {
|
||||
if (e.touches.length === 2 && editor) {
|
||||
e.preventDefault(); // 防止页面缩放
|
||||
const currentDistance = getTouchDistance(e.touches[0], e.touches[1]);
|
||||
const scale = currentDistance / initialDistance;
|
||||
const newFontSize = Math.round(initialFontSize * scale);
|
||||
|
||||
// 限制字体大小范围
|
||||
const clampedFontSize = Math.max(minFontSize, Math.min(maxFontSize, newFontSize));
|
||||
|
||||
if (clampedFontSize !== editor.getOption(monaco.editor.EditorOption.fontSize)) {
|
||||
editor.updateOptions({ fontSize: clampedFontSize });
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
editorContainer.value.addEventListener('touchstart', touchHandlers.start, { passive: false });
|
||||
editorContainer.value.addEventListener('touchmove', touchHandlers.move, { passive: false });
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Monaco Editor初始化失败:', error);
|
||||
console.error('错误详情:', error.stack);
|
||||
@@ -166,6 +220,11 @@ export default {
|
||||
});
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
// 清理触摸事件监听器
|
||||
if (editorContainer.value && touchHandlers.start && touchHandlers.move) {
|
||||
editorContainer.value.removeEventListener('touchstart', touchHandlers.start);
|
||||
editorContainer.value.removeEventListener('touchmove', touchHandlers.move);
|
||||
}
|
||||
if (editor) {
|
||||
editor.dispose();
|
||||
}
|
||||
@@ -187,10 +246,26 @@ export default {
|
||||
border: 1px solid #dcdfe6;
|
||||
border-radius: 4px;
|
||||
overflow: hidden;
|
||||
/* 允许用户选择文本 */
|
||||
-webkit-user-select: text;
|
||||
user-select: text;
|
||||
}
|
||||
|
||||
.monaco-editor-container :deep(.monaco-editor) {
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
/* 移动端:禁用页面缩放,只允许编辑器字体缩放 */
|
||||
@media (max-width: 768px) {
|
||||
.monaco-editor-container {
|
||||
/* 禁用页面级别的缩放,只允许编辑器内部字体缩放 */
|
||||
touch-action: pan-x pan-y;
|
||||
}
|
||||
|
||||
.monaco-editor-container :deep(.monaco-editor) {
|
||||
/* 禁用页面级别的缩放 */
|
||||
touch-action: pan-x pan-y;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
|
||||
@@ -10,7 +10,13 @@ const routes = [
|
||||
{ path: '/showFile', component: ShowFile },
|
||||
{ path: '/showList', component: ShowList },
|
||||
{ path: '/clientLinks', component: ClientLinks },
|
||||
{ path: '/playground', component: Playground }
|
||||
{ path: '/playground', component: Playground },
|
||||
// 404页面 - 必须放在最后
|
||||
{
|
||||
path: '/:pathMatch(.*)*',
|
||||
name: 'NotFound',
|
||||
component: () => import('@/views/NotFound.vue')
|
||||
}
|
||||
]
|
||||
|
||||
const router = createRouter({
|
||||
|
||||
@@ -1,5 +1,10 @@
|
||||
import axios from 'axios';
|
||||
|
||||
// 创建axios实例,配置携带cookie
|
||||
const axiosInstance = axios.create({
|
||||
withCredentials: true // 重要:允许跨域请求携带cookie
|
||||
});
|
||||
|
||||
/**
|
||||
* 演练场API服务
|
||||
*/
|
||||
@@ -10,7 +15,7 @@ export const playgroundApi = {
|
||||
*/
|
||||
async getStatus() {
|
||||
try {
|
||||
const response = await axios.get('/v2/playground/status');
|
||||
const response = await axiosInstance.get('/v2/playground/status');
|
||||
return response.data;
|
||||
} catch (error) {
|
||||
throw new Error(error.response?.data?.error || error.message || '获取状态失败');
|
||||
@@ -24,7 +29,7 @@ export const playgroundApi = {
|
||||
*/
|
||||
async login(password) {
|
||||
try {
|
||||
const response = await axios.post('/v2/playground/login', { password });
|
||||
const response = await axiosInstance.post('/v2/playground/login', { password });
|
||||
return response.data;
|
||||
} catch (error) {
|
||||
throw new Error(error.response?.data?.error || error.message || '登录失败');
|
||||
@@ -41,7 +46,7 @@ export const playgroundApi = {
|
||||
*/
|
||||
async testScript(jsCode, shareUrl, pwd = '', method = 'parse') {
|
||||
try {
|
||||
const response = await axios.post('/v2/playground/test', {
|
||||
const response = await axiosInstance.post('/v2/playground/test', {
|
||||
jsCode,
|
||||
shareUrl,
|
||||
pwd,
|
||||
@@ -69,7 +74,7 @@ export const playgroundApi = {
|
||||
*/
|
||||
async getTypesJs() {
|
||||
try {
|
||||
const response = await axios.get('/v2/playground/types.js', {
|
||||
const response = await axiosInstance.get('/v2/playground/types.js', {
|
||||
responseType: 'text'
|
||||
});
|
||||
return response.data;
|
||||
@@ -83,7 +88,7 @@ export const playgroundApi = {
|
||||
*/
|
||||
async getParserList() {
|
||||
try {
|
||||
const response = await axios.get('/v2/playground/parsers');
|
||||
const response = await axiosInstance.get('/v2/playground/parsers');
|
||||
// 框架会自动包装成JsonResult,需要从data字段获取
|
||||
if (response.data && response.data.data) {
|
||||
return {
|
||||
@@ -104,7 +109,7 @@ export const playgroundApi = {
|
||||
*/
|
||||
async saveParser(jsCode) {
|
||||
try {
|
||||
const response = await axios.post('/v2/playground/parsers', { jsCode });
|
||||
const response = await axiosInstance.post('/v2/playground/parsers', { jsCode });
|
||||
// 框架会自动包装成JsonResult
|
||||
if (response.data && response.data.data) {
|
||||
return {
|
||||
@@ -130,7 +135,7 @@ export const playgroundApi = {
|
||||
*/
|
||||
async updateParser(id, jsCode, enabled = true) {
|
||||
try {
|
||||
const response = await axios.put(`/v2/playground/parsers/${id}`, { jsCode, enabled });
|
||||
const response = await axiosInstance.put(`/v2/playground/parsers/${id}`, { jsCode, enabled });
|
||||
return response.data;
|
||||
} catch (error) {
|
||||
throw new Error(error.response?.data?.error || error.message || '更新解析器失败');
|
||||
@@ -142,7 +147,7 @@ export const playgroundApi = {
|
||||
*/
|
||||
async deleteParser(id) {
|
||||
try {
|
||||
const response = await axios.delete(`/v2/playground/parsers/${id}`);
|
||||
const response = await axiosInstance.delete(`/v2/playground/parsers/${id}`);
|
||||
return response.data;
|
||||
} catch (error) {
|
||||
throw new Error(error.response?.data?.error || error.message || '删除解析器失败');
|
||||
@@ -154,7 +159,7 @@ export const playgroundApi = {
|
||||
*/
|
||||
async getParserById(id) {
|
||||
try {
|
||||
const response = await axios.get(`/v2/playground/parsers/${id}`);
|
||||
const response = await axiosInstance.get(`/v2/playground/parsers/${id}`);
|
||||
// 框架会自动包装成JsonResult
|
||||
if (response.data && response.data.data) {
|
||||
return {
|
||||
|
||||
@@ -48,7 +48,7 @@
|
||||
</div>
|
||||
<!-- 项目简介移到卡片内 -->
|
||||
<div class="project-intro">
|
||||
<div class="intro-title">NFD网盘直链解析0.1.9_b12</div>
|
||||
<div class="intro-title">NFD网盘直链解析0.1.9_b15</div>
|
||||
<div class="intro-desc">
|
||||
<div>支持网盘:蓝奏云、蓝奏云优享、小飞机盘、123云盘、奶牛快传、移动云空间、QQ邮箱云盘、QQ闪传等 <el-link style="color:#606cf5" href="https://github.com/qaiu/netdisk-fast-download?tab=readme-ov-file#%E7%BD%91%E7%9B%98%E6%94%AF%E6%8C%81%E6%83%85%E5%86%B5" target="_blank"> >> </el-link></div>
|
||||
<div>文件夹解析支持:蓝奏云、蓝奏云优享、小飞机盘、123云盘</div>
|
||||
@@ -218,7 +218,7 @@
|
||||
<!-- 版本号显示 -->
|
||||
<div class="version-info">
|
||||
<span class="version-text">内部版本: {{ buildVersion }}</span>
|
||||
<!-- <el-link :href="'/playground'" class="playground-link">JS演练场</el-link>-->
|
||||
<el-link v-if="playgroundEnabled" :href="'/playground'" class="playground-link">脚本演练场</el-link>
|
||||
</div>
|
||||
|
||||
<!-- 文件解析结果区下方加分享按钮 -->
|
||||
@@ -248,6 +248,7 @@ import DirectoryTree from '@/components/DirectoryTree'
|
||||
import parserUrl from '../parserUrl1'
|
||||
import fileTypeUtils from '@/utils/fileTypeUtils'
|
||||
import { ElMessage } from 'element-plus'
|
||||
import { playgroundApi } from '@/utils/playgroundApi'
|
||||
|
||||
export const previewBaseUrl = 'https://nfd-parser.github.io/nfd-preview/preview.html?src=';
|
||||
|
||||
@@ -297,7 +298,10 @@ export default {
|
||||
errorButtonVisible: false,
|
||||
|
||||
// 版本信息
|
||||
buildVersion: ''
|
||||
buildVersion: '',
|
||||
|
||||
// 演练场启用状态
|
||||
playgroundEnabled: false
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
@@ -316,7 +320,9 @@ export default {
|
||||
// 主题切换
|
||||
handleThemeChange(isDark) {
|
||||
this.isDarkMode = isDark
|
||||
document.body.classList.toggle('dark-theme', isDark)
|
||||
if (document.body && document.body.classList) {
|
||||
document.body.classList.toggle('dark-theme', isDark)
|
||||
}
|
||||
window.localStorage.setItem('isDarkMode', isDark)
|
||||
|
||||
},
|
||||
@@ -552,6 +558,19 @@ export default {
|
||||
}
|
||||
},
|
||||
|
||||
// 检查演练场是否启用
|
||||
async checkPlaygroundEnabled() {
|
||||
try {
|
||||
const result = await playgroundApi.getStatus()
|
||||
if (result && result.data) {
|
||||
this.playgroundEnabled = result.data.enabled === true
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('检查演练场状态失败:', error)
|
||||
this.playgroundEnabled = false
|
||||
}
|
||||
},
|
||||
|
||||
// 新增切换目录树展示模式方法
|
||||
setDirectoryViewMode(mode) {
|
||||
this.directoryViewMode = mode
|
||||
@@ -656,6 +675,9 @@ export default {
|
||||
// 获取版本号
|
||||
this.getBuildVersion()
|
||||
|
||||
// 检查演练场是否启用
|
||||
this.checkPlaygroundEnabled()
|
||||
|
||||
// 自动读取剪切板
|
||||
if (this.autoReadClipboard) {
|
||||
this.getPaste()
|
||||
|
||||
135
web-front/src/views/NotFound.vue
Normal file
135
web-front/src/views/NotFound.vue
Normal file
@@ -0,0 +1,135 @@
|
||||
<template>
|
||||
<div class="not-found-container">
|
||||
<div class="not-found-content">
|
||||
<div class="not-found-icon">
|
||||
<el-icon :size="120"><DocumentDelete /></el-icon>
|
||||
</div>
|
||||
<h1 class="not-found-title">404</h1>
|
||||
<p class="not-found-message">抱歉,您访问的页面不存在</p>
|
||||
<div class="not-found-actions">
|
||||
<el-button type="primary" @click="goHome">返回首页</el-button>
|
||||
<el-button @click="goBack">返回上一页</el-button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { useRouter } from 'vue-router'
|
||||
import { DocumentDelete } from '@element-plus/icons-vue'
|
||||
|
||||
export default {
|
||||
name: 'NotFound',
|
||||
components: {
|
||||
DocumentDelete
|
||||
},
|
||||
setup() {
|
||||
const router = useRouter()
|
||||
|
||||
const goHome = () => {
|
||||
router.push('/')
|
||||
}
|
||||
|
||||
const goBack = () => {
|
||||
if (window.history.length > 1) {
|
||||
router.go(-1)
|
||||
} else {
|
||||
router.push('/')
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
goHome,
|
||||
goBack
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.not-found-container {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
min-height: 100vh;
|
||||
/* background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); */
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.not-found-content {
|
||||
text-align: center;
|
||||
background: white;
|
||||
padding: 60px 40px;
|
||||
border-radius: 20px;
|
||||
box-shadow: 0 20px 60px rgba(0, 0, 0, 0.3);
|
||||
max-width: 500px;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.not-found-icon {
|
||||
color: #909399;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.not-found-title {
|
||||
font-size: 72px;
|
||||
font-weight: bold;
|
||||
color: #303133;
|
||||
margin: 0 0 20px 0;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.not-found-message {
|
||||
font-size: 18px;
|
||||
color: #606266;
|
||||
margin: 0 0 40px 0;
|
||||
}
|
||||
|
||||
.not-found-actions {
|
||||
display: flex;
|
||||
gap: 15px;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
/* 暗色主题支持 */
|
||||
.dark-theme .not-found-content {
|
||||
background: #1d1e1f;
|
||||
}
|
||||
|
||||
.dark-theme .not-found-title {
|
||||
color: #e5eaf3;
|
||||
}
|
||||
|
||||
.dark-theme .not-found-message {
|
||||
color: #a3a6ad;
|
||||
}
|
||||
|
||||
.dark-theme .not-found-icon {
|
||||
color: #6c6e72;
|
||||
}
|
||||
|
||||
/* 移动端适配 */
|
||||
@media (max-width: 768px) {
|
||||
.not-found-content {
|
||||
padding: 40px 20px;
|
||||
}
|
||||
|
||||
.not-found-title {
|
||||
font-size: 48px;
|
||||
}
|
||||
|
||||
.not-found-message {
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.not-found-actions {
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.not-found-actions .el-button {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -61,12 +61,16 @@ export default {
|
||||
}
|
||||
},
|
||||
toggleTheme(isDark) {
|
||||
if (isDark) {
|
||||
document.body.classList.add('dark-theme')
|
||||
document.documentElement.classList.add('dark-theme')
|
||||
} else {
|
||||
document.body.classList.remove('dark-theme')
|
||||
document.documentElement.classList.remove('dark-theme')
|
||||
const html = document.documentElement;
|
||||
const body = document.body;
|
||||
if (html && body && html.classList && body.classList) {
|
||||
if (isDark) {
|
||||
body.classList.add('dark-theme')
|
||||
html.classList.add('dark-theme')
|
||||
} else {
|
||||
body.classList.remove('dark-theme')
|
||||
html.classList.remove('dark-theme')
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
@@ -4,8 +4,10 @@ const path = require("path");
|
||||
function resolve(dir) {
|
||||
return path.join(__dirname, dir)
|
||||
}
|
||||
|
||||
const CompressionPlugin = require('compression-webpack-plugin');
|
||||
const FileManagerPlugin = require('filemanager-webpack-plugin')
|
||||
const FileManagerPlugin = require('filemanager-webpack-plugin');
|
||||
const MonacoEditorPlugin = require('monaco-editor-webpack-plugin');
|
||||
|
||||
module.exports = {
|
||||
productionSourceMap: false, // 是否在构建生产包时生成sourceMap文件,false将提高构建速度
|
||||
@@ -43,7 +45,7 @@ module.exports = {
|
||||
'@': resolve('src')
|
||||
}
|
||||
},
|
||||
// Monaco Editor配置
|
||||
// Monaco Editor配置 - 使用本地打包
|
||||
module: {
|
||||
rules: [
|
||||
{
|
||||
@@ -53,9 +55,18 @@ module.exports = {
|
||||
]
|
||||
},
|
||||
plugins: [
|
||||
new MonacoEditorPlugin({
|
||||
languages: ['javascript', 'typescript', 'json'],
|
||||
features: ['coreCommands', 'find', 'format', 'suggest', 'quickCommand'],
|
||||
publicPath: process.env.NODE_ENV === 'production' ? './' : '/',
|
||||
// Worker 文件输出路径
|
||||
filename: 'js/[name].worker.js'
|
||||
}),
|
||||
new CompressionPlugin({
|
||||
test: /\.js$|\.html$|\.css/, // 匹配文件
|
||||
threshold: 10240 // 对超过10k文件压缩
|
||||
threshold: 10240, // 对超过10k文件压缩
|
||||
// 排除 js 目录下的 worker 文件(Monaco Editor 使用 vs/assets 下的)
|
||||
exclude: /js\/.*\.worker\.js$/
|
||||
}),
|
||||
new FileManagerPlugin({ //初始化 filemanager-webpack-plugin 插件实例
|
||||
events: {
|
||||
@@ -70,7 +81,11 @@ module.exports = {
|
||||
{ source: '../webroot/nfd-front/view/.gitignore', options: { force: true } },
|
||||
],
|
||||
copy: [
|
||||
{ source: './nfd-front', destination: '../webroot/nfd-front' }
|
||||
// 复制 Monaco Editor 的 vs 目录到 js/vs
|
||||
{
|
||||
source: './node_modules/monaco-editor/min/vs',
|
||||
destination: './nfd-front/js/vs'
|
||||
}
|
||||
],
|
||||
archive: [ //然后我们选择dist文件夹将之打包成dist.zip并放在根目录
|
||||
{
|
||||
|
||||
0
web-service/doc/PLAYGROUND_GUIDE.md
Normal file
0
web-service/doc/PLAYGROUND_GUIDE.md
Normal file
@@ -122,13 +122,23 @@ public class AppMain {
|
||||
if (parser.getBoolean("enabled", false)) {
|
||||
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) {
|
||||
log.error("加载演练场解析器失败: {}", parser.getString("name"), 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语法");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,6 +18,12 @@ public class PlaygroundConfig {
|
||||
*/
|
||||
private static PlaygroundConfig instance;
|
||||
|
||||
/**
|
||||
* 是否启用演练场
|
||||
* 默认false,不启用
|
||||
*/
|
||||
private boolean enabled = false;
|
||||
|
||||
/**
|
||||
* 是否公开模式(不需要密码)
|
||||
* 默认false,需要密码访问
|
||||
@@ -57,17 +63,20 @@ public class PlaygroundConfig {
|
||||
PlaygroundConfig cfg = getInstance();
|
||||
if (config != null && config.containsKey("playground")) {
|
||||
JsonObject playgroundConfig = config.getJsonObject("playground");
|
||||
cfg.enabled = playgroundConfig.getBoolean("enabled", false);
|
||||
cfg.isPublic = playgroundConfig.getBoolean("public", false);
|
||||
cfg.password = playgroundConfig.getString("password", "nfd_playground_2024");
|
||||
|
||||
log.info("Playground配置已加载: public={}, password={}",
|
||||
cfg.isPublic, cfg.isPublic ? "N/A" : "已设置");
|
||||
log.info("Playground配置已加载: enabled={}, public={}, password={}",
|
||||
cfg.enabled, cfg.isPublic, cfg.isPublic ? "N/A" : "已设置");
|
||||
|
||||
if (!cfg.isPublic && "nfd_playground_2024".equals(cfg.password)) {
|
||||
if (!cfg.enabled) {
|
||||
log.info("演练场功能已禁用");
|
||||
} else if (!cfg.isPublic && "nfd_playground_2024".equals(cfg.password)) {
|
||||
log.warn("⚠️ 警告:您正在使用默认密码,建议修改配置文件中的 playground.password 以确保安全!");
|
||||
}
|
||||
} else {
|
||||
log.info("未找到playground配置,使用默认值: public=false");
|
||||
log.info("未找到playground配置,使用默认值: enabled=false, public=false");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -50,10 +50,23 @@ public class PlaygroundApi {
|
||||
private static final String SESSION_AUTH_KEY = "playgroundAuthed";
|
||||
private final DbService dbService = AsyncServiceUtil.getAsyncServiceInstance(DbService.class);
|
||||
|
||||
/**
|
||||
* 检查Playground是否启用
|
||||
*/
|
||||
private boolean checkEnabled() {
|
||||
PlaygroundConfig config = PlaygroundConfig.getInstance();
|
||||
return config.isEnabled();
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查Playground访问权限
|
||||
*/
|
||||
private boolean checkAuth(RoutingContext ctx) {
|
||||
// 首先检查是否启用
|
||||
if (!checkEnabled()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
PlaygroundConfig config = PlaygroundConfig.getInstance();
|
||||
|
||||
// 如果是公开模式,直接允许访问
|
||||
@@ -77,9 +90,11 @@ public class PlaygroundApi {
|
||||
@RouteMapping(value = "/status", method = RouteMethod.GET)
|
||||
public Future<JsonObject> getStatus(RoutingContext ctx) {
|
||||
PlaygroundConfig config = PlaygroundConfig.getInstance();
|
||||
boolean authed = checkAuth(ctx);
|
||||
boolean enabled = config.isEnabled();
|
||||
boolean authed = enabled && checkAuth(ctx);
|
||||
|
||||
JsonObject result = new JsonObject()
|
||||
.put("enabled", enabled)
|
||||
.put("public", config.isPublic())
|
||||
.put("authed", authed);
|
||||
|
||||
@@ -91,6 +106,11 @@ public class PlaygroundApi {
|
||||
*/
|
||||
@RouteMapping(value = "/login", method = RouteMethod.POST)
|
||||
public Future<JsonObject> login(RoutingContext ctx) {
|
||||
// 检查是否启用
|
||||
if (!checkEnabled()) {
|
||||
return Future.succeededFuture(JsonResult.error("演练场功能已禁用").toJsonObject());
|
||||
}
|
||||
|
||||
Promise<JsonObject> promise = Promise.promise();
|
||||
|
||||
try {
|
||||
@@ -142,6 +162,11 @@ public class PlaygroundApi {
|
||||
*/
|
||||
@RouteMapping(value = "/test", method = RouteMethod.POST)
|
||||
public Future<JsonObject> test(RoutingContext ctx) {
|
||||
// 检查是否启用
|
||||
if (!checkEnabled()) {
|
||||
return Future.succeededFuture(JsonResult.error("演练场功能已禁用").toJsonObject());
|
||||
}
|
||||
|
||||
// 权限检查
|
||||
if (!checkAuth(ctx)) {
|
||||
return Future.succeededFuture(JsonResult.error("未授权访问").toJsonObject());
|
||||
@@ -345,6 +370,12 @@ public class PlaygroundApi {
|
||||
*/
|
||||
@RouteMapping(value = "/types.js", method = RouteMethod.GET)
|
||||
public void getTypesJs(RoutingContext ctx, HttpServerResponse response) {
|
||||
// 检查是否启用
|
||||
if (!checkEnabled()) {
|
||||
ResponseUtil.fireJsonResultResponse(response, JsonResult.error("演练场功能已禁用"));
|
||||
return;
|
||||
}
|
||||
|
||||
// 权限检查
|
||||
if (!checkAuth(ctx)) {
|
||||
ResponseUtil.fireJsonResultResponse(response, JsonResult.error("未授权访问"));
|
||||
@@ -377,6 +408,11 @@ public class PlaygroundApi {
|
||||
*/
|
||||
@RouteMapping(value = "/parsers", method = RouteMethod.GET)
|
||||
public Future<JsonObject> getParserList(RoutingContext ctx) {
|
||||
// 检查是否启用
|
||||
if (!checkEnabled()) {
|
||||
return Future.succeededFuture(JsonResult.error("演练场功能已禁用").toJsonObject());
|
||||
}
|
||||
|
||||
// 权限检查
|
||||
if (!checkAuth(ctx)) {
|
||||
return Future.succeededFuture(JsonResult.error("未授权访问").toJsonObject());
|
||||
@@ -389,6 +425,11 @@ public class PlaygroundApi {
|
||||
*/
|
||||
@RouteMapping(value = "/parsers", method = RouteMethod.POST)
|
||||
public Future<JsonObject> saveParser(RoutingContext ctx) {
|
||||
// 检查是否启用
|
||||
if (!checkEnabled()) {
|
||||
return Future.succeededFuture(JsonResult.error("演练场功能已禁用").toJsonObject());
|
||||
}
|
||||
|
||||
// 权限检查
|
||||
if (!checkAuth(ctx)) {
|
||||
return Future.succeededFuture(JsonResult.error("未授权访问").toJsonObject());
|
||||
@@ -504,6 +545,11 @@ public class PlaygroundApi {
|
||||
*/
|
||||
@RouteMapping(value = "/parsers/:id", method = RouteMethod.PUT)
|
||||
public Future<JsonObject> updateParser(RoutingContext ctx, Long id) {
|
||||
// 检查是否启用
|
||||
if (!checkEnabled()) {
|
||||
return Future.succeededFuture(JsonResult.error("演练场功能已禁用").toJsonObject());
|
||||
}
|
||||
|
||||
// 权限检查
|
||||
if (!checkAuth(ctx)) {
|
||||
return Future.succeededFuture(JsonResult.error("未授权访问").toJsonObject());
|
||||
@@ -585,6 +631,11 @@ public class PlaygroundApi {
|
||||
*/
|
||||
@RouteMapping(value = "/parsers/:id", method = RouteMethod.DELETE)
|
||||
public Future<JsonObject> deleteParser(RoutingContext ctx, Long id) {
|
||||
// 检查是否启用
|
||||
if (!checkEnabled()) {
|
||||
return Future.succeededFuture(JsonResult.error("演练场功能已禁用").toJsonObject());
|
||||
}
|
||||
|
||||
// 权限检查
|
||||
if (!checkAuth(ctx)) {
|
||||
return Future.succeededFuture(JsonResult.error("未授权访问").toJsonObject());
|
||||
@@ -628,6 +679,11 @@ public class PlaygroundApi {
|
||||
*/
|
||||
@RouteMapping(value = "/parsers/:id", method = RouteMethod.GET)
|
||||
public Future<JsonObject> getParserById(RoutingContext ctx, Long id) {
|
||||
// 检查是否启用
|
||||
if (!checkEnabled()) {
|
||||
return Future.succeededFuture(JsonResult.error("演练场功能已禁用").toJsonObject());
|
||||
}
|
||||
|
||||
// 权限检查
|
||||
if (!checkAuth(ctx)) {
|
||||
return Future.succeededFuture(JsonResult.error("未授权访问").toJsonObject());
|
||||
|
||||
@@ -15,6 +15,8 @@ proxyConf: server-proxy
|
||||
|
||||
# JS演练场配置
|
||||
playground:
|
||||
# 是否启用演练场,默认false不启用
|
||||
enabled: true
|
||||
# 公开模式,默认false需要密码访问,设为true则无需密码
|
||||
public: false
|
||||
# 访问密码,建议修改默认密码!
|
||||
@@ -108,4 +110,4 @@ auths:
|
||||
# 123网盘:配置用户名密码
|
||||
ye:
|
||||
username:
|
||||
password:
|
||||
password:
|
||||
|
||||
@@ -4,7 +4,7 @@ server-name: Vert.x-proxy-server(v4.1.2)
|
||||
proxy:
|
||||
- listen: 6401
|
||||
# 404的路径
|
||||
page404: webroot/err/404.html
|
||||
page404: webroot/nfd-front/index.html
|
||||
static:
|
||||
path: /
|
||||
add-headers:
|
||||
|
||||
Reference in New Issue
Block a user