MapStruct對象拷貝工具介紹

一曹傀、基本介紹

屬性拷貝工具有很多,也許你用過如下的一些:

  • Apache commons-beanutils
  • Spring BeanUtils
  • cglib BeanCopier
  • HuTool BeanUtils
  • MapStruct
  • getter & setter

這些屬性拷貝工具各自有什么特點(diǎn)和區(qū)別而昨?在日常開發(fā)使用中谍婉,我們該如何做出選擇?

1.1 Apache BeanUtils

  • 參數(shù)順序和其它的工具正好相反,導(dǎo)致使用不順手帮孔,容易產(chǎn)生問題;
  • 阿里巴巴代碼掃描插件會給出明確的告警不撑;
  • 基于反射實(shí)現(xiàn)文兢,性能較差;
  • 不推薦使用焕檬;

1.2 Spring BeanUtils

  • 基于內(nèi)省+反射,借助getter/setter方法實(shí)現(xiàn)屬性拷貝兼呵,性能比apache高腊敲;
  • 在簡單的屬性拷貝場景下推薦使用击喂;

1.3 cglib BeanCopier

  • 通過動態(tài)代理的方式來實(shí)現(xiàn)屬性拷貝;
  • 性能高效懂昂;
  • 在簡單的屬性拷貝場景下推薦使用没宾;

1.4 HuTool BeanUtils

  • 性能介于apache和Spring之間凌彬;
  • 需要額外引入HuTool的依賴沸柔;

1.5 MapStruct

  • 基于getter/setter方法實(shí)現(xiàn)屬性拷貝铲敛,在編譯時自動生成實(shí)現(xiàn)類的代碼;
  • 性能媲美getter & setter乱凿;
  • 強(qiáng)大的功能可以實(shí)現(xiàn)深度拷貝咽弦;
  • 缺點(diǎn)是需要聲明bean的轉(zhuǎn)換接口類胁出;

1.6 getter & setter

  • 性能最高全蝶,但是需要手動拷貝;

1.7 總結(jié)

經(jīng)過第三方的對比結(jié)果绷落,總的下來始苇,推薦使用順序?yàn)椋?/p>

apache < HuTool < Spring < cglib < Mapstruct

二催式、使用介紹

2.1 準(zhǔn)備工作

<!-- 導(dǎo)入MapStruct的核心注釋 -->
<dependencies>
    <dependency>
        <groupId>org.mapstruct</groupId>
        <artifactId>mapstruct</artifactId>
        <version>${org.mapstruct.version}</version>
    </dependency>
</dependencies>
...
<!-- MapStruct在編譯時工作荣月,并且會集成到像Maven和Gradle這樣的構(gòu)建工具上,我們還必須在<build中/>標(biāo)簽中添加一個插件maven-compiler-plugin捐下,并在其配置中添加annotationProcessorPaths萌业,該插件會在構(gòu)建時生成對應(yīng)的代碼咽白。 -->
<build>
    <plugins>
        <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-compiler-plugin</artifactId>
            <version>3.5.1</version>
            <configuration>
                <source>1.8</source>
                <target>1.8</target>
                <annotationProcessorPaths>
                    <path>
                        <groupId>org.mapstruct</groupId>
                        <artifactId>mapstruct-processor</artifactId>
                        <version>${org.mapstruct.version}</version>
                    </path>
                </annotationProcessorPaths>
            </configuration>
        </plugin>
    </plugins>
</build>

2.2 映射

2.2.1 基本映射

我們現(xiàn)在需要實(shí)現(xiàn)一個場景,Car是一個domain層的對象實(shí)例排抬,在從數(shù)據(jù)庫讀取出來后傳遞給service層需要轉(zhuǎn)換為CarDTO蹲蒲,這兩個實(shí)例的所有屬性全部相同,現(xiàn)在需要使用mapstruct來完成這個目標(biāo)缘薛。

public class Car {
    private String brand;
    private Double price;
    private Boolean onMarket;
    ...
    // setters + getters + toString
}
public class CarDTO {
    private String brand;
    private Double price;
    private Boolean onMarket;
    ...
    // setters + getters + toString
}

我們需要新建一個Mapper接口宴胧,來映射這兩個對象之間的屬性表锻。

@Mapper
public interface CarMapper {
    CarMapper INSTANCE = Mappers.getMapper(CarMapper.class);

    CarDTO carToCarDTO(Car car);
}

然后就可以進(jìn)行測試了:

    @Test
    public void test1(){
        Car wuling = new Car();
        wuling.setBrand("wuling");
        wuling.setPrice(6666.66);
        wuling.setOnMarket(true);

        CarDTO wulingDTO = CarMapper.INSTANCE.carToCarDTO(wuling);
        // 結(jié)果為:Car{brand='wuling', price=6666.66, onMarket=true}
        System.out.println("結(jié)果為:" + wulingDTO);
    }

可以看到瞬逊,mapstruct很好地完成了我們的目標(biāo),那么它是如何做到的呢士骤?我們查看CarMapper.INSTANCE.carToCarDTO(wuling)的實(shí)現(xiàn)類拷肌,可以看到在編譯過程中自動生成了如下內(nèi)容的接口實(shí)現(xiàn)類:

public class CarMapperImpl implements CarMapper {

    @Override
    public CarDTO carToCarDTO(Car car) {
        if ( car == null ) {
            return null;
        }

        CarDTO carDTO = new CarDTO();

        carDTO.setBrand( car.getBrand() );
        carDTO.setPrice( car.getPrice() );
        carDTO.setOnMarket( car.getOnMarket() );

        return carDTO;
    }
}

所以束铭,mapstruct并沒有使用反射的機(jī)制契沫,而是使用了普通的set和get方法來進(jìn)行屬性拷貝的,因此要求我們的對象也一定要有set和get方法拴清。

2.2.2 不同屬性名映射

在如上示例中会通,我們源對象和目標(biāo)對象的屬性名稱全都一致涕侈,但是在很多的場景下,源對象和目標(biāo)對象的同一個字段很可能名稱是不同的木张,這種情況下舷礼,只需要在映射接口類中指定即可:

public class Car {
    ...
    private Boolean onMarket;
    ...
    // setters + getters + toString
}
public class CarDTO {
    ...
    private Boolean onSale;
    ...
    // setters + getters + toString
}
@Mapper
public interface CarMapper {
    CarMapper INSTANCE = Mappers.getMapper(CarMapper.class);

    @Mapping(source = "car.onMarket", target = "onSale")
    CarDTO carToCarDTO(Car car);
}

如此,生成的接口實(shí)現(xiàn)類如下:

@Override
public CarDTO carToCarDTO(Car car) {
    if ( car == null ) {
        return null;
    }

    CarDTO carDTO = new CarDTO();

    carDTO.setOnSale( car.getOnMarket() );
    carDTO.setBrand( car.getBrand() );
    carDTO.setPrice( car.getPrice() );

    return carDTO;
}

2.2.3 不同個數(shù)屬性映射

我們假設(shè)Car和CarDTO各有一個對方?jīng)]有的屬性蛛株,那么在進(jìn)行對象拷貝時會發(fā)生什么谨履?

public class Car {
    ...
    private Date birthdate;
    // setters + getters + toString
}
public class CarDTO {
    ...
    private String owner;
    // setters + getters + toString
}
    @Test
    public void test1(){
        Car wuling = new Car();
        wuling.setBrand("wuling");
        wuling.setPrice(6666.66);
        wuling.setOnMarket(true);
        wuling.setBirthdate(new Date());

        CarDTO wulingDTO = CarMapper.INSTANCE.carToCarDTO(wuling);
        System.out.println("結(jié)果為:" + wulingDTO);
    }

然后我們執(zhí)行如上轉(zhuǎn)換的案例屉符,發(fā)現(xiàn)并沒有報錯锹引,從Car拷貝屬性到CarDTO時嫌变,CarDTO由于沒有birthdate屬性躬它,則不會賦值冯吓;同時,CarDTO的owner因?yàn)镃ar中沒有凸舵,因此也不會被賦值失尖,生成的接口實(shí)現(xiàn)類如下:

    @Override
    public CarDTO carToCarDTO(Car car) {
        if ( car == null ) {
            return null;
        }

        CarDTO carDTO = new CarDTO();

        carDTO.setOnSale( car.getOnMarket() );
        carDTO.setBrand( car.getBrand() );
        carDTO.setPrice( car.getPrice() );

        return carDTO;
    }

因此掀潮,mapstruct只會對共有的交集屬性進(jìn)行拷貝操作。

2.2.4 多個源合并映射

我們新增一個Person類仪吧,其中的name屬性對應(yīng)CarDTO中的owner屬性。

public class Person {
    private String name;
    private String age;
    // setters + getters + toString
}
@Mapper
public interface CarMapper {
    CarMapper INSTANCE = Mappers.getMapper(CarMapper.class);

    @Mapping(source = "car.onMarket", target = "onSale")
    @Mapping(source = "person.name", target = "owner")
    CarDTO carToCarDTO(Car car, Person person);
}
public class TestMapper1 {

    @Test
    public void test1(){
        Car wuling = new Car();
        wuling.setBrand("wuling");
        wuling.setPrice(6666.66);
        wuling.setOnMarket(true);
        wuling.setBirthdate(new Date());

        Person jack = new Person();
        jack.setName("jack");
        jack.setAge("22");

        CarDTO wulingDTO = CarMapper.INSTANCE.carToCarDTO(wuling, jack);
        // 結(jié)果為:CarDTO{brand='wuling', price=6666.66, onSale=true, owner='jack'}
        System.out.println("結(jié)果為:" + wulingDTO);
    }

自動生成的接口實(shí)現(xiàn)類如下:

    @Override
    public CarDTO carToCarDTO(Car car, Person person) {
        if ( car == null && person == null ) {
            return null;
        }

        CarDTO carDTO = new CarDTO();

        if ( car != null ) {
            carDTO.setOnSale( car.getOnMarket() );
            carDTO.setBrand( car.getBrand() );
            carDTO.setPrice( car.getPrice() );
        }
        if ( person != null ) {
            carDTO.setOwner( person.getName() );
        }

        return carDTO;
    }

2.2.5 子對象映射

如果需要轉(zhuǎn)換的Car對象中的某個屬性不是基本數(shù)據(jù)類型,而是一個對象怎么處理呢吭从。

public class Person {

    private String name;

    private String age;
    
    // setters + getters + toString
}

public class PersonDTO {

    private String name;

    private String age;
    
    // setters + getters + toString
}
public class Car {
    private String brand;
    private Double price;
    private Boolean onMarket;
    private Person owner;
    
    // setters + getters + toString
}

public class CarDTO {
    private String brand;
    private Double price;
    private Boolean onMarket;
    private PersonDTO owner;
    
    // setters + getters + toString
}
@Mapper
public interface PersonMapper {
    PersonMapper INSTANCE = Mappers.getMapper(PersonMapper.class);

    PersonDTO personToPersonDTO(Person person);
}
@Mapper(uses = {PersonMapper.class})
public interface CarMapper {
    CarMapper INSTANCE = Mappers.getMapper(CarMapper.class);

    @Mapping(source = "onMarket", target = "onSale")
    CarDTO carToCarDTO(Car car);
}
@Test
public void test1(){
    Person jack = new Person();
    jack.setName("jack");
    jack.setAge("22");

    Car wuling = new Car();
    wuling.setBrand("wuling");
    wuling.setPrice(6666.66);
    wuling.setOnMarket(true);
    wuling.setOwner(jack);

    CarDTO wulingDTO = CarMapper.INSTANCE.carToCarDTO(wuling);
    // 結(jié)果為:CarDTO{brand='wuling', price=6666.66, onSale=true, owner=Person{name='jack', age='22'}}
    System.out.println("結(jié)果為:" + wulingDTO);
}

這里最重要的是:

  • 需要增加PersonMapper接口谱醇,讓mapstruct能夠?qū)erson和PersonDTO進(jìn)行轉(zhuǎn)換步做;
  • CarMapper中需要引入PersonMapper全度,如果存在多個對象屬性,此處就要引入多個對象屬性的Mapper接口勉盅;

2.2.6 集合屬性映射

如果需要轉(zhuǎn)換的Car對象中的某個屬性不是基本數(shù)據(jù)類型顶掉,而是一個集合類型該怎么處理痒筒?

public class Car {
    private String brand;
    private Double price;
    private Boolean onMarket;
    private List<Person> ownerList;
    
    // setters + getters + toString
}

同2.2.5內(nèi)容。

2.2.7 枚舉映射

枚舉映射的工作方式與字段映射相同移袍。MapStruct會對具有相同名稱的枚舉進(jìn)行映射葡盗。

public enum PayType {
    CASH,
    ALIPAY,
    WEPAY,
    DIGITAL_CASH,
    CARD_VISA,
    CARD_CREDIT;
}

public enum PayTypeNew {
    CASH,
    ALIPAY,
    WEPAY,
    DIGITAL_CASH,
    CARD_VISA,
    CARD_CREDIT;
}
@Mapper
public interface PayTypeMapper {
    PayTypeMapper INSTANCE = Mappers.getMapper(PayTypeMapper.class);

    PayTypeNew payTypeToPayTypeNew(PayType payType);
}
    @Test
    public void test2(){
        PayType p1 = PayType.ALIPAY;

        PayTypeNew p2 = PayTypeMapper.INSTANCE.payTypeToPayTypeNew(p1);
        // 結(jié)果為:ALIPAY
        System.out.println("結(jié)果為:" + p2);
    }

但是在更多的場景下戳粒,源枚舉和目標(biāo)枚舉并不是一一對應(yīng)的虫啥,比如目標(biāo)枚舉如下:

public enum PayTypeNew {
    CASH,
    NETWORK,
    CARD;
}

此時涂籽,我們就需要手動指定源枚舉和目標(biāo)枚舉之間的對應(yīng)關(guān)系:

@Mapper
public interface PayTypeMapper {
    PayTypeMapper INSTANCE = Mappers.getMapper(PayTypeMapper.class);

    @ValueMappings({
            @ValueMapping(source = "ALIPAY", target = "NETWORK"),
            @ValueMapping(source = "WEPAY", target = "NETWORK"),
            @ValueMapping(source = "DIGITAL_CASH", target = "CASH"),
            @ValueMapping(source = "CARD_VISA", target = "CARD"),
            @ValueMapping(source = "CARD_CREDIT", target = "CARD")
    })
    PayTypeNew payTypeToPayTypeNew(PayType payType);
}

如果對應(yīng)CARD的場景比較多,手動一個個地對應(yīng)會比較繁瑣树枫,因此還有一種方式能實(shí)現(xiàn)相同的效果砂轻,而且比較簡潔:

@Mapper
public interface PayTypeMapper {
    PayTypeMapper INSTANCE = Mappers.getMapper(PayTypeMapper.class);

    @ValueMappings({
            @ValueMapping(source = "ALIPAY", target = "NETWORK"),
            @ValueMapping(source = "WEPAY", target = "NETWORK"),
            @ValueMapping(source = "DIGITAL_CASH", target = "CASH"),
            @ValueMapping(source = MappingConstants.ANY_REMAINING, target = "CARD")
    })
    PayTypeNew payTypeToPayTypeNew(PayType payType);
}

MappingConstants.ANY_REMAINING表示剩下其它的源枚舉和目標(biāo)枚舉對應(yīng)不上的全部映射為指定的枚舉對象搔涝。

還有一種方式,使用MappingConstants.ANY_UNMAPPED表示所有未顯示指定目標(biāo)枚舉的都會被映射為CARD:

@Mapper
public interface PayTypeMapper {
    PayTypeMapper INSTANCE = Mappers.getMapper(PayTypeMapper.class);

    @ValueMappings({
            @ValueMapping(source = "ALIPAY", target = "NETWORK"),
            @ValueMapping(source = "WEPAY", target = "NETWORK"),
            @ValueMapping(source = "DIGITAL_CASH", target = "CASH"),
            @ValueMapping(source = MappingConstants.ANY_UNMAPPED, target = "CARD")
    })
    PayTypeNew payTypeToPayTypeNew(PayType payType);
}

2.2.8 集合映射

如果源對象和目標(biāo)對象都是集合蜕煌,且對象中的屬性都是基本數(shù)據(jù)類型斜纪,則映射方法和之前類似文兑,映射接口改為如下即可:

@Mapper
public interface CarMapper {
    CarMapper INSTANCE = Mappers.getMapper(CarMapper.class);

    List<CarDTO> carToCarDTOList(List<Car> carList);

    Set<CarDTO> carToCarDTOSet(Set<Car> carSet);

    Map<String, CarDTO> carToCarDTOMap(Map<String, Car> carMap);
}

如果對象中屬性不僅是基本數(shù)據(jù)類型绿贞,還有對象類型或?qū)ο箢愋偷募项愋偷脑挘琺apstruct也是支持映射的,詳情參考官網(wǎng)文檔寨辩,此處不再贅述歼冰。

2.3 轉(zhuǎn)換

2.3.1 類型轉(zhuǎn)換

mapstruct提供了基本數(shù)據(jù)類型和包裝數(shù)據(jù)類型隔嫡、一些常見場景下的自動轉(zhuǎn)換;

  • 基本類型及其對應(yīng)的包裝類型之間的轉(zhuǎn)換梢杭;比如int和Integer秸滴、float和Float、long和Long届垫、boolean和Boolean等全释;
  • 任意基本類型和任意包裝類型之間的轉(zhuǎn)換浸船;比如int和long、byte和Integer等判族;
  • 任意基本類型项戴、包轉(zhuǎn)類型和String之間的轉(zhuǎn)換周叮;比如boolean和String、Integer和String合冀;
  • 枚舉和String君躺;
  • 大數(shù)類型(BigInteger开缎、BigDecimal)、基本類型奕删、基本類型包裝類型完残、String之間的相互轉(zhuǎn)換;
  • 其它一些場景熟掂,參考MapStruct 1.4.2.Final Reference Guide打掘;

2.3.2 格式轉(zhuǎn)換

mapstruct可以對源對象的屬性值進(jìn)行格式化之后拷貝給目標(biāo)對象的屬性;

  • 日期格式轉(zhuǎn)換

    public class Car {
        private String brand;
        private Double price;
        private LocalDate marketDate;
        // setters + getters + toString
    }
    
    public class CarDTO {
        private String brand;
        private Double price;
        private String saleDate;
        // setters + getters + toString
    }
    
    @Mapper
    public interface CarMapper {
        CarMapper INSTANCE = Mappers.getMapper(CarMapper.class);
    
        @Mapping(source = "marketDate", target = "saleDate", dateFormat = "dd/MM/yyyy")
        CarDTO carToCarDTO(Car car);
    }
    
        @Test
        public void test1(){
            Car wuling = new Car();
            wuling.setBrand("wuling");
            wuling.setPrice(6666.66);
            wuling.setMarketDate(LocalDate.now());
    
            // 轉(zhuǎn)換前為:Car{brand='wuling', price=6666.66, marketDate=2022-01-19}
            System.out.println("轉(zhuǎn)換前為:" + wuling);
            CarDTO wulingDTO = CarMapper.INSTANCE.carToCarDTO(wuling);
            // 結(jié)果為:CarDTO{brand='wuling', price=6666.66, saleDate='19/01/2022'}
            System.out.println("結(jié)果為:" + wulingDTO);
        }
    
  • 數(shù)字格式轉(zhuǎn)換

    @Mapper
    public interface CarMapper {
        CarMapper INSTANCE = Mappers.getMapper(CarMapper.class);
    
        @Mapping(source = "price", target = "price", numberFormat = "$#.00")
        CarDTO carToCarDTO(Car car);
    }
    

2.4 高級特性

2.4.1 依賴注入

在前面例子中的Mapper映射接口中,我們都需要CarMapper INSTANCE = Mappers.getMapper(CarMapper.class);來創(chuàng)建一個實(shí)例仑乌,如果我們是Spring工程的話琴锭,就可以把這個實(shí)例托管給Spring進(jìn)行管理决帖。

@Mapper(componentModel = "spring")
public interface CarMapper {
    List<CarDTO> carToCarDTOList(List<Car> carList);
}

然后使用的時候地回,從Spring中自動注入接口對象即可:

@SpringBootTest
public class TestMapper1 {
    @Autowired
    private CarMapper carMapper;

    @Test
    public void test1(){
        Car wuling = new Car();
        wuling.setBrand("wuling");
        wuling.setPrice(6666.66);
        wuling.setMarketDate(LocalDate.now());
        Car changan = new Car();
        changan.setBrand("changan");
        changan.setPrice(7777.77);
        changan.setMarketDate(LocalDate.now());
        List<Car> carList = new ArrayList<>();
        carList.add(wuling);
        carList.add(changan);

        List<CarDTO> carDTOList = carMapper.carToCarDTOList(carList);
        System.out.println("結(jié)果為:" + carDTOList);
    }
}

2.4.2 設(shè)置默認(rèn)值

  • 常量默認(rèn)值

    無論源對象的屬性字段值是什么刻像,目標(biāo)對象的該字段都是給定的常量值。

    @Mapper(componentModel = "spring")
    public interface PersonMapper {
        @Mapping(target = "name", constant = "zhangsan")
        PersonDTO personToPersonDTO(Person person);
    }
    
  • 空值默認(rèn)值

    如果源對象的屬性字段值為空谷羞,那么就使用指定的默認(rèn)值湃缎。

    @Mapper(componentModel = "spring")
    public interface PersonMapper {
        @Mapping(source = "name", target = "name", defaultValue = "unknown")
        PersonDTO personToPersonDTO(Person person);
    }
    

2.4.3 使用表達(dá)式

mapstruct甚至允許在對象屬性映射中使用java表達(dá)式:

@Mapper(componentModel = "spring", imports = {UUID.class, LocalDateTime.class})
public interface PersonMapper {

    @Mapping(target = "id", expression = "java(UUID.randomUUID().toString())")
    @Mapping(source = "birthdate", target = "birthdate", defaultExpression = "java(LocalDateTime.now())")
    PersonDTO personToPersonDTO(Person person);
}

或者等價寫法為:

@Mapper(componentModel = "spring")
public interface PersonMapper {

    @Mapping(target = "id", expression = "java(java.util.UUID.randomUUID().toString())")
    @Mapping(source = "birthdate", target = "birthdate", defaultExpression = "java(java.time.LocalDateTime.now())")
    PersonDTO personToPersonDTO(Person person);
}

2.4.4 前置及后置方法

@Mapper(componentModel = "spring")
public abstract class PersonMapper {

    @BeforeMapping
    public void before(Person person){
        System.out.println("前置處理Q愀琛V小求妹!");
        if(ObjectUtils.isEmpty(person.getName())){
            System.out.println("Person的name不能為空佳窑!");
            return;
        }
    }

    @Mapping(target = "id", expression = "java(java.util.UUID.randomUUID().toString())")
    @Mapping(source = "birthdate", target = "birthdate", defaultExpression = "java(java.time.LocalDateTime.now())")
    public abstract PersonDTO personToPersonDTO(Person person);

    @AfterMapping
    public void after(@MappingTarget PersonDTO personDTO){
        System.out.println("后置處理:" + personDTO.getName() + "!!!");
    }
}

三神凑、參考文獻(xiàn)

MapStruct 1.4.2.Final Reference Guide

MapStruct使用指南 - 知乎 (zhihu.com)

常見Bean拷貝框架使用姿勢及性能對比 - 知乎 (zhihu.com)

四、補(bǔ)充填坑

在正文的實(shí)例中爱榕,我們對于Bean對象都是使用手動寫getter坡慌、setter、toString方法的跪者,但是在真實(shí)開發(fā)中渣玲,大家都是采用了Lombok插件弟晚,如果不做特殊配置指巡,就會出現(xiàn)mapstruct運(yùn)行時lombok不生效的問題,只需要在pom配置中增加如下內(nèi)容:

<annotationProcessorPaths>
  <path>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
    <version>${lombok.version}</version>
   </path>                    
</annotationProcessorPaths>

具體原理和詳情可以參考:當(dāng) Lombok 遇見了 MapStruct の「坑」 - 知乎 (zhihu.com)

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市勉耀,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌至壤,老刑警劉巖枢纠,帶你破解...
    沈念sama閱讀 221,198評論 6 514
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件晋渺,死亡現(xiàn)場離奇詭異木西,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)吗讶,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,334評論 3 398
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來脚仔,“玉大人,你說我怎么就攤上這事葬凳∈易啵” “怎么了胧沫?”我有些...
    開封第一講書人閱讀 167,643評論 0 360
  • 文/不壞的土叔 我叫張陵绒怨,是天一觀的道長。 經(jīng)常有香客問我犬金,道長晚顷,這世上最難降的妖魔是什么疗疟? 我笑而不...
    開封第一講書人閱讀 59,495評論 1 296
  • 正文 為了忘掉前任策彤,我火速辦了婚禮店诗,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己恕洲,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,502評論 6 397
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著泌类,像睡著了一般刃榨。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上桌吃,一...
    開封第一講書人閱讀 52,156評論 1 308
  • 那天茅诱,我揣著相機(jī)與錄音瑟俭,去河邊找鬼契邀。 笑死蹂安,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的畜号。 我是一名探鬼主播简软,決...
    沈念sama閱讀 40,743評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼痹升,長吁一口氣:“原來是場噩夢啊……” “哼畦韭!你這毒婦竟也來了艺配?” 一聲冷哼從身側(cè)響起衍慎,我...
    開封第一講書人閱讀 39,659評論 0 276
  • 序言:老撾萬榮一對情侶失蹤稳捆,失蹤者是張志新(化名)和其女友劉穎乔夯,沒想到半個月后款侵,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體喳坠,經(jīng)...
    沈念sama閱讀 46,200評論 1 319
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡壕鹉,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,282評論 3 340
  • 正文 我和宋清朗相戀三年晾浴,在試婚紗的時候發(fā)現(xiàn)自己被綠了脊凰。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,424評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡切省,死狀恐怖帕胆,靈堂內(nèi)的尸體忽然破棺而出朝捆,到底是詐尸還是另有隱情,我是刑警寧澤懒豹,帶...
    沈念sama閱讀 36,107評論 5 349
  • 正文 年R本政府宣布芙盘,位于F島的核電站,受9級特大地震影響脸秽,放射性物質(zhì)發(fā)生泄漏儒老。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,789評論 3 333
  • 文/蒙蒙 一记餐、第九天 我趴在偏房一處隱蔽的房頂上張望驮樊。 院中可真熱鬧,春花似錦囚衔、人聲如沸铝穷。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,264評論 0 23
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至晦炊,卻和暖如春鞠鲜,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背断国。 一陣腳步聲響...
    開封第一講書人閱讀 33,390評論 1 271
  • 我被黑心中介騙來泰國打工贤姆, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人稳衬。 一個月前我還...
    沈念sama閱讀 48,798評論 3 376
  • 正文 我出身青樓霞捡,卻偏偏與公主長得像,于是被迫代替她去往敵國和親薄疚。 傳聞我的和親對象是個殘疾皇子碧信,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,435評論 2 359

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