使用Graph API批量创建OneDrive文件夹时创建1000个后停止的问题
问题
近期使用Microsoft Graph API实现功能:在共享文件夹中创建名称格式为uc_123455的目录,并在每个创建的文件夹内上传PDF文件。程序初始运行正常,但创建到1000个文件夹后便停止,总共需要创建约2323个文件夹。已尝试添加sleep或延长PHP执行时间,但均无效。
代码执行流程
- 从Microsoft获取access_token,初始化Graph实例并获取共享文件夹ID:
$this->graph = new Graph(); $this->graph->setAccessToken($this->accessToken); $this->directory_id = $this->fetchDirectoryId($this->graph);
- 进入循环:
- 根据循环阶段生成文件夹名称:
$subfolderName = "UC_" . $customer->numero_uc;
- 调用createIntoDirectory函数创建文件夹:
$folder = $this->createIntoDirectory($this->directory_id[0], $this->graph, $subfolderName);
核心函数实现
createIntoDirectory函数
public function createIntoDirectory(string $folderId, Graph $graph, string $ucNumber): string|bool { try { $response = $graph->createRequest('POST', "/me/drives/" . $this->drive_id . "/items/$folderId/children") ->attachBody(json_encode([ "name" => $ucNumber, "folder" => new stdClass() ], JSON_THROW_ON_ERROR)) ->setReturnType(DriveItem::class) ->execute(); return $response->getId(); } catch (GuzzleException|GraphException) { return $this->fetchExistenteDirectory($folderId, $graph, $ucNumber); } }
fetchExistenteDirectory函数
public function fetchExistenteDirectory(string $folderId, Graph $graph, string $ucNumber): string|bool { try { $folder = $graph->createRequest('GET', "/me/drives/" . $this->drive_id . "/items/$folderId/children") ->setReturnType(DriveItem::class) ->execute(); foreach ($folder as $item) { if ($item->getName() === $ucNumber) { return $item->getId(); } } return false; } catch (GuzzleException|GraphException) { return false; } }
补充说明:
- PDF文件:从数据库获取URL转换而来的图片生成的PDF。
- UC:葡萄牙语"unidade consumidora"(消费单元)的缩写,每个UC对应数据库中的一条数据。
问题分析与解决方案
核心问题排查
Graph API速率限制触发
Microsoft Graph API对OneDrive操作有默认速率限制(通常为每10分钟1000次请求),创建1000个文件夹刚好触达阈值,后续请求被拒绝,导致程序停止。文件夹存在性查询低效
fetchExistenteDirectory函数每次查询会拉取目录下所有子项,随着文件夹数量增多,响应数据量暴增,既耗时又额外消耗请求配额,加速触发速率限制;同时未处理分页逻辑,当子项超过100个时,仅能获取第一页数据,可能导致找不到已存在文件夹。异常处理不精准
捕获异常后直接跳转查询逻辑,未区分速率限制(HTTP 429)等特定错误,导致无效请求浪费配额。
改进方案
- 处理速率限制重试
在异常处理中检测HTTP 429错误,根据响应头Retry-After等待后重试:
catch (GuzzleException|GraphException $e) { if ($e instanceof ClientException && $e->getResponse()->getStatusCode() === 429) { $retryAfter = $e->getResponse()->getHeaderLine('Retry-After'); sleep((int)$retryAfter ?: 10); return $this->createIntoDirectory($folderId, $graph, $ucNumber); } return $this->fetchExistenteDirectory($folderId, $graph, $ucNumber); }
- 优化文件夹存在性查询
使用Graph API的$filter和$select参数精准查询,减少数据传输量:
public function fetchExistenteDirectory(string $folderId, Graph $graph, string $ucNumber): string|bool { try { $response = $graph->createRequest('GET', "/me/drives/" . $this->drive_id . "/items/$folderId/children?\$filter=name eq '$ucNumber' and folder ne null&\$select=id") ->setReturnType(DriveItem::class) ->execute(); return !empty($response) ? $response[0]->getId() : false; } catch (GuzzleException|GraphException $e) { if ($e instanceof ClientException && $e->getResponse()->getStatusCode() === 429) { $retryAfter = $e->getResponse()->getHeaderLine('Retry-After'); sleep((int)$retryAfter ?: 10); return $this->fetchExistenteDirectory($folderId, $graph, $ucNumber); } return false; } }
任务分段执行
将2323个文件夹创建任务拆分为多批次(如每500个一批),每批次执行完成后等待1-2分钟,避免短时间内请求过载。检查access_token有效期
access_token默认有效期1小时,若程序运行超时,需在循环中定期检测token状态,过期时重新获取。
内容的提问来源于stack exchange,提问作者Vinícius V. Beraldo
相关产品推荐
相关产品推荐

