Laravel 5.4点赞系统关联问题:点击按钮重复创建点赞记录
Hey there, let's fix that duplicate like issue you're running into—super common pitfall when building like systems in Laravel, so don't worry, we'll get it sorted!
First, let's start with the core problem: right now, your code is just creating a new Like record every time the button is clicked, without checking if the user already liked that memo. Let's break down the fixes step by step, starting with your models and controller.
1. Double-Check Your Model Relationships
First, let's make sure your associations are set up correctly. Depending on whether you're using a direct many-to-many relationship or a dedicated Like model (for extra fields like timestamps), here's how they should look:
If using a dedicated Like model:
User.php
public function likes() { return $this->hasMany(Like::class); }
Memo.php
public function likes() { return $this->hasMany(Like::class); }
Like.php
public function user() { return $this->belongsTo(User::class); } public function memo() { return $this->belongsTo(Memo::class); }
If using a many-to-many pivot table (no dedicated Like model):
You can skip the Like model entirely and use Laravel's built-in pivot functionality. Just make sure your likes table has user_id and memo_id columns:
User.php
public function likedMemos() { return $this->belongsToMany(Memo::class, 'likes')->withTimestamps(); }
Memo.php
public function likedByUsers() { return $this->belongsToMany(User::class, 'likes')->withTimestamps(); }
2. Fix the Controller Logic to Prevent Duplicates
This is where the magic happens—we need to check if the user already liked the memo before creating a new record, and toggle the like state instead.
Option 1: Using a dedicated Like model
Update your AJAX handler in MemoController.php:
public function toggleLike(Request $request) { // Validate the request first $request->validate([ 'memo_id' => 'required|exists:memos,id' ]); $userId = auth()->id(); $memoId = $request->memo_id; // Check if the like already exists $existingLike = Like::where('user_id', $userId) ->where('memo_id', $memoId) ->first(); if ($existingLike) { // User already liked it—delete the like $existingLike->delete(); return response()->json(['status' => 'unliked', 'message' => 'Like removed']); } else { // User hasn't liked it—create the like Like::create([ 'user_id' => $userId, 'memo_id' => $memoId ]); return response()->json(['status' => 'liked', 'message' => 'Like added']); } }
Option 2: Using many-to-many relationships
If you went the pivot table route, use attach() and detach() with an existence check:
public function toggleLike(Request $request) { $request->validate([ 'memo_id' => 'required|exists:memos,id' ]); $memo = Memo::findOrFail($request->memo_id); $user = auth()->user(); if ($user->likedMemos()->where('memo_id', $memo->id)->exists()) { $user->likedMemos()->detach($memo->id); return response()->json(['status' => 'unliked']); } else { $user->likedMemos()->attach($memo->id); return response()->json(['status' => 'liked']); } }
3. Add a Database Constraint (Critical!)
Even with perfect code, race conditions (like a user clicking twice super fast) can create duplicates. Let's add a composite unique index to your likes table to block duplicates at the database level.
In your create_likes_table migration file:
public function up() { Schema::create('likes', function (Blueprint $table) { $table->id(); $table->foreignId('user_id')->constrained()->onDelete('cascade'); $table->foreignId('memo_id')->constrained()->onDelete('cascade'); $table->timestamps(); // Add this line to enforce unique user-memo pairs $table->unique(['user_id', 'memo_id']); }); }
Run php artisan migrate:refresh (or migrate:rollback then migrate) to apply this change.
4. Prevent Duplicate AJAX Requests
Finally, update your frontend code to disable the like button while the request is processing—this stops users from spamming clicks:
$('.like-button').on('click', function(e) { e.preventDefault(); const button = $(this); const memoId = button.data('memo-id'); // Disable the button to prevent double clicks button.prop('disabled', true); $.ajax({ url: '/memos/toggle-like', // Make sure this matches your route method: 'POST', data: { memo_id: memoId, _token: '{{ csrf_token() }}' }, success: function(response) { // Update the button UI (e.g., change icon/text) if (response.status === 'liked') { button.html('<i class="fas fa-heart text-red-500"></i> Liked'); } else { button.html('<i class="far fa-heart"></i> Like'); } button.prop('disabled', false); }, error: function() { button.prop('disabled', false); alert('Oops, something went wrong!'); } }); });
That should do it! With these changes, users won't be able to create duplicate likes, and the like button will toggle between liked/unliked states properly.
内容的提问来源于stack exchange,提问作者Maz

