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 路由与菜单
- 路由路径:
/foundry/marketplace,路由名FoundryMarketplace,meta.title为「Foundry 商品市场」,meta.requiresAuth: true(定义于action/web/src/router/index.js第 370-375 行)。 - 菜单入口:Foundry 侧边栏(
action/web/src/views/FoundryLayout.vue第 111 行)菜单项「商品市场」。 - 源码文件:
action/web/src/views/MarketplacePage.vue(595 行)。
2.2 认证与权限
- 路由级
requiresAuth: true:未登录被拦截。 - API 级认证:经
action/web/src/api/client.js注入aip_token;401 跳/login。 - 权限要求:本页接口挂在 Foundry
protected组(RegisterMarketplaceRoutes由接线挂载),任意已登录用户可发布/安装/删除;删除与重发布以商品作者与角色语义由各来源服务约束。安装时user_id由鉴权中间件注入(currentUserID(c)),作为安装记录的创建者与指标等资源的 owner。 - 404 排错:若列表空且 alert 报 404,确认后端 18081 已启动、
protected组已挂RegisterMarketplaceRoutes(marketplace/rest.go);Vite 代理/api指向 Foundry 而非其它产品后端。
2.3 端口与 API 前缀
端口:18081(Foundry)。API 前缀:/api(baseURL /api/v1),如 GET /api/v1/marketplace/items、POST /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 预览 / 安装记录表
各板块职责:
- 发布新商品卡片:发布入口;来源 ID 的 placeholder 与 hint 随来源类型联动(见 typeHints)。
- 商品列表卡片:全量或筛选后的商品表;每行按钮安装/重发布/详情/删除;点击「详情」在行内展开 payload 预览与安装记录(不跳页)。
- 详情行:展示描述、RID 血缘、时间戳、payload 预览(object 为 YAML 文本,其余为配置 JSON,非法 payload 以 raw 展示)与安装记录 mini-table。
4. 交互元素详解
4.1 发布新商品
| 元素 | 含义 | 必填/默认 | 操作效果 | 后端调用 |
|---|---|---|---|---|
| 下拉「来源类型 *」 | 发布来源类型 | 必填,默认 object | 联动来源 ID 的 placeholder/hint | — |
| 输入「来源 ID *」 | 来源资源标识 | 必填 | 各类型取值见下表 | POST /marketplace/items |
| 输入「商品名(留空取来源名)」 | 商品名覆盖 | 选填 | 留空时后端取来源默认名 | 同上 |
| 输入「描述」 | 商品说明 | 选填 | 随 meta 提交 | 同上 |
| 输入「标签(逗号分隔)」 | 标签列表 | 选填 | 逗号拆分后组 meta.tags 数组 | 同上 |
| 下拉「状态」 | 商品状态 | 默认 published | published / 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.js(baseURL: '/api/v1'、timeout: 30000、aip_token Bearer 注入、401 跳登录),无自定义导出函数;列表查询用 URLSearchParams 组 buildQuery()。
5.2 端点表
| 方法 | 路径(前缀 /api/v1) | 请求体/参数 | 用途 |
|---|---|---|---|
| GET | /marketplace/items | query tags、q、item_type、status(无 limit/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/publish | body 可空(可选覆盖 name/description/tags/author) | 按 rid 重导出,版本 +1 |
| POST | /marketplace/items/:id/install | body 可空;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": "..." } }
Install 把失败写进安装记录(status: "failed" + error)后照常 200,前端依据 rec.status 分别提示。5.4 关联模块表
| 后端包/文件 | 职责 |
|---|---|
foundry/marketplace/service.go | Publish / Install / List / Update / Delete / ListInstalls;payload 组包与分发 |
foundry/marketplace/models.go | fm_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 关键机制
- payload 信封:
fm_items.payload为{"format":"yaml"|"json","content":...}——object 的 content 为 YAML 文本字符串;metric/dashboard/app/notebook/fusion 的 content 为导出配置 JSON 对象。 - rid 血缘:
rid = marketplace:<sourceType>:<sourceID>;getByRID命中即视为同商品重新发布(版本 +1、payload 替换、作者/标签/状态/描述更新,历史安装记录保留);首发布版本 1。 - 跨环境名称规避:安装时按各目标表做
nameExists重名判定,重名即追加_imported_<unix秒>(importSuffix),如revenue_imported_1725000000;metric 安装时对象类型以「原 id 优先、跨环境按 api_name 重解析」(ResolveObjectTypeID),分子/分母/度量以名称映射重建(measureNameByID)。 - unlisted 拒绝:
Install前置检查item.Status == StatusUnlisted→ValidationError("unlisted 商品不可安装")。 - 安装语义:成功返回 target_ref(
metric:name、dashboard:name、app:name、notebook:name、fusion:name、ontology:imported=N),downloads 计数 +1;失败仅写安装记录,不阻断列表展示。 - 标签过滤在内存:tags 为 JSON 文本无法 SQL 过滤,
List先按 item_type/status/q 落 SQL,再内存hasAnyTag过滤。 - 无服务端分页(前端渲染分页):列表查询参数
ListQuery只有 Tags/Q/ItemType/Status 四字段(foundry/marketplace/service.go:917-922),List直接Find全量(service.go:925-955),handler 也只解析这四个 query(foundry/marketplace/rest.go:51-64),无limit/offset/page;故本页分页只能在渲染层切片,数据仍一次全量拉取。
6. 核心流程详解
6.1 发布流程
- 选择来源类型(如
metric),填来源 ID(如sales_amount),可覆盖商品名、描述、标签(逗号分隔)、状态。 - 点「发布商品」→
POST /marketplace/items;后端exportMetric读库组包(实体+维度+度量+指标定义,实体引用对象 api_name),封装{"format":"json","content":...}落库。 - 若该 rid(
marketplace:metric:sales_amount)已存在,则视为重新发布:版本 +1、payload 替换,历史安装记录保留。 - 前端 alert「发布成功:收入指标(v1,metric)」并清空来源 ID 与商品名。
6.2 安装流程
- 列表中找到目标商品(确认状态非 unlisted),点「安装」。
- 后端
Install前置检查 unlisted 拒绝;按 item_type 分发重建。 - 重名规避:metric 实体名/指标名、dashboard/app/notebook/fusion 名称命中即加
_imported_<ts>。 - 结果落 fm_installs;成功 downloads +1、前端 alert「安装成功:metric:revenue_imported_xxx」;失败 alert「安装失败:<error>」且详情行安装记录中出现 failed 行与 error 文本。
6.3 重新发布与删除
- 重发布:confirm 后
POST /marketplace/items/:id/publish,后端按 rid 重新导出(body 可覆盖 meta),版本 +1。 - 删除:confirm 后
DELETE /marketplace/items/:id,删除商品但不级联安装记录(保留审计痕迹)。
6.4 状态与终态语义
商品状态机 draft → published → unlisted:published 正常安装;unlisted 禁止安装但列表仍可见(便于下架);draft 可安装但列表默认隐藏(筛选状态需手动选 draft)。安装是同步操作(无轮询),busyItem 行级锁定防重复点击。
7. 权限与安全
- 认证:aip_token JWT;401 跳登录。
- 数据级安全:发布/安装/删除在 protected 组下,任意已登录用户可执行;安装用户作为指标等资源的 owner 写入;payload 为来源快照,安装重建不读取源环境运行时数据。
- 写操作防护:删除/重发布有 confirm 二次确认;
busy/busyItem禁用重复提交;decodeJSON限制 body 2MB;错误仅透传 error 文本。
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/page(marketplace/service.go:917-922、rest.go:51-64),前端已补渲染层分页(10 条/页)但仍一次全量拉取,商品量大时首屏请求体积不变;真正分页需后端补分页契约 |
| 详情 payload 大文本 | 预览区 max-height 260px 滚动,超大 payload 展开成本高 |
| 各来源服务未注入即报错 | Fusion 等 deps 可空,未注入时发布/安装返回明确错误「Fusion 服务未注入」 |