文章创建旨在帮助快速查看黑马头条主要编写的功能代码,帮助复习代码,对于部分的中间件的简介和入门案例采用了略过,重点是业务代码。可以在面试前看看复习
目录
新增文章时添加分词标题和内容索引(ES)
保存搜索记录(mongodb)
搜索历史加载(mongodb)
自媒体上下架
主要功能
在自媒体端点击下架之后,就会导致app端的文章下架
具体实现:自媒体发布下架消息到消息队列中,文章微服务从消息队列中取消息然后下架
使用kafka消息通知到article端进行文章上下架的同步
好处:系统解耦,流量削峰
kafka简介
主要的名词:
- 消息生产 (Producer):将消息发布到指定的主题 (Topic) 中。
- 消息存储 (Broker):Kafka 集群中,每个服务器称为 Broker,负责存储和管理消息。
- 消息消费 (Consumer):订阅一个或多个主题,从 Broker 中拉取并处理消息。
- 主题管理 (Topic):Kafka 按主题分类消息,每个主题可以被多个消费者订阅。
整合kafka到项目中
首先当然是引入依赖了,但是就不说了
随后在app端(article)和自媒体端(wemedia端)nacos中配置kafka相关配置
如配置消费者组,kafka中心地址,序列化器
自媒体端
kafka:
bootstrap-servers: 192.168.200.130:9092
consumer:
group-id: ${spring.application.name}
key-deserializer: org.apache.kafka.common.serialization.StringDeserializer
value-deserializer: org.apache.kafka.common.serialization.StringDeserializer
文章端配置同上
在整合之后注意配置以及配置序列化等
可以在发送是指定传递的对象改成json字符串,接受消息后再用json字符串改变成对象
自媒体文章上下架实现
自媒体端设置下架标志,发送下架消息
思路:
id查找文章,找到就改变enable状态下架(wmnewsdto添加enable字段)
随后kafka携带enable和articleld,article监听消息状态
接口定义
自媒体端的开始文章下架的服务实现(传入dto,修改wmNews的值)随后再生产消息给app端服务等待消费下架文章
提供的接口
@PostMapping("/down_or_up")
public ResponseResult downOrUp(@RequestBody WmNewsDto dto) {
return null;
}
/*
*
* 文章上下架
* */
@Override
public ResponseResult downOrUp(WmNewsDto dto) {
//检查参数
if(dto.getId()==null){
return ResponseResult.errorResult(AppHttpCodeEnum.PARAM_INVALID);
}
//查询文章
WmNews wmNews=getById(dto.getId());
if(wmNews==null){
//数据不存在
return ResponseResult.errorResult(AppHttpCodeEnum.DATA_NOT_EXIST);
}
//判断文章是否已经发布
if(!wmNews.getStatus().equals(WmNews.Status.PUBLISHED.getCode())){
return ResponseResult.errorResult(AppHttpCodeEnum.PARAM_INVALID);
}
//修改文章enable
if(dto.getEnable()!=null&&dto.getEnable()>=-1&&dto.getEnable()<2){
update(Wrappers.<WmNews>lambdaUpdate().set(WmNews::getEnable,dto.getEnable())
.eq(WmNews::getId,wmNews.getId()));
}
if(wmNews.getArticleId()!=null){
Map<String,Object> map=new HashMap<>();
map.put("articleId",wmNews.getArticleId());
map.put("enable",dto.getEnable());
kafkaTemplate.send(WmNewsMessageConstants.WM_NEWS_UP_OR_DOWN_TOPIC,JSON.toJSONString(map));
}
return ResponseResult.okResult(AppHttpCodeEnum.SUCCESS);
}
- 校验参数和文章状态: 检查文章 ID 和发布状态是否合法。
- 修改上下架状态: 更新文章的
enable
字段,确保值在[-1, 1]
范围内。 - 发送消息通知: 通过 Kafka 发送上下架状态更新消息。
- 返回操作结果: 成功则返回操作成功响应。
通知app端下架
app端服务进行监听
如果kafka生产消息要求下架,随后就消费并且下架
1:接受消息并且反序列化获取信息,随后调用下架接口
@KafkaListener(topics= WmNewsMessageConstants.WM_NEWS_UP_OR_DOWN_TOPIC)
public void onMessgae(String message){
if (StringUtils.isNotBlank(message)) {
Map map= JSON.parseObject(message,Map.class);
apArticleConfigService.updateByMap(map);
log.info("article端文章配置修改,articleId={}",map.get("articleId"));
//修改文章配置
}
}
@Override
public void updateByMap(Map map) {
Object enable=map.get("enable");
boolean isDown=true;//下架
if(enable.equals(1)){
isDown=false;//修改文章信息
}
update(Wrappers.<ApArticleConfig>lambdaUpdate().eq(ApArticleConfig::getArticleId,map.get("articleId"))
.set(ApArticleConfig::getIsDown,isDown));
}
总结
学习了kafka的生产者,消费者,偏移量等等
kafka:
- 发送端指定话题,随后发送消息
- 消费端循环获取消息
- 发送端又ack机制,但是接收端没有,需要接收端发送给发送端偏移量来告诉发送端当前消费到哪里了
- 采用异步+同步的方式提交偏移量,避免重复操作
具体在业务中的运用:
- 接收端以及发送端都要配置序列化器,指定kafka中心在那台机器,重试次数,组id
- 发送端负责修改wmNews中的Enable下架标志,随后把Enable以及文章id发送出去app端等待消费
- 消费端接受到消息后就直接消费,更改app端文章
App端搜索功能(ES不是很会)
搜索结果,搜索记录,搜索联想
需求分析:
用户输入即可搜索文章列表
关键词高亮显示
思路分析
为了加快检索效率,在查询时不会直接从数据库中查询文章,而是直接去es中高速检索
文章保存成功后存储到es
用户搜索查询es
创建索引(数据库)和映射(表结构)
搜索中需要展示的内容为以上内容
索引分词是指在将文档数据存入Elasticsearch时,对文本字段进行分解和处理的过程,目的是方便全文检索。
为了方便全文搜索,选用查询中最重要的信息标题和内容。
映射表如下
随后向es发送请求,尝试创建映射表
http://192.168.233.136:9200/app_info_article
{
"mappings":{
"properties":{
"id":{
"type":"long"
},
"publishTime":{
"type":"date"
},
"layout":{
"type":"integer"
},
"images":{
"type":"keyword",
"index": false
},
"staticUrl":{
"type":"keyword",
"index": false
},
"authorId": {
"type": "long"
},
"authorName": {
"type": "text"
},
"title":{
"type":"text",
"analyzer":"ik_smart"
},
"content":{
"type":"text",
"analyzer":"ik_smart"
}
}
}
}
可以通过GET查询所有的映射
GET请求查询映射:http://192.168.233.136:9200/app_info_article
DELETE请求,删除索引及映射:http://192.168.233.136:9200/app_info_article
GET请求,查询所有文档:http://192.168.233.136:9200/app_info_article/_search
初始化索引库数据
首先导入依赖
配置配置类(通过配置文件注入)
@Getter
@Setter
@Configuration
@ConfigurationProperties(prefix = "elasticsearch")
public class ElasticSearchConfig {
private String host;
private int port;
@Bean
public RestHighLevelClient client(){
return new RestHighLevelClient(RestClient.builder(
new HttpHost(
host,
port,
"http"
)
));
}
}
以及看看实体类,包含着es映射上的所有字段的
@Data
public class SearchArticleVo {
// 文章id
private Long id;
// 文章标题
private String title;
// 文章发布时间
private Date publishTime;
// 文章布局
private Integer layout;
// 封面
private String images;
// 作者id
private Long authorId;
// 作者名词
private String authorName;
//静态url
private String staticUrl;
//文章内容
private String content;
}
随后查询数据库,将数据库的文章批量导入到es索引库中
方法就是查询所有的未下架,未删除的文章
@Mapper
public interface ApArticleMapper extends BaseMapper<ApArticle> {
public List<SearchArticleVo> loadArticleList();
}
查询数据库文章,批量插入数据到es中
@Autowired
private ApArticleMapper apArticleMapper;
@Autowired
private RestHighLevelClient restHighLevelClient;
/**
* 注意:数据量的导入,如果数据量过大,需要分页导入
* @throws Exception
*/
@Test
public void init() throws Exception {
//1.查询所有符合条件的文章数据
List<SearchArticleVo> searchArticleVos = apArticleMapper.loadArticleList();
//2.批量导入到es索引库
BulkRequest bulkRequest = new BulkRequest("app_info_article");
for (SearchArticleVo searchArticleVo : searchArticleVos) {
IndexRequest indexRequest = new IndexRequest().id(searchArticleVo.getId().toString())
.source(JSON.toJSONString(searchArticleVo), XContentType.JSON);
//批量添加数据
bulkRequest.add(indexRequest);
}
restHighLevelClient.bulk(bulkRequest, RequestOptions.DEFAULT);
}
}
对于上面内容的解释
- BulkRequest: 用于批量操作的请求对象。
- "app_info_article": Elasticsearch中的索引名称。
- IndexRequest: 每条数据对应一个索引请求。
- id: 通过文章ID作为唯一标识。
- source: 将文章对象转换为JSON格式,作为请求体。
- bulkRequest.add(indexRequest): 将每个请求添加到批量操作中。
- 最后就是执行批量请求操作
随后查看索引库的值,可以看得出来导入了40条记录
搜索微服务创建
导入过程自然是不说了的(注意配置,以及nacos添加服务)
搜索接口定义
随后查询具体实现
controller层(搜索功能)
@RestController
@RequestMapping("/api/v1/article/search")
public class ArticleSearchController {
@Autowired
private ArticleSearchService articleSearchService;
@PostMapping("/search")
public ResponseResult search(@RequestBody UserSearchDto dto) throws IOException {
return articleSearchService.search(dto);
}
}
@Autowired
private RestHighLevelClient restHighLevelClient;
@Autowired
private ApUserSearchService apUserSearchService;
@Override
public ResponseResult search(UserSearchDto dto) throws IOException {
//1.检查参数
if(dto == null || StringUtils.isBlank(dto.getSearchWords())){
return ResponseResult.errorResult(AppHttpCodeEnum.PARAM_INVALID);
}
//异步调用 保存搜索记录
//获取当前登录用户,如果存在且是第一页查询,则异步保存搜索记录。
//AppThreadLocalUtil: 工具类,获取当前用户信息。
ApUser user= AppThreadLocalUtil.getUser();
if(user!=null&&dto.getFromIndex()==0){
apUserSearchService.insert(dto.getSearchWords(),user.getId());
}
//2.设置查询条件
SearchRequest searchRequest = new SearchRequest("app_info_article");
SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
//布尔查询
BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery();
//关键字的分词之后查询
//queryStringQuery: 进行分词查询,查询条件是 标题 或 内容。
//defaultOperator(Operator.OR): 多个分词之间使用 OR 逻辑。
QueryStringQueryBuilder queryStringQueryBuilder = QueryBuilders.queryStringQuery(dto.getSearchWords()).field("title").field("content").defaultOperator(Operator.OR);
boolQueryBuilder.must(queryStringQueryBuilder);
//查询小于mindate的数据
RangeQueryBuilder rangeQueryBuilder = QueryBuilders.rangeQuery("publishTime").lt(dto.getMinBehotTime().getTime());
boolQueryBuilder.filter(rangeQueryBuilder);
//分页查询
searchSourceBuilder.from(0);
searchSourceBuilder.size(dto.getPageSize());
//按照发布时间倒序查询
searchSourceBuilder.sort("publishTime", SortOrder.DESC);
//设置高亮 title
//高亮字段: 仅对 标题 进行高亮
//preTags/postTags: 高亮标记设置,使用红色字体。
HighlightBuilder highlightBuilder = new HighlightBuilder();
highlightBuilder.field("title");
highlightBuilder.preTags("<font style='color: red; font-size: inherit;'>");
highlightBuilder.postTags("</font>");
searchSourceBuilder.highlighter(highlightBuilder);
searchSourceBuilder.query(boolQueryBuilder);
//将布尔查询条件和高亮设置添加到 SearchRequest 中。
searchRequest.source(searchSourceBuilder);
SearchResponse searchResponse = restHighLevelClient.search(searchRequest, RequestOptions.DEFAULT);
//3.结果封装返回
List<Map> list = new ArrayList<>();
SearchHit[] hits = searchResponse.getHits().getHits();
for (SearchHit hit : hits) {
String json = hit.getSourceAsString();
//高亮处理: 如果有高亮字段,则使用高亮标题;否则使用原始标题。
//封装到Map: 将处理后的数据添加到结果列表。
Map map = JSON.parseObject(json, Map.class);
//处理高亮
if(hit.getHighlightFields() != null && hit.getHighlightFields().size() > 0){
Text[] titles = hit.getHighlightFields().get("title").getFragments();
String title = StringUtils.join(titles);
//高亮标题
map.put("h_title",title);
}else {
//原始标题
map.put("h_title",map.get("title"));
}
list.add(map);
}
return ResponseResult.okResult(list);
}
}
简要说明
- 依赖注入:
-
RestHighLevelClient
:用于和 Elasticsearch 交互。ApUserSearchService
:用于异步保存用户搜索记录。
- 参数校验:
-
- 检查搜索词是否为空,避免空查询。
- 异步保存搜索记录:
-
- 如果用户存在且为第一页查询,则异步保存搜索关键词。
- 构建搜索请求:
-
- 布尔查询:构建查询条件,包括关键字匹配和时间过滤。
- 分页和排序:按发布时间倒序排列,分页显示。
- 高亮显示:对搜索词在标题中的匹配进行红色高亮标注。
- 执行查询和结果处理:
-
- 调用
restHighLevelClient.search()
执行查询。 - 处理查询结果,将高亮标题替换原始标题。
- 调用
- 返回响应:
-
- 封装查询结果列表,返回标准响应格式
高亮显示原理
- 搜索引擎生成高亮片段:
-
- 在进行全文检索时,Elasticsearch 会根据查询条件识别匹配的字段。
- 它会在匹配的关键词周围添加高亮标签(例如:
<font style='color: red;'>
)。
- 返回高亮字段:
-
- 搜索结果中会包含高亮片段,例如:
"highlight": {
"title": ["<font style='color: red;'>Elasticsearch</font> Tutorial"]
}
前端直接渲染:
- 前端页面直接显示后端返回的内容,由于内容中已经包含了 HTML 标签,因此浏览器会自动渲染为高亮效果。
新增文章时添加分词标题和内容索引
当文章审核成功时,需要将可以发布的文章同步到搜索微服务,让搜索微服务添加数据到索引库中
利用kafka发送消息,文章微服务作为生产者,搜索微服务作为消费者
作为消息的搜索文章实体类如下
package com.heima.model.search.vos;
import lombok.Data;
import java.util.Date;
@Data
public class SearchArticleVo {
// 文章id
private Long id;
// 文章标题
private String title;
// 文章发布时间
private Date publishTime;
// 文章布局
private Integer layout;
// 封面
private String images;
// 作者id
private Long authorId;
// 作者名词
private String authorName;
//静态url
private String staticUrl;
//文章内容
private String content;
}
文章微服务发送消息
注意发送消息实在文章微服务的ArticleFreemarkerServiceImpl 中的buildArticleToMinIO发送消息的,因为在这里包含着文章的内容,路径,和ApArticle(文章信息)
buildArticleToMinIO
// @Async
@Override
public void buildArticleToMinIO(ApArticle apArticle, String content) {
if(StringUtils.isNotBlank(content)){
Template template=null;
StringWriter out = new StringWriter();
try{
//获取模板文件
template=configuration.getTemplate("article.ftl");
//数据模型
Map<String,Object> contentDataModel=new HashMap<>();
contentDataModel.put("content",content);//将内容放入
//注入数据原型以及数据模型
template.process(contentDataModel,out);
}catch (Exception e){
e.printStackTrace();
}
InputStream in=new ByteArrayInputStream(out.toString().getBytes());
String path = fileStorageService.uploadHtmlFile("", apArticle.getId() + ".html", in);
//4.4 修改ap_article表,保存static_url字段
apArticleService.update(Wrappers.<ApArticle>lambdaUpdate().eq(ApArticle::getId,apArticle.getId())
.set(ApArticle::getStaticUrl,path));
//需要传递内容,路径
createArticleESIndex(apArticle,content,path);
}
}
发送方法,只需要把文章信息对象ApArticle转换为SearchArticleVo即可
private void createArticleESIndex(ApArticle apArticle,String content,String path){
SearchArticleVo vo=new SearchArticleVo();
BeanUtils.copyProperties(apArticle,vo);
vo.setStaticUrl(path);
vo.setContent(content);
kafkaTemplate.send(ArticleConstants.ARTICLE_ES_SYNC_TOPIC, JSON.toJSONString(vo));
}
--》 public static final String ARTICLE_ES_SYNC_TOPIC = "article.es.sync.topic";
随后就是文章服务配置中心添加es配置(其实用到es的都会用到)
搜索微服务接受消息并且创建索引
创建listener包专门用来监听kafka的topic中的信息
@Component
@Slf4j
public class SyncArticleListener {
@Autowired
private RestHighLevelClient restHighLevelClient;
@KafkaListener(topics= ArticleConstants.ARTICLE_ES_SYNC_TOPIC)
public void onMessage(String message){
if(StringUtils.isNotBlank(message)){
log.info("开始接受es同步消息:{}",message);
SearchArticleVo searchArticleVo = JSON.parseObject(message, SearchArticleVo.class);
IndexRequest indexRequest=new IndexRequest("app_info_article");//获取新的索引请求
indexRequest.id(searchArticleVo.getId().toString());
indexRequest.source(message, XContentType.JSON);
try{
restHighLevelClient.index(indexRequest, RequestOptions.DEFAULT);
}catch(Exception e){
e.printStackTrace();
log.info("sync es error={}",e);
}
}
}
}
思路:
这段代码是一个 Kafka 消息监听器,监听文章同步主题 (ARTICLE_ES_SYNC_TOPIC
) 。
收到消息后,将其转换为 SearchArticleVo
对象,并通过 RestHighLevelClient
将消息作为 JSON 文档写入 Elasticsearch 索引 (app_info_article
) 中。
XContentType.JSON
用于指定消息的内容类型为 JSON 格式。
搜索记录(mongodb)
需要用到mongoDb来保存搜索记录
需求说明
展示搜索记录十条,时间降序排列,可以删除,保存10条历史记录,多余的删除
spring集成,docker拉取镜像和创建容器,以及导包跳过
需要在nacos处配置mongodb端口,数据库以及port
server:
port: 9998
spring:
data:
mongodb:
host: 192.168.233.136
port: 27017
database: leadnews-history
随后是映射实体类,映射在mongodb中是将对象或者数据结构转换为BSON文档的过程
@Data
@Document("ap_associate_words")
public class ApAssociateWords implements Serializable {
private static final long serialVersionUID = 1L;
private String id;
/**
* 联想词
*/
private String associateWords;
/**
* 创建时间
*/
private Date createdTime;
}
指定了database(相当于mysql中库)以及document(相当于mysql中表)文档后即可test插入数据
下面包括了根据主键id查询,根据条件查询以及删除
@SpringBootTest(classes = MongoApplication.class)
@RunWith(SpringRunner.class)
public class MongoTest {
@Autowired
private MongoTemplate mongoTemplate;
//保存
@Test
public void saveTest(){
ApAssociateWords apAssociateWords = new ApAssociateWords();
apAssociateWords.setAssociateWords("黑马头条");
apAssociateWords.setCreatedTime(new Date());
mongoTemplate.save(apAssociateWords);
}
//查询一个
@Test
public void saveFindOne(){
ApAssociateWords apAssociateWords = mongoTemplate.findById("5fc2fc3fb60c9a039c44556e", ApAssociateWords.class);
System.out.println(apAssociateWords);
}
//条件查询
@Test
public void testQuery(){
Query query = Query.query(Criteria.where("associateWords").is("黑马头条"))
.with(Sort.by(Sort.Direction.DESC,"createdTime"));
List<ApAssociateWords> apAssociateWordsList = mongoTemplate.find(query, ApAssociateWords.class);
System.out.println(apAssociateWordsList);
}
@Test
public void testDel(){
mongoTemplate.remove(Query.query(Criteria.where("associateWords").is("黑马头条")),ApAssociateWords.class);
}
}
保存搜索记录
实现步骤图如下
用戶搜索记录的对应实体类
@Data
@Document("ap_user_search")
public class ApUserSearch implements Serializable {
private static final long serialVersionUID = 1L;
/**
* 主键
*/
private String id;
/**
* 用户ID
*/
private Integer userId;
/**
* 搜索词
*/
private String keyword;
/**
* 创建时间
*/
private Date createdTime;
}
用户输入搜索关键字后进行搜索的主要流程图(替换与更新)
随后当然是引入依赖,nacos添加配置
创建ApUserSearchService中的insert方法(需要指定搜索记录以及搜索人id)
@Autowired
private MongoTemplate mongoTemplate;
@Override
@Async
public void insert(String keyword, Integer userId) {
//看看当前用户查询关键期
Query query=Query.query(Criteria.where("userId").is(userId).and("keyword").is(keyword));
ApUserSearch apUserSearch=mongoTemplate.findOne(query, ApUserSearch.class);
//存在的话更新创建时间
if(apUserSearch!=null){
apUserSearch.setCreatedTime(new Date());
mongoTemplate.save(apUserSearch);
return;
}
//不存在,补充字段
apUserSearch=new ApUserSearch();
apUserSearch.setUserId(userId);
apUserSearch.setKeyword(keyword);
apUserSearch.setCreatedTime(new Date());
Query query1=Query.query(Criteria.where("userId").is(userId));
//看看记录条数有多少条
query1.with(Sort.by(Sort.Direction.DESC, "createdTime"));
List<ApUserSearch> apUserSearchList = mongoTemplate.find(query1, ApUserSearch.class);
if(apUserSearchList==null||apUserSearchList.size()《10){
mongoTemplate.save(apUserSearch);
}else{
ApUserSearch lastUserSearch = apUserSearchList.get(apUserSearchList.size() - 1);
mongoTemplate.findAndReplace(Query.query(Criteria.where("id").is(lastUserSearch.getId())),apUserSearch);
}
}
随后就是在搜索的时候需要被调用该方法
就是在搜索的时候需要保存搜索记录
当然了,你直接在搜索服务中调用搜索方法又无法传入当前用户id,当然要通过token中携带的userId获取信息,将其userId放入请求头中携带,随后拦截器拦截请求将userId放入ThreadLocal中(前面的也做过,就是从请求中获取userID)
1.在gateway中过滤器获取token中userId,放入请求头中
//获取用户信息
Object userId = claimsBody.get("id");
//存储header中
ServerHttpRequest serverHttpRequest = request.mutate().headers(httpHeaders -> {
httpHeaders.add("userId", userId + "");
}).build();
//重置请求
exchange.mutate().request(serverHttpRequest);
2.在搜索微服务器的拦截器中获取到userId并且保存
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
String userId = request.getHeader("userId");
if(userId != null){
//存入到当前线程中
ApUser apUser = new ApUser();
apUser.setId(Integer.valueOf(userId));
AppThreadLocalUtil.setUser(apUser);
}
return true;
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
AppThreadLocalUtil.clear();
}
最后就是在搜索方法中搜索后调用保存方法
检查搜索不是空的就可以保存了
//1.检查参数
if(dto == null || StringUtils.isBlank(dto.getSearchWords())){
return ResponseResult.errorResult(AppHttpCodeEnum.PARAM_INVALID);
}
//异步调用 保存搜索记录
ApUser user= AppThreadLocalUtil.getUser();
if(user!=null&&dto.getFromIndex()==0){
apUserSearchService.insert(dto.getSearchWords(),user.getId());
}
//2.设置查询条件
SearchRequest searchRequest = new SearchRequest("app_info_article");
SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
//布尔查询
搜索历史加载
需求分析
根据用户id,降序查询搜索记录
接口路径如下
ApUserSearchServiceImpl中添加方法
@Override
public ResponseResult findUserSearch() {
ApUser apUser= AppThreadLocalUtil.getUser();
if(apUser==null){
return ResponseResult.errorResult(AppHttpCodeEnum.NEED_LOGIN);
}
List<ApUserSearch> apUserSearches = mongoTemplate.find(Query.query(Criteria.where("userId")
.is(apUser.getId()))
.with(Sort.by(Sort.Direction.DESC, "createdTime"))
, ApUserSearch.class);
return ResponseResult.okResult(apUserSearches);
}
controller
/**
* <p>
* APP用户搜索信息表 前端控制器
* </p>
* @author itheima
*/
@Slf4j
@RestController
@RequestMapping("/api/v1/history")
public class ApUserSearchController{
@Autowired
private ApUserSearchService apUserSearchService;
@PostMapping("/load")
public ResponseResult findUserSearch() {
return apUserSearchService.findUserSearch();
}
}
删除历史搜索记录
需求分析
就是根据用户id直接清空用户搜索记录,搜索历史记录只需要保存用户的id
@Data
public class HistorySearchDto {
/**
* 接收搜索历史记录id
*/
String id;
}
思路
校验参数:用户id是否为空
校验登录状态
从mongodb中删除数据
@Override
public ResponseResult delUserSearch(HistorySearchDto historySearchDto) {
if(historySearchDto.getId()==null){
return ResponseResult.errorResult(AppHttpCodeEnum.PARAM_INVALID);
}
ApUser apUser=AppThreadLocalUtil.getUser();
if(apUser==null){
return ResponseResult.errorResult(AppHttpCodeEnum.NEED_LOGIN);//需要登录
}
//删除
mongoTemplate.remove(Query.query(Criteria.where("userId").is(apUser.getId()).and("id")
.is(historySearchDto.getId())),ApUserSearch.class);
return ResponseResult.okResult(AppHttpCodeEnum.SUCCESS);
}
controller
@PostMapping("/del")
public ResponseResult delUserSearch(@RequestBody HistorySearchDto historySearchDto) {
return apUserSearchService.delUserSearch(historySearchDto);
}
关键词联想
需求分析
需要实现根据用户的输入的关键词展示联想词
实体类包括id,联想词,创建时间
@Data
@Document("ap_associate_words")
public class ApAssociateWords implements Serializable {
private static final long serialVersionUID = 1L;
private String id;
/**
* 联想词
*/
private String associateWords;
/**
* 创建时间
*/
private Date createdTime;
}
具体的impl
@Service
@Transactional
@Slf4j
public class ApAssociateWordsServiceImpl implements ApAssociateWordsService {
@Autowired
private MongoTemplate mongoTemplate;
@Override
public ResponseResult findAssociate(UserSearchDto userSearchDto) {
//1 参数检查
if(userSearchDto == null || StringUtils.isBlank(userSearchDto.getSearchWords())){
return ResponseResult.errorResult(AppHttpCodeEnum.PARAM_INVALID);
}
if(userSearchDto.getPageSize()>20){
userSearchDto.setPageSize(20);
}
Query query = Query.query(Criteria.where("associateWords").regex(".*?\\" + userSearchDto.getSearchWords() + ".*"));
query.limit(userSearchDto.getPageSize());
List<ApAssociateWords> wordsList = mongoTemplate.find(query, ApAssociateWords.class);
return ResponseResult.okResult(wordsList);
}
}
regex(".*?\\" + userSearchDto.getSearchWords() + ".*")
:
- 这个方法是用来构建正则表达式查询条件的。
regex()
:表示进行正则匹配。- 正则表达式含义:
-
.*?
:匹配任意长度的任意字符(非贪婪匹配)。\\"
:是转义字符,实际效果是\"
,与前后的字符连接形成完整的正则表达式。userSearchDto.getSearchWords()
:是用户输入的搜索词。.*
:再次匹配任意长度的任意字符(贪婪匹配)。
- 综合效果:
这个正则表达式实际上就是:
.*?搜索词.*
也就是说,只要搜索词在字段 associateWords
中出现即可匹配,可以出现在任意位置。
controller层
@Slf4j
@RestController
@RequestMapping("/api/v1/associate")
public class ApAssociateWordsController{
@Autowired
private ApAssociateWordsService apAssociateWordsService;
@PostMapping("/search")
public ResponseResult findAssociate(@RequestBody UserSearchDto userSearchDto) {
return apAssociateWordsService.findAssociate(userSearchDto);
}
}