|
|
|
|
@ -58,16 +58,39 @@ class UpdateBookIsbnData extends Command
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$this->info("找到 {$books->count()} 本书需要处理");
|
|
|
|
|
$this->info("API限制:每秒最多10次请求,预计耗时约 " . ceil($books->count() / 10) . " 秒");
|
|
|
|
|
|
|
|
|
|
$bar = $this->output->createProgressBar($books->count());
|
|
|
|
|
$bar->start();
|
|
|
|
|
|
|
|
|
|
$successCount = 0;
|
|
|
|
|
$failCount = 0;
|
|
|
|
|
$requestCount = 0;
|
|
|
|
|
$startTime = microtime(true);
|
|
|
|
|
$lastResetTime = $startTime;
|
|
|
|
|
|
|
|
|
|
foreach ($books as $book) {
|
|
|
|
|
try {
|
|
|
|
|
// 每秒重置请求计数器
|
|
|
|
|
$currentTime = microtime(true);
|
|
|
|
|
if ($currentTime - $lastResetTime >= 1.0) {
|
|
|
|
|
$requestCount = 0;
|
|
|
|
|
$lastResetTime = $currentTime;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// API频率限制控制:每秒最多10次请求
|
|
|
|
|
if ($requestCount >= 10) {
|
|
|
|
|
$waitTime = 1.0 - ($currentTime - $lastResetTime);
|
|
|
|
|
if ($waitTime > 0) {
|
|
|
|
|
usleep(intval($waitTime * 1000000));
|
|
|
|
|
$requestCount = 0;
|
|
|
|
|
$lastResetTime = microtime(true);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$result = $this->processBook($book, $apiKey);
|
|
|
|
|
$requestCount++;
|
|
|
|
|
|
|
|
|
|
if ($result) {
|
|
|
|
|
$successCount++;
|
|
|
|
|
$this->line("\n✓ 成功处理书籍: {$book->title} (ISBN: {$book->isbn})");
|
|
|
|
|
@ -78,18 +101,31 @@ class UpdateBookIsbnData extends Command
|
|
|
|
|
} catch (\Exception $e) {
|
|
|
|
|
$failCount++;
|
|
|
|
|
$this->line("\n✗ 处理异常: {$book->title} - {$e->getMessage()}");
|
|
|
|
|
|
|
|
|
|
// 如果是API相关错误,增加等待时间
|
|
|
|
|
if (strpos($e->getMessage(), 'API') !== false || strpos($e->getMessage(), 'HTTP') !== false) {
|
|
|
|
|
$this->line("检测到API错误,等待2秒后继续...");
|
|
|
|
|
sleep(2);
|
|
|
|
|
$requestCount = 0;
|
|
|
|
|
$lastResetTime = microtime(true);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$bar->advance();
|
|
|
|
|
|
|
|
|
|
// 添加延迟避免API请求过快
|
|
|
|
|
sleep(1);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$bar->finish();
|
|
|
|
|
|
|
|
|
|
$totalTime = microtime(true) - $startTime;
|
|
|
|
|
$avgTimePerBook = $totalTime / $books->count();
|
|
|
|
|
$actualRequestsPerSecond = $books->count() / $totalTime;
|
|
|
|
|
|
|
|
|
|
$this->line('');
|
|
|
|
|
$this->info("处理完成!成功: {$successCount}, 失败: {$failCount}");
|
|
|
|
|
$this->info("处理完成!");
|
|
|
|
|
$this->info("成功: {$successCount}, 失败: {$failCount}");
|
|
|
|
|
$this->info("总耗时: " . round($totalTime, 2) . " 秒");
|
|
|
|
|
$this->info("平均每本书耗时: " . round($avgTimePerBook, 2) . " 秒");
|
|
|
|
|
$this->info("实际请求频率: " . round($actualRequestsPerSecond, 2) . " 次/秒");
|
|
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
@ -103,40 +139,64 @@ class UpdateBookIsbnData extends Command
|
|
|
|
|
*/
|
|
|
|
|
private function processBook(Book $book, string $apiKey): bool
|
|
|
|
|
{
|
|
|
|
|
// 调用ISBN接口
|
|
|
|
|
$response = Http::timeout(30)->get('https://api.tanshuapi.com/api/isbn/v2/index', [
|
|
|
|
|
'key' => $apiKey,
|
|
|
|
|
'isbn' => $book->isbn
|
|
|
|
|
]);
|
|
|
|
|
|
|
|
|
|
if (!$response->successful()) {
|
|
|
|
|
$this->error("API请求失败: HTTP {$response->status()}");
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
$attempt = 0;
|
|
|
|
|
$maxRetries = 3;
|
|
|
|
|
|
|
|
|
|
$data = $response->json();
|
|
|
|
|
while ($attempt < $maxRetries) {
|
|
|
|
|
try {
|
|
|
|
|
$attempt++;
|
|
|
|
|
|
|
|
|
|
if (!$data || $data['code'] !== 1) {
|
|
|
|
|
$this->error("API返回错误: " . ($data['msg'] ?? '未知错误'));
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
// 调用ISBN接口
|
|
|
|
|
$response = Http::timeout(30)->get('https://api.tanshuapi.com/api/isbn/v2/index', [
|
|
|
|
|
'key' => $apiKey,
|
|
|
|
|
'isbn' => $book->isbn
|
|
|
|
|
]);
|
|
|
|
|
|
|
|
|
|
$bookData = $data['data'];
|
|
|
|
|
if (!$response->successful()) {
|
|
|
|
|
throw new \Exception("API请求失败: HTTP {$response->status()}");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$data = $response->json();
|
|
|
|
|
|
|
|
|
|
if (!$data || $data['code'] !== 1) {
|
|
|
|
|
// 如果是API密钥错误或其他不可重试的错误,直接返回失败
|
|
|
|
|
if (isset($data['code']) && in_array($data['code'], [10001, 10002, 10003])) {
|
|
|
|
|
$this->error("API返回不可重试错误: " . ($data['msg'] ?? '未知错误'));
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
throw new \Exception("API返回错误: " . ($data['msg'] ?? '未知错误'));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$bookData = $data['data'];
|
|
|
|
|
|
|
|
|
|
// 更新书籍的other_data字段
|
|
|
|
|
$book->other_data = $bookData;
|
|
|
|
|
|
|
|
|
|
// 更新书籍的other_data字段
|
|
|
|
|
$book->other_data = $bookData;
|
|
|
|
|
// 如果有图片URL,下载图片
|
|
|
|
|
if (!empty($bookData['img'])) {
|
|
|
|
|
$coverId = $this->downloadAndSaveImage($bookData['img'], $book);
|
|
|
|
|
if ($coverId) {
|
|
|
|
|
$book->cover_id = $coverId;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$book->save();
|
|
|
|
|
return true;
|
|
|
|
|
|
|
|
|
|
} catch (\Exception $e) {
|
|
|
|
|
if ($attempt >= $maxRetries) {
|
|
|
|
|
$this->error("重试 {$maxRetries} 次后仍然失败: {$e->getMessage()}");
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 如果有图片URL,下载图片
|
|
|
|
|
if (!empty($bookData['img'])) {
|
|
|
|
|
$coverId = $this->downloadAndSaveImage($bookData['img'], $book);
|
|
|
|
|
if ($coverId) {
|
|
|
|
|
$book->cover_id = $coverId;
|
|
|
|
|
// 指数退避:第1次重试等待1秒,第2次等待2秒,第3次等待4秒
|
|
|
|
|
$waitTime = pow(2, $attempt - 1);
|
|
|
|
|
$this->line("第 {$attempt} 次尝试失败,{$waitTime} 秒后重试: {$e->getMessage()}");
|
|
|
|
|
sleep($waitTime);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$book->save();
|
|
|
|
|
|
|
|
|
|
return true;
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|