框架篇-easyexcel使用

EasyExcel

1.简介

传统Excel操作或者解析都是利用Apach POI进行操作,但是使用过这个框架的人都知道,这个框架并不完美,有较多的缺陷:

  • 使用步骤繁琐
  • 动态写出Excel操作非常麻烦
  • 对于新手来说,很难在短时间内上手
  • 读写时需要占用较大的内容,当数据量大时容器发生OOM

基于上述原因,阿里开源出一款易上手,且比较节省内存的Excel操作框架:EasyExcel

官网文档

image-20201225142524599

源码地址

image-20201225142812075

2.对比

  • 读取

    image-20201225143206677

    从上图可知:

    • POI

      当利用POI去读取Excel时,首先会将数据全部加载到内存中,然后返回给调用者

      当数据量比较大时,及其容易发生OOM

    • EasyExcel

      POI 不用的是,EasyExcel主要是采用sax模式一行一行解析,并将一行的解析结果以观察者的模式通知处理,即使数据量较大时也不会发生OOM,以下是其读取数据原理图

      image-20201225143933780

      这样即使数据量比较大时也不会发生OOM,节省了内存的开销,以下是其读取数据64M内存1分钟内读取75M(46W行25列)的Excel 内存开销图

      image-20201225144238809
  • 维护

    • 当其他开源框架去使用时,步骤复杂,EasyExcel上手及其简单

    • 其他开源框架存在一些BUG修复不及时,官方文档举了一个例子,如下:

      image-20201225144632555

3.API

3.1 写操作

3.1.1 简单写

  • 准备工作

    创建springboot项目(easyexcel) pom.xml内容如下:

    <?xml version="1.0" encoding="UTF-8"?>
    <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
        <modelVersion>4.0.0</modelVersion>
        <parent>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-parent</artifactId>
            <version>2.3.7.RELEASE</version>
            <relativePath/> <!-- lookup parent from repository -->
        </parent>
        <groupId>com.briup</groupId>
        <artifactId>easyexcel</artifactId>
        <version>0.0.1-SNAPSHOT</version>
        <name>easyexcel</name>
        <description>Demo project for Spring Boot</description>
    
        <properties>
            <java.version>1.8</java.version>
        </properties>
    
        <dependencies>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-web</artifactId>
            </dependency>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-test</artifactId>
                <scope>test</scope>
                <exclusions>
                    <exclusion>
                        <groupId>org.junit.vintage</groupId>
                        <artifactId>junit-vintage-engine</artifactId>
                    </exclusion>
                </exclusions>
            </dependency>
            <!-- easyexcel 依赖 -->
            <dependency>
                <groupId>com.alibaba</groupId>
                <artifactId>easyexcel</artifactId>
                <version>2.2.7</version>
            </dependency>
        </dependencies>
        <build>
            <plugins>
                <plugin>
                    <groupId>org.springframework.boot</groupId>
                    <artifactId>spring-boot-maven-plugin</artifactId>
                </plugin>
            </plugins>
        </build>
    </project>
    

    修改application.properties文件,内容如下:

    server.port=9991
    

    新增POJO

    package com.briup.easyexcel.pojo;
    
    import lombok.Data;
    
    import java.io.Serializable;
    import java.util.Date;
    
    @Data
    public class Student implements Serializable {
        private Integer id;
        private String name;
        private Double salary;
        private Date birthday;
    }
    
  • 简单写操作

    • 操作

      通过EasyExcel这个工具类,即可完成写操作,如下:

      在测试类中进行代码测试

      public List<Student> getData() {
          List<Student> lists = new ArrayList<>();
          for(int i = 0; i <= 10; i++) {
              Student student = new Student();
              student.setId(i + 1);
              student.setName("李四" + i);
              student.setBirthday(new Date());
              student.setSalary(1500.00D);
              lists.add(student);
          }
          return lists;
      }
      
      @Test
      void contextLoads() {
          EasyExcel.write("学生信息表.xlsx", Student.class).sheet().doWrite(getData());
      }
      
      

      执行测试方法,结果会在本地产生一个excel文件

      image-20201225152247751

      如下:
      image-20201225152340467
    • 代码解释:

      image-20201225152644631

      图中红框选中部分表示:

      EasyExcel.write 表示构建一个Excel写对象,其参数含义为:

      • 第一个参数: 写出表格的文件名
      • 第二个参数:写到表格数据类型的class对象

      查看EasyExcel源码,其所有write方法源码如下:

      public class EasyExcelFactory {
          /**
           * 构建一个Excel写对象
           *
           * @return
           */
          public static ExcelWriterBuilder write() {
              return new ExcelWriterBuilder();
          }
      
          /**
           * 构建一个Excel写对象
           *
           * @param file 用来写出文件对象
           *            
           * @return Excel writer builder
           */
          public static ExcelWriterBuilder write(File file) {
              return write(file, null);
          }
      
          /**
           * 构建 Excel写对象
           *
           * @param file
           *           用来写出的文件对象
           * @param head
           *          写出的数据类型的class对象
           * @return Excel writer builder
           */
          public static ExcelWriterBuilder write(File file, Class head) {
              ExcelWriterBuilder excelWriterBuilder = new ExcelWriterBuilder();
              excelWriterBuilder.file(file);
              if (head != null) {
                  excelWriterBuilder.head(head);
              }
              return excelWriterBuilder;
          }
      
          /**
           * 构建Excel 写对象
           *   
           * @param pathName
           *           写出的文件路径名
           * @return Excel writer builder
           */
          public static ExcelWriterBuilder write(String pathName) {
              return write(pathName, null);
          }
      
          /**
           * 构建excel 写对象
           *
           * @param pathName
           *            写出的文件路径名
           * @param head
           *            写出数据的数据类型的class对象
           * @return Excel writer builder
           */
          public static ExcelWriterBuilder write(String pathName, Class head) {
              ExcelWriterBuilder excelWriterBuilder = new ExcelWriterBuilder();
              excelWriterBuilder.file(pathName);
              if (head != null) {
                  excelWriterBuilder.head(head);
              }
              return excelWriterBuilder;
          }
      
          /**
           * 构建excel写对象
           *
           * @param outputStream
           *            写出的输出流对象
           * @return Excel writer builder
           */
          public static ExcelWriterBuilder write(OutputStream outputStream) {
              return write(outputStream, null);
          }
      
          /**
           * 构建excel写对象
           *
           * @param outputStream
           *           写出的输出流
           * @param head
           *           写出数据的数据类型的class对象
           * @return Excel writer builder
           */
          public static ExcelWriterBuilder write(OutputStream outputStream, Class head) {
              ExcelWriterBuilder excelWriterBuilder = new ExcelWriterBuilder();
              excelWriterBuilder.file(outputStream);
              if (head != null) {
                  excelWriterBuilder.head(head);
              }
              return excelWriterBuilder;
          }
      }
      
      

      通过源码知道,在构建Excel写对象时可以通过多种方式构建,具体使用哪种看具体的需求。

      image-20201225154431692

      如上图,sheet()代表要在excel 那个sheet页写入数据,如果不指定,默认在第一个sheet页写入数据,其sheet页的值为:0

      image-20201225154735583

      当然也可以手动指定在哪个sheet页,关于sheet源码如下:

      public class ExcelWriterBuilder extends AbstractExcelWriterParameterBuilder<ExcelWriterBuilder, WriteWorkbook> {
        
          /*
              选中第一个sheet页
              写操作 sheet 页的值为 0
          */
          public ExcelWriterSheetBuilder sheet() {
              return sheet(null, null);
          }
      
          /*
              选中 第一个的 sheet页
              sheet 页的名字 为 sheetNo
          */  
          public ExcelWriterSheetBuilder sheet(Integer sheetNo) {
              return sheet(sheetNo, null);
          }
      
          /*
              选中第一个的 sheet页
              sheet 页的名字 为 sheetName
          */  
          public ExcelWriterSheetBuilder sheet(String sheetName) {
              return sheet(null, sheetName);
          }
      
          /*
              选中第一个 sheet页
              sheet 页的名字 为 sheetNo 或者 sheetName
          */
          public ExcelWriterSheetBuilder sheet(Integer sheetNo, String sheetName) {
              ExcelWriter excelWriter = build();
              ExcelWriterSheetBuilder excelWriterSheetBuilder = new ExcelWriterSheetBuilder(excelWriter);
              if (sheetNo != null) {
                  excelWriterSheetBuilder.sheetNo(sheetNo);
              }
              if (sheetName != null) {
                  excelWriterSheetBuilder.sheetName(sheetName);
              }
              return excelWriterSheetBuilder;
          }
      }
      
      image-20201225155746393

      doWrite表示写出的数据,写出的数据为List集合

3.1.2 复杂写

  • 自定义表头

    image-20201225160019872

    如上图,之前写出的数据,表头均为属性名,且列的顺序为类中属性的顺序,但是在实际开发过程中,表头为自定义信息,且顺序也不一定按照属性的顺序来。

    因此需要自定义表头信息,具体实现如下:

    修改POJO类,内容如下:

    public class Student implements Serializable {
    
        @ExcelProperty("学生编号")
        private Integer id;
    
        @ExcelProperty("学生姓名")
        private String name;
    
        @ExcelProperty("学生薪水")
        private Double salary;
    
        @ExcelProperty("学生生日")
        private Date birthday;
    }
    

    @ExcelProperty 就是用来指定表头信息,再次执行之前的测试类方法,如下:

    image-20201225160926467

    当然如果想要自定义列的顺序时,可以修改POJO,如下:

    @Data
    public class Student implements Serializable {
    
        @ExcelProperty(value = "学生编号",order = 10)
        private Integer id;
    
        @ExcelProperty(value = "学生姓名",order = 2)
        private String name;
    
        @ExcelProperty(value = "学生薪水",order = 1)
        private Double salary;
    
        @ExcelProperty(value = "学生生日",order = 11)
        private Date birthday;
    }
    

    再次执行方法,order的值越大,列越往右,如下:

    image-20201225161443127

有时候更多的时候需要在表头上,在加上一个表头,例如为学生信息

修改POJO类如下:

@Data
public class Student implements Serializable {

    @ExcelProperty(value = {"学生信息","学生编号"},order = 10)
    private Integer id;

    @ExcelProperty(value = {"学生信息","学生姓名"},order = 2)
    private String name;

    @ExcelProperty(value = {"学生信息","学生薪水"},order = 1)
    private Double salary;

    @ExcelProperty(value = {"学生信息","学生生日"},order = 11)
    private Date birthday;
}

再去执行之前的测试方式,内容如下:

image-20201225162211889
  • 列宽,行高定义

    从上述例子可知,之前操作,产生的列的宽度与内容的宽度并没有对应,所以需要手动指定宽度,修改POJO类如下:

    @HeadRowHeight(value = 35) // 表头行高
    @ContentRowHeight(value = 25) // 内容行高
    @ColumnWidth(value = 50) // 列宽
    @Data
    public class Student implements Serializable {
    
        @ExcelProperty(value = {"学生信息","学生编号"},order = 10)
        private Integer id;
    
        @ExcelProperty(value = {"学生信息","学生姓名"},order = 2)
        private String name;
    
        @ExcelProperty(value = {"学生信息","学生薪水"},order = 1)
        private Double salary;
    
        @ExcelProperty(value = {"学生信息","学生生日"},order = 11)
        private Date birthday;
    }
    
    

    执行测试方法,内容如下:

    image-20201225162911641

    虽然上述方式能够修改列宽,但是宽度太大,实际开发中更希望根据内容自适应宽度,步骤如下:

    修改POJO类,将列宽注解去掉

    image-20201225163305882

    修改测试方法,内容如下:

    @Test
    void contextLoads() {
        EasyExcel.write("学生信息表.xlsx", Student.class)
            // 自适应宽度,但是这个不是特别精确
            .registerWriteHandler(new LongestMatchColumnWidthStyleStrategy())
            .sheet()
            .doWrite(getData());
    }
    

    执行测试方法,内容如下:

    image-20201225163405488

    注意:这个自适应宽度,不是特别精确

  • 日期格式化

    从上述例子中,发现日期格式都是固定的格式,但是有时候需要自定义格式,因此可以修改POJO类达到以下效果,如下:

    image-20201225163817239

    执行测试方法,结果如下:

    image-20201225163846055

3.1.3 忽略写

实际开发过程中,并不像把所有的属性数据全部写出,那么可以修改POJO类,增加@ExcelIgnore注解进行忽略,如下:

image-20201225164245033

执行测试方法,如下:

image-20201225164421398

3.1.4 指定写

上述"忽略写"例子中,可以指定哪些属性不输出到Excel表格中,但是这个种方式是固定的,更多的时候需要动态指定哪些输出,哪些不输出。实现步骤如下:

POJO类:

@HeadRowHeight(value = 35) // 表头行高
@ContentRowHeight(value = 25) // 内容行高
@Data
public class Student implements Serializable {

    @ExcelProperty(value = {"学生信息","学生编号"},order = 10)
    private Integer id;

    @ExcelProperty(value = {"学生信息","学生姓名"},order = 2)
    private String name;

    @ExcelProperty(value = {"学生信息","学生薪水"},order = 1)
    private Double salary;

    @ExcelProperty(value = {"学生信息","学生生日"},order = 11)
    @DateTimeFormat("yyyy-MM-dd")
    private Date birthday;
}

测试方法:

@Test
void contextLoads() {
    // 设置 要导出列的属性名
    // 必须要跟类型的属性名保持一致
    Set<String> set = new HashSet<>();
    set.add("id");
    set.add("name");

    EasyExcel.write("学生信息表.xlsx", Student.class)
        .includeColumnFiledNames(set)
        // 自适应宽度,但是这个不是特别精确
        .registerWriteHandler(new LongestMatchColumnWidthStyleStrategy())
        .sheet()
        .doWrite(getData());
}

执行测试方法,内容如下:

image-20201225165744896

3.2.读操作

3.2.1 简单读

  • 简介

    读取在实际开发中也占据了较大地位,但是读取并不是读取任意的一个Excel文件,而是读取按照事先提供好的Excel模板,用户在模块版上修改数据的Excel

  • 准备POJO类

    package com.briup.easyexcel.pojo;
    
    import com.alibaba.excel.annotation.ExcelProperty;
    import com.alibaba.excel.annotation.format.DateTimeFormat;
    import com.alibaba.excel.annotation.write.style.ContentRowHeight;
    import com.alibaba.excel.annotation.write.style.HeadRowHeight;
    import lombok.Data;
    
    import java.io.Serializable;
    import java.util.Date;
    
    @Data
    public class Student_Read implements Serializable {
    
        @ExcelProperty(value = {"学生信息","学生编号"})
        private Integer id;
    
        @ExcelProperty(value = {"学生信息","学生姓名"})
        private String name;
    
        @ExcelProperty(value = {"学生信息","学生薪水"})
        private Double salary;
    
        @ExcelProperty(value = {"学生信息","学生生日"})
        private Date birthday;
    }
    
    

    如上:如果使用该类的对象去装载Excel中的数据,那么读取时就只能读取以下样式的

    Excel数据,否则数据部分丢失或者全部丢失

    image-20201228105825344
  • 准备Excel文件,内容如下:

    这里我是放到项目的根路径下

    image-20201228105915389
  • 在测试类中准备测试方法,用来读取

    @Test
    void readExcel() throws Exception {
    
        List<Student_Read> list =  new ArrayList<>();
    
        /*
         * EasyExcel 读取 是基于SAX方式
         * 因此在解析时需要传入监听器
         */
        // 第一个参数 为 excel文件路径
        // 读取时的数据类型
        // 监听器
        EasyExcel.read("学生信息表" + ExcelTypeEnum.XLSX.getValue(), Student_Read.class, new AnalysisEventListener<Student_Read>() {
    
            // 每读取一行就调用该方法
            @Override
            public void invoke(Student_Read data, AnalysisContext context) {
                list.add(data);
            }
    
            // 全部读取完成就调用该方法
            @Override
            public void doAfterAllAnalysed(AnalysisContext context) {
                System.out.println("读取完成");
            }
        }).sheet().doRead();
    
        list.forEach(System.out::println);
    }
    
  • 执行测试方法,结果如下:

    image-20201228111624149

以下是对测试方法代码的解释(started)

image-20201228142133510

如上图:

EasyExcel.read 该方法是用来创建ExcelReaderBuilder对象,该对象就是用来解析Excel文档

read方法需要传入三个参数,其具体含义如下:

  • 第一个参数

    需要解析文件的路径,当然除了传入一个文件路径以外,还可以传入InputStream

    源码如下:

    image-20201228144555456
  • 第二参数

    数据类型的Class类型对象,可以不传

  • 第三个参数

    事件监听器,在之前介绍这款框架时说过,该框架是基于SAX的一种解析,加载一行数据到内存就会去解析一行,主要是为了节约内存。

    image-20201228144958463

    invoke方法代表每解析一行就会调用一次,data数据表示解析出来一行的数据

    doAfterAllAnalysed 该方法表示将所有数据解析完毕以后才会去调用该方法

sheet方法代表读取excel第几个sheet,常用sheet方法如下:

image-20201228145757740

用法与之前写的用法类似,这里就不再过多介绍

doRead方法代表开始读取excel数据

4.2 其他读

实际开发中一般读操作用的最多的就是简单读,如果实在是有复杂的需求,例如读取表头或者是调用Excel里面的公式,可以参照官方文档

4.WEB

上述例子中,读写操作就是在本地去操作Excel文档,实际开发中都是在web中,但是其实用法都是一样的,只不过数据的来源不一样,这里提供了一个Excel工具类,其中就包括web操作。如下:

package com.briup.server.util;

import com.alibaba.excel.EasyExcel;
import com.alibaba.excel.context.AnalysisContext;
import com.alibaba.excel.event.AnalysisEventListener;
import com.alibaba.excel.read.listener.ReadListener;
import com.alibaba.excel.support.ExcelTypeEnum;
import com.alibaba.excel.write.builder.ExcelWriterBuilder;
import com.alibaba.excel.write.style.column.LongestMatchColumnWidthStyleStrategy;
import com.briup.server.exception.SMSException;
import com.briup.server.logging.LogHolder;
import org.apache.commons.lang3.StringUtils;
import org.springframework.util.CollectionUtils;
import org.springframework.web.multipart.MultipartFile;

import javax.servlet.http.HttpServletResponse;
import java.io.*;
import java.util.*;

public class ExcelUtil {
    /**
     * 写出一个 excel 文件到本地
     * <br />
     * 将类型所有加了 @ExcelProperty 注解的属性全部写出
     *
     * @param fileName  文件名 不要后缀
     * @param sheetName sheet名
     * @param data      写出的数据
     * @param clazz     要写出数据类的Class类型对象
     * @param <T>       写出的数据类型
     */
    public static <T> void writeExcel(String fileName, String sheetName, List<T> data, Class<T> clazz) {
        writeExcel(null, fileName, sheetName, data, clazz);
    }


    /**
     * 按照指定的属性名进行写出 一个 excel
     *
     * @param attrName  指定的属性名 必须与数据类型的属性名一致
     * @param fileName  文件名 不要后缀
     * @param sheetName sheet名
     * @param data      要写出的数据
     * @param clazz     要写出数据类的Class类型对象
     * @param <T>       要写出的数据类型
     */
    public static <T> void writeExcel(Set<String> attrName, String fileName, String sheetName, List<T> data, Class<T> clazz) {
        fileName = StringUtils.isBlank(fileName) ? "学生管理系统" : fileName;
        sheetName = StringUtils.isBlank(sheetName) ? "sheet0" : sheetName;

        try(FileOutputStream fos = new FileOutputStream(fileName)) {
            write(fos,attrName,sheetName,data,clazz);
        } catch (Exception exception) {
            exception.printStackTrace();
         
        }


    }

    /**
     * 读取 指定格式的 excel文档
     *
     * @param fileName 文件名
     * @param clazz    数据类型的class对象
     * @param <T>      数据类型
     * @return
     */
    public static <T> List<T> readExcel(String fileName, Class<T> clazz) {
        
        return readExcel(fileName, clazz, null);
    }

    /**
     * 取 指定格式的 excel文档
     * 注意一旦传入自定义监听器,则返回的list为空,数据需要在自定义监听器里面获取
     *
     * @param fileName     文件名
     * @param clazz        数据类型的class对象
     * @param readListener 自定义监听器
     * @param <T>          数据类型
     * @return
     */
    public static <T> List<T> readExcel(String fileName, Class<T> clazz, ReadListener<T> readListener) {
  

        try(FileInputStream fis = new FileInputStream(fileName)) {
            return read(fis,clazz,readListener);
        } catch (Exception exception) {
            exception.printStackTrace();
        
        }
    }


    /**
     * 导出  一个 excel
     *         导出excel所有数据
     * @param response
     * @param fileName  件名 最好为英文,不要后缀名
     * @param sheetName sheet名
     * @param data      要写出的数据
     * @param clazz     要写出数据类的Class类型对象
     * @param <T>       要写出的数据类型
     */
    public static <T> void export(HttpServletResponse response, String fileName, String sheetName, List<T> data, Class<T> clazz) {
        export(response, null, fileName, sheetName, data, clazz);
    }

    /**
     * 按照指定的属性名进行写出 一个 excel
     *
     * @param response
     * @param attrName  指定的属性名 必须与数据类型的属性名一致
     * @param fileName  文件名 最好为英文,不要后缀名
     * @param sheetName sheet名
     * @param data      要写出的数据
     * @param clazz     要写出数据类的Class类型对象
     * @param <T>       要写出的数据类型
     */
    public static <T> void export(HttpServletResponse response, Set<String> attrName, String fileName, String sheetName, List<T> data, Class<T> clazz) {
      
        fileName = StringUtils.isBlank(fileName) ? "student-system-manager" : fileName;
        sheetName = StringUtils.isBlank(sheetName) ? "sheet0" : sheetName;

        response.setContentType("application/vnd.ms-excel;charset=utf-8");
        response.setCharacterEncoding("utf-8");
        response.addHeader("Content-disposition", "attachment;filename=" + fileName + ExcelTypeEnum.XLSX.getValue());

        try(OutputStream os = response.getOutputStream()) {
            write(os,attrName,sheetName,data,clazz);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }


    /**
     * 接收一个excel文件,并且进行解析
     *  注意一旦传入自定义监听器,则返回的list为空,数据需要在自定义监听器里面获取
     * @param multipartFile excel文件
     * @param clazz 数据类型的class对象
     * @param readListener 监听器
     * @param <T>
     * @return
     */
    public static <T> List<T> importExcel(MultipartFile multipartFile,Class<T> clazz,ReadListener<T> readListener) {
  
  
        try(InputStream inputStream = multipartFile.getInputStream()) {
            return read(inputStream,clazz,readListener);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }



    private static <T> void write(OutputStream os, Set<String> attrName, String sheetName, List<T> data, Class<T> clazz) {
        ExcelWriterBuilder write = EasyExcel.write(os, clazz);
        // 如果没有指定要写出那些属性数据,则写出全部
        if (!CollectionUtils.isEmpty(attrName)) {
            write.includeColumnFiledNames(attrName);
        }
        write.registerWriteHandler(new LongestMatchColumnWidthStyleStrategy()).sheet(sheetName).doWrite(data);
    }


    private static <T> List<T> read(InputStream in,Class<T> clazz, ReadListener<T> readListener) {
        List<T> list = new ArrayList<>();

        Optional<ReadListener> optional = Optional.ofNullable(readListener);

        EasyExcel.read(in, clazz, optional.orElse(new AnalysisEventListener<T>() {

            @Override
            public void invoke(T data, AnalysisContext context) {
                list.add(data);
            }

            @Override
            public void doAfterAllAnalysed(AnalysisContext context) {
                  System.out.println("解析完成");
            }
        })).sheet().doRead();

        return list;
    }


}

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

推荐阅读更多精彩内容