Express JS 集成 Google Sheet:如何实现用户授权访问及数据双向同步
Express JS 实现Google账号授权对接Google Sheet双向同步方案
前置准备
首先完成Google Cloud端的基础配置:
- 访问Google Cloud控制台创建新项目,搜索并启用Google Sheets API
- 进入「API和服务」→「OAuth同意屏幕」页面,配置应用名称、联系方式等必填信息,授权范围添加
https://www.googleapis.com/auth/spreadsheets(可读写Sheet权限),如果仅需要只读权限可以替换为https://www.googleapis.com/auth/spreadsheets.readonly - 进入「凭据」页面,创建「OAuth 2.0客户端ID」,应用类型选择「Web应用」,添加授权重定向URI为你的Express服务的回调地址(例如本地开发可以填
http://localhost:3000/auth/google/callback),创建完成后保存生成的客户端ID和客户端密钥
核心依赖安装
项目需要用到以下npm包:
npm install express express-session passport passport-google-oauth20 googleapis
功能实现步骤
1. 谷歌账号授权流程实现
首先初始化Express服务、Session、Passport授权策略:
const express = require('express'); const session = require('express-session'); const passport = require('passport'); const GoogleStrategy = require('passport-google-oauth20').Strategy; const { google } = require('googleapis'); const app = express(); // 解析post请求体 app.use(express.json()); // Session配置,生产环境建议把session存在数据库而非内存 app.use(session({ secret: '你自定义的session加密密钥', resave: false, saveUninitialized: false, cookie: { secure: process.env.NODE_ENV === 'production' } })); // 初始化Passport app.use(passport.initialize()); app.use(passport.session()); // 配置Google OAuth2授权策略 passport.use(new GoogleStrategy({ clientID: '你之前生成的Google客户端ID', clientSecret: '你之前生成的Google客户端密钥', callbackURL: "http://localhost:3000/auth/google/callback", scope: ['https://www.googleapis.com/auth/spreadsheets', 'profile', 'email'] }, (accessToken, refreshToken, profile, done) => { // 此处需要将accessToken、refreshToken和用户的唯一标识绑定存入你的数据库 // 后续调用Google Sheet接口需要用这两个凭证 const userInfo = { id: profile.id, name: profile.displayName, email: profile.emails[0].value, accessToken, refreshToken }; return done(null, userInfo); } )); // Passport序列化/反序列化用户 passport.serializeUser((user, done) => done(null, user)); passport.deserializeUser((user, done) => done(null, user)); // 触发谷歌授权的接口 app.get('/auth/google', passport.authenticate('google', { accessType: 'offline', // 必须加这个参数才能拿到refreshToken prompt: 'consent' // 强制用户每次授权都选账号,避免自动登录 })); // 授权回调接口 app.get('/auth/google/callback', passport.authenticate('google', { failureRedirect: '/login' }), (req, res) => { // 授权成功后跳转到应用的表格页面 res.redirect('/sheet-page'); } );
注意:只有首次授权时会返回refreshToken,如果后续需要重新获取,需要用户取消对应用的授权后重新发起授权请求
2. 拉取Google Sheet内容到应用
实现接口根据用户传入的表格ID和范围拉取对应数据:
app.get('/api/sheet/data', async (req, res) => { // 校验用户是否已授权 if (!req.user) return res.status(401).json({ error: '用户未授权' }); const { spreadsheetId, range = 'Sheet1!A:Z' } = req.query; // 初始化OAuth2客户端 const oauth2Client = new google.auth.OAuth2(); oauth2Client.setCredentials({ access_token: req.user.accessToken, refresh_token: req.user.refreshToken }); const sheets = google.sheets({ version: 'v4', auth: oauth2Client }); try { const result = await sheets.spreadsheets.values.get({ spreadsheetId, range }); res.json(result.data.values); } catch (err) { // 处理accessToken过期的情况,用refreshToken刷新 if (err.code === 401) { try { const { tokens } = await oauth2Client.refreshAccessToken(); // 更新数据库中对应用户的accessToken req.user.accessToken = tokens.access_token; // 重试拉取数据 const retryResult = await sheets.spreadsheets.values.get({ spreadsheetId, range }); return res.json(retryResult.data.values); } catch (refreshErr) { return res.status(401).json({ error: '授权已过期,请重新登录' }); } } res.status(500).json({ error: err.message }); } });
其中spreadsheetId可以从Google Sheet的URL中获取,URL格式为https://docs.google.com/spreadsheets/d/[spreadsheetId]/edit,中间的字符串就是表格ID。
3. 应用侧修改同步到Google Sheet
实现接口将用户在应用内的修改同步到对应的Google Sheet:
app.post('/api/sheet/update', async (req, res) => { if (!req.user) return res.status(401).json({ error: '用户未授权' }); const { spreadsheetId, range, values } = req.body; const oauth2Client = new google.auth.OAuth2(); oauth2Client.setCredentials({ access_token: req.user.accessToken, refresh_token: req.user.refreshToken }); const sheets = google.sheets({ version: 'v4', auth: oauth2Client }); try { const result = await sheets.spreadsheets.values.update({ spreadsheetId, range, valueInputOption: 'RAW', // 直接存储原始值,不需要Google解析公式等 resource: { values } }); res.json({ success: true, updatedCells: result.data.updatedCells }); } catch (err) { if (err.code === 401) { try { const { tokens } = await oauth2Client.refreshAccessToken(); req.user.accessToken = tokens.access_token; const retryResult = await sheets.spreadsheets.values.update({ spreadsheetId, range, valueInputOption: 'RAW', resource: { values } }); return res.json({ success: true, updatedCells: retryResult.data.updatedCells }); } catch (refreshErr) { return res.status(401).json({ error: '授权已过期,请重新登录' }); } } res.status(500).json({ error: err.message }); } });
4. 实时同步优化
如果需要做到用户修改后实时同步,可以在前端给表格的修改事件加300-500ms的防抖,避免每次修改都发起请求,减少接口调用频次。如果需要实现Google Sheet侧的修改也同步到你的应用,可以使用Google Sheets API的推送通知功能,配置Webhook接收变更事件,更新你侧的缓存数据。
参考文档
- Google Sheets API 官方开发文档,包含所有接口的参数说明、错误码处理方案
- Google OAuth 2.0 Web服务端授权官方文档,详细讲解授权流程的规则和注意事项
- Passport Google OAuth2 策略官方使用文档
内容的提问来源于stack exchange,提问作者NOOBIEBOY97
相关产品推荐
相关产品推荐

