1. 页面概览

1.1 是什么

商品市场(Marketplace)是 LightFoundry 的「可交换资产商店」(V5 Stage 6,B5-1),对应 MarketplacePage.vue(595 行)。它让用户把 Foundry 里已建模的本体对象、指标、仪表盘、应用、Notebook 工作簿、Fusion 消解项目六类来源一键「发布」为商品(落 fm_items 表),再在任意环境(包括另一套 Foundry 实例)「安装」重建(落 fm_installs 记录)。发布物是来源内容的快照 payload——object 为 YAML 文本,其余为配置 JSON——用「来源血缘标识」rid(marketplace:<sourceType>:<sourceID>)把同一来源的多次发布串成版本链。

页面上半部是发布入口(来源类型下拉 + 来源 ID + 元信息 + 状态),下半部是商品列表与筛选(关键词/标签/类型/状态 + 标签 chip),行内提供安装/重发布/详情/删除,详情展开后能看到 payload 预览与安装记录。

1.2 核心价值

能力说明
六类来源发布object / metric / dashboard / app / notebook / fusion_project 统一发布入口,object 走 ontology.ExportObject(YAML),其余读库组包 JSON
版本链与血缘rid 唯一标识来源;同 rid 重新发布版本 +1、payload 替换为最新导出,历史安装记录保留
一键安装重建按类型分发重建:object→ImportObjects、metric→指标服务重建、dashboard/app/notebook/fusion→JSON 导入
跨环境名称规避安装时目标重名自动追加 _imported_<ts> 后缀(如 revenue_imported_1725000000),源环境内 id 不可靠时按名称重解析
unlisted 门禁状态为 unlisted 的商品禁止安装(unlisted 商品不可安装),draft 允许安装但默认隐藏于列表
安装记录审计每次安装落 fm_installs(success/failed + error + 目标引用),下载计数 +1

1.3 一句话总结

商品市场是把 Foundry 资产「发布成包、跨环境安装重建」的一键商店——来源任选六类、发布组包落库、安装自动避重名,未列出的商品拒装。

2. 访问入口

2.1 路由与菜单

2.2 认证与权限

2.3 端口与 API 前缀

端口:18081(Foundry)。API 前缀:/api(baseURL /api/v1),如 GET /api/v1/marketplace/itemsPOST /api/v1/marketplace/items

3. 界面布局

商品市场
└─ 提示条(alert,右上角「关闭」)
└─ 卡片「发布新商品」
   ├─ 来源类型 *(下拉:object/metric/dashboard/app/notebook/fusion_project)
   ├─ 来源 ID *(输入,按类型动态 placeholder/hint)
   ├─ 商品名(留空取来源名)| 描述 | 标签(逗号分隔)| 状态(published/draft/unlisted)
   └─ 按钮「发布商品」+ hint「安装到本环境时重名自动追加 _imported_<ts> 后缀」
└─ 卡片「商品列表」(共 N 个)
   ├─ 筛选行:关键词 | 标签 | 类型 | 状态 + 按钮「查询」「重置」+ 标签 chip
   ├─ 表格:名称 / 类型 / 版本 / 状态 / 下载 / 作者 / 操作
   ├─ 本地分页:上一页 / 第 x / y 页(共 N 条)/ 下一页(仅商品数 > 每页 10 条时显示)
   ├─ 诚实提示:列表接口无服务端分页、仍一次全量拉取
   └─ 详情行(展开):描述 / RID(来源血缘)/ 创建·更新 / Payload 预览 / 安装记录表

各板块职责:

4. 交互元素详解

4.1 发布新商品

元素含义必填/默认操作效果后端调用
下拉「来源类型 *」发布来源类型必填,默认 object联动来源 ID 的 placeholder/hint
输入「来源 ID *」来源资源标识必填各类型取值见下表POST /marketplace/items
输入「商品名(留空取来源名)」商品名覆盖选填留空时后端取来源默认名同上
输入「描述」商品说明选填随 meta 提交同上
输入「标签(逗号分隔)」标签列表选填逗号拆分后组 meta.tags 数组同上
下拉「状态」商品状态默认 publishedpublished / draft / unlisted同上
按钮「发布商品」(busy 显示「发布中...」)提交发布source_id 非空成功 alert「发布成功:name(v版本,type)」并清空来源 ID/名称POST /marketplace/items

来源 ID 类型提示(typeHints,placeholder 与 hint 同步):

来源类型来源 ID 语义
object(本体对象)本体对象类型 id(如 1)
metric(指标)指标 api_name(如 sales_amount)
dashboard(仪表盘)仪表盘 id(如 1)
app(应用)应用 id(如 1)
notebook(工作簿)工作簿 id(uuid)
fusion_project(消解项目)消解项目 id(uuid)

发布 payload(前端组包):POST /marketplace/items body 为 {source_type, source_id, meta: {name, description, tags, author, status}}author 前端传 undefined,由后端 handler 在 meta.Author == "" 时以 currentUserID(c) 填充。

4.2 商品列表与筛选

元素含义操作效果后端调用
输入「关键词」名称/描述模糊搜索Enter 触发查询GET /marketplace/items?q=
输入「标签」逗号分隔,任一命中Enter 触发查询GET /marketplace/items?tags=
下拉「类型」按 item_type 过滤变更即查询GET /marketplace/items?item_type=
下拉「状态」按 status 过滤变更即查询GET /marketplace/items?status=
按钮「查询」按当前筛选请求重建 query 拉列表GET /marketplace/items
按钮「重置」清空全部筛选清空 filters 并查询GET /marketplace/items
标签 chip从当前列表聚合的全部标签点击切换选中集并写入 tags 筛选GET /marketplace/items?tags=
「上一页」/「下一页」列表本地分页(每页 10 条固定)setPage 切片 items,翻页同时收起详情行(详情所属商品可能不在当前页)无(纯前端切片)

分页为前端渲染分页GET /marketplace/items 不支持 limit/offset/page(见 5.2 与 5.5),商品列表仍一次全量拉取后本地切片;筛选(查询/重置/标签 chip/类型/状态)触发重新拉取时页码重置为第 1 页。当商品数 ≤ 10 时分页条不显示。

列表列:名称(含描述截断与标签)、类型(tag-blue 徽章)、版本(v{n})、状态(published=绿/term-approved、unlisted=红/term-deprecated、其余中性)、下载(downloads 计数)、作者(无则 -)、操作。

行内按钮:

按钮含义操作效果后端调用
「安装」/「安装中...」一键安装busyItem 锁定;成功 alert「安装成功:target_ref」,失败 alert「安装失败:error」POST /marketplace/items/:id/install
「重发布」从来源重新导出confirm「重新发布"xxx"?将从来源重新导出并升级到 vN+1(历史安装记录保留)。」POST /marketplace/items/:id/publish
「详情」/「收起」展开/收起详情行展开时加载安装记录GET /marketplace/items/:id/installs
「删除」删除商品confirm「确定删除商品"xxx"吗?(安装记录保留)」;不级联安装记录DELETE /marketplace/items/:id

4.3 详情行

区块说明
描述商品描述,无则「(无)」
RID(来源血缘)marketplace:<sourceType>:<sourceID>,code 样式展示
创建 / 更新时间戳格式化(T→空格,截 19 位)
Payload 预览payloadPreview:解析信封后 format=yaml 取 content 文本,format=json 取 JSON.stringify(content, null, 2),解析失败以 raw 展示原始 payload
安装记录mini-table(版本/状态/目标/时间);状态 success=绿、failed=红;含 error 的行另列出 error 文本

5. 后端关联

5.1 API 客户端

本页使用 action/web/src/api/client.jsbaseURL: '/api/v1'timeout: 30000、aip_token Bearer 注入、401 跳登录),无自定义导出函数;列表查询用 URLSearchParamsbuildQuery()

5.2 端点表

方法路径(前缀 /api/v1)请求体/参数用途
GET/marketplace/itemsquery tagsqitem_typestatuslimit/offset/page商品列表(tags 内存过滤,命中任一即返回;全量返回,无服务端分页)
POST/marketplace/items{source_type, source_id, meta:{name,description,tags,author,status}}从来源发布创建商品
GET/marketplace/items/:id商品详情(含 payload)
PUT/marketplace/items/:id{name?, description?, tags?, author?, status?}更新商品元信息(指针语义)
DELETE/marketplace/items/:id删除商品(不级联安装记录)
POST/marketplace/items/:id/publishbody 可空(可选覆盖 name/description/tags/author)按 rid 重导出,版本 +1
POST/marketplace/items/:id/installbody 可空;user_id 由鉴权注入一键安装,落 fm_installs
GET/marketplace/items/:id/installs安装记录列表

5.3 响应结构

商品列表 GET /api/v1/marketplace/items

{ "code": 0, "data": { "items": [
  { "id": "uuid", "rid": "marketplace:metric:revenue", "name": "收入指标",
    "description": "...", "item_type": "metric", "version": 2,
    "author": "u1", "tags": "[\"指标\",\"销售\"]", "status": "published",
    "downloads": 3, "payload": "{\"format\":\"json\",\"content\":{...}}",
    "created_at": "...", "updated_at": "..." }
], "total": 1 } }

安装返回 POST /api/v1/marketplace/items/:id/install

{ "code": 0, "data": { "id": "uuid", "item_id": "uuid", "version": 2,
  "target_ref": "metric:revenue_imported_1725000000", "status": "success",
  "created_at": "..." } }
安装失败不返回 HTTP 错误——Install 把失败写进安装记录(status: "failed" + error)后照常 200,前端依据 rec.status 分别提示。

5.4 关联模块表

后端包/文件职责
foundry/marketplace/service.goPublish / Install / List / Update / Delete / ListInstalls;payload 组包与分发
foundry/marketplace/models.gofm_items / fm_installs 模型与状态常量
foundry/marketplace/rest.go路由注册与 handler(maxJSONBodyBytes 2MB)
foundry/ontology(yaml.go)ExportObject / ImportObjects(object 商品)
foundry/metric / dashboard / apps / notebook / fusion各来源导出/重建端口(Deps 注入)

5.5 关键机制

6. 核心流程详解

6.1 发布流程

  1. 选择来源类型(如 metric),填来源 ID(如 sales_amount),可覆盖商品名、描述、标签(逗号分隔)、状态。
  2. 点「发布商品」→ POST /marketplace/items;后端 exportMetric 读库组包(实体+维度+度量+指标定义,实体引用对象 api_name),封装 {"format":"json","content":...} 落库。
  3. 若该 rid(marketplace:metric:sales_amount)已存在,则视为重新发布:版本 +1、payload 替换,历史安装记录保留。
  4. 前端 alert「发布成功:收入指标(v1,metric)」并清空来源 ID 与商品名。

6.2 安装流程

  1. 列表中找到目标商品(确认状态非 unlisted),点「安装」。
  2. 后端 Install 前置检查 unlisted 拒绝;按 item_type 分发重建。
  3. 重名规避:metric 实体名/指标名、dashboard/app/notebook/fusion 名称命中即加 _imported_<ts>
  4. 结果落 fm_installs;成功 downloads +1、前端 alert「安装成功:metric:revenue_imported_xxx」;失败 alert「安装失败:<error>」且详情行安装记录中出现 failed 行与 error 文本。

6.3 重新发布与删除

6.4 状态与终态语义

商品状态机 draft → published → unlisted:published 正常安装;unlisted 禁止安装但列表仍可见(便于下架);draft 可安装但列表默认隐藏(筛选状态需手动选 draft)。安装是同步操作(无轮询),busyItem 行级锁定防重复点击。

7. 权限与安全

8. 常见问题与排错

8.1 发布失败「对象类型/指标 xxx 不存在」

现象:发布时报 NOT_FOUND,如指标 sales_amount 不存在。原因:来源 ID 与类型不匹配(metric 用数字 id 而非 api_name,或 id 写错);object 的 source_id 非数字。排查步骤:1) 核对 typeHints:object/dashboard/app 用数字 id,metric 用 api_name,notebook/fusion 用 uuid;2) 在对应来源页面确认资源真实存在;3) 抓包看错误详情。

8.2 安装失败但列表无变化

现象:点安装后 alert「安装失败:...」,商品 downloads 不变。原因:安装失败写进 fm_installs 记录而非 HTTP 错误;常见为 payload 缺失/非法、源服务未注入、重建目标冲突。排查步骤:1) 点「详情」查看安装记录中的 failed 行与 error 文本;2) 确认各来源服务(Ontology/Metric/Dashboard/Apps/Notebook/Fusion)已注入 deps;3) object 安装失败常见为「对象导入未创建任何对象(skipped=N)」——检查 YAML 引用完整性。

8.3 unlisted 商品安装被拒

现象:点安装直接报「unlisted 商品不可安装」。原因:商品状态被设为 unlisted(下架语义)。排查步骤:1) 属预期行为——下架商品不可再装;2) 如需恢复安装,用「重发布」或 PUT 接口把状态改回 published;3) 注意重发布会把状态重置为 published(后端 publish 的 meta.Status 默认 published)。

8.4 安装后出现 _imported_ 后缀的新资源

现象:安装的指标/仪表盘名称带 _imported_1725000000 后缀。原因:本环境已存在同名资源,nameExists 判定后自动避重名(跨环境安装的预期行为,避免覆盖既有资产)。排查步骤:1) 确认这是设计语义而非错误;2) 如需同名覆盖,先删除本环境同名资源再安装;3) 在详情安装记录的 target_ref 中确认实际创建的引用名。

8.5 列表报「暂无商品(后端可能未就绪或筛选无结果)」

现象:列表空。原因:后端未就绪(路由未挂载/404)、无任何商品、或当前筛选无匹配。排查步骤:1) 看 alert 是否有错误提示;2) 点「重置」清空筛选;3) 直接访问 GET /api/v1/marketplace/items 验证后端。

9. 已知缺陷与边界

项目说明
安装失败不返回 HTTP 错误失败状态承载在 fm_installs 记录,前端需看详情行才能定位
tags 过滤在内存商品量大时列表全量拉取后内存过滤,性能随量线性
重发布状态重置publish 默认将状态置回 published,unlisted 商品重发布后变为可安装
object 安装全量导入ImportObjects 两遍扫描、单对象失败不整体回滚,created=0 时报错
列表无服务端分页GET /marketplace/items 不支持 limit/offset/pagemarketplace/service.go:917-922rest.go:51-64),前端已补渲染层分页(10 条/页)但仍一次全量拉取,商品量大时首屏请求体积不变;真正分页需后端补分页契约
详情 payload 大文本预览区 max-height 260px 滚动,超大 payload 展开成本高
各来源服务未注入即报错Fusion 等 deps 可空,未注入时发布/安装返回明确错误「Fusion 服务未注入」