黑马头条核心代码逻辑复习第四天

文章创建旨在帮助快速查看黑马头条主要编写的功能代码,帮助复习代码,对于部分的中间件的简介和入门案例采用了略过,重点是业务代码。可以在面试前看看复习

目录

自媒体上下架

主要功能

kafka简介

整合kafka到项目中

自媒体文章上下架实现

自媒体端设置下架标志,发送下架消息

接口定义

通知app端下架

App端搜索功能(ES不是很会)

需求分析:

思路分析

新增文章时添加分词标题和内容索引(ES)

文章微服务发送消息

搜索微服务接受消息并且创建索引

搜索记录功能(mongodb)

需求说明

保存搜索记录(mongodb)

搜索历史加载(mongodb)

需求分析

删除历史搜索记录

需求分析

关键词联想

需求分析


自媒体上下架

主要功能

在自媒体端点击下架之后,就会导致app端的文章下架

具体实现:自媒体发布下架消息到消息队列中,文章微服务从消息队列中取消息然后下架

使用kafka消息通知到article端进行文章上下架的同步

好处:系统解耦,流量削峰

kafka简介

主要的名词:

  • 消息生产 (Producer):将消息发布到指定的主题 (Topic) 中。
  • 消息存储 (Broker):Kafka 集群中,每个服务器称为 Broker,负责存储和管理消息。
  • 消息消费 (Consumer):订阅一个或多个主题,从 Broker 中拉取并处理消息。
  • 主题管理 (Topic):Kafka 按主题分类消息,每个主题可以被多个消费者订阅。

整合kafka到项目中

首先当然是引入依赖了,但是就不说了

随后在app端(article)和自媒体端(wemedia端)nacos中配置kafka相关配置

如配置消费者组,kafka中心地址,序列化器

自媒体端

  kafka:
    bootstrap-servers: 192.168.200.130:9092
    consumer:
      group-id: ${spring.application.name}
      key-deserializer: org.apache.kafka.common.serialization.StringDeserializer
      value-deserializer: org.apache.kafka.common.serialization.StringDeserializer

文章端配置同上

在整合之后注意配置以及配置序列化等

可以在发送是指定传递的对象改成json字符串,接受消息后再用json字符串改变成对象

自媒体文章上下架实现

自媒体端设置下架标志,发送下架消息

思路:

id查找文章,找到就改变enable状态下架(wmnewsdto添加enable字段)

随后kafka携带enable和articleld,article监听消息状态

接口定义

自媒体端的开始文章下架的服务实现(传入dto,修改wmNews的值)随后再生产消息给app端服务等待消费下架文章

提供的接口


    @PostMapping("/down_or_up")
    public ResponseResult downOrUp(@RequestBody WmNewsDto dto) {
        return null;
    }
   /*
    *
    * 文章上下架
    * */
    @Override
    public ResponseResult downOrUp(WmNewsDto dto) {
        //检查参数
        if(dto.getId()==null){
            return ResponseResult.errorResult(AppHttpCodeEnum.PARAM_INVALID);
        }
        //查询文章
        WmNews wmNews=getById(dto.getId());
        if(wmNews==null){
            //数据不存在
            return ResponseResult.errorResult(AppHttpCodeEnum.DATA_NOT_EXIST);
        }
        //判断文章是否已经发布
        if(!wmNews.getStatus().equals(WmNews.Status.PUBLISHED.getCode())){
            return ResponseResult.errorResult(AppHttpCodeEnum.PARAM_INVALID);
        }
        //修改文章enable
        if(dto.getEnable()!=null&&dto.getEnable()>=-1&&dto.getEnable()<2){
            update(Wrappers.<WmNews>lambdaUpdate().set(WmNews::getEnable,dto.getEnable())
                    .eq(WmNews::getId,wmNews.getId()));
        }
        if(wmNews.getArticleId()!=null){
            Map<String,Object> map=new HashMap<>();
            map.put("articleId",wmNews.getArticleId());
            map.put("enable",dto.getEnable());
            kafkaTemplate.send(WmNewsMessageConstants.WM_NEWS_UP_OR_DOWN_TOPIC,JSON.toJSONString(map));
        }
        return ResponseResult.okResult(AppHttpCodeEnum.SUCCESS);
    }
  • 校验参数和文章状态: 检查文章 ID 和发布状态是否合法。
  • 修改上下架状态: 更新文章的 enable 字段,确保值在 [-1, 1] 范围内。
  • 发送消息通知: 通过 Kafka 发送上下架状态更新消息。
  • 返回操作结果: 成功则返回操作成功响应。

通知app端下架

app端服务进行监听

如果kafka生产消息要求下架,随后就消费并且下架

1:接受消息并且反序列化获取信息,随后调用下架接口

    @KafkaListener(topics= WmNewsMessageConstants.WM_NEWS_UP_OR_DOWN_TOPIC)
    public void onMessgae(String message){
        if (StringUtils.isNotBlank(message)) {
            Map map= JSON.parseObject(message,Map.class);
            apArticleConfigService.updateByMap(map);
            log.info("article端文章配置修改,articleId={}",map.get("articleId"));
            //修改文章配置
        }
    }
  @Override
    public void updateByMap(Map map) {
        Object enable=map.get("enable");
        boolean isDown=true;//下架
        if(enable.equals(1)){
            isDown=false;//修改文章信息
        }
        update(Wrappers.<ApArticleConfig>lambdaUpdate().eq(ApArticleConfig::getArticleId,map.get("articleId"))
                .set(ApArticleConfig::getIsDown,isDown));
    }

总结

学习了kafka的生产者,消费者,偏移量等等

kafka:

  • 发送端指定话题,随后发送消息
  • 消费端循环获取消息
  • 发送端又ack机制,但是接收端没有,需要接收端发送给发送端偏移量来告诉发送端当前消费到哪里了
  • 采用异步+同步的方式提交偏移量,避免重复操作

具体在业务中的运用:

  • 接收端以及发送端都要配置序列化器,指定kafka中心在那台机器,重试次数,组id
  • 发送端负责修改wmNews中的Enable下架标志,随后把Enable以及文章id发送出去app端等待消费
  • 消费端接受到消息后就直接消费,更改app端文章

App端搜索功能(ES不是很会)

搜索结果,搜索记录,搜索联想

需求分析:

用户输入即可搜索文章列表

关键词高亮显示

思路分析

为了加快检索效率,在查询时不会直接从数据库中查询文章,而是直接去es中高速检索

文章保存成功后存储到es

用户搜索查询es

创建索引(数据库)和映射(表结构)

搜索中需要展示的内容为以上内容

索引分词是指在将文档数据存入Elasticsearch时,对文本字段进行分解和处理的过程,目的是方便全文检索

为了方便全文搜索,选用查询中最重要的信息标题和内容。

映射表如下

随后向es发送请求,尝试创建映射表

http://192.168.233.136: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"
            }
        }
    }
}

可以通过GET查询所有的映射

GET请求查询映射:http://192.168.233.136:9200/app_info_article

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

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

初始化索引库数据

首先导入依赖

配置配置类(通过配置文件注入)

@Getter
@Setter
@Configuration
@ConfigurationProperties(prefix = "elasticsearch")
public class ElasticSearchConfig {
    private String host;
    private int port;

    @Bean
    public RestHighLevelClient client(){
        return new RestHighLevelClient(RestClient.builder(
                new HttpHost(
                        host,
                        port,
                        "http"
                )
        ));
    }
}

以及看看实体类,包含着es映射上的所有字段的

@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;

}

随后查询数据库,将数据库的文章批量导入到es索引库中

方法就是查询所有的未下架,未删除的文章

@Mapper
public interface ApArticleMapper extends BaseMapper<ApArticle> {

    public List<SearchArticleVo> loadArticleList();

}

查询数据库文章,批量插入数据到es中

  @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);

    }

}

对于上面内容的解释

  • BulkRequest: 用于批量操作的请求对象。
  • "app_info_article": Elasticsearch中的索引名称。
  • IndexRequest: 每条数据对应一个索引请求。
  • id: 通过文章ID作为唯一标识。
  • source: 将文章对象转换为JSON格式,作为请求体。
  • bulkRequest.add(indexRequest): 将每个请求添加到批量操作中。
  • 最后就是执行批量请求操作

随后查看索引库的值,可以看得出来导入了40条记录

搜索微服务创建

导入过程自然是不说了的(注意配置,以及nacos添加服务)

搜索接口定义

随后查询具体实现

controller层(搜索功能)

@RestController
@RequestMapping("/api/v1/article/search")
public class ArticleSearchController {


    @Autowired
    private ArticleSearchService articleSearchService;

    @PostMapping("/search")
    public ResponseResult search(@RequestBody UserSearchDto dto) throws IOException {
        return articleSearchService.search(dto);
    }
}
  @Autowired
    private RestHighLevelClient restHighLevelClient;
    @Autowired
    private ApUserSearchService apUserSearchService;
    @Override
    public ResponseResult search(UserSearchDto dto) throws IOException {

        //1.检查参数
        if(dto == null || StringUtils.isBlank(dto.getSearchWords())){
            return ResponseResult.errorResult(AppHttpCodeEnum.PARAM_INVALID);
        }

        //异步调用 保存搜索记录
        //获取当前登录用户,如果存在且是第一页查询,则异步保存搜索记录。
//AppThreadLocalUtil: 工具类,获取当前用户信息。
        ApUser user= AppThreadLocalUtil.getUser();
        if(user!=null&&dto.getFromIndex()==0){
            apUserSearchService.insert(dto.getSearchWords(),user.getId());
        }
        //2.设置查询条件
        SearchRequest searchRequest = new SearchRequest("app_info_article");
        SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();

        //布尔查询
        BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery();

        //关键字的分词之后查询
        //queryStringQuery: 进行分词查询,查询条件是 标题 或 内容。
        //defaultOperator(Operator.OR): 多个分词之间使用 OR 逻辑。
        QueryStringQueryBuilder queryStringQueryBuilder = QueryBuilders.queryStringQuery(dto.getSearchWords()).field("title").field("content").defaultOperator(Operator.OR);
        boolQueryBuilder.must(queryStringQueryBuilder);

        //查询小于mindate的数据
        RangeQueryBuilder rangeQueryBuilder = QueryBuilders.rangeQuery("publishTime").lt(dto.getMinBehotTime().getTime());
        boolQueryBuilder.filter(rangeQueryBuilder);

        //分页查询
        searchSourceBuilder.from(0);
        searchSourceBuilder.size(dto.getPageSize());

        //按照发布时间倒序查询
        searchSourceBuilder.sort("publishTime", SortOrder.DESC);

        //设置高亮  title
        //高亮字段: 仅对 标题 进行高亮
        //preTags/postTags: 高亮标记设置,使用红色字体。
        HighlightBuilder highlightBuilder = new HighlightBuilder();
        highlightBuilder.field("title");
        highlightBuilder.preTags("<font style='color: red; font-size: inherit;'>");
        highlightBuilder.postTags("</font>");
        searchSourceBuilder.highlighter(highlightBuilder);


        searchSourceBuilder.query(boolQueryBuilder);
        //将布尔查询条件和高亮设置添加到 SearchRequest 中。
        searchRequest.source(searchSourceBuilder);
        SearchResponse searchResponse = restHighLevelClient.search(searchRequest, RequestOptions.DEFAULT);


        //3.结果封装返回

        List<Map> list = new ArrayList<>();

        SearchHit[] hits = searchResponse.getHits().getHits();
        for (SearchHit hit : hits) {
            String json = hit.getSourceAsString();
            //高亮处理: 如果有高亮字段,则使用高亮标题;否则使用原始标题。
//封装到Map: 将处理后的数据添加到结果列表。
            Map map = JSON.parseObject(json, Map.class);
            //处理高亮
            if(hit.getHighlightFields() != null && hit.getHighlightFields().size() > 0){
                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);
        }

        return ResponseResult.okResult(list);

    }
}

简要说明

  • 依赖注入:
    • RestHighLevelClient:用于和 Elasticsearch 交互。
    • ApUserSearchService:用于异步保存用户搜索记录。
  • 参数校验:
    • 检查搜索词是否为空,避免空查询。
  • 异步保存搜索记录:
    • 如果用户存在且为第一页查询,则异步保存搜索关键词。
  • 构建搜索请求:
    • 布尔查询:构建查询条件,包括关键字匹配和时间过滤。
    • 分页和排序:按发布时间倒序排列,分页显示。
  • 高亮显示:对搜索词在标题中的匹配进行红色高亮标注。
  • 执行查询和结果处理:
    • 调用 restHighLevelClient.search() 执行查询。
    • 处理查询结果,将高亮标题替换原始标题。
  • 返回响应:
    • 封装查询结果列表,返回标准响应格式

高亮显示原理

  1. 搜索引擎生成高亮片段
    • 在进行全文检索时,Elasticsearch 会根据查询条件识别匹配的字段。
    • 它会在匹配的关键词周围添加高亮标签(例如:<font style='color: red;'>)。
  1. 返回高亮字段
    • 搜索结果中会包含高亮片段,例如:
"highlight": {
  "title": ["<font style='color: red;'>Elasticsearch</font> Tutorial"]
}

前端直接渲染

  • 前端页面直接显示后端返回的内容,由于内容中已经包含了 HTML 标签,因此浏览器会自动渲染为高亮效果。

新增文章时添加分词标题和内容索引

当文章审核成功时,需要将可以发布的文章同步到搜索微服务,让搜索微服务添加数据到索引库中

利用kafka发送消息,文章微服务作为生产者,搜索微服务作为消费者

作为消息的搜索文章实体类如下

package com.heima.model.search.vos;

import lombok.Data;

import java.util.Date;

@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;

}

文章微服务发送消息

注意发送消息实在文章微服务的ArticleFreemarkerServiceImpl 中的buildArticleToMinIO发送消息的,因为在这里包含着文章的内容,路径,和ApArticle(文章信息)

buildArticleToMinIO

//    @Async
    @Override
    public void buildArticleToMinIO(ApArticle apArticle, String content) {
        if(StringUtils.isNotBlank(content)){
            Template template=null;
            StringWriter out = new StringWriter();
            try{
                //获取模板文件
                template=configuration.getTemplate("article.ftl");
                //数据模型
                Map<String,Object> contentDataModel=new HashMap<>();
                contentDataModel.put("content",content);//将内容放入
                //注入数据原型以及数据模型
                template.process(contentDataModel,out);

            }catch (Exception e){
                e.printStackTrace();
            }
            InputStream in=new ByteArrayInputStream(out.toString().getBytes());
            String path = fileStorageService.uploadHtmlFile("", apArticle.getId() + ".html", in);


            //4.4 修改ap_article表,保存static_url字段
            apArticleService.update(Wrappers.<ApArticle>lambdaUpdate().eq(ApArticle::getId,apArticle.getId())
                    .set(ApArticle::getStaticUrl,path));


            //需要传递内容,路径
            createArticleESIndex(apArticle,content,path);
        }
    }

发送方法,只需要把文章信息对象ApArticle转换为SearchArticleVo即可

 private void createArticleESIndex(ApArticle apArticle,String content,String path){
        SearchArticleVo vo=new SearchArticleVo();
        BeanUtils.copyProperties(apArticle,vo);
        vo.setStaticUrl(path);
        vo.setContent(content);
        kafkaTemplate.send(ArticleConstants.ARTICLE_ES_SYNC_TOPIC, JSON.toJSONString(vo));
    }

--》    public static final String ARTICLE_ES_SYNC_TOPIC = "article.es.sync.topic";

随后就是文章服务配置中心添加es配置(其实用到es的都会用到)

搜索微服务接受消息并且创建索引

创建listener包专门用来监听kafka的topic中的信息

@Component
@Slf4j
public class SyncArticleListener {
    @Autowired
    private RestHighLevelClient restHighLevelClient;
    @KafkaListener(topics= ArticleConstants.ARTICLE_ES_SYNC_TOPIC)
    public void onMessage(String message){
        if(StringUtils.isNotBlank(message)){
            log.info("开始接受es同步消息:{}",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(Exception e){
                e.printStackTrace();
                log.info("sync es error={}",e);
            }
        }
    }
}

思路:

这段代码是一个 Kafka 消息监听器,监听文章同步主题 (ARTICLE_ES_SYNC_TOPIC) 。
收到消息后,将其转换为 SearchArticleVo 对象,并通过 RestHighLevelClient 将消息作为 JSON 文档写入 Elasticsearch 索引 (app_info_article) 中。
XContentType.JSON 用于指定消息的内容类型为 JSON 格式。

搜索记录(mongodb)

需要用到mongoDb来保存搜索记录

需求说明

展示搜索记录十条,时间降序排列,可以删除,保存10条历史记录,多余的删除

spring集成,docker拉取镜像和创建容器,以及导包跳过

需要在nacos处配置mongodb端口,数据库以及port

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

随后是映射实体类,映射在mongodb中是将对象或者数据结构转换为BSON文档的过程

@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;

}

指定了database(相当于mysql中库)以及document(相当于mysql中表)文档后即可test插入数据

下面包括了根据主键id查询,根据条件查询以及删除

@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("5fc2fc3fb60c9a039c44556e", 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);
    }
}

保存搜索记录

实现步骤图如下

用戶搜索记录的对应实体类

@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;

}

用户输入搜索关键字后进行搜索的主要流程图(替换与更新)

随后当然是引入依赖,nacos添加配置

创建ApUserSearchService中的insert方法(需要指定搜索记录以及搜索人id)

   @Autowired
    private MongoTemplate  mongoTemplate;
    @Override
    @Async
    public void insert(String keyword, Integer userId) {
        //看看当前用户查询关键期
        Query query=Query.query(Criteria.where("userId").is(userId).and("keyword").is(keyword));
        ApUserSearch apUserSearch=mongoTemplate.findOne(query, ApUserSearch.class);

        //存在的话更新创建时间
        if(apUserSearch!=null){
            apUserSearch.setCreatedTime(new Date());
            mongoTemplate.save(apUserSearch);
            return;
        }
        //不存在,补充字段
        apUserSearch=new ApUserSearch();
        apUserSearch.setUserId(userId);
        apUserSearch.setKeyword(keyword);
        apUserSearch.setCreatedTime(new Date());

        Query query1=Query.query(Criteria.where("userId").is(userId));
        //看看记录条数有多少条
        query1.with(Sort.by(Sort.Direction.DESC, "createdTime"));
        List<ApUserSearch> apUserSearchList = mongoTemplate.find(query1, ApUserSearch.class);
        if(apUserSearchList==null||apUserSearchList.size()《10){
            mongoTemplate.save(apUserSearch);
        }else{
            ApUserSearch lastUserSearch = apUserSearchList.get(apUserSearchList.size() - 1);
            mongoTemplate.findAndReplace(Query.query(Criteria.where("id").is(lastUserSearch.getId())),apUserSearch);
        }
    }

随后就是在搜索的时候需要被调用该方法

就是在搜索的时候需要保存搜索记录

当然了,你直接在搜索服务中调用搜索方法又无法传入当前用户id,当然要通过token中携带的userId获取信息,将其userId放入请求头中携带,随后拦截器拦截请求将userId放入ThreadLocal中(前面的也做过,就是从请求中获取userID)

1.在gateway中过滤器获取token中userId,放入请求头中

            //获取用户信息
            Object userId = claimsBody.get("id");

            //存储header中
            ServerHttpRequest serverHttpRequest = request.mutate().headers(httpHeaders -> {
                httpHeaders.add("userId", userId + "");
            }).build();
            //重置请求
            exchange.mutate().request(serverHttpRequest);

2.在搜索微服务器的拦截器中获取到userId并且保存

    @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();
    }

最后就是在搜索方法中搜索后调用保存方法

检查搜索不是空的就可以保存了

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

搜索历史加载

需求分析

根据用户id,降序查询搜索记录

接口路径如下

ApUserSearchServiceImpl中添加方法

@Override
    public ResponseResult findUserSearch() {
        ApUser apUser= AppThreadLocalUtil.getUser();
        if(apUser==null){
            return ResponseResult.errorResult(AppHttpCodeEnum.NEED_LOGIN);
        }
        List<ApUserSearch> apUserSearches = mongoTemplate.find(Query.query(Criteria.where("userId")
                .is(apUser.getId()))
                .with(Sort.by(Sort.Direction.DESC, "createdTime"))
                , ApUserSearch.class);
        return ResponseResult.okResult(apUserSearches);
    }

controller

/**
 * <p>
 * APP用户搜索信息表 前端控制器
 * </p>
 * @author itheima
 */
@Slf4j
@RestController
@RequestMapping("/api/v1/history")
public class ApUserSearchController{

    @Autowired
    private ApUserSearchService apUserSearchService;

    @PostMapping("/load")
    public ResponseResult findUserSearch() {
        return apUserSearchService.findUserSearch();
    }

}

删除历史搜索记录

需求分析

就是根据用户id直接清空用户搜索记录,搜索历史记录只需要保存用户的id

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

思路

校验参数:用户id是否为空

校验登录状态

从mongodb中删除数据


    @Override
    public ResponseResult delUserSearch(HistorySearchDto historySearchDto) {
        if(historySearchDto.getId()==null){
            return ResponseResult.errorResult(AppHttpCodeEnum.PARAM_INVALID);
        }
        ApUser apUser=AppThreadLocalUtil.getUser();
        if(apUser==null){
            return ResponseResult.errorResult(AppHttpCodeEnum.NEED_LOGIN);//需要登录
        }
        //删除
        mongoTemplate.remove(Query.query(Criteria.where("userId").is(apUser.getId()).and("id")
                .is(historySearchDto.getId())),ApUserSearch.class);
        return ResponseResult.okResult(AppHttpCodeEnum.SUCCESS);
    }

controller

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

关键词联想

需求分析

需要实现根据用户的输入的关键词展示联想词

实体类包括id,联想词,创建时间

@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;

}

具体的impl

@Service
@Transactional
@Slf4j
public class ApAssociateWordsServiceImpl implements ApAssociateWordsService {
    @Autowired
    private MongoTemplate mongoTemplate;

    @Override
    public ResponseResult findAssociate(UserSearchDto userSearchDto) {
        //1 参数检查
        if(userSearchDto == null || StringUtils.isBlank(userSearchDto.getSearchWords())){
            return ResponseResult.errorResult(AppHttpCodeEnum.PARAM_INVALID);
        }
        if(userSearchDto.getPageSize()>20){
            userSearchDto.setPageSize(20);
        }
        Query query = Query.query(Criteria.where("associateWords").regex(".*?\\" + userSearchDto.getSearchWords() + ".*"));
        query.limit(userSearchDto.getPageSize());
        List<ApAssociateWords> wordsList = mongoTemplate.find(query, ApAssociateWords.class);

        return ResponseResult.okResult(wordsList);
    }
}

regex(".*?\\" + userSearchDto.getSearchWords() + ".*")

  • 这个方法是用来构建正则表达式查询条件的。
  • regex():表示进行正则匹配。
  • 正则表达式含义:
    • .*?:匹配任意长度的任意字符(非贪婪匹配)。
    • \\":是转义字符,实际效果是 \",与前后的字符连接形成完整的正则表达式。
    • userSearchDto.getSearchWords():是用户输入的搜索词。
    • .*:再次匹配任意长度的任意字符(贪婪匹配)。
  • 综合效果:
    这个正则表达式实际上就是:
.*?搜索词.*

也就是说,只要搜索词在字段 associateWords 中出现即可匹配,可以出现在任意位置。

controller层

@Slf4j
@RestController
@RequestMapping("/api/v1/associate")
public class ApAssociateWordsController{

    @Autowired
    private ApAssociateWordsService apAssociateWordsService;

    @PostMapping("/search")
    public ResponseResult findAssociate(@RequestBody UserSearchDto userSearchDto) {
        return apAssociateWordsService.findAssociate(userSearchDto);
    }
}
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值