赞
踩
①拉取镜像
docker pull elasticsearch:7.4.0
②创建容器
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
因为在创建elasticsearch容器的时候,映射了目录,所以可以在宿主机上进行配置ik中文分词器
在去选择ik分词器的时候,需要与elasticsearch的版本好对应上
把资料中的elasticsearch-analysis-ik-7.4.0.zip
上传到服务器上,放到对应目录(plugins)解压
- #切换目录
- cd /usr/share/elasticsearch/plugins
- #新建目录
- mkdir analysis-ik
- cd analysis-ik
- #root根目录中拷贝文件
- mv elasticsearch-analysis-ik-7.4.0.zip /usr/share/elasticsearch/plugins/analysis-ik
- #解压文件
- cd /usr/share/elasticsearch/plugins/analysis-ik
- unzip elasticsearch-analysis-ik-7.4.0.zip
postman测试
实现思路
使用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"
- }
- }
- }
- }
GET请求查询映射:http://192.168.200.130:9200/app_info_article
DELETE请求,删除索引及映射:http://192.168.200.130:9200/app_info_article
GET请求,查询所有文档:http://192.168.200.130:9200/app_info_article/_search
导入es-init到heima-leadnews-test工程下
查询所有的文章信息,批量导入到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);
-
- }
-
- }
接口
①导入 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.130
- port: 9200
④搜索接口定义
- 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);
- }
- }
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);
- }
- }
⑤创建业务层接口: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;
- }
实现类
- 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 {
-
- @Autowired
- private RestHighLevelClient restHighLevelClient;
-
- /**
- * es文章分页检索
- *
- * @param dto
- * @return
- */
- @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);
-
- }
- }
⑥测试
需要在app的网关中添加搜索微服务的路由配置
- #搜索微服务
- - id: leadnews-search
- uri: lb://leadnews-search
- predicates:
- - Path=/search/**
- filters:
- - StripPrefix= 1
思路
一、文章微服务发送消息
①把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;
-
- }
②文章微服务的ArticleFreemarkerService中的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 FileStorageService fileStorageService;
-
- @Autowired
- private ApArticleService apArticleService;
-
- /**
- * 生成静态文件上传到minIO中
- * @param apArticle
- * @param content
- */
- @Async
- @Override
- public void buildArticleToMinIO(ApArticle apArticle, String content) {
- //已知文章的id
- //4.1 获取文章内容
- if(StringUtils.isNotBlank(content)){
- //4.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();
- }
-
-
- //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));
-
- //发送消息,创建索引
- createArticleESIndex(apArticle,content,path);
-
- }
- }
-
- @Autowired
- private KafkaTemplate<String,String> kafkaTemplate;
-
- /**
- * 送消息,创建索引
- * @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_";
- }
③文章微服务集成kafka发送消息
- 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
二、搜索微服务接收消息并创建索引
①搜索微服务中添加kafka的配置,nacos配置如下
- spring:
- 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
②定义监听接收消息,保存索引数据
- 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);
- }
- }
-
- }
- }
展示用户的搜索记录10条,按照搜索关键词的时间倒序
可以删除搜索记录
保存历史记录,保存10条,多余的则删除最久的历史记录
用户的搜索记录,需要给每一个用户都保存一份,数据量较大,要求加载速度快,通常这样的数据存储到mongodb更合适,不建议直接存储到关系型数据库中
安装MongoDB
拉取镜像
docker pull mongo
创建容器
docker run -di --name mongo-service --restart=always -p 27017:27017 -v ~/data/mongodata:/data mongo
导入资料中的mongo-demo项目到heima-leadnews-test中
第一: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("60bd973eb0c1d430a71a7928", 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);
- }
- }
一、搜索微服务集成mongodb
①pom依赖
- <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
③用户搜索记录对应的集合,对应实体类:
- 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;
-
- /**
- * 创建时间
- */
- private Date createdTime;
-
- }
二、创建ApUserSearchService新增insert方法
- public interface ApUserSearchService {
-
- /**
- * 保存用户搜索历史记录
- * @param keyword
- * @param userId
- */
- public void insert(String keyword,Integer userId);
- }
实现类
- @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,"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);
- }
- }
- }
三、参考自媒体相关微服务,在搜索微服务中获取当前登录的用户
四、在ArticleSearchService的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 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 {
-
- @Autowired
- private RestHighLevelClient restHighLevelClient;
-
- @Autowired
- private ApUserSearchService apUserSearchService;
-
- /**
- * es文章分页检索
- *
- * @param dto
- * @return
- */
- @Override
- public ResponseResult search(UserSearchDto dto) throws IOException {
-
- //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();
-
- //布尔查询
- 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);
-
- }
- }
五、保存历史记录中开启异步调用,添加注解@Async
六、在搜索微服务引导类上开启异步调用
①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();
- }
-
- }
②service
在ApUserSearchService中新增方法
- /**
- 查询搜索历史
- @return
- */
- ResponseResult findUserSearch();
实现
- /**
- * 查询搜索历史
- *
- * @return
- */
- @Override
- public ResponseResult findUserSearch() {
- //获取当前用户
- ApUser user = AppThreadLocalUtil.getUser();
- if(user == null){
- return ResponseResult.errorResult(AppHttpCodeEnum.NEED_LOGIN);
- }
-
- //根据用户查询数据,按照时间倒序
- 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);
- }
①在ApUserSearchController接口新增方法
- @PostMapping("/del")
- public ResponseResult delUserSearch(@RequestBody HistorySearchDto historySearchDto) {
- return apUserSearchService.delUserSearch(historySearchDto);
- }
HistorySearchDto
- @Data
- public class HistorySearchDto {
- /**
- * 接收搜索历史记录id
- */
- String id;
- }
②在ApUserSearchService中新增方法
- /**
- 删除搜索历史
- @param historySearchDto
- @return
- */
- ResponseResult delUserSearch(HistorySearchDto historySearchDto);
实现
- /**
- * 删除历史记录
- *
- * @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);
- }
需求
根据用户输入的关键字展示联想词
搜索词-数据来源
联想词对应实体类
- 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>
- * 联想词表
- * </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;
-
- }
通常是网上搜索频率比较高的一些词,通常在企业中有两部分来源:
第一:自己维护搜索词
通过分析用户搜索频率较高的词,按照排名作为搜索词
第二:第三方获取
关键词规划师(百度)、5118、爱站网
功能实现
①新建联想词控制器ApAssociateWordsControlle
- 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>
- * 联想词表 前端控制器
- * </p>
- * @author itheima
- */
- @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);
- }
- }
②新建联想词业务层接口ApAssociateWordsService
- package com.heima.search.service;
-
- import com.heima.model.common.dtos.ResponseResult;
- import com.heima.model.search.dtos.UserSearchDto;
-
- /**
- * <p>
- * 联想词表 服务类
- * </p>
- *
- * @author itheima
- */
- public interface ApAssociateWordsService {
-
- /**
- 联想词
- @param userSearchDto
- @return
- */
- ResponseResult findAssociate(UserSearchDto userSearchDto);
-
- }
实现
- 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 {
-
- @Autowired
- MongoTemplate mongoTemplate;
-
- /**
- * 联想词
- * @param userSearchDto
- * @return
- */
- @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);
- }
-
- //3 执行查询 模糊查询
- 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);
- }
- }
需求
目前实现的思路:从数据库直接按照发布时间倒序查询
问题1:如何访问量较大,直接查询数据库,压力较大
问题2:
新发布的文章会展示在前面,并不是热点文章
实现思路
把热点数据存入redis进行展示
判断文章是否是热点,有几项标准: 点赞数量,评论数量,阅读数量,收藏数量
计算文章热度,有两种方案:
定时计算文章热度
实时计算文章热度
根据文章的行为(点赞、评论、阅读、收藏)计算文章的分值,利用定时任务每天完成一次计算
把分值较大的文章数据存入到redis中
App端用户查询文章列表的时候,优先从redis中查询热度较高的文章数据
spring传统的定时任务@Scheduled,但是这样存在这一些问题 :
做集群任务的重复执行问题,可以使用分布式锁解决,但是较麻烦
cron表达式定义在代码之中,修改不方便
定时任务失败了,无法重试也没有统计
如果任务量过大,不能有效的分片执行
解决这些问题的方案为:
xxl-job 分布式任务调度框架
当前软件的架构已经开始向分布式架构转变,将单体结构拆分为若干服务,服务之间通过网络交互来完成业务处理。在分布式架构下,一个服务往往会部署多个实例来运行我们的业务,如果在这种分布式系统环境下运行任务调度,我们称之为分布式任务调度。
将任务调度程序分布式构建,这样就可以具有分布式系统的特点,并且提高任务的调度处理能力:
1、并行任务调度
并行任务调度实现靠多线程,如果有大量任务需要调度,此时光靠多线程就会有瓶颈了,因为一台计算机CPU的处理能力是有限的。
如果将任务调度程序分布式部署,每个结点还可以部署为集群,这样就可以让多台计算机共同去完成任务调度,我们可以将任务分割为若干个分片,由不同的实例并行执行,来提高任务调度的处理效率。
2、高可用
若某一个实例宕机,不影响其他实例来执行任务。
3、弹性扩容
当集群中增加实例就可以提高并执行任务的处理效率。
4、任务管理与监测
对系统中存在的所有定时任务进行统一的管理及监测。让开发人员及运维人员能够时刻了解任务执行情况,从而做出快速的应急处理响应。
分布式任务调度面临的问题
当任务调度以集群方式部署,同一个任务调度可能会执行多次,例如:电商系统定期发放优惠券,就可能重复发放优惠券,对公司造成损失,信用卡还款提醒就会重复执行多次,给用户造成烦恼,所以我们需要控制相同的任务在多个运行实例上只执行一次。常见解决方案:
分布式锁,多个实例在任务执行前首先需要获取锁,如果获取失败那么就证明有其他服务已经在运行,如果获取成功那么证明没有服务在运行定时任务,那么就可以执行。
ZooKeeper选举,利用ZooKeeper对Leader实例执行定时任务,执行定时任务的时候判断自己是否是Leader,如果不是则不执行,如果是则执行业务逻辑,这样也能达到目的。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。