一、缓存管理:

1.1 为zyg-content-service添加依赖:

  1. <!--3.引入redis-->
  2. <dependency>
  3. <groupId>org.springframework.boot</groupId>
  4. <artifactId>spring-boot-starter-data-redis</artifactId>
  5. </dependency>

1.2 配置redis

  1. spring:
  2. redis:
  3. host: 192.168.56.10

1.3 配置redis的序列化机制(默认使用jdk的序列化器进行序列化)

  1. @Configuration
  2. public class RedisTemplateSerializerConfig {
  3. @Bean
  4. public RedisTemplate redisTemplate(RedisConnectionFactory factory){
  5. RedisTemplate redisTemplate = new RedisTemplate();
  6. redisTemplate.setConnectionFactory(factory);
  7. //1. 设置key的序列化器
  8. redisTemplate.setKeySerializer(new StringRedisSerializer());
  9. //2. 设置value的序列化器
  10. //方法一:
  11. // redisTemplate.setValueSerializer(new GenericFastJsonRedisSerializer());
  12. //方法二:
  13. redisTemplate.setValueSerializer(new GenericJackson2JsonRedisSerializer());
  14. return redisTemplate;
  15. }
  16. }

1.4 查看序列化后的结果:

image.png

1.5 同步数据库与缓存库:

1.5.1 zyg-content-service中新增如下方法:

  1. /**
  2. * 功能: 添加广告
  3. * 参数:
  4. * 返回值: void
  5. * 时间: 2021/8/2 15:00
  6. */
  7. @Override
  8. public void add(ContentEntity content) {
  9. //1. 删除redis中的缓存
  10. redisTemplate.delete("contents");
  11. //2. 添加到数据库中
  12. this.save(content);
  13. }
  14. @Override
  15. public void update(ContentEntity content) {
  16. //1. 删除redis中的缓存
  17. redisTemplate.delete("contents");
  18. //2. 调用 修改方法
  19. this.updateById(content);
  20. }
  21. @Override
  22. public void delete(List<Long> ids) {
  23. //1. 删除redis中的缓存
  24. redisTemplate.delete("contents");
  25. //2. 根据id删除广告
  26. this.removeByIds(ids);
  27. }

1.5.2 zyg-manager-web中修改如下方法:

  1. /**
  2. * 保存
  3. */
  4. @RequestMapping("/save")
  5. public R save(@RequestBody ContentEntity content){
  6. contentService.add(content);
  7. return R.ok();
  8. }
  9. /**
  10. * 修改
  11. */
  12. @RequestMapping("/update")
  13. public R update(@RequestBody ContentEntity content){
  14. contentService.update(content);
  15. return R.ok();
  16. }
  17. /**
  18. * 删除
  19. */
  20. @RequestMapping("/delete")
  21. public R delete(@RequestBody Long[] ids){
  22. contentService.delete(Arrays.asList(ids));
  23. return R.ok();
  24. }

二、搜索(ES)

2.1、新建模块zyg-search-interface与zyg-search-service服务层

2.1.1 zyg-search-interface 模块添加依赖:

  1. <dependencies>
  2. <dependency>
  3. <groupId>com.zelin</groupId>
  4. <artifactId>zyg-pojo</artifactId>
  5. <version>2.0</version>
  6. </dependency>
  7. </dependencies>

2.1.2 zyg-search-service模块添加依赖:

  1. <dependencies>
  2. <dependency>
  3. <groupId>org.springframework.boot</groupId>
  4. <artifactId>spring-boot-starter-web</artifactId>
  5. </dependency>
  6. <dependency>
  7. <groupId>com.zelin</groupId>
  8. <artifactId>zyg-search-interface</artifactId>
  9. <version>2.0</version>
  10. </dependency>
  11. <dependency>
  12. <groupId>com.zelin</groupId>
  13. <artifactId>zyg-dao</artifactId>
  14. <version>2.0</version>
  15. </dependency>
  16. <!--3.引入redis-->
  17. <dependency>
  18. <groupId>org.springframework.boot</groupId>
  19. <artifactId>spring-boot-starter-data-redis</artifactId>
  20. </dependency>
  21. <!--4.引入单元测试-->
  22. <dependency>
  23. <groupId>org.springframework.boot</groupId>
  24. <artifactId>spring-boot-starter-test</artifactId>
  25. <scope>test</scope>
  26. </dependency>
  27. <dependency>
  28. <groupId>org.springframework.boot</groupId>
  29. <artifactId>spring-boot-test</artifactId>
  30. </dependency>
  31. <dependency>
  32. <groupId>junit</groupId>
  33. <artifactId>junit</artifactId>
  34. </dependency>
  35. <dependency>
  36. <groupId>org.springframework</groupId>
  37. <artifactId>spring-test</artifactId>
  38. </dependency>
  39. </dependencies>

2.1.3 添加application.yml,配置如下:

  1. server:
  2. port: 7003
  3. logging:
  4. level:
  5. com.zelin: debug
  6. spring:
  7. dubbo:
  8. application:
  9. name: zyg-search-service
  10. registry:
  11. address: zookeeper://192.168.56.10:2181
  12. base-package: com.zelin.search.service
  13. protocol:
  14. name: dubbo
  15. port: 21883
  16. redis:
  17. host: 192.168.56.10
  18. elasticsearch:
  19. host: 192.168.56.10
  20. port: 9200

2.1.4 添加ES的相关配置:

  1. /**
  2. * 描述: ES的配置
  3. * 作者: WF
  4. * 创建时间: 2021/7/20 15:20
  5. */
  6. @ConfigurationProperties(prefix = "elasticsearch")
  7. @Configuration //相当于:applicationContext.xml
  8. @Data
  9. public class ElasticSearchConfig {
  10. private String host ;
  11. private Integer port ;
  12. @Bean //相当于:<bean>标签
  13. public RestHighLevelClient highLevelClient(){
  14. return new RestHighLevelClient(
  15. RestClient.builder(new HttpHost(host, port, "http")));
  16. }
  17. }

2.2、新建zyg-search-web模块:

2.2.1 引入依赖:

  1. <dependencies>
  2. <dependency>
  3. <groupId>org.springframework.boot</groupId>
  4. <artifactId>spring-boot-starter-web</artifactId>
  5. </dependency>
  6. <dependency>
  7. <groupId>com.zelin</groupId>
  8. <artifactId>zyg-search-interface</artifactId>
  9. <version>2.0</version>
  10. </dependency>
  11. <!--1.添加thymeleaf依赖-->
  12. <dependency>
  13. <groupId>org.springframework.boot</groupId>
  14. <artifactId>spring-boot-starter-thymeleaf</artifactId>
  15. </dependency>
  16. <!--2.添加devtools-->
  17. <dependency>
  18. <groupId>org.springframework.boot</groupId>
  19. <artifactId>spring-boot-devtools</artifactId>
  20. <optional>true</optional>
  21. </dependency>
  22. <dependency>
  23. <groupId>com.zelin</groupId>
  24. <artifactId>zyg-dao</artifactId>
  25. <version>2.0</version>
  26. </dependency>
  27. </dependencies>

2.2.2 添加application.yml文件,配置如下:

  1. server:
  2. port: 9004
  3. logging:
  4. level:
  5. com.zelin: debug
  6. spring:
  7. thymeleaf:
  8. cache: false
  9. prefix: classpath:/templates/
  10. suffix: .html
  11. dubbo:
  12. registry:
  13. address: zookeeper://192.168.56.10:2181
  14. base-package: com.zelin.search.web
  15. application:
  16. name: zyg-search-web

2.2.3 在resources下新建templates目录:

注意:在此目录下放search.html这个搜索文件。

2.2.4 将关于前端搜索的资源添加到/mydat/nginx/html/static/search目录下

image.png

2.2.5 配置nginx服务器:

第一步:在/mydata/nginx/conf/nginx.conf文件下添加上游服务器:
image.png
第二步:在/mydata/nginx/conf/下定义static.conf文件
image.png
第三步:在/mydata/nginx/conf/conf.d/下的zeyigou.conf下添加配置
image.png

2.3 导入数据到索引库中

  1. @RunWith(SpringRunner.class)
  2. @SpringBootTest
  3. public class TestES {
  4. @Autowired
  5. private ItemDao itemDao;
  6. @Autowired
  7. private ElasticsearchRestTemplate restTemplate;
  8. /**
  9. * 功能: 导入数据到索引库中
  10. * 参数:
  11. * 返回值: void
  12. * 时间: 2021/8/2 16:39
  13. */
  14. @Test
  15. public void importData(){
  16. List<ItemEntity> itemEntities = itemDao.selectList(null);
  17. List<ItemEntity> collect = itemEntities.stream().filter(f -> f.getStatus().equals("1"))
  18. .collect(Collectors.toList());
  19. for (ItemEntity itemEntity : collect) {
  20. //1. 得到原来的spec字符串
  21. String spec = itemEntity.getSpec();
  22. //2. 将字符串转换为map
  23. Map specMap = JSON.parseObject(spec, Map.class);
  24. //3. 绑定到实体类中
  25. itemEntity.setSpecMap(specMap);
  26. }
  27. restTemplate.save(collect);
  28. System.out.println("保存到索引库成功!");
  29. }
  30. }

2.4 在kibana中查看数据:

image.png
image.png

三、统一调整所有的项目:

3.1 对于所有的服务层,web层都要引入zyg-dao依赖:

  1. <dependency>
  2. <groupId>com.zelin</groupId>
  3. <artifactId>zyg-dao</artifactId>
  4. <version>2.0</version>
  5. </dependency>

3.2 对于所有的服务层,必须添加如下的dubbo配置:

  1. spring:
  2. dubbo:
  3. application:
  4. name: zyg-search-service
  5. registry:
  6. address: zookeeper://192.168.56.10:2181
  7. base-package: com.zelin.search.service
  8. protocol:
  9. name: dubbo
  10. port: 21883

3.3 对于所有的web必须添加如下的dubbo配置:

  1. spring:
  2. dubbo:
  3. registry:
  4. address: zookeeper://192.168.56.10:2181
  5. base-package: com.zelin.search.web
  6. application:
  7. name: zyg-search-web

3.4 在服务层使用alibaba的@Service注解注册服务:

image.png

3.5 在web层使用@Reference(timeout=5000)引入服务

image.png

四、进行查询

4.1 根据关键字进行分类查询:

image.png

说明: 此时查询时必须使用”字段名.keyword”这种形式【将查询关键字当作一个整体,不会分词】,否则,报错!

image.png

说明: 此时是对聚合结果进行分析,得到我们想要的结果。

image.png
image.png

4.2 在redis存放数据(分类、品牌、规格)

分析: ① 当我们查询分类时,可以以分类名称为key,以分类的模板id为值,将所有的分类放到redis中 ② 当我们查询模板时,可以以模板id为key,以品牌列表为值(List)放到redis中 ② 当我们查询模板时,可以以模板id为key,以规格及其选项列表为值(List)放到redis中

4.2.1 在zyg-sellergoods-service的ItemCatServiceImpl中:(存放分类到redis中)

  1. /**
  2. * 功能: 根据父id查询分类列表
  3. * 参数:
  4. * 返回值: java.util.List<com.zelin.entity.ItemCatEntity>
  5. * 时间: 2021/7/27 16:48
  6. */
  7. @Override
  8. public List<ItemCatEntity> findByParentId(Long pid) {
  9. //1. 根据父id查询所有分类
  10. List<ItemCatEntity> itemCatEntities = baseMapper.selectList(new QueryWrapper<ItemCatEntity>()
  11. .eq("parent_id", pid));
  12. //2. 将所有的分类放到redis中
  13. for (ItemCatEntity entity : list()) {
  14. //以分类名称为key,以模板id为值放到redis中
  15. redisTemplate.boundHashOps("itemCats").put(entity.getName(),entity.getTypeId());
  16. }
  17. return itemCatEntities;
  18. }

4.2.2 在zyg-sellergoods-service的typeTemplateServiceImpl中:(存放品牌及规格列表)

  1. /**
  2. * 功能: 以模板id为key,以规格列表及品牌列表为值放到redis中
  3. * 参数:
  4. * 返回值: com.zelin.utils.PageUtils
  5. * 时间: 2021/8/3 14:55
  6. */
  7. @Override
  8. public PageUtils list(Map<String, Object> params) {
  9. for (TypeTemplateEntity entity : this.list()) {
  10. //1. 得到模板的品牌字符串
  11. String brandIds = entity.getBrandIds();
  12. //2. 将字符串转换为List<Map>
  13. List<Map> brandMap = JSON.parseArray(brandIds, Map.class);
  14. //3. 这个品牌列表放到redis中
  15. redisTemplate.boundHashOps("brandList").put(entity.getId(),brandMap);
  16. //4. 得到规格字符串
  17. String specIds = entity.getSpecIds();
  18. //5. 将规格字符串转换为List<Map>
  19. List<Map> specMap = JSON.parseArray(specIds, Map.class);
  20. //6. 对每个规格添加规格选项
  21. for (Map map : specMap) {
  22. String id = (String) map.get("id");
  23. List<SpecificationOptionEntity> options = optionService.list(new QueryWrapper<SpecificationOptionEntity>()
  24. .eq("spec_id", id));
  25. map.put("options",options);
  26. }
  27. //7. 将规格列表保存到redis中
  28. redisTemplate.boundHashOps("specList").put(entity.getId(),specMap);
  29. }
  30. return queryPage(params);
  31. }

4.2.3 在zyg-sellergoods-service中对redis进行序列化配置:(存放时序列化)

  1. /**
  2. * ------------------------------
  3. * 功能:
  4. * 作者:WF
  5. * 微信:hbxfwf13590332912
  6. * 创建时间:2021/7/31-21:55
  7. * ------------------------------
  8. */
  9. @Configuration
  10. public class RedisTemplateSerializerConfig {
  11. @Bean
  12. public RedisTemplate redisTemplate(RedisConnectionFactory factory){
  13. RedisTemplate redisTemplate = new RedisTemplate();
  14. redisTemplate.setConnectionFactory(factory);
  15. //1. 设置key的序列化器
  16. redisTemplate.setKeySerializer(new StringRedisSerializer());
  17. //2. 设置value的序列化器
  18. redisTemplate.setValueSerializer(new GenericFastJsonRedisSerializer());
  19. // redisTemplate.setValueSerializer(new GenericJackson2JsonRedisSerializer());
  20. //3. 如果值是hash,需要使用如下的方式设置序列化器(redis中的hash类型中的小key与小value的序列化)
  21. redisTemplate.setHashKeySerializer(new FastJsonRedisSerializer(Long.class));
  22. redisTemplate.setHashValueSerializer(new GenericJackson2JsonRedisSerializer());
  23. return redisTemplate;
  24. }
  25. }

重点强调: 1、序列化不光时在存放时要进行,在从redis中取数据时,也需要执行同样的序列化,否则,得不到值!

4.2.4 在zyg-search-service中,进行的redis的序列化:

  1. /**
  2. * ------------------------------
  3. * 功能:
  4. * 作者:WF
  5. * 微信:hbxfwf13590332912
  6. * 创建时间:2021/7/31-21:55
  7. * ------------------------------
  8. */
  9. @Configuration
  10. public class RedisTemplateSerializerConfig {
  11. @Bean
  12. public RedisTemplate redisTemplate(RedisConnectionFactory factory){
  13. RedisTemplate redisTemplate = new RedisTemplate();
  14. redisTemplate.setConnectionFactory(factory);
  15. //1. 设置key的序列化器
  16. redisTemplate.setKeySerializer(new StringRedisSerializer());
  17. //2. 设置value的序列化器
  18. redisTemplate.setValueSerializer(new GenericFastJsonRedisSerializer());
  19. // redisTemplate.setValueSerializer(new GenericJackson2JsonRedisSerializer());
  20. //3. 如果值是hash,需要使用如下的方式设置序列化器(redis中的hash类型中的小key与小value的序列化)
  21. redisTemplate.setHashKeySerializer(new FastJsonRedisSerializer(Long.class));
  22. redisTemplate.setHashValueSerializer(new GenericJackson2JsonRedisSerializer());
  23. return redisTemplate;
  24. }
  25. }

4.2.5 执行如下方法,就可以向redis存放数据了:

添加分类在redishttp://localhost:9001/itemcat/findByParentId/0 添加品牌及规格在redishttp://localhost:9001/typetemplate/list

4.3 展示品牌及规格:

4.3.1 在itemSearchServiceImpl中定义从redis中得到品牌及规格列表

  1. /**
  2. * 功能: 根据分类名称得到品牌列表及规格列表
  3. * 参数:
  4. * 返回值: java.util.Map
  5. * 时间: 2021/8/3 8:39
  6. */
  7. private Map findBrandAndSpecList(String category) {
  8. //1. 根据分类名称得到模板id
  9. Long typeId = (Long) redisTemplate.boundHashOps("itemCats").get(category);
  10. //2. 根据模板id得到品牌列表
  11. List<Map> brandList = (List<Map>) redisTemplate.boundHashOps("brandList").get(typeId);
  12. //3. 根据模板id得到规格列表
  13. List<Map> specList = (List<Map>) redisTemplate.boundHashOps("specList").get(typeId);
  14. //4. 定义结果map
  15. Map brandAndSpecmap = new HashMap();
  16. //5. 将上面得到的品牌列表及规格列表都放到map中
  17. brandAndSpecmap.put("brandList",brandList);
  18. brandAndSpecmap.put("specList",specList);
  19. //6. 返回
  20. return brandAndSpecmap;
  21. }

4.3.2 在itemSearchServiceImpl的search()方法中调用:

image.png

4.3.3 在前端展示页面:

image.png
image.png

4.3.4 最后的页面效果:

image.png

4.3.5 定义用于在前端传递数据到后端的实体类:

  1. @Data
  2. @AllArgsConstructor
  3. @NoArgsConstructor
  4. public class ItemVo implements Serializable {
  5. //1. 查询关键字对象
  6. private String keywords;
  7. }

4.4 进行高亮查询:

4.4.1 前端:

image.png

4.4.2 后端itemSearchServiceImpl

  1. @Service
  2. public class ItemSearchServiceImpl implements ItemSearchService {
  3. @Autowired
  4. private RedisTemplate redisTemplate;
  5. @Autowired
  6. private ElasticsearchRestTemplate restTemplate;
  7. /**
  8. * 功能: 根据查询参数得到查询结果
  9. * 参数:
  10. * 返回值: java.util.Map
  11. * 时间: 2021/8/2 16:06
  12. * @param params
  13. */
  14. @Override
  15. public Map<String, Object> search(ItemVo params) {
  16. //1. 得到查询关键字
  17. String keyword = params.getKeywords();
  18. if(StringUtils.isBlank(keyword)){
  19. keyword = "华为";
  20. }
  21. System.out.println("keyword = " + keyword);
  22. //2. 定义返回结果
  23. Map<String, Object> resultMap = new HashMap<>();
  24. //3. 定义NativeSearchQueryBuilder对象
  25. NativeSearchQueryBuilder searchQueryBuilder = new NativeSearchQueryBuilder()
  26. .withPageable(PageRequest.of(0,10));
  27. //4. 按关键字进行分组查询
  28. searchQueryBuilder.addAggregation(AggregationBuilders.terms("categoryGroup")
  29. .field("category.keyword").size(50));
  30. //5. 添加高亮查询
  31. //5.1 设置高亮查询
  32. searchQueryBuilder.withHighlightBuilder(new HighlightBuilder()
  33. .field("title") //设置高亮字段
  34. .preTags("<span style='color:red'>") //设置高亮显示内容的前缀部分
  35. .postTags("</span>")); //设置高亮显示内容的后缀部分
  36. //5. 添加查询
  37. searchQueryBuilder.withQuery(QueryBuilders.multiMatchQuery( keyword,"title","brand","category"));
  38. //6. 得到查询对象
  39. NativeSearchQuery searchQuery = searchQueryBuilder.build();
  40. //7. 得到命中对象
  41. SearchHits<ItemEntity> searchHits = restTemplate.search(searchQuery, ItemEntity.class, IndexCoordinates.of("item"));
  42. //8. 得到命中的结果
  43. List<SearchHit<ItemEntity>> list = searchHits.getSearchHits();
  44. //9. 得到命中结果的选项
  45. long total = searchHits.getTotalHits(); //总记录数
  46. int totalPage = (int) Math.ceil(total/10.0);//总页数
  47. //10. 处理命中结果(原始的关键字查询)
  48. //List<ItemEntity> collect = list.stream().map(m -> m.getContent()).collect(Collectors.toList());
  49. //11. 得到分组数据
  50. Aggregations aggregations = searchHits.getAggregations();
  51. //12. 得到关于分类的分组
  52. ParsedStringTerms categoryGroupResult = aggregations.get("categoryGroup");
  53. //13. 处理分类分组的结果数据
  54. List<String> categoryList = categoryGroupResult.getBuckets().stream()
  55. .map(m -> m.getKeyAsString()).collect(Collectors.toList());
  56. System.out.println("categoryList = " + categoryList);
  57. //14. 得到高亮查询的结果
  58. //14.1 定义存放高亮字段的内容
  59. List<ItemEntity> highlights = new ArrayList<>();
  60. //14.2 遍历所有命中的数据,从中挑选出高亮数据
  61. for (SearchHit<ItemEntity> searchHit : list) { //list: 所有命中的结果对象
  62. //14.3 可以直接通过高亮字段名称得到此高亮字段的值
  63. List<String> title = searchHit.getHighlightField("title");
  64. //14.4 得到未高亮前的数据
  65. ItemEntity itemEntity = searchHit.getContent();
  66. //14.5 定义存放高亮字段的字符串
  67. StringBuffer buffer = new StringBuffer();
  68. //14.6 组合高亮字段的值
  69. for (String s : title) {
  70. buffer.append(s);
  71. }
  72. //14.7 将高亮字段的值重新设置回原来的对象
  73. itemEntity.setTitle(buffer.toString());
  74. //14.8 将高亮对象添加到集合中
  75. highlights.add(itemEntity);
  76. }
  77. System.out.println("highlights = " + highlights);
  78. //15. 从redis中得到品牌及规格列表
  79. Map brandAndSpecMap = new HashMap();
  80. //16. 得到用户选择的分类
  81. String category = params.getCategory();
  82. if(StringUtils.isBlank(category)){
  83. if(categoryList != null && categoryList.size() > 0) {
  84. category = categoryList.get(0);
  85. }
  86. }
  87. //17. 根据分类进行查询品牌及规格
  88. brandAndSpecMap = findBrandAndSpecList(category);
  89. //18. 将品牌及规格列表放到大集合中
  90. resultMap.putAll(brandAndSpecMap);
  91. resultMap.put("rows",highlights); //当前分页记录集合
  92. resultMap.put("total",total); //总记录数
  93. resultMap.put("categoryList",categoryList); //分类
  94. resultMap.put("totalPage",totalPage); //总页数
  95. return resultMap;
  96. }
  97. /**
  98. * 功能: 根据分类名称得到品牌列表及规格列表
  99. * 参数:
  100. * 返回值: java.util.Map
  101. * 时间: 2021/8/3 8:39
  102. */
  103. private Map findBrandAndSpecList(String category) {
  104. //1. 根据分类名称得到模板id
  105. Object typeId = redisTemplate.boundHashOps("itemCats").get(category);
  106. //2. 根据模板id得到品牌列表
  107. List<Map> brandList = (List<Map>) redisTemplate.boundHashOps("brandList").get(typeId);
  108. //3. 根据模板id得到规格列表
  109. List<Map> specList = (List<Map>) redisTemplate.boundHashOps("specList").get(typeId);
  110. //4. 定义结果map
  111. Map brandAndSpecmap = new HashMap();
  112. //5. 将上面得到的品牌列表及规格列表都放到map中
  113. brandAndSpecmap.put("brandList",brandList);
  114. brandAndSpecmap.put("specList",specList);
  115. //6. 返回
  116. return brandAndSpecmap;
  117. }
  118. }

4.4.3 查看结果:

image.png