使用服务账号调用Google Drive API files.list无法获取子文件夹
问题描述
运行Node.js API访问Google Drive,因限制无法使用OAuth,改用服务账号认证,但出现以下异常:
- 相同逻辑下,OAuth认证可正常调用
files.list()获取子目录,服务账号认证无法获取 - 已确认目标父文件夹及子目录均给服务账号邮箱配置了编辑权限
- 调用
files.list()时的异常现象:- 省略
q参数可返回所有文件(含回收站文件),但仍无目录 - 设置
q: 'trashed=false',仍返回回收站文件 - 设置
q: "mimeType = 'application/vnd.google-apps.folder'",无法获取任何文件夹 - 设置
q: '${req.body.folderId}' in parents,无任何子文件夹返回
- 省略
目标需求:获取服务账号有权限的顶层文件夹中非回收站的所有项(含子目录),不限创建者。
相关代码:
const authenticateGdrive = async () => { const serviceAccountKey = await readFile(SERVICE_ACCOUNT_KEY_PATH, 'utf8'); const credentials = JSON.parse(serviceAccountKey); const auth = new google.auth.GoogleAuth({ credentials, scopes: ['https://www.googleapis.com/auth/drive.file'], }); return google.drive({ version: 'v3', auth }); }; export const list = async(req, res, next) => { try { const drive = await authenticateGdrive(); // problems observed when using a service account for authentication: // q: `'${req.body.folderId}' in parents` does not return any subfolder // q: 'trashed=false', // pulls in trash anyway, but no q parameter does pull in trash // q: "mimeType = 'application/vnd.google-apps.folder'", // doesn't retrieve any folders const response = await drive.files.list({ q: `'${req.body.folderId}' in parents`, fields: 'files(id, name, mimeType)', }); const files = response.data.files; if (files.length) { res.status(200).send(files); } else { res.status(500).send({message: "No files retrieved"}); }; } catch (error) { res.status(500).send({message: error.message}); }; };
问题排查与解决
1. 权限范围配置错误
当前使用的https://www.googleapis.com/auth/drive.file范围存在限制:
- 服务账号仅能通过此范围访问自身创建或通过API上传的文件/文件夹,无法访问其他用户创建但共享给它的资源
- 替换为
https://www.googleapis.com/auth/drive(全权限)或https://www.googleapis.com/auth/drive.readonly(只读权限,满足需求的话更安全),才能让服务账号访问所有已获得权限的资源
2. 查询参数q的逻辑优化
需要组合多个条件匹配需求,正确的q参数应为:
q: `'${req.body.folderId}' in parents AND trashed=false`
'${req.body.folderId}' in parents:限定目标父文件夹下的资源trashed=false:排除回收站中的资源
如果需要单独筛选文件夹,可追加条件:
q: `'${req.body.folderId}' in parents AND trashed=false AND mimeType='application/vnd.google-apps.folder'`
3. 代码调整示例
修改认证函数的权限范围,同时更新files.list的查询参数:
const authenticateGdrive = async () => { const serviceAccountKey = await readFile(SERVICE_ACCOUNT_KEY_PATH, 'utf8'); const credentials = JSON.parse(serviceAccountKey); const auth = new google.auth.GoogleAuth({ credentials, // 替换为合适的权限范围 scopes: ['https://www.googleapis.com/auth/drive.readonly'], }); return google.drive({ version: 'v3', auth }); }; export const list = async(req, res, next) => { try { const drive = await authenticateGdrive(); const response = await drive.files.list({ // 组合查询条件 q: `'${req.body.folderId}' in parents AND trashed=false`, fields: 'files(id, name, mimeType)', }); const files = response.data.files; if (files.length) { res.status(200).send(files); } else { // 无资源时返回200更合理,500属于服务器错误范畴 res.status(200).send({message: "No files retrieved"}); }; } catch (error) { res.status(500).send({message: error.message}); }; };
额外说明
- 之前
trashed=false无效的原因是权限范围限制,返回的是服务账号自身的回收站资源,而非共享文件夹内的内容 - 再次确认服务账号邮箱已被添加为目标文件夹的协作者(编辑权限),且文件夹未被设置共享限制
内容的提问来源于stack exchange,提问作者CindyLuWho
相关产品推荐
相关产品推荐

