Django搭建個人博客:用django-mptt實現(xiàn)多級評論功能

現(xiàn)在我們的博客已經(jīng)具有評論功能了金踪。隨著文章的評論者越來越多捺氢,有的時候評論者之間也需要交流买雾,甚至部分評論還能合并成一個小的整體把曼。因此最好是有某種方法可以將相關(guān)的評論聚集到一起,這時候多級評論就非常的有用了漓穿。

多級評論意味著你需要將模型重新組織為樹形結(jié)構(gòu)嗤军。“樹根”是一級評論晃危,而眾多“樹葉”則是次級評論叙赚。本教程會以第三方庫django-mptt為基礎(chǔ),開發(fā)多級評論功能僚饭。

django-mptt模塊包含了樹形數(shù)據(jù)結(jié)構(gòu)以及查詢纠俭、修改樹形數(shù)據(jù)的眾多方法。

任何需要樹形結(jié)構(gòu)的地方浪慌,都可以用 django-mptt 來搭建冤荆。比如目錄。

注意:本章新知識點(diǎn)較多权纤,請讀者做好心理準(zhǔn)備钓简,一定要耐心閱讀乌妒。

重構(gòu)模型

既然要建立樹形結(jié)構(gòu),老的評論模型肯定是要修改了外邓。

首先安裝django-mptt

(env) > pip install django-mptt

安裝成功后撤蚊,在配置中注冊

my_blog/settings.py

...
INSTALLED_APPS = [
    ...
    'mptt',

    ...
]
...

這些你已經(jīng)輕車熟路了。

接下來损话,修改評論模型

comment/models.py

...
# django-mptt
from mptt.models import MPTTModel, TreeForeignKey

# 替換 models.Model 為 MPTTModel
class Comment(MPTTModel):
    ...
    
    # 新增侦啸,mptt樹形結(jié)構(gòu)
    parent = TreeForeignKey(
        'self',
        on_delete=models.CASCADE,
        null=True,
        blank=True,
        related_name='children'
    )

    # 新增,記錄二級評論回復(fù)給誰, str
    reply_to = models.ForeignKey(
        User,
        null=True,
        blank=True,
        on_delete=models.CASCADE,
        related_name='replyers'
    )
    
    # 替換 Meta 為 MPTTMeta
    # class Meta:
    #     ordering = ('created',)
    class MPTTMeta:
        order_insertion_by = ['created']

    ...

先引入MPTT相關(guān)模塊丧枪,然后改動下列幾個位置:

  • 模型不再繼承內(nèi)置的models.Model類光涂,替換為MPTTModel,因此你的模型自動擁有了幾個用于樹形算法的新字段拧烦。(有興趣的讀者忘闻,可以在遷移好數(shù)據(jù)之后在SQLiteStudio中查看)
  • parent字段是必須定義的,用于存儲數(shù)據(jù)之間的關(guān)系恋博,不要去修改它齐佳。
  • reply_to外鍵用于存儲被評論人
  • class Meta替換為class MPTTMeta债沮,參數(shù)也有小的變化炼吴,這是模塊的默認(rèn)定義,實際功能是相同的疫衩。

這些改動大部分都是django-mptt文檔的默認(rèn)設(shè)置缺厉。需要說明的是這個reply_to

先思考一下隧土,多級評論是否允許無限級數(shù)提针?無限級數(shù)聽起來很美好,但是嵌套的層級如果過多曹傀,反而會導(dǎo)致結(jié)構(gòu)混亂辐脖,并且難以排版。所以這里就限制評論最多只能兩級皆愉,超過兩級的評論一律重置為兩級嗜价,然后再將實際的被評論人存儲在reply_to字段中。

舉例說明:一級評論人為 a幕庐,二級評論人為 b(parent 為 a)久锥,三級評論人為 c(parent 為 b)。因為我們不允許評論超過兩級异剥,因此將 c 的 parent 重置為 a瑟由,reply_to 記錄為 b,這樣就能正確追溯真正的被評論者了冤寿。

模型修改完了歹苦,添加了很多非空的字段進(jìn)去青伤,因此最好先清空所有的評論數(shù)據(jù),再進(jìn)行數(shù)據(jù)遷移殴瘦。

遷移時出現(xiàn)下面的提示也不要慌狠角,一律選第 1 項、填入數(shù)據(jù) 0 就可以了:

(env) > python manage.py makemigrations

You are trying to add a non-nullable field 'level' to comment without a default; we can't do that (the database needs something to populate existing rows).
Please select a fix:
 1) Provide a one-off default now (will be set on all existing rows with a null value for this column)
 2) Quit, and let me add a default in models.py

Select an option: 1
Please enter the default value now, as valid Python
The datetime and django.utils.timezone modules are available, so you can do e.g. timezone.now
Type 'exit' to exit this prompt
>>> 0

要還不行蚪腋,就把數(shù)據(jù)庫文件刪了重新遷移吧丰歌。開發(fā)階段用點(diǎn)笨辦法也沒關(guān)系。

數(shù)據(jù)遷移還是老規(guī)矩:

(env) > python manage.py makemigrations
(env) > python manage.py migrate

這就完成了屉凯。

視圖

前面章節(jié)已經(jīng)寫過一個視圖post_comment用于處理評論了立帖,我們將復(fù)用它,以求精簡代碼神得。

改動較大厘惦,代碼全貼出來偷仿,請對照改動:

comment/views.py

...
# 記得引入 Comment 哩簿!
from .models import Comment

...
@login_required(login_url='/userprofile/login/')
# 新增參數(shù) parent_comment_id
def post_comment(request, article_id, parent_comment_id=None):
    article = get_object_or_404(ArticlePost, id=article_id)

    # 處理 POST 請求
    if request.method == 'POST':
        comment_form = CommentForm(request.POST)
        if comment_form.is_valid():
            new_comment = comment_form.save(commit=False)
            new_comment.article = article
            new_comment.user = request.user

            # 二級回復(fù)
            if parent_comment_id:
                parent_comment = Comment.objects.get(id=parent_comment_id)
                # 若回復(fù)層級超過二級,則轉(zhuǎn)換為二級
                new_comment.parent_id = parent_comment.get_root().id
                # 被回復(fù)人
                new_comment.reply_to = parent_comment.user
                new_comment.save()
                return HttpResponse('200 OK')

            new_comment.save()
            return redirect(article)
        else:
            return HttpResponse("表單內(nèi)容有誤酝静,請重新填寫节榜。")
    # 處理 GET 請求
    elif request.method == 'GET':
        comment_form = CommentForm()
        context = {
            'comment_form': comment_form,
            'article_id': article_id,
            'parent_comment_id': parent_comment_id
        }
        return render(request, 'comment/reply.html', context)
    # 處理其他請求
    else:
        return HttpResponse("僅接受GET/POST請求。")

主要變化有3個地方:

  • 視圖的參數(shù)新增parent_comment_id=None别智。此參數(shù)代表父評論id值宗苍,若為None則表示評論為一級評論,若有具體值則為多級評論薄榛。
  • 如果視圖處理的是多級評論讳窟,則用MPTTget_root()方法將其父級重置為樹形結(jié)構(gòu)最底部的一級評論,然后在reply_to中保存實際的被回復(fù)人并保存敞恋。視圖最終返回的是HttpResponse字符串丽啡,后面會用到。
  • 新增處理GET請求的邏輯硬猫,用于給二級回復(fù)提供空白的表單补箍。后面會用到。

很好啸蜜,現(xiàn)在視圖中有一個parent_comment_id參數(shù)用于區(qū)分多級評論坑雅,因此就要求有的url傳入此參數(shù),有的不傳入衬横,像下面這樣:

comment/urls.py

...
urlpatterns = [
    # 已有代碼裹粤,處理一級回復(fù)
    path('post-comment/<int:article_id>', views.post_comment, name='post_comment'),
    # 新增代碼,處理二級回復(fù)
    path('post-comment/<int:article_id>/<int:parent_comment_id>', views.post_comment, name='comment_reply')
]

兩個path都使用了同一個視圖函數(shù)蜂林,但是傳入的參數(shù)卻不一樣多蛹尝,仔細(xì)看后豫。第一個path沒有parent_comment_id參數(shù),因此視圖就使用了缺省值None突那,達(dá)到了區(qū)分評論層級的目的挫酿。

前端渲染

在前端的邏輯上,我們的理想很豐滿:

  • 二級回復(fù)同樣要使用富文本編輯器
  • 回復(fù)時不能離開當(dāng)前頁面
  • 多個ckeditor加載時愕难,不能有性能問題

然而理想越豐滿早龟,代碼寫得就越痛苦。

首先就是detail.html的代碼要大改猫缭,主要集中在顯示評論部分以及相關(guān)的JavaScript葱弟。

需要改動的地方先全部貼出來:

templates/article/detail.html

...

<!-- 改動 顯示評論 部分 -->
<!-- 不要漏了 load mptt_tags! -->
{% load mptt_tags %}
<h4>共有{{ comments.count }}條評論</h4>
<div class="row">
    <!-- 遍歷樹形結(jié)構(gòu) -->
    {% recursetree comments %}
        <!-- 給 node 取個別名 comment -->
        {% with comment=node %}
            <div class="{% if comment.reply_to %}
                        offset-1 col-11
                        {% else %}
                        col-12
                        {% endif %}"
            >
                <hr>
                <p>
                    <strong style="color: pink">
                        {{ comment.user }}
                    </strong> 

                    {% if comment.reply_to %}
                        <i class="far fa-arrow-alt-circle-right" 
                           style="color: cornflowerblue;"
                        ></i>
                        <strong style="color: pink">
                            {{ comment.reply_to }}
                        </strong> 
                    {% endif %}

                </p>
                <div>{{ comment.body|safe }}</div>

                <div>
                    <span style="color: gray">
                        {{ comment.created|date:"Y-m-d H:i" }}
                    </span>

                    <!-- modal 按鈕 -->
                    <button type="button" 
                            class="btn btn-light btn-sm text-muted" 
                            onclick="load_modal({{ article.id }}, {{ comment.id }})"
                    >
                        回復(fù)
                    </button>
                </div>

                <!-- Modal -->
                <div class="modal fade" 
                     id="comment_{{ comment.id }}" 
                     tabindex="-1" 
                     role="dialog" 
                     aria-labelledby="CommentModalCenter" 
                     aria-hidden="true"
                >
                    <div class="modal-dialog modal-dialog-centered modal-lg" role="document">
                        <div class="modal-content" style="height: 480px">
                            <div class="modal-header">
                                <h5 class="modal-title" id="exampleModalCenterTitle">回復(fù) {{ comment.user }}:</h5>
                            </div>
                            <div class="modal-body" id="modal_body_{{ comment.id }}"></div>
                        </div>

                    </div>
                </div>

                {% if not comment.is_leaf_node %}
                    <div class="children">
                        {{ children }}
                    </div>
                {% endif %}
            </div>
            

        {% endwith %}
    {% endrecursetree %}
</div>

...

{% block script %}
...

<!-- 新增代碼猜丹,喚醒二級回復(fù)的 modal -->
<script>
    // 加載 modal
    function load_modal(article_id, comment_id) {
        let modal_body = '#modal_body_' + comment_id;
        let modal_id = '#comment_' + comment_id;
        
        // 加載編輯器
        if ($(modal_body).children().length === 0) {
            let content = '<iframe src="/comment/post-comment/' + 
                article_id + 
                '/' + 
                comment_id + 
                '"' + 
                ' frameborder="0" style="width: 100%; height: 100%;" id="iframe_' + 
                comment_id + 
                '"></iframe>';
            $(modal_body).append(content);
        };

        $(modal_id).modal('show');
    }
</script>
{% endblock script %}

這么大段肯定把你看暈了芝加,不要急,讓我們拆開來講解射窒。

遍歷樹

第一個問題藏杖,如何遍歷樹形結(jié)構(gòu)?

django-mptt提供了一個快捷方式:

{% load mptt_tags %}
<ul>
    {% recursetree objs %}
        <li>
            {{ node.your_field }}
            {% if not node.is_leaf_node %}
                <ul class="children">
                    {{ children }}
                </ul>
            {% endif %}
        </li>
    {% endrecursetree %}
</ul>

內(nèi)部的實現(xiàn)你不用去管脉顿,當(dāng)成一個黑盒子去用就好了蝌麸。objs是需要遍歷的數(shù)據(jù)集node是其中的單個數(shù)據(jù)艾疟。有兩個地方要注意:

  • {% load mptt_tags %}不要忘記寫
  • node這個變量名太寬泛来吩,用{% with comment=node %}給它起了個別名

Modal

ModalBootstrap內(nèi)置的彈窗。本文相關(guān)代碼如下:

<!-- modal 按鈕 -->
<button type="button" 
        class="btn btn-light btn-sm text-muted" 
        onclick="load_modal({{ article.id }}, {{ comment.id }})"
>
    回復(fù)
</button>

<!-- Modal -->
<div class="modal fade" 
     id="comment_{{ comment.id }}" 
     tabindex="-1" 
     role="dialog" 
     aria-labelledby="CommentModalCenter" 
     aria-hidden="true"
     >
    <div class="modal-dialog modal-dialog-centered modal-lg" role="document">
        <div class="modal-content" style="height: 480px">
            <div class="modal-header">
                <h5 class="modal-title" id="exampleModalCenterTitle">回復(fù) {{ comment.user }}:</h5>
            </div>
            <div class="modal-body" id="modal_body_{{ comment.id }}"></div>
        </div>
    </div>
</div>

它幾乎就是從Bootstrap官方文檔抄下來的(所以讀者要多瀏覽官網(wǎng)氨卫场)弟疆。有點(diǎn)不同的是本文沒有用原生的按鈕,而是用JavaScript加載的Modal盗冷;還有就是增加了幾個容器的id屬性怠苔,方便后面的JavaScript查詢。

和之前章節(jié)用的Layer.js相比正塌,Bootstrap的彈窗更笨重些嘀略,也更精致些,很適合在這里使用乓诽。

加載Modal

最難理解的可能就是這段加載Modal的JavaScript代碼了:

// 加載 modal
function load_modal(article_id, comment_id) {
    let modal_body = '#modal_body_' + comment_id;
    let modal_id = '#comment_' + comment_id;

    // 加載編輯器
    if ($(modal_body).children().length === 0) {
        let content = '<iframe src="/comment/post-comment/' + 
            article_id + 
            '/' + 
            comment_id + 
            '" frameborder="0" style="width: 100%; height: 100%;"></iframe>';
        $(modal_body).append(content);
    };

    $(modal_id).modal('show');
}

實際上核心邏輯只有3步:

  • 點(diǎn)擊回復(fù)按鈕時喚醒了load_modal()函數(shù)帜羊,并將文章id、父級評論id傳遞進(jìn)去
  • $(modal_body).append(content)找到對應(yīng)Modal的容器鸠天,并將一個iframe容器動態(tài)添加進(jìn)去
  • $(modal_id).modal('show')找到對應(yīng)的Modal讼育,并將其喚醒

為什么iframe需要動態(tài)加載?這是為了避免潛在的性能問題。你確實可以在頁面初始加載時把所有iframe都渲染好奶段,但是這需要花費(fèi)額外的時間饥瓷,并且絕大部分的Modal用戶根本不會用到,很不劃算痹籍。

if語句的作用是判斷Modal中如果已經(jīng)加載過呢铆,就不再重復(fù)加載了。

最后蹲缠,什么是iframe棺克?這是HTML5中的新特性,可以理解成當(dāng)前網(wǎng)頁中嵌套的另一個獨(dú)立的網(wǎng)頁线定。既然是獨(dú)立的網(wǎng)頁娜谊,那自然也會獨(dú)立的向后臺請求數(shù)據(jù)。仔細(xì)看src中請求的位置斤讥,正是前面我們在urls.py中寫好的第二個path纱皆。即對應(yīng)了post_comment視圖中的GET邏輯:

comment/views.py

def post_comment(request, article_id, parent_comment_id=None):
    ...
    # 處理 GET 請求
    elif request.method == 'GET':
        ...
        return render(request, 'comment/reply.html', context)
    ...

視圖返回的comment/reply.html模板還沒有寫,接下來就把它寫好芭商。

老實說用iframe來加載ckeditor彈窗并不是很“優(yōu)雅”派草。單頁面上多個ckeditor的動態(tài)加載、取值蓉坎、傳參澳眷,博主沒能嘗試成功胡嘿。有興趣的讀者可以和我交流蛉艾。

Ajax提交表單

templates中新建comment目錄,并新建reply.html衷敌,寫入代碼:

templates/comment/reply.html

<!-- 載入靜態(tài)文件 -->
{% load staticfiles %}

<!DOCTYPE html>
<html lang="zh-cn">
<head>
    <meta charset="utf-8">
    <link rel="stylesheet" href="{% static 'bootstrap/css/bootstrap.min.css' %}">
</head>

<body>
    <form 
    action="." 
    method="POST"
    id="reply_form" 
    >
        {% csrf_token %}
        <div class="form-group">
            <div id="test">
                {{ comment_form.media }}
                {{ comment_form.body }}
            </div>
        </div>
    </form>
    <!-- 提交按鈕 -->
    <button onclick="confirm_submit({{ article_id }}, {{ parent_comment_id }})" class="btn btn-primary">發(fā)送</button>

    <script src="{% static 'jquery/jquery-3.3.1.js' %}"></script>
    <script src="{% static 'popper/popper-1.14.4.js' %}"></script>
    <script src="{% static 'bootstrap/js/bootstrap.min.js' %}"></script>

    <!-- csrf token -->
    <script src="{% static 'csrf.js' %}"></script>
    
    <script>
    $(function(){
        $(".django-ckeditor-widget").removeAttr('style');
    });

    function confirm_submit(article_id, comment_id){
        // 從 ckeditor 中取值
        let content = CKEDITOR.instances['id_body'].getData();
        // 調(diào)用 ajax 與后端交換數(shù)據(jù)
        $.ajax({
            url: '/comment/post-comment/' + article_id + '/' + comment_id,
            type: 'POST',
            data: {body: content},
            // 成功回調(diào)
            success: function(e){
                if(e === '200 OK'){
                    parent.location.reload();
                }
            }
        })
    }
    </script>

</body>
</html>

這個模板的作用是提供一個ckeditor的編輯器勿侯,所以沒有繼承base.html。讓我們拆開來講缴罗。

Ajax是什么

Ajax技術(shù)來提交表單助琐,與傳統(tǒng)方法非常不同。

傳統(tǒng)方法提交表單時向后端提交一個請求面氓。后端處理請求后會返回一個全新的網(wǎng)頁兵钮。這種做法浪費(fèi)了很多帶寬,因為前后兩個頁面中大部分內(nèi)容往往都是相同的舌界。與此不同掘譬,AJAX技術(shù)可以僅向服務(wù)器發(fā)送并取回必須的數(shù)據(jù),并在客戶端采用JavaScript處理來自服務(wù)器的回應(yīng)呻拌。因為在服務(wù)器和瀏覽器之間交換的數(shù)據(jù)大量減少葱轩,服務(wù)器回應(yīng)更快了。

雖然本教程只用到Ajax的一點(diǎn)皮毛,但是Ajax的應(yīng)用非常廣泛靴拱,建議讀者多了解相關(guān)知識垃喊。

這里會用到Ajax,倒不是因為其效率高袜炕,而是因為Ajax可以在表單提交成功后得到反饋本谜,以便刷新頁面。

核心代碼如下:

function confirm_submit(article_id, comment_id){
    // 從 ckeditor 中取值
    let content = CKEDITOR.instances['id_body'].getData();
    // 調(diào)用 ajax 與后端交換數(shù)據(jù)
    $.ajax({
        url: '/comment/post-comment/' + article_id + '/' + comment_id,
        type: 'POST',
        data: {body: content},
        // 成功回調(diào)
        success: function(e){
            if(e === '200 OK'){
                parent.location.reload();
            }
        }
    })
}
  • CKEDITOR是編輯器提供的全局變量偎窘,這里用CKEDITOR.instances['id_body'].getData()取得當(dāng)前編輯器中用戶輸入的內(nèi)容耕突。
  • 接下來調(diào)用了Jquery的ajax方法與視圖進(jìn)行數(shù)據(jù)交換。ajax中定義了視圖的url评架、請求的方法眷茁、提交的數(shù)據(jù)。
  • success是ajax的回調(diào)函數(shù)纵诞。當(dāng)?shù)玫揭晥D的相應(yīng)后執(zhí)行內(nèi)部的函數(shù)上祈。

前面寫視圖的時候,二級評論提交成功后會返回200 OK浙芙,回調(diào)函數(shù)接收到這個信號后登刺,就會調(diào)用reload()方法,刷新當(dāng)前的父頁面(即文章所在的頁面)嗡呼,實現(xiàn)了數(shù)據(jù)的更新纸俭。

csrf問題

代碼中有這么一行:

<script src="{% static 'csrf.js' %}"></script>

沒有這一行,后端會返回403 Forbidden錯誤南窗,并且表單提交失敗揍很。

還記得之前提交傳統(tǒng)表單時的{% csrf_token %}嗎?Django為了防止跨域攻擊万伤,要求表單必須提供這個token窒悔,驗證提交者的身份。

問題是在Ajax中怎么解決這個問題呢敌买?一種方法就是在頁面中插入這個csrf.js模塊简珠。

在static目錄中將csrf.js文件粘貼進(jìn)去,并在頁面中引用虹钮,就可以解決此問題了聋庵。

csrf.js文件可以在我的GitHub倉庫下載

測試芙粱!

進(jìn)入文章頁面祭玉,評論的邊上多出一個按鈕,可以對評論者進(jìn)行評論了:

image

點(diǎn)擊回復(fù)按鈕宅倒,彈出帶有富文本編輯器的彈窗:

image

點(diǎn)擊發(fā)送按鈕攘宙,頁面會自動刷新屯耸,并且二級評論也出現(xiàn)了:

image

還可以繼續(xù)對二級評論者評論,不過更高級的評論會被強(qiáng)制轉(zhuǎn)換為二級評論:

image

功能正常運(yùn)行了蹭劈。

有興趣的讀者可以打開SQLiteStudio疗绣,研究一下comment數(shù)據(jù)表的結(jié)構(gòu)。

總結(jié)

認(rèn)真看完本章并實現(xiàn)了多級評論的同學(xué)铺韧,可以給自己點(diǎn)掌聲了多矮。本章應(yīng)該是教程到目前為止知識點(diǎn)最多、最雜的章節(jié)哈打,涵蓋了MTV塔逃、Jquery、Ajax料仗、iframe湾盗、modal等多種前后端技術(shù)。

沒成功實現(xiàn)也不要急躁立轧,web開發(fā)嘛格粪,走點(diǎn)彎路很正常的。多觀察Django和控制臺的報錯信息氛改,找到問題并解決它帐萎。


?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末疆导,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子葛躏,更是在濱河造成了極大的恐慌澈段,老刑警劉巖,帶你破解...
    沈念sama閱讀 219,039評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件紫新,死亡現(xiàn)場離奇詭異均蜜,居然都是意外死亡李剖,警方通過查閱死者的電腦和手機(jī)芒率,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,426評論 3 395
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來篙顺,“玉大人偶芍,你說我怎么就攤上這事〉旅担” “怎么了匪蟀?”我有些...
    開封第一講書人閱讀 165,417評論 0 356
  • 文/不壞的土叔 我叫張陵,是天一觀的道長宰僧。 經(jīng)常有香客問我材彪,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,868評論 1 295
  • 正文 為了忘掉前任段化,我火速辦了婚禮嘁捷,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘显熏。我一直安慰自己雄嚣,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,892評論 6 392
  • 文/花漫 我一把揭開白布喘蟆。 她就那樣靜靜地躺著缓升,像睡著了一般。 火紅的嫁衣襯著肌膚如雪蕴轨。 梳的紋絲不亂的頭發(fā)上港谊,一...
    開封第一講書人閱讀 51,692評論 1 305
  • 那天,我揣著相機(jī)與錄音橙弱,去河邊找鬼封锉。 笑死,一個胖子當(dāng)著我的面吹牛膘螟,可吹牛的內(nèi)容都是我干的成福。 我是一名探鬼主播,決...
    沈念sama閱讀 40,416評論 3 419
  • 文/蒼蘭香墨 我猛地睜開眼荆残,長吁一口氣:“原來是場噩夢啊……” “哼奴艾!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起内斯,我...
    開封第一講書人閱讀 39,326評論 0 276
  • 序言:老撾萬榮一對情侶失蹤蕴潦,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后俘闯,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體潭苞,經(jīng)...
    沈念sama閱讀 45,782評論 1 316
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,957評論 3 337
  • 正文 我和宋清朗相戀三年真朗,在試婚紗的時候發(fā)現(xiàn)自己被綠了此疹。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,102評論 1 350
  • 序言:一個原本活蹦亂跳的男人離奇死亡遮婶,死狀恐怖蝗碎,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情旗扑,我是刑警寧澤蹦骑,帶...
    沈念sama閱讀 35,790評論 5 346
  • 正文 年R本政府宣布,位于F島的核電站臀防,受9級特大地震影響眠菇,放射性物質(zhì)發(fā)生泄漏边败。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,442評論 3 331
  • 文/蒙蒙 一捎废、第九天 我趴在偏房一處隱蔽的房頂上張望放闺。 院中可真熱鬧,春花似錦缕坎、人聲如沸怖侦。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,996評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽匾寝。三九已至,卻和暖如春荷腊,著一層夾襖步出監(jiān)牢的瞬間艳悔,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,113評論 1 272
  • 我被黑心中介騙來泰國打工女仰, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留猜年,地道東北人。 一個月前我還...
    沈念sama閱讀 48,332評論 3 373
  • 正文 我出身青樓疾忍,卻偏偏與公主長得像乔外,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子一罩,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,044評論 2 355

推薦閱讀更多精彩內(nèi)容