Django搭建我的博客:基於 LocalStorage 的點贊功能

假設你的博客已經順利部署到了線上。你寫了不少好文章,和粉絲們互動並感覺成就感。javascript

如今你想更進一步,努力提升文章質量,使其更受讀者歡迎,打造圈內一流博客。問題是該如何判斷一篇文章是「受歡迎的」?靠瀏覽量是個方法,可是並不能區分出內容花拳繡腿的標題黨。靠評論數也是個好方法,但我的博客一般讀者很少,好文章零評論是很正常的。css

這時候「點贊」功能就顯得重要了。若是大部分讀者都給了一個贊,那就代表文章確實還不錯。html

動手以前的思考

點贊功能可不簡單,實現途徑很是的多。別急着動手,耐心思考:咱們的博客到底須要什麼樣的點贊?前端

首先,點贊是否要求用戶必須登陸?要求登陸的好處是能夠精確的記錄是哪些用戶、對哪些文章點過贊(多對多關係),以便進行細緻的數據分析。壞處是登陸這個要求很笨重,會屏蔽掉大部分的遊客用戶。博主傾向於不要求用戶登陸,畢竟小站一般用戶就很少,提升參與度纔是點贊最核心的任務。java

若是某天你的小站火了,就把要求用戶登陸的交互功能讓給「收藏」吧!

其次,用戶是否能夠重複點贊?不少視頻平臺的用戶能夠對某個喜歡的女主播瘋狂點贊,以表達本身很是很是的喜歡。這對用戶較多的平臺是沒問題的,由於用戶數量多了以後,你點幾百個贊也只是九牛一毛。但博客網站這樣作很容易形成某些文章點贊爲零,某些文章點贊數又出奇的高。顯然這不表明文章質量的差別。python

好了,目前咱們的策略是不要求用戶登陸,也不容許用戶重複點贊。下一個問題是,在哪裏記錄用戶的點贊相關的數據呢?點贊數量毫無疑問要保存在數據庫裏,以便隨時取出數據並呈現出來。git

但問題是校驗用戶是否已點讚的記錄保存在哪?在數據庫中記錄用戶的IP地址是個方法,但你得處理好記錄IP和記錄登陸用戶的關係,稍微有點麻煩。另外每次用戶的點贊都須要向後端發送校驗請求,增長了服務器的負擔。github

既然數據保存在後端數據庫裏很差,那能不能保存在瀏覽器端呢?答案是能夠的,而且有 CookieLocalStorage 均可以讓你保存數據。它兩的主要區別以下:ajax

特性 Cookie LocalStorage
生命週期 可設置失效時間,默認是關閉瀏覽器後失效 除非被清除,不然永久保存
存儲空間 4K左右 通常爲5MB
與服務器通訊 每次都會攜帶在HTTP頭中 不參與服務器的通訊
易用性 源生接口不友好 源生接口能夠接受

比較下來會發現 LocalStorage 能夠永久保存數據,存儲空間大,也不參與服務器通訊,很適合點讚的需求。因爲數據保存在瀏覽器中,因此也不須要區分用戶有沒有登陸了:實際上每次請求點贊時,校驗的是當前這個瀏覽器是否已經點過讚了,而不是用戶!數據庫

可能你會反駁說,那要是用戶換一個瀏覽器不就能夠重複點讚了嗎,更況且瀏覽器端的數據是很是容易篡改的。但這又有什麼關係呢?點贊數據並不須要很是精確,隨他去吧。

全部的現代瀏覽器都支持 LocalStorage 功能。若是你還在用 IE6 ,趕忙考慮升級瀏覽器吧。

總結一下,咱們的點贊功能以下:

  • 不要求用戶登陸
  • 不容許重複點贊
  • 點贊數保存在服務器數據庫中
  • 點贊校驗數據保存在瀏覽器的 LocalStorage 中

當用戶點贊時,前端腳本會在 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'
    ),
]

很簡單吧。剩下的就是專心寫前端代碼了。

JS與Ajax

因爲校驗數據保存在瀏覽器中,所以前端的工做較多。

先把完整代碼貼出來(講解在後面):

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:

  • localStorage:查看 LocalStorage 的數據
  • localStorage.clear():清除全部數據
  • localStorage.getItem():獲取某個數據
  • localStorage.setItem():保存某個數據

測試

代碼講完了,接下來就打開文章詳情頁面測試一下:

點擊點贊按鈕,點贊數 +1;再次點擊點贊按鈕,點贊數不會增長,而且會彈窗提示用戶已經點過了。

你能夠隨意嘗試關閉頁面或瀏覽器,保存的點贊校驗數據是不會消失的。

這樣就完成了一個簡單的點贊功能。

固然還能夠繼續往下優化:

  • 沒點讚的愛心應該顯示爲灰色,點過的顯示爲紅色,這樣才人性化
  • 最好再來一點酷炫的點贊動畫,或者提示性文字
  • 要不要給被點贊人發一條通知信息呢?
  • ...

教程篇幅有限,不打算再深刻下去了,就當作讀者朋友的課後做業吧,要用心完成哦。給你點贊!

第一條的提示:初始加載頁面時,愛心統一顯示爲灰色,而後調用 JavaScript 腳本比對 LocalStorage 中的數據,靈活運用 Jquery ,將點過讚的愛心顏色修改成紅色。

總結

咱們的博客項目如今擁有了井井有條的用戶交互結構:瀏覽量數據最輕巧,評價文章類型的受歡迎度;點贊數據比較平衡,評價文章內容的受歡迎度;評論數據最笨重,但價值也最高。讀者之後在開發功能的時候,也要像這樣把核心需求想清楚才行。

另外一個須要提出的是,只有非敏感、不重要的數據才保存在 LocalStorage,不要對它太過依賴。

再一次提醒,教程爲了便於講解,代碼文件已經變得愈來愈龐大。請在適當的時候把它分割成多個更小的組件,方便維護和重用。


相關文章
相關標籤/搜索