当脚本部署为 Web 应用程序时锁定文档上的脚本执行

Lock script execution on a document when script is deployed as web app

我的 Google Apps 脚本部署为 Web 应用程序,任何用户都可以访问。它的功能是打开和更改该文档中的文本。

我将脚本 document ID 作为查询参数发送,如下所示:

https://script.google.com/a/macros/s/AKfycbzCP...TnwHUbXxzDM/exec?docId=1_cMN0nuJadBw6QVjKtdEA6eXhE8ubIoxIJai1ticxnE`

Web 应用打开文档并更改文档中的文本。

function doGet(e){
  var params=e.parameters;
  var doc = DocumentApp.openById(params['docId']);
  ...
  /* change text of the document */
}

问题

现在,当有多个用户尝试在 同一文档 上同时 运行 应用程序脚本时,Web 应用程序无法处理并发和功能中断.

我调查了 Lock Service but lock service's document lock 仅适用于容器绑定脚本,不适用于 Web 应用程序。

然后我尝试使用 cache service var cache = CacheService.getDocumentCache(); and property service var documentProperties = PropertiesService.getDocumentProperties(); for the document but document property and document cache returns null in web apps and are restricted to container bound scripts only, as stated in the documentation 设置 属性:

If this method is called outside of the context of a containing document (such as from a standalone script or web app), this method returns null.

当 Google Apps 脚本部署为 Web 应用程序时,是否有任何方法可以处理文档中脚本执行的并发性。 (非容器绑定)

正如@azawaza 指出的那样,您应该使用具有适当范围的锁,而脚本锁更适合您的场景。

中对此进行了讨论

如果代码的关键部分足够快,那么就不必担心让用户更新文档 2 等待文档 1 的另一个更新进行;他们不会等太久。类似于:

function doGet1(e){
  // Perform any "pre" operations on private
  // or non-critical shared resources.
  var params=e.parameters;

  // Get a script lock, because we're about to modify a shared resource.
  var lock = LockService.getScriptLock();
  // Wait for up to 10 seconds for other processes to finish.
  lock.waitLock(10000);

  ////// Critical section begins   vvvvv

  var doc = DocumentApp.openById(params['docId']);

  // change text of the document here

  doc.saveAndClose();

  ////// Critical section ends     ^^^^^
  lock.releaseLock();

  // Continue with operations on private
  // or non-critical shared resources.

  return ContentService.createTextOutput("Document updated.")
}

特定资源锁

开箱即用的 Google Apps 脚本锁定服务旨在保护代码的关键部分。如果我们想控制对特定资源的访问(可能是 long-ish 时间),例如 Google 文档,我们可以通过更改我们的内容来调整它 "locking".

在此示例中,Lock 服务保护关键部分,其中检查和更新脚本属性。这些属性有 "keys" 匹配我们的 docId 参数;值并不重要,因为我们可以使用密钥的简单存在作为我们的测试。

注意:目前,如果另一个脚本未能删除 属性 保护用户对共享文档的使用,此脚本可能会阻止用户 "forever"(直到脚本超时)。您需要更加小心生产代码。

function doGet2(e){
  // Perform any "pre" operations on private
  // or non-critical shared resources.
  var params=e.parameters;

  // Wait for exclusive access to docId
  var ready = false;
  // Get a script lock, because we're about to modify a shared resource.
  var lock = LockService.getScriptLock();

  while (!ready) {
    // Wait for up to 1 second for other processes to finish.
    if (lock.tryLock(1000)) {
      ////// Critical section begins   vvvvv      

      var properties = PropertiesService.getScriptProperties();

      // If nobody has "locked" this document, lock it; we're ready.
      if (properties.getProperty(docId) == null) {
        // Set a property with key=docId.
        properties.setProperty(docId,"Locked"); 
        ready = true;
      }

      ////// Critical section ends     ^^^^^
      lock.releaseLock();
    }
  }

  // We have exclusive access to docId now.

  var doc = DocumentApp.openById(params['docId']);

  // change text of the document here

  doc.saveAndClose();

  // Delete the "key" for this document, so others can access it.
  properties.deleteProperty(docId); 

  return ContentService.createTextOutput("Document updated.")
}

命名锁

我们在上一个示例中使用的逻辑可以封装到对象中以提供更优雅的接口。事实上,Bruce McPherson 已经用他的 cNamedLock Library, described on his Desktop Liberation site 做到了这一点。使用该库,您可以像这样实现 document-specific 锁定:

function doGet3(e){
  // Perform any "pre" operations on private
  // or non-critical shared resources.
  var params=e.parameters;

  // Get a named lock.
  var namedLock = new NamedLock().setKey(docId);

  namedLock.lock();
  ////// Critical section begins   vvvvv      

  // We have exclusive access to docId now.

  var doc = DocumentApp.openById(params['docId']);

  // change text of the document here

  doc.saveAndClose();

  ////// Critical section ends     ^^^^^
  namedLock.unlock();

  return ContentService.createTextOutput("Document updated.")
}