LaravelAPI回收站怎么设计

wen PHP项目 4

本文目录导读:

LaravelAPI回收站怎么设计

  1. 核心思路:SoftDeletes + 统一查询作用域
  2. 第一步:数据库与模型准备
  3. 第二步:设计 API 路由
  4. 第三步:控制器具体实现
  5. 第四步:高级优化与注意事项
  6. 最佳实践清单

设计一个既优雅又实用的 Laravel API 回收站功能,需要从 模型、控制器、中间件/全局作用域、以及 API 路由 四个层面来规划。

下面是主流且推荐的设计方案,结合了 Laravel 内置的 SoftDeletes 特性。

核心思路:SoftDeletes + 统一查询作用域

  • 模型层:使用 Illuminate\Database\Eloquent\SoftDeletes Trait。
  • 数据库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(...),创建 PostResourcePostCollection

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/trashedDELETE /posts/trashed/clear 都保持一致。
控制器 分离清晰:index (正常列表) / getTrashed (回收站) / restore / forceDelete
安全性 批量操作时验证 ids 数组;强制删除前检查是否已在回收站。
资源类 统一响应结构,便于前端处理。
维护 定期清理过期回收站数据,避免表体积膨胀。

这样做之后,你的 API 回收站功能既符合 RESTful 设计,又充分利用了 Laravel 的 Eloquent 特性,并且具备良好的扩展性。

抱歉,评论功能暂时关闭!