【黑马头条】-day07APP端文章搜索-ES-mongoDB

news2024/11/24 5:40:27

文章目录

  • 今日内容
  • 1 搭建es环境
    • 1.1 拉取es镜像
    • 1.2 创建容器
    • 1.3 配置中文分词器ik
    • 1.4 测试
  • 2 app文章搜索
    • 2.1 需求说明
    • 2.2 思路分析
    • 2.3 创建索引和映射
      • 2.3.1 PUT请求添加映射
      • 2.3.2 其他操作
    • 2.4 初始化索引库数据
      • 2.4.1 导入es-init
      • 2.4.2 es-init配置
      • 2.4.3 导入数据
      • 2.4.4 查询已导入的文档
    • 2.5 接口定义
      • 2.5.1 UserSearchDto
    • 2.6 app端文章搜索项目准备
      • 2.6.1 导入heima-leadnews-search
      • 2.6.2 导入依赖
      • 2.6.3 导入配置
    • 2.7 实现app端文章搜索
      • 2.7.1 Controller接口定义
      • 2.7.2 业务层
      • 2.7.3 为app端文章搜索添加网关
      • 2.7.3 测试
  • 3 新增文章创建索引
    • 3.1 思路分析
    • 3.2 SearchArticleVo
    • 3.3 创建kafka的topic
    • 3.3 修改文章微服务配置
    • 3.4 静态文件路径生成后文章生产通知
    • 3.5 搜索微服务监听消息
      • 3.5.1 修改搜索微服务配置
      • 3.5.2 定义监听消息
    • 3.6 综合测试
  • 4 app端搜索-保存搜索记录
    • 4.1 需求说明
    • 4.2 安装mongoDB
      • 4.2.1 拉取镜像
      • 4.2.2 创建容器
      • 4.2.3 本地连接mongodb
    • 4.3 SpringBoot集成mongoDB
      • 4.3.1 导入资料中的mongo-demo
      • 4.3.2 导入依赖
      • 4.3.3. 配置mongoDB
      • 4.3.4 添加表映射
      • 4.3.5 核心方法
        • 4.3.5.1 保存
        • 4.3.5.2 查询
        • 4.3.5.3 条件查询
        • 4.3.5.4 删除
    • 4.4 保存搜索记录
      • 4.4.1 实现思路
      • 4.4.2 为搜索微服务添加mongoDB
      • 4.4.3 在nacos中配置mongoDB
      • 4.4.4 运行sql脚本
      • 4.4.5 导入对应的表的实体类
      • 4.4.6 Service
      • 4.4.7 过滤器解析token获取id放入头部,拦截器将id存入线程
      • 4.4.8 Search微服务也采用这样的方法
      • 4.4.9 异步调用保存搜索记录
    • 4.5 测试
  • 5 app端搜索-加载搜索历史
    • 5.1 接口
    • 5.2 Controller
    • 5.3 Service
    • 5.4 测试
  • 6 app端搜索-删除搜索历史
    • 6.1 接口
    • 6.2 Dto
    • 6.3 Controller
    • 6.4 Service
    • 6.5 测试
  • 7 app端搜索-关键字联想功能
    • 7.1 需求分析
    • 7.2 接口定义
    • 7.3 自动补全py插件
    • 7.4 自定义分词器


今日内容

在这里插入图片描述

在这里插入图片描述

1 搭建es环境

1.1 拉取es镜像

docker pull elasticsearch:7.4.0

1.2 创建容器

docker run -id \
--name es -d --restart=always \
-p 9200:9200 \
-p 9300:9300 \
-v /usr/share/elasticsearch/plugins:/usr/share/elasticsearch/plugins \
-e "discovery.type=single-node" \
elasticsearch:7.4.0

1.3 配置中文分词器ik

把资料中的elasticsearch-analysis-ik-7.4.0.zip上传到服务器上,放到对应目录(plugins)解压

#切换目录
cd /usr/share/elasticsearch/plugins
#新建目录
mkdir analysis-ik
cd analysis-ik
#root根目录中拷贝文件
mv elasticsearch-analysis-ik-7.4.0.zip /usr/share/elasticsearch/plugins/analysis-ik
#解压文件
cd /usr/share/elasticsearch/plugins/analysis-ik
unzip elasticsearch-analysis-ik-7.4.0.zip

在这里插入图片描述

并且重启当前es容器

docker restart es

1.4 测试

发送post请求 192.168.204.129:9200/_analyze

在这里插入图片描述

{
    "analyzer":"ik_max_word",
    "text":"欢迎来到黑马学习java"
}

返回

{
    "tokens": [
        {
            "token": "欢迎",
            "start_offset": 0,
            "end_offset": 2,
            "type": "CN_WORD",
            "position": 0
        },
        {
            "token": "迎来",
            "start_offset": 1,
            "end_offset": 3,
            "type": "CN_WORD",
            "position": 1
        },
        {
            "token": "来到",
            "start_offset": 2,
            "end_offset": 4,
            "type": "CN_WORD",
            "position": 2
        },
        {
            "token": "黑马",
            "start_offset": 4,
            "end_offset": 6,
            "type": "CN_WORD",
            "position": 3
        },
        {
            "token": "学习",
            "start_offset": 6,
            "end_offset": 8,
            "type": "CN_WORD",
            "position": 4
        },
        {
            "token": "java",
            "start_offset": 8,
            "end_offset": 12,
            "type": "ENGLISH",
            "position": 5
        }
    ]
}

2 app文章搜索

2.1 需求说明

在这里插入图片描述

2.2 思路分析

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

2.3 创建索引和映射

2.3.1 PUT请求添加映射

PUT请求:192.168.204.129:9200/app_info_article

请求体设置映射

{
    "mappings":{
        "properties":{
            "id":{
                "type":"long"
            },
            "publishTime":{
                "type":"date"
            },
            "layout":{
                "type":"integer"
            },
            "images":{
                "type":"keyword",
                "index": false
            },
            "staticUrl":{
                "type":"keyword",
                "index": false
            },
            "authorId": {
                "type": "long"
            },
            "authorName": {
                "type": "text"
            },
            "title":{
                "type":"text",
                "analyzer":"ik_smart"
            },
            "content":{
                "type":"text",
                "analyzer":"ik_smart"
            }
        }
    }
}

返回:

{
    "acknowledged": true,
    "shards_acknowledged": true,
    "index": "app_info_article"
}

2.3.2 其他操作

GET请求查询映射:192.168.204.129:9200/app_info_article

{
    "app_info_article": {
        "aliases": {},
        "mappings": {
            "properties": {
                "authorId": {
                    "type": "long"
                },
                "authorName": {
                    "type": "text"
                },
                "content": {
                    "type": "text",
                    "analyzer": "ik_smart"
                },
                "id": {
                    "type": "long"
                },
                "images": {
                    "type": "keyword",
                    "index": false
                },
                "layout": {
                    "type": "integer"
                },
                "publishTime": {
                    "type": "date"
                },
                "staticUrl": {
                    "type": "keyword",
                    "index": false
                },
                "title": {
                    "type": "text",
                    "analyzer": "ik_smart"
                }
            }
        },
        "settings": {
            "index": {
                "creation_date": "1712574364409",
                "number_of_shards": "1",
                "number_of_replicas": "1",
                "uuid": "IPlVoSqUSOm5dRBfJmaV_A",
                "version": {
                    "created": "7040099"
                },
                "provided_name": "app_info_article"
            }
        }
    }
}

DELETE请求,删除索引及映射:192.168.204.129:9200/app_info_article

GET请求,查询所有文档:192.168.204.129:9200/app_info_article/_search

2.4 初始化索引库数据

2.4.1 导入es-init

将es-init导入heima-leadnews-testk模块中

在这里插入图片描述

将其引入heima-leadnews-testk模块的pom文件中

<modules>
    <module>freemarker-demo</module>
    <module>minio-demo</module>
    <module>tess4j-demo</module>
    <module>kafka-demo</module>
    <module>es-init</module>
</modules>

2.4.2 es-init配置

在这里插入图片描述

通过配置类进行RestHighLevelClient的初始化

2.4.3 导入数据

在com.heima.es.ApArticleTest中编写测试方法导入数据

先创建BulkRequest,在把一条条数据组成IndexRequest再放到BulkRequest中,在用RestHighLevelClient的bulk方法批量添加

@SpringBootTest
@RunWith(SpringRunner.class)
public class ApArticleTest {
    @Autowired
    private ApArticleMapper apArticleMapper;
    @Autowired
    private RestHighLevelClient restHighLevelClient;

    /**
     * 注意:数据量的导入,如果数据量过大,需要分页导入
     * @throws Exception
     */
    @Test
    public void init() throws Exception {
        //1. 查询文章列表
        List<SearchArticleVo> searchArticleVos = apArticleMapper.loadArticleList();

        //2. 将数据导入到es中
        BulkRequest bulkRequest = new BulkRequest("app_info_article");
        for(SearchArticleVo searchArticleVo : searchArticleVos){
            IndexRequest indexRequest = new IndexRequest()
                    .id(searchArticleVo.getId().toString())
                    .source(JSON.toJSONString(searchArticleVo), XContentType.JSON);
            //添加到批量请求中
            bulkRequest.add(indexRequest);
        }
        restHighLevelClient.bulk(bulkRequest, RequestOptions.DEFAULT);
    }
}

2.4.4 查询已导入的文档

GET请求,查询所有文档:192.168.204.129:9200/app_info_article/_search,有23条数据

在这里插入图片描述

2.5 接口定义

在这里插入图片描述

2.5.1 UserSearchDto

@Data
public class UserSearchDto {
    /**
    * 搜索关键字
    */
    String searchWords;
    /**
    * 当前页
    */
    int pageNum;
    /**
    * 分页条数
    */
    int pageSize;
    /**
    * 最小时间
    */
    Date minBehotTime;
 
    public int getFromIndex(){
        if(this.pageNum<1)return 0;
        if(this.pageSize<1) this.pageSize = 10;
        return this.pageSize * (pageNum-1);
    }
}

2.6 app端文章搜索项目准备

2.6.1 导入heima-leadnews-search

导入heima-leadnews-search到heima-leadnews-service中

在这里插入图片描述

引入heima-leadnews-search到pom文件中

2.6.2 导入依赖

在heima-leadnews-service的pom中添加依赖

<!--elasticsearch-->
<dependency>
    <groupId>org.elasticsearch.client</groupId>
    <artifactId>elasticsearch-rest-high-level-client</artifactId>
    <version>7.4.0</version>
</dependency>
<dependency>
    <groupId>org.elasticsearch.client</groupId>
    <artifactId>elasticsearch-rest-client</artifactId>
    <version>7.4.0</version>
</dependency>
<dependency>
    <groupId>org.elasticsearch</groupId>
    <artifactId>elasticsearch</artifactId>
    <version>7.4.0</version>
</dependency>

2.6.3 导入配置

server:
  port: 51804
spring:
  application:
    name: leadnews-search
  cloud:
    nacos:
      discovery:
        server-addr: 192.168.204.129:8848
      config:
        server-addr: 192.168.204.129:8848
        file-extension: yml

这里并没有数据库相关配置,需要在nacos配置,在nacos中创建leadnews-search

因为暂时不需要数据库,所以取消掉DataSourceAutoConfiguration自动配置类

spring:
  autoconfigure:
    exclude: org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration
elasticsearch:
  host: 192.168.204.129
  port: 9200
minio:
  accessKey: minio
  secretKey: minio123
  bucket: leadnews
  endpoint: http://192.168.204.129:9000
  readPath: http://192.168.204.129:9000

在这里插入图片描述

2.7 实现app端文章搜索

2.7.1 Controller接口定义

创建com.heima.search.controller.v1.ArticleSearchController

@RestController
@RequestMapping("/api/v1/article/search")
public class ArticleSearchController {
    @Autowired
    private ArticleSearchService articleSearchService;
    @PostMapping("/search")
    public ResponseResult search(@RequestBody UserSearchDto userSearchDto) {
        return articleSearchService.search(userSearchDto);
    }
}

2.7.2 业务层

创建com.heima.search.service.ArticleSearchService接口

public interface ArticleSearchService {
    ResponseResult search(UserSearchDto userSearchDto);
}

实现

@Service
@Slf4j
public class ArticleSearchServiceImpl implements ArticleSearchService  {
    @Autowired
    private RestHighLevelClient restHighLevelClient;
    @Override
    public ResponseResult search(UserSearchDto userSearchDto) {
        //1. 检查参数
        if(userSearchDto == null|| StringUtils.isBlank(userSearchDto.getSearchWords())){
            return ResponseResult.errorResult(AppHttpCodeEnum.PARAM_INVALID,"参数不合法");
        }
        //2. 设置查询条件
        SearchRequest searchRequest = new SearchRequest("app_info_article");
        SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();

        //复合查询需要使用boolQuery
        BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery();
        //2.1 关键词的分词之查询
        QueryStringQueryBuilder queryStringQueryBuilder = QueryBuilders.queryStringQuery(userSearchDto
                .getSearchWords())
                .field("title")
                .field("content")
                .defaultOperator(Operator.OR);
        boolQueryBuilder.must(queryStringQueryBuilder);
        //2.2 查询小于mindate的数据
        RangeQueryBuilder publishTime = QueryBuilders.rangeQuery("publishTime").lt(userSearchDto.getMinBehotTime().getTime());
        boolQueryBuilder.filter(publishTime);
        //2.3 分页查询
        searchSourceBuilder.from(0).size(userSearchDto.getPageSize());
        //2.4 按照时间倒序排序
        searchSourceBuilder.sort("publishTime", SortOrder.DESC);
        //2.5 设置高亮显示title
        HighlightBuilder highlightBuilder = new HighlightBuilder();
        highlightBuilder.field("title");
        highlightBuilder.preTags("<font style='color: red; font-size: inherit;'>");
        highlightBuilder.postTags("</font>");
        searchSourceBuilder.highlighter(highlightBuilder);

        //2.6 查询
        searchSourceBuilder.query(boolQueryBuilder);
        searchRequest.source(searchSourceBuilder);
        try {
            SearchResponse response = restHighLevelClient.search(searchRequest, RequestOptions.DEFAULT);
            //3. 结果封装
            List<Map> list= new ArrayList<>();
            SearchHits searchHits= response.getHits();
            SearchHit[] hits = searchHits.getHits();
            for (SearchHit hit : hits) {
                //3.1 获取source
                String json= hit.getSourceAsString();
                Map map = JSON.parseObject(json, Map.class);
                //3.2 获取高亮数据
                Map<String, HighlightField> highlightFields = hit.getHighlightFields();
                if(!CollectionUtils.isEmpty(highlightFields)){
                    HighlightField highlightField = highlightFields.get("title");
                    if(highlightField!=null){
                        //3.3 拿到所有高亮数据
                        Text[] titles = hit.getHighlightFields().get("title").getFragments();
                        String title = StringUtils.join(titles);
                        map.put("h_title",title);
                    }
                    else{
                        map.put("h_title",map.get("title"));
                    }
                }
                list.add(map);
            }
            //4. 返回结果
            return ResponseResult.okResult(list);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }
}

2.7.3 为app端文章搜索添加网关

需要在app的网关中添加搜索微服务的路由配置

#搜索微服务
- id: leadnews-search
 uri: lb://leadnews-search
 predicates:
   - Path=/search/**
 filters:
   - StripPrefix= 1
spring:
  cloud:
    gateway:
      globalcors:
        add-to-simple-url-handler-mapping: true
        corsConfigurations:
          '[/**]':
            allowedHeaders: "*"
            allowedOrigins: "*"
            allowedMethods:
              - GET
              - POST
              - DELETE
              - PUT
              - OPTION
      routes:
        # 用户管理
        - id: user
          uri: lb://leadnews-user
          predicates:
            - Path=/user/**
          filters:
            - StripPrefix= 1
        # 文章管理
        - id: article
          uri: lb://leadnews-article
          predicates:
            - Path=/article/**
          filters:
            - StripPrefix= 1
        #搜索微服务
        - id: leadnews-search
          uri: lb://leadnews-search
          predicates:
            - Path=/search/**
          filters:
            - StripPrefix= 1

在这里插入图片描述

2.7.3 测试

启动对应微服务

在这里插入图片描述

打开 localhost:8801

在这里插入图片描述

数据显示没有问题,测试成功

在这里插入图片描述

3 新增文章创建索引

3.1 思路分析

在这里插入图片描述

3.2 SearchArticleVo

文章问微服务需要组装SearchArticleVo给搜索微服务

所以需要先定义SearchArticleVo,创建com.heima.model.search.SearchArticleVo类

@Data
public class SearchArticleVo {
 
    // 文章id
    private Long id;
    // 文章标题
    private String title;
    // 文章发布时间
    private Date publishTime;
    // 文章布局
    private Integer layout;
    // 封面
    private String images;
    // 作者id
    private Long authorId;
    // 作者名词
    private String authorName;
    //静态url
    private String staticUrl;
    //文章内容
    private String content;
}

3.3 创建kafka的topic

在com.heima.common.constants.ArticleConstants类中创建新的属性

public class ArticleConstants {
    public static final Short LOADTYPE_LOAD_MORE = 1;
    public static final Short LOADTYPE_LOAD_NEW = 2;
    public static final String DEFAULT_TAG = "__all__";

    public static final String ARTICLE_ES_INDEX_TOPIC = "article.es.syn.topic";

}

3.3 修改文章微服务配置

因为文章微服务相当于生产者,所以要更新文章微服务的nacos配置

spring:
  datasource:
    driver-class-name: com.mysql.jdbc.Driver
    url: jdbc:mysql://localhost:3306/leadnews_article?useUnicode=true&characterEncoding=UTF-8&serverTimezone=UTC&useSSL=false
    username: root
    password: 123sjbsjb
  kafka:
    bootstrap-servers: 192.168.204.129:9092
    producer:
      retries: 10
      key-serializer: org.apache.kafka.common.serialization.StringSerializer
      value-serializer: org.apache.kafka.common.serialization.StringSerializer
    consumer:
      group-id: ${spring.application.name}
      key-deserializer: org.apache.kafka.common.serialization.StringDeserializer
      value-deserializer: org.apache.kafka.common.serialization.StringDeserializer

# 设置Mapper接口所对应的XML文件位置,如果你在Mapper接口中有自定义方法,需要进行该配置
mybatis-plus:
  mapper-locations: classpath*:mapper/*.xml
  # 设置别名包扫描路径,通过该属性可以给包中的类注册别名
  type-aliases-package: com.heima.model.article.pojos
  global-config:
    datacenter-id: 1
    workerId: 1
minio:
  accessKey: minio
  secretKey: minio123
  bucket: leadnews
  endpoint: http://192.168.204.129:9000
  readPath: http://192.168.204.129:9000

在这里插入图片描述

3.4 静态文件路径生成后文章生产通知

在com.heima.article.service.impl.ArticleFreemarkerServiceImpl类中

最后生成

//4.把静态页面的路径保存到数据库
apArticleService.update(Wrappers
        .<ApArticle>lambdaUpdate()
        .eq(ApArticle::getId,apArticle.getId())
        .set(ApArticle::getStaticUrl,path));

//5. 发送消息到kafka,创建es索引
createArticleESIndex(apArticle,content,path);

创建es索引

@Autowired
private KafkaTemplate<String,String> kafkaTemplate;

/**
 * 创建文章索引
 * @param apArticle
 * @param content
 * @param path
 */
private void createArticleESIndex(ApArticle apArticle, String content, String path) {
    SearchArticleVo searchArticleVo = new SearchArticleVo();
    BeanUtils.copyProperties(apArticle,searchArticleVo);
    searchArticleVo.setContent(content);
    searchArticleVo.setStaticUrl(path);

    kafkaTemplate.send(ArticleConstants.ARTICLE_ES_INDEX_TOPIC, JSON.toJSONString(searchArticleVo));
}

3.5 搜索微服务监听消息

3.5.1 修改搜索微服务配置

搜索微服务作为kafka的消费者,进行nacos配置

spring:
  autoconfigure:
    exclude: org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration
  kafka:
    bootstrap-servers: 192.168.204.129:9092
    consumer:
      group-id: ${spring.application.name}
      key-deserializer: org.apache.kafka.common.serialization.StringDeserializer
      value-deserializer: org.apache.kafka.common.serialization.StringDeserializer
elasticsearch:
  host: 192.168.204.129
  port: 9200
minio:
  accessKey: minio
  secretKey: minio123
  bucket: leadnews
  endpoint: http://192.168.204.129:9000
  readPath: http://192.168.204.129:9000

在这里插入图片描述

3.5.2 定义监听消息

在heima-leadnews-search模块中创建com.heima.search.listen.SyncArticleListener类

@Component
@Slf4j
public class SyncArticleListener {
    @Autowired
    private RestHighLevelClient restHighLevelClient;
    @KafkaListener(topics = ArticleConstants.ARTICLE_ES_INDEX_TOPIC)
    public void onMessage(String message){
        if(StringUtils.isNotBlank(message)){
            log.info("SyncArticleListener,message={}",message);
            SearchArticleVo searchArticleVo = JSON.parseObject(message, SearchArticleVo.class);
            IndexRequest indexRequest = new IndexRequest("app_info_article");
            indexRequest.id(searchArticleVo.getId().toString());
            indexRequest.source(message, XContentType.JSON);
            try {
                restHighLevelClient.index(indexRequest, RequestOptions.DEFAULT);
            } catch (IOException e) {
                e.printStackTrace();
                log.error("sync es error={}",e);
            }
        }
    }
}

3.6 综合测试

启动es、kafka、redis、rabbitmq、minio、nacos、zookeeper

在这里插入图片描述

再启动相应启动类

在这里插入图片描述

现在app端显示

在这里插入图片描述

在自媒体端添加测试:黑马测试搜索123

在这里插入图片描述

点击发布,自动审核成功

在这里插入图片描述

重新加载app端,此时审核已上架的文章已经显示

在这里插入图片描述

搜索栏中尝试搜搜

在这里插入图片描述

成功搜索到

查看SearchApplication的日志显示

2024-04-09 13:41:44.683  INFO 16292 --- [ntainer#0-0-C-1] c.h.search.listen.SyncArticleListener    : SyncArticleListener,message={"authorId":1102,"authorName":"admin","content":"[{\"type\":\"image\",\"value\":\"http://192.168.204.129:9000/leadnews/2024/03/25/40ecf5d3f9084e1094b5469fe48f7603.jpg\"},{\"type\":\"text\",\"value\":\"请在这里输入正文\"}]","id":1777572583391236097,"images":"http://192.168.204.129:9000/leadnews/2024/03/25/40ecf5d3f9084e1094b5469fe48f7603.jpg","publishTime":1712641152000,"staticUrl":"http://192.168.204.129:9000/leadnews/2024/04/09/1777572583391236097.html","title":"黑马测试搜索123"}

说明我们的测试非常成功。

4 app端搜索-保存搜索记录

4.1 需求说明

在这里插入图片描述

在这里插入图片描述

4.2 安装mongoDB

4.2.1 拉取镜像

docker pull mongo

4.2.2 创建容器

docker run -di --name mongo \
--restart=always \
-p 27017:27017 \
-v ~/data/mongodata:/data mongo

4.2.3 本地连接mongodb

使用navicat,,同时创建leadnews-history数据库

在这里插入图片描述

在这里插入图片描述

4.3 SpringBoot集成mongoDB

4.3.1 导入资料中的mongo-demo

导入资料中的mongo-demo并将其导入test模块中的pom文件里

在这里插入图片描述

4.3.2 导入依赖

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-mongodb</artifactId>
</dependency>

4.3.3. 配置mongoDB

创建application.yaml

server:
  port: 9998
spring:
  data:
    mongodb:
      host: 192.168.204.129
      port: 27017
      database: leadnews-history

4.3.4 添加表映射

@Data
@Document("ap_associate_words")
public class ApAssociateWords implements Serializable {

    private static final long serialVersionUID = 1L;

    private String id;

    /**
     * 联想词
     */
    private String associateWords;

    /**
     * 创建时间
     */
    private Date createdTime;

}

4.3.5 核心方法

在这里插入图片描述

com.itheima.mongo.test.MongoTest测试方法

@SpringBootTest(classes = MongoApplication.class)
@RunWith(SpringRunner.class)
public class MongoTest {


    @Autowired
    private MongoTemplate mongoTemplate;

    //保存,会自动创建相应的表结构
    @Test
    public void saveTest(){
        ApAssociateWords apAssociateWords = new ApAssociateWords();
        apAssociateWords.setAssociateWords("黑马头条");
        apAssociateWords.setCreatedTime(new Date());
        mongoTemplate.save(apAssociateWords);
    }

    //查询一个
    @Test
    public void saveFindOne(){
        ApAssociateWords apAssociateWords = mongoTemplate.findById("6614e7011f52f0112ac7df19", ApAssociateWords.class);
        System.out.println(apAssociateWords);
    }

    //条件查询
    @Test
    public void testQuery(){
        Query query = Query.query(Criteria.where("associateWords").is("黑马头条"))
                .with(Sort.by(Sort.Direction.DESC,"createdTime"));
        List<ApAssociateWords> apAssociateWordsList = mongoTemplate.find(query, ApAssociateWords.class);
        System.out.println(apAssociateWordsList);
    }

    //删除
    @Test
    public void testDel(){
        mongoTemplate.remove(Query.query(Criteria.where("associateWords").is("黑马头条")),ApAssociateWords.class);
    }
}
4.3.5.1 保存
//保存
@Test
public void saveTest(){
    ApAssociateWords apAssociateWords = new ApAssociateWords();
    apAssociateWords.setAssociateWords("黑马头条");
    apAssociateWords.setCreatedTime(new Date());
    mongoTemplate.save(apAssociateWords);
}

在这里插入图片描述

4.3.5.2 查询
//查询一个
@Test
public void saveFindOne(){
    ApAssociateWords apAssociateWords = mongoTemplate.findById("6614e7011f52f0112ac7df19", ApAssociateWords.class);
    System.out.println(apAssociateWords);
}
2024-04-09 15:04:51.338  INFO 13360 --- [           main] o.m.d.connection                         : Opened connection [connectionId{localValue:2, serverValue:16}] to 192.168.204.129:27017
[ApAssociateWords(id=6614e7011f52f0112ac7df19, associateWords=黑马头条, createdTime=Tue Apr 09 14:58:08 CST 2024)]
4.3.5.3 条件查询
//条件查询
@Test
public void testQuery(){
    Query query = Query.query(Criteria.where("associateWords").is("黑马头条"))
            .with(Sort.by(Sort.Direction.DESC,"createdTime"));
    List<ApAssociateWords> apAssociateWordsList = mongoTemplate.find(query, ApAssociateWords.class);
    System.out.println(apAssociateWordsList);
}
2024-04-09 15:06:09.968  INFO 13672 --- [           main] o.m.d.connection                         : Opened connection [connectionId{localValue:2, serverValue:18}] to 192.168.204.129:27017
[ApAssociateWords(id=6614e7011f52f0112ac7df19, associateWords=黑马头条, createdTime=Tue Apr 09 14:58:08 CST 2024)]
4.3.5.4 删除
//删除
@Test
public void testDel(){
    mongoTemplate.remove(Query.query(Criteria.where("associateWords").is("黑马头条")),ApAssociateWords.class);
}

4.4 保存搜索记录

4.4.1 实现思路

在这里插入图片描述

在这里插入图片描述

4.4.2 为搜索微服务添加mongoDB

为heima-leadnews-search搜索微服务添加mongoDB

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-mongodb</artifactId>
</dependency>

4.4.3 在nacos中配置mongoDB

spring:
  data:
   mongodb:
    host: 192.168.204.129
    port: 27017
    database: leadnews-history
  kafka:
    bootstrap-servers: 192.168.204.129:9092
    producer:
      retries: 10
      key-serializer: org.apache.kafka.common.serialization.StringSerializer
      value-serializer: org.apache.kafka.common.serialization.StringSerializer
    consumer:
      group-id: ${spring.application.name}
      key-deserializer: org.apache.kafka.common.serialization.StringDeserializer
      value-deserializer: org.apache.kafka.common.serialization.StringDeserializer
  autoconfigure:
    exclude: org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration
elasticsearch:
  host: 192.168.204.129
  port: 9200
minio:
  accessKey: minio
  secretKey: minio123
  bucket: leadnews
  endpoint: http://192.168.204.129:9000
  readPath: http://192.168.204.129:9000

在这里插入图片描述

4.4.4 运行sql脚本

在这里插入图片描述

4.4.5 导入对应的表的实体类

@Data
@Document("ap_user_search")
public class ApUserSearch implements Serializable {
    private static final long serialVersionUID = 1L;
    /**
     * 主键
     */
    private String id;
    /**
     * 用户ID
     */
    private Integer userId;
    /**
     * 搜索词
     */
    private String keyword;
    /**
     * 创建时间
     */
    private Date createdTime;
}
@Data
@Document("ap_associate_words")
public class ApAssociateWords implements Serializable {
    private static final long serialVersionUID = 1L;
    private String id;
    /**
     * 联想词
     */
    private String associateWords;
    /**
     * 创建时间
     */
    private Date createdTime;
}

4.4.6 Service

接口

public interface ApUserSearchService {
    /**
     * 保存用户搜索历史记录
     * @param keyword
     * @param userId
     */
    void insert(String keyword,Integer userId);
}

实现

@Service
@Slf4j
public class ApUserSearchServiceImpl implements ApUserSearchService {
    @Autowired
    private MongoTemplate mongoTemplate;
    @Override
    public void insert(String keyword, Integer userId) {
        //1. 查询用户搜索历史记录
        Query query = Query.query(Criteria.where("userId").is(userId)
                .and("keyword").is(keyword));
        ApUserSearch apUserSearch = mongoTemplate.findOne(query, ApUserSearch.class);

        //2. 存在则更新时间
        if(apUserSearch != null) {
            apUserSearch.setCreatedTime(new Date());
            mongoTemplate.save(apUserSearch);
            return;
        }
        //3. 不存在则插入,判断是否超过10条,超过则删除最早的一条
        long count = mongoTemplate.count(Query.query(Criteria.where("userId").is(userId)), ApUserSearch.class);
        if(count >= 10) {
            Query query1 = Query.query(Criteria.where("userId").is(userId))
                    .with(Sort.by(Sort.Order.asc("createdTime"))).limit(1);
            ApUserSearch apUserSearch1 = mongoTemplate.findOne(query1, ApUserSearch.class);
            mongoTemplate.remove(apUserSearch1);
        }
        else{
            ApUserSearch apUserSearch1 = new ApUserSearch();
            apUserSearch1.setUserId(userId);
            apUserSearch1.setKeyword(keyword);
            apUserSearch1.setCreatedTime(new Date());
            mongoTemplate.save(apUserSearch1);
        }
    }
}

4.4.7 过滤器解析token获取id放入头部,拦截器将id存入线程

针对每个用户的保存历史记录,需要放在对应的userid下

在gateway网关中通过过滤器fliter解析token获取的jwt令牌获取用户id

@Component
@Slf4j
public class AuthorizeFilter implements Ordered, GlobalFilter {
    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        //1.获取Request对象和Response对象
        ServerHttpRequest request = exchange.getRequest();
        ServerHttpResponse response = exchange.getResponse();
        //2.判断当前请求是否为登录请求,如果是,直接放行
        if (request.getURI().getPath().contains("/login")) {
            //放行
            return chain.filter(exchange);
        }
        //3.获取当前请求的token信息
        String token = request.getHeaders().getFirst("token");
        //4.判断token是否存在
        if(StringUtils.isBlank(token)) {
            response.setStatusCode(HttpStatus.UNAUTHORIZED);
            return response.setComplete();
        }
        //5.判断token是否有效
        //5.1 解析token
        try{
            Claims body = AppJwtUtil.getClaimsBody(token);
            //5.2 判断token是否有效
            int result = AppJwtUtil.verifyToken(body);
            if(result == 1||result == 2) {
                //5.3 token过期
                response.setStatusCode(HttpStatus.UNAUTHORIZED);
                return response.setComplete();
            }
            //获取用户信息
            Integer userId = (Integer) body.get("id");
            //将用户信息放入到header中
            ServerHttpRequest serverHttpRequest = request.mutate().headers(httpHeaders -> {
                httpHeaders.add("userId", userId + "");
            }).build();
            //重置请求
            exchange.mutate().request(serverHttpRequest);
        }catch (Exception e) {
            e.printStackTrace();
            //5.4 token无效
            response.setStatusCode(HttpStatus.UNAUTHORIZED);
            return response.setComplete();
        }
        //6.放行
        return chain.filter(exchange);
    }

解析完token后,将其发在httpheaders的头部,重置请求request

然后再通过拦截器解析request获取userid,再存入线程中

public class WmTokenInterceptor implements HandlerInterceptor {
    /**
     * 拦截器的前置方法,得到header中的用户信息,存入到当前线程中
     * @param request
     * @param response
     * @param handler
     * @return
     * @throws Exception
     */
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        String userId = request.getHeader("userId");
        if(userId != null){
            //存入当前线程
            WmUser wmUser = new WmUser();
            wmUser.setId(Integer.valueOf(userId));
            WmThreadLocalUtil.setUser(wmUser);
        }
        return true;
    }

    /**
     * 后置方法,清除当前线程中的用户信息
     * @param request
     * @param response
     * @param handler
     * @param ex
     * @throws Exception
     */
    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        WmThreadLocalUtil.clear();
    }
}

4.4.8 Search微服务也采用这样的方法

在WmTokenInterceptor中存放userid的是WmThreadLocalUtil,search微服务也需要一个线程空间来存放userid,自然创建AppThreadLocalUtil,在heima-leadnews-utils模块下创建com.heima.utils.thread.AppThreadLocalUtil类

public class AppThreadLocalUtil {
    private static final ThreadLocal<ApUser> APP_USER_THREAD_LOCAL = new ThreadLocal<>();
    public static void setUser(ApUser user) {
        APP_USER_THREAD_LOCAL.set(user);
    }
    public static ApUser getUser() {
        return APP_USER_THREAD_LOCAL.get();
    }
    public static void clear() {
        APP_USER_THREAD_LOCAL.remove();
    }
}

在heima-leadnews-search模块中添加com.heima.search.interceptor.AppTokenInterceptor类

public class AppTokenInterceptor implements HandlerInterceptor {
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        String userId = request.getHeader("userId");
        if(userId != null){
            //存入当前线程
            ApUser apUser = new ApUser();
            apUser.setId(Integer.valueOf(userId));
            AppThreadLocalUtil.setUser(apUser);
        }
        return true;
    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        AppThreadLocalUtil.clear();
    }
}

要想使拦截器AppTokenInterceptor生效,需要将拦截器载入拦截器注册器里,在heima-leadnews-search模块中添加com.heima.search.config.WebMvcConfig类

@Configuration
public class WebMvcConfig implements WebMvcConfigurer {
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(new AppTokenInterceptor()).addPathPatterns("/**");
    }
}

将拦截器AppTokenInterceptor加入到拦截器注册器里,拦截所有path

4.4.9 异步调用保存搜索记录

在搜索之前就可以进行保存了,所以在com.heima.search.service.impl.ArticleSearchServiceImpl检查完参数之后就可以写进mongoDB里了,并且为apUserSearchService.insert(userSearchDto.getSearchWords(), AppThreadLocalUtil.getUser().getId());添加异步调用方法@Async

userSearchDto.getFromIndex()==0是因为只有在第一页的时候才保存,你都翻到第二页了,不用再保存一次关键字了。

@Override
public ResponseResult search(UserSearchDto userSearchDto) {
    //1. 检查参数
    if(userSearchDto == null|| StringUtils.isBlank(userSearchDto.getSearchWords())){
        return ResponseResult.errorResult(AppHttpCodeEnum.PARAM_INVALID,"参数不合法");
    }
    ApUser user=AppThreadLocalUtil.getUser();
    //1.1 异步调用保存搜索记录
    if(user!=null&& userSearchDto.getFromIndex()==0){
        apUserSearchService.insert(userSearchDto.getSearchWords(), AppThreadLocalUtil.getUser().getId());
    }
    //2. 设置查询条件
    SearchRequest searchRequest = new SearchRequest("app_info_article");
    SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();

apUserSearchService.insert(userSearchDto.getSearchWords(), AppThreadLocalUtil.getUser().getId());添加异步调用方法@Async

@Override
@Async
public void insert(String keyword, Integer userId) {
    //1. 查询用户搜索历史记录
    Query query = Query.query(Criteria.where("userId").is(userId)
            .and("keyword").is(keyword));
    ApUserSearch apUserSearch = mongoTemplate.findOne(query, ApUserSearch.class);

    //2. 存在则更新时间

并且在com.heima.search.SearchApplication启动类中添加启动异步方法@EnableAsync

@SpringBootApplication
@EnableDiscoveryClient
@EnableAsync
public class SearchApplication {

    public static void main(String[] args) {
        SpringApplication.run(SearchApplication.class,args);
    }
}

4.5 测试

启动

在这里插入图片描述

搜索黑马

在这里插入图片描述

查看mongoDB,说明插入成功

在这里插入图片描述

测试10个会不会删除

在这里插入图片描述

需求满足

5 app端搜索-加载搜索历史

5.1 接口

在这里插入图片描述

5.2 Controller

创建com.heima.search.controller.v1.ApUserSearchController

@RestController
@RequestMapping("/api/v1/history")
public class ApUserSearchController {
    @Autowired
    private ApUserSearchService apUserSearchService;
    @PostMapping("/load")
    public ResponseResult load() {
        return apUserSearchService.load();
    }
}

5.3 Service

接口:

public interface ApUserSearchService {
    public void insert(String keyword, Integer userId);

    public ResponseResult load();

}

实现

@Override
public ResponseResult load() {
    //1. 获取当前用户
    ApUser user = AppThreadLocalUtil.getUser();
    if(user == null) {
        return ResponseResult.errorResult(400, "请先登录");
    }
    //2. 查询用户搜索历史记录
    Query query = Query.query(Criteria.where("userId").is(user.getId()))
            .with(Sort.by(Sort.Order.desc("createdTime")));
    List<ApUserSearch> apUserSearches = mongoTemplate.find(query, ApUserSearch.class);
    return ResponseResult.okResult(apUserSearches);

}

5.4 测试

在这里插入图片描述

探花都在

6 app端搜索-删除搜索历史

6.1 接口

在这里插入图片描述

6.2 Dto

创建com.heima.model.search.dtos.HistorySearchDto类接收历史记录id

public class HistorySearchDto {
    /**
     * 接收历史记录id
     */
    String id;
}

6.3 Controller

@PostMapping("/del")
public ResponseResult del(@RequestBody HistorySearchDto historySearchDto) {
    return apUserSearchService.deleteHistorySearch(historySearchDto);
}

6.4 Service

接口:

ResponseResult deleteHistorySearch(HistorySearchDto historySearchDto);

实现:

@Override
public ResponseResult deleteHistorySearch(HistorySearchDto historySearchDto) {
    //1. 获取当前用户
    ApUser user = AppThreadLocalUtil.getUser();
    if(user == null) {
        return ResponseResult.errorResult(400, "请先登录");
    }
    //2. 删除用户搜索历史记录
    Query query = Query.query(Criteria.where("userId").is(user.getId())
            .and("id").is(historySearchDto.getId()));
    mongoTemplate.remove(query, ApUserSearch.class);
    return ResponseResult.okResult(AppHttpCodeEnum.SUCCESS);
}

6.5 测试

在这里插入图片描述

探花7已经没有了

查看数据库

在这里插入图片描述

永失我探花7,测试成功

7 app端搜索-关键字联想功能

7.1 需求分析

es的补全使这个数据本身在es的数据库里就有,才能补全,而这个是不管有没有都能补全。

这是与es的补全不同的地方。

我觉得老师这个不好,因为ES补全的是可以搜索到文章的,我要用ES实现。

7.2 接口定义

在这里插入图片描述

在这里插入图片描述

7.3 自动补全py插件

要实现根据字母自动补全,就必须对文章按照拼音分词。在GitHub上恰好有es的拼音分词插件,版本是7.4.0。

将py插件放到es的插件库里

在这里插入图片描述

重启es

docker restart es

测试

在这里插入图片描述

{
    "analyzer":"pinyin",
    "text":"欢迎来到黑马学习java"
}

响应

{
    "tokens": [
        {
            "token": "huan",
            "start_offset": 0,
            "end_offset": 0,
            "type": "word",
            "position": 0
        },
        {
            "token": "hyldhmxxjava",
            "start_offset": 0,
            "end_offset": 0,
            "type": "word",
            "position": 0
        },
        {
            "token": "ying",
            "start_offset": 0,
            "end_offset": 0,
            "type": "word",
            "position": 1
        },
        {
            "token": "lai",
            "start_offset": 0,
            "end_offset": 0,
            "type": "word",
            "position": 2
        },
        {
            "token": "dao",
            "start_offset": 0,
            "end_offset": 0,
            "type": "word",
            "position": 3
        },
        {
            "token": "hei",
            "start_offset": 0,
            "end_offset": 0,
            "type": "word",
            "position": 4
        },
        {
            "token": "ma",
            "start_offset": 0,
            "end_offset": 0,
            "type": "word",
            "position": 5
        },
        {
            "token": "xue",
            "start_offset": 0,
            "end_offset": 0,
            "type": "word",
            "position": 6
        },
        {
            "token": "xi",
            "start_offset": 0,
            "end_offset": 0,
            "type": "word",
            "position": 7
        },
        {
            "token": "ja",
            "start_offset": 0,
            "end_offset": 0,
            "type": "word",
            "position": 8
        },
        {
            "token": "v",
            "start_offset": 0,
            "end_offset": 0,
            "type": "word",
            "position": 9
        },
        {
            "token": "a",
            "start_offset": 0,
            "end_offset": 0,
            "type": "word",
            "position": 10
        }
    ]
}

7.4 自定义分词器

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

删除以前索引和文档

创建自定义分词器以及新的索引

在这里插入图片描述

{
  "settings": {
    "analysis": {
      "analyzer": { 
        "my_analyzer": { 
          "tokenizer": "ik_max_word",
          "filter": "py"
        },
        "completion_analyzer": {
            "tokenizer": "keyword",
          	"filter": "py"
        }
      },
      "filter": {
        "py": { 
          "type": "pinyin",
          "keep_full_pinyin": false,
          "keep_joined_full_pinyin": true,
          "keep_original": true,
          "limit_first_letter_length": 16,
          "remove_duplicated_term": true,
          "none_chinese_pinyin_tokenize": false
        }
      }
    }
  },
  "mappings":{
        "properties":{
            "id":{
                "type":"long"
            },
            "publishTime":{
                "type":"date"
            },
            "layout":{
                "type":"integer"
            },
            "images":{
                "type":"keyword",
                "index": false
            },
            "staticUrl":{
                "type":"keyword",
                "index": false
            },
            "authorId": {
                "type": "long"
            },
            "authorName": {
                "type": "text"
            },
            "title":{
                "type":"text",
                "analyzer":"my_analyzer",
                "search_analyzer":"ik_smart",
                "copy_to": "all"
            },
            "content":{
                "type":"text",
                "analyzer":"my_analyzer"
            },
            "suggestion":{
                "type":"completion",
                "analyzer":"completion_analyzer"    
            }
        }
    }
} 

因为多了suggestion字段,因此创建一个实体类接收com.heima.model.search.vos.SearchArticlewithSuggestion

@Data
public class SearchArticlewithSuggestion {

    // 文章id
    private Long id;
    // 文章标题
    private String title;
    // 文章发布时间
    private Date publishTime;
    // 文章布局
    private Integer layout;
    // 封面
    private String images;
    // 作者id
    private Long authorId;
    // 作者名词
    private String authorName;
    //静态url
    private String staticUrl;
    //文章内容
    private String content;
    //建议
    private List<String> suggestion;
    SearchArticlewithSuggestion(SearchArticleVo vo){
        BeanUtils.copyProperties(vo,this);
        this.suggestion= Arrays.asList(this.title);
    }

}

响应

{
    "acknowledged": true,
    "shards_acknowledged": true,
    "index": "app_info_article"
}

导入数据,创建新的单元测试

@Test
void testBatchInsertIndexDocument2() throws IOException {

    ///1. 查询文章列表
    List<SearchArticleVo> searchArticleVos = apArticleMapper.loadArticleList();
    //2. 将数据导入到es中
    BulkRequest bulkRequest = new BulkRequest("app_info_article");
    for(SearchArticleVo searchArticleVo : searchArticleVos){
        SearchArticlewithSuggestion searchArticleVoWithSuggestion = new SearchArticlewithSuggestion(searchArticleVo);
        IndexRequest indexRequest = new IndexRequest()
                .id(searchArticleVoWithSuggestion.getId().toString())
                .source(JSON.toJSONString(searchArticleVoWithSuggestion), XContentType.JSON);
        //添加到批量请求中
        bulkRequest.add(indexRequest);
    }
    restHighLevelClient.bulk(bulkRequest, RequestOptions.DEFAULT);
}

导入后查看数据,成功有suggestion

在这里插入图片描述

测试自动补全

{
  "suggest": {
    "title_suggest": {
      "text": "h", 
      "completion": {
        "field": "suggestion", 
        "skip_duplicates": false, 
        "size": 10 
      }
    }
  }
}

在这里插入图片描述

{
    "took": 30,
    "timed_out": false,
    "_shards": {
        "total": 1,
        "successful": 1,
        "skipped": 0,
        "failed": 0
    },
    "hits": {
        "total": {
            "value": 0,
            "relation": "eq"
        },
        "max_score": null,
        "hits": []
    },
    "suggest": {
        "title_suggest": [
            {
                "text": "h",
                "offset": 0,
                "length": 1,
                "options": [
                    {
                        "text": "黄龄工作室发视频回应",
                        "_index": "app_info_article",
                        "_type": "_doc",
                        "_id": "1302977754114826241",
                        "_score": 1.0,
                        "_source": {
                            "authorId": 4,
                            "authorName": "admin",
                            "content": "[{\"type\":\"text\",\"value\":\"3黄龄工作室发视频回应黄龄工作室发视频回应黄龄工作室发视频回应黄龄工作室发视频回应黄龄工作室发视频回应黄龄工作室发视频回应黄龄工作室发视频回应黄龄工作室发视频回应黄龄工作室发视频回应黄龄工作室发视频回应黄龄工作室发视频回应黄龄工作室发视频回应黄龄工作室发视频回应黄龄工作室发视频回应黄龄工作室发视频回应\"},{\"type\":\"image\",\"value\":\"http://192.168.200.130/group1/M00/00/00/wKjIgl892vuAXr_MAASCMYD0yzc919.jpg\"},{\"type\":\"text\",\"value\":\"请在这里输入正文黄龄工作室发视频回应黄龄工作室发视频回应黄龄工作室发视频回应黄龄工作室发视频回应黄龄工作室发视频回应黄龄工作室发视频回应黄龄工作室发视频回应黄龄工作室发视频回应黄龄工作室发视频回应黄龄工作室发视频回应黄龄工作室发视频回应黄龄工作室发视频回应\"}]",
                            "id": 1302977754114826241,
                            "images": "http://192.168.204.129:9000/leadnews/2024/03/25/71413acd3df847759c70b121ed526ff9.jpg",
                            "layout": 1,
                            "publishTime": 1599489079000,
                            "suggestion": [
                                "黄龄工作室发视频回应"
                            ],
                            "title": "黄龄工作室发视频回应"
                        }
                    },
                    {
                        "text": "黑马测试搜索123",
                        "_index": "app_info_article",
                        "_type": "_doc",
                        "_id": "1777572583391236097",
                        "_score": 1.0,
                        "_source": {
                            "authorId": 1102,
                            "authorName": "admin",
                            "content": "[{\"type\":\"image\",\"value\":\"http://192.168.204.129:9000/leadnews/2024/03/25/40ecf5d3f9084e1094b5469fe48f7603.jpg\"},{\"type\":\"text\",\"value\":\"请在这里输入正文\"}]",
                            "id": 1777572583391236097,
                            "images": "http://192.168.204.129:9000/leadnews/2024/03/25/40ecf5d3f9084e1094b5469fe48f7603.jpg",
                            "layout": 1,
                            "publishTime": 1712641152000,
                            "staticUrl": "http://192.168.204.129:9000/leadnews/2024/04/09/1777572583391236097.html",
                            "suggestion": [
                                "黑马测试搜索123"
                            ],
                            "title": "黑马测试搜索123"
                        }
                    }
                ]
            }
        ]
    }
}

只能匹配到第一个字。

分析:如果想要句中也能匹配,就要把completion_analyzer的tokenizer变成ik_max_word,保证都能覆盖到,而不是keyword作为一个整体。

在这里插入图片描述

如果想要做成黑马旅游那样,就要改前端了,这不是本节课的重点,我也就不进行了。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1581392.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Docker容器嵌入式开发:Docker Ubuntu18.04配置mysql数据库

在 Ubuntu 18.04 操作系统中安装 MySQL 数据库的过程。下面是安装过程的详细描述: 首先,使用以下命令安装 MySQL 服务器: sudo apt install mysql-server系统会提示是否继续安装,按下 Y 键确认。 安装过程中,系统会下载并安装 MySQL 相关的软件包,包括 libaio1、mysql…

ChromeDriver / Selenium-server

一、简介 ChromeDriver 是一个 WebDriver 的实现&#xff0c;专门用于自动化控制 Google Chrome 浏览器。以下是关于 ChromeDriver 的详细说明&#xff1a; 定义与作用&#xff1a; ChromeDriver 是一个独立的服务器程序&#xff0c;作为客户端库与 Google Chrome 浏览…

STM32H7通用定时器计数功能的使用

目录 概述 1 STM32定时器介绍 1.1 认识通用定时器 1.2 通用定时器的特征 1.3 递增计数模式 1.4 时钟选择 2 STM32Cube配置定时器时钟 2.1 配置定时器参数 2.2 配置定时器时钟 3 STM32H7定时器使用 3.1 认识定时器的数据结构 3.2 计数功能实现 4 测试案例 4.1 代码…

Python 基于 OpenCV 视觉图像处理实战 之 OpenCV 简单视频处理实战案例 之七 简单指定视频某片段快放效果

Python 基于 OpenCV 视觉图像处理实战 之 OpenCV 简单视频处理实战案例 之七 简单指定视频某片段快放效果 目录 Python 基于 OpenCV 视觉图像处理实战 之 OpenCV 简单视频处理实战案例 之七 简单指定视频某片段快放效果 一、简单介绍 二、简单指定视频某片段快放效果实现原理…

vue3使用jsQR解析二维码

1.了解jsQR jsQR是一个纯javascript脚本实现的二维码识别库&#xff0c;不仅可以在浏览器端使用&#xff0c;而且支持后端node.js环境。jsQR使用较为简单&#xff0c;有着不错的识别率。 2.效果图 3.二维码 4.下载jsqr包 npm i -d jsqr5.代码 <script setup> import …

STM32F407+FreeRTOS+LWIP UDP组播

开发环境介绍&#xff1a; MCU&#xff1a;STM32F407ZET6 网卡&#xff1a;LAN8720A LWIP版本&#xff1a;V1.1.0 FreeRTOS 版本&#xff1a;V10.2.1 LAN8720A硬件原理图&#xff1a; 硬件连接说明&#xff1a; MII_RX_CLK/RMII_REF_CLK ------>PA1 …

[lesson15]类与封装的概念

类与封装的概念 类的封装 类通常分为以下两个部分 类的实现细节类的使用方式 当使用类时&#xff0c;不需要关心其实现细节 当创建类时&#xff0c;才需要考虑其内部实现细节 封装的基本概念 根据经验&#xff1a;并不是类的每个属性都是对外公开的 如&#xff1a;女孩子不…

hive-3.1.2分布式搭建与hive的三种交互方式

hive-3.1.2分布式搭建&#xff1a; 一、上传解压配置环境变量 在官网或者镜像站下载驱动包 华为云镜像站地址&#xff1a; hive&#xff1a;Index of apache-local/hive/hive-3.1.2 mysql驱动包&#xff1a;Index of mysql-local/Downloads/Connector-J # 1、解压 tar -zx…

gpt科普1 GPT与搜索引擎的对比

GPT&#xff08;Generative Pre-trained Transformer&#xff09;是一种基于Transformer架构的自然语言处理模型。它通过大规模的无监督学习来预训练模型&#xff0c;在完成这个阶段后&#xff0c;可以用于各种NLP任务&#xff0c;如文本生成、机器翻译、文本分类等。 以下是关…

在Linux系统上实现TCP(socket)通信

一.什么TCP TCP&#xff08;传输控制协议&#xff09;是一种面向连接的、可靠的、基于字节流的传输层通信协议。 二.TCP通信流程 三. TCP 服务器端 1 创建socket int sockfd socket(AF_INET, SOCK_STREAM, 0); //SOCK_STREAM tcp通信2 绑定(bind) struct sockaddr_in myad…

.net 6 集成NLog

.net 6 webapi项目集成NLog 上代码step 1 添加nugetstep 2 添加支持step 3 添加配置文件 结束 上代码 step 1 添加nuget 添加nuget 包 Roc step 2 添加支持 修改program.cs var builder WebApplication.CreateBuilder(args); // 添加NLog日志支持 builder.AddRocNLog();ste…

UDP简单总结

UDP&#xff1a;用户数据报协议 特点: 无连接、不可靠通信 不事先建立连接&#xff0c;数据按照包发&#xff0c;一包数据包含&#xff1a;自己的IP、程序端口、目的地IP、程序端口和数据(限制在64KB内) 发送方不管对方是否在线&#xff0c;数据在中间丢失也不管&#xff0c;…

用友NC Cloud importhttpscer接口存在任意文件上传漏洞

声明&#xff1a; 本文仅用于技术交流&#xff0c;请勿用于非法用途 由于传播、利用此文所提供的信息而造成的任何直接或者间接的后果及损失&#xff0c;均由使用者本人负责&#xff0c;文章作者不为此承担任何责任。 简介 用友NC Cloud 是基于云计算技术的企业管理软件。它提…

监控指标体系:交互延迟上的探索与最佳实践

FID 在互联网高速发展的时代,用户体验已成为企业竞争的关键所在。网页性能作为用户体验的重要组成部分,直接影响着用户的满意度和工作效率。First Input Delay(FID)作为衡量网页性能的重要指标,越来越受到业界关注。今天,让我们一起来深入了解FID,探讨如何优化FID以提升…

GitHub 仓库 (repository) Pulse - Contributors - Network

GitHub 仓库 [repository] Pulse - Contributors - Network 1. Pulse2. Contributors3. NetworkReferences 1. Pulse 显示该仓库最近的活动信息。该仓库中的软件是无人问津&#xff0c;还是在火热地开发之中&#xff0c;从这里可以一目了然。 2. Contributors 显示对该仓库进…

51单片机之LED点阵屏

目录 1.LED点阵屏简介 2.配置LED点阵屏代码 1.LED点阵屏简介 LED点阵屏真的是遍布我们我们生活的每个角落&#xff0c;从街边的流动显示字的招牌到你的液晶显示屏&#xff0c;都是基于点阵屏的原理研究出来的。还有那个世界上最大的球状建筑物&#xff1a;MSG Sphere&#xff…

签名失败当前系统没有安装苹果根证书

发生背景 第一次我没有iOS证书(.p12)和描述文件(.mobileprovision)。按照这个文档https://ask.dcloud.net.cn/article/152拿到后&#xff0c;安心打包出问题。 错误信息 [Info] begin getCertCName... [Info] begin codesignFramwork to ipa... [Error] DCUniBase.framework…

Mac 安装 brew brew cask 遇到的问题以及解决办法

安装Homebrew和Homebrew Cask是在Mac上管理软件包的常用方法。虽然大多数情况下安装这两个工具是比较简单的&#xff0c;但有时候也可能遇到一些问题。下面是一些常见的问题以及解决办法&#xff1a; 问题1&#xff1a;无法安装Homebrew 解决办法&#xff1a; 1.确保你的Mac已连…

跟TED演讲学英文:Why AI will spark exponential economic growth by Cathie Wood

TED英文文稿 文章目录 TED英文文稿Why AI will spark exponential economic growthIntroductionVocabularyTranscriptSummary Why AI will spark exponential economic growth Link: https://www.ted.com/talks/cathie_wood_why_ai_will_spark_exponential_economic_growth? …

打印CSDN博客只需两步

打印博客 关闭浏览器限制 浏览器打开对应博客&#xff0c;F12&#xff0c;在console下粘贴如下代码&#xff0c;回车 (function doPrint(){var head_str "<html><head><title></title></head><body>"; var foot_str "&…