feat: IP-based daily account priority for save
- Added ip_daily_save_counts table to track per-IP daily usage - getAndValidateCredential now accepts ipAddress parameter - First 2 saves by same IP use primary account; 3rd+ round-robins to other accounts - Counter increments on each successful credential acquisition - All timestamps in Asia/Shanghai timezone
This commit is contained in:
@@ -141,7 +141,7 @@ async function doSaveFromShare(shareUrl: string, cloudType: string, sourceTitle?
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ── Unified credential validation ──
|
// ── Unified credential validation ──
|
||||||
const credential = await getAndValidateCredential(cloudType);
|
const credential = await getAndValidateCredential(cloudType, ipAddress);
|
||||||
if (!credential.valid || !credential.config) {
|
if (!credential.valid || !credential.config) {
|
||||||
return { success: false, message: credential.message };
|
return { success: false, message: credential.message };
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -397,16 +397,79 @@ export interface CredentialValidationResult {
|
|||||||
*
|
*
|
||||||
* Reference: search-ucmao get_and_validate_credential() pattern.
|
* Reference: search-ucmao get_and_validate_credential() pattern.
|
||||||
*/
|
*/
|
||||||
export async function getAndValidateCredential(cloudType: string): Promise<CredentialValidationResult> {
|
export async function getAndValidateCredential(cloudType: string, ipAddress?: string): Promise<CredentialValidationResult> {
|
||||||
const db = getDb();
|
const db = getDb();
|
||||||
|
|
||||||
const config = db.prepare(
|
let config: CloudConfig | undefined;
|
||||||
|
|
||||||
|
if (!ipAddress) {
|
||||||
|
// No IP info — fallback to simple LUR
|
||||||
|
config = db.prepare(
|
||||||
`SELECT * FROM cloud_configs
|
`SELECT * FROM cloud_configs
|
||||||
WHERE cloud_type = ? AND is_active = 1
|
WHERE cloud_type = ? AND is_active = 1
|
||||||
AND consecutive_failures < 5
|
AND consecutive_failures < 5
|
||||||
ORDER BY last_used_at ASC NULLS FIRST
|
ORDER BY last_used_at ASC NULLS FIRST
|
||||||
LIMIT 1`
|
LIMIT 1`
|
||||||
).get(cloudType) as CloudConfig | undefined;
|
).get(cloudType) as CloudConfig | undefined;
|
||||||
|
} else {
|
||||||
|
// Get today's date in Shanghai time
|
||||||
|
const today = (() => {
|
||||||
|
const now = new Date();
|
||||||
|
const shanghai = new Date(now.toLocaleString('en-US', { timeZone: 'Asia/Shanghai' }));
|
||||||
|
return shanghai.toISOString().slice(0, 10);
|
||||||
|
})();
|
||||||
|
|
||||||
|
// Count how many times this IP has saved today for this cloud type
|
||||||
|
const ipCountRow = db.prepare(
|
||||||
|
`SELECT COALESCE(SUM(save_count), 0) as total
|
||||||
|
FROM ip_daily_save_counts
|
||||||
|
WHERE ip_address = ? AND date = ? AND cloud_type = ?`
|
||||||
|
).get(ipAddress, today, cloudType) as { total: number };
|
||||||
|
|
||||||
|
const ipTodayCount = ipCountRow?.total || 0;
|
||||||
|
|
||||||
|
if (ipTodayCount < 3) {
|
||||||
|
// First 2 saves — use the primary account (least recently used, healthy)
|
||||||
|
config = db.prepare(
|
||||||
|
`SELECT * FROM cloud_configs
|
||||||
|
WHERE cloud_type = ? AND is_active = 1
|
||||||
|
AND consecutive_failures < 5
|
||||||
|
ORDER BY last_used_at ASC NULLS FIRST
|
||||||
|
LIMIT 1`
|
||||||
|
).get(cloudType) as CloudConfig | undefined;
|
||||||
|
} else {
|
||||||
|
// 3rd+ save — exclude accounts this IP has already used today,
|
||||||
|
// fall back to other available accounts round-robin
|
||||||
|
const usedConfigIds = db.prepare(
|
||||||
|
`SELECT DISTINCT config_id FROM ip_daily_save_counts
|
||||||
|
WHERE ip_address = ? AND date = ? AND cloud_type = ?
|
||||||
|
ORDER BY config_id`
|
||||||
|
).all(ipAddress, today, cloudType) as { config_id: number }[];
|
||||||
|
|
||||||
|
const usedIds = usedConfigIds.map(r => r.config_id);
|
||||||
|
const placeholders = usedIds.length > 0 ? usedIds.map(() => '?').join(',') : '-1';
|
||||||
|
|
||||||
|
config = db.prepare(
|
||||||
|
`SELECT * FROM cloud_configs
|
||||||
|
WHERE cloud_type = ? AND is_active = 1
|
||||||
|
AND consecutive_failures < 5
|
||||||
|
AND id NOT IN (${placeholders})
|
||||||
|
ORDER BY last_used_at ASC NULLS FIRST
|
||||||
|
LIMIT 1`
|
||||||
|
).get(cloudType, ...usedIds) as CloudConfig | undefined;
|
||||||
|
|
||||||
|
// If all accounts have been used by this IP, fall back to primary
|
||||||
|
if (!config) {
|
||||||
|
config = db.prepare(
|
||||||
|
`SELECT * FROM cloud_configs
|
||||||
|
WHERE cloud_type = ? AND is_active = 1
|
||||||
|
AND consecutive_failures < 5
|
||||||
|
ORDER BY last_used_at ASC NULLS FIRST
|
||||||
|
LIMIT 1`
|
||||||
|
).get(cloudType) as CloudConfig | undefined;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (!config) {
|
if (!config) {
|
||||||
return {
|
return {
|
||||||
@@ -457,6 +520,21 @@ export async function getAndValidateCredential(cloudType: string): Promise<Crede
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Track IP daily usage count (if ipAddress provided)
|
||||||
|
if (ipAddress && config) {
|
||||||
|
const today = (() => {
|
||||||
|
const now = new Date();
|
||||||
|
const shanghai = new Date(now.toLocaleString('en-US', { timeZone: 'Asia/Shanghai' }));
|
||||||
|
return shanghai.toISOString().slice(0, 10);
|
||||||
|
})();
|
||||||
|
db.prepare(
|
||||||
|
`INSERT INTO ip_daily_save_counts (ip_address, date, cloud_type, config_id, save_count)
|
||||||
|
VALUES (?, ?, ?, ?, 1)
|
||||||
|
ON CONFLICT(ip_address, date, cloud_type, config_id)
|
||||||
|
DO UPDATE SET save_count = save_count + 1`
|
||||||
|
).run(ipAddress, today, cloudType, config.id);
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
valid: true,
|
valid: true,
|
||||||
config: { ...config, cookie: decryptedCookie },
|
config: { ...config, cookie: decryptedCookie },
|
||||||
|
|||||||
@@ -119,6 +119,16 @@ function runMigrations(db: Database.Database): void {
|
|||||||
source TEXT,
|
source TEXT,
|
||||||
updated_at TEXT NOT NULL DEFAULT (datetime('now', 'localtime'))
|
updated_at TEXT NOT NULL DEFAULT (datetime('now', 'localtime'))
|
||||||
);
|
);
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS ip_daily_save_counts (
|
||||||
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
|
ip_address TEXT NOT NULL,
|
||||||
|
date TEXT NOT NULL,
|
||||||
|
cloud_type TEXT NOT NULL,
|
||||||
|
config_id INTEGER NOT NULL,
|
||||||
|
save_count INTEGER NOT NULL DEFAULT 0,
|
||||||
|
UNIQUE(ip_address, date, cloud_type, config_id)
|
||||||
|
);
|
||||||
`);
|
`);
|
||||||
seedSystemConfigs(db);
|
seedSystemConfigs(db);
|
||||||
migrateSaveRecords(db);
|
migrateSaveRecords(db);
|
||||||
|
|||||||
Reference in New Issue
Block a user