mirror of
https://github.com/qaiu/netdisk-fast-download.git
synced 2025-12-16 20:33:03 +00:00
Compare commits
21 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b2a7187fc5 | ||
|
|
ace7cdc88e | ||
|
|
2e909b5868 | ||
|
|
de78bcbc98 | ||
|
|
c560f0e902 | ||
|
|
88860c9302 | ||
|
|
ef65d0e095 | ||
|
|
6438505f4a | ||
|
|
1be5030dd1 | ||
|
|
421b2f4a42 | ||
|
|
a66bf84381 | ||
|
|
0c4d366d6d | ||
|
|
a1d0a921fa | ||
|
|
2092230a61 | ||
|
|
6e5ae6eff3 | ||
|
|
4f8259d772 | ||
|
|
8b987d9824 | ||
|
|
e8ba451d18 | ||
|
|
77758db463 | ||
|
|
6c58598a8e | ||
|
|
3ac35230a3 |
3
.github/workflows/maven.yml
vendored
3
.github/workflows/maven.yml
vendored
@@ -80,6 +80,8 @@ jobs:
|
||||
username: ${{ github.actor }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v3
|
||||
|
||||
- name: Extract git tag
|
||||
id: tag
|
||||
@@ -93,6 +95,7 @@ jobs:
|
||||
with:
|
||||
context: .
|
||||
push: true
|
||||
platforms: linux/amd64,linux/arm64,linux/arm/v7
|
||||
tags: |
|
||||
ghcr.io/qaiu/netdisk-fast-download:${{ steps.tag.outputs.tag }}
|
||||
ghcr.io/qaiu/netdisk-fast-download:latest
|
||||
|
||||
@@ -1,7 +1,10 @@
|
||||
FROM eclipse-temurin:17-alpine
|
||||
FROM azul/zulu-openjdk:17-jre-headless
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
# 安装 unzip
|
||||
RUN apt-get update && apt-get install -y unzip && rm -rf /var/lib/apt/lists/*
|
||||
|
||||
COPY ./web-service/target/netdisk-fast-download-bin.zip .
|
||||
|
||||
RUN unzip netdisk-fast-download-bin.zip && \
|
||||
|
||||
33
README.md
33
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://img.shields.io/github/actions/workflow/status/qaiu/netdisk-fast-download/maven.yml?branch=v0.1.9b2&style=flat"></a>
|
||||
<a href="https://github.com/qaiu/netdisk-fast-download/actions/workflows/maven.yml"><img src="https://img.shields.io/github/actions/workflow/status/qaiu/netdisk-fast-download/maven.yml?branch=v0.1.9b8a&style=flat"></a>
|
||||
<a href="https://www.oracle.com/cn/java/technologies/downloads"><img src="https://img.shields.io/badge/jdk-%3E%3D17-blue"></a>
|
||||
<a href="https://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>
|
||||
@@ -18,6 +18,25 @@ QQ群:1017480890
|
||||
|
||||
netdisk-fast-download网盘直链云解析(nfd云解析)能把网盘分享下载链接转化为直链,支持多款云盘,已支持蓝奏云/蓝奏云优享/奶牛快传/移动云云空间/小飞机盘/亿方云/123云盘/Cloudreve等,支持加密分享,以及部分网盘文件夹分享。
|
||||
|
||||
## 快速开始
|
||||
命令行下载分享文件:
|
||||
```shell
|
||||
curl -LOJ "https://lz.qaiu.top/parser?url=https://share.feijipan.com/s/nQOaNRPW&pwd=1234"
|
||||
```
|
||||
或者使用wget:
|
||||
```shell
|
||||
wget -O bilibili.mp4 "https://lz.qaiu.top/parser?url=https://share.feijipan.com/s/nQOaNRPW&pwd=1234"
|
||||
```
|
||||
或者使用浏览器[直接访问](https://nfd-parser.github.io/nfd-preview/preview.html?src=https%3A%2F%2Flz.qaiu.top%2Fparser%3Furl%3Dhttps%3A%2F%2Fshare.feijipan.com%2Fs%2FnQOaNRPW&name=bilibili.mp4&ext=mp4):
|
||||
```
|
||||
### 调用演示站下载:
|
||||
https://lz.qaiu.top/parser?url=https://share.feijipan.com/s/nQOaNRPW&pwd=1234
|
||||
### 调用演示站预览:
|
||||
https://nfd-parser.github.io/nfd-preview/preview.html?src=https%3A%2F%2Flz.qaiu.top%2Fparser%3Furl%3Dhttps%3A%2F%2Fshare.feijipan.com%2Fs%2FnQOaNRPW&name=bilibili.mp4&ext=mp4
|
||||
|
||||
```
|
||||
|
||||
## 预览地址
|
||||
[预览地址1](https://lz.qaiu.top)
|
||||
[预览地址2](http://www.722shop.top:6401)
|
||||
[天翼云盘大文件解析限时开放](https://189.qaiu.top)
|
||||
@@ -65,7 +84,7 @@ main分支依赖JDK17, 提供了JDK11分支[main-jdk11](https://github.com/qaiu/
|
||||
- [联通云盘-pwo](https://pan.wo.cn/)
|
||||
- [天翼云盘-p189](https://cloud.189.cn/)
|
||||
|
||||
### API接口说明
|
||||
## API接口说明
|
||||
your_host指的是您的域名或者IP,实际使用时替换为实际域名或者IP,端口默认6400,可以使用nginx代理来做域名访问。
|
||||
解析方式分为两种类型直接跳转下载文件和获取下载链接,
|
||||
每一种都提供了两种接口形式: `通用接口parser?url=`和`网盘标志/分享key拼接的短地址(标志短链)`,所有规则参考示例。
|
||||
@@ -93,7 +112,7 @@ API规则:
|
||||
|
||||
### json接口说明
|
||||
|
||||
1. 文件解析:/json/parser?url=分享链接&pwd=xxx
|
||||
#### 1. 文件解析:/json/parser?url=分享链接&pwd=xxx
|
||||
|
||||
json返回数据格式示例:
|
||||
`shareKey`: 全局分享key
|
||||
@@ -117,7 +136,7 @@ json返回数据格式示例:
|
||||
"timestamp": 1726637151902
|
||||
}
|
||||
```
|
||||
2. 分享链接详情接口 /v2/linkInfo?url=分享链接
|
||||
#### 2. 分享链接详情接口 /v2/linkInfo?url=分享链接
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
@@ -146,7 +165,7 @@ json返回数据格式示例:
|
||||
"timestamp": 1736489219402
|
||||
}
|
||||
```
|
||||
3. 文件夹解析(仅支持蓝奏云/蓝奏优享/小飞机网盘)
|
||||
#### 3. 文件夹解析(仅支持蓝奏云/蓝奏优享/小飞机网盘)
|
||||
/v2/getFileList?url=分享链接&pwd=分享密码
|
||||
|
||||
```json
|
||||
@@ -167,7 +186,7 @@ json返回数据格式示例:
|
||||
"updateTime": null,
|
||||
"createBy": null,
|
||||
"description": null,
|
||||
"downloadCount": 下载次数,
|
||||
"downloadCount": "下载次数",
|
||||
"panType": "lz",
|
||||
"parserUrl": "下载链接/文件夹链接",
|
||||
"extParameters": null
|
||||
@@ -175,7 +194,7 @@ json返回数据格式示例:
|
||||
]
|
||||
}
|
||||
```
|
||||
4. 解析次数统计接口 /v2/statisticsInfo
|
||||
#### 4. 解析次数统计接口 /v2/statisticsInfo
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
|
||||
@@ -8,7 +8,11 @@ public interface ConfigConstant {
|
||||
String SERVER = "server";
|
||||
String CACHE = "cache";
|
||||
|
||||
String PROXY_SERVER = "proxy-server";
|
||||
|
||||
String PROXY = "proxy";
|
||||
|
||||
String AUTHS = "auths";
|
||||
String GLOBAL_CONFIG = "globalConfig";
|
||||
String CUSTOM_CONFIG = "customConfig";
|
||||
String ASYNC_SERVICE_INSTANCES = "asyncServiceInstances";
|
||||
|
||||
@@ -12,7 +12,8 @@ import static io.vertx.core.http.HttpHeaders.CONTENT_TYPE;
|
||||
public class ResponseUtil {
|
||||
|
||||
public static void redirect(HttpServerResponse response, String url) {
|
||||
response.putHeader(HttpHeaders.LOCATION, url).setStatusCode(302).end();
|
||||
response.putHeader(CONTENT_TYPE, "text/html; charset=utf-8")
|
||||
.putHeader(HttpHeaders.LOCATION, url).setStatusCode(302).end();
|
||||
}
|
||||
|
||||
public static void redirect(HttpServerResponse response, String url, Promise<?> promise) {
|
||||
|
||||
@@ -72,6 +72,9 @@ public class FileInfo {
|
||||
//预览地址
|
||||
private String previewUrl;
|
||||
|
||||
// 文件hash默认类型为md5
|
||||
private String hash;
|
||||
|
||||
/**
|
||||
* 扩展参数
|
||||
*/
|
||||
@@ -210,6 +213,15 @@ public class FileInfo {
|
||||
return this;
|
||||
}
|
||||
|
||||
public String getHash() {
|
||||
return hash;
|
||||
}
|
||||
|
||||
public FileInfo setHash(String hash) {
|
||||
this.hash = hash;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Map<String, Object> getExtParameters() {
|
||||
return extParameters;
|
||||
}
|
||||
|
||||
@@ -23,6 +23,11 @@ import static java.util.regex.Pattern.compile;
|
||||
public enum PanDomainTemplate {
|
||||
|
||||
|
||||
// https://www.ilanzou.com/s/
|
||||
IZ("蓝奏云优享",
|
||||
compile("https://www\\.ilanzou\\.com/s/(?<KEY>.+)"),
|
||||
"https://www.ilanzou.com/s/{shareKey}",
|
||||
IzTool.class),
|
||||
// 网盘定义
|
||||
/*
|
||||
lanzoul.com
|
||||
@@ -95,7 +100,6 @@ public enum PanDomainTemplate {
|
||||
"lanzoux|" +
|
||||
"lanzout|" +
|
||||
"lanzouc|" +
|
||||
"ilanzou|" +
|
||||
"lanzoui|" +
|
||||
"lanzoug|" +
|
||||
"lanzoum" +
|
||||
@@ -122,11 +126,6 @@ public enum PanDomainTemplate {
|
||||
"https://v2.fangcloud.com/s/{shareKey}",
|
||||
"https://www.fangcloud.com/",
|
||||
FcTool.class),
|
||||
// https://www.ilanzou.com/s/
|
||||
IZ("蓝奏云优享",
|
||||
compile("https://www\\.ilanzou\\.com/s/(?<KEY>.+)"),
|
||||
"https://www.ilanzou.com/s/{shareKey}",
|
||||
IzTool.class),
|
||||
// https://wx.mail.qq.com/ftn/download?
|
||||
QQ("QQ邮箱中转站",
|
||||
compile("https://i?wx\\.mail\\.qq\\.com/ftn/download\\?(?<KEY>.+)"),
|
||||
@@ -244,7 +243,7 @@ public enum PanDomainTemplate {
|
||||
PgdTool.class),
|
||||
// iCloud https://www.icloud.com.cn/iclouddrive/xxx#fonts
|
||||
PIC("iCloud",
|
||||
compile("https://www\\.icloud\\.com\\.cn/iclouddrive/(?<KEY>[a-z_A-Z\\d-=]+)(#(.+))?"),
|
||||
compile("https://www\\.icloud\\.com(\\.cn)?/iclouddrive/(?<KEY>[a-z_A-Z\\d-=]+)(#(.+))?"),
|
||||
"https://www.icloud.com.cn/iclouddrive/{shareKey}",
|
||||
PicTool.class),
|
||||
// https://www.dropbox.com/scl/fi/cwnbms1yn8u6rcatzyta7/emqx-5.0.26-el7-amd64.tar.gz?rlkey=3uoi4bxz5mv93jmlaws0nlol1&e=8&st=fe0lclc2&dl=0
|
||||
|
||||
@@ -28,7 +28,7 @@ import java.util.regex.Pattern;
|
||||
*/
|
||||
public class LzTool extends PanBase {
|
||||
|
||||
public static final String SHARE_URL_PREFIX = "https://wwwa.lanzoux.com";
|
||||
public static final String SHARE_URL_PREFIX = "https://wwww.lanzoup.com";
|
||||
|
||||
|
||||
public LzTool(ShareLinkInfo shareLinkInfo) {
|
||||
@@ -225,7 +225,6 @@ public class LzTool extends PanBase {
|
||||
.setParserUrl(getDomainName() + "/d/" + panType + "/" + id)
|
||||
.setPreviewUrl(String.format("%s/v2/view/%s/%s", getDomainName(),
|
||||
shareLinkInfo.getType(), id));
|
||||
;
|
||||
log.debug("文件信息: {}", fileInfo);
|
||||
list.add(fileInfo);
|
||||
});
|
||||
|
||||
@@ -4,10 +4,13 @@ import cn.qaiu.WebClientVertxInit;
|
||||
import cn.qaiu.entity.FileInfo;
|
||||
import cn.qaiu.entity.ShareLinkInfo;
|
||||
import cn.qaiu.parser.PanBase;
|
||||
import cn.qaiu.util.FileSizeConverter;
|
||||
import cn.qaiu.util.HeaderUtils;
|
||||
import io.vertx.core.Future;
|
||||
import io.vertx.core.MultiMap;
|
||||
import io.vertx.core.Promise;
|
||||
import io.vertx.core.json.JsonObject;
|
||||
import io.vertx.core.json.pointer.JsonPointer;
|
||||
import io.vertx.ext.web.client.WebClient;
|
||||
import io.vertx.uritemplate.UriTemplate;
|
||||
|
||||
@@ -42,11 +45,10 @@ public class PvyyTool extends PanBase {
|
||||
user-agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36
|
||||
""");
|
||||
|
||||
private String api;
|
||||
private final String api;
|
||||
public PvyyTool(ShareLinkInfo shareLinkInfo) {
|
||||
super(shareLinkInfo);
|
||||
api = new String(hexArray);
|
||||
System.out.println(api);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -75,7 +77,18 @@ public class PvyyTool extends PanBase {
|
||||
.putHeaders(header)
|
||||
.send().onSuccess(res -> {
|
||||
try {
|
||||
String id = asJson(res).getJsonObject("data").getJsonObject("data").getString("id");
|
||||
JsonObject resJson = asJson(res);
|
||||
if (!resJson.containsKey("code") || resJson.getInteger("code") != 0) {
|
||||
fail("获取文件信息失败: " + resJson.getString("message"));
|
||||
return;
|
||||
}
|
||||
JsonObject fileData = resJson.getJsonObject("data").getJsonObject("data");
|
||||
if (fileData == null) {
|
||||
fail("文件数据为空");
|
||||
return;
|
||||
}
|
||||
setFileInfo(fileData);
|
||||
String id = fileData.getString("id");
|
||||
|
||||
client.getAbs(UriTemplate.of(apiUrl))
|
||||
.setTemplateParam("key", shareLinkInfo.getShareKey())
|
||||
@@ -97,6 +110,23 @@ public class PvyyTool extends PanBase {
|
||||
});
|
||||
}
|
||||
|
||||
private void setFileInfo(JsonObject fileData) {
|
||||
JsonObject attributes = fileData.getJsonObject("attributes");
|
||||
JsonObject user = (JsonObject)(JsonPointer.from("/relationships/user/data").queryJson(fileData));
|
||||
int downCount = (Integer)(JsonPointer.from("/relationships/shared/data/attributes/down").queryJson(fileData));
|
||||
String filesize = attributes.getString("filesize");
|
||||
FileInfo fileInfo = new FileInfo()
|
||||
.setFileId(fileData.getString("id"))
|
||||
.setFileName(attributes.getString("basename"))
|
||||
.setFileType(attributes.getString("mimetype"))
|
||||
.setPanType(shareLinkInfo.getType())
|
||||
.setCreateBy(user.getString("email"))
|
||||
.setDownloadCount(downCount)
|
||||
.setSize(FileSizeConverter.convertToBytes(filesize))
|
||||
.setSizeStr(filesize);
|
||||
shareLinkInfo.getOtherParam().put("fileInfo", fileInfo);
|
||||
}
|
||||
|
||||
private static final String DIR_API = "https://www.vyuyun.com/apiv1/share/folders/809Pt6/bMjnUg?sort=created_at&direction=DESC&password={pwd}";
|
||||
private static final String SHARE_TYPE_API = "https://www.vyuyun.com/apiv1/share/info/{key}?password={pwd}";
|
||||
//
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package cn.qaiu.parser.impl;
|
||||
|
||||
import cn.qaiu.entity.FileInfo;
|
||||
import cn.qaiu.entity.ShareLinkInfo;
|
||||
import io.vertx.core.Future;
|
||||
import java.util.HashMap;
|
||||
@@ -15,9 +16,15 @@ public class QQwTool extends QQTool {
|
||||
|
||||
@Override
|
||||
public Future<String> parse() {
|
||||
client.getAbs(shareLinkInfo.getStandardUrl()).send().onSuccess(res -> {
|
||||
client.getAbs(shareLinkInfo.getShareUrl()).send().onSuccess(res -> {
|
||||
String html = res.bodyAsString();
|
||||
String url = extractVariables(html).get("url");
|
||||
Map<String, String> stringStringMap = extractVariables(html);
|
||||
String url = stringStringMap.get("url");
|
||||
String fn = stringStringMap.get("filename");
|
||||
String size = stringStringMap.get("filesize");
|
||||
String createBy = stringStringMap.get("nick");
|
||||
FileInfo fileInfo = new FileInfo().setFileName(fn).setSize(Long.parseLong(size)).setCreateBy(createBy);
|
||||
shareLinkInfo.getOtherParam().put("fileInfo", fileInfo);
|
||||
if (url != null) {
|
||||
String url302 = url.replace("\\x26", "&");
|
||||
promise.complete(url302);
|
||||
@@ -44,16 +51,17 @@ public class QQwTool extends QQTool {
|
||||
|
||||
private Map<String, String> extractVariables(String jsCode) {
|
||||
Map<String, String> variables = new HashMap<>();
|
||||
|
||||
// 正则表达式匹配 var 变量定义
|
||||
String regex = "var\\s+(\\w+)\\s*=\\s*([\"']?)([^\"';\\s]+)\\2\n";
|
||||
Pattern pattern = Pattern.compile(regex);
|
||||
Matcher matcher = pattern.matcher(jsCode);
|
||||
String regex = "\\s+var\\s+(\\w+)\\s*=\\s*(?:\"([^\"]*)\"|'([^']*)'|([^;\\r\\n]*))";
|
||||
Pattern p = Pattern.compile(regex);
|
||||
Matcher m = p.matcher(jsCode);
|
||||
|
||||
while (matcher.find()) {
|
||||
String variableName = matcher.group(1); // 变量名
|
||||
String variableValue = matcher.group(3); // 变量值
|
||||
variables.put(variableName, variableValue);
|
||||
while (m.find()) {
|
||||
String name = m.group(1);
|
||||
String value = m.group(2) != null ? m.group(2)
|
||||
: m.group(3) != null ? m.group(3)
|
||||
: m.group(4);
|
||||
variables.put(name, value);
|
||||
}
|
||||
|
||||
return variables;
|
||||
|
||||
@@ -7,8 +7,17 @@ public class FileSizeConverter {
|
||||
throw new IllegalArgumentException("Invalid file size string");
|
||||
}
|
||||
|
||||
sizeStr = sizeStr.trim().toUpperCase();
|
||||
char unit = sizeStr.charAt(sizeStr.length() - 1);
|
||||
sizeStr = sizeStr.replace(",","").trim().toUpperCase();
|
||||
// 判断是2位单位还是1位单位
|
||||
// 判断单位是否为2位
|
||||
int unitIndex = sizeStr.length() - 1;
|
||||
char unit = sizeStr.charAt(unitIndex);
|
||||
if (Character.isLetter(sizeStr.charAt(unitIndex - 1))) {
|
||||
unit = sizeStr.charAt(unitIndex - 1);
|
||||
sizeStr = sizeStr.substring(0, unitIndex - 1);
|
||||
} else {
|
||||
sizeStr = sizeStr.substring(0, unitIndex);
|
||||
}
|
||||
double size = Double.parseDouble(sizeStr.substring(0, sizeStr.length() - 1));
|
||||
|
||||
return switch (unit) {
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
"dependencies": {
|
||||
"@element-plus/icons-vue": "^2.3.1",
|
||||
"@vueuse/core": "^11.2.0",
|
||||
"axios": "^1.7.4",
|
||||
"axios": "1.11.0",
|
||||
"clipboard": "^2.0.11",
|
||||
"core-js": "^3.8.3",
|
||||
"element-plus": "^2.8.7",
|
||||
|
||||
@@ -153,6 +153,13 @@
|
||||
}
|
||||
}
|
||||
</style>
|
||||
<script>
|
||||
const saved = localStorage.getItem('isDarkMode') === 'true'
|
||||
const systemDark = window.matchMedia('(prefers-color-scheme: dark)').matches
|
||||
if (saved || (!saved && systemDark)) {
|
||||
document.body.classList.add('dark-theme')
|
||||
}
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
<div id="app">
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<div id="app" :class="{ 'dark-theme': isDarkMode }">
|
||||
<div id="app" v-cloak :class="{ 'dark-theme': isDarkMode }">
|
||||
<!-- <el-dialog
|
||||
v-model="showRiskDialog"
|
||||
title="使用本网站您应改同意"
|
||||
@@ -293,6 +293,9 @@ export default {
|
||||
// 主题切换
|
||||
handleThemeChange(isDark) {
|
||||
this.isDarkMode = isDark
|
||||
document.body.classList.toggle('dark-theme', isDark)
|
||||
window.localStorage.setItem('isDarkMode', isDark)
|
||||
|
||||
},
|
||||
|
||||
// 验证输入
|
||||
@@ -600,6 +603,8 @@ export default {
|
||||
</script>
|
||||
|
||||
<style>
|
||||
[v-cloak] { display: none; }
|
||||
|
||||
body {
|
||||
background-color: #f5f7fa;
|
||||
color: #2c3e50;
|
||||
|
||||
@@ -3,29 +3,38 @@ package cn.qaiu.lz.common.cache;
|
||||
import cn.qaiu.db.pool.JDBCPoolInit;
|
||||
import cn.qaiu.db.pool.JDBCType;
|
||||
import cn.qaiu.lz.web.model.CacheLinkInfo;
|
||||
import cn.qaiu.lz.web.model.PanFileInfo;
|
||||
import cn.qaiu.lz.web.model.PanFileInfoRowMapper;
|
||||
import io.vertx.core.Future;
|
||||
import io.vertx.core.Promise;
|
||||
import io.vertx.core.json.JsonObject;
|
||||
import io.vertx.jdbcclient.JDBCPool;
|
||||
import io.vertx.sqlclient.Pool;
|
||||
import io.vertx.sqlclient.Row;
|
||||
import io.vertx.sqlclient.RowSet;
|
||||
import io.vertx.sqlclient.templates.SqlTemplate;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
public class CacheManager {
|
||||
private final JDBCPool jdbcPool = JDBCPoolInit.instance().getPool();
|
||||
private final Pool jdbcPool = JDBCPoolInit.instance().getPool();
|
||||
private final JDBCType jdbcType = JDBCPoolInit.instance().getType();
|
||||
|
||||
private static final Logger LOGGER = LoggerFactory.getLogger(CacheManager.class);
|
||||
|
||||
public Future<CacheLinkInfo> get(String cacheKey) {
|
||||
String sql = "SELECT share_key as shareKey, direct_link as directLink, expiration FROM cache_link_info WHERE share_key = #{share_key}";
|
||||
String sql2 = "SELECT * FROM pan_file_info WHERE share_key = #{share_key}";
|
||||
Map<String, Object> params = new HashMap<>();
|
||||
params.put("share_key", cacheKey);
|
||||
Promise<CacheLinkInfo> promise = Promise.promise();
|
||||
|
||||
Future<RowSet<PanFileInfo>> execute = SqlTemplate.forQuery(jdbcPool, sql2)
|
||||
.mapTo(PanFileInfoRowMapper.INSTANCE)
|
||||
.execute(params);
|
||||
SqlTemplate.forQuery(jdbcPool, sql)
|
||||
.mapTo(CacheLinkInfo.class)
|
||||
.execute(params)
|
||||
@@ -34,10 +43,18 @@ public class CacheManager {
|
||||
if (rows.size() > 0) {
|
||||
cacheHit = rows.iterator().next();
|
||||
cacheHit.setCacheHit(true);
|
||||
execute.onSuccess(r2 -> {
|
||||
if (r2.size() > 0) {
|
||||
cacheHit.setFileInfo(r2.iterator().next().toFileInfo());
|
||||
}
|
||||
promise.complete(cacheHit);
|
||||
}).onFailure(e -> {
|
||||
promise.complete(cacheHit);
|
||||
});
|
||||
} else {
|
||||
cacheHit = new CacheLinkInfo(JsonObject.of("cacheHit", false, "shareKey", cacheKey));
|
||||
promise.complete(cacheHit);
|
||||
}
|
||||
promise.complete(cacheHit);
|
||||
}).onFailure(e->{
|
||||
promise.fail(e);
|
||||
LOGGER.error("cache get:", e);
|
||||
@@ -47,7 +64,7 @@ public class CacheManager {
|
||||
|
||||
|
||||
// 插入或更新缓存数据
|
||||
public Future<Void> cacheShareLink(CacheLinkInfo cacheLinkInfo) {
|
||||
public void cacheShareLink(CacheLinkInfo cacheLinkInfo) {
|
||||
String sql;
|
||||
if (jdbcType == JDBCType.MySQL) {
|
||||
sql = """
|
||||
@@ -63,12 +80,53 @@ public class CacheManager {
|
||||
"VALUES (#{shareKey}, #{directLink}, #{expiration})";
|
||||
}
|
||||
|
||||
|
||||
// 直接传递 CacheLinkInfo 实体类
|
||||
return SqlTemplate.forUpdate(jdbcPool, sql)
|
||||
SqlTemplate.forUpdate(jdbcPool, sql)
|
||||
.mapFrom(CacheLinkInfo.class) // 将实体类映射为 Tuple 参数
|
||||
.execute(cacheLinkInfo)
|
||||
.mapEmpty();
|
||||
.execute(cacheLinkInfo).onSuccess(result -> {
|
||||
if (result.rowCount() > 0) {
|
||||
LOGGER.debug("Cache link info updated for shareKey: {}", cacheLinkInfo.getShareKey());
|
||||
} else {
|
||||
LOGGER.warn("No rows affected when updating cache link info for shareKey: {}", cacheLinkInfo.getShareKey());
|
||||
}
|
||||
}).onFailure(Throwable::printStackTrace);
|
||||
|
||||
if (cacheLinkInfo.getFileInfo() != null) {
|
||||
String sql2 = """
|
||||
INSERT IGNORE INTO pan_file_info (
|
||||
share_key, file_name, file_id, file_icon, size, size_str, file_type,
|
||||
file_path, create_time, update_time, create_by, description, download_count,
|
||||
pan_type, parser_url, preview_url, hash
|
||||
) VALUES (
|
||||
#{shareKey}, #{fileName}, #{fileId}, #{fileIcon}, #{size}, #{sizeStr}, #{fileType},
|
||||
#{filePath}, #{createTime}, #{updateTime}, #{createBy}, #{description}, #{downloadCount},
|
||||
#{panType}, #{parserUrl}, #{previewUrl}, #{hash}
|
||||
);
|
||||
""";
|
||||
// 判断文件信息是否缓存
|
||||
SqlTemplate
|
||||
.forQuery(jdbcPool, "SELECT count(1) AS count FROM pan_file_info WHERE share_key = #{share_key};")
|
||||
.mapTo(Row::toJson)
|
||||
.execute(Collections.singletonMap("share_key", cacheLinkInfo.getShareKey()))
|
||||
.onSuccess(rows -> {
|
||||
JsonObject row = rows.iterator().next();
|
||||
int count = row.getInteger("count");
|
||||
if (count == 0) {
|
||||
// 没有缓存,执行插入
|
||||
PanFileInfo fileInfo = PanFileInfo.fromFileInfo(cacheLinkInfo.getFileInfo());
|
||||
fileInfo.setShareKey(cacheLinkInfo.getShareKey());
|
||||
SqlTemplate.forUpdate(jdbcPool, sql2)
|
||||
.mapFrom(PanFileInfo.class) // 将实体类映射为 Tuple 参数
|
||||
.execute(fileInfo).onSuccess(result -> {
|
||||
if (result.rowCount() > 0) {
|
||||
LOGGER.debug("Pan file info inserted for shareKey: {}", cacheLinkInfo.getShareKey());
|
||||
} else {
|
||||
LOGGER.warn("No rows affected when inserting pan file info for shareKey: {}", cacheLinkInfo.getShareKey());
|
||||
}
|
||||
}).onFailure(Throwable::printStackTrace);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// 写入网盘厂商API解析次数
|
||||
|
||||
185
web-service/src/main/java/cn/qaiu/lz/common/util/JwtUtil.java
Normal file
185
web-service/src/main/java/cn/qaiu/lz/common/util/JwtUtil.java
Normal file
@@ -0,0 +1,185 @@
|
||||
package cn.qaiu.lz.common.util;
|
||||
|
||||
import cn.qaiu.lz.web.model.SysUser;
|
||||
import io.vertx.core.json.JsonObject;
|
||||
|
||||
import javax.crypto.Mac;
|
||||
import javax.crypto.spec.SecretKeySpec;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.security.InvalidKeyException;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.time.Instant;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.ZoneId;
|
||||
import java.util.Base64;
|
||||
import java.util.Date;
|
||||
|
||||
/**
|
||||
* JWT工具类,用于生成和验证JWT token
|
||||
*/
|
||||
public class JwtUtil {
|
||||
|
||||
private static final long EXPIRE_TIME = 24 * 60 * 60 * 1000; // token过期时间,24小时
|
||||
private static final String SECRET_KEY = "netdisk-fast-download-jwt-secret-key"; // 密钥
|
||||
private static final String ALGORITHM = "HmacSHA256";
|
||||
|
||||
/**
|
||||
* 生成JWT token
|
||||
*
|
||||
* @param user 用户信息
|
||||
* @return JWT token
|
||||
*/
|
||||
public static String generateToken(SysUser user) {
|
||||
long expireTime = getExpireTime();
|
||||
|
||||
// Header
|
||||
JsonObject header = new JsonObject()
|
||||
.put("alg", "HS256")
|
||||
.put("typ", "JWT");
|
||||
|
||||
// Payload
|
||||
JsonObject payload = new JsonObject()
|
||||
.put("id", user.getId())
|
||||
.put("username", user.getUsername())
|
||||
.put("role", user.getRole())
|
||||
.put("exp", expireTime)
|
||||
.put("iat", System.currentTimeMillis())
|
||||
.put("iss", "netdisk-fast-download");
|
||||
|
||||
// Base64 encode header and payload
|
||||
String encodedHeader = Base64.getUrlEncoder().withoutPadding().encodeToString(header.encode().getBytes(StandardCharsets.UTF_8));
|
||||
String encodedPayload = Base64.getUrlEncoder().withoutPadding().encodeToString(payload.encode().getBytes(StandardCharsets.UTF_8));
|
||||
|
||||
// Create signature
|
||||
String signature = hmacSha256(encodedHeader + "." + encodedPayload, SECRET_KEY);
|
||||
|
||||
// Combine to form JWT
|
||||
return encodedHeader + "." + encodedPayload + "." + signature;
|
||||
}
|
||||
|
||||
/**
|
||||
* 使用HMAC-SHA256算法生成签名
|
||||
*
|
||||
* @param data 要签名的数据
|
||||
* @param key 密钥
|
||||
* @return 签名
|
||||
*/
|
||||
private static String hmacSha256(String data, String key) {
|
||||
try {
|
||||
Mac sha256Hmac = Mac.getInstance(ALGORITHM);
|
||||
SecretKeySpec secretKey = new SecretKeySpec(key.getBytes(StandardCharsets.UTF_8), ALGORITHM);
|
||||
sha256Hmac.init(secretKey);
|
||||
byte[] signedBytes = sha256Hmac.doFinal(data.getBytes(StandardCharsets.UTF_8));
|
||||
return Base64.getUrlEncoder().withoutPadding().encodeToString(signedBytes);
|
||||
} catch (NoSuchAlgorithmException | InvalidKeyException e) {
|
||||
throw new RuntimeException("Error creating HMAC SHA256 signature", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 验证JWT token
|
||||
*
|
||||
* @param token JWT token
|
||||
* @return 如果token有效返回true,否则返回false
|
||||
*/
|
||||
public static boolean validateToken(String token) {
|
||||
try {
|
||||
String[] parts = token.split("\\.");
|
||||
if (parts.length != 3) {
|
||||
return false;
|
||||
}
|
||||
|
||||
String encodedHeader = parts[0];
|
||||
String encodedPayload = parts[1];
|
||||
String signature = parts[2];
|
||||
|
||||
// 验证签名
|
||||
String expectedSignature = hmacSha256(encodedHeader + "." + encodedPayload, SECRET_KEY);
|
||||
if (!expectedSignature.equals(signature)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// 验证过期时间
|
||||
String payload = new String(Base64.getUrlDecoder().decode(encodedPayload), StandardCharsets.UTF_8);
|
||||
JsonObject payloadJson = new JsonObject(payload);
|
||||
long expTime = payloadJson.getLong("exp", 0L);
|
||||
|
||||
return System.currentTimeMillis() < expTime;
|
||||
} catch (Exception e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 从token中获取用户ID
|
||||
*
|
||||
* @param token JWT token
|
||||
* @return 用户ID
|
||||
*/
|
||||
public static String getUserIdFromToken(String token) {
|
||||
String[] parts = token.split("\\.");
|
||||
if (parts.length != 3) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Base64解码
|
||||
String payload = new String(Base64.getUrlDecoder().decode(parts[1]), StandardCharsets.UTF_8);
|
||||
JsonObject jsonObject = new JsonObject(payload);
|
||||
return jsonObject.getString("id");
|
||||
}
|
||||
|
||||
/**
|
||||
* 从token中获取用户名
|
||||
*
|
||||
* @param token JWT token
|
||||
* @return 用户名
|
||||
*/
|
||||
public static String getUsernameFromToken(String token) {
|
||||
String[] parts = token.split("\\.");
|
||||
if (parts.length != 3) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Base64解码
|
||||
String payload = new String(Base64.getUrlDecoder().decode(parts[1]), StandardCharsets.UTF_8);
|
||||
JsonObject jsonObject = new JsonObject(payload);
|
||||
return jsonObject.getString("username");
|
||||
}
|
||||
|
||||
/**
|
||||
* 从token中获取用户角色
|
||||
*
|
||||
* @param token JWT token
|
||||
* @return 用户角色
|
||||
*/
|
||||
public static String getRoleFromToken(String token) {
|
||||
String[] parts = token.split("\\.");
|
||||
if (parts.length != 3) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Base64解码
|
||||
String payload = new String(Base64.getUrlDecoder().decode(parts[1]), StandardCharsets.UTF_8);
|
||||
JsonObject jsonObject = new JsonObject(payload);
|
||||
return jsonObject.getString("role");
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取过期时间
|
||||
*
|
||||
* @return 过期时间戳
|
||||
*/
|
||||
private static long getExpireTime() {
|
||||
return System.currentTimeMillis() + EXPIRE_TIME;
|
||||
}
|
||||
|
||||
/**
|
||||
* 将过期时间戳转换为LocalDateTime
|
||||
*
|
||||
* @param expireTime 过期时间戳
|
||||
* @return LocalDateTime
|
||||
*/
|
||||
public static LocalDateTime getExpireTimeAsLocalDateTime(long expireTime) {
|
||||
return LocalDateTime.ofInstant(Instant.ofEpochMilli(expireTime), ZoneId.systemDefault());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,90 @@
|
||||
package cn.qaiu.lz.common.util;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.security.MessageDigest;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.security.SecureRandom;
|
||||
import java.util.Base64;
|
||||
|
||||
/**
|
||||
* 密码加密工具类
|
||||
* 使用SHA-256算法加盐进行密码加密和验证
|
||||
*/
|
||||
public class PasswordUtil {
|
||||
|
||||
private static final String ALGORITHM = "SHA-256";
|
||||
private static final int SALT_LENGTH = 16; // 盐的长度
|
||||
private static final String DELIMITER = ":"; // 用于分隔盐和哈希值的分隔符
|
||||
|
||||
/**
|
||||
* 对密码进行加密
|
||||
*
|
||||
* @param plainPassword 明文密码
|
||||
* @return 加密后的密码(格式:salt:hash)
|
||||
*/
|
||||
public static String hashPassword(String plainPassword) {
|
||||
if (plainPassword == null || plainPassword.isEmpty()) {
|
||||
throw new IllegalArgumentException("密码不能为空");
|
||||
}
|
||||
|
||||
try {
|
||||
// 生成随机盐
|
||||
SecureRandom random = new SecureRandom();
|
||||
byte[] salt = new byte[SALT_LENGTH];
|
||||
random.nextBytes(salt);
|
||||
|
||||
// 计算哈希值
|
||||
MessageDigest md = MessageDigest.getInstance(ALGORITHM);
|
||||
md.update(salt);
|
||||
byte[] hashedPassword = md.digest(plainPassword.getBytes(StandardCharsets.UTF_8));
|
||||
|
||||
// 将盐和哈希值编码为Base64并拼接
|
||||
String saltBase64 = Base64.getEncoder().encodeToString(salt);
|
||||
String hashBase64 = Base64.getEncoder().encodeToString(hashedPassword);
|
||||
|
||||
// 返回格式:salt:hash
|
||||
return saltBase64 + DELIMITER + hashBase64;
|
||||
} catch (NoSuchAlgorithmException e) {
|
||||
throw new RuntimeException("加密算法不可用", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 验证密码是否正确
|
||||
*
|
||||
* @param plainPassword 明文密码
|
||||
* @param hashedPassword 加密后的密码(格式:salt:hash)
|
||||
* @return 如果密码匹配返回true,否则返回false
|
||||
*/
|
||||
public static boolean checkPassword(String plainPassword, String hashedPassword) {
|
||||
if (plainPassword == null || hashedPassword == null || hashedPassword.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
// 分割盐和哈希值
|
||||
String[] parts = hashedPassword.split(DELIMITER);
|
||||
if (parts.length != 2) {
|
||||
return false;
|
||||
}
|
||||
|
||||
String saltBase64 = parts[0];
|
||||
String hashBase64 = parts[1];
|
||||
|
||||
// 解码盐
|
||||
byte[] salt = Base64.getDecoder().decode(saltBase64);
|
||||
|
||||
// 使用相同的盐计算哈希值
|
||||
MessageDigest md = MessageDigest.getInstance(ALGORITHM);
|
||||
md.update(salt);
|
||||
byte[] calculatedHash = md.digest(plainPassword.getBytes(StandardCharsets.UTF_8));
|
||||
String calculatedHashBase64 = Base64.getEncoder().encodeToString(calculatedHash);
|
||||
|
||||
// 比较计算出的哈希值和存储的哈希值
|
||||
return hashBase64.equals(calculatedHashBase64);
|
||||
} catch (Exception e) {
|
||||
// 如果发生异常(例如格式不正确),返回false
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,13 @@
|
||||
package cn.qaiu.lz.common.util;
|
||||
|
||||
import cn.qaiu.parser.ParserCreate;
|
||||
import cn.qaiu.vx.core.util.ConfigConstant;
|
||||
import cn.qaiu.vx.core.util.SharedDataUtil;
|
||||
import cn.qaiu.vx.core.util.VertxHolder;
|
||||
import io.vertx.core.MultiMap;
|
||||
import io.vertx.core.http.HttpServerRequest;
|
||||
import io.vertx.core.json.JsonObject;
|
||||
import io.vertx.core.shareddata.LocalMap;
|
||||
|
||||
import java.net.URLDecoder;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
@@ -72,4 +78,42 @@ public class URLParamUtil {
|
||||
|
||||
return urlBuilder.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加共享链接的其他参数到ParserCreate对象中
|
||||
* @param parserCreate ParserCreate对象,包含共享链接信息
|
||||
*/
|
||||
public static void addParam(ParserCreate parserCreate) {
|
||||
LocalMap<Object, Object> localMap = VertxHolder.getVertxInstance().sharedData()
|
||||
.getLocalMap(ConfigConstant.LOCAL);
|
||||
|
||||
String type = parserCreate.getShareLinkInfo().getType();
|
||||
if (localMap.containsKey(ConfigConstant.PROXY)) {
|
||||
JsonObject proxy = (JsonObject) localMap.get(ConfigConstant.PROXY);
|
||||
if (proxy.containsKey(type)) {
|
||||
parserCreate.getShareLinkInfo().getOtherParam().put(ConfigConstant.PROXY, proxy.getJsonObject(type));
|
||||
}
|
||||
}
|
||||
if (localMap.containsKey(ConfigConstant.AUTHS)) {
|
||||
JsonObject auths = (JsonObject) localMap.get(ConfigConstant.AUTHS);
|
||||
if (auths.containsKey(type)) {
|
||||
// 需要处理引号
|
||||
MultiMap entries = MultiMap.caseInsensitiveMultiMap();
|
||||
JsonObject jsonObject = auths.getJsonObject(type);
|
||||
if (jsonObject != null) {
|
||||
jsonObject.forEach(entity -> {
|
||||
if (entity == null || entity.getValue() == null) {
|
||||
return;
|
||||
}
|
||||
entries.set(entity.getKey(), entity.getValue().toString());
|
||||
});
|
||||
}
|
||||
|
||||
parserCreate.getShareLinkInfo().getOtherParam().put(ConfigConstant.AUTHS, entries);
|
||||
}
|
||||
}
|
||||
|
||||
String linkPrefix = SharedDataUtil.getJsonConfig("server").getString("domainName");
|
||||
parserCreate.getShareLinkInfo().getOtherParam().put("domainName", linkPrefix);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,8 +5,10 @@ import cn.qaiu.db.ddl.Table;
|
||||
import cn.qaiu.db.ddl.TableGenIgnore;
|
||||
import cn.qaiu.entity.FileInfo;
|
||||
import cn.qaiu.lz.common.ToJson;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import io.vertx.codegen.annotations.DataObject;
|
||||
import io.vertx.core.json.JsonObject;
|
||||
import io.vertx.core.json.jackson.DatabindCodec;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
@@ -66,6 +68,11 @@ public class CacheLinkInfo implements ToJson {
|
||||
if (json.containsKey("expiration")) {
|
||||
this.setExpiration(json.getLong("expiration"));
|
||||
}
|
||||
|
||||
if (json.containsKey("fileInfo")) {
|
||||
ObjectMapper mapper = DatabindCodec.mapper(); // Vert.x 自带的 mapper
|
||||
this.setFileInfo(mapper.convertValue(json.getJsonObject("fileInfo"), FileInfo.class));
|
||||
}
|
||||
this.setCacheHit(json.getBoolean("cacheHit", false));
|
||||
}
|
||||
|
||||
|
||||
163
web-service/src/main/java/cn/qaiu/lz/web/model/PanFileInfo.java
Normal file
163
web-service/src/main/java/cn/qaiu/lz/web/model/PanFileInfo.java
Normal file
@@ -0,0 +1,163 @@
|
||||
package cn.qaiu.lz.web.model;
|
||||
|
||||
import cn.qaiu.db.ddl.Table;
|
||||
import cn.qaiu.entity.FileInfo;
|
||||
import io.vertx.codegen.annotations.DataObject;
|
||||
import io.vertx.codegen.format.SnakeCase;
|
||||
import io.vertx.core.json.JsonObject;
|
||||
import io.vertx.sqlclient.templates.annotations.RowMapped;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
/**
|
||||
* @author <a href="https://qaiu.top">QAIU</a>
|
||||
* @date 2025/8/4 12:38
|
||||
*/
|
||||
@Table(keyFields = "share_key")
|
||||
@DataObject
|
||||
@RowMapped(formatter = SnakeCase.class)
|
||||
@NoArgsConstructor
|
||||
@Data
|
||||
public class PanFileInfo {
|
||||
|
||||
String shareKey;
|
||||
|
||||
/**
|
||||
* 文件名
|
||||
*/
|
||||
private String fileName;
|
||||
|
||||
/**
|
||||
* 文件ID
|
||||
*/
|
||||
private String fileId;
|
||||
|
||||
private String fileIcon;
|
||||
|
||||
/**
|
||||
* 文件大小(byte)
|
||||
*/
|
||||
private Long size;
|
||||
|
||||
|
||||
private String sizeStr;
|
||||
|
||||
/**
|
||||
* 类型
|
||||
*/
|
||||
private String fileType;
|
||||
|
||||
/**
|
||||
* 文件路径
|
||||
*/
|
||||
private String filePath;
|
||||
|
||||
/**
|
||||
* 创建(上传)时间 yyyy-MM-dd HH:mm:ss格式
|
||||
*/
|
||||
private String createTime;
|
||||
|
||||
/**
|
||||
* 上次修改时间
|
||||
*/
|
||||
private String updateTime;
|
||||
|
||||
/**
|
||||
* 创建者
|
||||
*/
|
||||
private String createBy;
|
||||
|
||||
/**
|
||||
* 文件描述
|
||||
*/
|
||||
private String description;
|
||||
|
||||
/**
|
||||
* 下载次数
|
||||
*/
|
||||
private Integer downloadCount;
|
||||
|
||||
/**
|
||||
* 网盘标识
|
||||
*/
|
||||
private String panType;
|
||||
|
||||
/**
|
||||
* nfd下载链接(可能获取不到)
|
||||
* note: 不是下载直链
|
||||
*/
|
||||
private String parserUrl;
|
||||
|
||||
//预览地址
|
||||
private String previewUrl;
|
||||
|
||||
// 文件hash默认类型为md5
|
||||
private String hash;
|
||||
|
||||
public PanFileInfo(JsonObject jsonObject) {
|
||||
this.shareKey = jsonObject.getString("shareKey");
|
||||
this.fileName = jsonObject.getString("fileName");
|
||||
this.fileId = jsonObject.getString("fileId");
|
||||
this.fileIcon = jsonObject.getString("fileIcon");
|
||||
this.size = jsonObject.getLong("size");
|
||||
this.sizeStr = jsonObject.getString("sizeStr");
|
||||
this.fileType = jsonObject.getString("fileType");
|
||||
this.filePath = jsonObject.getString("filePath");
|
||||
this.createTime = jsonObject.getString("createTime");
|
||||
this.updateTime = jsonObject.getString("updateTime");
|
||||
this.createBy = jsonObject.getString("createBy");
|
||||
this.description = jsonObject.getString("description");
|
||||
this.downloadCount = jsonObject.getInteger("downloadCount");
|
||||
this.panType = jsonObject.getString("panType");
|
||||
this.parserUrl = jsonObject.getString("parserUrl");
|
||||
this.previewUrl = jsonObject.getString("previewUrl");
|
||||
this.hash = jsonObject.getString("hash");
|
||||
}
|
||||
public static PanFileInfo fromFileInfo(FileInfo info) {
|
||||
PanFileInfo panFileInfo = new PanFileInfo();
|
||||
if (info == null) {
|
||||
return panFileInfo;
|
||||
}
|
||||
|
||||
// 拷贝 FileInfo 的字段
|
||||
panFileInfo.setFileName(info.getFileName());
|
||||
panFileInfo.setFileId(info.getFileId());
|
||||
panFileInfo.setFileIcon(info.getFileIcon());
|
||||
panFileInfo.setSize(info.getSize());
|
||||
panFileInfo.setSizeStr(info.getSizeStr());
|
||||
panFileInfo.setFileType(info.getFileType());
|
||||
panFileInfo.setFilePath(info.getFilePath());
|
||||
panFileInfo.setCreateTime(info.getCreateTime());
|
||||
panFileInfo.setUpdateTime(info.getUpdateTime());
|
||||
panFileInfo.setCreateBy(info.getCreateBy());
|
||||
panFileInfo.setDescription(info.getDescription());
|
||||
panFileInfo.setDownloadCount(info.getDownloadCount());
|
||||
panFileInfo.setPanType(info.getPanType());
|
||||
panFileInfo.setParserUrl(info.getParserUrl());
|
||||
panFileInfo.setPreviewUrl(info.getPreviewUrl());
|
||||
panFileInfo.setHash(info.getHash());
|
||||
return panFileInfo;
|
||||
}
|
||||
|
||||
public FileInfo toFileInfo() {
|
||||
FileInfo fileInfo = new FileInfo();
|
||||
|
||||
fileInfo.setFileName(this.getFileName());
|
||||
fileInfo.setFileId(this.getFileId());
|
||||
fileInfo.setFileIcon(this.getFileIcon());
|
||||
fileInfo.setSize(this.getSize());
|
||||
fileInfo.setSizeStr(this.getSizeStr());
|
||||
fileInfo.setFileType(this.getFileType());
|
||||
fileInfo.setFilePath(this.getFilePath());
|
||||
fileInfo.setCreateTime(this.getCreateTime());
|
||||
fileInfo.setUpdateTime(this.getUpdateTime());
|
||||
fileInfo.setCreateBy(this.getCreateBy());
|
||||
fileInfo.setDescription(this.getDescription());
|
||||
fileInfo.setDownloadCount(this.getDownloadCount());
|
||||
fileInfo.setPanType(this.getPanType());
|
||||
fileInfo.setParserUrl(this.getParserUrl());
|
||||
fileInfo.setPreviewUrl(this.getPreviewUrl());
|
||||
fileInfo.setHash(this.getHash());
|
||||
return fileInfo;
|
||||
}
|
||||
}
|
||||
@@ -1,38 +1,33 @@
|
||||
package cn.qaiu.lz.web.service.impl;
|
||||
|
||||
import cn.qaiu.entity.FileInfo;
|
||||
import cn.qaiu.entity.ShareLinkInfo;
|
||||
import cn.qaiu.lz.common.cache.CacheConfigLoader;
|
||||
import cn.qaiu.lz.common.cache.CacheManager;
|
||||
import cn.qaiu.lz.common.cache.CacheTotalField;
|
||||
import cn.qaiu.lz.common.util.URLParamUtil;
|
||||
import cn.qaiu.lz.web.model.CacheLinkInfo;
|
||||
import cn.qaiu.lz.web.service.CacheService;
|
||||
import cn.qaiu.parser.IPanTool;
|
||||
import cn.qaiu.parser.ParserCreate;
|
||||
import cn.qaiu.vx.core.annotaions.Service;
|
||||
import cn.qaiu.vx.core.util.ConfigConstant;
|
||||
import cn.qaiu.vx.core.util.VertxHolder;
|
||||
import io.vertx.core.Future;
|
||||
import io.vertx.core.Promise;
|
||||
import io.vertx.core.json.JsonObject;
|
||||
import io.vertx.core.shareddata.LocalMap;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.lang3.time.DateFormatUtils;
|
||||
|
||||
import java.util.Date;
|
||||
|
||||
@Service
|
||||
@Slf4j
|
||||
public class CacheServiceImpl implements CacheService {
|
||||
|
||||
private final CacheManager cacheManager = new CacheManager();
|
||||
|
||||
private Future<CacheLinkInfo> getAndSaveCachedShareLink(ParserCreate parserCreate) {
|
||||
LocalMap<Object, Object> localMap = VertxHolder.getVertxInstance().sharedData()
|
||||
.getLocalMap(ConfigConstant.LOCAL);
|
||||
if (localMap.containsKey(ConfigConstant.PROXY)) {
|
||||
JsonObject proxy = (JsonObject) localMap.get(ConfigConstant.PROXY);
|
||||
String type = parserCreate.getShareLinkInfo().getType();
|
||||
if (proxy.containsKey(type)) {
|
||||
parserCreate.getShareLinkInfo().getOtherParam().put(ConfigConstant.PROXY, proxy.getJsonObject(type));
|
||||
}
|
||||
}
|
||||
|
||||
URLParamUtil.addParam(parserCreate);
|
||||
|
||||
Promise<CacheLinkInfo> promise = Promise.promise();
|
||||
// 构建组合的缓存key
|
||||
@@ -46,20 +41,36 @@ public class CacheServiceImpl implements CacheService {
|
||||
// parse
|
||||
result.setCacheHit(false);
|
||||
result.setExpiration(0L);
|
||||
parserCreate.createTool().parse().onSuccess(redirectUrl -> {
|
||||
IPanTool tool;
|
||||
try {
|
||||
tool = parserCreate.createTool();
|
||||
} catch (Exception e) {
|
||||
promise.fail(e.getCause().getCause());
|
||||
return;
|
||||
}
|
||||
tool.parse().onSuccess(redirectUrl -> {
|
||||
long expires = System.currentTimeMillis() +
|
||||
CacheConfigLoader.getDuration(shareLinkInfo.getType()) * 60 * 1000L;
|
||||
result.setDirectLink(redirectUrl);
|
||||
// result.setExpires(generateDate(expires));
|
||||
promise.complete(result);
|
||||
// 更新缓存
|
||||
// 将直链存储到缓存
|
||||
|
||||
CacheLinkInfo cacheLinkInfo = new CacheLinkInfo(JsonObject.of(
|
||||
"directLink", redirectUrl,
|
||||
"expiration", expires,
|
||||
"shareKey", cacheKey
|
||||
));
|
||||
cacheManager.cacheShareLink(cacheLinkInfo).onFailure(Throwable::printStackTrace);
|
||||
if (shareLinkInfo.getOtherParam().containsKey("fileInfo")) {
|
||||
try {
|
||||
FileInfo fileInfo = (FileInfo) shareLinkInfo.getOtherParam().get("fileInfo");
|
||||
result.setFileInfo(fileInfo);
|
||||
cacheLinkInfo.setFileInfo(fileInfo);
|
||||
} catch (Exception ignored) {
|
||||
log.error("文件对象转换异常");
|
||||
}
|
||||
}
|
||||
promise.complete(result);
|
||||
// 更新缓存
|
||||
// 将直链存储到缓存
|
||||
cacheManager.cacheShareLink(cacheLinkInfo);
|
||||
cacheManager.updateTotalByField(cacheKey, CacheTotalField.API_PARSER_TOTAL).onFailure(Throwable::printStackTrace);
|
||||
}).onFailure(promise::fail);
|
||||
} else {
|
||||
|
||||
Reference in New Issue
Block a user