引言
嵌入式分析(embedded analytics)指的是把图表、仪表盘或完整分析能力嵌进第三方产品——你的 SaaS 后台里出现一块「数据分析」标签页,客户在自己的系统里看到属于自己租户的数据图表。它与「给分析师用的 BI 平台」是两种产品:前者面对的是产品的最终用户,图表要像产品原生功能一样无缝;后者面对的是数据团队,追求自助探索的灵活度。
难点几乎全在边界上。安全边界:嵌入的图表必须只能看到当前租户、当前用户有权看的数据,任何越权都是严重事故。身份边界:产品有自己的用户体系,BI 有自己的一套,两者必须打通且不能双份维护。体验边界:白标要求图表的样式、字体、加载态都融入宿主产品,任何「BI 工具味」的痕迹都是减分项。这三条边界共同决定了嵌入式分析是一套以安全与集成为核心的工程,而非单纯的图表渲染。
本文按「形态 → 嵌入方式 → 安全 → 隔离 → 定制 → 集成 → 运维」的顺序展开。与 Metabase 轻量 BI 平台 和 Superset 自助分析平台 的差别是:那两篇讲平台自身的能力与治理,本文聚焦「把分析能力嵌进产品」时的安全模型与集成模式。
目录
- 嵌入式分析的三种形态与选型
- iframe 与 SDK 嵌入的取舍
- 签名令牌与安全边界
- 行级权限与多租户数据隔离
- 白标与主题定制
- 单点登录与用户身份映射
- 查询下推与缓存策略
- 前端集成的生命周期管理
- 审计、用量计量与配额
- 租户隔离下的性能
- 部署形态与多环境治理
- 安全与集成陷阱
1. 嵌入式分析的三种形态与选型
按集成深度分三档,选择取决于产品对「分析」的定位。
| 形态 | 集成方式 | 定制能力 | 实现成本 | 适合 |
|---|---|---|---|---|
| 嵌入式仪表盘 | iframe 嵌入整块看板 | 低(主题级) | 低 | 快速上线、看板固定 |
| 嵌入式图表 | SDK/JS 嵌入单个图表 | 中(样式+布局) | 中 | 图表要融入自有页面 |
| 分析即服务 | 只取查询 API,前端自绘 | 高(完全自控) | 高 | 图表是产品核心体验 |
形态一是最快落地的方案:BI 平台提供 iframe 嵌入,产品把整个看板塞进一个页面。缺点是定制能力受限,宿主无法控制内部布局,且 iframe 的加载态、错误态难以与产品统一。
形态二通过 SDK 把单个图表挂到宿主 DOM 上,宿主负责页面布局,BI 只负责图表本身。这是大多数 SaaS 的选择——既保留图表的成熟能力,又让页面结构由产品掌控。
形态三最灵活:BI 退化成「查询服务」,产品拿到聚合后的数据自己用图表库渲染。它适合分析是产品核心竞争力的场景,但意味着要自己承担图表工程的全部成本(见 ECharts 图表工程实战 )。选择的关键是问「分析能力是产品的差异化卖点吗」——是,走形态三;否,形态一或二。
实践中常见的是混合形态:产品的主看板用 SDK 嵌入(融入自有页面),临时探索与深度分析用 iframe 嵌入完整 BI(保留平台的灵活度)。这种「两层结构」平衡了体验与能力——产品自研的看板负责高频、固定的核心指标,平台提供的分析界面负责长尾、临时的探索需求。
选型还要考虑演进路径。产品早期用 iframe 快速上线,随着需求变复杂逐步迁移到 SDK 与形态三——迁移成本主要来自「看板定义能否导出与复用」。因此即便起步用 iframe,也应优先选择支持规格化导出(如可导出为 JSON 规格)的平台,为后续迁移留出余地。
2. iframe 与 SDK 嵌入的取舍
iframe 与 SDK 各有不可替代的场景,不能简单说哪个更好。
iframe 的优势:天然的样式与脚本隔离。BI 的 CSS 不会污染宿主,宿主的样式也不会破坏图表;BI 的 JS 与宿主运行在不同执行上下文,第三方库冲突、全局变量污染都被隔绝。安全上也更干净——跨域 iframe 受同源策略保护,宿主无法读取 iframe 内部数据。它的劣势同样是隔离:宿主无法直接操作 iframe 内部 DOM,尺寸自适应要靠 postMessage 通信,加载态与错误态要在两侧各做一套。
<iframe
src="https://bi.example.com/embed/dashboard/42?token=..."
style="width:100%; height:600px; border:0"
sandbox="allow-scripts allow-same-origin allow-popups"
referrerpolicy="no-referrer"
title="销售分析看板"
></iframe>
SDK 的优势:图表与宿主在同一上下文,尺寸自适应、主题继承、事件联动都自然。劣势是样式与脚本可能冲突,且宿主要承担 SDK 的版本升级成本。
sandbox 属性是 iframe 的安全关键。默认应最小化权限,按需开放:allow-scripts(必须)、allow-same-origin(若 BI 需要读写 cookie/localStorage)、allow-popups(若图表有外链)、allow-forms。不要同时给 allow-scripts 与 allow-same-origin 且嵌入第三方内容——那等于给了对方在宿主域下执行脚本的能力(若同源)。跨域嵌入时两者同时存在是安全的,但仍应审查必要性。
通信要用 postMessage 且校验来源:
// 宿主侧:监听 iframe 事件,严格校验 origin
window.addEventListener('message', (e) => {
if (e.origin !== 'https://bi.example.com') return; // 必须校验
if (e.data?.type === 'dashboard:resize') {
iframe.style.height = `${e.data.height}px`;
}
});
不校验 e.origin 是最常见的 iframe 通信漏洞——任何页面都能向宿主窗口发消息,不校验就可能导致信息泄漏或注入。
3. 签名令牌与安全边界
嵌入式分析的认证不能用「用户名密码」或「长期 API Key」,而是服务端签发的短时效签名令牌(signed token / JWT)。
流程是:宿主后端用与 BI 共享的密钥,签一个包含用户身份、租户、权限声明、过期时间的令牌,前端拿到令牌后交给 BI 的嵌入 SDK 或作为 iframe 参数。密钥只存在宿主后端,绝不下发到浏览器。
// 宿主后端:签发嵌入令牌(Node 示例)
import jwt from 'jsonwebtoken';
function signEmbedToken({ userId, tenantId, role, dashboardId }) {
return jwt.sign(
{
sub: userId,
tenant: tenantId,
role, // 决定行级权限
dashboards: [dashboardId], // 限定可访问范围
exp: Math.floor(Date.now() / 1000) + 300, // 5 分钟过期
},
process.env.BI_EMBED_SECRET, // 仅在服务端
{ algorithm: 'HS256' },
);
}
四条安全规则。其一,令牌要短时效(几分钟级),过期后由前端向宿主后端换取新令牌,而不是给长时效令牌。其二,令牌要限定范围(能访问哪些看板、哪些租户),遵循最小权限。其三,令牌不能出现在 URL 查询串里被日志记录——优先用 postMessage 传递或一次性兑换码。其四,签名密钥要能轮换,支持新旧双密钥并存的过渡期。
一次性兑换码(auth code)模式比直接传令牌更安全:前端先拿一个短时效的一次性码,BI 侧用它向宿主后端换取真正的令牌。这样令牌从不经过浏览器,URL 里只出现一次性码。代价是增加一次往返,但安全收益明显。
4. 行级权限与多租户数据隔离
多租户是嵌入式分析的核心难题:同一个看板定义,不同租户看到的数据必须严格隔离,且不能为每个租户复制一份看板。
方案一,行级安全(RLS)。在查询层注入租户过滤条件,BI 生成的 SQL 自动带上 WHERE tenant_id = ?。这需要 BI 支持 RLS 且能把令牌里的租户声明传到查询层。Superset 的 RLS 与 Metabase 的沙箱都属此类。
方案二,物理隔离。每个租户一个数据库(或 schema),连接由令牌决定。隔离最强,运维成本最高,适合强合规场景。
方案三,数据沙箱。把租户字段作为隐藏的强制过滤列,用户无法移除该过滤。
-- RLS 的本质:BI 在用户 SQL 外层套上租户过滤
SELECT * FROM (
<用户定义的查询>
) AS user_query
WHERE tenant_id = '{{ current_tenant }}';
三种方案的取舍:
| 方案 | 隔离强度 | 运维成本 | 适合 |
|---|---|---|---|
| 行级安全 | 中(依赖 BI 正确实现) | 低 | 多数 SaaS |
| 数据沙箱 | 中高 | 中 | 需强制过滤 |
| 物理隔离 | 高 | 高 | 强合规、大客户 |
最危险的反模式是「在应用层过滤」:让前端传租户 ID 作为筛选条件,服务端不做强制。只要用户改一个参数就能看到别人的数据。隔离必须发生在服务端且不可被客户端绕过——这是嵌入式分析的红线。
权限要分层:租户级(数据行)、角色级(能看哪些指标/看板)、操作级(能否导出、能否下钻到明细)。三层缺一不可——只做租户隔离但所有人都能导出明细,等于把数据泄漏的口子留在了导出上。
5. 白标与主题定制
白标(white-label)要求图表看不出「用的是某 BI 工具」。四个层面。
其一,视觉主题。颜色、字体、圆角、间距都要匹配宿主的设计令牌。多数 BI 平台支持传入主题配置或 CSS 变量。
// SDK 主题注入:把宿主设计令牌映射到 BI 主题
const theme = {
colors: {
primary: brandTokens.color.primary,
categorical: brandTokens.chart.categorical,
background: brandTokens.color.surface,
},
font: { family: brandTokens.font.family, size: 13 },
borderRadius: brandTokens.radius.card,
};
embedDashboard({ token, theme });
其二,去除品牌痕迹。BI 工具的 logo、页脚、帮助入口、默认的加载动画都要能关闭。iframe 形态下尤其要注意——很多平台的 iframe 默认带自己的页眉页脚。
其三,加载态与错误态。宿主产品有自己的骨架屏(skeleton)与错误提示规范,图表加载时不能跳出一个风格迥异的转圈。宿主应控制外层的加载与错误展示,把 BI 内部的加载态隐藏。
其四,深色模式同步。宿主切换到深色主题时,图表要同步切换。这要求主题既能静态传入,也能在运行时动态更新。把主题做成可订阅的状态,宿主主题变化时通知 BI 重新渲染,而不是要求用户刷新页面。
6. 单点登录与用户身份映射
不能让用户在 BI 里再登录一次。嵌入式分析必须支持 SSO,且宿主用户与 BI 用户要一一映射。
两种映射策略。策略一,预同步:宿主的用户创建/更新/删除时,同步到 BI 的用户体系。缺点是双写、易不一致。策略二,即时创建(JIT provisioning):令牌里带上用户信息,BI 在首次见到该用户时自动创建。这是更现代的方案,避免了同步的一致性问题。
// JIT:令牌里携带足够的信息,BI 侧首次见到即建用户
{
sub: 'user_12345', // 宿主用户 ID,作为 BI 侧的外部 ID
email: 'alice@customer.com',
tenant: 'acme',
groups: ['analyst'], // 映射到 BI 的角色
exp: ...,
}
用宿主的用户 ID 作为稳定标识,而不是邮箱——邮箱会变,用户 ID 不会。删除用户时要有回收机制:宿主的用户停用后,其对应的 BI 会话与令牌要能立即失效,否则离职员工仍能通过旧令牌访问。
SSO 的常见坑是「角色映射不一致」:宿主有「管理员/普通用户」,BI 有「admin/editor/viewer」,映射关系要显式定义并测试。最忌讳的是把宿主管理员自动映射成 BI 管理员——BI 管理员往往能看全租户数据,这在多租户场景下是越权。
会话撤销要即时生效。用户被停用、角色被降级后,其已签发的令牌在过期前仍然有效。短时效令牌能缩小这个窗口(几分钟),但对敏感操作(如导出全量数据)应做即时校验——在关键操作前回调宿主后端确认用户状态。「令牌有效」不等于「用户仍有权限」,高敏感操作必须二次确认。
7. 查询下推与缓存策略
嵌入的图表查询的是宿主的业务数据,性能直接受数据层影响。
查询下推:让 BI 生成尽可能靠近数据源的查询,而不是把原始数据拉到 BI 层再聚合。下推的深度取决于数据源能力——对列式数仓(如 ClickHouse
),BI 应生成带 GROUP BY、WHERE 的聚合 SQL 直接执行;对不支持复杂查询的源,则可能需要部分在应用层聚合。
缓存分层:
| 层级 | 缓存对象 | 失效策略 | 收益 |
|---|---|---|---|
| 结果缓存 | 查询结果 | TTL / 显式失效 | 高 |
| 图表缓存 | 渲染后的图 | 数据变更时失效 | 中 |
| 数据源缓存 | 连接与元数据 | 长 TTL | 低 |
按租户维度缓存。多租户下同一个看板的查询结果因租户而异,缓存键必须包含租户 ID,否则会串数据——这是缓存引入的最严重的安全风险。缓存键至少要包含「租户 + 看板 + 筛选参数 + 用户角色」。
// 缓存键必须包含租户与权限维度
const cacheKey = `chart:${tenantId}:${dashboardId}:${userId}:${hash(filters)}`;
预热与失效。高频看板可以在数据更新后主动预热缓存;实时性要求高的看板则用短 TTL 或走实时查询。失效策略要能按租户粒度触发——某个租户的数据更新不应让所有租户的缓存失效。
预聚合是缓存之外的另一条路。对于固定维度的看板,可以在数据写入时就把结果聚合成物化视图或聚合表,查询时直接读取。它比结果缓存更彻底——省去了每次查询的聚合计算,代价是灵活性下降(只能查预聚合的维度组合)。把「高频固定维度」预聚合、「长尾临时维度」走实时查询,是嵌入场景下的常见组合。
缓存要能观测命中率。命中率低说明缓存键设计有问题(如把高基数参数放进了键)或 TTL 太短。按租户与看板统计命中率,能定位到哪些图表白白浪费了缓存层。缓存不是越多越好——维护一致性本身也有成本。
8. 前端集成的生命周期管理
SDK 集成本质上是在宿主的 SPA 里管理一批图表的生命周期。
import { embedDashboard } from 'bi-sdk';
class EmbeddedDashboard {
private handle: { destroy: () => void } | null = null;
async mount(container: HTMLElement, token: string) {
this.handle = await embedDashboard({
container,
token,
onError: (err) => this.reportError(err),
onLoaded: () => this.hideSkeleton(),
});
}
async unmount() {
this.handle?.destroy(); // 必须显式销毁,释放监听与连接
this.handle = null;
}
async refreshToken() {
// 令牌快过期时向宿主后端换取新令牌,而不是重新挂载
const token = await fetchEmbedToken();
this.handle?.updateToken?.(token);
}
}
三条规则。其一,挂载与卸载成对。SPA 路由切换、标签页关闭都要调用 destroy,否则累积僵尸实例与监听。其二,令牌续期走更新而非重挂载。重新挂载会重建整个图表,闪烁且丢失状态。其三,容器尺寸变化用 ResizeObserver,不要用 window.resize——宿主布局变化(如侧边栏收起)不一定触发 window 事件。
错误要有降级。BI 服务不可用时,图表区域应显示友好的占位而非白屏或报错堆栈。把 BI 的可用性对产品的影响降到最低是嵌入集成的核心目标——分析功能挂了,产品的主流程不能挂。
9. 审计、用量计量与配额
嵌入分析是多租户共享的基础设施,必须能回答「谁在什么时候查了什么」。
审计日志要记录:租户、用户、看板/图表、执行的查询、耗时、返回行数、是否导出。这些既是合规要求(谁看了哪些数据),也是运营依据(哪些图表受欢迎)。
{
"ts": "2026-10-07T07:45:12Z",
"tenant": "acme",
"user": "user_12345",
"action": "query",
"resource": "dashboard:42",
"duration_ms": 320,
"rows": 1840,
"exported": false
}
**用量计量(metering)**用于按用量计费或配额控制。常见计量维度:查询次数、渲染时长、导出次数、存储的看板数。计量要按租户聚合,且要能对账——计量数字与账单不符会引发信任危机。
配额与限流。防止单个租户拖垮共享基础设施:限制并发查询数、限制单次查询返回行数、限制导出频率。限流要按租户维度,且要有明确的错误提示(「查询过于频繁,请稍后再试」)而非静默失败。
导出是高风险操作:它把数据从受控的图表界面导出到不受控的文件。导出必须有独立的权限开关与审计,且要考虑导出行数上限——一次导出百万行既是性能风险也是数据泄漏风险。
审计日志本身要合规。涉及个人数据时,日志里的用户标识、查询内容可能也受隐私法规约束——日志需要脱敏、设定保留期、并控制访问权限。审计日志与业务数据适用同等的保护标准,不能因为「它只是日志」就疏于管理。
用量数据要能对账。计量系统与账单系统的数字必须一致,差异要能追溯到具体原因(重复计数、时区导致的边界差异、失败查询是否计费)。对账机制应在设计阶段就考虑,事后补救往往发现历史数据已无法还原。这对按用量计费的商业模式尤其关键。
10. 租户隔离下的性能
多租户共享同一套 BI 基础设施,性能隔离(noisy neighbor)是必须处理的问题。
问题:一个租户写了低效查询或拉了海量数据,会拖慢所有租户。应对:按租户设置查询超时与资源上限,用独立的查询队列或资源池隔离大租户。关键租户(付费大客户)应能独占资源。
连接池要按租户隔离或限流。共享连接池下,一个租户的慢查询会占满连接,导致其他租户排队。给每个租户设最大并发连接数是简单有效的隔离手段。
# 租户级资源配额
tenant_quota:
default:
max_concurrent_queries: 5
query_timeout_seconds: 30
max_rows: 50000
enterprise:
max_concurrent_queries: 30
query_timeout_seconds: 120
max_rows: 500000
冷热数据分离。活跃租户的常用看板结果缓存住,长尾租户走实时查询。按租户的使用频率动态调整缓存策略,把资源倾斜给高频租户。
监控要按租户切片。整体的 P95 查询延迟可能很健康,但某个租户可能长期在超时边缘。按租户维度看延迟与错误率,才能发现被平均值掩盖的问题。
弹性伸缩要跟上租户增长。多租户下负载的峰值往往来自少数大租户的批量查询,固定容量要么平时浪费要么峰值打满。把查询执行层做成可水平扩展的(无状态、可加副本),配合队列深度或队列等待时间做扩缩容信号,比按 CPU 利用率更贴合查询型负载的特征。
11. 部署形态与多环境治理
嵌入式分析的部署形态影响运维复杂度。
单实例多租户:所有租户共享一套 BI 实例。成本最低,隔离最弱,适合中小规模。每租户独立实例:隔离最强,成本最高,适合强合规或大客户。混合:多数租户共享,大客户独立实例——这是常见的折中。
多环境要一致:开发、预发、生产环境的 BI 配置(主题、权限、看板定义)应通过代码或配置管理,而不是在 UI 上手工点。看板定义应能版本化——把它存成声明式规格文件纳入版本控制,才能做变更评审与回滚。这与「配置即代码」的思路一致:BI 的权限、主题、看板都应是可 diff、可评审、可回滚的文本,而不是散落在 UI 里的手工状态。
升级要可控。BI 平台的版本升级可能改变默认样式、图表行为甚至 API。升级前跑视觉回归测试,对比升级前后的图表渲染结果,比人工比对可靠。SDK 版本要锁定,避免自动升级引入破坏性变更。
灾备与数据一致性:BI 层通常是无状态的(看板定义与权限配置除外),灾备的重点是把配置与元数据备份好,计算层可快速重建。
12. 安全与集成陷阱
陷阱一,令牌下发到前端后被窃取。短时效 + 一次性兑换码 + 不落 URL 日志能降低风险,但根本的防线是令牌权限最小化——即使泄漏,能做的也有限。
陷阱二,租户过滤在客户端。前端传租户 ID 的筛选参数,服务端不强制,改参数即越权。隔离必须在服务端。
陷阱三,缓存键不含租户。不同租户命中同一缓存,A 看到 B 的数据。缓存键必须包含租户与权限维度。
陷阱四,导出绕过权限。图表界面限制了行数,但导出接口没有,通过导出拿到全量数据。导出必须复用同一套权限校验。
陷阱五,iframe 未校验 origin。postMessage 不校验来源,任意页面可注入消息。严格校验 e.origin。
陷阱六,SSO 角色映射过宽。把宿主管理员映射成 BI 管理员,越权看全租户数据。映射要最小权限且显式定义。
陷阱七,错误信息泄漏内部结构。查询报错把 SQL 与表名返回给最终用户,暴露数据模型。错误信息要脱敏,详细错误只进日志。
陷阱八,深色模式不同步。宿主切深色,嵌入图表还是浅色,视觉割裂。主题要能运行时更新。
权衡取舍
| 决策点 | 选项 A | 选项 B | 何时选 A | 何时选 B |
|---|---|---|---|---|
| 嵌入形态 | iframe | SDK | 需强隔离、快速上线 | 需融入自有页面 |
| 认证 | 短时效签名令牌 | 一次性兑换码 | 实现简单 | 令牌不落浏览器 |
| 隔离 | 行级安全 | 物理隔离 | 多数 SaaS | 强合规、大客户 |
| 缓存 | 结果缓存 | 实时查询 | 高频看板 | 强实时要求 |
| 部署 | 单实例多租户 | 每租户独立 | 成本敏感 | 隔离优先 |
| 用户同步 | JIT 即时创建 | 预同步 | 避免双写 | 需离线对账 |
常见坑清单
- 租户过滤放前端——改参数即越权;隔离必须在服务端强制且不可绕过。
- 缓存键不含租户——跨租户命中缓存导致数据串户;键须含租户与权限维度。
- 令牌长时效或落 URL 日志——泄漏后可长期越权;用短时效令牌或一次性兑换码。
- postMessage 不校验 origin——任意页面可注入消息;严格校验
e.origin。 - SSO 角色映射过宽——宿主管理员变 BI 管理员,越权看全租户;最小权限映射。
- 卸载不调 destroy——SPA 切换累积僵尸实例与监听;挂载与卸载必须成对。
- 令牌续期靠重挂载——图表闪烁且丢状态;用 updateToken 增量更新。
- 导出不复用权限校验——绕过图表限制拿到全量数据;导出走同一套校验。
- 共享连接池无限流——一个租户慢查询拖垮全部;按租户设并发上限。
- 错误信息含 SQL 与表名——暴露数据模型给最终用户;错误脱敏、详情只进日志。
小结
嵌入式分析的本质是把分析能力安全地嵌进别人的产品,因此它的核心不是图表渲染,而是三条边界:安全边界(租户与权限的强制隔离)、身份边界(SSO 与用户映射)、体验边界(白标与生命周期)。任何一条处理不当,都会让「嵌入式」变成「嵌入风险」。
工程上的优先级很清晰:先保证安全与隔离,再谈体验与性能。服务端强制隔离、短时效令牌、租户维度的缓存键、导出复用权限,这四条是底线;在此之上,主题定制、加载态统一、按租户配额是体验与稳定性的加分项。
下一步可以对照 Metabase 与 Superset 两篇,看平台侧的权限与治理模型如何支撑嵌入;被嵌入的看板本身该如何设计(信息架构、布局、刷新策略),则回到仪表盘与数据大屏设计里找答案。
继续阅读
探索更多技术文章
浏览归档,发现更多关于系统设计、工具链和工程实践的内容。