mirror of
https://github.com/qaiu/netdisk-fast-download.git
synced 2026-07-12 14:31:14 +00:00
Compare commits
9 Commits
v0.3.4
...
65d518373d
| Author | SHA1 | Date | |
|---|---|---|---|
| 65d518373d | |||
| bca4da4b6c | |||
| 0fd78defcb | |||
| 452fd0ea2c | |||
| dd8f2efb37 | |||
| 0feb8e798a | |||
| d55d8edd2f | |||
| 451496f102 | |||
| 6d6351bd58 |
+207
-37
@@ -1,14 +1,5 @@
|
|||||||
# This workflow will build a Java project with Maven, and cache/restore any dependencies to improve the workflow execution time
|
name: Java CI(Maven 构建 + Docker 镜像 + 原生环境打包)
|
||||||
# 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
|
||||||
@@ -16,9 +7,9 @@ permissions:
|
|||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
tags:
|
tags:
|
||||||
- '*' # 只有推送tag时才会触发构建
|
- '*'
|
||||||
branches-ignore:
|
branches-ignore:
|
||||||
- '*' # 排除所有分支的提交
|
- '*'
|
||||||
paths-ignore:
|
paths-ignore:
|
||||||
- 'bin/**'
|
- 'bin/**'
|
||||||
- '.github/**'
|
- '.github/**'
|
||||||
@@ -32,71 +23,250 @@ on:
|
|||||||
- "main"
|
- "main"
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
|
# ================================================================
|
||||||
|
# 阶段一:构建前端 + Maven 打包(只执行一次,产物共享)
|
||||||
|
# ================================================================
|
||||||
build:
|
build:
|
||||||
|
name: 编译构建
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- name: 检出代码
|
||||||
|
uses: actions/checkout@v3
|
||||||
|
|
||||||
- uses: actions/setup-node@v4
|
- name: 设置 Node.js 18
|
||||||
|
uses: actions/setup-node@v4
|
||||||
with:
|
with:
|
||||||
node-version: '18'
|
node-version: '18'
|
||||||
|
|
||||||
- name: Set up JDK 17
|
- name: 设置 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: Build Frontend
|
- name: 构建前端
|
||||||
run: cd web-front && yarn install && yarn run build
|
run: cd web-front && yarn install && yarn run build
|
||||||
|
|
||||||
- name: Build with Maven
|
- name: Maven 编译打包
|
||||||
run: mvn -B package -DskipTests --file pom.xml
|
run: mvn -B package -DskipTests --file pom.xml
|
||||||
|
|
||||||
# Optional: Uploads the full dependency graph to GitHub to improve the quality of Dependabot alerts this repository can receive
|
- name: 更新依赖图谱
|
||||||
- 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
|
continue-on-error: true
|
||||||
with:
|
with:
|
||||||
ignore-maven-wrapper: true
|
ignore-maven-wrapper: true
|
||||||
|
|
||||||
# - uses: release-drafter/release-drafter@v5
|
- name: 分享应用打包目录(供原生包和 Docker 复用)
|
||||||
# env:
|
if: github.event_name != 'pull_request'
|
||||||
# GITHUB_TOKEN: ${{ secrets.ACCESS_TOKEN }}
|
|
||||||
|
|
||||||
- name: Upload Artifact
|
|
||||||
uses: actions/upload-artifact@v4
|
uses: actions/upload-artifact@v4
|
||||||
with:
|
with:
|
||||||
|
name: app-package
|
||||||
|
path: web-service/target/package/
|
||||||
|
|
||||||
|
- name: 分享 bin-zip(供 Docker 复用)
|
||||||
|
if: github.event_name != 'pull_request'
|
||||||
|
uses: actions/upload-artifact@v4
|
||||||
|
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: Set up Docker Buildx
|
- name: 设置 QEMU(多平台构建支持)
|
||||||
|
uses: docker/setup-qemu-action@v3
|
||||||
|
|
||||||
|
- name: 设置 Docker Buildx
|
||||||
uses: docker/setup-buildx-action@v3
|
uses: docker/setup-buildx-action@v3
|
||||||
|
|
||||||
- name: Extract git tag
|
- name: 构建并推送 Docker 镜像
|
||||||
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 }}:${{ steps.tag.outputs.tag }}
|
ghcr.io/${{ github.repository }}:${{ github.ref_name }}
|
||||||
ghcr.io/${{ github.repository }}:latest
|
ghcr.io/${{ github.repository }}: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
|
||||||
|
|||||||
@@ -92,3 +92,4 @@ yarn-error.log*
|
|||||||
**/${project.build.directory}/
|
**/${project.build.directory}/
|
||||||
**/${project.basedir}/target/
|
**/${project.basedir}/target/
|
||||||
**/${basedir}/target/
|
**/${basedir}/target/
|
||||||
|
.spec-workflow/
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
# 一款网盘分享链接云解析快速下载服务
|
# 一款网盘分享链接云解析快速下载服务
|
||||||
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/maven.yml?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/build.yml?branch=main&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.27-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>
|
||||||
|
|||||||
@@ -249,7 +249,13 @@ public enum PanDomainTemplate {
|
|||||||
CT("城通网盘",
|
CT("城通网盘",
|
||||||
compile("https://(?:[a-zA-Z\\d-]+\\.)?(ctfile|545c|u062|ghpym|474b)\\.com/f(ile)?/" +
|
compile("https://(?:[a-zA-Z\\d-]+\\.)?(ctfile|545c|u062|ghpym|474b)\\.com/f(ile)?/" +
|
||||||
"(?<KEY>[0-9a-zA-Z_-]+)(\\?p=(?<PWD>\\w+))?"),
|
"(?<KEY>[0-9a-zA-Z_-]+)(\\?p=(?<PWD>\\w+))?"),
|
||||||
"https://474b.com/file/{shareKey}",
|
"https://ctfile.com/file/{shareKey}",
|
||||||
|
CtTool.class),
|
||||||
|
// https://url94.ctfile.com/d/64115194-164803691-48508c?p=7609&d=164803691&fk=decb36
|
||||||
|
CTD("城通网盘-目录",
|
||||||
|
compile("https://(?:[a-zA-Z\\d-]+\\.)?(ctfile|545c|u062|ghpym|474b)\\.com/d/" +
|
||||||
|
"(?<KEY>[0-9a-zA-Z_-]+)(\\?p=(?<PWD>\\w+))?"),
|
||||||
|
"https://ctfile.com/d/{shareKey}",
|
||||||
CtTool.class),
|
CtTool.class),
|
||||||
// https://www.vyuyun.com/s/QMa6ie?password=I4KG7H
|
// https://www.vyuyun.com/s/QMa6ie?password=I4KG7H
|
||||||
// https://www.vyuyun.com/s/QMa6ie/file?password=I4KG7H
|
// https://www.vyuyun.com/s/QMa6ie/file?password=I4KG7H
|
||||||
|
|||||||
@@ -1,16 +1,23 @@
|
|||||||
package cn.qaiu.parser.impl;
|
package cn.qaiu.parser.impl;
|
||||||
|
|
||||||
|
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.RandomStringGenerator;
|
import cn.qaiu.util.FileSizeConverter;
|
||||||
import io.vertx.core.Future;
|
import io.vertx.core.Future;
|
||||||
|
import io.vertx.core.Promise;
|
||||||
import io.vertx.core.buffer.Buffer;
|
import io.vertx.core.buffer.Buffer;
|
||||||
|
import io.vertx.core.json.JsonArray;
|
||||||
import io.vertx.core.json.JsonObject;
|
import io.vertx.core.json.JsonObject;
|
||||||
import io.vertx.ext.web.client.HttpRequest;
|
import io.vertx.ext.web.client.HttpRequest;
|
||||||
import io.vertx.uritemplate.UriTemplate;
|
import io.vertx.uritemplate.UriTemplate;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
import java.util.regex.Matcher;
|
||||||
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* <a href="https://www.ctfile.com">诚通网盘</a>
|
* <a href="https://www.ctfile.com">诚通网盘</a>
|
||||||
@@ -18,19 +25,34 @@ import java.util.Map;
|
|||||||
public class CtTool extends PanBase {
|
public class CtTool extends PanBase {
|
||||||
private static final String API_URL_PREFIX = "https://webapi.ctfile.com";
|
private static final String API_URL_PREFIX = "https://webapi.ctfile.com";
|
||||||
|
|
||||||
// https://webapi.ctfile.com/getfile.php?path=f&f=55050874-1246660795-6464f6&
|
// https://webapi.ctfile.com/getfile.php?path=f&f=64115194-17569800420720-06c697&
|
||||||
// passcode=7548&token=30wiijxs1fzhb6brw0p9m6&r=0.5885881231735761&
|
// passcode=7609&r=0.6611183001986635&ref=&url=https%3A%2F%2Furl94.ctfile.com%2Ff%2F64115194-17569800420720-06c697%3Fp%3D7609
|
||||||
// ref=&url=https%3A%2F%2F474b.com%2Ff%2F55050874-1246660795-6464f6%3Fp%3D7548
|
|
||||||
private static final String API1 = API_URL_PREFIX + "/getfile.php?path={path}" +
|
private static final String API1 = API_URL_PREFIX + "/getfile.php?path={path}" +
|
||||||
"&f={shareKey}&passcode={pwd}&token={token}&r={rand}&ref=";
|
"&f={shareKey}&passcode={pwd}&r={rand}&ref=&url={url}";
|
||||||
|
|
||||||
//https://webapi.ctfile.com/get_file_url.php?uid=55050874&fid=1246660795&folder_id=0&
|
// https://webapi.ctfile.com/get_file_url.php?uid=64115194&fid=17569800420720&folder_id=0&
|
||||||
// file_chk=054bc20461f5c63ff82015b9e69fb7fc&mb=1&token=30wiijxs1fzhb6brw0p9m6&app=0&
|
// share_id=&file_chk=af5c8757a49cbc69a557eb3da59b246c&start_time=1780471868&wait_seconds=0&
|
||||||
// acheck=1&verifycode=&rd=0.965929071503574
|
// mb=0&app=0&acheck=1&verifycode=1780471868.2951fe63abedf36ec02f34ed5711ce70&rd=0.36350981353622636
|
||||||
private static final String API2 = API_URL_PREFIX + "/get_file_url.php?" +
|
private static final String API2 = API_URL_PREFIX + "/get_file_url.php?" +
|
||||||
"uid={uid}&fid={fid}&folder_id=0&file_chk={file_chk}&mb=0&token={token}&app=0&acheck=0&verifycode=" +
|
"uid={uid}&fid={fid}&folder_id=0&share_id=&file_chk={file_chk}" +
|
||||||
"&rd={rand}";
|
"&start_time={start_time}&wait_seconds={wait_seconds}&mb=0&app=0&acheck=1" +
|
||||||
|
"&verifycode={verifycode}&rd={rand}";
|
||||||
|
|
||||||
|
// https://webapi.ctfile.com/getdir.php?path=d&d=64115194-164803691-48508c&
|
||||||
|
// folder_id=164803691&fk=decb36&passcode=7609&r=0.23...&ref=&url=https://url94.ctfile.com/d/...
|
||||||
|
private static final String API_GETDIR = API_URL_PREFIX + "/getdir.php?path={path}" +
|
||||||
|
"&d={shareKey}&folder_id={folder_id}&fk={fk}&passcode={pwd}&r={rand}&ref=&url={url}";
|
||||||
|
|
||||||
|
// DataTables参数,用于获取目录文件列表
|
||||||
|
private static final String FILE_LIST_PARAMS = "&sEcho=1&iColumns=4&sColumns=%2C%2C%2C" +
|
||||||
|
"&iDisplayStart=0&iDisplayLength=500&mDataProp_0=0&mDataProp_1=1&mDataProp_2=2&mDataProp_3=3" +
|
||||||
|
"&iSortCol_0=3&sSortDir_0=desc&iSortingCols=1";
|
||||||
|
|
||||||
|
// 文件列表HTML解析正则
|
||||||
|
private static final Pattern FILE_ID_PATTERN = Pattern.compile("value=\"f(\\d+)\"");
|
||||||
|
private static final Pattern FILE_HREF_PATTERN = Pattern.compile("href=\"#/f/([^\"]+)\"");
|
||||||
|
private static final Pattern FILE_NAME_PATTERN = Pattern.compile(">([^<]+)</a>");
|
||||||
|
private static final Pattern FILE_ICON_PATTERN = Pattern.compile("alt=\"([^\"]+)\"");
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 子类重写此构造方法不需要添加额外逻辑
|
* 子类重写此构造方法不需要添加额外逻辑
|
||||||
@@ -57,7 +79,6 @@ public class CtTool extends PanBase {
|
|||||||
}
|
}
|
||||||
String[] split = shareKey.split("-");
|
String[] split = shareKey.split("-");
|
||||||
String uid = split[0], fid = split[1];
|
String uid = split[0], fid = split[1];
|
||||||
String token = RandomStringGenerator.generateRandomString();
|
|
||||||
// 获取url path
|
// 获取url path
|
||||||
int i1 = shareLinkInfo.getShareUrl().indexOf("com/");
|
int i1 = shareLinkInfo.getShareUrl().indexOf("com/");
|
||||||
int i2 = shareLinkInfo.getShareUrl().lastIndexOf("/");
|
int i2 = shareLinkInfo.getShareUrl().lastIndexOf("/");
|
||||||
@@ -67,8 +88,8 @@ public class CtTool extends PanBase {
|
|||||||
.setTemplateParam("path", path)
|
.setTemplateParam("path", path)
|
||||||
.setTemplateParam("shareKey", shareKey)
|
.setTemplateParam("shareKey", shareKey)
|
||||||
.setTemplateParam("pwd", shareLinkInfo.getSharePassword())
|
.setTemplateParam("pwd", shareLinkInfo.getSharePassword())
|
||||||
.setTemplateParam("token", token)
|
.setTemplateParam("rand", String.valueOf(Math.random()))
|
||||||
.setTemplateParam("r", Math.random() + "");
|
.setTemplateParam("url", shareLinkInfo.getShareUrl());
|
||||||
|
|
||||||
bufferHttpRequest1
|
bufferHttpRequest1
|
||||||
.send().onSuccess(res -> {
|
.send().onSuccess(res -> {
|
||||||
@@ -77,12 +98,29 @@ public class CtTool extends PanBase {
|
|||||||
var fileJson = resJson.getJsonObject("file");
|
var fileJson = resJson.getJsonObject("file");
|
||||||
if (fileJson.containsKey("file_chk")) {
|
if (fileJson.containsKey("file_chk")) {
|
||||||
var file_chk = fileJson.getString("file_chk");
|
var file_chk = fileJson.getString("file_chk");
|
||||||
|
String startTime = fileJson.getValue("start_time").toString();
|
||||||
|
String waitSeconds = fileJson.getValue("wait_seconds").toString();
|
||||||
|
String verifycode = fileJson.getString("verifycode");
|
||||||
|
|
||||||
|
// 提取文件信息并存储
|
||||||
|
FileInfo fileInfo = new FileInfo()
|
||||||
|
.setFileName(fileJson.getString("file_name"))
|
||||||
|
.setFileId(String.valueOf(fileJson.getLong("file_id", 0L)))
|
||||||
|
.setSizeStr(fileJson.getString("file_size"))
|
||||||
|
.setCreateTime(fileJson.getString("file_time"))
|
||||||
|
.setCreateBy(fileJson.getString("username"))
|
||||||
|
.setFileType("file")
|
||||||
|
.setPanType(shareLinkInfo.getType());
|
||||||
|
shareLinkInfo.getOtherParam().put("fileInfo", fileInfo);
|
||||||
|
|
||||||
HttpRequest<Buffer> bufferHttpRequest2 = clientSession.getAbs(UriTemplate.of(API2))
|
HttpRequest<Buffer> bufferHttpRequest2 = clientSession.getAbs(UriTemplate.of(API2))
|
||||||
.setTemplateParam("uid", uid)
|
.setTemplateParam("uid", uid)
|
||||||
.setTemplateParam("fid", fid)
|
.setTemplateParam("fid", fid)
|
||||||
.setTemplateParam("file_chk", file_chk)
|
.setTemplateParam("file_chk", file_chk)
|
||||||
.setTemplateParam("token", token)
|
.setTemplateParam("start_time", startTime)
|
||||||
.setTemplateParam("rd", Math.random() + "");
|
.setTemplateParam("wait_seconds", waitSeconds)
|
||||||
|
.setTemplateParam("verifycode", verifycode)
|
||||||
|
.setTemplateParam("rand", String.valueOf(Math.random()));
|
||||||
bufferHttpRequest2
|
bufferHttpRequest2
|
||||||
.send().onSuccess(res2 -> {
|
.send().onSuccess(res2 -> {
|
||||||
JsonObject resJson2 = asJson(res2);
|
JsonObject resJson2 = asJson(res2);
|
||||||
@@ -109,4 +147,132 @@ public class CtTool extends PanBase {
|
|||||||
}).onFailure(handleFail(bufferHttpRequest1.queryParams().toString()));
|
}).onFailure(handleFail(bufferHttpRequest1.queryParams().toString()));
|
||||||
return promise.future();
|
return promise.future();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Future<List<FileInfo>> parseFileList() {
|
||||||
|
Promise<List<FileInfo>> listPromise = Promise.promise();
|
||||||
|
|
||||||
|
final String shareKey = shareLinkInfo.getShareKey();
|
||||||
|
final String shareUrl = shareLinkInfo.getShareUrl();
|
||||||
|
final String pwd = shareLinkInfo.getSharePassword();
|
||||||
|
|
||||||
|
// shareKey格式: uid-folder_id-hash (例如 64115194-164803691-48508c)
|
||||||
|
String[] split = shareKey.split("-");
|
||||||
|
if (split.length < 2) {
|
||||||
|
listPromise.fail(baseMsg() + " shareKey格式不正确: " + shareKey);
|
||||||
|
return listPromise.future();
|
||||||
|
}
|
||||||
|
String folderId = split[1];
|
||||||
|
|
||||||
|
// 从分享URL中提取fk参数
|
||||||
|
String fk = extractQueryParam(shareUrl, "fk");
|
||||||
|
|
||||||
|
// 从URL中提取path (例如从 "https://url94.ctfile.com/d/xxx?p=..." 中提取 "d")
|
||||||
|
int comIdx = shareUrl.indexOf("com/");
|
||||||
|
int qIdx = shareUrl.indexOf('?');
|
||||||
|
String pathAndKey = qIdx > 0 ? shareUrl.substring(comIdx + 4, qIdx) : shareUrl.substring(comIdx + 4);
|
||||||
|
int slashIdx = pathAndKey.indexOf('/');
|
||||||
|
String path = slashIdx > 0 ? pathAndKey.substring(0, slashIdx) : pathAndKey;
|
||||||
|
|
||||||
|
clientSession.getAbs(UriTemplate.of(API_GETDIR))
|
||||||
|
.setTemplateParam("path", path)
|
||||||
|
.setTemplateParam("shareKey", shareKey)
|
||||||
|
.setTemplateParam("folder_id", folderId)
|
||||||
|
.setTemplateParam("fk", fk != null ? fk : "")
|
||||||
|
.setTemplateParam("pwd", pwd != null ? pwd : "")
|
||||||
|
.setTemplateParam("rand", String.valueOf(Math.random()))
|
||||||
|
.setTemplateParam("url", shareUrl)
|
||||||
|
.send().onSuccess(res -> {
|
||||||
|
var resJson = asJson(res);
|
||||||
|
if (!resJson.containsKey("file")) {
|
||||||
|
listPromise.fail(baseMsg() + " 目录解析失败: " + resJson.encode());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
var dirInfo = resJson.getJsonObject("file");
|
||||||
|
String fileListRelUrl = dirInfo.getString("url");
|
||||||
|
if (fileListRelUrl == null) {
|
||||||
|
listPromise.fail(baseMsg() + " 文件列表URL为空");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
String fileListUrl = API_URL_PREFIX + fileListRelUrl + FILE_LIST_PARAMS;
|
||||||
|
clientSession.getAbs(fileListUrl)
|
||||||
|
.send().onSuccess(res2 -> {
|
||||||
|
var listJson = asJson(res2);
|
||||||
|
JsonArray aaData = listJson.getJsonArray("aaData");
|
||||||
|
if (aaData == null) {
|
||||||
|
listPromise.fail(baseMsg() + " 文件列表为空");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
List<FileInfo> fileList = new ArrayList<>();
|
||||||
|
String panType = shareLinkInfo.getType();
|
||||||
|
for (int i = 0; i < aaData.size(); i++) {
|
||||||
|
var row = aaData.getJsonArray(i);
|
||||||
|
try {
|
||||||
|
String checkboxHtml = row.getString(0);
|
||||||
|
String nameCellHtml = row.getString(1);
|
||||||
|
String sizeStr = row.getString(2).trim();
|
||||||
|
|
||||||
|
// 从checkbox HTML中提取文件ID
|
||||||
|
String fileId = null;
|
||||||
|
Matcher idMatcher = FILE_ID_PATTERN.matcher(checkboxHtml);
|
||||||
|
if (idMatcher.find()) fileId = idMatcher.group(1);
|
||||||
|
|
||||||
|
// 从文件名单元格HTML中提取临时分享key
|
||||||
|
String fileShareKey = null;
|
||||||
|
Matcher hrefMatcher = FILE_HREF_PATTERN.matcher(nameCellHtml);
|
||||||
|
if (hrefMatcher.find()) fileShareKey = hrefMatcher.group(1);
|
||||||
|
|
||||||
|
// 提取文件名
|
||||||
|
String fileName = null;
|
||||||
|
Matcher nameMatcher = FILE_NAME_PATTERN.matcher(nameCellHtml);
|
||||||
|
if (nameMatcher.find()) fileName = nameMatcher.group(1).trim();
|
||||||
|
|
||||||
|
// 提取文件图标/类型
|
||||||
|
String fileIcon = null;
|
||||||
|
Matcher iconMatcher = FILE_ICON_PATTERN.matcher(nameCellHtml);
|
||||||
|
if (iconMatcher.find()) fileIcon = iconMatcher.group(1);
|
||||||
|
|
||||||
|
if (fileName == null || fileShareKey == null) continue;
|
||||||
|
|
||||||
|
long sizeBytes = 0;
|
||||||
|
try {
|
||||||
|
sizeBytes = FileSizeConverter.convertToBytes(sizeStr);
|
||||||
|
} catch (Exception ignored) {}
|
||||||
|
|
||||||
|
FileInfo fileInfo = new FileInfo()
|
||||||
|
.setFileName(fileName)
|
||||||
|
.setFileId(fileId)
|
||||||
|
.setSizeStr(sizeStr)
|
||||||
|
.setSize(sizeBytes)
|
||||||
|
.setFileType(fileIcon)
|
||||||
|
.setFileIcon(fileIcon)
|
||||||
|
.setPanType(panType)
|
||||||
|
.setParserUrl(String.format("%s/v2/redirectUrl/%s/%s",
|
||||||
|
getDomainName(), panType, fileShareKey));
|
||||||
|
fileList.add(fileInfo);
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.warn("解析文件行失败: {}", e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
listPromise.complete(fileList);
|
||||||
|
}).onFailure(listPromise::fail);
|
||||||
|
}).onFailure(listPromise::fail);
|
||||||
|
|
||||||
|
return listPromise.future();
|
||||||
|
}
|
||||||
|
|
||||||
|
private String extractQueryParam(String url, String paramName) {
|
||||||
|
if (url == null) return null;
|
||||||
|
int qIdx = url.indexOf('?');
|
||||||
|
if (qIdx < 0) return null;
|
||||||
|
String query = url.substring(qIdx + 1);
|
||||||
|
for (String param : query.split("&")) {
|
||||||
|
int eqIdx = param.indexOf('=');
|
||||||
|
if (eqIdx > 0 && param.substring(0, eqIdx).equals(paramName)) {
|
||||||
|
return param.substring(eqIdx + 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ import java.net.URLEncoder;
|
|||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Base64;
|
import java.util.Base64;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Random;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -24,6 +25,7 @@ public class LeTool extends PanBase {
|
|||||||
private static final String API_URL_PREFIX = "https://lecloud.lenovo.com/mshare/api/clouddiskapi/share/public/v1/";
|
private static final String API_URL_PREFIX = "https://lecloud.lenovo.com/mshare/api/clouddiskapi/share/public/v1/";
|
||||||
private static final String DEFAULT_FILE_TYPE = "file";
|
private static final String DEFAULT_FILE_TYPE = "file";
|
||||||
private static final int FILE_TYPE_DIRECTORY = 0; // 目录类型
|
private static final int FILE_TYPE_DIRECTORY = 0; // 目录类型
|
||||||
|
private static final Random RANDOM = new Random();
|
||||||
|
|
||||||
private static final MultiMap HEADERS;
|
private static final MultiMap HEADERS;
|
||||||
|
|
||||||
@@ -100,8 +102,8 @@ public class LeTool extends PanBase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
String fileId = fileInfoJson.getString("fileId");
|
String fileId = fileInfoJson.getString("fileId");
|
||||||
// 根据文件ID获取跳转链接
|
// 根据文件ID获取跳转链接(随机选择方式,失败自动fallback)
|
||||||
getDownURL(dataKey, fileId);
|
getDownURLWithFallback(dataKey, fileId);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
fail("{}: {}", resJson.getString("errcode"), resJson.getString("errmsg"));
|
fail("{}: {}", resJson.getString("errcode"), resJson.getString("errmsg"));
|
||||||
@@ -260,8 +262,8 @@ public class LeTool extends PanBase {
|
|||||||
String shareId = paramJson.getString("shareId");
|
String shareId = paramJson.getString("shareId");
|
||||||
String fileId = paramJson.getString("fileId");
|
String fileId = paramJson.getString("fileId");
|
||||||
|
|
||||||
// 调用获取下载链接
|
// 调用获取下载链接(随机选择方式,失败自动fallback)
|
||||||
getDownURLForById(shareId, fileId, parsePromise);
|
getDownURLWithFallbackForById(shareId, fileId, parsePromise);
|
||||||
|
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
parsePromise.fail("解析参数失败: " + e.getMessage());
|
parsePromise.fail("解析参数失败: " + e.getMessage());
|
||||||
@@ -304,14 +306,22 @@ public class LeTool extends PanBase {
|
|||||||
}).onFailure(err -> promise.fail(err));
|
}).onFailure(err -> promise.fail(err));
|
||||||
}
|
}
|
||||||
|
|
||||||
private void getDownURL(String key, String fileId) {
|
/**
|
||||||
|
* 通过 packageDownloadWithFileIds 接口获取下载链接
|
||||||
|
* 需要两步:先获取 downloadUrl,再请求 302 跳转
|
||||||
|
*
|
||||||
|
* @param shareId 分享ID
|
||||||
|
* @param fileId 文件ID
|
||||||
|
* @param promise 完成时会写入此 promise
|
||||||
|
*/
|
||||||
|
private void getDownURL(String shareId, String fileId, Promise<String> promise) {
|
||||||
String uuid = UUID.randomUUID().toString();
|
String uuid = UUID.randomUUID().toString();
|
||||||
JsonArray fileIds = JsonArray.of(fileId);
|
JsonArray fileIds = JsonArray.of(fileId);
|
||||||
String apiUrl2 = API_URL_PREFIX + "packageDownloadWithFileIds";
|
String apiUrl = API_URL_PREFIX + "packageDownloadWithFileIds";
|
||||||
// {"fileIds":[123],"shareId":"xxx","browserId":"uuid"}
|
// {"fileIds":[123],"shareId":"xxx","browserId":"uuid"}
|
||||||
client.postAbs(apiUrl2)
|
client.postAbs(apiUrl)
|
||||||
.putHeaders(HEADERS)
|
.putHeaders(HEADERS)
|
||||||
.sendJsonObject(JsonObject.of("fileIds", fileIds, "shareId", key, "browserId", uuid))
|
.sendJsonObject(JsonObject.of("fileIds", fileIds, "shareId", shareId, "browserId", uuid))
|
||||||
.onSuccess(res -> {
|
.onSuccess(res -> {
|
||||||
JsonObject resJson = asJson(res);
|
JsonObject resJson = asJson(res);
|
||||||
if (resJson.containsKey("result")) {
|
if (resJson.containsKey("result")) {
|
||||||
@@ -320,20 +330,107 @@ public class LeTool extends PanBase {
|
|||||||
// 获取重定向链接跳转链接
|
// 获取重定向链接跳转链接
|
||||||
String downloadUrl = dataJson.getString("downloadUrl");
|
String downloadUrl = dataJson.getString("downloadUrl");
|
||||||
if (downloadUrl == null) {
|
if (downloadUrl == null) {
|
||||||
fail("Result JSON数据异常: downloadUrl不存在");
|
promise.fail("Result JSON数据异常: downloadUrl不存在");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// 获取重定向链接跳转链接
|
// 获取重定向链接跳转链接
|
||||||
clientNoRedirects.getAbs(downloadUrl).send()
|
clientNoRedirects.getAbs(downloadUrl).send()
|
||||||
.onSuccess(res2 -> promise.complete(res2.headers().get("Location")))
|
.onSuccess(res2 -> promise.complete(res2.headers().get("Location")))
|
||||||
.onFailure(handleFail(downloadUrl));
|
.onFailure(err -> promise.fail(err));
|
||||||
} else {
|
} else {
|
||||||
fail("{}: {}", resJson.getString("errcode"), resJson.getString("errmsg"));
|
promise.fail(resJson.getString("errcode") + ": " + resJson.getString("errmsg"));
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
fail("Result JSON数据异常: result字段不存在");
|
promise.fail("Result JSON数据异常: result字段不存在");
|
||||||
|
}
|
||||||
|
}).onFailure(err -> promise.fail(err));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 通过 directDownload 接口获取下载链接
|
||||||
|
* 相比 packageDownloadWithFileIds 少一次请求,直接返回302
|
||||||
|
*
|
||||||
|
* @param shareId 分享ID
|
||||||
|
* @param fileId 文件ID
|
||||||
|
* @param promise 完成时会写入此 promise
|
||||||
|
*/
|
||||||
|
private void getDownURLDirect(String shareId, String fileId, Promise<String> promise) {
|
||||||
|
String uuid = UUID.randomUUID().toString();
|
||||||
|
String apiUrl = API_URL_PREFIX + "directDownload"
|
||||||
|
+ "?shareId=" + shareId
|
||||||
|
+ "&fileId=" + fileId
|
||||||
|
+ "&browserId=" + uuid;
|
||||||
|
|
||||||
|
clientNoRedirects.getAbs(apiUrl)
|
||||||
|
.putHeaders(HEADERS)
|
||||||
|
.send()
|
||||||
|
.onSuccess(res -> {
|
||||||
|
String location = res.headers().get("Location");
|
||||||
|
if (location != null && !location.isEmpty()) {
|
||||||
|
promise.complete(location);
|
||||||
|
} else {
|
||||||
|
log.warn("directDownload 返回非302响应: shareId={}, fileId={}, statusCode={}", shareId, fileId, res.statusCode());
|
||||||
|
promise.fail("directDownload 未返回有效的 Location, statusCode=" + res.statusCode());
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.onFailure(err -> {
|
||||||
|
log.warn("directDownload 请求失败: shareId={}, fileId={}, error={}", shareId, fileId, err.getMessage());
|
||||||
|
promise.fail(err);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 随机选择下载方式并带 fallback(用于 parse)
|
||||||
|
* 先随机选择 directDownload 或 packageDownloadWithFileIds,失败则尝试另一个
|
||||||
|
*/
|
||||||
|
private void getDownURLWithFallback(String shareId, String fileId) {
|
||||||
|
boolean useDirect = RANDOM.nextBoolean();
|
||||||
|
log.info("乐云下载方式选择: shareId={}, fileId={}, method={}", shareId, fileId, useDirect ? "directDownload" : "packageDownloadWithFileIds");
|
||||||
|
|
||||||
|
Promise<String> fallbackPromise = Promise.promise();
|
||||||
|
fallbackPromise.future().onSuccess(url -> {
|
||||||
|
promise.complete(url);
|
||||||
|
}).onFailure(err -> {
|
||||||
|
log.warn("乐云第一种下载方式失败,尝试另一种: {}", err.getMessage());
|
||||||
|
if (useDirect) {
|
||||||
|
getDownURL(shareId, fileId, promise);
|
||||||
|
} else {
|
||||||
|
getDownURLDirect(shareId, fileId, promise);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (useDirect) {
|
||||||
|
getDownURLDirect(shareId, fileId, fallbackPromise);
|
||||||
|
} else {
|
||||||
|
getDownURL(shareId, fileId, fallbackPromise);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 随机选择下载方式并带 fallback(用于 parseById)
|
||||||
|
* 先随机选择 directDownload 或 packageDownloadWithFileIds,失败则尝试另一个
|
||||||
|
*/
|
||||||
|
private void getDownURLWithFallbackForById(String shareId, String fileId, Promise<String> promise) {
|
||||||
|
boolean useDirect = RANDOM.nextBoolean();
|
||||||
|
log.info("乐云下载方式选择(parseById): shareId={}, fileId={}, method={}", shareId, fileId, useDirect ? "directDownload" : "packageDownloadWithFileIds");
|
||||||
|
|
||||||
|
Promise<String> fallbackPromise = Promise.promise();
|
||||||
|
fallbackPromise.future().onSuccess(url -> {
|
||||||
|
promise.complete(url);
|
||||||
|
}).onFailure(err -> {
|
||||||
|
log.warn("乐云第一种下载方式失败,尝试另一种: {}", err.getMessage());
|
||||||
|
if (useDirect) {
|
||||||
|
getDownURLForById(shareId, fileId, promise);
|
||||||
|
} else {
|
||||||
|
getDownURLDirect(shareId, fileId, promise);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (useDirect) {
|
||||||
|
getDownURLDirect(shareId, fileId, fallbackPromise);
|
||||||
|
} else {
|
||||||
|
getDownURLForById(shareId, fileId, fallbackPromise);
|
||||||
}
|
}
|
||||||
}).onFailure(handleFail(apiUrl2));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -21,6 +21,8 @@ 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;
|
||||||
@@ -78,12 +80,35 @@ 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()) {
|
if (addr == null || addr.isBlank()) {
|
||||||
addr = "http://127.0.0.1:" + jsonObject.getJsonObject(ConfigConstant.SERVER).getInteger("port", 6400);
|
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);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -125,7 +150,7 @@ public class AppMain {
|
|||||||
/**
|
/**
|
||||||
* 在启动时加载所有已发布的演练场解析器
|
* 在启动时加载所有已发布的演练场解析器
|
||||||
*/
|
*/
|
||||||
private static void loadPlaygroundParsers() {
|
private static void loadPlaygroundParsers(String accessAddr) {
|
||||||
DbService dbService = AsyncServiceUtil.getAsyncServiceInstance(DbService.class);
|
DbService dbService = AsyncServiceUtil.getAsyncServiceInstance(DbService.class);
|
||||||
|
|
||||||
dbService.getPlaygroundParserList().onSuccess(result -> {
|
dbService.getPlaygroundParserList().onSuccess(result -> {
|
||||||
@@ -165,6 +190,8 @@ public class AppMain {
|
|||||||
}
|
}
|
||||||
}).onFailure(e -> {
|
}).onFailure(e -> {
|
||||||
log.error("加载演练场解析器列表失败", e);
|
log.error("加载演练场解析器列表失败", e);
|
||||||
|
}).onComplete(ar -> {
|
||||||
|
log.info("服务已启动,可通过 {} 访问页面", accessAddr);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -74,28 +74,54 @@ cache:
|
|||||||
type: h2db
|
type: h2db
|
||||||
# 默认时长: 单位分钟,大部分网盘未严格验证,建议不要太大
|
# 默认时长: 单位分钟,大部分网盘未严格验证,建议不要太大
|
||||||
defaultDuration: 5
|
defaultDuration: 5
|
||||||
# 具体网盘的缓存配置,如果不加配置则不缓存,每次请求都会请求网盘API,格式:网盘标识: 时长
|
# 具体网盘的缓存配置(单位:分钟)
|
||||||
|
# - 配置 key 且有值(如 le: 2879):使用指定时长
|
||||||
|
# - 配置 key 但无值(如 fc:):使用上面的 defaultDuration
|
||||||
|
# - 未配置的 key:不缓存,每次都请求网盘API
|
||||||
|
# 格式:网盘标识: 时长
|
||||||
duration:
|
duration:
|
||||||
ce: 5
|
# ---- 网盘类 ----
|
||||||
cow: 5
|
ce: 5 # Cloudreve
|
||||||
ec: 5
|
cow: 5 # 奶牛快传
|
||||||
fc:
|
ct: 30 # 城通网盘
|
||||||
fj: 20
|
ec: 5 # 移动云空间
|
||||||
iz: 20
|
fc: # 亿方云
|
||||||
le: 2879
|
fj: 20 # 小飞机网盘
|
||||||
lz: 20
|
fs: # 飞书云盘
|
||||||
qq: 9999999
|
iz: 20 # 蓝奏云优享
|
||||||
qqw: 30
|
kd: # 可道云
|
||||||
ws: 10
|
le: 2879 # 联想乐云
|
||||||
ye: -1
|
lz: 20 # 蓝奏云
|
||||||
mne: 30
|
other: # 其他网盘
|
||||||
mqq: 30
|
p115: 30 # 115网盘
|
||||||
mkg: 30
|
pdb: # Dropbox
|
||||||
p115: 30
|
pcx: # 超星云盘(需要 referer 头)
|
||||||
ct: 30
|
pgd: # Google Drive
|
||||||
qishui_music: 5
|
pic: # iCloud
|
||||||
baidu_photo: 5
|
pod: # OneDrive
|
||||||
migu: 5
|
pvyy: # 微雨云存储
|
||||||
|
pwps: # WPS云文档
|
||||||
|
qk: # 夸克网盘
|
||||||
|
qq: 9999999 # QQ邮箱中转站 (iwx.mail.qq.com/ftn/download)
|
||||||
|
qqsc: # QQ闪传 (qfile.qq.com)
|
||||||
|
qqw: 30 # QQ邮箱云盘 (wx.mail.qq.com/s)
|
||||||
|
uc: # UC网盘
|
||||||
|
ws: 10 # 文叔叔
|
||||||
|
ye: -1 # 123网盘
|
||||||
|
|
||||||
|
# ---- 音乐类 ----
|
||||||
|
baidu_photo: 5 # 百度网盘相册
|
||||||
|
migu: 5 # 咪咕音乐
|
||||||
|
mkg: 30 # 酷狗音乐
|
||||||
|
mkgs: # 酷狗音乐分享短链
|
||||||
|
mkgs2: # 酷狗音乐分享2(share/*.html)
|
||||||
|
mkws: # 酷我音乐分享
|
||||||
|
mmgs: # 咪咕音乐分享短链
|
||||||
|
mne: 30 # 网易云音乐
|
||||||
|
mnes: # 网易云音乐分享短链
|
||||||
|
mqq: 30 # QQ音乐
|
||||||
|
mqqs: # QQ音乐分享短链
|
||||||
|
qishui_music: 5 # 汽水音乐
|
||||||
|
|
||||||
# httpClient静态代理服务器配置(外网代理)
|
# httpClient静态代理服务器配置(外网代理)
|
||||||
proxy:
|
proxy:
|
||||||
|
|||||||
Reference in New Issue
Block a user