mirror of
https://github.com/qaiu/netdisk-fast-download.git
synced 2026-06-19 19:57:28 +00:00
Compare commits
4 Commits
v3.0.4
...
copilot/up
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b05b2f15b9 | ||
|
|
895661b8df | ||
|
|
93d676207c | ||
|
|
b3083c1286 |
249
.github/workflows/maven.yml
vendored
249
.github/workflows/maven.yml
vendored
@@ -1,5 +1,14 @@
|
|||||||
name: Java CI(Maven 构建 + Docker 镜像 + 原生环境打包)
|
# This workflow will build a Java project with Maven, and cache/restore any dependencies to improve the workflow execution time
|
||||||
|
# For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-java-with-maven
|
||||||
|
|
||||||
|
# This workflow uses actions that are not certified by GitHub.
|
||||||
|
# They are provided by a third-party and are governed by
|
||||||
|
# separate terms of service, privacy policy, and support
|
||||||
|
# documentation.
|
||||||
|
|
||||||
|
name: Java CI with Maven
|
||||||
|
|
||||||
|
# The API requires write permission on the repository to submit dependencies
|
||||||
permissions:
|
permissions:
|
||||||
contents: write
|
contents: write
|
||||||
packages: write
|
packages: write
|
||||||
@@ -7,9 +16,9 @@ permissions:
|
|||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
tags:
|
tags:
|
||||||
- '*'
|
- '*' # 只有推送tag时才会触发构建
|
||||||
branches-ignore:
|
branches-ignore:
|
||||||
- '*'
|
- '*' # 排除所有分支的提交
|
||||||
paths-ignore:
|
paths-ignore:
|
||||||
- 'bin/**'
|
- 'bin/**'
|
||||||
- '.github/**'
|
- '.github/**'
|
||||||
@@ -23,250 +32,70 @@ on:
|
|||||||
- "main"
|
- "main"
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
# ================================================================
|
|
||||||
# 阶段一:构建前端 + Maven 打包(只执行一次,产物共享)
|
|
||||||
# ================================================================
|
|
||||||
build:
|
build:
|
||||||
name: 编译构建
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- name: 检出代码
|
|
||||||
uses: actions/checkout@v3
|
|
||||||
|
|
||||||
- name: 设置 Node.js 18
|
runs-on: ubuntu-latest
|
||||||
uses: actions/setup-node@v4
|
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v3
|
||||||
|
|
||||||
|
- uses: actions/setup-node@v4
|
||||||
with:
|
with:
|
||||||
node-version: '18'
|
node-version: '18'
|
||||||
|
|
||||||
- name: 设置 JDK 17
|
- name: Set up JDK 17
|
||||||
uses: actions/setup-java@v3
|
uses: actions/setup-java@v3
|
||||||
with:
|
with:
|
||||||
java-version: '17'
|
java-version: '17'
|
||||||
distribution: 'temurin'
|
distribution: 'temurin'
|
||||||
cache: maven
|
cache: maven
|
||||||
|
|
||||||
- name: 构建前端
|
- name: Build Frontend
|
||||||
run: cd web-front && yarn install && yarn run build
|
run: cd web-front && yarn install && yarn run build
|
||||||
|
|
||||||
- name: Maven 编译打包
|
- name: Build with Maven
|
||||||
run: mvn -B package -DskipTests --file pom.xml
|
run: mvn -B package -DskipTests --file pom.xml
|
||||||
|
|
||||||
- name: 更新依赖图谱
|
# Optional: Uploads the full dependency graph to GitHub to improve the quality of Dependabot alerts this repository can receive
|
||||||
|
- name: Update dependency graph
|
||||||
uses: advanced-security/maven-dependency-submission-action@v3
|
uses: advanced-security/maven-dependency-submission-action@v3
|
||||||
if: github.event_name != 'pull_request'
|
if: github.event_name != 'pull_request'
|
||||||
continue-on-error: true
|
|
||||||
with:
|
with:
|
||||||
ignore-maven-wrapper: true
|
ignore-maven-wrapper: true
|
||||||
|
|
||||||
- name: 分享应用打包目录(供原生包和 Docker 复用)
|
# - uses: release-drafter/release-drafter@v5
|
||||||
if: github.event_name != 'pull_request'
|
# env:
|
||||||
uses: actions/upload-artifact@v4
|
# GITHUB_TOKEN: ${{ secrets.ACCESS_TOKEN }}
|
||||||
with:
|
|
||||||
name: app-package
|
|
||||||
path: web-service/target/package/
|
|
||||||
|
|
||||||
- name: 分享 bin-zip(供 Docker 复用)
|
- name: Upload Artifact
|
||||||
if: github.event_name != 'pull_request'
|
|
||||||
uses: actions/upload-artifact@v4
|
uses: actions/upload-artifact@v4
|
||||||
with:
|
with:
|
||||||
name: app-bin-zip
|
|
||||||
path: web-service/target/netdisk-fast-download-bin.zip
|
path: web-service/target/netdisk-fast-download-bin.zip
|
||||||
|
|
||||||
# ================================================================
|
- name: Login to GitHub Container Registry
|
||||||
# 阶段二-A:Docker 镜像构建(并行)
|
|
||||||
# ================================================================
|
|
||||||
docker:
|
|
||||||
name: Docker 镜像
|
|
||||||
needs: build
|
|
||||||
if: github.event_name != 'pull_request'
|
if: github.event_name != 'pull_request'
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- name: 检出代码
|
|
||||||
uses: actions/checkout@v3
|
|
||||||
|
|
||||||
- name: 下载 bin-zip 产物
|
|
||||||
uses: actions/download-artifact@v4
|
|
||||||
with:
|
|
||||||
name: app-bin-zip
|
|
||||||
path: web-service/target/
|
|
||||||
|
|
||||||
- name: 登录 GitHub 容器仓库
|
|
||||||
uses: docker/login-action@v3
|
uses: docker/login-action@v3
|
||||||
with:
|
with:
|
||||||
registry: ghcr.io
|
registry: ghcr.io
|
||||||
username: ${{ github.actor }}
|
username: ${{ github.actor }}
|
||||||
password: ${{ secrets.GITHUB_TOKEN }}
|
password: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|
||||||
- name: 设置 QEMU(多平台构建支持)
|
- name: Set up Docker Buildx
|
||||||
uses: docker/setup-qemu-action@v3
|
|
||||||
|
|
||||||
- name: 设置 Docker Buildx
|
|
||||||
uses: docker/setup-buildx-action@v3
|
uses: docker/setup-buildx-action@v3
|
||||||
|
|
||||||
- name: 构建并推送 Docker 镜像
|
- name: Extract git tag
|
||||||
|
id: tag
|
||||||
|
run: |
|
||||||
|
GIT_TAG=$(git tag --points-at HEAD | head -n 1)
|
||||||
|
echo "tag=$GIT_TAG" >> $GITHUB_OUTPUT
|
||||||
|
|
||||||
|
- name: Build and push Docker image
|
||||||
|
if: github.event_name != 'pull_request'
|
||||||
uses: docker/build-push-action@v5
|
uses: docker/build-push-action@v5
|
||||||
with:
|
with:
|
||||||
context: .
|
context: .
|
||||||
push: true
|
push: true
|
||||||
platforms: linux/amd64,linux/arm64,linux/arm/v7
|
platforms: linux/amd64,linux/arm64,linux/arm/v7
|
||||||
tags: |
|
tags: |
|
||||||
ghcr.io/${{ github.repository }}:${{ github.ref_name }}
|
ghcr.io/qaiu/netdisk-fast-download:${{ steps.tag.outputs.tag }}
|
||||||
ghcr.io/${{ github.repository }}:latest
|
ghcr.io/qaiu/netdisk-fast-download:latest
|
||||||
|
|
||||||
|
|
||||||
# ================================================================
|
|
||||||
# 阶段二-B:原生环境打包 Linux + Windows(并行)
|
|
||||||
# ================================================================
|
|
||||||
native-package:
|
|
||||||
name: 原生环境打包 → ${{ matrix.artifact-name }}
|
|
||||||
needs: build
|
|
||||||
if: github.event_name != 'pull_request'
|
|
||||||
strategy:
|
|
||||||
matrix:
|
|
||||||
include:
|
|
||||||
- os: ubuntu-latest
|
|
||||||
artifact-name: netdisk-fast-download-linux-amd64
|
|
||||||
- os: windows-latest
|
|
||||||
artifact-name: netdisk-fast-download-windows-amd64
|
|
||||||
runs-on: ${{ matrix.os }}
|
|
||||||
defaults:
|
|
||||||
run:
|
|
||||||
shell: bash
|
|
||||||
|
|
||||||
steps:
|
|
||||||
- name: 设置 JDK 17
|
|
||||||
uses: actions/setup-java@v3
|
|
||||||
with:
|
|
||||||
java-version: '17'
|
|
||||||
distribution: 'temurin'
|
|
||||||
|
|
||||||
- name: 下载 Maven 构建产物
|
|
||||||
uses: actions/download-artifact@v4
|
|
||||||
with:
|
|
||||||
name: app-package
|
|
||||||
path: web-service/target/package
|
|
||||||
|
|
||||||
# ============================================================
|
|
||||||
# jdeps 分析 → 确定所需 JDK 模块
|
|
||||||
# ============================================================
|
|
||||||
- name: 分析所需 JDK 模块(jdeps)
|
|
||||||
run: |
|
|
||||||
MAIN_JAR="web-service/target/package/netdisk-fast-download.jar"
|
|
||||||
LIB_DIR="web-service/target/package/lib"
|
|
||||||
|
|
||||||
CP=""
|
|
||||||
for jar in "$LIB_DIR"/*.jar; do
|
|
||||||
CP="$CP${CP:+:}$jar"
|
|
||||||
done
|
|
||||||
|
|
||||||
RAW_MODULES=$(jdeps --print-module-deps --ignore-missing-deps --multi-release 17 \
|
|
||||||
--class-path "$CP" "$MAIN_JAR" 2>/dev/null | head -n 1 | tr -d '\r\n' || true)
|
|
||||||
|
|
||||||
if [ -z "$RAW_MODULES" ] || [[ "$RAW_MODULES" == *"Missing"* ]] || [[ "$RAW_MODULES" == *"Error"* ]]; then
|
|
||||||
# ⚠️ 回退列表:若项目新增了需要 java.* / jdk.* 模块的依赖,需同步更新此处
|
|
||||||
RAW_MODULES="java.base,java.logging,java.sql,java.naming,java.management,java.xml,jdk.unsupported,java.net.http,java.instrument,java.security.jgss,java.security.sasl,java.desktop,jdk.crypto.ec"
|
|
||||||
echo "jdeps 分析失败,使用回退模块列表"
|
|
||||||
else
|
|
||||||
# 补上 jdeps 无法检测的反射/SPI依赖
|
|
||||||
RAW_MODULES="$RAW_MODULES,java.desktop,jdk.crypto.ec"
|
|
||||||
fi
|
|
||||||
|
|
||||||
echo "detected modules: $RAW_MODULES"
|
|
||||||
printf 'JDK_MODULES=%s\n' "$RAW_MODULES" >> $GITHUB_ENV
|
|
||||||
|
|
||||||
# ============================================================
|
|
||||||
# jlink 生成精简 JRE
|
|
||||||
# ============================================================
|
|
||||||
- name: 生成精简 JRE(jlink)
|
|
||||||
run: |
|
|
||||||
jlink \
|
|
||||||
--module-path "$JAVA_HOME/jmods" \
|
|
||||||
--add-modules "$JDK_MODULES" \
|
|
||||||
--output "native-package/netdisk-fast-download/jre" \
|
|
||||||
--strip-debug \
|
|
||||||
--compress=2 \
|
|
||||||
--no-header-files \
|
|
||||||
--no-man-pages
|
|
||||||
|
|
||||||
echo "JRE size:"
|
|
||||||
du -sh native-package/netdisk-fast-download/jre || true
|
|
||||||
|
|
||||||
# Windows: 确保 MSVC 运行时 DLL 到位
|
|
||||||
if [[ "$RUNNER_OS" == "Windows" ]]; then
|
|
||||||
JRE_BIN="native-package/netdisk-fast-download/jre/bin"
|
|
||||||
for dll in vcruntime140.dll msvcp140.dll vcruntime140_1.dll; do
|
|
||||||
if [ ! -f "$JRE_BIN/$dll" ] && [ -f "$JAVA_HOME/bin/$dll" ]; then
|
|
||||||
echo "jlink 未包含 $dll,从 JDK 补拷"
|
|
||||||
cp "$JAVA_HOME/bin/$dll" "$JRE_BIN/"
|
|
||||||
fi
|
|
||||||
done
|
|
||||||
echo "=== JRE bin 目录 DLL 清单 ==="
|
|
||||||
ls -la "$JRE_BIN"/*.dll 2>/dev/null || echo "(无 .dll 文件)"
|
|
||||||
fi
|
|
||||||
|
|
||||||
# ============================================================
|
|
||||||
# 组装包目录
|
|
||||||
# ============================================================
|
|
||||||
- name: 组装包目录
|
|
||||||
run: |
|
|
||||||
PKG="native-package/netdisk-fast-download"
|
|
||||||
SRC="web-service/target/package"
|
|
||||||
|
|
||||||
cp "$SRC/netdisk-fast-download.jar" "$PKG/"
|
|
||||||
cp -r "$SRC/lib" "$PKG/"
|
|
||||||
cp -r "$SRC/resources" "$PKG/"
|
|
||||||
cp -r "$SRC/webroot" "$PKG/"
|
|
||||||
|
|
||||||
mkdir -p "$PKG/db"
|
|
||||||
mkdir -p "$PKG/logs"
|
|
||||||
|
|
||||||
# ============================================================
|
|
||||||
# 生成启动脚本
|
|
||||||
# ============================================================
|
|
||||||
- name: 生成启动脚本(Linux)
|
|
||||||
run: |
|
|
||||||
PKG="native-package/netdisk-fast-download"
|
|
||||||
echo '#!/bin/bash' > "$PKG/run.sh"
|
|
||||||
echo 'DIR="$(cd "$(dirname "$0")" && pwd)"' >> "$PKG/run.sh"
|
|
||||||
echo 'cd "$DIR" || exit 1' >> "$PKG/run.sh"
|
|
||||||
echo 'exec "$DIR/jre/bin/java" -Xmx512M -Dfile.encoding=utf-8 -jar "$DIR/netdisk-fast-download.jar" "$@"' >> "$PKG/run.sh"
|
|
||||||
chmod +x "$PKG/run.sh"
|
|
||||||
|
|
||||||
- name: 生成启动脚本(Windows)
|
|
||||||
run: |
|
|
||||||
PKG="native-package/netdisk-fast-download"
|
|
||||||
echo '@echo off' > "$PKG/run.bat"
|
|
||||||
echo 'chcp 65001 > nul' >> "$PKG/run.bat"
|
|
||||||
echo 'pushd %~dp0' >> "$PKG/run.bat"
|
|
||||||
echo '"%~dp0jre\bin\java.exe" -Xmx512M -Dfile.encoding=utf-8 -jar "%~dp0netdisk-fast-download.jar" %*' >> "$PKG/run.bat"
|
|
||||||
|
|
||||||
# ============================================================
|
|
||||||
# 打包为 zip
|
|
||||||
# ============================================================
|
|
||||||
- name: 打包 ZIP(Linux)
|
|
||||||
if: runner.os == 'Linux'
|
|
||||||
run: |
|
|
||||||
cd native-package
|
|
||||||
zip -r "../${{ matrix.artifact-name }}.zip" netdisk-fast-download/
|
|
||||||
|
|
||||||
- name: 打包 ZIP(Windows)
|
|
||||||
if: runner.os == 'Windows'
|
|
||||||
shell: pwsh
|
|
||||||
run: |
|
|
||||||
Compress-Archive -Path native-package/netdisk-fast-download -DestinationPath "${{ matrix.artifact-name }}.zip"
|
|
||||||
|
|
||||||
# ============================================================
|
|
||||||
# 上传产物
|
|
||||||
# ============================================================
|
|
||||||
- name: 上传原生安装包
|
|
||||||
uses: actions/upload-artifact@v4
|
|
||||||
with:
|
|
||||||
name: ${{ matrix.artifact-name }}
|
|
||||||
path: ${{ matrix.artifact-name }}.zip
|
|
||||||
|
|
||||||
- name: 上传到 Release
|
|
||||||
uses: softprops/action-gh-release@v2
|
|
||||||
with:
|
|
||||||
files: ${{ matrix.artifact-name }}.zip
|
|
||||||
tag_name: ${{ github.ref_name }}
|
|
||||||
generate_release_notes: true
|
|
||||||
|
|||||||
2
.gitignore
vendored
2
.gitignore
vendored
@@ -31,7 +31,6 @@ target/
|
|||||||
sdkTest.log
|
sdkTest.log
|
||||||
app.yml
|
app.yml
|
||||||
app-local.yml
|
app-local.yml
|
||||||
secret.yml
|
|
||||||
|
|
||||||
|
|
||||||
#some local files
|
#some local files
|
||||||
@@ -92,4 +91,3 @@ yarn-error.log*
|
|||||||
**/${project.build.directory}/
|
**/${project.build.directory}/
|
||||||
**/${project.basedir}/target/
|
**/${project.basedir}/target/
|
||||||
**/${basedir}/target/
|
**/${basedir}/target/
|
||||||
.spec-workflow/
|
|
||||||
|
|||||||
11
Dockerfile
11
Dockerfile
@@ -10,13 +10,8 @@ COPY ./web-service/target/netdisk-fast-download-bin.zip .
|
|||||||
RUN unzip netdisk-fast-download-bin.zip && \
|
RUN unzip netdisk-fast-download-bin.zip && \
|
||||||
mv netdisk-fast-download/* ./ && \
|
mv netdisk-fast-download/* ./ && \
|
||||||
rm netdisk-fast-download-bin.zip && \
|
rm netdisk-fast-download-bin.zip && \
|
||||||
chmod +x run.sh && \
|
chmod +x run.sh
|
||||||
mkdir -p db logs
|
|
||||||
|
|
||||||
COPY ./docker-entrypoint.sh /docker-entrypoint.sh
|
EXPOSE 6400 6401
|
||||||
RUN chmod +x /docker-entrypoint.sh
|
|
||||||
|
|
||||||
EXPOSE 6401
|
ENTRYPOINT ["sh", "run.sh"]
|
||||||
|
|
||||||
RUN addgroup --system appgroup && adduser --system --ingroup appgroup appuser
|
|
||||||
ENTRYPOINT ["/docker-entrypoint.sh"]
|
|
||||||
|
|||||||
13
README.md
13
README.md
@@ -1,9 +1,9 @@
|
|||||||
# 一款网盘分享链接云解析快速下载服务
|
# 一款网盘分享链接云解析快速下载服务
|
||||||
QQ交流群:1017480890
|
QQ交流群:1017480890
|
||||||
<p align="center">
|
<p align="center">
|
||||||
<a href="https://github.com/qaiu/netdisk-fast-download/actions/workflows/maven.yml"><img src="https://img.shields.io/github/actions/workflow/status/qaiu/netdisk-fast-download/build.yml?branch=main&style=flat"></a>
|
<a href="https://github.com/qaiu/netdisk-fast-download/actions/workflows/maven.yml"><img src="https://img.shields.io/github/actions/workflow/status/qaiu/netdisk-fast-download/maven.yml?branch=v0.1.9b8a&style=flat"></a>
|
||||||
<a href="https://www.oracle.com/cn/java/technologies/downloads"><img src="https://img.shields.io/badge/jdk-%3E%3D17-blue"></a>
|
<a href="https://www.oracle.com/cn/java/technologies/downloads"><img src="https://img.shields.io/badge/jdk-%3E%3D17-blue"></a>
|
||||||
<a href="https://vertx-china.github.io"><img src="https://img.shields.io/badge/vert.x-4.5.27-blue?style=flat"></a>
|
<a href="https://vertx-china.github.io"><img src="https://img.shields.io/badge/vert.x-4.5.24-blue?style=flat"></a>
|
||||||
<a href="https://raw.githubusercontent.com/qaiu/netdisk-fast-download/master/LICENSE"><img src="https://img.shields.io/github/license/qaiu/netdisk-fast-download?style=flat"></a>
|
<a href="https://raw.githubusercontent.com/qaiu/netdisk-fast-download/master/LICENSE"><img src="https://img.shields.io/github/license/qaiu/netdisk-fast-download?style=flat"></a>
|
||||||
<a href="https://github.com/qaiu/netdisk-fast-download/releases/"><img src="https://img.shields.io/github/v/release/qaiu/netdisk-fast-download?style=flat"></a>
|
<a href="https://github.com/qaiu/netdisk-fast-download/releases/"><img src="https://img.shields.io/github/v/release/qaiu/netdisk-fast-download?style=flat"></a>
|
||||||
<a href="https://atomgit.com/QAIU/netdisk-fast-download"><img src="https://atomgit.com/QAIU/netdisk-fast-download/star/badge.svg" alt="AtomGit"></a>
|
<a href="https://atomgit.com/QAIU/netdisk-fast-download"><img src="https://atomgit.com/QAIU/netdisk-fast-download/star/badge.svg" alt="AtomGit"></a>
|
||||||
@@ -58,6 +58,7 @@ https://nfd-parser.github.io/nfd-preview/preview.html?src=https%3A%2F%2Flz.qaiu.
|
|||||||
|
|
||||||
**注意⚠️小飞机解析有IP限制,多数云服务商的大陆IP会被拦截(可以自行配置代理),和本程序无关**
|
**注意⚠️小飞机解析有IP限制,多数云服务商的大陆IP会被拦截(可以自行配置代理),和本程序无关**
|
||||||
**注意⚠️收到很多用户反馈,小飞机近期封号频繁,请尽可能选择其他网盘分享**
|
**注意⚠️收到很多用户反馈,小飞机近期封号频繁,请尽可能选择其他网盘分享**
|
||||||
|
**注意⚠️123云盘解析可能受账号地区、访问区域、出口IP等因素影响,即使已配置登录认证,跨区域访问仍可能失败,通常更接近网盘侧限制/风控,和本程序无关**
|
||||||
**注意⚠️请不要过度依赖 lz.qaiu.top,建议本地搭建或者云服务器自行搭建。请求量过多的话服务器可能会被云盘厂商限制,遇到解析失败的分享链接不要着急提issues,请先检查分享是否有效。**
|
**注意⚠️请不要过度依赖 lz.qaiu.top,建议本地搭建或者云服务器自行搭建。请求量过多的话服务器可能会被云盘厂商限制,遇到解析失败的分享链接不要着急提issues,请先检查分享是否有效。**
|
||||||
|
|
||||||
## 网盘支持情况:
|
## 网盘支持情况:
|
||||||
@@ -334,7 +335,7 @@ json返回数据格式示例:
|
|||||||
| 移动云云空间(个人版) | √ | √(密码可忽略) | 5G(个人) | 不限大小 |
|
| 移动云云空间(个人版) | √ | √(密码可忽略) | 5G(个人) | 不限大小 |
|
||||||
| 小飞机网盘 | √ | √ | 10G | 不限大小 |
|
| 小飞机网盘 | √ | √ | 10G | 不限大小 |
|
||||||
| 360亿方云 | √ | √ | 100G(须实名) | 不限大小 |
|
| 360亿方云 | √ | √ | 100G(须实名) | 不限大小 |
|
||||||
| 123云盘 | √ | √ | 2T | 100G(>100M需要登录) |
|
| 123云盘 | x | √ | 2T | 100G(>100M需要登录) |
|
||||||
| 文叔叔 | √ | √ | 10G | 5GB |
|
| 文叔叔 | √ | √ | 10G | 5GB |
|
||||||
| WPS云文档 | √ | X | 5G(免费) | 10M(免费)/2G(会员) |
|
| WPS云文档 | √ | X | 5G(免费) | 10M(免费)/2G(会员) |
|
||||||
| 夸克网盘 | x | √ | 10G | 不限大小 |
|
| 夸克网盘 | x | √ | 10G | 不限大小 |
|
||||||
@@ -419,7 +420,7 @@ docker run --rm -v /var/run/docker.sock:/var/run/docker.sock containrrr/watchtow
|
|||||||
> 注意: netdisk-fast-download.service中的ExecStart的路径改为实际路径
|
> 注意: netdisk-fast-download.service中的ExecStart的路径改为实际路径
|
||||||
```shell
|
```shell
|
||||||
cd ~
|
cd ~
|
||||||
wget -O netdisk-fast-download.zip https://github.com/qaiu/netdisk-fast-download/releases/download/v3.0.2/netdisk-fast-download-bin.zip
|
wget -O netdisk-fast-download.zip https://github.com/qaiu/netdisk-fast-download/releases/download/v0.1.9b7/netdisk-fast-download-bin.zip
|
||||||
unzip netdisk-fast-download-bin.zip
|
unzip netdisk-fast-download-bin.zip
|
||||||
cd netdisk-fast-download
|
cd netdisk-fast-download
|
||||||
bash service-install.sh
|
bash service-install.sh
|
||||||
@@ -492,6 +493,8 @@ auths:
|
|||||||
|
|
||||||
**注意:** 目前仅支持 123(ye)的认证配置。
|
**注意:** 目前仅支持 123(ye)的认证配置。
|
||||||
|
|
||||||
|
**补充说明:** 123(ye)登录认证是必要条件之一,但不保证所有部署环境都稳定可用;若公网云服务器解析异常而本地/NAS环境正常,通常更可能与123侧跨区域访问限制或风控相关。建议优先在本地、家庭宽带或NAS环境部署,必要时再结合代理。
|
||||||
|
|
||||||
|
|
||||||
**技术栈:**
|
**技术栈:**
|
||||||
Jdk17+Vert.x4
|
Jdk17+Vert.x4
|
||||||
@@ -516,5 +519,3 @@ Core模块集成Vert.x实现类似spring的注解式路由API
|
|||||||
</p>
|
</p>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
# set -x
|
# set -x
|
||||||
LAUNCH_JAR="netdisk-fast-download.jar"
|
LAUNCH_JAR="netdisk-fast-download.jar"
|
||||||
exec java -Xmx${JVM_XMX:-512M} ${JVM_OPTS} -jar "$LAUNCH_JAR" "$@"
|
nohup java -Xmx512M -jar "$LAUNCH_JAR" "$@" >startup.log 2>&1 &
|
||||||
|
tail -f startup.log
|
||||||
|
|
||||||
|
|||||||
@@ -65,7 +65,7 @@
|
|||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.postgresql</groupId>
|
<groupId>org.postgresql</groupId>
|
||||||
<artifactId>postgresql</artifactId>
|
<artifactId>postgresql</artifactId>
|
||||||
<version>42.7.11</version>
|
<version>42.7.3</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
</dependencies>
|
</dependencies>
|
||||||
|
|||||||
@@ -53,7 +53,7 @@ public class CreateDatabase {
|
|||||||
stmt.executeUpdate("CREATE DATABASE IF NOT EXISTS " + dbName + " CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci");
|
stmt.executeUpdate("CREATE DATABASE IF NOT EXISTS " + dbName + " CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci");
|
||||||
LOGGER.info(">>>>>>>>>>> 数据库'{}'创建成功 <<<<<<<<<<<<", dbName);
|
LOGGER.info(">>>>>>>>>>> 数据库'{}'创建成功 <<<<<<<<<<<<", dbName);
|
||||||
} catch (SQLException e) {
|
} catch (SQLException e) {
|
||||||
LOGGER.error("创建数据库失败", e);
|
e.printStackTrace();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -24,39 +24,35 @@ import java.util.*;
|
|||||||
* @author <a href="https://qaiu.top">QAIU</a>
|
* @author <a href="https://qaiu.top">QAIU</a>
|
||||||
*/
|
*/
|
||||||
public class CreateTable {
|
public class CreateTable {
|
||||||
public static final Map<Class<?>, String> javaProperty2SqlColumnMap;
|
public static Map<Class<?>, String> javaProperty2SqlColumnMap = new HashMap<>() {{
|
||||||
static {
|
|
||||||
Map<Class<?>, String> map = new HashMap<>();
|
|
||||||
// Java类型到SQL类型的映射
|
// Java类型到SQL类型的映射
|
||||||
map.put(Integer.class, "INT");
|
put(Integer.class, "INT");
|
||||||
map.put(Short.class, "SMALLINT");
|
put(Short.class, "SMALLINT");
|
||||||
map.put(Byte.class, "TINYINT");
|
put(Byte.class, "TINYINT");
|
||||||
map.put(Long.class, "BIGINT");
|
put(Long.class, "BIGINT");
|
||||||
map.put(java.math.BigDecimal.class, "DECIMAL");
|
put(java.math.BigDecimal.class, "DECIMAL");
|
||||||
map.put(Double.class, "DOUBLE");
|
put(Double.class, "DOUBLE");
|
||||||
map.put(Float.class, "REAL");
|
put(Float.class, "REAL");
|
||||||
map.put(Boolean.class, "BOOLEAN");
|
put(Boolean.class, "BOOLEAN");
|
||||||
map.put(String.class, "VARCHAR");
|
put(String.class, "VARCHAR");
|
||||||
map.put(Date.class, "TIMESTAMP");
|
put(Date.class, "TIMESTAMP");
|
||||||
map.put(java.time.LocalDateTime.class, "TIMESTAMP");
|
put(java.time.LocalDateTime.class, "TIMESTAMP");
|
||||||
map.put(java.sql.Timestamp.class, "TIMESTAMP");
|
put(java.sql.Timestamp.class, "TIMESTAMP");
|
||||||
map.put(java.sql.Date.class, "DATE");
|
put(java.sql.Date.class, "DATE");
|
||||||
map.put(java.sql.Time.class, "TIME");
|
put(java.sql.Time.class, "TIME");
|
||||||
|
|
||||||
// 基本数据类型
|
// 基本数据类型
|
||||||
map.put(int.class, "INT");
|
put(int.class, "INT");
|
||||||
map.put(short.class, "SMALLINT");
|
put(short.class, "SMALLINT");
|
||||||
map.put(byte.class, "TINYINT");
|
put(byte.class, "TINYINT");
|
||||||
map.put(long.class, "BIGINT");
|
put(long.class, "BIGINT");
|
||||||
map.put(double.class, "DOUBLE");
|
put(double.class, "DOUBLE");
|
||||||
map.put(float.class, "REAL");
|
put(float.class, "REAL");
|
||||||
map.put(boolean.class, "BOOLEAN");
|
put(boolean.class, "BOOLEAN");
|
||||||
|
}};
|
||||||
javaProperty2SqlColumnMap = Collections.unmodifiableMap(map);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static final Logger LOGGER = LoggerFactory.getLogger(CreateTable.class);
|
private static final Logger LOGGER = LoggerFactory.getLogger(CreateTable.class);
|
||||||
public static final String UNIQUE_PREFIX = "idx_";
|
public static String UNIQUE_PREFIX = "idx_";
|
||||||
|
|
||||||
private static Case getCase(Class<?> clz) {
|
private static Case getCase(Class<?> clz) {
|
||||||
return switch (clz.getName()) {
|
return switch (clz.getName()) {
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ import org.slf4j.LoggerFactory;
|
|||||||
*
|
*
|
||||||
* @author <a href="https://qaiu.top">QAIU</a>
|
* @author <a href="https://qaiu.top">QAIU</a>
|
||||||
*/
|
*/
|
||||||
public class JDBCPoolInit implements AutoCloseable {
|
public class JDBCPoolInit {
|
||||||
|
|
||||||
private static final Logger LOGGER = LoggerFactory.getLogger(JDBCPoolInit.class);
|
private static final Logger LOGGER = LoggerFactory.getLogger(JDBCPoolInit.class);
|
||||||
|
|
||||||
@@ -101,16 +101,4 @@ public class JDBCPoolInit implements AutoCloseable {
|
|||||||
synchronized public JDBCPool getPool() {
|
synchronized public JDBCPool getPool() {
|
||||||
return pool;
|
return pool;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 关闭连接池,释放数据库资源
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public synchronized void close() {
|
|
||||||
if (pool != null) {
|
|
||||||
pool.close();
|
|
||||||
LOGGER.info("数据库连接池已关闭: URL={}", url);
|
|
||||||
pool = null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,8 +16,6 @@ import org.slf4j.Logger;
|
|||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
import java.lang.management.ManagementFactory;
|
import java.lang.management.ManagementFactory;
|
||||||
import java.nio.file.Files;
|
|
||||||
import java.nio.file.Path;
|
|
||||||
import java.util.Calendar;
|
import java.util.Calendar;
|
||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
@@ -64,19 +62,10 @@ public final class Deploy {
|
|||||||
path.append("-").append(args[0].replace("app-",""));
|
path.append("-").append(args[0].replace("app-",""));
|
||||||
}
|
}
|
||||||
|
|
||||||
// 读取yml配置,优先当前目录,其次 resources/ 子目录
|
// 读取yml配置
|
||||||
String configFile = path + ".yml";
|
|
||||||
if (!Files.exists(Path.of(configFile)) && Files.exists(Path.of("resources", configFile))) {
|
|
||||||
path.insert(0, "resources/");
|
|
||||||
LOGGER.info("从 resources/ 目录加载配置: {}", path + ".yml");
|
|
||||||
}
|
|
||||||
ConfigUtil.readYamlConfig(path.toString(), tempVertx)
|
ConfigUtil.readYamlConfig(path.toString(), tempVertx)
|
||||||
.onSuccess(this::readConf)
|
.onSuccess(this::readConf)
|
||||||
.onFailure(err -> {
|
.onFailure(Throwable::printStackTrace);
|
||||||
LOGGER.error("读取配置文件失败: {}", err.getMessage(), err);
|
|
||||||
LockSupport.unpark(mainThread);
|
|
||||||
System.exit(-1);
|
|
||||||
});
|
|
||||||
LockSupport.park();
|
LockSupport.park();
|
||||||
deployVerticle();
|
deployVerticle();
|
||||||
}
|
}
|
||||||
@@ -148,17 +137,6 @@ public final class Deploy {
|
|||||||
vertxOptions.getWorkerPoolSize());
|
vertxOptions.getWorkerPoolSize());
|
||||||
var vertx = Vertx.vertx(vertxOptions);
|
var vertx = Vertx.vertx(vertxOptions);
|
||||||
VertxHolder.init(vertx);
|
VertxHolder.init(vertx);
|
||||||
|
|
||||||
// 注册 ShutdownHook,确保进程退出时优雅关闭资源
|
|
||||||
Runtime.getRuntime().addShutdownHook(new Thread(() -> {
|
|
||||||
LOGGER.info("JVM shutting down, closing Vert.x...");
|
|
||||||
try {
|
|
||||||
vertx.close().toCompletionStage().toCompletableFuture().get(10, java.util.concurrent.TimeUnit.SECONDS);
|
|
||||||
LOGGER.info("Vert.x closed successfully");
|
|
||||||
} catch (Exception e) {
|
|
||||||
LOGGER.warn("Vert.x close error or timeout", e);
|
|
||||||
}
|
|
||||||
}));
|
|
||||||
//配置保存在共享数据中
|
//配置保存在共享数据中
|
||||||
var sharedData = vertx.sharedData();
|
var sharedData = vertx.sharedData();
|
||||||
LocalMap<String, Object> localMap = sharedData.getLocalMap(LOCAL);
|
LocalMap<String, Object> localMap = sharedData.getLocalMap(LOCAL);
|
||||||
|
|||||||
@@ -127,9 +127,8 @@ public class RouterHandlerFactory implements BaseHttpApi {
|
|||||||
// 错误请求处理
|
// 错误请求处理
|
||||||
mainRouter.errorHandler(405, ctx -> doFireJsonResultResponse(ctx, JsonResult
|
mainRouter.errorHandler(405, ctx -> doFireJsonResultResponse(ctx, JsonResult
|
||||||
.error("Method Not Allowed", 405)));
|
.error("Method Not Allowed", 405)));
|
||||||
mainRouter.errorHandler(404, ctx -> {
|
mainRouter.errorHandler(404, ctx -> ctx.response().setStatusCode(404).setChunked(true)
|
||||||
ctx.response().setStatusCode(404).end("404 not found");
|
.end("Internal server error: 404 not found"));
|
||||||
});
|
|
||||||
|
|
||||||
return mainRouter;
|
return mainRouter;
|
||||||
}
|
}
|
||||||
@@ -180,9 +179,8 @@ public class RouterHandlerFactory implements BaseHttpApi {
|
|||||||
if (ctx.statusCode() == 503 || ctx.failure() == null) {
|
if (ctx.statusCode() == 503 || ctx.failure() == null) {
|
||||||
doFireJsonResultResponse(ctx, JsonResult.error("未知异常, 请联系管理员"), 503);
|
doFireJsonResultResponse(ctx, JsonResult.error("未知异常, 请联系管理员"), 503);
|
||||||
} else {
|
} else {
|
||||||
LOGGER.error("路由处理失败", ctx.failure());
|
ctx.failure().printStackTrace();
|
||||||
String msg = ctx.failure() != null ? ctx.failure().getMessage() : "未知异常";
|
doFireJsonResultResponse(ctx, JsonResult.error(ctx.failure().getMessage()), 500);
|
||||||
doFireJsonResultResponse(ctx, JsonResult.error(msg), 500);
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
} else if (method.isAnnotationPresent(SockRouteMapper.class)) {
|
} else if (method.isAnnotationPresent(SockRouteMapper.class)) {
|
||||||
@@ -200,7 +198,7 @@ public class RouterHandlerFactory implements BaseHttpApi {
|
|||||||
try {
|
try {
|
||||||
ReflectionUtil.invokeWithArguments(method, instance, sock);
|
ReflectionUtil.invokeWithArguments(method, instance, sock);
|
||||||
} catch (Throwable e) {
|
} catch (Throwable e) {
|
||||||
LOGGER.error("WebSocket处理异常", e);
|
e.printStackTrace();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
if (url.endsWith("*")) {
|
if (url.endsWith("*")) {
|
||||||
@@ -324,7 +322,7 @@ public class RouterHandlerFactory implements BaseHttpApi {
|
|||||||
parameterValueList.put(k, entity);
|
parameterValueList.put(k, entity);
|
||||||
}
|
}
|
||||||
} catch (ClassNotFoundException e) {
|
} catch (ClassNotFoundException e) {
|
||||||
LOGGER.error("实体类绑定异常: {}", typeName, e);
|
e.printStackTrace();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -367,7 +365,7 @@ public class RouterHandlerFactory implements BaseHttpApi {
|
|||||||
Object entity = ParamUtil.multiMapToEntity(queryParams, aClass);
|
Object entity = ParamUtil.multiMapToEntity(queryParams, aClass);
|
||||||
parameterValueList.put(k, entity);
|
parameterValueList.put(k, entity);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
LOGGER.error("参数绑定异常: {}", v.getRight().getName(), e);
|
e.printStackTrace();
|
||||||
}
|
}
|
||||||
} else if (parameterValueList.get(k) == null
|
} else if (parameterValueList.get(k) == null
|
||||||
&& JsonObject.class.getName().equals(v.getRight().getName())) {
|
&& JsonObject.class.getName().equals(v.getRight().getName())) {
|
||||||
@@ -410,19 +408,22 @@ public class RouterHandlerFactory implements BaseHttpApi {
|
|||||||
doFireJsonResultResponse(ctx, JsonResult.data(null));
|
doFireJsonResultResponse(ctx, JsonResult.data(null));
|
||||||
}
|
}
|
||||||
|
|
||||||
}).onFailure(e -> {
|
}).onFailure(e -> doFireJsonResultResponse(ctx, JsonResult.error(e.getMessage()), 500));
|
||||||
LOGGER.error("请求处理失败", e);
|
|
||||||
String msg = e.getMessage() != null ? e.getMessage() : "服务器内部错误";
|
|
||||||
doFireJsonResultResponse(ctx, JsonResult.error(msg), 500);
|
|
||||||
});
|
|
||||||
} else {
|
} else {
|
||||||
doFireJsonResultResponse(ctx, JsonResult.data(data));
|
doFireJsonResultResponse(ctx, JsonResult.data(data));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (Throwable e) {
|
} catch (Throwable e) {
|
||||||
LOGGER.error("请求处理异常", e);
|
e.printStackTrace();
|
||||||
String msg = e.getMessage() != null ? e.getMessage() : "服务器内部错误";
|
String err = e.getMessage();
|
||||||
doFireJsonResultResponse(ctx, JsonResult.error(msg), 500);
|
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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -153,7 +153,7 @@ public class CommonUtil {
|
|||||||
appVersion = properties.getProperty("app.version") + "build" + properties.getProperty("build");
|
appVersion = properties.getProperty("app.version") + "build" + properties.getProperty("build");
|
||||||
}
|
}
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
LOGGER.error("读取app.properties失败", e);
|
e.printStackTrace();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return appVersion;
|
return appVersion;
|
||||||
|
|||||||
@@ -10,8 +10,6 @@ import io.vertx.core.json.JsonObject;
|
|||||||
|
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
import java.nio.charset.StandardCharsets;
|
import java.nio.charset.StandardCharsets;
|
||||||
import java.nio.file.Files;
|
|
||||||
import java.nio.file.Path;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 异步读取配置工具类
|
* 异步读取配置工具类
|
||||||
@@ -64,35 +62,12 @@ public class ConfigUtil {
|
|||||||
// 异步获取配置
|
// 异步获取配置
|
||||||
// 成功直接完成 promise
|
// 成功直接完成 promise
|
||||||
retriever.getConfig()
|
retriever.getConfig()
|
||||||
.onSuccess(config -> {
|
.onSuccess(promise::complete)
|
||||||
promise.complete(config);
|
|
||||||
retriever.close();
|
|
||||||
})
|
|
||||||
.onFailure(err -> {
|
.onFailure(err -> {
|
||||||
retriever.close();
|
// 配置读取失败,直接返回失败 Future
|
||||||
// 读取失败时,尝试从 resources/ 子目录读取(兼容 Docker 卷挂载场景)
|
|
||||||
String resourcesPath = "resources/" + path;
|
|
||||||
if (!path.startsWith("resources/") && Files.exists(Path.of(resourcesPath))) {
|
|
||||||
ConfigStoreOptions fallbackStore = new ConfigStoreOptions()
|
|
||||||
.setType("file")
|
|
||||||
.setFormat(format)
|
|
||||||
.setConfig(new JsonObject().put("path", resourcesPath));
|
|
||||||
ConfigRetriever fallbackRetriever = ConfigRetriever
|
|
||||||
.create(vertx, new ConfigRetrieverOptions().addStore(fallbackStore));
|
|
||||||
fallbackRetriever.getConfig()
|
|
||||||
.onSuccess(config -> {
|
|
||||||
promise.complete(config);
|
|
||||||
fallbackRetriever.close();
|
|
||||||
})
|
|
||||||
.onFailure(e2 -> {
|
|
||||||
promise.fail(new RuntimeException(
|
|
||||||
"读取配置文件失败: " + path + " (也尝试了 " + resourcesPath + ")", e2));
|
|
||||||
fallbackRetriever.close();
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
promise.fail(new RuntimeException(
|
promise.fail(new RuntimeException(
|
||||||
"读取配置文件失败: " + path, err));
|
"读取配置文件失败: " + path, err));
|
||||||
}
|
retriever.close();
|
||||||
});
|
});
|
||||||
|
|
||||||
return promise.future();
|
return promise.future();
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
package cn.qaiu.vx.core.util;
|
package cn.qaiu.vx.core.util;
|
||||||
|
|
||||||
|
import java.util.HashMap;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.concurrent.ConcurrentHashMap;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* vertx 上下文外的本地容器 为不在vertx线程的方法传递数据
|
* vertx 上下文外的本地容器 为不在vertx线程的方法传递数据
|
||||||
@@ -10,10 +10,11 @@ import java.util.concurrent.ConcurrentHashMap;
|
|||||||
* @author <a href="https://qaiu.top">QAIU</a>
|
* @author <a href="https://qaiu.top">QAIU</a>
|
||||||
*/
|
*/
|
||||||
public class LocalConstant {
|
public class LocalConstant {
|
||||||
private static final Map<String, Object> LOCAL_CONST = new ConcurrentHashMap<>();
|
private static final Map<String, Object> LOCAL_CONST = new HashMap<>();
|
||||||
|
|
||||||
public static Map<String, Object> put(String k, Object v) {
|
public static Map<String, Object> put(String k, Object v) {
|
||||||
LOCAL_CONST.putIfAbsent(k, v);
|
if (LOCAL_CONST.containsKey(k)) return LOCAL_CONST;
|
||||||
|
LOCAL_CONST.put(k, v);
|
||||||
return LOCAL_CONST;
|
return LOCAL_CONST;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -36,20 +36,16 @@ public final class ParamUtil {
|
|||||||
|
|
||||||
public static MultiMap paramsToMap(String paramString) {
|
public static MultiMap paramsToMap(String paramString) {
|
||||||
MultiMap entries = MultiMap.caseInsensitiveMultiMap();
|
MultiMap entries = MultiMap.caseInsensitiveMultiMap();
|
||||||
if (paramString == null || paramString.isEmpty()) return entries;
|
if (paramString == null) return entries;
|
||||||
String[] params = paramString.split("&");
|
String[] params = paramString.split("&");
|
||||||
if (params.length == 0) return entries;
|
if (params.length == 0) return entries;
|
||||||
for (String param : params) {
|
for (String param : params) {
|
||||||
if (param == null || param.isEmpty()) {
|
String[] kv = param.split("=");
|
||||||
continue;
|
|
||||||
}
|
|
||||||
String[] kv = param.split("=", 2);
|
|
||||||
if (kv.length == 2) {
|
if (kv.length == 2) {
|
||||||
entries.set(kv[0], kv[1]);
|
entries.set(kv[0], kv[1]);
|
||||||
} else if (kv.length == 1) {
|
} else {
|
||||||
entries.set(kv[0], "");
|
entries.set(kv[0], "");
|
||||||
}
|
}
|
||||||
// kv.length == 0 时(空字符串),跳过
|
|
||||||
}
|
}
|
||||||
return entries;
|
return entries;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -25,9 +25,6 @@ import java.net.URL;
|
|||||||
import java.text.ParseException;
|
import java.text.ParseException;
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
|
|
||||||
import org.slf4j.Logger;
|
|
||||||
import org.slf4j.LoggerFactory;
|
|
||||||
|
|
||||||
import static cn.qaiu.vx.core.util.ConfigConstant.BASE_LOCATIONS;
|
import static cn.qaiu.vx.core.util.ConfigConstant.BASE_LOCATIONS;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -39,8 +36,6 @@ import static cn.qaiu.vx.core.util.ConfigConstant.BASE_LOCATIONS;
|
|||||||
*/
|
*/
|
||||||
public final class ReflectionUtil {
|
public final class ReflectionUtil {
|
||||||
|
|
||||||
private static final Logger LOGGER = LoggerFactory.getLogger(ReflectionUtil.class);
|
|
||||||
|
|
||||||
// 缓存Reflections实例,避免重复扫描(每次扫描约35K+值,耗时1-3秒,占用大量内存)
|
// 缓存Reflections实例,避免重复扫描(每次扫描约35K+值,耗时1-3秒,占用大量内存)
|
||||||
private static final Map<String, Reflections> REFLECTIONS_CACHE = new java.util.concurrent.ConcurrentHashMap<>();
|
private static final Map<String, Reflections> REFLECTIONS_CACHE = new java.util.concurrent.ConcurrentHashMap<>();
|
||||||
|
|
||||||
@@ -133,7 +128,7 @@ public final class ReflectionUtil {
|
|||||||
parameterTypes[j - k]));
|
parameterTypes[j - k]));
|
||||||
}
|
}
|
||||||
} catch (NotFoundException e) {
|
} catch (NotFoundException e) {
|
||||||
LOGGER.error("获取方法参数失败", e);
|
e.printStackTrace();
|
||||||
}
|
}
|
||||||
return paramMap;
|
return paramMap;
|
||||||
}
|
}
|
||||||
@@ -188,7 +183,7 @@ public final class ReflectionUtil {
|
|||||||
try {
|
try {
|
||||||
return DateUtils.parseDate(value, fmt);
|
return DateUtils.parseDate(value, fmt);
|
||||||
} catch (ParseException e) {
|
} catch (ParseException e) {
|
||||||
LOGGER.error("日期解析失败: {}", value, e);
|
e.printStackTrace();
|
||||||
throw new RuntimeException("无法将格式化日期");
|
throw new RuntimeException("无法将格式化日期");
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
@@ -220,7 +215,7 @@ public final class ReflectionUtil {
|
|||||||
}
|
}
|
||||||
return arr;
|
return arr;
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
LOGGER.error("数组类型转换失败: {}", value, e);
|
e.printStackTrace();
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@@ -246,7 +241,7 @@ public final class ReflectionUtil {
|
|||||||
public static boolean isBasicTypeArray(CtClass ctClass) {
|
public static boolean isBasicTypeArray(CtClass ctClass) {
|
||||||
if (!ctClass.isArray()) {
|
if (!ctClass.isArray()) {
|
||||||
return false;
|
return false;
|
||||||
} else return (ctClass.getName().matches("^(boolean|char|byte|short|int|long|float|double|String)\\[]$"));
|
} else return (ctClass.getName().matches("^(boolen|char|byte|short|int|long|float|double|String)\\[]$"));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -129,27 +129,18 @@ public class HttpProxyVerticle extends AbstractVerticle {
|
|||||||
clientRequest.response().setStatusCode(403).end();
|
clientRequest.response().setStatusCode(403).end();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
String[] split;
|
String[] split = new String(Base64.getDecoder().decode(s.replace("Basic ", ""))).split(":");
|
||||||
try {
|
if (split.length > 1) {
|
||||||
split = new String(Base64.getDecoder().decode(s.replace("Basic ", ""))).split(":");
|
// TODO
|
||||||
} catch (IllegalArgumentException e) {
|
|
||||||
LOGGER.warn("Proxy-Authorization header is not valid Base64");
|
|
||||||
clientRequest.response().setStatusCode(403).end();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (split.length <= 1) {
|
|
||||||
LOGGER.warn("Proxy-Authorization header format invalid: missing username:password separator");
|
|
||||||
clientRequest.response().setStatusCode(403).end();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
String username = proxyServerConf.getString("username");
|
String username = proxyServerConf.getString("username");
|
||||||
String password = proxyServerConf.getString("password");
|
String password = proxyServerConf.getString("password");
|
||||||
if (!split[0].equals(username) || !split[1].equals(password)) {
|
if (!split[0].equals(username) || !split[1].equals(password)) {
|
||||||
LOGGER.info("-----auth failed------\nusername: {}", split[0]);
|
LOGGER.info("-----auth failed------\nusername: {}\npassword: {}", username, password);
|
||||||
clientRequest.response().setStatusCode(403).end();
|
clientRequest.response().setStatusCode(403).end();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (clientRequest.method() == HttpMethod.CONNECT) {
|
if (clientRequest.method() == HttpMethod.CONNECT) {
|
||||||
// 处理 CONNECT 请求
|
// 处理 CONNECT 请求
|
||||||
@@ -196,7 +187,7 @@ public class HttpProxyVerticle extends AbstractVerticle {
|
|||||||
);
|
);
|
||||||
})
|
})
|
||||||
.onFailure(err -> {
|
.onFailure(err -> {
|
||||||
LOGGER.error("HTTP请求失败", err);
|
err.printStackTrace();
|
||||||
clientRequest.response().setStatusCode(502).end("Bad Gateway: Request failed");
|
clientRequest.response().setStatusCode(502).end("Bad Gateway: Request failed");
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -222,7 +213,7 @@ public class HttpProxyVerticle extends AbstractVerticle {
|
|||||||
}
|
}
|
||||||
return port;
|
return port;
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
LOGGER.error("提取端口失败: {}", urlString, e);
|
e.printStackTrace();
|
||||||
// 出现异常时返回 -1,表示提取失败
|
// 出现异常时返回 -1,表示提取失败
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -351,7 +351,7 @@ public class ReverseProxyVerticle extends AbstractVerticle {
|
|||||||
String host = url.getHost();
|
String host = url.getHost();
|
||||||
int port = url.getPort();
|
int port = url.getPort();
|
||||||
if (port == -1) {
|
if (port == -1) {
|
||||||
port = 443;
|
port = 80;
|
||||||
}
|
}
|
||||||
String originPath = url.getPath();
|
String originPath = url.getPath();
|
||||||
LOGGER.info("path {}, originPath {}, to {}:{}", path, originPath, host, port);
|
LOGGER.info("path {}, originPath {}, to {}:{}", path, originPath, host, port);
|
||||||
|
|||||||
@@ -23,11 +23,12 @@ public class RouterVerticle extends AbstractVerticle {
|
|||||||
private static final Logger LOGGER = LoggerFactory.getLogger(RouterVerticle.class);
|
private static final Logger LOGGER = LoggerFactory.getLogger(RouterVerticle.class);
|
||||||
|
|
||||||
private static final int port = SharedDataUtil.getValueForServerConfig("port");
|
private static final int port = SharedDataUtil.getValueForServerConfig("port");
|
||||||
|
private static final Router router = new RouterHandlerFactory(
|
||||||
|
SharedDataUtil.getJsonStringForServerConfig("contextPath")).createRouter();
|
||||||
|
|
||||||
private static final JsonObject globalConfig = SharedDataUtil.getJsonConfig("globalConfig");
|
private static final JsonObject globalConfig = SharedDataUtil.getJsonConfig("globalConfig");
|
||||||
|
|
||||||
private HttpServer server;
|
private HttpServer server;
|
||||||
private Router router;
|
|
||||||
|
|
||||||
static {
|
static {
|
||||||
LOGGER.info(JacksonConfig.class.getSimpleName() + " >> ");
|
LOGGER.info(JacksonConfig.class.getSimpleName() + " >> ");
|
||||||
@@ -60,8 +61,6 @@ public class RouterVerticle extends AbstractVerticle {
|
|||||||
.setReuseAddress(true) // 允许地址重用
|
.setReuseAddress(true) // 允许地址重用
|
||||||
.setReusePort(true); // 允许端口重用
|
.setReusePort(true); // 允许端口重用
|
||||||
|
|
||||||
router = new RouterHandlerFactory(
|
|
||||||
SharedDataUtil.getJsonStringForServerConfig("contextPath")).createRouter();
|
|
||||||
server = vertx.createHttpServer(options);
|
server = vertx.createHttpServer(options);
|
||||||
|
|
||||||
server.requestHandler(router).webSocketHandler(s->{}).listen()
|
server.requestHandler(router).webSocketHandler(s->{}).listen()
|
||||||
|
|||||||
@@ -5,15 +5,11 @@ import cn.qaiu.vx.core.base.BaseAsyncService;
|
|||||||
import cn.qaiu.vx.core.util.ReflectionUtil;
|
import cn.qaiu.vx.core.util.ReflectionUtil;
|
||||||
import io.vertx.core.AbstractVerticle;
|
import io.vertx.core.AbstractVerticle;
|
||||||
import io.vertx.core.Promise;
|
import io.vertx.core.Promise;
|
||||||
import io.vertx.core.eventbus.MessageConsumer;
|
|
||||||
import io.vertx.core.json.JsonObject;
|
|
||||||
import io.vertx.serviceproxy.ServiceBinder;
|
import io.vertx.serviceproxy.ServiceBinder;
|
||||||
import org.reflections.Reflections;
|
import org.reflections.Reflections;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.concurrent.atomic.AtomicInteger;
|
import java.util.concurrent.atomic.AtomicInteger;
|
||||||
|
|
||||||
@@ -28,7 +24,6 @@ public class ServiceVerticle extends AbstractVerticle {
|
|||||||
Logger LOGGER = LoggerFactory.getLogger(ServiceVerticle.class);
|
Logger LOGGER = LoggerFactory.getLogger(ServiceVerticle.class);
|
||||||
private static final AtomicInteger ID = new AtomicInteger(1);
|
private static final AtomicInteger ID = new AtomicInteger(1);
|
||||||
private static final Set<Class<?>> handlers;
|
private static final Set<Class<?>> handlers;
|
||||||
private final List<MessageConsumer<JsonObject>> consumers = new ArrayList<>();
|
|
||||||
|
|
||||||
static {
|
static {
|
||||||
Reflections reflections = ReflectionUtil.getReflections();
|
Reflections reflections = ReflectionUtil.getReflections();
|
||||||
@@ -44,10 +39,7 @@ public class ServiceVerticle extends AbstractVerticle {
|
|||||||
try {
|
try {
|
||||||
serviceNames.append(asyncService.getName()).append("|");
|
serviceNames.append(asyncService.getName()).append("|");
|
||||||
BaseAsyncService asInstance = (BaseAsyncService) ReflectionUtil.newWithNoParam(asyncService);
|
BaseAsyncService asInstance = (BaseAsyncService) ReflectionUtil.newWithNoParam(asyncService);
|
||||||
String address = asInstance.getAddress();
|
binder.setAddress(asInstance.getAddress()).register(asInstance.getAsyncInterfaceClass(), asInstance);
|
||||||
MessageConsumer<JsonObject> consumer = binder.setAddress(address)
|
|
||||||
.register(asInstance.getAsyncInterfaceClass(), asInstance);
|
|
||||||
consumers.add(consumer);
|
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
LOGGER.error("Failed to register service: {}", asyncService.getName(), e);
|
LOGGER.error("Failed to register service: {}", asyncService.getName(), e);
|
||||||
}
|
}
|
||||||
@@ -57,19 +49,4 @@ public class ServiceVerticle extends AbstractVerticle {
|
|||||||
}
|
}
|
||||||
startPromise.complete();
|
startPromise.complete();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public void stop(Promise<Void> stopPromise) {
|
|
||||||
int count = consumers.size();
|
|
||||||
consumers.forEach(consumer -> {
|
|
||||||
try {
|
|
||||||
consumer.unregister();
|
|
||||||
} catch (Exception e) {
|
|
||||||
LOGGER.warn("Failed to unregister service consumer at address: {}", consumer.address(), e);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
consumers.clear();
|
|
||||||
LOGGER.info("ServiceVerticle stopped, unregistered {} services", count);
|
|
||||||
stopPromise.complete();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ public class HttpProxyConf {
|
|||||||
public HttpProxyConf() {
|
public HttpProxyConf() {
|
||||||
this.username = DEFAULT_USERNAME;
|
this.username = DEFAULT_USERNAME;
|
||||||
this.password = DEFAULT_PASSWORD;
|
this.password = DEFAULT_PASSWORD;
|
||||||
this.port = DEFAULT_PORT;
|
this.timeout = DEFAULT_PORT;
|
||||||
this.timeout = DEFAULT_TIMEOUT;
|
this.timeout = DEFAULT_TIMEOUT;
|
||||||
this.preProxyOptions = new ProxyOptions();
|
this.preProxyOptions = new ProxyOptions();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,9 +0,0 @@
|
|||||||
#!/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
|
|
||||||
@@ -4,26 +4,26 @@ NFD 解析器模块:聚合各类网盘/分享页解析,统一输出文件列
|
|||||||
|
|
||||||
- 语言:Java 17
|
- 语言:Java 17
|
||||||
- 构建:Maven
|
- 构建:Maven
|
||||||
- 模块版本:10.2.5
|
- 模块版本:10.1.17
|
||||||
|
|
||||||
## 依赖(Maven Central)
|
## 依赖(Maven Central)
|
||||||
```xml
|
```xml
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>cn.qaiu</groupId>
|
<groupId>cn.qaiu</groupId>
|
||||||
<artifactId>parser</artifactId>
|
<artifactId>parser</artifactId>
|
||||||
<version>10.2.5</version>
|
<version>10.1.17</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
```
|
```
|
||||||
- Gradle Groovy DSL:
|
- Gradle Groovy DSL:
|
||||||
```groovy
|
```groovy
|
||||||
dependencies {
|
dependencies {
|
||||||
implementation 'cn.qaiu:parser:10.2.5'
|
implementation 'cn.qaiu:parser:10.1.17'
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
- Gradle Kotlin DSL:
|
- Gradle Kotlin DSL:
|
||||||
```kotlin
|
```kotlin
|
||||||
dependencies {
|
dependencies {
|
||||||
implementation("cn.qaiu:parser:10.2.5")
|
implementation("cn.qaiu:parser:10.1.17")
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|||||||
@@ -28,7 +28,7 @@
|
|||||||
<dependency>
|
<dependency>
|
||||||
<groupId>cn.qaiu</groupId>
|
<groupId>cn.qaiu</groupId>
|
||||||
<artifactId>parser</artifactId>
|
<artifactId>parser</artifactId>
|
||||||
<version>10.2.5</version>
|
<version>10.1.17</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|||||||
@@ -11,7 +11,7 @@
|
|||||||
<dependency>
|
<dependency>
|
||||||
<groupId>cn.qaiu</groupId>
|
<groupId>cn.qaiu</groupId>
|
||||||
<artifactId>parser</artifactId>
|
<artifactId>parser</artifactId>
|
||||||
<version>10.2.5</version>
|
<version>10.1.17</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|||||||
@@ -12,7 +12,7 @@
|
|||||||
|
|
||||||
<groupId>cn.qaiu</groupId>
|
<groupId>cn.qaiu</groupId>
|
||||||
<artifactId>parser</artifactId>
|
<artifactId>parser</artifactId>
|
||||||
<version>${parserVersion}</version>
|
<version>10.2.5</version>
|
||||||
<packaging>jar</packaging>
|
<packaging>jar</packaging>
|
||||||
|
|
||||||
<name>cn.qaiu:parser</name>
|
<name>cn.qaiu:parser</name>
|
||||||
@@ -35,9 +35,9 @@
|
|||||||
</developers>
|
</developers>
|
||||||
|
|
||||||
<scm>
|
<scm>
|
||||||
<connection>scm:git:https://github.com/${github.owner}/${github.repo}.git</connection>
|
<connection>scm:git:https://github.com/qaiu/netdisk-fast-download.git</connection>
|
||||||
<developerConnection>scm:git:ssh://git@github.com:${github.owner}/${github.repo}.git</developerConnection>
|
<developerConnection>scm:git:ssh://git@github.com:qaiu/netdisk-fast-download.git</developerConnection>
|
||||||
<url>https://github.com/${github.owner}/${github.repo}</url>
|
<url>https://github.com/qaiu/netdisk-fast-download</url>
|
||||||
</scm>
|
</scm>
|
||||||
|
|
||||||
<distributionManagement>
|
<distributionManagement>
|
||||||
@@ -52,19 +52,20 @@
|
|||||||
</distributionManagement>
|
</distributionManagement>
|
||||||
|
|
||||||
<properties>
|
<properties>
|
||||||
|
<revision>0.2.1</revision>
|
||||||
<java.version>17</java.version>
|
<java.version>17</java.version>
|
||||||
<maven.compiler.source>17</maven.compiler.source>
|
<maven.compiler.source>17</maven.compiler.source>
|
||||||
<maven.compiler.target>17</maven.compiler.target>
|
<maven.compiler.target>17</maven.compiler.target>
|
||||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||||
|
|
||||||
<!-- Versions -->
|
<!-- Versions -->
|
||||||
<vertx.version>4.5.27</vertx.version>
|
<vertx.version>4.5.24</vertx.version>
|
||||||
<org.reflections.version>0.10.2</org.reflections.version>
|
<org.reflections.version>0.10.2</org.reflections.version>
|
||||||
<lombok.version>1.18.38</lombok.version>
|
<lombok.version>1.18.38</lombok.version>
|
||||||
<slf4j.version>2.0.16</slf4j.version>
|
<slf4j.version>2.0.16</slf4j.version>
|
||||||
<commons-lang3.version>3.18.0</commons-lang3.version>
|
<commons-lang3.version>3.18.0</commons-lang3.version>
|
||||||
<jackson.version>2.18.6</jackson.version>
|
<jackson.version>2.18.6</jackson.version>
|
||||||
<logback.version>1.5.32</logback.version>
|
<logback.version>1.5.19</logback.version>
|
||||||
<junit.version>4.13.2</junit.version>
|
<junit.version>4.13.2</junit.version>
|
||||||
</properties>
|
</properties>
|
||||||
|
|
||||||
@@ -123,41 +124,6 @@
|
|||||||
<build>
|
<build>
|
||||||
<plugins>
|
<plugins>
|
||||||
|
|
||||||
<!-- 从 git remote origin 自动识别 GitHub 仓库地址 -->
|
|
||||||
<plugin>
|
|
||||||
<groupId>org.codehaus.gmavenplus</groupId>
|
|
||||||
<artifactId>gmavenplus-plugin</artifactId>
|
|
||||||
<version>4.1.1</version>
|
|
||||||
<dependencies>
|
|
||||||
<dependency>
|
|
||||||
<groupId>org.apache.groovy</groupId>
|
|
||||||
<artifactId>groovy</artifactId>
|
|
||||||
<version>4.0.24</version>
|
|
||||||
</dependency>
|
|
||||||
</dependencies>
|
|
||||||
<executions>
|
|
||||||
<execution>
|
|
||||||
<phase>initialize</phase>
|
|
||||||
<goals><goal>execute</goal></goals>
|
|
||||||
<configuration>
|
|
||||||
<scripts>
|
|
||||||
<script>
|
|
||||||
def url = 'git remote get-url origin'.execute().text.trim()
|
|
||||||
def m = (url =~ 'github\\.com[:/]([^/]+)/([^/.]+?)(?:\\.git)?$')
|
|
||||||
if (m.find()) {
|
|
||||||
project.properties.setProperty('github.owner', m.group(1))
|
|
||||||
project.properties.setProperty('github.repo', m.group(2))
|
|
||||||
} else {
|
|
||||||
project.properties.setProperty('github.owner', 'qaiu')
|
|
||||||
project.properties.setProperty('github.repo', 'netdisk-fast-download')
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
</scripts>
|
|
||||||
</configuration>
|
|
||||||
</execution>
|
|
||||||
</executions>
|
|
||||||
</plugin>
|
|
||||||
|
|
||||||
<!-- 编译 -->
|
<!-- 编译 -->
|
||||||
<plugin>
|
<plugin>
|
||||||
<groupId>org.apache.maven.plugins</groupId>
|
<groupId>org.apache.maven.plugins</groupId>
|
||||||
|
|||||||
@@ -86,10 +86,7 @@ public class ShareLinkInfo {
|
|||||||
// 将type和shareKey组合成一个字符串作为缓存key
|
// 将type和shareKey组合成一个字符串作为缓存key
|
||||||
String key = type + ":" + shareKey;
|
String key = type + ":" + shareKey;
|
||||||
if (type.equals("p115")) {
|
if (type.equals("p115")) {
|
||||||
Object ua = otherParam != null ? otherParam.get("UA") : null;
|
key += ("_" + otherParam.get("UA").toString().hashCode());
|
||||||
if (ua != null) {
|
|
||||||
key += ("_" + ua.toString().hashCode());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return key;
|
return key;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -40,34 +40,28 @@ public abstract class PanBase implements IPanTool {
|
|||||||
protected Promise<String> promise = Promise.promise();
|
protected Promise<String> promise = Promise.promise();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 共享的 WebClient 实例(线程安全,避免每请求创建导致资源泄漏)
|
* Http client
|
||||||
*/
|
*/
|
||||||
private static final WebClient SHARED_CLIENT = WebClient.create(WebClientVertxInit.get(),
|
protected WebClient client = WebClient.create(WebClientVertxInit.get(),
|
||||||
new WebClientOptions());
|
new WebClientOptions());
|
||||||
private static final WebClient SHARED_CLIENT_NO_REDIRECTS = WebClient.create(WebClientVertxInit.get(),
|
|
||||||
new WebClientOptions().setFollowRedirects(false));
|
|
||||||
private static final WebClient SHARED_CLIENT_DISABLE_UA = WebClient.create(WebClientVertxInit.get(),
|
|
||||||
new WebClientOptions().setUserAgentEnabled(false));
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Http client (默认使用共享实例,代理模式下使用独立实例)
|
* Http client session (会话管理, 带cookie请求)
|
||||||
*/
|
|
||||||
protected WebClient client = SHARED_CLIENT;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Http client session (会话管理, 带cookie请求, 每实例独立)
|
|
||||||
*/
|
*/
|
||||||
protected WebClientSession clientSession = WebClientSession.create(client);
|
protected WebClientSession clientSession = WebClientSession.create(client);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Http client 不自动跳转
|
* Http client 不自动跳转
|
||||||
*/
|
*/
|
||||||
protected WebClient clientNoRedirects = SHARED_CLIENT_NO_REDIRECTS;
|
protected WebClient clientNoRedirects = WebClient.create(WebClientVertxInit.get(),
|
||||||
|
new WebClientOptions().setFollowRedirects(false));
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Http client disable UserAgent
|
* Http client disable UserAgent
|
||||||
*/
|
*/
|
||||||
protected WebClient clientDisableUA = SHARED_CLIENT_DISABLE_UA;
|
protected WebClient clientDisableUA = WebClient.create(WebClientVertxInit.get()
|
||||||
|
, new WebClientOptions().setUserAgentEnabled(false)
|
||||||
|
);
|
||||||
|
|
||||||
protected ShareLinkInfo shareLinkInfo;
|
protected ShareLinkInfo shareLinkInfo;
|
||||||
|
|
||||||
|
|||||||
@@ -81,7 +81,7 @@ public class ParserCreate {
|
|||||||
if (shareKey != null) {
|
if (shareKey != null) {
|
||||||
shareLinkInfo.setShareKey(shareKey);
|
shareLinkInfo.setShareKey(shareKey);
|
||||||
}
|
}
|
||||||
} catch (IllegalStateException | IllegalArgumentException ignored) {}
|
} catch (Exception ignored) {}
|
||||||
|
|
||||||
// 提取密码
|
// 提取密码
|
||||||
try {
|
try {
|
||||||
@@ -89,7 +89,7 @@ public class ParserCreate {
|
|||||||
if (StringUtils.isNotEmpty(pwd)) {
|
if (StringUtils.isNotEmpty(pwd)) {
|
||||||
shareLinkInfo.setSharePassword(pwd);
|
shareLinkInfo.setSharePassword(pwd);
|
||||||
}
|
}
|
||||||
} catch (IllegalStateException | IllegalArgumentException ignored) {}
|
} catch (Exception ignored) {}
|
||||||
|
|
||||||
// 设置标准URL
|
// 设置标准URL
|
||||||
if (customParserConfig.getStandardUrlTemplate() != null) {
|
if (customParserConfig.getStandardUrlTemplate() != null) {
|
||||||
@@ -133,7 +133,7 @@ public class ParserCreate {
|
|||||||
shareLinkInfo.setSharePassword(pwd);
|
shareLinkInfo.setSharePassword(pwd);
|
||||||
}
|
}
|
||||||
standardUrl = standardUrl.replace("{pwd}", pwd);
|
standardUrl = standardUrl.replace("{pwd}", pwd);
|
||||||
} catch (IllegalStateException | IllegalArgumentException ignored) {}
|
} catch (Exception ignored) {}
|
||||||
|
|
||||||
shareLinkInfo.setShareUrl(shareUrl);
|
shareLinkInfo.setShareUrl(shareUrl);
|
||||||
shareLinkInfo.setShareKey(shareKey);
|
shareLinkInfo.setShareKey(shareKey);
|
||||||
@@ -266,14 +266,14 @@ public class ParserCreate {
|
|||||||
if (shareKey != null) {
|
if (shareKey != null) {
|
||||||
shareLinkInfo.setShareKey(shareKey);
|
shareLinkInfo.setShareKey(shareKey);
|
||||||
}
|
}
|
||||||
} catch (IllegalStateException | IllegalArgumentException ignored) {}
|
} catch (Exception ignored) {}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
String password = matcher.group("PWD");
|
String password = matcher.group("PWD");
|
||||||
if (password != null) {
|
if (password != null) {
|
||||||
shareLinkInfo.setSharePassword(password);
|
shareLinkInfo.setSharePassword(password);
|
||||||
}
|
}
|
||||||
} catch (IllegalStateException | IllegalArgumentException ignored) {}
|
} catch (Exception ignored) {}
|
||||||
|
|
||||||
// 设置标准URL(如果有模板)
|
// 设置标准URL(如果有模板)
|
||||||
if (customConfig.getStandardUrlTemplate() != null) {
|
if (customConfig.getStandardUrlTemplate() != null) {
|
||||||
|
|||||||
@@ -61,7 +61,7 @@ public class JsHttpClient {
|
|||||||
};
|
};
|
||||||
|
|
||||||
public JsHttpClient() {
|
public JsHttpClient() {
|
||||||
this.client = WebClient.create(WebClientVertxInit.get(), new WebClientOptions());
|
this.client = WebClient.create(WebClientVertxInit.get(), new WebClientOptions());;
|
||||||
this.clientSession = WebClientSession.create(client);
|
this.clientSession = WebClientSession.create(client);
|
||||||
this.headers = MultiMap.caseInsensitiveMultiMap();
|
this.headers = MultiMap.caseInsensitiveMultiMap();
|
||||||
// 设置默认的Accept-Encoding头以支持压缩响应
|
// 设置默认的Accept-Encoding头以支持压缩响应
|
||||||
@@ -534,7 +534,7 @@ public class JsHttpClient {
|
|||||||
} else {
|
} else {
|
||||||
promise.fail(result.cause());
|
promise.fail(result.cause());
|
||||||
}
|
}
|
||||||
}).onFailure(e -> log.error("HTTP请求失败", e));
|
}).onFailure(Throwable::printStackTrace);
|
||||||
|
|
||||||
// 等待响应完成(使用配置的超时时间)
|
// 等待响应完成(使用配置的超时时间)
|
||||||
HttpResponse<Buffer> response = promise.future().toCompletionStage()
|
HttpResponse<Buffer> response = promise.future().toCompletionStage()
|
||||||
@@ -677,13 +677,4 @@ public class JsHttpClient {
|
|||||||
return buffer.length();
|
return buffer.length();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 关闭 WebClient 释放连接池资源
|
|
||||||
*/
|
|
||||||
public void close() {
|
|
||||||
if (client != null) {
|
|
||||||
client.close();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -29,12 +29,11 @@ import java.util.stream.Collectors;
|
|||||||
* @author <a href="https://qaiu.top">QAIU</a>
|
* @author <a href="https://qaiu.top">QAIU</a>
|
||||||
* Create at 2025/10/17
|
* Create at 2025/10/17
|
||||||
*/
|
*/
|
||||||
public class JsParserExecutor implements IPanTool, AutoCloseable {
|
public class JsParserExecutor implements IPanTool {
|
||||||
|
|
||||||
private static final Logger log = LoggerFactory.getLogger(JsParserExecutor.class);
|
private static final Logger log = LoggerFactory.getLogger(JsParserExecutor.class);
|
||||||
|
|
||||||
private static volatile WorkerExecutor EXECUTOR;
|
private static final WorkerExecutor EXECUTOR = WebClientVertxInit.get().createSharedWorkerExecutor("parser-executor", 32);
|
||||||
private static final Object EXECUTOR_LOCK = new Object();
|
|
||||||
|
|
||||||
private static String FETCH_RUNTIME_JS = null;
|
private static String FETCH_RUNTIME_JS = null;
|
||||||
|
|
||||||
@@ -147,57 +146,12 @@ public class JsParserExecutor implements IPanTool, AutoCloseable {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 释放资源(ScriptEngine 和 HttpClient),避免内存泄漏
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public void close() {
|
|
||||||
if (httpClient != null) {
|
|
||||||
httpClient.close();
|
|
||||||
}
|
|
||||||
// 清除 ScriptEngine 持有的 Java 对象引用,帮助 GC 回收
|
|
||||||
if (engine != null) {
|
|
||||||
engine.put("http", null);
|
|
||||||
engine.put("logger", null);
|
|
||||||
engine.put("shareLinkInfo", null);
|
|
||||||
engine.put("JavaFetch", null);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 关闭全局 WorkerExecutor(应在应用关闭时调用)
|
|
||||||
*/
|
|
||||||
public static void shutdownExecutor() {
|
|
||||||
synchronized (EXECUTOR_LOCK) {
|
|
||||||
if (EXECUTOR != null) {
|
|
||||||
EXECUTOR.close();
|
|
||||||
EXECUTOR = null;
|
|
||||||
log.info("JsParserExecutor WorkerExecutor 已关闭");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取或创建 WorkerExecutor(懒加载)
|
|
||||||
*/
|
|
||||||
private static WorkerExecutor getExecutor() {
|
|
||||||
if (EXECUTOR != null) {
|
|
||||||
return EXECUTOR;
|
|
||||||
}
|
|
||||||
synchronized (EXECUTOR_LOCK) {
|
|
||||||
if (EXECUTOR == null) {
|
|
||||||
EXECUTOR = WebClientVertxInit.get().createSharedWorkerExecutor("parser-executor", 32);
|
|
||||||
}
|
|
||||||
return EXECUTOR;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Future<String> parse() {
|
public Future<String> parse() {
|
||||||
jsLogger.info("开始执行JavaScript解析器: {}", config.getType());
|
jsLogger.info("开始执行JavaScript解析器: {}", config.getType());
|
||||||
|
|
||||||
// 使用executeBlocking在工作线程上执行,避免阻塞EventLoop线程
|
// 使用executeBlocking在工作线程上执行,避免阻塞EventLoop线程
|
||||||
return getExecutor().executeBlocking(() -> {
|
return EXECUTOR.executeBlocking(() -> {
|
||||||
// 直接调用全局parse函数
|
// 直接调用全局parse函数
|
||||||
Object parseFunction = engine.get("parse");
|
Object parseFunction = engine.get("parse");
|
||||||
if (parseFunction == null) {
|
if (parseFunction == null) {
|
||||||
@@ -219,7 +173,7 @@ public class JsParserExecutor implements IPanTool, AutoCloseable {
|
|||||||
} else {
|
} else {
|
||||||
throw new RuntimeException("parse函数类型错误");
|
throw new RuntimeException("parse函数类型错误");
|
||||||
}
|
}
|
||||||
}).onComplete(ar -> close());
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -227,7 +181,7 @@ public class JsParserExecutor implements IPanTool, AutoCloseable {
|
|||||||
jsLogger.info("开始执行JavaScript文件列表解析: {}", config.getType());
|
jsLogger.info("开始执行JavaScript文件列表解析: {}", config.getType());
|
||||||
|
|
||||||
// 使用executeBlocking在工作线程上执行,避免阻塞EventLoop线程
|
// 使用executeBlocking在工作线程上执行,避免阻塞EventLoop线程
|
||||||
return getExecutor().executeBlocking(() -> {
|
return EXECUTOR.executeBlocking(() -> {
|
||||||
// 直接调用全局parseFileList函数
|
// 直接调用全局parseFileList函数
|
||||||
Object parseFileListFunction = engine.get("parseFileList");
|
Object parseFileListFunction = engine.get("parseFileList");
|
||||||
if (parseFileListFunction == null) {
|
if (parseFileListFunction == null) {
|
||||||
@@ -252,7 +206,7 @@ public class JsParserExecutor implements IPanTool, AutoCloseable {
|
|||||||
} else {
|
} else {
|
||||||
throw new RuntimeException("parseFileList函数类型错误");
|
throw new RuntimeException("parseFileList函数类型错误");
|
||||||
}
|
}
|
||||||
}).onComplete(ar -> close());
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -260,7 +214,7 @@ public class JsParserExecutor implements IPanTool, AutoCloseable {
|
|||||||
jsLogger.info("开始执行JavaScript按ID解析: {}", config.getType());
|
jsLogger.info("开始执行JavaScript按ID解析: {}", config.getType());
|
||||||
|
|
||||||
// 使用executeBlocking在工作线程上执行,避免阻塞EventLoop线程
|
// 使用executeBlocking在工作线程上执行,避免阻塞EventLoop线程
|
||||||
return getExecutor().executeBlocking(() -> {
|
return EXECUTOR.executeBlocking(() -> {
|
||||||
// 直接调用全局parseById函数
|
// 直接调用全局parseById函数
|
||||||
Object parseByIdFunction = engine.get("parseById");
|
Object parseByIdFunction = engine.get("parseById");
|
||||||
if (parseByIdFunction == null) {
|
if (parseByIdFunction == null) {
|
||||||
@@ -283,7 +237,7 @@ public class JsParserExecutor implements IPanTool, AutoCloseable {
|
|||||||
} else {
|
} else {
|
||||||
throw new RuntimeException("parseById函数类型错误");
|
throw new RuntimeException("parseById函数类型错误");
|
||||||
}
|
}
|
||||||
}).onComplete(ar -> close());
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -355,7 +355,7 @@ public class JsPlaygroundExecutor {
|
|||||||
*/
|
*/
|
||||||
public List<JsPlaygroundLogger.LogEntry> getLogs() {
|
public List<JsPlaygroundLogger.LogEntry> getLogs() {
|
||||||
List<JsPlaygroundLogger.LogEntry> logs = playgroundLogger.getLogs();
|
List<JsPlaygroundLogger.LogEntry> logs = playgroundLogger.getLogs();
|
||||||
log.debug("获取日志,数量: {}", logs.size());
|
System.out.println("[JsPlaygroundExecutor] 获取日志,数量: " + logs.size());
|
||||||
return logs;
|
return logs;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -4,9 +4,6 @@ import java.util.ArrayList;
|
|||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
import org.slf4j.Logger;
|
|
||||||
import org.slf4j.LoggerFactory;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 演练场日志收集器
|
* 演练场日志收集器
|
||||||
* 收集JavaScript执行过程中的日志信息
|
* 收集JavaScript执行过程中的日志信息
|
||||||
@@ -16,10 +13,7 @@ import org.slf4j.LoggerFactory;
|
|||||||
*/
|
*/
|
||||||
public class JsPlaygroundLogger {
|
public class JsPlaygroundLogger {
|
||||||
|
|
||||||
private static final Logger log = LoggerFactory.getLogger(JsPlaygroundLogger.class);
|
|
||||||
|
|
||||||
// 使用线程安全的列表
|
// 使用线程安全的列表
|
||||||
private static final int MAX_LOG_SIZE = 1000;
|
|
||||||
private final List<LogEntry> logs = Collections.synchronizedList(new ArrayList<>());
|
private final List<LogEntry> logs = Collections.synchronizedList(new ArrayList<>());
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -65,18 +59,6 @@ public class JsPlaygroundLogger {
|
|||||||
return obj.toString();
|
return obj.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 添加日志条目,超过最大容量时移除最早的条目
|
|
||||||
*/
|
|
||||||
private void addLog(LogEntry entry) {
|
|
||||||
synchronized (logs) {
|
|
||||||
if (logs.size() >= MAX_LOG_SIZE) {
|
|
||||||
logs.remove(0);
|
|
||||||
}
|
|
||||||
logs.add(entry);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 记录日志(内部方法)
|
* 记录日志(内部方法)
|
||||||
* @param level 日志级别
|
* @param level 日志级别
|
||||||
@@ -85,8 +67,8 @@ public class JsPlaygroundLogger {
|
|||||||
*/
|
*/
|
||||||
private void log(String level, Object message, String source) {
|
private void log(String level, Object message, String source) {
|
||||||
String msg = toString(message);
|
String msg = toString(message);
|
||||||
addLog(new LogEntry(level, msg, source));
|
logs.add(new LogEntry(level, msg, source));
|
||||||
log.debug("[{}PlaygroundLogger] {}: {}", source, level, msg);
|
System.out.println("[" + source + "PlaygroundLogger] " + level + ": " + msg);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -129,8 +111,8 @@ public class JsPlaygroundLogger {
|
|||||||
if (throwable != null) {
|
if (throwable != null) {
|
||||||
msg = msg + ": " + throwable.getMessage();
|
msg = msg + ": " + throwable.getMessage();
|
||||||
}
|
}
|
||||||
addLog(new LogEntry("ERROR", msg, "JS"));
|
logs.add(new LogEntry("ERROR", msg, "JS"));
|
||||||
log.debug("[JSPlaygroundLogger] ERROR: {}", msg);
|
System.out.println("[JSPlaygroundLogger] ERROR: " + msg);
|
||||||
}
|
}
|
||||||
|
|
||||||
// ===== 以下是供Java层调用的内部方法 =====
|
// ===== 以下是供Java层调用的内部方法 =====
|
||||||
@@ -171,8 +153,8 @@ public class JsPlaygroundLogger {
|
|||||||
if (throwable != null) {
|
if (throwable != null) {
|
||||||
msg = msg + ": " + throwable.getMessage();
|
msg = msg + ": " + throwable.getMessage();
|
||||||
}
|
}
|
||||||
addLog(new LogEntry("ERROR", msg, "JAVA"));
|
logs.add(new LogEntry("ERROR", msg, "JAVA"));
|
||||||
log.debug("[JAVAPlaygroundLogger] ERROR: {}", msg);
|
System.out.println("[JAVAPlaygroundLogger] ERROR: " + msg);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -139,8 +139,8 @@ public class JsScriptLoader {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
String jarPath = jarUrl.getPath().substring(5, jarUrl.getPath().indexOf("!"));
|
String jarPath = jarUrl.getPath().substring(5, jarUrl.getPath().indexOf("!"));
|
||||||
|
JarFile jarFile = new JarFile(jarPath);
|
||||||
|
|
||||||
try (JarFile jarFile = new JarFile(jarPath)) {
|
|
||||||
Enumeration<JarEntry> entries = jarFile.entries();
|
Enumeration<JarEntry> entries = jarFile.entries();
|
||||||
while (entries.hasMoreElements()) {
|
while (entries.hasMoreElements()) {
|
||||||
JarEntry entry = entries.nextElement();
|
JarEntry entry = entries.nextElement();
|
||||||
@@ -152,7 +152,8 @@ public class JsScriptLoader {
|
|||||||
resourceFiles.add(entryName);
|
resourceFiles.add(entryName);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
jarFile.close();
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
log.debug("解析JAR包资源文件失败", e);
|
log.debug("解析JAR包资源文件失败", e);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -109,9 +109,9 @@ public class FjTool extends PanBase {
|
|||||||
|
|
||||||
// String uuid = UUID.randomUUID().toString().toLowerCase(); // 也可以使用 UUID.randomUUID().toString()
|
// String uuid = UUID.randomUUID().toString().toLowerCase(); // 也可以使用 UUID.randomUUID().toString()
|
||||||
|
|
||||||
static volatile String token = null;
|
static String token = null;
|
||||||
static volatile String userId = null;
|
static String userId = null;
|
||||||
public static volatile boolean authFlag = true;
|
public static boolean authFlag = true;
|
||||||
|
|
||||||
public FjTool(ShareLinkInfo shareLinkInfo) {
|
public FjTool(ShareLinkInfo shareLinkInfo) {
|
||||||
super(shareLinkInfo);
|
super(shareLinkInfo);
|
||||||
@@ -289,14 +289,12 @@ public class FjTool extends PanBase {
|
|||||||
JsonObject json = asJson(res2);
|
JsonObject json = asJson(res2);
|
||||||
if (json.getInteger("code") == 200) {
|
if (json.getInteger("code") == 200) {
|
||||||
token = json.getJsonObject("data").getString("appToken");
|
token = json.getJsonObject("data").getString("appToken");
|
||||||
MultiMap h0 = MultiMap.caseInsensitiveMultiMap();
|
header0.set("appToken", token);
|
||||||
h0.addAll(header0);
|
log.info("登录成功 token: {}", token);
|
||||||
h0.set("appToken", token);
|
|
||||||
log.info("登录成功 token: {}...", token != null ? token.substring(0, Math.min(8, token.length())) : "null");
|
|
||||||
client.postAbs(UriTemplate.of(TOKEN_VERIFY_URL))
|
client.postAbs(UriTemplate.of(TOKEN_VERIFY_URL))
|
||||||
.setTemplateParam("uuid", uuid)
|
.setTemplateParam("uuid", uuid)
|
||||||
.setTemplateParam("ts", tsEncode2)
|
.setTemplateParam("ts", tsEncode2)
|
||||||
.putHeaders(h0).send().onSuccess(res -> {
|
.putHeaders(header0).send().onSuccess(res -> {
|
||||||
if (asJson(res).getInteger("code") == 200) {
|
if (asJson(res).getInteger("code") == 200) {
|
||||||
if (FjTool.userId == null) {
|
if (FjTool.userId == null) {
|
||||||
FjTool.userId = asJson(res).getJsonObject("map").getString("userId");
|
FjTool.userId = asJson(res).getJsonObject("map").getString("userId");
|
||||||
@@ -456,10 +454,7 @@ public class FjTool extends PanBase {
|
|||||||
// 如果参数里的目录ID不为空,则直接解析目录
|
// 如果参数里的目录ID不为空,则直接解析目录
|
||||||
String dirId = (String) shareLinkInfo.getOtherParam().get("dirId");
|
String dirId = (String) shareLinkInfo.getOtherParam().get("dirId");
|
||||||
if (dirId != null && !dirId.isEmpty()) {
|
if (dirId != null && !dirId.isEmpty()) {
|
||||||
Object uuidObj = shareLinkInfo.getOtherParam().get("uuid");
|
uuid = shareLinkInfo.getOtherParam().get("uuid").toString();
|
||||||
if (uuidObj != null) {
|
|
||||||
uuid = uuidObj.toString();
|
|
||||||
}
|
|
||||||
parserDir(dirId, shareId, promise0);
|
parserDir(dirId, shareId, promise0);
|
||||||
return promise0.future();
|
return promise0.future();
|
||||||
}
|
}
|
||||||
@@ -500,7 +495,7 @@ public class FjTool extends PanBase {
|
|||||||
JsonArray list;
|
JsonArray list;
|
||||||
try {
|
try {
|
||||||
JsonObject jsonObject = asJson(res);
|
JsonObject jsonObject = asJson(res);
|
||||||
log.debug("目录列表: {}", jsonObject.encodePrettily());
|
System.out.println(jsonObject.encodePrettily());
|
||||||
list = jsonObject.getJsonArray("list");
|
list = jsonObject.getJsonArray("list");
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
log.error("解析目录失败: {}", res.bodyAsString());
|
log.error("解析目录失败: {}", res.bodyAsString());
|
||||||
@@ -581,10 +576,6 @@ public class FjTool extends PanBase {
|
|||||||
|
|
||||||
// 第二次请求
|
// 第二次请求
|
||||||
JsonObject paramJson = (JsonObject)shareLinkInfo.getOtherParam().get("paramJson");
|
JsonObject paramJson = (JsonObject)shareLinkInfo.getOtherParam().get("paramJson");
|
||||||
if (paramJson == null) {
|
|
||||||
promise.fail("缺少 paramJson 参数");
|
|
||||||
return promise.future();
|
|
||||||
}
|
|
||||||
clientNoRedirects.getAbs(UriTemplate.of(SECOND_REQUEST_URL_VIP))
|
clientNoRedirects.getAbs(UriTemplate.of(SECOND_REQUEST_URL_VIP))
|
||||||
.setTemplateParam("fidEncode", paramJson.getString("fidEncode"))
|
.setTemplateParam("fidEncode", paramJson.getString("fidEncode"))
|
||||||
.setTemplateParam("uuid", paramJson.getString("uuid"))
|
.setTemplateParam("uuid", paramJson.getString("uuid"))
|
||||||
|
|||||||
@@ -389,10 +389,6 @@ public class FsTool extends PanBase {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
JsonObject paramJson = (JsonObject) shareLinkInfo.getOtherParam().get("paramJson");
|
JsonObject paramJson = (JsonObject) shareLinkInfo.getOtherParam().get("paramJson");
|
||||||
if (paramJson == null) {
|
|
||||||
parsePromise.fail("缺少 paramJson 参数");
|
|
||||||
return parsePromise.future();
|
|
||||||
}
|
|
||||||
String shareUrl = paramJson.getString("shareUrl");
|
String shareUrl = paramJson.getString("shareUrl");
|
||||||
String objToken = paramJson.getString("objToken");
|
String objToken = paramJson.getString("objToken");
|
||||||
String tenant = extractTenant(shareUrl);
|
String tenant = extractTenant(shareUrl);
|
||||||
@@ -448,7 +444,7 @@ public class FsTool extends PanBase {
|
|||||||
if (m1.find()) {
|
if (m1.find()) {
|
||||||
try {
|
try {
|
||||||
return URLDecoder.decode(m1.group(1).trim(), StandardCharsets.UTF_8);
|
return URLDecoder.decode(m1.group(1).trim(), StandardCharsets.UTF_8);
|
||||||
} catch (IllegalArgumentException ignored) {
|
} catch (Exception ignored) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -457,7 +453,7 @@ public class FsTool extends PanBase {
|
|||||||
if (m2.find()) {
|
if (m2.find()) {
|
||||||
try {
|
try {
|
||||||
return URLDecoder.decode(m2.group(1).trim(), StandardCharsets.UTF_8);
|
return URLDecoder.decode(m2.group(1).trim(), StandardCharsets.UTF_8);
|
||||||
} catch (IllegalArgumentException ignored) {
|
} catch (Exception ignored) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -89,8 +89,8 @@ public class IzTool extends PanBase {
|
|||||||
|
|
||||||
String uuid = UUID.randomUUID().toString().toLowerCase(); // 也可以使用 UUID.randomUUID().toString()
|
String uuid = UUID.randomUUID().toString().toLowerCase(); // 也可以使用 UUID.randomUUID().toString()
|
||||||
|
|
||||||
public static volatile String token = null;
|
public static String token = null;
|
||||||
public static volatile boolean authFlag = true;
|
public static boolean authFlag = true;
|
||||||
|
|
||||||
public Future<String> parse() {
|
public Future<String> parse() {
|
||||||
|
|
||||||
@@ -102,7 +102,7 @@ public class IzTool extends PanBase {
|
|||||||
if (shareLinkInfo.getOtherParam().containsKey("auths")) {
|
if (shareLinkInfo.getOtherParam().containsKey("auths")) {
|
||||||
boolean isTempAuth = shareLinkInfo.getOtherParam().containsKey("__TEMP_AUTH_ADDED");
|
boolean isTempAuth = shareLinkInfo.getOtherParam().containsKey("__TEMP_AUTH_ADDED");
|
||||||
log.info("文件解析检测到认证信息: isTempAuth={}, authFlag={}, token={}",
|
log.info("文件解析检测到认证信息: isTempAuth={}, authFlag={}, token={}",
|
||||||
isTempAuth, authFlag, token != null ? "已登录(" + token.substring(0, Math.min(8, token.length())) + "...)" : "未登录");
|
isTempAuth, authFlag, token != null ? "已登录(" + token.substring(0, Math.min(10, token.length())) + "...)" : "未登录");
|
||||||
|
|
||||||
// 如果需要认证但还没有token,先执行登录
|
// 如果需要认证但还没有token,先执行登录
|
||||||
if ((isTempAuth || authFlag) && token == null) {
|
if ((isTempAuth || authFlag) && token == null) {
|
||||||
@@ -118,7 +118,7 @@ public class IzTool extends PanBase {
|
|||||||
// 登录失败,继续使用免登录模式
|
// 登录失败,继续使用免登录模式
|
||||||
});
|
});
|
||||||
} else if (token != null) {
|
} else if (token != null) {
|
||||||
log.info("文件解析使用已有token: {}...", token.substring(0, Math.min(8, token.length())));
|
log.info("文件解析使用已有token: {}...", token.substring(0, Math.min(10, token.length())));
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
log.debug("文件解析无认证信息,使用免登录模式");
|
log.debug("文件解析无认证信息,使用免登录模式");
|
||||||
@@ -247,7 +247,7 @@ public class IzTool extends PanBase {
|
|||||||
log.warn("登录失败: {}", failRes.getMessage());
|
log.warn("登录失败: {}", failRes.getMessage());
|
||||||
fail(failRes.getMessage());
|
fail(failRes.getMessage());
|
||||||
}).onSuccess(r-> {
|
}).onSuccess(r-> {
|
||||||
httpRequest.setTemplateParam("appToken", token)
|
httpRequest.setTemplateParam("appToken", header.get("appToken"))
|
||||||
.putHeaders(header);
|
.putHeaders(header);
|
||||||
httpRequest.send().onSuccess(this::down).onFailure(handleFail("请求2"));
|
httpRequest.send().onSuccess(this::down).onFailure(handleFail("请求2"));
|
||||||
});
|
});
|
||||||
@@ -263,12 +263,12 @@ public class IzTool extends PanBase {
|
|||||||
log.warn("重新登录失败: {}", failRes.getMessage());
|
log.warn("重新登录失败: {}", failRes.getMessage());
|
||||||
fail(failRes.getMessage());
|
fail(failRes.getMessage());
|
||||||
}).onSuccess(r-> {
|
}).onSuccess(r-> {
|
||||||
httpRequest.setTemplateParam("appToken", token)
|
httpRequest.setTemplateParam("appToken", header.get("appToken"))
|
||||||
.putHeaders(header);
|
.putHeaders(header);
|
||||||
httpRequest.send().onSuccess(this::down).onFailure(handleFail("请求2"));
|
httpRequest.send().onSuccess(this::down).onFailure(handleFail("请求2"));
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
httpRequest.setTemplateParam("appToken", token)
|
httpRequest.setTemplateParam("appToken", header.get("appToken"))
|
||||||
.putHeaders(header);
|
.putHeaders(header);
|
||||||
httpRequest.send().onSuccess(this::down).onFailure(handleFail("请求2"));
|
httpRequest.send().onSuccess(this::down).onFailure(handleFail("请求2"));
|
||||||
}
|
}
|
||||||
@@ -311,7 +311,8 @@ public class IzTool extends PanBase {
|
|||||||
JsonObject json = asJson(res2);
|
JsonObject json = asJson(res2);
|
||||||
if (json.getInteger("code") == 200) {
|
if (json.getInteger("code") == 200) {
|
||||||
token = json.getJsonObject("data").getString("appToken");
|
token = json.getJsonObject("data").getString("appToken");
|
||||||
log.info("登录成功 token: {}...", token != null ? token.substring(0, Math.min(8, token.length())) : "null");
|
header.set("appToken", token);
|
||||||
|
log.info("登录成功 token: {}", token);
|
||||||
promise1.complete();
|
promise1.complete();
|
||||||
} else {
|
} else {
|
||||||
// 检查是否为临时认证
|
// 检查是否为临时认证
|
||||||
@@ -462,10 +463,7 @@ public class IzTool extends PanBase {
|
|||||||
// 如果参数里的目录ID不为空,则直接解析目录
|
// 如果参数里的目录ID不为空,则直接解析目录
|
||||||
String dirId = (String) shareLinkInfo.getOtherParam().get("dirId");
|
String dirId = (String) shareLinkInfo.getOtherParam().get("dirId");
|
||||||
if (dirId != null && !dirId.isEmpty()) {
|
if (dirId != null && !dirId.isEmpty()) {
|
||||||
Object uuidObj = shareLinkInfo.getOtherParam().get("uuid");
|
uuid = shareLinkInfo.getOtherParam().get("uuid").toString();
|
||||||
if (uuidObj != null) {
|
|
||||||
uuid = uuidObj.toString();
|
|
||||||
}
|
|
||||||
parserDir(dirId, shareId, promise);
|
parserDir(dirId, shareId, promise);
|
||||||
return promise.future();
|
return promise.future();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -88,8 +88,8 @@ public class IzToolWithAuth extends PanBase {
|
|||||||
|
|
||||||
String uuid = UUID.randomUUID().toString().toLowerCase(); // 也可以使用 UUID.randomUUID().toString()
|
String uuid = UUID.randomUUID().toString().toLowerCase(); // 也可以使用 UUID.randomUUID().toString()
|
||||||
|
|
||||||
public static volatile String token = null;
|
public static String token = null;
|
||||||
public static volatile boolean authFlag = true;
|
public static boolean authFlag = true;
|
||||||
|
|
||||||
public Future<String> parse() {
|
public Future<String> parse() {
|
||||||
|
|
||||||
@@ -216,7 +216,7 @@ public class IzToolWithAuth extends PanBase {
|
|||||||
log.warn("登录失败: {}", failRes.getMessage());
|
log.warn("登录失败: {}", failRes.getMessage());
|
||||||
fail(failRes.getMessage());
|
fail(failRes.getMessage());
|
||||||
}).onSuccess(r-> {
|
}).onSuccess(r-> {
|
||||||
httpRequest.setTemplateParam("appToken", token)
|
httpRequest.setTemplateParam("appToken", header.get("appToken"))
|
||||||
.putHeaders(header);
|
.putHeaders(header);
|
||||||
httpRequest.send().onSuccess(this::down).onFailure(handleFail("请求2"));
|
httpRequest.send().onSuccess(this::down).onFailure(handleFail("请求2"));
|
||||||
});
|
});
|
||||||
@@ -232,12 +232,12 @@ public class IzToolWithAuth extends PanBase {
|
|||||||
log.warn("重新登录失败: {}", failRes.getMessage());
|
log.warn("重新登录失败: {}", failRes.getMessage());
|
||||||
fail(failRes.getMessage());
|
fail(failRes.getMessage());
|
||||||
}).onSuccess(r-> {
|
}).onSuccess(r-> {
|
||||||
httpRequest.setTemplateParam("appToken", token)
|
httpRequest.setTemplateParam("appToken", header.get("appToken"))
|
||||||
.putHeaders(header);
|
.putHeaders(header);
|
||||||
httpRequest.send().onSuccess(this::down).onFailure(handleFail("请求2"));
|
httpRequest.send().onSuccess(this::down).onFailure(handleFail("请求2"));
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
httpRequest.setTemplateParam("appToken", token)
|
httpRequest.setTemplateParam("appToken", header.get("appToken"))
|
||||||
.putHeaders(header);
|
.putHeaders(header);
|
||||||
httpRequest.send().onSuccess(this::down).onFailure(handleFail("请求2"));
|
httpRequest.send().onSuccess(this::down).onFailure(handleFail("请求2"));
|
||||||
}
|
}
|
||||||
@@ -280,7 +280,8 @@ public class IzToolWithAuth extends PanBase {
|
|||||||
JsonObject json = asJson(res2);
|
JsonObject json = asJson(res2);
|
||||||
if (json.getInteger("code") == 200) {
|
if (json.getInteger("code") == 200) {
|
||||||
token = json.getJsonObject("data").getString("appToken");
|
token = json.getJsonObject("data").getString("appToken");
|
||||||
log.info("登录成功 token: {}...", token != null ? token.substring(0, Math.min(8, token.length())) : "null");
|
header.set("appToken", token);
|
||||||
|
log.info("登录成功 token: {}", token);
|
||||||
promise1.complete();
|
promise1.complete();
|
||||||
} else {
|
} else {
|
||||||
// 检查是否为临时认证
|
// 检查是否为临时认证
|
||||||
@@ -431,8 +432,7 @@ public class IzToolWithAuth extends PanBase {
|
|||||||
// 如果参数里的目录ID不为空,则直接解析目录
|
// 如果参数里的目录ID不为空,则直接解析目录
|
||||||
String dirId = (String) shareLinkInfo.getOtherParam().get("dirId");
|
String dirId = (String) shareLinkInfo.getOtherParam().get("dirId");
|
||||||
if (dirId != null && !dirId.isEmpty()) {
|
if (dirId != null && !dirId.isEmpty()) {
|
||||||
Object uuidObj = shareLinkInfo.getOtherParam().get("uuid");
|
uuid = shareLinkInfo.getOtherParam().get("uuid").toString();
|
||||||
uuid = uuidObj != null ? uuidObj.toString() : null;
|
|
||||||
parserDir(dirId, shareId, promise);
|
parserDir(dirId, shareId, promise);
|
||||||
return promise.future();
|
return promise.future();
|
||||||
}
|
}
|
||||||
@@ -480,7 +480,7 @@ public class IzToolWithAuth extends PanBase {
|
|||||||
requestDirList(id, shareId, tsEncode, promise);
|
requestDirList(id, shareId, tsEncode, promise);
|
||||||
})
|
})
|
||||||
.onSuccess(r -> {
|
.onSuccess(r -> {
|
||||||
log.info("目录解析登录成功,token={}, 使用 VIP 模式", token != null ? token.substring(0, Math.min(8, token.length())) + "..." : "null");
|
log.info("目录解析登录成功,token={}, 使用 VIP 模式", token != null ? token.substring(0, 10) + "..." : "null");
|
||||||
requestDirList(id, shareId, tsEncode, promise);
|
requestDirList(id, shareId, tsEncode, promise);
|
||||||
});
|
});
|
||||||
return;
|
return;
|
||||||
@@ -627,7 +627,7 @@ public class IzToolWithAuth extends PanBase {
|
|||||||
|
|
||||||
// 如果有 token,使用 VIP 接口
|
// 如果有 token,使用 VIP 接口
|
||||||
if (StringUtils.isNotBlank(appToken)) {
|
if (StringUtils.isNotBlank(appToken)) {
|
||||||
log.debug("parseById 使用 VIP 接口, appToken={}", appToken.substring(0, Math.min(8, appToken.length())) + "...");
|
log.debug("parseById 使用 VIP 接口, appToken={}", appToken.substring(0, Math.min(10, appToken.length())) + "...");
|
||||||
webClientSession.getAbs(UriTemplate.of(SECOND_REQUEST_URL_VIP))
|
webClientSession.getAbs(UriTemplate.of(SECOND_REQUEST_URL_VIP))
|
||||||
.putHeaders(header)
|
.putHeaders(header)
|
||||||
.setTemplateParam("fidEncode", paramJson.getString("fidEncode"))
|
.setTemplateParam("fidEncode", paramJson.getString("fidEncode"))
|
||||||
|
|||||||
@@ -14,7 +14,6 @@ import io.vertx.ext.web.client.WebClientSession;
|
|||||||
import org.openjdk.nashorn.api.scripting.ScriptObjectMirror;
|
import org.openjdk.nashorn.api.scripting.ScriptObjectMirror;
|
||||||
|
|
||||||
import javax.script.ScriptException;
|
import javax.script.ScriptException;
|
||||||
import java.net.MalformedURLException;
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
@@ -82,8 +81,8 @@ public class LzTool extends PanBase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void doParser(String html, String pwd, String sUrl) {
|
private void doParser(String html, String pwd, String sUrl) {
|
||||||
// 检测是否为目录分享链接 (含 /s/、/b/ 路径段或 b 开头的路径段)
|
// 检测是否为目录分享链接 (含 /s/、/b/ 路径段或 b0 开头的路径段)
|
||||||
if (sUrl.matches(".*/(s|b)/[^/]+.*") || sUrl.matches(".*/b[^/]+.*")) {
|
if (sUrl.matches(".*/(s|b)/[^/]+.*") || sUrl.matches(".*/b0[^/]+.*")) {
|
||||||
fail("该链接为蓝奏云目录分享,请使用目录解析接口");
|
fail("该链接为蓝奏云目录分享,请使用目录解析接口");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -108,7 +107,7 @@ public class LzTool extends PanBase {
|
|||||||
try {
|
try {
|
||||||
setFileInfo(html, shareLinkInfo);
|
setFileInfo(html, shareLinkInfo);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
log.error("文件信息解析异常", e);
|
e.printStackTrace();
|
||||||
}
|
}
|
||||||
// 匹配iframe
|
// 匹配iframe
|
||||||
Pattern compile = Pattern.compile("src=\"(/fn\\?[a-zA-Z\\d_+/=]{16,})\"");
|
Pattern compile = Pattern.compile("src=\"(/fn\\?[a-zA-Z\\d_+/=]{16,})\"");
|
||||||
@@ -176,7 +175,7 @@ public class LzTool extends PanBase {
|
|||||||
if (firstDot >= 0) {
|
if (firstDot >= 0) {
|
||||||
domain = host.substring(firstDot); // e.g. ".lanzoum.com"
|
domain = host.substring(firstDot); // e.g. ".lanzoum.com"
|
||||||
}
|
}
|
||||||
} catch (MalformedURLException ignored) {}
|
} catch (Exception ignored) {}
|
||||||
// 创建一个 Cookie 并放入 CookieStore
|
// 创建一个 Cookie 并放入 CookieStore
|
||||||
DefaultCookie nettyCookie = new DefaultCookie("acw_sc__v2", acw_sc__v2);
|
DefaultCookie nettyCookie = new DefaultCookie("acw_sc__v2", acw_sc__v2);
|
||||||
nettyCookie.setDomain(domain);
|
nettyCookie.setDomain(domain);
|
||||||
@@ -218,7 +217,7 @@ public class LzTool extends PanBase {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
Map<?, ?> signMap = (Map<?, ?>)obj.get("data");
|
Map<?, ?> signMap = (Map<?, ?>)obj.get("data");
|
||||||
String url0 = String.valueOf(obj.get("url"));
|
String url0 = obj.get("url").toString();
|
||||||
MultiMap map = MultiMap.caseInsensitiveMultiMap();
|
MultiMap map = MultiMap.caseInsensitiveMultiMap();
|
||||||
signMap.forEach((k, v) -> {
|
signMap.forEach((k, v) -> {
|
||||||
map.add((String) k, v.toString());
|
map.add((String) k, v.toString());
|
||||||
@@ -276,7 +275,7 @@ public class LzTool extends PanBase {
|
|||||||
String h = du.getHost();
|
String h = du.getHost();
|
||||||
int dot = h.indexOf('.');
|
int dot = h.indexOf('.');
|
||||||
if (dot >= 0) downDomain = h.substring(dot);
|
if (dot >= 0) downDomain = h.substring(dot);
|
||||||
} catch (MalformedURLException ignored) {}
|
} catch (Exception ignored) {}
|
||||||
// 创建一个 Cookie 并放入 CookieStore
|
// 创建一个 Cookie 并放入 CookieStore
|
||||||
DefaultCookie nettyCookie = new DefaultCookie("acw_sc__v2", acw_sc__v2);
|
DefaultCookie nettyCookie = new DefaultCookie("acw_sc__v2", acw_sc__v2);
|
||||||
nettyCookie.setDomain(downDomain);
|
nettyCookie.setDomain(downDomain);
|
||||||
@@ -291,12 +290,12 @@ public class LzTool extends PanBase {
|
|||||||
if (location0 == null) {
|
if (location0 == null) {
|
||||||
fail(downUrl + " -> 直链获取失败2, 可能分享已失效");
|
fail(downUrl + " -> 直链获取失败2, 可能分享已失效");
|
||||||
} else {
|
} else {
|
||||||
setDateAndComplete(location0);
|
setDateAndComplate(location0);
|
||||||
}
|
}
|
||||||
}).onFailure(handleFail(downUrl));
|
}).onFailure(handleFail(downUrl));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
setDateAndComplete(location);
|
setDateAndComplate(location);
|
||||||
})
|
})
|
||||||
.onFailure(handleFail(downUrl));
|
.onFailure(handleFail(downUrl));
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
@@ -305,7 +304,7 @@ public class LzTool extends PanBase {
|
|||||||
}).onFailure(handleFail(url));
|
}).onFailure(handleFail(url));
|
||||||
}
|
}
|
||||||
|
|
||||||
private void setDateAndComplete(String location0) {
|
private void setDateAndComplate(String location0) {
|
||||||
// 分享时间 提取url中的时间戳格式:lanzoui.com/abc/abc/yyyy/mm/dd/
|
// 分享时间 提取url中的时间戳格式:lanzoui.com/abc/abc/yyyy/mm/dd/
|
||||||
String regex = "(\\d{4}/\\d{1,2}/\\d{1,2})";
|
String regex = "(\\d{4}/\\d{1,2}/\\d{1,2})";
|
||||||
Matcher matcher = Pattern.compile(regex).matcher(location0);
|
Matcher matcher = Pattern.compile(regex).matcher(location0);
|
||||||
@@ -355,8 +354,8 @@ public class LzTool extends PanBase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void handleFileListParse(String html, String pwd, String sUrl, Promise<List<FileInfo>> promise) {
|
private void handleFileListParse(String html, String pwd, String sUrl, Promise<List<FileInfo>> promise) {
|
||||||
// 检测是否为文件分享链接 (不含 /s/、/b/ 路径段且不含 b 开头的路径段)
|
// 检测是否为文件分享链接 (不含 /s/、/b/ 路径段且不含 b0 开头的路径段)
|
||||||
if (!sUrl.matches(".*/(s|b)/[^/]+.*") && !sUrl.matches(".*/b[^/]+.*")) {
|
if (!sUrl.matches(".*/(s|b)/[^/]+.*") && !sUrl.matches(".*/b0[^/]+.*")) {
|
||||||
promise.fail(baseMsg() + "该链接为蓝奏云文件分享,请使用文件解析接口");
|
promise.fail(baseMsg() + "该链接为蓝奏云文件分享,请使用文件解析接口");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -86,10 +86,10 @@ public class MkgsTool extends PanBase {
|
|||||||
// 查找并输出 hash 字段的值
|
// 查找并输出 hash 字段的值
|
||||||
if (matcher.find()) {
|
if (matcher.find()) {
|
||||||
String hashValue = matcher.group(1); // 获取第一个捕获组
|
String hashValue = matcher.group(1); // 获取第一个捕获组
|
||||||
log.debug("hash: {}", hashValue);
|
System.out.println(hashValue);
|
||||||
client.getAbs(UriTemplate.of(API_URL)).setTemplateParam("hash", hashValue).send().onSuccess(res3 -> {
|
client.getAbs(UriTemplate.of(API_URL)).setTemplateParam("hash", hashValue).send().onSuccess(res3 -> {
|
||||||
JsonObject jsonObject = asJson(res3);
|
JsonObject jsonObject = asJson(res3);
|
||||||
log.debug("API response: {}", jsonObject.encodePrettily());
|
System.out.println(jsonObject.encodePrettily());
|
||||||
if (jsonObject.containsKey("url")) {
|
if (jsonObject.containsKey("url")) {
|
||||||
promise.complete(jsonObject.getString("url"));
|
promise.complete(jsonObject.getString("url"));
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -29,19 +29,19 @@ public class MkwTool extends PanBase {
|
|||||||
clientSession.getAbs(shareUrl).send().onSuccess(result -> {
|
clientSession.getAbs(shareUrl).send().onSuccess(result -> {
|
||||||
String cookie = result.headers().get("set-cookie");
|
String cookie = result.headers().get("set-cookie");
|
||||||
|
|
||||||
if (cookie != null && !cookie.isEmpty()) {
|
if (!cookie.isEmpty()) {
|
||||||
|
|
||||||
String regex = "([A-Za-z0-9_]+)=([A-Za-z0-9]+)";
|
String regex = "([A-Za-z0-9_]+)=([A-Za-z0-9]+)";
|
||||||
Pattern pattern = Pattern.compile(regex);
|
Pattern pattern = Pattern.compile(regex);
|
||||||
Matcher matcher = pattern.matcher(cookie);
|
Matcher matcher = pattern.matcher(cookie);
|
||||||
if (matcher.find()) {
|
if (matcher.find()) {
|
||||||
log.debug("cookie key: {}", matcher.group(1));
|
System.out.println(matcher.group(1));
|
||||||
log.debug("cookie value: {}", matcher.group(2));
|
System.out.println(matcher.group(2));
|
||||||
|
|
||||||
var key = matcher.group(1);
|
var key = matcher.group(1);
|
||||||
var token = matcher.group(2);
|
var token = matcher.group(2);
|
||||||
String sign = JsExecUtils.getKwSign(token, key);
|
String sign = JsExecUtils.getKwSign(token, key);
|
||||||
log.debug("sign: {}", sign);
|
System.out.println(sign);
|
||||||
clientSession.getAbs(UriTemplate.of(API_URL)).setTemplateParam("mid", shareLinkInfo.getShareKey())
|
clientSession.getAbs(UriTemplate.of(API_URL)).setTemplateParam("mid", shareLinkInfo.getShareKey())
|
||||||
.putHeader("Secret", sign).send().onSuccess(res -> {
|
.putHeader("Secret", sign).send().onSuccess(res -> {
|
||||||
JsonObject json = asJson(res);
|
JsonObject json = asJson(res);
|
||||||
@@ -54,7 +54,7 @@ public class MkwTool extends PanBase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
log.error("解析失败", e);
|
e.printStackTrace();
|
||||||
fail("解析失败");
|
fail("解析失败");
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -21,8 +21,6 @@ public class P115Tool extends PanBase {
|
|||||||
private static final String SECOND_REQUEST_URL = API_URL_PREFIX + "share/skip_login_downurl";
|
private static final String SECOND_REQUEST_URL = API_URL_PREFIX + "share/skip_login_downurl";
|
||||||
|
|
||||||
|
|
||||||
private static final String DEFAULT_UA = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36";
|
|
||||||
|
|
||||||
private static final MultiMap header;
|
private static final MultiMap header;
|
||||||
|
|
||||||
static {
|
static {
|
||||||
@@ -51,11 +49,9 @@ public class P115Tool extends PanBase {
|
|||||||
|
|
||||||
public Future<String> parse() {
|
public Future<String> parse() {
|
||||||
// 第一次请求 获取文件信息
|
// 第一次请求 获取文件信息
|
||||||
Object uaObj = shareLinkInfo.getOtherParam().get("UA");
|
|
||||||
String ua = uaObj != null ? uaObj.toString() : DEFAULT_UA;
|
|
||||||
client.getAbs(UriTemplate.of(FIRST_REQUEST_URL))
|
client.getAbs(UriTemplate.of(FIRST_REQUEST_URL))
|
||||||
.putHeaders(header)
|
.putHeaders(header)
|
||||||
.putHeader("User-Agent", ua)
|
.putHeader("User-Agent", shareLinkInfo.getOtherParam().get("UA").toString())
|
||||||
.setTemplateParam("dataKey", shareLinkInfo.getShareKey())
|
.setTemplateParam("dataKey", shareLinkInfo.getShareKey())
|
||||||
.setTemplateParam("dataPwd", shareLinkInfo.getSharePassword())
|
.setTemplateParam("dataPwd", shareLinkInfo.getSharePassword())
|
||||||
.send().onSuccess(res -> {
|
.send().onSuccess(res -> {
|
||||||
@@ -72,7 +68,7 @@ public class P115Tool extends PanBase {
|
|||||||
// share_code={dataKey}&receive_code={dataPwd}&file_id={file_id}
|
// share_code={dataKey}&receive_code={dataPwd}&file_id={file_id}
|
||||||
client.postAbs(SECOND_REQUEST_URL)
|
client.postAbs(SECOND_REQUEST_URL)
|
||||||
.putHeader("Content-Type", "application/x-www-form-urlencoded; charset=UTF-8")
|
.putHeader("Content-Type", "application/x-www-form-urlencoded; charset=UTF-8")
|
||||||
.putHeader("User-Agent", ua)
|
.putHeader("User-Agent", shareLinkInfo.getOtherParam().get("UA").toString())
|
||||||
.sendForm(MultiMap.caseInsensitiveMultiMap()
|
.sendForm(MultiMap.caseInsensitiveMultiMap()
|
||||||
.set("share_code", shareLinkInfo.getShareKey())
|
.set("share_code", shareLinkInfo.getShareKey())
|
||||||
.set("receive_code", shareLinkInfo.getSharePassword())
|
.set("receive_code", shareLinkInfo.getSharePassword())
|
||||||
|
|||||||
@@ -85,7 +85,7 @@ public class PdbTool extends PanBase implements IPanTool {
|
|||||||
})
|
})
|
||||||
.onFailure(handleFail());
|
.onFailure(handleFail());
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
log.error("URL编码异常", e);
|
e.printStackTrace();
|
||||||
}
|
}
|
||||||
|
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -129,7 +129,7 @@ public class PodTool extends PanBase {
|
|||||||
|
|
||||||
if (urlMatcher.find()) {
|
if (urlMatcher.find()) {
|
||||||
String url = urlMatcher.group("url");
|
String url = urlMatcher.group("url");
|
||||||
log.debug("URL: {}", url);
|
System.out.println("URL: " + url);
|
||||||
return url;
|
return url;
|
||||||
}
|
}
|
||||||
throw new RuntimeException("URL匹配失败");
|
throw new RuntimeException("URL匹配失败");
|
||||||
@@ -172,7 +172,7 @@ public class PodTool extends PanBase {
|
|||||||
|
|
||||||
if (tokenMatcher.find()) {
|
if (tokenMatcher.find()) {
|
||||||
String token = tokenMatcher.group(1);
|
String token = tokenMatcher.group(1);
|
||||||
log.debug("Token: {}***", token.length() > 4 ? token.substring(0, 4) : "***");
|
System.out.println("Token: " + token);
|
||||||
return token;
|
return token;
|
||||||
}
|
}
|
||||||
throw new RuntimeException("token匹配失败");
|
throw new RuntimeException("token匹配失败");
|
||||||
@@ -198,8 +198,8 @@ public class PodTool extends PanBase {
|
|||||||
// 发送请求并处理响应
|
// 发送请求并处理响应
|
||||||
client.sendAsync(request, HttpResponse.BodyHandlers.ofString())
|
client.sendAsync(request, HttpResponse.BodyHandlers.ofString())
|
||||||
.thenApply(response -> {
|
.thenApply(response -> {
|
||||||
log.debug("Response Status Code: {}", response.statusCode());
|
System.out.println("Response Status Code: " + response.statusCode());
|
||||||
log.debug("Response Body: {}", response.body());
|
System.out.println("Response Body: " + response.body());
|
||||||
promise.complete(response.body());
|
promise.complete(response.body());
|
||||||
return null;
|
return null;
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -74,9 +74,9 @@ public class QQTool extends PanBase {
|
|||||||
});
|
});
|
||||||
|
|
||||||
// 调试匹配的情况
|
// 调试匹配的情况
|
||||||
log.debug("文件名称: {}", filename);
|
System.out.println("文件名称: " + filename);
|
||||||
log.debug("文件大小: {}", filesize);
|
System.out.println("文件大小: " + filesize);
|
||||||
log.debug("文件直链: {}", fileurl);
|
System.out.println("文件直链: " + fileurl);
|
||||||
|
|
||||||
// 提交
|
// 提交
|
||||||
promise.complete(fileurl.replace("\\x26", "&"));
|
promise.complete(fileurl.replace("\\x26", "&"));
|
||||||
|
|||||||
@@ -3,11 +3,9 @@ package cn.qaiu.parser.impl;
|
|||||||
import cn.qaiu.entity.FileInfo;
|
import cn.qaiu.entity.FileInfo;
|
||||||
import cn.qaiu.entity.ShareLinkInfo;
|
import cn.qaiu.entity.ShareLinkInfo;
|
||||||
import cn.qaiu.parser.PanBase;
|
import cn.qaiu.parser.PanBase;
|
||||||
import cn.qaiu.util.CommonUtils;
|
|
||||||
import cn.qaiu.util.HeaderUtils;
|
import cn.qaiu.util.HeaderUtils;
|
||||||
import io.vertx.core.Future;
|
import io.vertx.core.Future;
|
||||||
import io.vertx.core.MultiMap;
|
import io.vertx.core.MultiMap;
|
||||||
import io.vertx.core.Promise;
|
|
||||||
import io.vertx.core.json.JsonArray;
|
import io.vertx.core.json.JsonArray;
|
||||||
import io.vertx.core.json.JsonObject;
|
import io.vertx.core.json.JsonObject;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
@@ -15,33 +13,27 @@ import org.slf4j.LoggerFactory;
|
|||||||
|
|
||||||
import java.net.URLEncoder;
|
import java.net.URLEncoder;
|
||||||
import java.nio.charset.StandardCharsets;
|
import java.nio.charset.StandardCharsets;
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.regex.Matcher;
|
import java.util.regex.Matcher;
|
||||||
import java.util.regex.Pattern;
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* QQ闪传 <br>
|
* QQ闪传 <br>
|
||||||
* 支持多文件、多级目录解析。通过 GetFileList API 获取文件列表,BatchDownload API 获取下载直链。<br>
|
* 只能客户端上传 支持Android QQ 9.2.5, MACOS QQ 6.9.78,可生成分享链接,通过浏览器下载,支持超大文件,有效期默认7天(暂时没找到续期方法)。<br>
|
||||||
* 有效期默认7天。
|
|
||||||
*/
|
*/
|
||||||
public class QQscTool extends PanBase {
|
public class QQscTool extends PanBase {
|
||||||
|
|
||||||
Logger LOG = LoggerFactory.getLogger(QQscTool.class);
|
Logger LOG = LoggerFactory.getLogger(QQscTool.class);
|
||||||
|
|
||||||
private static final String BATCH_DOWNLOAD_API =
|
private static final String API_URL = "https://qfile.qq.com/http2rpc/gotrpc/noauth/trpc.qqntv2.richmedia.InnerProxy/BatchDownload";
|
||||||
"https://qfile.qq.com/http2rpc/gotrpc/noauth/trpc.qqntv2.richmedia.InnerProxy/BatchDownload";
|
|
||||||
|
|
||||||
private static final String GET_FILE_LIST_API =
|
private static final MultiMap HEADERS = HeaderUtils.parseHeaders("""
|
||||||
"https://qfile.qq.com/http2rpc/gotrpc/noauth/trpc.file.FileFlashTrans/GetFileList";
|
|
||||||
|
|
||||||
private static final MultiMap BATCH_DOWNLOAD_HEADERS = HeaderUtils.parseHeaders("""
|
|
||||||
Accept-Encoding: gzip, deflate
|
Accept-Encoding: gzip, deflate
|
||||||
Accept-Language: zh-CN,zh;q=0.9
|
Accept-Language: zh-CN,zh;q=0.9
|
||||||
Connection: keep-alive
|
Connection: keep-alive
|
||||||
Cookie: uin=9000002; p_uin=9000002
|
Cookie: uin=9000002; p_uin=9000002
|
||||||
DNT: 1
|
DNT: 1
|
||||||
Origin: https://qfile.qq.com
|
Origin: https://qfile.qq.com
|
||||||
|
Referer: https://qfile.qq.com/q/Xolxtv5b4O
|
||||||
Sec-Fetch-Dest: empty
|
Sec-Fetch-Dest: empty
|
||||||
Sec-Fetch-Mode: cors
|
Sec-Fetch-Mode: cors
|
||||||
Sec-Fetch-Site: same-origin
|
Sec-Fetch-Site: same-origin
|
||||||
@@ -54,255 +46,84 @@ public class QQscTool extends PanBase {
|
|||||||
x-oidb: {"uint32_command":"0x9248", "uint32_service_type":"4"}
|
x-oidb: {"uint32_command":"0x9248", "uint32_service_type":"4"}
|
||||||
""");
|
""");
|
||||||
|
|
||||||
private static final MultiMap GET_FILE_LIST_HEADERS = HeaderUtils.parseHeaders("""
|
|
||||||
Accept-Encoding: gzip, deflate
|
|
||||||
Cookie: uin=9000002; p_uin=9000002
|
|
||||||
Origin: https://qfile.qq.com
|
|
||||||
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/138.0.0.0 Safari/537.36 Edg/138.0.0.0
|
|
||||||
content-type: application/json
|
|
||||||
x-oidb: {"uint32_command":"0x93d4", "uint32_service_type":"1"}
|
|
||||||
""");
|
|
||||||
|
|
||||||
public QQscTool(ShareLinkInfo shareLinkInfo) {
|
public QQscTool(ShareLinkInfo shareLinkInfo) {
|
||||||
super(shareLinkInfo);
|
super(shareLinkInfo);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public Future<String> parse() {
|
public Future<String> parse() {
|
||||||
|
String jsonTemplate = """
|
||||||
|
{"req_head":{"agent":8},"download_info":[{"batch_id":"%s","scene":{"business_type":4,"app_type":22,"scene_type":5},"index_node":{"file_uuid":"%s"},"url_type":2,"download_scene":0}],"scene_type":103}
|
||||||
|
""";
|
||||||
|
|
||||||
client.getAbs(shareLinkInfo.getShareUrl()).send(result -> {
|
client.getAbs(shareLinkInfo.getShareUrl()).send(result -> {
|
||||||
if (result.failed()) {
|
if (result.succeeded()) {
|
||||||
LOG.error("请求失败: {}", result.cause().getMessage());
|
String htmlJs = result.result().bodyAsString();
|
||||||
promise.fail(result.cause());
|
LOG.debug("获取到的HTML内容: {}", htmlJs);
|
||||||
return;
|
String fileUUID = getFileUUID(htmlJs);
|
||||||
}
|
String fileName = extractFileNameFromTitle(htmlJs);
|
||||||
String html = result.result().bodyAsString();
|
|
||||||
String fileName = extractFileNameFromTitle(html);
|
|
||||||
if (fileName != null) {
|
if (fileName != null) {
|
||||||
|
LOG.info("提取到的文件名: {}", fileName);
|
||||||
FileInfo fileInfo = new FileInfo();
|
FileInfo fileInfo = new FileInfo();
|
||||||
fileInfo.setFileName(fileName);
|
fileInfo.setFileName(fileName);
|
||||||
shareLinkInfo.getOtherParam().put("fileInfo", fileInfo);
|
shareLinkInfo.getOtherParam().put("fileInfo", fileInfo);
|
||||||
}
|
|
||||||
// 尝试用 GetFileList API 获取第一个文件的下载链接
|
|
||||||
String filesetId = extractFilesetId(html);
|
|
||||||
if (filesetId != null) {
|
|
||||||
fetchFileList(filesetId, "").onSuccess(fileList -> {
|
|
||||||
for (int i = 0; i < fileList.size(); i++) {
|
|
||||||
JsonObject file = fileList.getJsonObject(i);
|
|
||||||
if (!file.getBoolean("is_dir", false)) {
|
|
||||||
String physicalId = file.getJsonObject("physical").getString("id");
|
|
||||||
String name = file.getString("name");
|
|
||||||
downloadFile(physicalId, name);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
promise.fail("未找到可下载的文件");
|
|
||||||
}).onFailure(e -> {
|
|
||||||
LOG.warn("GetFileList 失败,回退到旧解析方式: {}", e.getMessage());
|
|
||||||
parseLegacy(html, fileName);
|
|
||||||
});
|
|
||||||
} else {
|
} else {
|
||||||
parseLegacy(html, fileName);
|
LOG.warn("未能提取到文件名");
|
||||||
}
|
|
||||||
});
|
|
||||||
return promise.future();
|
|
||||||
}
|
}
|
||||||
|
if (fileUUID != null) {
|
||||||
|
LOG.info("提取到的文件UUID: {}", fileUUID);
|
||||||
|
String formatted = jsonTemplate.formatted(fileUUID, fileUUID);
|
||||||
|
JsonObject entries = new JsonObject(formatted);
|
||||||
|
client.postAbs(API_URL)
|
||||||
|
.putHeaders(HEADERS)
|
||||||
|
.sendJsonObject(entries)
|
||||||
|
.onSuccess(result2 -> {
|
||||||
|
if (result2.statusCode() == 200) {
|
||||||
|
JsonObject body = asJson(result2);
|
||||||
|
LOG.debug("API响应内容: {}", body.encodePrettily());
|
||||||
|
// {
|
||||||
|
// "retcode": 0,
|
||||||
|
// "cost": 132,
|
||||||
|
// "message": "",
|
||||||
|
// "error": {
|
||||||
|
// "message": "",
|
||||||
|
// "code": 0
|
||||||
|
// },
|
||||||
|
// "data": {
|
||||||
|
// "download_rsp": [{
|
||||||
|
|
||||||
@Override
|
// 取 download_rsp
|
||||||
public Future<List<FileInfo>> parseFileList() {
|
if (!body.containsKey("retcode") || body.getInteger("retcode") != 0) {
|
||||||
Promise<List<FileInfo>> resultPromise = Promise.promise();
|
promise.fail("API请求失败,错误信息: " + body.encodePrettily());
|
||||||
String dirId = (String) shareLinkInfo.getOtherParam().get("dirId");
|
|
||||||
|
|
||||||
client.getAbs(shareLinkInfo.getShareUrl()).send(result -> {
|
|
||||||
if (result.failed()) {
|
|
||||||
resultPromise.fail(result.cause());
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
String html = result.result().bodyAsString();
|
|
||||||
String filesetId = extractFilesetId(html);
|
|
||||||
if (filesetId == null) {
|
|
||||||
resultPromise.fail("无法从页面提取 filesetId");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
String parentId = dirId != null ? dirId : "";
|
|
||||||
fetchFileList(filesetId, parentId).onSuccess(fileList -> {
|
|
||||||
try {
|
|
||||||
List<FileInfo> list = new ArrayList<>();
|
|
||||||
String panType = shareLinkInfo.getType();
|
|
||||||
for (int i = 0; i < fileList.size(); i++) {
|
|
||||||
JsonObject file = fileList.getJsonObject(i);
|
|
||||||
FileInfo fileInfo = new FileInfo();
|
|
||||||
String name = file.getString("name");
|
|
||||||
String cliFileid = file.getString("cli_fileid");
|
|
||||||
boolean isDir = file.getBoolean("is_dir", false);
|
|
||||||
String sizeStr = file.getString("file_size");
|
|
||||||
|
|
||||||
fileInfo.setFileName(name)
|
|
||||||
.setFileId(cliFileid)
|
|
||||||
.setPanType(panType)
|
|
||||||
.setSizeStr(sizeStr);
|
|
||||||
|
|
||||||
if (isDir) {
|
|
||||||
fileInfo.setFileType("folder")
|
|
||||||
.setParserUrl(String.format("%s/v2/getFileList?url=%s&dirId=%s",
|
|
||||||
getDomainName(),
|
|
||||||
URLEncoder.encode(shareLinkInfo.getShareUrl(), StandardCharsets.UTF_8),
|
|
||||||
cliFileid));
|
|
||||||
} else {
|
|
||||||
String physicalId = file.getJsonObject("physical").getString("id");
|
|
||||||
JsonObject paramJson = new JsonObject()
|
|
||||||
.put("fileId", physicalId)
|
|
||||||
.put("fileName", name)
|
|
||||||
.put("cliFileid", cliFileid);
|
|
||||||
String param = CommonUtils.urlBase64Encode(paramJson.encode());
|
|
||||||
fileInfo.setFileType("file")
|
|
||||||
.setParserUrl(String.format("%s/v2/redirectUrl/%s/%s",
|
|
||||||
getDomainName(), panType, param));
|
|
||||||
}
|
|
||||||
list.add(fileInfo);
|
|
||||||
}
|
|
||||||
resultPromise.complete(list);
|
|
||||||
} catch (Exception e) {
|
|
||||||
resultPromise.fail(e);
|
|
||||||
}
|
|
||||||
}).onFailure(resultPromise::fail);
|
|
||||||
});
|
|
||||||
return resultPromise.future();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Future<String> parseById() {
|
|
||||||
JsonObject paramJson = (JsonObject) shareLinkInfo.getOtherParam().get("paramJson");
|
|
||||||
String fileId = paramJson.getString("fileId");
|
|
||||||
String fileName = paramJson.getString("fileName");
|
|
||||||
|
|
||||||
Promise<String> p = Promise.promise();
|
|
||||||
callBatchDownload(fileId, fileName, p);
|
|
||||||
return p.future();
|
|
||||||
}
|
|
||||||
|
|
||||||
// ========== 内部方法 ==========
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 调用 BatchDownload API 获取单个文件的下载直链
|
|
||||||
*/
|
|
||||||
private void downloadFile(String physicalId, String fileName) {
|
|
||||||
callBatchDownload(physicalId, fileName, promise);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void callBatchDownload(String physicalId, String fileName, Promise<String> p) {
|
|
||||||
String body = """
|
|
||||||
{"req_head":{"agent":8},"download_info":[{"batch_id":"%s","scene":{"business_type":4,"app_type":22,"scene_type":5},"index_node":{"file_uuid":"%s"},"url_type":2,"download_scene":0}],"scene_type":103}
|
|
||||||
""".formatted(physicalId, physicalId);
|
|
||||||
|
|
||||||
client.postAbs(BATCH_DOWNLOAD_API)
|
|
||||||
.putHeaders(BATCH_DOWNLOAD_HEADERS)
|
|
||||||
.sendJsonObject(new JsonObject(body))
|
|
||||||
.onSuccess(resp -> {
|
|
||||||
if (resp.statusCode() != 200) {
|
|
||||||
p.fail("BatchDownload 请求失败,状态码: " + resp.statusCode());
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
JsonObject respBody = asJson(resp);
|
|
||||||
if (!respBody.containsKey("retcode") || respBody.getInteger("retcode") != 0) {
|
|
||||||
p.fail("BatchDownload 请求失败: " + respBody.encodePrettily());
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
JsonArray downloadRsp = respBody.getJsonObject("data").getJsonArray("download_rsp");
|
|
||||||
if (downloadRsp == null || downloadRsp.isEmpty()) {
|
|
||||||
p.fail("BatchDownload 响应中缺少 download_rsp");
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
JsonArray downloadRsp = body.getJsonObject("data").getJsonArray("download_rsp");
|
||||||
|
if (downloadRsp != null && !downloadRsp.isEmpty()) {
|
||||||
String url = downloadRsp.getJsonObject(0).getString("url");
|
String url = downloadRsp.getJsonObject(0).getString("url");
|
||||||
if (url != null && url.startsWith("&filename=")) {
|
|
||||||
p.fail("该文件已被和谐");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (fileName != null) {
|
if (fileName != null) {
|
||||||
url = url + "&filename=" + URLEncoder.encode(fileName, StandardCharsets.UTF_8);
|
url = url + "&filename=" + URLEncoder.encode(fileName, StandardCharsets.UTF_8);
|
||||||
}
|
}
|
||||||
p.complete(url);
|
promise.complete(url);
|
||||||
})
|
} else {
|
||||||
.onFailure(e -> {
|
promise.fail("API响应中缺少 download_rsp");
|
||||||
LOG.error("BatchDownload 请求异常", e);
|
}
|
||||||
p.fail(e);
|
} else {
|
||||||
|
promise.fail("API请求失败,状态码: " + result2.statusCode());
|
||||||
|
}
|
||||||
|
}).onFailure(e -> {
|
||||||
|
LOG.error("API请求异常", e);
|
||||||
|
promise.fail(e);
|
||||||
});
|
});
|
||||||
}
|
} else {
|
||||||
|
LOG.error("未能提取到文件UUID");
|
||||||
/**
|
|
||||||
* 调用 GetFileList API 获取指定目录下的文件列表
|
|
||||||
*/
|
|
||||||
private Future<JsonArray> fetchFileList(String filesetId, String parentId) {
|
|
||||||
Promise<JsonArray> p = Promise.promise();
|
|
||||||
JsonObject body = new JsonObject()
|
|
||||||
.put("fileset_id", filesetId)
|
|
||||||
.put("req_infos", new JsonArray()
|
|
||||||
.add(new JsonObject()
|
|
||||||
.put("parent_id", parentId)
|
|
||||||
.put("req_depth", 1)
|
|
||||||
.put("count", 50)
|
|
||||||
.put("filter_condition", new JsonObject().put("file_category", 0))
|
|
||||||
.put("sort_conditions", new JsonArray()
|
|
||||||
.add(new JsonObject()
|
|
||||||
.put("sort_field", 0)
|
|
||||||
.put("sort_order", 0)))))
|
|
||||||
.put("support_folder_status", true);
|
|
||||||
|
|
||||||
MultiMap headers = GET_FILE_LIST_HEADERS.set("Referer", shareLinkInfo.getShareUrl());
|
|
||||||
|
|
||||||
client.postAbs(GET_FILE_LIST_API)
|
|
||||||
.putHeaders(headers)
|
|
||||||
.sendJsonObject(body)
|
|
||||||
.onSuccess(resp -> {
|
|
||||||
if (resp.statusCode() != 200) {
|
|
||||||
p.fail("GetFileList 请求失败,状态码: " + resp.statusCode());
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
JsonObject respBody = asJson(resp);
|
|
||||||
if (respBody.getInteger("retcode", -1) != 0) {
|
|
||||||
p.fail("GetFileList 请求失败: " + respBody.getString("message", "未知错误"));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
JsonArray fileLists = respBody.getJsonObject("data").getJsonArray("file_lists");
|
|
||||||
if (fileLists == null || fileLists.isEmpty()) {
|
|
||||||
p.fail("GetFileList 响应中缺少 file_lists");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
JsonArray fileList = fileLists.getJsonObject(0).getJsonArray("file_list");
|
|
||||||
p.complete(fileList != null ? fileList : new JsonArray());
|
|
||||||
})
|
|
||||||
.onFailure(e -> {
|
|
||||||
LOG.error("GetFileList 请求异常", e);
|
|
||||||
p.fail(e);
|
|
||||||
});
|
|
||||||
return p.future();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 从 HTML 的 __NUXT_DATA__ 中提取 fileset_id
|
|
||||||
*/
|
|
||||||
String extractFilesetId(String html) {
|
|
||||||
// Nuxt __NUXT_DATA__ 中 fileset_id 出现在缓存 key 的嵌套 JSON 中
|
|
||||||
// 直接匹配 fileset_id 后面最近的 UUID(跳过转义引号、冒号等非hex字符)
|
|
||||||
Pattern pattern = Pattern.compile(
|
|
||||||
"fileset_id[^a-f0-9]*([a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12})");
|
|
||||||
Matcher matcher = pattern.matcher(html);
|
|
||||||
if (matcher.find()) {
|
|
||||||
return matcher.group(1);
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 旧版解析方式(兼容单文件链接,通过 HTML 字符串搜索提取 UUID)
|
|
||||||
*/
|
|
||||||
private void parseLegacy(String html, String fileName) {
|
|
||||||
String fileUUID = getFileUUID(html);
|
|
||||||
if (fileUUID == null) {
|
|
||||||
promise.fail("未能提取到文件UUID");
|
promise.fail("未能提取到文件UUID");
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
LOG.info("使用旧版解析,提取到的文件UUID: {}", fileUUID);
|
} else {
|
||||||
downloadFile(fileUUID, fileName);
|
LOG.error("请求失败: {}", result.cause().getMessage());
|
||||||
|
promise.fail(result.cause());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return promise.future();
|
||||||
}
|
}
|
||||||
|
|
||||||
String getFileUUID(String htmlJs) {
|
String getFileUUID(String htmlJs) {
|
||||||
@@ -319,23 +140,32 @@ public class QQscTool extends PanBase {
|
|||||||
String extracted = htmlJs.substring(quoteStart, quoteEnd);
|
String extracted = htmlJs.substring(quoteStart, quoteEnd);
|
||||||
LOG.debug("提取结果: {}", extracted);
|
LOG.debug("提取结果: {}", extracted);
|
||||||
return extracted;
|
return extracted;
|
||||||
|
} else {
|
||||||
|
LOG.error("未找到结束引号: {}", marker);
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
LOG.error("未找到标记: {} 在关键字: {} 之后", marker, keyword);
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
LOG.error("未找到关键字: {}", keyword);
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static String extractFileNameFromTitle(String content) {
|
public static String extractFileNameFromTitle(String content) {
|
||||||
|
// 匹配<title>和</title>之间的内容
|
||||||
Pattern pattern = Pattern.compile("<title>(.*?)</title>");
|
Pattern pattern = Pattern.compile("<title>(.*?)</title>");
|
||||||
Matcher matcher = pattern.matcher(content);
|
Matcher matcher = pattern.matcher(content);
|
||||||
if (matcher.find()) {
|
if (matcher.find()) {
|
||||||
String fullTitle = matcher.group(1);
|
String fullTitle = matcher.group(1);
|
||||||
|
// 按 "|" 分割,取前半部分
|
||||||
int sepIndex = fullTitle.indexOf("|");
|
int sepIndex = fullTitle.indexOf("|");
|
||||||
if (sepIndex != -1) {
|
if (sepIndex != -1) {
|
||||||
return fullTitle.substring(0, sepIndex);
|
return fullTitle.substring(0, sepIndex);
|
||||||
}
|
}
|
||||||
return fullTitle;
|
return fullTitle; // 如果没有分隔符,就返回全部
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -3,11 +3,10 @@ package cn.qaiu.parser.impl;
|
|||||||
import cn.qaiu.entity.FileInfo;
|
import cn.qaiu.entity.FileInfo;
|
||||||
import cn.qaiu.entity.ShareLinkInfo;
|
import cn.qaiu.entity.ShareLinkInfo;
|
||||||
import io.vertx.core.Future;
|
import io.vertx.core.Future;
|
||||||
import io.vertx.core.buffer.Buffer;
|
import java.util.HashMap;
|
||||||
import io.vertx.core.json.JsonObject;
|
import java.util.Map;
|
||||||
|
import java.util.regex.Matcher;
|
||||||
import java.net.URLEncoder;
|
import java.util.regex.Pattern;
|
||||||
import java.nio.charset.StandardCharsets;
|
|
||||||
|
|
||||||
public class QQwTool extends QQTool {
|
public class QQwTool extends QQTool {
|
||||||
|
|
||||||
@@ -17,51 +16,54 @@ public class QQwTool extends QQTool {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Future<String> parse() {
|
public Future<String> parse() {
|
||||||
String k = shareLinkInfo.getShareKey();
|
client.getAbs(shareLinkInfo.getShareUrl()).send().onSuccess(res -> {
|
||||||
String postBody = "f=json&k=" + URLEncoder.encode(k, StandardCharsets.UTF_8);
|
String html = res.bodyAsString();
|
||||||
|
Map<String, String> stringStringMap = extractVariables(html);
|
||||||
client.postAbs("https://wx.mail.qq.com/s")
|
String url = stringStringMap.get("url");
|
||||||
.putHeader("Content-Type", "application/x-www-form-urlencoded")
|
String fn = stringStringMap.get("filename");
|
||||||
.putHeader("Accept", "application/json, text/plain, */*")
|
String size = stringStringMap.get("filesize");
|
||||||
.putHeader("Referer", shareLinkInfo.getShareUrl())
|
String createBy = stringStringMap.get("nick");
|
||||||
.putHeader("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 "
|
FileInfo fileInfo = new FileInfo().setFileName(fn).setSize(Long.parseLong(size)).setCreateBy(createBy);
|
||||||
+ "(KHTML, like Gecko) Chrome/134.0.0.0 Safari/537.36 Edg/134.0.0.0")
|
|
||||||
.sendBuffer(Buffer.buffer(postBody))
|
|
||||||
.onSuccess(res -> {
|
|
||||||
try {
|
|
||||||
JsonObject data = asJson(res);
|
|
||||||
JsonObject head = data.getJsonObject("head");
|
|
||||||
if (head == null || head.getInteger("ret", -1) != 0) {
|
|
||||||
String msg = head != null ? head.getString("msg", "未知错误") : "未知错误";
|
|
||||||
fail("API错误: " + msg);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
JsonObject body = data.getJsonObject("body");
|
|
||||||
if (body == null) {
|
|
||||||
fail("文件信息为空");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
String url = body.getString("url");
|
|
||||||
String fn = body.getString("name", "");
|
|
||||||
long size = body.getLong("size", 0L);
|
|
||||||
|
|
||||||
if (url == null || url.isEmpty()) {
|
|
||||||
fail("分享链接解析失败, 可能是链接失效");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
FileInfo fileInfo = new FileInfo().setFileName(fn).setSize(size);
|
|
||||||
shareLinkInfo.getOtherParam().put("fileInfo", fileInfo);
|
shareLinkInfo.getOtherParam().put("fileInfo", fileInfo);
|
||||||
|
if (url != null) {
|
||||||
String url302 = url.replace("\\x26", "&");
|
String url302 = url.replace("\\x26", "&");
|
||||||
complete(url302);
|
promise.complete(url302);
|
||||||
} catch (Exception e) {
|
|
||||||
fail(e, "解析响应失败");
|
/*
|
||||||
|
clientNoRedirects.getAbs(url302).send().onSuccess(res2 -> {
|
||||||
|
MultiMap headers = res2.headers();
|
||||||
|
if (headers.contains("Location")) {
|
||||||
|
promise.complete(headers.get("Location"));
|
||||||
|
} else {
|
||||||
|
fail("找不到重定向URL");
|
||||||
|
}
|
||||||
|
|
||||||
|
}).onFailure(handleFail());
|
||||||
|
*/
|
||||||
|
} else {
|
||||||
|
fail("分享链接解析失败, 可能是链接失效");
|
||||||
}
|
}
|
||||||
}).onFailure(handleFail());
|
}).onFailure(handleFail());
|
||||||
|
|
||||||
return promise.future();
|
return promise.future();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private Map<String, String> extractVariables(String jsCode) {
|
||||||
|
Map<String, String> variables = new HashMap<>();
|
||||||
|
// 正则表达式匹配 var 变量定义
|
||||||
|
String regex = "\\s+var\\s+(\\w+)\\s*=\\s*(?:\"([^\"]*)\"|'([^']*)'|([^;\\r\\n]*))";
|
||||||
|
Pattern p = Pattern.compile(regex);
|
||||||
|
Matcher m = p.matcher(jsCode);
|
||||||
|
|
||||||
|
while (m.find()) {
|
||||||
|
String name = m.group(1);
|
||||||
|
String value = m.group(2) != null ? m.group(2)
|
||||||
|
: m.group(3) != null ? m.group(3)
|
||||||
|
: m.group(4);
|
||||||
|
variables.put(name, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
return variables;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -67,7 +67,11 @@ public class WsTool extends PanBase {
|
|||||||
String filepid = asJson(res2).getJsonObject("data").getString("ufileid"); // 文件夹pid
|
String filepid = asJson(res2).getJsonObject("data").getString("ufileid"); // 文件夹pid
|
||||||
String filebid = asJson(res2).getJsonObject("data").getString("boxid"); // 文件夹bid
|
String filebid = asJson(res2).getJsonObject("data").getString("boxid"); // 文件夹bid
|
||||||
|
|
||||||
log.debug("文件夹期限: {}, 大小: {}, pid: {}, bid: {}", filetime, filesize, filepid, filebid);
|
// 调试输出文件夹信息
|
||||||
|
System.out.println("文件夹期限: " + filetime);
|
||||||
|
System.out.println("文件夹大小: " + filesize);
|
||||||
|
System.out.println("文件夹pid: " + filepid);
|
||||||
|
System.out.println("文件夹bid: " + filebid);
|
||||||
|
|
||||||
// 获取文件信息
|
// 获取文件信息
|
||||||
httpClient.postAbs(SHARE_URL_API + "ufile/list").putHeaders(headers)
|
httpClient.postAbs(SHARE_URL_API + "ufile/list").putHeaders(headers)
|
||||||
@@ -93,7 +97,9 @@ public class WsTool extends PanBase {
|
|||||||
String filefid = asJson(res3).getJsonObject("data")
|
String filefid = asJson(res3).getJsonObject("data")
|
||||||
.getJsonArray("fileList").getJsonObject(0).getString("fid"); // 文件fid
|
.getJsonArray("fileList").getJsonObject(0).getString("fid"); // 文件fid
|
||||||
|
|
||||||
log.debug("文件名称: {}, fid: {}", filename, filefid);
|
// 调试输出文件信息
|
||||||
|
System.out.println("文件名称: " + filename);
|
||||||
|
System.out.println("文件fid: " + filefid);
|
||||||
|
|
||||||
// 检查文件是否失效
|
// 检查文件是否失效
|
||||||
httpClient.postAbs(SHARE_URL_API + "dl/sign").putHeaders(headers)
|
httpClient.postAbs(SHARE_URL_API + "dl/sign").putHeaders(headers)
|
||||||
@@ -108,7 +114,8 @@ public class WsTool extends PanBase {
|
|||||||
// 获取直链
|
// 获取直链
|
||||||
String fileurl = asJson(res4).getJsonObject("data").getString("url");
|
String fileurl = asJson(res4).getJsonObject("data").getString("url");
|
||||||
|
|
||||||
log.debug("文件直链: {}", fileurl);
|
// 调试输出文件直链
|
||||||
|
System.out.println("文件直链: " + fileurl);
|
||||||
|
|
||||||
if (!fileurl.equals("")) {
|
if (!fileurl.equals("")) {
|
||||||
promise.complete(URLDecoder.decode(fileurl, StandardCharsets.UTF_8));
|
promise.complete(URLDecoder.decode(fileurl, StandardCharsets.UTF_8));
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ import java.security.spec.X509EncodedKeySpec;
|
|||||||
import java.util.Base64;
|
import java.util.Base64;
|
||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
import java.util.HexFormat;
|
import java.util.HexFormat;
|
||||||
|
import java.util.Random;
|
||||||
import java.util.regex.Matcher;
|
import java.util.regex.Matcher;
|
||||||
import java.util.regex.Pattern;
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
@@ -298,7 +299,7 @@ public class AESUtils {
|
|||||||
//length用户要求产生字符串的长度
|
//length用户要求产生字符串的长度
|
||||||
public static String getRandomString(int length){
|
public static String getRandomString(int length){
|
||||||
String str="abcdefghijklmnopqrstuvwxyz0123456789";
|
String str="abcdefghijklmnopqrstuvwxyz0123456789";
|
||||||
SecureRandom random=new SecureRandom();
|
Random random=new Random();
|
||||||
StringBuilder sb=new StringBuilder();
|
StringBuilder sb=new StringBuilder();
|
||||||
for(int i=0;i<length;i++){
|
for(int i=0;i<length;i++){
|
||||||
int number=random.nextInt(36);
|
int number=random.nextInt(36);
|
||||||
|
|||||||
@@ -33,9 +33,6 @@ public class CommonUtils {
|
|||||||
public static Map<String, String> getURLParams(String url) throws MalformedURLException {
|
public static Map<String, String> getURLParams(String url) throws MalformedURLException {
|
||||||
URL fullUrl = new URL(url);
|
URL fullUrl = new URL(url);
|
||||||
String query = fullUrl.getQuery();
|
String query = fullUrl.getQuery();
|
||||||
if (query == null || query.isEmpty()) {
|
|
||||||
return new HashMap<>();
|
|
||||||
}
|
|
||||||
String[] params = query.split("&");
|
String[] params = query.split("&");
|
||||||
Map<String, String> map = new HashMap<>();
|
Map<String, String> map = new HashMap<>();
|
||||||
for (String param : params) {
|
for (String param : params) {
|
||||||
|
|||||||
@@ -5,8 +5,6 @@ import io.vertx.core.Vertx;
|
|||||||
import io.vertx.core.http.impl.headers.HeadersMultiMap;
|
import io.vertx.core.http.impl.headers.HeadersMultiMap;
|
||||||
import io.vertx.ext.web.client.WebClient;
|
import io.vertx.ext.web.client.WebClient;
|
||||||
import io.vertx.ext.web.client.WebClientSession;
|
import io.vertx.ext.web.client.WebClientSession;
|
||||||
import org.slf4j.Logger;
|
|
||||||
import org.slf4j.LoggerFactory;
|
|
||||||
|
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
@@ -17,8 +15,6 @@ import java.util.ArrayList;
|
|||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
public class IpExtractor {
|
public class IpExtractor {
|
||||||
private static final Logger log = LoggerFactory.getLogger(IpExtractor.class);
|
|
||||||
|
|
||||||
public static void main(String[] args) throws InterruptedException {
|
public static void main(String[] args) throws InterruptedException {
|
||||||
|
|
||||||
|
|
||||||
@@ -46,9 +42,9 @@ public class IpExtractor {
|
|||||||
WebClient client = WebClient.create(Vertx.vertx());
|
WebClient client = WebClient.create(Vertx.vertx());
|
||||||
WebClientSession webClientSession = WebClientSession.create(client);
|
WebClientSession webClientSession = WebClientSession.create(client);
|
||||||
webClientSession.getAbs("https://ip.ihuan.me").putHeaders(headers).send().onSuccess(res->{
|
webClientSession.getAbs("https://ip.ihuan.me").putHeaders(headers).send().onSuccess(res->{
|
||||||
log.debug("response: {}", res.toString());
|
System.out.println(res.toString());
|
||||||
webClientSession.getAbs("https://ip.ihuan.me").putHeaders(headers).send().onSuccess(res2->{
|
webClientSession.getAbs("https://ip.ihuan.me").putHeaders(headers).send().onSuccess(res2->{
|
||||||
log.debug("response2: {}", res2.toString());
|
System.out.println(res2.toString());
|
||||||
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -21,11 +21,11 @@ import static cn.qaiu.util.AESUtils.encrypt;
|
|||||||
*/
|
*/
|
||||||
public class JsExecUtils {
|
public class JsExecUtils {
|
||||||
private static final Invocable inv;
|
private static final Invocable inv;
|
||||||
private static final ScriptEngineManager ENGINE_MANAGER = new ScriptEngineManager();
|
|
||||||
|
|
||||||
// 初始化脚本引擎
|
// 初始化脚本引擎
|
||||||
static {
|
static {
|
||||||
ScriptEngine engine = ENGINE_MANAGER.getEngineByName("JavaScript"); // 得到脚本引擎
|
ScriptEngineManager engineManager = new ScriptEngineManager();
|
||||||
|
ScriptEngine engine = engineManager.getEngineByName("JavaScript"); // 得到脚本引擎
|
||||||
|
|
||||||
try {
|
try {
|
||||||
engine.eval(JsContent.ye123);
|
engine.eval(JsContent.ye123);
|
||||||
@@ -45,11 +45,12 @@ public class JsExecUtils {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 调用执行蓝奏云js文件(每次动态JS代码,无法复用引擎)
|
* 调用执行蓝奏云js文件
|
||||||
*/
|
*/
|
||||||
public static ScriptObjectMirror executeDynamicJs(String jsText, String funName) throws ScriptException,
|
public static ScriptObjectMirror executeDynamicJs(String jsText, String funName) throws ScriptException,
|
||||||
NoSuchMethodException {
|
NoSuchMethodException {
|
||||||
ScriptEngine engine = ENGINE_MANAGER.getEngineByName("JavaScript"); // 得到脚本引擎
|
ScriptEngineManager engineManager = new ScriptEngineManager();
|
||||||
|
ScriptEngine engine = engineManager.getEngineByName("JavaScript"); // 得到脚本引擎
|
||||||
engine.eval(JsContent.lz + "\n" + jsText);
|
engine.eval(JsContent.lz + "\n" + jsText);
|
||||||
Invocable inv = (Invocable) engine;
|
Invocable inv = (Invocable) engine;
|
||||||
//调用js中的函数
|
//调用js中的函数
|
||||||
@@ -62,11 +63,12 @@ public class JsExecUtils {
|
|||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 调用执行js文件(使用缓存的 ScriptEngineManager 创建新引擎实例)
|
* 调用执行蓝奏云js文件
|
||||||
*/
|
*/
|
||||||
public static Object executeOtherJs(String jsText, String funName, Object ... args) throws ScriptException,
|
public static Object executeOtherJs(String jsText, String funName, Object ... args) throws ScriptException,
|
||||||
NoSuchMethodException {
|
NoSuchMethodException {
|
||||||
ScriptEngine engine = ENGINE_MANAGER.getEngineByName("JavaScript"); // 得到脚本引擎
|
ScriptEngineManager engineManager = new ScriptEngineManager();
|
||||||
|
ScriptEngine engine = engineManager.getEngineByName("JavaScript"); // 得到脚本引擎
|
||||||
engine.eval(jsText);
|
engine.eval(jsText);
|
||||||
Invocable inv = (Invocable) engine;
|
Invocable inv = (Invocable) engine;
|
||||||
//调用js中的函数
|
//调用js中的函数
|
||||||
|
|||||||
@@ -8,19 +8,15 @@ import io.vertx.core.http.impl.headers.HeadersMultiMap;
|
|||||||
import io.vertx.ext.web.client.HttpResponse;
|
import io.vertx.ext.web.client.HttpResponse;
|
||||||
import io.vertx.ext.web.client.WebClient;
|
import io.vertx.ext.web.client.WebClient;
|
||||||
import io.vertx.ext.web.client.WebClientSession;
|
import io.vertx.ext.web.client.WebClientSession;
|
||||||
import org.slf4j.Logger;
|
|
||||||
import org.slf4j.LoggerFactory;
|
|
||||||
|
|
||||||
public class ReqIpUtil {
|
public class ReqIpUtil {
|
||||||
private static final Logger log = LoggerFactory.getLogger(ReqIpUtil.class);
|
public static String BASE_URL = "https://ip.ihuan.me";
|
||||||
|
public static String BASE_URL_TEMPLATE = BASE_URL + "/{path}";
|
||||||
public static final String BASE_URL = "https://ip.ihuan.me";
|
|
||||||
public static final String BASE_URL_TEMPLATE = BASE_URL + "/{path}";
|
|
||||||
|
|
||||||
// GET https://ip.ihuan.me/mouse.do -> $("input[name='key']").val("30b4975b5547fed806bd2b9caa18485a");
|
// GET https://ip.ihuan.me/mouse.do -> $("input[name='key']").val("30b4975b5547fed806bd2b9caa18485a");
|
||||||
public static final String PATH1 = "mouse.do";
|
public static String PATH1 = "mouse.do";
|
||||||
|
|
||||||
public static final String PATH2 = "tqdl.html";
|
public static String PATH2 = "tqdl.html";
|
||||||
|
|
||||||
// 创建请求头Map
|
// 创建请求头Map
|
||||||
static MultiMap headers = new HeadersMultiMap();
|
static MultiMap headers = new HeadersMultiMap();
|
||||||
@@ -62,15 +58,15 @@ public class ReqIpUtil {
|
|||||||
|
|
||||||
void next(AsyncResult<HttpResponse<Buffer>> response) {
|
void next(AsyncResult<HttpResponse<Buffer>> response) {
|
||||||
if (response.failed()) {
|
if (response.failed()) {
|
||||||
log.error("请求失败", response.cause());
|
response.cause().printStackTrace();
|
||||||
} else {
|
} else {
|
||||||
HttpResponse<Buffer> res = response.result();
|
HttpResponse<Buffer> res = response.result();
|
||||||
log.debug("Received response with status code {}", res.statusCode());
|
System.out.println("Received response with status code " + res.statusCode());
|
||||||
log.debug("Body: {}", res.body());
|
System.out.println("Body: " + res.body());
|
||||||
webClientSession.getAbs(BASE_URL_TEMPLATE).setTemplateParam("path", PATH1)
|
webClientSession.getAbs(BASE_URL_TEMPLATE).setTemplateParam("path", PATH1)
|
||||||
.putHeaders(headers) // 将请求头Map添加到请求中
|
.putHeaders(headers) // 将请求头Map添加到请求中
|
||||||
.send(response2 -> {
|
.send(response2 -> {
|
||||||
log.debug("response2: {}", response2.result().bodyAsString());
|
System.out.println(response2.result().bodyAsString());
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -2,9 +2,6 @@ package cn.qaiu.util;
|
|||||||
|
|
||||||
import org.apache.commons.lang3.StringUtils;
|
import org.apache.commons.lang3.StringUtils;
|
||||||
|
|
||||||
import org.slf4j.Logger;
|
|
||||||
import org.slf4j.LoggerFactory;
|
|
||||||
|
|
||||||
import java.net.URL;
|
import java.net.URL;
|
||||||
import java.net.URLDecoder;
|
import java.net.URLDecoder;
|
||||||
import java.nio.charset.StandardCharsets;
|
import java.nio.charset.StandardCharsets;
|
||||||
@@ -13,8 +10,6 @@ import java.util.Map;
|
|||||||
|
|
||||||
public class URLUtil {
|
public class URLUtil {
|
||||||
|
|
||||||
private static final Logger LOGGER = LoggerFactory.getLogger(URLUtil.class);
|
|
||||||
|
|
||||||
private final Map<String, String> queryParams = new HashMap<>();
|
private final Map<String, String> queryParams = new HashMap<>();
|
||||||
|
|
||||||
// 构造函数,传入URL并解析参数
|
// 构造函数,传入URL并解析参数
|
||||||
@@ -36,7 +31,7 @@ public class URLUtil {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
LOGGER.error("URL解析失败: {}", url, e);
|
e.printStackTrace();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -8,8 +8,6 @@ import cn.qaiu.parser.customjs.JsParserExecutor;
|
|||||||
import cn.qaiu.WebClientVertxInit;
|
import cn.qaiu.WebClientVertxInit;
|
||||||
import io.vertx.core.Vertx;
|
import io.vertx.core.Vertx;
|
||||||
import io.vertx.core.json.JsonObject;
|
import io.vertx.core.json.JsonObject;
|
||||||
import org.junit.After;
|
|
||||||
import org.junit.Before;
|
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
|
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
@@ -24,26 +22,15 @@ import java.util.Map;
|
|||||||
*/
|
*/
|
||||||
public class BaiduPhotoParserTest {
|
public class BaiduPhotoParserTest {
|
||||||
|
|
||||||
private Vertx vertx;
|
|
||||||
|
|
||||||
@Before
|
|
||||||
public void setUp() {
|
|
||||||
vertx = Vertx.vertx();
|
|
||||||
WebClientVertxInit.init(vertx);
|
|
||||||
}
|
|
||||||
|
|
||||||
@After
|
|
||||||
public void tearDown() {
|
|
||||||
if (vertx != null) {
|
|
||||||
vertx.close();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testBaiduPhotoParserRegistration() {
|
public void testBaiduPhotoParserRegistration() {
|
||||||
// 清理注册表
|
// 清理注册表
|
||||||
CustomParserRegistry.clear();
|
CustomParserRegistry.clear();
|
||||||
|
|
||||||
|
// 初始化Vertx
|
||||||
|
Vertx vertx = Vertx.vertx();
|
||||||
|
WebClientVertxInit.init(vertx);
|
||||||
|
|
||||||
// 检查是否加载了百度相册解析器
|
// 检查是否加载了百度相册解析器
|
||||||
CustomParserConfig config = CustomParserRegistry.get("baidu_photo");
|
CustomParserConfig config = CustomParserRegistry.get("baidu_photo");
|
||||||
assert config != null : "百度相册解析器未加载";
|
assert config != null : "百度相册解析器未加载";
|
||||||
@@ -58,6 +45,10 @@ public class BaiduPhotoParserTest {
|
|||||||
// 清理注册表
|
// 清理注册表
|
||||||
CustomParserRegistry.clear();
|
CustomParserRegistry.clear();
|
||||||
|
|
||||||
|
// 初始化Vertx
|
||||||
|
Vertx vertx = Vertx.vertx();
|
||||||
|
WebClientVertxInit.init(vertx);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// 创建解析器 - 测试文件分享链接
|
// 创建解析器 - 测试文件分享链接
|
||||||
IPanTool tool = ParserCreate.fromType("baidu_photo")
|
IPanTool tool = ParserCreate.fromType("baidu_photo")
|
||||||
@@ -86,6 +77,10 @@ public class BaiduPhotoParserTest {
|
|||||||
// 清理注册表
|
// 清理注册表
|
||||||
CustomParserRegistry.clear();
|
CustomParserRegistry.clear();
|
||||||
|
|
||||||
|
// 初始化Vertx
|
||||||
|
Vertx vertx = Vertx.vertx();
|
||||||
|
WebClientVertxInit.init(vertx);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// 创建解析器 - 测试文件夹分享链接
|
// 创建解析器 - 测试文件夹分享链接
|
||||||
IPanTool tool = ParserCreate.fromType("baidu_photo")
|
IPanTool tool = ParserCreate.fromType("baidu_photo")
|
||||||
@@ -114,6 +109,10 @@ public class BaiduPhotoParserTest {
|
|||||||
// 清理注册表
|
// 清理注册表
|
||||||
CustomParserRegistry.clear();
|
CustomParserRegistry.clear();
|
||||||
|
|
||||||
|
// 初始化Vertx
|
||||||
|
Vertx vertx = Vertx.vertx();
|
||||||
|
WebClientVertxInit.init(vertx);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
IPanTool tool = ParserCreate.fromType("baidu_photo")
|
IPanTool tool = ParserCreate.fromType("baidu_photo")
|
||||||
// 分享key PPgOEodBVE
|
// 分享key PPgOEodBVE
|
||||||
@@ -168,6 +167,10 @@ public class BaiduPhotoParserTest {
|
|||||||
// 清理注册表
|
// 清理注册表
|
||||||
CustomParserRegistry.clear();
|
CustomParserRegistry.clear();
|
||||||
|
|
||||||
|
// 初始化Vertx
|
||||||
|
Vertx vertx = Vertx.vertx();
|
||||||
|
WebClientVertxInit.init(vertx);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// 创建ShareLinkInfo
|
// 创建ShareLinkInfo
|
||||||
Map<String, Object> otherParam = new HashMap<>();
|
Map<String, Object> otherParam = new HashMap<>();
|
||||||
|
|||||||
@@ -7,8 +7,6 @@ import cn.qaiu.parser.custom.CustomParserRegistry;
|
|||||||
import cn.qaiu.parser.customjs.JsParserExecutor;
|
import cn.qaiu.parser.customjs.JsParserExecutor;
|
||||||
import cn.qaiu.WebClientVertxInit;
|
import cn.qaiu.WebClientVertxInit;
|
||||||
import io.vertx.core.Vertx;
|
import io.vertx.core.Vertx;
|
||||||
import org.junit.After;
|
|
||||||
import org.junit.Before;
|
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
|
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
@@ -23,26 +21,15 @@ import java.util.Map;
|
|||||||
*/
|
*/
|
||||||
public class JsParserTest {
|
public class JsParserTest {
|
||||||
|
|
||||||
private Vertx vertx;
|
|
||||||
|
|
||||||
@Before
|
|
||||||
public void setUp() {
|
|
||||||
vertx = Vertx.vertx();
|
|
||||||
WebClientVertxInit.init(vertx);
|
|
||||||
}
|
|
||||||
|
|
||||||
@After
|
|
||||||
public void tearDown() {
|
|
||||||
if (vertx != null) {
|
|
||||||
vertx.close();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testJsParserRegistration() {
|
public void testJsParserRegistration() {
|
||||||
// 清理注册表
|
// 清理注册表
|
||||||
CustomParserRegistry.clear();
|
CustomParserRegistry.clear();
|
||||||
|
|
||||||
|
// 初始化Vertx
|
||||||
|
Vertx vertx = Vertx.vertx();
|
||||||
|
WebClientVertxInit.init(vertx);
|
||||||
|
|
||||||
// 检查是否加载了JavaScript解析器
|
// 检查是否加载了JavaScript解析器
|
||||||
CustomParserConfig config = CustomParserRegistry.get("demo_js");
|
CustomParserConfig config = CustomParserRegistry.get("demo_js");
|
||||||
assert config != null : "JavaScript解析器未加载";
|
assert config != null : "JavaScript解析器未加载";
|
||||||
@@ -57,6 +44,10 @@ public class JsParserTest {
|
|||||||
// 清理注册表
|
// 清理注册表
|
||||||
CustomParserRegistry.clear();
|
CustomParserRegistry.clear();
|
||||||
|
|
||||||
|
// 初始化Vertx
|
||||||
|
Vertx vertx = Vertx.vertx();
|
||||||
|
WebClientVertxInit.init(vertx);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// 创建解析器
|
// 创建解析器
|
||||||
IPanTool tool = ParserCreate.fromType("demo_js")
|
IPanTool tool = ParserCreate.fromType("demo_js")
|
||||||
@@ -84,6 +75,10 @@ public class JsParserTest {
|
|||||||
// 清理注册表
|
// 清理注册表
|
||||||
CustomParserRegistry.clear();
|
CustomParserRegistry.clear();
|
||||||
|
|
||||||
|
// 初始化Vertx
|
||||||
|
Vertx vertx = Vertx.vertx();
|
||||||
|
WebClientVertxInit.init(vertx);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// 创建解析器
|
// 创建解析器
|
||||||
IPanTool tool = ParserCreate.fromType("demo_js")
|
IPanTool tool = ParserCreate.fromType("demo_js")
|
||||||
@@ -120,6 +115,10 @@ public class JsParserTest {
|
|||||||
// 清理注册表
|
// 清理注册表
|
||||||
CustomParserRegistry.clear();
|
CustomParserRegistry.clear();
|
||||||
|
|
||||||
|
// 初始化Vertx
|
||||||
|
Vertx vertx = Vertx.vertx();
|
||||||
|
WebClientVertxInit.init(vertx);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// 创建ShareLinkInfo
|
// 创建ShareLinkInfo
|
||||||
Map<String, Object> otherParam = new HashMap<>();
|
Map<String, Object> otherParam = new HashMap<>();
|
||||||
|
|||||||
@@ -7,8 +7,6 @@ import cn.qaiu.parser.ParserCreate;
|
|||||||
import cn.qaiu.parser.custom.CustomParserConfig;
|
import cn.qaiu.parser.custom.CustomParserConfig;
|
||||||
import cn.qaiu.parser.custom.CustomParserRegistry;
|
import cn.qaiu.parser.custom.CustomParserRegistry;
|
||||||
import io.vertx.core.Vertx;
|
import io.vertx.core.Vertx;
|
||||||
import org.junit.After;
|
|
||||||
import org.junit.Before;
|
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
@@ -21,23 +19,12 @@ public class JsFetchBridgeTest {
|
|||||||
|
|
||||||
private static final Logger log = LoggerFactory.getLogger(JsFetchBridgeTest.class);
|
private static final Logger log = LoggerFactory.getLogger(JsFetchBridgeTest.class);
|
||||||
|
|
||||||
private Vertx vertx;
|
|
||||||
|
|
||||||
@Before
|
|
||||||
public void setUp() {
|
|
||||||
vertx = Vertx.vertx();
|
|
||||||
WebClientVertxInit.init(vertx);
|
|
||||||
}
|
|
||||||
|
|
||||||
@After
|
|
||||||
public void tearDown() {
|
|
||||||
if (vertx != null) {
|
|
||||||
vertx.close();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testFetchPolyfillLoaded() {
|
public void testFetchPolyfillLoaded() {
|
||||||
|
// 初始化Vertx
|
||||||
|
Vertx vertx = Vertx.vertx();
|
||||||
|
WebClientVertxInit.init(vertx);
|
||||||
|
|
||||||
// 清理注册表
|
// 清理注册表
|
||||||
CustomParserRegistry.clear();
|
CustomParserRegistry.clear();
|
||||||
|
|
||||||
@@ -96,6 +83,10 @@ public class JsFetchBridgeTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testPromiseBasicUsage() {
|
public void testPromiseBasicUsage() {
|
||||||
|
// 初始化Vertx
|
||||||
|
Vertx vertx = Vertx.vertx();
|
||||||
|
WebClientVertxInit.init(vertx);
|
||||||
|
|
||||||
// 清理注册表
|
// 清理注册表
|
||||||
CustomParserRegistry.clear();
|
CustomParserRegistry.clear();
|
||||||
|
|
||||||
|
|||||||
@@ -1,329 +0,0 @@
|
|||||||
// ==FetchRuntime==
|
|
||||||
// @name Fetch API Polyfill for ES5
|
|
||||||
// @description Fetch API and Promise implementation for ES5 JavaScript engines
|
|
||||||
// @version 1.0.0
|
|
||||||
// @author QAIU
|
|
||||||
// ==============
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Simple Promise implementation compatible with ES5
|
|
||||||
* Supports basic Promise functionality needed for fetch API
|
|
||||||
*/
|
|
||||||
function SimplePromise(executor) {
|
|
||||||
var state = 'pending';
|
|
||||||
var value;
|
|
||||||
var handlers = [];
|
|
||||||
var self = this;
|
|
||||||
|
|
||||||
function resolve(result) {
|
|
||||||
if (state !== 'pending') return;
|
|
||||||
state = 'fulfilled';
|
|
||||||
value = result;
|
|
||||||
handlers.forEach(handle);
|
|
||||||
handlers = [];
|
|
||||||
}
|
|
||||||
|
|
||||||
function reject(err) {
|
|
||||||
if (state !== 'pending') return;
|
|
||||||
state = 'rejected';
|
|
||||||
value = err;
|
|
||||||
handlers.forEach(handle);
|
|
||||||
handlers = [];
|
|
||||||
}
|
|
||||||
|
|
||||||
function handle(handler) {
|
|
||||||
if (state === 'pending') {
|
|
||||||
handlers.push(handler);
|
|
||||||
} else {
|
|
||||||
setTimeout(function() {
|
|
||||||
if (state === 'fulfilled' && typeof handler.onFulfilled === 'function') {
|
|
||||||
try {
|
|
||||||
var result = handler.onFulfilled(value);
|
|
||||||
if (result && typeof result.then === 'function') {
|
|
||||||
result.then(handler.resolve, handler.reject);
|
|
||||||
} else {
|
|
||||||
handler.resolve(result);
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
handler.reject(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (state === 'rejected' && typeof handler.onRejected === 'function') {
|
|
||||||
try {
|
|
||||||
var result = handler.onRejected(value);
|
|
||||||
if (result && typeof result.then === 'function') {
|
|
||||||
result.then(handler.resolve, handler.reject);
|
|
||||||
} else {
|
|
||||||
handler.resolve(result);
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
handler.reject(e);
|
|
||||||
}
|
|
||||||
} else if (state === 'rejected' && !handler.onRejected) {
|
|
||||||
handler.reject(value);
|
|
||||||
}
|
|
||||||
}, 0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
this.then = function(onFulfilled, onRejected) {
|
|
||||||
return new SimplePromise(function(resolveNext, rejectNext) {
|
|
||||||
handle({
|
|
||||||
onFulfilled: onFulfilled,
|
|
||||||
onRejected: onRejected,
|
|
||||||
resolve: resolveNext,
|
|
||||||
reject: rejectNext
|
|
||||||
});
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
this['catch'] = function(onRejected) {
|
|
||||||
return this.then(null, onRejected);
|
|
||||||
};
|
|
||||||
|
|
||||||
this['finally'] = function(onFinally) {
|
|
||||||
return this.then(
|
|
||||||
function(value) {
|
|
||||||
return SimplePromise.resolve(onFinally()).then(function() {
|
|
||||||
return value;
|
|
||||||
});
|
|
||||||
},
|
|
||||||
function(reason) {
|
|
||||||
return SimplePromise.resolve(onFinally()).then(function() {
|
|
||||||
throw reason;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
try {
|
|
||||||
executor(resolve, reject);
|
|
||||||
} catch (e) {
|
|
||||||
reject(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Static methods
|
|
||||||
SimplePromise.resolve = function(value) {
|
|
||||||
if (value && typeof value.then === 'function') {
|
|
||||||
return value;
|
|
||||||
}
|
|
||||||
return new SimplePromise(function(resolve) {
|
|
||||||
resolve(value);
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
SimplePromise.reject = function(reason) {
|
|
||||||
return new SimplePromise(function(resolve, reject) {
|
|
||||||
reject(reason);
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
SimplePromise.all = function(promises) {
|
|
||||||
return new SimplePromise(function(resolve, reject) {
|
|
||||||
var results = [];
|
|
||||||
var remaining = promises.length;
|
|
||||||
|
|
||||||
if (remaining === 0) {
|
|
||||||
resolve(results);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
function handleResult(index, value) {
|
|
||||||
results[index] = value;
|
|
||||||
remaining--;
|
|
||||||
if (remaining === 0) {
|
|
||||||
resolve(results);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for (var i = 0; i < promises.length; i++) {
|
|
||||||
(function(index) {
|
|
||||||
var promise = promises[index];
|
|
||||||
if (promise && typeof promise.then === 'function') {
|
|
||||||
promise.then(
|
|
||||||
function(value) { handleResult(index, value); },
|
|
||||||
reject
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
handleResult(index, promise);
|
|
||||||
}
|
|
||||||
})(i);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
SimplePromise.race = function(promises) {
|
|
||||||
return new SimplePromise(function(resolve, reject) {
|
|
||||||
if (promises.length === 0) {
|
|
||||||
// Per spec, Promise.race with empty array stays pending forever
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (var i = 0; i < promises.length; i++) {
|
|
||||||
var promise = promises[i];
|
|
||||||
if (promise && typeof promise.then === 'function') {
|
|
||||||
promise.then(resolve, reject);
|
|
||||||
} else {
|
|
||||||
resolve(promise);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
// Make Promise global if not already defined
|
|
||||||
if (typeof Promise === 'undefined') {
|
|
||||||
var Promise = SimplePromise;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Response object that mimics the Fetch API Response
|
|
||||||
*/
|
|
||||||
function FetchResponse(jsHttpResponse) {
|
|
||||||
this._jsResponse = jsHttpResponse;
|
|
||||||
this.status = jsHttpResponse.statusCode();
|
|
||||||
this.ok = this.status >= 200 && this.status < 300;
|
|
||||||
|
|
||||||
// Map HTTP status codes to standard status text
|
|
||||||
var statusTexts = {
|
|
||||||
200: 'OK',
|
|
||||||
201: 'Created',
|
|
||||||
204: 'No Content',
|
|
||||||
301: 'Moved Permanently',
|
|
||||||
302: 'Found',
|
|
||||||
304: 'Not Modified',
|
|
||||||
400: 'Bad Request',
|
|
||||||
401: 'Unauthorized',
|
|
||||||
403: 'Forbidden',
|
|
||||||
404: 'Not Found',
|
|
||||||
405: 'Method Not Allowed',
|
|
||||||
408: 'Request Timeout',
|
|
||||||
409: 'Conflict',
|
|
||||||
410: 'Gone',
|
|
||||||
500: 'Internal Server Error',
|
|
||||||
501: 'Not Implemented',
|
|
||||||
502: 'Bad Gateway',
|
|
||||||
503: 'Service Unavailable',
|
|
||||||
504: 'Gateway Timeout'
|
|
||||||
};
|
|
||||||
|
|
||||||
this.statusText = statusTexts[this.status] || (this.ok ? 'OK' : 'Error');
|
|
||||||
this.headers = {
|
|
||||||
get: function(name) {
|
|
||||||
return jsHttpResponse.header(name);
|
|
||||||
},
|
|
||||||
has: function(name) {
|
|
||||||
return jsHttpResponse.header(name) !== null;
|
|
||||||
},
|
|
||||||
entries: function() {
|
|
||||||
var headerMap = jsHttpResponse.headers();
|
|
||||||
var entries = [];
|
|
||||||
for (var key in headerMap) {
|
|
||||||
if (headerMap.hasOwnProperty(key)) {
|
|
||||||
entries.push([key, headerMap[key]]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return entries;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
FetchResponse.prototype.text = function() {
|
|
||||||
var body = this._jsResponse.body();
|
|
||||||
return SimplePromise.resolve(body || '');
|
|
||||||
};
|
|
||||||
|
|
||||||
FetchResponse.prototype.json = function() {
|
|
||||||
var self = this;
|
|
||||||
return this.text().then(function(text) {
|
|
||||||
try {
|
|
||||||
return JSON.parse(text);
|
|
||||||
} catch (e) {
|
|
||||||
throw new Error('Invalid JSON: ' + e.message);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
FetchResponse.prototype.arrayBuffer = function() {
|
|
||||||
var bytes = this._jsResponse.bodyBytes();
|
|
||||||
return SimplePromise.resolve(bytes);
|
|
||||||
};
|
|
||||||
|
|
||||||
FetchResponse.prototype.blob = function() {
|
|
||||||
// Blob not supported in ES5, return bytes
|
|
||||||
return this.arrayBuffer();
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Fetch API implementation using JavaFetch bridge
|
|
||||||
* @param {string} url - Request URL
|
|
||||||
* @param {Object} options - Fetch options (method, headers, body, etc.)
|
|
||||||
* @returns {Promise<FetchResponse>}
|
|
||||||
*/
|
|
||||||
function fetch(url, options) {
|
|
||||||
return new SimplePromise(function(resolve, reject) {
|
|
||||||
try {
|
|
||||||
// Parse options
|
|
||||||
options = options || {};
|
|
||||||
var method = (options.method || 'GET').toUpperCase();
|
|
||||||
var headers = options.headers || {};
|
|
||||||
var body = options.body;
|
|
||||||
|
|
||||||
// Prepare request options for JavaFetch
|
|
||||||
var requestOptions = {
|
|
||||||
method: method,
|
|
||||||
headers: {}
|
|
||||||
};
|
|
||||||
|
|
||||||
// Convert headers to simple object
|
|
||||||
if (headers) {
|
|
||||||
if (typeof headers.forEach === 'function') {
|
|
||||||
// Headers object
|
|
||||||
headers.forEach(function(value, key) {
|
|
||||||
requestOptions.headers[key] = value;
|
|
||||||
});
|
|
||||||
} else if (typeof headers === 'object') {
|
|
||||||
// Plain object
|
|
||||||
for (var key in headers) {
|
|
||||||
if (headers.hasOwnProperty(key)) {
|
|
||||||
requestOptions.headers[key] = headers[key];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add body if present
|
|
||||||
if (body !== undefined && body !== null) {
|
|
||||||
if (typeof body === 'string') {
|
|
||||||
requestOptions.body = body;
|
|
||||||
} else if (typeof body === 'object') {
|
|
||||||
// Assume JSON
|
|
||||||
requestOptions.body = JSON.stringify(body);
|
|
||||||
if (!requestOptions.headers['Content-Type'] && !requestOptions.headers['content-type']) {
|
|
||||||
requestOptions.headers['Content-Type'] = 'application/json';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Call JavaFetch bridge
|
|
||||||
var jsHttpResponse = JavaFetch.fetch(url, requestOptions);
|
|
||||||
|
|
||||||
// Create Response object
|
|
||||||
var response = new FetchResponse(jsHttpResponse);
|
|
||||||
resolve(response);
|
|
||||||
|
|
||||||
} catch (e) {
|
|
||||||
reject(e);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// Export for global use
|
|
||||||
if (typeof window !== 'undefined') {
|
|
||||||
window.fetch = fetch;
|
|
||||||
window.Promise = Promise;
|
|
||||||
} else if (typeof global !== 'undefined') {
|
|
||||||
global.fetch = fetch;
|
|
||||||
global.Promise = Promise;
|
|
||||||
}
|
|
||||||
11
pom.xml
11
pom.xml
@@ -17,7 +17,7 @@
|
|||||||
</modules>
|
</modules>
|
||||||
|
|
||||||
<properties>
|
<properties>
|
||||||
<revision>0.3.4</revision>
|
<revision>0.2.1</revision>
|
||||||
<java.version>17</java.version>
|
<java.version>17</java.version>
|
||||||
<maven.compiler.source>17</maven.compiler.source>
|
<maven.compiler.source>17</maven.compiler.source>
|
||||||
<maven.compiler.target>17</maven.compiler.target>
|
<maven.compiler.target>17</maven.compiler.target>
|
||||||
@@ -25,17 +25,16 @@
|
|||||||
|
|
||||||
<packageDirectory>${project.basedir}/web-service/target/package</packageDirectory>
|
<packageDirectory>${project.basedir}/web-service/target/package</packageDirectory>
|
||||||
|
|
||||||
<!-- Vert.x 4.5.27 包含安全修复,无需单独指定 Netty 版本 -->
|
<!-- Vert.x 4.5.24 已包含安全修复,无需单独指定 Netty 版本 -->
|
||||||
<vertx.version>4.5.27</vertx.version>
|
<vertx.version>4.5.24</vertx.version>
|
||||||
<org.reflections.version>0.10.2</org.reflections.version>
|
<org.reflections.version>0.10.2</org.reflections.version>
|
||||||
<lombok.version>1.18.38</lombok.version>
|
<lombok.version>1.18.38</lombok.version>
|
||||||
<slf4j.version>2.0.16</slf4j.version>
|
<slf4j.version>2.0.16</slf4j.version>
|
||||||
<commons-lang3.version>3.18.0</commons-lang3.version>
|
<commons-lang3.version>3.18.0</commons-lang3.version>
|
||||||
<commons-beanutils2.version>2.0.0</commons-beanutils2.version>
|
<commons-beanutils2.version>2.0.0</commons-beanutils2.version>
|
||||||
<parserVersion>10.2.5</parserVersion>
|
|
||||||
<jackson.version>2.18.6</jackson.version>
|
<jackson.version>2.18.6</jackson.version>
|
||||||
<!-- Logback 最新稳定版 -->
|
<!-- Logback 最新稳定版 -->
|
||||||
<logback.version>1.5.32</logback.version>
|
<logback.version>1.5.18</logback.version>
|
||||||
<junit.version>4.13.2</junit.version>
|
<junit.version>4.13.2</junit.version>
|
||||||
</properties>
|
</properties>
|
||||||
|
|
||||||
@@ -75,7 +74,7 @@
|
|||||||
<dependency>
|
<dependency>
|
||||||
<groupId>cn.qaiu</groupId>
|
<groupId>cn.qaiu</groupId>
|
||||||
<artifactId>parser</artifactId>
|
<artifactId>parser</artifactId>
|
||||||
<version>${parserVersion}</version>
|
<version>10.2.5</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
</dependencies>
|
</dependencies>
|
||||||
</dependencyManagement>
|
</dependencyManagement>
|
||||||
|
|||||||
@@ -5,15 +5,15 @@
|
|||||||
"scripts": {
|
"scripts": {
|
||||||
"serve": "vue-cli-service serve",
|
"serve": "vue-cli-service serve",
|
||||||
"dev": "vue-cli-service serve",
|
"dev": "vue-cli-service serve",
|
||||||
"build": "node scripts/sync-version.js && vue-cli-service build && node scripts/compress-vs.js",
|
"build": "vue-cli-service build && node scripts/compress-vs.js",
|
||||||
"build:no-compress": "node scripts/sync-version.js && vue-cli-service build",
|
"build:no-compress": "vue-cli-service build",
|
||||||
"lint": "vue-cli-service lint"
|
"lint": "vue-cli-service lint"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@element-plus/icons-vue": "^2.3.1",
|
"@element-plus/icons-vue": "^2.3.1",
|
||||||
"@monaco-editor/loader": "^1.4.0",
|
"@monaco-editor/loader": "^1.4.0",
|
||||||
"@vueuse/core": "^11.2.0",
|
"@vueuse/core": "^11.2.0",
|
||||||
"axios": "1.16.1",
|
"axios": "1.13.5",
|
||||||
"clipboard": "^2.0.11",
|
"clipboard": "^2.0.11",
|
||||||
"core-js": "^3.8.3",
|
"core-js": "^3.8.3",
|
||||||
"crypto-js": "^4.2.0",
|
"crypto-js": "^4.2.0",
|
||||||
|
|||||||
@@ -10,7 +10,7 @@
|
|||||||
<meta name="description"
|
<meta name="description"
|
||||||
content="Netdisk fast download 网盘直链解析工具">
|
content="Netdisk fast download 网盘直链解析工具">
|
||||||
<!-- Font Awesome 图标库 - 使用国内CDN -->
|
<!-- Font Awesome 图标库 - 使用国内CDN -->
|
||||||
<link rel="stylesheet" href="https://s4.zstatic.net/ajax/libs/font-awesome/6.4.0/css/all.min.css">
|
<link rel="stylesheet" href="https://cdn.bootcdn.net/ajax/libs/font-awesome/6.4.0/css/all.min.css">
|
||||||
<!-- 迅雷 JS-SDK -->
|
<!-- 迅雷 JS-SDK -->
|
||||||
<script src="//open.thunderurl.com/thunder-link.js"></script>
|
<script src="//open.thunderurl.com/thunder-link.js"></script>
|
||||||
<style>
|
<style>
|
||||||
|
|||||||
@@ -1,23 +0,0 @@
|
|||||||
const fs = require('fs');
|
|
||||||
const path = require('path');
|
|
||||||
|
|
||||||
const pomPath = path.resolve(__dirname, '../../pom.xml');
|
|
||||||
const pkgPath = path.resolve(__dirname, '../package.json');
|
|
||||||
|
|
||||||
const pomContent = fs.readFileSync(pomPath, 'utf-8');
|
|
||||||
const match = pomContent.match(/<revision>([^<]+)<\/revision>/);
|
|
||||||
if (!match) {
|
|
||||||
console.error('sync-version: <revision> not found in root pom.xml');
|
|
||||||
process.exit(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
const version = match[1];
|
|
||||||
const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf-8'));
|
|
||||||
if (pkg.version === version) {
|
|
||||||
console.log(`sync-version: package.json already at ${version}`);
|
|
||||||
process.exit(0);
|
|
||||||
}
|
|
||||||
|
|
||||||
pkg.version = version;
|
|
||||||
fs.writeFileSync(pkgPath, JSON.stringify(pkg, null, 2) + '\n');
|
|
||||||
console.log(`sync-version: package.json ${pkg.version} -> ${version}`);
|
|
||||||
@@ -36,6 +36,7 @@ if (item) {
|
|||||||
const darkMode = ref(item)
|
const darkMode = ref(item)
|
||||||
|
|
||||||
watch(darkMode, (newValue) => {
|
watch(darkMode, (newValue) => {
|
||||||
|
console.log(`darkMode: ${newValue}`)
|
||||||
window.localStorage.setItem("darkMode", newValue);
|
window.localStorage.setItem("darkMode", newValue);
|
||||||
|
|
||||||
// 发射主题变化事件
|
// 发射主题变化事件
|
||||||
|
|||||||
@@ -190,14 +190,6 @@
|
|||||||
>
|
>
|
||||||
<i class="fas fa-paper-plane"></i> 发送到下载器
|
<i class="fas fa-paper-plane"></i> 发送到下载器
|
||||||
</el-button>
|
</el-button>
|
||||||
<el-button
|
|
||||||
v-if="selectedNode.parserUrl"
|
|
||||||
size="small"
|
|
||||||
@click="copyDirectLink(selectedNode)"
|
|
||||||
:loading="copyLinkLoading"
|
|
||||||
>
|
|
||||||
<i class="fas fa-link"></i> 复制直链
|
|
||||||
</el-button>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div v-else class="file-detail-empty">
|
<div v-else class="file-detail-empty">
|
||||||
@@ -266,14 +258,6 @@
|
|||||||
>
|
>
|
||||||
<i class="fas fa-paper-plane"></i> 发送到下载器
|
<i class="fas fa-paper-plane"></i> 发送到下载器
|
||||||
</el-button>
|
</el-button>
|
||||||
<el-button
|
|
||||||
v-if="selectedNode.parserUrl"
|
|
||||||
size="small"
|
|
||||||
@click="copyDirectLink(selectedNode)"
|
|
||||||
:loading="copyLinkLoading"
|
|
||||||
>
|
|
||||||
<i class="fas fa-link"></i> 复制直链
|
|
||||||
</el-button>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div v-else class="file-detail-empty">
|
<div v-else class="file-detail-empty">
|
||||||
@@ -340,14 +324,6 @@
|
|||||||
>
|
>
|
||||||
发送到下载器
|
发送到下载器
|
||||||
</el-button>
|
</el-button>
|
||||||
<el-button
|
|
||||||
v-if="selectedFile && selectedFile.parserUrl"
|
|
||||||
@click="copyDirectLink(selectedFile)"
|
|
||||||
style="margin-left: 8px;"
|
|
||||||
:loading="copyLinkLoading"
|
|
||||||
>
|
|
||||||
复制直链
|
|
||||||
</el-button>
|
|
||||||
</span>
|
</span>
|
||||||
</el-dialog>
|
</el-dialog>
|
||||||
<div v-if="isPreviewing" class="preview-mask">
|
<div v-if="isPreviewing" class="preview-mask">
|
||||||
@@ -415,7 +391,6 @@ export default {
|
|||||||
downloadInfo: null,
|
downloadInfo: null,
|
||||||
downloadLoading: false,
|
downloadLoading: false,
|
||||||
singleSendLoading: false,
|
singleSendLoading: false,
|
||||||
copyLinkLoading: false,
|
|
||||||
treeProps: {
|
treeProps: {
|
||||||
label: 'fileName',
|
label: 'fileName',
|
||||||
children: 'children',
|
children: 'children',
|
||||||
@@ -487,6 +462,10 @@ export default {
|
|||||||
}
|
}
|
||||||
return `${baseUrl}?${params.toString()}`
|
return `${baseUrl}?${params.toString()}`
|
||||||
},
|
},
|
||||||
|
// 文件树与窗格同源:直接返回当前目录数据
|
||||||
|
buildTree(list) {
|
||||||
|
return list || []
|
||||||
|
},
|
||||||
// 懒加载子节点
|
// 懒加载子节点
|
||||||
loadNode(node, resolve) {
|
loadNode(node, resolve) {
|
||||||
if (node.level === 0) {
|
if (node.level === 0) {
|
||||||
@@ -500,14 +479,9 @@ export default {
|
|||||||
}))
|
}))
|
||||||
resolve(children)
|
resolve(children)
|
||||||
} else {
|
} else {
|
||||||
this.$message.error(res.data.msg || '获取子节点失败')
|
|
||||||
resolve([])
|
resolve([])
|
||||||
}
|
}
|
||||||
}).catch(err => {
|
}).catch(() => resolve([]))
|
||||||
const msg = err.response?.data?.msg || err.message
|
|
||||||
if (msg) this.$message.error(msg)
|
|
||||||
resolve([])
|
|
||||||
})
|
|
||||||
} else {
|
} else {
|
||||||
resolve([])
|
resolve([])
|
||||||
}
|
}
|
||||||
@@ -517,6 +491,7 @@ export default {
|
|||||||
},
|
},
|
||||||
// 处理文件点击
|
// 处理文件点击
|
||||||
handleFileClick(file) {
|
handleFileClick(file) {
|
||||||
|
console.log('点击文件', file, this.viewMode)
|
||||||
if (file.fileType === 'folder') {
|
if (file.fileType === 'folder') {
|
||||||
this.enterFolder(file)
|
this.enterFolder(file)
|
||||||
} else if (this.viewMode === 'pane') {
|
} else if (this.viewMode === 'pane') {
|
||||||
@@ -545,8 +520,7 @@ export default {
|
|||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('进入文件夹失败:', error)
|
console.error('进入文件夹失败:', error)
|
||||||
const msg = error.response?.data?.msg || error.message || '进入文件夹失败'
|
this.$message.error('进入文件夹失败')
|
||||||
this.$message.error(msg)
|
|
||||||
} finally {
|
} finally {
|
||||||
this.loading = false
|
this.loading = false
|
||||||
}
|
}
|
||||||
@@ -577,8 +551,7 @@ export default {
|
|||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('加载目录失败:', error)
|
console.error('加载目录失败:', error)
|
||||||
const msg = error.response?.data?.msg || error.message || '加载目录失败'
|
this.$message.error('加载目录失败')
|
||||||
this.$message.error(msg)
|
|
||||||
} finally {
|
} finally {
|
||||||
this.loading = false
|
this.loading = false
|
||||||
}
|
}
|
||||||
@@ -676,8 +649,7 @@ export default {
|
|||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('获取下载信息失败:', error)
|
console.error('获取下载信息失败:', error)
|
||||||
const msg = error.response?.data?.msg || '获取下载信息失败,尝试直接下载'
|
this.$message.error('获取下载信息失败,尝试直接下载')
|
||||||
this.$message.error(msg)
|
|
||||||
this.downloadFile(file)
|
this.downloadFile(file)
|
||||||
} finally {
|
} finally {
|
||||||
this.downloadLoading = false
|
this.downloadLoading = false
|
||||||
@@ -763,8 +735,7 @@ export default {
|
|||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('发送到下载器失败:', error)
|
console.error('发送到下载器失败:', error)
|
||||||
const msg = error.response?.data?.msg || error.message || '发送到下载器失败'
|
this.$message.error('发送到下载器失败: ' + error.message)
|
||||||
this.$message.error(msg)
|
|
||||||
} finally {
|
} finally {
|
||||||
this.singleSendLoading = false
|
this.singleSendLoading = false
|
||||||
}
|
}
|
||||||
@@ -773,32 +744,6 @@ export default {
|
|||||||
this.fileDialogVisible = false
|
this.fileDialogVisible = false
|
||||||
this.selectedFile = null
|
this.selectedFile = null
|
||||||
},
|
},
|
||||||
async copyDirectLink(file) {
|
|
||||||
if (!file?.parserUrl) {
|
|
||||||
this.$message.warning('该文件暂无直链')
|
|
||||||
return
|
|
||||||
}
|
|
||||||
const rawUrl = file.parserUrl.startsWith('http') ? file.parserUrl : (window.location.origin + file.parserUrl)
|
|
||||||
const url = this.appendToken(rawUrl)
|
|
||||||
this.copyLinkLoading = true
|
|
||||||
try {
|
|
||||||
await navigator.clipboard.writeText(url)
|
|
||||||
this.$message.success('直链已复制到剪贴板')
|
|
||||||
} catch {
|
|
||||||
// fallback
|
|
||||||
const ta = document.createElement('textarea')
|
|
||||||
ta.value = url
|
|
||||||
ta.style.position = 'fixed'
|
|
||||||
ta.style.opacity = '0'
|
|
||||||
document.body.appendChild(ta)
|
|
||||||
ta.select()
|
|
||||||
document.execCommand('copy')
|
|
||||||
document.body.removeChild(ta)
|
|
||||||
this.$message.success('直链已复制到剪贴板')
|
|
||||||
} finally {
|
|
||||||
this.copyLinkLoading = false
|
|
||||||
}
|
|
||||||
},
|
|
||||||
closePreview() {
|
closePreview() {
|
||||||
this.isPreviewing = false
|
this.isPreviewing = false
|
||||||
this.previewUrl = ''
|
this.previewUrl = ''
|
||||||
@@ -857,7 +802,7 @@ export default {
|
|||||||
this.toggleFileSelect(file)
|
this.toggleFileSelect(file)
|
||||||
},
|
},
|
||||||
selectAll() {
|
selectAll() {
|
||||||
this.selectedFiles = this.currentFileList.filter(f => f.fileType !== 'folder' && f.parserUrl)
|
this.selectedFiles = this.currentFileList.filter(f => f.fileType !== 'folder')
|
||||||
},
|
},
|
||||||
deselectAll() {
|
deselectAll() {
|
||||||
this.selectedFiles = []
|
this.selectedFiles = []
|
||||||
|
|||||||
125
web-front/src/utils/api.js
Normal file
125
web-front/src/utils/api.js
Normal file
@@ -0,0 +1,125 @@
|
|||||||
|
import axios from 'axios'
|
||||||
|
|
||||||
|
// 创建 axios 实例
|
||||||
|
const api = axios.create({
|
||||||
|
baseURL: process.env.VUE_APP_API_BASE_URL || 'http://localhost:6400',
|
||||||
|
timeout: 30000,
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json'
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// 请求拦截器
|
||||||
|
api.interceptors.request.use(
|
||||||
|
config => {
|
||||||
|
// 可以在这里添加认证token等
|
||||||
|
return config
|
||||||
|
},
|
||||||
|
error => {
|
||||||
|
return Promise.reject(error)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
// 响应拦截器
|
||||||
|
api.interceptors.response.use(
|
||||||
|
response => {
|
||||||
|
return response.data
|
||||||
|
},
|
||||||
|
error => {
|
||||||
|
console.error('API请求错误:', error)
|
||||||
|
|
||||||
|
if (error.response) {
|
||||||
|
// 服务器返回错误状态码
|
||||||
|
const message = error.response.data?.message || error.response.data?.error || '服务器错误'
|
||||||
|
return Promise.reject(new Error(message))
|
||||||
|
} else if (error.request) {
|
||||||
|
// 网络错误
|
||||||
|
return Promise.reject(new Error('网络连接失败,请检查网络设置'))
|
||||||
|
} else {
|
||||||
|
// 其他错误
|
||||||
|
return Promise.reject(new Error(error.message || '请求失败'))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
// 客户端链接 API
|
||||||
|
export const clientLinksApi = {
|
||||||
|
/**
|
||||||
|
* 获取所有客户端下载链接
|
||||||
|
* @param {string} shareUrl - 分享链接
|
||||||
|
* @param {string} password - 提取码(可选)
|
||||||
|
* @returns {Promise} 客户端链接响应
|
||||||
|
*/
|
||||||
|
async getClientLinks(shareUrl, password = '') {
|
||||||
|
const params = new URLSearchParams()
|
||||||
|
params.append('url', shareUrl)
|
||||||
|
if (password) {
|
||||||
|
params.append('pwd', password)
|
||||||
|
}
|
||||||
|
|
||||||
|
return await api.get(`/v2/clientLinks?${params.toString()}`)
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取指定类型的客户端下载链接
|
||||||
|
* @param {string} shareUrl - 分享链接
|
||||||
|
* @param {string} password - 提取码(可选)
|
||||||
|
* @param {string} clientType - 客户端类型
|
||||||
|
* @returns {Promise} 指定类型的客户端链接
|
||||||
|
*/
|
||||||
|
async getClientLink(shareUrl, password = '', clientType) {
|
||||||
|
const params = new URLSearchParams()
|
||||||
|
params.append('url', shareUrl)
|
||||||
|
if (password) {
|
||||||
|
params.append('pwd', password)
|
||||||
|
}
|
||||||
|
params.append('clientType', clientType)
|
||||||
|
|
||||||
|
return await api.get(`/v2/clientLink?${params.toString()}`)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 其他 API(如果需要的话)
|
||||||
|
export const parserApi = {
|
||||||
|
/**
|
||||||
|
* 解析分享链接
|
||||||
|
* @param {string} shareUrl - 分享链接
|
||||||
|
* @param {string} password - 提取码(可选)
|
||||||
|
* @returns {Promise} 解析结果
|
||||||
|
*/
|
||||||
|
async parseLink(shareUrl, password = '') {
|
||||||
|
const params = new URLSearchParams()
|
||||||
|
params.append('url', shareUrl)
|
||||||
|
if (password) {
|
||||||
|
params.append('pwd', password)
|
||||||
|
}
|
||||||
|
|
||||||
|
return await api.get(`/v2/linkInfo?${params.toString()}`)
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取文件列表
|
||||||
|
* @param {string} shareUrl - 分享链接
|
||||||
|
* @param {string} password - 提取码(可选)
|
||||||
|
* @param {string} dirId - 目录ID(可选)
|
||||||
|
* @param {string} uuid - UUID(可选)
|
||||||
|
* @returns {Promise} 文件列表
|
||||||
|
*/
|
||||||
|
async getFileList(shareUrl, password = '', dirId = '', uuid = '') {
|
||||||
|
const params = new URLSearchParams()
|
||||||
|
params.append('url', shareUrl)
|
||||||
|
if (password) {
|
||||||
|
params.append('pwd', password)
|
||||||
|
}
|
||||||
|
if (dirId) {
|
||||||
|
params.append('dirId', dirId)
|
||||||
|
}
|
||||||
|
if (uuid) {
|
||||||
|
params.append('uuid', uuid)
|
||||||
|
}
|
||||||
|
|
||||||
|
return await api.get(`/v2/getFileList?${params.toString()}`)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default api
|
||||||
@@ -1,6 +0,0 @@
|
|||||||
/**
|
|
||||||
* 前端全局常量
|
|
||||||
*/
|
|
||||||
|
|
||||||
/** 预览服务基础 URL */
|
|
||||||
export const PREVIEW_BASE_URL = 'https://nfd-parser.github.io/nfd-preview/preview.html?src='
|
|
||||||
@@ -410,6 +410,7 @@ function addThunderDownload(tasks, config) {
|
|||||||
if (userAgent) taskParam.userAgent = userAgent
|
if (userAgent) taskParam.userAgent = userAgent
|
||||||
taskParam.threadCount = '1'
|
taskParam.threadCount = '1'
|
||||||
|
|
||||||
|
console.log('[Thunder SDK] newTask params:', JSON.stringify(taskParam))
|
||||||
window.thunderLink.newTask(taskParam)
|
window.thunderLink.newTask(taskParam)
|
||||||
return Promise.resolve('thunder-ok')
|
return Promise.resolve('thunder-ok')
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -313,6 +313,7 @@ export async function loadTypesFromApi(monaco) {
|
|||||||
cachedContent,
|
cachedContent,
|
||||||
'file:///types.js'
|
'file:///types.js'
|
||||||
);
|
);
|
||||||
|
console.log('从缓存加载types.js成功');
|
||||||
// 异步更新缓存
|
// 异步更新缓存
|
||||||
updateTypesJsCache();
|
updateTypesJsCache();
|
||||||
return;
|
return;
|
||||||
@@ -333,6 +334,7 @@ export async function loadTypesFromApi(monaco) {
|
|||||||
typesJsContent,
|
typesJsContent,
|
||||||
'file:///types.js'
|
'file:///types.js'
|
||||||
);
|
);
|
||||||
|
console.log('加载types.js成功并已缓存');
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.warn('加载types.js失败,使用内置类型定义:', error);
|
console.warn('加载types.js失败,使用内置类型定义:', error);
|
||||||
@@ -348,6 +350,7 @@ async function updateTypesJsCache() {
|
|||||||
if (response.ok) {
|
if (response.ok) {
|
||||||
const typesJsContent = await response.text();
|
const typesJsContent = await response.text();
|
||||||
localStorage.setItem('playground_types_js', typesJsContent);
|
localStorage.setItem('playground_types_js', typesJsContent);
|
||||||
|
console.log('types.js缓存已更新');
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.warn('更新types.js缓存失败:', error);
|
console.warn('更新types.js缓存失败:', error);
|
||||||
|
|||||||
@@ -293,6 +293,24 @@ export default {
|
|||||||
return clientConfig[type]?.downloadUrl || '#'
|
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 getOSInfo = () => {
|
||||||
const userAgent = navigator.userAgent.toLowerCase()
|
const userAgent = navigator.userAgent.toLowerCase()
|
||||||
@@ -351,7 +369,7 @@ export default {
|
|||||||
copyToClipboard(link)
|
copyToClipboard(link)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
window.open(link, '_blank', 'noopener,noreferrer')
|
window.open(link, '_blank')
|
||||||
ElMessage.success('正在唤起迅雷下载')
|
ElMessage.success('正在唤起迅雷下载')
|
||||||
break
|
break
|
||||||
|
|
||||||
@@ -365,6 +383,13 @@ export default {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 下载客户端
|
||||||
|
const downloadClient = (type) => {
|
||||||
|
const url = getClientDownloadUrl(type)
|
||||||
|
window.open(url, '_blank')
|
||||||
|
ElMessage.success(`正在跳转到 ${getClientDisplayName(type)} 下载页面`)
|
||||||
|
}
|
||||||
|
|
||||||
// 格式化文件大小
|
// 格式化文件大小
|
||||||
const formatFileSize = (bytes) => {
|
const formatFileSize = (bytes) => {
|
||||||
if (!bytes) return '未知'
|
if (!bytes) return '未知'
|
||||||
@@ -415,7 +440,9 @@ export default {
|
|||||||
getTextareaRows,
|
getTextareaRows,
|
||||||
goBack,
|
goBack,
|
||||||
getClientLogo,
|
getClientLogo,
|
||||||
|
downloadClient,
|
||||||
handleImageError,
|
handleImageError,
|
||||||
|
shouldShowDownloadButton,
|
||||||
getClientSupportsCookie,
|
getClientSupportsCookie,
|
||||||
goToAuthConfig
|
goToAuthConfig
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,11 +19,11 @@
|
|||||||
</el-dialog> -->
|
</el-dialog> -->
|
||||||
<!-- 顶部反馈栏(小号、灰色、无红边框) -->
|
<!-- 顶部反馈栏(小号、灰色、无红边框) -->
|
||||||
<div class="feedback-bar">
|
<div class="feedback-bar">
|
||||||
<a :href="githubRepoUrl + '/issues'" target="_blank" rel="noopener" class="feedback-link mini">
|
<a href="https://github.com/qaiu/netdisk-fast-download/issues" target="_blank" rel="noopener" class="feedback-link mini">
|
||||||
<i class="fas fa-bug feedback-icon"></i>
|
<i class="fas fa-bug feedback-icon"></i>
|
||||||
反馈
|
反馈
|
||||||
</a>
|
</a>
|
||||||
<a :href="githubRepoUrl" target="_blank" rel="noopener" class="feedback-link mini">
|
<a href="https://github.com/qaiu/netdisk-fast-download" target="_blank" rel="noopener" class="feedback-link mini">
|
||||||
<i class="fab fa-github feedback-icon"></i>
|
<i class="fab fa-github feedback-icon"></i>
|
||||||
源码
|
源码
|
||||||
</a>
|
</a>
|
||||||
@@ -73,9 +73,9 @@
|
|||||||
</div>
|
</div>
|
||||||
<!-- 项目简介移到卡片内 -->
|
<!-- 项目简介移到卡片内 -->
|
||||||
<div class="project-intro">
|
<div class="project-intro">
|
||||||
<div class="intro-title">NFD网盘直链解析 {{ projectVersion }}</div>
|
<div class="intro-title">NFD网盘直链解析0.3.0</div>
|
||||||
<div class="intro-desc">
|
<div class="intro-desc">
|
||||||
<div>支持网盘:蓝奏云、蓝奏云优享、小飞机盘、123云盘、iCloud、移动云空间、联想乐云、QQ闪传等 <el-link style="color:#606cf5" :href="githubRepoUrl + '?tab=readme-ov-file#%E7%BD%91%E7%9B%98%E6%94%AF%E6%8C%81%E6%83%85%E5%86%B5'" target="_blank"> >> </el-link></div>
|
<div>支持网盘:蓝奏云、蓝奏云优享、小飞机盘、123云盘、iCloud、移动云空间、联想乐云、QQ闪传等 <el-link style="color:#606cf5" href="https://github.com/qaiu/netdisk-fast-download?tab=readme-ov-file#%E7%BD%91%E7%9B%98%E6%94%AF%E6%8C%81%E6%83%85%E5%86%B5" target="_blank"> >> </el-link></div>
|
||||||
<div>文件夹解析支持:蓝奏云、蓝奏云优享、小飞机盘、123云盘</div>
|
<div>文件夹解析支持:蓝奏云、蓝奏云优享、小飞机盘、123云盘</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -90,7 +90,7 @@
|
|||||||
<!-- 开关按钮,控制是否自动读取剪切板 -->
|
<!-- 开关按钮,控制是否自动读取剪切板 -->
|
||||||
<el-switch v-model="autoReadClipboard" active-text="自动识别剪切板"></el-switch>
|
<el-switch v-model="autoReadClipboard" active-text="自动识别剪切板"></el-switch>
|
||||||
|
|
||||||
<el-input placeholder="请粘贴分享链接(http://或https://)" v-model="link" id="url" @paste="onPaste">
|
<el-input placeholder="请粘贴分享链接(http://或https://)" v-model="link" id="url">
|
||||||
<template #prepend>分享链接</template>
|
<template #prepend>分享链接</template>
|
||||||
<template #append v-if="!autoReadClipboard">
|
<template #append v-if="!autoReadClipboard">
|
||||||
<el-button @click="getPaste(true)">读取剪切板</el-button>
|
<el-button @click="getPaste(true)">读取剪切板</el-button>
|
||||||
@@ -257,44 +257,28 @@
|
|||||||
</el-descriptions>
|
</el-descriptions>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 错误徽章,解析失败时显示,可手动关闭,解析成功自动关闭 -->
|
<!-- 错误时显示小按钮 -->
|
||||||
<transition name="el-fade-in">
|
<div v-if="errorButtonVisible" style="text-align: center; margin-top: 10px;">
|
||||||
<div v-if="errorBadgeVisible"
|
<el-button type="text" @click="errorDialogVisible = true"> 反馈错误详情>> </el-button>
|
||||||
style="position:fixed;z-index:9999;top:80px;right:24px;display:flex;align-items:center;background:#fff1f0;border:1px solid #ffccc7;border-radius:16px;padding:6px 8px 6px 14px;box-shadow:0 2px 10px rgba(217,48,38,.12);cursor:pointer;"
|
|
||||||
@click.self="errorDialogVisible=true">
|
|
||||||
<i class="fas fa-exclamation-circle" style="color:#d93026;font-size:15px;margin-right:7px;pointer-events:none;"></i>
|
|
||||||
<span style="font-size:14px;color:#d93026;margin-right:8px;pointer-events:none;" @click="errorDialogVisible=true">解析出错</span>
|
|
||||||
<el-button
|
|
||||||
size="small" circle
|
|
||||||
style="width:20px;height:20px;min-height:20px;padding:0;background:transparent;border:none;color:#d93026;"
|
|
||||||
@click.stop="errorBadgeVisible=false">
|
|
||||||
<i class="el-icon-close" style="font-size:12px;"></i>
|
|
||||||
</el-button>
|
|
||||||
</div>
|
</div>
|
||||||
</transition>
|
|
||||||
|
|
||||||
<!-- 错误弹窗 -->
|
<!-- 错误 JSON 弹窗 -->
|
||||||
<el-dialog v-model="errorDialogVisible" width="500px" :show-close="true" @close="errorMsgExpanded=false">
|
<el-dialog
|
||||||
|
v-model="errorDialogVisible"
|
||||||
|
width="60%">
|
||||||
<template #title>
|
<template #title>
|
||||||
<i class="fas fa-exclamation-circle" style="color:#d93026;margin-right:8px;"></i>
|
错误详情
|
||||||
<span style="color:#d93026;font-weight:600;">解析出错</span>
|
<el-link
|
||||||
|
@click.prevent="copyErrorDetails"
|
||||||
|
target="_blank"
|
||||||
|
style="margin-left:8px"
|
||||||
|
type="primary">
|
||||||
|
复制当前错误信息,提交Issue
|
||||||
|
</el-link>
|
||||||
</template>
|
</template>
|
||||||
<div style="font-size:13px;color:#606266;margin-bottom:12px;word-break:break-all;line-height:1.6;">
|
<json-viewer :value="errorDetail" :expand-depth="5" copyable boxed sort />
|
||||||
<template v-if="errorDetail">
|
|
||||||
<span>{{ errorMsgExpanded ? (errorDetail.msg || errorDetail.message) : truncateMsg(errorDetail.msg || errorDetail.message) }}</span>
|
|
||||||
<a v-if="!errorMsgExpanded && (errorDetail.msg || errorDetail.message || '').length > 150"
|
|
||||||
href="#" style="color:#1677ff;margin-left:4px;"
|
|
||||||
@click.prevent="errorMsgExpanded=true">展开</a>
|
|
||||||
<a v-if="errorMsgExpanded"
|
|
||||||
href="#" style="color:#1677ff;margin-left:4px;"
|
|
||||||
@click.prevent="errorMsgExpanded=false">收起</a>
|
|
||||||
</template>
|
|
||||||
</div>
|
|
||||||
<template #footer>
|
<template #footer>
|
||||||
<el-button @click="errorDialogVisible = false">关闭</el-button>
|
<el-button @click="errorDialogVisible = false">关闭</el-button>
|
||||||
<el-button type="danger" plain @click="copyErrorDetails">
|
|
||||||
<i class="fab fa-github" style="margin-right:6px;"></i>复制信息并前往 GitHub Issues
|
|
||||||
</el-button>
|
|
||||||
</template>
|
</template>
|
||||||
</el-dialog>
|
</el-dialog>
|
||||||
|
|
||||||
@@ -611,10 +595,11 @@ import fileTypeUtils from '@/utils/fileTypeUtils'
|
|||||||
import { ElMessage, ElMessageBox } from 'element-plus'
|
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||||
import { playgroundApi } from '@/utils/playgroundApi'
|
import { playgroundApi } from '@/utils/playgroundApi'
|
||||||
import { testConnection, autoDetect, addDownload, getConfig, saveConfig } from '@/utils/downloaderService'
|
import { testConnection, autoDetect, addDownload, getConfig, saveConfig } from '@/utils/downloaderService'
|
||||||
import { PREVIEW_BASE_URL } from '@/utils/constants'
|
|
||||||
|
export const previewBaseUrl = 'https://nfd-parser.github.io/nfd-preview/preview.html?src=';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'Home',
|
name: 'App',
|
||||||
components: { DarkMode, DirectoryTree, DownloadDialog },
|
components: { DarkMode, DirectoryTree, DownloadDialog },
|
||||||
mixins: [fileTypeUtils],
|
mixins: [fileTypeUtils],
|
||||||
data() {
|
data() {
|
||||||
@@ -623,8 +608,6 @@ export default {
|
|||||||
autoReadClipboard: true,
|
autoReadClipboard: true,
|
||||||
isDarkMode: false,
|
isDarkMode: false,
|
||||||
isLoading: false,
|
isLoading: false,
|
||||||
errorBadgeVisible: false,
|
|
||||||
errorMsgExpanded: false,
|
|
||||||
|
|
||||||
// 输入数据
|
// 输入数据
|
||||||
link: "",
|
link: "",
|
||||||
@@ -634,7 +617,7 @@ export default {
|
|||||||
parseResult: {},
|
parseResult: {},
|
||||||
downloadUrl: null,
|
downloadUrl: null,
|
||||||
directLink: '',
|
directLink: '',
|
||||||
previewBaseUrl: PREVIEW_BASE_URL,
|
previewBaseUrl,
|
||||||
|
|
||||||
// 功能结果
|
// 功能结果
|
||||||
markdownText: '',
|
markdownText: '',
|
||||||
@@ -731,12 +714,6 @@ export default {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
githubRepoUrl() {
|
|
||||||
return process.env.VUE_APP_GITHUB_REPO_URL
|
|
||||||
},
|
|
||||||
projectVersion() {
|
|
||||||
return process.env.VUE_APP_VERSION || '0.0.0'
|
|
||||||
},
|
|
||||||
// 检查是否配置了认证信息(针对当前链接的网盘类型)
|
// 检查是否配置了认证信息(针对当前链接的网盘类型)
|
||||||
hasAuthConfig() {
|
hasAuthConfig() {
|
||||||
const panType = this.getCurrentPanType()
|
const panType = this.getCurrentPanType()
|
||||||
@@ -982,16 +959,18 @@ export default {
|
|||||||
// 优先使用个人配置
|
// 优先使用个人配置
|
||||||
if (this.allAuthConfigs[panType]) {
|
if (this.allAuthConfigs[panType]) {
|
||||||
config = this.allAuthConfigs[panType]
|
config = this.allAuthConfigs[panType]
|
||||||
|
console.log(`[认证] 使用个人配置: ${this.getPanDisplayName(panType)}`)
|
||||||
} else {
|
} else {
|
||||||
// 从后端随机获取捐赠账号(后端已加密,直接使用 encryptedAuth)
|
// 从后端随机获取捐赠账号(后端已加密,直接使用 encryptedAuth)
|
||||||
try {
|
try {
|
||||||
const response = await axios.get(`${this.baseAPI}/v2/randomAuth`, { params: { panType } })
|
const response = await axios.get(`${this.baseAPI}/v2/randomAuth`, { params: { panType } })
|
||||||
const encryptedAuth = response.data?.data?.encryptedAuth
|
const encryptedAuth = response.data?.data?.encryptedAuth
|
||||||
if (encryptedAuth) {
|
if (encryptedAuth) {
|
||||||
|
console.log(`[认证] 使用捐赠账号: ${this.getPanDisplayName(panType)}`)
|
||||||
return encryptedAuth
|
return encryptedAuth
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
// no available donated account
|
console.log(`[认证] 无可用捐赠账号: ${this.getPanDisplayName(panType)}`)
|
||||||
}
|
}
|
||||||
return ''
|
return ''
|
||||||
}
|
}
|
||||||
@@ -1112,45 +1091,17 @@ export default {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
// 识别并转换短链输入(如 lz:shareKey@pwd),或从文本中提取链接
|
// 识别并转换短链输入(如 lz:shareKey@pwd)
|
||||||
normalizeShortcutInput() {
|
normalizeShortcutInput() {
|
||||||
if (!this.link) return
|
const shortInfo = this.expandShortFormat(this.link)
|
||||||
const trimmed = this.link.trim()
|
if (!shortInfo) return
|
||||||
if (!trimmed) return
|
|
||||||
|
|
||||||
// 已经是直接链接,跳过
|
|
||||||
if (trimmed.startsWith('http://') || trimmed.startsWith('https://')) return
|
|
||||||
|
|
||||||
// 尝试短格式
|
|
||||||
const shortInfo = this.expandShortFormat(trimmed)
|
|
||||||
if (shortInfo) {
|
|
||||||
this.link = shortInfo.link
|
this.link = shortInfo.link
|
||||||
if (!this.password && shortInfo.pwd) {
|
if (!this.password && shortInfo.pwd) {
|
||||||
this.password = shortInfo.pwd
|
this.password = shortInfo.pwd
|
||||||
}
|
}
|
||||||
this.$message.success(`已识别短格式并自动转换,网盘类型: ${shortInfo.name}`)
|
this.$message.success(`已识别短格式并自动转换,网盘类型: ${shortInfo.name}`)
|
||||||
this.updateDirectLink()
|
this.updateDirectLink()
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// 从文本中自动提取链接
|
|
||||||
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()
|
|
||||||
})
|
|
||||||
},
|
},
|
||||||
|
|
||||||
// 清除结果
|
// 清除结果
|
||||||
@@ -1167,7 +1118,6 @@ export default {
|
|||||||
// 统一API调用(自动添加认证参数)
|
// 统一API调用(自动添加认证参数)
|
||||||
async callAPI(endpoint, params = {}) {
|
async callAPI(endpoint, params = {}) {
|
||||||
this.errorButtonVisible = false
|
this.errorButtonVisible = false
|
||||||
this.errorBadgeVisible = false
|
|
||||||
try {
|
try {
|
||||||
this.isLoading = true
|
this.isLoading = true
|
||||||
// 添加认证参数(异步获取)
|
// 添加认证参数(异步获取)
|
||||||
@@ -1179,28 +1129,14 @@ export default {
|
|||||||
|
|
||||||
if (response.data.code === 200) {
|
if (response.data.code === 200) {
|
||||||
// this.$message.success(response.data.msg || '操作成功')
|
// this.$message.success(response.data.msg || '操作成功')
|
||||||
this.errorBadgeVisible = false
|
|
||||||
return response.data
|
return response.data
|
||||||
} else {
|
} else {
|
||||||
// 解析失败,显示徽章和弹窗
|
// 在页面右下角显示一个“查看详情”按钮 可以查看原json
|
||||||
this.errorDetail = response?.data
|
this.errorDetail = response?.data
|
||||||
this.errorButtonVisible = true
|
this.errorButtonVisible = true
|
||||||
this.errorBadgeVisible = true
|
|
||||||
this.errorDialogVisible = true
|
|
||||||
throw new Error(response.data.msg || '操作失败')
|
throw new Error(response.data.msg || '操作失败')
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
// HTTP 非2xx时,从响应体中提取后端返回的错误信息
|
|
||||||
if (error.response?.data?.msg) {
|
|
||||||
this.errorDetail = error.response.data
|
|
||||||
this.errorButtonVisible = true
|
|
||||||
this.errorBadgeVisible = true
|
|
||||||
this.errorDialogVisible = true
|
|
||||||
this.$message.error(error.response.data.msg)
|
|
||||||
throw new Error(error.response.data.msg)
|
|
||||||
}
|
|
||||||
this.errorBadgeVisible = true
|
|
||||||
this.errorDialogVisible = true
|
|
||||||
this.$message.error(error.message || '网络错误')
|
this.$message.error(error.message || '网络错误')
|
||||||
throw error
|
throw error
|
||||||
} finally {
|
} finally {
|
||||||
@@ -1373,7 +1309,7 @@ export default {
|
|||||||
// 文件点击处理
|
// 文件点击处理
|
||||||
handleFileClick(file) {
|
handleFileClick(file) {
|
||||||
if (file.parserUrl) {
|
if (file.parserUrl) {
|
||||||
window.open(file.parserUrl, '_blank', 'noopener,noreferrer')
|
window.open(file.parserUrl, '_blank')
|
||||||
} else {
|
} else {
|
||||||
this.$message.warning('该文件暂无下载链接')
|
this.$message.warning('该文件暂无下载链接')
|
||||||
}
|
}
|
||||||
@@ -1383,6 +1319,7 @@ export default {
|
|||||||
async getPaste(isManual = false) {
|
async getPaste(isManual = false) {
|
||||||
try {
|
try {
|
||||||
const text = await navigator.clipboard.readText()
|
const text = await navigator.clipboard.readText()
|
||||||
|
console.log('获取到的文本内容是:', text)
|
||||||
|
|
||||||
const shortInfo = this.expandShortFormat(text)
|
const shortInfo = this.expandShortFormat(text)
|
||||||
if (shortInfo) {
|
if (shortInfo) {
|
||||||
@@ -1427,9 +1364,7 @@ export default {
|
|||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('读取剪切板失败:', error)
|
console.error('读取剪切板失败:', error)
|
||||||
if (isManual) {
|
this.$message.error('读取剪切板失败,请检查浏览器权限')
|
||||||
this.$message.warning('读取剪切板失败,请手动粘贴链接到输入框')
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -1498,18 +1433,13 @@ export default {
|
|||||||
})
|
})
|
||||||
},
|
},
|
||||||
|
|
||||||
truncateMsg(msg) {
|
|
||||||
if (!msg) return ''
|
|
||||||
return msg.length > 150 ? msg.slice(0, 150) + '...' : msg
|
|
||||||
},
|
|
||||||
|
|
||||||
copyErrorDetails() {
|
copyErrorDetails() {
|
||||||
const text = `分享链接:${this.link}
|
const text = `分享链接:${this.link}
|
||||||
分享密码:${this.password || ''}
|
分享密码:${this.password || ''}
|
||||||
错误信息:${JSON.stringify(this.errorDetail, null, 2)}`;
|
错误信息:${JSON.stringify(this.errorDetail, null, 2)}`;
|
||||||
navigator.clipboard.writeText(text).then(() => {
|
navigator.clipboard.writeText(text).then(() => {
|
||||||
this.$message.success('已复制分享信息和错误详情');
|
this.$message.success('已复制分享信息和错误详情');
|
||||||
window.open(`${this.githubRepoUrl}/issues/new`, '_blank', 'noopener,noreferrer');
|
window.open('https://github.com/qaiu/netdisk-fast-download/issues/new', '_blank');
|
||||||
}).catch(() => {
|
}).catch(() => {
|
||||||
this.$message.error('复制失败');
|
this.$message.error('复制失败');
|
||||||
});
|
});
|
||||||
@@ -1856,13 +1786,12 @@ export default {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 监听窗口焦点事件
|
// 监听窗口焦点事件
|
||||||
this._onFocusHandler = () => {
|
window.addEventListener('focus', () => {
|
||||||
if (this.autoReadClipboard) {
|
if (this.autoReadClipboard) {
|
||||||
this.hasClipboardSuccessTip = false // 聚焦时重置,只提示一次
|
this.hasClipboardSuccessTip = false // 聚焦时重置,只提示一次
|
||||||
this.getPaste()
|
this.getPaste()
|
||||||
}
|
}
|
||||||
}
|
})
|
||||||
window.addEventListener('focus', this._onFocusHandler)
|
|
||||||
|
|
||||||
// 首次打开页面弹出风险提示
|
// 首次打开页面弹出风险提示
|
||||||
if (!window.localStorage.getItem('nfd_risk_ack')) {
|
if (!window.localStorage.getItem('nfd_risk_ack')) {
|
||||||
@@ -1870,12 +1799,6 @@ export default {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
beforeUnmount() {
|
|
||||||
if (this._onFocusHandler) {
|
|
||||||
window.removeEventListener('focus', this._onFocusHandler)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
watch: {
|
watch: {
|
||||||
downloadUrl(val) {
|
downloadUrl(val) {
|
||||||
if (!val) {
|
if (!val) {
|
||||||
|
|||||||
@@ -653,22 +653,22 @@
|
|||||||
<p>更多详细信息,请参考 GitHub 仓库文档:</p>
|
<p>更多详细信息,请参考 GitHub 仓库文档:</p>
|
||||||
<ul>
|
<ul>
|
||||||
<li>
|
<li>
|
||||||
<a :href="githubRepoUrl + '/blob/main/parser/doc/JAVASCRIPT_PARSER_GUIDE.md'" target="_blank" rel="noopener noreferrer">
|
<a href="https://github.com/qaiu/netdisk-fast-download/blob/main/parser/doc/JAVASCRIPT_PARSER_GUIDE.md" target="_blank" rel="noopener noreferrer">
|
||||||
JavaScript 解析器开发指南
|
JavaScript 解析器开发指南
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<a :href="githubRepoUrl + '/blob/main/parser/doc/CUSTOM_PARSER_GUIDE.md'" target="_blank" rel="noopener noreferrer">
|
<a href="https://github.com/qaiu/netdisk-fast-download/blob/main/parser/doc/CUSTOM_PARSER_GUIDE.md" target="_blank" rel="noopener noreferrer">
|
||||||
自定义解析器扩展指南
|
自定义解析器扩展指南
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<a :href="githubRepoUrl + '/blob/main/parser/doc/CUSTOM_PARSER_QUICKSTART.md'" target="_blank" rel="noopener noreferrer">
|
<a href="https://github.com/qaiu/netdisk-fast-download/blob/main/parser/doc/CUSTOM_PARSER_QUICKSTART.md" target="_blank" rel="noopener noreferrer">
|
||||||
快速开始教程
|
快速开始教程
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<a :href="githubRepoUrl + '/blob/main/parser/README.md'" target="_blank" rel="noopener noreferrer">
|
<a href="https://github.com/qaiu/netdisk-fast-download/blob/main/parser/README.md" target="_blank" rel="noopener noreferrer">
|
||||||
解析器模块文档
|
解析器模块文档
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
@@ -858,7 +858,6 @@ export default {
|
|||||||
},
|
},
|
||||||
setup() {
|
setup() {
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const githubRepoUrl = process.env.VUE_APP_GITHUB_REPO_URL;
|
|
||||||
|
|
||||||
// 语言常量
|
// 语言常量
|
||||||
const LANGUAGE = {
|
const LANGUAGE = {
|
||||||
@@ -1179,7 +1178,7 @@ function parseById(shareLinkInfo, http, logger) {
|
|||||||
|
|
||||||
// 新窗口打开首页
|
// 新窗口打开首页
|
||||||
const goHomeInNewWindow = () => {
|
const goHomeInNewWindow = () => {
|
||||||
window.open('/', '_blank', 'noopener,noreferrer');
|
window.open('/', '_blank');
|
||||||
};
|
};
|
||||||
|
|
||||||
// 检查是否有未保存的文件
|
// 检查是否有未保存的文件
|
||||||
@@ -1759,6 +1758,7 @@ function parseFileList(shareLinkInfo, http, logger) {
|
|||||||
testParams.value.method
|
testParams.value.method
|
||||||
);
|
);
|
||||||
|
|
||||||
|
console.log('测试结果:', result);
|
||||||
testResult.value = result;
|
testResult.value = result;
|
||||||
|
|
||||||
// 将日志添加到控制台
|
// 将日志添加到控制台
|
||||||
@@ -1820,8 +1820,10 @@ function parseFileList(shareLinkInfo, http, logger) {
|
|||||||
loadingList.value = true;
|
loadingList.value = true;
|
||||||
try {
|
try {
|
||||||
const result = await playgroundApi.getParserList();
|
const result = await playgroundApi.getParserList();
|
||||||
|
console.log('获取解析器列表响应:', result);
|
||||||
// 检查响应格式
|
// 检查响应格式
|
||||||
if (result.code === 200 || result.success) {
|
if (result.code === 200 || result.success) {
|
||||||
|
console.log('列表数据:', result.data);
|
||||||
parserList.value = result.data || [];
|
parserList.value = result.data || [];
|
||||||
} else if (result.data && Array.isArray(result.data)) {
|
} else if (result.data && Array.isArray(result.data)) {
|
||||||
// 如果data直接是数组
|
// 如果data直接是数组
|
||||||
@@ -1855,6 +1857,7 @@ function parseFileList(shareLinkInfo, http, logger) {
|
|||||||
try {
|
try {
|
||||||
const codeToPublish = currentCode.value;
|
const codeToPublish = currentCode.value;
|
||||||
const result = await playgroundApi.saveParser(codeToPublish);
|
const result = await playgroundApi.saveParser(codeToPublish);
|
||||||
|
console.log('保存解析器响应:', result);
|
||||||
// 检查响应格式
|
// 检查响应格式
|
||||||
if (result.code === 200 || result.success) {
|
if (result.code === 200 || result.success) {
|
||||||
// 从响应或代码中提取type信息
|
// 从响应或代码中提取type信息
|
||||||
@@ -2220,8 +2223,6 @@ curl "${baseUrl}/json/parser?url=${encodeURIComponent(exampleUrl)}"</pre>
|
|||||||
}, 100);
|
}, 100);
|
||||||
};
|
};
|
||||||
|
|
||||||
let themeObserver = null;
|
|
||||||
|
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
// 初始化移动端检测
|
// 初始化移动端检测
|
||||||
updateIsMobile();
|
updateIsMobile();
|
||||||
@@ -2248,10 +2249,10 @@ curl "${baseUrl}/json/parser?url=${encodeURIComponent(exampleUrl)}"</pre>
|
|||||||
const html = document.documentElement;
|
const html = document.documentElement;
|
||||||
if (html && html.classList) {
|
if (html && html.classList) {
|
||||||
try {
|
try {
|
||||||
themeObserver = new MutationObserver(() => {
|
const observer = new MutationObserver(() => {
|
||||||
checkDarkMode();
|
checkDarkMode();
|
||||||
});
|
});
|
||||||
themeObserver.observe(html, {
|
observer.observe(html, {
|
||||||
attributes: true,
|
attributes: true,
|
||||||
attributeFilter: ['class', 'data-theme']
|
attributeFilter: ['class', 'data-theme']
|
||||||
});
|
});
|
||||||
@@ -2268,11 +2269,9 @@ curl "${baseUrl}/json/parser?url=${encodeURIComponent(exampleUrl)}"</pre>
|
|||||||
window.removeEventListener('resize', updateIsMobile);
|
window.removeEventListener('resize', updateIsMobile);
|
||||||
// 移除页面关闭/刷新前的提示
|
// 移除页面关闭/刷新前的提示
|
||||||
window.removeEventListener('beforeunload', handleBeforeUnload);
|
window.removeEventListener('beforeunload', handleBeforeUnload);
|
||||||
themeObserver?.disconnect();
|
|
||||||
});
|
});
|
||||||
|
|
||||||
return {
|
return {
|
||||||
githubRepoUrl,
|
|
||||||
LANGUAGE,
|
LANGUAGE,
|
||||||
editorRef,
|
editorRef,
|
||||||
jsCode,
|
jsCode,
|
||||||
|
|||||||
@@ -32,7 +32,7 @@
|
|||||||
<script>
|
<script>
|
||||||
import axios from 'axios'
|
import axios from 'axios'
|
||||||
import fileTypeUtils from '@/utils/fileTypeUtils'
|
import fileTypeUtils from '@/utils/fileTypeUtils'
|
||||||
import { PREVIEW_BASE_URL } from '@/utils/constants'
|
import { previewBaseUrl } from '@/views/Home.vue'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'ShowFile',
|
name: 'ShowFile',
|
||||||
@@ -44,7 +44,7 @@ export default {
|
|||||||
downloadUrl: '',
|
downloadUrl: '',
|
||||||
shareUrl: '', // 添加原始分享链接
|
shareUrl: '', // 添加原始分享链接
|
||||||
fileTypeUtils,
|
fileTypeUtils,
|
||||||
previewBaseUrl: PREVIEW_BASE_URL
|
previewBaseUrl
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
@@ -73,7 +73,7 @@ export default {
|
|||||||
this.parseResult = res.data
|
this.parseResult = res.data
|
||||||
this.downloadUrl = res.data.data?.directLink
|
this.downloadUrl = res.data.data?.directLink
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
this.error = e.response?.data?.msg || e.response?.data?.error || '解析失败'
|
this.error = '解析失败'
|
||||||
} finally {
|
} finally {
|
||||||
this.loading = false
|
this.loading = false
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -55,7 +55,7 @@ export default {
|
|||||||
const res = await axios.get('/v2/getFileList', { params: { url: this.url } })
|
const res = await axios.get('/v2/getFileList', { params: { url: this.url } })
|
||||||
this.directoryData = res.data.data || []
|
this.directoryData = res.data.data || []
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
this.error = e.response?.data?.msg || e.response?.data?.error || '目录解析失败'
|
this.error = '目录解析失败'
|
||||||
} finally {
|
} finally {
|
||||||
this.loading = false
|
this.loading = false
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,34 +1,10 @@
|
|||||||
|
|
||||||
const path = require("path");
|
const path = require("path");
|
||||||
const { execSync } = require("child_process");
|
|
||||||
const webpack = require("webpack");
|
|
||||||
|
|
||||||
function resolve(dir) {
|
function resolve(dir) {
|
||||||
return path.join(__dirname, dir)
|
return path.join(__dirname, dir)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 从 git remote origin 自动识别 GitHub 仓库地址
|
|
||||||
function getGitHubRepoUrl() {
|
|
||||||
try {
|
|
||||||
const remoteUrl = execSync('git remote get-url origin', { encoding: 'utf-8', cwd: path.resolve(__dirname, '..') }).trim();
|
|
||||||
const match = remoteUrl.match(/github\.com[:/]([^/]+\/[^/.]+?)(?:\.git)?$/);
|
|
||||||
if (match) return `https://github.com/${match[1]}`;
|
|
||||||
} catch (e) {}
|
|
||||||
return 'https://github.com/qaiu/netdisk-fast-download';
|
|
||||||
}
|
|
||||||
// 从根 pom.xml 读取项目版本号(单一版本来源)
|
|
||||||
function getProjectVersion() {
|
|
||||||
try {
|
|
||||||
const pomContent = require('fs').readFileSync(path.resolve(__dirname, '../pom.xml'), 'utf-8');
|
|
||||||
const match = pomContent.match(/<revision>([^<]+)<\/revision>/);
|
|
||||||
if (match) return match[1];
|
|
||||||
} catch (e) {}
|
|
||||||
return require('./package.json').version;
|
|
||||||
}
|
|
||||||
const PROJECT_VERSION = getProjectVersion();
|
|
||||||
|
|
||||||
const GITHUB_REPO_URL = getGitHubRepoUrl();
|
|
||||||
|
|
||||||
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');
|
const MonacoEditorPlugin = require('monaco-editor-webpack-plugin');
|
||||||
@@ -79,10 +55,6 @@ module.exports = {
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
plugins: [
|
plugins: [
|
||||||
new webpack.DefinePlugin({
|
|
||||||
'process.env.VUE_APP_GITHUB_REPO_URL': JSON.stringify(GITHUB_REPO_URL),
|
|
||||||
'process.env.VUE_APP_VERSION': JSON.stringify(PROJECT_VERSION)
|
|
||||||
}),
|
|
||||||
new MonacoEditorPlugin({
|
new MonacoEditorPlugin({
|
||||||
languages: ['javascript', 'typescript', 'json'],
|
languages: ['javascript', 'typescript', 'json'],
|
||||||
features: ['coreCommands', 'find', 'format', 'suggest', 'quickCommand'],
|
features: ['coreCommands', 'find', 'format', 'suggest', 'quickCommand'],
|
||||||
|
|||||||
@@ -21,8 +21,6 @@ import io.vertx.core.shareddata.LocalMap;
|
|||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.apache.commons.lang3.time.DateFormatUtils;
|
import org.apache.commons.lang3.time.DateFormatUtils;
|
||||||
|
|
||||||
import java.nio.file.Files;
|
|
||||||
import java.nio.file.Path;
|
|
||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
|
|
||||||
import static cn.qaiu.vx.core.util.ConfigConstant.LOCAL;
|
import static cn.qaiu.vx.core.util.ConfigConstant.LOCAL;
|
||||||
@@ -38,20 +36,6 @@ import static cn.qaiu.vx.core.util.ConfigConstant.LOCAL;
|
|||||||
public class AppMain {
|
public class AppMain {
|
||||||
|
|
||||||
public static void main(String[] args) {
|
public static void main(String[] args) {
|
||||||
// 先注册 ShutdownHook(JVM 逆序执行,先注册的后执行)
|
|
||||||
// 确保关闭顺序:Vert.x -> JDBCPoolInit -> JsParserExecutor
|
|
||||||
Runtime.getRuntime().addShutdownHook(new Thread(() -> {
|
|
||||||
try {
|
|
||||||
JDBCPoolInit.instance().close();
|
|
||||||
} catch (Exception e) {
|
|
||||||
// ignore
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
cn.qaiu.parser.customjs.JsParserExecutor.shutdownExecutor();
|
|
||||||
} catch (Exception e) {
|
|
||||||
// ignore
|
|
||||||
}
|
|
||||||
}));
|
|
||||||
// start
|
// start
|
||||||
Deploy.instance().start(args, AppMain::exec);
|
Deploy.instance().start(args, AppMain::exec);
|
||||||
}
|
}
|
||||||
@@ -80,35 +64,9 @@ public class AppMain {
|
|||||||
System.out.println("数据库连接成功");
|
System.out.println("数据库连接成功");
|
||||||
|
|
||||||
// 加载演练场解析器
|
// 加载演练场解析器
|
||||||
|
loadPlaygroundParsers();
|
||||||
|
|
||||||
String addr = jsonObject.getJsonObject(ConfigConstant.SERVER).getString("domainName");
|
String addr = jsonObject.getJsonObject(ConfigConstant.SERVER).getString("domainName");
|
||||||
if (addr == null || addr.isBlank()) {
|
|
||||||
addr = "http://127.0.0.1:" + jsonObject.getJsonObject(ConfigConstant.SERVER).getInteger("port", 6400);
|
|
||||||
}
|
|
||||||
// 读取代理配置获取前端页面端口(同步读取小文件,仅启动时执行一次)
|
|
||||||
String proxyConfName = jsonObject.getString("proxyConf", "server-proxy");
|
|
||||||
String pageAddr = addr;
|
|
||||||
try {
|
|
||||||
String configFile = proxyConfName + ".yml";
|
|
||||||
// 与 Deploy 保持一致:优先当前目录,其次 resources/
|
|
||||||
Path configPath = Path.of(configFile);
|
|
||||||
if (!Files.exists(configPath)) {
|
|
||||||
configPath = Path.of("resources", configFile);
|
|
||||||
}
|
|
||||||
if (Files.exists(configPath)) {
|
|
||||||
String yamlContent = Files.readString(configPath);
|
|
||||||
// 匹配项目约定的 YAML 格式: "- listen: 8080"
|
|
||||||
java.util.regex.Matcher m = java.util.regex.Pattern
|
|
||||||
.compile("^\\s*-\\s+listen:\\s*(\\d+)", java.util.regex.Pattern.MULTILINE)
|
|
||||||
.matcher(yamlContent);
|
|
||||||
if (m.find()) {
|
|
||||||
int pagePort = Integer.parseInt(m.group(1));
|
|
||||||
pageAddr = "http://127.0.0.1:" + pagePort;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (Exception e) {
|
|
||||||
log.warn("读取代理配置失败,使用默认页面地址: {}", e.getMessage());
|
|
||||||
}
|
|
||||||
loadPlaygroundParsers(pageAddr);
|
|
||||||
System.out.println("启动成功: \n本地服务地址: " + addr);
|
System.out.println("启动成功: \n本地服务地址: " + addr);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -150,7 +108,7 @@ public class AppMain {
|
|||||||
/**
|
/**
|
||||||
* 在启动时加载所有已发布的演练场解析器
|
* 在启动时加载所有已发布的演练场解析器
|
||||||
*/
|
*/
|
||||||
private static void loadPlaygroundParsers(String accessAddr) {
|
private static void loadPlaygroundParsers() {
|
||||||
DbService dbService = AsyncServiceUtil.getAsyncServiceInstance(DbService.class);
|
DbService dbService = AsyncServiceUtil.getAsyncServiceInstance(DbService.class);
|
||||||
|
|
||||||
dbService.getPlaygroundParserList().onSuccess(result -> {
|
dbService.getPlaygroundParserList().onSuccess(result -> {
|
||||||
@@ -190,8 +148,6 @@ public class AppMain {
|
|||||||
}
|
}
|
||||||
}).onFailure(e -> {
|
}).onFailure(e -> {
|
||||||
log.error("加载演练场解析器列表失败", e);
|
log.error("加载演练场解析器列表失败", e);
|
||||||
}).onComplete(ar -> {
|
|
||||||
log.info("服务已启动,可通过 {} 访问页面", accessAddr);
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -89,7 +89,7 @@ public class CacheManager {
|
|||||||
} else {
|
} else {
|
||||||
LOGGER.warn("No rows affected when updating cache link info for shareKey: {}", cacheLinkInfo.getShareKey());
|
LOGGER.warn("No rows affected when updating cache link info for shareKey: {}", cacheLinkInfo.getShareKey());
|
||||||
}
|
}
|
||||||
}).onFailure(e -> LOGGER.error("缓存链接更新失败", e));
|
}).onFailure(Throwable::printStackTrace);
|
||||||
|
|
||||||
if (cacheLinkInfo.getFileInfo() != null) {
|
if (cacheLinkInfo.getFileInfo() != null) {
|
||||||
String sql2 = """
|
String sql2 = """
|
||||||
@@ -123,7 +123,7 @@ public class CacheManager {
|
|||||||
} else {
|
} else {
|
||||||
LOGGER.warn("No rows affected when inserting pan file info for shareKey: {}", cacheLinkInfo.getShareKey());
|
LOGGER.warn("No rows affected when inserting pan file info for shareKey: {}", cacheLinkInfo.getShareKey());
|
||||||
}
|
}
|
||||||
}).onFailure(e -> LOGGER.error("文件信息插入失败", e));
|
}).onFailure(Throwable::printStackTrace);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -153,21 +153,18 @@ public class CacheManager {
|
|||||||
|
|
||||||
getShareKeyTotal(shareKey, fieldLower).onSuccess(total -> {
|
getShareKeyTotal(shareKey, fieldLower).onSuccess(total -> {
|
||||||
Integer newTotal = (total == null ? 0 : total) + 1;
|
Integer newTotal = (total == null ? 0 : total) + 1;
|
||||||
Map<String, Object> updateParams = new HashMap<>();
|
|
||||||
updateParams.put("panType", getShareType(shareKey));
|
|
||||||
updateParams.put("shareKey", shareKey);
|
|
||||||
updateParams.put("total", newTotal);
|
|
||||||
updateParams.put("ts", System.currentTimeMillis());
|
|
||||||
SqlTemplate.forUpdate(jdbcPool, sql)
|
SqlTemplate.forUpdate(jdbcPool, sql)
|
||||||
.execute(updateParams)
|
.execute(new HashMap<>() {{
|
||||||
|
put("panType", getShareType(shareKey));
|
||||||
|
put("shareKey", shareKey);
|
||||||
|
put("total", newTotal);
|
||||||
|
put("ts", System.currentTimeMillis());
|
||||||
|
}})
|
||||||
.onSuccess(res -> promise.complete(res.rowCount()))
|
.onSuccess(res -> promise.complete(res.rowCount()))
|
||||||
.onFailure(e->{
|
.onFailure(e->{
|
||||||
promise.fail(e);
|
promise.fail(e);
|
||||||
LOGGER.error("updateTotalByField: ", e);
|
LOGGER.error("updateTotalByField: ", e);
|
||||||
});
|
});
|
||||||
}).onFailure(e -> {
|
|
||||||
promise.fail(e);
|
|
||||||
LOGGER.error("getShareKeyTotal in updateTotalByField: ", e);
|
|
||||||
});
|
});
|
||||||
return promise.future();
|
return promise.future();
|
||||||
}
|
}
|
||||||
@@ -203,59 +200,6 @@ public class CacheManager {
|
|||||||
return promise.future();
|
return promise.future();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 清理过期缓存记录,防止数据库无限增长
|
|
||||||
* @return 删除的行数
|
|
||||||
*/
|
|
||||||
public Future<Integer> cleanupExpiredCache() {
|
|
||||||
String sql = "DELETE FROM cache_link_info WHERE expiration > 0 AND expiration < #{now}";
|
|
||||||
Map<String, Object> params = new HashMap<>();
|
|
||||||
params.put("now", System.currentTimeMillis());
|
|
||||||
Promise<Integer> promise = Promise.promise();
|
|
||||||
SqlTemplate.forUpdate(jdbcPool, sql)
|
|
||||||
.execute(params)
|
|
||||||
.onSuccess(res -> {
|
|
||||||
int deleted = res.rowCount();
|
|
||||||
if (deleted > 0) {
|
|
||||||
LOGGER.info("清理过期缓存记录 {} 条", deleted);
|
|
||||||
}
|
|
||||||
promise.complete(deleted);
|
|
||||||
})
|
|
||||||
.onFailure(e -> {
|
|
||||||
LOGGER.error("清理过期缓存失败", e);
|
|
||||||
promise.fail(e);
|
|
||||||
});
|
|
||||||
return promise.future();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 注册定时清理过期缓存任务(每小时执行一次)
|
|
||||||
* 应在应用启动后调用
|
|
||||||
*/
|
|
||||||
private static volatile boolean cleanupRegistered = false;
|
|
||||||
|
|
||||||
public static void registerPeriodicCleanup() {
|
|
||||||
if (cleanupRegistered) return;
|
|
||||||
try {
|
|
||||||
io.vertx.core.Vertx vertx = cn.qaiu.vx.core.util.VertxHolder.getVertxInstance();
|
|
||||||
if (vertx == null) {
|
|
||||||
LOGGER.warn("Vertx 未就绪,缓存定时清理任务延迟注册");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
cleanupRegistered = true;
|
|
||||||
vertx.setPeriodic(3600_000, 3600_000, id -> {
|
|
||||||
try {
|
|
||||||
new CacheManager().cleanupExpiredCache();
|
|
||||||
} catch (Exception e) {
|
|
||||||
LOGGER.warn("定时清理缓存任务跳过(数据库可能未就绪)", e);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
LOGGER.info("缓存定时清理任务已注册(每小时执行)");
|
|
||||||
} catch (Exception e) {
|
|
||||||
LOGGER.warn("注册缓存定时清理任务失败", e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public Future<Map<String, Integer>> getShareKeyTotal(String shareKey) {
|
public Future<Map<String, Integer>> getShareKeyTotal(String shareKey) {
|
||||||
String sql = """
|
String sql = """
|
||||||
SELECT `share_key`, SUM(cache_hit_total) AS hit_total, SUM(api_parser_total) AS parser_total
|
SELECT `share_key`, SUM(cache_hit_total) AS hit_total, SUM(api_parser_total) AS parser_total
|
||||||
@@ -273,9 +217,10 @@ public class CacheManager {
|
|||||||
.onSuccess(res -> {
|
.onSuccess(res -> {
|
||||||
if(res.iterator().hasNext()) {
|
if(res.iterator().hasNext()) {
|
||||||
JsonObject next = res.iterator().next();
|
JsonObject next = res.iterator().next();
|
||||||
Map<String, Integer> resp = new HashMap<>();
|
Map<String, Integer> resp = new HashMap<>(){{
|
||||||
resp.put("hit_total", next.getInteger("hit_total"));
|
put("hit_total" ,next.getInteger("hit_total"));
|
||||||
resp.put("parser_total", next.getInteger("parser_total"));
|
put("parser_total" ,next.getInteger("parser_total"));
|
||||||
|
}};
|
||||||
promise.complete(resp);
|
promise.complete(resp);
|
||||||
} else {
|
} else {
|
||||||
promise.complete();
|
promise.complete();
|
||||||
|
|||||||
@@ -48,6 +48,6 @@ public class LogStatistics implements AfterInterceptor {
|
|||||||
.execute(info)
|
.execute(info)
|
||||||
.onSuccess(res -> {
|
.onSuccess(res -> {
|
||||||
log.info("inserted log: id={}, path={}, code={}", info.getId(), info.getPath(), info.getCode());
|
log.info("inserted log: id={}, path={}, code={}", info.getId(), info.getPath(), info.getCode());
|
||||||
}).onFailure(e -> log.error("插入解析日志失败: id={}", info.getId(), e));
|
}).onFailure(Throwable::printStackTrace);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,7 +10,6 @@ import java.math.BigDecimal;
|
|||||||
import java.math.RoundingMode;
|
import java.math.RoundingMode;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.concurrent.ConcurrentHashMap;
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
import java.util.concurrent.atomic.AtomicInteger;
|
|
||||||
|
|
||||||
@Slf4j
|
@Slf4j
|
||||||
public class RateLimiter {
|
public class RateLimiter {
|
||||||
@@ -29,7 +28,7 @@ public class RateLimiter {
|
|||||||
MAX_REQUESTS, TIME_WINDOW, PATH_REG);
|
MAX_REQUESTS, TIME_WINDOW, PATH_REG);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Future<Void> checkRateLimit(HttpServerRequest request) {
|
synchronized public static Future<Void> checkRateLimit(HttpServerRequest request) {
|
||||||
Promise<Void> promise = Promise.promise();
|
Promise<Void> promise = Promise.promise();
|
||||||
if (!request.path().matches(PATH_REG)) {
|
if (!request.path().matches(PATH_REG)) {
|
||||||
// 如果请求路径不匹配正则,则不进行限流
|
// 如果请求路径不匹配正则,则不进行限流
|
||||||
@@ -39,25 +38,20 @@ public class RateLimiter {
|
|||||||
|
|
||||||
String ip = request.remoteAddress().host();
|
String ip = request.remoteAddress().host();
|
||||||
|
|
||||||
// 定期清理过期条目,防止 Map 无限增长
|
ipRequestMap.compute(ip, (key, requestInfo) -> {
|
||||||
if (ipRequestMap.size() > 1000) {
|
|
||||||
long now = System.currentTimeMillis();
|
|
||||||
ipRequestMap.entrySet().removeIf(entry -> now - entry.getValue().timestamp > TIME_WINDOW);
|
|
||||||
}
|
|
||||||
|
|
||||||
RequestInfo info = ipRequestMap.compute(ip, (key, requestInfo) -> {
|
|
||||||
long currentTime = System.currentTimeMillis();
|
long currentTime = System.currentTimeMillis();
|
||||||
if (requestInfo == null || currentTime - requestInfo.timestamp > TIME_WINDOW) {
|
if (requestInfo == null || currentTime - requestInfo.timestamp > TIME_WINDOW) {
|
||||||
// 初始化或重置计数器
|
// 初始化或重置计数器
|
||||||
return new RequestInfo(1, currentTime);
|
return new RequestInfo(1, currentTime);
|
||||||
} else {
|
} else {
|
||||||
// 增加计数器
|
// 增加计数器
|
||||||
requestInfo.count.incrementAndGet();
|
requestInfo.count++;
|
||||||
return requestInfo;
|
return requestInfo;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
if (info.count.get() > MAX_REQUESTS) {
|
RequestInfo info = ipRequestMap.get(ip);
|
||||||
|
if (info.count > MAX_REQUESTS) {
|
||||||
// 超过限制
|
// 超过限制
|
||||||
// 计算剩余时间
|
// 计算剩余时间
|
||||||
long remainingTime = TIME_WINDOW - (System.currentTimeMillis() - info.timestamp);
|
long remainingTime = TIME_WINDOW - (System.currentTimeMillis() - info.timestamp);
|
||||||
@@ -72,11 +66,11 @@ public class RateLimiter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private static class RequestInfo {
|
private static class RequestInfo {
|
||||||
final AtomicInteger count;
|
int count;
|
||||||
volatile long timestamp;
|
long timestamp;
|
||||||
|
|
||||||
RequestInfo(int count, long time) {
|
RequestInfo(int count, long time) {
|
||||||
this.count = new AtomicInteger(count);
|
this.count = count;
|
||||||
this.timestamp = time;
|
this.timestamp = time;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,7 +7,6 @@ import javax.crypto.Mac;
|
|||||||
import javax.crypto.spec.SecretKeySpec;
|
import javax.crypto.spec.SecretKeySpec;
|
||||||
import java.nio.charset.StandardCharsets;
|
import java.nio.charset.StandardCharsets;
|
||||||
import java.security.InvalidKeyException;
|
import java.security.InvalidKeyException;
|
||||||
import java.security.MessageDigest;
|
|
||||||
import java.security.NoSuchAlgorithmException;
|
import java.security.NoSuchAlgorithmException;
|
||||||
import java.time.Instant;
|
import java.time.Instant;
|
||||||
import java.time.LocalDateTime;
|
import java.time.LocalDateTime;
|
||||||
@@ -94,10 +93,9 @@ public class JwtUtil {
|
|||||||
String encodedPayload = parts[1];
|
String encodedPayload = parts[1];
|
||||||
String signature = parts[2];
|
String signature = parts[2];
|
||||||
|
|
||||||
// 验证签名(使用常量时间比较防止时序攻击)
|
// 验证签名
|
||||||
String expectedSignature = hmacSha256(encodedHeader + "." + encodedPayload, SECRET_KEY);
|
String expectedSignature = hmacSha256(encodedHeader + "." + encodedPayload, SECRET_KEY);
|
||||||
if (!MessageDigest.isEqual(expectedSignature.getBytes(StandardCharsets.UTF_8),
|
if (!expectedSignature.equals(signature)) {
|
||||||
signature.getBytes(StandardCharsets.UTF_8))) {
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -80,10 +80,8 @@ public class PasswordUtil {
|
|||||||
byte[] calculatedHash = md.digest(plainPassword.getBytes(StandardCharsets.UTF_8));
|
byte[] calculatedHash = md.digest(plainPassword.getBytes(StandardCharsets.UTF_8));
|
||||||
String calculatedHashBase64 = Base64.getEncoder().encodeToString(calculatedHash);
|
String calculatedHashBase64 = Base64.getEncoder().encodeToString(calculatedHash);
|
||||||
|
|
||||||
// 比较计算出的哈希值和存储的哈希值(使用常量时间比较防止时序攻击)
|
// 比较计算出的哈希值和存储的哈希值
|
||||||
return MessageDigest.isEqual(
|
return hashBase64.equals(calculatedHashBase64);
|
||||||
hashBase64.getBytes(StandardCharsets.UTF_8),
|
|
||||||
calculatedHashBase64.getBytes(StandardCharsets.UTF_8));
|
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
// 如果发生异常(例如格式不正确),返回false
|
// 如果发生异常(例如格式不正确),返回false
|
||||||
return false;
|
return false;
|
||||||
|
|||||||
@@ -119,17 +119,11 @@ public class URLParamUtil {
|
|||||||
}
|
}
|
||||||
|
|
||||||
String linkPrefix = SharedDataUtil.getJsonConfig("server").getString("domainName");
|
String linkPrefix = SharedDataUtil.getJsonConfig("server").getString("domainName");
|
||||||
if (StringUtils.isBlank(linkPrefix)) {
|
|
||||||
// 未配置 domainName 时,从请求地址推断
|
|
||||||
linkPrefix = parserCreate.getShareLinkInfo().getOtherParam()
|
|
||||||
.getOrDefault("_requestOrigin", "").toString();
|
|
||||||
}
|
|
||||||
if (StringUtils.isNotBlank(linkPrefix)) {
|
|
||||||
parserCreate.getShareLinkInfo().getOtherParam().put("domainName", linkPrefix);
|
parserCreate.getShareLinkInfo().getOtherParam().put("domainName", linkPrefix);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
* 添加临时认证参数(一次性,不保存到数据库或共享内存)
|
||||||
* 如果提供了临时认证参数,将覆盖后台配置的认证信息
|
* 如果提供了临时认证参数,将覆盖后台配置的认证信息
|
||||||
*
|
*
|
||||||
* @param parserCreate ParserCreate对象
|
* @param parserCreate ParserCreate对象
|
||||||
@@ -161,13 +155,7 @@ public class URLParamUtil {
|
|||||||
}
|
}
|
||||||
|
|
||||||
String linkPrefix = SharedDataUtil.getJsonConfig("server").getString("domainName");
|
String linkPrefix = SharedDataUtil.getJsonConfig("server").getString("domainName");
|
||||||
if (StringUtils.isBlank(linkPrefix)) {
|
|
||||||
linkPrefix = parserCreate.getShareLinkInfo().getOtherParam()
|
|
||||||
.getOrDefault("_requestOrigin", "").toString();
|
|
||||||
}
|
|
||||||
if (StringUtils.isNotBlank(linkPrefix)) {
|
|
||||||
parserCreate.getShareLinkInfo().getOtherParam().put("domainName", linkPrefix);
|
parserCreate.getShareLinkInfo().getOtherParam().put("domainName", linkPrefix);
|
||||||
}
|
|
||||||
|
|
||||||
// 构建临时认证信息
|
// 构建临时认证信息
|
||||||
MultiMap tempAuth = MultiMap.caseInsensitiveMultiMap();
|
MultiMap tempAuth = MultiMap.caseInsensitiveMultiMap();
|
||||||
|
|||||||
@@ -43,38 +43,14 @@ public class ParserApi {
|
|||||||
|
|
||||||
private final DbService dbService = AsyncServiceUtil.getAsyncServiceInstance(DbService.class);
|
private final DbService dbService = AsyncServiceUtil.getAsyncServiceInstance(DbService.class);
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取链接前缀:优先用配置的 domainName,未配置则从请求头推断
|
|
||||||
* 支持反向代理:优先读 X-Forwarded-Host/X-Forwarded-Proto,再回退到 Host 头
|
|
||||||
*/
|
|
||||||
private static String getLinkPrefix(HttpServerRequest request) {
|
|
||||||
String domainName = SharedDataUtil.getJsonConfig("server").getString("domainName");
|
|
||||||
if (StringUtils.isNotBlank(domainName)) {
|
|
||||||
return domainName;
|
|
||||||
}
|
|
||||||
if (request != null) {
|
|
||||||
// 反向代理场景:优先从转发头获取原始域名
|
|
||||||
String forwardedHost = request.getHeader("X-Forwarded-Host");
|
|
||||||
if (StringUtils.isNotBlank(forwardedHost)) {
|
|
||||||
String proto = request.getHeader("X-Forwarded-Proto");
|
|
||||||
if (StringUtils.isBlank(proto)) {
|
|
||||||
proto = request.scheme();
|
|
||||||
}
|
|
||||||
return proto + "://" + forwardedHost;
|
|
||||||
}
|
|
||||||
return request.scheme() + "://" + request.host();
|
|
||||||
}
|
|
||||||
return "";
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@RouteMapping(value = "/statisticsInfo", method = RouteMethod.GET, order = 99)
|
@RouteMapping(value = "/statisticsInfo", method = RouteMethod.GET, order = 99)
|
||||||
public Future<StatisticsInfo> statisticsInfo() {
|
public Future<StatisticsInfo> statisticsInfo() {
|
||||||
return dbService.getStatisticsInfo();
|
return dbService.getStatisticsInfo();
|
||||||
}
|
}
|
||||||
|
|
||||||
private static final CacheManager cacheManager = new CacheManager();
|
private final CacheManager cacheManager = new CacheManager();
|
||||||
private static final ServerApi serverApi = new ServerApi();
|
private final ServerApi serverApi = new ServerApi();
|
||||||
|
|
||||||
@RouteMapping(value = "/linkInfo", method = RouteMethod.GET)
|
@RouteMapping(value = "/linkInfo", method = RouteMethod.GET)
|
||||||
public Future<LinkInfoResp> parse(HttpServerRequest request, String pwd, String auth) {
|
public Future<LinkInfoResp> parse(HttpServerRequest request, String pwd, String auth) {
|
||||||
@@ -85,11 +61,10 @@ public class ParserApi {
|
|||||||
|
|
||||||
// 构建链接信息响应,如果有 auth 参数则附加到链接中
|
// 构建链接信息响应,如果有 auth 参数则附加到链接中
|
||||||
String authSuffix = (auth != null && !auth.isEmpty()) ? "&auth=" + auth : "";
|
String authSuffix = (auth != null && !auth.isEmpty()) ? "&auth=" + auth : "";
|
||||||
shareLinkInfo.getOtherParam().put("_requestOrigin", getLinkPrefix(request));
|
|
||||||
LinkInfoResp build = LinkInfoResp.builder()
|
LinkInfoResp build = LinkInfoResp.builder()
|
||||||
.downLink(getDownLink(parserCreate, false, request) + authSuffix)
|
.downLink(getDownLink(parserCreate, false) + authSuffix)
|
||||||
.apiLink(getDownLink(parserCreate, true, request) + authSuffix)
|
.apiLink(getDownLink(parserCreate, true) + authSuffix)
|
||||||
.viewLink(getViewLink(parserCreate, request) + authSuffix)
|
.viewLink(getViewLink(parserCreate) + authSuffix)
|
||||||
.shareLinkInfo(shareLinkInfo).build();
|
.shareLinkInfo(shareLinkInfo).build();
|
||||||
// 解析次数统计
|
// 解析次数统计
|
||||||
shareLinkInfo.getOtherParam().put("UA",request.headers().get("user-agent"));
|
shareLinkInfo.getOtherParam().put("UA",request.headers().get("user-agent"));
|
||||||
@@ -101,23 +76,25 @@ public class ParserApi {
|
|||||||
}
|
}
|
||||||
promise.complete(build);
|
promise.complete(build);
|
||||||
}).onFailure(t->{
|
}).onFailure(t->{
|
||||||
log.error("获取统计信息失败", t);
|
t.printStackTrace();
|
||||||
promise.complete(build);
|
promise.complete(build);
|
||||||
});
|
});
|
||||||
return promise.future();
|
return promise.future();
|
||||||
}
|
}
|
||||||
|
|
||||||
private static String getDownLink(ParserCreate create, boolean isJson, HttpServerRequest request) {
|
private static String getDownLink(ParserCreate create, boolean isJson) {
|
||||||
String linkPrefix = getLinkPrefix(request);
|
|
||||||
|
String linkPrefix = SharedDataUtil.getJsonConfig("server").getString("domainName");
|
||||||
if (StringUtils.isBlank(linkPrefix)) {
|
if (StringUtils.isBlank(linkPrefix)) {
|
||||||
linkPrefix = "http://127.0.0.1:" + SharedDataUtil.getJsonConfig("server").getInteger("port", 6400);
|
linkPrefix = "http://127.0.0.1";
|
||||||
}
|
}
|
||||||
// 下载短链前缀 /d
|
// 下载短链前缀 /d
|
||||||
return linkPrefix + (isJson ? "/json/" : "/d/") + create.genPathSuffix();
|
return linkPrefix + (isJson ? "/json/" : "/d/") + create.genPathSuffix();
|
||||||
}
|
}
|
||||||
|
|
||||||
private static String getViewLink(ParserCreate create, HttpServerRequest request) {
|
private static String getViewLink(ParserCreate create) {
|
||||||
String linkPrefix = getLinkPrefix(request);
|
|
||||||
|
String linkPrefix = SharedDataUtil.getJsonStringForServerConfig("domainName");
|
||||||
if (StringUtils.isBlank(linkPrefix)) {
|
if (StringUtils.isBlank(linkPrefix)) {
|
||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
@@ -142,9 +119,8 @@ public class ParserApi {
|
|||||||
public Future<List<FileInfo>> getFileList(HttpServerRequest request, String pwd, String dirId, String uuid) {
|
public Future<List<FileInfo>> getFileList(HttpServerRequest request, String pwd, String dirId, String uuid) {
|
||||||
String url = URLParamUtil.parserParams(request);
|
String url = URLParamUtil.parserParams(request);
|
||||||
ParserCreate parserCreate = ParserCreate.fromShareUrl(url).setShareLinkInfoPwd(pwd);
|
ParserCreate parserCreate = ParserCreate.fromShareUrl(url).setShareLinkInfoPwd(pwd);
|
||||||
String linkPrefix = getLinkPrefix(request);
|
String linkPrefix = SharedDataUtil.getJsonConfig("server").getString("domainName");
|
||||||
parserCreate.getShareLinkInfo().getOtherParam().put("domainName", linkPrefix);
|
parserCreate.getShareLinkInfo().getOtherParam().put("domainName", linkPrefix);
|
||||||
parserCreate.getShareLinkInfo().getOtherParam().put("_requestOrigin", linkPrefix);
|
|
||||||
if (StringUtils.isNotBlank(dirId)) {
|
if (StringUtils.isNotBlank(dirId)) {
|
||||||
parserCreate.getShareLinkInfo().getOtherParam().put("dirId", dirId);
|
parserCreate.getShareLinkInfo().getOtherParam().put("dirId", dirId);
|
||||||
}
|
}
|
||||||
@@ -156,7 +132,7 @@ public class ParserApi {
|
|||||||
|
|
||||||
// 目录解析下载文件
|
// 目录解析下载文件
|
||||||
// @RouteMapping("/getFileDownUrl/:type/:param")
|
// @RouteMapping("/getFileDownUrl/:type/:param")
|
||||||
public Future<String> getFileDownUrl(HttpServerRequest request, String type, String param) {
|
public Future<String> getFileDownUrl(String type, String param) {
|
||||||
ParserCreate parserCreate = ParserCreate.fromType(type).shareKey("-") // shareKey not null
|
ParserCreate parserCreate = ParserCreate.fromType(type).shareKey("-") // shareKey not null
|
||||||
.setShareLinkInfoPwd("-");
|
.setShareLinkInfoPwd("-");
|
||||||
|
|
||||||
@@ -171,21 +147,17 @@ public class ParserApi {
|
|||||||
shareLinkInfo.getOtherParam().put("paramJson", new JsonObject(paramStr));
|
shareLinkInfo.getOtherParam().put("paramJson", new JsonObject(paramStr));
|
||||||
|
|
||||||
// domainName
|
// domainName
|
||||||
String linkPrefix = getLinkPrefix(request);
|
String linkPrefix = SharedDataUtil.getJsonConfig("server").getString("domainName");
|
||||||
shareLinkInfo.getOtherParam().put("domainName", linkPrefix);
|
shareLinkInfo.getOtherParam().put("domainName", linkPrefix);
|
||||||
shareLinkInfo.getOtherParam().put("_requestOrigin", linkPrefix);
|
|
||||||
return parserCreate.createTool().parseById();
|
return parserCreate.createTool().parseById();
|
||||||
}
|
}
|
||||||
|
|
||||||
@RouteMapping("/redirectUrl/:type/:param")
|
@RouteMapping("/redirectUrl/:type/:param")
|
||||||
public Future<Void> redirectUrl(HttpServerRequest request, HttpServerResponse response, String type, String param) {
|
public Future<Void> redirectUrl(HttpServerResponse response, String type, String param) {
|
||||||
Promise<Void> promise = Promise.promise();
|
Promise<Void> promise = Promise.promise();
|
||||||
|
|
||||||
getFileDownUrl(request, type, param)
|
getFileDownUrl(type, param)
|
||||||
.onSuccess(res -> {
|
.onSuccess(res -> ResponseUtil.redirect(response, res))
|
||||||
ResponseUtil.redirect(response, res);
|
|
||||||
promise.complete();
|
|
||||||
})
|
|
||||||
.onFailure(t -> promise.fail(t.fillInStackTrace()));
|
.onFailure(t -> promise.fail(t.fillInStackTrace()));
|
||||||
return promise.future();
|
return promise.future();
|
||||||
}
|
}
|
||||||
@@ -248,7 +220,7 @@ public class ParserApi {
|
|||||||
}
|
}
|
||||||
|
|
||||||
String previewURL = SharedDataUtil.getJsonStringForServerConfig("previewURL");
|
String previewURL = SharedDataUtil.getJsonStringForServerConfig("previewURL");
|
||||||
serverApi.parseJson(request, pwd, null).onSuccess(res -> {
|
new ServerApi().parseJson(request, pwd, null).onSuccess(res -> {
|
||||||
redirect(response, previewURL, res);
|
redirect(response, previewURL, res);
|
||||||
}).onFailure(e -> {
|
}).onFailure(e -> {
|
||||||
ResponseUtil.fireJsonResultResponse(response, JsonResult.error(e.toString()));
|
ResponseUtil.fireJsonResultResponse(response, JsonResult.error(e.toString()));
|
||||||
@@ -257,15 +229,14 @@ public class ParserApi {
|
|||||||
|
|
||||||
|
|
||||||
@RouteMapping("/viewUrl/:type/:param")
|
@RouteMapping("/viewUrl/:type/:param")
|
||||||
public Future<Void> viewUrl(HttpServerRequest request, HttpServerResponse response, String type, String param) {
|
public Future<Void> viewUrl(HttpServerResponse response, String type, String param) {
|
||||||
Promise<Void> promise = Promise.promise();
|
Promise<Void> promise = Promise.promise();
|
||||||
|
|
||||||
String viewPrefix = SharedDataUtil.getJsonConfig("server").getString("previewURL");
|
String viewPrefix = SharedDataUtil.getJsonConfig("server").getString("previewURL");
|
||||||
getFileDownUrl(request, type, param)
|
getFileDownUrl(type, param)
|
||||||
.onSuccess(res -> {
|
.onSuccess(res -> {
|
||||||
String url = viewPrefix + URLEncoder.encode(res, StandardCharsets.UTF_8);
|
String url = viewPrefix + URLEncoder.encode(res, StandardCharsets.UTF_8);
|
||||||
ResponseUtil.redirect(response, url);
|
ResponseUtil.redirect(response, url);
|
||||||
promise.complete();
|
|
||||||
})
|
})
|
||||||
.onFailure(t -> promise.fail(t.fillInStackTrace()));
|
.onFailure(t -> promise.fail(t.fillInStackTrace()));
|
||||||
return promise.future();
|
return promise.future();
|
||||||
@@ -298,7 +269,6 @@ public class ParserApi {
|
|||||||
String shareUrl = URLParamUtil.parserParams(request);
|
String shareUrl = URLParamUtil.parserParams(request);
|
||||||
ParserCreate parserCreate = ParserCreate.fromShareUrl(shareUrl).setShareLinkInfoPwd(pwd);
|
ParserCreate parserCreate = ParserCreate.fromShareUrl(shareUrl).setShareLinkInfoPwd(pwd);
|
||||||
ShareLinkInfo shareLinkInfo = parserCreate.getShareLinkInfo();
|
ShareLinkInfo shareLinkInfo = parserCreate.getShareLinkInfo();
|
||||||
shareLinkInfo.getOtherParam().put("_requestOrigin", getLinkPrefix(request));
|
|
||||||
|
|
||||||
// 处理认证参数
|
// 处理认证参数
|
||||||
if (auth != null && !auth.isEmpty()) {
|
if (auth != null && !auth.isEmpty()) {
|
||||||
@@ -315,8 +285,6 @@ public class ParserApi {
|
|||||||
authParam.getExt5());
|
authParam.getExt5());
|
||||||
log.debug("客户端链接API: 已解码认证参数 authType={}", authParam.getAuthType());
|
log.debug("客户端链接API: 已解码认证参数 authType={}", authParam.getAuthType());
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
URLParamUtil.addParam(parserCreate);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 使用默认方法解析并生成客户端链接
|
// 使用默认方法解析并生成客户端链接
|
||||||
@@ -358,8 +326,6 @@ public class ParserApi {
|
|||||||
try {
|
try {
|
||||||
String shareUrl = URLParamUtil.parserParams(request);
|
String shareUrl = URLParamUtil.parserParams(request);
|
||||||
ParserCreate parserCreate = ParserCreate.fromShareUrl(shareUrl).setShareLinkInfoPwd(pwd);
|
ParserCreate parserCreate = ParserCreate.fromShareUrl(shareUrl).setShareLinkInfoPwd(pwd);
|
||||||
parserCreate.getShareLinkInfo().getOtherParam().put("_requestOrigin", getLinkPrefix(request));
|
|
||||||
URLParamUtil.addParam(parserCreate);
|
|
||||||
|
|
||||||
// 使用默认方法解析并生成客户端链接
|
// 使用默认方法解析并生成客户端链接
|
||||||
parserCreate.createTool().parseWithClientLinks()
|
parserCreate.createTool().parseWithClientLinks()
|
||||||
|
|||||||
@@ -28,7 +28,6 @@ import java.io.BufferedReader;
|
|||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
import java.io.InputStreamReader;
|
import java.io.InputStreamReader;
|
||||||
import java.nio.charset.StandardCharsets;
|
import java.nio.charset.StandardCharsets;
|
||||||
import java.security.MessageDigest;
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.regex.Matcher;
|
import java.util.regex.Matcher;
|
||||||
@@ -130,11 +129,8 @@ public class PlaygroundApi {
|
|||||||
return promise.future();
|
return promise.future();
|
||||||
}
|
}
|
||||||
|
|
||||||
// 验证密码(使用常量时间比较防止时序攻击)
|
// 验证密码
|
||||||
String storedPassword = config.getPassword();
|
if (config.getPassword().equals(password)) {
|
||||||
if (storedPassword != null && MessageDigest.isEqual(
|
|
||||||
storedPassword.getBytes(StandardCharsets.UTF_8),
|
|
||||||
password.getBytes(StandardCharsets.UTF_8))) {
|
|
||||||
String token = config.generateToken();
|
String token = config.generateToken();
|
||||||
JsonObject tokenData = new JsonObject().put("token", token);
|
JsonObject tokenData = new JsonObject().put("token", token);
|
||||||
promise.complete(JsonResult.data(tokenData).toJsonObject());
|
promise.complete(JsonResult.data(tokenData).toJsonObject());
|
||||||
@@ -303,6 +299,7 @@ public class PlaygroundApi {
|
|||||||
}).onFailure(e -> {
|
}).onFailure(e -> {
|
||||||
long executionTime = System.currentTimeMillis() - startTime;
|
long executionTime = System.currentTimeMillis() - startTime;
|
||||||
String errorMessage = e.getMessage();
|
String errorMessage = e.getMessage();
|
||||||
|
String stackTrace = getStackTrace(e);
|
||||||
|
|
||||||
log.error("演练场执行失败", e);
|
log.error("演练场执行失败", e);
|
||||||
|
|
||||||
@@ -320,6 +317,7 @@ public class PlaygroundApi {
|
|||||||
PlaygroundTestResp response = PlaygroundTestResp.builder()
|
PlaygroundTestResp response = PlaygroundTestResp.builder()
|
||||||
.success(false)
|
.success(false)
|
||||||
.error(errorMessage)
|
.error(errorMessage)
|
||||||
|
.stackTrace(stackTrace)
|
||||||
.executionTime(executionTime)
|
.executionTime(executionTime)
|
||||||
.logs(respLogs)
|
.logs(respLogs)
|
||||||
.build();
|
.build();
|
||||||
@@ -330,12 +328,14 @@ public class PlaygroundApi {
|
|||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
long executionTime = System.currentTimeMillis() - startTime;
|
long executionTime = System.currentTimeMillis() - startTime;
|
||||||
String errorMessage = e.getMessage();
|
String errorMessage = e.getMessage();
|
||||||
|
String stackTrace = getStackTrace(e);
|
||||||
|
|
||||||
log.error("演练场初始化失败", e);
|
log.error("演练场初始化失败", e);
|
||||||
|
|
||||||
PlaygroundTestResp response = PlaygroundTestResp.builder()
|
PlaygroundTestResp response = PlaygroundTestResp.builder()
|
||||||
.success(false)
|
.success(false)
|
||||||
.error(errorMessage)
|
.error(errorMessage)
|
||||||
|
.stackTrace(stackTrace)
|
||||||
.executionTime(executionTime)
|
.executionTime(executionTime)
|
||||||
.logs(new ArrayList<>())
|
.logs(new ArrayList<>())
|
||||||
.build();
|
.build();
|
||||||
@@ -346,7 +346,8 @@ public class PlaygroundApi {
|
|||||||
log.error("解析请求参数失败", e);
|
log.error("解析请求参数失败", e);
|
||||||
promise.complete(JsonObject.mapFrom(PlaygroundTestResp.builder()
|
promise.complete(JsonObject.mapFrom(PlaygroundTestResp.builder()
|
||||||
.success(false)
|
.success(false)
|
||||||
.error("解析请求参数失败")
|
.error("解析请求参数失败: " + e.getMessage())
|
||||||
|
.stackTrace(getStackTrace(e))
|
||||||
.build()));
|
.build()));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -695,5 +696,18 @@ public class PlaygroundApi {
|
|||||||
}
|
}
|
||||||
return ip;
|
return ip;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取异常堆栈信息
|
||||||
|
*/
|
||||||
|
private String getStackTrace(Throwable throwable) {
|
||||||
|
if (throwable == null) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
java.io.StringWriter sw = new java.io.StringWriter();
|
||||||
|
java.io.PrintWriter pw = new java.io.PrintWriter(sw);
|
||||||
|
throwable.printStackTrace(pw);
|
||||||
|
return sw.toString();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -68,8 +68,7 @@ public class ServerApi {
|
|||||||
key = keys[0];
|
key = keys[0];
|
||||||
pwd = keys[1];
|
pwd = keys[1];
|
||||||
}
|
}
|
||||||
String origin = resolveOrigin(request);
|
return cacheService.getCachedByShareKeyAndPwd(type, key, pwd, JsonObject.of("UA",request.headers().get("user-agent")));
|
||||||
return cacheService.getCachedByShareKeyAndPwd(type, key, pwd, JsonObject.of("UA",request.headers().get("user-agent"), "_requestOrigin", origin));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@RouteMapping(value = "/:type/:key", method = RouteMethod.GET)
|
@RouteMapping(value = "/:type/:key", method = RouteMethod.GET)
|
||||||
@@ -81,8 +80,7 @@ public class ServerApi {
|
|||||||
key = keys[0];
|
key = keys[0];
|
||||||
pwd = keys[1];
|
pwd = keys[1];
|
||||||
}
|
}
|
||||||
String origin = resolveOrigin(request);
|
cacheService.getCachedByShareKeyAndPwd(type, key, pwd, JsonObject.of("UA",request.headers().get("user-agent")))
|
||||||
cacheService.getCachedByShareKeyAndPwd(type, key, pwd, JsonObject.of("UA",request.headers().get("user-agent"), "_requestOrigin", origin))
|
|
||||||
.onSuccess(res -> ResponseUtil.redirect(
|
.onSuccess(res -> ResponseUtil.redirect(
|
||||||
response.putHeader("nfd-cache-hit", res.getCacheHit().toString())
|
response.putHeader("nfd-cache-hit", res.getCacheHit().toString())
|
||||||
.putHeader("nfd-cache-expires", res.getExpires()),
|
.putHeader("nfd-cache-expires", res.getExpires()),
|
||||||
@@ -91,21 +89,6 @@ public class ServerApi {
|
|||||||
return promise.future();
|
return promise.future();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 解析请求来源地址,支持反向代理
|
|
||||||
*/
|
|
||||||
private static String resolveOrigin(HttpServerRequest request) {
|
|
||||||
String forwardedHost = request.getHeader("X-Forwarded-Host");
|
|
||||||
if (forwardedHost != null && !forwardedHost.isBlank()) {
|
|
||||||
String proto = request.getHeader("X-Forwarded-Proto");
|
|
||||||
if (proto == null || proto.isBlank()) {
|
|
||||||
proto = request.scheme();
|
|
||||||
}
|
|
||||||
return proto + "://" + forwardedHost;
|
|
||||||
}
|
|
||||||
return request.scheme() + "://" + request.host();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 构建 otherParam,包含 UA 和解码后的认证参数
|
* 构建 otherParam,包含 UA 和解码后的认证参数
|
||||||
*
|
*
|
||||||
@@ -114,7 +97,7 @@ public class ServerApi {
|
|||||||
* @return JsonObject
|
* @return JsonObject
|
||||||
*/
|
*/
|
||||||
private JsonObject buildOtherParam(HttpServerRequest request, String auth) {
|
private JsonObject buildOtherParam(HttpServerRequest request, String auth) {
|
||||||
JsonObject otherParam = JsonObject.of("UA", request.headers().get("user-agent"), "_requestOrigin", resolveOrigin(request));
|
JsonObject otherParam = JsonObject.of("UA", request.headers().get("user-agent"));
|
||||||
|
|
||||||
// 解码认证参数
|
// 解码认证参数
|
||||||
if (auth != null && !auth.isEmpty()) {
|
if (auth != null && !auth.isEmpty()) {
|
||||||
|
|||||||
@@ -29,11 +29,6 @@ public class CacheServiceImpl implements CacheService {
|
|||||||
|
|
||||||
private final CacheManager cacheManager = new CacheManager();
|
private final CacheManager cacheManager = new CacheManager();
|
||||||
|
|
||||||
static {
|
|
||||||
// 服务类加载时注册缓存定时清理任务
|
|
||||||
CacheManager.registerPeriodicCleanup();
|
|
||||||
}
|
|
||||||
|
|
||||||
private Future<CacheLinkInfo> getAndSaveCachedShareLink(ParserCreate parserCreate) {
|
private Future<CacheLinkInfo> getAndSaveCachedShareLink(ParserCreate parserCreate) {
|
||||||
|
|
||||||
// 认证、域名相关(检查是否已经添加过参数,避免重复调用)
|
// 认证、域名相关(检查是否已经添加过参数,避免重复调用)
|
||||||
@@ -104,7 +99,7 @@ public class CacheServiceImpl implements CacheService {
|
|||||||
promise.complete(result);
|
promise.complete(result);
|
||||||
// 更新缓存
|
// 更新缓存
|
||||||
cacheManager.cacheShareLink(cacheLinkInfo);
|
cacheManager.cacheShareLink(cacheLinkInfo);
|
||||||
cacheManager.updateTotalByField(cacheKey, CacheTotalField.API_PARSER_TOTAL).onFailure(e -> log.error("更新API解析计数失败: cacheKey={}", cacheKey, e));
|
cacheManager.updateTotalByField(cacheKey, CacheTotalField.API_PARSER_TOTAL).onFailure(Throwable::printStackTrace);
|
||||||
}).onFailure(promise::fail);
|
}).onFailure(promise::fail);
|
||||||
} else {
|
} else {
|
||||||
// 缓存命中,生成过期时间并生成下载命令
|
// 缓存命中,生成过期时间并生成下载命令
|
||||||
@@ -120,7 +115,7 @@ public class CacheServiceImpl implements CacheService {
|
|||||||
|
|
||||||
promise.complete(result);
|
promise.complete(result);
|
||||||
cacheManager.updateTotalByField(cacheKey, CacheTotalField.CACHE_HIT_TOTAL)
|
cacheManager.updateTotalByField(cacheKey, CacheTotalField.CACHE_HIT_TOTAL)
|
||||||
.onFailure(e -> log.error("更新缓存命中计数失败: cacheKey={}", cacheKey, e));
|
.onFailure(Throwable::printStackTrace);
|
||||||
}
|
}
|
||||||
}).onFailure(t -> promise.fail(t.fillInStackTrace()));
|
}).onFailure(t -> promise.fail(t.fillInStackTrace()));
|
||||||
|
|
||||||
|
|||||||
@@ -42,11 +42,12 @@ public class DbServiceImpl implements DbService {
|
|||||||
@Override
|
@Override
|
||||||
public Future<JsonObject> sayOk(String data) {
|
public Future<JsonObject> sayOk(String data) {
|
||||||
log.info("say ok1 -> wait...");
|
log.info("say ok1 -> wait...");
|
||||||
Promise<JsonObject> promise = Promise.promise();
|
try {
|
||||||
cn.qaiu.vx.core.util.VertxHolder.getVertxInstance().setTimer(4000, id -> {
|
Thread.sleep(4000);
|
||||||
promise.complete(JsonObject.mapFrom(JsonResult.data("Hi: " + data)));
|
} catch (InterruptedException e) {
|
||||||
});
|
e.printStackTrace();
|
||||||
return promise.future();
|
}
|
||||||
|
return Future.succeededFuture(JsonObject.mapFrom(JsonResult.data("Hi: " + data)));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -125,7 +125,7 @@ public class UserServiceImpl implements UserService {
|
|||||||
if (rows.size() == 0) {
|
if (rows.size() == 0) {
|
||||||
promise.complete(new JsonObject()
|
promise.complete(new JsonObject()
|
||||||
.put("success", false)
|
.put("success", false)
|
||||||
.put("message", "用户名或密码错误"));
|
.put("message", "用户不存在"));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -136,7 +136,7 @@ public class UserServiceImpl implements UserService {
|
|||||||
if (!PasswordUtil.checkPassword(user.getPassword(), existUser.getPassword())) {
|
if (!PasswordUtil.checkPassword(user.getPassword(), existUser.getPassword())) {
|
||||||
promise.complete(new JsonObject()
|
promise.complete(new JsonObject()
|
||||||
.put("success", false)
|
.put("success", false)
|
||||||
.put("message", "用户名或密码错误"));
|
.put("message", "密码错误"));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -169,7 +169,7 @@ public class UserServiceImpl implements UserService {
|
|||||||
log.error("登录查询失败", err);
|
log.error("登录查询失败", err);
|
||||||
promise.complete(new JsonObject()
|
promise.complete(new JsonObject()
|
||||||
.put("success", false)
|
.put("success", false)
|
||||||
.put("message", "登录失败,请稍后重试"));
|
.put("message", "登录失败: " + err.getMessage()));
|
||||||
});
|
});
|
||||||
|
|
||||||
return promise.future();
|
return promise.future();
|
||||||
@@ -189,7 +189,7 @@ public class UserServiceImpl implements UserService {
|
|||||||
.execute(Tuple.of(username))
|
.execute(Tuple.of(username))
|
||||||
.onSuccess(rows -> {
|
.onSuccess(rows -> {
|
||||||
if (rows.size() == 0) {
|
if (rows.size() == 0) {
|
||||||
promise.fail("用户名或密码错误");
|
promise.fail("用户不存在");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -296,7 +296,7 @@ public class UserServiceImpl implements UserService {
|
|||||||
.execute(Tuple.of(user.getUsername()))
|
.execute(Tuple.of(user.getUsername()))
|
||||||
.onSuccess(rows -> {
|
.onSuccess(rows -> {
|
||||||
if (rows.size() == 0) {
|
if (rows.size() == 0) {
|
||||||
promise.fail("用户名或密码错误");
|
promise.fail("用户不存在");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -406,7 +406,7 @@ public class UserServiceImpl implements UserService {
|
|||||||
.onFailure(err -> {
|
.onFailure(err -> {
|
||||||
promise.complete(new JsonObject()
|
promise.complete(new JsonObject()
|
||||||
.put("success", false)
|
.put("success", false)
|
||||||
.put("message", "认证失败,请重新登录"));
|
.put("message", "用户不存在"));
|
||||||
});
|
});
|
||||||
|
|
||||||
return promise.future();
|
return promise.future();
|
||||||
|
|||||||
@@ -4,12 +4,13 @@ server:
|
|||||||
contextPath: /
|
contextPath: /
|
||||||
# 使用数据库
|
# 使用数据库
|
||||||
enableDatabase: true
|
enableDatabase: true
|
||||||
# 服务域名或者IP 生成二维码链接时需要,不设置则自动从请求地址获取
|
# 服务域名或者IP 生成二维码链接时需要
|
||||||
# domainName: http://127.0.0.1:6401
|
domainName: http://127.0.0.1:6401
|
||||||
# 预览服务URL
|
# 预览服务URL
|
||||||
previewURL: https://nfd-parser.github.io/nfd-preview/preview.html?src=
|
previewURL: https://nfd-parser.github.io/nfd-preview/preview.html?src=
|
||||||
# auth参数加密密钥(16位AES密钥)
|
# auth参数加密密钥(16位AES密钥)
|
||||||
authEncryptKey: 'nfd_auth_key2026'
|
authEncryptKey: 'nfd_auth_key2026'
|
||||||
|
# domainName: https://lz.qaiu.top
|
||||||
|
|
||||||
# 反向代理服务器配置路径(不用加后缀)
|
# 反向代理服务器配置路径(不用加后缀)
|
||||||
proxyConf: server-proxy
|
proxyConf: server-proxy
|
||||||
|
|||||||
@@ -0,0 +1 @@
|
|||||||
|
|
||||||
Reference in New Issue
Block a user