Seeder 的环境隔离与幂等性
Seeder 有两类用途:填充必要初始数据(角色、权限、默认分类)和填充开发测试数据(假用户、假任务)。两者要分开,生产环境只运行前者——否则测试数据会进入生产库。
Seeder 结构设计
database/seeders/
├── DatabaseSeeder.php ← 入口,调度其他 Seeder
├── RolePermissionSeeder.php ← 必要数据(生产+开发都运行)
├── DefaultCategorySeeder.php ← 必要数据
└── DevDataSeeder.php ← 开发专用(只在 local/staging 运行)
<?php
// database/seeders/DatabaseSeeder.php
namespace Database\Seeders;
use Illuminate\Database\Seeder;
class DatabaseSeeder extends Seeder
{
public function run(): void
{
// 必要数据:所有环境都运行
$this->call([
RolePermissionSeeder::class,
DefaultCategorySeeder::class,
]);
// 开发/Staging 测试数据:只在非生产环境运行
if (!app()->isProduction()) {
$this->call([
DevDataSeeder::class,
]);
}
}
}
幂等性 Seeder(可重复执行)
<?php
// database/seeders/RolePermissionSeeder.php
namespace Database\Seeders;
use Illuminate\Database\Seeder;
use Spatie\Permission\Models\Role;
use Spatie\Permission\Models\Permission;
class RolePermissionSeeder extends Seeder
{
public function run(): void
{
// 重置权限缓存(必须)
app()[\Spatie\Permission\PermissionRegistrar::class]->forgetCachedPermissions();
// 幂等写法:firstOrCreate / updateOrCreate(重复执行不会报错)
$permissions = [
'tasks.create',
'tasks.read',
'tasks.update',
'tasks.delete',
'projects.create',
'projects.manage',
'users.manage',
'billing.manage',
];
foreach ($permissions as $permission) {
Permission::firstOrCreate(['name' => $permission, 'guard_name' => 'web']);
Permission::firstOrCreate(['name' => $permission, 'guard_name' => 'api']);
}
// 创建角色并分配权限
$adminRole = Role::firstOrCreate(['name' => 'admin']);
$adminRole->syncPermissions($permissions); // sync 是幂等的
$memberRole = Role::firstOrCreate(['name' => 'member']);
$memberRole->syncPermissions(['tasks.create', 'tasks.read', 'tasks.update']);
$viewerRole = Role::firstOrCreate(['name' => 'viewer']);
$viewerRole->syncPermissions(['tasks.read']);
$this->command->info('✅ 角色和权限已初始化');
}
}
开发数据 Seeder
<?php
// database/seeders/DevDataSeeder.php
namespace Database\Seeders;
use Illuminate\Database\Seeder;
use App\Models\User;
use App\Models\Task;
use App\Models\Project;
class DevDataSeeder extends Seeder
{
public function run(): void
{
// 创建固定的测试账号(方便开发时登录)
$admin = User::factory()->create([
'name' => 'Admin User',
'email' => 'admin@taskflow.test',
]);
$admin->assignRole('admin');
$member = User::factory()->create([
'name' => 'Test Member',
'email' => 'member@taskflow.test',
]);
$member->assignRole('member');
// 创建项目
$project = Project::factory()->create([
'user_id' => $admin->id,
'name' => '演示项目',
]);
// 为测试用户创建各种状态的任务
Task::factory()->count(5)->pending()->forUser($admin)->create();
Task::factory()->count(3)->done()->forUser($admin)->create();
Task::factory()->count(2)->overdue()->forUser($admin)->create();
Task::factory()->count(10)->forUser($member)->create();
// 带项目关联的任务
Task::factory()->count(8)->create([
'user_id' => $admin->id,
'project_id' => $project->id,
]);
$this->command->info("✅ 测试数据已创建:admin@taskflow.test / member@taskflow.test(密码:password)");
}
}
执行 Seeder
# 执行所有 Seeder(不重置数据库)
php artisan db:seed
# 只执行特定 Seeder
php artisan db:seed --class=RolePermissionSeeder
# 重置数据库并重新运行所有 Seeder(开发常用)
php artisan migrate:fresh --seed
# 生产环境:只运行必要数据 Seeder
php artisan db:seed --class=RolePermissionSeeder
# 或者在 DatabaseSeeder 中已通过 isProduction() 控制
# 生产环境安全执行(有确认提示)
php artisan db:seed --force # 跳过确认(CI/CD 中用)
避免重复数据的技巧
// 方法一:truncate + 重新填充(只适合开发环境)
public function run(): void
{
if (!app()->isProduction()) {
Task::truncate();
}
// ... 填充数据
}
// 方法二:检查数量,只在空表时填充
public function run(): void
{
if (Role::count() === 0) {
// 首次运行才填充
}
}
// 方法三:updateOrCreate(最安全,推荐用于必要数据)
public function run(): void
{
$categories = [
['slug' => 'work', 'name' => '工作'],
['slug' => 'personal', 'name' => '个人'],
['slug' => 'shopping', 'name' => '购物'],
];
foreach ($categories as $category) {
Category::updateOrCreate(
['slug' => $category['slug']], // 查找条件
['name' => $category['name']] // 更新或创建的数据
);
}
}
下一章:认证与权限:Sanctum、Gates 与 Policies——数据模型准备好了,下一步是认证系统。Laravel Sanctum 提供 SPA 认证和 API Token 两种模式,Policies 让权限控制精细到单条记录。