搜索出来还需要打标 —— 标签系统的诞生

需求背景

用户同时勾选了多个搜索项,系统返回了一个列表。但产品提出了一个新问题:

"这个列表里,用户怎么知道每本书是因为什么条件被搜出来的?"

比如用户同时勾选了【好评如潮】和【历史时期-唐朝】,返回的列表里有些书两个条件都满足,有些只满足其中一个。如果能在列表项上直接显示标签,用户一眼就能看出来。

这个需求看起来只是个展示问题,但仔细想想,它和搜索项的底层逻辑完全一样。

核心矛盾

标签的本质是什么?是判断一个实体是否满足某套属性规则,满足就打上这个标签。

搜索项的本质是什么?也是判断一个实体是否满足某套属性规则,满足就被搜索出来。

规则相同,用途不同。 用于过滤时叫搜索项,用于展示时叫标签。

如果为标签单独建一套系统,就是在重复造轮子。更合理的做法是:扩展现有的搜索标签配置表,让同一条配置既能作为搜索项,也能作为标签

方案设计

search_tag_config 表上新增几个字段:

create table search_tag_config (
  tag_id            bigint primary key comment '搜索标签 ID',
  tag_name          varchar(255) comment '搜索项名称',
  category_id       bigint comment '搜索项分类ID',
  search_tag_dsl    text comment '搜索项映射的 ES 搜索 DSL',
  valid_status      int comment '标签状态,1=预发灰度,2=发布,3=下线',

  -- 新增:标签相关字段
  as_search_term    boolean comment '是否作为搜索项展示',
  as_label          boolean comment '是否作为标签展示',
  label_spel        text comment '标签规则的 SpEL 表达式',
  label_show_config text comment '标签样式配置(文案、颜色等)JSON'
)

一个搜索标签可以同时具备两种身份,通过 as_search_termas_label 字段控制:

  • as_search_term = true:在搜索栏展示,用户勾选后作为过滤条件,使用 search_tag_dsl 生成 ES 查询
  • as_label = true:在列表中展示,标记实体特征,使用 label_spel 进行规则判断

为什么用 SpEL 做标签判断

ES DSL 是给 Elasticsearch 用的查询语言,没法直接拿来判断一个 Java 对象是否满足条件。标签打标的场景是:拿到一个实体对象,判断它是否命中某条规则,返回 true/false。

SpEL(Spring Expression Language)正好适合这个场景:语法直观,支持布尔运算、属性访问、方法调用,Spring 自带无需额外依赖。

好评如潮:starLevel == 4 && commentCount > 3000
最近出版:publishTime > T(java.time.LocalDateTime).now().minusDays(7)
短篇:    wordCount <= 100000
中篇:    wordCount > 100000 && wordCount <= 200000

标签计算流程

用户搜索后,返回 ID 列表,从 MySQL 反查实体数据,然后对每条数据计算标签:

  1. 查询所有 as_label = true 且状态为发布的标签配置
  2. 对每条实体数据,遍历所有标签配置,用 SpEL 逐一判断是否命中
  3. 命中的标签 ID 列表随实体数据一起返回给前端
  4. 前端根据 label_show_config 中的样式配置渲染标签

成本对比

优化前(新增标签【好评如潮】):编写 ES DSL + 编写标签判断逻辑 + 前端开发 + 联调上线,耗时 1 天

优化后:写 ES DSL + 写 SpEL 表达式 + 配置入库,耗时 2 小时,无需上线。

统一术语:搜索标签

从这篇开始,我们不再单独称"搜索项",而是统一叫搜索标签——它既可以做搜索项,也可以做标签,是同一个概念的两种用途。

这一步解决了什么

搜索标签实现了真正的配置化:新增一个搜索标签,只需要写 DSL 和 SpEL,配置入库即可生效,无需代码开发,无需上线。

但业务继续发展,产品提出了一个新需求:分级搜索标签

比如【十分推荐】的规则是:满足【经典名著】,或者同时满足【好评如潮】和【线上热销】。这是一套用标签来定义标签的规则,当前的 SpEL 方案完全支持不了——因为标签列表本身就是一个需要计算的结果,不能作为 SpEL 的输入。

这个问题,是后续大迭代的导火索之一。但在那之前,还有一个更紧迫的问题需要先解决。

这引出了下一篇:EAV 异构的优化。