使用Masonite框架构建博客

构建博客

前言

本文档中的章节将包含多方面的教程。这些指导设计指引你使用Masonite从头到尾构建各式项目。

文档每个章节内容并没有细讲,我们目的是初步让你熟悉Masonite的内部工作机制。

由于本章仅设计让你尝试开始用Masonite编码,任何更多详细描述都在“提示”部分有指出。

一旦完成本教程,如果你想了解更多相关主题,建议你通过“提示”提供的链接来参考文档更多的描述。

提示区

在教程里包含了多个“提示区”。以下是不同“提示区”颜色示例:

{% hint style="success" %}

绿色“提示块”提供了当前讨论内容更详细的信息。

{% endhint %}

{% hint style="info" %}

蓝色“提示块”包含了要理解当前内容的背景知识,这是你绝对不能略过的内容。

{% endhint %}

安装

本教程假设你已经安装好了Masonite。如果你还没有安装,确保阅读安装 文档进行全新安装,并将

它跑起来。一旦你安装完成,你就可以继续阅读后面的内容了。

构建一个博客应用

我们会构建一个博客应用。我们将接触到Masonite包含的主要系统,以便让你更有信心区尝试更多高级教程,

或自己构建应用程序。

路由

通常在Masonite开发流中我们起点会先创建一个路由。所有路由都放置在routes/web.py,很容易理解。

它们包含了请求方法和路由方法。路由将请求的URI映射到控制器。

例如,创建一个GET路由:

{% tabs %}

{% tab title="routes/web.py" %}


from masonite.routes import Get

Get('/url', 'Controller@method')

{% endtab %}

{% endtabs %}

我们后面会再详细讨论控制器。

{% hint style="success" %}

你可以阅读 路由 文档了解更多内容

{% endhint %}

创建路由

我们准备为博客应用创建一个视图和控制器。

控制器是通过一个控制器类来实现的。这些控制器包含的方法由路由来请求,用来实现应用程序业务逻辑。

{% hint style="info" %}

如果你了解Django框架,你可以将控制器方法视为views.py文件的函数

{% endhint %}

让我们来创建第一个路由。我们可以将所有的路由放置在routes/web.py文件里的ROUTES列表。你可以看到

该文件已经有一条主页路由。让我们为博客应用增加一条路由。

{% tabs %}

{% tab title="routes/web.py" %}


ROUTES = [

    Get('/', 'WelcomeController@show').name('welcome'),

    # Blog Routes

    Get('/blog', 'BlogController@show')

]

{% endtab %}

{% endtabs %}

你可能注意到了BlogController@show字符串。它的意思为“适用blog控制器里的show方法来呈现该路由”。

这里唯一的问题我们还没有创建blog控制器。

{% hint style="success" %}

让我们进入下一步: 第二部 - 创建我们第一个控制器

{% endhint %}

创建控制器

所有的控制器都存放在app/http/controllers目录,Masonite建议一个文件只含一个控制器,有利于提升构建大型应用程序开发效率,大部分开发者

使用带有高级搜索功能特性的文本编辑器例如Sublime,VSCode或者Atom,这样更加快速在不同类切换。因为文件名和控制器对应,

也就更加能够快速定位到文件了。

当然,你也可以将控制器放置在任何想放置额地方。不过craft命令工具会将它们放置在不同文件中。如果你不能接受此目录行为,你也可以尝试自己

布局。

创建我们第一个控制器

就和Masonite大部分一样,你可以使用craft命令来生成一个脚手架控制器:

{% tabs %}

{% tab title="terminal" %}


$ craft controller Blog

{% endtab %}

{% endtabs %}

这将会在app/http/controllers目录下创一个类似如下的控制器:

{% tabs %}

{% tab title="app/http/controller/BlogController.py" %}


"""A BlogController Module."""

from masonite.request import Request

from masonite.view import View

from masonite.controllers import Controller

class BlogController(Controller):

    """BlogController Controller Class."""

    def __init__(self, request: Request):

        """BlogController Initializer

        Arguments:

            request {masonite.request.Request} -- The Masonite Request class.

        """

        self.request = request

    def show(self, view: View):

        pass

{% endtab %}

{% endtabs %}

很简答,对吧!你应该注意到show方法了。这些称之为“控制器方法”,类似Django的“视图”。

咱们也可以看到该show方法也被定义了在我们之前查看的路由文件中了。

返回视图

控制器中可以返回各种类型,现在我们仅尝试返回视图。在Masonite中视图就是html文件或者是“模板”。

不像其他Python框架它并不是Python对象。视图就是用户将会看到的东西。

这里比较重要,因为这次我们首次提到Python的IOC容器。我们在参数列表中指定需要一个view类,Masonite将会为我们注入。

现在我们不会关注整个控制器代码。...区域指示当前并不过多关注此区域代码:

{% tabs %}

{% tab title="app/http/controllers/BlogController.py" %}


from masonite.view import View

...

def show(self, view: View):

    return view.render('blogView')

{% endtab %}

{% endtabs %}

这里我们“类型提示”指出了View类。Masonite成为“自动依赖注入”。如果现在你还不能理解也不用担心。

多阅读文档内容你将逐步理解其中概念。

{% hint style="success" %}

确认学习更多关于服务容器.

{% endhint %}

创建我们的视图

你可能注意到返回的blog视图还不存在。

所有的视图都存放在resources/templates目录。我们可以创建一个新文件resources/templates/blog.html

或者我也可以使用craft另外一个命令来生成该视图文件:

{% tabs %}

{% tab title="terminal" %}


$ craft view blog

{% endtab %}

{% endtabs %}

该命令会为我们创建一个空模板文件。

我们可以放入如下文本:

{% tabs %}

{% tab title="resources/templates/blog.html" %}


这是一个博客。

{% endtab %}

{% endtabs %}

然后运行服务器

{% tabs %}

{% tab title="terminal" %}


$ craft serve

{% endtab %}

{% endtabs %}

接着打开浏览器浏览 http://localhost:8000/blog. 你将看到“这是一个博客。”出现在浏览器页面中。

身份验证

大多数应用程序都需要身份认证。Masonite提供了一个craft命令来生成身份认证系统的脚手架。通常在新建应用后就可以执行了,

因为该命令会创建一些路由和视图。

就我们博客,我们需要提供注册功能以便可以发布博客。我们可以使用如下命令来创建身份认证系统;

{% tabs %}

{% tab title="terminal" %}


$ craft auth

{% endtab %}

{% endtabs %}

执行命令后我们应该可以看到一些新文件被生成提示。你可以检查控制器目录,找到一些和注册相关的处理。

稍后我们会详细介绍创建的文件。

数据库配置

为了可以让用户注册,我们需要一个数据库。希望你本地已将安装类似MySQL或者Postgres数据库,不过这里

我们假设你并没有,所以本例中我们仅使用SQLite。

现在我们只需要更改一些环境变量以便让Masonite可以创建SQLite数据库。

这些环境变量可以在根目录的.env文件找到。打开该文件你可以看到类似如下内容:

{% tabs %}

{% tab title=".env" %}


DB_CONNECTION=mysql

DB_HOST=127.0.0.1

DB_PORT=3306

DB_DATABASE=masonite

DB_USERNAME=root

DB_PASSWORD=root

{% endtab %}

{% endtabs %}

继续,我们修改数据库连接配置,将sqlite加入到DB_CONNECTION变量中,以及当使用数据库迁移时创建的数据库文件名,这里我使用blog.db

{% tabs %}

{% tab title=".env" %}


DB_CONNECTION=sqlite

DB_HOST=127.0.0.1

DB_PORT=3306

DB_DATABASE=blog.db

DB_USERNAME=root

DB_PASSWORD=root

{% endtab %}

{% endtabs %}

迁移

一旦设置好环境变量,我们就可以继续迁移数据库。Masonite提供了一个开箱即用的用户表,包含了用户的基础信息。你可以在进行迁移前进行修改。

默认配置适用大部分长江,你可以在后期任意添加或者删除列。

{% tabs %}

{% tab title="terminal" %}


$ craft migrate

{% endtab %}

{% endtabs %}

这将创建用户表,还有一个迁移表用来追踪任何后期迁移操作。

创建用户

现在我们有了身份认证以及完成了迁移,现在让我们来创建第一个用户。还记得我们之前运行的craft auth命令生成了的一些模板和控制器吗?

我们来运行服务器:

{% tabs %}

{% tab title="terminal" %}


$ craft serve

{% endtab %}

{% endtabs %}

访问 http://localhost:8000/register 并填充表单。你可以选择任意的用户名和邮箱,例如:


Username: demo

Email: demo@email.com

Password: Password123@

迁移

现在我们已经配置好了身份认证和必要的迁移,让我们来创建一些新的迁移以便存储我们的博文。

在本教程中我们的博文表应该有一些较为明显的字段。让我们来一步步学习使用Masonite创建迁移。

Craft 命令

不惊讶,我们已经有了craft命令来创建迁移。你可以阅读更多关于 数据库迁移 ,这里将会

简单介绍:

{% tabs %}

{% tab title="terminal" %}


$ craft migration create_posts_table --create posts

{% endtab %}

{% endtabs %}

该命令会构建一个创建博文表的基础迁移。按约定,表名应该使用复数(列名使用单数)。

迁移会建立在databases/migrations目录下。让我们打开该文件从第6行开始:

{% tabs %}

{% tab title="databases/migrations/2018_01_09_043202_create_posts_table.py" %}


def up(self):

    """

    Run the migrations.

    """

    with self.schema.create('posts') as table:

        table.increments('id')

        table.timestamps()

{% endtab %}

{% endtabs %}

我们来为我们的博文表加入标题,作者和主体字段。

{% tabs %}

{% tab title="databases/migrations/2018_01_09_043202_create_posts_table.py" %}


def up(self):

    """

    Run the migrations.

    """

    with self.schema.create('posts') as table:

        table.increments('id')

        table.string('title')

        table.integer('author_id').unsigned()

        table.foreign('author_id').references('id').on('users')

        table.string('body')

        table.timestamps()

{% endtab %}

{% endtabs %}

{% hint style="success" %}

这里已经相当直译了,如果想了解更多,可以阅读 数据库迁移文档。

{% endhint %}

现在我们可以将这个迁移应用到博文表中了。

{% tabs %}

{% tab title="terminal" %}


$ craft migrate

{% endtab %}

{% endtabs %}

模型

现在我们已经完成了我们的迁移工作。现在我们来创建模型。

Masonite中模型和其他框架有一点不同。Masonite使用Orator,基于Active Record的ORM实现。这就意味着并不是通过将模型转化为迁移。

在Masonite中模型和迁移是独立的。模型负责呈现表却不管表实际是怎么样的。

创建模型

再次,我们使用craft命令来创建我们的模型:

{% tabs %}

{% tab title="terminal" %}


$ craft model Post

{% endtab %}

{% endtabs %}

注意这里我们使用单数来命名我们的模型。默认Orator会通过在追加"s"到名称中查找数据库中复数命名的表(这里是posts) 。

我们会描述如何指定其表名称。

模型创建在app/Post.py文件中,打开类似如下:

{% tabs %}

{% tab title="app/Post.py" %}


"""A Post Database Model."""

from config.database import Model

class Post(Model):

    pass

{% endtab %}

{% endtabs %}

很简单,对吧!就如前述,我们不用修改模型。模型会自动映射到迁移表。

表名称

再一次说明,表名称会使用模型名称的复数形式(通过追加"s")。如果你使用了类似不一样类似"blog"而不是"blogs"来命名的话,

我们可以手动指名:

{% tabs %}

{% tab title="app/Post.py" %}


"""A Post Database Model."""

from config.database import Model

class Post(Model):

    __table__ = 'blog'

{% endtab %}

{% endtabs %}

批量赋值

Orator 出于安全措施默认不允许批量赋值。我们需要手动指明哪些字段是可以填充的:

{% tabs %}

{% tab title="app/Post.py" %}


"""A Post Database Model."""

from config.database import Model

class Post(Model):

    __fillable__ = ['title', 'author_id', 'body']

{% endtab %}

{% endtabs %}

表关系

表关系非常直观。还记得我们之前创建迁移时包含了一个外键吗?我们可以在模型创建类似如下关系:

{% tabs %}

{% tab title="app/Post.py" %}


"""A Post Database Model."""

from config.database import Model

from orator.orm import belongs_to

class Post(Model):

    __fillable__ = ['title', 'author_id', 'body']

    @belongs_to('author_id', 'id')

    def author(self):

        from app.User import User

        return User

{% endtab %}

{% endtabs %}

因为Masonite模型工作方式,一些模型可能会相互依赖,所以为了防止循环引用,通常最好按照如上方式进行表关系模型导入。

{% hint style="success" %}

这里我们没有过多讨论不同关系类型,想学习更多的话阅读 ORM 文档。

{% endhint %}

设计我们的博客

让我们配置一些HTML以便我们能学习更多视图是如何工作。为了不涉及太多HTML代码,这里我们只会编写一个非常基础的模板,你可以基于

所学构建更加丰富的博客模板(或者从互联网搜集)。

现在我们已经配置号了模型和迁移,现在基础工作昨晚了,可以开始创建和修改博文了。

在构建模板前我还需要检查用户是否登录。

新建博文的模板

新建博文的URL是/blog/create,提供一个简单的表单

{% tabs %}

{% tab title="resources/templates/blog.html" %}


<form action="/blog/create" method="POST">

    {{ csrf_field }}

    <input type="name" name="title">

    <textarea name="body"></textarea>

</form>

{% endtab %}

{% endtabs %}

注意这里有一个奇怪的{{ csrf_field }} 记号。Masonite利用该标记来生成CSRF字段进行CSRF保护。

现在因为在我们的博文表里有一个外键,所以我们在提供创建页面前要确保用户已经登录,让我们稍微改一下模板:

{% tabs %}

{% tab title="resources/templates/blog.html" %}


{% if auth() %}

    <form action="/blog/create" method="POST">

        {{ csrf_field }}

        <label> Title </label>

        <input type="name" name="title"><br>

        <label> Body </label>

        <textarea name="body"></textarea>

        <input type="submit" value="Post!">

    </form>

{% else %}

    <a href="/login">Please Login</a>

{% endif %}

{% endtab %}

{% endtabs %}

auth()是一个视图辅助函数,要么返回当前用户,要么返回None

{% hint style="success" %}

Masonite使用Jinja2模板引擎,如果你还不太熟悉该模板引擎。确保阅读官方文档

{% endhint %}

静态文件

出于简单考虑,我们不会对我们的博客使用类似Bootstrap进行美化,但是了解如何在Masonite中如何使用类似CSS静态文件是很有必要的。

我们将通过为我们的博客加入一个CSS文件来学习。

首先,进入到storage/static/目录,创建一个blog.css文件,加入任意内容。本教程里仅将html页面背景设为灰色。

{% tabs %}

{% tab title="storage/static/blog.css" %}


html {

    background-color: #ddd;

}

{% endtab %}

{% endtabs %}

现在我们可以添加到我们模板顶部:

{% tabs %}

{% tab title="resources/templates/blog.html" %}


<link href="/static/blog.css" rel="stylesheet">

{% if auth() %}

    <form action="/blog/create" method="POST">

        {{ csrf_field }}

        <label> Title </label>

        <input type="name" name="title"><br>

        <label> Body </label>

        <textarea name="body"></textarea>

        <input type="submit" value="Post!">

    </form>

{% else %}

    <a href="/login">Please Login</a>

{% endif %}

{% endtab %}

{% endtabs %}

好了,静态文件很简单。了解其工作非常重要,不过在这里我们更多关注后端的东西,所以暂时忽略它。

javascript使用方式一样:

{% tabs %}

{% tab title="resources/templates/blog.html" %}


<link href="/static/blog.css" rel="stylesheet">

{% if auth() %}

    <form action="/blog/create" method="POST">

        {{ csrf_field }}

        <label> Title </label>

        <input type="name" name="title"><br>

        <label> Body </label>

        <textarea name="body"></textarea>

        <input type="submit" value="Post!">

    </form>

{% else %}

    <a href="/login">Please Login</a>

{% endif %}

<script src="/static/script.js"></script>

{% endtab %}

{% endtabs %}

{% hint style="success" %}

关于关于静态文件信息,查看静态文件 文档。

{% endhint %}

博文创建控制器和Container

注意我们的URI地址为/blog/create,我们需要指向到我们控制器方法。本例我们指向到store方法。

让我们打开routes/web.py文件,创建新路由。仅下如下内容加入到ROUTES列表:

{% tabs %}

{% tab title="routes/web.py" %}


from masonite.routes import Get, Post

...

Post('/blog/create', 'BlogController@store'),

{% endtab %}

{% endtabs %}

我们在控制器添加store方法:

{% tabs %}

{% tab title="app/http/controllers/BlogController.py" %}


...

def show(self, view: View):

    return view.render('blog')

# New store Method

def store(self):

    pass

{% endtab %}

{% endtabs %}

回到之前的表单,这里我们接收两个表单元素:title和body。我们导入Post模型,根据输入内容创建博文。

{% tabs %}

{% tab title="app/http/controllers/BlogController.py" %}


from app.Post import Post

from masonite.request import Request

...

def store(self, request: Request):

    Post.create(

        title=request.input('title'),

        body=request.input('body'),

        author_id=request.user().id

    )

    return 'post created'

{% endtab %}

{% endtabs %}

注意到这里我们使用了request: Request。它是Request对象。它从哪里来的?这就是Masonite强大的地方,也就是我们要首次介绍到的

服务容器服务容器 是Masonite中,提供了

强大的能力来帮你从Masonite中获取对象(如这里的 Request)。确保阅读了更多文档来理解该重要概念。

{% hint style="success" %}

阅读关于 服务容器 文档。

{% endhint %}

这里还注意到我们使用了一个input()方法。Masonite并没有区分GET或者POST请求获取输入参数,统一使用该input方法方法来接收。

继续,使用craft serve跑起服务器,访问http://localhost:8000/blog并新建一个博文。这里应该会向/blog/create路由发起一个POST请求,并得到

响应结果为"post created"。

显示我们的博文

让我们继续看看如何显示我们刚才新建的博文。这部分我们将创建2个新的模板用来显示所有博文和指定的单个博文。

创建模板

让我们创建2个新模板。

{% tabs %}

{% tab title="terminal" %}


$ craft view posts

$ craft view single

{% endtab %}

{% endtabs %}

让我们来显示所有的博文。

创建控制器

让我们为博文创建一个独立的控制器,与BlogController隔离开。

{% tabs %}

{% tab title="terminal" %}


$ craft controller Post

{% endtab %}

{% endtabs %}

真棒!现在我们会在show方法来显示所有的博文,用single方法来显示特定博文。

Show 方法

让我们找到show方法并带有所有博文的视图:

{% tabs %}

{% tab title="app/http/controllers/PostController.py" %}


from app.Post import Post

...

def show(self, view: View):

    posts = Post.all()

    return view.render('posts', {'posts': posts})

{% endtab %}

{% endtabs %}

博文路由

我需要为该方法增加一条路由:

{% tabs %}

{% tab title="routes/web.py" %}


Get('/posts', 'PostController@show')

{% endtab %}

{% endtabs %}

博文视图

我们的博文视图非常简单:

{% tabs %}

{% tab title="resources/templates/posts.html" %}


{% for post in posts %}

    {{ post.title }}

    <br>

    {{ post.body }}

    <hr>

{% endfor %}

{% endtab %}

{% endtabs %}

进行跑起服务器,访问http://localhost:8000/posts路由。你应该可以看到一个博文列表。如果只看到了1条,访问http://localhost:8000/blog创建多个博文,以便稍后我们要展现单个博文。

显示作者

还记得之前我们建立了作者关联。Orator将会取得该关联为我们生成一个属性,这样我们可以直接将作者的名字也显示出来:

{% tabs %}

{% tab title="resources/templates/posts.html" %}


{% for post in posts %}

    {{ post.title }} by {{ post.author.name }}

    <br>

    {{ post.body }}

    <hr>

{% endfor %}

{% endtab %}

{% endtabs %}

让我们来重复该过程,但是稍微做一些修改。

单个博文路由

接下来我们来显示单个博文。我需要为该方法加入路由:

{% tabs %}

{% tab title="routes/web.py" %}


Get('/post/@id', 'PostController@single')

{% endtab %}

{% endtabs %}

注意这里拥有一个@id字符串。后面我们可以通过此方式从URL中来获取参数。

Single 方法

让我们来创建一个single方法,用来显示单条博文。

{% tabs %}

{% tab title="app/http/controllers/PostController.py" %}


from app.Post import Post

from masonite.request import Request

from masonite.view import View

...

def single(self, view: View, request: Request):

    post = Post.find(request.param('id'))

    return view.render('single', {'post': post})

{% endtab %}

{% endtabs %}

我们使用param()方法来获取URL的参数。注意这里键是由之前路由中指定的@id

{% hint style="info" %}

在真是应用中我可能会命名类似@slug,以及使用request().param('slug')来获取.

{% endhint %}

单个博文视图

我只需要显示单个博文,所以就放在一个简单视图中:

{% tabs %}

{% tab title="resources/templates/single.html" %}


{{ post.title }}

<br>

{{ post.body }}

<hr>

{% endtab %}

{% endtabs %}

继续跑起服务,访问http://localhost:8000/post/1 路由以及http://localhost:8000/post/2 看看有什么不一样。

更新和删除博文

到现在,我们花了很长时间来实现之前的逻辑,现在我们加快速度来实现更新和删除博文。我们假设你已经了解我们之前所学,所以我们会快速去实现余下部分,因为接下来的内容都是之前有学习过的知识点。

更新控制器方法

我们在 PostController 新增一个update方法:

{% tabs %}

{% tab title="app/http/controllers/PostController.py" %}


def update(self, view: View, request: Request):

    post = Post.find(request.param('id'))

    return view.render('update', {'post': post})

def store(self, request: Request):

    post = Post.find(request.param('id'))

    post.title = request.input('title')

    post.body = request.input('body')

    post.save()

    return 'post updated'

{% endtab %}

{% endtabs %}

由于现在我们所学内容足以让我们快速一下实现两个方法了。一个用来显示修改表单,另一个用来处理更新数据库。

创建视图


$ craft view update

{% tabs %}

{% tab title="resources/templates/update.html" %}


<form action="/post/{{ post.id }}/update" method="POST">

    {{ csrf_field }}

    <label for="">Title</label>

    <input type="text" name="title" value="{{ post.title }}"><br>

    <label>Body</label>

    <textarea name="body">{{ post.body }}</textarea><br>

    <input type="submit" value="Update">

</form>

{% endtab %}

{% endtabs %}

创建路由

记得要为我们创建的两个方法添加路由:

{% tabs %}

{% tab title="routes/web.py" %}


Get('/post/@id/update', 'PostController@update'),

Post('/post/@id/update', 'PostController@store'),

{% endtab %}

{% endtabs %}

就是这样,我们现在应该可以更新我们的博文了。

删除方法

我们继续来扩展它,为它实现删除方法。

{% tabs %}

{% tab title="app/http/controllers/PostController.py" %}


from masonite.request import Request

...

def delete(self, request: Request):

    post = Post.find(request.param('id'))

    post.delete()

    return 'post deleted'

{% endtab %}

{% endtabs %}

添加路由

{% tabs %}

{% tab title="routes/web.py" %}


Get('/post/@id/delete', 'PostController@delete'),

{% endtab %}

{% endtabs %}

注意这里我们使用了GET路由,这里使用POST方法会更合理,这里留给你自己实现。我们这里只是在更新视图里加入一个删除链接用来删除博文。

更新模板

我们可以方一个删除链接到我们更新模板:

{% tabs %}

{% tab title="resources/templates/update.html" %}


<form action="/post/{{ post.id }}/update" method="POST">

    {{ csrf_field }}

    <label for="">Title</label>

    <input type="text" name="title" value="{{ post.title }}"><br>

    <label>Body</label>

    <textarea name="body">{{ post.body }}</textarea><br>

    <input type="submit" value="Update">

    <a href="/post/{{ post.id }}/delete"> Delete </a>

</form>

{% endtab %}

{% endtabs %}

真棒!你现在应该有个博客,你可以创建,显示,更新以及删除博文!继续去实现惊人的东西吧!

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,293评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,604评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,958评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,729评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,719评论 5 366
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,630评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,000评论 3 397
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,665评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,909评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,646评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,726评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,400评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,986评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,959评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,197评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,996评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,481评论 2 342