Drupal 8:通过 AJAX 请求运行批处理

这个问题来自我最近在做的一个项目。我不得不代表用户执行一堆 API 查找,这可能需要一分钟左右的时间才能完成。API 查找的结果被缓存,因此一旦完成,站点就会非常快,不幸的是,最初的 API 查找显着减慢了页面加载速度,因此产生了问题。

我创建了一个批处理过程,以更易于管理的方式加载 API 结果,而不是仅仅在页面加载过程中进行 API 加载并让用户坐下来。这产生了另一个问题,尽管 Drupal 中的批处理运行器非常好,但仅仅向用户展示并期望他们了解正在发生的事情可能有点太多了。这让我想到是否可以通过 AJAX 回调从他们试图加载的页面运行批处理。

不得不说,这个问题的解决方法我找了好久。事实证明,以前没有人解决过这个问题(我可以看到)。

第一步是创建一个库来控制批处理的 AJAX 回调。

loader:
  js:
    js/loader.js: {}
  dependencies:
    - core/jquery
    - core/drupalSettings

连同名为 loader.js 的相关 JavaScript 文件。我不知道我需要在这里填写什么,所以只是一个存根,所以我首先使用最小的 AJAX 回调来创建它,该回调会触发批处理过程。

(function ($, Drupal) {
  'use strict';
 
  Drupal.behaviors.account = {
    attach: function attach(context, settings) {
      $.ajax({
        url: Drupal.url('loading/ajax'),
        type: 'POST',
        contentType: 'application/json; charset=utf-8',
        dataType: 'json',
        success: function success(value) {
          // 做东西...
        }
      });
    }
  };
})(jQuery, Drupal);

这是通过将其附加到页面的输出而加载到页面上的。在这种情况下,我只是使用静态插页式页面来运行 AJAX 请求。

  public function myLoading() {
    return [
      '#theme' => 'my_loading',
      '#attached' => [
        'library' => [
          'my_module/loader',
        ],
      ],
    ];
  }

这是一个标准的 Drupal 控制器,所以它没有做任何特别的事情。这可能在一个块或其他东西中,但我们希望将用户带到“加载”页面,同时我们从 API 获取内容,然后将它们发送回他们试图访问的页面。

最后一步是设置 AJAX 端点,以便它可以触发批处理。

  public function ajaxBatchProcess() {
    // 设置批处理。
    $this->batchService->setupLengthyBatchProcess();
 
    // 获取我们刚刚创建的批次。
    $batch =& batch_get();
 
    // 确保完成的响应不会产生任何消息。
    $batch['sets'][0]['finished'] = NULL;
 
    // 创建batch_process(),并为它提供一个它将要去的URL。
    $url = Url::fromRoute('user.page');
    $response = batch_process($url);
 
    // 将响应返回到 ajax 输出。
    $ajaxResponse = new AjaxResponse();
    return $ajaxResponse->addCommand(new BaseCommand('batchcustomer', $response->getTargetUrl()));
  }

我错过了这里的一些复杂性,但基本上我已经将批量创建包装在一个服务中。该服务本质上是一个设置和管理批处理运行的类。该函数setupLengthyBatchProcess()本质上只是包装了batch_set($batch); 调用并且可以在提交处理程序或类似的东西中使用。批处理完成功能还报告它刚刚完成的内容,因此将其删除以防止向用户显示这些消息。

经过一番调查,我发现该batch_process()函数的结果是返回批处理运行器的路径(例如batch?id=12345&op=start)。我不想向用户显示这个,所以我不能只向 AJAX 请求返回重定向响应。

解决此问题的一种方法是在批处理设置中设置“渐进式”选项。这实际上是一次处理整个批次,这并不是我真正想做的。我们失去的(除了能够以小块处理正在处理的数据量之外)是能够告诉用户他们必须等待多长时间,因为我们无法报告我们在批处理过程中走了多远。一个简单的进度指示器非常有用,即使用户只需要等待 10 秒钟。

如果您确实想沿着这条渐进式路线走下去,那么只需更改上面代码中的几个文件,到目前为止我所展示的一切都将起作用。请注意,我不再向batch_process()函数发送 URL,因为我们不会进行任何重定向。您还需要确保当 AJAX 请求完成时,它会正确重定向,因为它目前什么都不做。

$batch =& batch_get();
 
$batch['progressive'] = TRUE;
 
$response = batch_process();

我最终做的是对 Drupal 8 的批处理运行器进行逆向工程。这使用了一个名为ProgressBar的插件,该插件基本上监视端点并报告进度。该updateCallback()函数用于更新用户的进度或执行重定向。这有效地以与 Drupal 通常运行它的方式相同的方式运行批处理。

(function ($, Drupal) {
  'use strict';
 
  Drupal.behaviors.account = {
    attach: function attach(context, settings) {
      var progressBar = void 0;
 
      function updateCallback(progress, status, pb) {
        $('#updateprogress').html(progress + '%');
        if (progress === '100') {
          pb.stopMonitoring();
         window.location= '/user';
        }
      }
 
      function errorCallback(pb) {
      }
 
      $.ajax({
        url: Drupal.url('account/loading/ajax'),
        type: 'POST',
        contentType: 'application/json; charset=utf-8',
        dataType: 'json',
        success: function success(value) {
          progressBar = new Drupal.ProgressBar('updateprogress', updateCallback, 'POST', errorCallback);
          progressBar.startMonitoring(value[0].data + '&op=do', 10);
        }
      });
    }
  };
})(jQuery, Drupal);

最终,这非常有效。当用户登录时(我们检测到他们还没有缓存),他们被发送到这个中间页面,批处理在后台运行,同时他们等待 API 调用完成。该过程会报告它已经完成了多远,一旦完成,用户就会被发送回他们的帐户页面。