Physical Address
304 North Cardinal St.
Dorchester Center, MA 02124
Physical Address
304 North Cardinal St.
Dorchester Center, MA 02124

之前的文章 git commit 自動更新 Laravel+Vue3 前端顯示版本 是將版本異動記錄在檔案 version.json 裡。
這篇文章將使用 spatie/laravel-activitylog 來記錄「當 Git Tag 變動時」的活動,由於 Git Tag 的變動通常發生在 Git 操作(非 Model 變動)時,最適合的切入點是在你原本的 Artisan Command (version:update) 中手動觸發記錄。
以下是結合 spatie/laravel-activitylog 的實作方式:
composer require spatie/laravel-activitylog
php artisan vendor:publish --provider="Spatie\Activitylog\ActivitylogServiceProvider" --tag="activitylog-migrations"
php artisan migrateUpdateVersionJson.php我們在指令中比對新舊 Tag,若不同則呼叫 activity() 進行記錄。
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 已更新。");
}
}可以建立一個 API 來取得這些記錄。在 routes/api.php:
use Spatie\Activitylog\Models\Activity;
Route::get('/system-logs', function () {
return Activity::where('log_name', 'system_update')
->latest()
->get();
});如果希望不只是 Tag 變動才記錄,而是平時使用者新增/刪除資料時,Log 裡也能自動標記當時的 Git Tag,可以在 AppServiceProvider.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');
}
});
}
}git tag 並觸發 commit 時,才會產生一條「版本更新」的活動記錄。activity_log 資料表時,你會清楚知道某一次的資料錯誤(例如訂單損壞)是發生在系統的哪一個版本(Tag)期間。Process::run 避開了 2>/dev/null 的相容性問題。git tag v1.0.0
php artisan version:updatesubject(被操作的對象) & causer(執行操作的人) 在 spatie/laravel-activitylog 中,subject 指的是被操作的對象(例如哪個版本、哪個 Model),而 causer 指的是執行操作的人(通常是 User)。
在 UpdateVersionJson 指令中,因為這是一個自動化腳本,通常沒有「登入使用者」,但我們可以手動指定。
現在來建立一個專門的 VersionLog Model,並將它與 ActivityLog 完整串接起來。
這能讓版本資訊不僅僅存在於 JSON 檔案中,還能以結構化的方式儲存在資料庫,並成為活動記錄(Activity Log)的對象(Subject)。
執行以下指令:
php artisan make:model VersionLog -m編輯 database/migrations/xxxx_xx_xx_create_version_logs_table.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();
});
}執行遷移:
php artisan migrate為了完整保存 Tag Hash,我們需要執行兩個步驟:首先更新資料庫結構(Migration),接著優化 UpdateVersionJson 指令來抓取並儲存這些精確的資訊。
為 version_logs 資料表增加 tag_hash 欄位。
php artisan make:migration add_tag_hash_to_version_logs_table --table=version_logs在產生的檔案中撰寫:
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
編輯 app/Models/VersionLog.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']);
}
}現在修改 app/Console/Commands/UpdateVersionJson.php。會先建立 VersionLog 紀錄,再將它設為 Activity Log 的 subject。
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 資料表中,會看到:
App\Models\VersionLogversion_logs 表的 ID1 (管理員)修改 routes/api.php:
use Spatie\Activitylog\Models\Activity;
Route::get('/updates', function () {
// 預加載 causer 與 subject 資訊
return Activity::with(['causer', 'subject'])
->where('log_name', 'system_update')
->latest()
->get();
});version_logs 存儲純粹的版本歷史,activity_log 存儲「誰在何時觸發了版本變更」。VersionLog 詳細資料。