假设你的博客已经顺利部署到了线上。你写了不少好文章,和粉丝们互动并感觉成就感。javascript
如今你想更进一步,努力提升文章质量,使其更受读者欢迎,打造圈内一流博客。问题是该如何判断一篇文章是“受欢迎的”?靠浏览量是个方法,可是并不能区分出内容花拳绣腿的标题党。靠评论数也是个好方法,但我的博客一般读者很少,好文章零评论是很正常的。css
这时候“点赞”功能就显得重要了。若是大部分读者都给了一个赞,那就代表文章确实还不错。html
点赞功能可不简单,实现途径很是的多。别急着动手,耐心思考:咱们的博客到底须要什么样的点赞?前端
首先,点赞是否要求用户必须登陆?要求登陆的好处是能够精确的记录是哪些用户、对哪些文章点过赞(多对多关系),以便进行细致的数据分析。坏处是登陆这个要求很笨重,会屏蔽掉大部分的游客用户。博主倾向于不要求用户登陆,毕竟小站一般用户就很少,提升参与度才是点赞最核心的任务。java
若是某天你的小站火了,就把要求用户登陆的交互功能让给“收藏”吧!
其次,用户是否能够重复点赞?不少视频平台的用户能够对某个喜欢的女主播疯狂点赞,以表达本身很是很是的喜欢。这对用户较多的平台是没问题的,由于用户数量多了以后,你点几百个赞也只是九牛一毛。但博客网站这样作很容易形成某些文章点赞为零,某些文章点赞数又出奇的高。显然这不表明文章质量的差别。python
好了,目前咱们的策略是不要求用户登陆,也不容许用户重复点赞。下一个问题是,在哪里记录用户的点赞相关的数据呢?点赞数量毫无疑问要保存在数据库里,以便随时取出数据并呈现出来。git
但问题是校验用户是否已点赞的记录保存在哪?在数据库中记录用户的IP地址是个方法,但你得处理好记录IP和记录登陆用户的关系,稍微有点麻烦。另外每次用户的点赞都须要向后端发送校验请求,增长了服务器的负担。github
既然数据保存在后端数据库里很差,那能不能保存在浏览器端呢?答案是能够的,而且有 Cookie 和 LocalStorage 均可以让你保存数据。它两的主要区别以下:ajax
特性 | Cookie | LocalStorage |
---|---|---|
生命周期 | 可设置失效时间,默认是关闭浏览器后失效 | 除非被清除,不然永久保存 |
存储空间 | 4K左右 | 通常为5MB |
与服务器通讯 | 每次都会携带在HTTP头中 | 不参与服务器的通讯 |
易用性 | 源生接口不友好 | 源生接口能够接受 |
比较下来会发现 LocalStorage 能够永久保存数据,存储空间大,也不参与服务器通讯,很适合点赞的需求。因为数据保存在浏览器中,因此也不须要区分用户有没有登陆了:实际上每次请求点赞时,校验的是当前这个浏览器是否已经点过赞了,而不是用户!数据库
可能你会反驳说,那要是用户换一个浏览器不就能够重复点赞了吗,更况且浏览器端的数据是很是容易篡改的。但这又有什么关系呢?点赞数据并不须要很是精确,随他去吧。
全部的现代浏览器都支持 LocalStorage 功能。若是你还在用 IE6 ,赶忙考虑升级浏览器吧。
总结一下,咱们的点赞功能以下:
当用户点赞时,前端脚本会在 LocalStorage 里校验是否已经点过赞了;如未点过赞,才会向服务器发送点赞请求,并记录数据。
想清楚需求,难题就迎刃而解了。接下来就是代码实现。
须要说明的是,以上分析并不表明其余方法很差,仅仅是在博客小站的环境下,博主以为合适的技术路径而已。若是你心中住着另外一个哈姆雷特,请想办法去实现它。
本章的重点工做在前端,所以先把简单的后端代码写了,权当热身。
有的读者听到前端就以为头疼。你的痛苦我明白,但也是必不可少的。光写 Python 是作不出漂亮网站的。
因为点赞数须要保存在数据库中,所以修改文章模型是必须的了:
article/models.py ... # 文章模型 class ArticlePost(models.Model): ... # 新增点赞数统计 likes = models.PositiveIntegerField(default=0) ...
迁移数据:
(env) > python manage.py makemigrations (env) > python manage.py migrate
继续用类视图:
article/views.py ... # 点赞数 +1 class IncreaseLikesView(View): def post(self, request, *args, **kwargs): article = ArticlePost.objects.get(id=kwargs.get('id')) article.likes += 1 article.save() return HttpResponse('success')
功能是让点赞数增长1个,而且返回 success
。至于为何是 success
后面再讲。
最后就是路由了:
article/urls.py ... urlpatterns = [ ... # 点赞 +1 path( 'increase-likes/<int:id>/', views.IncreaseLikesView.as_view(), name='increase_likes' ), ]
很简单吧。剩下的就是专心写前端代码了。
因为校验数据保存在浏览器中,所以前端的工做较多。
先把完整代码贴出来(讲解在后面):
templates/article/detail.html ... <!-- 已有代码,文章正文 --> <div class="col-12"> <p>{{ article.body|safe }}</p> </div> <!-- 新增点赞按钮 --> <div style="text-align:center;" class="mt-4"> <button class="btn btn-outline-danger" type="button" onclick="validate_is_like( '{% url 'article:increase_likes' article.id %}', {{ article.id }}, {{ article.likes }} )" > <span>点赞</span> <span> <i class="fas fa-heart"></i> </span> <span id="likes_number"> {{ article.likes }} </span> </button> </div> ... {% block script %} ... <!-- 如下均为新代码 --> <!-- csrf token --> <script src="{% static 'csrf.js' %}"></script> <script> // 点赞功能主函数 function validate_is_like(url, id, likes) { // 取出 LocalStorage 中的数据 let storage = window.localStorage; const storage_str_data = storage.getItem("my_blog_data"); let storage_json_data = JSON.parse(storage_str_data); // 若数据不存在,则建立空字典 if (!storage_json_data) { storage_json_data = {} }; // 检查当前文章是否已点赞。是则 status = true const status = check_status(storage_json_data, id); if (status) { layer.msg('已经点过赞了哟~'); // 点过赞则当即退出函数 return; } else { // 用 Jquery 找到点赞数量,并 +1 $('span#likes_number').text(likes + 1).css('color', '#dc3545'); } // 用 ajax 向后端发送 post 请求 $.post( url, // post 只是为了作 csrf 校验,所以数据为空 {}, function(result) { if (result === 'success') { // 尝试修改点赞数据 try { storage_json_data[id] = true; } catch (e) { window.localStorage.clear(); }; // 将字典转换为字符串,以便存储到 LocalStorage const d = JSON.stringify(storage_json_data); // 尝试存储点赞数据到 LocalStorage try { storage.setItem("my_blog_data", d); } catch (e) { // code 22 错误表示 LocalStorage 空间满了 if (e.code === 22) { window.localStorage.clear(); storage.setItem("my_blog_data", d); } }; } else { layer.msg("与服务器通讯失败..过一下子再试试呗~"); } } ); }; // 辅助点赞主函数,验证点赞状态 function check_status(data, id) { // 尝试查询点赞状态 try { if (id in data && data[id]) { return true; } else { return false; } } catch (e) { window.localStorage.clear(); return false; }; }; </script> {% endblock script %}
代码内容不少,接下来拆分讲解。
<!-- 新增点赞代码 --> <div style="text-align:center;" class="mt-4"> <button class="btn btn-outline-danger" type="button" onclick="validate_is_like( '{% url 'article:increase_likes' article.id %}', {{ article.id }}, {{ article.likes }} )" > <span>点赞</span> <span> <i class="fas fa-heart"></i> </span> <span id="likes_number"> {{ article.likes }} </span> </button> </div>
上面的 HTML 代码功能很简单,提供一个点赞的按钮,点击此按钮时会触发叫作validate_is_like
的 JavaScript 函数。特别须要注意的是 '{% url 'article:increase_likes' article.id %}'
都是用的单引号,这里千万不能用双引号,缘由请读者思考一下。
<script src="{% static 'csrf.js' %}"></script>
还记得csrf.js吗?咱们在多级评论章节已经将它引入了,目的是让 Ajax 也能经过 csrf 校验。若是尚未这个文件的请点击连接下载。
接下来就是占据最多版面的函数validate_is_like()
,咱们来拆分里面的内容。
// 取出 LocalStorage 中的数据 let storage = window.localStorage; const storage_str_data = storage.getItem("my_blog_data"); let storage_json_data = JSON.parse(storage_str_data); // 若数据不存在,则建立空字典 if (!storage_json_data) { storage_json_data = {} };
浏览器里面, window
对象指当前的浏览器窗口。它也是当前页面的顶层对象(即最高一层的对象),全部其余对象都是它的下属,localStorage
也是如此。
要校验数据,首先必须取出数据。这里用localStorage.getItem()
接口取出了数据。
虽然 LocalStorage 的存储方式为标准的键值对类型(相似Python的字典),可是很怪的是存储的值只支持字符串类型。因此这里要用 JSON.parse()
将字符串还原为对象。
用户第一次点赞时,LocalStorage 中确定是没有任何数据的,因此 if
语句的做用是建立一个空的字典待用。
// 检查当前文章是否已点赞。是则 status = true const status = check_status(storage_json_data, id); if (status) { layer.msg('已经点过赞了哟~'); // 点过赞则当即退出函数 return; } else { // 用 Jquery 找到点赞数量,并 +1 $('span#likes_number').text(likes + 1).css('color', '#dc3545'); }
接下来立刻调用函数 check_status
检查用户是否已经对本文点过赞了。若是点过了,就弹窗提示,而且用 return
立刻终止 validate_is_like
函数,后面的代码就不执行了;若是还没点过,就让按钮的点赞数 +1。
但这时候其实后台数据库的点赞数并无更新。接着往下看。
// 用 ajax 向后端发送 post 请求 $.post( url, // post 只是为了作 csrf 校验,所以数据为空 {}, function(result) { if (result === 'success') { // 尝试修改点赞数据 try { storage_json_data[id] = true; } catch (e) { window.localStorage.clear(); }; const d = JSON.stringify(storage_json_data); // 尝试存储点赞数据到 LocalStorage try { storage.setItem("my_blog_data", d); } catch (e) { // code 22 错误表示 LocalStorage 空间满了 if (e.code === 22) { window.localStorage.clear(); storage.setItem("my_blog_data", d); } }; } else { layer.msg("与服务器通讯失败..过一下子再试试呗~"); } } );
这里开始尝试与后端通讯并更新点赞数。整块代码被 $.post()
包裹,它其实就是 Ajax 的 post 请求。function(result) {...}
是请求成功时才执行的回调函数,参数 result
是后端的返回值。若是通讯成功,则尝试将点赞的校验数据保存到 LocalStorage 中。期间发生任何错误(特别是 LocalStorage 存储已满的错误),都会清除 LocalStorage 中的全部数据,以便后续的数据记录。
能够看出,博主采用的数据结构比较简单,像这样:
{ 2: true, 31: true ... }
键表明文章的 id
,布尔值表明点赞的状态。上面数据的意思是 id
为 2 和 31 的文章已经点过赞了。读者之后可能会但愿文章、评论以及其余内容均可以点赞,那就须要设计更加复杂的数据结构。
// 辅助点赞主函数,验证点赞状态 function check_status(data, id) { // 尝试查询点赞状态 try { if (id in data && data[id]) { return true; } else { return false; } } catch (e) { window.localStorage.clear(); return false; }; };
至于 check_status()
函数就很简单了,做用是查询是否已经点过赞了,是则返回 true,不然返回 false。
整个 JavaScript 脚本就完成了。
读者在调试时可能会出现各类问题,请按 Ctrl + Shift + I
打开浏览器控制台的 Console
界面,利用如下命令 debug:
代码讲完了,接下来就打开文章详情页面测试一下:
点击点赞按钮,点赞数 +1;再次点击点赞按钮,点赞数不会增长,而且会弹窗提示用户已经点过了。
你能够随意尝试关闭页面或浏览器,保存的点赞校验数据是不会消失的。
这样就完成了一个简单的点赞功能。
固然还能够继续往下优化:
教程篇幅有限,不打算再深刻下去了,就当作读者朋友的课后做业吧,要用心完成哦。给你点赞!
第一条的提示:初始加载页面时,爱心统一显示为灰色,而后调用 JavaScript 脚本比对 LocalStorage 中的数据,灵活运用 Jquery ,将点过赞的爱心颜色修改成红色。
咱们的博客项目如今拥有了井井有条的用户交互结构:浏览量数据最轻巧,评价文章类型的受欢迎度;点赞数据比较平衡,评价文章内容的受欢迎度;评论数据最笨重,但价值也最高。读者之后在开发功能的时候,也要像这样把核心需求想清楚才行。
另外一个须要提出的是,只有非敏感、不重要的数据才保存在 LocalStorage,不要对它太过依赖。
再一次提醒,教程为了便于讲解,代码文件已经变得愈来愈庞大。请在适当的时候把它分割成多个更小的组件,方便维护和重用。