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自然会拒绝请求。需要补充授权逻辑:
- 首先开启离线访问权限,这样能刷新令牌,避免每次运行都重新授权:
$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())); }
二、修复Gmail IMAP登录问题
现在谷歌已经彻底禁用了普通密码登录IMAP(哪怕关闭2FA也不行),你得二选一:
方式1:使用App Password(推荐,适合开启2FA的账号)
- 确保你的谷歌账号开启了两步验证
- 登录谷歌账号的安全设置页面,找到「App Passwords」选项
- 创建一个专门用于IMAP的App Password,把这个密码替换代码里的
$password值
方式2:改用OAuth2登录IMAP(更安全,适合长期项目)
如果不想用App Password,可以通过OAuth2授权登录IMAP,但实现复杂度稍高,先推荐用App Password快速解决当前问题。
三、其他优化细节(避免后续踩坑)
- 附件编码处理:你的代码默认所有附件都是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 }
- 文件名安全过滤:直接用发件人名称当文件名可能包含特殊字符,导致Drive上传失败,建议过滤:
$filename = $formatted_date_sent . ' ' . preg_replace('/[^\w\s\-.]/u', '', $sender_name . '_' . $original_filename);
- 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
相关产品推荐
相关产品推荐

