黑马头条Day07-app端文章搜索

news2024/9/21 4:22:17

一、今日内容介绍

1. App端搜索效果图

2. 今日内容

(1)文章搜索

  • Elasticsearch环境搭建
  • 索引库创建
  • 文章搜索多条件复合查询
  • 索引数据同步

(2)搜索历史记录

  • MongoDB环境搭建
  • 异步保存搜索历史
  • 查看搜索历史列表
  • 删除搜索历史

(3)联想词查询

  • 联想词的来源
  • 联想词功能实现

二、app端文章搜索

1. ElasticSearch环境搭建

ElasticSearch详解:Docs

步骤①:拉取ElasticSearch镜像

docker pull elasticsearch:7.4.0

②创建ElasticSerach容器

docker run -id --name elasticsearch -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

③配置中文分词器ik

进入文件夹/usr/share/elasticsearch/plugins/

cd /usr/share/elasticsearch/plugins/

创建并进入文件夹

mkdir analysis-ik
cd analysis-ik

把资料中的elasticsearch-analysis-ik-7.4.0.zip上传到服务器上,解压

unzip elasticsearch-analysis-ik-7.4.0.zip

删除压缩包

rm -rf elasticsearch-analysis-ik-7.4.0.zip

重启elasticsearch

docker restart elasticsearch

④使用postman测试

2. app端文章搜索

2.1 需求分析

  • 用户输入关键可搜索文章列表
  • 关键词高亮显示
  • 文章列表展示与home展示一样,当用户点击某一篇文章,可查看文章详情

2.2 思路分析

为了加快检索的效率,在查询的时候不会直接从数据库中查询文章,需要在elasticsearch中进行高速检索。

2.3 创建索引和映射

使用postman添加映射

put请求 : http://192.168.200.130: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"
            }
        }
    }
}

注:老师提供的虚拟机里已经创建过了,不用再创建了

2.4 数据初始化到索引库

步骤①:将资料中的es_init导入到heima-leadnews-test工程下

在pom.xml(heima-leadnews-test)添加模块

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

②查询所有的文章信息,批量导入到es索引库中

package com.heima.es;

import com.alibaba.fastjson.JSON;
import com.heima.es.mapper.ApArticleMapper;
import com.heima.es.pojo.SearchArticleVo;
import org.elasticsearch.action.bulk.BulkRequest;
import org.elasticsearch.action.index.IndexRequest;
import org.elasticsearch.client.RequestOptions;
import org.elasticsearch.client.RestHighLevelClient;
import org.elasticsearch.common.xcontent.XContentType;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;

import java.util.List;

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

}

根据个人情况修改数据库连接密码

运行单元测试

③使用postman查询所有的es中数据

http://192.168.200.130:9200/app_info_article/_search

2.5 文章搜索功能实现

(1)搭建搜索微服务

步骤①:导入heima-leadnews-search微服务

在pom.xml(heima-leadnews-service)添加模块

    <modules>
        <module>heima-leadnews-user</module>
        <module>heima-leadnews-article</module>
        <module>heima-leadnews-wemedia</module>
        <module>heima-leadnews-schedule</module>
        <module>heima-leadnews-search</module>
    </modules>

②在pom.xml(heima-leadnews-service)中添加依赖

<!--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>

③在nacos配置中心添加leadnews-search.yml

spring:
  autoconfigure:
    exclude: org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration
elasticsearch:
  host: 192.168.200.130
  port: 9200

(2)搜索接口定义

步骤①:UserSearchDto

package com.heima.model.search.dtos;

import lombok.Data;

import java.util.Date;


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

②ArticleSearchController

package com.heima.search.controller.v1;

import com.heima.model.common.dtos.ResponseResult;
import com.heima.model.search.dtos.UserSearchDto;
import com.heima.search.service.ArticleSearchService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.io.IOException;

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

③ArticleSearchService

package com.heima.search.service;

import com.heima.model.common.dtos.ResponseResult;
import com.heima.model.search.dtos.UserSearchDto;

import java.io.IOException;

public interface ArticleSearchService {
    /**
     * ES文章分页搜索
     * @param dto
     * @return
     * @throws IOException
     */
    ResponseResult search(UserSearchDto dto) throws IOException;
}

④ArticleSearchServiceImpl

package com.heima.search.service.impl;

import com.alibaba.fastjson.JSON;
import com.heima.model.common.dtos.ResponseResult;
import com.heima.model.common.enums.AppHttpCodeEnum;
import com.heima.model.search.dtos.UserSearchDto;
import com.heima.search.service.ArticleSearchService;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.elasticsearch.action.search.SearchRequest;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.client.RequestOptions;
import org.elasticsearch.client.RestHighLevelClient;
import org.elasticsearch.common.text.Text;
import org.elasticsearch.index.query.*;
import org.elasticsearch.search.SearchHit;
import org.elasticsearch.search.builder.SearchSourceBuilder;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightBuilder;
import org.elasticsearch.search.sort.SortOrder;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;

@Service
@Slf4j
public class ArticleSearchServiceImpl implements ArticleSearchService {
    @Autowired
    private RestHighLevelClient restHighLevelClient;

    /**
     * es文章分页搜索
     * @param dto
     * @return
     * @throws IOException
     */
    @Override
    public ResponseResult search(UserSearchDto dto) throws IOException {
        // 1. 检查参数
        if(dto == null || StringUtils.isBlank(dto.getSearchWords())) {
            return ResponseResult.errorResult(AppHttpCodeEnum.PARAM_INVALID);
        }

        // 2. 设置查询条件
        SearchRequest searchRequest = new SearchRequest("app_info_article");
        SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();

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

        // 关键字的分词之后查询
        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
        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.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 = 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);
    }
}

⑤在nacos配置中心的leadnews-app-gateway.yml添加搜索微服务的路由配置

#搜索微服务
- id: leadnews-search
 uri: lb://leadnews-search
 predicates:
   - Path=/search/**
 filters:
   - StripPrefix= 1

⑥启动以下微服务进行测试

2.6 文章自动审核构建索引

(1)思路分析

(2)文章微服务发送消息

①把SearchArticleVo放到model工程下

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;

}

②在ArticleConstants类中添加新的常量

package com.heima.common.constants;

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_SYNC_TOPIC = "article.es.sync.topic";

    public static final Integer HOT_ARTICLE_LIKE_WEIGHT = 3;
    public static final Integer HOT_ARTICLE_COMMENT_WEIGHT = 5;
    public static final Integer HOT_ARTICLE_COLLECTION_WEIGHT = 8;

    public static final String HOT_ARTICLE_FIRST_PAGE = "hot_article_first_page_";

}

③在文章微服务的ArticleFreemarkerServiceImpl中的buildArticleToMinIO方法中收集数据并发送消息

package com.heima.article.service.impl;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.heima.article.mapper.ApArticleContentMapper;
import com.heima.article.service.ApArticleService;
import com.heima.article.service.ArticleFreemarkerService;
import com.heima.common.constants.ArticleConstants;
import com.heima.file.service.FileStorageService;
import com.heima.model.article.pojos.ApArticle;
import com.heima.model.search.vos.SearchArticleVo;
import freemarker.template.Configuration;
import freemarker.template.Template;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.kafka.core.KafkaTemplate;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.io.ByteArrayInputStream;
import java.io.InputStream;
import java.io.StringWriter;
import java.util.HashMap;
import java.util.Map;

@Service
@Slf4j
@Transactional
public class ArticleFreemarkerServiceImpl implements ArticleFreemarkerService {
    @Autowired
    private ApArticleContentMapper apArticleContentMapper;
    @Autowired
    private Configuration configuration;
    @Autowired
    private ApArticleService apArticleService;
    @Autowired
    private FileStorageService fileStorageService;
    @Autowired
    private KafkaTemplate<String, String> kafkaTemplate;

    /**
     * 生成静态文件上传到MinIO中
     * @param apArticle
     * @param content
     */
    @Async  // 异步
    @Override
    public void buildArticleToMinIO(ApArticle apArticle, String content) {
        // 已知文章的id
        // 1. 获取文章内容
        if(StringUtils.isNotBlank(content)) {
            // 2. 文章内容通过freemarker生成html
            Template template = null;
            StringWriter out = new StringWriter();
            try {
                // 获取模板
                template = configuration.getTemplate("article.ftl");
                // 数据模型
                Map<String, Object> contentDataModel = new HashMap<>();
                contentDataModel.put("content", JSONArray.parseArray(content));
                // 合成
                template.process(contentDataModel, out);
            } catch (Exception e) {
                e.printStackTrace();
            }

            // 3. 把html文件上传到MinIO中
            InputStream in = new ByteArrayInputStream(out.toString().getBytes());
            String path = fileStorageService.uploadHtmlFile("", apArticle.getId() + ".html", in);

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

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

    /**
     * 发送消息,创建索引
     * @param apArticle
     * @param content
     * @param path
     */
    private void createArticleESIndex(ApArticle apArticle, String content, String path) {
        SearchArticleVo vo = new SearchArticleVo();
        BeanUtils.copyProperties(apArticle, vo);
        vo.setContent(content);
        vo.setStaticUrl(path);

        kafkaTemplate.send(ArticleConstants.ARTICLE_ES_SYNC_TOPIC, JSON.toJSONString(vo));
    }


}

④在文章微服务的nacos配置中心添加如下配置 - leadnews-article.yml

kafka:
    bootstrap-servers: 192.168.200.130:9092
    producer:
      retries: 10
      key-serializer: org.apache.kafka.common.serialization.StringSerializer
      value-serializer: org.apache.kafka.common.serialization.StringSerializer

⑤定义监听接收消息,保存索引数据

package com.heima.search.listener;

import com.alibaba.fastjson.JSON;
import com.heima.common.constants.ArticleConstants;
import com.heima.model.search.vos.SearchArticleVo;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.elasticsearch.action.index.IndexRequest;
import org.elasticsearch.client.RequestOptions;
import org.elasticsearch.client.RestHighLevelClient;
import org.elasticsearch.common.xcontent.XContentType;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.kafka.annotation.KafkaListener;
import org.springframework.stereotype.Component;

import java.io.IOException;

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

⑥启动以下7个微服务进行测试

3. app端搜索-搜索记录

3.1 需求分析

3.2 数据存储说明

用户的搜索记录,需要给每一个用户都保存一份,数据量较大,要求加载速度快,通常这样的数据存储到mongoDB更合适,不建议直接存储到关系型数据库中。

3.3 MongoDB安装及集成

(1)安装MongoDB

步骤①:拉取镜像

docker pull mongo

②创建容器

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

(2)导入资料中的mongo-demo项目到heima-leadnews-test中

pom.xml(heima-leadnews-test)中添加模块

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

其中有三项配置比较关键:

第一:mongo依赖

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

第二:mongo配置

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

第三:映射

package com.itheima.mongo.pojo;

import lombok.Data;
import org.springframework.data.mongodb.core.mapping.Document;

import java.io.Serializable;
import java.util.Date;

/**
 * <p>
 * 联想词表
 * </p>
 *
 * @author itheima
 */
@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;

}

核心方法:

package com.itheima.mongo.test;


import com.itheima.mongo.MongoApplication;
import com.itheima.mongo.pojo.ApAssociateWords;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.data.domain.Sort;
import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.data.mongodb.core.query.Criteria;
import org.springframework.data.mongodb.core.query.Query;
import org.springframework.test.context.junit4.SpringRunner;

import java.util.Date;
import java.util.List;

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


    @Autowired
    private MongoTemplate mongoTemplate;

    //保存
    @Test
    public void saveTest(){
        /*for (int i = 0; i < 10; i++) {
            ApAssociateWords apAssociateWords = new ApAssociateWords();
            apAssociateWords.setAssociateWords("黑马头条");
            apAssociateWords.setCreatedTime(new Date());
            mongoTemplate.save(apAssociateWords);
        }*/
        ApAssociateWords apAssociateWords = new ApAssociateWords();
        apAssociateWords.setAssociateWords("黑马直播");
        apAssociateWords.setCreatedTime(new Date());
        mongoTemplate.save(apAssociateWords);
    }

    //查询一个
    @Test
    public void saveFindOne(){
        ApAssociateWords apAssociateWords = mongoTemplate.findById("60bdbfc1f2c2745b46b7f4a1", 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);
    }
}

IDEA连接MongoDB

3.4 保存搜索记录

(1)实现思路

用户输入关键字进行搜索,异步记录关键字

(2)实现步骤

步骤①:在搜索微服务集成MongoDB - pom.xml(heima-leadnews-search)

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

②添加nacos配置

spring:
  data:
   mongodb:
    host: 192.168.200.130
    port: 27017
    database: leadnews-history

③拷贝对应的实体类到搜索微服务heima-leadnews-search下

④创建ApUserSearchService新增insert方法

package com.heima.search.service;

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

⑤实现类 - ApUserSearchServiceImpl

package com.heima.search.service.impl;

import com.heima.search.pojos.ApUserSearch;
import com.heima.search.service.ApUserSearchService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Sort;
import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.data.mongodb.core.query.Criteria;
import org.springframework.data.mongodb.core.query.Query;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;

import java.util.Date;
import java.util.List;

@Service
@Slf4j
public class ApUserSearchServiceImpl implements ApUserSearchService {
    @Autowired
    private MongoTemplate mongoTemplate;

    /**
     * 保存用户搜索历史记录
     * @param keyword
     * @param userId
     */
    @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. 存在则更新创建时间
        if(apUserSearch != null) {
            apUserSearch.setCreatedTime(new Date());
            mongoTemplate.save(apUserSearch);
            return;
        }

        // 3. 不存在,判断当前历史记录总数量是否超过10
        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, "createTime"));
        List<ApUserSearch> apUserSearchList = mongoTemplate.find(query1, ApUserSearch.class);

        if(apUserSearchList == null || apUserSearchList.size() < 10) {
            // 少于10条搜索历史记录
            mongoTemplate.save(apUserSearch);
        } else {
            // 删除第一条搜索记录
            ApUserSearch firstUserSearch = apUserSearchList.get(0);
            mongoTemplate.remove(Query.query(Criteria.where("id").is(firstUserSearch.getId())), ApUserSearch.class);
            // 添加新的搜索记录
            mongoTemplate.insert(apUserSearch);

            /*// 获取最后一条搜索记录
            ApUserSearch lastUserSearch = apUserSearchList.get(apUserSearchList.size() - 1);
            // 替换最后一条搜索记录
            mongoTemplate.findAndReplace(Query.query(Criteria.where("id").is(lastUserSearch.getId())), apUserSearch);*/
        }
    }
}

(按照老师的逻辑是移除最近的一条记录,,不太符合常理)

⑥在搜索微服务中获取当前登录的用户

在app端网关的AuthorizeFilter添加如下

package com.heima.app.gateway.filter;

import com.heima.app.gateway.util.AppJwtUtil;
import io.jsonwebtoken.Claims;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang.StringUtils;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.http.HttpStatus;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;

@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是否有效
        try {
            Claims claimsBody = AppJwtUtil.getClaimsBody(token);
            // 判断token是否过期
            int result = AppJwtUtil.verifyToken(claimsBody);
            if (result == 1 || result == 2) {
                response.setStatusCode(HttpStatus.UNAUTHORIZED);
                return response.setComplete();
            }

            // 获取token解析后的用户信息
            Object userId = claimsBody.get("id");

            // 存入header中
            ServerHttpRequest serverHttpRequest = request.mutate().headers(httpHeaders -> {
                httpHeaders.add("userId", userId + "");
            }).build();

            // 重置请求
            exchange.mutate().request(serverHttpRequest);
            
        } catch (Exception e) {
            e.printStackTrace();
            response.setStatusCode(HttpStatus.UNAUTHORIZED);
            return response.setComplete();
        }

        // 6. 放行
        return chain.filter(exchange);
    }

    /**
     * 优先级设置:值越小,优先级越高
     * @return
     */
    @Override
    public int getOrder() {
        return 0;
    }
}

在search微服务下添加拦截器

package com.heima.search.interceptor;

import com.heima.model.user.pojos.ApUser;
import com.heima.utils.thread.AppThreadLocalUtil;
import org.springframework.web.servlet.HandlerInterceptor;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

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

在utils微服务下添加AppThreadLocalUtil类

package com.heima.utils.thread;

import com.heima.model.user.pojos.ApUser;

public class AppThreadLocalUtil {
    private final static ThreadLocal<ApUser> AP_USER_THREAD_LOCAL = new ThreadLocal<>();

    /**
     * 添加用户
     * @param apUser
     */
    public static void setUser(ApUser apUser) {
        AP_USER_THREAD_LOCAL.set(apUser);
    }

    /**
     * 获取用户
     * @return
     */
    public static ApUser getUser() {
        return AP_USER_THREAD_LOCAL.get();
    }

    /**
     * 清理用户
     */
    public static void clear() {
        AP_USER_THREAD_LOCAL.remove();
    }
}

在article微服务中新建interceptor文件夹,新增AppTokenInterceptor类

package com.heima.article.interceptor;

import com.heima.model.user.pojos.ApUser;
import com.heima.utils.thread.AppThreadLocalUtil;
import org.springframework.web.servlet.HandlerInterceptor;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

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

search微服务下注册拦截器

package com.heima.search.config;


import com.heima.search.interceptor.AppTokenInterceptor;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

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

⑦在ArticleSearchServiceImpl的search方法中调用保存历史记录

package com.heima.search.service.impl;

import com.alibaba.fastjson.JSON;
import com.heima.model.common.dtos.ResponseResult;
import com.heima.model.common.enums.AppHttpCodeEnum;
import com.heima.model.search.dtos.UserSearchDto;
import com.heima.model.user.pojos.ApUser;
import com.heima.search.service.ApUserSearchService;
import com.heima.search.service.ArticleSearchService;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.elasticsearch.action.search.SearchRequest;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.client.RequestOptions;
import org.elasticsearch.client.RestHighLevelClient;
import org.elasticsearch.common.text.Text;
import org.elasticsearch.index.query.*;
import org.elasticsearch.search.SearchHit;
import org.elasticsearch.search.builder.SearchSourceBuilder;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightBuilder;
import org.elasticsearch.search.sort.SortOrder;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;

@Service
@Slf4j
public class ArticleSearchServiceImpl implements ArticleSearchService {
    @Autowired
    private RestHighLevelClient restHighLevelClient;
    @Autowired
    private ApUserSearchService apUserSearchService;

    /**
     * es文章分页搜索
     * @param dto
     * @return
     * @throws IOException
     */
    @Override
    public ResponseResult search(UserSearchDto dto) throws IOException {
        // 1. 检查参数
        if(dto == null || StringUtils.isBlank(dto.getSearchWords())) {
            return ResponseResult.errorResult(AppHttpCodeEnum.PARAM_INVALID);
        }

        // 2. 保存搜索历史记录
        ApUser user = AppThreadLocalUtil.getUser();
        if(user != null && dto.getFromIndex() == 0) {
            apUserSearchService.insert(dto.getSearchWords(), user.getId());
        }

        // 3. 设置查询条件
        // ... ...

        return ResponseResult.okResult(list);
    }
}

⑧在启动类添加@EnableAsync注解,开启异步调用

3.5 加载搜索记录列表

(1)思路分析

按照当前用户,按照时间倒序查询

说明
接口路径/api/v1/history/load
请求方式POST
参数
响应结果ResponseResult

(2)实现步骤

步骤①:新增ApUserSearchController

package com.heima.search.controller.v1;

import com.heima.model.common.dtos.ResponseResult;
import com.heima.search.service.ApUserSearchService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("/api/v1/history")
public class ApUserSearchController {
    @Autowired
    private ApUserSearchService apUserSearchService;

    /**
     * 查询搜索历史
     * @return
     */
    @PostMapping("/load")
    public ResponseResult findUserSearch() {
        return apUserSearchService.findUserSearch();
    }
}

②在资料中拷贝两个mapper文件到mapper文件夹下

②在ApUserSearchService中新增方法

    /**
     * 查询搜索历史
     * @return
     */
    public ResponseResult findUserSearch();

③在ApUserSearchServiceImpl中实现方法

    /**
     * 查询搜索历史
     * @return
     */
    @Override
    public ResponseResult findUserSearch() {
        // 1. 获取当前用户
        ApUser user = AppThreadLocalUtil.getUser();
        if(user == null) {
            return ResponseResult.errorResult(AppHttpCodeEnum.NEED_LOGIN);
        }

        // 2. 根据用户查询数据,按照时间倒序排序
        List<ApUserSearch> apUserSearchList = mongoTemplate.find(Query.query(Criteria.where("userId").is(user.getId())).with(Sort.by(Sort.Direction.DESC, "createdTime")), ApUserSearch.class);

        return ResponseResult.okResult(apUserSearchList);
    }

④启动SearchApplication、AppGatewayApplication、ArticleApplication、UserApplication进行测试:http://localhost:8801/#/search

3.6 删除搜索历史

(1)接口定义

(2)实现步骤

①在model模块下新增HistorySearchDto

package com.heima.model.schedule.dtos;

import lombok.Data;

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

②在ApUserSearchController新增方法

    /**
     * 删除搜索历史记录
     * @param dto
     * @return
     */
    @PostMapping("/del")
    public ResponseResult delUserSearch(@RequestBody HistorySearchDto dto) {
        return apUserSearchService.delUserSearch(dto);
    }

③在ApUserService中新增方法

    /**
     * 删除搜索历史记录
     * @param dto
     * @return
     */
    public ResponseResult delUserSearch(HistorySearchDto dto);

④在ApUserServiceImpl中实现方法

    /**
     * 根据搜索记录id删除搜索历史记录
     * @param dto
     * @return
     */
    @Override
    public ResponseResult delUserSearch(HistorySearchDto dto) {
        // 1. 检查参数
        if(dto.getId() == null) {
            return ResponseResult.errorResult(AppHttpCodeEnum.PARAM_INVALID);
        }
        
        // 2. 判断是否登录
        ApUser user = AppThreadLocalUtil.getUser();
        if(user == null) {
            return ResponseResult.errorResult(AppHttpCodeEnum.NEED_LOGIN);
        }
        
        // 3. 删除
        mongoTemplate.remove(Query.query(Criteria.where("userId").is(user.getId())
                .and("id").is(dto.getId())), ApUserSearch.class);
        
        return ResponseResult.okResult(AppHttpCodeEnum.SUCCESS);
    }

⑤启动SearchApplication、AppGatewayApplication、ArticleApplication、UserApplication进行测试

4. app端搜索-关键词联想词

4.1 需求分析

根据用户输入的关键词展示联想词

4.2 搜索词 - 数据来源

通常是网上搜索频率比较高的一些词,通常在企业中有两部分来源:

第一:自己维护搜索词。通过分析用户搜索频率较高的词,按照排名作为搜索词

第二:第三方获取。关键词规划师(百度)、5118、爱站网

步骤①:导入资料中的ap_associate_words.js脚本到MongoDB中

5.3 功能实现

(1)接口定义

说明
接口路径/api/v1/associate
请求方式POST
参数UserSearchDto
响应结果ResponseResult

(2)实现步骤

①新建接口 - ApAssociateWordsController

package com.heima.search.controller.v1;

import com.heima.model.common.dtos.ResponseResult;
import com.heima.model.search.dtos.UserSearchDto;
import com.heima.search.service.ApAssociateWordsService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("/api/v1/associate")
public class ApAssociateWordsController {
    @Autowired
    private ApAssociateWordsService apAssociateWordsService;

    /**
     * 查询联想词
     * @param dto
     * @return
     */
    @PostMapping("/search")
    public ResponseResult search(@RequestBody UserSearchDto dto) {
        return apAssociateWordsService.findAssociate(dto);
    }
}

②新建联想词业务接口 - ApAssociateWordsService

package com.heima.search.service;

import com.heima.model.common.dtos.ResponseResult;
import com.heima.model.search.dtos.UserSearchDto;

/**
 * 联想词表 服务类
 */
public interface ApAssociateWordsService {

    /**
     * 查询联想词
     * @param dto
     * @return
     */
    ResponseResult findAssociate(UserSearchDto dto);
}

③实现类 - ApAssociateWordsServiceImpl

package com.heima.search.service.impl;

import com.heima.model.common.dtos.ResponseResult;
import com.heima.model.common.enums.AppHttpCodeEnum;
import com.heima.model.search.dtos.UserSearchDto;
import com.heima.search.pojos.ApAssociateWords;
import com.heima.search.service.ApAssociateWordsService;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.data.mongodb.core.query.Criteria;
import org.springframework.data.mongodb.core.query.Query;
import org.springframework.stereotype.Service;

import java.util.List;

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

    /**
     * 查询联想词
     * @param dto
     * @return
     */
    @Override
    public ResponseResult findAssociate(UserSearchDto dto) {
        // 1. 参数检查
        if(dto == null || StringUtils.isBlank(dto.getSearchWords())) {
            return ResponseResult.errorResult(AppHttpCodeEnum.PARAM_INVALID);
        }

        // 2. 分页检查
        if(dto.getPageSize() > 20) {
            dto.setPageSize(20);
        }

        // 3. 执行查询,模糊查询
        Query query = Query.query(Criteria.where("associateWords").regex(".*?\\" + dto.getSearchWords() + ".*"));
        query.limit(dto.getPageSize());
        List<ApAssociateWords> wordsList = mongoTemplate.find(query, ApAssociateWords.class);

        return ResponseResult.okResult(wordsList);
    }
}

正则表达式说明:

说明
.表示匹配任意字符
*表示配置0次以上
*?表示非贪婪匹配,碰到符合条件的立马就匹配

④前后端联调测试

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

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

相关文章

Linux源码安装的Redis如何配置systemd管理并设置开机启动

文章目录 实验前提实验 实验前提 已完成源码安装并能正常启动redis /usr/local/bin/redis-server能正常启动redis 实验 vim /etc/systemd/system/redis.service内容如下&#xff1a; [unit] Descriptionredis-server Afternetwork.target[Service] Typeforking ExecStart/…

从零开始创建vue3项目——包含项目初始化、element-plus、eslint、axios、router、pinia、echarts

项目启动 初始化vue3项目 这里建议先下载pnpm&#xff0c;下载速度更快&#xff0c;如果还没下载可以使用 npm install -g pnpm 如果遇到报错问题&#xff0c;如下 可以在命令行输入下面的指令以切换到淘宝镜像源 npm config set registry https://registry.npm.taobao.org…

Facebook云手机引流运营方法

Facebook&#xff0c;作为全球用户数达到30亿的最大社交媒体平台&#xff0c;汇聚了各类客户群体&#xff0c;蕴藏着巨大的商业潜力。对于外贸电商而言&#xff0c;Facebook是不可或缺的营销平台。一方面&#xff0c;我们可以在Facebook上发布产品信息&#xff0c;寻找并筛选目…

linux系统进程占cpu 100%解决步骤

1.查找进程 ps aux 查看指定进程: ps aux | grep process_name2.根据进程查找对应的主进程 pstree -p | grep process_name 3.查看主进程目录并删除 ps -axu | grep process_name rm -rf /usr/bin/2cbbb

PDF-Extract-Kit

文章目录 一、关于 PDF-Extract-Kit整体介绍效果展示 二、评测指标1、布局检测2、公式检测3、公式识别 三、安装四、模型下载1、安装 Git LFS2、从 Hugging Face 下载模型3、从 ModelScope 下载模型SDK 下载Git 下载 五、运行提取脚本六、其它待办事项协议致谢 一、关于 PDF-Ex…

Spark实时(三):Structured Streaming入门案例

文章目录 Structured Streaming入门案例 一、Scala代码如下 二、Java 代码如下 三、以上代码注意点如下 Structured Streaming入门案例 我们使用Structured Streaming来监控socket数据统计WordCount。这里我们使用Spark版本为3.4.3版本&#xff0c;首先在Maven pom文件中导…

Delphi 11.2 配置Android SDK 环境

打开 Delphi 11 点击 Tools–Options… 然后点击 Deployment–SDK Manager–Add… 这里如果配置64位就选 Android 64-bit&#xff0c;如果配置32位就选 Android 32-bit 点击 Select an SDK version–Add New… 有警告图标的就是有问题的项&#xff0c;需要手动更新一下&#xf…

NO.1 Hadoop概述

1.1 Hadoop是什么 1.2 Hadoop优势 1.3 Hadoop组成 1.3.1 HDFS架构概述 1.3.2 YARN架构概述 1.3.3 MapReduce架构概述 1.3.4 HDFS、YARN、MapReduce三者关系 1.4 大数据技术生态体系 1.5 推荐系统框架图

【UE5】可反射的射线检测

目录 效果 步骤 一、准备射线 二、生成第一次反射后的射线 三、多次反射 四、通过循环进行多次反射 效果 步骤 一、准备射线 1. 新建一个工程&#xff0c;添加一个俯视角游戏资源包 2. 双击打开俯视角游戏地图 删除大纲中的后期处理体积使得地图可以正常显示 3. 添加一…

【JavaEE初阶】线程的概念及创建

目录 &#x1f4d5; 前言 &#x1f4d5; 认识线程&#xff08;Thread&#xff09; &#x1f6a9; 概念 &#x1f60a;线程是什么 &#x1f642; 为啥要有线程 &#x1f62d; 进程和线程的区别&#xff08;面试题重点&#xff09; &#x1f92d; Java的线程和操作系统线程…

黑马JavaWeb企业级开发(知识清单)01——前端介绍,HTML实现标题:排版

文章目录 前言一、认识web前端、HTML、CSS二、VS Code开发工具&#xff08;插件弃用问题&#xff09;三、HTML结构标签介绍1. 标签页标题< title >2. 图片标签< img >1) 常见属性2) src路径书写方式 3. 标题标签< h >4. 水平分页线标签< hr > 四、用Vs…

“萝卜快跑”自动驾驶技术,夺走了谁的方向盘?

在前几年&#xff0c;科幻电影中无人驾驶车自如地穿梭在城市大街小巷的场景&#xff0c;似乎还遥不可及&#xff0c;然而&#xff0c;随着“萝卜快跑”无人驾驶车辆在多个城市的成功运营&#xff0c;这一愿景已悄然变为现实。由百度Apollo倾力打造的“萝卜快跑”&#xff0c;以…

基于FPGA的以太网设计(3)----详解各类xMII接口

1、什么是xMII接口 MII (Media Independent Interface)接口,即介质无关接口或称为媒体独立接口,它是IEEE-802.3定义的以太网行业标准。“介质无关” 表明在不对MAC硬件重新设计或替换的情况下,任何类型的PHY设备都可以正常工作。 MII接口是MAC和PHY之间的通信接口,MAC产生…

STM32(七):STM32指南者-串口实验

目录 一、基本概念通讯基本概念1、串行和并行2、同步通讯与异步通讯3、全双工、半双工、单工4、通讯速率 串口基本概念1、串口通讯基本概念2、物理层3、协议层 指南者的串口USART 二、串口实验前期准备1、安装安装 USB 转串口驱动_CH3402、野火多功能调试助手3、使用USB转串口&…

RedHat9 | Ansible 编写循环和条件任务

环境版本说明 RedHat9 [Red Hat Enterprise Linux release 9.0]Ansible [core 2.13.3]Python [3.9.10]jinja [3.1.2] 1. 利用循环迭代任务 通过利用循环&#xff0c;管理员无需编写多个使用同一模块的任务。Ansible支持使用loop关键字对一组项目迭代任务&#xff0c;通过配置…

基于单片机控制的变压器油压油温故障检测

摘 要 在电力系统的运行中&#xff0c;通过对其核心设备变压器的故障进行检测&#xff0c;以此能够及时、准确的发现变压器的故障&#xff0c;基于单片机控制的变压器油压油温的故障检测的方法&#xff0c;利用压力传感器、温度传感器对变压器的油压、油温进行采集并送入单片机…

靶机Metasploitable2的安装

Metasploitable2是一款基于Ubuntu Linux的操作系统。Metasploitable2是一个虚拟机文件&#xff0c;从网上下载解压之后就可以直接使用&#xff0c;无需安装。该系统本身设计作为安全工具测试和演示常见漏洞攻击的靶机&#xff0c;所以它存在大量未打补丁漏洞&#xff0c;并且开…

Xilinx Ultrascale+ FPGA 驱动MIPI DSI屏显示源码工程

作者&#xff1a;Hello&#xff0c;Panda 大家早上好&#xff0c;中午好&#xff0c;下午好&#xff0c;我是熊猫君。 曾记否&#xff0c;之前熊猫家发了一篇博文《分享一下使用Xilinx FPGA驱动MIPI DSI屏的心路历程》&#xff0c;此文发布以后&#xff0c;后台收到了不少朋友…

Ubuntu 22.04安装Visual Studio Code(VS Code)配置C++,Python

目录 1,下载 通过命令行安装 2,配置 2.1 vscode安装C/C 2.1.1 vscode安装运行环境 3,测试 vscode测试 4&#xff0c;配置python 选择解释器Python是一个解释性语言&#xff0c;现在需告知VSCode使用哪个解释器 ctrlshiftp 输入&#xff1a;Python: Select Interprete…

记一次因敏感信息泄露而导致的越权+存储型XSS

1、寻找测试目标 可能各位师傅会有苦于不知道如何寻找测试目标的烦恼&#xff0c;这里我惯用的就是寻找可进站的思路。这个思路分为两种&#xff0c;一是弱口令进站测试&#xff0c;二是可注册进站测试。依照这个思路&#xff0c;我依旧是用鹰图进行了一波资产的搜集&#xff…