mirror of
https://github.com/qaiu/netdisk-fast-download.git
synced 2026-01-11 17:04:13 +00:00
Compare commits
4 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e17fb99de4 | ||
|
|
0f926a57ef | ||
|
|
4380bfe0d6 | ||
|
|
8127cd0758 |
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/
|
/src/logs/
|
||||||
*.zip
|
*.zip
|
||||||
sdkTest.log
|
sdkTest.log
|
||||||
|
app.yml
|
||||||
|
app-local.yml
|
||||||
|
|
||||||
|
|
||||||
#some local files
|
#some local files
|
||||||
|
|||||||
@@ -128,7 +128,9 @@ public class ReverseProxyVerticle extends AbstractVerticle {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private HttpServer getHttpsServer(JsonObject proxyConf) {
|
private HttpServer getHttpsServer(JsonObject proxyConf) {
|
||||||
HttpServerOptions httpServerOptions = new HttpServerOptions();
|
HttpServerOptions httpServerOptions = new HttpServerOptions()
|
||||||
|
.setCompressionSupported(true);
|
||||||
|
|
||||||
if (proxyConf.containsKey("ssl")) {
|
if (proxyConf.containsKey("ssl")) {
|
||||||
JsonObject sslConfig = proxyConf.getJsonObject("ssl");
|
JsonObject sslConfig = proxyConf.getJsonObject("ssl");
|
||||||
|
|
||||||
@@ -182,6 +184,7 @@ public class ReverseProxyVerticle extends AbstractVerticle {
|
|||||||
} else {
|
} else {
|
||||||
staticHandler = StaticHandler.create();
|
staticHandler = StaticHandler.create();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (staticConf.containsKey("directory-listing")) {
|
if (staticConf.containsKey("directory-listing")) {
|
||||||
staticHandler.setDirectoryListing(staticConf.getBoolean("directory-listing"));
|
staticHandler.setDirectoryListing(staticConf.getBoolean("directory-listing"));
|
||||||
} else if (staticConf.containsKey("index")) {
|
} else if (staticConf.containsKey("index")) {
|
||||||
|
|||||||
@@ -244,19 +244,17 @@ public class JsHttpClient {
|
|||||||
|
|
||||||
if (data != null) {
|
if (data != null) {
|
||||||
if (data instanceof String) {
|
if (data instanceof String) {
|
||||||
request.sendBuffer(Buffer.buffer((String) data));
|
return request.sendBuffer(Buffer.buffer((String) data));
|
||||||
} else if (data instanceof Map) {
|
} else if (data instanceof Map) {
|
||||||
@SuppressWarnings("unchecked")
|
@SuppressWarnings("unchecked")
|
||||||
Map<String, String> mapData = (Map<String, String>) data;
|
Map<String, String> mapData = (Map<String, String>) data;
|
||||||
request.sendForm(MultiMap.caseInsensitiveMultiMap().addAll(mapData));
|
return request.sendForm(MultiMap.caseInsensitiveMultiMap().addAll(mapData));
|
||||||
} else {
|
} else {
|
||||||
request.sendJson(data);
|
return request.sendJson(data);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
request.send();
|
return request.send();
|
||||||
}
|
}
|
||||||
|
|
||||||
return request.send();
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -276,19 +274,17 @@ public class JsHttpClient {
|
|||||||
|
|
||||||
if (data != null) {
|
if (data != null) {
|
||||||
if (data instanceof String) {
|
if (data instanceof String) {
|
||||||
request.sendBuffer(Buffer.buffer((String) data));
|
return request.sendBuffer(Buffer.buffer((String) data));
|
||||||
} else if (data instanceof Map) {
|
} else if (data instanceof Map) {
|
||||||
@SuppressWarnings("unchecked")
|
@SuppressWarnings("unchecked")
|
||||||
Map<String, String> mapData = (Map<String, String>) data;
|
Map<String, String> mapData = (Map<String, String>) data;
|
||||||
request.sendForm(MultiMap.caseInsensitiveMultiMap().addAll(mapData));
|
return request.sendForm(MultiMap.caseInsensitiveMultiMap().addAll(mapData));
|
||||||
} else {
|
} else {
|
||||||
request.sendJson(data);
|
return request.sendJson(data);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
request.send();
|
return request.send();
|
||||||
}
|
}
|
||||||
|
|
||||||
return request.send();
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -322,19 +318,17 @@ public class JsHttpClient {
|
|||||||
|
|
||||||
if (data != null) {
|
if (data != null) {
|
||||||
if (data instanceof String) {
|
if (data instanceof String) {
|
||||||
request.sendBuffer(Buffer.buffer((String) data));
|
return request.sendBuffer(Buffer.buffer((String) data));
|
||||||
} else if (data instanceof Map) {
|
} else if (data instanceof Map) {
|
||||||
@SuppressWarnings("unchecked")
|
@SuppressWarnings("unchecked")
|
||||||
Map<String, String> mapData = (Map<String, String>) data;
|
Map<String, String> mapData = (Map<String, String>) data;
|
||||||
request.sendForm(MultiMap.caseInsensitiveMultiMap().addAll(mapData));
|
return request.sendForm(MultiMap.caseInsensitiveMultiMap().addAll(mapData));
|
||||||
} else {
|
} else {
|
||||||
request.sendJson(data);
|
return request.sendJson(data);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
request.send();
|
return request.send();
|
||||||
}
|
}
|
||||||
|
|
||||||
return request.send();
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -111,7 +111,7 @@ public class JsPlaygroundExecutor {
|
|||||||
playgroundLogger.infoJava("✅ Fetch API和Promise polyfill注入成功");
|
playgroundLogger.infoJava("✅ Fetch API和Promise polyfill注入成功");
|
||||||
}
|
}
|
||||||
|
|
||||||
playgroundLogger.infoJava("🔒 安全的JavaScript引擎初始化成功(演练场)");
|
playgroundLogger.infoJava("初始化成功");
|
||||||
|
|
||||||
// 执行JavaScript代码
|
// 执行JavaScript代码
|
||||||
engine.eval(jsCode);
|
engine.eval(jsCode);
|
||||||
|
|||||||
@@ -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
|
@Test
|
||||||
public void testSetTimeout() {
|
public void testSetTimeout() {
|
||||||
System.out.println("\n[测试15] 设置超时时间 - setTimeout方法");
|
System.out.println("\n[测试15] 设置超时时间 - setTimeout方法");
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ module.exports = {
|
|||||||
'@vue/cli-plugin-babel/preset'
|
'@vue/cli-plugin-babel/preset'
|
||||||
],
|
],
|
||||||
plugins: [
|
plugins: [
|
||||||
'@vue/babel-plugin-transform-vue-jsx'
|
'@vue/babel-plugin-transform-vue-jsx',
|
||||||
|
'@babel/plugin-transform-class-static-block'
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,7 +5,8 @@
|
|||||||
"scripts": {
|
"scripts": {
|
||||||
"serve": "vue-cli-service serve",
|
"serve": "vue-cli-service serve",
|
||||||
"dev": "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"
|
"lint": "vue-cli-service lint"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
@@ -28,6 +29,7 @@
|
|||||||
"@babel/core": "^7.26.0",
|
"@babel/core": "^7.26.0",
|
||||||
"@babel/eslint-parser": "^7.25.9",
|
"@babel/eslint-parser": "^7.25.9",
|
||||||
"@babel/plugin-transform-class-properties": "^7.26.0",
|
"@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/babel-plugin-transform-vue-jsx": "^1.4.0",
|
||||||
"@vue/cli-plugin-babel": "~5.0.8",
|
"@vue/cli-plugin-babel": "~5.0.8",
|
||||||
"@vue/cli-plugin-eslint": "~5.0.8",
|
"@vue/cli-plugin-eslint": "~5.0.8",
|
||||||
@@ -35,7 +37,8 @@
|
|||||||
"compression-webpack-plugin": "^11.1.0",
|
"compression-webpack-plugin": "^11.1.0",
|
||||||
"eslint": "^9.0.0",
|
"eslint": "^9.0.0",
|
||||||
"eslint-plugin-vue": "^9.30.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": {
|
"eslintConfig": {
|
||||||
"root": true,
|
"root": true,
|
||||||
|
|||||||
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();
|
||||||
|
|
||||||
@@ -34,6 +34,7 @@ export default {
|
|||||||
const editorContainer = ref(null);
|
const editorContainer = ref(null);
|
||||||
let editor = null;
|
let editor = null;
|
||||||
let monaco = null;
|
let monaco = null;
|
||||||
|
let touchHandlers = { start: null, move: null };
|
||||||
|
|
||||||
const defaultOptions = {
|
const defaultOptions = {
|
||||||
value: props.modelValue,
|
value: props.modelValue,
|
||||||
@@ -94,13 +95,18 @@ export default {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 配置Monaco Editor使用国内CDN (npmmirror)
|
// 配置Monaco Editor使用本地打包的文件,而不是CDN
|
||||||
// npmmirror的路径格式: https://registry.npmmirror.com/包名/版本号/files/文件路径
|
if (loader.config) {
|
||||||
loader.config({
|
const vsPath = process.env.NODE_ENV === 'production'
|
||||||
paths: {
|
? './js/vs' // 生产环境使用相对路径
|
||||||
vs: 'https://registry.npmmirror.com/monaco-editor/0.55.1/files/min/vs'
|
: '/js/vs'; // 开发环境使用绝对路径
|
||||||
}
|
|
||||||
});
|
loader.config({
|
||||||
|
paths: {
|
||||||
|
vs: vsPath
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
// 初始化Monaco Editor
|
// 初始化Monaco Editor
|
||||||
monaco = await loader.init();
|
monaco = await loader.init();
|
||||||
@@ -131,6 +137,46 @@ export default {
|
|||||||
if (editorContainer.value) {
|
if (editorContainer.value) {
|
||||||
editorContainer.value.style.height = props.height;
|
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) {
|
} catch (error) {
|
||||||
console.error('Monaco Editor初始化失败:', error);
|
console.error('Monaco Editor初始化失败:', error);
|
||||||
console.error('错误详情:', error.stack);
|
console.error('错误详情:', error.stack);
|
||||||
@@ -174,6 +220,11 @@ export default {
|
|||||||
});
|
});
|
||||||
|
|
||||||
onBeforeUnmount(() => {
|
onBeforeUnmount(() => {
|
||||||
|
// 清理触摸事件监听器
|
||||||
|
if (editorContainer.value && touchHandlers.start && touchHandlers.move) {
|
||||||
|
editorContainer.value.removeEventListener('touchstart', touchHandlers.start);
|
||||||
|
editorContainer.value.removeEventListener('touchmove', touchHandlers.move);
|
||||||
|
}
|
||||||
if (editor) {
|
if (editor) {
|
||||||
editor.dispose();
|
editor.dispose();
|
||||||
}
|
}
|
||||||
@@ -195,10 +246,26 @@ export default {
|
|||||||
border: 1px solid #dcdfe6;
|
border: 1px solid #dcdfe6;
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
|
/* 允许用户选择文本 */
|
||||||
|
-webkit-user-select: text;
|
||||||
|
user-select: text;
|
||||||
}
|
}
|
||||||
|
|
||||||
.monaco-editor-container :deep(.monaco-editor) {
|
.monaco-editor-container :deep(.monaco-editor) {
|
||||||
border-radius: 4px;
|
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>
|
</style>
|
||||||
|
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -4,8 +4,10 @@ const path = require("path");
|
|||||||
function resolve(dir) {
|
function resolve(dir) {
|
||||||
return path.join(__dirname, dir)
|
return path.join(__dirname, dir)
|
||||||
}
|
}
|
||||||
|
|
||||||
const CompressionPlugin = require('compression-webpack-plugin');
|
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 = {
|
module.exports = {
|
||||||
productionSourceMap: false, // 是否在构建生产包时生成sourceMap文件,false将提高构建速度
|
productionSourceMap: false, // 是否在构建生产包时生成sourceMap文件,false将提高构建速度
|
||||||
@@ -43,7 +45,7 @@ module.exports = {
|
|||||||
'@': resolve('src')
|
'@': resolve('src')
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
// Monaco Editor配置 - 使用国内CDN
|
// Monaco Editor配置 - 使用本地打包
|
||||||
module: {
|
module: {
|
||||||
rules: [
|
rules: [
|
||||||
{
|
{
|
||||||
@@ -53,9 +55,18 @@ module.exports = {
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
plugins: [
|
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({
|
new CompressionPlugin({
|
||||||
test: /\.js$|\.html$|\.css/, // 匹配文件
|
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 插件实例
|
new FileManagerPlugin({ //初始化 filemanager-webpack-plugin 插件实例
|
||||||
events: {
|
events: {
|
||||||
@@ -70,7 +81,11 @@ module.exports = {
|
|||||||
{ source: '../webroot/nfd-front/view/.gitignore', options: { force: true } },
|
{ source: '../webroot/nfd-front/view/.gitignore', options: { force: true } },
|
||||||
],
|
],
|
||||||
copy: [
|
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并放在根目录
|
archive: [ //然后我们选择dist文件夹将之打包成dist.zip并放在根目录
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ proxyConf: server-proxy
|
|||||||
# JS演练场配置
|
# JS演练场配置
|
||||||
playground:
|
playground:
|
||||||
# 是否启用演练场,默认false不启用
|
# 是否启用演练场,默认false不启用
|
||||||
enabled: false
|
enabled: true
|
||||||
# 公开模式,默认false需要密码访问,设为true则无需密码
|
# 公开模式,默认false需要密码访问,设为true则无需密码
|
||||||
public: false
|
public: false
|
||||||
# 访问密码,建议修改默认密码!
|
# 访问密码,建议修改默认密码!
|
||||||
@@ -110,4 +110,4 @@ auths:
|
|||||||
# 123网盘:配置用户名密码
|
# 123网盘:配置用户名密码
|
||||||
ye:
|
ye:
|
||||||
username:
|
username:
|
||||||
password:
|
password:
|
||||||
|
|||||||
Reference in New Issue
Block a user