我在Google Sheets API中发现了奇怪的行为(是问题吗?):
Firebase函数日志未显示任何错误,但是values#batchGet
函数忽略范围。它返回第一个范围内的整个工作表dataRange
。
我根据代码Github: Sheets API batchGet ranges parameter issue编辑了代码
sheets.spreadsheets.values.batchGet({
// your options
}, function (err, data, response) {
console.log(response.req.res.request.url);
});
Node.js代码部分:
const sheets = google.sheets({ version: 'v4', access_token });
// set auth as a global default:
google.options({ auth: jwt });
const request = {
auth: jwt,
spreadsheetId: 'xxxxx', //<---------- "Project Checklist" and "Control" sheets
ranges: [
"'Project Checklist'!B:K",
"Control!A:F"
],
majorDimension: "ROWS",
dateTimeRenderOption: "FORMATTED_STRING",
valueRenderOption: "FORMATTED_VALUE"
}
这包裹在一个Promise中...
sheets.spreadsheets.values.batchGet(request, (err, data, response) => {
console.log("inside: sheets.spreadsheets.values.batchGet() --------");
if (err) {
console.log('The Sheets API returned an error: ' + err);
//The API returned an error: Error: API key not valid. Please pass a valid API key.
reject(err);
};
try {
console.log("response:-------------------------------");
console.log(JSON.stringify(response));
console.log("data.valueRanges:-------------------------------");
console.log(data.valueRanges);
resolve(JSON.stringify(data.valueRanges));
} catch (err) {
console.log("Error processing Sheets API response: " + err);
reject(err);
}
})
Firebase日志:
Function execution took 3822 ms, finished with status code: 200
undefined
data.valueRanges:-------------------------------
undefined
response:-------------------------------
getJwt() --------------- OK
index.js
'use strict'
const functions = require('firebase-functions');
const { google } = require('googleapis');
var serviceAccount = require("./credentials/admin-service-account-key.json");
function getJwt() {
// Define the required scopes.
var scopes = [
'https://www.googleapis.com/auth/spreadsheets'
];
return new google.auth.JWT(
serviceAccount.client_email,
null,
serviceAccount.private_key,
scopes
);
}
function getSpreadsheetData(jwt) {
return new Promise((resolve, reject) => {
jwt.authorize((error, access_token) => {
if (error) {
console.log('Error in jwt.authorize: ' + error);
reject(error);
} else {
// access_token ready to use to fetch data and return to client
const sheets = google.sheets({ version: 'v4', access_token });
// set auth as a global default:
google.options({ auth: jwt }); //<----------------------
const request = {
auth: jwt,
spreadsheetId: 'xxxxxxxxxxxxxxxxxxxx', //<---------- Project Checklist / Control
range: 'Control!A:F', //
}
sheets.spreadsheets.values.get(request, (err, response) => {
console.log("inside: sheets.spreadsheets.values.get() -------------------------------");
if (err) {
console.log('The Sheets API returned an error: ' + err);
//The API returned an error: Error: API key not valid. Please pass a valid API key.
reject(err);
};
try {
var numRows = response.data.values ? response.data.values.length : 0;
console.log('%d rows retrieved.', numRows);
console.log("response.data:-------------------------------");
console.log(response.data.values);
resolve(response.data.values);
} catch (err) {
console.log("Error processing Sheets API response: " + err);
reject(err);
}
})
}
})
})
}
function getSheetBatchData(jwt) {
return new Promise((resolve, reject) => {
jwt.authorize((error, access_token) => {
if (error) {
console.log('Error in jwt.authorize: ' + error);
reject(error);
} else {
// access_token ready to use to fetch data and return to client
const sheets = google.sheets({ version: 'v4', access_token });
// set auth as a global default:
google.options({ auth: jwt }); //<----------------------
const request = {
auth: jwt,
spreadsheetId: 'xxxxxxxxxxxxxxxxxxxxxxxxxx', //<---------- Project Checklist / Control
ranges: [
"Project Checklist!B:K",
"Control!A:F"
],
majorDimension: "ROWS",
dateTimeRenderOption: "FORMATTED_STRING",
valueRenderOption: "FORMATTED_VALUE"
}
/*
sheets.spreadsheets.values.batchGet({
// your options
}, function (err, data, response) {
console.log(response.req.res.request.url);
});
*/
//https://developers.google.com/sheets/api/reference/rest/v4/spreadsheets.values/batchGet
sheets.spreadsheets.values.batchGet(request, (err, data, response) => {
if (err) {
console.log('The Sheets API returned an error: ' + err);
//The API returned an error: Error: API key not valid. Please pass a valid API key.
reject(err);
};
try {
/*
* Returns: Array: data.valueRanges =
* [
* "range": "....",
* "values": []
* ]
*/
console.log("response:-------------------------------");
console.log(JSON.stringify(response));
console.log("data.valueRanges:-------------------------------");
console.log(data.valueRanges);
resolve(JSON.stringify(data.valueRanges));
} catch (err) {
console.log("Error processing Sheets API response: " + err);
reject(err);
}
});
}
})
})
}
/* Working */
exports.getControlSheetData = functions.https.onCall((data, context) => {
console.log("getData()---------------------------");
if (!context.auth) {
throw new functions.https.HttpsError('failed-precondition', 'The function must be called ' + 'while authenticated.');
} else {
console.log("context.auth ------------ OK");
const uid = context.auth.uid;
console.log(uid);
var jwt = getJwt();
console.log("getJwt() --------------- OK");
return getSpreadsheetData(jwt); //<------------ Requested Spreadsheet's Data
}
})
/* Error */
exports.getBatchData = functions.https.onCall((data, context) => {
console.log("getBatchData()---------------------------");
if (!context.auth) {
throw new functions.https.HttpsError('failed-precondition', 'The function must be called ' + 'while authenticated.');
} else {
console.log("context.auth ------------ OK");
const uid = context.auth.uid;
console.log(uid);
var jwt = getJwt();
console.log("getJwt() --------------- OK");
return getSheetBatchData(jwt); //<------------ Requested Spreadsheet's Data
}
})
新测试:index.js
'use strict'
const functions = require('firebase-functions');
const { google } = require('googleapis');
var serviceAccount = require("./credentials/admin-service-account-key.json");
function getJwt() {
// Define the required scopes.
var scopes = [
'https://www.googleapis.com/auth/spreadsheets'
];
return new google.auth.JWT(
serviceAccount.client_email,
null,
serviceAccount.private_key,
scopes
);
}
function getDataTest2(jwt) {
jwt.authorize().then(access_token => {
// access_token ready to use to fetch data and return to client
const sheets = google.sheets({ version: 'v4', access_token });
// set auth as a global default:
google.options({ auth: jwt }); //<----------------------
const request = {
auth: jwt,
spreadsheetId: 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx',
ranges: [
"Project Checklist!B:K",
"Control!A:F"
],
majorDimension: "ROWS",
dateTimeRenderOption: "FORMATTED_STRING",
valueRenderOption: "FORMATTED_VALUE"
}
function callback(data,resp) {
try {
console.log("inside callback-----------");
console.log("returned data:--------------")
console.log(data);
console.log("returned resp:--------------")
console.log(resp);
console.log("expected data.valueRanges------------------------")
console.log(data.valueRanges); //<--------- expected data.valueRanges
return JSON.stringify(data.valueRanges);
} catch(err) {
console.log('The Sheets API returned an error: ' + err);
//The API returned an error: Error: API key not valid. Please pass a valid API key.
return(err);
}
}
sheets.spreadsheets.values.batchGet(request, callback);
}).catch(error => {
console.log('Error in jwt.authorize: ' + error);
reject(error);
})
}
exports.getBatchData = functions.https.onCall((data, context) => {
console.log("getBatchData()---------------------------");
if (!context.auth) {
throw new functions.https.HttpsError('failed-precondition', 'The function must be called ' + 'while authenticated.');
} else {
console.log("context.auth ------------ OK");
const uid = context.auth.uid;
console.log(uid);
var jwt = getJwt();
console.log("getJwt() --------------- OK");
//return getSheetBatchData(jwt); //<------------ Requested Spreadsheet's Data
return getDataTest2(jwt)
}
})
Firebase日志:
getJwt()--------------- OK
Function execution took 965 ms, finished with status code: 200
inside callback-----------
returned data: --------------
null
returned resp: --------------
{
status: 200, statusText: 'OK', headers: { 'content-type': 'application/json; charset=UTF-8', vary: 'Origin, X-Origin, Referer', date: 'Tue, 18 Sep 2018 02:28:15 GMT', server: 'ESF', 'cache-control': 'private', 'x-xss-protection': '1; mode=block', 'x-frame-options': 'SAMEORIGIN', 'alt-svc': 'quic=":443"; ma=2592000; v="44,43,39,35"', connection: 'close', 'transfer-encoding': 'chunked' }, config: { adapter: [Function: httpAdapter], transformRequest: { '0': [Function: transformRequest] }, transformResponse: { '0': [Function: transformResponse] }, timeout: 0, xsrfCookieName: 'XSRF-TOKEN', xsrfHeaderName: 'X-XSRF-TOKEN', maxContentLength: 2147483648, validateStatus: [Function], headers: { Accept: 'application/json, text/plain, */*', 'Accept-Encoding': 'gzip', 'User-Agent': 'google-api-nodejs-client/0.2.1 (gzip)', Authorization: 'Bearer ya29.c.ElocBg_A_xxxxxxxxxxxxxxxxxxxx' }, method: 'get', access_token: { access_token: 'ya29.c.ElocBg_A_xxxxxxxxxxxxxxxxxx', token_type: 'Bearer', expiry_date: 1537241286000, id_token: undefined, refresh_token: 'jwt-placeholder' }, url: 'https://sheets.googleapis.com/v4/spreadsheets/xxxxxxxxxxxxxxxxxxxxxxxxxxx/values:batchGet', paramsSerializer: [Function], data: undefined, params: { ranges: [Object], majorDimension: 'ROWS', dateTimeRenderOption: 'FORMATTED_STRING', valueRenderOption: 'FORMATTED_VALUE' } }, request: ClientRequest {
domain: null, _events: { socket: [Function], abort: [Function], aborted: [Function], error: [Function], timeout: [Function], prefinish: [Function: requestOnPrefinish] }, _eventsCount: 6, _maxListeners: undefined, output: [], outputEncodings: [], outputCallbacks: [], outputSize: 0, writable: true, _last: true, upgrading: false, chunkedEncoding: false, shouldKeepAlive: false, useChunkedEncodingByDefault: false, sendDate: false, _removedHeader: { }, _contentLength: 0, _hasBody: true, _trailer: '', finished: true, _headerSent: true, socket: TLSSocket { _tlsOptions: [Object], _secureEstablished: true, _securePending: false, _newSessionPending: false, _controlReleased: true, _SNICallback: null, servername: null, npnProtocol: false, alpnProtocol: false, authorized: true, authorizationError: null, encrypted: true, _events: [Object], _eventsCount: 8, connecting: false, _hadError: false, _handle: null, _parent: null, _host: 'sheets.googleapis.com', _readableState: [Object], readable: false, domain: null, _maxListeners: undefined, _writableState: [Object], writable: false, allowHalfOpen: false, destroyed: true, _bytesDispatched: 563, _sockname: null, _pendingData: null, _pendingEncoding: '', server: undefined, _server: null, ssl: null, _requestCert: true, _rejectUnauthorized: true, parser: null, _httpMessage: [Circular], read: [Function], _consuming: true, write: [Function: writeAfterFIN], _idleNext: null, _idlePrev: null, _idleTimeout: -1 }, connection: TLSSocket {
_tlsOptions: [Object], _secureEstablished: true, _securePending: false, _newSessionPending: false, _controlReleased: true, _SNICallback: null, servername: null, npnProtocol: false, alpnProtocol: false, authorized: true, authorizationError: null, encrypted: true, _events: [Object], _eventsCount: 8, connecting: false, _hadError: false, _handle: null, _parent: null, _host: 'sheets.googleapis.com', _readableState: [Object], readable: false, domain: null, _maxListeners: undefined, _writableState: [Object], writable: false, allowHalfOpen: false, destroyed: true, _bytesDispatched: 563, _sockname: null, _pendingData: null, _pendingEncoding: '', server: undefined, _server: null, ssl: null, _requestCert: true, _rejectUnauthorized: true, parser: null, _httpMessage: [Circular], read: [Function], _consuming: true, write: [Function: writeAfte
...
expected data.valueRanges------------------------
The Sheets API returned an error: TypeError: Cannot read property 'valueRanges' of null