diff --git a/parser/src/main/java/cn/qaiu/parser/impl/Ye2Tool.java b/parser/src/main/java/cn/qaiu/parser/impl/Ye2Tool.java
index e63d577..55959fb 100644
--- a/parser/src/main/java/cn/qaiu/parser/impl/Ye2Tool.java
+++ b/parser/src/main/java/cn/qaiu/parser/impl/Ye2Tool.java
@@ -99,10 +99,26 @@ public class Ye2Tool extends PanBase {
return null;
}
String token = auths.get("token");
- if (StringUtils.isNotEmpty(token)) {
+ if (StringUtils.isEmpty(token)) {
+ token = auths.get("authorization");
+ }
+ return stripBearerPrefix(token);
+ }
+
+ /**
+ * 用户/捐赠账号提供的 token 经常是从浏览器开发者工具里连同 "Bearer " 前缀一起复制过来的,
+ * 如果不做清理,后续 header.set("Authorization", "Bearer " + token) 会拼出
+ * "Bearer Bearer xxx",导致123网盘接口报错 tokenstring should not contain 'bearer '。
+ */
+ private String stripBearerPrefix(String token) {
+ if (StringUtils.isBlank(token)) {
return token;
}
- return auths.get("authorization");
+ String trimmed = token.trim();
+ if (trimmed.regionMatches(true, 0, "Bearer ", 0, 7)) {
+ trimmed = trimmed.substring(7).trim();
+ }
+ return trimmed;
}
private boolean isTokenExpired() {
diff --git a/web-front/src/views/Home.vue b/web-front/src/views/Home.vue
index f095ce5..55fce9f 100644
--- a/web-front/src/views/Home.vue
+++ b/web-front/src/views/Home.vue
@@ -326,6 +326,10 @@
蓝奏优享 (IZ)
大文件
+
+ 123云盘 (YE)
+ 部分分享需登录
+
@@ -767,7 +771,8 @@ export default {
if (url.includes('drive.uc.cn') || url.includes('fast.uc.cn')) return 'UC'
if (url.includes('feijipan.com') || url.includes('feijihe.com') || url.includes('xiaofeiyang.com')) return 'FJ'
if (url.includes('ilanzou.com') || url.includes('lanzouv.com')) return 'IZ'
- if (url.includes('123pan.com') || url.includes('123684.com') || url.includes('123865.com')) return 'YE'
+ // 123网盘域名较多(如 123pan.com/123pan.cn/share.123pan.cn/123684.com/123865.com 等数字域名),使用规则匹配代替枚举
+ if (/123\d{3}\.com|123panpay\.com|123pan\.(?:com|cn)/.test(url)) return 'YE'
return ''
},
@@ -789,7 +794,8 @@ export default {
'QK': '夸克网盘必须配置 Cookie 才能解析和下载(登录后从浏览器开发者工具获取)',
'UC': 'UC网盘必须配置 Cookie 才能解析和下载(登录后从浏览器开发者工具获取)',
'FJ': '小飞机网盘大文件(>100MB)需要配置认证信息',
- 'IZ': '蓝奏优享大文件需要配置认证信息'
+ 'IZ': '蓝奏优享大文件需要配置认证信息',
+ 'YE': '123云盘部分分享(需要登录才能查看/下载)需要配置账号密码或 Authorization Token'
}
return hints[this.authConfig.panType] || '请选择网盘类型后配置认证信息'
},
@@ -1230,10 +1236,11 @@ export default {
duration: 5000,
showClose: true
})
- } else if (panType === 'fj' || panType === 'lz' || panType === 'iz' || panType === 'le') {
- // 小飞机、蓝奏、优享、联想乐云:提示大文件需要认证
+ } else if (panType === 'fj' || panType === 'lz' || panType === 'iz' || panType === 'le' || panType === 'ye') {
+ // 小飞机、蓝奏、优享、联想乐云、123云盘:提示大文件/需登录分享需要认证
const hasAuth = this.allAuthConfigs[panType]?.cookie ||
this.allAuthConfigs[panType]?.username ||
+ this.allAuthConfigs[panType]?.token ||
(this.donateAccountCounts.active[panType.toUpperCase()] || 0) > 0
if (!hasAuth) {
this.$message.info({
@@ -1612,12 +1619,14 @@ export default {
this.donateSubmitting = true
try {
+ // 只提交当前认证方式实际用到的字段,避免切换认证类型后遗留的用户名/密码脏数据被一起提交
+ const isPasswordAuth = this.donateConfig.authType === 'password'
const payload = {
panType: this.donateConfig.panType,
authType: this.donateConfig.authType,
- username: this.donateConfig.username || '',
- password: this.donateConfig.password || '',
- token: this.donateConfig.token || '',
+ username: isPasswordAuth ? (this.donateConfig.username || '') : '',
+ password: isPasswordAuth ? (this.donateConfig.password || '') : '',
+ token: isPasswordAuth ? '' : (this.donateConfig.token || ''),
remark: this.donateConfig.remark || ''
}
await axios.post(`${this.baseAPI}/v2/donateAccount`, payload)
diff --git a/web-service/src/main/java/cn/qaiu/lz/common/util/URLParamUtil.java b/web-service/src/main/java/cn/qaiu/lz/common/util/URLParamUtil.java
index f761094..a640eb3 100644
--- a/web-service/src/main/java/cn/qaiu/lz/common/util/URLParamUtil.java
+++ b/web-service/src/main/java/cn/qaiu/lz/common/util/URLParamUtil.java
@@ -100,21 +100,27 @@ public class URLParamUtil {
}
}
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());
- });
- }
+ // 如果本次请求已经通过 auth 临时参数(个人配置/捐赠账号)设置过认证信息,
+ // 则不要再用后台 app-dev.yml 的静态配置覆盖,否则临时认证会被静默清空/替换为空配置。
+ boolean tempAuthAdded = Boolean.TRUE.equals(
+ parserCreate.getShareLinkInfo().getOtherParam().get("__TEMP_AUTH_ADDED"));
+ if (!tempAuthAdded) {
+ 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);
+ parserCreate.getShareLinkInfo().getOtherParam().put(ConfigConstant.AUTHS, entries);
+ }
}
}
diff --git a/web-service/src/main/java/cn/qaiu/lz/web/service/impl/CacheServiceImpl.java b/web-service/src/main/java/cn/qaiu/lz/web/service/impl/CacheServiceImpl.java
index 8b8a303..6a8cb73 100644
--- a/web-service/src/main/java/cn/qaiu/lz/web/service/impl/CacheServiceImpl.java
+++ b/web-service/src/main/java/cn/qaiu/lz/web/service/impl/CacheServiceImpl.java
@@ -8,15 +8,22 @@ 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.lz.web.service.DbService;
import cn.qaiu.parser.IPanTool;
import cn.qaiu.parser.ParserCreate;
import cn.qaiu.parser.clientlink.ClientLinkGeneratorFactory;
import cn.qaiu.parser.clientlink.ClientLinkType;
import cn.qaiu.vx.core.annotaions.Service;
+import cn.qaiu.vx.core.util.AsyncServiceUtil;
+import cn.qaiu.vx.core.util.ConfigConstant;
+import cn.qaiu.vx.core.util.VertxHolder;
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.shareddata.LocalMap;
import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.time.DateFormatUtils;
import java.util.Date;
@@ -28,8 +35,11 @@ import java.util.Map;
public class CacheServiceImpl implements CacheService {
private static final String SKIP_CLIENT_LINKS = "_skipClientLinks";
+ private static final String TEMP_AUTH_ADDED = "__TEMP_AUTH_ADDED";
+ private static final String DONATED_ACCOUNT_TOKEN = "__AUTO_DONATED_ACCOUNT_TOKEN";
private final CacheManager cacheManager = new CacheManager();
+ private final DbService dbService = AsyncServiceUtil.getAsyncServiceInstance(DbService.class);
static {
// 服务类加载时注册缓存定时清理任务
@@ -71,7 +81,9 @@ public class CacheServiceImpl implements CacheService {
promise.fail(cause);
return;
}
- IPanTool.closeAfter(tool, tool::parse).onSuccess(redirectUrl -> {
+ IPanTool.closeAfter(tool, tool::parse).onFailure(err -> {
+ recordAutoDonatedFailureIfNeeded(shareLinkInfo, err);
+ }).onSuccess(redirectUrl -> {
// 使用 effectiveCacheDuration
long expires = System.currentTimeMillis() + effectiveCacheDuration * 60 * 1000L;
result.setDirectLink(redirectUrl);
@@ -141,6 +153,124 @@ public class CacheServiceImpl implements CacheService {
return DateFormatUtils.format(new Date(ts), "yyyy-MM-dd HH:mm:ss");
}
+ /**
+ * 当本次请求没有携带临时认证参数(auth=xxx),且该网盘类型在 app-dev.yml 中也没有配置可用的
+ * 静态账号密码/token 时,自动从捐赠账号池中随机挑选一个可用账号使用,
+ * 这样捐赠者捐赠账号后,普通请求(不带 auth 参数)也能直接受益,无需前端额外拼接参数。
+ */
+ private Future applyDonatedAccountFallback(ParserCreate parserCreate) {
+ ShareLinkInfo shareLinkInfo = parserCreate.getShareLinkInfo();
+ Map otherParam = shareLinkInfo.getOtherParam();
+ if (Boolean.TRUE.equals(otherParam.get(TEMP_AUTH_ADDED))) {
+ // 请求已经携带了个人配置/捐赠账号的临时认证参数,无需再自动回退
+ return Future.succeededFuture();
+ }
+ String type = shareLinkInfo.getType();
+ if (StringUtils.isBlank(type) || hasUsableStaticAuthConfig(type)) {
+ return Future.succeededFuture();
+ }
+ return dbService.getRandomDonatedAccount(type.toUpperCase())
+ .compose(res -> {
+ if (!Integer.valueOf(200).equals(res.getInteger("code"))) {
+ return Future.succeededFuture();
+ }
+ JsonObject data = res.getJsonObject("data");
+ if (data == null || data.isEmpty()) {
+ return Future.succeededFuture();
+ }
+ String username = data.getString("username");
+ String password = data.getString("password");
+ String token = data.getString("token");
+ if (StringUtils.isBlank(username) && StringUtils.isBlank(password) && StringUtils.isBlank(token)) {
+ return Future.succeededFuture();
+ }
+ MultiMap tempAuth = MultiMap.caseInsensitiveMultiMap();
+ if (StringUtils.isNotBlank(username)) {
+ tempAuth.set("username", username);
+ }
+ if (StringUtils.isNotBlank(password)) {
+ tempAuth.set("password", password);
+ }
+ if (StringUtils.isNotBlank(token)) {
+ tempAuth.set("token", token);
+ }
+ otherParam.put(ConfigConstant.AUTHS, tempAuth);
+ otherParam.put(TEMP_AUTH_ADDED, true);
+ String donatedAccountToken = data.getString("donatedAccountToken");
+ if (StringUtils.isNotBlank(donatedAccountToken)) {
+ otherParam.put(DONATED_ACCOUNT_TOKEN, donatedAccountToken);
+ }
+ log.debug("已自动应用捐赠账号: type={}", type);
+ return Future.succeededFuture();
+ })
+ .recover(err -> {
+ log.warn("自动获取捐赠账号失败: type={}", type, err);
+ return Future.succeededFuture();
+ });
+ }
+
+ /**
+ * 检查 app-dev.yml 中该网盘类型是否已配置了可用的静态认证信息(非空的 username/password/token/authorization 等)。
+ * 如果已配置,则不需要再自动回退到捐赠账号池。
+ */
+ private boolean hasUsableStaticAuthConfig(String type) {
+ LocalMap