SSH框架之Struts2初识(一)

第一节:Struts2概述

Struts2是一个基于MVC设计模式的Web应用框架,它本质上相当于一个servlet,在MVC设计模式中,Struts2作为控制器(Controller)来建立模型与视图的数据交互。Struts 2是Struts的下一代产品,是在 struts 1和WebWork的技术基础上进行了合并的全新的Struts 2框架。其全新的Struts 2的体系结构与Struts 1的体系结构差别巨大。Struts 2以WebWork为核心,采用拦截器的机制来处理用户的请求,这样的设计也使得业务逻辑控制器能够与ServletAPI完全脱离开,所以Struts 2可以理解为WebWork的更新产品。虽然从Struts 1到Struts 2有着太大的变化,但是相对于WebWork,Struts 2的变化很小。

Struts拥有优良的设计和功能,其优势具体如下:

  • 项目开源,使用及拓展方便,天生优势。
  • 提供Exception处理机制。
  • Result方式的页面导航,通过Result标签很方便的实现重定向和页面跳转。
  • 通过简单、集中的配置来调度业务类,使得配置和修改都非常容易。
  • 提供简单、统一的表达式语音来访问所有可供访问的数据。
  • 提供标准、强大的验证框架和国际框架。
  • 提供强大的、可以有效减少页面代码的标签。
  • 提供良好的Ajax支持。
  • 拥有简单的插件,只需要放入相应的JAR包,任何人都可以扩展Struts2框架,比如自定义拦截器、自定义结果类型、自定义标签等,为Struts定制需要的功能,不需要什么特殊配置,并且可以发布给其他人使用。
  • 拥有智能的默认设置,不需要另外进行繁琐的设置。使用默认设置就可以完成大多数项目程序开发所需要的功能。

1.1 前端控制器模式

什么是前端控制器模式呢?如下图


image.png

在图中传统方式的开发,有一次请求就会对应一个Servlet。这样会导致出现很多Servlet。而Struts2将所有的请求都先经过一个前端控制器,在前端控制器中实现框架的部分功能,剩下具体操作要提交到具体的Action中。那么所有的请求都会经过前端控制器,那用什么来实现前端控制器呢?过滤器就是最好的一个实现方式,因为需要所有的请求都可以被过滤器拦截,然后在过滤器中实现部分功能。

第二节:Struts2的入门(下载、集成、配置、运行)

2.1下载

下载地址:https://struts.apache.org/

image.png

2.2 jar包结构分析

解压后的目录详情:


image.png
  • apps:该文件夹用于存放官方提供的Struts2示例程序,这些程序可以作为学习者的学习资料,可为学习者提供很好的参照。各示例均为war文件,可以通过zip方法进行解压。
  • docs:该文件夹用于存放官方提供的Struts2文档,包括Struts2的快速入门、Struts2的文档以及API文档等内容。
  • lib:存放Struts2的核心类库,以及Struts2的第三方插件类库。
  • src:用于存放该版本Struts2框架所对应的源代码。

2.3 创建web项目,引入Struts2jar包

将示例工程struts-2.5.20\apps\struts2-rest-showcase\WEB-INF\lib下的jar包引入项目中。


image.png

部分jar包的作用

  • asm :操作java字节码的类库
  • asm-commons:提供基于事件的表现形式
  • asm-tree:提供基于对象的表现形式
  • struts-core:Struts2框架的核心类库
  • xwork-core:webWork核心库,Struts2的构建集成
  • ognl:对象图导航语言(Object Graph Navigation Lanaguage),Struts2框架通过其读写对象的属性。
  • freemarker:Struts2标签模板使用的类库
  • javassist:javaScript字节码解释器
  • commons-fileupload:Struts2文件上传组件依赖包
  • common-long:包含一些数据类型工具,是对java.lang包的增强
  • log4j-api:Struts2的日志管理组件依赖包的api
  • log4j-core:Struts2的日志管理组件依赖包

2.4 测试页面Jsp的编写

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
  <head>
    <title>Struts2初识</title>
  </head>
  <body>
  <a href="${pageContext.request.contextPath}/Struts2Demo.action">点击访问第一个action</a>
  </body>
</html>

2.5 测试Action的编写

在src下建立Struts2Demo的文件,并且创建一个public的execute的方法。返回一个"success"的字符串与struts.xml中的result标签配合,实现页面的跳转。

public class Struts2Demo {

    /**
     * 不提供任何参数,供反射使用
     * @return
     */
    public String execute(){
        System.out.println("我的第一个action访问成功了。");
        return "success";
    }
}

Action类编写好了以后,Struts2框架如何识别它就是一个Action呢,则需要我们队Action类进行配置。

2.6 Action的配置:

在示例代码WEB-INF的classes中,有一个名称为struts.xml的文件,这个文件就是struts2的配置文件。
我们在开发中需要将struts.xml文件引入到工程的src下,配置的内容如下:

<?xml version="1.0" encoding="UTF-8" ?>

<!DOCTYPE struts PUBLIC
        "-//Apache Software Foundation//DTD Struts Configuration 2.5//EN"
        "http://struts.apache.org/dtds/struts-2.5.dtd">


<struts>
    <!--  Overwrite Convention -->
    <constant name="struts.convention.action.suffix" value="Controller"/>
    <constant name="struts.convention.action.mapAllMatches" value="true"/>

    <!--配置一个包-->
    <package name="strutsDemo1" extends="struts-default" namespace="/">
        <!--配置Action-->
        <action name="Struts2Demo" class="com.seapp.struts2.action.Struts2Demo">

        </action>
    </package>
</struts>

Action类已经配置好了,但现在还不可以执行。因为之前我们介绍过,WEB层框架都有一个特点就是基于前端控制器的模式,这个前端控制器是由过滤器实现的,所以我们需要配置Struts2的核心过滤器。这个过滤器的名称是StrutsPrepareAndExecuteFilter

2.7 配置核心过滤器

Struts2框架要想执行,所有的请求都需要经过这个前端控制器(核心过滤器),所以需要配置这个核心过滤器。因为这个过滤器完成了框架的部分的功能。在web.xml中具体的配置如下:

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
         version="4.0">

    <!--配置Struts2的过滤器(核心控制器)-->
    <filter>
        <filter-name>struts</filter-name>
        <filter-class>org.apache.struts2.dispatcher.filter.StrutsPrepareAndExecuteFilter</filter-class>
    </filter>

    <filter-mapping>
        <filter-name>struts</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>

</web-app>

2.8 配置结果页面跳转,并访问action

<?xml version="1.0" encoding="UTF-8" ?>

<!DOCTYPE struts PUBLIC
        "-//Apache Software Foundation//DTD Struts Configuration 2.5//EN"
        "http://struts.apache.org/dtds/struts-2.5.dtd">
<struts>
    <!--配置一个包-->
    <package name="strutsDemo1" extends="struts-default" namespace="/">
        <!--配置Action-->
        <action name="struts2Demo" class="com.seapp.struts2.action.struts2Demo">
            <result name="success">/demo01.jsp</result>
        </action>
    </package>
</struts>

访问http://localhost:8080/struts2/index.jsp后跳转到如下界面:

image.png

点击跳转后,进入如下界面:
image.png

至此,入门测试项目完成。

2.9 总结:

在测试阶段碰到如下问题:在通过配置文件struts.xml来访问struts2Demo的action时报错:“Wrong method was defined as an action method: index”,参考https://blog.csdn.net/github_38616039/article/details/79045432该文档解决问题。根据文档描述是因为“struts2-rest-plugin-2.5.20.jar”包导致,将其移除后可正常访问。暂不明白具体原因,望各位前辈指点.....

第二节:Struts2的开发流程分析

从客户端发送请求过来,先经过前端控制器(核心过滤器StrutsPrepareAndExecuteFilter)过滤器中执行一组拦截器(一组拦截器,就会玩部分功能代码),在Struts2中定义很多拦截器,在其默认栈中的拦截器会得到执行。


image.png

第三节:Struts2的常见配置

在针对Struts2中的struts.xml文件的配置等内容进行详细的学习前,先对Struts2的配置文件的加载顺序有一定的了解,这样对后面学习Struts2的配置都是有帮助的。

3.1 Struts2配置文件的加载顺序

每次从客户端发送请求到服务器都要先经过Struts2的核心过滤器StrutsPrepareAndExecuteFilter,这个过滤器有两个功能:预处理和执行。在预处理中主要就是来加载配置文件的。对应的是过滤器中的init方法,而执行是用来执行一组拦截器完成部分功能的,对应的是过滤器的doFilter方法。所以要了解Struts2的配置文件的加载顺序,需要从过滤器的init方法开始:


image.png

在init方法中,调用了init的initDispacher的方法来加载配置文件,进入该方法:


image.png

在该方法中又调用了dispatcher的init方法,继续往下跟踪:
image.png

这一系列代码就是用来截止Struts2的配置文件的:

  • this.init_DefaultProperties():加载org.apache.struts.default.properties配置,是Struts2的所有常量。
  • this.init_TraditionalXmlConfigurations():加载struts-default.xml,struts-plugin.xml,struts.xml的配置项。
  • this.init_LegacyStrutsProperties():加载用户自定义的struts.properties.
  • this.init_CustomConfigurationProviders():加载用户配置的提供对象
  • this.init_FilterInitParameters():加载web.xml
  • this.init_AliasStandardObjects():加载标准对象。
    根据上面的代码我们可以得出配置文件的加载顺序如下:
/******
  * default.properties
  * struts-default.xml
  * struts-plugin.xml
  * struts.xml      配置Action以及常量
  * struts.properties     配置常量
  * web.xml        配置核心过滤器以及常量
******/

前三个配置文件我们不关心,是Struts2内部的配置文件,我们无法修改,能修改的文件就是struts.xml,struts.properties,web.xml配置文件。这三个配置文件的加载是有顺序的,这三个配置文件都可以修改Struts2常量的值,要记住的是,后加载配置文件中常量的值会将先加载的配置文件中的常量的值给覆盖。

3.2 Action的配置

Struts2框架的核心配置文件是struts.xml文件,该文件主要用来配置Action的请求和对应关系。
<package>标签的配置
Struts2框架的核心组件是Action和拦截器,它使用包来管理Action和拦截器。每个包就是多个Action,多个拦截器,多个拦截器引用的集合。在struts.xml文件中,package元素用于定义包配置,每个package元素定义一个包配置。package元素的常用属性,如表所示:

image.png

其中在配置包时,必须制定name属性,就是包的标识。除此之外,还可以指定一个可选的extends属性,extends属性值必须是另一个包的name属性值,但该属性值通常都设置为struts-default,这个该包中的action就具有了Struts2框架默认的拦截器等功能了。Struts2还提供了一种所谓的抽象包,抽象包不能包含Action定义。为了显示指定一个包是抽象包,可以为该package元素增加abstract = "true"属性。
在package中还有namespace的配置,namespace属性与action标签的name属性共同决定了访问路径。namespace有如下三种配置:

  • 默认名称空间:默认名称空间就是namespace=""。
  • 根名称空间:根名称空间就是namespace = "/"
  • 带名称的名称空间:带名称的名称空间就是namespace = "/demo1"

<Action>的配置
Action映射是框架中的基本“工作单元”。Action映射就是讲一个请求的URL映射到一个Action类,当一个请求匹配某个Action名称时,框架就使用这个映射来确定如何处理请求。在sturts.xml文件中,通过<action>元素对请求的Action和Action类进行配置。
<action>元素中共有4个属性,这4个属性的说明如下:

image.png

其中name属性和namespace属性共同决定了访问路径,class对应的是Action类的全路径。Method指定了执行Action的那个方法,默认是execute方法。

第四节:Struts2常量的配置:

Sturts2中的常量大多在默认配置文件中已经配置好了,但根据用户需求的不同,开发的要求也不同,可能需要修改这些常量值,修改的方法就是在配置文件中对常量进行重新配置。
Struts2常量配置共有3种方式,分别如下:

  • 在struts.xml文件中使用<constant>元素配置常量。
  • 在struts.properties文件中配置常量。
  • 在web.xml文件中通过<init-param>元素配置常量。
    具体如下:

4.1 在Struts.xml文件中通过<constant>元素配置常量

在struts.xml文件中通过<constant>元素来配置常量,是最常用的方式。在struts.xml文件中通过<constant.../>元素来配置常量时,需要指定两个必填的属性name和value。

  • name:该属性指定了常量的常量名。
  • value:该属性指定了常量的常量值。
    在struts.xml文件中配置的示例代码如下:
 <!--设置默认编码集为UTF-8-->
    <constant name="struts.i18n.encoding" value="UTF-8"/>
    <!--设置开发模式-->
    <constant name="struts.devMode" value="true"/>

值的一提的是,struts.properties文件能配置的常量都可以在struts.xml文件中用<constant>元素来配置。

4.2 在struts.properties文件中配置常量

struts.properties文件是一个标准的properties文件,其格式是key-value对,即每个key对应一个value,key表示的是Struts2框架中的常量,而value则是其常量值。在struts.properties文件中配置常量的方式

# 设置默认编码集为UTF-8
struts.il8n.encoding=UTF-8
# 设置action请求拓展名为action或者没有拓展名
struts.action.extension=action
# 设置不使用开发模式
struts.devMode = false
# 设置不开启动态方法调用
struts.enable.DynamicMethodInvocation = false

需要注意的是,和struts.xml文件一样,struts.properties文件也应存放于WEB-INF/classes路径下。

4.3 在web.xml文件中通过初始化参数配置常量

在web.xml文件中配置核心过滤器StrutsPrepareAndExecuteFilter时,通过初始化参数来配置常量。通过<filter>元素的<init-param>子元素指定,每个<init-param>元素配置了一个Struts2常量。在web.xml文件中通过初始化参数配置常量方式,具体如下:

   <!--配置Struts2的过滤器(核心控制器)-->
    <filter>
        <filter-name>struts</filter-name>
        <filter-class>org.apache.struts2.dispatcher.filter.StrutsPrepareAndExecuteFilter</filter-class>
        
        <!--配置编码集为UTF-8-->
        <init-param>
            <param-name>struts.i18n.encoding</param-name>
            <param-value>UTF-8</param-value>
        </init-param>
        
    </filter>

注意:<init-param>标签必须放在<filter>标签下。
Struts2所支持的常量数量众多,在struts2-core-***.jar压缩文件的org/apche/struts2路径下有一个default.properties文件,该文件里为Struts2的所有常量都指定了默认值,我们可以通过查看该文件来了解Struts2所支持的常量。
后加载的配置文件的常量的值会覆盖先加载的配置文件中常量的值
在实际的开发中我们更习惯使用struts.xml修改struts2的常量。

4.4 分模块开发的配置

在实际开发中,我们通常很多人都需要修改同一个配置文件就是struts.xml。因为这个文件是struts2的核心配置文件,而且这个文件一旦改错了一点,那么会导致整个项目都会出现问题,所以struts2提供了<include>标签解决这个问题。
<include>元素用来在一个struts.xml配置文件中包含其他的配置文件,包含配置体现的是软件工程中的“分而治之”原则。Struts2允许将一个配置文件分解成多个配置文件,从而提供配置文件的可读性。Struts2默认只加载WEB-INF/classes下的strts.xml文件,但一旦通过多个xml文件来配置Action,就必须通过struts.xml文件来包含其他配置文件。
具体示例如下:

<struts>
<!--不指定默认路径在src下时的方式 -->
<include file="struts-shop.xml"/>
<!-- 配置文件在具体包中时的方式 -->
<include file = "com/seapp/struts2/action/strurs-product.xml"/>
</struts>

需要注意的是:每一个被包含的配置文件都是标准的Struts2配置文件,一样包含DTD信息、Struts2配置文件的根元素信息等。通常将Struts2的所有配置文件都放在Web项目的WEB-INF/classes路径下,struts.xml文件包含了其他配置文件,在Struts2框架自动加载struts.xml文件时,完成加载所有的配置信息。

第五节:Struts2中Action类的创建

在Struts2的应用开发中,Action作为框架的核心类,实现对用户请求的处理,Action类被称为业务逻辑控制器。一个Action类代表一次请求或调用,每个请求的动作都对应于一个相应的Action类,一个Action类是一个对立的工作单元。也就是说,用户的每次请求,都会转到一个相应的Action类里面,由这个Action类来进行处理。简而言之,Action就是用来处理一次用户请求的对象。

5.1 Action的编写方式:

Action是一个POJO的类
在Struts2中,Action可以不继承特殊的类或不实现任何特殊的接口,仅仅是一个POJO。POJO全称Plain Ordinary Java Object(简单的Java对象),只是具有一部分getter/setter方法的类,就可以称作POJO,一般在POJO类中,要有一个公共的无参的构造方法(采用默认的构造方法就可以)和一个execute()方法。定义格式如下:

public class struts2Demo {

    /**
     * 不提供任何参数,供反射使用
     * @return
     */
    public String execute(){
        System.out.println("我的第一个action访问成功了。");
        return "success";
    }
}

execute()方法的要求如下:

  • 方法的权限修饰符为public
  • 返回一个字符串,就是指示的下一个页面的Result
  • 方法没有参数
    也就是说,满足上述要求的POJO都可算作是Struts2的Action实现。

Action类实现一个Action的接口
为了让用户开发的Action类更规范,Struts2提供了一个Action接口,用户在实现Action控制类时,可以实现Struts2提供的这个Action接口。
Action接口定义了Struts的Action处理类应该实现的规范,具体如下:

import com.opensymphony.xwork2.Action;

public class Struts2Action01 implements Action {
    @Override
    public String execute() throws Exception {
        return null;
    }
}

Action接口位于com.opensymphony.xwork2.Action包中,这个接口中定义了一个execute()方法,该接口的规范规定了Action处理类应该包含一个execute()方法,该方法返回一个字符串。除此之外,该接口还定义了5个字符串常量,它们的作用是统一execute()方法的返回值。
Action接口中提供了5个已经定义的常量如下:

  • SUCCESS :success,代表成功。
  • NONE : none,代表页面不跳转。
  • ERROR: error,代表跳转到错误页面。
  • INPUT: input,数据校验的时候跳转的路径。
  • LOGIN:login,用来跳转到登录页面。

Action类继承ActionSupport类 ,开发中通常使用这种方式,示例如下:

import com.opensymphony.xwork2.ActionSupport;

public class Struts2Action02 extends ActionSupport {

    @Override
    public String execute() throws Exception {
        return NONE;
    }
}

ActionSupport类本身实现了Action接口,是Struts2中默认的Action接口的实现类,所以继承ActionSupport就相当于实现了Action接口。ActionSupport类还实现了Validateable,ValidationAware,TextProvider,LocaleProvider和Serializable等接口,来为开发这提供更多的功能。
ActionSupport类中提供了许多默认方法,这些方法包括获取国际化信息的方法、数据校验的方法、默认的处理用户请求的方法等。实际上,ActionSupport类时Struts2默认的Action处理类,让开发者的Action类继承ActionSupport类,则会大大简化Action的开发。

第六节:Action的访问

Action类的执行访问可以通过前面的配置来完成,但是之前的方式有一个缺点,就是多次请求不能对应同一个Action。因为实际的开发中,一个模块的请求通常由一个Action类处理就好,否则就会造成Action类过多。
那么如何让一个模块的操作提交到一个Action中呢?在<action>的标签中有一个属性method,通过method的配置就可以指定Action中的某个方法执行。

6.1 解决Action的访问方式一:通过配置method属性完成。

  • 编写jsp页面
<%--通过配置method来实现调用方法的切换--%>
  <a href="${pageContext.request.contextPath}/addCustomerAction.action">增加用户</a>
  <br/>
  <a href="${pageContext.request.contextPath}/updateCustomerAction.action">更新用户</a>
  <br/>
  <a href="${pageContext.request.contextPath}/delCustomerAction.action">删除用户</a>
  <br/>
  <a href="${pageContext.request.contextPath}/queryCustomerAction.action">查询用户</a>
  <br/>
  • 在struts.xml配置文件中,增加method的配置
       <action name="addCustomerAction" class="com.seapp.struts2.action.Struts2Action02" method="add">
            
        </action>
        <action name="updateCustomerAction" class="com.seapp.struts2.action.Struts2Action02" method="update">
            
        </action>
        <action name="delCustomerAction" class="com.seapp.struts2.action.Struts2Action02" method="del">
            
        </action>
        <action name="queryCustomerAction" class="com.seapp.struts2.action.Struts2Action02" method="query">
            
        </action>
  • 在Struts2Action02中,增加对应的方法
import com.opensymphony.xwork2.ActionSupport;

public class Struts2Action02 extends ActionSupport {

    @Override
    public String execute() throws Exception {
        return NONE;
    }

    /**
     * 执行保存用户的方法
     * @return
     * @throws Exception
     */
    public String add() throws Exception{
        System.out.println("用户保存的方法被调用了");
        return NONE;
    }
    /**
     * 执行更新用户的方法
     * @return
     * @throws Exception
     */
    public String update() throws Exception{
        System.out.println("用户更新的方法被调用了");
        return NONE;
    }  /**
     * 执行删除用户的方法
     * @return
     * @throws Exception
     */
    public String del() throws Exception{
        System.out.println("用户删除的方法被调用了");
        return NONE;
    }  /**
     * 执行查询用户的方法
     * @return
     * @throws Exception
     */
    public String query() throws Exception{
        System.out.println("用户查询的方法被调用了");
        return NONE;
    }
}
  • 调用执行结果
    执行界面如下:


    image.png

    返回结果:


    image.png

    但是这种方式我们会发现,同一个Action类被配置了很多次,只是修改了后面的method的值。那么能不能配置简单化呢?也就是一个Action类,只配置一次就好。这就需要我们使用通配符的配置方式了。

6.2 解决Action的访问问题方式二:通过配置符的配置完成

  • 编写jsp访问界面
 <%--通过配置通配符的方式实现方法的切换--%>
  <h2>通过配置通配符的方式实现方法的切换</h2>
  <a href="${pageContext.request.contextPath}/CustomerAction_add.action">增加用户</a>
  <br/>
  <a href="${pageContext.request.contextPath}/CustomerAction_update.action">更新用户</a>
  <br/>
  <a href="${pageContext.request.contextPath}/CustomerAction_del.action">删除用户</a>
  <br/>
  <a href="${pageContext.request.contextPath}/CustomerAction_query.action">查询用户</a>
  <br/>
  • 在struts.xml配置文件中,关于通配符的配置:
 <!--配置一个包-->
    <package name="strutsDemo1" extends="struts-default" namespace="/" strict-method-invocation="false">
        <!--配置Action-->
       
        <!--使用通配符的方式配置action不同方法的访问-->
        <action name="CustomerAction_*" class="com.seapp.struts2.action.Struts2Action02" method="{1}">

        </action>
    </package>

注意:struts2从2.5版本开始,为了提升安全性,默认开启了严格的方法调用。如果要使用通配符*,必须在package中设置 strict-method-invocation="false"。在没有配置的情况下报错信息会提示

There is no Action mapped for namespace [/] and action name [Action!Metho
  • Struts2Action02中的方法没变,则访问测试结果如下:


    image.png
image.png

6.3 解决Action的访问问题方式三:动态方法访问

动态方法访问在Struts2中默认是不开启的,如果需要使用则需要卡其一个常量

 <!--开启动态访问-->
    <constant name="struts.enable.DynamicMethodInvocation" value="true"/>
  • jsp访问界面代码编写
  <%--通过配置动态访问的方式实现方法的切换--%>
  <h2>通过配置动态访问的方式实现方法的切换</h2>
  <a href="${pageContext.request.contextPath}/CustomerAction!add.action">增加用户</a>
  <br/>
  <a href="${pageContext.request.contextPath}/CustomerAction!update.action">更新用户</a>
  <br/>
  <a href="${pageContext.request.contextPath}/CustomerAction!del.action">删除用户</a>
  <br/>
  <a href="${pageContext.request.contextPath}/CustomerAction!query.action">查询用户</a>
  <br/>
  • struts.xml中action的配置
  <!--配置动态访问action的方式-->
        <action name="CustomerAction" class="com.seapp.struts2.action.Struts2Action02">

        </action>
  • Struts2Action02中方法不变,则对应的测试结果如下:


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

推荐阅读更多精彩内容