ElasticSerach+MongoDB:实现文章检索历史功能

实现目标:

  • 展示用户的搜索记录10条,按照搜索关键词的时间倒序
  • 可以删除搜索记录
  • 保存历史记录,保存10条,多余的则删除最久的历史记录

数据库的选择:

用户的搜索记录,需要给每一个用户都保存一份,数据量较大,要求加载速度快,通常这样的数据存储到mongodb更合适,不建议直接存储到关系型数据库中。与redis对比,MongoDB是结构化数据,而redis中只有keyValue。

实现思路:

实现

保存

1. 创建实体类

java 复制代码
@Data
@Document("ap_user_search") // mongoDB的映射注解
public class ApUserSearch implements Serializable {
   private static final long serialVersionUID = 1L;
   
   private Integer userId;
   private String keyword;
   private Date createdTime;
}

2. 保存userSearch

java 复制代码
@Autowired
private MongoTemplate mongoTemplate;

// keyword从dto中获取,userId从线程中获取
@Override
public void insert(String keyword, Integer userId){
    // 1. 查询当前用户的搜索关键词
    Query query = Query.query(Criteria
                .where("userId").is(userId)
                .and("keyword").is(keyword));
    ApUserSearch apUserSearch = ApUsermongoTemplate.findOne(query, ApUserSearch.class);
    // 2. 存在,更新创建时间
    if(apUserSearch != null) {
        apUserSerach.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> list = mongoTemplate.find(query1, ApUserSearch.class);
    // 保存历史记录,保存10条,多余的则删除最久的历史记录
    if(list == null || list.size() < 10){
        mongoTemplate.save(apUserSearch);
    }else{
        ApUserSearch lastInfo = apUserSearchList.get(apUserSearchList.size() - 1);
        // 替换
        mongoTemplate.findAndReplace(
            Query.query(Criteria.where("id").is(lastInfo.getId())
            , apUserSearch);
    }
    
}

3. 获取当前的用户

拦截器类,继承Ordered,GlobalFilter,其中重写filter方法,获取到了用户信息,存储到了header中,再重置请求

java 复制代码
@Override  
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {  
//1.获取request和response对象  
ServerHttpRequest request = exchange.getRequest();  
ServerHttpResponse response = exchange.getResponse();
}

实现一个工具类AppThreadLocalUtil在utils的Service服务中,用于获取用户

java 复制代码
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);
    }

    //从线程中获取
    public static ApUser getUser(){
        return WM_USER_THREAD_LOCAL.get();
    }

    //清理
    public static void clear(){
        WM_USER_THREAD_LOCAL.remove();
    }

}

实现拦截器

java 复制代码
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();
    }
}

在config中的WebMvcConfig中配置

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

4. 最后在ArticleSerachServiceImple中的search函数中添加

java 复制代码
ApUser user = AppThreadLocalUtil.getUser();
if(user != null && dto.getFromIndex() == 0){ // 首页
    // 异步调用 保存搜索记录
    apUserSearchService.insert(dto.getSearchWords(), user.getId());
}

查询

java 复制代码
List<ApUserSearch> apUserSearches = mongoTemplate.find(Query.query(Criteria.where("userId").is(user.getId())).with(Sort.by(Sort.Direction.DESC, "createdTime")), ApUserSearch.class);

删除

java 复制代码
 //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);
相关推荐
sg_knight13 小时前
Spring 框架中的 SseEmitter 使用详解
java·spring boot·后端·spring·spring cloud·sse·sseemitter
喵个咪16 小时前
初学者入门:用 go-kratos-admin + protoc-gen-typescript-http 快速搭建企业级 Admin 系统
后端·typescript·go
用户214118326360217 小时前
手把手教你用Claude制作专属PPT生成器-从模板学习到自动生成全流程实战
后端
计算机毕设匠心工作室19 小时前
【python大数据毕设实战】全面皮肤病症状数据可视化分析系统、Hadoop、计算机毕业设计、包括数据爬取、数据分析、数据可视化、机器学习、实战教学
后端·python·mysql
摆烂工程师19 小时前
2025年12月最新的 Google AI One Pro 1年会员教育认证通关指南
前端·后端·ai编程
qq_124987075319 小时前
基于SpringBoot+vue的小黄蜂外卖平台(源码+论文+部署+安装)
java·开发语言·vue.js·spring boot·后端·mysql·毕业设计
代码与野兽20 小时前
AI交易,怎么让LLM自己挑选数据源?
前端·javascript·后端
天天摸鱼的java工程师20 小时前
JDK 25 到底更新了什么?这篇全景式解读带你全面掌握
java·后端
非鱼feiyu20 小时前
自关联数据表查询优化实践:以 Django + 递归 CTE 构建树结构为例
数据库·后端·django
零日失眠者20 小时前
这5个Python库一旦掌握就离不开
后端·python