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);
相关推荐
红尘散仙1 小时前
我把终端小说阅读器接上了 AI Agent:TRNovel 现在能用 skill 生成书源了
人工智能·后端·rust
卷毛的技术笔记2 小时前
告别硬编码!Spring AI Alibaba 实现 AI Agent 智能工具调用(Tool Calling)
java·人工智能·后端·python·spring·ai编程
会编程的土豆3 小时前
Go 语言反射(Reflection)详解
开发语言·后端·golang
喵个咪3 小时前
GoWind Toolkit Go后端代码生成 完整全流程实战
后端·go·orm
basketball6164 小时前
Go 语言从入门到进阶:4. 数组和MAP使用方法总结
开发语言·后端·golang
qq_2518364574 小时前
SpringBoot+Vue 共享电池柜管理系统 完整实现 前后端分离项目实战 完整代码
vue.js·spring boot·后端
zhangxingchao4 小时前
AI 大模型核心六:量化、Workflow 与 Agent、多轮 RAG
前端·人工智能·后端
IT_陈寒5 小时前
Vite打包时遇到的坑,原来问题出在这里
前端·人工智能·后端
ayqy贾杰6 小时前
基层管理的三板斧,在AI时代行不通了
前端·后端·团队管理
Apifox6 小时前
Apifox 5 月更新|Postman 导入优化、Runner 支持非 root 运行、请求代码自动带鉴权
前端·后端·安全