范文健康探索娱乐情感热点
投稿投诉
热点动态
科技财经
情感日志
励志美文
娱乐时尚
游戏搞笑
探索旅游
历史星座
健康养生
美丽育儿
范文作文
教案论文

MyBatisPlus同款ElasticsearchORM框架,用起来够优雅

  使用过Spring Data操作ES的小伙伴应该有所了解,它只能实现一些非常基本的数据管理工作,一旦遇到稍微复杂点的查询,基本都要依赖ES官方提供的RestHighLevelClient,Spring Data只是在其基础上进行了简单的封装。最近发现一款更优雅的ES ORM框架Easy-Es,使用它能像MyBatis-Plus一样操作ES,今天就以mall项目中的商品搜索功能为例,来聊聊它的使用!Easy-Es简介
  Easy-Es(简称EE)是一款基于Elasticsearch(简称ES)官方提供的RestHighLevelClient打造的ORM开发框架,在RestHighLevelClient的基础上,只做增强不做改变,为简化开发、提高效率而生。EE和Mybatis-Plus(简称MP)的用法非常相似,如果你之前使用过MP的话,应该能很快上手EE。EE的理念是:把简单、易用、方便留给用户,把复杂留给框架。
  EE的主要特性如下:全自动索引托管:开发者无需关心索引的创建、更新及数据迁移等繁琐步骤,框架能自动完成。屏蔽语言差异:开发者只需要会MySQL的语法即可使用ES。代码量极少:与直接使用官方提供的RestHighLevelClient相比,相同的查询平均可以节省3-5倍的代码量。零魔法值:字段名称直接从实体中获取,无需手写。零额外学习成本: 开发者只要会国内最受欢迎的Mybatis-Plus用法,即可无缝迁移至EE。MySQL与Easy-Es语法对比
  首先我们来对MySQL、Easy-Es和RestHighLevelClient的语法做过对比,来快速学习下Easy-Es的语法。
  MySQL
  Easy-Es
  es-DSL/es java api
  and
  and
  must
  or
  or
  should
  =
  eq
  term
  !=
  ne
  boolQueryBuilder.mustNot(queryBuilder)
  gt
  QueryBuilders.rangeQuery("es field").gt()
  >=
  ge
  .rangeQuery("es field").gte()
  <
  lt
  .rangeQuery("es field").lt()
  <=
  le
  .rangeQuery("es field").lte()
  like "%field%"
  like
  QueryBuilders.wildcardQuery(field,value)
  not like "%field%"
  notLike
  must not wildcardQuery(field,value)
  like "%field"
  likeLeft
  QueryBuilders.wildcardQuery(field,*value)
  like "field%"
  likeRight
  QueryBuilders.wildcardQuery(field,value*)
  between
  between
  QueryBuilders.rangeQuery("es field").from(xx).to(xx)
  notBetween
  notBetween
  must not QueryBuilders.rangeQuery("es field").from(xx).to(xx)
  is null
  isNull
  must not QueryBuilders.existsQuery(field)
  is notNull
  isNotNull
  QueryBuilders.existsQuery(field)
  in
  in
  QueryBuilders.termsQuery(" xx es field", xx)
  not in
  notIn
  must not QueryBuilders.termsQuery(" xx es field", xx)
  group by
  groupBy
  AggregationBuilders.terms()
  order by
  orderBy
  fieldSortBuilder.order(ASC/DESC)
  min
  min
  AggregationBuilders.min
  max
  max
  AggregationBuilders.max
  avg
  avg
  AggregationBuilders.avg
  sum
  sum
  AggregationBuilders.sum
  order by xxx asc
  orderByAsc
  fieldSortBuilder.order(SortOrder.ASC)
  order by xxx desc
  orderByDesc
  fieldSortBuilder.order(SortOrder.DESC)
  -
  match
  matchQuery
  -
  matchPhrase
  QueryBuilders.matchPhraseQuery
  -
  matchPrefix
  QueryBuilders.matchPhrasePrefixQuery
  -
  queryStringQuery
  QueryBuilders.queryStringQuery
  select *
  matchAllQuery
  QueryBuilders.matchAllQuery()
  -
  highLight
  HighlightBuilder.Field
  ...
  ...
  ...集成及配置
  接下来把Easy-Es集成到项目中配置下就可以使用了。首先需要在pom.xml中添加Easy-Es的相关依赖;     cn.easy-es     easy-es-boot-starter     1.0.2 
  由于底层使用了ES官方提供的RestHighLevelClient,这里ES的相关依赖版本需要统一下,这里使用的ES客户端版本为 7.14.0  ,ES版本为 7.17.3  ;                            org.elasticsearch.client             elasticsearch-rest-high-level-client             7.14.0                               org.elasticsearch             elasticsearch             7.14.0               
  再修改配置文件 application.yml  对Easy-Es进行配置。 easy-es:   # 是否开启EE自动配置   enable: true   # ES连接地址+端口   address: localhost:9200   # 关闭自带banner   banner: false
  添加Easy-Es的Java配置,使用 @EsMapperScan  配置好Easy-Es的Mapper接口和文档对象路径,如果你使用了MyBatis-Plus的话,需要和它的扫描路径区分开来。 /**  * EasyEs配置类  * Created by macro on 2022/9/16.  */ @Configuration @EsMapperScan("com.macro.mall.tiny.easyes") public class EasyEsConfig { }使用
  Easy-Es集成和配置完成后,就可以开始使用了。这里还是以mall项目的商品搜索功能为例,聊聊Easy-Es的使用,Spring Data的实现方式可以参考Elasticsearch项目实战,商品搜索功能设计与实现! 。注解的使用
  下面我们来学习下Easy-Es中注解的使用。首先我们需要创建文档对象EsProduct,然后给类和字段添加上Easy-Es的注解;/**  * 搜索商品的信息  * Created by macro on 2018/6/19.  */ @Data @EqualsAndHashCode @IndexName(value = "pms", shardsNum = 1, replicasNum = 0) public class EsProduct implements Serializable {     private static final long serialVersionUID = -1L;     @IndexId(type = IdType.CUSTOMIZE)     private Long id;     @IndexField(fieldType = FieldType.KEYWORD)     private String productSn;     private Long brandId;     @IndexField(fieldType = FieldType.KEYWORD)     private String brandName;     private Long productCategoryId;     @IndexField(fieldType = FieldType.KEYWORD)     private String productCategoryName;     private String pic;     @IndexField(fieldType = FieldType.TEXT, analyzer = "ik_max_word")     private String name;     @IndexField(fieldType = FieldType.TEXT, analyzer = "ik_max_word")     private String subTitle;     @IndexField(fieldType = FieldType.TEXT, analyzer = "ik_max_word")     private String keywords;     private BigDecimal price;     private Integer sale;     private Integer newStatus;     private Integer recommandStatus;     private Integer stock;     private Integer promotionType;     private Integer sort;     @IndexField(fieldType = FieldType.NESTED, nestedClass = EsProductAttributeValue.class)     private List attrValueList;     @Score     private Float score; }EsProduct中的注解具体说明如下:
  注解名称
  用途
  参数
  @IndexName
  索引名注解
  value:指定索引名;shardsNum:分片数;replicasNum:副本数
  @IndexId
  ES主键注解
  type:指定注解类型,CUSTOMIZE表示自定义
  @IndexField
  ES字段注解
  fieldType:字段在索引中的类型;analyzer:索引文档时用的分词器;nestedClass:嵌套类
  @Score
  得分注解
  decimalPlaces:得分保留小数位,实体类中被作为ES查询得分返回的字段使用EsProduct中嵌套类型EsProductAttributeValue的代码如下。/**  * 搜索商品的属性信息  * Created by macro on 2018/6/27.  */ @Data @EqualsAndHashCode public class EsProductAttributeValue implements Serializable {     private static final long serialVersionUID = 1L;     @IndexField(fieldType = FieldType.LONG)     private Long id;     @IndexField(fieldType = FieldType.KEYWORD)     private Long productAttributeId;     //属性值     @IndexField(fieldType = FieldType.KEYWORD)     private String value;     //属性参数:0->规格;1->参数     @IndexField(fieldType = FieldType.INTEGER)     private Integer type;     //属性名称     @IndexField(fieldType=FieldType.KEYWORD)     private String name; }商品信息维护
  下面我们来实现几个简单的商品信息维护接口,包括商品信息的导入、创建和删除。  首先我们需要定义一个Mapper,继承BaseEsMapper; /**  * 商品ES操作类  * Created by macro on 2018/6/19.  */ public interface EsProductMapper extends BaseEsMapper {  }
  然后在Service实现类中直接使用EsProductMapper内置方法实现即可,是不是和MyBatis-Plus的用法一致? /**  * 搜索商品管理Service实现类  * Created by macro on 2018/6/19.  */ @Service public class EsProductServiceImpl implements EsProductService {     @Autowired     private EsProductDao productDao;     @Autowired     private EsProductMapper esProductMapper;     @Override     public int importAll() {         List esProductList = productDao.getAllEsProductList(null);         return esProductMapper.insertBatch(esProductList);     }      @Override     public void delete(Long id) {         esProductMapper.deleteById(id);     }      @Override     public EsProduct create(Long id) {         EsProduct result = null;         List esProductList = productDao.getAllEsProductList(id);         if (esProductList.size() > 0) {             result = esProductList.get(0);             esProductMapper.insert(result);         }         return result;     }      @Override     public void delete(List ids) {         if (!CollectionUtils.isEmpty(ids)) {             esProductMapper.deleteBatchIds(ids);         }     } }简单商品搜索
  下面我们来实现一个最简单的商品搜索,分页搜索商品名称、副标题、关键词中包含指定关键字的商品。  通过QueryWrapper来构造查询条件,然后使用Mapper中的方法来进行查询,使用过MyBatis-Plus的小伙伴应该很熟悉了; /**  * 搜索商品管理Service实现类  * Created by macro on 2018/6/19.  */ @Service public class EsProductServiceImpl implements EsProductService {     @Autowired     private EsProductMapper esProductMapper;     @Override     public PageInfo search(String keyword, Integer pageNum, Integer pageSize) {         LambdaEsQueryWrapper wrapper = new LambdaEsQueryWrapper<>();         if(StrUtil.isEmpty(keyword)){             wrapper.matchAllQuery();         }else{             wrapper.multiMatchQuery(keyword,EsProduct::getName,EsProduct::getSubTitle,EsProduct::getKeywords);         }         return esProductMapper.pageQuery(wrapper, pageNum, pageSize);     } }
  使用Swagger访问接口后,可以在控制台输出查看生成的DSL语句
  把DSL语句直接复制Kibana中即可执行查看结果了,这和我们手写DSL语句没什么两样的。
  综合商品搜索
  下面我们来实现一个复杂的商品搜索,涉及到过滤、不同字段匹配权重不同以及可以进行排序。首先来说需求,按输入的关键字搜索商品名称(权重10)、副标题(权重5)和关键词(权重2),可以按品牌和分类进行筛选,可以有5种排序方式,默认按相关度进行排序,看下接口文档有助于理解;
  这个功能之前使用Spring Data来实现非常复杂,使用Easy-Es来实现确实简洁不少,下面是使用Easy-Es的实现方式;/**  * 搜索商品管理Service实现类  * Created by macro on 2018/6/19.  */ @Service public class EsProductServiceImpl implements EsProductService {     @Autowired     private EsProductMapper esProductMapper;     @Override     public PageInfo search(String keyword, Long brandId, Long productCategoryId, Integer pageNum, Integer pageSize,Integer sort) {         LambdaEsQueryWrapper wrapper = new LambdaEsQueryWrapper<>();         //过滤         if (brandId != null || productCategoryId != null) {             if (brandId != null) {                 wrapper.eq(EsProduct::getBrandId,brandId);             }             if (productCategoryId != null) {                 wrapper.eq(EsProduct::getProductCategoryId,productCategoryId).enableMust2Filter(true);             }         }         //搜索         if (StrUtil.isEmpty(keyword)) {             wrapper.matchAllQuery();         } else {             wrapper.and(i -> i.match(EsProduct::getName, keyword, 10f)                     .or().match(EsProduct::getSubTitle, keyword, 5f)                     .or().match(EsProduct::getKeywords, keyword, 2f));         }         //排序         if(sort==1){             //按新品从新到旧             wrapper.orderByDesc(EsProduct::getId);         }else if(sort==2){             //按销量从高到低             wrapper.orderByDesc(EsProduct::getSale);         }else if(sort==3){             //按价格从低到高             wrapper.orderByAsc(EsProduct::getPrice);         }else if(sort==4){             //按价格从高到低             wrapper.orderByDesc(EsProduct::getPrice);         }else{             //按相关度             wrapper.sortByScore(SortOrder.DESC);         }         return esProductMapper.pageQuery(wrapper, pageNum, pageSize);     } }再对比下之前使用Spring Data的实现方式,没有QueryWrapper来构造条件,还要硬编码字段名称,确实优雅了不少!
  相关商品推荐
  当我们查看相关商品的时候,一般底部会有一些商品推荐,这里简单来实现下。首先来说下需求,可以根据指定商品的ID来查找相关商品,看下接口文档有助于理解;
  这里我们的实现原理是这样的:首先根据ID获取指定商品信息,然后以指定商品的名称、品牌和分类来搜索商品,并且要过滤掉当前商品,调整搜索条件中的权重以获取最好的匹配度;使用Easy-Es来实现依旧是那么简洁!/**  * 搜索商品管理Service实现类  * Created by macro on 2018/6/19.  */ @Service public class EsProductServiceImpl implements EsProductService {     @Autowired     private EsProductMapper esProductMapper;     @Override     public PageInfo recommend(Long id, Integer pageNum, Integer pageSize) {         LambdaEsQueryWrapper wrapper = new LambdaEsQueryWrapper<>();         List esProductList = productDao.getAllEsProductList(id);         if (esProductList.size() > 0) {             EsProduct esProduct = esProductList.get(0);             String keyword = esProduct.getName();             Long brandId = esProduct.getBrandId();             Long productCategoryId = esProduct.getProductCategoryId();             //用于过滤掉相同的商品             wrapper.ne(EsProduct::getId,id);             //根据商品标题、品牌、分类进行搜索             wrapper.and(i -> i.match(EsProduct::getName, keyword, 8f)                     .or().match(EsProduct::getSubTitle, keyword, 2f)                     .or().match(EsProduct::getKeywords, keyword, 2f)                     .or().match(EsProduct::getBrandId, brandId, 5f)                     .or().match(EsProduct::getProductCategoryId, productCategoryId, 3f));             return esProductMapper.pageQuery(wrapper, pageNum, pageSize);         }         return esProductMapper.pageQuery(wrapper, pageNum, pageSize);     } }聚合搜索商品相关信息
  在搜索商品时,经常会有一个筛选界面来帮助我们找到想要的商品,这里我们来简单实现下。首先来说下需求,可以根据搜索关键字获取到与关键字匹配商品相关的分类、品牌以及属性,下面这张图有助于理解;
  这里我们可以使用ES的聚合来实现,搜索出相关商品,聚合出商品的品牌、商品的分类以及商品的属性,只要出现次数最多的前十个即可;由于Easy-Es目前只用groupBy实现了简单的聚合,对于我们这种有嵌套对象的聚合无法支持,所以需要使用RestHighLevelClient来实现,如果你对照之前的Spring Data实现方式的话,可以发现用法差不多,看样子Spring Data只是做了简单的封装而已。/**  * 搜索商品管理Service实现类  * Created by macro on 2018/6/19.  */ @Service public class EsProductServiceImpl implements EsProductService {     @Autowired     private EsProductMapper esProductMapper;     @Override     public EsProductRelatedInfo searchRelatedInfo(String keyword) {         SearchRequest searchRequest = new SearchRequest();         searchRequest.indices("pms_*");         SearchSourceBuilder builder = new SearchSourceBuilder();         //搜索条件         if (StrUtil.isEmpty(keyword)) {             builder.query(QueryBuilders.matchAllQuery());         } else {             builder.query(QueryBuilders.multiMatchQuery(keyword, "name", "subTitle", "keywords"));         }         //聚合搜索品牌名称         builder.aggregation(AggregationBuilders.terms("brandNames").field("brandName"));         //集合搜索分类名称         builder.aggregation(AggregationBuilders.terms("productCategoryNames").field("productCategoryName"));         //聚合搜索商品属性,去除type=1的属性         AbstractAggregationBuilder aggregationBuilder = AggregationBuilders.nested("allAttrValues", "attrValueList")                 .subAggregation(AggregationBuilders.filter("productAttrs", QueryBuilders.termQuery("attrValueList.type", 1))                         .subAggregation(AggregationBuilders.terms("attrIds")                                 .field("attrValueList.productAttributeId")                                 .subAggregation(AggregationBuilders.terms("attrValues")                                         .field("attrValueList.value"))                                 .subAggregation(AggregationBuilders.terms("attrNames")                                         .field("attrValueList.name"))));         builder.aggregation(aggregationBuilder);         searchRequest.source(builder);         try {             SearchResponse searchResponse = esProductMapper.search(searchRequest, RequestOptions.DEFAULT);             return convertProductRelatedInfo(searchResponse);         } catch (IOException e) {             e.printStackTrace();         }         return null;     }      /**      * 将返回结果转换为对象      */     private EsProductRelatedInfo convertProductRelatedInfo(SearchResponse response) {         EsProductRelatedInfo productRelatedInfo = new EsProductRelatedInfo();         Map aggregationMap = response.getAggregations().asMap();         //设置品牌         Aggregation brandNames = aggregationMap.get("brandNames");         List brandNameList = new ArrayList<>();         for(int i = 0; i<((Terms) brandNames).getBuckets().size(); i++){             brandNameList.add(((Terms) brandNames).getBuckets().get(i).getKeyAsString());         }         productRelatedInfo.setBrandNames(brandNameList);         //设置分类         Aggregation productCategoryNames = aggregationMap.get("productCategoryNames");         List productCategoryNameList = new ArrayList<>();         for(int i=0;i<((Terms) productCategoryNames).getBuckets().size();i++){             productCategoryNameList.add(((Terms) productCategoryNames).getBuckets().get(i).getKeyAsString());         }         productRelatedInfo.setProductCategoryNames(productCategoryNameList);         //设置参数         Aggregation productAttrs = aggregationMap.get("allAttrValues");         List<? extends Terms.Bucket> attrIds = ((ParsedStringTerms) ((ParsedFilter) ((ParsedNested) productAttrs).getAggregations().get("productAttrs")).getAggregations().get("attrIds")).getBuckets();         List attrList = new ArrayList<>();         for (Terms.Bucket attrId : attrIds) {             EsProductRelatedInfo.ProductAttr attr = new EsProductRelatedInfo.ProductAttr();             attr.setAttrId(Long.parseLong((String) attrId.getKey()));             List attrValueList = new ArrayList<>();             List<? extends Terms.Bucket> attrValues = ((ParsedStringTerms) attrId.getAggregations().get("attrValues")).getBuckets();             List<? extends Terms.Bucket> attrNames = ((ParsedStringTerms) attrId.getAggregations().get("attrNames")).getBuckets();             for (Terms.Bucket attrValue : attrValues) {                 attrValueList.add(attrValue.getKeyAsString());             }             attr.setAttrValues(attrValueList);             if(!CollectionUtils.isEmpty(attrNames)){                 String attrName = attrNames.get(0).getKeyAsString();                 attr.setAttrName(attrName);             }             attrList.add(attr);         }         productRelatedInfo.setProductAttrs(attrList);         return productRelatedInfo;     } }总结
  今天将之前的使用Spring Data的商品搜索案例使用Easy-Es改写了一下,确实使用Easy-Es更简单,但是对于复杂的聚合搜索功能,两者都需要使用原生的RestHighLevelClient用法来实现。使用Easy-Es来操作ES确实足够优雅,它类似MyBatis-Plus的用法能大大降低我们的学习成本,快速完成开发工作!

关于生不生二胎的讨论和观点今天在这里和大家聊一聊二胎的问题本人也是90后,现在也是两个孩子的爸爸,平时朋友们在一起讨论的很多话题就是围绕二胎开始现在大部分人的观念比较开放,大部分是没有重男轻女的思想,不排除成语的错写与误读成语是在我国几千年历史进程中逐渐积累起来的,近万条成语,不仅大大丰富了汉语的语汇,也是我们民族文化的宝贵遗产。举凡历史故事文史典故人生经验哲理智慧以及生活常识,在成语中无所不包。因19岁女儿记录妈妈给她和15岁弟弟分零食,网友直呼真一碗水端平题记是一碗水端平还是供养巨婴宝宝?作者南海看云家有多个孩子的,很多父母为了公平起见,总想用各种各样的方式,在孩子面前进行公平对待,这也引发有些人的评论。有些家庭,孩子已经很大了,还宝贝变阳怎样对症治疗?自从全面解封放开后,感觉一下子没有了保护,谁也不知道身边的人有没有变阳,大家心里也越发的担忧。大人还好,抵抗力强一点,最担心的就是孩子。到底孩子阳了怎样应对呢?近期有不少儿童新冠阳一直怀不上怎么办?抓住4个机会,造人指日可待!很多人为了能够拥有自己的孩子做了很多功课,但却迟迟不见肚子有好消息,有些人怀不上可能是疾病的原因,这个就需要请专业的医生进行治疗后才能好孕了。还有一部分人是没有做好下面几件事,这样(真实事件改编)年轻宝妈站在楼顶我终于还是熬不下去了导语本漫画由真实事件改编分割线分割线我们一直都在说,孩子是两个人的,可只有实践到生活中才会发现,很多时候,真正面对一切的,其实是女人。女人在月子的时候是最脆弱的,期间任何一点情绪都隔代教育和望子成龙式教育毁掉了一颗北大苗子头条创作挑战赛(上篇)儿子从小聪明伶俐,人见人爱,一岁半时就能从1数到50,他爷爷更是把他当宝贝看。每天都要抱出去到外面去转一圈,听别人夸他孙子又聪明又可爱时,他就笑的合不拢嘴。从富二代和富一代戴表有什么区别?超级富二代一般都有两个标配,一个是超级跑车,成为Hypercar,另一个是超级腕表,也被成为Hyperwatch。超级跑车大家都比较熟悉了,可以说见怪不怪了。而超级腕表近年才初露端买羽绒服,选择国货还是外国品牌?听听过来人的经验,别被坑了羽绒服作为冬季御寒的最重要服装,主要的作用就是保暖,因此在购买羽绒服时,品质是第一位,其次才是款式。不同品牌的羽绒服,其品质和款式也不一样。相信大家在购买羽绒服时都有一个疑问到底是佟丽娅电影节登台献唱,一袭彩虹色亮片长裙,美成焦点!头条创作挑战赛第四届海南岛国际电影节闭幕式暨颁奖典礼上,佟丽娅登台献唱歌曲早十年的童话,不仅歌声悠扬,穿搭造型也十分养眼,看她的表演,简直就是一场视觉与听觉完美结合的盛宴,不得不说时尚圈再闹出丑闻,顶级时尚杂志弄虚作假制造身材焦虑最近时尚圈顶级杂志W杂志卷入了一桩杂志圈的丑闻,来到了漩涡的中央。事情的起源是在好莱坞女星DemiMoore六十岁生日当天,被誉为电影行业最高阶的老牌时尚杂志W杂志通过社交媒体账号
不快乐不止是晚上很晚睡,比起心动我更喜欢心安,你喜欢什么歌?有几首是晚上最喜欢听的音乐,陪你入眠,想到某人??下辈子第一个遇见你你过的好吗我要你至少还有你三生三幸心要让你听见贼拉拉的爱你相见恨晚缺氧别来无恙不该用情慢慢自愈一生所爱空心一不小踩影子的人静,一个自名安静的女孩。是怎样的一个午日,宁静,静如止水,或许。。我的世界太过于安静,静得竟然能够听到自己心跳的声音。午日阳光明媚在世界末日的一个轮回后,血液再次缓缓地从心室流回心不完美正是一种完美!有遗憾的人生才是真正的人生世界本就不完美,人生本就应该不足。对每个人来说,不完美是客观存在的,没有必要抱怨。完美主义者表面上是傲慢的,但内心是自卑的,因为他们很少看到优点,总是注意缺点。如果你总是不满足,很随笔,随念,随安世上存在着不能流泪的悲哀,这种悲哀无法向人解释,即使解释人家也不会理解,它永远一成不变,如无风夜晚的雪花静静沉积在心底。村上春树世界尽头与冷酷仙境但凡有酒,故事就像古老的播皮机吱呀冷暴力的危害到底有多大?冷暴力的危害到底有多大?简单来说就是让人绝望,它就像洗脑一样,会一点点地腐蚀你的骄傲自尊心和安全感,会一点点地吞噬你的热情信任和存在感,然后在你的心里植入我毫无价值毫无魅力糟糕透顶我不会成为杠精时常以每个人都是独立的个体来保持对一切看似不寻常的做法保持同理心。着装大众化的自己能够理解那些异装人的行为性取向异性恋的自己可以理解同性婚姻母胎信仰佛教的自己可以感同身受那些寻短见3部奇人奇景的古风悬疑小说,在黄沙大漠之中,书写千年传奇古人旧物已化作历史的尘埃,其风物精神却仍留存在当下,吸引着我们。古风悬疑便是其中,我们无法拒绝的题材。分享三本古风悬疑小说,既有古代韵味,又悬念十足,在黄沙大漠之中,书写千年传奇。心藏盖叫天寻迹燕南寄庐偶然得知杭州还有盖叫天的故居,惊喜之中欣然打探。辗转到西湖,拐上杨公堤,步上赵公堤,绕花圃,走小径,穿林区,伴溪流,过小桥迷惘之时,花木幽静处凸现一座青瓦白墙的民居。抬19年67岁大妈不顾儿女反对,坚持生三胎,如今70岁长相却像50岁2019年6月,在山东枣庄的一家医院里,一对老夫妻正不安地坐在医生的面前,医生变幻不定的脸色让他们心里七上八下的。医生,我老伴究竟得了什么病,你就直说吧,我们能承受得住。等了半天,惠州市中级人民法院原党组书记院长陈斯严重违纪违法被开除党籍和公职日前,经广东省委批准,广东省纪委监委对广东省惠州市中级人民法院原党组书记院长陈斯严重违纪违法问题进行了立案审查调查。经查,陈斯丧失理想信念,背弃初心使命,对党不忠诚不老实,与他人串不要被零添加轻易忽悠今天,零添加已经成为食品业的一个重要招牌。不少企业频频打出零添加的旗号,如某品牌的果汁强调不加水不加糖不加添加剂,另一款山楂果汁标注不添加防腐剂不添加色素。不少顾客也真信这一套,零