Laravel 7实现用户单次测试限制及视图切换功能需求求助
解决方案:限制Laravel 7用户仅完成一次测试
要实现仅注册用户可参与、且每个用户只能完成一次测试的需求,我们可以通过「检查用户已有测试结果」的方式来实现,同时要兼顾前端访问限制和后端提交验证,防止用户绕过限制。
步骤1:确保User模型与Result模型的关联正确
首先,你的User模型需要和Result模型建立一对一关联(因为每个用户只能有一个测试结果),打开app/User.php添加关联:
public function userResults() { return $this->hasOne(Result::class); }
注:如果之前用的是
hasMany,也能正常工作,但hasOne更贴合「仅一次测试」的业务逻辑。
步骤2:修改TestController的index方法,拦截已完成测试的用户
在测试页面的入口方法里,先检查当前登录用户是否已经有测试结果,如果有就直接返回「已完成测试」的视图:
public function index() { // 核心:检查用户是否已有测试结果 if (auth()->user()->userResults()->exists()) { // 返回自定义的已完成提示视图,比如client.test_completed return view('client.test_completed'); } $categories = Category::with(['categoryQuestions' => function ($query) { $query->orderBy('id') ->with(['questionOptions' => function ($query) { $query->orderBy('id'); }]); }]) ->whereHas('categoryQuestions') ->get(); return view('client.test', compact('categories')); }
步骤3:在store方法中添加二次验证,防止恶意提交
即使前端拦截了,也可能有用户通过直接POST请求绕过限制,所以在提交测试结果的方法里也要加检查:
public function store(StoreTestRequest $request) { // 二次验证:确保用户未完成过测试 if (auth()->user()->userResults()->exists()) { return redirect()->route('client.test.index') ->with('error', '你已经完成过这个测试,无法再次提交'); } $options = Option::find(array_values($request->input('questions'))); $result = auth()->user()->userResults()->create([ 'total_points' => $options->sum('points') ]); $questions = $options->mapWithKeys(function ($option) { return [$option->question_id => [ 'option_id' => $option->id, 'points' => $option->points ] ]; })->toArray(); $result->questions()->sync($questions); return redirect()->route('client.results.show', $result->id); }
步骤4:创建「已完成测试」的视图
在resources/views/client/目录下新建test_completed.blade.php,添加提示内容:
<div class="container mt-5"> <div class="card"> <div class="card-body text-center"> <h2 class="card-title">测试已完成</h2> <p class="card-text">你已经参与过这个测试,无法再次进行。</p> <!-- 可以添加查看结果的链接 --> <a href="{{ route('client.results.show', auth()->user()->userResults->id) }}" class="btn btn-primary mt-3">查看我的测试结果</a> </div> </div> </div>
进阶:用中间件封装检查逻辑(可选)
如果后续有多个路由需要限制用户重复测试,可以把检查逻辑抽成中间件,让代码更简洁:
- 创建中间件:
php artisan make:middleware PreventDuplicateTest
- 编辑中间件
app/Http/Middleware/PreventDuplicateTest.php:
public function handle($request, Closure $next) { // 仅对登录用户检查 if (auth()->check() && auth()->user()->userResults()->exists()) { return redirect()->route('client.test.index'); // 或者直接返回已完成视图:return view('client.test_completed'); } return $next($request); }
- 在
app/Http/Kernel.php中注册中间件:
protected $routeMiddleware = [ // ... 其他中间件 'prevent.duplicate.test' => \App\Http\Middleware\PreventDuplicateTest::class, ];
- 在路由中使用中间件:
Route::group(['middleware' => ['auth', 'prevent.duplicate.test']], function () { Route::get('/test', 'TestController@index')->name('client.test.index'); Route::post('/test', 'TestController@store')->name('client.test.store'); });
这样就完整实现了「用户仅能完成一次测试,已完成用户访问测试页加载专属视图」的需求。
内容的提问来源于stack exchange,提问作者luislalohdz




