You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

基于NestJS+PostgreSQL,如何在Typesense中高效排除已申请职位

解决方案:Typesense + PostgreSQL 实现求职者未申请职位的精准搜索

针对你遇到的「长UUID列表导致Typesense过滤失效、搜后过滤破坏分页」的问题,这里提供两个可落地的方案,均能在Typesense层面完成过滤,保证分页逻辑正常:

方案一:在职位集合中维护已申请用户ID数组

核心思路是给每个职位文档添加一个数组字段,存储所有申请过该职位的用户UUID,搜索时直接过滤掉包含当前用户ID的职位。

实现步骤

  1. 定义Typesense职位集合Schema
    给jobs集合添加applied_user_ids数组字段,并设为可过滤(facet: true):

    const jobSchema = {
      name: 'jobs',
      fields: [
        { name: 'id', type: 'string' },
        { name: 'title', type: 'string' },
        { name: 'description', type: 'string' },
        { name: 'applied_user_ids', type: 'string[]', facet: true },
        { name: 'created_at', type: 'int64' },
      ],
      default_sorting_field: 'created_at'
    };
    
  2. 同步申请记录到Typesense
    当用户申请/取消职位时,同步更新对应职位文档的applied_user_ids数组:

    // 申请职位
    async applyJob(userId: string, jobId: string) {
      // 先写入PostgreSQL申请记录
      await this.jobApplicationRepo.create({ userId, jobId });
    
      // 更新Typesense职位文档
      const jobDoc = await this.typesenseClient.collections('jobs').documents(jobId).retrieve();
      const updatedIds = [...(jobDoc.applied_user_ids || []), userId];
      // 去重避免重复添加
      const uniqueIds = [...new Set(updatedIds)];
      await this.typesenseClient.collections('jobs').documents(jobId).update({
        applied_user_ids: uniqueIds
      });
    }
    
    // 取消申请
    async cancelApplyJob(userId: string, jobId: string) {
      await this.jobApplicationRepo.delete({ userId, jobId });
      const jobDoc = await this.typesenseClient.collections('jobs').documents(jobId).retrieve();
      const updatedIds = (jobDoc.applied_user_ids || []).filter(id => id !== userId);
      await this.typesenseClient.collections('jobs').documents(jobId).update({
        applied_user_ids: updatedIds
      });
    }
    
  3. 搜索时过滤未申请职位
    使用not contains语法过滤掉当前用户已申请的职位:

    async searchJobs(userId: string, query: string, page: number = 1, perPage: number = 10) {
      const searchParams = {
        q: query,
        filter_by: `applied_user_ids: not contains '${userId}'`,
        page,
        per_page: perPage,
        sort_by: 'created_at:desc'
      };
      return await this.typesenseClient.collections('jobs').documents().search(searchParams);
    }
    

优缺点

  • ✅ 优点:查询语法简单,性能稳定,Typesense对数组字段的过滤效率高
  • ❌ 缺点:职位文档会随申请用户增多而变大,但对于绝大多数招聘场景(单职位申请量不会极端庞大)来说完全可控

方案二:用次级集合存储申请记录,通过跨集合查询过滤

核心思路是创建专门存储申请记录的job_applications集合,搜索时通过子查询排除当前用户已申请的职位ID,类似SQL的左外连接排除逻辑。

实现步骤

  1. 创建申请记录集合Schema
    确保user_id设为可过滤字段,方便子查询快速定位用户的申请记录:

    const jobApplicationSchema = {
      name: 'job_applications',
      fields: [
        { name: 'user_id', type: 'string', facet: true },
        { name: 'job_id', type: 'string' },
        { name: 'applied_at', type: 'int64' },
      ],
      default_sorting_field: 'applied_at'
    };
    
  2. 同步申请记录到次级集合
    用户申请/取消职位时,直接新增/删除job_applications集合的文档:

    // 申请职位
    async applyJob(userId: string, jobId: string) {
      await this.jobApplicationRepo.create({ userId, jobId });
      // 使用upsert避免重复创建
      await this.typesenseClient.collections('job_applications').documents().upsert({
        user_id: userId,
        job_id: jobId,
        applied_at: Date.now()
      });
    }
    
    // 取消申请
    async cancelApplyJob(userId: string, jobId: string) {
      await this.jobApplicationRepo.delete({ userId, jobId });
      // 通过user_id和job_id定位文档删除
      const docs = await this.typesenseClient.collections('job_applications').documents().search({
        q: '*',
        filter_by: `user_id:='${userId}' && job_id:='${jobId}'`,
        per_page: 1
      });
      if (docs.hits.length > 0) {
        await this.typesenseClient.collections('job_applications').documents(docs.hits[0].document.id).delete();
      }
    }
    
  3. 搜索时用跨集合子查询过滤
    使用not in结合子查询,排除当前用户已申请的职位:

    async searchJobs(userId: string, query: string, page: number = 1, perPage: number = 10) {
      const searchParams = {
        q: query,
        filter_by: `id: not in (select job_id from job_applications where user_id = '${userId}')`,
        page,
        per_page: perPage,
        sort_by: 'created_at:desc'
      };
      return await this.typesenseClient.collections('jobs').documents().search(searchParams);
    }
    

优缺点

  • ✅ 优点:职位集合结构干净,申请记录独立管理,无需修改原职位文档结构
  • ❌ 缺点:依赖Typesense v0.24+的跨集合查询功能,子查询性能受用户申请数量影响(但单用户申请量通常不大,可忽略)

关键注意事项

  1. 数据一致性:必须保证PostgreSQL和Typesense的申请记录同步,建议用NestJS的事件监听或事务机制处理,避免出现数据不一致的情况。
  2. 去重与幂等性:处理申请/取消操作时要做幂等校验,避免重复添加或删除记录。
  3. 权限控制:搜索接口要确保用户只能查询自己的未申请职位,避免泄露其他用户的申请数据。

内容的提问来源于stack exchange,提问作者Stephen

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.19 04:57:35