Bug分析,假刪除導致文章發佈成功卻打不開的問題

語言: CN / TW / HK

公司有一個內部博客,大家可以在上面創建自己的賬號,然後寫文章在全公司分享。昨天這個內部博客開通了API,因此我準備寫一個Python程序,把自己文章都搬運上去。

然後我就發現這個API接口有一個bug。並且根據它的現象,猜到它問題出在哪裏。

我先來簡單描述一下現象。

假設我硬盤上現在有50個Markdown文件。現在我要把它發佈到網站上。簡化代碼如下:

import glob
import requests

for path in glob.glob('blog/*.md'):
   with open(path) as f:
        article = f.read()
    requests.post('http://xxx.yyy.com/post?token=abcasdf', json={'content': content})

發佈完成以後,文章確實都已經在網頁上出現了,並且每篇文章都能正常顯示。但我粗略瀏覽了一下,發現裏面有一些文章的末尾帶上來二維碼。我不想讓公司的人知道我的公眾號,所以準備修改一下文章。

有一些文章有二維碼,有一些沒有,一個一個改起來很麻煩,所以我做了兩步操作。首先寫了一個程序,掃描所有Markdown文件,發現二維碼就刪掉。然後,我直接在網站上把剛剛發佈的所有文章都刪了(懶得去看哪篇有二維碼,哪篇沒有,乾脆全刪了重發)。

接下來,我再次運行程序批量重新發布文章。2秒鐘以後發佈完成。

本來一切看起來都很正常,但是當我到網站上查看的時候,發現有很多文章點開以後,都提示『該文章已經刪除』。

我一開始在想是不是我的程序寫得不對,漏掉了這些文章。我重新單獨一篇一篇發佈這篇文章,API接口返回發佈成功,可在網頁上還是顯示文章已經刪除。

然後我一篇一篇檢查這些發佈失敗的文章,發現有一個共同的特點:他們是一開始就沒有二維碼的文章。相當於這些文章我在網站上刪除以後原樣重新又發了一次。

那我就有了一個初步的猜測,大概知道原因是什麼了:

  • 因為每篇文章有一個docid,當第一次發佈文章的時候,這個docid就是文章正文內容的md5值。只要文章完全一樣,連續發多少次,它的docid都一樣。這樣就可以防止出現重複文章。(更新的時候,需要用户主動提供docid,避免重新生成新的)。
  • 這個網站的刪除功能,肯定是假刪除。也就是當我點了刪除文章的按鈕時,文章其實依然在數據庫裏面,只不過增加了一個字段removed=True。網頁顯示文章的時候,查詢條件肯定是col.find({'removed': {"$ne": True}}),所以就不會把這些被軟刪除的文章顯示出來。
  • API發佈新文章的時候,肯定使用的是更新操作。並且使用了upsert=True。

以MongoDB為例,這個API背後的邏輯肯定是這樣的:

def post_article(docid, article_info):
      mongo.update_one({'_id': docid}, {'$set': article_info}, upsert=True)

upsert=True的作用,是先檢查數據是否存在,如果存在就更新,如果不存在就插入。

第一次發佈的時候,文章不存在,直接插入,正常。如果用户正常使用修改接口,修改了正文,因為用户主動提供了docid,所以也能正常更新。

但如果用户先刪除了數據,此時數據庫中,增加了一個字段removed=True。然後用户又原封不動重新發一次文章。那麼docid肯定還是原來那個。這條文章已經在數據庫中存在了。於是逐一更新了每個字段。但是新發布的字段裏面是沒有removed這個字段的,所以更新的時候不會更新它,它還在數據庫裏面。所以就出現了發佈成功,但是打開新聞又提示文章已經刪除。

我去問了一下做這個API的同學,果然它的bug原因跟我設想的一模一樣。

這個bug解決方法非常簡單,發佈新文章的時候,把update_one改成replace_one就可以了:

def post_article(docid, article_info):
      mongo.replace_one({'_id': docid}, {'$set': article_info}, upsert=True)

以上就是本次分享的所有內容,想要了解更多 python 知識歡迎前往公眾號:Python 編程學習圈 ,發送 “J” 即可免費獲取,每日干貨分享