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

Django博客如何避免同一用户多次GET请求重复增加浏览量

博客单用户单次访问计数实现方案

核心逻辑是单独存储每个用户/匿名访客的文章访问记录,只有首次访问时才累加浏览量,同时通过数据库约束和原子操作避免高并发下的计数错误。

1. 新增访问记录模型

原有Blog模型保持不变,新增ReadRecord表存储访问痕迹:登录用户关联用户ID,匿名用户关联会话ID,同时加数据库层面的联合唯一约束,从根源避免重复记录。

from django.db import models
from django.contrib.auth.models import User

# 原有Blog模型无需修改
class Blog(models.Model):
    author=models.ForeignKey(User,on_delete=models.CASCADE,related_name='post_author')
    blog_title=models.CharField(max_length=264,verbose_name='Put a Title')
    category=models.ForeignKey(Category,on_delete=models.CASCADE,related_name='category',default=None)
    slug= models.SlugField(max_length=264,unique=True,null=True,allow_unicode=True)
    blog_content=models.TextField(verbose_name='what is on your mind?')
    blog_image=models.ImageField(upload_to='blog_images',verbose_name='Image')
    publish_date=models.DateTimeField(auto_now_add=True)
    update_date=models.DateTimeField(auto_now=True)
    view_count=models.IntegerField(null=True,default=0,blank=True)
    class Meta:
        ordering = ('-publish_date',)

# 新增访问记录模型
class ReadRecord(models.Model):
    blog = models.ForeignKey(Blog, on_delete=models.CASCADE, related_name='read_records')
    user = models.ForeignKey(User, on_delete=models.CASCADE, null=True, blank=True)
    # 匿名用户用Django自带的session_key做身份标识
    session_key = models.CharField(max_length=40, null=True, blank=True)
    first_read_time = models.DateTimeField(auto_now_add=True)

    class Meta:
        constraints = [
            # 登录用户对单篇文章只能有一条访问记录
            models.UniqueConstraint(fields=['blog', 'user'], condition=models.Q(user__isnull=False), name='unique_user_blog_read'),
            # 同一会话的匿名用户对单篇文章只能有一条访问记录
            models.UniqueConstraint(fields=['blog', 'session_key'], condition=models.Q(session_key__isnull=False), name='unique_session_blog_read')
        ]

改完模型后执行两条命令完成数据表创建:

  • python manage.py makemigrations
  • python manage.py migrate

2. 修改视图计数逻辑

替换原来每次GET请求直接加1的逻辑,先判断当前访客是否已有该文章的访问记录,没有记录时才累加浏览量,同时用原子操作和F表达式避免并发计数错误:

from django.db import transaction
from django.db.models import F
from .models import Blog, ReadRecord

def blog_details(request, slug):
    blog = Blog.objects.get(slug=slug)
    if request.method == 'GET':
        # 区分登录用户和匿名用户的身份标识
        current_user = request.user if request.user.is_authenticated else None
        session_key = request.session.session_key

        # 匿名用户没有有效session时主动创建,保证身份可识别
        if not current_user and not session_key:
            request.session.create()
            session_key = request.session.session_key

        # 开启事务保证查询、创建记录、更新计数的原子性
        with transaction.atomic():
            # 区分身份查询访问记录,没有就创建新记录
            if current_user:
                _, is_first_visit = ReadRecord.objects.get_or_create(
                    blog=blog,
                    user=current_user
                )
            else:
                _, is_first_visit = ReadRecord.objects.get_or_create(
                    blog=blog,
                    session_key=session_key
                )

            # 只有首次访问才累加浏览量
            if is_first_visit:
                # 用F表达式直接在数据库层面做数值更新,避免多请求同时读写导致的计数偏差
                blog.view_count = F('view_count') + 1
                blog.save(update_fields=['view_count'])
                # 刷新对象拿到最新计数,方便后续模板渲染使用
                blog.refresh_from_db()

    # 下方保留原有的其余视图逻辑,比如模板渲染、响应返回等
    # *省略原有无关代码*

实现说明

  • 匿名用户识别依赖Django默认session机制,用户清除浏览器Cookie后会被判定为新访客,这是无登录态下的通用实现方案,兼顾实现成本和统计准确率。
  • 不建议直接把已读文章ID存在session里:如果用户浏览的文章数量多,会撑大session存储体积,且Cookie清理后所有记录都会丢失,单独建表的方案长期运行更稳定。
  • 数据库层的唯一约束是最后一道防线,就算视图逻辑出现异常,也不会插入重复的访问记录,不会出现重复计数问题。

内容的提问来源于stack exchange,提问作者Abu RayhaN

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.29 06:48:15