diff --git a/web-front/src/views/Home.vue b/web-front/src/views/Home.vue
index 92fa027..e6857f1 100644
--- a/web-front/src/views/Home.vue
+++ b/web-front/src/views/Home.vue
@@ -2,7 +2,7 @@
+
+
+
+
+ 捐赠您的网盘 Cookie/Token,解析时将从所有捐赠账号中随机选择使用,分摊请求压力。
+
+
+
+
+
+ 当前账号池(共 {{ donateAccountCounts.total }} 个)
+
+ {{ getPanDisplayName(panType) }}: {{ count }} 个
+
+
+
+ 暂无捐赠账号,成为第一个捐赠者吧!
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 关闭
+
+ 捐赠此账号
+
+
+
+
@@ -436,7 +517,21 @@ export default {
ext5: ''
},
// 所有网盘的认证配置 { panType: config }
- allAuthConfigs: {}
+ allAuthConfigs: {},
+
+ // 捐赠账号相关
+ showDonateDialog: false,
+ donateSubmitting: false,
+ donateConfig: {
+ panType: '',
+ authType: 'cookie',
+ username: '',
+ password: '',
+ token: '',
+ remark: ''
+ },
+ // 捐赠账号数量统计 { panType: count, total: N }
+ donateAccountCounts: { total: 0 }
}
},
computed: {
@@ -460,6 +555,7 @@ 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'
return ''
},
@@ -469,7 +565,8 @@ export default {
'QK': '夸克网盘',
'UC': 'UC网盘',
'FJ': '小飞机网盘',
- 'IZ': '蓝奏优享'
+ 'IZ': '蓝奏优享',
+ 'YE': '123云盘'
}
return names[panType] || panType
},
@@ -663,14 +760,36 @@ export default {
}
},
- // 生成加密的 auth 参数(根据当前链接的网盘类型)
- generateAuthParam() {
+ // 生成加密的 auth 参数(优先使用个人配置,否则从后端随机获取捐赠账号)
+ async generateAuthParam() {
const panType = this.getCurrentPanType()
- if (!panType || !this.allAuthConfigs[panType]) {
- return ''
+ if (!panType) return ''
+
+ let config = null
+
+ // 优先使用个人配置
+ if (this.allAuthConfigs[panType]) {
+ config = this.allAuthConfigs[panType]
+ console.log(`[认证] 使用个人配置: ${this.getPanDisplayName(panType)}`)
+ } else {
+ // 从后端随机获取捐赠账号
+ try {
+ const response = await axios.get(`${this.baseAPI}/v2/randomAuth`, { params: { panType } })
+ // 解包 JsonResult 嵌套
+ let data = response.data
+ while (data && data.data !== undefined && data.code !== undefined) {
+ data = data.data
+ }
+ if (data && (data.token || data.username)) {
+ config = data
+ console.log(`[认证] 使用捐赠账号: ${this.getPanDisplayName(panType)}`)
+ }
+ } catch (e) {
+ console.log(`[认证] 无可用捐赠账号: ${this.getPanDisplayName(panType)}`)
+ }
}
- const config = this.allAuthConfigs[panType]
+ if (!config) return ''
// 构建 JSON 对象
const authObj = {}
@@ -710,9 +829,9 @@ export default {
},
// 更新智能直链
- updateDirectLink() {
+ async updateDirectLink() {
if (this.link) {
- const authParam = this.generateAuthParam()
+ const authParam = await this.generateAuthParam()
const authSuffix = authParam ? `&auth=${authParam}` : ''
this.directLink = `${this.baseAPI}/parser?url=${this.link}${this.password ? `&pwd=${this.password}` : ''}${authSuffix}`
}
@@ -766,8 +885,8 @@ export default {
this.errorButtonVisible = false
try {
this.isLoading = true
- // 添加认证参数
- const authParam = this.generateAuthParam()
+ // 添加认证参数(异步获取)
+ const authParam = await this.generateAuthParam()
if (authParam) {
params.auth = authParam
}
@@ -1086,7 +1205,7 @@ export default {
if (this.password) params.pwd = this.password
// 添加认证参数
- const authParam = this.generateAuthParam()
+ const authParam = await this.generateAuthParam()
if (authParam) params.auth = authParam
const response = await axios.get(`${this.baseAPI}/v2/clientLinks`, { params })
@@ -1115,6 +1234,101 @@ export default {
} finally {
this.isLoading = false
}
+ },
+
+ // ========== 捐赠账号相关方法 ==========
+
+ // 捐赠弹窗中网盘类型变更
+ onDonatePanTypeChange(panType) {
+ const types = this.getDonateAuthTypes()
+ this.donateConfig.authType = types.length > 0 ? types[0].value : 'cookie'
+ this.donateConfig.username = ''
+ this.donateConfig.password = ''
+ this.donateConfig.token = ''
+ this.donateConfig.remark = ''
+ },
+
+ // 获取捐赠弹窗支持的认证类型
+ getDonateAuthTypes() {
+ const pt = (this.donateConfig.panType || '').toLowerCase()
+ const allTypes = {
+ cookie: { label: 'Cookie', value: 'cookie' },
+ accesstoken: { label: 'AccessToken', value: 'accesstoken' },
+ authorization: { label: 'Authorization', value: 'authorization' },
+ password: { label: '用户名密码', value: 'password' },
+ custom: { label: '自定义', value: 'custom' }
+ }
+ switch (pt) {
+ case 'qk': case 'uc': return [allTypes.cookie]
+ case 'fj': case 'iz': return [allTypes.password]
+ case 'ye': return [allTypes.password, allTypes.authorization]
+ default: return Object.values(allTypes)
+ }
+ },
+
+ // 提交捐赠账号(调用后端 API)
+ async submitDonateAccount() {
+ if (!this.donateConfig.panType) {
+ this.$message.warning('请选择网盘类型')
+ return
+ }
+ if (!this.donateConfig.token && !this.donateConfig.username) {
+ this.$message.warning('请填写认证信息(Cookie/Token 或 用户名密码)')
+ return
+ }
+
+ this.donateSubmitting = true
+ try {
+ const payload = {
+ panType: this.donateConfig.panType,
+ authType: this.donateConfig.authType,
+ username: this.donateConfig.username || '',
+ password: this.donateConfig.password || '',
+ token: this.donateConfig.token || '',
+ remark: this.donateConfig.remark || ''
+ }
+ await axios.post(`${this.baseAPI}/v2/donateAccount`, payload)
+ this.$message.success(`已捐赠 ${this.getPanDisplayName(this.donateConfig.panType)} 账号,感谢您的贡献!`)
+
+ // 重置表单
+ this.donateConfig.username = ''
+ this.donateConfig.password = ''
+ this.donateConfig.token = ''
+ this.donateConfig.remark = ''
+
+ // 刷新计数
+ await this.loadDonateAccountCounts()
+ } catch (e) {
+ console.error('捐赠账号失败:', e)
+ this.$message.error('捐赠失败,请稍后重试')
+ } finally {
+ this.donateSubmitting = false
+ }
+ },
+
+ // 从后端加载捐赠账号数量统计
+ async loadDonateAccountCounts() {
+ try {
+ const response = await axios.get(`${this.baseAPI}/v2/donateAccountCounts`)
+ // 解包可能的 JsonResult 嵌套: { code, data: { code, data: { QK: 3, total: 4 } } }
+ let data = response.data
+ while (data && data.data !== undefined && data.code !== undefined) {
+ data = data.data
+ }
+ if (data && typeof data === 'object') {
+ // 确保有 total 字段
+ if (data.total === undefined) {
+ let total = 0
+ for (const [key, val] of Object.entries(data)) {
+ if (typeof val === 'number') total += val
+ }
+ data.total = total
+ }
+ this.donateAccountCounts = data
+ }
+ } catch (e) {
+ console.error('加载捐赠账号统计失败:', e)
+ }
}
},
@@ -1128,6 +1342,9 @@ export default {
// 加载认证配置
this.loadAuthConfig()
+ // 加载捐赠账号统计
+ this.loadDonateAccountCounts()
+
// 获取初始统计信息
this.getInfo()
diff --git a/web-service/src/main/java/cn/qaiu/lz/web/controller/ParserApi.java b/web-service/src/main/java/cn/qaiu/lz/web/controller/ParserApi.java
index 5f6f458..29dc5e5 100644
--- a/web-service/src/main/java/cn/qaiu/lz/web/controller/ParserApi.java
+++ b/web-service/src/main/java/cn/qaiu/lz/web/controller/ParserApi.java
@@ -477,4 +477,32 @@ public class ParserApi {
ClientLinkType type = ClientLinkType.valueOf(clientType.toUpperCase());
return clientLinks.get(type);
}
+
+ // ========== 捐赠账号 API ==========
+
+ /**
+ * 捐赠网盘账号
+ */
+ @RouteMapping(value = "/donateAccount", method = RouteMethod.POST)
+ public Future donateAccount(HttpServerRequest request, JsonObject body) {
+ String ip = request.remoteAddress().host();
+ body.put("ip", ip);
+ return dbService.saveDonatedAccount(body);
+ }
+
+ /**
+ * 获取各网盘捐赠账号数量
+ */
+ @RouteMapping(value = "/donateAccountCounts", method = RouteMethod.GET)
+ public Future getDonateAccountCounts() {
+ return dbService.getDonatedAccountCounts();
+ }
+
+ /**
+ * 随机获取指定网盘类型的捐赠账号(内部使用,返回加密后的 auth 参数)
+ */
+ @RouteMapping(value = "/randomAuth", method = RouteMethod.GET)
+ public Future getRandomAuth(String panType) {
+ return dbService.getRandomDonatedAccount(panType);
+ }
}
diff --git a/web-service/src/main/java/cn/qaiu/lz/web/model/DonatedAccount.java b/web-service/src/main/java/cn/qaiu/lz/web/model/DonatedAccount.java
new file mode 100644
index 0000000..f374a04
--- /dev/null
+++ b/web-service/src/main/java/cn/qaiu/lz/web/model/DonatedAccount.java
@@ -0,0 +1,52 @@
+package cn.qaiu.lz.web.model;
+
+import cn.qaiu.db.ddl.Constraint;
+import cn.qaiu.db.ddl.Length;
+import cn.qaiu.db.ddl.Table;
+import com.fasterxml.jackson.annotation.JsonFormat;
+import lombok.Data;
+
+import java.util.Date;
+
+/**
+ * 捐赠账号实体
+ * 用户捐赠的网盘认证信息,解析时随机选择使用
+ */
+@Data
+@Table("donated_account")
+public class DonatedAccount {
+
+ private static final long serialVersionUID = 1L;
+
+ @Constraint(autoIncrement = true, notNull = true)
+ private Long id;
+
+ @Length(varcharSize = 16)
+ @Constraint(notNull = true)
+ private String panType; // 网盘类型: QK, UC, FJ, IZ, YE
+
+ @Length(varcharSize = 32)
+ @Constraint(notNull = true)
+ private String authType; // 认证类型: cookie, accesstoken, authorization, password, custom
+
+ @Length(varcharSize = 128)
+ private String username; // 用户名
+
+ @Length(varcharSize = 128)
+ private String password; // 密码
+
+ @Length(varcharSize = 4096)
+ private String token; // Cookie/Token
+
+ @Length(varcharSize = 64)
+ private String remark; // 备注
+
+ @Length(varcharSize = 64)
+ private String ip; // 捐赠者IP
+
+ @Constraint(notNull = true, defaultValue = "true")
+ private Boolean enabled = true; // 是否启用
+
+ @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
+ private Date createTime = new Date();
+}
diff --git a/web-service/src/main/java/cn/qaiu/lz/web/service/DbService.java b/web-service/src/main/java/cn/qaiu/lz/web/service/DbService.java
index 0256d2e..8dcc8d6 100644
--- a/web-service/src/main/java/cn/qaiu/lz/web/service/DbService.java
+++ b/web-service/src/main/java/cn/qaiu/lz/web/service/DbService.java
@@ -50,4 +50,21 @@ public interface DbService extends BaseAsyncService {
*/
Future getPlaygroundParserById(Long id);
+ // ========== 捐赠账号相关 ==========
+
+ /**
+ * 保存捐赠账号
+ */
+ Future saveDonatedAccount(JsonObject account);
+
+ /**
+ * 获取各网盘捐赠账号数量统计
+ */
+ Future getDonatedAccountCounts();
+
+ /**
+ * 随机获取指定网盘类型的一个启用账号
+ */
+ Future getRandomDonatedAccount(String panType);
+
}
diff --git a/web-service/src/main/java/cn/qaiu/lz/web/service/impl/DbServiceImpl.java b/web-service/src/main/java/cn/qaiu/lz/web/service/impl/DbServiceImpl.java
index 87aa228..7ebee36 100644
--- a/web-service/src/main/java/cn/qaiu/lz/web/service/impl/DbServiceImpl.java
+++ b/web-service/src/main/java/cn/qaiu/lz/web/service/impl/DbServiceImpl.java
@@ -265,4 +265,94 @@ public class DbServiceImpl implements DbService {
return promise.future();
}
+
+ // ========== 捐赠账号相关 ==========
+
+ @Override
+ public Future saveDonatedAccount(JsonObject account) {
+ JDBCPool client = JDBCPoolInit.instance().getPool();
+ Promise promise = Promise.promise();
+
+ String sql = """
+ INSERT INTO donated_account
+ (pan_type, auth_type, username, password, token, remark, ip, enabled, create_time)
+ VALUES (?, ?, ?, ?, ?, ?, ?, true, NOW())
+ """;
+
+ client.preparedQuery(sql)
+ .execute(Tuple.of(
+ account.getString("panType"),
+ account.getString("authType"),
+ account.getString("username"),
+ account.getString("password"),
+ account.getString("token"),
+ account.getString("remark"),
+ account.getString("ip")
+ ))
+ .onSuccess(res -> {
+ promise.complete(JsonResult.success("捐赠成功").toJsonObject());
+ })
+ .onFailure(e -> {
+ log.error("saveDonatedAccount failed", e);
+ promise.fail(e);
+ });
+
+ return promise.future();
+ }
+
+ @Override
+ public Future getDonatedAccountCounts() {
+ JDBCPool client = JDBCPoolInit.instance().getPool();
+ Promise promise = Promise.promise();
+
+ String sql = "SELECT pan_type, COUNT(*) as count FROM donated_account WHERE enabled = true GROUP BY pan_type";
+
+ client.query(sql).execute().onSuccess(rows -> {
+ JsonObject counts = new JsonObject();
+ int total = 0;
+ for (Row row : rows) {
+ String panType = row.getString("pan_type");
+ Integer count = row.getInteger("count");
+ counts.put(panType, count);
+ total += count;
+ }
+ counts.put("total", total);
+ promise.complete(JsonResult.data(counts).toJsonObject());
+ }).onFailure(e -> {
+ log.error("getDonatedAccountCounts failed", e);
+ promise.fail(e);
+ });
+
+ return promise.future();
+ }
+
+ @Override
+ public Future getRandomDonatedAccount(String panType) {
+ JDBCPool client = JDBCPoolInit.instance().getPool();
+ Promise promise = Promise.promise();
+
+ String sql = "SELECT * FROM donated_account WHERE pan_type = ? AND enabled = true ORDER BY RAND() LIMIT 1";
+
+ client.preparedQuery(sql)
+ .execute(Tuple.of(panType))
+ .onSuccess(rows -> {
+ if (rows.size() > 0) {
+ Row row = rows.iterator().next();
+ JsonObject account = new JsonObject();
+ account.put("authType", row.getString("auth_type"));
+ account.put("username", row.getString("username"));
+ account.put("password", row.getString("password"));
+ account.put("token", row.getString("token"));
+ promise.complete(JsonResult.data(account).toJsonObject());
+ } else {
+ promise.complete(JsonResult.data(new JsonObject()).toJsonObject());
+ }
+ })
+ .onFailure(e -> {
+ log.error("getRandomDonatedAccount failed", e);
+ promise.fail(e);
+ });
+
+ return promise.future();
+ }
}