在 Laravel-admin 后台开发附件/文件管理模块时,默认删除会直接硬删数据库,不删除物理文件、不执行自定义逻辑,极易造成服务器垃圾文件堆积。
本文提供可直接复制上线的完整方案:自定义单行删除 + 批量删除,统一删除逻辑、自动删文件、写日志、弹窗确认,完美解决原生删除的所有痛点。
目录
[控制器 Grid 配置](#控制器 Grid 配置)
[控制器原有 destroy 方法(可保留)](#控制器原有 destroy 方法(可保留))
[必须禁用 actions-\>disableDelete()](#必须禁用 actions->disableDelete())
[批量删除必须用 BatchAction 类](#批量删除必须用 BatchAction 类)
核心问题原理
laravel-admin 默认删除按钮:前端直接 AJAX 硬删数据库,不进入控制器 destroy() 方法
所以:自定义逻辑(删文件、日志、权限判断)全部失效
解决方案:禁用系统默认删除 + 自定义行删除动作类 接管删除逻辑
整体实现
控制器:保留原生 destroy(备用),不再使用
自定义单行删除动作类:接管单条删除
自定义批量删除动作类:接管批量删除
模型封装 deleteFile():统一处理物理文件删除
Grid 配置:关闭默认删除、注入自定义删除按钮
分步完整代码实现
模型层:封装文件删除逻辑
模型文件Attachment.php统一处理本地文件删除、日志记录,所有删除场景复用。
代码如下:
php
/**
* 删除服务器上的文件
*/
public function deleteFile()
{
try {
// 正确用法:用 Storage 删除(你的上传驱动是 admin)
$deleted = Storage::disk('admin')->delete($this->path);
Log::info('删除文件结果:'.($deleted ? '成功' : '文件不存在'), [
'path' => $this->path,
'url' => $this->url
]);
return true;
} catch (\Exception $e) {
Log::error('删除文件失败:'.$e->getMessage());
return false;
}
}
单条自定义删除(行操作)
创建行删除动作类
路径:app/Admin/Actions/DeleteAttachment.php
php
<?php
namespace App\Admin\Actions;
use Encore\Admin\Actions\RowAction;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Facades\Log;
class DeleteAttachment extends RowAction
{
public $name = '删除';
public function dialog()
{
$this->confirm('确定删除这个附件吗?文件会被永久删除!');
}
public function handle(Model $model)
{
try {
Log::info('===== 自定义单条删除执行 =====');
// 删除文件
$model->deleteFile();
// 删除数据库
$model->delete();
return $this->response()->success('删除成功!')->refresh();
} catch (\Exception $e) {
Log::error('删除失败:'.$e->getMessage());
return $this->response()->error('删除失败:'.$e->getMessage());
}
}
}
批量自定义删除
创建批量删除动作类
路径:app/Admin/Actions/BatchDeleteAttachment.php
php
<?php
namespace App\Admin\Actions;
use Encore\Admin\Actions\BatchAction;
use Illuminate\Database\Eloquent\Collection;
use Illuminate\Support\Facades\Log;
class BatchDeleteAttachment extends BatchAction
{
public $name = '批量删除';
public function handle(Collection $collection)
{
foreach ($collection as $model) {
try {
// 删除文件
$model->deleteFile();
// 删除数据库记录
$model->delete();
} catch (\Exception $e) {
Log::error('批量删除失败:'.$e->getMessage());
continue;
}
}
return $this->response()->success('批量删除成功')->refresh();
}
// 确认弹窗
public function dialog()
{
$this->confirm('确定删除选中的附件吗?文件会被永久删除!');
}
}
控制器 Grid 配置
AttachmentController.php 中 grid() 方法内,关闭默认删除,注入自定义按钮
php
/**
* Make a grid builder.
* @return Grid
*/
protected function grid()
{
$grid = new Grid(new Attachment());
$grid->column('id', __('ID'))->sortable();
$grid->column('name', __('原文件名称'))->limit(30);
$grid->column('thumbnail', __('预览'))->display(function () {
// 判断是否为图片
$isImage = in_array(strtolower($this->extension), ['jpg', 'jpeg', 'png', 'gif', 'bmp', 'webp']);
if ($isImage) {
return "<img src='{$this->url}' style='max-width:100px;max-height:100px;border-radius:4px;' />";
} else {
return "<i class='fa {$this->file_icon}' style='font-size:2rem;color:#1890ff;'></i>";
}
});
$grid->column('filename', __('文件名'))->limit(20);
$grid->column('type', __('类型'))->using(Attachment::getTypeOptions());
$grid->column('extension', __('扩展名'));
$grid->column('size', __('文件大小'))->display(function () {
return Attachment::formatFileSize($this->size);
});
$grid->column('dimensions', __('尺寸'))->display(function () {
// 判断是否为图片
$isImage = in_array(strtolower($this->extension), ['jpg', 'jpeg', 'png', 'gif', 'bmp', 'webp']);
if ($isImage && $this->width && $this->height) {
return "{$this->width} x {$this->height}";
}
return '-';
});
$grid->column('uploaded_by', __('上传人'))->display(function () {
return $this->uploader ? $this->uploader->name : '-';
});
$grid->column('created_at', __('上传时间'))->display(function ($time) {
return date('Y-m-d H:i:s', strtotime($time));
});
$grid->disableFilter();
$grid->disableExport();
// ======================
// 核心:禁用默认删除,使用自定义删除
// ======================
$grid->actions(function ($actions) {
$actions->disableDelete(); // 关闭默认删除
$actions->disableView(); // 关闭默认查看
$actions->add(new \App\Admin\Actions\DeleteAttachment()); // 使用自定义删除
});
// 批量删除
$grid->batchActions(function ($batch) {
$batch->disableDelete();
$batch->add(new \App\Admin\Actions\BatchDeleteAttachment());
});
return $grid;
}
控制器原有 destroy 方法(可保留)
因为我们已经用自定义动作类接管删除,此方法不会执行,可保留做备用。
php
public function destroy($id)
{
// 原生销毁方法,当前方案不再使用
}
完整执行流程
列表点击【删除】按钮
触发自定义 DeleteAttachment 动作类
弹窗二次确认
执行 $model->deleteFile() 删除服务器文件
执行 $model->delete() 删除数据库记录
返回成功提示 + 刷新列表
自动写入日志,完整记录删除行为
关键避坑点
不要使用默认删除
默认删除不走控制器、不走自定义代码,只删库不删文件
必须禁用 $actions->disableDelete()
不禁用会同时存在两套删除,逻辑冲突
文件路径必须对应
使用 public_path() 拼接绝对路径,适配本地上传目录
编辑删除旧文件复用同一方法
编辑重新上传时,直接调用 $old->deleteFile() 复用逻辑
批量删除必须用 BatchAction 类
低版本 laravel-admin 不支持闭包批量操作,会直接报错
总结
本文完整实现了 Laravel-admin 附件模块安全删除方案,核心总结:
解决痛点:修复原生删除只删库不删文件的严重问题。
方案标准:使用官方 Action 扩展,无兼容风险、结构清晰。
功能完整:支持单行删除、批量删除、确认弹窗、文件删除、日志记录。
高度复用:模型封装删除方法,编辑 / 删除场景共用一套逻辑。
线上安全:异常捕获、错误提示、日志追踪,生产环境可直接使用。
该方案是 Laravel-admin 文件/附件管理的标准最佳实践,几乎所有后台项目都能直接套用。