SSM框架之SpringMVC

一、SpringMVC概述

1. 基本组件

  • SpringMVC组件
image-20200607151127712.png

2. 工作流程

  • SpringMVC工作流程
  • SpringMVC摆脱了每个servlet程序都要在web.xml中配置对应的servlet和servletMapping的麻烦。通过配置==DispatcherServlet==来拦截所有的请求,至于具体的每个请求,则单独在==controller包里配置不同的Controller类==,每个类都自定义一个==String返回值(当然也可以无返回值)的方法==,@RequestMapping注解在这些方法上,就匹配了对应的请求路径,return的值通过==视图解析器==就找到了对应的文件路径。从而完成了页面跳转。在web.xml中配置去读取SpringMVC的配置文件的init-param
image-20200607151258286.png

3. 工作环境搭建

  1. 创建Maven-webapp工程,如果下载速度过慢,将修改Maven仓库镜像源
  2. 添加一个键值对archeTypeCatalog,internal,解决工程创建过慢问题
  3. 导入坐标
  4. 配置Tomcat服务器
  5. 添加SpringMVC.xml配置文件
  6. 配置web.xml文件

依赖

<dependencies>
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-context</artifactId>
        <version>5.2.5.RELEASE</version>
    </dependency>
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-web</artifactId>
        <version>5.2.5.RELEASE</version>
    </dependency>
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-webmvc</artifactId>
        <version>5.2.5.RELEASE</version>
    </dependency>
    <dependency>
        <groupId>javax.servlet</groupId>
        <artifactId>servlet-api</artifactId>
        <version>2.5</version>
        <scope>provided</scope>
    </dependency>
    <dependency>
        <groupId>javax.servlet.jsp</groupId>
        <artifactId>jsp-api</artifactId>
        <version>2.0</version>
        <scope>provided</scope>
    </dependency>
</dependencies>

web.xml

<!--配置servlet-->
<servlet>
    <!--配置Servlet的名称和对应的类DispatcherServlet(Spring固定的类(servlet))-->
    <servlet-name>dispatcherServlet</servlet-name>
    <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
    <!--配置DispatcherServlet的初始化参数,让它去读取bean.xml配置文件(应该说是SpringMVC的配置文件,而不是Spring的配置文件),从而可以扫描包下的注解-->
    <init-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>classpath:bean.xml</param-value>
    </init-param>
    <!--配置load-on-startup,以后启动服务器的时候就创建DispatcherServlet,而不是在第一次请求的时候创建-->
    <load-on-startup>1</load-on-startup>
</servlet>

<!--配置servlet匹配-->
<servlet-mapping>
    <servlet-name>dispatcherServlet</servlet-name>
    <!--配置拦截,/表示任何请求都被拦截-->
    <url-pattern>/</url-pattern>
</servlet-mapping>

二、实例配置

1. springmvc配置

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/mvc
https://www.springframework.org/schema/mvc/spring-mvc.xsd
http://www.springframework.org/schema/context
https://www.springframework.org/schema/context/spring-context.xsd">
<-- 开启SpringMVC注解配置支持,开启它,就可以使得requestMapping生效,一定要配置-->
//区别于Spring的annotaiton-driven,后者是开启Spring的事务注解配置支持<tx:annotation-driven transaction-manager="transactionManager"></tx:annotation-driven>

<!--开启注解扫描-->
<mvc:annotation-driven/>
<!--扫描指定包下的注解-->
<context:component-scan base-package="com.zt"/>

<!--视图解析器对象InternalResourceViewResolver类,将来通过return的字符内容找目录内容里对应名称的jsp文件,跳转到对应的jsp页面-->
<bean id="internalResourceViewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver">
    <!--配置解析的目录(前缀)-->
    <property name="prefix" value="/WEB-INF/pages/"/>
    <!--配置解析的文件后缀名-->
    <property name="suffix" value=".jsp"/>
</bean>
</beans>

2. Controller配置

  • 每一个controller对应一个应用
//交给Spring管理该控制类
@Controller
public class HelloController{
    // 给该方法添加一个请求路径,以后访问该web工程下的这个路径,就执行这个方法
    @RequestMapping(path = "/hello") //请求的路径
    public String sayHello(){
        System.out.println("Hello SpringMVC");
        // return的内容一般默认为是要跳转的页面的名称(success.jsp,在WEB-INF下创建)
        // 当返回success时,通过bean.xml中配置的视图解析器,找到对应目录下的对应名称的jsp文件(视图解析器配置了前缀和后缀路径)
        return "success";
    }
}

3. web.xml配置中文拦截器

  • 配置字符集过滤器,拦截中文字符,转为utf-8格式
<!--配置解决中文乱码的过滤器-->
<filter>
    <filter-name>CharacterEncodingFilter</filter-name>
    <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
    <init-param>
        <!--CharacterEncodingFilter中的参数encoding-->
        <param-name>encoding</param-name>
        <!--设置encoding字符集为UTF-8-->
        <param-value>UTF-8</param-value>
    </init-param>
</filter>
<filter-mapping>
    <filter-name>CharacterEncodingFilter</filter-name>
    <!--拦截所有-->
    <url-pattern>/*</url-pattern>
</filter-mapping>

三、部分注解

1. @RequestMapping

  • 作用:用于建立请求url和处理请求的方法之间的关系。放在某个类上,就相当于:
<a href="user/hello">SpringMVC入门</a>
@Controller
@RequestMapping(path = "/user")
public class HelloController{
    @RequestMapping(path = "/hello")
        public String sayHello(){
        System.out.println("Hello SpringMVC");
        return "success";
    }
}
  • value=属性,path=属性
  • method=post\get\delete...等等(参数为数组类型)
@RequestMapping(method = {RequestMethod.GET})
  • params,要求必须传入一个该参数
@RequestMapping(path = "/hello",params = {"username"})
@RequestMapping(path = "/hello",params = {"username=abc"})  //要求页面上的username值必须是abc才能访问

2. @RequestParams

  • 作用:1、把请求中指定名称的参数赋值给方法中的形参 2、要求必须提供一个该名称的参数,与@RequstMapping的param区别在于,这里的参数名为一个键,"name=zt"是作为一个整体,而不是要求必须值为zt。
@RequestMapping(value = "/testRequestParam")
// @RequestParam修饰Parameters,name="name",表示访问时客户端url必须传入一个name属性的值,
// 即使是和形参username相同名字的也不行
// 然后将拿到的值放进username
public String testAnno(@RequestParam(name = "name",required = true) String username){
    System.out.println(username);
    return "success";
}

3. @RequestBody

  • 区别于@ResponseBody,@ResponseBody可作用于类或方法上,把return的值直接返回给客户端页面如果有了@ResponseBody,那么同样的字符串"success"就会覆盖视图解析器的功能。
  • 作用:用于获取请求体(整个form表单)内容,直接使用得到的是key=value&key=value...结构的数据GET请求方式不适用
  • 属性:required:表示是否有请求体,默认值是true,当取值为true时,GET请求方式会报错。如果取值为false,GET请求得到的是null。
<form action="anno/testRequestBody" method="post">
    用户名:<input type="text" name="username"><br/>
    密码:<input type="password" name="password"><br/>
        <input type="submit" value="提交">
</form>
public String testBody(@RequestBody String body){
    System.out.println(body);
    return "success";
}

4. @PathVariable

  • 用占位符获取客户端传递的参数
// {uid}作占位符,和@PathVariable中的value名相同
@RequestMapping(value = "/testPathVariable/{uid}")
public String testPath(@PathVariable(value = "uid") String id){
    System.out.println(id);
    return "success";
}

5. @CookieValue

  • 获取Cookie的值,value为固定的"JSESSIONID"
@RequestMapping(value = "/testCookie")
public String testCookie(@CookieValue(value = "JSESSIONID") String cookieValue){
    System.out.println(cookieValue);
    return "success";
}

6. @ModelAttribute

  • 范围:方法或者参数
  • 作用:出现在方法上,方法会优先于servlet程序的方法执行。即用户请求所访问的那个url对应的方法会在注解标记的方法之后执行。应用场景:用户没有提交完整的javabean中的所有属性的值,即空了一些属性没有写,这时候可以用@ModelAttribute注解修饰的方法来填充这些没有填的属性的值(可以用数据库中的历史值)

设置表单中只提交两项,第三项由@ModelAttribute注解的方法添加

<form action="anno/testModelAttribute" method="post">
    姓名<input type="text" name="name"><br/>
    年龄<input type="text" name="age"><br/>
    <input type="submit" value="提交"/>
</form>
@RequestMapping(value = "/testModelAttribute")
public String testAttribute(User u){
    System.out.println(u);
    return "success";
}
@ModelAttribute
public User setUser(User user){  //参数user,把表单中的项添加到user中
    user.setDate(new Date());
    return user;
}

7. @SessionAttribute

  • 作用:用于多次执行控制器方法间的参数共享,只能作用在类上
  • 属性: value\name:用于指定存入的属性名称 types:用于指定存入的数据类型
@Controller
@RequestMapping(value = "/anno")
//把name=value键值对存到session域中
@SessionAttributes(value = {"name"})
public class Annotation {

@RequestMapping(value = "/testSessionAttributes")
public String testSession(Model model){
    // SpringMVC底层会存储到request域对象中
    model.addAttribute("name","周涛");
    return "success";
}

@RequestMapping(value = "/getSessionAttributes")
public String getSession(ModelMap modelMap){
    String value =(String)modelMap.get("name"); //modelMap取到request域中的值
    System.out.println(value);
    return "success";
}

@RequestMapping(value = "/delSessionAttributes")
    public String delSession(SessionStatus status){
    status.setComplete();
    return "success";
}

四、请求参数绑定

4.1 封装基本类型

  • 用户参数username=abcd,password=1234MVC框架,sayhello(String username,String password)当MVC框架形参名和用户参数的name属性名相同时,会自动传递过来
<a href="param/testParam?username=我">请求参数绑定</a>
@RequestMapping(value = "testParam")
public String testParam(String username){
System.out.println(username);
return "success";
}

4.2 封装Javabean

1.Javabean中只有基本类型

  • 表单项内容要和Javabean的属性名称一致
<form action="param/saveAccount" method="post">
    <input type="text" name="username"><br/>  //名称要对应
    <input type="password" name="password"><br/>
    <input type="text" name="money"><br/>
    <input type="submit" value="提交"/>
</form>
@RequestMapping(value = "/saveAccount")
public String saveAccount(Account account){ //直接形参就用实体类对象
    System.out.println(account);
    return "success";
}

2.Javabean中有别的引用类型

  • 表单项要和引用类型的属性名相同
<form action="param/saveAccount" method="post">
    <input type="text" name="username"><br/>
    <input type="password" name="password"><br/>
    <input type="text" name="money"><br/>
    <input type="text" name="user.name"><br/>  //这两行改为Account对象中的user对象加属性,user.name
    <input type="text" name="user.age"><br/>
    <input type="submit" value="提交"/>
</form>
@RequestMapping(value = "/saveAccount")
public String saveAccount(Account account){ //直接形参就用实体类对象
    System.out.println(account);
    return "success";
}

3.封装集合对象

<form action="param/saveAccount" method="post">
    姓名<input type="text" name="username"><br/>
    密码<input type="password" name="password"><br/>
    工资<input type="text" name="money"><br/>
    Listname<input type="text" name="users[0].name">   //要封装的是User,实际需要对应的是User的name和age
    Listage<input type="text" name="users[0].age">
    mapusername<input type="text" name="map['1'].name">  //手动给String=1,map[1]就表示user(根据键得值)
    mapuserage<input type="text" name="map['1'].age">
    <input type="submit" value="提交"/>
</form>
private List<User> users;
private Map<String,User> map;

4.自定义类型转换器

  • 对于浏览器提交的数据类型,有些需要进行转换才能封装到Javabean中

转换日期

  1. 前端页面
<form action="param/saveUser" method="post">
    姓名<input type="text" name="name"><br/>
    年龄<input type="text" name="age"><br/>
    生日<input type="text" name="date"><br/>
    <input type="submit" value="提交"/>
</form>
  1. 自定义日期类型转换器
public class StringToDateConvertor implements Converter<String,Date> {  //实现Converter<S,T>接口

    @Override
    public Date convert(String source) {
        Date date = null ;
        if (source==null) {
        throw new RuntimeException("请输入日期");
        }else {
            DateFormat df = new SimpleDateFormat("yyyy-MM-dd");
            try {
                date = df.parse(source);
                return date;
            } catch (ParseException e) {
            e.printStackTrace();
            }
        }
        return null;
    }
}
  1. 将类型转换器配置到SpringMVC.xml中
<!--开启SpringMVC注解配置支持,支持conversionService-->
<mvc:annotation-driven conversion-service="conversionService"/>
<!--配置自定义类型转换器-->
<bean id="conversionService" class="org.springframework.context.support.ConversionServiceFactoryBean">
    <!--配置converters属性,converters是set集合类型-->
    <property name="converters">
    <!--配set集合用set标签-->
    <set>
        <!--注入自定义转换器到IoC容器中,可以配置多个转换器-->
        <bean class="com.zt.utils.StringToDateConvertor"/>
    </set>
    </property>
</bean>
  1. controller中接收
@RequestMapping(value = "/saveUser")
public String saveUser(User user){
    System.out.println(user);
    return "success";
}

五、响应数据和结果视图

1. Controller控制器返回

1.方法返回值为String字符串

@RequestMapping(value = "/testString")
public String testString(Model model){
    System.out.println("testString方法执行了");
    // 模拟从数据库中查询出一个用户的对象,把对象存起来转发到页面上
    User user = new User();
    user.setUsername("美美");
    user.setPassword("123");
    user.setAge(20);
    // 把user存入到request域中
    model.addAttribute("users",user);
    return "success";
}
${users.username}<br/>
${users.password}<br/>
${users.age}

2.方法返回值为void类型

  • 默认会转发到请求路径上,也就是访问的资源路径。
/*
* * @param
*@return 如果不写请求转发或重定向,默认转发到value名的jsp页面:/testVoid.jsp页面
* 如果项目中没有testVoid.jsp页面,可以请求转发或重定向到已有页面
*/
@RequestMapping(value = "/testVoid")
public void voidTest(HttpServletRequest req, HttpServletResponse resp){
    System.out.println("voidTest被执行了");
}
/*
请求转发:
req.getRequestDispatcher("/WEB-INF/pages/success.jsp").forward(req,resp);
请求重定向:
resp.sendRedirect(req.getContextPath()+"/index.jsp"); //req.getContextPath()获得项目路径
直接响应:
resp.getWriter().write("你好");
*/

3.返回ModelAndView类型

  • ModelAndView底层返回视图时调用的是视图解析器,return字符串的底层其实就是调用的ModelAndView
@RequestMapping(value = "/testModelAndView")
public ModelAndView testModel(){
    ModelAndView mv = new ModelAndView();
    User user = new User();
    user.setUsername("美美");
    user.setPassword("123");
    user.setAge(20);
    // 往ModelAndView中添加对象
    mv.addObject("users",user);
    // 调用视图解析器返回success.jsp
    mv.setViewName("success");
    return mv;
}

2. 转发和重定向

  • 转发和重定向用不了视图解析器,所以路径得自己去补全。==重定向不能访问WEB-INF这个安全目录==,因为重定向相当于两次客户端请求,而客户端是不能直接访问的。
@RequestMapping(value = "/testForward")
public String Forward(){
    // 使用forward: 关键字表明这是一个请求转发
    // 以前直接return字符串需要先配置视图解析器,现在可以不用配置而直接转发
    '/'在服务器被解析为tomcat服务器配置时的路径,https://ip:port/+工程名/,也就是webapp下
    return "forward:/WEB-INF/pages/success.jsp";请求转发和重定向也可以重定向到某个方法

    return "redirect:/index.jsp"; //使用关键字的重定向不用加项目名,即req.getContextPath()获得项目路径,可以直接写该项目下的路径
    }
}

3. @ResponseBody响应数据

  • @ResponseBody可以直接响应给客户端字符串数据,以JSON数据格式返回

六、SpringMVC文件上传实例

1. 依赖环境

<dependency>
    <groupId>commons-io</groupId>
    <artifactId>commons-io</artifactId>
    <version>2.6</version>
</dependency>
<dependency>
    <groupId>commons-fileupload</groupId>
    <artifactId>commons-fileupload</artifactId>
    <version>1.4</version>
</dependency>

表单项

<%--必须是多表单和post请求--%>
<form action="file/upload" method="post" enctype="multipart/form-data"> //必须是多表单和post请求,设置多表单的目的是把这个表单中的文件内容和普通表单项分开
比如分开username,password和file。
    <input type="text" name="username"><br/>
    <input type="password" name="password"><br/>
    <input type="file" name="upload"><br/>  //必须是file类型
    <input type="submit" value="上传"><br/>
</form>

2. 传统文件上传

@Controller
@RequestMapping(value = "/file")
public class FileController {
@RequestMapping(value = "/upload")
public String upload(HttpServletRequest request) throws Exception {
    ServletFileUpload upload = new ServletFileUpload(new DiskFileItemFactory());
    List<FileItem> items = upload.parseRequest(request);
    for (FileItem item : items){
        if (!item.isFormField()){//如果不是普通表单项,则是文件项
            String path = request.getSession().getServletContext().getRealPath("/upload/"); //取得upload目录在磁盘中的绝对路径
            UUID uuid = UUID.randomUUID();
            String name = uuid + item.getName(); //UUID是随机生成的,在分布式系统中是唯一值,以确保下载的文件不重名,即使是相同文件
            item.write(new File(path+item.getName())); //加上文件名,写入
        }
    }
    return "success";
}

3. SpringMVC上传

1.配置文件解析器

<!--配置文件解析器对象,名称必须为multipartResolver-->
<bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
    <!--maxUploadSize属性:文件最大size,单位比特-->
    <property name="maxUploadSize" value="10485760"/>
</bean>

2.配置前端页面

<form action="user/upload" enctype="multipart/form-data" method="post">
姓名:<input type="text" name="username"><br/>
密码:<input type="password" name="password"><br/>
<input type="file" name="upload"><br/>  //file的name属性要和controller的形参名一样,这样才能参数绑定,完成解析
<input type="submit" value="提交"><br/>
</form>

3.配置controller

@RequestMapping(value = "/upload")
public String upload(HttpServletRequest req, MultipartFile upload) throws Exception {  //需要upload,来完成上传,需要req来取得路径
// 解析的目的是区分普通表单项和文件项
// 文件upload已经通过绑定参数拿到,不需要再解析
// 拿到web工程下upload目录的绝对路径,可以先判断该路径是否存在,如果不存在就创建这个目录。路径取决于该web工程输出到哪,而不是Java工程的路径
    String path = req.getSession().getServletContext().getRealPath("/upload/");//如果是部署在maven工程下,则输出到的是target下
    File file = new File(path);
    if (!file.exists()){
    file.mkdirs(); //如果不存在就创建这个目录
    }
    // 拿到文件名,传统文件名,上传时的文件名
    String filename = upload.getOriginalFilename();
    // 通过设置UUID把文件的名称设置为唯一值
    // 把UUID中的"-"用""替代
    String uuid = UUID.randomUUID().toString().replace("-","");
    // 修改当前上传到服务器的文件名
    filename = uuid + "_" + filename;
    // 完成文件上传,File(String parent,String Child)
    upload.transferTo(new File(path,filename));
    return "success";
}

4.原理分析

image-20200607163555585.png

4. 跨服务器文件上传

  • 在很多时候,应用服务器和文件服务器分别负责不同的功能,文件多数由应用服务器转发保存到文件服务器。
  • 每个服务器都是一个进程,要配置不同的端口号

导入依赖

<dependency>
    <groupId>com.sun.jersey</groupId>
    <artifactId>jersey-client</artifactId>
    <version>1.19.4</version>
</dependency>
<dependency>
    <groupId>com.sun.jersey</groupId>
    <artifactId>jersey-core</artifactId>
    <version>1.19.4</version>
</dependency>

编写服务

@RequestMapping(value = "/upload2")

public String upload2(HttpServletRequest req, MultipartFile upload) throws Exception {
    // 拿到跨服务器的图片服务器的path,上传至此
    String path = "http://localhost:9090/SpringMVC_02_image/images/";

    // 生成唯一的文件名
    String filename = upload.getOriginalFilename();
    String uuid = UUID.randomUUID().toString().replace("-","");
    filename = uuid + "_" + filename;

    // 应用服务器作为客户端
    Client client = Client.create();
    // 和图片服务器进行连接
    WebResource webResource = client.resource(path + filename);
    // 上传文件,通过字节流的方式
    webResource.put(upload.getBytes());
    return "success";
}

七、拦截器Interceptors

  • 拦截器是SpringMVC框架特有的,类似Servlet中的过滤器。也有拦截器链(类似过滤器链),先执行前置代码,然后放行(类似doFilter())最后执行后置代码。
  • 区别:拦截器的/只会拦截Controller类中的方法,而不会拦截.jsp.html等等。拦截器能做的过滤器都能做,过滤器能做的拦截器不一定能做。过滤器在DispatcherServlet之前执行,拦截器在DispatcherServlet之后执行*。
  • 预处理:preHandle:类似AOP的前置通知,在controller的方法被执行之前执行
  • 后处理:postHandle:类似AOP的后置通知,在controller的方法执行之后(但是在方法中的return语句之前,如果方法没有return值,那就在方法全部执行之后再执行)执行,要求预处理必须return true
  • 最终处理:afterCompletion :类似AOP的最终通知,在controller的return执行完后执行
  1. 定义拦截器类
//实现拦截器必须实现HandlerInterceptor方法
//如果在拦截器预处理或后处理中指定了跳转页面,就不会再执行controller中的跳转页面了
public class MyInterceptor implements HandlerInterceptor {

    /*拦截器预处理,在执行controller中的方法之前执行
    * 返回true表示放行,false表示不放行,即无法执行controller中的方法,也不会执行后处理
    * */
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        System.out.println("拦截成功");
        request.getRequestDispatcher("/WEB-INF/pages/error.jsp").forward(request,response);
        return true;
    }

    /*
    * 要求预处理必须返回true的时候才会执行后处理
    * 后处理方法,在controller方法执行后,方法中的return之前
    * */
    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
        System.out.println("后置方法执行了");
    }

    /*
    * 最终处理,一定会执行
    * */
    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        System.out.println("最终处理执行了");
    }
}
  1. xml中配置拦截器
<mvc:interceptors>
    <!--配置某个拦截器-->
    <mvc:interceptor>
        <!--mapping配置需要拦截的路径,/*表示拦截user目录下的任意controller方法-->
        <mvc:mapping path="/user/*"/>
        <!--exclude-mapping配置不需要拦截的路径-->
        <!-- <mvc:exclude-mapping path=""/>-->
        <!--配置用哪个拦截器来拦截-->
        <bean id="interceptor" class="com.zt.controller.interceptors.MyInterceptor"/>
    </mvc:interceptor>
</mvc:interceptors>
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,884评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,755评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 158,369评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,799评论 1 285
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,910评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,096评论 1 291
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,159评论 3 411
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,917评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,360评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,673评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,814评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,509评论 4 334
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,156评论 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,882评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,123评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,641评论 2 362
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,728评论 2 351