LaravelでIPアドレスを使ったアクセスカウンターの実装方法

LaravelでIPアドレスを使ったアクセスカウンターの実装方法

193 回閲覧されました

みなさんこんにちは、jonioです。

今回はIPアドレスを使ったアクセスカウンターの実装方法の解説になります。

この記事で知れること

  • 閲覧ユーザーは〜時間に1回しかアクセスカウントされない
  • 特定のページのアクセスを除外する

Laravelのバージョン

8系です。

テーブルの作成

下記のコマンドを叩きます、モデルも一緒に作成します。

php artisan make:model AccessCounter -m

マイグレーションファイルに下記の記述をします。

<?php

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

class CreateAccessCountersTable extends Migration
{
    /**
     * Run the migrations.
     *
     * @return void
     */
    public function up()
    {
        Schema::create('access_counters', function (Blueprint $table) {
            $table->id();
            $table->string('visitor_id');
            $table->string('page');
            $table->timestamps();
        });
    }

    /**
     * Reverse the migrations.
     *
     * @return void
     */
    public function down()
    {
        Schema::dropIfExists('access_counters');
    }
}

カラムの用途は以下になります。

  • visitor_id : IPアドレスの登録
  • page : アクセスページの登録

下記コマンドを叩いてテーブルを作成します。

php artisan migrate

ミドルウェアの作成

コントローラーにアクセスカウントする為の記述をいちいちするとコードが汚れるので全てのビューでアクセスカウントできる様にする為にミドルウェアに記述します。

ミドルウェアを作成する為に下記のコマンドを叩きます。

php artisan make:middleware GetAccess

「Laravelのプロジェクト > app > Http > Middleware > GetAccess.php」のコードを下記にします。

<?php

namespace App\Http\Middleware;

use Closure;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Cookie;
use Carbon\Carbon;
use App\Models\AccessCounter;

class GetAccess
{
    /**
     * Handle an incoming request.
     *
     * @param  \Illuminate\Http\Request  $request
     * @param  \Closure(\Illuminate\Http\Request): (\Illuminate\Http\Response|\Illuminate\Http\RedirectResponse)  $next
     * @return \Illuminate\Http\Response|\Illuminate\Http\RedirectResponse
     */
    public function handle(Request $request, Closure $next)
    {
        $page = $request->url();

        $visitor_id = $request->ip();

        $now = Carbon::now();

        $latest_access = AccessCounter::where('visitor_id', $visitor_id)
                                       ->where('page', $page)
                                       ->latest('created_at')
                                       ->first();

        if (!$latest_access || $now->diffInHours($latest_access->created_at) >= 12) {
            AccessCounter::create([
                'visitor_id' => $visitor_id,
                'page' => $page
            ]);
        }

        return $next($request);
    }
}

28行目〜31行目はページへの一番最新のアクセス情報を取得しています。

33行目〜38行目はページへのアクセスがないか1回アクセスしてから12時間以上経っている場合はアクセスをカウントできるようにしています。

特定のページのアクセスを除外

今の状態はアプリを作った自分が編集ページや管理画面にアクセスした時もアクセスのカウントをされますが除外したいはずです。

その場合はコードを下記にします。

<?php

namespace App\Http\Middleware;

use Closure;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Cookie;
use Illuminate\Support\Str;                 //この行を追加
use Carbon\Carbon;
use App\Models\AccessCounter;

class GetAccess
{
    /**
     * Handle an incoming request.
     *
     * @param  \Illuminate\Http\Request  $request
     * @param  \Closure(\Illuminate\Http\Request): (\Illuminate\Http\Response|\Illuminate\Http\RedirectResponse)  $next
     * @return \Illuminate\Http\Response|\Illuminate\Http\RedirectResponse
     */
    public function handle(Request $request, Closure $next)
    {
        $page = $request->url();

        $visitor_id = $request->ip();

        $now = Carbon::now();

        $user = Auth::user();


        //ここから追加
        $excluded_pages = [
            'login',
            'work/edit/*',
        ];

        foreach ($excluded_pages as $excluded_page) {
            if (Str::is($excluded_page, $request->path())) {
                return $next($request);
            }
        }
        //ここまで追加
        

        $latest_access = AccessCounter::where('visitor_id', $visitor_id)
                                       ->where('page', $page)
                                       ->latest('created_at')
                                       ->first();

        if (!$latest_access || $now->diffInHours($latest_access->created_at) >= 12) {
            AccessCounter::create([
                'visitor_id' => $visitor_id,
                'page' => $page
            ]);
        }

        return $next($request);
    }
}

33行目〜36行目は除外したいページのURLを記載しています。

「login」や「work/edit/*」の前に「/」をつけていませんが「/login」など/をつけるとアクセスカウントの除外ができません。

「work/edit/*」は「work/edit/1」や「work/edit/3」などパラメーターが色々変わる時の全てのパラメーター(ワイルドカード)を表現してます。

38行目〜42行目現在のURLと除外するURLを比較して同じ場合はそのURLに対してアクセスカウントの除外をします。

ミドルウェアの適用

「Laravelのプロジェクト > app > Http > Kernel.php」に下記の記述をします。

<?php

namespace App\Http;

use Illuminate\Foundation\Http\Kernel as HttpKernel;

class Kernel extends HttpKernel
{
    /**
     * The application's global HTTP middleware stack.
     *
     * These middleware are run during every request to your application.
     *
     * @var array<int, class-string|string>
     */
    protected $middleware = [
        // \App\Http\Middleware\TrustHosts::class,
        \App\Http\Middleware\TrustProxies::class,
        \Fruitcake\Cors\HandleCors::class,
        \App\Http\Middleware\PreventRequestsDuringMaintenance::class,
        \Illuminate\Foundation\Http\Middleware\ValidatePostSize::class,
        \App\Http\Middleware\TrimStrings::class,
        \Illuminate\Foundation\Http\Middleware\ConvertEmptyStringsToNull::class,
        \App\Http\Middleware\GetAccess::class           //この行を追加
    ];
    


24行目を追加しています、GetAccessは作成したミドルウェアのクラス名です。

これでアクセスカウンターが動作します。

ここからはアクセスカウンターの結果をビューに表示します。

アクセスカウンターをビューに表示

web.phpを下記にします。

Route::controller(AccessCounterController::class)->group(function() {
    Route::get('/access-count', 'index')->name('access-count.index');
});

下記のコマンドでコントローラーを作成します。

php artisan make:controller AccessCounterController

モデルのコードを下記にします。

<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;

class AccessCounter extends Model
{
    use HasFactory;

    protected $fillable = [
        'visitor_id',
        'page'
    ];

    public function accessCount()
    {
        return AccessCounter::all();
    }
}

コントローラーのコードを下記にします。

<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;
use App\Models\AccessCounter;

class AccessCounterController extends Controller
{
    public function index(AccessCounter $access)
    {
        $visitors = $access->accessCount();

        return view('access-count.index', compact('visitors'));
    }
}

ビューのコードを下記にします。

<body>
    <div class="container pt-5 pb-5">
        <h2 class="text-center">アクセス一覧</h2>
        <div class="mt-5">
            <table class="table" style="background-color: transparent !important;">
                <thead>
                    <tr class="border-left border-right">
                        <th class="border-bottom-0" scope="col">日付</th>
                        <th class="border-bottom-0" scope="col">ipアドレス</th>
                        <th class="border-bottom-0" scope="col">ページ</th>
                    </tr>
                </thead>
                <tbody>
                    @forelse($visitors as $visitor)
                        <tr class="border-bottom border-left border-right">
                            <td>{{ $visitor->created_at->format('Y年n月j日') }}</td>
                            <td>{{ $visitor->visitor_id }}</td>
                            <td>{{ $visitor->page }}</td>
                        </tr>
                    @empty
                        <tr>
                            <td>データがありません</td>
                            <td>データがありません</td>
                            <td>データがありません</td>
                        </tr>
                    @endforelse
                </tbody>
            </table>
        </div>
        <a href="/">トップページ</a>
    </div>
</body>

これでページにアクセスすると下記みたいな表示になると思います。(サンプルはbootstrapを使っていますが解説はbootstrapの説明をしていないので見た目が崩れているかもしれません)

これで完成です。