You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何基于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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.24 23:33:21