mirror of
https://github.com/qaiu/netdisk-fast-download.git
synced 2026-08-27 20:12:02 +00:00
Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 8bd9830e04 | |||
| 2aeb64b87c |
@@ -6,10 +6,8 @@ QQ交流群:1017480890
|
|||||||
<a href="https://vertx-china.github.io"><img src="https://img.shields.io/badge/vert.x-4.5.27-blue?style=flat"></a>
|
<a href="https://vertx-china.github.io"><img src="https://img.shields.io/badge/vert.x-4.5.27-blue?style=flat"></a>
|
||||||
<a href="https://raw.githubusercontent.com/qaiu/netdisk-fast-download/master/LICENSE"><img src="https://img.shields.io/github/license/qaiu/netdisk-fast-download?style=flat"></a>
|
<a href="https://raw.githubusercontent.com/qaiu/netdisk-fast-download/master/LICENSE"><img src="https://img.shields.io/github/license/qaiu/netdisk-fast-download?style=flat"></a>
|
||||||
<a href="https://github.com/qaiu/netdisk-fast-download/releases/"><img src="https://img.shields.io/github/v/release/qaiu/netdisk-fast-download?style=flat"></a>
|
<a href="https://github.com/qaiu/netdisk-fast-download/releases/"><img src="https://img.shields.io/github/v/release/qaiu/netdisk-fast-download?style=flat"></a>
|
||||||
<a href="https://github.com/QAIU/netdisk-fast-download">
|
|
||||||
<img src="https://img.shields.io/github/stars/QAIU/netdisk-fast-download?style=flat&logo=github" alt="GitHub Stars">
|
|
||||||
</a>
|
|
||||||
<a href="https://atomgit.com/QAIU/netdisk-fast-download"><img src="https://atomgit.com/QAIU/netdisk-fast-download/star/badge.svg" alt="AtomGit"></a>
|
<a href="https://atomgit.com/QAIU/netdisk-fast-download"><img src="https://atomgit.com/QAIU/netdisk-fast-download/star/badge.svg" alt="AtomGit"></a>
|
||||||
|
<a href="https://oosmetrics.com/repo/qaiu/netdisk-fast-download"><img src="https://api.oosmetrics.com/api/v1/badge/achievement/826aa27a-6e59-4de5-b7fa-cd189f484035.svg"></a>
|
||||||
<p align="center">
|
<p align="center">
|
||||||
<a href="https://trendshift.io/repositories/12101" target="_blank"><img src="https://trendshift.io/api/badge/repositories/12101" alt="qaiu%2Fnetdisk-fast-download | Trendshift" style="width: 250px; height: 55px;" width="250" height="55"/></a>
|
<a href="https://trendshift.io/repositories/12101" target="_blank"><img src="https://trendshift.io/api/badge/repositories/12101" alt="qaiu%2Fnetdisk-fast-download | Trendshift" style="width: 250px; height: 55px;" width="250" height="55"/></a>
|
||||||
</p>
|
</p>
|
||||||
@@ -69,7 +67,7 @@ https://nfd-parser.github.io/nfd-preview/preview.html?src=https%3A%2F%2Flz.qaiu.
|
|||||||
- [移动云云空间-ec](https://www.ecpan.cn/web)
|
- [移动云云空间-ec](https://www.ecpan.cn/web)
|
||||||
- [小飞机网盘-fj](https://www.feijipan.com/)
|
- [小飞机网盘-fj](https://www.feijipan.com/)
|
||||||
- [亿方云-fc](https://www.fangcloud.com/)
|
- [亿方云-fc](https://www.fangcloud.com/)
|
||||||
- [123云盘-ye](https://www.123pan.com/) ⚠️仅建议本地部署使用,需登录认证,公共/云端服务器风控严格建议自行部署(Windows 可直接用 run.bat 一键运行)
|
- [123云盘-ye](https://www.123pan.com/)
|
||||||
- ~[115网盘(失效)-p115](https://115.com/)~
|
- ~[115网盘(失效)-p115](https://115.com/)~
|
||||||
- [文叔叔-ws](https://www.wenshushu.cn/)
|
- [文叔叔-ws](https://www.wenshushu.cn/)
|
||||||
- [联想乐云-le](https://lecloud.lenovo.com/)
|
- [联想乐云-le](https://lecloud.lenovo.com/)
|
||||||
@@ -176,7 +174,6 @@ GET /json/getFileList?url={分享链接}&pwd={密码}
|
|||||||
| UC网盘(UC) | **必须** | 必须配置 Cookie 才能解析 |
|
| UC网盘(UC) | **必须** | 必须配置 Cookie 才能解析 |
|
||||||
| 小飞机网盘(FJ) | 可选 | 大文件(>100MB)需要认证 |
|
| 小飞机网盘(FJ) | 可选 | 大文件(>100MB)需要认证 |
|
||||||
| 蓝奏优享(IZ) | 可选 | 大文件需要认证 |
|
| 蓝奏优享(IZ) | 可选 | 大文件需要认证 |
|
||||||
| 123网盘(YE) | 可选 | 需要下载大文件/需要登录的分享时才需要认证,支持账号密码或 token/authorization |
|
|
||||||
|
|
||||||
**使用示例**:
|
**使用示例**:
|
||||||
```
|
```
|
||||||
@@ -197,38 +194,6 @@ GET /parser?url={分享链接}&pwd={密码}&auth={加密后的认证参数}
|
|||||||
- 目的:防止客户端伪造失败计数请求
|
- 目的:防止客户端伪造失败计数请求
|
||||||
- 建议:使用高强度随机字符串,且不要与 `authEncryptKey` 相同
|
- 建议:使用高强度随机字符串,且不要与 `authEncryptKey` 相同
|
||||||
|
|
||||||
#### `auth` 临时认证参数 与 `auths` 静态配置认证 的区别
|
|
||||||
|
|
||||||
本项目存在两种互相独立的认证配置方式,作用范围不同,不要混淆:
|
|
||||||
|
|
||||||
| 方式 | 配置位置 | 生效范围 | 适用场景 |
|
|
||||||
|------|---------|---------|---------|
|
|
||||||
| `auth` 临时认证参数 | 请求 URL 上的 `auth` 查询参数 | **仅当次请求**,优先级高于 app-dev.yml 中的静态配置 | 调用方按用户临时提供的账号/Cookie/token 解析,不同请求可携带不同认证信息 |
|
|
||||||
| `auths` 静态配置认证 | `app-dev.yml` 的 `auths.<网盘标识>` 节点 | **服务端长期生效**,所有未携带 `auth` 参数的请求都会复用 | 部署方自己长期配置一份账号,供所有请求默认使用 |
|
|
||||||
|
|
||||||
以 123网盘(`ye`)为例,`app-dev.yml` 中支持以下几种写法(三选一即可):
|
|
||||||
|
|
||||||
```yaml
|
|
||||||
auths:
|
|
||||||
ye:
|
|
||||||
username: 你的123网盘账号
|
|
||||||
password: 你的123网盘密码
|
|
||||||
```
|
|
||||||
```yaml
|
|
||||||
auths:
|
|
||||||
ye:
|
|
||||||
token: 已登录后获取的 Authorization/AccessToken
|
|
||||||
```
|
|
||||||
```yaml
|
|
||||||
auths:
|
|
||||||
ye:
|
|
||||||
authorization: 已登录后获取的 Authorization/AccessToken # 与 token 等价,二选一
|
|
||||||
```
|
|
||||||
|
|
||||||
> ⚠️ 注意:YAML 中 key 后面不写值(如 `authorization:` 空着)等同于没配置,不会生效,必须填入真实的账号密码或 token 内容。
|
|
||||||
|
|
||||||
如果只是临时调用一次,不想改动服务端配置,也可以用上面提到的 `auth` 参数临时传递(`authType` 可选 `password`/`accesstoken`/`authorization`),无需重启服务,仅本次请求生效。
|
|
||||||
|
|
||||||
### 特殊说明
|
### 特殊说明
|
||||||
|
|
||||||
- 移动云云空间的 `分享key` 取分享链接中的 `data` 参数值
|
- 移动云云空间的 `分享key` 取分享链接中的 `data` 参数值
|
||||||
|
|||||||
@@ -79,32 +79,13 @@ public class Ye2Tool extends PanBase {
|
|||||||
accountId = auths.get("_configId");
|
accountId = auths.get("_configId");
|
||||||
} else if (auths.contains("username")) {
|
} else if (auths.contains("username")) {
|
||||||
accountId = auths.get("username");
|
accountId = auths.get("username");
|
||||||
} else {
|
} else if (auths.contains("token")) {
|
||||||
String token = resolveProvidedToken(auths);
|
String token = auths.get("token");
|
||||||
if (StringUtils.isNotEmpty(token)) {
|
|
||||||
accountId = token.substring(0, Math.min(16, token.length()));
|
accountId = token.substring(0, Math.min(16, token.length()));
|
||||||
}
|
}
|
||||||
}
|
|
||||||
return accountId;
|
return accountId;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 从配置/临时认证参数中取出可直接使用的 Bearer token。
|
|
||||||
* 兼容两种 key:
|
|
||||||
* - token:URLParamUtil 处理 auth= 临时参数(authType=accesstoken/authorization)时写入的字段名
|
|
||||||
* - authorization:app-dev.yml 静态配置中更符合直觉的写法(auths.ye.authorization: xxx)
|
|
||||||
*/
|
|
||||||
private String resolveProvidedToken(MultiMap auths) {
|
|
||||||
if (auths == null) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
String token = auths.get("token");
|
|
||||||
if (StringUtils.isNotEmpty(token)) {
|
|
||||||
return token;
|
|
||||||
}
|
|
||||||
return auths.get("authorization");
|
|
||||||
}
|
|
||||||
|
|
||||||
private boolean isTokenExpired() {
|
private boolean isTokenExpired() {
|
||||||
return TokenCache.isExpired(cacheKey);
|
return TokenCache.isExpired(cacheKey);
|
||||||
}
|
}
|
||||||
@@ -208,14 +189,15 @@ public class Ye2Tool extends PanBase {
|
|||||||
private Future<String> resolveTokenFuture() {
|
private Future<String> resolveTokenFuture() {
|
||||||
MultiMap auths = (MultiMap) shareLinkInfo.getOtherParam().get("auths");
|
MultiMap auths = (MultiMap) shareLinkInfo.getOtherParam().get("auths");
|
||||||
if (auths != null) {
|
if (auths != null) {
|
||||||
// 当同时提供了用户名+密码时,优先走真实登录流程。
|
String raw = null;
|
||||||
// 注:前端/auth参数中 authType=password|username_password 时,URLParamUtil 出于兼容旧解析器的目的
|
if (auths.contains("token")) {
|
||||||
// 会把 authToken(用户名) 同时写入 token 字段,如果这里无条件信任 token,会把用户名当成
|
raw = auths.get("token");
|
||||||
// Bearer token 使用,导致真实的账号密码登录被跳过。
|
} else if (auths.contains("Authorization")) {
|
||||||
boolean hasCredential = StringUtils.isNotEmpty(auths.get("username"))
|
raw = auths.get("Authorization");
|
||||||
&& StringUtils.isNotEmpty(auths.get("password"));
|
}
|
||||||
String providedToken = resolveProvidedToken(auths);
|
String providedToken = normalizeBearerToken(raw);
|
||||||
if (!hasCredential && StringUtils.isNotEmpty(providedToken)) {
|
if (StringUtils.isNotEmpty(providedToken)) {
|
||||||
|
log.info("使用配置token,前8位: {}...", providedToken.substring(0, Math.min(8, providedToken.length())));
|
||||||
TokenCache.putToken(cacheKey, providedToken);
|
TokenCache.putToken(cacheKey, providedToken);
|
||||||
return Future.succeededFuture(providedToken);
|
return Future.succeededFuture(providedToken);
|
||||||
}
|
}
|
||||||
@@ -228,6 +210,17 @@ public class Ye2Tool extends PanBase {
|
|||||||
return Future.succeededFuture(cached);
|
return Future.succeededFuture(cached);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private String normalizeBearerToken(String raw) {
|
||||||
|
if (StringUtils.isBlank(raw)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
String token = raw.trim();
|
||||||
|
if (token.regionMatches(true, 0, "Bearer ", 0, 7)) {
|
||||||
|
token = token.substring(7).trim();
|
||||||
|
}
|
||||||
|
return StringUtils.isNotEmpty(token) ? token : null;
|
||||||
|
}
|
||||||
|
|
||||||
private Future<String> loginAndGetToken() {
|
private Future<String> loginAndGetToken() {
|
||||||
MultiMap auths = (MultiMap) shareLinkInfo.getOtherParam().get("auths");
|
MultiMap auths = (MultiMap) shareLinkInfo.getOtherParam().get("auths");
|
||||||
if (auths == null) {
|
if (auths == null) {
|
||||||
|
|||||||
@@ -0,0 +1,140 @@
|
|||||||
|
package cn.qaiu.parser.impl;
|
||||||
|
|
||||||
|
import cn.qaiu.entity.ShareLinkInfo;
|
||||||
|
import io.vertx.core.MultiMap;
|
||||||
|
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Ye2Tool token 解析验证测试
|
||||||
|
* 验证 Authorization 字段和 ******
|
||||||
|
*/
|
||||||
|
public class Ye2ToolTokenValidationTest {
|
||||||
|
|
||||||
|
public static void main(String[] args) {
|
||||||
|
System.out.println("========================================");
|
||||||
|
System.out.println(" Ye2Tool Token 解析验证测试");
|
||||||
|
System.out.println("========================================\n");
|
||||||
|
|
||||||
|
testNormalizeBearerToken();
|
||||||
|
testYe2ToolWithTokenField();
|
||||||
|
testYe2ToolWithAuthorizationField();
|
||||||
|
testYe2ToolWithoutAuth();
|
||||||
|
|
||||||
|
System.out.println("\n========================================");
|
||||||
|
System.out.println(" 所有验证通过! ✓");
|
||||||
|
System.out.println("========================================");
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 独立验证 normalizeBearerToken 逻辑(内联实现与 Ye2Tool 保持一致)*/
|
||||||
|
private static String normalizeBearerToken(String raw) {
|
||||||
|
if (raw == null || raw.trim().isEmpty()) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
String token = raw.trim();
|
||||||
|
if (token.regionMatches(true, 0, "Bearer ", 0, 7)) {
|
||||||
|
token = token.substring(7).trim();
|
||||||
|
}
|
||||||
|
return token.isEmpty() ? null : token;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void testNormalizeBearerToken() {
|
||||||
|
System.out.println("=== 验证 normalizeBearerToken 逻辑 ===");
|
||||||
|
|
||||||
|
assertNormalize("testtoken123abc", "testtoken123abc", "纯 token 不变");
|
||||||
|
assertNormalize("Bearer testtoken123abc", "testtoken123abc", "******");
|
||||||
|
assertNormalize("bearer testtoken123abc", "testtoken123abc", "小写 bearer 前缀被去掉");
|
||||||
|
assertNormalize("BEARER testtoken123abc", "testtoken123abc", "大写 BEARER 前缀被去掉");
|
||||||
|
assertNormalize(" Bearer testtoken123abc ", "testtoken123abc", "前后空格被 trim");
|
||||||
|
assertNormalizeNull(null, "null 输入返回 null");
|
||||||
|
assertNormalizeNull("", "空字符串返回 null");
|
||||||
|
assertNormalizeNull(" ", "纯空格返回 null");
|
||||||
|
|
||||||
|
System.out.println();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void assertNormalize(String input, String expected, String desc) {
|
||||||
|
String actual = normalizeBearerToken(input);
|
||||||
|
if (!expected.equals(actual)) {
|
||||||
|
throw new AssertionError("FAIL [" + desc + "]: expected='" + expected + "', actual='" + actual + "'");
|
||||||
|
}
|
||||||
|
System.out.println("✓ " + desc);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void assertNormalizeNull(String input, String desc) {
|
||||||
|
String actual = normalizeBearerToken(input);
|
||||||
|
if (actual != null) {
|
||||||
|
throw new AssertionError("FAIL [" + desc + "]: expected null, actual='" + actual + "'");
|
||||||
|
}
|
||||||
|
System.out.println("✓ " + desc);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void testYe2ToolWithTokenField() {
|
||||||
|
System.out.println("=== 测试 Ye2Tool(token 字段)===");
|
||||||
|
try {
|
||||||
|
MultiMap auths = MultiMap.caseInsensitiveMultiMap();
|
||||||
|
auths.set("token", "testtoken123abc");
|
||||||
|
|
||||||
|
Map<String, Object> otherParam = new HashMap<>();
|
||||||
|
otherParam.put("auths", auths);
|
||||||
|
|
||||||
|
ShareLinkInfo shareLinkInfo = ShareLinkInfo.newBuilder()
|
||||||
|
.type("YE")
|
||||||
|
.panName("123网盘")
|
||||||
|
.shareKey("test_key_token")
|
||||||
|
.shareUrl("https://www.123pan.com/s/test123")
|
||||||
|
.build();
|
||||||
|
shareLinkInfo.setOtherParam(otherParam);
|
||||||
|
|
||||||
|
Ye2Tool ye2Tool = new Ye2Tool(shareLinkInfo);
|
||||||
|
System.out.println("✓ Ye2Tool 实例创建成功(token 字段)\n");
|
||||||
|
} catch (Exception e) {
|
||||||
|
System.err.println("✗ Ye2Tool(token 字段)测试失败: " + e.getMessage());
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void testYe2ToolWithAuthorizationField() {
|
||||||
|
System.out.println("=== 测试 Ye2Tool(Authorization 字段,带 Bearer)===");
|
||||||
|
try {
|
||||||
|
MultiMap auths = MultiMap.caseInsensitiveMultiMap();
|
||||||
|
auths.set("Authorization", "Bearer testtoken123abc");
|
||||||
|
|
||||||
|
Map<String, Object> otherParam = new HashMap<>();
|
||||||
|
otherParam.put("auths", auths);
|
||||||
|
|
||||||
|
ShareLinkInfo shareLinkInfo = ShareLinkInfo.newBuilder()
|
||||||
|
.type("YE")
|
||||||
|
.panName("123网盘")
|
||||||
|
.shareKey("test_key_auth")
|
||||||
|
.shareUrl("https://www.123pan.com/s/test456")
|
||||||
|
.build();
|
||||||
|
shareLinkInfo.setOtherParam(otherParam);
|
||||||
|
|
||||||
|
Ye2Tool ye2Tool = new Ye2Tool(shareLinkInfo);
|
||||||
|
System.out.println("✓ Ye2Tool 实例创建成功(Authorization 字段)\n");
|
||||||
|
} catch (Exception e) {
|
||||||
|
System.err.println("✗ Ye2Tool(Authorization 字段)测试失败: " + e.getMessage());
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void testYe2ToolWithoutAuth() {
|
||||||
|
System.out.println("=== 测试 Ye2Tool(无认证配置)===");
|
||||||
|
try {
|
||||||
|
ShareLinkInfo shareLinkInfo = ShareLinkInfo.newBuilder()
|
||||||
|
.type("YE")
|
||||||
|
.panName("123网盘")
|
||||||
|
.shareKey("test_key_noauth")
|
||||||
|
.shareUrl("https://www.123pan.com/s/test789")
|
||||||
|
.build();
|
||||||
|
|
||||||
|
Ye2Tool ye2Tool = new Ye2Tool(shareLinkInfo);
|
||||||
|
System.out.println("✓ Ye2Tool 实例创建成功(无认证)\n");
|
||||||
|
} catch (Exception e) {
|
||||||
|
System.err.println("✗ Ye2Tool(无认证)测试失败: " + e.getMessage());
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -17,7 +17,7 @@
|
|||||||
</modules>
|
</modules>
|
||||||
|
|
||||||
<properties>
|
<properties>
|
||||||
<revision>0.3.7</revision>
|
<revision>0.3.4</revision>
|
||||||
<java.version>17</java.version>
|
<java.version>17</java.version>
|
||||||
<maven.compiler.source>17</maven.compiler.source>
|
<maven.compiler.source>17</maven.compiler.source>
|
||||||
<maven.compiler.target>17</maven.compiler.target>
|
<maven.compiler.target>17</maven.compiler.target>
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "nfd-web",
|
"name": "nfd-web",
|
||||||
"version": "0.3.7",
|
"version": "0.2.1",
|
||||||
"private": true,
|
"private": true,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"serve": "vue-cli-service serve",
|
"serve": "vue-cli-service serve",
|
||||||
|
|||||||
Reference in New Issue
Block a user