laravel spatie activitylog 記錄 git commit 歷程記錄

之前的文章 git commit 自動更新 Laravel+Vue3 前端顯示版本 是將版本異動記錄在檔案 version.json 裡。

這篇文章將使用 spatie/laravel-activitylog 來記錄「當 Git Tag 變動時」的活動,由於 Git Tag 的變動通常發生在 Git 操作(非 Model 變動)時,最適合的切入點是在你原本的 Artisan Command (version:update) 中手動觸發記錄。

以下是結合 spatie/laravel-activitylog 的實作方式:

安裝與設定(若尚未安裝)

Bash
composer require spatie/laravel-activitylog
php artisan vendor:publish --provider="Spatie\Activitylog\ActivitylogServiceProvider" --tag="activitylog-migrations"
php artisan migrate

修改 UpdateVersionJson.php

我們在指令中比對新舊 Tag,若不同則呼叫 activity() 進行記錄。

PHP
namespace App\Console\Commands;

use Illuminate\Console\Command;
use Illuminate\Support\Facades\File;
use Illuminate\Support\Facades\Process;

class UpdateVersionJson extends Command
{
    protected $signature = 'version:update';

    public function handle()
    {
        $path = base_path('version.json');
        
        // 1. 取得舊資料
        $oldData = File::exists($path) ? json_decode(File::get($path), true) : [];
        $oldTag = $oldData['tag'] ?? 'none';

        // 2. 取得新資料 (使用 Process 門面相容 Windows/Linux)
        $currentHash = trim(Process::run('git rev-parse --short HEAD')->output());
        $tagResult = Process::run('git describe --tags --abbrev=0');
        $currentTag = $tagResult->successful() ? trim($tagResult->output()) : 'v0.0.0';

        $newData = [
            'current' => $currentHash,
            'previous' => $oldData['current'] ?? 'none',
            'tag' => $currentTag,
            'updated_at' => now()->toDateTimeString(),
        ];

        // 3. 寫入 JSON
        File::put($path, json_encode($newData, JSON_PRETTY_PRINT));

        // 4. 當 Tag 有變動時,記錄到 Activity Log
        if ($currentTag !== $oldTag) {
            activity('system_update') // 設定 log 名稱
                ->withProperties([
                    'old_tag' => $oldTag,
                    'new_tag' => $currentTag,
                    'commit'  => $currentHash
                ])                
                ->log("系統版本已從 {$oldTag} 更新至 {$currentTag}");
                
            $this->info("偵測到 Tag 變動,已記錄至 Activity Log");
        }

        $this->info("Version.json 已更新。");
    }
}

在 Vue 中讀取活動記錄

可以建立一個 API 來取得這些記錄。在 routes/api.php:

PHP
use Spatie\Activitylog\Models\Activity;

Route::get('/system-logs', function () {
    return Activity::where('log_name', 'system_update')
        ->latest()
        ->get();
});

進階:讓所有 Model 記錄都帶有當前 Tag

如果希望不只是 Tag 變動才記錄,而是平時使用者新增/刪除資料時,Log 裡也能自動標記當時的 Git Tag,可以在 AppServiceProvider.php 這樣設定:

PHP
namespace App\Providers;

use Illuminate\Support\ServiceProvider;
use Spatie\Activitylog\Models\Activity;
use Illuminate\Support\Facades\File;

class AppServiceProvider extends ServiceProvider
{
    public function boot()
    {
        // 每一條 Activity Log 儲存前,自動注入當前 version.json 的 Tag
        Activity::creating(function (Activity $activity) {
            $path = base_path('version.json');
            if (File::exists($path)) {
                $version = json_decode(File::get($path), true);
                // 將版本資訊存入 properties 欄位
                $activity->properties = $activity->properties->put('app_version', $version['tag'] ?? 'n/a');
            }
        });
    }
}

這樣做的好處:

  1. 精準記錄:只有當你真正執行 git tag 並觸發 commit 時,才會產生一條「版本更新」的活動記錄。
  2. 追溯性:日後你在查看 activity_log 資料表時,你會清楚知道某一次的資料錯誤(例如訂單損壞)是發生在系統的哪一個版本(Tag)期間。
  3. Windows 相容:使用 Process::run 避開了 2>/dev/null 的相容性問題。

測試的 Tag

Bash
git tag v1.0.0
php artisan version:update

subject(被操作的對象) & causer(執行操作的人)

spatie/laravel-activitylog 中,subject 指的是被操作的對象(例如哪個版本、哪個 Model),而 causer 指的是執行操作的人(通常是 User)。

UpdateVersionJson 指令中,因為這是一個自動化腳本,通常沒有「登入使用者」,但我們可以手動指定。

現在來建立一個專門的 VersionLog Model,並將它與 ActivityLog 完整串接起來。

這能讓版本資訊不僅僅存在於 JSON 檔案中,還能以結構化的方式儲存在資料庫,並成為活動記錄(Activity Log)的對象(Subject)。

建立 Model 與 Migration

執行以下指令:

Bash
php artisan make:model VersionLog -m

編輯 database/migrations/xxxx_xx_xx_create_version_logs_table.php

PHP
public function up(): void
{
    Schema::create('version_logs', function (Blueprint $table) {
        $table->id();
        $table->string('tag')->nullable();      // Git Tag
        $table->string('current_hash');         // 當前 Commit Hash
        $table->string('previous_hash');        // 上次 Commit Hash
        $table->timestamp('committed_at')->nullable(); // Commit 時間
        $table->timestamps();
    });
}

執行遷移:

Bash
php artisan migrate

建立新的 Migration 增加欄位

為了完整保存 Tag Hash,我們需要執行兩個步驟:首先更新資料庫結構(Migration),接著優化 UpdateVersionJson 指令來抓取並儲存這些精確的資訊。

version_logs 資料表增加 tag_hash 欄位。

Bash
php artisan make:migration add_tag_hash_to_version_logs_table --table=version_logs

在產生的檔案中撰寫:

PHP
public function up(): void
{
    Schema::table('version_logs', function (Blueprint $table) {
        // 在 tag 欄位之後增加 tag_hash
        $table->string('tag_hash')->nullable()->after('tag');
    });
}

public function down(): void
{
    Schema::table('version_logs', function (Blueprint $table) {
        $table->dropColumn('tag_hash');
    });
}

執行遷移:php artisan migrate

設定 VersionLog Model

編輯 app/Models/VersionLog.php

PHP
namespace App\Models;

use Illuminate\Database\Eloquent\Model;
use Spatie\Activitylog\Traits\LogsActivity;
use Spatie\Activitylog\LogOptions;

class VersionLog extends Model
{
    // 如果你也想讓這個 Model 的變動被自動記錄,可以加上這行
    use LogsActivity;

    protected $fillable = ['tag', 'current_hash', 'previous_hash', 'committed_at'];

    public function getActivitylogOptions(): LogOptions
    {
        return LogOptions::defaults()
            ->logOnly(['tag', 'current_hash']);
    }
}

修改 Artisan 指令串接 Subject & Causer

現在修改 app/Console/Commands/UpdateVersionJson.php。會先建立 VersionLog 紀錄,再將它設為 Activity Log 的 subject

PHP
namespace App\Console\Commands;

use Illuminate\Console\Command;
use Illuminate\Support\Facades\File;
use Illuminate\Support\Facades\Process;
use App\Models\VersionLog;
use App\Models\User;

class UpdateVersionJson extends Command
{
    protected $signature = 'version:update';

    public function handle()
    {
        $path = base_path('version.json');
        $oldData = File::exists($path) ? json_decode(File::get($path), true) : [];

        // 1. 獲取 Git 資訊 (Windows 相容寫法)
        $currentHash = trim(Process::run('git rev-parse --short HEAD')->output());
        $tagResult = Process::run('git describe --tags --abbrev=0');
        $currentTag = $tagResult->successful() ? trim($tagResult->output()) : 'v0.0.0';
        
        // 取得 Commit 時間
        $commitTime = trim(Process::run('git log -1 --format=%ai')->output());

        // 2. 更新或建立資料庫紀錄 (作為 Subject)
        $versionLog = VersionLog::updateOrCreate(
            ['current_hash' => $currentHash],
            [
                'tag' => $currentTag,
                'previous_hash' => $oldData['current'] ?? 'none',
                'committed_at' => $commitTime,
            ]
        );

        // 3. 寫入 JSON (供前端使用)
        File::put($path, json_encode([
            'current' => $currentHash,
            'tag' => $currentTag,
            'updated_at' => now()->toDateTimeString(),
        ], JSON_PRETTY_PRINT));

        // 4. 只有當版本變動時,才產生一條活動記錄
        if ($oldData['tag'] !== $currentTag) {
            activity('system_update')
                ->performedOn($versionLog) // 設定 subject_id & subject_type
                ->causedBy(User::find(1))  // 手動指定執行者 (如管理員 ID)
                ->withProperties([
                    'from' => $oldData['tag'] ?? 'init',
                    'to' => $currentTag
                ])
                ->log("系統版本已標記為 {$currentTag}");

            $this->info("活動記錄已更新 (Subject: VersionLog #{$versionLog->id})");
        }
    }
}

成果確認

當執行 php artisan version:update 後,在資料庫的 activity_log 資料表中,會看到:

  • subject_type: App\Models\VersionLog
  • subject_id: 指向 version_logs 表的 ID
  • causer_id: 1 (管理員)
  • properties: 包含舊版與新版的 Tag 對比。

在 Vue 顯示完整日誌(API 範例)

修改 routes/api.php

PHP
use Spatie\Activitylog\Models\Activity;

Route::get('/updates', function () {
    // 預加載 causer 與 subject 資訊
    return Activity::with(['causer', 'subject'])
        ->where('log_name', 'system_update')
        ->latest()
        ->get();
});

為什麼這樣設計?

  1. 資料完整性version_logs 存儲純粹的版本歷史,activity_log 存儲「誰在何時觸發了版本變更」。
  2. 多型關聯:符合 Laravel 慣例,日後可以輕鬆擴展,例如在前端點擊一條 Log,就能直接跳轉到對應的 VersionLog 詳細資料。
  3. Git Tag 追蹤:所有的自動化與資料庫儲存都已經包含 Git Tag 資訊。

發佈留言

發佈留言必須填寫的電子郵件地址不會公開。 必填欄位標示為 *


內容索引