diff --git a/core/src/main/java/cn/qaiu/vx/core/handlerfactory/RouterHandlerFactory.java b/core/src/main/java/cn/qaiu/vx/core/handlerfactory/RouterHandlerFactory.java index c9892b8..cfb886b 100644 --- a/core/src/main/java/cn/qaiu/vx/core/handlerfactory/RouterHandlerFactory.java +++ b/core/src/main/java/cn/qaiu/vx/core/handlerfactory/RouterHandlerFactory.java @@ -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> interceptorSet = getInterceptorSet(); Route route0 = mainRouter.route("/*"); diff --git a/parser/src/main/java/cn/qaiu/parser/customjs/JsPlaygroundExecutor.java b/parser/src/main/java/cn/qaiu/parser/customjs/JsPlaygroundExecutor.java index a579972..e0e0c38 100644 --- a/parser/src/main/java/cn/qaiu/parser/customjs/JsPlaygroundExecutor.java +++ b/parser/src/main/java/cn/qaiu/parser/customjs/JsPlaygroundExecutor.java @@ -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(); } diff --git a/parser/src/main/java/cn/qaiu/parser/impl/LzTool.java b/parser/src/main/java/cn/qaiu/parser/impl/LzTool.java index 9a19acd..fb9a229 100644 --- a/parser/src/main/java/cn/qaiu/parser/impl/LzTool.java +++ b/parser/src/main/java/cn/qaiu/parser/impl/LzTool.java @@ -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 diff --git a/web-front/MONACO_EDITOR_NPM.md b/web-front/MONACO_EDITOR_NPM.md index b3fa91d..c90f252 100644 --- a/web-front/MONACO_EDITOR_NPM.md +++ b/web-front/MONACO_EDITOR_NPM.md @@ -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),但这是正 + + diff --git a/web-front/package.json b/web-front/package.json index b26be00..8a963dd 100644 --- a/web-front/package.json +++ b/web-front/package.json @@ -16,7 +16,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", diff --git a/web-front/public/index.html b/web-front/public/index.html index 8d7336c..d3e118b 100644 --- a/web-front/public/index.html +++ b/web-front/public/index.html @@ -9,8 +9,8 @@ content="Netdisk fast download,网盘直链解析工具"> - - + + diff --git a/web-front/src/components/DarkMode.vue b/web-front/src/components/DarkMode.vue index 6e420f3..b41e4ea 100644 --- a/web-front/src/components/DarkMode.vue +++ b/web-front/src/components/DarkMode.vue @@ -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') } }) diff --git a/web-front/src/components/DirectoryTree.vue b/web-front/src/components/DirectoryTree.vue index 7cd2e73..1c161a0 100644 --- a/web-front/src/components/DirectoryTree.vue +++ b/web-front/src/components/DirectoryTree.vue @@ -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' diff --git a/web-front/src/components/MonacoEditor.vue b/web-front/src/components/MonacoEditor.vue index 8060b23..6a5dbbb 100644 --- a/web-front/src/components/MonacoEditor.vue +++ b/web-front/src/components/MonacoEditor.vue @@ -94,6 +94,14 @@ export default { return; } + // 配置Monaco Editor使用国内CDN (npmmirror) + // npmmirror的路径格式: https://registry.npmmirror.com/包名/版本号/files/文件路径 + loader.config({ + paths: { + vs: 'https://registry.npmmirror.com/monaco-editor/0.55.1/files/min/vs' + } + }); + // 初始化Monaco Editor monaco = await loader.init(); diff --git a/web-front/src/router/index.js b/web-front/src/router/index.js index 8b3bf55..099e63d 100644 --- a/web-front/src/router/index.js +++ b/web-front/src/router/index.js @@ -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({ diff --git a/web-front/src/utils/playgroundApi.js b/web-front/src/utils/playgroundApi.js index 44130b2..566f87f 100644 --- a/web-front/src/utils/playgroundApi.js +++ b/web-front/src/utils/playgroundApi.js @@ -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 { diff --git a/web-front/src/views/Home.vue b/web-front/src/views/Home.vue index 306565b..9887fe2 100644 --- a/web-front/src/views/Home.vue +++ b/web-front/src/views/Home.vue @@ -218,7 +218,7 @@
内部版本: {{ buildVersion }} - + 脚本演练场
@@ -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() diff --git a/web-front/src/views/NotFound.vue b/web-front/src/views/NotFound.vue new file mode 100644 index 0000000..20e96f9 --- /dev/null +++ b/web-front/src/views/NotFound.vue @@ -0,0 +1,135 @@ + + + + + + + diff --git a/web-front/src/views/Playground.vue b/web-front/src/views/Playground.vue index fb1211f..2640d76 100644 --- a/web-front/src/views/Playground.vue +++ b/web-front/src/views/Playground.vue @@ -21,12 +21,26 @@ 正在检查访问权限... + +
+
+
+ +
+
演练场功能已禁用
+
请联系管理员启用演练场功能
+ + 返回首页 + +
+
+
-
JS解析器演练场
+
脚本解析器演练场
请输入访问密码
- JS解析器演练场 + 脚本解析器演练场 JavaScript (ES5) @@ -118,8 +132,133 @@ - - + +
+ +
+ +
+ + +
+ + + + +
+ + + + + + + + parse + parseFileList + + + + + 执行测试 + + + +
+
+
+ + + + + +
+
+ + +
+
结果数据:
+
+ 结果内容:{{ testResult.result }} +
+ +
+ +
+
错误信息:
+ +
+ + +
{{ testResult.stackTrace }}
+
+
+
+
+ +
+
执行时间:
+
{{ testResult.executionTime }}ms
+
+
+
+ +
+
+
+
+
+
+ + + +
- + + :size="splitSizes[1]" min-size="20" class="test-pane" style="margin-left: 10px;">
@@ -331,7 +470,7 @@
-

什么是JS解析器演练场?

+

什么是脚本解析器演练场?

演练场允许您快速编写、测试和发布JavaScript解析脚本,无需重启服务器即可调试和验证解析逻辑。

快速开始

@@ -459,7 +598,7 @@ - +