Compare commits

...

7 Commits

Author SHA1 Message Date
qaiu
e17fb99de4 启用在线脚本解析器 2026-01-06 02:21:19 +08:00
q
0f926a57ef 更新Playground和JsHttpClient相关功能,整理文档结构 2026-01-06 00:00:37 +08:00
q
4380bfe0d6 Merge branch 'main' of github.com:qaiu/netdisk-fast-download 2026-01-04 09:30:54 +08:00
q
8127cd0758 fixed. 演练场静态打包问题 2026-01-04 09:29:20 +08:00
qaiu
71a220f42b 升级前端版本 2026-01-04 01:14:26 +08:00
qaiu
d3b02676ec Update README.md 2026-01-03 21:20:54 +08:00
q
d8f0dc4f8e 更新代码和文档 2026-01-03 21:11:04 +08:00
35 changed files with 2125 additions and 251 deletions

17
.github/dependabot.yml vendored Normal file
View 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
View File

@@ -29,6 +29,8 @@ target/
/src/logs/
*.zip
sdkTest.log
app.yml
app-local.yml
#some local files

View File

@@ -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)

View File

@@ -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("/*");

View File

@@ -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")) {

View File

@@ -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();
});
}

View File

@@ -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();
}

View File

@@ -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

View File

@@ -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方法");

View File

@@ -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'
]
}

View File

@@ -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但这是正

View File

@@ -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,

View File

@@ -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
View 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();

View File

@@ -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>

View File

@@ -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'

View File

@@ -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>

View File

@@ -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({

View File

@@ -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 {

View File

@@ -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"> &gt;&gt; </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()

View 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

View File

@@ -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')
}
}
}
},

View File

@@ -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并放在根目录
{

View File

View 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语法");
}
}
}
}

View File

@@ -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");
}
}
}

View File

@@ -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());

View File

@@ -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:

View File

@@ -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: