使用Service Account读取谷歌表格

1,763 阅读4分钟

前言

随着谷歌表格API升级到第四个版本以后,虽然提供了更快的表格数据读取API,但是还有就是需要授权才能读取到数据,即使是发布到网络的表格。所以我们在做这一升级的时候,尝试了很多的方法,也走过了很多的坑,下面我就简单总结一下。

方案

首先我找到了读取表格数据的API 接口:

developers.google.com/sheets/api/…

该接口需要的参数如下(必传的参数):

{
    "spreadsheetId": "",
    "range": "",
    "access_token": ""
}

使用过的朋友都知道,spreadsheetId和range是表格的id和表格的tab范围。但是access_token并不是一个固定的东西,所以我研究了google获取access_token的两种方式(都是基于OAuth 2.0的):

  1. 使用用户自己的账号,简单来讲就是通知用户授权,然后用户确认后可以拿到用户的access_token读取该用户拥有读取权限的表格数据。
  2. 使用服务账号,将表格共享给这个谷歌账号,然后使用服务器登录服务账号,获取服务账号的access_token读取表格数据。

刚开始的时候我使用了第一种方式,但是出现了下面的缺点:

  1. 需要写死一个账号,每次都是用这个账号进行授权。
  2. access_token 的有效期很短(一般是两个小时),隔一段时间需要手动授权,需要人工的干预。
  3. 要是哪天我的账号被删除了就需要重新设置新的账号,并生成新的项目配置。

后来看到谷歌在服务器授权的时候,建议使用服务账号(虽然API KEY也可以),就实现了服务端无用户参与的授权。

实现

一、创建一个项目

在开发者控制台创建一个项目

link:console.developers.google.com/projectcrea…

填写完必要的信息后,直接点击创建即可(注意项目的id不能修改)。

二、开通Google Sheet API服务

在创建完账号以后就会跳转到该项目的dashboard,如下面的视图,直接在搜索框搜索Google Sheet API,如果不开启创建的服务账号是没有权限读取表格数据的。

在API页面直接点击启用即可开启该项目的Sheet API功能。

三、生成一个服务账号

开启Sheet API以后会跳转到Sheet API的配置页面,我们现在为这个项目创建一个服务账号。

首先按照标记点击"凭据",然后点击"API与服务中的凭据"来创建服务账号。

我们在这里选择创建"服务账号密钥";

四、生成凭据

点击上面的"服务账号密钥"跳转到了密钥创建页面,我们选择新的服务账号,根据自己的要求进行填写,如下所示。

这里我选择了JSON格式,方便Node.js(本次以Node.js作为实践)读取;

点击创建以后我们会得到一个json文件,注意改json文件一定要保存好了,后面的授权全靠它了。

接下来就开始编码实现读取的功能了;

五、开始编码

下面是Node.js作为实现,首先需要安装googleapis,下面代码是授权部分。

npm install googleapis --save

const {google} = require('googleapis');

// 后面的json文件就是我们在上面第四步下载的json文件。 
const {client_email: email, private_key: key} = require('./service-account.json');

// 指定权限,这里仅需要读取权限就可以了
const scopes = ['https://www.googleapis.com/auth/spreadsheets.readonly'];

// 全局存储授权后的token信息,后面也可以存储到redis等缓存中,现在为了测试,我就存储在本地的变量中
let token = null;

module.exports = {
    authorize() {
        return new Promise((resolve) => {
            // 首先判断有没有token文件
            if (token) {
                const {access_token, expiry_date} = token;
                // 看看有没有过期 + 1分钟
                if (expiry_date > (new Date().getTime() + 60 * 1000)) {
                    return resolve(access_token);
                }
            }
            
            // 重新授权
            let jwtClient = new google.auth.JWT(email, null, key, scopes);
            // 直接可以获取到access_token,很方便,不需要人工干预
            jwtClient.authorize().then(data => {
                // 保存token到全局变量
                token = data;
                // 返回token
                resolve(data.access_token)
            }).catch(() => {
                resolve('');
            });
        });
    }
};

然后我们通过上面的authorize可以取到token信息,最后使用ajax客户端来调用表格的接口,即可实现数据的读取。

下面是读取表格数据的实现:

module.exports = async function getSheetData({spreadsheetId, worksheets}) {
    const axios = require('axios');
    const qs = require('qs');
    const auth = require('../auth/jwtclient');
    const serviceEmailAddr = require('../auth/service-account').client_email;
    const token = await auth.authorize();
    const url = `https://sheets.googleapis.com/v4/spreadsheets/${spreadsheetId}/values:batchGet`;
    return await axios
        .get(url, {
            params: {access_token: token, ranges: worksheets},
            paramsSerializer(params) {
                return qs.stringify(params, {arrayFormat: 'repeat', encodeValuesOnly: true});
            }
        })
        .then(resp => resp.data.valueRanges)
        .catch(error => {
            if (error.response.status === 404) {
                return Promise.reject('表格不存在,请检查表格id是否正确');
            }
            if (error.response.status === 403) {
                return Promise.reject('没有权限访问,请将表格分享给:' + serviceEmailAddr);
            }
            if (error.response.status === 400) {
                return Promise.reject(error.response.data.error.message.replace('Unable to parse range', '请检查后面表格是否存在'));
            }
            Promise.reject(error.message);
        });
};

为了保证隐私,我没有上传项目配置的json文件;

参考实现:github.com/gslnzfq/dev…

六、测试

如果读取数据的时候,google返回了403,那可能是因为你没有将表格共享给服务账号,查看服务账号:

console.developers.google.com/iam-admin/s…

在上面选择你的项目,就能看到右边的电子邮件,将谷歌表格共享给改邮箱就可以访问了。

参考文档