将 Google 日历与 Laravel 网站集成
引言
概述
创建项目
添加凭据
需要 Google API 客户端
授权
从 Google 日历中提取预订信息
将预订信息推送至 Google 日历
陷阱
概括
由 Mux 主办的 DEV 全球展示挑战赛:展示你的项目!
我们即将完成将 Google 日历通过 Google API 集成到客户网站(Laravel)中的工作。我想把我们的解决方案写下来,希望能帮助到下一个需要做同样事情的人。
引言
客户希望将提供免费或低价场地的场所与需要场地开展活动的青年组织联系起来。
各个场地在网站上都有个人资料页面,页面上会显示场地的可用日期。需求是场地能够将预订添加到此日历中,系统会自动将这些预订添加到关联的 Google 日历中,反之亦然。
概述

图片来源:https://developers.google.com/identity/protocols/OAuth2
要从场馆关联的 Google 日历中提取任何数据,场馆必须同意该网站访问该日历。
如上图所示,授予访问权限的场所会向网站提供一个授权码,网站随后可使用该授权码向 Google API 兑换访问令牌。此访问令牌只能用于与场所已授权访问的服务进行交互。在本例中,该服务为 Google 日历。
创建项目
第一步是让客户使用他们的 Google 帐户创建一个项目。Google 提供的这份快速入门指南提供了很好的指导:https://developers.google.com/google-apps/calendar/quickstart/php
它还介绍了如何启动并运行演示,但我打算跳过这部分。
项目设置完成后,客户需要设置凭据才能通过此项目访问 Google Calendar API。
添加凭据
创建凭据时会提供一个向导。但它的说明不是很清楚,所以这里附上我们设置过程的截图。注意:我使用了示例内容,而不是客户数据。
第二步是将 URL 列入白名单并设置 OAuth 回调路径。
点击最后一个屏幕上的“下载”按钮,即可获得 client_id.json 文件,该文件是客户端项目通过 API 访问网站的密钥。此文件应存储在服务器上的私密位置。
{
"web":{
"client_id":"[hash-string].apps.googleusercontent.com",
"project_id":"calendar-integration-[project-id]",
"auth_uri":"https://accounts.google.com/o/oauth2/auth",
"token_uri":"https://accounts.google.com/o/oauth2/token",
"auth_provider_x509_cert_url":"https://www.googleapis.com/oauth2/v1/certs",
"client_secret":"[hash-string]",
"redirect_uris":[
"https://www.example.com/oauth2callback"
],
"javascript_origins":[
"https://www.example.com"
]
}
}
需要 Google API 客户端
由于这是一个 Laravel 网站,我们已经配置好了 Composer,所以首先我们需要 Google API 客户端:
composer require google/apiclient:^2.0
这为我们提供了一个 PHP 库,用于与 Google API 通信,以及大量用于每个 API 和 OAuth2 的辅助函数。
更多信息请访问:https://github.com/google/google-api-php-client
授权
请求同意
该网站的第一步是为场地提供一种方法,以便场地可以授权该网站访问其 Google 日历。
为此,我们需要创建一个链接,将场所信息发送到 Google,Google 将显示同意屏幕。
为此,我们将初始化 Google 提供的客户端google/apiclient,并设置我们应用程序的特定设置。
<?php
namespace App\Helpers;
// Initialise the client.
$client = new Google_Client();
// Set the application name, this is included in the User-Agent HTTP header.
$client->setApplicationName('Calendar integration');
// Set the authentication credentials we downloaded from Google.
$client->setAuthConfig('[private-path]/client_id.json');
// Setting offline here means we can pull data from the venue's calendar when they are not actively using the site.
$client->setAccessType("offline");
// This will include any other scopes (Google APIs) previously granted by the venue
$client->setIncludeGrantedScopes(true);
// Set this to force to consent form to display.
$client->setApprovalPrompt('force');
// Add the Google Calendar scope to the request.
$client->addScope(Google_Service_Calendar::CALENDAR);
// Set the redirect URL back to the site to handle the OAuth2 response. This handles both the success and failure journeys.
$client->setRedirectUri(URL::to('/') . '/oauth2callback');
注意:以上代码块用于所有与 Google API 的交互。我们将其放入一个辅助类中,这样就无需在其他地方重复编写。
一旦我们设置好 Google 客户端,我们就可以使用内置createAuthUrl()方法向 Google 返回一个链接,以便场所授予其 Google 日历访问权限。
<?php
// Set state allows us to match the consent to a specific venues
$client->setState($venue->id);
// The Google Client gives us a method for creating the
$client->createAuthUrl();
当场地点击链接时,他们会被重定向到谷歌,并被要求授予网站访问其谷歌日历的权限。
处理回复
Google 会将场地重定向回此处指定的 URL $client->setRedirectUri(URL::to('/') . '/oauth2callback');。无论场地是否同意访问其 Google 日历,都会使用此路径。
<?php
/**
* Google OAuth2 route
*/
Route::get('oauth2callback', [
'as' => 'oauth',
'uses' => 'OAuthController@index'
]);
该路由指示当发出 GET 请求时/oauth2callback,使用控制器index中的方法OAuthController。
有关 Laravel 路由的更多信息,请访问:https://laravel.com/docs/5.5/routing
该方法的具体步骤如下:
<?php
public function index(Request $request)
{
// Get all the request parameters
$input = $request->all();
// Attempt to load the venue from the state we set in $client->setState($venue->id);
$venue = Venue::findOrFail($input['state']);
// If the user cancels the process then they should be send back to
// the venue with a message.
if (isset($input['error']) && $input['error'] == 'access_denied') {
\Session::flash('global-error', 'Authentication was cancelled. Your calendar has not been integrated.');
return redirect()->route('venues.show', ['slug' => $venue->slug]);
} elseif (isset($input['code'])) {
// Else we have an auth code we can use to generate an access token
// This is the helper we added to setup the Google Client with our
// application settings
$gcHelper = new GoogleCalendarHelper($venue);
// This helper method calls fetchAccessTokenWithAuthCode() provided by
// the Google Client and returns the access and refresh tokens or
// throws an exception
$accessToken = $gcHelper->getAccessTokenFromAuthCode($input['code']);
// We store the access and refresh tokens against the venue and set the
// integration to active.
$venue->update([
'gcalendar_credentials' => json_encode($accessToken),
'gcalendar_integration_active' => true,
]);
\Session::flash('global-success', 'Google Calendar integration enabled.');
return redirect()->route('venues.show', ['slug' => $venue->slug]);
}
}
现在我们可以使用从返回的访问令牌访问场地的 Google 日历fetchAccessTokenWithAuthCode()。此方法不仅返回访问令牌,还返回其他一些信息:
{
"access_token":"[hash-string]",
"token_type":"Bearer",
"expires_in":3600,
"created":1510917377,
"refresh_token":"[hash-string]"
}
该返回数据已进行 JSON 编码,以便存储在数据库中。
如上所示,此令牌的有效期仅为一小时expires_in。当访问令牌过期时,我们需要使用该令牌refresh_token请求新的访问令牌。
刷新访问令牌
Google 客户端提供了检查当前访问令牌是否过期的方法。我们将$venue->gcalendar_credentials上述 JSON 值传递给该方法,setAccessToken()如果令牌过期,则刷新令牌。
<?php
// Refresh the token if it's expired.
$client->setAccessToken($venue->gcalendar_credentials);
if ($client->isAccessTokenExpired()) {
$accessToken = $client->fetchAccessTokenWithRefreshToken($client->getRefreshToken());
$venue->update([
'gcalendar_credentials' => json_encode($accessToken),
]);
}
我们将该信息再次保存到数据库中,并与场地关联起来。这部分内容已添加到我们设置的用于初始化 Google 客户端的辅助类中。
从 Google 日历中提取预订信息
现在我们有了有效的访问令牌,我们可以开始轮询场地的谷歌日历以获取活动信息。
<?php
// Load up the helper to initialise the Google Client
$gcHelper = new GoogleCalendarHelper($venue);
// Use the Google Client calendar service. This gives us methods for interacting
// with the Google Calendar API
$service = $gcHelper->getCalendarService();
// Set over what timeframe we want to grab calendar events
// Dates must be an RFC3339 timestamp with mandatory time zone offset, e.g.,
// 2011-06-03T10:00:00-07:00
$optParams = array(
'orderBy' => 'startTime',
'singleEvents' => true,
'timeMin' => '2011-06-03T10:00:00-07:00',
'timeMax' => '2011-06-03T10:00:00-23:00',
);
// Poll this venue's Google Calendar
$googleBookings = $service->events->listEvents($calendarId, $optParams);
// Check if we have any events returned
if (count($googleBookings->getItems()) > 0) {
从 Google 日历中获取活动列表后,我们会将其保存到数据库中,以便在网站上显示为预订信息。
将预订信息推送至 Google 日历
当场地在网站上添加预订时,系统会自动在其 Google 日历中创建一个新的预订。
<?php
// Set the start time and date for pushing to Google.
$tz = new DateTimeZone('Europe/London');
$startTime = Carbon::create(
2017,
11,
25,
10,
0,
0,
$tz
);
// Use the Google date handling provided by the Google Client
$googleStartTime = new Google_Service_Calendar_EventDateTime();
$googleStartTime->setTimeZone($tz);
$googleStartTime->setDateTime($endTime->format('c'));
// Create the calendar event and give a default title.
$event = new Google_Service_Calendar_Event();
$event->setStart($googleStartTime);
// Same process to create end time as we use for the start time above. Code
// omitted for brevity.
$event->setEnd($googleEndTime);
$event->setSummary('Booking automatically created from Calendar Example');
// Use the Google Client calendar service to push
$service = $gcHelper->getCalendarService();
$createdEvent = $service->events->insert($calendarId, $event);
// Save the newly created event id against the booking.
if (!empty($createdEvent->id)) {
如果该$createdEvent预订有 ID,则表示我们已成功将此预订作为新事件推送到 Google 日历。
可以使用此 ID 从 Google 日历中删除事件,如下所示$service->events->delete($calendarId,$eventId);。
陷阱
刷新令牌
根据我们的经验,$client->setApprovalPrompt('force');除了身份验证令牌之外,还需要获取刷新令牌。查阅了大量 Stack Overflow 上的文章后,似乎每次身份验证令牌过期时,系统都会强制用户授权访问其 Google 日历,但我们遇到的情况并非如此。
时区
你会注意到,我们在场地 Google 日历中创建活动时,将时区设置为了美国Europe/London。默认情况下,API 使用的是美国时区之一。这对于我们英国的机构来说并不合适。
创建的事件
即使通过 API 创建了活动,场地仍然需要将其添加到自己的日历中。这与有人邀请你参加活动,你必须先接受邀请,活动才会添加到你的日历中的情况类似。
概括
总而言之,使用 Google API 非常简单直接google/apiclient。OAuth 相关内容是最棘手的部分,这主要是因为 Stack Overflow 和其他网站上关于身份验证/访问/刷新令牌的工作原理存在大量相互矛盾的信息。
谷歌的文档本身对于最佳情况来说是很好的,但是对于出现问题时的信息却完全不存在。
欢迎提出任何意见和建议
文章来源:https://dev.to/tommym9/integrating-google-calendar-with-a-laravel-site-2bd




