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

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 API
  • google_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;
}
?>

现在需要从数据库中根据当前网站用户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);
    }
}
?>

用于引导用户跳转到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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.27 09:38:57