一、缓存管理:
1.1 为zyg-content-service添加依赖:
<!--3.引入redis-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
1.2 配置redis
spring:
redis:
host: 192.168.56.10
1.3 配置redis的序列化机制(默认使用jdk的序列化器进行序列化)
@Configuration
public class RedisTemplateSerializerConfig {
@Bean
public RedisTemplate redisTemplate(RedisConnectionFactory factory){
RedisTemplate redisTemplate = new RedisTemplate();
redisTemplate.setConnectionFactory(factory);
//1. 设置key的序列化器
redisTemplate.setKeySerializer(new StringRedisSerializer());
//2. 设置value的序列化器
//方法一:
// redisTemplate.setValueSerializer(new GenericFastJsonRedisSerializer());
//方法二:
redisTemplate.setValueSerializer(new GenericJackson2JsonRedisSerializer());
return redisTemplate;
}
}
1.4 查看序列化后的结果:
1.5 同步数据库与缓存库:
1.5.1 zyg-content-service中新增如下方法:
/**
* 功能: 添加广告
* 参数:
* 返回值: void
* 时间: 2021/8/2 15:00
*/
@Override
public void add(ContentEntity content) {
//1. 删除redis中的缓存
redisTemplate.delete("contents");
//2. 添加到数据库中
this.save(content);
}
@Override
public void update(ContentEntity content) {
//1. 删除redis中的缓存
redisTemplate.delete("contents");
//2. 调用 修改方法
this.updateById(content);
}
@Override
public void delete(List<Long> ids) {
//1. 删除redis中的缓存
redisTemplate.delete("contents");
//2. 根据id删除广告
this.removeByIds(ids);
}
1.5.2 zyg-manager-web中修改如下方法:
/**
* 保存
*/
@RequestMapping("/save")
public R save(@RequestBody ContentEntity content){
contentService.add(content);
return R.ok();
}
/**
* 修改
*/
@RequestMapping("/update")
public R update(@RequestBody ContentEntity content){
contentService.update(content);
return R.ok();
}
/**
* 删除
*/
@RequestMapping("/delete")
public R delete(@RequestBody Long[] ids){
contentService.delete(Arrays.asList(ids));
return R.ok();
}
二、搜索(ES)
2.1、新建模块zyg-search-interface与zyg-search-service服务层
2.1.1 zyg-search-interface 模块添加依赖:
<dependencies>
<dependency>
<groupId>com.zelin</groupId>
<artifactId>zyg-pojo</artifactId>
<version>2.0</version>
</dependency>
</dependencies>
2.1.2 zyg-search-service模块添加依赖:
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>com.zelin</groupId>
<artifactId>zyg-search-interface</artifactId>
<version>2.0</version>
</dependency>
<dependency>
<groupId>com.zelin</groupId>
<artifactId>zyg-dao</artifactId>
<version>2.0</version>
</dependency>
<!--3.引入redis-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<!--4.引入单元测试-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-test</artifactId>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
</dependency>
</dependencies>
2.1.3 添加application.yml,配置如下:
server:
port: 7003
logging:
level:
com.zelin: debug
spring:
dubbo:
application:
name: zyg-search-service
registry:
address: zookeeper://192.168.56.10:2181
base-package: com.zelin.search.service
protocol:
name: dubbo
port: 21883
redis:
host: 192.168.56.10
elasticsearch:
host: 192.168.56.10
port: 9200
2.1.4 添加ES的相关配置:
/**
* 描述: ES的配置
* 作者: WF
* 创建时间: 2021/7/20 15:20
*/
@ConfigurationProperties(prefix = "elasticsearch")
@Configuration //相当于:applicationContext.xml
@Data
public class ElasticSearchConfig {
private String host ;
private Integer port ;
@Bean //相当于:<bean>标签
public RestHighLevelClient highLevelClient(){
return new RestHighLevelClient(
RestClient.builder(new HttpHost(host, port, "http")));
}
}
2.2、新建zyg-search-web模块:
2.2.1 引入依赖:
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>com.zelin</groupId>
<artifactId>zyg-search-interface</artifactId>
<version>2.0</version>
</dependency>
<!--1.添加thymeleaf依赖-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<!--2.添加devtools-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>com.zelin</groupId>
<artifactId>zyg-dao</artifactId>
<version>2.0</version>
</dependency>
</dependencies>
2.2.2 添加application.yml文件,配置如下:
server:
port: 9004
logging:
level:
com.zelin: debug
spring:
thymeleaf:
cache: false
prefix: classpath:/templates/
suffix: .html
dubbo:
registry:
address: zookeeper://192.168.56.10:2181
base-package: com.zelin.search.web
application:
name: zyg-search-web
2.2.3 在resources下新建templates目录:
注意:在此目录下放search.html这个搜索文件。
2.2.4 将关于前端搜索的资源添加到/mydat/nginx/html/static/search目录下
2.2.5 配置nginx服务器:
第一步:在/mydata/nginx/conf/nginx.conf文件下添加上游服务器:
第二步:在/mydata/nginx/conf/下定义static.conf文件
第三步:在/mydata/nginx/conf/conf.d/下的zeyigou.conf下添加配置
2.3 导入数据到索引库中
@RunWith(SpringRunner.class)
@SpringBootTest
public class TestES {
@Autowired
private ItemDao itemDao;
@Autowired
private ElasticsearchRestTemplate restTemplate;
/**
* 功能: 导入数据到索引库中
* 参数:
* 返回值: void
* 时间: 2021/8/2 16:39
*/
@Test
public void importData(){
List<ItemEntity> itemEntities = itemDao.selectList(null);
List<ItemEntity> collect = itemEntities.stream().filter(f -> f.getStatus().equals("1"))
.collect(Collectors.toList());
for (ItemEntity itemEntity : collect) {
//1. 得到原来的spec字符串
String spec = itemEntity.getSpec();
//2. 将字符串转换为map
Map specMap = JSON.parseObject(spec, Map.class);
//3. 绑定到实体类中
itemEntity.setSpecMap(specMap);
}
restTemplate.save(collect);
System.out.println("保存到索引库成功!");
}
}
2.4 在kibana中查看数据:
三、统一调整所有的项目:
3.1 对于所有的服务层,web层都要引入zyg-dao依赖:
<dependency>
<groupId>com.zelin</groupId>
<artifactId>zyg-dao</artifactId>
<version>2.0</version>
</dependency>
3.2 对于所有的服务层,必须添加如下的dubbo配置:
spring:
dubbo:
application:
name: zyg-search-service
registry:
address: zookeeper://192.168.56.10:2181
base-package: com.zelin.search.service
protocol:
name: dubbo
port: 21883
3.3 对于所有的web必须添加如下的dubbo配置:
spring:
dubbo:
registry:
address: zookeeper://192.168.56.10:2181
base-package: com.zelin.search.web
application:
name: zyg-search-web
3.4 在服务层使用alibaba的@Service注解注册服务:
3.5 在web层使用@Reference(timeout=5000)引入服务
四、进行查询
4.1 根据关键字进行分类查询:
说明: 此时查询时必须使用”字段名.keyword”这种形式【将查询关键字当作一个整体,不会分词】,否则,报错!
说明: 此时是对聚合结果进行分析,得到我们想要的结果。
4.2 在redis存放数据(分类、品牌、规格)
分析: ① 当我们查询分类时,可以以分类名称为key,以分类的模板id为值,将所有的分类放到redis中 ② 当我们查询模板时,可以以模板id为key,以品牌列表为值(List
4.2.1 在zyg-sellergoods-service的ItemCatServiceImpl中:(存放分类到redis中)
/**
* 功能: 根据父id查询分类列表
* 参数:
* 返回值: java.util.List<com.zelin.entity.ItemCatEntity>
* 时间: 2021/7/27 16:48
*/
@Override
public List<ItemCatEntity> findByParentId(Long pid) {
//1. 根据父id查询所有分类
List<ItemCatEntity> itemCatEntities = baseMapper.selectList(new QueryWrapper<ItemCatEntity>()
.eq("parent_id", pid));
//2. 将所有的分类放到redis中
for (ItemCatEntity entity : list()) {
//以分类名称为key,以模板id为值放到redis中
redisTemplate.boundHashOps("itemCats").put(entity.getName(),entity.getTypeId());
}
return itemCatEntities;
}
4.2.2 在zyg-sellergoods-service的typeTemplateServiceImpl中:(存放品牌及规格列表)
/**
* 功能: 以模板id为key,以规格列表及品牌列表为值放到redis中
* 参数:
* 返回值: com.zelin.utils.PageUtils
* 时间: 2021/8/3 14:55
*/
@Override
public PageUtils list(Map<String, Object> params) {
for (TypeTemplateEntity entity : this.list()) {
//1. 得到模板的品牌字符串
String brandIds = entity.getBrandIds();
//2. 将字符串转换为List<Map>
List<Map> brandMap = JSON.parseArray(brandIds, Map.class);
//3. 这个品牌列表放到redis中
redisTemplate.boundHashOps("brandList").put(entity.getId(),brandMap);
//4. 得到规格字符串
String specIds = entity.getSpecIds();
//5. 将规格字符串转换为List<Map>
List<Map> specMap = JSON.parseArray(specIds, Map.class);
//6. 对每个规格添加规格选项
for (Map map : specMap) {
String id = (String) map.get("id");
List<SpecificationOptionEntity> options = optionService.list(new QueryWrapper<SpecificationOptionEntity>()
.eq("spec_id", id));
map.put("options",options);
}
//7. 将规格列表保存到redis中
redisTemplate.boundHashOps("specList").put(entity.getId(),specMap);
}
return queryPage(params);
}
4.2.3 在zyg-sellergoods-service中对redis进行序列化配置:(存放时序列化)
/**
* ------------------------------
* 功能:
* 作者:WF
* 微信:hbxfwf13590332912
* 创建时间:2021/7/31-21:55
* ------------------------------
*/
@Configuration
public class RedisTemplateSerializerConfig {
@Bean
public RedisTemplate redisTemplate(RedisConnectionFactory factory){
RedisTemplate redisTemplate = new RedisTemplate();
redisTemplate.setConnectionFactory(factory);
//1. 设置key的序列化器
redisTemplate.setKeySerializer(new StringRedisSerializer());
//2. 设置value的序列化器
redisTemplate.setValueSerializer(new GenericFastJsonRedisSerializer());
// redisTemplate.setValueSerializer(new GenericJackson2JsonRedisSerializer());
//3. 如果值是hash,需要使用如下的方式设置序列化器(redis中的hash类型中的小key与小value的序列化)
redisTemplate.setHashKeySerializer(new FastJsonRedisSerializer(Long.class));
redisTemplate.setHashValueSerializer(new GenericJackson2JsonRedisSerializer());
return redisTemplate;
}
}
重点强调: 1、序列化不光时在存放时要进行,在从redis中取数据时,也需要执行同样的序列化,否则,得不到值!
4.2.4 在zyg-search-service中,进行的redis的序列化:
/**
* ------------------------------
* 功能:
* 作者:WF
* 微信:hbxfwf13590332912
* 创建时间:2021/7/31-21:55
* ------------------------------
*/
@Configuration
public class RedisTemplateSerializerConfig {
@Bean
public RedisTemplate redisTemplate(RedisConnectionFactory factory){
RedisTemplate redisTemplate = new RedisTemplate();
redisTemplate.setConnectionFactory(factory);
//1. 设置key的序列化器
redisTemplate.setKeySerializer(new StringRedisSerializer());
//2. 设置value的序列化器
redisTemplate.setValueSerializer(new GenericFastJsonRedisSerializer());
// redisTemplate.setValueSerializer(new GenericJackson2JsonRedisSerializer());
//3. 如果值是hash,需要使用如下的方式设置序列化器(redis中的hash类型中的小key与小value的序列化)
redisTemplate.setHashKeySerializer(new FastJsonRedisSerializer(Long.class));
redisTemplate.setHashValueSerializer(new GenericJackson2JsonRedisSerializer());
return redisTemplate;
}
}
4.2.5 执行如下方法,就可以向redis存放数据了:
添加分类在redis:http://localhost:9001/itemcat/findByParentId/0 添加品牌及规格在redis:http://localhost:9001/typetemplate/list
4.3 展示品牌及规格:
4.3.1 在itemSearchServiceImpl中定义从redis中得到品牌及规格列表
/**
* 功能: 根据分类名称得到品牌列表及规格列表
* 参数:
* 返回值: java.util.Map
* 时间: 2021/8/3 8:39
*/
private Map findBrandAndSpecList(String category) {
//1. 根据分类名称得到模板id
Long typeId = (Long) redisTemplate.boundHashOps("itemCats").get(category);
//2. 根据模板id得到品牌列表
List<Map> brandList = (List<Map>) redisTemplate.boundHashOps("brandList").get(typeId);
//3. 根据模板id得到规格列表
List<Map> specList = (List<Map>) redisTemplate.boundHashOps("specList").get(typeId);
//4. 定义结果map
Map brandAndSpecmap = new HashMap();
//5. 将上面得到的品牌列表及规格列表都放到map中
brandAndSpecmap.put("brandList",brandList);
brandAndSpecmap.put("specList",specList);
//6. 返回
return brandAndSpecmap;
}
4.3.2 在itemSearchServiceImpl的search()方法中调用:
4.3.3 在前端展示页面:
4.3.4 最后的页面效果:
4.3.5 定义用于在前端传递数据到后端的实体类:
@Data
@AllArgsConstructor
@NoArgsConstructor
public class ItemVo implements Serializable {
//1. 查询关键字对象
private String keywords;
}
4.4 进行高亮查询:
4.4.1 前端:
4.4.2 后端itemSearchServiceImpl
@Service
public class ItemSearchServiceImpl implements ItemSearchService {
@Autowired
private RedisTemplate redisTemplate;
@Autowired
private ElasticsearchRestTemplate restTemplate;
/**
* 功能: 根据查询参数得到查询结果
* 参数:
* 返回值: java.util.Map
* 时间: 2021/8/2 16:06
* @param params
*/
@Override
public Map<String, Object> search(ItemVo params) {
//1. 得到查询关键字
String keyword = params.getKeywords();
if(StringUtils.isBlank(keyword)){
keyword = "华为";
}
System.out.println("keyword = " + keyword);
//2. 定义返回结果
Map<String, Object> resultMap = new HashMap<>();
//3. 定义NativeSearchQueryBuilder对象
NativeSearchQueryBuilder searchQueryBuilder = new NativeSearchQueryBuilder()
.withPageable(PageRequest.of(0,10));
//4. 按关键字进行分组查询
searchQueryBuilder.addAggregation(AggregationBuilders.terms("categoryGroup")
.field("category.keyword").size(50));
//5. 添加高亮查询
//5.1 设置高亮查询
searchQueryBuilder.withHighlightBuilder(new HighlightBuilder()
.field("title") //设置高亮字段
.preTags("<span style='color:red'>") //设置高亮显示内容的前缀部分
.postTags("</span>")); //设置高亮显示内容的后缀部分
//5. 添加查询
searchQueryBuilder.withQuery(QueryBuilders.multiMatchQuery( keyword,"title","brand","category"));
//6. 得到查询对象
NativeSearchQuery searchQuery = searchQueryBuilder.build();
//7. 得到命中对象
SearchHits<ItemEntity> searchHits = restTemplate.search(searchQuery, ItemEntity.class, IndexCoordinates.of("item"));
//8. 得到命中的结果
List<SearchHit<ItemEntity>> list = searchHits.getSearchHits();
//9. 得到命中结果的选项
long total = searchHits.getTotalHits(); //总记录数
int totalPage = (int) Math.ceil(total/10.0);//总页数
//10. 处理命中结果(原始的关键字查询)
//List<ItemEntity> collect = list.stream().map(m -> m.getContent()).collect(Collectors.toList());
//11. 得到分组数据
Aggregations aggregations = searchHits.getAggregations();
//12. 得到关于分类的分组
ParsedStringTerms categoryGroupResult = aggregations.get("categoryGroup");
//13. 处理分类分组的结果数据
List<String> categoryList = categoryGroupResult.getBuckets().stream()
.map(m -> m.getKeyAsString()).collect(Collectors.toList());
System.out.println("categoryList = " + categoryList);
//14. 得到高亮查询的结果
//14.1 定义存放高亮字段的内容
List<ItemEntity> highlights = new ArrayList<>();
//14.2 遍历所有命中的数据,从中挑选出高亮数据
for (SearchHit<ItemEntity> searchHit : list) { //list: 所有命中的结果对象
//14.3 可以直接通过高亮字段名称得到此高亮字段的值
List<String> title = searchHit.getHighlightField("title");
//14.4 得到未高亮前的数据
ItemEntity itemEntity = searchHit.getContent();
//14.5 定义存放高亮字段的字符串
StringBuffer buffer = new StringBuffer();
//14.6 组合高亮字段的值
for (String s : title) {
buffer.append(s);
}
//14.7 将高亮字段的值重新设置回原来的对象
itemEntity.setTitle(buffer.toString());
//14.8 将高亮对象添加到集合中
highlights.add(itemEntity);
}
System.out.println("highlights = " + highlights);
//15. 从redis中得到品牌及规格列表
Map brandAndSpecMap = new HashMap();
//16. 得到用户选择的分类
String category = params.getCategory();
if(StringUtils.isBlank(category)){
if(categoryList != null && categoryList.size() > 0) {
category = categoryList.get(0);
}
}
//17. 根据分类进行查询品牌及规格
brandAndSpecMap = findBrandAndSpecList(category);
//18. 将品牌及规格列表放到大集合中
resultMap.putAll(brandAndSpecMap);
resultMap.put("rows",highlights); //当前分页记录集合
resultMap.put("total",total); //总记录数
resultMap.put("categoryList",categoryList); //分类
resultMap.put("totalPage",totalPage); //总页数
return resultMap;
}
/**
* 功能: 根据分类名称得到品牌列表及规格列表
* 参数:
* 返回值: java.util.Map
* 时间: 2021/8/3 8:39
*/
private Map findBrandAndSpecList(String category) {
//1. 根据分类名称得到模板id
Object typeId = redisTemplate.boundHashOps("itemCats").get(category);
//2. 根据模板id得到品牌列表
List<Map> brandList = (List<Map>) redisTemplate.boundHashOps("brandList").get(typeId);
//3. 根据模板id得到规格列表
List<Map> specList = (List<Map>) redisTemplate.boundHashOps("specList").get(typeId);
//4. 定义结果map
Map brandAndSpecmap = new HashMap();
//5. 将上面得到的品牌列表及规格列表都放到map中
brandAndSpecmap.put("brandList",brandList);
brandAndSpecmap.put("specList",specList);
//6. 返回
return brandAndSpecmap;
}
}