Laravel 数据库重播种:安全添加新权限而不影响现有数据

在 laravel 中,通过将 `create()` 替换为 `firstorcreate()` 可安全重运行种子文件,避免重复插入已有权限或角色,同时保留全部历史数据与迁移记录。

当你使用 Spatie 的 laravel-permission 包管理角色与权限时,常需在开发过程中动态扩展权限(例如新增 blog read、blog update)。此时若直接执行 php artisan db:seed --class=RoleAndPermissionSeeder,原代码中的 Permission::create() 和 Role::create() 会因主键/唯一约束抛出异常(如 Integrity constraint violation),或导致重复记录。

✅ 正确做法是改用 firstOrCreate() —— 它会先按条件查询,仅当记录不存在时才创建:

// 替换原 Permission::create() → 使用 firstOrCreate()
foreach ($permissions as $permission) {
    Permission::firstOrCreate(['name' => $permission]);
}

// 同样处理角色,确保幂等性
$admin = Role::firstOrCreate(['name' => 'admin']);
$member = Role::firstOrCreate(['name' => 'member']);
Role::firstOrCreate(['name' => 'super-admin']);

? 关键优势:

  • 完全幂等:无论执行多少次,权限和角色只存在一份;
  • 零数据丢失:不删除表、不回滚迁移、不影响用户已分配的权限;
  • 兼容 syncPermissions():该方法底层使用 sync(),自动处理新增/移除关联,不会覆盖已有授权关系。

⚠️ 注意事项:

  • 确保 name 字段在 permissions 和 roles 表中具有唯一索引(Spatie 包默认已建,可检查迁移文件确认);
  • 若后续需移除旧权限,firstOrCreate() 不会自动清理,应单独编写逻辑(如 Permission::whereNotIn('name', $allowedNames)->delete());
  • 生产环境慎用 db:seed;建议将权限变更纳入版本化迁移(如新建 AddBlogPermissionsToPermissionsTable 迁移),种子文件仅用于本地/CI 初始化。

最终,你只需更新 $permissions 数组并重新运行种子命令即可:

php artisan db:seed --class=RoleAndPermissionSeeder

系统将自动注入新权限(如 'blog read'),而原有权限、角色及用户授权关系毫发无损。