第162集,第163集 读写锁
读写者逻辑:保证一定能读到最新数据,修改期间,写锁是一个排它锁(互斥锁、独享锁),读锁是一个共享锁 写锁没释放读锁必须等待。
读 + 读 :相当于无锁,并发读,只会在Redis中记录好,所有当前的读锁。他们都会同时加锁成功
写 + 读 :必须等待写锁释放
写 + 写 :阻塞方式。
读 + 写 :有读锁。
写也需要等待 只要有读或者写的存都必须等待。
/**
* 保证一定能读到最新数据,修改期间,写锁是一个排它锁(互斥锁、独享锁),读锁是一个共享锁
* 写锁没释放读锁必须等待
* 读 + 读 :相当于无锁,并发读,只会在Redis中记录好,所有当前的读锁。他们都会同时加锁成功
* 写 + 读 :必须等待写锁释放
* 写 + 写 :阻塞方式
* 读 + 写 :有读锁。写也需要等待
* 只要有读或者写的存都必须等待
* @return
*/
@GetMapping(value = "/write")
@ResponseBody
public String writeValue() {
String s = "";
RReadWriteLock readWriteLock = redisson.getReadWriteLock("rw-lock");
RLock rLock = readWriteLock.writeLock();
try {
//1、改数据加写锁,读数据加读锁
rLock.lock();
s = UUID.randomUUID().toString();
ValueOperations<String, String> ops = stringRedisTemplate.opsForValue();
ops.set("writeValue",s);
TimeUnit.SECONDS.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
rLock.unlock();
}
return s;
}
@GetMapping(value = "/read")
@ResponseBody
public String readValue() {
String s = "";
RReadWriteLock readWriteLock = redisson.getReadWriteLock("rw-lock");
//加读锁
RLock rLock = readWriteLock.readLock();
try {
rLock.lock();
ValueOperations<String, String> ops = stringRedisTemplate.opsForValue();
s = ops.get("writeValue");
try { TimeUnit.SECONDS.sleep(10); } catch (InterruptedException e) { e.printStackTrace(); }
} catch (Exception e) {
e.printStackTrace();
} finally {
rLock.unlock();
}
return s;
}
第164集 闭锁测试
类似于放学,锁门,必须等所有人都走了,才能进行锁门。
/**
* 放假、锁门
* 1班没人了
* 5个班,全部走完,我们才可以锁大门
* 分布式闭锁
*/
@GetMapping(value = "/lockDoor")
@ResponseBody
public String lockDoor() throws InterruptedException {
RCountDownLatch door = redisson.getCountDownLatch("door");
door.trySetCount(5);
door.await(); //等待闭锁完成
return "放假了...";
}
@GetMapping(value = "/gogogo/{id}")
@ResponseBody
public String gogogo(@PathVariable("id") Long id) {
RCountDownLatch door = redisson.getCountDownLatch("door");
door.countDown(); //计数-1
return id + "班的人都走了...";
}
第165集 信号量机制
就是操作系统的PV操作。
/**
* 车库停车
* 3车位
* 信号量也可以做分布式限流
*/
@GetMapping(value = "/park")
@ResponseBody
public String park() throws InterruptedException {
RSemaphore park = redisson.getSemaphore("park");
park.acquire(); //获取一个信号、获取一个值,占一个车位
boolean flag = park.tryAcquire();
if (flag) {
//执行业务
} else {
return "error";
}
return "ok=>" + flag;
}
@GetMapping(value = "/go")
@ResponseBody
public String go() {
RSemaphore park = redisson.getSemaphore("park");
park.release(); //释放一个车位
return "ok";
}
第166集 缓存与数据库一致性问题
由于,我们只是给缓存加上锁,要求其同一时间只有一个进入数据库。但是如果有有人修改数据库,我们是不管的。这时我们要注意缓存与数据库的一致性问题。
解决方案,有缺陷,没缺陷的是锁的方式:
1、双写方式
由于卡顿等原因,导致写缓存2在最前,写缓存1在后面就出现了不一致脏数据问题:这是暂时性的脏数据问题,但是在数据稳定,缓存过期以后,又能得到最新的正确数据
2、失效模式
这样流程导致更新的缓存得到的是,错误的数据。
总结
论是双写模式还是失效模式,都会导致缓存的不一致问题。即多个实例同时更新会出事。怎么办?如果是用户纬度数据(订单数据、用户数据),这种并发几率非常小,不用考虑这个问题,缓存数据加上过期时间,每隔一段时间触发读的主动更新即可 2、如果是菜单,商品介绍等基础数据,也可以去使用canal订阅binlog的方式。3、缓存数据+过期时间也足够解决大部分业务对于缓存的要求。4、通过加锁保证并发读写,写写的时候按顺序排好队。读读无所谓。所以适合使用读写锁。(业务不关心脏数据,允许临时脏数据可忽略);
·总结:·我们能放入缓存的数据本就不应该是实时性、一致性要求超高的。所以缓存数据的时候加上过期时间,保证每天拿到当前最新数据即可。·我们不应该过度设计,增加系统的复杂性·遇到实时性、一致性要求高的数据,就应该查数据库,即使慢点。
第167集 springcache简介
缓存管理器,这个就是管理缓存的东西
第168集 整合
pom文件
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-cache</artifactId>
</dependency>
spring
cache:
type: redis
@EnableCaching
@SpringBootApplication
@EnableDiscoveryClient
@EnableFeignClients("com.buu.gulimall.product.feign")
@MapperScan("com.buu.gulimall.product.dao")
@Slf4j
public class GulimallProductApplication {
public static void main(String[] args) {
SpringApplication.run(GulimallProductApplication.class, args);
}
}
@Cacheable("{category}")
@Override
public List<CategoryEntity> getLevel1Categorys() {
System.out.println("getLevel1Categorys........");
long l = System.currentTimeMillis();
List<CategoryEntity> categoryEntities = this.baseMapper.selectList(
new QueryWrapper<CategoryEntity>().eq("parent_cid", 0));
System.out.println("消耗时间:"+ (System.currentTimeMillis() - l));
return categoryEntities;
}
注解
@Cacheable:Triggers
@CacheEvict:Triggerscacheeviction.:触发将数据从缓存删除的操作
@CachePut:Updates the cache without interfering with the method execution.:不影响方法执行更新缓存
@Caching: Regroups multiple cache operationsstobeappliedonamethod.:组合以上多个操作
@CacheConfig: Sharessomecommon cache-related settings at cLass-Level.:在类级别共享缓存的相同配置
成功运行
第169集 @cacheable细节
默认行为、
1)、如果缓存中有,方法不用调用。
2)、key默认自动生感:存的名字(自主生成的hey值)
3)、存的value的值。默认使用序列化机制。将序列化后的数据存到redis
4)、默认时间-1
要将其转化为自定义的,指定生成的key,指定存的数据的存活时间,数据保存为json
spring:
cache:
type: redis
redis:
time-to-live: 3600000
@Cacheable(value={"category"},key = "'levelCategory'")
@Override
public List<CategoryEntity> getLevel1Categorys() {
System.out.println("getLevel1Categorys........");
long l = System.currentTimeMillis();
List<CategoryEntity> categoryEntities = this.baseMapper.selectList(
new QueryWrapper<CategoryEntity>().eq("parent_cid", 0));
System.out.println("消耗时间:"+ (System.currentTimeMillis() - l));
return categoryEntities;
}
第170集 转化为json的方式
用配置类的方式进行处理,同时设置空值也进行保存。
@EnableConfigurationProperties(CacheProperties.class)
@Configuration
@EnableCaching
public class MyCacheConfig {
// @Autowired
// public CacheProperties cacheProperties;
/**
* 配置文件的配置没有用上
* @return
*/
@Bean
public RedisCacheConfiguration redisCacheConfiguration(CacheProperties cacheProperties) {
RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig();
// config = config.entryTtl();
config = config.serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(new StringRedisSerializer()));
config = config.serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(new GenericJackson2JsonRedisSerializer()));
CacheProperties.Redis redisProperties = cacheProperties.getRedis();
//将配置文件中所有的配置都生效
if (redisProperties.getTimeToLive() != null) {
config = config.entryTtl(redisProperties.getTimeToLive());
}
if (redisProperties.getKeyPrefix() != null) {
config = config.prefixKeysWith(redisProperties.getKeyPrefix());
}
if (!redisProperties.isCacheNullValues()) {
config = config.disableCachingNullValues();
}
if (!redisProperties.isUseKeyPrefix()) {
config = config.disableKeyPrefix();
}
return config;
}
}
spring.cache.type=redis
#spring.cache.cache-names=qq,毫秒为单位
spring.cache.redis.time-to-live=3600000
#如果指定了前缀就用我们指定的前缀,如果没有就默认使用缓存的名字作为前缀
#spring.cache.redis.key-prefix=CACHE_
spring.cache.redis.use-key-prefix=true
#是否缓存空值,防止缓存穿透
spring.cache.redis.cache-null-values=true
第171集 测试springcache的双写,删除模式
@CacheEvict(value = "category",key = "'getLevel1Categorys'")意思就是方法执行完之后执行删除缓存getLevel1Categorys,删除模式
@CachePut双写模式
@Override
@CacheEvict(value = "category",key = "'getLevel1Categorys'")
@Transactional
public void updateCascade(CategoryEntity category) {
this.updateById(category);
categoryBrandRelationService.updateCategory(category.getCatId(),category.getName());
}
组合多种注解进行操作,更新完之后同时删除两个缓存。
@Override
@Caching(evict = {
@CacheEvict(value = "category",key = "'getLevel1Categorys'")
@CacheEvict(value = "category",key = "'getCategoryJson'")
})
@Transactional
public void updateCascade(CategoryEntity category) {
this.updateById(category);
categoryBrandRelationService.updateCategory(category.getCatId(),category.getName());
}
第172集 springcache的不足
总结:常规缓存的使用可以用springcache,读多写少的情况可以用,但是要求高的数据,特殊数据要特殊处理。读模式的缓存击穿,穿透,雪崩都基础的解决了,但是写模式的一致性没处理。
第173集 检索服务的搭建
这个和之前配置首页面的操作类似
nginx改个nginx.conf
该spring-gateway的配置,直接用这个,以后也不用改了
spring:
cloud:
# sentinel:
# transport:
# #配置sentinel dashboard地址
# dashboard: localhost:8080
gateway:
routes:
# - id: test_route
# uri: https://www.baidu.com
# predicates:
# - Query=uri,baidu
#
# - id: qq_route
# uri: https://www.qq.com
# predicates:
# - Query=uri,qq
- id: product_route
uri: lb://gulimall-product
predicates:
- Path=/api/product/**
filters:
- RewritePath=/api/(?<segment>/?.*),/$\{segment}
- id: coupon_route
uri: lb://gulimall-coupon
predicates:
- Path=/api/coupon/**
filters:
- RewritePath=/api/(?<segment>/?.*),/$\{segment}
- id: third_party_route
uri: lb://gulimall-third-party
predicates:
- Path=/api/thirdparty/**
filters:
- RewritePath=/api/thirdparty/(?<segment>/?.*),/$\{segment}
- id: member_route
uri: lb://gulimall-member
predicates:
- Path=/api/member/**
filters:
- RewritePath=/api/(?<segment>/?.*),/$\{segment}
- id: ware_route
uri: lb://gulimall-ware
predicates:
- Path=/api/ware/**
filters:
- RewritePath=/api/(?<segment>/?.*),/$\{segment}
- id: order_route
uri: lb://gulimall-order
predicates:
- Path=/api/order/**
filters:
- RewritePath=/api/(?<segment>/?.*),/$\{segment}
- id: admin_route
uri: lb://renren-fast
predicates:
- Path=/api/**
filters:
- RewritePath=/api/(?<segment>/?.*), /renren-fast/$\{segment}
- id: gulimall_host_route
uri: lb://gulimall-product
predicates:
- Host=gulimall.com,item.gulimall.com
- id: gulimall_search_route
uri: lb://gulimall-search
predicates:
- Host=search.gulimall.com
- id: gulimall_auth_route
uri: lb://gulimall-auth-server
predicates:
- Host=auth.gulimall.com
- id: gulimall_cart_route
uri: lb://gulimall-cart
predicates:
- Host=cart.gulimall.com
- id: gulimall_order_route
uri: lb://gulimall-order
predicates:
- Host=order.gulimall.com
- id: gulimall_member_route
uri: lb://gulimall-member
predicates:
- Host=member.gulimall.com
- id: gulimall_seckill_route
uri: lb://gulimall-seckill
predicates:
- Host=seckill.gulimall.com
#暴露所有端点
management:
endpoints:
web:
exposure:
include: '*'
## 前端项目,/api
## http://localhost:88/api/captcha.jpg http://localhost:8080/renren-fast/captcha.jpg
## http://localhost:88/api/product/category/list/tree http://localhost:10000/product/category/list/tree
改hosts文件 C:\Windows\System32\drivers\etc;
之后还要修改前端文件,就按照的步骤改好就行, 由于太长我就不附了。
成功
第174集 调整页面跳转
有些报错问题解决
放到nginx里面的catelog.js文件出错,改为gulimall.com,
product服务的index.html修改
之后要跟老师修改的保持一致,修改完这些过后,如果还不起效果,则使用无痕浏览器 ,再试试。
第175集 检索查询参数模型
这一节主要分析有哪些请求参数
keyword=‘小米’&sort=saleCount_desc/asc&hasStock=0/1&skuPrice=400_1900&brandld=1&catalog3ld=1&attrs=1_3G:4G:5G&attrs=2_骁龙845&attrs=4_高清屏
1、全文检索:skuTitle -> keyword
2、排序:saleCount(销量)、hotScoree(热度分)、skuPrice(价格)
3、过滤:hasStock、skuPrice区间、brandld、catalog3ld、attrs
4、聚合:attrs
构造一个请求体类
@Data
public class SearchParam {
/**
* 页面传递过来的全文匹配关键字
*/
private String keyword;
/**
* 品牌id,可以多选
*/
private List<Long> brandId;
/**
* 三级分类id
*/
private Long catalog3Id;
/**
* 排序条件:sort=price/salecount/hotscore_desc/asc
*/
private String sort;
/**
* 是否显示有货
*/
private Integer hasStock;
/**
* 价格区间查询
*/
private String skuPrice;
/**
* 按照属性进行筛选
*/
private List<String> attrs;
/**
* 页码
*/
private Integer pageNum = 1;
/**
* 原生的所有查询条件
*/
private String _queryString;
}
第176集 返回结果模型
这就是页面需要返回的结果。
@Data
public class SearchResult {
/**
* 查询到的所有商品信息
*/
private List<SkuEsModel> product;
/**
* 当前页码
*/
private Integer pageNum;
/**
* 总记录数
*/
private Long total;
/**
* 总页码
*/
private Integer totalPages;
private List<Integer> pageNavs;
/**
* 当前查询到的结果,所有涉及到的品牌
*/
private List<BrandVo> brands;
/**
* 当前查询到的结果,所有涉及到的所有属性
*/
private List<AttrVo> attrs;
/**
* 当前查询到的结果,所有涉及到的所有分类
*/
private List<CatalogVo> catalogs;
//===========================以上是返回给页面的所有信息============================//
/* 面包屑导航数据 */
private List<NavVo> navs;
@Data
public static class NavVo {
private String navName;
private String navValue;
private String link;
}
@Data
public static class BrandVo {
private Long brandId;
private String brandName;
private String brandImg;
}
@Data
public static class AttrVo {
private Long attrId;
private String attrName;
private List<String> attrValue;
}
@Data
public static class CatalogVo {
private Long catalogId;
private String catalogName;
}
}
第177集 检索DSL测试
我这里出现个bug
就是发现我没有product索引,后来发现我用的老师资料的代码,他用的是gulimall-product作为索引,所以我在商品上架的时候,product索引没有数据。我将这里改为product,并且重新添加一般索引mapping,并且重新上架商品。
之后编写DSL语句
bool可以搭配must,should等内容进行多条件查询,搜索出标题为小米的数据
http://127.0.0.1:9200/gulimall_product/_search
{
"query":{
"bool":{
"must":[
{
"match":{
"skuTitle":"小米"
}
}
]
}
}
}
之后我们规定不参与评分的放筛选里面,成功,这代表搜索catalogId=225(手机),以及品牌id为1,2,10,且题目为小米的数据。这个逻辑其实有点怪,你都查小米了,还要查手机品牌为1,2,10的数据。可能小米代表全局搜索吧,可能有吃的小米,手机小米,他们品牌不一样。
http://127.0.0.1:9200/product/_search
{
"query":{
"bool":{
"must":[
{
"match":{
"skuTitle":"小米"
}
}
],
"filter":[
{
"term":{
"catalogId":"225"
}
},
{
"terms":{
"brandId":["1","2","10"]
}
}
]
}
}
}
下一轮筛选
这里由于太长了,把nasted的处理挑出来了。terms或 term且
{
"query": {
"bool": {
"filter":{
"nested":{
"path":"attrs",
"query":{
"bool":{
"must":[
{
"term":{
"attrs.attrId":{
"value": 15
}
}
},
{
"terms":{
"attrs.attrValue":[
"海思(Hisilicon)","HUAWEI Kirin 980"
]
}
}
]
}
}
}
}
}
}
}
排序
{
"query": {
"match_all": {}
},
"sort": {
"skuPrice": {
"order": "desc"
}
}
}
范围区间
{
"query": {
"match_all": {}
"range": {
"skuPrice": {
"gte": 0,
"lte": 6000
}
}
}
}
分页
{
"query": {
"match_all": {}
},
"from":0,
"size":20
}
高亮
{
"query": {
"match_all": {}
},
"highlight":{
"fields":{
"skuTitle":{}
}
}
}
将这些所有都合并成一个DSL
get http://127.0.0.1:9200/product/_search
{
"query": {
"bool": {
"must": [
{
"match": {
"skuTitle": "锤子手机"
}
}
],
"filter": [
{
"term": {
"catalogId": "225"
}
},
{
"term": {
"hasStock": "false"
}
},
{
"terms": {
"brandId": [
"1",
"2",
"10"
]
}
},
{
"nested": {
"path": "attrs",
"query": {
"bool": {
"must": [
{
"term": {
"attrs.attrId": {
"value": 15
}
}
},
{
"terms": {
"attrs.attrValue": [
"海思(Hisilicon)",
"HUAWEI Kirin 980"
]
}
}
]
}
}
}
},
{
"range": {
"skuPrice": {
"gte": 0,
"lte": 6000
}
}
}
]
}
},
"sort": {
"skuPrice": {
"order": "desc"
}
},
"from":0,
"size":20,
"highlight":{
"fields":{
"skuTitle":{}
}
}
}
上面内容包括:模糊匹配,过滤(按照属性,分类,品牌,价格区间,库存),排序,分页,高亮等等内容。
第178集 聚合分析
聚合操作
get http://127.0.0.1:9200/product/_search
{
"query": {
"match_all": {}
},
"aggs": {
"brand_agg": {
"terms": {
"field": "catalogId",
"size": 10
}
}
}
}
之后要改映射了,终于知道为啥上一集的老师的代码索引是gulimall_product了,设置gulimall_product的索引。
{
"mappings": {
"properties": {
"skuId": {
"type": "long"
},
"spuId": {
"type": "long"
},
"skuTitle": {
"type": "text",
"analyzer": "ik_smart"
},
"skuPrice": {
"type": "keyword"
},
"skuImg": {
"type": "keyword"
},
"saleCount": {
"type": "long"
},
"hosStock": {
"type": "boolean"
},
"hotScore": {
"type": "long"
},
"brandId": {
"type": "long"
},
"catelogId": {
"type": "long"
},
"brandName": {
"type": "keyword"
},
"brandImg": {
"type": "keyword"
},
"catalogName": {
"type": "keyword"
},
"attrs": {
"type": "nested",
"properties": {
"attrId": {
"type": "long"
},
"attrName": {
"type": "keyword"
},
"attrValue": {
"type": "keyword"
}
}
}
}
}
}
迁移数据代码,或者修改数据库表pms_spu_info中上架字段改成0,前端重新上架即可。
post http://127.0.0.1:9200/_reindex
{
"source": {
"index": "product"
},
"dest": {
"index": "gulimall_product"
}
}
根据brandId,brandName,brandImg这三个聚合,就是有相同数据的合并到一起,相当于group by
嵌入式聚合,反正也是合并。大致了解即可。
汇总DSL
{
"query": {
"bool": {
"must": [
{
"match": {
"skuTitle": "华为"
}
}
],
"filter": [
{
"term": {
"catalogId": "225"
}
},
{
"terms": {
"brandId": [
"1",
"2",
"9"
]
}
},
{
"nested": {
"path": "attrs",
"query": {
"bool": {
"must": [
{
"term": {
"attrs.attrId": {
"value": "9"
}
}
},
{
"terms": {
"attrs.attrValue": [
"高通",
"海思"
]
}
}
]
}
}
}
},
{
"term": {
"hasStock": {
"value": "true"
}
}
},
{
"range":{
"skuPrice" : {
"gte" :0,
"lte" :6500
}
}
}
]
}
},
"sort": [
{
"skuPrice": {
"order": "desc"
}
}
],
"from": 0,
"size": 5,
"highlight": {
"fields": {"skuTitle": {}},
"pre_tags": "<b style='color:red'>",
"post_tags": "</b>"
},
"aggs": {
"brand_agg": {
"terms": {
"field": "brandId",
"size": 10
},
"aggs": {
"brand_name_agg": {
"terms": {
"field": "brandName",
"size": 10
}
},
"brand_img_agg": {
"terms": {
"field": "brandImg",
"size": 10
}
}
}
},
"catalog_agg": {
"terms": {
"field": "catalogId",
"size": 10
},
"aggs": {
"catalog_name_agg": {
"terms": {
"field": "catalogName",
"size": 10
}
}
}
},
"attr_agg": {
"nested": {
"path": "attrs"
},
"aggs": {
"attr_id_agg": {
"terms": {
"field": "attrs.attrId",
"size": 100
},
"aggs": {
"attr_name_agg": {
"terms": {
"field": "attrs.attrName",
"size": 10
}
},
"attr_value_agg": {
"terms": {
"field": "attrs.attrValue",
"size": 10
}
}
}
}
}
}
}
}
第179集 searchrequest的构建
总体来说,java的代码就是根据DSL语句根据json一层一层的剥离开进行编写的。这块需要自己手动写一次印象更深。逻辑不难。
SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
/**
* 模糊匹配,过滤(按照属性,分类,品牌,价格区间,库存)
*/
//1. 构建bool-query
BoolQueryBuilder boolQueryBuilder=new BoolQueryBuilder();
//1.1 bool-must
if(!StringUtils.isEmpty(param.getKeyword())){
boolQueryBuilder.must(QueryBuilders.matchQuery("skuTitle",param.getKeyword()));
}
//1.2 bool-fiter
//1.2.1 catelogId
if(null != param.getCatalog3Id()){
boolQueryBuilder.filter(QueryBuilders.termQuery("catalogId",param.getCatalog3Id()));
}
//1.2.2 brandId
if(null != param.getBrandId() && param.getBrandId().size() >0){
boolQueryBuilder.filter(QueryBuilders.termsQuery("brandId",param.getBrandId()));
}
//1.2.3 attrs
if(param.getAttrs() != null && param.getAttrs().size() > 0){
param.getAttrs().forEach(item -> {
//attrs=1_5寸:8寸&2_16G:8G
BoolQueryBuilder boolQuery = QueryBuilders.boolQuery();
//attrs=1_5寸:8寸
String[] s = item.split("_");
String attrId=s[0];
String[] attrValues = s[1].split(":");//这个属性检索用的值
boolQuery.must(QueryBuilders.termQuery("attrs.attrId",attrId));
boolQuery.must(QueryBuilders.termsQuery("attrs.attrValue",attrValues));
NestedQueryBuilder nestedQueryBuilder = QueryBuilders.nestedQuery("attrs",boolQuery, ScoreMode.None);
boolQueryBuilder.filter(nestedQueryBuilder);
});
}
//1.2.4 hasStock
if(null != param.getHasStock()){
boolQueryBuilder.filter(QueryBuilders.termQuery("hasStock",param.getHasStock() == 1));
}
//1.2.5 skuPrice
if(!StringUtils.isEmpty(param.getSkuPrice())){
//skuPrice形式为:1_500或_500或500_
RangeQueryBuilder rangeQueryBuilder = QueryBuilders.rangeQuery("skuPrice");
String[] price = param.getSkuPrice().split("_");
if(price.length==2){
rangeQueryBuilder.gte(price[0]).lte(price[1]);
}else if(price.length == 1){
if(param.getSkuPrice().startsWith("_")){
rangeQueryBuilder.lte(price[1]);
}
if(param.getSkuPrice().endsWith("_")){
rangeQueryBuilder.gte(price[0]);
}
}
boolQueryBuilder.filter(rangeQueryBuilder);
}
//封装所有的查询条件
searchSourceBuilder.query(boolQueryBuilder);
第180集 构建排序,分页,高亮
/**
* 排序,分页,高亮
*/
//排序
//形式为sort=hotScore_asc/desc
if(!StringUtils.isEmpty(param.getSort())){
String sort = param.getSort();
String[] sortFileds = sort.split("_");
SortOrder sortOrder="asc".equalsIgnoreCase(sortFileds[1])?SortOrder.ASC:SortOrder.DESC;
searchSourceBuilder.sort(sortFileds[0],sortOrder);
}
//分页
searchSourceBuilder.from((param.getPageNum()-1)*EsConstant.PRODUCT_PAGESIZE);
searchSourceBuilder.size(EsConstant.PRODUCT_PAGESIZE);
//高亮
if(!StringUtils.isEmpty(param.getKeyword())){
HighlightBuilder highlightBuilder = new HighlightBuilder();
highlightBuilder.field("skuTitle");
highlightBuilder.preTags("<b style='color:red'>");
highlightBuilder.postTags("</b>");
searchSourceBuilder.highlighter(highlightBuilder);
}