fix: cookie加密后cloud.service.ts未解密导致API 401; 更新版本号至2.1.1/1.1.9

This commit is contained in:
2026-05-15 18:23:40 +08:00
parent a12fec4d82
commit 4b9bcd7a96
8 changed files with 42 additions and 24 deletions

View File

@@ -1,6 +1,6 @@
{ {
"name": "cloudsearch-backend", "name": "cloudsearch-backend",
"version": "2.1.0", "version": "2.1.1",
"private": true, "private": true,
"scripts": { "scripts": {
"dev": "tsx watch src/main.ts", "dev": "tsx watch src/main.ts",

View File

@@ -1,6 +1,7 @@
import { getDb } from '../database/database'; import { getDb } from '../database/database';
import { localTimestamp, formatLocalDateTime } from '../utils/time'; import { localTimestamp, formatLocalDateTime } from '../utils/time';
import { getSystemConfig } from '../admin/system-config.service'; import { getSystemConfig } from '../admin/system-config.service';
import { decrypt } from '../utils/crypto';
import { QuarkDriver } from './drivers/quark.driver'; import { QuarkDriver } from './drivers/quark.driver';
import { BaiduDriver } from './drivers/baidu.driver'; import { BaiduDriver } from './drivers/baidu.driver';
import { CloudConfig, getAndValidateCredential, getActiveCloudConfigs } from './credential.service'; import { CloudConfig, getAndValidateCredential, getActiveCloudConfigs } from './credential.service';
@@ -159,12 +160,12 @@ async function doSaveFromShare(shareUrl: string, cloudType: string, sourceTitle?
switch (cloudType) { switch (cloudType) {
case 'quark': { case 'quark': {
const driver = new QuarkDriver({ cookie: config.cookie!, nickname: config.nickname }); const driver = new QuarkDriver({ cookie: decrypt(config.cookie!), nickname: config.nickname });
driverResult = await driver.saveFromShare(shareUrl, sourceTitle); driverResult = await driver.saveFromShare(shareUrl, sourceTitle);
break; break;
} }
case 'baidu': { case 'baidu': {
const driver = new BaiduDriver({ cookie: config.cookie!, nickname: config.nickname }); const driver = new BaiduDriver({ cookie: decrypt(config.cookie!), nickname: config.nickname });
driverResult = await driver.saveFromShare(shareUrl, sourceTitle); driverResult = await driver.saveFromShare(shareUrl, sourceTitle);
break; break;
} }
@@ -174,6 +175,21 @@ async function doSaveFromShare(shareUrl: string, cloudType: string, sourceTitle?
return { success: false, message: `暂不支持 ${cloudType} 的保存功能` }; return { success: false, message: `暂不支持 ${cloudType} 的保存功能` };
} }
// ── If save failed, get actual error reason from PanSou validation ──
let actualError: string | null = null;
if (!driverResult.success) {
try {
const { LinkValidator } = await import('../validation/link-validator.service');
const validator = new LinkValidator();
const validation = await validator.validate(shareUrl, cloudType);
if (validation.message) {
actualError = validation.message;
}
} catch {
// PanSou unreachable
}
}
const durationMs = Date.now() - startTime; const durationMs = Date.now() - startTime;
if (driverResult.success) { if (driverResult.success) {
@@ -196,7 +212,7 @@ async function doSaveFromShare(shareUrl: string, cloudType: string, sourceTitle?
driverResult.shareUrl || null, driverResult.sharePwd || null, driverResult.shareUrl || null, driverResult.sharePwd || null,
driverResult.fileSize == null ? null : String(driverResult.fileSize), driverResult.fileCount || 0, driverResult.folderCount || 0, driverResult.fileSize == null ? null : String(driverResult.fileSize), driverResult.fileCount || 0, driverResult.folderCount || 0,
durationMs, driverResult.success ? 'success' : 'failed', durationMs, driverResult.success ? 'success' : 'failed',
driverResult.success ? null : driverResult.message, driverResult.success ? null : (actualError ? `${driverResult.message} | ${actualError}` : driverResult.message),
driverResult.folderName || null, driverResult.originalFolderName || null, driverResult.folderName || null, driverResult.originalFolderName || null,
ipAddress || null, ipLocation, localTimestamp(), config.id ipAddress || null, ipLocation, localTimestamp(), config.id
); );
@@ -312,7 +328,7 @@ export async function refreshAllStorageInfo(): Promise<void> {
for (const cfg of configs) { for (const cfg of configs) {
try { try {
const { QuarkDriver } = require('./drivers/quark.driver'); const { QuarkDriver } = require('./drivers/quark.driver');
const driver = new QuarkDriver({ cookie: cfg.cookie, nickname: cfg.nickname }); const driver = new QuarkDriver({ cookie: decrypt(cfg.cookie!), nickname: cfg.nickname });
const storage = await driver.getStorageInfo(); const storage = await driver.getStorageInfo();
if (storage.totalBytes > 0 || storage.usedBytes > 0) { if (storage.totalBytes > 0 || storage.usedBytes > 0) {
const db = getDb(); const db = getDb();

View File

@@ -189,7 +189,7 @@ export function saveCloudConfig(data: {
consecutive_failures = 0, consecutive_failures = 0,
updated_at = ? updated_at = ?
WHERE id = ?` WHERE id = ?`
).run(data.cloud_type, encryptedCookie || null, data.nickname || null, data.is_active ?? 1, data.promotion_account ?? '', data.is_transfer_enabled ?? 1, data.storage_used || null, data.storage_total || null, cloudTypeUid || null, localTimestamp(), data.id); ).run(data.cloud_type, encryptedCookie || null, data.nickname || null, data.is_active == null ? 1 : Number(data.is_active), data.promotion_account ?? '', data.is_transfer_enabled == null ? 1 : Number(data.is_transfer_enabled), data.storage_used || null, data.storage_total || null, cloudTypeUid || null, localTimestamp(), data.id);
} else { } else {
// Try to find existing config by cloud_type + cloud_type_uid // Try to find existing config by cloud_type + cloud_type_uid
let existing: any = null; let existing: any = null;
@@ -220,7 +220,7 @@ export function saveCloudConfig(data: {
consecutive_failures = 0, consecutive_failures = 0,
updated_at = ? updated_at = ?
WHERE id = ?` WHERE id = ?`
).run(encryptedCookie || null, data.nickname || null, data.is_active ?? 1, data.promotion_account ?? '', data.is_transfer_enabled ?? 1, data.storage_used || null, data.storage_total || null, cloudTypeUid || null, localTimestamp(), existing.id); ).run(encryptedCookie || null, data.nickname || null, data.is_active == null ? 1 : Number(data.is_active), data.promotion_account ?? '', data.is_transfer_enabled == null ? 1 : Number(data.is_transfer_enabled), data.storage_used || null, data.storage_total || null, cloudTypeUid || null, localTimestamp(), existing.id);
// Re-read savedId for return // Re-read savedId for return
const savedId = existing.id; const savedId = existing.id;
@@ -236,7 +236,7 @@ export function saveCloudConfig(data: {
// No existing config found — insert new // No existing config found — insert new
db.prepare( db.prepare(
'INSERT INTO cloud_configs (cloud_type, cookie, nickname, is_active, promotion_account, is_transfer_enabled, storage_used, storage_total, cloud_type_uid, consecutive_failures) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, 0)' 'INSERT INTO cloud_configs (cloud_type, cookie, nickname, is_active, promotion_account, is_transfer_enabled, storage_used, storage_total, cloud_type_uid, consecutive_failures) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, 0)'
).run(data.cloud_type, encryptedCookie || null, data.nickname || null, data.is_active ?? 1, data.promotion_account ?? '', data.is_transfer_enabled ?? 1, data.storage_used || null, data.storage_total || null, cloudTypeUid || null); ).run(data.cloud_type, encryptedCookie || null, data.nickname || null, data.is_active == null ? 1 : Number(data.is_active), data.promotion_account ?? '', data.is_transfer_enabled == null ? 1 : Number(data.is_transfer_enabled), data.storage_used || null, data.storage_total || null, cloudTypeUid || null);
} }
const savedId = data.id || (db.prepare('SELECT last_insert_rowid() as id').get() as any).id; const savedId = data.id || (db.prepare('SELECT last_insert_rowid() as id').get() as any).id;

View File

@@ -89,14 +89,8 @@ export async function getStorageInfoQuick(cookie: string, fallbackTotal?: string
*/ */
export async function getStorageInfo(cookie: string): Promise<{ used: string; total: string; usedBytes: number; totalBytes: number }> { export async function getStorageInfo(cookie: string): Promise<{ used: string; total: string; usedBytes: number; totalBytes: number }> {
try { try {
const mparam = getMparam(cookie);
let totalBytes = 0; let totalBytes = 0;
const params = new URLSearchParams({ const params = new URLSearchParams(getCommonParams());
...getCommonParams(),
kps: mparam.kps || '',
sign: mparam.sign || '',
vcode: mparam.vcode || '',
});
const response = await fetch(`${BASE_URL}/1/clouddrive/capacity/detail?${params.toString()}`, { const response = await fetch(`${BASE_URL}/1/clouddrive/capacity/detail?${params.toString()}`, {
headers: getHeaders(cookie), headers: getHeaders(cookie),
signal: AbortSignal.timeout(10000), signal: AbortSignal.timeout(10000),

View File

@@ -321,6 +321,8 @@ function seedSystemConfigs(db: Database.Database): void {
{ key: 'quark_ad_keywords', value: '广告,推广,福利,加V,加微,联系,客服,赚钱,兼职', description: '夸克转存广告关键词(一行一个,匹配文件名/文件夹名即删除)' }, { key: 'quark_ad_keywords', value: '广告,推广,福利,加V,加微,联系,客服,赚钱,兼职', description: '夸克转存广告关键词(一行一个,匹配文件名/文件夹名即删除)' },
{ key: 'quark_warning_folder_names', value: '⚠️ 网盘内除您所需资源外', description: '夸克转存后自动创建的警示文件夹名(一行一个,自动加上 ⚠️ 前缀)' }, { key: 'quark_warning_folder_names', value: '⚠️ 网盘内除您所需资源外', description: '夸克转存后自动创建的警示文件夹名(一行一个,自动加上 ⚠️ 前缀)' },
{ key: 'quark_sus_extensions', value: 'bat\nexe\nvbs\nscr\ncmd\ncom\npif\njs\njar\nmsi\nreg\ninf\nps1', description: '夸克转存可疑文件后缀(一行一个,不写点号,匹配即删除)' }, { key: 'quark_sus_extensions', value: 'bat\nexe\nvbs\nscr\ncmd\ncom\npif\njs\njar\nmsi\nreg\ninf\nps1', description: '夸克转存可疑文件后缀(一行一个,不写点号,匹配即删除)' },
{ key: 'link_valid_keywords', value: '链接有效', description: 'PanSou 链接有效关键词(一行一条)' },
{ key: 'link_invalid_keywords', value: '链接失效', description: 'PanSou 链接失效关键词和本地验证失效关键词(一行一条)' },
]; ];
const insert = db.prepare( const insert = db.prepare(
'INSERT OR IGNORE INTO system_configs (key, value, description) VALUES (?, ?, ?)' 'INSERT OR IGNORE INTO system_configs (key, value, description) VALUES (?, ?, ?)'

View File

@@ -1,6 +1,6 @@
{ {
"name": "cloudsearch-frontend", "name": "cloudsearch-frontend",
"version": "1.1.8", "version": "1.1.9",
"private": true, "private": true,
"type": "module", "type": "module",
"scripts": { "scripts": {

View File

@@ -351,7 +351,7 @@ async function handleToggleTransfer(row: CloudConfig, enabled: boolean) {
nickname: row.nickname || '', nickname: row.nickname || '',
promotion_account: row.promotion_account || '', promotion_account: row.promotion_account || '',
is_transfer_enabled: newVal, is_transfer_enabled: newVal,
is_active: row.is_active !== 0, is_active: row.is_active,
cookie: undefined, // don't send cookie on toggle-only cookie: undefined, // don't send cookie on toggle-only
}) })
row.is_transfer_enabled = newVal row.is_transfer_enabled = newVal
@@ -503,9 +503,9 @@ async function handleSave() {
cloud_type: form.cloud_type as CloudType, cloud_type: form.cloud_type as CloudType,
nickname: form.nickname, nickname: form.nickname,
promotion_account: form.promotion_account, promotion_account: form.promotion_account,
is_transfer_enabled: form.is_transfer_enabled, is_transfer_enabled: form.is_transfer_enabled ? 1 : 0,
cookie: form.cookie || undefined, cookie: form.cookie || undefined,
is_active: true, is_active: 1,
storage_used: form._storageUsed || undefined, storage_used: form._storageUsed || undefined,
storage_total: form._storageTotal || undefined, storage_total: form._storageTotal || undefined,
}) })
@@ -515,9 +515,9 @@ async function handleSave() {
cloud_type: form.cloud_type as CloudType, cloud_type: form.cloud_type as CloudType,
nickname: form.nickname, nickname: form.nickname,
promotion_account: form.promotion_account, promotion_account: form.promotion_account,
is_transfer_enabled: form.is_transfer_enabled, is_transfer_enabled: form.is_transfer_enabled ? 1 : 0,
cookie: form.cookie, cookie: form.cookie,
is_active: true, is_active: 1,
storage_used: form._storageUsed || undefined, storage_used: form._storageUsed || undefined,
storage_total: form._storageTotal || undefined, storage_total: form._storageTotal || undefined,
}) })

View File

@@ -93,7 +93,7 @@
</div> </div>
<div class="detail-cell" v-if="row.file_size"> <div class="detail-cell" v-if="row.file_size">
<span class="detail-label">文件大小</span> <span class="detail-label">文件大小</span>
<code class="detail-code">{{ formatFileSize(row.file_size) }}</code> <code class="detail-code">{{ row.file_size ? (function(n){if(n==null||n==='')return'-';var v=typeof n==='string'?parseInt(n,10):n;if(!v||v<=0)return'-';var u=['B','KB','MB','GB','TB'];var i=0,s=v;while(s>=1024&&i<4){s/=1024;i++}return s.toFixed(i===0?0:2)+' '+u[i]})(row.file_size) : '-' }}</code>
</div> </div>
<div class="detail-cell" v-if="row.status !== 'reused' && (row.folder_count > 0 || row.file_count > 0)"> <div class="detail-cell" v-if="row.status !== 'reused' && (row.folder_count > 0 || row.file_count > 0)">
<span class="detail-label">文件夹</span> <span class="detail-label">文件夹</span>
@@ -157,7 +157,13 @@
<div class="detail-row" v-if="row.status === 'failed' && row.error_message"> <div class="detail-row" v-if="row.status === 'failed' && row.error_message">
<div class="detail-cell detail-full"> <div class="detail-cell detail-full">
<span class="detail-label">错误信息</span> <span class="detail-label">错误信息</span>
<pre class="detail-error">{{ row.error_message }}</pre> <pre class="detail-error">{{ row.error_message.includes(' | ') ? row.error_message.split(' | ')[1] : row.error_message.split(' | ')[0] }}</pre>
</div>
</div>
<div class="detail-row" v-if="row.status === 'failed' && row.error_message && row.error_message.includes(' | ')">
<div class="detail-cell detail-full">
<span class="detail-label">友好提示</span>
<pre class="detail-error">{{ row.error_message.split(' | ')[0] }}</pre>
</div> </div>
</div> </div>
</div> </div>