1. 页面概览

1.1 是什么

语义检索页面(SemanticSearchPage.vue)是 LightFoundry 提供的结构化语义检索入口:用户输入一段自然语言查询(如「营收」「订单」「金额」),后端在三个语义域——指标(metric)、对象(object)、属性(property)——中做文本匹配,按命中字段权重打分,返回相似度从高到低的结构化结果卡片。

它走的是 POST /ontology/semantic-search 契约(设计规格 design_overview §3.3 ①),该接口同时被 LightAIP OAG 与 LightSwift 风控消费,是一个跨产品复用的检索能力。与「对象查询」(/objects/:id/query 精确过滤)不同,语义检索面向「不知道确切的词、只记得大概说法」的场景——例如输入「订单的总销售额是多少?」这类中文整句,也能命中名称里包含「总销售额」的指标。

页面特点还包括:

1.2 核心价值

维度说明
低门槛中文整句/关键词都能搜,无需知道确切 api_name
多域覆盖一次搜索同时命中指标、对象、属性三类语义资产
可解释命中字段 + 权重分 + 进度条,清楚说明「为什么命中」
安全兜底对象级可见性过滤越权结果,未授权对象不泄漏存在性
跨产品复用同一接口被 AIP OAG 与 Swift 风控消费,语义一致

1.3 一句话总结

用一句话/关键词在 Foundry 里「想找但说不出确切名字」的指标、对象、属性,结果带相似度分数与命中说明,且严格受对象级权限过滤。

2. 访问入口

2.1 路由与菜单

2.2 认证与权限

2.3 端口与 API 前缀

3. 界面布局

页面纵向分为 4 个板块(从上到下):

┌──────────────────────────────────────────────────────────────┐
│ ① 页头:标题「语义检索」                                        │
│ ② 操作结果提示条(alert,失败红,可关闭)                        │
│ ③ 检索条件卡片                                                 │
│    ├─ 查询语句 query *(输入框,回车可搜)                       │
│    ├─ limit(上限 50) 与 data_source 过滤(可选)两输入框        │
│    ├─ 检索范围 scopes 多选:[指标(metric)] [对象(object)]    │
│    │   [属性(property)](不选=全部)                           │
│    └─ [检索] 按钮                                               │
│ ④ 检索结果卡片(共 N 条命中,展示 M 条)                         │
│    ├─ 诚实提示:接口无 offset/total 分页参数,仅返回前 limit 条   │
│    ├─ 渲染层分页条(命中数 > 10,最小档):第 x/y 页 + 上/下一页  │
│    │   + 每页 10/20/50 条下拉                                   │
│    ├─ 命中项列表:type 徽章 + name + display_name + score 进度条 │
│    ├─ 描述 + 命中字段标签                                        │
│    └─ payload 明细(可展开 JSON)                               │
└──────────────────────────────────────────────────────────────┘

各板块职责:

4. 交互元素详解

4.1 查询语句 query 输入框

属性
控件<input> 文本输入框,placeholder「输入要检索的内容,回车搜索(如:营收、订单、金额)」
必填是(handleSearchquery.trim() 为空时提示「请输入查询语句」并中止)
快捷操作@keyup.enter 直接触发检索,无需点按钮
提交处理提交前 trim(),防首尾空格影响匹配

4.2 limit 输入框

属性
控件<input type=number>min=1 max=50,绑定 v-model.number
默认20(空时前端回退 20)
前端钳制Math.min(Math.max(limit || 20, 1), 50),超界自动收进 1~50
后端兜底后端 Search 同样处理:<=0 取 20,>50 钳 50

4.3 data_source 过滤输入框

属性
控件<input type=number>,placeholder「数据源 id」,v-model.number
可选是(留空不过滤)
提交处理仅当 dataSource != null && dataSource !== ''hasFilters computed)时才在请求体加 filters: { data_source: <id> }
后端能力filters.data_source 支持数字 id 或名称;传名称时需后端注入 DataSourceNameResolver 反查(本服务已注入,按 data_sources 表解析)

4.4 检索范围 scopes 多选

属性
控件三个 checkbox(.row-check),绑定 v-model 数组 form.scopes
选项「指标(metric)」「对象(object)」「属性(property)」
语义不选 = 全部三类都搜(后端 normalizeScopes 空数组回退全类);选中的类型才参与检索
去重后端对 scope 做 strings.ToLower 去重保序,非法值忽略

4.5 「检索」按钮

属性
按钮文字「检索」(检索中变「检索中...」并禁用)
触发handleSearch:校验 query → 组装 {query, scopes, limit, filters?}POST /ontology/semantic-search
失败红条提示「语义检索失败:<error>」,结果清空为 null
成功result = {items, total}searched = true,渲染结果卡片

4.6 检索结果卡片

元素说明
标题「检索结果」+「共 N 条命中,展示 M 条」(total 为命中总数,items 为截断后展示数)
诚实提示常驻一行提示「后端接口无 offset/total 分页参数,仅返回前 limit 条(上限 50);下方分页为渲染层分页,只切分已返回结果、不减少请求数据量」
渲染层分页条仅当 items.length > 10(10 为页大小最小档)时显示:第 x / y 页 + 「上一页」「下一页」(首/末页禁用)+ 每页条数下拉(10/20/50,默认 10)。页大小切到 20/50 后若命中数不足一页,分页条与页大小下拉仍保留,便于切回更小页大小;否则切大后条会整条消失、无法恢复这是对已返回 items 的本地切片,不是服务端翻页
空态「未检索到相关内容,请调整查询语句或检索范围后重试。」
未检索态searched 为 true 但无 result 时显示「请先输入查询语句,然后点击"检索"。」

4.7 渲染层分页(补)

说明
状态page(当前页,默认 1)、pageSize(每页条数,默认 10)
显示阈值items.length > 10(页大小最小档):命中数超过最小一页即常驻分页条与页大小下拉,避免页大小切大后整条消失无法切回
派生totalPages = max(1, ceil(items.length / pageSize))pagedItems = items.slice((page-1)*pageSize, page*pageSize)
复位watch(result)watch(pageSize) 都回到第 1 页(新检索 / 改每页条数不残留页码)
边界夹紧setPage(p) 把页码夹到 [1, totalPages]
服务端口径不改请求:limit 仍参与后端「score 降序截断」(后端 Search 只接受 limit,见 5.5)

4.8 命中项卡片

元素说明
type 徽章type-metric(紫)/type-object(绿)/type-property(黄),对应 item.type
name加粗展示(api_name)
display_name右侧灰色显示,为空显示 -
score 进度条.score-bar.score-fill 宽度按 scorePercent(item.score) 填充(蓝→绿渐变),右侧显示百分比整数(Math.round(score*100)+'%'
描述item.description 存在时显示「描述:...」,title 悬浮全文
命中字段matched_fields 逐个渲染为 dim-tag 蓝色小标签,如 metric_name/object_category/property_synonyms
payload 明细<details> 折叠「payload 明细」,展开显示 JSON.stringify(payload, null, 2),超长内容 260px 滚动

5. 后端关联

5.1 API 客户端

5.2 端点表

方法路径请求体超时
POST/ontology/semantic-search{query, scopes, limit, filters?}30 秒

请求示例:

{
  "query": "订单的总销售额是多少?",
  "scopes": ["metric", "object"],
  "limit": 20,
  "filters": { "data_source": 1 }
}

5.3 响应结构

响应为 {code, data, meta}(无统一外层二次包装,前端取 res.data.data):

{
  "code": 0,
  "data": {
    "items": [
      {
        "type": "object",
        "id": "o_orders",
        "name": "orders",
        "display_name": "订单",
        "description": "订单主表",
        "matched_fields": ["object_display_name", "object_name"],
        "score": 0.9,
        "payload": {
          "category": "交易",
          "data_source_id": 1,
          "base_table": "order",
          "object_type_id": 3,
          "property_count": 12,
          "link_count": 4,
          "action_count": 2,
          "actions": ["create_order", "cancel_order"]
        }
      }
    ],
    "total": 5
  },
  "meta": { "trace_id": "t_<uuid>" }
}

错误响应:{code: "VALIDATION_ERROR", error: "query 不能为空"} 等,HTTP 状态取 ierr 映射。

5.4 关联模块表

后端包职责
foundry/semantic/search.goSemanticSearchService:检索主流程、按 scope 分发、打分与过滤、对象级可见性回调
foundry/server/semantic_handlers.gohandleSemanticSearch:注入当前用户(WithSearchUser + username 兜底)后调用服务
foundry/server/server.go组装:注入 ObjectTypeProviderMetricSearchProviderDataSourceNameResolverWithObjectAuthorizer(RBAC 对象级闸门)
foundry/metricGetSearchMetrics 适配提供指标检索条目
foundry/ontologyOntologyService.ListObjectTypes/GetObjectType 提供对象/属性数据

5.5 关键机制

打分规则(score = 命中字段最高权重,保留两位小数)

字段权重
objectname 1.0 / display_name 0.9 / category 0.7 / description 0.3
propertyname 1.0 / display_name 0.9 / synonyms 0.7
metricname 1.0 / display_name 0.9 / synonyms 0.7

匹配算法 matchField:双向子串匹配(统一小写)——方向① query 包含字段值(中文整句命中关键),方向② 字段值包含 query 分词(英文原名命中);字段值按逗号切词逐项参与(synonyms 兼容);matched_fields 收集所有命中的字段名(排序去重)。

排序与截断:score 降序;同分按 type/name 稳定排序保证确定性;total = 命中总数(截断前len(items)search.go:249),超出 limit 时 items 截断为前 limit 条(search.go:250-252)。接口无 offset/page/page_size 参数,无法翻页取更多——limit 上限 50(search.go:219-226),前端渲染层分页只切分这 ≤50 条(见 4.7)。

对象级可见性(8-4 语义反转)searchObjects/searchProperties 对每个对象先做 objectVisible 判定——已注入 WithObjectAuthorizer 时调用 AuthorizeUser(ctx, uid, uname, "ontology:<name>:read")不可见即跳过(fail-closed,不泄漏存在性),不再依赖 req.ObjectLevelVisiblesearch.go:20-22 文件头注释、:332/:424 内联注释、:56 ObjectLevelVisible 字段说明:「标志保留仅为兼容旧调用方(不再影响过滤行为)」)。仅当请求显式传 ObjectLevelVisible=true 未注入授权回调(s.authorize == nil)时,payload 才带 object_visible: "needs_authorization" 标记(search.go:383-384/462-463);本产品 server 组装时总是注入 WithObjectAuthorizer(见 5.4),故该分支在本页不会命中。

可选向量增强WithVectorSearch 注入时,向量命中的对象加入候选池并叠加 0.2 * 向量相似度matched_fields 追加 vector;本产品默认不接向量后端。

6. 核心流程详解

6.1 主流程:执行一次语义检索

  1. 在 query 输入框输入查询语句(回车或点「检索」);
  2. handleSearch 校验 query 非空 → 组装 {query, scopes, limit, filters?}
  3. POST /ontology/semantic-search → 后端 Search:空 query 直接报 query 不能为空
  4. 后端按 scope 分发:searchObjects / searchProperties / searchMetrics 各拉候选,逐条做 matchField 匹配打分;
  5. 未命中任何字段的对象/属性/指标被丢弃;命中项合并后 score 降序、limit 截断;
  6. 前端 scorePercent 渲染进度条,展示 total 与 items 数量。

6.2 对象级可见性过滤时序

  1. handleSemanticSearch 把当前用户注入 ctx(WithSearchUser(currentUserID(c))),并附带 username 兜底(跨产品 JWT 时 user_id 不在本库,按用户名反查);
  2. searchObjects/searchProperties 遍历每个对象:先 passDataSourceFilter(若配了 data_source 过滤)→ 再 objectVisible
  3. 已注入授权回调:AuthorizeUser(uid, uname, "ontology:<name>:read") 返回 false → 对象从结果中移除(8-4 语义反转后不再依赖 object_level_visible,服务端默认强制过滤);
  4. admin 角色经 AuthorizeUser 内置通配自动通过;无用户(空 uid)按无权限处理(fail-closed)。
注:object_level_visible 请求字段仍存在于契约(search.go:62),但 search.go:56 明确其「保留仅为兼容旧调用方(不再影响过滤行为)」——服务端注入授权回调后默认强制对象级过滤。因此本页不暴露该开关(暴露会产生「可关闭过滤」的误导),前端请求体亦不携带。该字段属兼容保留/已语义反转,不是 UI 缺口(详见第 9 章)。

6.3 分支流程:data_source 过滤

hasFilters 为 true 时请求体带 filters.data_source。后端 passDataSourceFilter:数字/数字字符串与对象 data_source_id 比较;字符串且非数字时经 DataSourceNameResolver 反查名称后 EqualFold 比较;未配置该 filter 恒通过。filter 传了但类型不支持时按不过滤处理(返回 false → 不命中)。

7. 权限与安全

8. 常见问题与排错

8.1 点「检索」提示「请输入查询语句」

8.2 检索失败提示「语义检索失败:...」

8.3 结果里「对象」比预期少或缺失

8.4 score 显示 0% 但该条仍出现

8.5 limit 设置了 50 结果却只有几条

9. 已知缺陷与边界

说明
object_level_visible 不暴露(设计如此,非缺口)该字段在后端已语义反转search.go:56 注明「ObjectLevelVisible 标志保留仅为兼容旧调用方(不再影响过滤行为)」,服务端注入授权回调后默认强制对象级过滤search.go:332/:424)。因此本页不提供该开关——暴露「可关闭对象级过滤」会产生误导;前端请求体不携带该字段亦不影响过滤语义
无向量后端本产品默认不注入 WithVectorSearch,纯文本匹配,无语义向量增强
过滤后 total 口径total 为过滤+去重后的命中总数,越权对象不计入
中文匹配依赖子串中文同义词需在 synonyms 里显式声明,否则整句命中依赖「query 包含字段值」方向
无服务端分页(已补渲染层分页)后端 Search 只认 limit(上限 50,search.go:219-226),无 offset/page/page_sizetotal 为截断前命中总数(:249),items 截断为前 limit 条(:250-252)。前端已补渲染层分页(10/20/50 条/页 + 上/下一页,见 4.7)——只切分这 ≤50 条已返回结果,不减少请求数据量、不能取更多命中;真正翻页需后端补 offset/分页契约(属后端契约缺口
大数据量性能object 域需遍历全部对象(properties 域还逐个拉 detail),对象很多时响应变慢

后端文件

项目文档

相邻页面链接