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

Google Cloud脚本报错:保存Gmail发票附件并删除时提示Login Required

解决Google Drive授权401错误及Gmail登录问题

看起来你踩了两个谷歌服务的常见坑:Google Drive的OAuth2授权流程不完整导致401登录失败,还有Gmail IMAP登录方式不符合当前谷歌安全政策。我来一步步帮你理顺修复思路:

一、先搞定Google Drive的401 "Login Required"错误

你的代码里直接调用$client->authorize()但跳过了完整的OAuth2授权流程——用户还没完成授权并生成有效令牌,Google API自然会拒绝请求。需要补充授权逻辑:

  1. 首先开启离线访问权限,这样能刷新令牌,避免每次运行都重新授权:
$client->setAccessType('offline');
$client->setPrompt('select_account consent');
  1. 添加令牌的保存与刷新逻辑:检查本地是否有已保存的令牌,没有就跳转到授权页面;令牌过期则自动刷新。

修改后的授权部分代码:

// 令牌保存路径(要确保这个文件所在目录可写)
$tokenPath = __DIR__ . '/token.json';

// 加载已保存的令牌
if (file_exists($tokenPath)) {
    $accessToken = json_decode(file_get_contents($tokenPath), true);
    $client->setAccessToken($accessToken);
}

// 处理令牌过期情况
if ($client->isAccessTokenExpired()) {
    // 有刷新令牌的话自动刷新
    if ($client->getRefreshToken()) {
        $client->fetchAccessTokenWithRefreshToken($client->getRefreshToken());
    } else {
        // 没有刷新令牌,跳转到谷歌授权页面
        $authUrl = $client->createAuthUrl();
        header("Location: $authUrl");
        exit;
    }
    // 保存更新后的令牌
    file_put_contents($tokenPath, json_encode($client->getAccessToken()));
}

二、修复Gmail IMAP登录问题

现在谷歌已经彻底禁用了普通密码登录IMAP(哪怕关闭2FA也不行),你得二选一:

方式1:使用App Password(推荐,适合开启2FA的账号)

  1. 确保你的谷歌账号开启了两步验证
  2. 登录谷歌账号的安全设置页面,找到「App Passwords」选项
  3. 创建一个专门用于IMAP的App Password,把这个密码替换代码里的$password值

方式2:改用OAuth2登录IMAP(更安全,适合长期项目)

如果不想用App Password,可以通过OAuth2授权登录IMAP,但实现复杂度稍高,先推荐用App Password快速解决当前问题。

三、其他优化细节(避免后续踩坑)

  1. 附件编码处理:你的代码默认所有附件都是base64编码,但实际邮件附件可能有多种编码,需要根据类型解码:
switch ($attachments->parts[$i]->encoding) {
    case 3: $data = base64_decode($attachment_data); break; // base64
    case 4: $data = quoted_printable_decode($attachment_data); break; // quoted-printable
    case 2: $data = imap_binary($attachment_data); break; // binary
}
  1. 文件名安全过滤:直接用发件人名称当文件名可能包含特殊字符,导致Drive上传失败,建议过滤:
$filename = $formatted_date_sent . ' ' . preg_replace('/[^\w\s\-.]/u', '', $sender_name . '_' . $original_filename);
  1. IMAP搜索优化:原来用ALL会遍历所有邮件,改成搜索带附件的邮件能提升效率:
$emails = imap_search($inbox, 'HASATTACH');

修改后的完整代码示例

<?php
error_reporting(E_ALL);
ini_set('display_errors', 1);

require_once __DIR__ . '/vendor/autoload.php';

// Google OAuth2配置
$client_id = '123456789012-abcdefghijklmnopqrstuvwxyz.apps.googleusercontent.com';
$client_secret = 'ABC-XYZ_123';
$redirect_uri = 'https://example.com/invoices';

$client = new Google_Client();
$client->setClientId($client_id);
$client->setClientSecret($client_secret);
$client->setRedirectUri($redirect_uri);
$client->addScope(Google_Service_Drive::DRIVE);
$client->setAccessType('offline');
$client->setPrompt('select_account consent');

// 处理令牌逻辑
$tokenPath = __DIR__ . '/token.json';
if (file_exists($tokenPath)) {
    $accessToken = json_decode(file_get_contents($tokenPath), true);
    $client->setAccessToken($accessToken);
}

if ($client->isAccessTokenExpired()) {
    if ($client->getRefreshToken()) {
        $client->fetchAccessTokenWithRefreshToken($client->getRefreshToken());
    } else {
        $authUrl = $client->createAuthUrl();
        header("Location: $authUrl");
        exit;
    }
    file_put_contents($tokenPath, json_encode($client->getAccessToken()));
}

$driveService = new Google_Service_Drive($client);

// Gmail IMAP配置(替换为你的App Password)
$hostname = '{imap.gmail.com:993/imap/ssl/novalidate-cert}INBOX';
$username = 'email@gmail.com';
$password = '你的App Password';

$inbox = imap_open($hostname, $username, $password) or die('Cannot connect to Gmail: ' . imap_last_error());

// 仅搜索带附件的邮件
$emails = imap_search($inbox, 'HASATTACH');

if ($emails) {
    rsort($emails);
    foreach ($emails as $email_number) {
        $header = imap_fetchheader($inbox, $email_number);
        $header_obj = imap_rfc822_parse_headers($header);
        
        $sender_name = isset($header_obj->from[0]->personal) ? $header_obj->from[0]->personal : 'Unknown Sender';
        $date_sent = strtotime($header_obj->date);
        $formatted_date_sent = date("Y-m-d", $date_sent);

        $attachments = imap_fetchstructure($inbox, $email_number);
        
        if (isset($attachments->parts) && count($attachments->parts)) {
            for ($i = 0; $i < count($attachments->parts); $i++) {
                $part = $attachments->parts[$i];
                // 仅处理标记为附件的部分
                if (isset($part->disposition) && strtolower($part->disposition) === 'attachment') {
                    // 获取原始附件文件名
                    $original_filename = isset($part->dparameters[0]->value) ? $part->dparameters[0]->value : 'unknown.pdf';
                    // 安全格式化文件名
                    $filename = $formatted_date_sent . ' ' . preg_replace('/[^\w\s\-.]/u', '', $sender_name . '_' . $original_filename);
                    
                    $attachment_data = imap_fetchbody($inbox, $email_number, $i+1);
                    // 根据编码解码附件
                    switch ($part->encoding) {
                        case 3: $attachment_data = base64_decode($attachment_data); break;
                        case 4: $attachment_data = quoted_printable_decode($attachment_data); break;
                        case 2: $attachment_data = imap_binary($attachment_data); break;
                    }

                    // 上传到Google Drive
                    $file = new Google_Service_Drive_DriveFile();
                    $file->setName($filename);
                    $file->setDescription('Invoice attachment');
                    $file->setMimeType($part->type . '/' . $part->subtype); // 使用附件实际MIME类型
                    $file->setParents(array('1nkfPyEvFYR_DGUGJDiy8WLx_ANUNsAdW'));
                    
                    $createdFile = $driveService->files->create($file, array(
                        'data' => $attachment_data,
                        'mimeType' => $part->type . '/' . $part->subtype,
                        'uploadType' => 'multipart'
                    ));
                }
            }
        }
        // 标记邮件为删除
        imap_delete($inbox, $email_number);
    }
    // 永久删除标记的邮件
    imap_expunge($inbox);
}

imap_close($inbox);
?>

最后一步:完成授权流程

第一次运行脚本时,会自动跳转到谷歌授权页面,登录你的账号并授予权限,授权成功后会生成token.json文件,后续运行就无需重复授权了。

内容的提问来源于stack exchange,提问作者imbehindyou

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.04 17:50:23