如何将Gmail API授权码传递至Heroku上的Node.js Discord Bot进程
你的问题核心在于Heroku的部署环境不支持交互式的stdin输入,而且它的文件系统是临时的——即使你成功输入授权码保存了token,dyno重启后文件也会丢失。下面给你几个可行的解决方案,按推荐程度排序:
方案1:本地预生成Token,用Heroku环境变量存储(最简便)
这是最适合你的场景的方法,因为你只需要一次授权,之后靠refresh token自动续期:
本地生成有效Token
在本地运行你的Bot,完成授权流程,生成token.json文件(对应你代码里的TOKEN_PATH)。这个文件里包含了access_token和refresh_token,后者可以长期用来刷新令牌。将Token存入Heroku环境变量
- 打开Heroku控制台,找到你的应用,进入「Settings」→「Config Vars」
- 添加一个新的环境变量,比如命名为
GMAIL_TOKEN,值就是token.json里的完整JSON字符串(直接复制文件里的内容即可)
修改代码读取环境变量
替换原来读取本地文件和保存文件的逻辑,因为Heroku的临时文件系统无法持久化保存token:function authorize(credentials, callback) { const {client_secret, client_id, redirect_uris} = credentials.installed; const oAuth2Client = new google.auth.OAuth2( client_id, client_secret, redirect_uris[0]); // 从环境变量读取token const token = JSON.parse(process.env.GMAIL_TOKEN); oAuth2Client.setCredentials(token); // googleapis库会自动处理token刷新,无需手动保存 oAuth2Client.on('tokens', (tokens) => { // 除非refresh_token更新(极少发生),否则无需额外操作 }); callback(oAuth2Client); } // 移除原来的Fs.writeFile逻辑,环境变量已实现持久化存储这样你的Bot启动时会直接从环境变量加载token,不需要再交互式输入授权码。
方案2:使用服务账号认证(适合G Suite/Workspace用户)
如果你的Bot需要访问的是你自己的Gmail账号,且你使用的是Google Workspace(原G Suite),可以用服务账号跳过授权码流程:
创建服务账号
在Google Cloud Console中,找到你的项目→「IAM与管理」→「服务账号」,创建一个新的服务账号,下载JSON密钥文件。授权服务账号访问你的邮箱
在Workspace管理控制台中,给这个服务账号授权「Gmail API权限」,并允许它访问你的个人邮箱(需要管理员权限)。将密钥存入Heroku环境变量
把服务账号JSON文件的内容作为环境变量GMAIL_SERVICE_ACCOUNT_KEY添加到Heroku。修改代码使用服务账号认证
const { google } = require('googleapis'); const serviceAccount = JSON.parse(process.env.GMAIL_SERVICE_ACCOUNT_KEY); const auth = new google.auth.JWT( serviceAccount.client_email, null, serviceAccount.private_key, ['https://www.googleapis.com/auth/gmail.readonly'], // 替换成你需要的权限 'your-email@example.com' // 你要访问的邮箱地址 ); // 初始化Gmail客户端 const gmail = google.gmail({ version: 'v1', auth }); // 之后直接用gmail对象调用API即可注意:个人Gmail账号无法使用服务账号直接访问,必须是Workspace账号才行。
方案3:临时启动HTTP服务器接收授权码(适合一次性授权)
如果不想用环境变量,也可以临时启动一个简单的HTTP服务器,让Google把授权码回调到Heroku的URL:
修改授权URL的回调地址
在Google Cloud Console中,把OAuth2的回调URL改成你的Heroku应用URL,比如https://your-heroku-app.herokuapp.com/auth/callback。添加HTTP服务器代码
用Express快速搭建一个临时服务器:const express = require('express'); const app = express(); const PORT = process.env.PORT || 3000; // 处理授权回调 app.get('/auth/callback', (req, res) => { const code = req.query.code; // 用这个code获取token,和你原来的getNewToken逻辑一致 oAuth2Client.getToken(code, (err, token) => { if (err) { res.send('Error getting token'); return console.error(err); } // 把token打印到控制台,之后你可以复制出来存到环境变量 console.log('Token:', JSON.stringify(token)); res.send('Authorization successful! You can close this page now.'); // 完成授权后关闭服务器,继续运行Bot process.exit(0); }); }); app.listen(PORT, () => { console.log('Server running on port', PORT); const authUrl = oAuth2Client.generateAuthUrl({ access_type: "offline", scope: SCOPES, }); console.log('Authorize via:', authUrl); });部署到Heroku后,打开控制台查看日志里的授权URL,访问它完成授权,控制台会输出token,之后你把这个token存到环境变量,再改用方案1的方式加载即可。
为什么之前的方法失败?
- Heroku的dyno进程启动后,stdin并没有和你的CLI会话关联,所以你通过Heroku CLI或Dynamo输入的内容无法被
readline捕获。 - Heroku的文件系统是临时的,即使你成功保存了token文件,dyno重启后文件会被清空,所以必须用持久化的存储方式(比如环境变量)。
内容的提问来源于stack exchange,提问作者Gh05d




