You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Apps Script时间驱动触发器不运行,手动执行脚本正常

Google Apps Script 时间驱动触发器无执行效果问题修复

问题描述

手动执行backupAll()函数可正常备份Google文档、表格、幻灯片,但设置时间驱动触发器后,触发器显示已运行却未生成任何备份文件。已确认项目时区设置正确,更换多个执行时间后问题依旧。

核心问题分析

1. 无人值守环境下的用户身份判断失效

触发器以后台模式运行时,Session.getActiveUser().getEmail()会返回空值,导致代码中file.getOwner().getEmail() == Session.getActiveUser().getEmail()的判断条件永远不成立,直接跳过所有文件的备份逻辑。

2. MIME类型键的类型不匹配

原代码通过Object.keys(NATIVE_MIME_TYPES)获取备份类型数组,返回的是字符串类型(如"application/vnd.google-apps.document"),但DriveApp.getFilesByType()要求传入MimeType枚举值,部分环境下类型不匹配会导致无法获取目标文件。

3. OAuth权限范围不足

触发器运行时,ScriptApp.getOAuthToken()可能未获取到Drive API导出接口的权限,导致导出文件请求失败。

修复方案

步骤1:替换用户身份判断逻辑

将Session.getActiveUser().getEmail()替换为你自己的邮箱字符串,避免无人值守环境下的身份获取限制。

步骤2:修正MIME类型数组定义

直接使用MimeType枚举值数组,确保与DriveApp.getFilesByType()的参数类型匹配。

步骤3:显式声明OAuth权限

在脚本开头添加权限注释,确保触发器能获取到足够的API访问权限。

步骤4:添加调试日志

在关键请求处添加异常静默和日志记录,便于排查触发器运行时的错误。

修改后的完整脚本

// 显式声明所需权限,确保触发器能获取足够权限
// @OnlyCurrentDoc
// @OAuthScope https://www.googleapis.com/auth/drive
// @OAuthScope https://www.googleapis.com/auth/drive.file

var BACKUP_FOLDER_ID = '16SGkgNCPeNn9DgB9RmhLAv-z5C_kCbCM';

var NATIVE_MIME_TYPES = {};
NATIVE_MIME_TYPES[MimeType.GOOGLE_DOCS] = MimeType.MICROSOFT_WORD;
NATIVE_MIME_TYPES[MimeType.GOOGLE_SHEETS] = MimeType.MICROSOFT_EXCEL;
NATIVE_MIME_TYPES[MimeType.GOOGLE_SLIDES] = MimeType.MICROSOFT_POWERPOINT;

var NATIVE_EXTENSIONS = {};
NATIVE_EXTENSIONS[MimeType.GOOGLE_DOCS] = '.docx';
NATIVE_EXTENSIONS[MimeType.GOOGLE_SHEETS] = '.xlsx';
NATIVE_EXTENSIONS[MimeType.GOOGLE_SLIDES] = '.pptx';

// 直接使用MimeType枚举值数组,避免类型不匹配
var BACKUP_MIME_TYPES = [MimeType.GOOGLE_DOCS, MimeType.GOOGLE_SHEETS, MimeType.GOOGLE_SLIDES];

function backupAll() {
  const backupFolder = DriveApp.getFolderById(BACKUP_FOLDER_ID);
  // 替换为你的邮箱地址
  const currentUserEmail = 'your-email@example.com';

  BACKUP_MIME_TYPES.forEach(function(mimeType) {
    var files = DriveApp.getFilesByType(mimeType);
    while (files.hasNext()) {
      var file = files.next();
      if (file.getOwner() && file.getOwner().getEmail() === currentUserEmail) {
        backup(file, backupFolder);
      }
    }
  });
}

function backup(file, folder) {
  var targetName = file.getName() + ' ' + file.getId();
  var lastUpdated = file.getLastUpdated();
  
  var pdf = getPdfBlob(file);
  var native = getNativeBlob(file);
  
  var zip = Utilities.zip([pdf, native], targetName + '.zip');
  
  createOrUpdateFileForBlob(zip, folder, lastUpdated);
}

function createOrUpdateFileForBlob(blob, folder, ifOlderThan) {
  var existingFiles = folder.getFilesByName(blob.getName());
  if (existingFiles.hasNext()) {
    var file = existingFiles.next();
    if (file.getLastUpdated() < ifOlderThan) {
      updateFile(file, blob);
    }
  } else {
    folder.createFile(blob);
  }
}

function updateFile(file, blob) {
  const url = 'https://www.googleapis.com/upload/drive/v2/files/' + file.getId() + '?uploadType=media';
  
  const params = {
    method: 'put',
    headers: { Authorization: 'Bearer ' + ScriptApp.getOAuthToken() },
    payload: blob,
    muteHttpExceptions: true
  };
  
  var response = UrlFetchApp.fetch(url, params);
  
  if (response.getResponseCode() < 200 || response.getResponseCode() > 299) {
    console.error('更新文件失败:' + file.getName() + ',响应码:' + response.getResponseCode());
    throw 'Failed to update file named ' + file.getName();
  }
}

function getPdfBlob(file) {
  var blob = file.getAs('application/pdf');
  return blob;
}

function getNativeBlob(file) {
  const nativeMimeType = NATIVE_MIME_TYPES[file.getMimeType()];
  const extension = NATIVE_EXTENSIONS[file.getMimeType()];
  
  const url = 'https://www.googleapis.com/drive/v2/files/' + file.getId() + '/export?mimeType=' + nativeMimeType;

  const params = {
    method: 'get',
    headers: { Authorization: 'Bearer ' + ScriptApp.getOAuthToken() },
    muteHttpExceptions: true
  };
  
  const response = UrlFetchApp.fetch(url, params);
  if (response.getResponseCode() < 200 || response.getResponseCode() > 299) {
    console.error('导出文件失败:' + file.getName() + ',响应码:' + response.getResponseCode());
    throw 'Failed to export file named ' + file.getName();
  }
  
  const blob = response.getBlob();
  blob.setName(file.getName() + extension);
  return blob;
}

额外调试建议

  • 查看执行日志:在脚本编辑器中点击「查看」→「日志」,检查触发器运行时的错误信息。
  • 重新创建触发器:删除现有触发器,重新创建并完成授权流程,确保权限生效。

内容的提问来源于stack exchange,提问作者Kayley Harrison

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.03 17:40:40