公司的網(wǎng)站建設(shè)一般需要多少費用sem優(yōu)化怎么做
本筆記內(nèi)容為黑馬頭條項目的app端文章搜索部分
目錄
一、今日內(nèi)容介紹
1、App端搜索-效果圖
2、今日內(nèi)容
二、搭建ElasticSearch環(huán)境
1、拉取鏡像
2、創(chuàng)建容器
3、配置中文分詞器 ik
4、使用postman測試
三、app端文章搜索
1、需求分析
2、思路分析
3、創(chuàng)建索引和映射
4、數(shù)據(jù)初始化到索引庫
5、文章搜索功能實現(xiàn)
6、文章自動審核構(gòu)建索引
四、app端搜索-搜索記錄
1、需求分析
2、數(shù)據(jù)存儲說明
3、MongoDB安裝及集成
4、保存搜索記錄
5、加載搜索記錄列表
6、刪除搜索記錄
五、app端搜索-關(guān)鍵字聯(lián)想詞
1、需求分析
2、搜索詞-數(shù)據(jù)來源
3、功能實現(xiàn)
一、今日內(nèi)容介紹
1、App端搜索-效果圖
2、今日內(nèi)容
文章搜索
ElasticSearch環(huán)境搭建
索引庫創(chuàng)建
文章搜索多條件復(fù)合查詢
索引數(shù)據(jù)同步
搜索歷史記錄
Mongodb環(huán)境搭建
異步保存搜索歷史
查看搜索歷史列表
刪除搜索歷史
聯(lián)想詞查詢
聯(lián)想詞的來源
聯(lián)想詞功能實現(xiàn)
二、搭建ElasticSearch環(huán)境
1、拉取鏡像
docker pull elasticsearch:7.4.0
2、創(chuàng)建容器
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
3、配置中文分詞器 ik
因為在創(chuàng)建elasticsearch容器的時候,映射了目錄,所以可以在宿主機上進(jìn)行配置ik中文分詞器
在去選擇ik分詞器的時候,需要與elasticsearch的版本好對應(yīng)上
把資料中的elasticsearch-analysis-ik-7.4.0.zip
上傳到服務(wù)器上,放到對應(yīng)目錄(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
4、使用postman測試
三、app端文章搜索
1、需求分析
-
用戶輸入關(guān)鍵可搜索文章列表
-
關(guān)鍵詞高亮顯示
-
文章列表展示與home展示一樣,當(dāng)用戶點擊某一篇文章,可查看文章詳情
2、思路分析
為了加快檢索的效率,在查詢的時候不會直接從數(shù)據(jù)庫中查詢文章,需要在elasticsearch中進(jìn)行高速檢索。
3、創(chuàng)建索引和映射
使用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"}}}
}
4、數(shù)據(jù)初始化到索引庫
1.導(dǎo)入es-init到heima-leadnews-test工程下
2.查詢所有的文章信息,批量導(dǎo)入到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 {@Autowiredprivate ApArticleMapper apArticleMapper;@Autowiredprivate RestHighLevelClient restHighLevelClient;/*** 注意:數(shù)據(jù)量的導(dǎo)入,如果數(shù)據(jù)量過大,需要分頁導(dǎo)入* @throws Exception*/@Testpublic void init() throws Exception {//1.查詢所有符合條件的文章數(shù)據(jù)List<SearchArticleVo> searchArticleVos = apArticleMapper.loadArticleList();//2.批量導(dǎo)入到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);//批量添加數(shù)據(jù)bulkRequest.add(indexRequest);}restHighLevelClient.bulk(bulkRequest, RequestOptions.DEFAULT);}}
3.測試
postman查詢所有的es中數(shù)據(jù) GET請求: http://192.168.200.130:9200/app_info_article/_search
5、文章搜索功能實現(xiàn)
1.搭建搜索微服務(wù)
①導(dǎo)入 heima-leadnews-search
②在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>
③nacos配置中心leadnews-search
spring:autoconfigure:exclude: org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration
elasticsearch:host: 192.168.200.130port: 9200
2.搜索接口定義
package com.heima.search.controller.v1;import com.heima.model.common.dtos.ResponseResult;
import com.heima.model.search.dtos.UserSearchDto;
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 {@PostMapping("/search")public ResponseResult search(@RequestBody UserSearchDto dto) throws IOException {return null;}
}
UserSearchDto
package com.heima.model.search.dtos;import lombok.Data;import java.util.Date;@Data
public class UserSearchDto {/*** 搜索關(guān)鍵字*/String searchWords;/*** 當(dāng)前頁*/int pageNum;/*** 分頁條數(shù)*/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);}
}
3.業(yè)務(wù)層實現(xiàn)
創(chuàng)建業(yè)務(wù)層接口:ApArticleSearchService
package com.heima.search.service;import com.heima.model.search.dtos.UserSearchDto;
import com.heima.model.common.dtos.ResponseResult;import java.io.IOException;public interface ArticleSearchService {/**ES文章分頁搜索@return*/ResponseResult search(UserSearchDto userSearchDto) throws IOException;
}
實現(xiàn)類:
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.ArticleSearchService;
import com.heima.utils.thread.AppThreadLocalUtil;
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 {@Autowiredprivate RestHighLevelClient restHighLevelClient;/*** es文章分頁檢索** @param dto* @return*/@Overridepublic ResponseResult search(UserSearchDto dto) throws IOException {//1.檢查參數(shù)if(dto == null || StringUtils.isBlank(dto.getSearchWords())){return ResponseResult.errorResult(AppHttpCodeEnum.PARAM_INVALID);}//2.設(shè)置查詢條件SearchRequest searchRequest = new SearchRequest("app_info_article");SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();//布爾查詢BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery();//關(guān)鍵字的分詞之后查詢QueryStringQueryBuilder queryStringQueryBuilder = QueryBuilders.queryStringQuery(dto.getSearchWords()).field("title").field("content").defaultOperator(Operator.OR);boolQueryBuilder.must(queryStringQueryBuilder);//查詢小于mindate的數(shù)據(jù)RangeQueryBuilder rangeQueryBuilder = QueryBuilders.rangeQuery("publishTime").lt(dto.getMinBehotTime().getTime());boolQueryBuilder.filter(rangeQueryBuilder);//分頁查詢searchSourceBuilder.from(0);searchSourceBuilder.size(dto.getPageSize());//按照發(fā)布時間倒序查詢searchSourceBuilder.sort("publishTime", SortOrder.DESC);//設(shè)置高亮 titleHighlightBuilder 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.結(jié)果封裝返回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);//高亮標(biāo)題map.put("h_title",title);}else {//原始標(biāo)題map.put("h_title",map.get("title"));}list.add(map);}return ResponseResult.okResult(list);}
}
4.控制層實現(xiàn)
新建控制器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 {@Autowiredprivate ArticleSearchService articleSearchService;@PostMapping("/search")public ResponseResult search(@RequestBody UserSearchDto dto) throws IOException {return articleSearchService.search(dto);}
}
5.測試
需要在app的網(wǎng)關(guān)中添加搜索微服務(wù)的路由配置
#搜索微服務(wù)
- id: leadnews-searchuri: lb://leadnews-searchpredicates:- Path=/search/**filters:- StripPrefix= 1
啟動項目進(jìn)行測試,至少要啟動文章微服務(wù),用戶微服務(wù),搜索微服務(wù),app網(wǎng)關(guān)微服務(wù),app前端工程
6、文章自動審核構(gòu)建索引
1.思路分析
2.文章微服務(wù)發(fā)送消息
①把SearchArticleVo放到model工程下
package com.heima.model.search.vos;import lombok.Data;import java.util.Date;@Data
public class SearchArticleVo {// 文章idprivate Long id;// 文章標(biāo)題private String title;// 文章發(fā)布時間private Date publishTime;// 文章布局private Integer layout;// 封面private String images;// 作者idprivate Long authorId;// 作者名詞private String authorName;//靜態(tài)urlprivate String staticUrl;//文章內(nèi)容private String content;}
②文章微服務(wù)的ArticleFreemarkerService中的buildArticleToMinIO方法中收集數(shù)據(jù)并發(fā)送消息
完整代碼如下:
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 {@Autowiredprivate ApArticleContentMapper apArticleContentMapper;@Autowiredprivate Configuration configuration;@Autowiredprivate FileStorageService fileStorageService;@Autowiredprivate ApArticleService apArticleService;/*** 生成靜態(tài)文件上傳到minIO中* @param apArticle* @param content*/@Async@Overridepublic void buildArticleToMinIO(ApArticle apArticle, String content) {//已知文章的id//4.1 獲取文章內(nèi)容if(StringUtils.isNotBlank(content)){//4.2 文章內(nèi)容通過freemarker生成html文件Template template = null;StringWriter out = new StringWriter();try {template = configuration.getTemplate("article.ftl");//數(shù)據(jù)模型Map<String,Object> contentDataModel = new HashMap<>();contentDataModel.put("content", JSONArray.parseArray(content));//合成template.process(contentDataModel,out);} catch (Exception e) {e.printStackTrace();}//4.3 把html文件上傳到minio中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));//發(fā)送消息,創(chuàng)建索引createArticleESIndex(apArticle,content,path);}}@Autowiredprivate KafkaTemplate<String,String> kafkaTemplate;/*** 送消息,創(chuàng)建索引* @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));}}
在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_";
}
③文章微服務(wù)集成kafka發(fā)送消息
在文章微服務(wù)的nacos的配置中心添加如下配置
kafka:bootstrap-servers: 192.168.200.130:9092producer:retries: 10key-serializer: org.apache.kafka.common.serialization.StringSerializervalue-serializer: org.apache.kafka.common.serialization.StringSerializer
3.搜索微服務(wù)創(chuàng)建索引
①搜索微服務(wù)中添加kafka的配置,nacos配置如下
spring:kafka:bootstrap-servers: 192.168.200.130:9092consumer:group-id: ${spring.application.name}key-deserializer: org.apache.kafka.common.serialization.StringDeserializervalue-deserializer: org.apache.kafka.common.serialization.StringDeserializer
②定義監(jiān)聽接收消息,保存索引數(shù)據(jù)
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 {@Autowiredprivate 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);}}}
}
四、app端搜索-搜索記錄
1、需求分析
2、數(shù)據(jù)存儲說明
用戶的搜索記錄,需要給每一個用戶都保存一份,數(shù)據(jù)量較大,要求加載速度快,通常這樣的數(shù)據(jù)存儲到mongodb更合適,不建議直接存儲到關(guān)系型數(shù)據(jù)庫中
3、MongoDB安裝及集成
1.安裝MongoDB
拉取鏡像
docker pull mongo
創(chuàng)建容器
docker run -di --name mongo-service --restart=always -p 27017:27017 -v ~/data/mongodata:/data mongo
2.導(dǎo)入資料中的mongo-demo項目到heima-leadnews-test中
其中有三項配置比較關(guān)鍵:
第一: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.130port: 27017database: 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>* 聯(lián)想詞表* </p>** @author itheima*/
@Data
@Document("ap_associate_words")
public class ApAssociateWords implements Serializable {private static final long serialVersionUID = 1L;private String id;/*** 聯(lián)想詞*/private String associateWords;/*** 創(chuàng)建時間*/private Date createdTime;}
3.核心方法
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 {@Autowiredprivate MongoTemplate mongoTemplate;//保存@Testpublic 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);}//查詢一個@Testpublic void saveFindOne(){ApAssociateWords apAssociateWords = mongoTemplate.findById("60bd973eb0c1d430a71a7928", ApAssociateWords.class);System.out.println(apAssociateWords);}//條件查詢@Testpublic 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);}@Testpublic void testDel(){mongoTemplate.remove(Query.query(Criteria.where("associateWords").is("黑馬頭條")),ApAssociateWords.class);}
}
4、保存搜索記錄
1.實現(xiàn)思路
用戶輸入關(guān)鍵字進(jìn)行搜索的異步記錄關(guān)鍵字
用戶搜索記錄對應(yīng)的集合,對應(yīng)實體類:
package com.heima.search.pojos;import lombok.Data;
import org.springframework.data.mongodb.core.mapping.Document;import java.io.Serializable;
import java.util.Date;/*** <p>* APP用戶搜索信息表* </p>* @author itheima*/
@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;/*** 創(chuàng)建時間*/private Date createdTime;}
2.實現(xiàn)步驟
①搜索微服務(wù)集成mongodb
pom依賴
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-mongodb</artifactId>
</dependency>
nacos配置
spring:data:mongodb:host: 192.168.200.130port: 27017database: leadnews-history
在當(dāng)天資料中找到對應(yīng)的實體類拷貝到搜索微服務(wù)下
package com.heima.search.pojos;import lombok.Data;
import org.springframework.data.mongodb.core.mapping.Document;import java.io.Serializable;
import java.util.Date;/*** <p>* APP用戶搜索信息表* </p>* @author itheima*/
@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;/*** 創(chuàng)建時間*/private Date createdTime;}
package com.heima.search.pojos;import lombok.Data;
import org.springframework.data.mongodb.core.mapping.Document;import java.io.Serializable;
import java.util.Date;/*** <p>* 聯(lián)想詞表* </p>** @author itheima*/
@Data
@Document("ap_associate_words")
public class ApAssociateWords implements Serializable {private static final long serialVersionUID = 1L;private String id;/*** 聯(lián)想詞*/private String associateWords;/*** 創(chuàng)建時間*/private Date createdTime;}
②創(chuàng)建ApUserSearchService新增insert方法
public interface ApUserSearchService {/*** 保存用戶搜索歷史記錄* @param keyword* @param userId*/public void insert(String keyword,Integer userId);
}
實現(xiàn)類:
@Service
@Slf4j
public class ApUserSearchServiceImpl implements ApUserSearchService {@Autowiredprivate MongoTemplate mongoTemplate;/*** 保存用戶搜索歷史記錄* @param keyword* @param userId*/@Override@Asyncpublic void insert(String keyword, Integer userId) {//1.查詢當(dāng)前用戶的搜索關(guān)鍵詞Query query = Query.query(Criteria.where("userId").is(userId).and("keyword").is(keyword));ApUserSearch apUserSearch = mongoTemplate.findOne(query, ApUserSearch.class);//2.存在 更新創(chuàng)建時間if(apUserSearch != null){apUserSearch.setCreatedTime(new Date());mongoTemplate.save(apUserSearch);return;}//3.不存在,判斷當(dāng)前歷史記錄總數(shù)量是否超過10apUserSearch = 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);}}
}
3.參考自媒體相關(guān)微服務(wù),在搜索微服務(wù)中獲取當(dāng)前登錄的用戶
Util類
package com.heima.utils.thread;import com.heima.model.user.pojos.ApUser;
import com.heima.model.wemedia.pojos.WmUser;/*** Description: new java files header..** @author zhangzuhao* @version 1.0* @date 2023/7/18 15:56*/public class AppThreadLocalUtil {private final static ThreadLocal<ApUser> WM_USER_THREAD_LOCAL = new ThreadLocal<>();//存入線程中public static void setUser(ApUser apUser){WM_USER_THREAD_LOCAL.set(apUser);}//獲取線程數(shù)據(jù)public static ApUser getUser(){return WM_USER_THREAD_LOCAL.get();}//清理數(shù)據(jù)public static void clear(){WM_USER_THREAD_LOCAL.remove();}
}
interceptor
package com.heima.search.interceptor;import com.heima.model.user.pojos.ApUser;
import com.heima.model.wemedia.pojos.WmUser;
import com.heima.utils.thread.AppThreadLocalUtil;
import com.heima.utils.thread.WmThreadLocalUtil;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;/*** Description: new java files header..** @author zhangzuhao* @version 1.0* @date 2023/8/7 16:54*/public class AppTokenInterceptor implements HandlerInterceptor {@Overridepublic 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;}@Overridepublic void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {AppThreadLocalUtil.clear();}
}
config
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;/*** Description: new java files header..** @author zhangzuhao* @version 1.0* @date 2023/7/18 16:06*/@Configuration
public class WebMvcConfig implements WebMvcConfigurer {@Overridepublic void addInterceptors(InterceptorRegistry registry) {registry.addInterceptor(new AppTokenInterceptor()).addPathPatterns("/**");}
}
4.在ArticleSearchService的search方法中調(diào)用保存歷史記錄
完整代碼如下:
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 com.heima.utils.thread.AppThreadLocalUtil;
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 {@Autowiredprivate RestHighLevelClient restHighLevelClient;@Autowiredprivate ApUserSearchService apUserSearchService;/*** es文章分頁檢索** @param dto* @return*/@Overridepublic ResponseResult search(UserSearchDto dto) throws IOException {//1.檢查參數(shù)if(dto == null || StringUtils.isBlank(dto.getSearchWords())){return ResponseResult.errorResult(AppHttpCodeEnum.PARAM_INVALID);}ApUser user = AppThreadLocalUtil.getUser();//異步調(diào)用 保存搜索記錄if(user != null && dto.getFromIndex() == 0){apUserSearchService.insert(dto.getSearchWords(), user.getId());}//2.設(shè)置查詢條件SearchRequest searchRequest = new SearchRequest("app_info_article");SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();//布爾查詢BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery();//關(guān)鍵字的分詞之后查詢QueryStringQueryBuilder queryStringQueryBuilder = QueryBuilders.queryStringQuery(dto.getSearchWords()).field("title").field("content").defaultOperator(Operator.OR);boolQueryBuilder.must(queryStringQueryBuilder);//查詢小于mindate的數(shù)據(jù)RangeQueryBuilder rangeQueryBuilder = QueryBuilders.rangeQuery("publishTime").lt(dto.getMinBehotTime().getTime());boolQueryBuilder.filter(rangeQueryBuilder);//分頁查詢searchSourceBuilder.from(0);searchSourceBuilder.size(dto.getPageSize());//按照發(fā)布時間倒序查詢searchSourceBuilder.sort("publishTime", SortOrder.DESC);//設(shè)置高亮 titleHighlightBuilder 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.結(jié)果封裝返回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);//高亮標(biāo)題map.put("h_title",title);}else {//原始標(biāo)題map.put("h_title",map.get("title"));}list.add(map);}return ResponseResult.okResult(list);}
}
5.保存歷史記錄中開啟異步調(diào)用,添加注解@Async
6.在搜索微服務(wù)引導(dǎo)類上開啟異步調(diào)用
7.測試,搜索后查看結(jié)果
5、加載搜索記錄列表
1.思路分析
按照當(dāng)前用戶,按照時間倒序查詢
說明 | |
---|---|
接口路徑 | /api/v1/history/load |
請求方式 | POST |
參數(shù) | 無 |
響應(yīng)結(jié)果 | ResponseResult |
2.接口定義
/*** <p>* APP用戶搜索信息表 前端控制器* </p>** @author itheima*/
@Slf4j
@RestController
@RequestMapping("/api/v1/history")
public class ApUserSearchController{@PostMapping("/load")@Overridepublic ResponseResult findUserSearch() {return null;}}
3.mapper
已定義
4.業(yè)務(wù)層
在ApUserSearchService中新增方法
/**查詢搜索歷史@return*/
ResponseResult findUserSearch();
實現(xiàn)方法
/*** 查詢搜索歷史** @return*/
@Override
public ResponseResult findUserSearch() {//獲取當(dāng)前用戶ApUser user = AppThreadLocalUtil.getUser();if(user == null){return ResponseResult.errorResult(AppHttpCodeEnum.NEED_LOGIN);}//根據(jù)用戶查詢數(shù)據(jù),按照時間倒序List<ApUserSearch> apUserSearches = mongoTemplate.find(Query.query(Criteria.where("userId").is(user.getId())).with(Sort.by(Sort.Direction.DESC, "createdTime")), ApUserSearch.class);return ResponseResult.okResult(apUserSearches);
}
5.控制器
/*** <p>* APP用戶搜索信息表 前端控制器* </p>* @author itheima*/
@Slf4j
@RestController
@RequestMapping("/api/v1/history")
public class ApUserSearchController{@Autowiredprivate ApUserSearchService apUserSearchService;@PostMapping("/load")public ResponseResult findUserSearch() {return apUserSearchService.findUserSearch();}}
6.測試
打開app的搜索頁面,可以查看搜索記錄列表
6、刪除搜索記錄
1.思路分析
按照搜索歷史id刪除
說明 | |
---|---|
接口路徑 | /api/v1/history/del |
請求方式 | POST |
參數(shù) | HistorySearchDto |
響應(yīng)結(jié)果 | ResponseResult |
2.接口定義
在ApUserSearchController接口新增方法
@PostMapping("/del")
public ResponseResult delUserSearch(@RequestBody HistorySearchDto historySearchDto) {return null;
}
HistorySearchDto
@Data
public class HistorySearchDto {/*** 接收搜索歷史記錄id*/String id;
}
3.業(yè)務(wù)層
在ApUserSearchService中新增方法
/**刪除搜索歷史@param historySearchDto@return*/
ResponseResult delUserSearch(HistorySearchDto historySearchDto);
實現(xiàn)方法
/*** 刪除歷史記錄** @param dto* @return*/
@Override
public ResponseResult delUserSearch(HistorySearchDto dto) {//1.檢查參數(shù)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);
}
4.控制器
修改ApUserSearchController,補全方法
@PostMapping("/del")
public ResponseResult delUserSearch(@RequestBody HistorySearchDto historySearchDto) {return apUserSearchService.delUserSearch(historySearchDto);
}
5.測試
打開app可以刪除搜索記錄
五、app端搜索-關(guān)鍵字聯(lián)想詞
1、需求分析
對應(yīng)實體類
package com.heima.search.pojos;import lombok.Data;
import org.springframework.data.mongodb.core.mapping.Document;import java.io.Serializable;
import java.util.Date;/*** <p>* 聯(lián)想詞表* </p>** @author itheima*/
@Data
@Document("ap_associate_words")
public class ApAssociateWords implements Serializable {private static final long serialVersionUID = 1L;private String id;/*** 聯(lián)想詞*/private String associateWords;/*** 創(chuàng)建時間*/private Date createdTime;}
2、搜索詞-數(shù)據(jù)來源
通常是網(wǎng)上搜索頻率比較高的一些詞,通常在企業(yè)中有兩部分來源:
第一:自己維護(hù)搜索詞
通過分析用戶搜索頻率較高的詞,按照排名作為搜索詞
第二:第三方獲取
關(guān)鍵詞規(guī)劃師(百度)、5118、愛站網(wǎng)
3、功能實現(xiàn)
1.接口定義
說明 | |
---|---|
接口路徑 | /api/v1/associate/search |
請求方式 | POST |
參數(shù) | UserSearchDto |
響應(yīng)結(jié)果 | ResponseResult |
新建接口
package com.heima.search.controller.v1;import com.heima.model.common.dtos.ResponseResult;
import com.heima.model.search.dtos.UserSearchDto;
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 {@PostMapping("/search")public ResponseResult search(@RequestBody UserSearchDto userSearchDto) {return null;}
}
2.業(yè)務(wù)層
新建聯(lián)想詞業(yè)務(wù)層接口
package com.heima.search.service;import com.heima.model.common.dtos.ResponseResult;
import com.heima.model.search.dtos.UserSearchDto;/*** <p>* 聯(lián)想詞表 服務(wù)類* </p>** @author itheima*/
public interface ApAssociateWordsService {/**聯(lián)想詞@param userSearchDto@return*/ResponseResult findAssociate(UserSearchDto userSearchDto);}
實現(xiàn)類
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;/*** @Description:* @Version: V1.0*/
@Service
public class ApAssociateWordsServiceImpl implements ApAssociateWordsService {@AutowiredMongoTemplate mongoTemplate;/*** 聯(lián)想詞* @param userSearchDto* @return*/@Overridepublic ResponseResult findAssociate(UserSearchDto userSearchDto) {//1 參數(shù)檢查if(userSearchDto == null || StringUtils.isBlank(userSearchDto.getSearchWords())){return ResponseResult.errorResult(AppHttpCodeEnum.PARAM_INVALID);}//分頁檢查if (userSearchDto.getPageSize() > 20) {userSearchDto.setPageSize(20);}//3 執(zhí)行查詢 模糊查詢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);}
}
3.控制器
新建聯(lián)想詞控制器
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 lombok.extern.slf4j.Slf4j;
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;/*** <p>* 聯(lián)想詞表 前端控制器* </p>* @author itheima*/
@Slf4j
@RestController
@RequestMapping("/api/v1/associate")
public class ApAssociateWordsController{@Autowiredprivate ApAssociateWordsService apAssociateWordsService;@PostMapping("/search")public ResponseResult findAssociate(@RequestBody UserSearchDto userSearchDto) {return apAssociateWordsService.findAssociate(userSearchDto);}
}
4.測試
同樣,打開前端聯(lián)調(diào)測試效果
結(jié)束!