diff --git a/.gitignore b/.gitignore index 32c8707..6cfc6c6 100644 --- a/.gitignore +++ b/.gitignore @@ -31,6 +31,7 @@ target/ sdkTest.log app.yml app-local.yml +secret.yml #some local files diff --git a/Dockerfile b/Dockerfile index 8d50655..be3c89b 100644 --- a/Dockerfile +++ b/Dockerfile @@ -10,8 +10,13 @@ COPY ./web-service/target/netdisk-fast-download-bin.zip . RUN unzip netdisk-fast-download-bin.zip && \ mv netdisk-fast-download/* ./ && \ rm netdisk-fast-download-bin.zip && \ - chmod +x run.sh + chmod +x run.sh && \ + mkdir -p db logs -EXPOSE 6400 6401 +COPY ./docker-entrypoint.sh /docker-entrypoint.sh +RUN chmod +x /docker-entrypoint.sh -ENTRYPOINT ["sh", "run.sh"] +EXPOSE 6401 + +RUN addgroup --system appgroup && adduser --system --ingroup appgroup appuser +ENTRYPOINT ["/docker-entrypoint.sh"] diff --git a/core-database/pom.xml b/core-database/pom.xml index 398fd2d..0ae8493 100644 --- a/core-database/pom.xml +++ b/core-database/pom.xml @@ -65,7 +65,7 @@ org.postgresql postgresql - 42.7.3 + 42.7.11 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 aca7f7b..92bb224 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 @@ -127,8 +127,9 @@ public class RouterHandlerFactory implements BaseHttpApi { // 错误请求处理 mainRouter.errorHandler(405, ctx -> doFireJsonResultResponse(ctx, JsonResult .error("Method Not Allowed", 405))); - mainRouter.errorHandler(404, ctx -> ctx.response().setStatusCode(404).setChunked(true) - .end("Internal server error: 404 not found")); + mainRouter.errorHandler(404, ctx -> { + ctx.response().setStatusCode(404).end("404 not found"); + }); return mainRouter; } @@ -179,8 +180,9 @@ public class RouterHandlerFactory implements BaseHttpApi { if (ctx.statusCode() == 503 || ctx.failure() == null) { doFireJsonResultResponse(ctx, JsonResult.error("未知异常, 请联系管理员"), 503); } else { - ctx.failure().printStackTrace(); - doFireJsonResultResponse(ctx, JsonResult.error(ctx.failure().getMessage()), 500); + LOGGER.error("路由处理失败", ctx.failure()); + String msg = ctx.failure() != null ? ctx.failure().getMessage() : "未知异常"; + doFireJsonResultResponse(ctx, JsonResult.error(msg), 500); } }); } else if (method.isAnnotationPresent(SockRouteMapper.class)) { @@ -198,7 +200,7 @@ public class RouterHandlerFactory implements BaseHttpApi { try { ReflectionUtil.invokeWithArguments(method, instance, sock); } catch (Throwable e) { - e.printStackTrace(); + LOGGER.error("WebSocket处理异常", e); } }); if (url.endsWith("*")) { @@ -322,7 +324,7 @@ public class RouterHandlerFactory implements BaseHttpApi { parameterValueList.put(k, entity); } } catch (ClassNotFoundException e) { - e.printStackTrace(); + LOGGER.error("实体类绑定异常: {}", typeName, e); } } }); @@ -365,7 +367,7 @@ public class RouterHandlerFactory implements BaseHttpApi { Object entity = ParamUtil.multiMapToEntity(queryParams, aClass); parameterValueList.put(k, entity); } catch (Exception e) { - e.printStackTrace(); + LOGGER.error("参数绑定异常: {}", v.getRight().getName(), e); } } else if (parameterValueList.get(k) == null && JsonObject.class.getName().equals(v.getRight().getName())) { @@ -408,22 +410,19 @@ public class RouterHandlerFactory implements BaseHttpApi { doFireJsonResultResponse(ctx, JsonResult.data(null)); } - }).onFailure(e -> doFireJsonResultResponse(ctx, JsonResult.error(e.getMessage()), 500)); + }).onFailure(e -> { + LOGGER.error("请求处理失败", e); + String msg = e.getMessage() != null ? e.getMessage() : "服务器内部错误"; + doFireJsonResultResponse(ctx, JsonResult.error(msg), 500); + }); } else { doFireJsonResultResponse(ctx, JsonResult.data(data)); } } } catch (Throwable e) { - e.printStackTrace(); - String err = e.getMessage(); - if (e.getCause() != null) { - if (e.getCause() instanceof InvocationTargetException) { - err = ((InvocationTargetException) e.getCause()).getTargetException().getMessage(); - } else { - err = e.getCause().getMessage(); - } - } - doFireJsonResultResponse(ctx, JsonResult.error(err), 500); + LOGGER.error("请求处理异常", e); + String msg = e.getMessage() != null ? e.getMessage() : "服务器内部错误"; + doFireJsonResultResponse(ctx, JsonResult.error(msg), 500); } } diff --git a/core/src/main/java/cn/qaiu/vx/core/util/LocalConstant.java b/core/src/main/java/cn/qaiu/vx/core/util/LocalConstant.java index 0a7fa8c..6111e9b 100644 --- a/core/src/main/java/cn/qaiu/vx/core/util/LocalConstant.java +++ b/core/src/main/java/cn/qaiu/vx/core/util/LocalConstant.java @@ -1,7 +1,7 @@ package cn.qaiu.vx.core.util; -import java.util.HashMap; import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; /** * vertx 上下文外的本地容器 为不在vertx线程的方法传递数据 @@ -10,11 +10,10 @@ import java.util.Map; * @author QAIU */ public class LocalConstant { - private static final Map LOCAL_CONST = new HashMap<>(); + private static final Map LOCAL_CONST = new ConcurrentHashMap<>(); public static Map put(String k, Object v) { - if (LOCAL_CONST.containsKey(k)) return LOCAL_CONST; - LOCAL_CONST.put(k, v); + LOCAL_CONST.putIfAbsent(k, v); return LOCAL_CONST; } diff --git a/docker-entrypoint.sh b/docker-entrypoint.sh new file mode 100644 index 0000000..2bc18db --- /dev/null +++ b/docker-entrypoint.sh @@ -0,0 +1,9 @@ +#!/bin/sh +set -e + +# Fix permissions on volume-mounted directories (runs as root) +chown -R appuser:appgroup /app/db /app/logs /app/resources 2>/dev/null || true + +# Run Java directly - entrypoint is PID 1, exec makes Java PID 1 +# Docker SIGTERM goes directly to Java, triggering ShutdownHook +exec java -Xmx${JVM_XMX:-512M} ${JVM_OPTS} -Duser.timezone=${TZ:-Asia/Shanghai} -jar /app/netdisk-fast-download.jar diff --git a/parser/pom.xml b/parser/pom.xml index b5791d1..2a55f4d 100644 --- a/parser/pom.xml +++ b/parser/pom.xml @@ -12,7 +12,7 @@ cn.qaiu parser - 10.2.5 + ${parserVersion} jar cn.qaiu:parser @@ -35,9 +35,9 @@ - scm:git:https://github.com/qaiu/netdisk-fast-download.git - scm:git:ssh://git@github.com:qaiu/netdisk-fast-download.git - https://github.com/qaiu/netdisk-fast-download + scm:git:https://github.com/${github.owner}/${github.repo}.git + scm:git:ssh://git@github.com:${github.owner}/${github.repo}.git + https://github.com/${github.owner}/${github.repo} @@ -52,20 +52,19 @@ - 0.2.1 17 17 17 UTF-8 - 4.5.24 + 4.5.27 0.10.2 1.18.38 2.0.16 3.18.0 2.18.6 - 1.5.19 + 1.5.32 4.13.2 @@ -124,6 +123,41 @@ + + + org.codehaus.gmavenplus + gmavenplus-plugin + 4.1.1 + + + org.apache.groovy + groovy + 4.0.24 + + + + + initialize + execute + + + + + + + + + org.apache.maven.plugins diff --git a/parser/src/main/java/cn/qaiu/parser/customjs/JsParserExecutor.java b/parser/src/main/java/cn/qaiu/parser/customjs/JsParserExecutor.java index 69310b4..7226e0b 100644 --- a/parser/src/main/java/cn/qaiu/parser/customjs/JsParserExecutor.java +++ b/parser/src/main/java/cn/qaiu/parser/customjs/JsParserExecutor.java @@ -33,7 +33,7 @@ public class JsParserExecutor implements IPanTool, AutoCloseable { private static final Logger log = LoggerFactory.getLogger(JsParserExecutor.class); - private static WorkerExecutor EXECUTOR; + private static volatile WorkerExecutor EXECUTOR; private static final Object EXECUTOR_LOCK = new Object(); private static String FETCH_RUNTIME_JS = null; diff --git a/parser/src/main/java/cn/qaiu/util/AESUtils.java b/parser/src/main/java/cn/qaiu/util/AESUtils.java index 2fc41c7..a3a42b0 100644 --- a/parser/src/main/java/cn/qaiu/util/AESUtils.java +++ b/parser/src/main/java/cn/qaiu/util/AESUtils.java @@ -14,7 +14,6 @@ import java.security.spec.X509EncodedKeySpec; import java.util.Base64; import java.util.Date; import java.util.HexFormat; -import java.util.Random; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -299,7 +298,7 @@ public class AESUtils { //length用户要求产生字符串的长度 public static String getRandomString(int length){ String str="abcdefghijklmnopqrstuvwxyz0123456789"; - Random random=new Random(); + SecureRandom random=new SecureRandom(); StringBuilder sb=new StringBuilder(); for(int i=0;i${project.basedir}/web-service/target/package - - 4.5.24 + + 4.5.27 0.10.2 1.18.38 2.0.16 3.18.0 2.0.0 + 10.2.5 2.18.6 - 1.5.18 + 1.5.32 4.13.2 @@ -74,7 +75,7 @@ cn.qaiu parser - 10.2.5 + ${parserVersion} diff --git a/web-front/package.json b/web-front/package.json index b73baba..5abfb36 100644 --- a/web-front/package.json +++ b/web-front/package.json @@ -5,15 +5,15 @@ "scripts": { "serve": "vue-cli-service serve", "dev": "vue-cli-service serve", - "build": "vue-cli-service build && node scripts/compress-vs.js", - "build:no-compress": "vue-cli-service build", + "build": "node scripts/sync-version.js && vue-cli-service build && node scripts/compress-vs.js", + "build:no-compress": "node scripts/sync-version.js && vue-cli-service build", "lint": "vue-cli-service lint" }, "dependencies": { "@element-plus/icons-vue": "^2.3.1", "@monaco-editor/loader": "^1.4.0", "@vueuse/core": "^11.2.0", - "axios": "1.13.5", + "axios": "1.16.1", "clipboard": "^2.0.11", "core-js": "^3.8.3", "crypto-js": "^4.2.0", diff --git a/web-front/src/components/DarkMode.vue b/web-front/src/components/DarkMode.vue index b41e4ea..5a3870f 100644 --- a/web-front/src/components/DarkMode.vue +++ b/web-front/src/components/DarkMode.vue @@ -36,7 +36,6 @@ if (item) { const darkMode = ref(item) watch(darkMode, (newValue) => { - console.log(`darkMode: ${newValue}`) window.localStorage.setItem("darkMode", newValue); // 发射主题变化事件 diff --git a/web-front/src/views/ClientLinks.vue b/web-front/src/views/ClientLinks.vue index 0080a6b..ecb6bed 100644 --- a/web-front/src/views/ClientLinks.vue +++ b/web-front/src/views/ClientLinks.vue @@ -293,24 +293,6 @@ export default { return clientConfig[type]?.downloadUrl || '#' } - // 判断是否应该显示下载客户端按钮 - const shouldShowDownloadButton = (type) => { - const os = getOSInfo() - switch (type) { - case 'CURL': - // cURL 在 Windows 上可能需要安装 - return os === 'windows' - case 'ARIA2': - // Aria2 需要手动安装 - return true - case 'THUNDER': - // 迅雷主要在 Windows 上使用 - return os === 'windows' - default: - return false - } - } - // 获取操作系统信息 const getOSInfo = () => { const userAgent = navigator.userAgent.toLowerCase() @@ -369,7 +351,7 @@ export default { copyToClipboard(link) return } - window.open(link, '_blank') + window.open(link, '_blank', 'noopener,noreferrer') ElMessage.success('正在唤起迅雷下载') break @@ -383,13 +365,6 @@ export default { } } - // 下载客户端 - const downloadClient = (type) => { - const url = getClientDownloadUrl(type) - window.open(url, '_blank') - ElMessage.success(`正在跳转到 ${getClientDisplayName(type)} 下载页面`) - } - // 格式化文件大小 const formatFileSize = (bytes) => { if (!bytes) return '未知' @@ -440,9 +415,7 @@ export default { getTextareaRows, goBack, getClientLogo, - downloadClient, handleImageError, - shouldShowDownloadButton, getClientSupportsCookie, goToAuthConfig } diff --git a/web-front/src/views/Home.vue b/web-front/src/views/Home.vue index 6e9f719..8456887 100644 --- a/web-front/src/views/Home.vue +++ b/web-front/src/views/Home.vue @@ -19,11 +19,11 @@ --> - + 反馈 - + 源码 @@ -73,9 +73,9 @@ - NFD网盘直链解析0.3.0 + NFD网盘直链解析 {{ projectVersion }} - 支持网盘:蓝奏云、蓝奏云优享、小飞机盘、123云盘、iCloud、移动云空间、联想乐云、QQ闪传等 >> + 支持网盘:蓝奏云、蓝奏云优享、小飞机盘、123云盘、iCloud、移动云空间、联想乐云、QQ闪传等 >> 文件夹解析支持:蓝奏云、蓝奏云优享、小飞机盘、123云盘 @@ -90,7 +90,7 @@ - + 分享链接 读取剪切板 @@ -595,11 +595,10 @@ import fileTypeUtils from '@/utils/fileTypeUtils' import { ElMessage, ElMessageBox } from 'element-plus' import { playgroundApi } from '@/utils/playgroundApi' import { testConnection, autoDetect, addDownload, getConfig, saveConfig } from '@/utils/downloaderService' - -export const previewBaseUrl = 'https://nfd-parser.github.io/nfd-preview/preview.html?src='; +import { PREVIEW_BASE_URL } from '@/utils/constants' export default { - name: 'App', + name: 'Home', components: { DarkMode, DirectoryTree, DownloadDialog }, mixins: [fileTypeUtils], data() { @@ -617,7 +616,7 @@ export default { parseResult: {}, downloadUrl: null, directLink: '', - previewBaseUrl, + previewBaseUrl: PREVIEW_BASE_URL, // 功能结果 markdownText: '', @@ -714,6 +713,12 @@ export default { } }, computed: { + githubRepoUrl() { + return process.env.VUE_APP_GITHUB_REPO_URL + }, + projectVersion() { + return process.env.VUE_APP_VERSION || '0.0.0' + }, // 检查是否配置了认证信息(针对当前链接的网盘类型) hasAuthConfig() { const panType = this.getCurrentPanType() @@ -959,18 +964,16 @@ export default { // 优先使用个人配置 if (this.allAuthConfigs[panType]) { config = this.allAuthConfigs[panType] - console.log(`[认证] 使用个人配置: ${this.getPanDisplayName(panType)}`) } else { // 从后端随机获取捐赠账号(后端已加密,直接使用 encryptedAuth) try { const response = await axios.get(`${this.baseAPI}/v2/randomAuth`, { params: { panType } }) const encryptedAuth = response.data?.data?.encryptedAuth if (encryptedAuth) { - console.log(`[认证] 使用捐赠账号: ${this.getPanDisplayName(panType)}`) return encryptedAuth } } catch (e) { - console.log(`[认证] 无可用捐赠账号: ${this.getPanDisplayName(panType)}`) + // no available donated account } return '' } @@ -1091,17 +1094,45 @@ export default { } }, - // 识别并转换短链输入(如 lz:shareKey@pwd) + // 识别并转换短链输入(如 lz:shareKey@pwd),或从文本中提取链接 normalizeShortcutInput() { - const shortInfo = this.expandShortFormat(this.link) - if (!shortInfo) return + if (!this.link) return + const trimmed = this.link.trim() + if (!trimmed) return - this.link = shortInfo.link - if (!this.password && shortInfo.pwd) { - this.password = shortInfo.pwd + // 已经是直接链接,跳过 + if (trimmed.startsWith('http://') || trimmed.startsWith('https://')) return + + // 尝试短格式 + const shortInfo = this.expandShortFormat(trimmed) + if (shortInfo) { + this.link = shortInfo.link + if (!this.password && shortInfo.pwd) { + this.password = shortInfo.pwd + } + this.$message.success(`已识别短格式并自动转换,网盘类型: ${shortInfo.name}`) + this.updateDirectLink() + return } - this.$message.success(`已识别短格式并自动转换,网盘类型: ${shortInfo.name}`) - this.updateDirectLink() + + // 从文本中自动提取链接 + const linkInfo = parserUrl.parseLink(trimmed) + if (linkInfo.link) { + this.link = linkInfo.link + const pwd = parserUrl.parsePwd(trimmed) + if (!this.password && pwd) { + this.password = pwd + } + this.$message.success(`已从文本中识别到 ${linkInfo.name} 分享链接`) + this.updateDirectLink() + } + }, + + // 粘贴事件:从粘贴的文本中自动提取链接 + onPaste(e) { + this.$nextTick(() => { + this.normalizeShortcutInput() + }) }, // 清除结果 @@ -1126,17 +1157,24 @@ export default { params.auth = authParam } const response = await axios.get(`${this.baseAPI}${endpoint}`, { params }) - + if (response.data.code === 200) { // this.$message.success(response.data.msg || '操作成功') return response.data } else { - // 在页面右下角显示一个“查看详情”按钮 可以查看原json + // 在页面右下角显示一个”查看详情”按钮 可以查看原json this.errorDetail = response?.data this.errorButtonVisible = true throw new Error(response.data.msg || '操作失败') } } catch (error) { + // HTTP 非2xx时,从响应体中提取后端返回的错误信息 + if (error.response?.data?.msg) { + this.errorDetail = error.response.data + this.errorButtonVisible = true + this.$message.error(error.response.data.msg) + throw new Error(error.response.data.msg) + } this.$message.error(error.message || '网络错误') throw error } finally { @@ -1309,7 +1347,7 @@ export default { // 文件点击处理 handleFileClick(file) { if (file.parserUrl) { - window.open(file.parserUrl, '_blank') + window.open(file.parserUrl, '_blank', 'noopener,noreferrer') } else { this.$message.warning('该文件暂无下载链接') } @@ -1319,7 +1357,6 @@ export default { async getPaste(isManual = false) { try { const text = await navigator.clipboard.readText() - console.log('获取到的文本内容是:', text) const shortInfo = this.expandShortFormat(text) if (shortInfo) { @@ -1364,7 +1401,9 @@ export default { } } catch (error) { console.error('读取剪切板失败:', error) - this.$message.error('读取剪切板失败,请检查浏览器权限') + if (isManual) { + this.$message.warning('读取剪切板失败,请手动粘贴链接到输入框') + } } }, @@ -1439,7 +1478,7 @@ export default { 错误信息:${JSON.stringify(this.errorDetail, null, 2)}`; navigator.clipboard.writeText(text).then(() => { this.$message.success('已复制分享信息和错误详情'); - window.open('https://github.com/qaiu/netdisk-fast-download/issues/new', '_blank'); + window.open(`${this.githubRepoUrl}/issues/new`, '_blank', 'noopener,noreferrer'); }).catch(() => { this.$message.error('复制失败'); }); @@ -1786,19 +1825,26 @@ export default { } // 监听窗口焦点事件 - window.addEventListener('focus', () => { + this._onFocusHandler = () => { if (this.autoReadClipboard) { this.hasClipboardSuccessTip = false // 聚焦时重置,只提示一次 this.getPaste() } - }) + } + window.addEventListener('focus', this._onFocusHandler) // 首次打开页面弹出风险提示 if (!window.localStorage.getItem('nfd_risk_ack')) { this.showRiskDialog = true } }, - + + beforeUnmount() { + if (this._onFocusHandler) { + window.removeEventListener('focus', this._onFocusHandler) + } + }, + watch: { downloadUrl(val) { if (!val) { diff --git a/web-front/src/views/Playground.vue b/web-front/src/views/Playground.vue index 42b79f2..a68693e 100644 --- a/web-front/src/views/Playground.vue +++ b/web-front/src/views/Playground.vue @@ -653,22 +653,22 @@ 更多详细信息,请参考 GitHub 仓库文档: - + JavaScript 解析器开发指南 - + 自定义解析器扩展指南 - + 快速开始教程 - + 解析器模块文档 @@ -858,6 +858,7 @@ export default { }, setup() { const router = useRouter(); + const githubRepoUrl = process.env.VUE_APP_GITHUB_REPO_URL; // 语言常量 const LANGUAGE = { @@ -1178,7 +1179,7 @@ function parseById(shareLinkInfo, http, logger) { // 新窗口打开首页 const goHomeInNewWindow = () => { - window.open('/', '_blank'); + window.open('/', '_blank', 'noopener,noreferrer'); }; // 检查是否有未保存的文件 @@ -1758,7 +1759,6 @@ function parseFileList(shareLinkInfo, http, logger) { testParams.value.method ); - console.log('测试结果:', result); testResult.value = result; // 将日志添加到控制台 @@ -1820,10 +1820,8 @@ function parseFileList(shareLinkInfo, http, logger) { loadingList.value = true; try { const result = await playgroundApi.getParserList(); - console.log('获取解析器列表响应:', result); // 检查响应格式 if (result.code === 200 || result.success) { - console.log('列表数据:', result.data); parserList.value = result.data || []; } else if (result.data && Array.isArray(result.data)) { // 如果data直接是数组 @@ -1857,7 +1855,6 @@ function parseFileList(shareLinkInfo, http, logger) { try { const codeToPublish = currentCode.value; const result = await playgroundApi.saveParser(codeToPublish); - console.log('保存解析器响应:', result); // 检查响应格式 if (result.code === 200 || result.success) { // 从响应或代码中提取type信息 @@ -2223,6 +2220,8 @@ curl "${baseUrl}/json/parser?url=${encodeURIComponent(exampleUrl)}" }, 100); }; + let themeObserver = null; + onMounted(async () => { // 初始化移动端检测 updateIsMobile(); @@ -2249,10 +2248,10 @@ curl "${baseUrl}/json/parser?url=${encodeURIComponent(exampleUrl)}" const html = document.documentElement; if (html && html.classList) { try { - const observer = new MutationObserver(() => { + themeObserver = new MutationObserver(() => { checkDarkMode(); }); - observer.observe(html, { + themeObserver.observe(html, { attributes: true, attributeFilter: ['class', 'data-theme'] }); @@ -2269,9 +2268,11 @@ curl "${baseUrl}/json/parser?url=${encodeURIComponent(exampleUrl)}" window.removeEventListener('resize', updateIsMobile); // 移除页面关闭/刷新前的提示 window.removeEventListener('beforeunload', handleBeforeUnload); + themeObserver?.disconnect(); }); return { + githubRepoUrl, LANGUAGE, editorRef, jsCode, diff --git a/web-front/src/views/ShowFile.vue b/web-front/src/views/ShowFile.vue index 37d8436..52e67ae 100644 --- a/web-front/src/views/ShowFile.vue +++ b/web-front/src/views/ShowFile.vue @@ -32,7 +32,7 @@
更多详细信息,请参考 GitHub 仓库文档: