BBS项目之点赞点踩功能
需求分析
# 需求1
已经支持过的用户(点赞,点擦)不能再点
# 需求2
未经过认证的用户(匿名用户)不能点赞点踩,需要让其(登录或注册账号)
# 需求3
该文章的作者不能给自己的文章点赞
这里需要注意的是:点赞数
和点踩数
是出现较为频繁的数据字段,但凡文章出现的地方,其也随之出现,为了避免频繁的跨表查询,对数据库查询进行优化,在Article 表中引入up_num,down_num。由此,引入了需求4。
# 需求4
文章表和点赞点踩表,需要同时进行数据更新,这里利用事务的一致性进行处理
文章表结构
class Article(models.Model):
"""文章表"""
title = models.CharField(verbose_name='文章标题', max_length=64)
desc = models.CharField(verbose_name='文章简介', max_length=255)
# 文章内容有很多,一般使用TextField
content = models.TextField(verbose_name='文章内容')
create_time = models.DateField(auto_now_add=True)
# 数据库字段优化设计(避免频繁的跨表查询操作)
up_num = models.BigIntegerField(verbose_name='点赞数', default=0)
down_num = models.BigIntegerField(verbose_name='点踩数', default=0)
comment_num = models.BigIntegerField(verbose_name='评论数', default=0)
# 外键字段
# 一个站点有多篇文章(一对多)
blog = models.ForeignKey(to='Blog', null=True)
# 一个分类有多篇文章(一对多)
category = models.ForeignKey(to='Category', null=True)
# 一个标签下游多篇文章,一篇文章可以有多个标签(多对多)
tags = models.ManyToManyField(to='Tag',
through='Article2Tag',
through_fields=('article', 'tag'),
)
点赞点踩表结构
class UpAndDown(models.Model):
"""点赞点踩表"""
user = models.ForeignKey(to='UserInfo')
article = models.ForeignKey(to='Article')
is_up = models.BooleanField() # 传布尔值,数据库中存0/1
业务逻辑
前端:
# 点赞和点踩触发的是同一事件,通过ajax请求向后端提交数据;
# 点赞和点踩的不同之处在于,他们的布尔值不同,在数据库中存储为is_up(0/1);
# 当用户点完赞/踩后,前端页面是即时更新的,可以通过DOM操作,而不是render渲染。
后端:
# 匿名用户不支持点赞/点踩
# 文章作者不支持点赞/点踩
# 评论过的用户不支持点赞/点踩
基于以上的逻辑,进行如下的设计。
代码
前端
{# 点赞点踩开始 #}
<div class="clearfix">
<div id="div_digg">
<div class="diggit action">
<span class="diggnum" id="digg_count">{{ article_obj.up_num }}</span>
</div>
<div class="buryit action">
<span class="burynum" id="bury_count">{{ article_obj.down_num }}</span>
</div>
<div class="clear"></div>
<div class="diggword" id="digg_tips" style="color: red;"></div>
</div>
</div>
{# 点赞点踩结束 #}
//给所有的action绑定事件
$('.action').click(function () {
let isUp = $(this).hasClass('diggit'); //判断是不是赞图片被点击了,它返回的是一个布尔值
let $div = $(this);
//朝后端发送ajax请求
$.ajax({
url: '/up_or_down/',
type: 'post',
data: {
'article_id': '{{ article_obj.pk }}',
'is_up': isUp,
'csrfmiddlewaretoken': '{{ csrf_token }}',
},
success: function (args) {
if (args.code === 1500) {
//将前端的数字加1
let old_num = $div.children().text();
$div.children().text(Number(old_num) + 1);
$('.diggword').html(args.msg)
} else (
$('.diggword').html(args.msg)
)
}
})
});
后端
def up_or_down(request):
if request.is_ajax():
back_dict = {'code': 1500, 'msg': ''}
# 判断当前用户是否登录
if request.user.is_authenticated():
article_id = request.POST.get('article_id')
is_up = json.loads(request.POST.get('is_up'))
article_obj = models.Article.objects.filter(pk=article_id).first()
# 判断当前的文章是否是当前用户写的
if not article_obj.blog.userinfo == request.user:
# 校验当前用户是否已经点过了(哪个地方记录了用户到底点了还是没有点)
is_click = models.UpAndDown.objects.filter(user=request.user, article=article_obj)
if not is_click:
# 操作数据库来记录数据库 要同步article表中的普通字段
if is_up:
# 给点赞数加1
# 利用F查询来获取表字段加1
models.Article.objects.filter(pk=article_id).update(up_num=F('up_num') + 1)
back_dict['msg'] = '点赞成功'
else:
# 给点踩数加1
models.Article.objects.filter(pk=article_id).update(down_num=F('down_num') + 1)
back_dict['msg'] = '点踩成功'
# 操作点赞点踩表
models.UpAndDown.objects.create(user=request.user, article=article_obj, is_up=is_up)
else:
# 查询已经点过的是赞还是踩
up_or_down_obj = models.UpAndDown.objects.filter(user=request.user, article=article_obj).first()
once_click_up = up_or_down_obj.is_up
back_dict['code'] = 1501
back_dict['msg'] = '您已经支持过(赞)' if once_click_up else '您已经支持过(踩)'
else:
back_dict['code'] = 1502
back_dict['msg'] = '不能给自己的文章投票'
else:
back_dict['code'] = 1503
back_dict['msg'] = '<a href="/login/" style="text-decoration: none;color: red;">请先登录</a>'
return JsonResponse(back_dict)