精简指南:如何使用 Laravel 同步 Google 日历


同步的概念

要正确同步您的资源,了解 Google API 的原理很重要。这些原则对所有Google资源都适用,但对Outlook会有所不同我们将弄清楚如何以及为什么使用查询参数并研究最佳实践。

为了优化性能,API 将重要参数用作syncTokenpageToken

大多数情况下,API 数据会分页返回,以免给网络造成负担,并在网络及其缓存上分配资源。

资源分页——pageToken

当响应中有多个页面时,可以看到该nextPageToken字段,该字段存储接收到的关于下一页的数据。

不要忘记保存该nextPageToken字段,以防您在同步其中一个页面时出错并且不想检索成功保存的资源,而只是从某个页面开始。

当要获取下一页数据时,必须指定pageToken值为nextPageToken您不需要发送额外的参数,因为令牌已经具备了一切。

一个例子看起来像这样:

1. GET /calendars/primary/events

// Response
"items": [...]
"nextPageToken":"CiAKGjBpNDd2Nmp2Zml2cXRwYjBpOXA"

以下查询从 中获取值nextPageToken并将其作为 的值发送pageToken

2. GET /calendars/primary/events?pageToken=CiAKGjBpNDd2Nmp2Zml2cXRwYjBpOXA

您可以通过参数控制响应中显示资源的数量maxResults

同步标记 – nextSyncToken

在第一次同步期间,将对集合中要同步的每个资源执行初始查询。

同步令牌表示为nextSyncToken在列表操作响应中命名的字段。

nextSyncToken是优化资源同步、节省带宽的重要领域。它允许您仅检索令牌首次发布时的新数据。

不要忘记保存它nextPageToken以从上次收到的页面中检索资源项目。

例如,如果您在日历中创建了一个新事件,则无需检索整个事件列表并检查和处理每个事件,而只需获取更新后的数据。

一个例子看起来像这样:

1. GET https://www.googleapis.com/calendar/v3/users/me/calendarList

// Response 
...
"items": [...]
"nextSyncToken": "CPDAlvWDx70CEPDAlvWDx70CGAU=",

nextSyncToken字段只会出现在最后一页的响应中,因为所有请求都是逐页给出的,并且会包含 nextPageToken 参数。

以下查询从 中获取值nextSyncToken并将其作为 的值发送syncToken

2. GET https://www.googleapis.com/calendar/v3/users/me/calendarList?syncToken=CPDAlvWDx70CEPDAlvWDx70CGAU=

// Response 
...
"items": [...]
"nextSyncToken": "v7GC9pHgvO6kpTHAxRx71KebukwS=",

在您syncToken不再有效的情况下,您应该将其从数据库中删除并重新请求整个资源集合。

同步谷歌日历

在上一篇文章中,我们通过oauth2 设置授权,之后将Google Account 数据写入数据库。

授权成功后,您应该从帐户中获取用户的可用日历列表。

public function callback(string $driver): RedirectResponse
{
    /** @var ProviderInterface $provider */
    $provider = $this->manager->driver($driver);

    /** @var Account $account */
    $account = $provider->callback();

    $accountId = app(AccountService::class)->createFrom($account, $driver);

    $account->setId($accountId);

    // Sync calendars of user account
    $provider->synchronize('Calendar', $account);

    return redirect()->to(
      config('services.' . $driver . '.redirect_callback', '/')
    );
}

对于日历记录及其基本信息,让我们在数据库中设计一个表,如下所示:

Schema::create('calendars', function (Blueprint $table) {
    $table->id();
    $table->string('summary')->nullable();
    $table->string('timezone')->nullable();
    $table->string('provider_id');
    $table->string('provider_type');
    $table->text('description')->nullable();
    $table->text('page_token')->nullable();
    $table->text('sync_token')->nullable();
    $table->timestamp('last_sync_at')->nullable();
    $table->boolean('selected')->default(false);
    $table->unsignedBigInteger('account_id');
    $table->foreign('account_id')->references('id')->on('calendar_accounts')->onDelete('CASCADE');
    $table->index(['provider_id', 'provider_type']);
    $table->timestamps();
});

该表将存储有关日历的信息、用于事件同步和分页的令牌以及指向他的帐户的链接。

要执行同步,请向我们的日历驱动程序添加一些逻辑 – GoogleProvider.php

public function synchronize(string $resource, Account $account, array $options = [])
{
    $resource = Str::ucfirst($resource);

    $method = 'synchronize' . Str::plural($resource);

    $synchronizer = $this->getSynchronizer();

    if (method_exists($synchronizer, $method) === false) {
        throw new /InvalidArgumentException('Method is not allowed.', 400);
    }

    return call_user_func([$synchronizer, $method], $account, $options);
}

getSynchronizer()函数将向我们返回同步器类,它将调解资源。其中有方法:synchronizeCalendars()

public function synchronizeCalendars(Account $account, array $options = [])
{
    $token = $account->getToken();
    $accountId = $account->getId();
    $syncToken = $account->getSyncToken();

    if ($token->isExpired()) {
        return false;
    }

    $query = array_merge([
        'maxResults' => 100,
        'minAccessRole' => 'owner',
    ], $options['query'] ?? []);

    if (isset($syncToken)) {
        $query = [
            'syncToken' => $syncToken,
        ];
    }

    $body = $this->call('GET', "/calendar/{$this->provider->getVersion()}/users/me/calendarList", [
        'headers' => ['Authorization' => 'Bearer ' . $token->getAccessToken()],
        'query' => $query
    ]);

    $nextSyncToken = $body['nextSyncToken'];
    $calendarIterator = new /ArrayIterator($body['items']);

    /** @var CalendarRepository $calendarRepository */
    $calendarRepository = app(CalendarRepository::class);

    // Check user calendars
    $providersIds = $calendarRepository
        ->setColumns(['provider_id'])
        ->getByAttributes(['account_id' => $accountId, 'provider_type' => $this->provider->getProviderName()])
        ->pluck('provider_id');

    $now = now();

    while ($calendarIterator->valid()) {
        $calendar = $calendarIterator->current();
        $calendarId = $calendar['id'];

        // Delete account calendar by ID
        if (key_exists('deleted', $calendar) && $calendar['deleted'] === true && $providersIds->contains($calendarId)) {
            $calendarRepository->deleteWhere([
                'provider_id' => $calendarId,
                'provider_type' => $this->provider->getProviderName(),
                'account_id' => $accountId,
            ]);

        // Update account calendar by ID
        } else if ($providersIds->contains($calendarId)) {
            $calendarRepository->updateByAttributes(
                [
                    'provider_id' => $calendarId,
                    'provider_type' => $this->provider->getProviderName(),
                    'account_id' => $accountId,
                ],
                [
                    'summary' => $calendar['summary'],
                    'timezone' => $calendar['timeZone'],
                    'description' => $calendar['description'] ?? null,
                    'updated_at' => $now,
                ]
            );
        // Create account calendar
        } else {
            $calendarRepository->insert([
                'provider_id' => $calendarId,
                'provider_type' => $this->provider->getProviderName(),
                'account_id' => $accountId,
                'summary' => $calendar['summary'],
                'timezone' => $calendar['timeZone'],
                'description' => $calendar['description'] ?? null,
                'selected' => $calendar['selected'] ?? false,
                'created_at' => $now,
                'updated_at' => $now,
            ]);
        }

        $calendarIterator->next();
    }

    $this->getAccountRepository()->updateByAttributes(
        ['id' => $accountId],
        ['sync_token' => Crypt::encryptString($nextSyncToken), 'updated_at' => $now]
    );
}

上面的代码获取拥有所有者访问权限的用户日历列表。在检查每个日历在数据库中的一致性并采取操作以删除、更新或创建之后

本站声明:
1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;

2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;

3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;

4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;

5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

原创文章,作者:ItWorker,如若转载,请注明出处:https://blog.ytso.com/tech/pnotes/295264.html

(0)
上一篇 2023年2月10日 11:41
下一篇 2023年2月10日 11:41

相关推荐

发表回复

登录后才能评论