mirror of
https://github.com/qaiu/netdisk-fast-download.git
synced 2025-12-16 12:23:03 +00:00
Compare commits
25 Commits
v0.1.8.tes
...
v0.1.9b6
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
94a46d2833 | ||
|
|
1631a0faa1 | ||
|
|
06d5943cb6 | ||
|
|
3095e13676 | ||
|
|
482cbce7e8 | ||
|
|
ef2fc3ab98 | ||
|
|
5b57b05eae | ||
|
|
093579c6f5 | ||
|
|
c2d4990d7f | ||
|
|
40e8380738 | ||
|
|
b716e1e861 | ||
|
|
8432d4952c | ||
|
|
dd8f085f63 | ||
|
|
161ff8d8a3 | ||
|
|
1390cd0104 | ||
|
|
7a02b1e97f | ||
|
|
036f107c90 | ||
|
|
5652383450 | ||
|
|
9a047a5da0 | ||
|
|
8975743a37 | ||
|
|
0e30eafe49 | ||
|
|
7facb62f21 | ||
|
|
30d43cb961 | ||
|
|
c505b17e35 | ||
|
|
080c4c753d |
8
.github/workflows/maven.yml
vendored
8
.github/workflows/maven.yml
vendored
@@ -16,8 +16,9 @@ permissions:
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- '*'
|
||||
branches: [ "main" ]
|
||||
- '*' # 只有推送tag时才会触发构建
|
||||
branches-ignore:
|
||||
- '*' # 排除所有分支的提交
|
||||
paths-ignore:
|
||||
- 'bin/**'
|
||||
- '.github/**'
|
||||
@@ -27,7 +28,8 @@ on:
|
||||
- '*.txt'
|
||||
- '*.md'
|
||||
pull_request:
|
||||
branches: [ "main" ]
|
||||
branches:
|
||||
- "main"
|
||||
|
||||
jobs:
|
||||
build:
|
||||
|
||||
34
.github/workflows/update-release-badge.yml
vendored
Normal file
34
.github/workflows/update-release-badge.yml
vendored
Normal file
@@ -0,0 +1,34 @@
|
||||
name: Update Release Badge
|
||||
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- 'v*' # 可按需调整
|
||||
|
||||
jobs:
|
||||
update-badge:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Checkout repo
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Get latest tag
|
||||
id: get_tag
|
||||
run: echo "tag_name=${GITHUB_REF#refs/tags/}" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Update README badge
|
||||
run: |
|
||||
TAG=${{ steps.get_tag.outputs.tag_name }}
|
||||
BADGE="https://img.shields.io/github/actions/workflow/status/qaiu/netdisk-fast-download/maven.yml?branch=$TAG"
|
||||
echo "Using badge: $BADGE"
|
||||
|
||||
# 替换 README 中 badge 行(标记行需特殊注释)
|
||||
sed -i -E "s#(!\[release-badge\]\(.*\))##" README.md
|
||||
|
||||
- name: Commit and push
|
||||
run: |
|
||||
git config user.name "github-actions[bot]"
|
||||
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
|
||||
git commit -am "🔄 update release badge for ${{ steps.get_tag.outputs.tag_name }}" || echo "No changes"
|
||||
git push
|
||||
37
README.md
37
README.md
@@ -3,7 +3,7 @@
|
||||
</p>
|
||||
|
||||
<p align="center">
|
||||
<a href="https://github.com/qaiu/netdisk-fast-download/actions/workflows/maven.yml"><img src="https://github.com/qaiu/netdisk-fast-download/actions/workflows/maven.yml/badge.svg?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.9b2&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://vertx-china.github.io"><img src="https://img.shields.io/badge/vert.x-4.5.6-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>
|
||||
@@ -76,23 +76,28 @@ main分支依赖JDK17, 提供了JDK11分支[main-jdk11](https://github.com/qaiu/
|
||||
|
||||
API规则:
|
||||
> 建议使用UrlEncode编码分享链接
|
||||
```
|
||||
|
||||
1. 解析并自动302跳转
|
||||
http://your_host/parser?url=分享链接&pwd=xxx
|
||||
或者 http://your_host/parser?url=UrlEncode(分享链接)&pwd=xxx
|
||||
http://your_host/parser?url=分享链接&pwd=xxx
|
||||
http://your_host/parser?url=UrlEncode(分享链接)&pwd=xxx
|
||||
http://your_host/d/网盘标识/分享key@分享密码
|
||||
2. 获取解析后的直链--JSON格式
|
||||
http://your_host/json/parser?url=分享链接&pwd=xxx
|
||||
http://your_host/json/parser?url=分享链接&pwd=xxx
|
||||
http://your_host/json/网盘标识/分享key@分享密码
|
||||
3. 文件夹解析v0.1.8fixed3新增
|
||||
http://your_host/json/getFileList?url=分享链接&pwd=xxx
|
||||
```
|
||||
|
||||
|
||||
### json接口说明
|
||||
|
||||
1. 文件解析:/json/parser?url=分享链接&pwd=xxx
|
||||
|
||||
json返回数据格式示例:
|
||||
`shareKey`: 全局分享key
|
||||
`directLink`: 下载链接
|
||||
`cacheHit`: 是否为缓存链接
|
||||
`expires`: 缓存到期时间
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
@@ -139,6 +144,8 @@ json返回数据格式示例:
|
||||
}
|
||||
```
|
||||
3. 文件夹解析(仅支持蓝奏云/蓝奏优享/小飞机网盘)
|
||||
/v2/getFileList?url=分享链接&pwd=分享密码
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
@@ -151,15 +158,15 @@ json返回数据格式示例:
|
||||
"fileIcon": null,
|
||||
"size": 999,
|
||||
"sizeStr": "999 M",
|
||||
"fileType": "apk",
|
||||
"fileType": "file/folder",
|
||||
"filePath": null,
|
||||
"createTime": "17 小时前",
|
||||
"updateTime": null,
|
||||
"createBy": null,
|
||||
"description": null,
|
||||
"downloadCount": null,
|
||||
"downloadCount": 下载次数,
|
||||
"panType": "lz",
|
||||
"parserUrl": "下载链接",
|
||||
"parserUrl": "下载链接/文件夹链接",
|
||||
"extParameters": null
|
||||
}
|
||||
]
|
||||
@@ -260,15 +267,15 @@ mkdir -p netdisk-fast-download
|
||||
cd netdisk-fast-download
|
||||
|
||||
# 拉取镜像
|
||||
docker pull ghcr.io/qaiu/netdisk-fast-download:main
|
||||
docker pull ghcr.io/qaiu/netdisk-fast-download:lastest
|
||||
|
||||
# 复制配置文件(或下载仓库web-service\src\main\resources)
|
||||
docker create --name netdisk-fast-download ghcr.io/qaiu/netdisk-fast-download:main
|
||||
docker create --name netdisk-fast-download ghcr.io/qaiu/netdisk-fast-download:lastest
|
||||
docker cp netdisk-fast-download:/app/resources ./resources
|
||||
docker rm netdisk-fast-download
|
||||
|
||||
# 启动容器
|
||||
docker run -d -it --name netdisk-fast-download -p 6401:6401 --restart unless-stopped -e TZ=Asia/Shanghai -v ./resources:/app/resources -v ./db:/app/db -v ./logs:/app/logs ghcr.io/qaiu/netdisk-fast-download:main
|
||||
docker run -d -it --name netdisk-fast-download -p 6401:6401 --restart unless-stopped -e TZ=Asia/Shanghai -v ./resources:/app/resources -v ./db:/app/db -v ./logs:/app/logs ghcr.io/qaiu/netdisk-fast-download:lastest
|
||||
|
||||
# 反代6401端口
|
||||
|
||||
@@ -283,15 +290,15 @@ mkdir -p netdisk-fast-download
|
||||
cd netdisk-fast-download
|
||||
|
||||
# 拉取镜像
|
||||
docker pull ghcr.nju.edu.cn/qaiu/netdisk-fast-download:main
|
||||
docker pull ghcr.nju.edu.cn/qaiu/netdisk-fast-download:lastest
|
||||
|
||||
# 复制配置文件(或下载仓库web-service\src\main\resources)
|
||||
docker create --name netdisk-fast-download ghcr.nju.edu.cn/qaiu/netdisk-fast-download:main
|
||||
docker create --name netdisk-fast-download ghcr.nju.edu.cn/qaiu/netdisk-fast-download:lastest
|
||||
docker cp netdisk-fast-download:/app/resources ./resources
|
||||
docker rm netdisk-fast-download
|
||||
|
||||
# 启动容器
|
||||
docker run -d -it --name netdisk-fast-download -p 6401:6401 --restart unless-stopped -e TZ=Asia/Shanghai -v ./resources:/app/resources -v ./db:/app/db -v ./logs:/app/logs ghcr.nju.edu.cn/qaiu/netdisk-fast-download:main
|
||||
docker run -d -it --name netdisk-fast-download -p 6401:6401 --restart unless-stopped -e TZ=Asia/Shanghai -v ./resources:/app/resources -v ./db:/app/db -v ./logs:/app/logs ghcr.nju.edu.cn/qaiu/netdisk-fast-download:lastest
|
||||
|
||||
# 反代6401端口
|
||||
|
||||
|
||||
86
bin/nfd-install.sh
Normal file
86
bin/nfd-install.sh
Normal file
@@ -0,0 +1,86 @@
|
||||
#!/bin/bash
|
||||
set -e
|
||||
|
||||
# ----------- 配置区域 ------------
|
||||
# JRE 下载目录
|
||||
JRE_DIR="/opt/custom-jre17"
|
||||
# 使用阿里云镜像下载 JRE(OpenJDK 17)
|
||||
JRE_TARBALL_URL="https://mirrors.tuna.tsinghua.edu.cn/Adoptium/17/jre/x64/linux/OpenJDK17U-jre_x64_linux_hotspot_17.0.15_6.tar.gz"
|
||||
|
||||
# ZIP 文件下载相关
|
||||
ZIP_URL="http://www.722shop.top:6401/parser?url="
|
||||
ZIP_DEST_DIR="/opt/target-zip"
|
||||
ZIP_FILE_NAME="nfd.zip"
|
||||
# --------------------------------
|
||||
|
||||
# 创建目录
|
||||
mkdir -p "$JRE_DIR"
|
||||
mkdir -p "$ZIP_DEST_DIR"
|
||||
|
||||
# -------- 检查 unzip 是否存在 --------
|
||||
if ! command -v unzip >/dev/null 2>&1; then
|
||||
echo "unzip 未安装,正在安装..."
|
||||
|
||||
if command -v apt-get >/dev/null 2>&1; then
|
||||
apt-get update && apt-get install -y unzip
|
||||
elif command -v yum >/dev/null 2>&1; then
|
||||
yum install -y unzip
|
||||
elif command -v dnf >/dev/null 2>&1; then
|
||||
dnf install -y unzip
|
||||
else
|
||||
echo "不支持的包管理器,无法自动安装 unzip,请手动安装后重试。"
|
||||
exit 1
|
||||
fi
|
||||
else
|
||||
echo "unzip 已安装"
|
||||
fi
|
||||
|
||||
# -------- 下载并解压 JRE --------
|
||||
echo "下载 JRE 17 到 $JRE_DIR..."
|
||||
curl -L "$JRE_TARBALL_URL" -o "$JRE_DIR/jre17.tar.gz"
|
||||
|
||||
echo "解压 JRE..."
|
||||
tar -xzf "$JRE_DIR/jre17.tar.gz" -C "$JRE_DIR" --strip-components=1
|
||||
rm "$JRE_DIR/jre17.tar.gz"
|
||||
echo "JRE 解压完成"
|
||||
|
||||
# -------- 下载 ZIP 文件 --------
|
||||
ZIP_PATH="$ZIP_DEST_DIR/$ZIP_FILE_NAME"
|
||||
echo "下载 ZIP 文件到 $ZIP_PATH..."
|
||||
curl -L "$ZIP_URL" -o "$ZIP_PATH"
|
||||
|
||||
# -------- 解压 ZIP 文件 --------
|
||||
echo "解压 ZIP 文件到 $ZIP_DEST_DIR..."
|
||||
unzip -o "$ZIP_PATH" -d "$ZIP_DEST_DIR"
|
||||
echo "解压完成"
|
||||
|
||||
# -------- 启动 JAR 程序 --------
|
||||
echo "进入 JAR 目录并后台运行程序..."
|
||||
|
||||
JAR_DIR="/opt/target-zip/netdisk-fast-download"
|
||||
JAR_FILE="netdisk-fast-download.jar"
|
||||
JAVA_BIN="$JRE_DIR/bin/java"
|
||||
LOG_FILE="$JAR_DIR/app.log"
|
||||
|
||||
if [ ! -d "$JAR_DIR" ]; then
|
||||
echo "[错误] 找不到 JAR 目录: $JAR_DIR"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
cd "$JAR_DIR"
|
||||
|
||||
if [ ! -f "$JAR_FILE" ]; then
|
||||
echo "[错误] 找不到 JAR 文件: $JAR_FILE"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ ! -x "$JAVA_BIN" ]; then
|
||||
echo "[错误] 找不到可执行的 java: $JAVA_BIN"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# 后台运行,日志记录
|
||||
nohup "$JAVA_BIN" -jar "$JAR_FILE" > "$LOG_FILE" 2>&1 &
|
||||
|
||||
echo "程序已在后台启动 ✅"
|
||||
echo "日志路径: $LOG_FILE"
|
||||
@@ -16,17 +16,24 @@ public interface BeforeInterceptor extends Handler<RoutingContext> {
|
||||
default Handler<RoutingContext> doHandle() {
|
||||
|
||||
return ctx -> {
|
||||
ctx.put(IS_NEXT, false);
|
||||
BeforeInterceptor.this.handle(ctx);
|
||||
if (!(Boolean) ctx.get(IS_NEXT) && !ctx.response().ended()) {
|
||||
sendError(ctx, 403);
|
||||
// 加同步锁
|
||||
synchronized (BeforeInterceptor.class) {
|
||||
ctx.put(IS_NEXT, false);
|
||||
BeforeInterceptor.this.handle(ctx);
|
||||
if (!(Boolean) ctx.get(IS_NEXT) && !ctx.response().ended()) {
|
||||
sendError(ctx, 403);
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
default void doNext(RoutingContext context) {
|
||||
context.put(IS_NEXT, true);
|
||||
context.next();
|
||||
// 设置上下文状态为可以继续执行
|
||||
// 添加同步锁保障多线程下执行时序
|
||||
synchronized (BeforeInterceptor.class) {
|
||||
context.put(IS_NEXT, true);
|
||||
context.next();
|
||||
}
|
||||
}
|
||||
|
||||
void handle(RoutingContext context);
|
||||
|
||||
@@ -69,6 +69,9 @@ public class FileInfo {
|
||||
*/
|
||||
private String parserUrl;
|
||||
|
||||
//预览地址
|
||||
private String previewUrl;
|
||||
|
||||
/**
|
||||
* 扩展参数
|
||||
*/
|
||||
@@ -199,6 +202,13 @@ public class FileInfo {
|
||||
this.parserUrl = parserUrl;
|
||||
return this;
|
||||
}
|
||||
public String getPreviewUrl() {
|
||||
return previewUrl;
|
||||
}
|
||||
public FileInfo setPreviewUrl(String previewUrl) {
|
||||
this.previewUrl = previewUrl;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Map<String, Object> getExtParameters() {
|
||||
return extParameters;
|
||||
|
||||
@@ -15,6 +15,7 @@ public class ShareLinkInfo {
|
||||
|
||||
/**
|
||||
* 其他参数预定义
|
||||
* dirId: 目录ID 传入
|
||||
* auths: 认证相关 传入
|
||||
* UA: 浏览器请求头 传入
|
||||
* fileInfo: 解析成功的文件信息对象 传出
|
||||
|
||||
@@ -19,7 +19,7 @@ public interface IPanTool {
|
||||
*/
|
||||
default Future<List<FileInfo>> parseFileList() {
|
||||
Promise<List<FileInfo>> promise = Promise.promise();
|
||||
promise.complete();
|
||||
promise.fail("Not implemented yet");
|
||||
return promise.future();
|
||||
}
|
||||
|
||||
@@ -29,7 +29,7 @@ public interface IPanTool {
|
||||
*/
|
||||
default Future<String> parseById() {
|
||||
Promise<String> promise = Promise.promise();
|
||||
promise.complete();
|
||||
promise.complete("Not implemented yet");
|
||||
return promise.future();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -116,12 +116,21 @@ public abstract class PanBase implements IPanTool {
|
||||
*/
|
||||
protected void fail(Throwable t, String errorMsg, Object... args) {
|
||||
try {
|
||||
// 判断是否已经完成
|
||||
if (promise.future().isComplete()) {
|
||||
log.warn("Promise 已经完成, 无法再次失败: {}, {}", errorMsg, promise.future().cause());
|
||||
return;
|
||||
}
|
||||
String s = String.format(errorMsg.replaceAll("\\{}", "%s"), args);
|
||||
log.error("解析异常: " + s, t.fillInStackTrace());
|
||||
promise.fail(baseMsg() + ": 解析异常: " + s + " -> " + t);
|
||||
} catch (Exception e) {
|
||||
log.error("ErrorMsg format fail. The parameter has been discarded", e);
|
||||
log.error("解析异常: " + errorMsg, t.fillInStackTrace());
|
||||
if (promise.future().isComplete()) {
|
||||
log.warn("ErrorMsg format. Promise 已经完成, 无法再次失败: {}", errorMsg);
|
||||
return;
|
||||
}
|
||||
promise.fail(baseMsg() + ": 解析异常: " + errorMsg + " -> " + t);
|
||||
}
|
||||
}
|
||||
@@ -134,9 +143,18 @@ public abstract class PanBase implements IPanTool {
|
||||
*/
|
||||
protected void fail(String errorMsg, Object... args) {
|
||||
try {
|
||||
// 判断是否已经完成
|
||||
if (promise.future().isComplete()) {
|
||||
log.warn("Promise 已经完成, 无法再次失败: {}, {}", errorMsg, promise.future().cause());
|
||||
return;
|
||||
}
|
||||
String s = String.format(errorMsg.replaceAll("\\{}", "%s"), args);
|
||||
promise.fail(baseMsg() + " - 解析异常: " + s);
|
||||
} catch (Exception e) {
|
||||
if (promise.future().isComplete()) {
|
||||
log.warn("ErrorMsg format. Promise 已经完成, 无法再次失败: {}", errorMsg);
|
||||
return;
|
||||
}
|
||||
log.error("ErrorMsg format fail. The parameter has been discarded", e);
|
||||
promise.fail(baseMsg() + " - 解析异常: " + errorMsg);
|
||||
}
|
||||
|
||||
@@ -14,6 +14,7 @@ import io.vertx.core.json.JsonArray;
|
||||
import io.vertx.core.json.JsonObject;
|
||||
import io.vertx.ext.web.client.HttpRequest;
|
||||
import io.vertx.uritemplate.UriTemplate;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Base64;
|
||||
@@ -89,6 +90,9 @@ public class FjTool extends PanBase {
|
||||
final String shareId = shareLinkInfo.getShareKey();
|
||||
|
||||
// 24.5.12 飞机盘 规则修改 需要固定UUID先请求会员接口, 再请求后续接口
|
||||
String url = StringUtils.isBlank(shareLinkInfo.getSharePassword()) ? FIRST_REQUEST_URL
|
||||
: (FIRST_REQUEST_URL + "&code=" + shareLinkInfo.getSharePassword());
|
||||
|
||||
client.postAbs(UriTemplate.of(VIP_REQUEST_URL))
|
||||
.setTemplateParam("uuid", uuid)
|
||||
.setTemplateParam("ts", tsEncode)
|
||||
@@ -96,7 +100,7 @@ public class FjTool extends PanBase {
|
||||
|
||||
// 第一次请求 获取文件信息
|
||||
// POST https://api.feijipan.com/ws/recommend/list?devType=6&devModel=Chrome&extra=2&shareId=146731&type=0&offset=1&limit=60
|
||||
client.postAbs(UriTemplate.of(FIRST_REQUEST_URL))
|
||||
client.postAbs(UriTemplate.of(url))
|
||||
.putHeaders(header)
|
||||
.setTemplateParam("shareId", shareId)
|
||||
.setTemplateParam("uuid", uuid)
|
||||
@@ -107,13 +111,21 @@ public class FjTool extends PanBase {
|
||||
fail(FIRST_REQUEST_URL + " 返回异常: " + resJson);
|
||||
return;
|
||||
}
|
||||
if (resJson.getJsonArray("list").size() == 0) {
|
||||
if (resJson.getJsonArray("list").isEmpty()) {
|
||||
fail(FIRST_REQUEST_URL + " 解析文件列表为空: " + resJson);
|
||||
return;
|
||||
}
|
||||
if (!resJson.containsKey("list") || resJson.getJsonArray("list").isEmpty()) {
|
||||
fail(FIRST_REQUEST_URL + " 解析文件列表为空: " + resJson);
|
||||
return;
|
||||
}
|
||||
// 文件Id
|
||||
JsonObject fileInfo = resJson.getJsonArray("list").getJsonObject(0);
|
||||
// 如果是目录返回目录ID
|
||||
if (!fileInfo.containsKey("fileList") || fileInfo.getJsonArray("fileList").isEmpty()) {
|
||||
fail(FIRST_REQUEST_URL + " 文件列表为空: " + fileInfo);
|
||||
return;
|
||||
}
|
||||
JsonObject fileList = fileInfo.getJsonArray("fileList").getJsonObject(0);
|
||||
if (fileList.getInteger("fileType") == 2) {
|
||||
promise.complete(fileList.getInteger("folderId").toString());
|
||||
@@ -158,104 +170,112 @@ public class FjTool extends PanBase {
|
||||
Promise<List<FileInfo>> promise = Promise.promise();
|
||||
|
||||
String shareId = shareLinkInfo.getShareKey(); // String.valueOf(AESUtils.idEncrypt(dataKey));
|
||||
|
||||
// 如果参数里的目录ID不为空,则直接解析目录
|
||||
String dirId = (String) shareLinkInfo.getOtherParam().get("dirId");
|
||||
if (dirId != null && !dirId.isEmpty()) {
|
||||
uuid = shareLinkInfo.getOtherParam().get("uuid").toString();
|
||||
parserDir(dirId, shareId, promise);
|
||||
return promise.future();
|
||||
}
|
||||
parse().onSuccess(id -> {
|
||||
// 拿到目录ID
|
||||
client.postAbs(UriTemplate.of(FILE_LIST_URL))
|
||||
.putHeaders(header)
|
||||
.setTemplateParam("shareId", shareId)
|
||||
.setTemplateParam("uuid", uuid)
|
||||
.setTemplateParam("ts", tsEncode)
|
||||
.setTemplateParam("folderId", id)
|
||||
.send().onSuccess(res -> {
|
||||
JsonObject jsonObject = asJson(res);
|
||||
System.out.println(jsonObject.encodePrettily());
|
||||
/*
|
||||
{
|
||||
"iconId" : 13,
|
||||
"fileName" : "酷我音乐车机版 6.4.2.20.apk",
|
||||
"fileSaves" : 52,
|
||||
"fileStars" : 5.0,
|
||||
"type" : 1,
|
||||
"userId" : 1392902,
|
||||
"fileComments" : 0,
|
||||
"fileSize" : 68854,
|
||||
"fileIcon" : "https://d.feijix.com/storage/files/icon/2024/06/08/7/8146637/6534494874910391.gz?t=67a5ea7c&rlimit=20&us=nMfuftjBN5&sign=f72be03007a301217f90dcc20333bd9a",
|
||||
"updTime" : "2024-06-10 17:26:53",
|
||||
"sortId" : 1487918143,
|
||||
"name" : "酷我音乐车机版 6.4.2.20.apk",
|
||||
"fileDownloads" : 109,
|
||||
"fileUrl" : null,
|
||||
"fileLikes" : 0,
|
||||
"fileType" : 1,
|
||||
"fileId" : 1487918143
|
||||
}
|
||||
*/
|
||||
JsonArray list = jsonObject.getJsonArray("list");
|
||||
ArrayList<FileInfo> result = new ArrayList<>();
|
||||
list.forEach(item->{
|
||||
JsonObject fileJson = (JsonObject) item;
|
||||
FileInfo fileInfo = new FileInfo();
|
||||
parserDir(id, shareId, promise);
|
||||
}).onFailure(failRes -> {
|
||||
log.error("解析目录失败: {}", failRes.getMessage());
|
||||
promise.fail(failRes);
|
||||
});
|
||||
return promise.future();
|
||||
}
|
||||
|
||||
// 映射已知字段fileInfo
|
||||
String fileId = fileJson.getString("fileId");
|
||||
String userId = fileJson.getString("userId");
|
||||
private void parserDir(String id, String shareId, Promise<List<FileInfo>> promise) {
|
||||
log.debug("开始解析目录: {}, shareId: {}, uuid: {}, ts: {}", id, shareId, uuid, tsEncode);
|
||||
// 开始解析目录: 164312216, shareId: bPMsbg5K, uuid: 0fmVWTx2Ea4zFwkpd7KXf, ts: 20865d7b7f00828279f437cd1f097860
|
||||
// 拿到目录ID
|
||||
client.postAbs(UriTemplate.of(FILE_LIST_URL))
|
||||
.putHeaders(header)
|
||||
.setTemplateParam("shareId", shareId)
|
||||
.setTemplateParam("uuid", uuid)
|
||||
.setTemplateParam("ts", tsEncode)
|
||||
.setTemplateParam("folderId", id)
|
||||
.send().onSuccess(res -> {
|
||||
JsonObject jsonObject = asJson(res);
|
||||
System.out.println(jsonObject.encodePrettily());
|
||||
JsonArray list = jsonObject.getJsonArray("list");
|
||||
ArrayList<FileInfo> result = new ArrayList<>();
|
||||
list.forEach(item->{
|
||||
JsonObject fileJson = (JsonObject) item;
|
||||
FileInfo fileInfo = new FileInfo();
|
||||
|
||||
// 其他参数
|
||||
long nowTs2 = System.currentTimeMillis();
|
||||
String tsEncode2 = AESUtils.encrypt2Hex(Long.toString(nowTs2));
|
||||
String fidEncode = AESUtils.encrypt2Hex(fileId + "|" + userId);
|
||||
String auth = AESUtils.encrypt2Hex(fileId + "|" + nowTs2);
|
||||
// 映射已知字段fileInfo
|
||||
String fileId = fileJson.getString("fileId");
|
||||
String userId = fileJson.getString("userId");
|
||||
|
||||
// 回传用到的参数
|
||||
//"fidEncode", paramJson.getString("fidEncode"))
|
||||
//"uuid", paramJson.getString("uuid"))
|
||||
//"ts", paramJson.getString("ts"))
|
||||
//"auth", paramJson.getString("auth"))
|
||||
//"shareId", paramJson.getString("shareId"))
|
||||
JsonObject entries = JsonObject.of(
|
||||
"fidEncode", fidEncode,
|
||||
"uuid", uuid,
|
||||
"ts", tsEncode2,
|
||||
"auth", auth,
|
||||
"shareId", shareId);
|
||||
byte[] encode = Base64.getEncoder().encode(entries.encode().getBytes());
|
||||
String param = new String(encode);
|
||||
// 其他参数
|
||||
long nowTs2 = System.currentTimeMillis();
|
||||
String tsEncode2 = AESUtils.encrypt2Hex(Long.toString(nowTs2));
|
||||
String fidEncode = AESUtils.encrypt2Hex(fileId + "|" + userId);
|
||||
String auth = AESUtils.encrypt2Hex(fileId + "|" + nowTs2);
|
||||
|
||||
long fileSize = fileJson.getLong("fileSize") * 1024;
|
||||
fileInfo.setFileName(fileJson.getString("fileName"))
|
||||
.setFileId(fileJson.getString("fileId"))
|
||||
.setCreateTime(fileJson.getString("createTime"))
|
||||
.setFileType(fileJson.getString("fileType"))
|
||||
.setSize(fileSize)
|
||||
.setSizeStr(FileSizeConverter.convertToReadableSize(fileSize))
|
||||
// 回传用到的参数
|
||||
//"fidEncode", paramJson.getString("fidEncode"))
|
||||
//"uuid", paramJson.getString("uuid"))
|
||||
//"ts", paramJson.getString("ts"))
|
||||
//"auth", paramJson.getString("auth"))
|
||||
//"shareId", paramJson.getString("shareId"))
|
||||
JsonObject entries = JsonObject.of(
|
||||
"fidEncode", fidEncode,
|
||||
"uuid", uuid,
|
||||
"ts", tsEncode2,
|
||||
"auth", auth,
|
||||
"shareId", shareId);
|
||||
byte[] encode = Base64.getEncoder().encode(entries.encode().getBytes());
|
||||
String param = new String(encode);
|
||||
|
||||
if (fileJson.getInteger("fileType") == 2) {
|
||||
// 如果是目录
|
||||
fileInfo.setFileName(fileJson.getString("name"))
|
||||
.setFileId(fileJson.getString("folderId"))
|
||||
.setCreateTime(fileJson.getString("updTime"))
|
||||
.setFileType("folder")
|
||||
.setSize(0L)
|
||||
.setSizeStr("0B")
|
||||
.setCreateBy(fileJson.getLong("userId").toString())
|
||||
.setDownloadCount(fileJson.getInteger("fileDownloads"))
|
||||
.setCreateTime(fileJson.getString("updTime"))
|
||||
.setFileIcon(fileJson.getString("fileIcon"))
|
||||
.setPanType(shareLinkInfo.getType())
|
||||
.setParserUrl(String.format("%s/v2/redirectUrl/%s/%s", getDomainName(),
|
||||
shareLinkInfo.getType(), param));
|
||||
// 设置目录解析的URL
|
||||
.setParserUrl(String.format("%s/v2/getFileList?url=%s&dirId=%s&uuid=%s", getDomainName(),
|
||||
shareLinkInfo.getShareUrl(), fileJson.getString("folderId"), uuid));
|
||||
result.add(fileInfo);
|
||||
});
|
||||
promise.complete(result);
|
||||
return;
|
||||
}
|
||||
long fileSize = fileJson.getLong("fileSize") * 1024;
|
||||
fileInfo.setFileName(fileJson.getString("fileName"))
|
||||
.setFileId(fileJson.getString("fileId"))
|
||||
.setCreateTime(fileJson.getString("createTime"))
|
||||
.setFileType("file")
|
||||
.setSize(fileSize)
|
||||
.setSizeStr(FileSizeConverter.convertToReadableSize(fileSize))
|
||||
.setCreateBy(fileJson.getLong("userId").toString())
|
||||
.setDownloadCount(fileJson.getInteger("fileDownloads"))
|
||||
.setCreateTime(fileJson.getString("updTime"))
|
||||
.setFileIcon(fileJson.getString("fileIcon"))
|
||||
.setPanType(shareLinkInfo.getType())
|
||||
.setParserUrl(String.format("%s/v2/redirectUrl/%s/%s", getDomainName(),
|
||||
shareLinkInfo.getType(), param))
|
||||
.setPreviewUrl(String.format("%s/v2/viewUrl/%s/%s", getDomainName(),
|
||||
shareLinkInfo.getType(), param));
|
||||
result.add(fileInfo);
|
||||
});
|
||||
});
|
||||
return promise.future();
|
||||
promise.complete(result);
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public Future<String> parseById() {
|
||||
// 第二次请求
|
||||
JsonObject paramJson = (JsonObject)shareLinkInfo.getOtherParam().get("paramJson");
|
||||
|
||||
// clientNoRedirects.getAbs(UriTemplate.of(SECOND_REQUEST_URL))
|
||||
// .putHeaders(header)
|
||||
// .setTemplateParam("fidEncode", fidEncode)
|
||||
// .setTemplateParam("uuid", uuid)
|
||||
// .setTemplateParam("ts", tsEncode2)
|
||||
// .setTemplateParam("auth", auth)
|
||||
// .setTemplateParam("dataKey", shareId)
|
||||
|
||||
clientNoRedirects.getAbs(UriTemplate.of(SECOND_REQUEST_URL))
|
||||
.setTemplateParam("fidEncode", paramJson.getString("fidEncode"))
|
||||
.setTemplateParam("uuid", paramJson.getString("uuid"))
|
||||
|
||||
@@ -12,6 +12,7 @@ import io.vertx.core.Promise;
|
||||
import io.vertx.core.json.JsonArray;
|
||||
import io.vertx.core.json.JsonObject;
|
||||
import io.vertx.uritemplate.UriTemplate;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
@@ -77,17 +78,15 @@ public class IzTool extends PanBase {
|
||||
|
||||
// 第一次请求 获取文件信息
|
||||
// POST https://api.ilanzou.com/ws/recommend/list?devType=6&devModel=Chrome&extra=2&shareId=146731&type=0&offset=1&limit=60
|
||||
|
||||
String url = StringUtils.isBlank(shareLinkInfo.getSharePassword()) ? FIRST_REQUEST_URL
|
||||
: (FIRST_REQUEST_URL + "&code=" + shareLinkInfo.getSharePassword());
|
||||
client.postAbs(UriTemplate.of(VIP_REQUEST_URL))
|
||||
.setTemplateParam("uuid", uuid)
|
||||
.setTemplateParam("ts", tsEncode)
|
||||
.send().onSuccess(r0 -> { // 忽略res
|
||||
// 第一次请求 获取文件信息
|
||||
// POST https://api.feijipan.com/ws/recommend/list?devType=6&devModel=Chrome&extra=2&shareId=146731&type=0&offset=1&limit=60
|
||||
client.postAbs(UriTemplate.of(
|
||||
shareLinkInfo.getSharePassword() == null ?
|
||||
FIRST_REQUEST_URL : (FIRST_REQUEST_URL + "&code=" + shareLinkInfo.getSharePassword()))
|
||||
)
|
||||
client.postAbs(UriTemplate.of(url))
|
||||
.putHeaders(header)
|
||||
.setTemplateParam("shareId", shareId)
|
||||
.setTemplateParam("uuid", uuid)
|
||||
@@ -98,14 +97,21 @@ public class IzTool extends PanBase {
|
||||
fail(FIRST_REQUEST_URL + " 返回异常: " + resJson);
|
||||
return;
|
||||
}
|
||||
if (resJson.getJsonArray("list").size() == 0) {
|
||||
if (resJson.getJsonArray("list").isEmpty()) {
|
||||
fail(FIRST_REQUEST_URL + " 解析文件列表为空: " + resJson);
|
||||
return;
|
||||
}
|
||||
if (!resJson.containsKey("list") || resJson.getJsonArray("list").isEmpty()) {
|
||||
fail(FIRST_REQUEST_URL + " 解析文件列表为空: " + resJson);
|
||||
return;
|
||||
}
|
||||
// 文件Id
|
||||
JsonObject fileInfo = resJson.getJsonArray("list").getJsonObject(0);
|
||||
|
||||
// 如果是目录返回目录ID
|
||||
if (!fileInfo.containsKey("fileList") || fileInfo.getJsonArray("fileList").isEmpty()) {
|
||||
fail(FIRST_REQUEST_URL + " 文件列表为空: " + fileInfo);
|
||||
return;
|
||||
}
|
||||
JsonObject fileList = fileInfo.getJsonArray("fileList").getJsonObject(0);
|
||||
if (fileList.getInteger("fileType") == 2) {
|
||||
promise.complete(fileList.getInteger("folderId").toString());
|
||||
@@ -143,65 +149,102 @@ public class IzTool extends PanBase {
|
||||
Promise<List<FileInfo>> promise = Promise.promise();
|
||||
|
||||
String shareId = shareLinkInfo.getShareKey(); // String.valueOf(AESUtils.idEncrypt(dataKey));
|
||||
|
||||
// 如果参数里的目录ID不为空,则直接解析目录
|
||||
String dirId = (String) shareLinkInfo.getOtherParam().get("dirId");
|
||||
if (dirId != null && !dirId.isEmpty()) {
|
||||
uuid = shareLinkInfo.getOtherParam().get("uuid").toString();
|
||||
parserDir(dirId, shareId, promise);
|
||||
return promise.future();
|
||||
}
|
||||
parse().onSuccess(id -> {
|
||||
// 拿到目录ID
|
||||
client.postAbs(UriTemplate.of(FILE_LIST_URL))
|
||||
.putHeaders(header)
|
||||
.setTemplateParam("shareId", shareId)
|
||||
.setTemplateParam("uuid", uuid)
|
||||
.setTemplateParam("ts", tsEncode)
|
||||
.setTemplateParam("folderId", id)
|
||||
.send().onSuccess(res -> {
|
||||
JsonObject jsonObject = asJson(res);
|
||||
System.out.println(jsonObject.encodePrettily());
|
||||
JsonArray list = jsonObject.getJsonArray("list");
|
||||
ArrayList<FileInfo> result = new ArrayList<>();
|
||||
list.forEach(item->{
|
||||
JsonObject fileJson = (JsonObject) item;
|
||||
FileInfo fileInfo = new FileInfo();
|
||||
parserDir(id, shareId, promise);
|
||||
}).onFailure(failRes -> {
|
||||
log.error("解析目录失败: {}", failRes.getMessage());
|
||||
promise.fail(failRes);
|
||||
});
|
||||
return promise.future();
|
||||
}
|
||||
|
||||
// 映射已知字段
|
||||
String fileId = fileJson.getString("fileId");
|
||||
String userId = fileJson.getString("userId");
|
||||
private void parserDir(String id, String shareId, Promise<List<FileInfo>> promise) {
|
||||
log.debug("开始解析目录: {}, shareId: {}, uuid: {}, ts: {}", id, shareId, uuid, tsEncode);
|
||||
// 开始解析目录: 164312216, shareId: bPMsbg5K, uuid: 0fmVWTx2Ea4zFwkpd7KXf, ts: 20865d7b7f00828279f437cd1f097860
|
||||
// 拿到目录ID
|
||||
client.postAbs(UriTemplate.of(FILE_LIST_URL))
|
||||
.putHeaders(header)
|
||||
.setTemplateParam("shareId", shareId)
|
||||
.setTemplateParam("uuid", uuid)
|
||||
.setTemplateParam("ts", tsEncode)
|
||||
.setTemplateParam("folderId", id)
|
||||
.send().onSuccess(res -> {
|
||||
JsonObject jsonObject = asJson(res);
|
||||
System.out.println(jsonObject.encodePrettily());
|
||||
JsonArray list = jsonObject.getJsonArray("list");
|
||||
ArrayList<FileInfo> result = new ArrayList<>();
|
||||
list.forEach(item->{
|
||||
JsonObject fileJson = (JsonObject) item;
|
||||
FileInfo fileInfo = new FileInfo();
|
||||
|
||||
// 回传用到的参数
|
||||
//"fidEncode", paramJson.getString("fidEncode"))
|
||||
//"uuid", paramJson.getString("uuid"))
|
||||
//"ts", paramJson.getString("ts"))
|
||||
//"auth", paramJson.getString("auth"))
|
||||
//"shareId", paramJson.getString("shareId"))
|
||||
String fidEncode = AESUtils.encrypt2HexIz(fileId + "|" + userId);
|
||||
String auth = AESUtils.encrypt2HexIz(fileId + "|" + nowTs);
|
||||
JsonObject entries = JsonObject.of(
|
||||
"fidEncode", fidEncode,
|
||||
"uuid", uuid,
|
||||
"ts", tsEncode,
|
||||
"auth", auth,
|
||||
"shareId", shareId);
|
||||
byte[] encode = Base64.getEncoder().encode(entries.encode().getBytes());
|
||||
String param = new String(encode);
|
||||
// 映射已知字段
|
||||
String fileId = fileJson.getString("fileId");
|
||||
String userId = fileJson.getString("userId");
|
||||
|
||||
// 回传用到的参数
|
||||
//"fidEncode", paramJson.getString("fidEncode"))
|
||||
//"uuid", paramJson.getString("uuid"))
|
||||
//"ts", paramJson.getString("ts"))
|
||||
//"auth", paramJson.getString("auth"))
|
||||
//"shareId", paramJson.getString("shareId"))
|
||||
String fidEncode = AESUtils.encrypt2HexIz(fileId + "|" + userId);
|
||||
String auth = AESUtils.encrypt2HexIz(fileId + "|" + nowTs);
|
||||
JsonObject entries = JsonObject.of(
|
||||
"fidEncode", fidEncode,
|
||||
"uuid", uuid,
|
||||
"ts", tsEncode,
|
||||
"auth", auth,
|
||||
"shareId", shareId);
|
||||
byte[] encode = Base64.getEncoder().encode(entries.encode().getBytes());
|
||||
String param = new String(encode);
|
||||
|
||||
long fileSize = fileJson.getLong("fileSize") * 1024;
|
||||
fileInfo.setFileName(fileJson.getString("fileName"))
|
||||
.setFileId(fileId)
|
||||
.setCreateTime(fileJson.getString("createTime"))
|
||||
.setFileType(fileJson.getString("fileType"))
|
||||
.setSize(fileSize)
|
||||
.setSizeStr(FileSizeConverter.convertToReadableSize(fileSize))
|
||||
if (fileJson.getInteger("fileType") == 2) {
|
||||
// 如果是目录
|
||||
fileInfo.setFileName(fileJson.getString("name"))
|
||||
.setFileId(fileJson.getString("folderId"))
|
||||
.setCreateTime(fileJson.getString("updTime"))
|
||||
.setFileType("folder")
|
||||
.setSize(0L)
|
||||
.setSizeStr("0B")
|
||||
.setCreateBy(fileJson.getLong("userId").toString())
|
||||
.setDownloadCount(fileJson.getInteger("fileDownloads"))
|
||||
.setCreateTime(fileJson.getString("updTime"))
|
||||
.setFileIcon(fileJson.getString("fileIcon"))
|
||||
.setPanType(shareLinkInfo.getType())
|
||||
.setParserUrl(String.format("%s/v2/redirectUrl/%s/%s", getDomainName(),
|
||||
shareLinkInfo.getType(), param));
|
||||
// 设置目录解析的URL
|
||||
.setParserUrl(String.format("%s/v2/getFileList?url=%s&dirId=%s&uuid=%s", getDomainName(),
|
||||
shareLinkInfo.getShareUrl(), fileJson.getString("folderId"), uuid));
|
||||
result.add(fileInfo);
|
||||
});
|
||||
promise.complete(result);
|
||||
return;
|
||||
}
|
||||
long fileSize = fileJson.getLong("fileSize") * 1024;
|
||||
fileInfo.setFileName(fileJson.getString("fileName"))
|
||||
.setFileId(fileId)
|
||||
.setCreateTime(fileJson.getString("createTime"))
|
||||
.setFileType("file")
|
||||
.setSize(fileSize)
|
||||
.setSizeStr(FileSizeConverter.convertToReadableSize(fileSize))
|
||||
.setCreateBy(fileJson.getLong("userId").toString())
|
||||
.setDownloadCount(fileJson.getInteger("fileDownloads"))
|
||||
.setCreateTime(fileJson.getString("updTime"))
|
||||
.setFileIcon(fileJson.getString("fileIcon"))
|
||||
.setPanType(shareLinkInfo.getType())
|
||||
.setParserUrl(String.format("%s/v2/redirectUrl/%s/%s", getDomainName(),
|
||||
shareLinkInfo.getType(), param))
|
||||
.setPreviewUrl(String.format("%s/v2/viewUrl/%s/%s", getDomainName(),
|
||||
shareLinkInfo.getType(), param));
|
||||
result.add(fileInfo);
|
||||
});
|
||||
});
|
||||
return promise.future();
|
||||
promise.complete(result);
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -222,7 +222,10 @@ public class LzTool extends PanBase {
|
||||
.setSizeStr(fileJson.getString("size"))
|
||||
.setSize(sizeNum)
|
||||
.setPanType(panType)
|
||||
.setParserUrl(getDomainName() + "/d/" + panType + "/" + id);
|
||||
.setParserUrl(getDomainName() + "/d/" + panType + "/" + id)
|
||||
.setPreviewUrl(String.format("%s/v2/view/%s/%s", getDomainName(),
|
||||
shareLinkInfo.getType(), id));
|
||||
;
|
||||
log.debug("文件信息: {}", fileInfo);
|
||||
list.add(fileInfo);
|
||||
});
|
||||
|
||||
@@ -1,11 +1,14 @@
|
||||
package cn.qaiu.parser.impl;
|
||||
|
||||
import cn.qaiu.entity.FileInfo;
|
||||
import cn.qaiu.entity.ShareLinkInfo;
|
||||
import cn.qaiu.parser.PanBase;
|
||||
import cn.qaiu.util.CommonUtils;
|
||||
import cn.qaiu.util.FileSizeConverter;
|
||||
import cn.qaiu.util.JsExecUtils;
|
||||
import io.vertx.core.Future;
|
||||
import io.vertx.core.MultiMap;
|
||||
import io.vertx.core.Promise;
|
||||
import io.vertx.core.json.JsonArray;
|
||||
import io.vertx.core.json.JsonObject;
|
||||
import io.vertx.core.json.pointer.JsonPointer;
|
||||
@@ -15,7 +18,9 @@ import org.apache.commons.lang3.StringUtils;
|
||||
import org.openjdk.nashorn.api.scripting.ScriptObjectMirror;
|
||||
|
||||
import java.net.MalformedURLException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Base64;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
@@ -32,7 +37,7 @@ public class YeTool extends PanBase {
|
||||
|
||||
private static final String GET_FILE_INFO_URL = "https://www.123pan.com/a/api/share/get?limit=100&next=1&orderBy" +
|
||||
"=file_name&orderDirection=asc" +
|
||||
"&shareKey={shareKey}&SharePwd={pwd}&ParentFileId=0&Page=1&event=homeListFile&operateType=1";
|
||||
"&shareKey={shareKey}&SharePwd={pwd}&ParentFileId={ParentFileId}&Page=1&event=homeListFile&operateType=1";
|
||||
private static final String DOWNLOAD_API_URL = "https://www.123pan.com/a/api/share/download/info?{authK}={authV}";
|
||||
|
||||
private static final String BATCH_DOWNLOAD_API_URL = "https://www.123pan.com/b/api/file/batch_download_share_info?{authK}={authV}";
|
||||
@@ -97,6 +102,7 @@ public class YeTool extends PanBase {
|
||||
client.getAbs(UriTemplate.of(GET_FILE_INFO_URL))
|
||||
.setTemplateParam("shareKey", shareKey)
|
||||
.setTemplateParam("pwd", pwd)
|
||||
.setTemplateParam("ParentFileId", "0")
|
||||
// .setTemplateParam("authKey", AESUtils.getAuthKey("/a/api/share/get"))
|
||||
.putHeader("Platform", "web")
|
||||
.putHeader("App-Version", "3")
|
||||
@@ -227,4 +233,121 @@ public class YeTool extends PanBase {
|
||||
}
|
||||
}).onFailure(this.handleFail(DOWNLOAD_API_URL));
|
||||
}
|
||||
|
||||
|
||||
// dir parser
|
||||
@Override
|
||||
public Future<List<FileInfo>> parseFileList() {
|
||||
Promise<List<FileInfo>> promise = Promise.promise();
|
||||
|
||||
String shareKey = shareLinkInfo.getShareKey(); // 分享链接的唯一标识
|
||||
String pwd = shareLinkInfo.getSharePassword(); // 分享密码
|
||||
String parentFileId = "0"; // 根目录的文件ID
|
||||
String shareId = shareLinkInfo.getShareKey(); // String.valueOf(AESUtils.idEncrypt(dataKey));
|
||||
|
||||
// 如果参数里的目录ID不为空,则直接解析目录
|
||||
String dirId = (String) shareLinkInfo.getOtherParam().get("dirId");
|
||||
if (StringUtils.isNotBlank(dirId)) {
|
||||
parentFileId = dirId;
|
||||
}
|
||||
|
||||
|
||||
// 构造文件列表接口的URL
|
||||
client.getAbs(UriTemplate.of(GET_FILE_INFO_URL))
|
||||
.setTemplateParam("shareKey", shareKey)
|
||||
.setTemplateParam("pwd", pwd)
|
||||
.setTemplateParam("ParentFileId", parentFileId)
|
||||
.putHeaders(header)
|
||||
.send().onSuccess(res -> {
|
||||
JsonObject response = asJson(res);
|
||||
if (response.getInteger("code") != 0) {
|
||||
promise.fail("API错误: " + response.getString("message"));
|
||||
return;
|
||||
}
|
||||
|
||||
JsonArray infoList = response.getJsonObject("data").getJsonArray("InfoList");
|
||||
List<FileInfo> result = new ArrayList<>();
|
||||
|
||||
// 遍历返回的文件和目录信息
|
||||
for (int i = 0; i < infoList.size(); i++) {
|
||||
JsonObject item = infoList.getJsonObject(i);
|
||||
FileInfo fileInfo = new FileInfo();
|
||||
// "FileId": 16603582,
|
||||
// "FileName": "pdf",
|
||||
// "Type": 1,
|
||||
// "Size": 0,
|
||||
// "ContentType": "0",
|
||||
// "S3KeyFlag": "",
|
||||
// "CreateAt": "2025-07-09T06:56:20+08:00",
|
||||
// "UpdateAt": "2025-07-09T06:56:20+08:00",
|
||||
// "Etag": "",
|
||||
// "DownloadUrl": "",
|
||||
// "Status": 0,
|
||||
// "ParentFileId": 16603579,
|
||||
// "Category": 0,
|
||||
// "PunishFlag": 0,
|
||||
// "StorageNode": "m0",
|
||||
// "PreviewType": 0
|
||||
|
||||
// =>
|
||||
// {
|
||||
// "ShareKey":"iaKtVv-FTaCd",
|
||||
// "FileID":16604189,
|
||||
// "S3keyFlag":"1815268665-0",
|
||||
// "Size":425929,
|
||||
// "Etag":"70049de67075ab2b269c62d690424601",
|
||||
// "OrderId":""}
|
||||
JsonObject postData = JsonObject.of()
|
||||
.put("ShareKey", shareKey)
|
||||
.put("FileID", item.getInteger("FileId"))
|
||||
.put("S3keyFlag", item.getString("S3KeyFlag"))
|
||||
.put("Size", item.getLong("Size"))
|
||||
.put("Etag", item.getString("Etag"));
|
||||
|
||||
byte[] encode = Base64.getEncoder().encode(postData.encode().getBytes());
|
||||
String param = new String(encode);
|
||||
|
||||
if (item.getInteger("Type") == 0) { // 文件
|
||||
fileInfo.setFileName(item.getString("FileName"))
|
||||
.setFileId(item.getString("FileId"))
|
||||
.setFileType("file")
|
||||
.setSize(item.getLong("Size"))
|
||||
.setCreateTime(item.getString("CreateAt"))
|
||||
.setUpdateTime(item.getString("UpdateAt"))
|
||||
.setSizeStr(FileSizeConverter.convertToReadableSize(item.getLong("Size")))
|
||||
.setParserUrl(String.format("%s/v2/redirectUrl/%s/%s", getDomainName(),
|
||||
shareLinkInfo.getType(), param))
|
||||
.setPreviewUrl(String.format("%s/v2/viewUrl/%s/%s", getDomainName(),
|
||||
shareLinkInfo.getType(), param));
|
||||
result.add(fileInfo);
|
||||
} else if (item.getInteger("Type") == 1) { // 目录
|
||||
fileInfo.setFileName(item.getString("FileName"))
|
||||
.setFileId(item.getString("FileId"))
|
||||
.setCreateTime(item.getString("CreateAt"))
|
||||
.setUpdateTime(item.getString("UpdateAt"))
|
||||
.setSize(0L)
|
||||
.setFileType("folder")
|
||||
.setParserUrl(
|
||||
String.format("%s/v2/getFileList?url=%s&dirId=%s&pwd=%s",
|
||||
getDomainName(),
|
||||
shareLinkInfo.getShareUrl(),
|
||||
item.getString("FileId"),
|
||||
pwd)
|
||||
);
|
||||
result.add(fileInfo);
|
||||
}
|
||||
}
|
||||
promise.complete(result);
|
||||
}).onFailure(promise::fail);
|
||||
|
||||
return promise.future();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Future<String> parseById() {
|
||||
JsonObject paramJson = (JsonObject) shareLinkInfo.getOtherParam().get("paramJson");
|
||||
// 调用下载接口获取直链
|
||||
down(client, paramJson, DOWNLOAD_API_URL);
|
||||
return promise.future();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
|
||||
## 关于如何将前端项目和java一块打包:
|
||||
1. 先打包前端模块
|
||||
2. ~~打包后请将当前目录下的nfd-front目录放置在项目下webroot目录, 然后使用maven打包java模块即可~~ `npm run build` 会直接打包到后端代理目录下, 无需复制
|
||||
2. 运行`npm run build`
|
||||
3. 项目部署后演示页面的代理端口是6401默认使用http, 如需https可以加nginx代理, 也可以使用本项目自带的代理服务和配置证书路径
|
||||
|
||||
## nginx配置
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
module.exports = {
|
||||
presets: [
|
||||
'@vue/cli-plugin-babel/preset'
|
||||
],
|
||||
plugins: [
|
||||
'@vue/babel-plugin-transform-vue-jsx'
|
||||
]
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"serve": "vue-cli-service serve",
|
||||
"dev": "vue-cli-service serve",
|
||||
"build": "vue-cli-service build",
|
||||
"lint": "vue-cli-service lint"
|
||||
},
|
||||
@@ -15,18 +16,22 @@
|
||||
"core-js": "^3.8.3",
|
||||
"element-plus": "^2.8.7",
|
||||
"qrcode": "^1.5.4",
|
||||
"splitpanes": "^4.0.4",
|
||||
"vue": "^3.5.12",
|
||||
"vue-clipboard3": "^2.0.0",
|
||||
"vue-router": "^4.5.1",
|
||||
"vue3-json-viewer": "2.2.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.26.0",
|
||||
"@babel/eslint-parser": "^7.25.9",
|
||||
"@babel/plugin-transform-class-properties": "^7.26.0",
|
||||
"@vue/babel-plugin-transform-vue-jsx": "^1.4.0",
|
||||
"@vue/cli-plugin-babel": "~5.0.8",
|
||||
"@vue/cli-plugin-eslint": "~5.0.8",
|
||||
"@vue/cli-service": "~5.0.8",
|
||||
"compression-webpack-plugin": "^11.1.0",
|
||||
"eslint": "^9.14.0",
|
||||
"eslint": "^9.0.0",
|
||||
"eslint-plugin-vue": "^9.30.0",
|
||||
"filemanager-webpack-plugin": "8.0.0"
|
||||
},
|
||||
@@ -48,5 +53,12 @@
|
||||
"> 1%",
|
||||
"last 2 versions",
|
||||
"not dead"
|
||||
]
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=16.0.0 <=22.0.0",
|
||||
"npm": ">=8.0.0"
|
||||
},
|
||||
"overrides": {
|
||||
"eslint": "^9.0.0"
|
||||
}
|
||||
}
|
||||
|
||||
165
web-front/public/LICENSE.txt
Normal file
165
web-front/public/LICENSE.txt
Normal file
@@ -0,0 +1,165 @@
|
||||
Fonticons, Inc. (https://fontawesome.com)
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
Font Awesome Free License
|
||||
|
||||
Font Awesome Free is free, open source, and GPL friendly. You can use it for
|
||||
commercial projects, open source projects, or really almost whatever you want.
|
||||
Full Font Awesome Free license: https://fontawesome.com/license/free.
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
# Icons: CC BY 4.0 License (https://creativecommons.org/licenses/by/4.0/)
|
||||
|
||||
The Font Awesome Free download is licensed under a Creative Commons
|
||||
Attribution 4.0 International License and applies to all icons packaged
|
||||
as SVG and JS file types.
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
# Fonts: SIL OFL 1.1 License
|
||||
|
||||
In the Font Awesome Free download, the SIL OFL license applies to all icons
|
||||
packaged as web and desktop font files.
|
||||
|
||||
Copyright (c) 2024 Fonticons, Inc. (https://fontawesome.com)
|
||||
with Reserved Font Name: "Font Awesome".
|
||||
|
||||
This Font Software is licensed under the SIL Open Font License, Version 1.1.
|
||||
This license is copied below, and is also available with a FAQ at:
|
||||
http://scripts.sil.org/OFL
|
||||
|
||||
SIL OPEN FONT LICENSE
|
||||
Version 1.1 - 26 February 2007
|
||||
|
||||
PREAMBLE
|
||||
The goals of the Open Font License (OFL) are to stimulate worldwide
|
||||
development of collaborative font projects, to support the font creation
|
||||
efforts of academic and linguistic communities, and to provide a free and
|
||||
open framework in which fonts may be shared and improved in partnership
|
||||
with others.
|
||||
|
||||
The OFL allows the licensed fonts to be used, studied, modified and
|
||||
redistributed freely as long as they are not sold by themselves. The
|
||||
fonts, including any derivative works, can be bundled, embedded,
|
||||
redistributed and/or sold with any software provided that any reserved
|
||||
names are not used by derivative works. The fonts and derivatives,
|
||||
however, cannot be released under any other type of license. The
|
||||
requirement for fonts to remain under this license does not apply
|
||||
to any document created using the fonts or their derivatives.
|
||||
|
||||
DEFINITIONS
|
||||
"Font Software" refers to the set of files released by the Copyright
|
||||
Holder(s) under this license and clearly marked as such. This may
|
||||
include source files, build scripts and documentation.
|
||||
|
||||
"Reserved Font Name" refers to any names specified as such after the
|
||||
copyright statement(s).
|
||||
|
||||
"Original Version" refers to the collection of Font Software components as
|
||||
distributed by the Copyright Holder(s).
|
||||
|
||||
"Modified Version" refers to any derivative made by adding to, deleting,
|
||||
or substituting — in part or in whole — any of the components of the
|
||||
Original Version, by changing formats or by porting the Font Software to a
|
||||
new environment.
|
||||
|
||||
"Author" refers to any designer, engineer, programmer, technical
|
||||
writer or other person who contributed to the Font Software.
|
||||
|
||||
PERMISSION & CONDITIONS
|
||||
Permission is hereby granted, free of charge, to any person obtaining
|
||||
a copy of the Font Software, to use, study, copy, merge, embed, modify,
|
||||
redistribute, and sell modified and unmodified copies of the Font
|
||||
Software, subject to the following conditions:
|
||||
|
||||
1) Neither the Font Software nor any of its individual components,
|
||||
in Original or Modified Versions, may be sold by itself.
|
||||
|
||||
2) Original or Modified Versions of the Font Software may be bundled,
|
||||
redistributed and/or sold with any software, provided that each copy
|
||||
contains the above copyright notice and this license. These can be
|
||||
included either as stand-alone text files, human-readable headers or
|
||||
in the appropriate machine-readable metadata fields within text or
|
||||
binary files as long as those fields can be easily viewed by the user.
|
||||
|
||||
3) No Modified Version of the Font Software may use the Reserved Font
|
||||
Name(s) unless explicit written permission is granted by the corresponding
|
||||
Copyright Holder. This restriction only applies to the primary font name as
|
||||
presented to the users.
|
||||
|
||||
4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
|
||||
Software shall not be used to promote, endorse or advertise any
|
||||
Modified Version, except to acknowledge the contribution(s) of the
|
||||
Copyright Holder(s) and the Author(s) or with their explicit written
|
||||
permission.
|
||||
|
||||
5) The Font Software, modified or unmodified, in part or in whole,
|
||||
must be distributed entirely under this license, and must not be
|
||||
distributed under any other license. The requirement for fonts to
|
||||
remain under this license does not apply to any document created
|
||||
using the Font Software.
|
||||
|
||||
TERMINATION
|
||||
This license becomes null and void if any of the above conditions are
|
||||
not met.
|
||||
|
||||
DISCLAIMER
|
||||
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
|
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
|
||||
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
|
||||
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
|
||||
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
|
||||
OTHER DEALINGS IN THE FONT SOFTWARE.
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
# Code: MIT License (https://opensource.org/licenses/MIT)
|
||||
|
||||
In the Font Awesome Free download, the MIT license applies to all non-font and
|
||||
non-icon files.
|
||||
|
||||
Copyright 2024 Fonticons, Inc.
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
this software and associated documentation files (the "Software"), to deal in the
|
||||
Software without restriction, including without limitation the rights to use, copy,
|
||||
modify, merge, publish, distribute, sublicense, and/or sell copies of the Software,
|
||||
and to permit persons to whom the Software is furnished to do so, subject to the
|
||||
following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
|
||||
INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
|
||||
PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
|
||||
HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
||||
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
# Attribution
|
||||
|
||||
Attribution is required by MIT, SIL OFL, and CC BY licenses. Downloaded Font
|
||||
Awesome Free files already contain embedded comments with sufficient
|
||||
attribution, so you shouldn't need to do anything additional when using these
|
||||
files normally.
|
||||
|
||||
We've kept attribution comments terse, so we ask that you do not actively work
|
||||
to remove them from files, especially code. They're a great way for folks to
|
||||
learn about Font Awesome.
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
# Brand Icons
|
||||
|
||||
All brand icons are trademarks of their respective owners. The use of these
|
||||
trademarks does not indicate endorsement of the trademark holder by Font
|
||||
Awesome, nor vice versa. **Please do not use brand logos for any purpose except
|
||||
to represent the company, product, or service to which they refer.**
|
||||
9
web-front/public/css/all.min.css
vendored
Normal file
9
web-front/public/css/all.min.css
vendored
Normal file
File diff suppressed because one or more lines are too long
BIN
web-front/public/images/logo.jpg
Normal file
BIN
web-front/public/images/logo.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 5.5 KiB |
@@ -9,6 +9,8 @@
|
||||
content="Netdisk fast download,网盘直链解析工具">
|
||||
<meta name="description"
|
||||
content="Netdisk fast download 网盘直链解析工具">
|
||||
<!-- Font Awesome 图标库 -->
|
||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
|
||||
<style>
|
||||
.page-loading-wrap {
|
||||
padding: 120px;
|
||||
|
||||
693
web-front/public/list.html
Normal file
693
web-front/public/list.html
Normal file
@@ -0,0 +1,693 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>网盘目录管理系统</title>
|
||||
<!-- 本地引用Font Awesome -->
|
||||
<link rel="stylesheet" href="./css/all.min.css">
|
||||
<style>
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
font-family: 'Segoe UI', 'Microsoft YaHei', sans-serif;
|
||||
}
|
||||
|
||||
body {
|
||||
background: linear-gradient(135deg, #f5f7fa 0%, #e4edf5 100%);
|
||||
min-height: 100vh;
|
||||
padding: 20px;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.container {
|
||||
max-width: 1200px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
header {
|
||||
text-align: center;
|
||||
margin-bottom: 30px;
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
h1 {
|
||||
color: #2c3e50;
|
||||
font-size: 2.5rem;
|
||||
margin-bottom: 10px;
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
h1:after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
bottom: -10px;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
width: 80px;
|
||||
height: 4px;
|
||||
background: linear-gradient(90deg, #3498db, #2c3e50);
|
||||
border-radius: 2px;
|
||||
}
|
||||
|
||||
.subtitle {
|
||||
color: #7f8c8d;
|
||||
font-size: 1.1rem;
|
||||
margin-top: 5px;
|
||||
}
|
||||
|
||||
.dashboard {
|
||||
background: white;
|
||||
border-radius: 12px;
|
||||
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.08);
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.breadcrumb {
|
||||
background: #f8f9fa;
|
||||
padding: 16px 24px;
|
||||
border-bottom: 1px solid #eaeaea;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.breadcrumb-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
font-size: 0.95rem;
|
||||
color: #7f8c8d;
|
||||
cursor: pointer;
|
||||
transition: color 0.2s;
|
||||
}
|
||||
|
||||
.breadcrumb-item:hover {
|
||||
color: #3498db;
|
||||
}
|
||||
|
||||
.breadcrumb-item i {
|
||||
margin: 0 8px;
|
||||
font-size: 0.8rem;
|
||||
color: #bdc3c7;
|
||||
}
|
||||
|
||||
.content {
|
||||
padding: 24px;
|
||||
min-height: 500px;
|
||||
}
|
||||
|
||||
.grid-view {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(160px, 1fr));
|
||||
gap: 20px;
|
||||
}
|
||||
|
||||
.item {
|
||||
background: #fff;
|
||||
border-radius: 10px;
|
||||
overflow: hidden;
|
||||
box-shadow: 0 3px 15px rgba(0, 0, 0, 0.05);
|
||||
transition: all 0.3s ease;
|
||||
cursor: pointer;
|
||||
text-align: center;
|
||||
padding: 20px 10px;
|
||||
border: 2px solid transparent;
|
||||
}
|
||||
|
||||
.item:hover {
|
||||
transform: translateY(-5px);
|
||||
box-shadow: 0 5px 20px rgba(0, 0, 0, 0.1);
|
||||
border-color: #3498db;
|
||||
}
|
||||
|
||||
.item-icon {
|
||||
font-size: 3.5rem;
|
||||
margin-bottom: 15px;
|
||||
transition: transform 0.3s;
|
||||
}
|
||||
|
||||
.item:hover .item-icon {
|
||||
transform: scale(1.1);
|
||||
}
|
||||
|
||||
.folder .item-icon {
|
||||
color: #3498db;
|
||||
}
|
||||
|
||||
.image .item-icon {
|
||||
color: #e74c3c;
|
||||
}
|
||||
|
||||
.document .item-icon {
|
||||
color: #f39c12;
|
||||
}
|
||||
|
||||
.archive .item-icon {
|
||||
color: #9b59b6;
|
||||
}
|
||||
|
||||
.audio .item-icon {
|
||||
color: #1abc9c;
|
||||
}
|
||||
|
||||
.video .item-icon {
|
||||
color: #d35400;
|
||||
}
|
||||
|
||||
.code .item-icon {
|
||||
color: #27ae60;
|
||||
}
|
||||
|
||||
.item-name {
|
||||
font-weight: 500;
|
||||
font-size: 0.95rem;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
display: -webkit-box;
|
||||
-webkit-line-clamp: 2;
|
||||
-webkit-box-orient: vertical;
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
.item-meta {
|
||||
font-size: 0.8rem;
|
||||
color: #95a5a6;
|
||||
margin-top: 8px;
|
||||
}
|
||||
|
||||
.empty-state {
|
||||
text-align: center;
|
||||
padding: 50px 20px;
|
||||
color: #7f8c8d;
|
||||
grid-column: 1 / -1;
|
||||
}
|
||||
|
||||
.empty-state i {
|
||||
font-size: 5rem;
|
||||
margin-bottom: 20px;
|
||||
color: #bdc3c7;
|
||||
}
|
||||
|
||||
.empty-state h3 {
|
||||
font-size: 1.5rem;
|
||||
margin-bottom: 10px;
|
||||
color: #2c3e50;
|
||||
}
|
||||
|
||||
.loading {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
min-height: 300px;
|
||||
grid-column: 1 / -1;
|
||||
}
|
||||
|
||||
.spinner {
|
||||
width: 50px;
|
||||
height: 50px;
|
||||
border: 5px solid rgba(52, 152, 219, 0.2);
|
||||
border-top: 5px solid #3498db;
|
||||
border-radius: 50%;
|
||||
animation: spin 1s linear infinite;
|
||||
}
|
||||
|
||||
@keyframes spin {
|
||||
0% { transform: rotate(0deg); }
|
||||
100% { transform: rotate(360deg); }
|
||||
}
|
||||
|
||||
.action-bar {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
padding: 15px 24px;
|
||||
background: #f8f9fa;
|
||||
border-top: 1px solid #eaeaea;
|
||||
}
|
||||
|
||||
.btn {
|
||||
background: #3498db;
|
||||
color: white;
|
||||
border: none;
|
||||
padding: 10px 20px;
|
||||
border-radius: 6px;
|
||||
cursor: pointer;
|
||||
font-weight: 500;
|
||||
transition: background 0.3s;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.btn:hover {
|
||||
background: #2980b9;
|
||||
}
|
||||
|
||||
.btn i {
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
.stats {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 15px;
|
||||
color: #7f8c8d;
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
.stat-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 5px;
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.grid-view {
|
||||
grid-template-columns: repeat(auto-fill, minmax(130px, 1fr));
|
||||
gap: 15px;
|
||||
}
|
||||
|
||||
.item {
|
||||
padding: 15px 8px;
|
||||
}
|
||||
|
||||
.item-icon {
|
||||
font-size: 3rem;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 480px) {
|
||||
.grid-view {
|
||||
grid-template-columns: repeat(auto-fill, minmax(110px, 1fr));
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-size: 2rem;
|
||||
}
|
||||
|
||||
.action-bar {
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<header>
|
||||
<h1><i class="fas fa-cloud"></i> 网盘目录管理系统</h1>
|
||||
<p class="subtitle">管理您的文件与文件夹,操作简单直观</p>
|
||||
</header>
|
||||
|
||||
<div class="dashboard">
|
||||
<div class="breadcrumb" id="breadcrumb">
|
||||
<!-- 面包屑导航会通过JS动态生成 -->
|
||||
</div>
|
||||
|
||||
<div class="content">
|
||||
<div class="grid-view" id="file-grid">
|
||||
<!-- 文件列表会通过JS动态生成 -->
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="action-bar">
|
||||
<button class="btn" id="back-btn">
|
||||
<i class="fas fa-arrow-left"></i> 返回上一级
|
||||
</button>
|
||||
<div class="stats">
|
||||
<div class="stat-item">
|
||||
<i class="fas fa-folder"></i> <span id="folder-count">0</span> 个文件夹
|
||||
</div>
|
||||
<div class="stat-item">
|
||||
<i class="fas fa-file"></i> <span id="file-count">0</span> 个文件
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
// 文件类型映射
|
||||
const fileTypeIcons = {
|
||||
// 图片
|
||||
'jpg': { icon: 'fa-file-image', type: 'image' },
|
||||
'jpeg': { icon: 'fa-file-image', type: 'image' },
|
||||
'png': { icon: 'fa-file-image', type: 'image' },
|
||||
'gif': { icon: 'fa-file-image', type: 'image' },
|
||||
'bmp': { icon: 'fa-file-image', type: 'image' },
|
||||
'svg': { icon: 'fa-file-image', type: 'image' },
|
||||
'webp': { icon: 'fa-file-image', type: 'image' },
|
||||
|
||||
// 文档
|
||||
'pdf': { icon: 'fa-file-pdf', type: 'document' },
|
||||
'doc': { icon: 'fa-file-word', type: 'document' },
|
||||
'docx': { icon: 'fa-file-word', type: 'document' },
|
||||
'xls': { icon: 'fa-file-excel', type: 'document' },
|
||||
'xlsx': { icon: 'fa-file-excel', type: 'document' },
|
||||
'ppt': { icon: 'fa-file-powerpoint', type: 'document' },
|
||||
'pptx': { icon: 'fa-file-powerpoint', type: 'document' },
|
||||
'txt': { icon: 'fa-file-alt', type: 'document' },
|
||||
'rtf': { icon: 'fa-file-alt', type: 'document' },
|
||||
|
||||
// 压缩文件
|
||||
'zip': { icon: 'fa-file-archive', type: 'archive' },
|
||||
'rar': { icon: 'fa-file-archive', type: 'archive' },
|
||||
'7z': { icon: 'fa-file-archive', type: 'archive' },
|
||||
'tar': { icon: 'fa-file-archive', type: 'archive' },
|
||||
'gz': { icon: 'fa-file-archive', type: 'archive' },
|
||||
|
||||
// 音频
|
||||
'mp3': { icon: 'fa-file-audio', type: 'audio' },
|
||||
'wav': { icon: 'fa-file-audio', type: 'audio' },
|
||||
'ogg': { icon: 'fa-file-audio', type: 'audio' },
|
||||
'flac': { icon: 'fa-file-audio', type: 'audio' },
|
||||
|
||||
// 视频
|
||||
'mp4': { icon: 'fa-file-video', type: 'video' },
|
||||
'avi': { icon: 'fa-file-video', type: 'video' },
|
||||
'mov': { icon: 'fa-file-video', type: 'video' },
|
||||
'wmv': { icon: 'fa-file-video', type: 'video' },
|
||||
'mkv': { icon: 'fa-file-video', type: 'video' },
|
||||
'flv': { icon: 'fa-file-video', type: 'video' },
|
||||
|
||||
// 代码
|
||||
'html': { icon: 'fa-file-code', type: 'code' },
|
||||
'htm': { icon: 'fa-file-code', type: 'code' },
|
||||
'css': { icon: 'fa-file-code', type: 'code' },
|
||||
'js': { icon: 'fa-file-code', type: 'code' },
|
||||
'json': { icon: 'fa-file-code', type: 'code' },
|
||||
'php': { icon: 'fa-file-code', type: 'code' },
|
||||
'py': { icon: 'fa-file-code', type: 'code' },
|
||||
'java': { icon: 'fa-file-code', type: 'code' },
|
||||
'c': { icon: 'fa-file-code', type: 'code' },
|
||||
'cpp': { icon: 'fa-file-code', type: 'code' },
|
||||
'h': { icon: 'fa-file-code', type: 'code' },
|
||||
'sh': { icon: 'fa-file-code', type: 'code' },
|
||||
'bat': { icon: 'fa-file-code', type: 'code' },
|
||||
'md': { icon: 'fa-file-code', type: 'code' },
|
||||
|
||||
// 默认
|
||||
'default': { icon: 'fa-file', type: 'document' }
|
||||
};
|
||||
|
||||
const obj = new URL(window.location.href);
|
||||
// 获取 URL 参数
|
||||
const params = obj.searchParams;
|
||||
const shareUrl = params.get('url');
|
||||
const pwd = params.get('pwd');
|
||||
// 动态拼接并编码参数
|
||||
const apiUrl = `${window.location.origin}/v2/getFileList?url=${encodeURIComponent(shareUrl)}&pwd=${encodeURIComponent(pwd)}`;
|
||||
|
||||
// 当前目录状态
|
||||
let currentDir = {
|
||||
// url: 'http://192.168.101.227:6401/v2/getFileList?url=https://share.feijipan.com/s/3pMsofZd&pwd=qaiu',
|
||||
// 动态获取url encode 参数
|
||||
url: apiUrl,
|
||||
name: '全部文件'
|
||||
};
|
||||
const pathStack = [currentDir];
|
||||
|
||||
// DOM 元素
|
||||
const breadcrumbEl = document.getElementById('breadcrumb');
|
||||
const fileGridEl = document.getElementById('file-grid');
|
||||
const backBtn = document.getElementById('back-btn');
|
||||
const folderCountEl = document.getElementById('folder-count');
|
||||
const fileCountEl = document.getElementById('file-count');
|
||||
|
||||
// 初始化
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
renderBreadcrumb();
|
||||
fetchFileList(currentDir.url);
|
||||
|
||||
// 返回上一级按钮事件
|
||||
backBtn.addEventListener('click', goBack);
|
||||
});
|
||||
|
||||
// 渲染面包屑导航
|
||||
function renderBreadcrumb() {
|
||||
breadcrumbEl.innerHTML = '';
|
||||
|
||||
pathStack.forEach((item, index) => {
|
||||
const itemEl = document.createElement('div');
|
||||
itemEl.className = 'breadcrumb-item';
|
||||
itemEl.textContent = item.name;
|
||||
|
||||
if (index < pathStack.length - 1) {
|
||||
itemEl.addEventListener('click', () => {
|
||||
// 点击面包屑项返回对应目录
|
||||
goToDirectory(index);
|
||||
});
|
||||
} else {
|
||||
itemEl.style.cursor = 'default';
|
||||
itemEl.style.fontWeight = '600';
|
||||
itemEl.style.color = '#2c3e50';
|
||||
}
|
||||
|
||||
breadcrumbEl.appendChild(itemEl);
|
||||
|
||||
// 如果不是最后一个,添加分隔符
|
||||
if (index < pathStack.length - 1) {
|
||||
const separator = document.createElement('i');
|
||||
separator.className = 'fas fa-chevron-right';
|
||||
breadcrumbEl.appendChild(separator);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// 获取文件列表
|
||||
async function fetchFileList(url) {
|
||||
try {
|
||||
// 显示加载状态
|
||||
fileGridEl.innerHTML = `
|
||||
<div class="loading">
|
||||
<div class="spinner"></div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
const response = await fetch(url);
|
||||
if (!response.ok) {
|
||||
throw new Error(`HTTP error! status: ${response.status}`);
|
||||
}
|
||||
const data = await response.json();
|
||||
|
||||
if (data.code === 200 && data.success) {
|
||||
renderFileList(data.data);
|
||||
} else {
|
||||
throw new Error(data.msg || '获取文件列表失败');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('获取文件列表失败:', error);
|
||||
fileGridEl.innerHTML = `
|
||||
<div class="empty-state">
|
||||
<i class="fas fa-exclamation-circle"></i>
|
||||
<h3>加载失败</h3>
|
||||
<p>${error.message}</p>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
||||
// 渲染文件列表
|
||||
function renderFileList(files) {
|
||||
fileGridEl.innerHTML = '';
|
||||
|
||||
if (!files || files.length === 0) {
|
||||
fileGridEl.innerHTML = `
|
||||
<div class="empty-state">
|
||||
<i class="fas fa-folder-open"></i>
|
||||
<h3>此文件夹为空</h3>
|
||||
<p>暂无文件或文件夹</p>
|
||||
</div>
|
||||
`;
|
||||
folderCountEl.textContent = '0';
|
||||
fileCountEl.textContent = '0';
|
||||
return;
|
||||
}
|
||||
|
||||
let folderCount = 0;
|
||||
let fileCount = 0;
|
||||
|
||||
files.forEach(file => {
|
||||
const item = document.createElement('div');
|
||||
|
||||
if (file.fileType === 'folder') {
|
||||
// 文件夹
|
||||
item.className = 'item folder';
|
||||
item.innerHTML = `
|
||||
<div class="item-icon">
|
||||
<i class="fas fa-folder"></i>
|
||||
</div>
|
||||
<div class="item-name">${file.fileName || '未命名文件夹'}</div>
|
||||
<div class="item-meta">
|
||||
${file.sizeStr || '0B'} · ${formatDate(file.createTime)}
|
||||
</div>
|
||||
`;
|
||||
folderCount++;
|
||||
|
||||
// 添加点击事件
|
||||
item.addEventListener('click', () => {
|
||||
enterFolder(file);
|
||||
});
|
||||
} else {
|
||||
// 文件
|
||||
const fileExt = getFileExtension(file.fileName);
|
||||
const fileTypeInfo = fileTypeIcons[fileExt.toLowerCase()] || fileTypeIcons['default'];
|
||||
|
||||
item.className = `item ${fileTypeInfo.type}`;
|
||||
item.innerHTML = `
|
||||
<div class="item-icon">
|
||||
<i class="fas ${fileTypeInfo.icon}"></i>
|
||||
</div>
|
||||
<div class="item-name">${file.fileName}</div>
|
||||
<div class="item-meta">
|
||||
${file.sizeStr || '0B'} · ${formatDate(file.createTime)}
|
||||
</div>
|
||||
`;
|
||||
fileCount++;
|
||||
|
||||
// 添加点击事件
|
||||
item.addEventListener('click', () => {
|
||||
handleFileClick(file);
|
||||
});
|
||||
}
|
||||
|
||||
fileGridEl.appendChild(item);
|
||||
});
|
||||
|
||||
// 更新统计信息
|
||||
folderCountEl.textContent = folderCount;
|
||||
fileCountEl.textContent = fileCount;
|
||||
}
|
||||
|
||||
// 获取文件扩展名
|
||||
function getFileExtension(filename) {
|
||||
if (!filename) return '';
|
||||
return filename.split('.').pop();
|
||||
}
|
||||
|
||||
// 进入文件夹
|
||||
function enterFolder(folder) {
|
||||
if (!folder.parserUrl) {
|
||||
alert('无法进入该文件夹,缺少访问链接');
|
||||
return;
|
||||
}
|
||||
|
||||
const newDir = {
|
||||
url: folder.parserUrl,
|
||||
name: folder.fileName || '未命名文件夹'
|
||||
};
|
||||
|
||||
pathStack.push(newDir);
|
||||
currentDir = newDir;
|
||||
|
||||
fetchFileList(currentDir.url);
|
||||
renderBreadcrumb();
|
||||
}
|
||||
|
||||
// 下载文件
|
||||
function handleFileClick(file) {
|
||||
if (!file.parserUrl) {
|
||||
alert('无法操作该文件,缺少必要链接');
|
||||
return;
|
||||
}
|
||||
|
||||
// 更友好的选择对话框
|
||||
const modal = document.createElement('div');
|
||||
modal.style.position = 'fixed';
|
||||
modal.style.top = '0';
|
||||
modal.style.left = '0';
|
||||
modal.style.width = '100%';
|
||||
modal.style.height = '100%';
|
||||
modal.style.backgroundColor = 'rgba(0,0,0,0.5)';
|
||||
modal.style.display = 'flex';
|
||||
modal.style.justifyContent = 'center';
|
||||
modal.style.alignItems = 'center';
|
||||
modal.style.zIndex = '1000';
|
||||
|
||||
const dialog = document.createElement('div');
|
||||
dialog.style.backgroundColor = 'white';
|
||||
dialog.style.padding = '20px';
|
||||
dialog.style.borderRadius = '8px';
|
||||
dialog.style.width = '300px';
|
||||
dialog.style.textAlign = 'center';
|
||||
|
||||
dialog.innerHTML = `
|
||||
<p style="margin-bottom: 20px;">${file.fileName || '未命名文件'}</p>
|
||||
<div style="display: flex; justify-content: center; gap: 15px;">
|
||||
<button id="preview-btn" style="padding: 8px 15px; background: #3498db; color: white; border: none; border-radius: 4px; cursor: pointer;">
|
||||
预览文件
|
||||
</button>
|
||||
<button id="download-btn" style="padding: 8px 15px; background: #2ecc71; color: white; border: none; border-radius: 4px; cursor: pointer;">
|
||||
下载文件
|
||||
</button>
|
||||
</div>
|
||||
`;
|
||||
|
||||
modal.appendChild(dialog);
|
||||
document.body.appendChild(modal);
|
||||
|
||||
// 预览按钮事件
|
||||
dialog.querySelector('#preview-btn').addEventListener('click', () => {
|
||||
document.body.removeChild(modal);
|
||||
const previewUrl = file.previewUrl || file.parserUrl;
|
||||
window.open(previewUrl, '_blank');
|
||||
});
|
||||
|
||||
// 下载按钮事件
|
||||
dialog.querySelector('#download-btn').addEventListener('click', () => {
|
||||
document.body.removeChild(modal);
|
||||
const iframe = document.createElement('iframe');
|
||||
iframe.style.display = 'none';
|
||||
iframe.src = file.parserUrl;
|
||||
document.body.appendChild(iframe);
|
||||
setTimeout(() => {
|
||||
document.body.removeChild(iframe);
|
||||
}, 3000);
|
||||
});
|
||||
|
||||
// 点击蒙层关闭
|
||||
modal.addEventListener('click', (e) => {
|
||||
if (e.target === modal) {
|
||||
document.body.removeChild(modal);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// 返回上一级
|
||||
function goBack() {
|
||||
if (pathStack.length > 1) {
|
||||
pathStack.pop();
|
||||
currentDir = pathStack[pathStack.length - 1];
|
||||
|
||||
fetchFileList(currentDir.url);
|
||||
renderBreadcrumb();
|
||||
}
|
||||
}
|
||||
|
||||
// 跳转到指定目录
|
||||
function goToDirectory(index) {
|
||||
pathStack.splice(index + 1);
|
||||
currentDir = pathStack[pathStack.length - 1];
|
||||
|
||||
fetchFileList(currentDir.url);
|
||||
renderBreadcrumb();
|
||||
}
|
||||
|
||||
// 格式化日期
|
||||
function formatDate(dateString) {
|
||||
if (!dateString) return '未知日期';
|
||||
|
||||
try {
|
||||
const date = new Date(dateString);
|
||||
return isNaN(date.getTime())
|
||||
? '未知日期'
|
||||
: `${date.getFullYear()}-${(date.getMonth() + 1).toString().padStart(2, '0')}-${date.getDate().toString().padStart(2, '0')}`;
|
||||
} catch {
|
||||
return '未知日期';
|
||||
}
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
BIN
web-front/public/webfonts/fa-brands-400.ttf
Normal file
BIN
web-front/public/webfonts/fa-brands-400.ttf
Normal file
Binary file not shown.
BIN
web-front/public/webfonts/fa-brands-400.woff2
Normal file
BIN
web-front/public/webfonts/fa-brands-400.woff2
Normal file
Binary file not shown.
BIN
web-front/public/webfonts/fa-regular-400.ttf
Normal file
BIN
web-front/public/webfonts/fa-regular-400.ttf
Normal file
Binary file not shown.
BIN
web-front/public/webfonts/fa-regular-400.woff2
Normal file
BIN
web-front/public/webfonts/fa-regular-400.woff2
Normal file
Binary file not shown.
BIN
web-front/public/webfonts/fa-solid-900.ttf
Normal file
BIN
web-front/public/webfonts/fa-solid-900.ttf
Normal file
Binary file not shown.
BIN
web-front/public/webfonts/fa-solid-900.woff2
Normal file
BIN
web-front/public/webfonts/fa-solid-900.woff2
Normal file
Binary file not shown.
BIN
web-front/public/webfonts/fa-v4compatibility.ttf
Normal file
BIN
web-front/public/webfonts/fa-v4compatibility.ttf
Normal file
Binary file not shown.
BIN
web-front/public/webfonts/fa-v4compatibility.woff2
Normal file
BIN
web-front/public/webfonts/fa-v4compatibility.woff2
Normal file
Binary file not shown.
@@ -1,367 +1,21 @@
|
||||
<template>
|
||||
<div id="app">
|
||||
|
||||
<el-row :gutter="20">
|
||||
<el-card class="box-card">
|
||||
|
||||
<div style="text-align: right"><DarkMode/></div>
|
||||
<div class="demo-basic--circle">
|
||||
<div class="block" style="text-align: center;">
|
||||
<img :height="150" src="../public/images/lanzou111.png" alt="lz"></img>
|
||||
</div>
|
||||
</div>
|
||||
<h3 style="text-align: center;">NFD网盘直链解析0.1.8_bate32</h3>
|
||||
<div class="typo">
|
||||
<p style="text-align: center;">
|
||||
<span>
|
||||
<el-link href="https://github.com/qaiu/netdisk-fast-download" target="_blank" rel="nofollow">
|
||||
<u>GitHub</u></el-link>
|
||||
</span>
|
||||
<span style="margin-left: 30px">
|
||||
<el-link href="https://blog.qaiu.top/archives/netdisk-fast-download-bao-ta-an-zhuang-jiao-cheng" target="_blank"
|
||||
rel="nofollow"><u>宝塔部署安装教程</u>
|
||||
</el-link>
|
||||
</span>
|
||||
<span style="margin-left: 30px">
|
||||
<el-link href="https://blog.qaiu.top" target="_blank"
|
||||
rel="nofollow"><u>QAIU博客</u>
|
||||
</el-link>
|
||||
</span></p>
|
||||
<p><strong>目前支持 </strong>蓝奏云/蓝奏云优享/小飞机盘/123云盘/奶牛快传/移动云云空间/亿方云/文叔叔/QQ邮箱文件中转站</p>
|
||||
<p>已加入缓存机制, 如果遇到解析出的下载链接失效的情况请及时到<a href="https://github.com/qaiu/netdisk-fast-download/issues">
|
||||
<u><strong>项目GitHub反馈</strong></u></a></p>
|
||||
<p>节点1: 回源请求数:{{ node1Info.parserTotal }}, 缓存请求数:{{ node1Info.cacheTotal }}, 总数:{{ node1Info.total }}</p>
|
||||
<!-- <p>节点2: 成功:{{ node2Info.success }},失败:{{ node2Info.fail }},总数:{{ node2Info.total }}</p>-->
|
||||
</div>
|
||||
<hr>
|
||||
<div class="main" v-loading="isLoading">
|
||||
<div class="grid-content">
|
||||
|
||||
<!-- 开关按钮,控制是否自动读取剪切板 -->
|
||||
<el-switch
|
||||
v-model="autoReadClipboard"
|
||||
active-text="自动识别剪切板"
|
||||
></el-switch>
|
||||
|
||||
<el-input placeholder="请粘贴分享链接(http://或https://)" v-model="link" id="url">
|
||||
<template #prepend>分享链接</template>
|
||||
<template #append v-if="!autoReadClipboard">
|
||||
<el-button @click="() => getPaste(1)">读取剪切板</el-button>
|
||||
</template>
|
||||
</el-input>
|
||||
<el-input placeholder="请输入密码" v-model="password" id="url">
|
||||
<template #prepend>分享密码</template>
|
||||
</el-input>
|
||||
<el-input v-show="getLink2" :value="getLink2" id="url">
|
||||
<template #prepend>智能直链</template>
|
||||
<template #append>
|
||||
<el-button v-clipboard:copy="getLink2"
|
||||
v-clipboard:success="onCopy"
|
||||
v-clipboard:error="onError">
|
||||
<el-icon><CopyDocument/></el-icon>
|
||||
</el-button>
|
||||
</template>
|
||||
</el-input>
|
||||
|
||||
<p style="text-align: center">
|
||||
<el-button style="margin-left: 40px;margin-bottom: 10px" @click="onSubmit">解析测试</el-button>
|
||||
<el-button style="margin-left: 20px;margin-bottom: 10px" @click="genMd">生成Markdown链接</el-button>
|
||||
<el-button style="margin-left: 20px" @click="generateQRCode">扫码下载</el-button>
|
||||
<el-button style="margin-left: 20px" @click="getTj">链接信息统计</el-button>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div v-if="respData.code" style="margin-top: 10px">
|
||||
<strong>解析结果: </strong>
|
||||
<json-viewer
|
||||
:value="respData"
|
||||
:expand-depth=5
|
||||
copyable
|
||||
boxed
|
||||
sort
|
||||
/>
|
||||
<a :href="downUrl" v-show="downUrl">点击下载</a>
|
||||
</div>
|
||||
<div v-if="mdText" style="text-align: center">
|
||||
<el-input :value="mdText" readonly>
|
||||
<template #append>
|
||||
<el-button v-clipboard:copy="mdText"
|
||||
v-clipboard:success="onCopy"
|
||||
v-clipboard:error="onError">
|
||||
<el-icon><CopyDocument/></el-icon>
|
||||
</el-button>
|
||||
</template>
|
||||
</el-input>
|
||||
</div>
|
||||
<div style="text-align: center" v-show="showQrc">
|
||||
<canvas ref="qrcodeCanvas"></canvas>
|
||||
<div style="text-align: center"><el-link target="_blank" :href="codeUrl">{{ codeUrl }}</el-link></div>
|
||||
</div>
|
||||
<div v-if="tjData.shareLinkInfo">
|
||||
<el-descriptions class="margin-top" title="分享详情" :column="1" border>
|
||||
<template slot="extra">
|
||||
<el-button type="primary" size="small">操作</el-button>
|
||||
</template>
|
||||
<el-descriptions-item label="网盘名称">{{ tjData.shareLinkInfo.panName }}</el-descriptions-item>
|
||||
<el-descriptions-item label="网盘标识">{{ tjData.shareLinkInfo.type }}</el-descriptions-item>
|
||||
<el-descriptions-item label="分享Key">{{ tjData.shareLinkInfo.shareKey }}</el-descriptions-item>
|
||||
<el-descriptions-item label="分享链接"> <el-link target="_blank" :href="tjData.shareLinkInfo.shareUrl">{{ tjData.shareLinkInfo.shareUrl }}</el-link></el-descriptions-item>
|
||||
<el-descriptions-item label="jsonApi链接"> <el-link target="_blank" :href="tjData.apiLink">{{ tjData.apiLink }}</el-link></el-descriptions-item>
|
||||
<el-descriptions-item label="302下载链接"> <el-link target="_blank" :href="tjData.downLink">{{ tjData.downLink }}</el-link></el-descriptions-item>
|
||||
<el-descriptions-item label="解析次数">{{ tjData.parserTotal }}</el-descriptions-item>
|
||||
<el-descriptions-item label="缓存命中次数">{{ tjData.cacheHitTotal }}</el-descriptions-item>
|
||||
<el-descriptions-item label="总请求次数">{{ tjData.sumTotal }}</el-descriptions-item>
|
||||
</el-descriptions>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</el-card>
|
||||
</el-row>
|
||||
<router-view></router-view>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import axios from 'axios'
|
||||
import QRCode from 'qrcode'
|
||||
import DarkMode from '@/components/DarkMode'
|
||||
|
||||
import parserUrl from './parserUrl1'
|
||||
|
||||
export default {
|
||||
name: 'App',
|
||||
components: {DarkMode},
|
||||
data() {
|
||||
return {
|
||||
// baseAPI: `${location.protocol}//${location.hostname}:6400`,
|
||||
baseAPI: `${location.protocol}//${location.host}`,
|
||||
autoReadClipboard: true, // 开关状态,默认为自动读取
|
||||
current: {}, // 当前分享
|
||||
showQrc: false,
|
||||
codeUrl: '',
|
||||
mdText: '',
|
||||
link: "",
|
||||
password: "",
|
||||
isLoading: false,
|
||||
downUrl: null,
|
||||
select: "lz",
|
||||
respData: {},
|
||||
tjData: {},
|
||||
panList: [
|
||||
{
|
||||
name: "蓝奏云",
|
||||
value: 'lz'
|
||||
},
|
||||
{
|
||||
name: "奶牛快传",
|
||||
value: 'cow'
|
||||
},
|
||||
{
|
||||
name: "移动云空间",
|
||||
value: 'ec'
|
||||
},
|
||||
{
|
||||
name: "UC网盘",
|
||||
value: 'uc',
|
||||
disabled: true
|
||||
},
|
||||
{
|
||||
name: "小飞机网盘",
|
||||
value: 'fj'
|
||||
},
|
||||
{
|
||||
name: "360亿方云",
|
||||
value: 'fc'
|
||||
},
|
||||
{
|
||||
name: "123云盘",
|
||||
value: 'ye'
|
||||
},
|
||||
],
|
||||
getLink: null,
|
||||
getLink2: '',
|
||||
getLinkInfo: null,
|
||||
node1Info: {},
|
||||
node2Info: {},
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
// toggleDark() {
|
||||
// toggleDark()
|
||||
// },
|
||||
check() {
|
||||
this.mdText = ''
|
||||
this.showQrc = false
|
||||
this.respData = {}
|
||||
this.tjData = {}
|
||||
if (!this.link.startsWith("https://") && !this.link.startsWith("http://")) {
|
||||
this.$message.error("请输入有效链接!")
|
||||
throw new Error('请输入有效链接')
|
||||
}
|
||||
},
|
||||
onSubmit() {
|
||||
this.check()
|
||||
this.isLoading = true
|
||||
this.downUrl = ''
|
||||
this.respData = {}
|
||||
this.getLink2 = `${this.baseAPI}/parser?url=${this.link}`
|
||||
// this.getLink = `${location.protocol}//${location.host}/api/json/parser?url=${this.link}`
|
||||
// this.getLink = `${location.protocol}//${location.host}/json/parser`
|
||||
if (this.password) {
|
||||
this.getLink2 += `&pwd=${this.password}`
|
||||
}
|
||||
axios.get(this.getLink, {params: {url: this.link, pwd: this.password}}).then(
|
||||
response => {
|
||||
this.isLoading = false
|
||||
this.respData = response.data
|
||||
if (response.data.code === 200) {
|
||||
this.$message({
|
||||
message: response.data.msg,
|
||||
type: 'success'
|
||||
})
|
||||
this.downUrl = response.data.data.directLink
|
||||
} else {
|
||||
this.$message.error(response.data.msg)
|
||||
}
|
||||
this.getInfo()
|
||||
},
|
||||
error => {
|
||||
this.isLoading = false
|
||||
this.$message.error(error.message)
|
||||
}
|
||||
)
|
||||
},
|
||||
onCopy() {
|
||||
this.$message.success('复制成功')
|
||||
},
|
||||
onError() {
|
||||
this.$message.error('复制失败')
|
||||
},
|
||||
getInfo() {
|
||||
// 初始化统计信息
|
||||
axios.get('/v2/statisticsInfo').then(
|
||||
response => {
|
||||
if (response.data.success) {
|
||||
this.node1Info = response.data.data
|
||||
}
|
||||
})
|
||||
// axios.get('/n2/statisticsInfo').then(
|
||||
// response => {
|
||||
// if (response.data.success) {
|
||||
// this.node2Info = response.data.data
|
||||
// }
|
||||
// })
|
||||
},
|
||||
genMd() {
|
||||
this.check()
|
||||
axios.get(this.getLinkInfo, {params: {url: this.link, pwd: this.password}}).then(
|
||||
response => {
|
||||
this.isLoading = false
|
||||
if (response.data.code === 200) {
|
||||
this.$message({
|
||||
message: response.data.msg,
|
||||
type: 'success'
|
||||
})
|
||||
this.mdText = this.buildMd('快速下载地址',response.data.data.downLink)
|
||||
} else {
|
||||
this.$message.error(response.data.msg)
|
||||
}
|
||||
});
|
||||
},
|
||||
buildMd(title, url) {
|
||||
return `[${title}](${url})`
|
||||
},
|
||||
generateQRCode() {
|
||||
this.check()
|
||||
const options = { // 设置二维码的参数,例如大小、边距等
|
||||
width: 150,
|
||||
height: 150,
|
||||
margin: 2
|
||||
};
|
||||
axios.get(this.getLinkInfo, {params: {url: this.link, pwd: this.password}}).then(
|
||||
response => {
|
||||
this.isLoading = false
|
||||
if (response.data.code === 200) {
|
||||
this.$message({
|
||||
message: response.data.msg,
|
||||
type: 'success'
|
||||
})
|
||||
this.codeUrl = response.data.data.downLink
|
||||
QRCode.toCanvas(this.$refs.qrcodeCanvas, this.codeUrl, options, error => {
|
||||
if (error) console.error(error);
|
||||
});
|
||||
this.showQrc = true
|
||||
} else {
|
||||
this.$message.error(response.data.msg)
|
||||
}
|
||||
});
|
||||
},
|
||||
getTj() {
|
||||
this.check()
|
||||
axios.get(this.getLinkInfo, {params: {url: this.link, pwd: this.password}}).then(
|
||||
response => {
|
||||
this.isLoading = false
|
||||
if (response.data.code === 200) {
|
||||
this.$message({
|
||||
message: response.data.msg,
|
||||
type: 'success'
|
||||
})
|
||||
this.tjData = response.data.data
|
||||
} else {
|
||||
this.$message.error(response.data.msg)
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
async getPaste(v) {
|
||||
const text = await navigator.clipboard.readText();
|
||||
console.log('获取到的文本内容是:', text);
|
||||
let linkInfo = parserUrl.parseLink(text);
|
||||
let pwd = parserUrl.parsePwd(text) || '';
|
||||
if (linkInfo.link) {
|
||||
if(linkInfo.link !== this.link || pwd !== this.password ) {
|
||||
this.password = pwd;
|
||||
this.link = linkInfo.link;
|
||||
this.getLink2 = `${this.baseAPI}/parser?url=${this.link}`
|
||||
if (this.link) this.$message.success(`自动识别分享成功, 网盘类型: ${linkInfo.name}; 分享URL ${this.link}; 分享密码: ${this.password || '空'}`);
|
||||
} else {
|
||||
v || this.$message.warning(`[${linkInfo.name}]分享信息无变化`)
|
||||
}
|
||||
} else {
|
||||
this.$message.warning("未能提取到分享链接, 该分享可能尚未支持, 你可以复制任意网盘/音乐App的分享到该页面, 系统智能识别")
|
||||
}
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
this.getLinkInfo = `${this.baseAPI}/v2/linkInfo`
|
||||
this.getLink = `${this.baseAPI}/json/parser`
|
||||
let item = window.localStorage.getItem("autoReadClipboard");
|
||||
if (item) {
|
||||
this.autoReadClipboard = (item === 'true');
|
||||
}
|
||||
|
||||
this.getInfo()
|
||||
|
||||
// 页面首次加载时,根据开关状态判断是否读取剪切板
|
||||
if (this.autoReadClipboard) {
|
||||
this.getPaste()
|
||||
}
|
||||
// 当文档获得焦点时触发
|
||||
window.addEventListener('focus', () => {
|
||||
if (this.autoReadClipboard) {
|
||||
this.getPaste()
|
||||
}
|
||||
});
|
||||
},
|
||||
watch: {
|
||||
autoReadClipboard(val) {
|
||||
window.localStorage.setItem("autoReadClipboard", val)
|
||||
}
|
||||
}
|
||||
|
||||
name: 'App'
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
body {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
|
||||
}
|
||||
#app {
|
||||
font-family: 'Avenir', Helvetica, Arial, sans-serif;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
@@ -371,62 +25,12 @@ export default {
|
||||
padding: 1em;
|
||||
max-width: 900px;
|
||||
}
|
||||
|
||||
|
||||
body:before {
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
opacity: .3;
|
||||
z-index: -1;
|
||||
position: fixed;
|
||||
nav ul {
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.grid-content {
|
||||
margin-top: 1em;
|
||||
border-radius: 4px;
|
||||
min-height: 50px;
|
||||
}
|
||||
|
||||
.el-select .el-input {
|
||||
width: 130px;
|
||||
}
|
||||
|
||||
.box-card {
|
||||
margin-top: 4em !important;
|
||||
margin-bottom: 4em !important;
|
||||
opacity: .8;
|
||||
}
|
||||
|
||||
@media screen and (max-width: 700px) {
|
||||
.box-card {
|
||||
margin-top: 1em !important;
|
||||
margin-bottom: 1em !important;
|
||||
}
|
||||
}
|
||||
|
||||
.download h3 {
|
||||
margin-top: 2em;
|
||||
}
|
||||
|
||||
.download button {
|
||||
margin-right: 0.5em;
|
||||
margin-left: 0.5em;
|
||||
}
|
||||
|
||||
.typo {
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.typo a {
|
||||
color: #0077ff;
|
||||
}
|
||||
|
||||
hr {
|
||||
height: 10px;
|
||||
margin-bottom: .8em;
|
||||
border: none;
|
||||
border-bottom: 1px solid rgba(0, 0, 0, .12);
|
||||
nav li {
|
||||
display: inline;
|
||||
margin-right: 15px;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -11,14 +11,18 @@
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref,watch } from 'vue'
|
||||
import { ref, watch, onMounted } from 'vue'
|
||||
import { useDark, useToggle } from '@vueuse/core'
|
||||
/** 引入Element-Plus图标 */
|
||||
import { Sunny, Moon } from '@element-plus/icons-vue'
|
||||
|
||||
defineOptions({
|
||||
name: 'DarkMode'
|
||||
})
|
||||
|
||||
// 定义事件
|
||||
const emit = defineEmits(['theme-change'])
|
||||
|
||||
/** 切换模式 */
|
||||
const isDark = useDark({})
|
||||
|
||||
@@ -30,8 +34,32 @@ if (item) {
|
||||
}
|
||||
/** 是否切换为暗黑模式 */
|
||||
const darkMode = ref(item)
|
||||
|
||||
watch(darkMode, (newValue) => {
|
||||
console.log(`darkMode: ${newValue}`)
|
||||
window.localStorage.setItem("darkMode", newValue);
|
||||
|
||||
// 发射主题变化事件
|
||||
emit('theme-change', newValue)
|
||||
|
||||
// 应用主题到body
|
||||
if (newValue) {
|
||||
document.body.classList.add('dark-theme')
|
||||
document.documentElement.classList.add('dark-theme')
|
||||
} else {
|
||||
document.body.classList.remove('dark-theme')
|
||||
document.documentElement.classList.remove('dark-theme')
|
||||
}
|
||||
})
|
||||
|
||||
onMounted(() => {
|
||||
// 初始化时发射当前主题状态
|
||||
emit('theme-change', darkMode.value)
|
||||
|
||||
// 应用初始主题
|
||||
if (darkMode.value) {
|
||||
document.body.classList.add('dark-theme')
|
||||
document.documentElement.classList.add('dark-theme')
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
@@ -9,22 +9,24 @@ import 'element-plus/dist/index.css'
|
||||
import 'element-plus/theme-chalk/dark/css-vars.css'
|
||||
import "vue3-json-viewer/dist/index.css";
|
||||
import './styles/dark/css-vars.css'
|
||||
import router from './router'
|
||||
|
||||
window.$vueApp = Vue.createApp(App)
|
||||
const app = Vue.createApp(App)
|
||||
app.use(router)
|
||||
|
||||
|
||||
import * as ElementPlusIconsVue from '@element-plus/icons-vue'
|
||||
for (const [key, component] of Object.entries(ElementPlusIconsVue)) {
|
||||
window.$vueApp.component(key, component)
|
||||
app.component(key, component)
|
||||
}
|
||||
|
||||
// Import JsonViewer as a Vue.js plugin
|
||||
window.$vueApp.use(JsonViewer)
|
||||
window.$vueApp.use(DirectiveExtensions)
|
||||
app.use(JsonViewer)
|
||||
app.use(DirectiveExtensions)
|
||||
|
||||
// or
|
||||
// components: {JsonViewer}
|
||||
|
||||
window.$vueApp.use(VueClipboard)
|
||||
window.$vueApp.use(ElementPlus)
|
||||
window.$vueApp.mount('#app')
|
||||
app.use(VueClipboard)
|
||||
app.use(ElementPlus)
|
||||
app.mount('#app')
|
||||
|
||||
@@ -0,0 +1,164 @@
|
||||
/* 全局样式 */
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: 'Avenir', Helvetica, Arial, sans-serif;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
/* 亮色主题 */
|
||||
body {
|
||||
background-color: #f5f7fa;
|
||||
color: #2c3e50;
|
||||
}
|
||||
|
||||
/* 暗色主题 */
|
||||
body.dark-theme {
|
||||
background-color: #1a1a1a;
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
/* Element Plus 暗色主题适配 */
|
||||
.dark-theme .el-card {
|
||||
background-color: #2d2d2d !important;
|
||||
border-color: #404040 !important;
|
||||
color: #ffffff !important;
|
||||
}
|
||||
|
||||
.dark-theme .el-input__inner {
|
||||
background-color: #404040 !important;
|
||||
border-color: #555555 !important;
|
||||
color: #ffffff !important;
|
||||
}
|
||||
|
||||
.dark-theme .el-input__inner::placeholder {
|
||||
color: #bdc3c7 !important;
|
||||
}
|
||||
|
||||
.dark-theme .el-button {
|
||||
background-color: #404040 !important;
|
||||
border-color: #555555 !important;
|
||||
color: #ffffff !important;
|
||||
}
|
||||
|
||||
.dark-theme .el-button:hover {
|
||||
background-color: #555555 !important;
|
||||
}
|
||||
|
||||
.dark-theme .el-button--primary {
|
||||
background-color: #409eff !important;
|
||||
border-color: #409eff !important;
|
||||
}
|
||||
|
||||
.dark-theme .el-button--primary:hover {
|
||||
background-color: #66b1ff !important;
|
||||
}
|
||||
|
||||
.dark-theme .el-button--success {
|
||||
background-color: #67c23a !important;
|
||||
border-color: #67c23a !important;
|
||||
}
|
||||
|
||||
.dark-theme .el-button--success:hover {
|
||||
background-color: #85ce61 !important;
|
||||
}
|
||||
|
||||
.dark-theme .el-switch__core {
|
||||
background-color: #555555 !important;
|
||||
}
|
||||
|
||||
.dark-theme .el-descriptions {
|
||||
background-color: #2d2d2d !important;
|
||||
color: #ffffff !important;
|
||||
}
|
||||
|
||||
.dark-theme .el-descriptions__label {
|
||||
color: #bdc3c7 !important;
|
||||
}
|
||||
|
||||
.dark-theme .el-descriptions__content {
|
||||
color: #ffffff !important;
|
||||
}
|
||||
|
||||
.dark-theme .el-dialog {
|
||||
background-color: #2d2d2d !important;
|
||||
color: #ffffff !important;
|
||||
}
|
||||
|
||||
.dark-theme .el-dialog__title {
|
||||
color: #ffffff !important;
|
||||
}
|
||||
|
||||
.dark-theme .el-dialog__body {
|
||||
color: #ffffff !important;
|
||||
}
|
||||
|
||||
.dark-theme .el-message {
|
||||
background-color: #2d2d2d !important;
|
||||
color: #ffffff !important;
|
||||
}
|
||||
|
||||
.dark-theme .el-message--success {
|
||||
background-color: #67c23a !important;
|
||||
color: #ffffff !important;
|
||||
}
|
||||
|
||||
.dark-theme .el-message--error {
|
||||
background-color: #f56c6c !important;
|
||||
color: #ffffff !important;
|
||||
}
|
||||
|
||||
.dark-theme .el-message--warning {
|
||||
background-color: #e6a23c !important;
|
||||
color: #ffffff !important;
|
||||
}
|
||||
|
||||
.dark-theme .el-message--info {
|
||||
background-color: #909399 !important;
|
||||
color: #ffffff !important;
|
||||
}
|
||||
|
||||
/* 链接颜色 */
|
||||
.dark-theme a {
|
||||
color: #4a9eff !important;
|
||||
}
|
||||
|
||||
.dark-theme a:hover {
|
||||
color: #66b1ff !important;
|
||||
}
|
||||
|
||||
/* 滚动条样式 */
|
||||
.dark-theme ::-webkit-scrollbar {
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
}
|
||||
|
||||
.dark-theme ::-webkit-scrollbar-track {
|
||||
background: #2d2d2d;
|
||||
}
|
||||
|
||||
.dark-theme ::-webkit-scrollbar-thumb {
|
||||
background: #555555;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.dark-theme ::-webkit-scrollbar-thumb:hover {
|
||||
background: #666666;
|
||||
}
|
||||
|
||||
/* 选择文本样式 */
|
||||
.dark-theme ::selection {
|
||||
background-color: #409eff;
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
.dark-theme ::-moz-selection {
|
||||
background-color: #409eff;
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
@@ -16,15 +16,27 @@ module.exports = {
|
||||
host: '127.0.0.1',
|
||||
port: 6444,
|
||||
proxy: {
|
||||
'/': {
|
||||
target: 'http://127.0.0.1:6400', // 请求本地
|
||||
'/parser': {
|
||||
target: 'http://127.0.0.1:6400/', // 请求本地
|
||||
ws: false
|
||||
},
|
||||
'/v2': {
|
||||
target: 'http://127.0.0.1:6400/', // 请求本地
|
||||
ws: false
|
||||
},
|
||||
'/json': {
|
||||
target: 'http://127.0.0.1:6400/', // 请求本地
|
||||
ws: false
|
||||
},
|
||||
'/d': {
|
||||
target: 'http://127.0.0.1:6400/', // 请求本地
|
||||
ws: false
|
||||
},
|
||||
}
|
||||
},
|
||||
configureWebpack: {
|
||||
// provide the app's title in webpack's name field, so that
|
||||
// it can be accessed in index.html to inject the correct title.
|
||||
// it can be accessed in list.html to inject the correct title.
|
||||
name: 'Netdisk fast download',
|
||||
resolve: {
|
||||
alias: {
|
||||
|
||||
@@ -59,7 +59,7 @@ public class URLParamUtil {
|
||||
boolean firstParam = !decodedUrl.contains("?");
|
||||
|
||||
for (String paramName : params.names()) {
|
||||
if (!paramName.equals("url") && !paramName.equals("pwd")) { // 忽略 "url" 和 "pwd" 参数
|
||||
if (!paramName.equals("url") && !paramName.equals("pwd") && !paramName.equals("dirId") && !paramName.equals("uuid")) { // 忽略 "url" 和 "pwd" 参数
|
||||
if (firstParam) {
|
||||
urlBuilder.append("?");
|
||||
firstParam = false;
|
||||
|
||||
@@ -5,6 +5,7 @@ import cn.qaiu.entity.FileInfo;
|
||||
import cn.qaiu.entity.ShareLinkInfo;
|
||||
import cn.qaiu.lz.common.cache.CacheManager;
|
||||
import cn.qaiu.lz.common.util.URLParamUtil;
|
||||
import cn.qaiu.lz.web.model.CacheLinkInfo;
|
||||
import cn.qaiu.lz.web.model.LinkInfoResp;
|
||||
import cn.qaiu.lz.web.model.StatisticsInfo;
|
||||
import cn.qaiu.lz.web.model.SysUser;
|
||||
@@ -15,6 +16,7 @@ import cn.qaiu.parser.ParserCreate;
|
||||
import cn.qaiu.vx.core.annotaions.RouteHandler;
|
||||
import cn.qaiu.vx.core.annotaions.RouteMapping;
|
||||
import cn.qaiu.vx.core.enums.RouteMethod;
|
||||
import cn.qaiu.vx.core.model.JsonResult;
|
||||
import cn.qaiu.vx.core.util.AsyncServiceUtil;
|
||||
import cn.qaiu.vx.core.util.ResponseUtil;
|
||||
import cn.qaiu.vx.core.util.SharedDataUtil;
|
||||
@@ -26,6 +28,8 @@ import io.vertx.core.json.JsonObject;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
||||
import java.net.URLEncoder;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.*;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@@ -33,14 +37,8 @@ import java.util.stream.Collectors;
|
||||
@Slf4j
|
||||
public class ParserApi {
|
||||
|
||||
private final UserService userService = AsyncServiceUtil.getAsyncServiceInstance(UserService.class);
|
||||
private final DbService dbService = AsyncServiceUtil.getAsyncServiceInstance(DbService.class);
|
||||
|
||||
@RouteMapping(value = "/login", method = RouteMethod.GET)
|
||||
public Future<SysUser> login(SysUser user) {
|
||||
log.info("<------- login: {}", user.getUsername());
|
||||
return userService.login(user);
|
||||
}
|
||||
|
||||
@RouteMapping(value = "/statisticsInfo", method = RouteMethod.GET, order = 99)
|
||||
public Future<StatisticsInfo> statisticsInfo() {
|
||||
@@ -100,11 +98,17 @@ public class ParserApi {
|
||||
}
|
||||
|
||||
@RouteMapping("/getFileList")
|
||||
public Future<List<FileInfo>> getFileList(HttpServerRequest request, String pwd) {
|
||||
public Future<List<FileInfo>> getFileList(HttpServerRequest request, String pwd, String dirId, String uuid) {
|
||||
String url = URLParamUtil.parserParams(request);
|
||||
ParserCreate parserCreate = ParserCreate.fromShareUrl(url).setShareLinkInfoPwd(pwd);
|
||||
String linkPrefix = SharedDataUtil.getJsonConfig("server").getString("domainName");
|
||||
parserCreate.getShareLinkInfo().getOtherParam().put("domainName", linkPrefix);
|
||||
if (StringUtils.isNotBlank(dirId)) {
|
||||
parserCreate.getShareLinkInfo().getOtherParam().put("dirId", dirId);
|
||||
}
|
||||
if (StringUtils.isNotBlank(uuid)) {
|
||||
parserCreate.getShareLinkInfo().getOtherParam().put("uuid", uuid);
|
||||
}
|
||||
return parserCreate.createTool().parseFileList();
|
||||
}
|
||||
|
||||
@@ -130,7 +134,6 @@ public class ParserApi {
|
||||
return parserCreate.createTool().parseById();
|
||||
}
|
||||
|
||||
|
||||
@RouteMapping("/redirectUrl/:type/:param")
|
||||
public Future<Void> redirectUrl(HttpServerResponse response, String type, String param) {
|
||||
Promise<Void> promise = Promise.promise();
|
||||
@@ -140,4 +143,51 @@ public class ParserApi {
|
||||
.onFailure(t -> promise.fail(t.fillInStackTrace()));
|
||||
return promise.future();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 预览媒体文件
|
||||
*/
|
||||
@RouteMapping(value = "/view/:type/:key", method = RouteMethod.GET, order = 2)
|
||||
public void view(HttpServerRequest request, HttpServerResponse response, String type, String key) {
|
||||
String previewURL = SharedDataUtil.getJsonStringForServerConfig("previewURL");
|
||||
new ServerApi().parseKeyJson(request, type, key).onSuccess(res -> {
|
||||
redirect(response, previewURL, res);
|
||||
}).onFailure(e -> {
|
||||
ResponseUtil.fireJsonResultResponse(response, JsonResult.error(e.toString()));
|
||||
});
|
||||
}
|
||||
|
||||
private static void redirect(HttpServerResponse response, String previewURL, CacheLinkInfo res) {
|
||||
String directLink = res.getDirectLink();
|
||||
ResponseUtil.redirect(response, previewURL + URLEncoder.encode(directLink, StandardCharsets.UTF_8));
|
||||
}
|
||||
|
||||
/**
|
||||
* 预览媒体文件
|
||||
*/
|
||||
@RouteMapping(value = "/preview", method = RouteMethod.GET, order = 9)
|
||||
public void viewURL(HttpServerRequest request, HttpServerResponse response, String pwd) {
|
||||
String previewURL = SharedDataUtil.getJsonStringForServerConfig("previewURL");
|
||||
new ServerApi().parseJson(request, pwd).onSuccess(res -> {
|
||||
redirect(response, previewURL, res);
|
||||
}).onFailure(e -> {
|
||||
ResponseUtil.fireJsonResultResponse(response, JsonResult.error(e.toString()));
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@RouteMapping("/viewUrl/:type/:param")
|
||||
public Future<Void> viewUrl(HttpServerResponse response, String type, String param) {
|
||||
Promise<Void> promise = Promise.promise();
|
||||
|
||||
String viewPrefix = SharedDataUtil.getJsonConfig("server").getString("previewURL");
|
||||
getFileDownUrl(type, param)
|
||||
.onSuccess(res -> {
|
||||
String url = viewPrefix + URLEncoder.encode(res, StandardCharsets.UTF_8);
|
||||
ResponseUtil.redirect(response, url);
|
||||
})
|
||||
.onFailure(t -> promise.fail(t.fillInStackTrace()));
|
||||
return promise.future();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,6 +6,8 @@ server:
|
||||
enableDatabase: true
|
||||
# 服务域名或者IP 生成二维码链接时需要
|
||||
domainName: http://127.0.0.1:6401
|
||||
# 预览服务URL
|
||||
previewURL: https://nfd-parser.github.io/nfd-preview/preview.html?src=
|
||||
# domainName: https://lz.qaiu.top
|
||||
|
||||
# 反向代理服务器配置路径(不用加后缀)
|
||||
@@ -38,7 +40,7 @@ rateLimit:
|
||||
# 是否启用限流
|
||||
enable: true
|
||||
# 限流的请求数
|
||||
limit: 5
|
||||
limit: 10
|
||||
# 限流的时间窗口(单位秒)
|
||||
timeWindow: 10
|
||||
# 路径匹配规则
|
||||
|
||||
@@ -135,3 +135,23 @@ POST https://www.123684.com/b/api/file/batch_download_share_info?3697171543=1749
|
||||
Content-Type: application/json;charset=UTF-8
|
||||
|
||||
{"ShareKey":"LH3rTd-pENed","fileIdList":[{"fileId":17525951}]}
|
||||
|
||||
###
|
||||
|
||||
https://www.123865.com/b/api/share/get?limit=100&next=-1&orderBy=file_name&orderDirection=asc&shareKey=iaKtVv-FTaCd&SharePwd=qaiu&ParentFileId=0&Page=1&event=homeListFile&operateType=1&OrderId=
|
||||
Accept: */*
|
||||
Accept-Encoding: gzip, deflate, br, zstd
|
||||
Accept-Language: zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6
|
||||
App-Version: 3
|
||||
Connection: keep-alive
|
||||
DNT: 1
|
||||
Host: www.123865.com
|
||||
Referer: https://www.123865.com/s/iaKtVv-FTaCd?%E6%8F%90%E5%8F%96%E7%A0%81%3Aqaiu
|
||||
Sec-Fetch-Dest: empty
|
||||
Sec-Fetch-Mode: cors
|
||||
Sec-Fetch-Site: same-origin
|
||||
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/138.0.0.0 Safari/537.36 Edg/138.0.0.0
|
||||
platform: web
|
||||
sec-ch-ua: "Not)A;Brand";v="8", "Chromium";v="138", "Microsoft Edge";v="138"
|
||||
sec-ch-ua-mobile: ?0
|
||||
sec-ch-ua-platform: "Windows"
|
||||
Reference in New Issue
Block a user