You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在Flask+SQLite3中实现防重复点赞的点赞按钮功能

解决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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.09 00:37:33