如何在Flask+SQLite3中实现防重复点赞的点赞按钮功能
你现在碰到的核心问题是没法追踪已点赞的用户,导致重复点赞,而且现有的likers字段每次都会被新用户名覆盖,存不下多个点赞者信息。我从数据库优化、后端逻辑修复、前端状态判断三个部分帮你搞定这个问题。
一、优化数据库存储方式
你的likers字段是text类型,目前只能存单个用户名。这里有两种方案,你可以根据现有业务量选:
方案1:保留现有表,用分隔符存多个用户名
如果不想大改现有表结构,可以用逗号(或者其他不会出现在用户名里的分隔符)把多个用户名拼接起来,比如"user1,user2,user3"。不需要改表结构(text类型已经能存这种字符串),直接在逻辑里处理就行。
方案2:新建关联表(更规范的数据库设计)
这是更推荐的方式——用分隔符存数据后期查询、修改效率低,还不符合关系型数据库的设计规范。新建一个post_likes表来记录帖子和点赞用户的关联:
conn = sqlite3.connect('data/detailpost.db') c = conn.cursor() c.execute("""CREATE TABLE post_likes( post_oid INTEGER, liker_username TEXT, PRIMARY KEY(post_oid, liker_username) # 联合主键,天生防止同一用户重复点赞 )""") conn.commit() conn.close()
这个表用post_oid和liker_username做联合主键,同一用户给同一帖子点赞会直接触发数据库的完整性约束,不用自己写判断逻辑,查询点赞数、点赞者也更灵活。
二、修复后端点赞逻辑
先以方案1为例,修改你的后端代码,实现“先查是否已点赞,再决定是否更新点赞数和likers”:
@app.route('/social/post/like',methods=['POST']) def likedofsocial(): if request.method=='POST': oid = request.form['oid'] mainusernameofliker = request.form['username'] conn = sqlite3.connect('data/detailpost.db') c = conn.cursor() # 先查目标帖子的现有点赞数据 c.execute("SELECT likes, likers FROM postdetail WHERE oid=?", (oid,)) post_data = c.fetchone() if not post_data: conn.close() return jsonify({'status': 'error', 'msg': '帖子不存在'}) current_likes, current_likers = post_data # 把存的字符串转成列表方便判断 likers_list = current_likers.split(',') if current_likers else [] # 检查用户是不是已经点过赞了 if mainusernameofliker in likers_list: conn.close() return jsonify({'status': 'error', 'msg': '你已经点赞过这个帖子啦'}) # 更新点赞数和likers字段 new_likes = current_likes + 1 new_likers = ','.join(likers_list + [mainusernameofliker]) c.execute("""UPDATE postdetail SET likes=?, likers=? WHERE oid=?""", (new_likes, new_likers, oid)) conn.commit() conn.close() return jsonify({'status': 'success', 'likes': new_likes})
如果用方案2(推荐),后端代码会更简洁,还能避免SQL注入风险:
@app.route('/social/post/like',methods=['POST']) def likedofsocial(): if request.method=='POST': oid = request.form['oid'] mainusernameofliker = request.form['username'] conn = sqlite3.connect('data/detailpost.db') c = conn.cursor() try: # 尝试插入点赞记录,联合主键会阻止重复插入 c.execute("INSERT INTO post_likes(post_oid, liker_username) VALUES(?, ?)", (oid, mainusernameofliker)) # 插入成功就更新帖子的点赞数 c.execute("UPDATE postdetail SET likes = likes + 1 WHERE oid=?", (oid,)) conn.commit() # 获取更新后的点赞数返回给前端 c.execute("SELECT likes FROM postdetail WHERE oid=?", (oid,)) new_likes = c.fetchone()[0] conn.close() return jsonify({'status': 'success', 'likes': new_likes}) except sqlite3.IntegrityError: # 触发完整性约束,说明重复点赞了 conn.close() return jsonify({'status': 'error', 'msg': '你已经点赞过这个帖子啦'})
⚠️ 注意:你原来的代码里用SELECT *,oid FROM postdetail WHERE oid="+oid"存在严重的SQL注入风险,一定要用参数化查询(就像上面代码里的?占位符),别直接拼字符串!
三、实现前端状态判断
前端加载帖子时,要检查当前登录用户是否在点赞列表里(方案1),或者提前获取“是否已点赞”的标记(方案2),然后设置按钮状态:
方案1的前端逻辑示例(JavaScript):
// 假设当前登录用户存在currentUser变量里,帖子数据存在postData里 const likeButton = document.getElementById(`like-btn-${postData.oid}`); const likers = postData.likers ? postData.likers.split(',') : []; if (likers.includes(currentUser)) { likeButton.disabled = true; likeButton.textContent = `已点赞(${postData.likes})`; likeButton.classList.add('liked'); // 加个样式区分已点赞状态 } else { likeButton.disabled = false; likeButton.textContent = `点赞(${postData.likes})`; likeButton.addEventListener('click', () => { const formData = new FormData(); formData.append('oid', postData.oid); formData.append('username', currentUser); fetch('/social/post/like', { method: 'POST', body: formData }) .then(res => res.json()) .then(data => { if (data.status === 'success') { likeButton.disabled = true; likeButton.textContent = `已点赞(${data.likes})`; likeButton.classList.add('liked'); } else { alert(data.msg); } }); }); }
方案2的前端逻辑示例:
可以在后端返回帖子数据时,顺便返回is_liked字段:
# 后端获取帖子详情的接口 @app.route('/social/post/<oid>') def get_post(oid): conn = sqlite3.connect('data/detailpost.db') c = conn.cursor() current_user = session.get('username') # 假设用session存登录用户 # 获取帖子基本信息 c.execute("SELECT * FROM postdetail WHERE oid=?", (oid,)) post = c.fetchone() # 检查当前用户是否点赞过 c.execute("SELECT 1 FROM post_likes WHERE post_oid=? AND liker_username=?", (oid, current_user)) is_liked = c.fetchone() is not None conn.close() return jsonify({ 'post': post, 'is_liked': is_liked })
然后前端直接根据is_liked的值设置按钮状态就行,不用自己处理字符串拆分。
内容的提问来源于stack exchange,提问作者imxitiz

