Elasticsearch

一、简介安装

官方网址:https://www.elastic.co/cn/
安装elasticsearch:下载并解压
安装kibana:下载并解压
下载ik分词器:https://github.com/medcl/elasticsearch-analysis-ik/releases/tag/v8.2.0

1、适配kibana中kibana.yml文件
elasticsearch.hosts: ["http://localhost:9200"]
2、解压ik分词器包放在elasticsearch的plugins的新建ik文件夹下
官方网址:https://www.elastic.co/cn/
3、操作网址:http://localhost:5601/app/dev_tools#/console

二、ik分词解析器

1、分词最少切分
POST /_analyze
{
    "text": "河马生鲜,阿里巴巴出品",
    "analyzer": "ik_smart"
}
2、分词最细切分
POST /_analyze
{
    "text": "河马生鲜,阿里巴巴出品",
    "analyzer": "ik_max_word"
}
3、扩展ik分词器

在IKAnalyzer.xml文件中设置扩展配置

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE properties SYSTEM "http://java.sun.com/dtd/properties.dtd">
<properties>
    <comment>IK Analyzer 扩展配置</comment>
    <!--用户可以在这里配置自己的扩展字典 -->
    <entry key="ext_dict">ext.dic</entry>
     <!--用户可以在这里配置自己的扩展停止词字典-->
    <entry key="ext_stopwords">stopword.dic</entry>
    <!--用户可以在这里配置远程扩展字典 -->
    <!-- <entry key="remote_ext_dict">words_location</entry> -->
    <!--用户可以在这里配置远程扩展停止词字典-->
    <!-- <entry key="remote_ext_stopwords">words_location</entry> -->
</properties>

在ext.dic、stopword.dic文件中添加词汇

三、操作索引库

1、mapping常见属性

type:数据类型(是否参与搜索)
index:是否索引
analyzer:分词器
properties:子字段

2、创建索引库(spring索引库名称)
PUT /spring
{
  "mappings": {
    "properties": {
      "info": {
        "analyzer": "ik_smart", 
        "type": "text"
      },
      "email":{
        "type": "keyword",
        "index": false
      },
      "name":{
        "type": "object",
        "properties": {
          "lastName": {
            "type": "keyword"
          }
        }
      }
    }
  }
}
3、查询索引库
GET /spring
4、删除索引库
DELETE /spring
5、修改索引库
PUT /spring/_mapping
{
  "properties": {
    "新字段名": {
      "type": "keyword"
      "index": false
    }
  }
}

四、操作文档

1、插入文档(spring索引库名称,1文档id)
POST /spring/_doc/1
{
  "info": "文本内容分词",
  "email": "123456@163.com",
  "name": {
    "lastName": "赵"
  }
}
2、查询文档
GET /spring/_doc/1
3、删除文档
DELETE /spring/_doc/1
4、全量修改
PUT /spring/_doc/1
{
  "info": "阿里巴巴出品,等于白嫖了,真的是奥利给",
  "email": "ALIBB@COM",
  "name": {
    "lastName": "赵"
  }
}
5、局部修改
POST /spring/_update/1
{
  "doc": {
    "email": "alibaba@ali.cn"
  }
}

五、代码操作ES(RestClient)

1、官方文档地址

https://www.elastic.co/guide/en/elasticsearch/client/index.html
https://www.elastic.co/guide/en/elasticsearch/client/java-rest/current/java-rest-high.html

2、mapping映射字符串
{
  "mappings": {
    "properties": {
      "id": {
        "type": "keyword"
      },
      "name": {
        "type": "text",
        "analyzer": "ik_max_word"
      },
      "address": {
        "type": "keyword",
        "index": false
      },
      "price": {
        "type": "integer"
      },
      "score": {
        "type": "integer"
      },
      "brand": {
        "type": "keyword",
        "copy_to": "name"
      },
      "city": {
        "type": "keyword"
      },
      "starName": {
        "type": "keyword"
      },
      "business": {
        "type": "keyword",
        "copy_to": "name"
      },
      "location": {
        "type": "geo_point"
      },
      "pic": {
        "type": "keyword",
        "index": false
      }
    }
  }
}
3、在pom文件中引入
<dependency>
    <groupId>org.elasticsearch.client</groupId>
    <artifactId>elasticsearch-rest-high-level-client</artifactId>
</dependency>
4、索引库操作
public class HotelIndexTest {
    
    private RestHighLevelClient restHighLevelClient;

    @Test
    void createHotelIndex() throws IOException {
        //创建Request对象
        CreateIndexRequest request = new CreateIndexRequest("hotel");
        //DSL语句
        request.source(MAPPING_TEMPLATE, XContentType.JSON);
        //发送请求
        restHighLevelClient.indices().create(request, RequestOptions.DEFAULT);
    }

    //删除索引库
    @Test
    void deleteHotelIndex() throws IOException {
        //创建Request对象
        DeleteIndexRequest request = new DeleteIndexRequest("hotel");
        //发送请求
        restHighLevelClient.indices().delete(request, RequestOptions.DEFAULT);
    }

    //索引库是否存在
    @Test
    void existsHotelIndex() throws IOException {
        //创建Request对象
        GetIndexRequest request = new GetIndexRequest("hotel");
        //发送请求
        boolean exists = restHighLevelClient.indices().exists(request, RequestOptions.DEFAULT);
    }

    @BeforeEach
    void setUp() {
        this.restHighLevelClient = new RestHighLevelClient(RestClient.builder(
                HttpHost.create("http://localhost:9200")));
    }

    @AfterEach
    void tearDown() throws IOException {
        restHighLevelClient.close();
    }
}

设置MAPPING_TEMPLATE常量

public class HotelConstants {
    public static final String MAPPING_TEMPLATE = "{\n" +
            "  \"mappings\": {\n" +
            "    \"properties\": {\n" +
            "      \"id\": {\n" +
            "        \"type\": \"keyword\"\n" +
            "      },\n" +
            "      \"name\": {\n" +
            "        \"type\": \"text\",\n" +
            "        \"analyzer\": \"ik_max_word\",\n" +
            "        \"copy_to\": \"all\"\n" +
            "      },\n" +
            "      \"address\": {\n" +
            "        \"type\": \"keyword\",\n" +
            "        \"index\": false\n" +
            "      },\n" +
            "      \"price\": {\n" +
            "        \"type\": \"integer\"\n" +
            "      },\n" +
            "      \"score\": {\n" +
            "        \"type\": \"integer\"\n" +
            "      },\n" +
            "      \"brand\": {\n" +
            "        \"type\": \"keyword\",\n" +
            "        \"copy_to\": \"all\"\n" +
            "      },\n" +
            "      \"city\": {\n" +
            "        \"type\": \"keyword\"\n" +
            "      },\n" +
            "      \"starName\": {\n" +
            "        \"type\": \"keyword\"\n" +
            "      },\n" +
            "      \"business\": {\n" +
            "        \"type\": \"keyword\",\n" +
            "        \"copy_to\": \"all\"\n" +
            "      },\n" +
            "      \"location\": {\n" +
            "        \"type\": \"geo_point\"\n" +
            "      },\n" +
            "      \"pic\": {\n" +
            "        \"type\": \"keyword\",\n" +
            "        \"index\": false\n" +
            "      },\n" +
            "      \"all\": {\n" +
            "        \"type\": \"text\",\n" +
            "        \"analyzer\": \"ik_max_word\"\n" +
            "      }\n" +
            "    }\n" +
            "  }\n" +
            "}";
}
5、文档操作
@SpringBootTest
public class HotelDocumentTest {

    @Autowired
    private IHotelService hotelService;

    private RestHighLevelClient client;

    //插入文档数据
    @Test
    public void testAddDocument() throws IOException {
        //根据ID查询酒店数据
        Hotel hotel = hotelService.getById(38665L);
        //转换为文档类型
        HotelDoc hotelDoc = new HotelDoc(hotel);
        //创建Request对象
        IndexRequest request = new IndexRequest("hotel").id(hotel.getId().toString());
        //准备json文档
        request.source(JSON.toJSONString(hotelDoc), XContentType.JSON);
        //发送请求
        client.index(request, RequestOptions.DEFAULT);
    }

    //获取文档数据
    @Test
    public void testGetDocumentById() throws IOException {
        //创建Request对象
        GetRequest request = new GetRequest("hotel", "61083");
        //发送请求
        GetResponse documentFields = client.get(request, RequestOptions.DEFAULT);
        String sourceAsString = documentFields.getSourceAsString();
        HotelDoc hotelDoc = JSON.parseObject(sourceAsString, HotelDoc.class);
    }

    //更新文档
    @Test
    public void testUpdateDocumentById() throws IOException {
        //创建Request对象
        UpdateRequest request = new UpdateRequest("hotel", "61083");
        request.doc("price",888);
        //发送请求
        client.update(request, RequestOptions.DEFAULT);
    }

    //删除文档
    @Test
    public void testDeleteDocumentById() throws IOException {
        //创建Request对象
        DeleteRequest request = new DeleteRequest("hotel", "61083");
        //发送请求
        client.delete(request, RequestOptions.DEFAULT);
    }

    //批量导入
    @Test
    public void testBulk() throws IOException {
        //根据ID查询酒店数据
        List<Hotel> list = hotelService.list();
        //创建Request对象
        BulkRequest request = new BulkRequest();
        for (Hotel hotel : list) {
            //转换为文档类型
            HotelDoc hotelDoc = new HotelDoc(hotel);
            request.add(new IndexRequest("hotel").id(hotel.getId().toString()).source(JSON.toJSONString(hotelDoc), XContentType.JSON));
        }
        //发送请求
        client.bulk(request, RequestOptions.DEFAULT);
    }

    @BeforeEach
    void setUp() {
        this.client = new RestHighLevelClient(RestClient.builder(
                HttpHost.create("http://localhost:9200")));
    }

    @AfterEach
    void tearDown() throws IOException {
        client.close();
    }
}

六、DSL查询语句

查询所有:查询出所有数据,一般测试用
match_all
全文检索:利用分词器对用户输入内容分词,然后去倒排索引库中匹配
match_query、multi_match_query
精准查询:根据精确词条值查询数据,一般查询keyword、数值、日期、boolean等
ids、range、term
地理查询:根据经纬度查询
geo_distance、geo_bounding_box
复合查询:复合查询可以将上述各种查询条件组合起来,合并查询条件
bool、function_score

1、查询所有
GET /hotel/_search
{
  "query": {
    "match_all": {
      
    }
  }
}

#全文查询
GET /hotel/_search
{
  "query": {
    "match": {
      "name": "地区"
    }
  }
}

GET /hotel/_search
{
  "query": {
    "multi_match": {
      "query": "外滩如家",
      "fields": ["brand","name","business"]
    }
  }
}
2、精确查询
#根据词条精确查询
GET /hotel/_search
{
  "query": {
    "term": {
      "city": {
        "value": "北京"
      }
    }
  }
}
3、根据值的范围查询
GET /hotel/_search
{
  "query": {
    "range": {
      "price": {
        "gte": 100,
        "lte": 300
      }
    }
  }
}
4、地理查询
GET /hotel/_search
{
  "query": {
    "geo_bounding_box": {
      "location": {
        "top_left": {
          "lat": 40.73,
          "lon": -74.1
        },
        "bottom_right": {
          "lat": 40.717,
          "lon": -73.99
        }
      }
    }
  }
}

GET /hotel/_search
{
  "query": {
    "geo_distance": {
      "distance": "3km",
      "location": {
        "lat": 31.21,
        "lon": 121.5
      }
    }
  }
}
5、复合查询
GET /hotel/_search
{
  "query": {
    "function_score": {
      "query": {
        "match": {
          "name": "上海外滩"
        }
      },
      "functions": [
        {
          "filter": {
            "term": {
              "brand": "如家"
            }
          },
          "weight": 10
        }
      ],
      "boost_mode": "multiply"
    }
  }
}
6、组合查询
#must:必须匹配每个子查询,类似“与”
#should:选择性匹配子查询,类似“或”
#must_not:必须不匹配,不参与算分,类似“非”
#filter:必须匹配,不参与算分
GET /hotel/_search
{
  "query": {
    "bool": {
      "must": [
        {
          "term": {
            "city": {
            "value": "上海"
            }
          }
        }
      ],
      "should": [
        {
          "term": {
            "brand": {
            "value": "皇冠假日"
            }
          }
        },
        {
          "term": {
            "brand": {
              "value": "华美达"
            }
          }
        }
      ],
      "must_not": [
        {
          "range": {
            "price": {
              "lte": 500
            }
          }
        }
      ],
      "filter": [
        {
          "range": {
            "score": {
              "gte": 45
            }
          }
        }
      ]
    }
  }
}
7、排序
GET /hotel/_search
{
  "query": {
    "match_all": {}
  },
  "sort": [
    {
      "score": {
        "order": "desc"
      }
    },
    {
      "price": {
        "order": "asc"
      }
    }
  ]
}

GET /hotel/_search
{
  "query": {
    "match_all": {}
  },
  "sort": [
    {
      "_geo_distance": {
        "location": {
          "lat": 31.034661,
          "lon": 121.612282
        },
        "order": "asc",
        "unit": "km"
      }
    }
  ]
}

八、代码实现查询语句

public class HotelSearchTest {

    private RestHighLevelClient client;

    //查询所有
    @Test
    void searchMatchAll() throws IOException {
        MatchAllQueryBuilder query = QueryBuilders.matchAllQuery();
        search(query);
    }

    //全文查询
    @Test
    void searchMatch() throws IOException {
        MatchQueryBuilder query = QueryBuilders.matchQuery("name", "上海外滩");
        search(query);
    }

    @Test
    void searchMultiMatch() throws IOException {
        MultiMatchQueryBuilder query = QueryBuilders.multiMatchQuery("北京", "name", "brand");
        search(query);
    }

    //精准查询
    @Test
    void searchTerm() throws IOException {
        TermQueryBuilder query = QueryBuilders.termQuery("city", "深圳");
        search(query);
    }

    @Test
    void searchRange() throws IOException {
        RangeQueryBuilder query = QueryBuilders.rangeQuery("price").gte(100).lte(500);
        search(query);
    }

    /**
     * must:必须匹配每个子查询,类似“与”
     * should:选择性匹配子查询,类似“或”
     * must_not:必须不匹配,不参与算分,类似“非”
     * filter:必须匹配,不参与算分
     */
    //组合查询
    @Test
    void searchBool() throws IOException {
        BoolQueryBuilder boolQuery = QueryBuilders.boolQuery();
        boolQuery.must(QueryBuilders.termQuery("city", "上海"));
        boolQuery.filter(QueryBuilders.rangeQuery("price").lte(500));
        search(boolQuery);
    }

    //查询语句抽取
    public void search(QueryBuilder queryBuilder) throws IOException {
        //准备Request
        SearchRequest request = new SearchRequest("hotel");
        //准备DSL
        request.source().query(queryBuilder);
        //发送请求
        SearchResponse response = client.search(request, RequestOptions.DEFAULT);
        //解析响应
        handleResponse(response);
    }

    //分页查询
    @Test
    void searchPageAndSort() throws IOException {
        int page = 1;
        //准备Request
        SearchRequest request = new SearchRequest("hotel");
        //准备DSL
        request.source().query(QueryBuilders.matchAllQuery());
        request.source().sort("price", SortOrder.ASC);
        request.source().from((page - 1) * 20).size(20);
        //发送请求
        SearchResponse response = client.search(request, RequestOptions.DEFAULT);
        //解析响应
        handleResponse(response);
    }

    //高亮查询
    @Test
    void searchHighlight() throws IOException {
        //准备Request
        SearchRequest request = new SearchRequest("hotel");
        //准备DSL
        request.source().query(QueryBuilders.matchQuery("name", "如家"));
        request.source().highlighter(new HighlightBuilder().field("name").requireFieldMatch(false));
        //发送请求
        SearchResponse response = client.search(request, RequestOptions.DEFAULT);
        //解析响应
        handleResponse(response);
    }

    //聚合查询
    @Test
    void searchAggregation() throws IOException {
        //准备Request
        SearchRequest request = new SearchRequest("hotel");
        //准备DSL
        request.source().size(0);
        request.source().aggregation(AggregationBuilders.terms("brandAgg").field("brand").size(20));
        //发送请求
        SearchResponse response = client.search(request, RequestOptions.DEFAULT);
        //解析响应
        System.out.println(response);
        //解析聚合结果
        Aggregations aggregations = response.getAggregations();
        //根据名称获取聚合结果
        Terms brandAgg = aggregations.get("brandAgg");
        //获取桶
        List<? extends Terms.Bucket> buckets = brandAgg.getBuckets();
        //遍历
        for (Terms.Bucket bucket :buckets){
            String brandName = bucket.getKeyAsString();
            long docCount = bucket.getDocCount();
            System.out.println(brandName + docCount);
        }
    }

    //解析json抽取
    public void handleResponse(SearchResponse response) {
        SearchHits searchHits = response.getHits();
        long total = searchHits.getTotalHits().value;
        System.out.println("共搜索到" + total + "条");
        SearchHit[] hits = searchHits.getHits();
        for (SearchHit hit : hits) {
            String json = hit.getSourceAsString();
            HotelDoc hotelDoc = JSON.parseObject(json, HotelDoc.class);
            //获取高亮结果
            Map<String, HighlightField> highlightFields = hit.getHighlightFields();
            if (!CollectionUtils.isEmpty(highlightFields)) {
                HighlightField highlightField = highlightFields.get("name");
                if (highlightField != null) {
                    String name = highlightField.getFragments()[0].string();
                    hotelDoc.setName(name);
                }
            }
            System.out.println(hotelDoc);
        }
    }

    @BeforeEach
    void setUp() {
        this.client = new RestHighLevelClient(RestClient.builder(
                HttpHost.create("http://localhost:9200")));
    }

    @AfterEach
    void tearDown() throws IOException {
        client.close();
    }
}

九、实例练习

@Service
public class HotelService extends ServiceImpl<HotelMapper, Hotel> implements IHotelService {

    @Autowired
    private RestHighLevelClient client;

    @Override
    public PageResult search(RequestParams params) throws IOException {
        //准备Request
        SearchRequest request = new SearchRequest("hotel");
        //Query查询
        buildBasicQuery(request, params);
        //分页
        if (params.getPage() != null && params.getSize() != null)
            request.source().from(0).size(5);
        else
            request.source().from((params.getPage() - 1) * params.getSize()).size(params.getSize());
        //距离排序
        if (params.getLocation() != null && !params.getLocation().equals(""))
            request.source().sort(SortBuilders.
                    geoDistanceSort("location", new GeoPoint(params.getLocation())).
                    order(SortOrder.ASC).
                    unit(DistanceUnit.KILOMETERS));
        //发送请求
        SearchResponse response = client.search(request, RequestOptions.DEFAULT);
        return handleResponse(response);
    }

    private void buildBasicQuery(SearchRequest request, RequestParams params) {
        //1、组合查询
        BoolQueryBuilder queryBuilder = new BoolQueryBuilder();
        //输入框内容
        if (params.getKey() == null || params.getKey().equals(""))
            queryBuilder.must(QueryBuilders.matchAllQuery());
        else
            queryBuilder.must(QueryBuilders.matchQuery("name", params.getKey()));
        //地区
        if (params.getCity() != null && !params.getCity().equals(""))
            queryBuilder.filter(QueryBuilders.termQuery("city", params.getCity()));
        //品牌
        if (params.getBrand() != null && !params.getBrand().equals(""))
            queryBuilder.filter(QueryBuilders.termQuery("brand", params.getBrand()));
        //星级
        if (params.getStarName() != null && !params.getStarName().equals(""))
            queryBuilder.filter(QueryBuilders.termQuery("starName", params.getStarName()));
        //价格查询
        if (params.getMinPrice() != null && params.getMaxPrice() != null)
            queryBuilder.filter(QueryBuilders.rangeQuery("price").gte(params.getMinPrice()).lte(params.getMaxPrice()));

        //2、算分控制
        FunctionScoreQueryBuilder functionScoreQuery = QueryBuilders.functionScoreQuery(
                //原始查询
                queryBuilder,
                //function数组
                new FunctionScoreQueryBuilder.FilterFunctionBuilder[]{
                        //其中一个function元素
                        new FunctionScoreQueryBuilder.FilterFunctionBuilder(
                                //过滤条件
                                QueryBuilders.termQuery("isAD", true),
                                //算分函数
                                ScoreFunctionBuilders.weightFactorFunction(10)
                        )
                });
        //将条件查询封装
        request.source().query(functionScoreQuery);
    }

    //解析json抽取
    public PageResult handleResponse(SearchResponse response) {
        PageResult pageResult = new PageResult();
        //解析响应
        SearchHits searchHits = response.getHits();
        //获取总条数
        long total = searchHits.getTotalHits().value;
        pageResult.setTotal(total);
        //获取数据集合
        SearchHit[] hits = searchHits.getHits();
        List<HotelDoc> list = new ArrayList<>();
        for (SearchHit hit : hits) {
            //获取单个数据
            String json = hit.getSourceAsString();
            //反序列化
            HotelDoc hotelDoc = JSON.parseObject(json, HotelDoc.class);
            //获取排序值,距离
            Object[] sortValues = hit.getSortValues();
            if (sortValues != null && sortValues.length > 0) {
                Object value = sortValues[0];
                hotelDoc.setDistance(value);
            }
            //获取高亮结果
            Map<String, HighlightField> highlightFields = hit.getHighlightFields();
            if (!CollectionUtils.isEmpty(highlightFields)) {
                HighlightField highlightField = highlightFields.get("name");
                if (highlightField != null) {
                    String name = highlightField.getFragments()[0].string();
                    hotelDoc.setName(name);
                }
            }
            list.add(hotelDoc);
        }
        pageResult.setHotels(list);
        return pageResult;
    }

    /**
     * 聚合结果
     */
    @Override
    public Map<String, List<String>> filters(RequestParams params) throws IOException {
        Map<String, List<String>> map = new HashMap<>();
        //准备Request
        SearchRequest request = new SearchRequest("hotel");
        //准备DSL
        //Query查询
        buildBasicQuery(request, params);
        //聚合查询
        buildAggregation(request);
        //发送请求
        SearchResponse response = client.search(request, RequestOptions.DEFAULT);
        //解析响应
        System.out.println(response);
        //解析聚合结果
        Aggregations aggregations = response.getAggregations();
        //根据名称获取聚合结果
        List<String> cityList = getAggByName(aggregations, "cityAgg");
        List<String> brandList = getAggByName(aggregations, "brandAgg");
        List<String> starList = getAggByName(aggregations, "starAgg");
        map.put("城市", cityList);
        map.put("品牌", brandList);
        map.put("星级", starList);
        return map;
    }

    private void buildAggregation(SearchRequest request) {
        request.source().size(0);
        request.source().aggregation(AggregationBuilders.terms("brandAgg").field("brand").size(100));
        request.source().aggregation(AggregationBuilders.terms("cityAgg").field("city").size(100));
        request.source().aggregation(AggregationBuilders.terms("starAgg").field("starName").size(100));
    }

    public List<String> getAggByName(Aggregations aggregations, String agg) {
        //聚合集合
        List<String> list = new ArrayList<>();
        //根据聚合名称获取聚合数据 m
        Terms terms = aggregations.get(agg);
        //获取聚合数据结果集
        List<? extends Terms.Bucket> buckets = terms.getBuckets();
        //遍历
        for (Terms.Bucket bucket : buckets) {
            String brandName = bucket.getKeyAsString();
            list.add(brandName);
        }
        return list;
    }
} 
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,332评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,508评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,812评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,607评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,728评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,919评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,071评论 3 410
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,802评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,256评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,576评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,712评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,389评论 4 332
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,032评论 3 316
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,798评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,026评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,473评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,606评论 2 350

推荐阅读更多精彩内容