PHP+MySQL网站集成Google Calendar API OAuth实现用户日历同步
解决网站用户与Google账户绑定及日历数据同步的问题
这个问题的核心在于把网站用户的身份和Google OAuth凭证持久化绑定,而不是依赖临时的Session存储。你现在的代码用Session存access token,只能在当前会话有效,用户下次登录网站就找不到之前关联的Google账户了,而且也没法区分不同网站用户的Google数据。下面是具体的实现步骤:
1. 调整数据库结构
首先需要在你的网站用户表中添加字段,用来存储每个用户对应的Google OAuth凭证信息。假设你的用户表名为users,执行以下SQL修改表结构:
ALTER TABLE users ADD COLUMN google_email VARCHAR(255) UNIQUE NULL, ADD COLUMN google_access_token TEXT NULL, ADD COLUMN google_refresh_token TEXT NULL, ADD COLUMN google_token_expiry DATETIME NULL;
各字段作用说明:
google_email:存储用户关联的Google账户邮箱,用来唯一标识该Google账户,也方便后续验证google_access_token:Google返回的访问令牌(有效期约1小时),用来调用Calendar APIgoogle_refresh_token:刷新令牌(长期有效,除非用户撤销授权),用来在access token过期时获取新的令牌google_token_expiry:access token的过期时间,用来判断是否需要刷新令牌
2. 修改OAuth授权流程(更新oauth.php)
现在需要在用户授权Google后,将获取到的凭证与当前登录的网站用户ID绑定,存入数据库。首先确保用户已登录你的网站(Session中存在user_id):
<?php require_once __DIR__.'/vendor/autoload.php'; session_start(); // 先检查用户是否登录网站,未登录则跳转至登录页 if (!isset($_SESSION['user_id'])) { header('Location: /login.php'); exit; } $client = new Google_Client(); $client->setAuthConfig('client_secrets.json'); // 新版Google Client使用setAuthConfig,替代旧版setAuthConfigFile $client->setRedirectUri('http://' . $_SERVER['HTTP_HOST'] . '/oauth.php'); // 添加获取用户邮箱的权限,用于绑定身份 $client->addScope([ Google_Service_Calendar::CALENDAR_READONLY, Google_Service_Oauth2::USERINFO_EMAIL ]); if (!isset($_GET['code'])) { $auth_url = $client->createAuthUrl(); header('Location: ' . filter_var($auth_url, FILTER_SANITIZE_URL)); } else { $client->authenticate($_GET['code']); $token_data = $client->getAccessToken(); // 获取用户的Google邮箱信息 $oauth_service = new Google_Service_Oauth2($client); $google_user = $oauth_service->userinfo->get(); $google_email = $google_user->getEmail(); // 连接MySQL数据库(这里用PDO示例,可替换为你常用的连接方式) $pdo = new PDO('mysql:host=localhost;dbname=你的数据库名', '用户名', '密码'); $pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); // 将Google凭证与网站用户ID绑定,存入数据库 $stmt = $pdo->prepare(" UPDATE users SET google_email = ?, google_access_token = ?, google_refresh_token = ?, google_token_expiry = ? WHERE id = ? "); // 转换token过期时间为数据库可存储的DateTime格式 $expiry_date = date('Y-m-d H:i:s', $token_data['expires_in'] + time()); $stmt->execute([ $google_email, json_encode($token_data), // 将token数组转为字符串存储 $token_data['refresh_token'] ?? null, // 首次授权才返回refresh token,后续刷新可能无此值 $expiry_date, $_SESSION['user_id'] ]); // 跳转回用户的个人日历页面 header('Location: /user-calendar.php'); exit; } ?>
3. 优化日历事件拉取逻辑(更新link-google-account.php)
现在需要从数据库中根据当前网站用户ID取出对应的Google凭证,而非依赖Session。如果access token过期,自动用refresh token刷新并更新数据库:
<?php require_once __DIR__.'/vendor/autoload.php'; session_start(); // 检查用户是否登录网站 if (!isset($_SESSION['user_id'])) { header('Location: /login.php'); exit; } $user_id = $_SESSION['user_id']; $client = new Google_Client(); $client->setAuthConfig('client_secrets.json'); $client->addScope(Google_Service_Calendar::CALENDAR_READONLY); // 从数据库获取用户的Google凭证 $pdo = new PDO('mysql:host=localhost;dbname=你的数据库名', '用户名', '密码'); $pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); $stmt = $pdo->prepare("SELECT google_access_token, google_refresh_token, google_token_expiry FROM users WHERE id = ?"); $stmt->execute([$user_id]); $google_data = $stmt->fetch(PDO::FETCH_ASSOC); if (!$google_data || !$google_data['google_access_token']) { // 用户未关联Google账户,引导至关联页面 echo "请先关联你的Google账户:<a href='/link-google-init.php'>关联Google账户</a>"; exit; } // 解析存储的token数据 $token_data = json_decode($google_data['google_access_token'], true); $client->setAccessToken($token_data); // 检查access token是否过期,过期则用refresh token刷新 if ($client->isAccessTokenExpired()) { if ($google_data['google_refresh_token']) { $client->fetchAccessTokenWithRefreshToken($google_data['google_refresh_token']); $new_token_data = $client->getAccessToken(); // 更新数据库中的新access token和过期时间 $expiry_date = date('Y-m-d H:i:s', $new_token_data['expires_in'] + time()); $stmt = $pdo->prepare(" UPDATE users SET google_access_token = ?, google_token_expiry = ? WHERE id = ? "); $stmt->execute([ json_encode($new_token_data), $expiry_date, $user_id ]); } else { // 无refresh token,需用户重新授权 echo "你的Google授权已过期,请重新关联:<a href='/link-google-init.php'>重新关联</a>"; exit; } } // 拉取用户的Google日历事件 $service = new Google_Service_Calendar($client); $calendarId = 'primary'; $optParams = array( 'maxResults' => 10, 'orderBy' => 'startTime', 'singleEvents' => TRUE, 'timeMin' => date('c'), ); $results = $service->events->listEvents($calendarId, $optParams); if (count($results->getItems()) == 0) { print "No upcoming events found.<br>"; } else { print "Upcoming events:<br>"; foreach ($results->getItems() as $event) { $start = $event->start->dateTime; if (empty($start)) { $start = $event->start->date; } printf("%s (%s)<br>", $event->getSummary(), $start); } } ?>
4. 添加关联初始化页面(link-google-init.php)
用于引导用户跳转到Google授权页面,确保获取到refresh token:
<?php require_once __DIR__.'/vendor/autoload.php'; session_start(); if (!isset($_SESSION['user_id'])) { header('Location: /login.php'); exit; } $client = new Google_Client(); $client->setAuthConfig('client_secrets.json'); $client->setRedirectUri('http://' . $_SERVER['HTTP_HOST'] . '/oauth.php'); $client->addScope([ Google_Service_Calendar::CALENDAR_READONLY, Google_Service_Oauth2::USERINFO_EMAIL ]); // 强制获取离线访问权限(确保拿到refresh token) $client->setAccessType('offline'); $client->setPrompt('select_account consent'); $auth_url = $client->createAuthUrl(); header('Location: ' . filter_var($auth_url, FILTER_SANITIZE_URL)); ?>
关键注意事项
- 安全防护:Google凭证属于敏感信息,存储到数据库时建议用AES等加密方式加密,不要明文存储
- Refresh Token有效性:如果用户在Google账户中撤销了你的应用授权,refresh token会失效,此时需要引导用户重新授权
- 用户状态验证:每次操作前务必验证网站用户的登录状态(
$_SESSION['user_id']的有效性),避免数据混淆 - 错误处理:代码中需添加完善的错误处理(如数据库连接失败、API调用异常等),提升用户体验
内容的提问来源于stack exchange,提问作者johnniexo88
相关产品推荐
相关产品推荐

