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

基于Elasticsearch 9.15的音乐搜索条件匹配实现问询

Elasticsearch 9.15 音乐搜索DSL与.NET客户端实现思路

一、DSL动态构建逻辑

1. 无关键词场景:基础过滤 + 固定排序

过滤条件

  • 时长范围匹配:使用range查询实现minDuration ≤ duration ≤ maxDuration,仅当对应参数存在时加入条件。
  • 嵌套字段全匹配:针对tags/categories/singers/qualities这类嵌套数组,每个传入的参数(如tagId/categoryId等)都通过nested查询+term精确匹配,所有传入参数需同时满足(加入bool.must)。

排序规则

按优先级依次降序排序:

  1. ratings字段直接排序
  2. favorites字段直接排序
  3. 自定义评分排序:通过脚本计算votesUp/(votesUp+votesDown),为避免除以0,可加极小值兜底(如votesUp/(votesUp+votesDown+1e-8))。

2. 有关键词场景:扩展匹配 + 条件分支

关键词分支处理

  • ISRC精确匹配:用term查询直接匹配isrc字段。
  • 多字段模糊匹配:用multi_match(或bool.should组合多个match)覆盖多语言标题(title/title_en/title_cn等)、歌手全名(singers.fullName)、标签名(tags.name)、分类名(categories.name)。嵌套字段需通过nested查询包裹,或提前用copy_to将所有需搜索的字段合并到一个顶级字段简化查询。

动态组合逻辑

所有条件均为可选:仅当对应参数存在时,才将查询加入bool.must(基础过滤)或bool.should(关键词匹配,需设置minimum_should_match:1确保至少匹配一个分支)。

二、.NET客户端(NEST)链式实现示例

以下是基于NEST的链式构建代码,核心思路是逐步向BoolQuery中添加条件,最终组装成完整的搜索请求:

using Nest;

// 实体类定义与数据结构对应
public class Music
{
    public int Duration { get; set; }
    public double Ratings { get; set; }
    public int Favorites { get; set; }
    public int VotesUp { get; set; }
    public int VotesDown { get; set; }
    public List<MusicTag> Tags { get; set; }
    public List<MusicCategory> Categories { get; set; }
    public List<MusicSinger> Singers { get; set; }
    public List<MusicQuality> Qualities { get; set; }
    public string Isrc { get; set; }
    public string Title { get; set; }
    public string Title_en { get; set; }
    public string Title_cn { get; set; }
    public string Title_de { get; set; }
    public string Title_ja { get; set; }
    // 其他字段省略
}

public class MusicTag { public int TagId { get; set; } public string Name { get; set; } }
public class MusicCategory { public int CategoryId { get; set; } public string Name { get; set; } }
public class MusicSinger { public int SingerId { get; set; } public string FullName { get; set; } }
public class MusicQuality { public int QualityId { get; set; } }

// 搜索方法实现
public ISearchResponse<Music> SearchMusic(
    int? minDuration = null,
    int? maxDuration = null,
    List<int> tagIds = null,
    List<int> categoryIds = null,
    List<int> singerIds = null,
    List<int> qualityIds = null,
    string keyword = null)
{
    var client = new ElasticClient(new Uri("http://your-es-node:9200"));
    var boolQuery = new BoolQuery();

    // 添加时长范围过滤
    if (minDuration.HasValue || maxDuration.HasValue)
    {
        boolQuery.Must.Add(new RangeQuery
        {
            Field = Infer.Field<Music>(m => m.Duration),
            GreaterThanOrEqualTo = minDuration,
            LessThanOrEqualTo = maxDuration
        });
    }

    // 添加标签全匹配
    if (tagIds?.Any() == true)
    {
        foreach (var tagId in tagIds)
        {
            boolQuery.Must.Add(new NestedQuery
            {
                Path = Infer.Field<Music>(m => m.Tags),
                Query = new TermQuery { Field = Infer.Field<MusicTag>(t => t.TagId), Value = tagId }
            });
        }
    }

    // 处理分类全匹配
    if (categoryIds?.Any() == true)
    {
        foreach (var catId in categoryIds)
        {
            boolQuery.Must.Add(new NestedQuery
            {
                Path = Infer.Field<Music>(m => m.Categories),
                Query = new TermQuery { Field = Infer.Field<MusicCategory>(c => c.CategoryId), Value = catId }
            });
        }
    }

    // 处理歌手全匹配
    if (singerIds?.Any() == true)
    {
        foreach (var singerId in singerIds)
        {
            boolQuery.Must.Add(new NestedQuery
            {
                Path = Infer.Field<Music>(m => m.Singers),
                Query = new TermQuery { Field = Infer.Field<MusicSinger>(s => s.SingerId), Value = singerId }
            });
        }
    }

    // 处理音质全匹配
    if (qualityIds?.Any() == true)
    {
        foreach (var qualityId in qualityIds)
        {
            boolQuery.Must.Add(new NestedQuery
            {
                Path = Infer.Field<Music>(m => m.Qualities),
                Query = new TermQuery { Field = Infer.Field<MusicQuality>(q => q.QualityId), Value = qualityId }
            });
        }
    }

    // 处理关键词
    if (!string.IsNullOrWhiteSpace(keyword))
    {
        // ISRC精确匹配分支
        var isrcQuery = new TermQuery { Field = Infer.Field<Music>(m => m.Isrc), Value = keyword };
        // 多字段匹配分支(含嵌套字段)
        var multiMatchQuery = new MultiMatchQuery
        {
            Query = keyword,
            Fields = new[]
            {
                Infer.Field<Music>(m => m.Title),
                Infer.Field<Music>(m => m.Title_en),
                Infer.Field<Music>(m => m.Title_cn),
                Infer.Field<Music>(m => m.Title_de),
                Infer.Field<Music>(m => m.Title_ja),
                Infer.Field<MusicSinger>(s => s.FullName).AppendParentPath(),
                Infer.Field<MusicTag>(t => t.Name).AppendParentPath(),
                Infer.Field<MusicCategory>(c => c.Name).AppendParentPath()
            },
            Type = TextQueryType.CrossFields
        };
        // 两个分支满足其一即可
        boolQuery.Should.Add(isrcQuery);
        boolQuery.Should.Add(multiMatchQuery);
        boolQuery.MinimumShouldMatch = 1;
    }

    // 设置排序规则
    var sort = new List<ISort>
    {
        new FieldSort { Field = Infer.Field<Music>(m => m.Ratings), Order = SortOrder.Descending },
        new FieldSort { Field = Infer.Field<Music>(m => m.Favorites), Order = SortOrder.Descending },
        new ScriptSort
        {
            Script = new InlineScript("doc['votesUp'].value / (doc['votesUp'].value + doc['votesDown'].value + 1e-8)"),
            Order = SortOrder.Descending,
            Type = ScriptSortType.Number
        }
    };

    // 组装搜索请求
    var searchRequest = new SearchRequest<Music>
    {
        Query = boolQuery,
        Sort = sort
    };

    return client.Search<Music>(searchRequest);
}

关键注意事项

  1. 嵌套字段处理:所有嵌套数组的查询必须用NestedQuery指定路径,否则无法正确匹配嵌套文档。
  2. 脚本排序容错:添加1e-8避免votesUp+votesDown=0时的除以0错误。
  3. 动态条件控制:通过参数是否为空/存在来决定是否加入对应查询,实现条件的动态组合。
  4. 多语言字段优化:若多语言标题较多,可提前在映射中设置copy_to: "all_titles",简化multi_match的字段列表。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.18 01:05:00