11
17

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

プログレスサークルをLaravelの非同期処理でリアルタイムに表示

Last updated at Posted at 2019-06-29

企業向けに作成した勤怠管理のWebアプリケーションが、従業員数が増えてきた影響で、計算結果のダウンロードが始まるまで1分以上も待たされるようになってきた。
UIとして好ましくないし、リクエストがタイムアウトする危険もあるので、重たい処理は裏で非同期に実行し、プログレスサークルでその進捗状況を表示するように改修した。

プログレスサークルとは、下の動画にあるような進捗度を表すサークルバーのことだ。
mw_cap.gif

プログレスサークルを実装するjQueryプラグインは探せば色々と出てくると思うが、今回は jquery-circle-progress を使うことにした。

Laravelでは、ジョブキューに登録することで非同期処理を容易に実現できる。

クライアント側の処理

JavaScript

ジョブの進捗度をサーバからJSON形式で受け取り、プログレスサークルを表示する。

js
$(function(){
  var funcProgress = function(data){
    var circle = $('#daily > .circle');
    if(data.daily_progress < 0){
      circle.hide();  // サークルバーを非表示
    } else {
      circle.show();  // サークルバーを表示
      circle.find('strong').html(data.daily_progress + '<i>%</i>');  // 進捗度
      // サークルバーの設定
      circle.circleProgress({
        value: data.daily_progress / 100,
        size: 120,
        animation: false,
        fill: { gradient: ['#0681c4', '#07c6c1'] }
      });
    }
  }
  var funcInterval = function(){
    // HTTPのGET通信を行い、JOSN形式に変換されたデータをサーバから受け取る
    $.getJSON("{{ route('reports.progress') }}",funcProgress);
  }
  progressInterval = setInterval(funcInterval,1800);  // 一定時間が経過するごとに処理を実行

ジョブの初期化(事前チェック)と登録を行う。なお、アラートの表示には SweetAlert を使っている。

js
swal(
{
  title: '時間のかかる処理です',
  text: '現在のデータを削除して再作成します。',
  type: 'warning',
  showCancelButton: true,
  confirmButtonText: '再作成'
},
  function(){  // ジョブの初期化
    $.getJSON("{{ route('reports.init') }}",
      {
        jobname: job,
        term: term
      },
      function(data){
        if(data.result > 0){
          swal('実績データがありません','期間を確認して下さい。','error');
        }else{  // ジョブキューに登録
          $.get("{{ url('reports/jobs') }}/"+job+'/'+term);
        }
      }
    );
  }
);

サーバ側の処理

View

プログレスサークルの表示領域を確保する。
本画面では、プログレスサークルを2つ表示するとき(daily用とmonthly用)があるので、id属性ではなくclass属性で指定している。

HTML
<div class="panel-body" id="daily"><div class="circle"><strong><i></i></strong></div>
</div>

Controller

必要な処理は下表の3つで、すべてJavaScriptから起動する。

Route Name Action 処理内容
reports.init ReportController@getJsonInit ジョブの初期化(事前チェック)
reports.jobs ReportController@jobs ジョブの登録(ディスパッチ)
reports.progress ReportController@getJsonProgress ジョブの進捗度合を返す
app/Http/Controllers/ReportController.php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use App\Models\ResultFileCreator;
use App\Jobs\ProcessDailyFile;
use App\Jobs\ProcessMonthlyFile;

class ReportController extends Controller
{
    // ジョブの初期化(事前チェック)
    public function getJsonInit(Request $request)
    {
        $creator = new ResultFileCreator($request->jobname, Auth::user()->userid, $request->term);
        // チェック結果をJSONで返す
        return [
            'result' => $creator->init()  // 0以外ならデータが無い
        ];
    }

    // ジョブの登録(ディスパッチ)
    public function jobs($jobname, $term)
    {
        switch ($jobname) {
        case 'daily':
            ProcessDailyFile::dispatch(Auth::user()->userid, $term);
            break;
        case 'monthly':
            ProcessMonthlyFile::dispatch(Auth::user()->userid, $term);
            break;
        }
        return;
    }

    // ジョブの進捗度合を返す
    public function getJsonProgress(Request $request)
    {
        $daily = new ResultFileCreator('daily', Auth::user()->userid);
        $monthly = new ResultFileCreator('monthly', Auth::user()->userid);
        // JSONで返す
        return [
            'daily_progress'   => $daily->getProgress(),
            'daily_leftover'   => $daily->leftover(),
            'monthly_progress' => $monthly->getProgress(),
            'monthly_leftover' => $monthly->leftover(),
        ];
    }
}

Model

Controllerとジョブクラスから呼ばれる ResultFileCreator を定義しているが、アプリケーション固有のビジネスロジックなのでコードは省略させていただく。

ジョブクラス

ジョブクラスはキューワーカ(後述)が実行する。

php artisan make:job ProcessDailyFile

で雛形クラスを作成し、実行したい処理を handle メソッドに追加していくだけだ。

app/Jobs/ProcessDailyFile.php
namespace App\Jobs;
use Illuminate\Bus\Queueable;
use Illuminate\Queue\SerializesModels;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use App\Models\ResultFileCreator;

class ProcessDailyFile implements ShouldQueue
{
    use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;

    public $tries = 1;    // リトライしない
    public $timeout = 0;  // 実行時間無制限

    protected $userid;    // どのユーザーが作成したか
    protected $term;      // 期間

    /**
     * Create a new job instance.
     *
     * @return void
     */
    public function __construct($userid, $term)
    {
        $this->userid = $userid;
        $this->term   = $term;
    }

    /**
     * Execute the job.
     *
     * @return void
     */
    public function handle()
    {
        $creator = new ResultFileCreator('daily', $this->userid, $this->term);
        $creator->run();   // 時間のかかる処理
    }
}

環境設定

Laravelプロジェクトでキューを使うのが初めてなら環境設定を行う。

データベースにキューのテーブルを追加

php artisan queue:table
php artisan migrate

キューワーカをサービスに登録

キューワーカとは、キューを監視して新しいジョブを実行してくれるものだ。
このサービスはデータベースを掴むので、データベースの再作成などをする前には停止すること。

/etc/systemd/system/laravel-queue.service
[Unit]
Description=Laravel queue worker

[Service]
User=apache
Group=apache
Restart=on-failure
ExecStart=/bin/php /var/www/html/foo/artisan queue:work --daemon

[Install]
WantedBy=multi-user.target

起動確認

systemctl start laravel-queue.service
systemctl status laravel-queue.service

自動起動を有効化

systemctl enable laravel-queue.service
11
17
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
11
17

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?