1. 程式人生 > 程式設計 >Django搭建個人部落格:基於 LocalStorage 的點贊功能

Django搭建個人部落格:基於 LocalStorage 的點贊功能

假設你的部落格已經順利部署到了線上。你寫了很多好文章,和粉絲們互動並感受成就感。

現在你想更進一步,努力提高文章質量,使其更受讀者歡迎,打造圈內一流部落格。問題是該如何判斷一篇文章是“受歡迎的”?靠瀏覽量是個方法,但是並不能區分出內容花拳繡腿的標題黨。靠評論數也是個好方法,但個人部落格通常讀者不多,好文章零評論是很正常的。

這時候**“點贊”**功能就顯得重要了。如果大部分讀者都給了一個贊,那就表明文章確實還不錯。

動手之前的思考

點贊功能可不簡單,實現途徑非常的多。別急著動手,耐心思考:我們的部落格到底需要什麼樣的點贊?

**首先,點贊是否要求使用者必須登入?**要求登入的好處是可以精確的記錄是哪些使用者、對哪些文章點過贊(多對多關係),以便進行細緻的資料分析。壞處是登入這個要求很笨重,會遮蔽掉大部分的遊客使用者。博主傾向於不要求使用者登入,畢竟小站通常使用者就不多,提高參與度才是點贊最核心的任務。

如果某天你的小站火了,就把要求使用者登入的互動功能讓給“收藏”吧!

**其次,使用者是否可以重複點贊?**很多視訊平臺的使用者可以對某個喜歡的女主播瘋狂點贊,以表達自己非常非常的喜歡。這對使用者較多的平臺是沒問題的,因為使用者數量多了之後,你點幾百個贊也只是九牛一毛。但部落格網站這樣做很容易造成某些文章點贊為零,某些文章點贊數又出奇的高。顯然這不代表文章質量的差異。

好了,目前我們的策略是不要求使用者登入,也不允許使用者重複點贊。**下一個問題是,在哪裡記錄使用者的點贊相關的資料呢?**點贊數量毫無疑問要儲存在資料庫裡,以便隨時取出資料並呈現出來。

但問題是**校驗使用者是否已點讚的記錄儲存在哪?**在資料庫中記錄使用者的IP地址是個方法,但你得處理好記錄IP和記錄登入使用者的關係,稍微有點麻煩。另外每次使用者的點贊都需要向後端傳送校驗請求,增加了伺服器的負擔。

既然資料儲存在後端資料庫裡不好,**那能不能儲存在瀏覽器端呢?**答案是可以的,並且有 CookieLocalStorage 都可以讓你儲存資料。它兩的主要區別如下:

特性 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.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,不要對它太過依賴。

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