如何基于OAuth令牌实现Symfony后端向Google发起服务端请求
用Symfony后端调用Google Drive/Calendar API(基于已存储的OAuth令牌)
以下是针对你的场景的具体实现方案,核心是利用Google官方客户端库,结合Symfony服务封装令牌管理与API调用逻辑:
1. 安装依赖
首先通过Composer安装Google官方PHP客户端库,这是调用Google API最便捷的方式:
composer require google/apiclient:^2.0
2. 封装Google API服务类
创建一个服务类,统一处理令牌加载、自动刷新,以及Drive/Calendar服务实例化:
// src/Service/GoogleApiService.php namespace App\Service; use Google\Client; use Google\Service\Drive; use Google\Service\Calendar; use Doctrine\ORM\EntityManagerInterface; use App\Entity\UserOAuthToken; // 替换为你的令牌实体类 class GoogleApiService { private $client; private $entityManager; public function __construct(EntityManagerInterface $em, string $googleClientId, string $googleClientSecret) { $this->entityManager = $em; $this->client = new Client(); $this->client->setClientId($googleClientId); $this->client->setClientSecret($googleClientSecret); } // 初始化用户令牌并处理过期刷新 private function initUserToken(int $userId): void { $tokenEntity = $this->entityManager->getRepository(UserOAuthToken::class)->findOneBy(['userId' => $userId]); if (!$tokenEntity) { throw new \Exception('用户未完成Google授权'); } // 组装令牌数据 $tokenData = [ 'access_token' => $tokenEntity->getAccessToken(), 'refresh_token' => $tokenEntity->getRefreshToken(), 'expires_in' => $tokenEntity->getExpiresAt() - time(), 'created' => $tokenEntity->getCreatedAt()->getTimestamp() ]; $this->client->setAccessToken($tokenData); // 自动刷新过期令牌 if ($this->client->isAccessTokenExpired()) { $newToken = $this->client->fetchAccessTokenWithRefreshToken($tokenEntity->getRefreshToken()); // 更新数据库中的令牌信息 $tokenEntity->setAccessToken($newToken['access_token']); $tokenEntity->setExpiresAt(time() + $newToken['expires_in']); $this->entityManager->persist($tokenEntity); $this->entityManager->flush(); $this->client->setAccessToken($newToken); } } // 获取Drive服务实例 public function getDriveService(int $userId): Drive { $this->initUserToken($userId); return new Drive($this->client); } // 获取Calendar服务实例 public function getCalendarService(int $userId): Calendar { $this->initUserToken($userId); return new Calendar($this->client); } }
在config/services.yaml中配置服务,注入Google客户端ID和密钥:
services: App\Service\GoogleApiService: arguments: $googleClientId: '%env(GOOGLE_CLIENT_ID)%' $googleClientSecret: '%env(GOOGLE_CLIENT_SECRET)%'
3. 编写Controller接口
示例:获取用户Drive文件列表
// src/Controller/GoogleDriveController.php namespace App\Controller; use App\Service\GoogleApiService; use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; use Symfony\Component\HttpFoundation\JsonResponse; use Symfony\Component\Routing\Annotation\Route; class GoogleDriveController extends AbstractController { #[Route('/api/google/drive/files', methods: ['GET'])] public function getDriveFiles(GoogleApiService $googleApiService): JsonResponse { // 从当前认证用户获取ID(需结合你的后端认证逻辑,比如JWT) $userId = $this->getUser()->getId(); try { $driveService = $googleApiService->getDriveService($userId); // 调用Drive API获取文件列表,可按需调整参数 $files = $driveService->files->listFiles([ 'pageSize' => 10, 'fields' => 'files(id, name, webViewLink, mimeType)' ]); // 整理返回给前端的数据 $responseData = []; foreach ($files->getFiles() as $file) { $responseData[] = [ 'id' => $file->getId(), 'name' => $file->getName(), 'view_link' => $file->getWebViewLink(), 'type' => $file->getMimeType() ]; } return $this->json([ 'success' => true, 'data' => $responseData ]); } catch (\Exception $e) { return $this->json([ 'success' => false, 'message' => '获取Drive文件失败:' . $e->getMessage() ], 500); } } }
示例:创建Calendar事件
// src/Controller/GoogleCalendarController.php namespace App\Controller; use App\Service\GoogleApiService; use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; use Symfony\Component\HttpFoundation\JsonResponse; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\Routing\Annotation\Route; use Google\Service\Calendar\Event; class GoogleCalendarController extends AbstractController { #[Route('/api/google/calendar/events', methods: ['POST'])] public function createCalendarEvent(Request $request, GoogleApiService $googleApiService): JsonResponse { $userId = $this->getUser()->getId(); $payload = json_decode($request->getContent(), true); // 参数校验 if (empty($payload['summary']) || empty($payload['start_time']) || empty($payload['end_time'])) { return $this->json([ 'success' => false, 'message' => '缺少必要参数:标题、开始时间、结束时间' ], 400); } try { $calendarService = $googleApiService->getCalendarService($userId); $event = new Event([ 'summary' => $payload['summary'], 'description' => $payload['description'] ?? '', 'start' => [ 'dateTime' => $payload['start_time'], 'timeZone' => $payload['time_zone'] ?? 'Asia/Shanghai', ], 'end' => [ 'dateTime' => $payload['end_time'], 'timeZone' => $payload['time_zone'] ?? 'Asia/Shanghai', ], ]); $createdEvent = $calendarService->events->insert('primary', $event); // 按需将事件信息存储到数据库(关联用户ID与Google事件ID) // $eventEntity = new UserCalendarEvent(); // $eventEntity->setUserId($userId); // $eventEntity->setGoogleEventId($createdEvent->getId()); // $this->getDoctrine()->getManager()->persist($eventEntity); // $this->getDoctrine()->getManager()->flush(); return $this->json([ 'success' => true, 'data' => [ 'event_id' => $createdEvent->getId(), 'html_link' => $createdEvent->getHtmlLink() ] ]); } catch (\Exception $e) { return $this->json([ 'success' => false, 'message' => '创建日历事件失败:' . $e->getMessage() ], 500); } } }
4. 前端调用示例(Vue)
通过axios调用后端接口,需携带后端认证令牌(如JWT):
async getDriveFiles() { try { const response = await axios.get('/api/google/drive/files', { headers: { 'Authorization': `Bearer ${localStorage.getItem('your_jwt_token')}` } }); if (response.data.success) { this.driveFiles = response.data.data; } else { alert(response.data.message); } } catch (error) { console.error('获取文件失败:', error); } }
关键注意事项
- 令牌有效期:Google access_token有效期为1小时,服务类中已实现自动刷新逻辑,刷新后需同步更新数据库。
- 权限范围:前端授权时需请求对应API的权限scope,比如Drive只读用
https://www.googleapis.com/auth/drive.readonly,日历事件用https://www.googleapis.com/auth/calendar.events。 - 用户认证:后端接口需确保请求用户合法,可通过JWT或Session获取用户ID,再关联对应的OAuth令牌。
- 错误处理:捕获Google API抛出的异常(如权限不足、令牌无效),返回清晰的错误信息给前端。
内容的提问来源于stack exchange,提问作者Neo710
相关产品推荐
相关产品推荐

