fix: 转存时先查资源历史, 复用原账号; save_records加config_id
- 资源维度优先级 > IP维度: 先查share_url是否被转存过 - save_records 表新增 config_id 字段 + 写入时记录 - cloud.service.ts 所有 INSERT 写入 config.id - credential.service.ts: getAndValidateCredential 加 shareUrl 参数 - 数据库 migration: config_id 到 save_records
This commit is contained in:
@@ -70,14 +70,14 @@ async function doSaveFromShare(shareUrl: string, cloudType: string, sourceTitle?
|
||||
if (alreadySaved && recentRecord.share_url) {
|
||||
console.log(`[Share] 🛡️ Dedup: ${shareUrl} was saved ${DEDUP_WINDOW_SEC}s ago (status=${recentRecord.status}), returning existing share link`);
|
||||
db.prepare(
|
||||
`INSERT INTO save_records (source_type, source_title, source_url, target_cloud, share_url, share_pwd, file_size, file_count, folder_count, duration_ms, status, error_message, folder_name, original_folder_name, ip_address, ip_location, created_at)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`
|
||||
`INSERT INTO save_records (source_type, source_title, source_url, target_cloud, share_url, share_pwd, file_size, file_count, folder_count, duration_ms, status, error_message, folder_name, original_folder_name, ip_address, ip_location, created_at, config_id)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`
|
||||
).run(
|
||||
cloudType, sourceTitle || null, shareUrl, cloudType,
|
||||
recentRecord.share_url, recentRecord.share_pwd || null,
|
||||
null, 0, 0, 0, 'reused', null,
|
||||
recentRecord.folder_name || null, recentRecord.original_folder_name || null,
|
||||
ipAddress || null, ipLocation, localTimestamp(),
|
||||
ipAddress || null, ipLocation, localTimestamp(), null,
|
||||
);
|
||||
return {
|
||||
success: true,
|
||||
@@ -141,7 +141,7 @@ async function doSaveFromShare(shareUrl: string, cloudType: string, sourceTitle?
|
||||
}
|
||||
|
||||
// ── Unified credential validation ──
|
||||
const credential = await getAndValidateCredential(cloudType, ipAddress);
|
||||
const credential = await getAndValidateCredential(cloudType, ipAddress, shareUrl);
|
||||
if (!credential.valid || !credential.config) {
|
||||
return { success: false, message: credential.message };
|
||||
}
|
||||
@@ -189,8 +189,8 @@ async function doSaveFromShare(shareUrl: string, cloudType: string, sourceTitle?
|
||||
}
|
||||
|
||||
db.prepare(
|
||||
`INSERT INTO save_records (source_type, source_title, source_url, target_cloud, share_url, share_pwd, file_size, file_count, folder_count, duration_ms, status, error_message, folder_name, original_folder_name, ip_address, ip_location, created_at)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`
|
||||
`INSERT INTO save_records (source_type, source_title, source_url, target_cloud, share_url, share_pwd, file_size, file_count, folder_count, duration_ms, status, error_message, folder_name, original_folder_name, ip_address, ip_location, created_at, config_id)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`
|
||||
).run(
|
||||
cloudType, sourceTitle || driverResult.folderName || null, shareUrl, cloudType,
|
||||
driverResult.shareUrl || null, driverResult.sharePwd || null,
|
||||
@@ -198,7 +198,7 @@ async function doSaveFromShare(shareUrl: string, cloudType: string, sourceTitle?
|
||||
durationMs, driverResult.success ? 'success' : 'failed',
|
||||
driverResult.success ? null : driverResult.message,
|
||||
driverResult.folderName || null, driverResult.originalFolderName || null,
|
||||
ipAddress || null, ipLocation, localTimestamp(),
|
||||
ipAddress || null, ipLocation, localTimestamp(), config.id
|
||||
);
|
||||
|
||||
return {
|
||||
@@ -221,9 +221,9 @@ async function doSaveFromShare(shareUrl: string, cloudType: string, sourceTitle?
|
||||
).run(config.id);
|
||||
|
||||
db.prepare(
|
||||
`INSERT INTO save_records (source_type, source_url, target_cloud, duration_ms, status, error_message, ip_address, ip_location, created_at)
|
||||
`INSERT INTO save_records (source_type, source_url, target_cloud, duration_ms, status, error_message, ip_address, ip_location, created_at, config_id)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`
|
||||
).run(cloudType, shareUrl, cloudType, durationMs, 'failed', errorMessage, ipAddress || null, ipLocation, localTimestamp());
|
||||
).run(cloudType, shareUrl, cloudType, durationMs, 'failed', errorMessage, ipAddress || null, ipLocation, localTimestamp(), null);
|
||||
|
||||
return { success: false, message: errorMessage };
|
||||
}
|
||||
|
||||
@@ -423,11 +423,38 @@ export interface CredentialValidationResult {
|
||||
*
|
||||
* Reference: search-ucmao get_and_validate_credential() pattern.
|
||||
*/
|
||||
export async function getAndValidateCredential(cloudType: string, ipAddress?: string): Promise<CredentialValidationResult> {
|
||||
export async function getAndValidateCredential(cloudType: string, ipAddress?: string, shareUrl?: string): Promise<CredentialValidationResult> {
|
||||
const db = getDb();
|
||||
|
||||
let config: CloudConfig | undefined;
|
||||
|
||||
// ── Resource history lookup: if this share URL was saved before, reuse that account ──
|
||||
if (shareUrl) {
|
||||
const historyRecord = db.prepare(
|
||||
`SELECT config_id, target_cloud, folder_name FROM save_records
|
||||
WHERE share_url = ? AND target_cloud = ? AND status IN ('success', 'reused')
|
||||
ORDER BY id DESC LIMIT 1`
|
||||
).get(shareUrl, cloudType) as { config_id: number; target_cloud: string; folder_name: string } | undefined;
|
||||
|
||||
if (historyRecord) {
|
||||
// Resource was previously saved — reuse the exact same config if still healthy
|
||||
if (historyRecord.config_id) {
|
||||
config = db.prepare(
|
||||
`SELECT * FROM cloud_configs WHERE id = ? AND is_active = 1 AND consecutive_failures < 5`
|
||||
).get(historyRecord.config_id) as CloudConfig | undefined;
|
||||
}
|
||||
if (!config) {
|
||||
// Fallback: pick any healthy account from this cloud type
|
||||
config = db.prepare(
|
||||
`SELECT * FROM cloud_configs
|
||||
WHERE cloud_type = ? AND is_active = 1 AND consecutive_failures < 5
|
||||
ORDER BY is_primary DESC, last_used_at ASC NULLS FIRST
|
||||
LIMIT 1`
|
||||
).get(cloudType) as CloudConfig | undefined;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!ipAddress) {
|
||||
// No IP info — fallback to simple LUR
|
||||
config = db.prepare(
|
||||
|
||||
Reference in New Issue
Block a user