本文目录导读:

设计一个既优雅又实用的 Laravel API 回收站功能,需要从 模型、控制器、中间件/全局作用域、以及 API 路由 四个层面来规划。
下面是主流且推荐的设计方案,结合了 Laravel 内置的 SoftDeletes 特性。
核心思路:SoftDeletes + 统一查询作用域
- 模型层:使用
Illuminate\Database\Eloquent\SoftDeletesTrait。 - 数据库:
deleted_at字段 (datetime, 可空)。 - API 层:设计专门的回收站相关路由和控制器方法。
第一步:数据库与模型准备
迁移文件添加 deleted_at 字段
// database/migrations/xxxx_xx_xx_add_softdeletes_to_posts_table.php
Schema::table('posts', function (Blueprint $table) {
$table->softDeletes(); // 添加 deleted_at 字段
});
模型引入 SoftDeletes
// app/Models/Post.php
namespace App\Models;
use Illuminate\Database\Eloquent\SoftDeletes;
use Illuminate\Database\Eloquent\Model;
class Post extends Model
{
use SoftDeletes; // 关键 Trait
protected $fillable = ['title', 'content'];
// 1. 正常查询会自动排除被软删除的记录
// Post::all() -> 只返回 deleted_at 为 null 的记录
// 2. 定义自定义的已删除作用域
public function scopeOnlyTrashed($query)
{
return $query->whereNotNull('deleted_at');
}
}
第二步:设计 API 路由
建议遵循 RESTful 风格,并新增一组专门操作回收站的端点。
| 功能 | HTTP 方法 | 路由 | 控制器方法 | 说明 |
|---|---|---|---|---|
| 获取回收站列表 | GET | /api/posts/trashed |
getTrashed |
查看所有已软删除的记录 |
| 单条记录还原 | PATCH | /api/posts/{post}/restore |
restore |
恢复指定 ID 的记录 |
| 批量还原 | PATCH | /api/posts/restore |
restoreMultiple |
根据 ID 数组批量恢复 |
| 永久删除 (单条) | DELETE | /api/posts/{post}/force-delete |
forceDelete |
彻底从数据库删除 |
| 批量永久删除 | DELETE | /api/posts/force-delete |
forceDeleteMultiple |
批量永久删除 |
| 清空回收站 | DELETE | /api/posts/trashed/clear |
clearTrashed |
删除回收站中所有记录 |
路由定义示例 (routes/api.php):
Route::prefix('posts')->group(function () {
Route::get('/', [PostController::class, 'index']); // 正常列表
Route::post('/', [PostController::class, 'store']);
Route::get('/{post}', [PostController::class, 'show']);
Route::put('/{post}', [PostController::class, 'update']);
Route::delete('/{post}', [PostController::class, 'destroy']); // 普通删除 -> 软删除
// ===== 回收站专属路由 =====
Route::get('/trashed', [PostController::class, 'getTrashed']); // 回收站列表
Route::patch('/restore', [PostController::class, 'restoreMultiple']); // 批量还原 (需置于具体ID之前)
Route::delete('/force-delete', [PostController::class, 'forceDeleteMultiple']); // 批量强制删除
Route::delete('/trashed/clear', [PostController::class, 'clearTrashed']); // 清空回收站
// 带模型绑定的路由
Route::patch('/{post}/restore', [PostController::class, 'restore']);
Route::delete('/{post}/force-delete', [PostController::class, 'forceDelete']);
});
第三步:控制器具体实现
// app/Http/Controllers/PostController.php
namespace App\Http\Controllers;
use App\Models\Post;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Validator;
class PostController extends Controller
{
/**
* 1. 获取回收站列表 (分页)
*/
public function getTrashed(Request $request)
{
// onlyTrashed() 是 Eloquent 全局作用域提供的,配合 SoftDeletes
$posts = Post::onlyTrashed()
->orderBy('deleted_at', 'desc')
->paginate($request->per_page ?? 15);
return response()->json([
'success' => true,
'data' => $posts->items(),
'meta' => [
'total' => $posts->total(),
'per_page' => $posts->perPage(),
'current_page' => $posts->currentPage(),
'last_page' => $posts->lastPage(),
],
]);
}
/**
* 2. 还原单条记录
*/
public function restore($id)
{
// 注意:withTrashed() 允许查找已被软删除的记录
$post = Post::withTrashed()->findOrFail($id);
// 检查是否已经在正常状态
if (!$post->trashed()) {
return response()->json([
'success' => false,
'message' => '该记录未在回收站中。'
], 400);
}
$post->restore();
return response()->json([
'success' => true,
'message' => '记录已成功还原。',
'data' => $post
]);
}
/**
* 3. 批量还原
*/
public function restoreMultiple(Request $request)
{
$validator = Validator::make($request->all(), [
'ids' => 'required|array',
'ids.*' => 'integer|exists:posts,id',
]);
if ($validator->fails()) {
return response()->json(['success' => false, 'errors' => $validator->errors()], 422);
}
// 只还原那些确实在回收站的记录
$restoredCount = Post::onlyTrashed()
->whereIn('id', $request->ids)
->restore(); // restore() 在批量作用域下生效
return response()->json([
'success' => true,
'message' => "成功还原 {$restoredCount} 条记录。",
'count' => $restoredCount
]);
}
/**
* 4. 永久删除单条
*/
public function forceDelete($id)
{
$post = Post::withTrashed()->findOrFail($id);
// 可选:限制只能删除回收站中的记录
if (!$post->trashed()) {
return response()->json([
'success' => false,
'message' => '只能从回收站进行永久删除,请先删除该记录。'
], 400);
}
$post->forceDelete();
return response()->json([
'success' => true,
'message' => '记录已被永久删除。'
]);
}
/**
* 5. 批量永久删除
*/
public function forceDeleteMultiple(Request $request)
{
$validator = Validator::make($request->all(), [
'ids' => 'required|array',
'ids.*' => 'integer|exists:posts,id',
]);
if ($validator->fails()) {
return response()->json(['success' => false, 'errors' => $validator->errors()], 422);
}
$deletedCount = Post::onlyTrashed()
->whereIn('id', $request->ids)
->forceDelete();
return response()->json([
'success' => true,
'message' => "已永久删除 {$deletedCount} 条记录。",
'count' => $deletedCount
]);
}
/**
* 6. 清空回收站
*/
public function clearTrashed()
{
$deletedCount = Post::onlyTrashed()->forceDelete();
return response()->json([
'success' => true,
'message' => "回收站已清空,共永久删除 {$deletedCount} 条记录。",
'count' => $deletedCount
]);
}
// ===== 标准 CRUD (仅展示软删除部分) =====
/**
* 标准删除 -> 执行软删除
*/
public function destroy($id)
{
$post = Post::findOrFail($id);
// 执行软删除
$post->delete();
return response()->json([
'success' => true,
'message' => '记录已移入回收站。'
]);
}
}
第四步:高级优化与注意事项
使用资源类统一响应格式
避免手动写 response()->json(...),创建 PostResource 和 PostCollection。
php artisan make:resource PostResource php artisan make:resource PostCollection
控制器返回:
use App\Http\Resources\PostResource;
public function getTrashed(Request $request)
{
$posts = Post::onlyTrashed()->paginate();
return PostResource::collection($posts);
}
public function restore($id)
{
$post = Post::withTrashed()->findOrFail($id);
$post->restore();
return new PostResource($post);
}
在 forceDelete 时处理关联外键
如果模型有 hasMany 关联,且外键 onDelete('set null') 或 cascade 配置不当,强制删除主记录可能导致数据不一致,建议在模型 booted 方法中添加监听:
protected static function booted()
{
static::forceDeleting(function ($post) {
// 永久删除前,手动清理关联数据
$post->comments()->delete(); // 或者 forceDelete()
});
}
回收站过期自动清理 (Cron Job)
在 app/Console/Kernel.php 中计划一个任务,定期清理超过 N 天的回收站数据:
// command: php artisan app:clear-old-trashed-posts
$schedule->command('app:clear-old-trashed-posts')->dailyAt('03:00');
命令实现:
public function handle()
{
$days = config('app.recycle_bin_retention_days', 30);
$count = Post::onlyTrashed()
->where('deleted_at', '<', now()->subDays($days))
->forceDelete();
$this->info("已清理 {$count} 条超过 {$days} 天的回收站记录。");
}
权限控制 (Policy/Laravel Permission)
确保不是所有用户都能操作回收站,在控制器中使用 authorize:
public function forceDelete($id)
{
$this->authorize('forceDelete', Post::class); // 需要管理员权限
// ...
}
最佳实践清单
| 类别 | 建议 |
|---|---|
| 模型 | 必须使用 SoftDeletes,自定义 onlyTrashed 作用域。 |
| 路由 | 从 GET /posts/trashed 到 DELETE /posts/trashed/clear 都保持一致。 |
| 控制器 | 分离清晰:index (正常列表) / getTrashed (回收站) / restore / forceDelete。 |
| 安全性 | 批量操作时验证 ids 数组;强制删除前检查是否已在回收站。 |
| 资源类 | 统一响应结构,便于前端处理。 |
| 维护 | 定期清理过期回收站数据,避免表体积膨胀。 |
这样做之后,你的 API 回收站功能既符合 RESTful 设计,又充分利用了 Laravel 的 Eloquent 特性,并且具备良好的扩展性。
版权声明:NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播