PHP实现OneDrive大文件上传遇阻:SDK及Curl方案均报错
解决OneDrive大文件上传的两种方案报错问题
一、Curl方案「API not found」错误修复
问题原因
- 端点混用错误:创建上传会话用了旧的
https://api.onedrive.com/v1.0端点,上传小文件又切换到Graph端点,且小文件上传的请求方法错误。 - 接口使用不当:上传小于4MB的文件需用
PUT方法,而非POST。
修复后的代码
1. 创建大文件上传会话
$createSessionUrl = "https://graph.microsoft.com/v1.0/me/drive/root:/$fileName:/createUploadSession"; $headers = [ "Authorization: Bearer $accessToken", "Content-Type: application/json" ]; $postData = json_encode([ 'item' => [ '@microsoft.graph.conflictBehavior' => 'rename', 'name' => $fileName ] ]); $ch = curl_init($createSessionUrl); curl_setopt($ch, CURLOPT_POST, true); curl_setopt($ch, CURLOPT_HTTPHEADER, $headers); curl_setopt($ch, CURLOPT_POSTFIELDS, $postData); curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); $response = curl_exec($ch); $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE); curl_close($ch); $responseArray = json_decode($response, true); if ($httpCode >= 200 && $httpCode < 300) { return $responseArray; } else { echo "Failed to create upload session: HTTP $httpCode - " . json_encode($responseArray); return null; }
2. 小文件上传
$uploadUrl = "https://graph.microsoft.com/v1.0/me/drive/root:/$fileName:/content"; $fileContent = file_get_contents($filePath); $headers = [ "Authorization: Bearer $accessToken", "Content-Type: application/octet-stream" ]; $ch = curl_init($uploadUrl); curl_setopt($ch, CURLOPT_PUT, true); // 改用PUT方法 curl_setopt($ch, CURLOPT_HTTPHEADER, $headers); curl_setopt($ch, CURLOPT_POSTFIELDS, $fileContent); curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); $response = curl_exec($ch); $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE); curl_close($ch); $responseArray = json_decode($response, true); if ($httpCode >= 200 && $httpCode < 300) { echo "File uploaded successfully"; } else { echo "Failed to upload small file: HTTP $httpCode - " . json_encode($responseArray); }
关键注意事项
- 统一使用Microsoft Graph端点
https://graph.microsoft.com/v1.0,废弃旧OneDrive独立端点。 accessToken需通过授权码流程获取(个人OneDrive),并拥有Files.ReadWrite或Files.ReadWrite.All权限,客户端凭证流程无法访问/me端点。
二、Graph SDK方案「invalid_grant」错误修复
问题原因
- 认证上下文不匹配:用客户端凭证流程获取token,却用
AuthorizationCodeContext初始化客户端,且尝试访问me()->drive(),但客户端凭证流程无当前用户,无法使用/me。 - 流程与场景不匹配:客户端凭证流程仅适用于企业/教育版OneDrive,个人OneDrive必须用授权码流程。
修复后的代码
情况1:个人OneDrive(授权码流程)
require_once 'vendor/autoload.php'; use GuzzleHttp\Client; use Microsoft\Graph\GraphServiceClient; use Microsoft\Kiota\Authentication\Oauth\AuthorizationCodeContext; use Microsoft\Graph\Core\Tasks\LargeFileUploadTask; use Microsoft\Graph\Generated\Drives\Item\Items\Item\CreateUploadSession\CreateUploadSessionPostRequestBody as DriveItemCreateUploadSessionPostRequestBody; use Microsoft\Graph\Generated\Models; use GuzzleHttp\Psr7\Utils; $clientId = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxx4"; $clientSecret = "xxxxxxxxxxxxxxxxxxraqR"; $tenantId = "common"; $redirectUri = "http://localhost/test1/src/test3/test.php"; // 先通过授权码地址获取code: // https://login.microsoftonline.com/common/oauth2/v2.0/authorize?client_id=$clientId&response_type=code&redirect_uri=$redirectUri&scope=Files.ReadWrite.All offline_access // 假设已获取授权码$code,交换token $guzzle = new Client(); $tokenUrl = "https://login.microsoftonline.com/$tenantId/oauth2/v2.0/token"; $tokenResponse = $guzzle->post($tokenUrl, [ 'form_params' => [ 'client_id' => $clientId, 'client_secret' => $clientSecret, 'code' => $code, 'redirect_uri' => $redirectUri, 'grant_type' => 'authorization_code' ] ]); $token = json_decode($tokenResponse->getBody()->getContents(), true); // 初始化客户端 $tokenContext = new AuthorizationCodeContext( $tenantId, $clientId, $clientSecret, $token['access_token'], $redirectUri, $token['refresh_token'] ); $graphClient = new GraphServiceClient($tokenContext, ['Files.ReadWrite.All']); function uploadFileToOneDrive(GraphServiceClient $graphClient, string $filePath, string $itemPath): void { $file = Utils::streamFor(fopen($filePath, 'r')); $uploadProperties = new Models\DriveItemUploadableProperties(); $uploadProperties->setAdditionalData([ '@microsoft.graph.conflictBehavior' => 'replace' ]); $uploadSessionRequest = new DriveItemCreateUploadSessionPostRequestBody(); $uploadSessionRequest->setItem($uploadProperties); $uploadSession = $graphClient->me()->drive()->root()->itemWithPath($itemPath)->createUploadSession()->post($uploadSessionRequest)->wait(); $largeFileUpload = new LargeFileUploadTask($uploadSession, $graphClient->getRequestAdapter(), $file); $totalSize = $file->getSize(); $progress = fn($prog) => print('Uploaded '.$prog[1].' of '.$totalSize.' bytes'.PHP_EOL); try { $largeFileUpload->upload($progress)->wait(); echo "File uploaded successfully"; } catch (\Psr\Http\Client\NetworkExceptionInterface $ex) { $largeFileUpload->resume()->wait(); } } uploadFileToOneDrive($graphClient, "test.txt", "test.txt");
情况2:企业/教育版OneDrive(客户端凭证流程)
require_once 'vendor/autoload.php'; use Microsoft\Graph\GraphServiceClient; use Microsoft\Kiota\Authentication\Oauth\ClientCredentialContext; use Microsoft\Graph\Core\Tasks\LargeFileUploadTask; use Microsoft\Graph\Generated\Drives\Item\Items\Item\CreateUploadSession\CreateUploadSessionPostRequestBody as DriveItemCreateUploadSessionPostRequestBody; use Microsoft\Graph\Generated\Models; use GuzzleHttp\Psr7\Utils; $clientId = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxx4"; $clientSecret = "xxxxxxxxxxxxxxxxxxraqR"; $tenantId = "your-tenant-id"; // 替换为实际租户ID,不能用common $driveId = "your-site-drive-id"; // 替换为目标站点的Drive ID // 初始化客户端凭证上下文 $tokenContext = new ClientCredentialContext( $tenantId, $clientId, $clientSecret ); $graphClient = new GraphServiceClient($tokenContext, ['Files.ReadWrite.All']); function uploadFileToOneDrive(GraphServiceClient $graphClient, string $filePath, string $itemPath, string $driveId): void { $file = Utils::streamFor(fopen($filePath, 'r')); $uploadProperties = new Models\DriveItemUploadableProperties(); $uploadProperties->setAdditionalData([ '@microsoft.graph.conflictBehavior' => 'replace' ]); $uploadSessionRequest = new DriveItemCreateUploadSessionPostRequestBody(); $uploadSessionRequest->setItem($uploadProperties); $uploadSession = $graphClient->drives()->byDriveId($driveId)->root()->itemWithPath($itemPath)->createUploadSession()->post($uploadSessionRequest)->wait(); $largeFileUpload = new LargeFileUploadTask($uploadSession, $graphClient->getRequestAdapter(), $file); $totalSize = $file->getSize(); $progress = fn($prog) => print('Uploaded '.$prog[1].' of '.$totalSize.' bytes'.PHP_EOL); try { $largeFileUpload->upload($progress)->wait(); echo "File uploaded successfully"; } catch (\Psr\Http\Client\NetworkExceptionInterface $ex) { $largeFileUpload->resume()->wait(); } } uploadFileToOneDrive($graphClient, "test.txt", "test.txt", $driveId);
关键注意事项
- 个人OneDrive必须用授权码流程,客户端凭证流程仅支持企业/教育版站点Drive。
- Azure AD应用需配置对应权限:个人场景添加
Files.ReadWrite.All并完成用户同意;企业场景需管理员同意。 - 认证上下文要与token流程匹配:授权码用
AuthorizationCodeContext,客户端凭证用ClientCredentialContext。
内容的提问来源于stack exchange,提问作者bia
相关产品推荐
相关产品推荐

