Google Apps Script中Properties Service存储end_time异常问题排查
问题分析与解决方案
你的核心问题出在全局共享的Script Properties被并发写入覆盖,加上代码里没必要的读写逻辑放大了问题,以下是具体原因和修复方案:
可能的原因
Script Properties的全局共享特性
Script Properties是整个脚本的全局存储,所有用户的函数执行都会读写同一个键值对。如果多个用户(或同一用户多次触发)同时调用getEventByMeetingRooms,就会出现并发写入冲突:比如用户A刚把计算好的end_time存入,用户B的执行立刻用自己计算的end_time覆盖掉,而用户A的循环还在读取这个值,自然会拿到错误的日期。不必要的全局存储逻辑
你的代码里,start_time和end_time是当前执行上下文计算出来的临时值,完全没必要存入Script Properties再读取。这种画蛇添足的操作,直接把局部变量的安全逻辑变成了全局共享的风险逻辑。Date对象的隐式转换风险
直接把Date对象存入Properties Service时,会自动调用toString()转成字符串,不同执行环境的字符串格式可能存在差异,读取时用new Date()解析可能出现意外(比如时区、格式解析错误),不过这不是你当前问题的主因,但也是潜在隐患。
修复方案
方案1:移除Script Properties的多余使用(最优解)
既然start_time和end_time是当前执行的临时计算值,直接用局部变量即可,完全不需要全局存储。修改后的核心代码如下:
function getEventByMeetingRooms(d = 7) { let meeting_rooms = listaSalasDeReunioes_(); let ss = SpreadsheetApp.openById("xxxx"); var ws = ss.getSheetByName("xxxxxxxxxx"); // 直接计算局部变量,不再用Script Properties let start_time = new Date(new Date().getFullYear(), new Date().getMonth(), 1); let end_time = new Date(new Date().getFullYear(), new Date().getMonth() + d, 0); var arr = []; if (meeting_rooms.length > 0) { for (let i = 0; i < meeting_rooms.length; i++) { console.log(meeting_rooms[i][2]); try { console.log("start_time: ", start_time, " / end_time: ", end_time); if (start_time > end_time) { console.log("Erro ao processar evento: Data de fim é menor que a data de inicio"); continue; } var calendar = CalendarApp.subscribeToCalendar(meeting_rooms[i][2]); let eventos = calendar.getEvents(start_time, end_time); if (eventos.length > 0) { eventos.forEach((ev) => { arr.push([ev.getId(), ev.getTitle(), ev.getStartTime().toLocaleDateString(), ev.getStartTime().toLocaleTimeString(), ev.getEndTime().toLocaleDateString(), ev.getEndTime().toLocaleTimeString(), ev.getDescription(), meeting_rooms[i][2]]); }); } } catch (e) { console.log(e); } } } else { console.log("O Array contendo as salas de reuniões veio vazio."); } // 后续的表格更新逻辑不变 if (arr.length > 0) { ws.getRange(2, 1, ws.getLastRow(), ws.getLastColumn()).clearContent(); ws.getRange(2, 1, arr.length, arr[0].length).setValues(arr); } else { console.log("Array de eventos vazios, sem atualização na planilha.") } }
方案2:如果确实需要全局共享日期范围(加锁防并发)
如果你有跨执行共享日期范围的需求,必须用Lock Service来保证读写的原子性,避免并发覆盖:
function getEventByMeetingRooms(d = 7) { let meeting_rooms = listaSalasDeReunioes_(); let ss = SpreadsheetApp.openById("xxxx"); var ws = ss.getSheetByName("xxxxxxxxxx"); var cache_script = PropertiesService.getScriptProperties(); // 获取锁,等待最多30秒 var lock = LockService.getScriptLock(); lock.waitLock(30000); try { // 加锁后再计算并存储日期 let start_time = new Date(new Date().getFullYear(), new Date().getMonth(), 1); let end_time = new Date(new Date().getFullYear(), new Date().getMonth() + d, 0); // 存储时转成ISO字符串,避免解析问题 cache_script.setProperty("start_time", start_time.toISOString()); cache_script.setProperty("end_time", end_time.toISOString()); } finally { // 释放锁 lock.releaseLock(); } var arr = []; if (meeting_rooms.length > 0) { for (let i = 0; i < meeting_rooms.length; i++) { console.log(meeting_rooms[i][2]); try { // 读取时解析ISO字符串 let storedStart = new Date(cache_script.getProperty("start_time")); let storedEnd = new Date(cache_script.getProperty("end_time")); console.log("start_time: ", storedStart, " / end_time: ", storedEnd); if (storedStart > storedEnd) { console.log("Erro ao processar evento: Data de fim é menor que a data de inicio"); continue; } var calendar = CalendarApp.subscribeToCalendar(meeting_rooms[i][2]); let eventos = calendar.getEvents(storedStart, storedEnd); if (eventos.length > 0) { eventos.forEach((ev) => { arr.push([ev.getId(), ev.getTitle(), ev.getStartTime().toLocaleDateString(), ev.getStartTime().toLocaleTimeString(), ev.getEndTime().toLocaleDateString(), ev.getEndTime().toLocaleTimeString(), ev.getDescription(), meeting_rooms[i][2]]); }); } } catch (e) { console.log(e); } } } else { console.log("O Array contendo as salas de reuniões veio vazio."); } // 后续表格更新逻辑不变 if (arr.length > 0) { ws.getRange(2, 1, ws.getLastRow(), ws.getLastColumn()).clearContent(); ws.getRange(2, 1, arr.length, arr[0].length).setValues(arr); } else { console.log("Array de eventos vazios, sem atualização na planilha.") } }
关键提醒
- 永远不要把局部执行的临时值存入全局共享的存储(Script Properties),这是并发问题的根源。
- 存储日期时,优先用ISO字符串(
toISOString())或时间戳(getTime()),避免直接存Date对象导致的解析异常。
内容的提问来源于stack exchange,提问作者João Paiva
相关产品推荐
相关产品推荐

