SpringBoot Data操作ES

14. SpringBoot Data操作ES

14.1 引入依赖

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-elasticsearch</artifactId>
</dependency>

14.2 编写yml配置

  • spring-data(2~3.x版本配置)
spring:
  data:
    elasticsearch:
      cluster-nodes: 172.16.251.142:9300
  • spring-data(新版本推荐配置) RestHighLevelClient rest客户端 ElasticSearchRespositoy接口
@Configuration
public class RestClientConfig extends AbstractElasticsearchConfiguration {

    @Override
    @Bean
    public RestHighLevelClient elasticsearchClient() {
        final ClientConfiguration clientConfiguration = ClientConfiguration.builder()
                .connectedTo("192.168.202.200:9200")
                .build();
        return RestClients.create(clientConfiguration).rest();
    }

}

14.3 编写entity

@Document(indexName = "dangdang",type = "book")
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Book {
    @Id
    private String id;

    @Field(type = FieldType.Text,analyzer ="ik_max_word")
    private String name;


    @Field(type = FieldType.Date)
    @JsonFormat(pattern="yyyy-MM-dd")
    private Date createDate;

    @Field(type = FieldType.Keyword)
    private String author;

    @Field(type = FieldType.Text,analyzer ="ik_max_word")
    private String content;
}

@Document: 代表一个文档记录

indexName: 用来指定索引名称

type: 用来指定索引类型

@Id: 用来将对象中id和ES中_id映射

@Field: 用来指定ES中的字段对应Mapping

type: 用来指定ES中存储类型

analyzer: 用来指定使用哪种分词器

14.4 编写BookRepository

public interface BookRepository extends ElasticsearchRepository<Book,String> {
}

14.5 索引or更新一条记录

NOTE:这种方式根据实体类中中配置自动在ES创建索引,类型以及映射

@SpringBootTest(classes = Application.class)
@RunWith(SpringRunner.class)
public class TestSpringBootDataEs {
    @Autowired
    private BookRepository bookRespistory;
    /**
     * 添加索引和更新索引 id 存在更新 不存在添加
     */
    @Test
    public void testSaveOrUpdate(){
        Book book = new Book();
        book.setId("21");
        book.setName("小陈");
        book.setCreateDate(new Date());
        book.setAuthor("李白");
        book.setContent("这是中国的好人,这真的是一个很好的人,李白很狂");
        bookRespistory.save(book);
    }
}

14.6 删除一条记录

    /**
     * 删除一条索引
     */
    @Test
    public void testDelete(){
        Book book = new Book();
        book.setId("21");
        bookRespistory.delete(book);
    }

14.7 查询

    /**
     * 查询所有
     */
    @Test
    public void testFindAll(){
        Iterable<Book> books = bookRespistory.findAll();
        for (Book book : books) {
            System.out.println(book);
        }
    }


    /**
     * 查询一个
     */
    @Test
    public void testFindOne(){
        Optional<Book> byId = bookRespistory.findById("21");
        System.out.println(byId.get());
    }

14.8 查询排序

    /**
     * 排序查询
     */
    @Test
    public void testFindAllOrder(){
        Iterable<Book> books = bookRespistory.findAll(Sort.by(Sort.Order.asc("createDate")));
        books.forEach(book -> System.out.println(book) );
    }

14.9 自定义基本查询

Keyword Sample Elasticsearch Query String
And findByNameAndPrice {"bool" : {"must" : [ {"field" : {"name" : "?"}}, {"field" : {"price" : "?"}} ]}}
Or findByNameOrPrice {"bool" : {"should" : [ {"field" : {"name" : "?"}}, {"field" : {"price" : "?"}} ]}}
Is findByName {"bool" : {"must" : {"field" : {"name" : "?"}}}}
Not findByNameNot {"bool" : {"must_not" : {"field" : {"name" : "?"}}}}
Between findByPriceBetween {"bool" : {"must" : {"range" : {"price" : {"from" : ?,"to" : ?,"include_lower" : true,"include_upper" : true}}}}}
LessThanEqual findByPriceLessThan {"bool" : {"must" : {"range" : {"price" : {"from" : null,"to" : ?,"include_lower" : true,"include_upper" : true}}}}}
GreaterThanEqual findByPriceGreaterThan {"bool" : {"must" : {"range" : {"price" : {"from" : ?,"to" : null,"include_lower" : true,"include_upper" : true}}}}}
Before findByPriceBefore {"bool" : {"must" : {"range" : {"price" : {"from" : null,"to" : ?,"include_lower" : true,"include_upper" : true}}}}}
After findByPriceAfter {"bool" : {"must" : {"range" : {"price" : {"from" : ?,"to" : null,"include_lower" : true,"include_upper" : true}}}}}
Like findByNameLike {"bool" : {"must" : {"field" : {"name" : {"query" : "?*","analyze_wildcard" : true}}}}}
StartingWith findByNameStartingWith {"bool" : {"must" : {"field" : {"name" : {"query" : "?*","analyze_wildcard" : true}}}}}
EndingWith findByNameEndingWith {"bool" : {"must" : {"field" : {"name" : {"query" : "*?","analyze_wildcard" : true}}}}}
Contains/Containing findByNameContaining {"bool" : {"must" : {"field" : {"name" : {"query" : "**?**","analyze_wildcard" : true}}}}}
In findByNameIn
(Collection<String>names)
{"bool" : {"must" : {"bool" : {"should" : [ {"field" : {"name" : "?"}}, {"field" : {"name" : "?"}} ]}}}}
NotIn findByNameNotIn
(Collection<String>names)
{"bool" : {"must_not" : {"bool" : {"should" : {"field" : {"name" : "?"}}}}}}
Near findByStoreNear Not Supported Yet !
True findByAvailableTrue {"bool" : {"must" : {"field" : {"available" : true}}}}
False findByAvailableFalse {"bool" : {"must" : {"field" : {"available" : false}}}}
OrderBy findByAvailable
TrueOrderByNameDesc
{"sort" : [{ "name" : {"order" : "desc"} }],"bool" : {"must" : {"field" : {"available" : true}}}}
public interface BookRepository extends ElasticsearchRepository<Book,String> {

    //根据作者查询
    List<Book> findByAuthor(String keyword);

    //根据内容查询
    List<Book> findByContent(String keyword);

    //根据内容和名字查
    List<Book> findByNameAndContent(String name,String content);

    //根据内容或名称查询
    List<Book> findByNameOrContent(String name,String content);

    //范围查询
    List<Book> findByPriceBetween(Double start,Double end);

    //查询名字以xx开始的
    List<Book>  findByNameStartingWith(String name);

    //查询某个字段值是否为false
    List<Book>  findByNameFalse();
    
    //.......
}

14.10 实现复杂查询

分页查询并排序
@Test
public void testSearchPage() throws IOException {
  SearchRequest searchRequest = new SearchRequest();
  SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();
  sourceBuilder.from(0).size(2).sort("age", SortOrder.DESC).query(QueryBuilders.matchAllQuery());
  searchRequest.indices("ems").types("emp").source(sourceBuilder);
  SearchResponse search = restHighLevelClient.search(searchRequest, RequestOptions.DEFAULT);
  SearchHit[] hits = search.getHits().getHits();
  for (SearchHit hit : hits) {
    System.out.println(hit.getSourceAsString());
  }
}
高亮查询
@Test
    public void testSearchHig() throws IOException {
        SearchRequest searchRequest = new SearchRequest();
        SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();
        HighlightBuilder highlightBuilder =  new HighlightBuilder();
        highlightBuilder.field("content").requireFieldMatch(false).preTags("<span style='color:red;'>").postTags("</span>");
        sourceBuilder.from(0).size(2).sort("age", SortOrder.DESC).highlighter(highlightBuilder).query(QueryBuilders.termQuery("content","框架"));
        searchRequest.indices("ems").types("emp").source(sourceBuilder);
        SearchResponse search = restHighLevelClient.search(searchRequest, RequestOptions.DEFAULT);
        SearchHit[] hits = search.getHits().getHits();
        for (SearchHit hit : hits) {
            System.out.println(hit.getSourceAsString());
            Map<String, HighlightField> highlightFields = hit.getHighlightFields();
            highlightFields.forEach((k,v)-> System.out.println("key: "+k + " value: "+v.fragments()[0]));
        }
    }

下面是编写的代码:

1、自动创建springboot项目
2、maven引入springboot-data的jar包

<dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-elasticsearch</artifactId>
        </dependency>

3、编写entity:

package com.nono.entity;

import com.fasterxml.jackson.annotation.JsonFormat;
import lombok.Data;
import org.springframework.data.annotation.Id;
import org.springframework.data.elasticsearch.annotations.DateFormat;
import org.springframework.data.elasticsearch.annotations.Document;
import org.springframework.data.elasticsearch.annotations.Field;
import org.springframework.data.elasticsearch.annotations.FieldType;

import java.util.Date;

@Data
/**
 * 用在类上 作用:将Emp的对象映射成ES中一条json格式文档
 *      indexName: 用来指定这个对象的转为json文档存入那个索引中 要求:ES服务器中之前不能存在此索引名
 *      type     : 用来指定在当前这个索引下创建的类型名称
 */
@Document(indexName = "ems",type = "emp")
public class Emp {

    @Id //用来将对象中id属性与文档中_id 一一对应
    private String id;
    // 用在属性上 代表mapping中一个属性 一个字段 type:属性 用来指定字段类型 analyzer:指定分词器
    @Field(type = FieldType.Text,analyzer = "ik_max_word")
    private String name;
    @Field(type = FieldType.Integer)
    private Integer age;
    @Field(type = FieldType.Date)
    @JsonFormat(pattern = "yyyy-MM-dd")
    private Date bir;
    @Field(type = FieldType.Text,analyzer = "ik_max_word")
    private String content;
    @Field(type = FieldType.Text,analyzer = "ik_max_word")
    private String address;

}

springboot操作es有两种情况 第一种是用RestHighLevelClient,第二种是用ElasticSearchRepository

4、编写ElasticSearchRestClientConfig继承AbstractElasticsearchConfiguration

package com.nono.config;

import org.elasticsearch.client.RestHighLevelClient;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.elasticsearch.client.ClientConfiguration;
import org.springframework.data.elasticsearch.client.RestClients;
import org.springframework.data.elasticsearch.config.AbstractElasticsearchConfiguration;

@Configuration
public class ElasticSearchRestClientConfig extends AbstractElasticsearchConfiguration {

    //这个client 用来替换 transportClient(9300)对象
    @Override
    @Bean
    public RestHighLevelClient elasticsearchClient() {
        //定义客户端配置对象 RestClient  9200
        ClientConfiguration clientConfiguration = ClientConfiguration.builder()
                .connectedTo("192.168.159.5:9200")
                .build();
       //通过RestClients对象创建
        return RestClients.create(clientConfiguration).rest();
    }
}

5、编写TestRestClient注入RestHighLevelClient的方式进行操作es。
(注意我们用自动构建的springboot项目在测试的时候只要用@SpringBootTest就可以了。不过记住@Test要引入的是import org.junit.jupiter.api.Test;)

package com.nono;

import org.elasticsearch.action.bulk.BulkItemResponse;
import org.elasticsearch.action.bulk.BulkRequest;
import org.elasticsearch.action.bulk.BulkResponse;
import org.elasticsearch.action.delete.DeleteRequest;
import org.elasticsearch.action.delete.DeleteResponse;
import org.elasticsearch.action.index.IndexRequest;
import org.elasticsearch.action.index.IndexResponse;
import org.elasticsearch.action.search.SearchRequest;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.action.update.UpdateRequest;
import org.elasticsearch.action.update.UpdateResponse;
import org.elasticsearch.client.RequestOptions;
import org.elasticsearch.client.RestHighLevelClient;
import org.elasticsearch.common.xcontent.XContentType;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.search.SearchHit;
import org.elasticsearch.search.builder.SearchSourceBuilder;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightBuilder;
import org.elasticsearch.search.sort.SortOrder;

import org.junit.jupiter.api.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.TestExecutionListeners;
import org.springframework.test.context.junit4.SpringRunner;

import java.io.IOException;


/**
 * client 操作
 *
 *  RestHighLevelClient         强大 更灵活  不能友好的对象操作
 *
 *  ElasticSearchRepository     对象操作友好
 *
 */
@SpringBootTest
public class TestRestClient {

    @Autowired
    private RestHighLevelClient restHighLevelClient;//复杂查询使用  高亮查询  指定条件查询  如同transportClient


    //添加文档
    @Test
    public void testAddIndex() throws IOException {
        IndexRequest indexRequest = new IndexRequest("ems","emp","14");
        indexRequest.source("{\"name\":\"小黑\",\"age\":23}", XContentType.JSON);
        IndexResponse indexResponse = restHighLevelClient.index(indexRequest, RequestOptions.DEFAULT);
        System.out.println(indexResponse.status());
    }

    //删除文档
    @Test
    public void testDeleteIndex() throws IOException {
        DeleteRequest deleteRequest = new DeleteRequest("ems","emp","14");
        DeleteResponse deleteResponse = restHighLevelClient.delete(deleteRequest, RequestOptions.DEFAULT);
        System.out.println(deleteResponse.status());
    }


    //更新文档
    @Test
    public void testUpdateIndex() throws IOException {
        UpdateRequest updateRequest = new UpdateRequest("ems","emp","12");
        updateRequest.doc("{\"name\":\"小黑\",\"age\":30}",XContentType.JSON);
        UpdateResponse updateResponse = restHighLevelClient.update(updateRequest, RequestOptions.DEFAULT);
        System.out.println(updateResponse.status());
    }

    //批量更新
    @Test
    public void testBulk() throws IOException {
        BulkRequest bulkRequest = new BulkRequest();

        //添加
        IndexRequest request = new IndexRequest("ems","emp","13");
        request.source("{\"name\":\"李四\",\"age\":23}",XContentType.JSON);
        //删除

        //修改


        bulkRequest.add(request);
        BulkResponse bulkResponse = restHighLevelClient.bulk(bulkRequest, RequestOptions.DEFAULT);
        BulkItemResponse[] items = bulkResponse.getItems();
        for (BulkItemResponse item : items) {
            System.out.println(item.status());
        }
    }

    //查询所有
    @Test
    public void testQueryAll() throws IOException {
        SearchRequest searchRequest = new SearchRequest("ems");
        //创建搜索条件
        SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
        //查询所有
        searchSourceBuilder.query(QueryBuilders.matchAllQuery());

        searchRequest.types("emp").source(searchSourceBuilder);
        SearchResponse searchResponse = restHighLevelClient.search(searchRequest, RequestOptions.DEFAULT);
        SearchHit[] hits = searchResponse.getHits().getHits();
        for (SearchHit hit : hits) {
            System.out.println(hit.getSourceAsString());
        }


    }


    @Test
    public void testSearch() throws IOException {
        //创建搜索对象
        SearchRequest searchRequest = new SearchRequest("ems");
        //搜索构建对象
        SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();

        searchSourceBuilder.query(QueryBuilders.matchAllQuery())//执行查询条件
                .from(0)//起始条数
                .size(20)//每页展示记录
                .postFilter(QueryBuilders.matchAllQuery()) //过滤条件
                .sort("age", SortOrder.DESC)//排序
                .highlighter(new HighlightBuilder().field("*").requireFieldMatch(false));//高亮

        //创建搜索请求
        searchRequest.types("emp").source(searchSourceBuilder);

        SearchResponse searchResponse = restHighLevelClient.search(searchRequest, RequestOptions.DEFAULT);

        System.out.println("符合条件的文档总数: "+searchResponse.getHits().getTotalHits());
        System.out.println("符合条件的文档最大得分: "+searchResponse.getHits().getMaxScore());
        SearchHit[] hits = searchResponse.getHits().getHits();
        for (SearchHit hit : hits) {
            System.out.println(hit.getSourceAsMap());
        }
    }








}

6、编写EmpRepository接口 实现ElasticsearchRepository

package com.nono.elasticsearch.dao;

import com.nono.entity.Emp;
import org.springframework.data.elasticsearch.repository.ElasticsearchRepository;

import java.util.List;

//自定义EmpRepository
public interface EmpRepository extends ElasticsearchRepository<Emp,String> {

    //根据姓名查询
    List<Emp> findByName(String  name);

    //根据年龄查询
    List<Emp> findByAge(Integer age);

    //根据姓名和地址
    List<Emp> findByNameAndAddressAndAge(String name,String address,Integer age);

    //根据姓名或年龄查询
    List<Emp> findByNameOrAge(String name,Integer age);

    //查询年龄大于等于 8
    List<Emp> findByAgeGreaterThanEqual(Integer value);





}

7、编写TestEmpRespository测试

package com.nono;

import com.nono.elasticsearch.dao.EmpRepository;
import com.nono.entity.Emp;
import lombok.ToString;
import org.elasticsearch.action.search.SearchRequest;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.client.RequestOptions;
import org.elasticsearch.client.RestHighLevelClient;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.search.SearchHit;
import org.elasticsearch.search.builder.SearchSourceBuilder;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightBuilder;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightField;
import org.elasticsearch.search.sort.SortBuilders;
import org.elasticsearch.search.sort.SortOrder;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.TestTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Sort;

import java.io.IOException;
import java.sql.SQLOutput;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.*;

@SpringBootTest
public class TestEmpRespository {


    @Autowired
    private EmpRepository empRepository;  //1

    @Autowired
    private RestHighLevelClient restHighLevelClient;//2


    //保存|更新一条文档 id存在更新  id不存在 添加
    @Test
    public void testSave(){
        Emp s = new Emp();
        //s.setId("a1389021-2ecf-40cb-95e7-4c3be73604d9");
        s.setAge(2);
        s.setBir(new Date());
        s.setName("张君宝一代武侠");
        s.setAddress("武当山武侠学院毕业");
        s.setContent("武侠大师!");
        empRepository.save(s);

    }

    //根据id删除一条文档
    @Test
    public void testDelete(){
        empRepository.deleteById("a1389021-2ecf-40cb-95e7-4c3be73604d9");
    }


    //删除所有
    @Test
    public void testDeleteAll(){
        empRepository.deleteAll();
    }

    //检索一条记录
    @Test
    public void testFindOne(){
        Optional<Emp> optional = empRepository.findById("a1389021-2ecf-40cb-95e7-4c3be73604d9");
        System.out.println(optional.get());
    }

    //查询所有 排序
    @Test
    public void testFindAll(){
        Iterable<Emp> all = empRepository.findAll(Sort.by(Sort.Order.asc("age")));
        all.forEach(emp-> System.out.println(emp));
    }

    //分页
    @Test
    public void testFindPage(){
        //PageRequest.of 参数1: 当前页-1
        Page<Emp> search = empRepository.search(QueryBuilders.matchAllQuery(), PageRequest.of(1, 1));
        search.forEach(emp-> System.out.println(emp));
    }

    //基础查询 根据姓名  姓名和年龄  地址 等
    @Test
    public void testFindByName(){
        List<Emp> emps = empRepository.findByName("张君宝");
        List<Emp> emps1 = empRepository.findByAge(23);
        List<Emp> emps2 = empRepository.findByNameAndAddressAndAge("张君宝", "武当山",23);
        List<Emp> emps3 = empRepository.findByNameOrAge("张君宝", 2);
        List<Emp> emps4 = empRepository.findByAgeGreaterThanEqual(23);
        emps4.forEach(emp-> System.out.println(emp));
    }



    //复杂查询RestHighLevelClient  高亮
    @Test
    public void testSearchQuery() throws IOException, ParseException {
        List<Emp> emps = new ArrayList<Emp>();
        //创建搜索请求
        SearchRequest searchRequest = new SearchRequest("ems");

        //创建搜索对象
        SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
        searchSourceBuilder.query(QueryBuilders.termQuery("content","武侠"))//设置条件
                .sort("age", SortOrder.DESC)//排序
                .from(0) //起始条数(当前页-1)*size的值
                .size(20)//每页展示条数
                .highlighter(new HighlightBuilder().field("*").requireFieldMatch(false).preTags("<span style='color:red'>").postTags("</span>"));//设置高亮

        searchRequest.types("emp").source(searchSourceBuilder);

        //执行搜索
        SearchResponse searchResponse = restHighLevelClient.search(searchRequest, RequestOptions.DEFAULT);

        SearchHit[] hits = searchResponse.getHits().getHits();
        for (SearchHit hit : hits) {
            //原始文档
            Map<String, Object> sourceAsMap = hit.getSourceAsMap();
            Emp emp =  new Emp();
            emp.setId(hit.getId());
            emp.setAge(Integer.parseInt(sourceAsMap.get("age").toString()));
            emp.setBir(new SimpleDateFormat("yyyy-MM-dd").parse(sourceAsMap.get("bir").toString()));
            emp.setContent(sourceAsMap.get("content").toString());
            emp.setName(sourceAsMap.get("name").toString());
            emp.setAddress(sourceAsMap.get("address").toString());

            //高亮字段
            Map<String, HighlightField> highlightFields = hit.getHighlightFields();
            if(highlightFields.containsKey("content")){
                emp.setContent(highlightFields.get("content").fragments()[0].toString());
            }
            if(highlightFields.containsKey("name")){
                emp.setName(highlightFields.get("name").fragments()[0].toString());
            }
            if(highlightFields.containsKey("address")){
                emp.setAddress(highlightFields.get("address").fragments()[0].toString());
            }

            //放入集合
            emps.add(emp);
        }


        emps.forEach(emp-> System.out.println(emp));
    }





}

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

推荐阅读更多精彩内容