#zyl: # comments/views.py # Django 视图文件:处理评论的创建和提交 import hashlib # 用于生成评论内容的 MD5 哈希,检测重复提交 from django.core.exceptions import ValidationError from django.http import HttpResponseRedirect from django.shortcuts import get_object_or_404 from django.utils.decorators import method_decorator from django.views.decorators.csrf import csrf_protect from django.views.generic.edit import FormView from django.contrib import messages # Django 消息框架,用于显示一次性通知 from django.utils.timezone import now from datetime import timedelta # 用于时间比较,检测短时间内重复提交 from accounts.models import BlogUser from blog.models import Article from .forms import CommentForm from .models import Comment class CommentPostView(FormView): """ 评论发布视图(基于表单类视图) 功能特性: - CSRF 防护 - 处理 GET 请求(重定向到文章页) - 表单验证(长度、重复提交) - 评论审核机制 - 嵌套评论(回复)支持 - 成功/错误消息提示 """ # 指定使用的表单类 form_class = CommentForm # 表单验证失败时使用的模板(即 article_detail.html) # 会在上下文中传递错误信息 template_name = 'blog/article_detail.html' @method_decorator(csrf_protect) def dispatch(self, *args, **kwargs): """ 使用 CSRF 保护装饰器,确保 POST 请求必须携带有效的 CSRF Token 防止跨站请求伪造攻击 """ return super(CommentPostView, self).dispatch(*args, **kwargs) def get(self, request, *args, **kwargs): """ 处理 GET 请求:用户直接访问此 URL 时重定向到文章详情页 逻辑: 1. 从 URL 参数获取 article_id 2. 查询文章对象(404 保护) 3. 重定向到文章详情页的评论区锚点 """ article_id = self.kwargs['article_id'] article = get_object_or_404(Article, pk=article_id) url = article.get_absolute_url() return HttpResponseRedirect(url + "#comments") # #comments 是页面上的锚点 def form_invalid(self, form): """ 表单验证失败时的处理 参数: form: 包含错误信息的表单实例 返回: 重新渲染文章详情页,显示表单错误 """ article_id = self.kwargs['article_id'] article = get_object_or_404(Article, pk=article_id) return self.render_to_response({ 'form': form, # 包含错误信息的表单对象 'article': article # 文章对象,模板需要 }) def form_valid(self, form): """ 表单验证成功后的核心处理逻辑 流程: 1. 检查文章评论状态 2. 检测重复提交(30秒内相同内容) 3. 保存评论(根据审核设置决定是否立即显示) 4. 处理嵌套评论(parent_comment 关联) 5. 发送成功消息并重定向 """ """提交的数据验证合法后的逻辑""" # 获取当前请求用户 user = self.request.user # 查询用户详细信息(BlogUser 扩展了默认用户模型) author = BlogUser.objects.get(pk=user.pk) # 获取文章 ID 和文章对象 article_id = self.kwargs['article_id'] article = get_object_or_404(Article, pk=article_id) # 1. 检查文章评论状态 # comment_status == 'c' 表示评论已关闭 # status == 'c' 表示文章已关闭 if article.comment_status == 'c' or article.status == 'c': messages.error(self.request, "该文章评论已关闭.") return HttpResponseRedirect(article.get_absolute_url() + "#comments") # 2. 重复提交检查:防止用户在30秒内重复提交相同内容 comment_body = form.cleaned_data['body'].strip() body_hash = hashlib.md5(comment_body.encode()).hexdigest() # 生成内容哈希 # 查询用户 30 秒内在该文章下发表的所有评论 recent_comments = Comment.objects.filter( author=author, article=article, creation_time__gte=now() - timedelta(seconds=30) ) # 遍历最近评论,对比内容哈希 for comment in recent_comments: if hashlib.md5(comment.body.encode()).hexdigest() == body_hash: messages.warning(self.request, "请勿重复提交相同评论内容!") return HttpResponseRedirect(article.get_absolute_url() + "#comments") # 3. 保存评论到数据库(commit=False 不立即提交) comment = form.save(False) comment.article = article # 设置评论所属文章 # 4. 获取博客设置,检查是否需要审核 from djangoblog.utils import get_blog_setting settings = get_blog_setting() # 如果不需要审核,则立即启用评论(is_enable=True) # 否则保持默认 False(待审核状态) if not settings.comment_need_review: comment.is_enable = True comment.author = author # 设置评论作者 # 5. 处理嵌套评论:如果是回复,关联父评论 if form.cleaned_data['parent_comment_id']: parent_comment = Comment.objects.get( pk=form.cleaned_data['parent_comment_id']) comment.parent_comment = parent_comment # 保存评论到数据库(commit=True) comment.save(True) # 发送成功消息,显示在下一个页面 messages.success(self.request, "评论提交成功!") # 6. 重定向到文章详情页,并定位到刚提交的评论位置 # 使用 #div-comment-{id} 锚点,前端模板需要有对应 ID 的元素 return HttpResponseRedirect( "%s#div-comment-%d" % (article.get_absolute_url(), comment.pk)) # 安全注意事项: # 1. 用户身份验证:此视图假设用户已登录,实际应在 URL 配置或视图中添加 login_required # 2. parent_comment_id 验证:应检查父评论是否属于同一文章,防止跨文章回复 # 3. 速率限制:当前仅检查30秒重复,建议增加更严格的速率限制(如每小时最多评论数) # 4. 内容安全:评论内容需防范 XSS 攻击,应在模板层或模型层进行 HTML 转义 # 5. 邮件通知:发送邮件可能耗时,生产环境建议使用 Celery 异步任务 # 性能优化建议: # 1. comment.save() 触发数据库写入,可考虑使用事务 # 2. 频繁查询 BlogUser 和 BlogSettings,可添加缓存 # 3. get_max_articleid_commentid() 如果涉及复杂查询,可优化