
我带过的很多 ThingsBoard 项目前期搭平台、接设备、画看板一路顺风顺水真正开始卡壳基本都是到了交付阶段。要么客户说“这个数据大屏我能不能不用登录就投到办公室墙上”要么甲方一开始看演示就皱眉指着页面左上角说“你们那个默认 logo 能不能换成我们公司的”。这一篇我就把这两个问题一次性讲透。这篇是 ThingsBoard 入门实战系列的第 7 篇前几篇把安装部署、设备接入、规则引擎、Widget 组合这些基础内容过完了。这一篇集中解决两件事一是把数据安全地“公开”出去二是把默认界面改成自己想要的样子。顺便把“功能都正常但打开就卡”这类影响观感的问题也治一治。针对的基础是你已经有一套能正常运行的 ThingsBoard版本在 3.x 都适用社区版和企业版在权限模型上思路一致。1. 公共发布的三种形态公开链接、客户账号和共享登录1.1 三种形态的边界差异ThingsBoard 默认的状态很纯粹不登录什么都看不到登录之后能看到多少取决于你的账号权限。所谓“公共发布”在 ThingsBoard 里并不是一个单一按钮而是一组权限策略的组合。我在实际项目里见到的“对外分享数据”的需求基本都能归到下面三种形态发布方式是否需要登录实现路径适用场景风险程度Public 公开链接不需要把 Dashboard 共享给内置的 Public 客户数据大屏、临时演示、嵌入官网高链接即凭证客户账号模式需要创建 Customer分配设备/资产/仪表盘创建受限用户正式交付给客户、需要操作设备低共享管理员账号需要直接把租户管理员账号发给对方内部临时演示图省事极高不推荐很多人一听到“公共发布”下意识想到的就是 Public 链接。其实在正规交付里客户账号模式才是主力。Public 链接更像临时访客门禁卡——不需要身份谁捡到都能进客户账号模式则像发一张只有特定楼层权限的门禁卡能去哪里、能按哪部电梯全都精确控制。1.2 底层是同一套权限模型Tenant、Customer 和设备授权不管走哪种方式背后都是同一套权限模型。ThingsBoard 的数据归属大致是三层系统管理员System Administrator管理平台本身。租户Tenant通常一个项目或公司对应一个租户。客户Customer租户下面的业务客户设备和资产可以分配给某个 Customer。设备、资产、仪表盘这些实体本身挂在某个租户下同时可以再分配给某个 Customer。当一个 Customer 被分配了某台设备又分享了一个 Dashboard 给它那么该 Customer 下的用户登录后就只能看到这台设备和这个仪表盘。这就是整篇所有操作的基础逻辑。有个设计非常关键ThingsBoard 内置了一个特殊的“Public”客户。你不需要为公开访问单独开发一套匿名逻辑只要把 Dashboard 和对应的设备都分配给 Public 这个客户系统就会自动生成一个无需登录就能打开的链接。换句话说公开链接并没有绕过权限模型只是复用了 Customer 模型只不过这个客户的账号是匿名身份。所以你会发现无论做哪种发布方式第一步永远不是点那个“公开”按钮而是先理清“哪些设备属于哪个客户”。我在不少项目里看到同事直接把整台设备列表全部一股脑分配给 Public客户能用公开链接看到所有设备的原始数据这种案例其实就是没想清楚边界。先把相关问题理清楚后面操作都是顺水推舟。2. 客户账号模式的完整落地流程从建客户到 RPC 权限开通2.1 五步完成受限账号的配置客户账号模式是整个交付场景里最稳的方案。具体分五步走。第一步创建客户。以租户管理员身份登录进入左侧“客户”菜单点右上角加号创建一个客户标题建议直接写公司或项目名称比如“某某智慧园区”。第二步把设备和资产分配给客户。进入设备列表点开某台设备详情点右上角编辑按钮在“客户”下拉框里选中刚才创建的客户保存即可。设备多的时候别一台台点用实体组Entity Group更省事先建一个分组把所有目标设备加入分组然后在分组详情里选择“分配客户”。资产Asset的分配逻辑完全一样。第三步创建客户用户。进入客户详情页的“用户”标签页点加号创建用户填写邮箱和密码。注意ThingsBoard 默认不会自动发激活邮件你填的密码就是初始密码。如果你希望客户首次登录后强制改密在创建用户的界面里把对应选项勾上但要不要开这个功能最好先问清楚客户对账号管理的要求。第四步分配 Dashboard。在仪表盘列表里选中要分享的那块面板点分享按钮在弹窗里勾选刚创建的客户保存。这一步经常被忽略结果客户登录进去看到的是空白首页一脸懵。分配完成之后如果希望客户登录后直接落到这块面板上可以在客户详情页配置“主仪表盘Home Dashboard”。第五步验证。这是最重要的一步见 2.3 的“Login as Customer”方法。2.2 客户设备下发命令的权限开通与常见坑客户账号建好了设备也分配过去了客户登录后能看到数据但如果客户还需要远程控制设备——比如开关空调、下发参数——你会发现界面上根本没有对应的 RPC 按钮或者点了没反应。原因在于默认情况下设备分配到 Customer 只是给了“查看”权限没有“控制”权限。RPC 权限需要额外授予。我在项目中常用两种做法设备级授权进入设备详情找到权限Permissions配置添加一条实体权限操作选择 RPC目标实体选择这个 Customer保存。角色模板创建一个自定义角色配置好指定类型设备的 RPC 权限然后把角色绑定给客户用户。多台设备、多个客户时效率明显更高。这里有个非常容易踩的坑就是“子设备下发”。如果设备接入方式是网关子设备的架构并且子设备是以网关子设备形式挂在下面的不是独立设备实体客户在 UI 上下发 RPC 时目标选择子设备很可能没反应。排查时要先分清架构如果子设备是独立设备实体平台直接就能下发如果子设备是挂在网关下的附属协议设备那命令要靠网关应用转发到具体的子协议通道。具体排查路径我建议按顺序来打开设备列表确认设备在线状态是绿色。换管理员账号登录如果管理员也下发不到说明是设备接入或 RPC Topic 的问题不是权限问题。用客户账号试如果管理员能下、客户不能下那就是权限没给到位。如果是网关子设备看网关日志有没有收到 RPC message。网关收到了但没解析那是网关应用侧的转发逻辑问题网关压根没收到检查平台侧的 RPC 发送目标和权限订阅。另外直接用 MQTT 调试设备 RPC 时要注意消息方向服务端发给设备的请求主题是v1/devices/me/rpc/request/设备回复要发到v1/devices/me/rpc/response/请求ID。很多人在命令行里用 mqtt 客户端测试时会把 request 和 response 方向搞反怎么调都不通。2.3 验证客户视角Login as Customer 是排查利器配置完权限后最忌讳的事情就是“我觉得配置好了”。我见过太多因为漏配一步导致项目验收时当场翻车的例子所以强烈建议养成“以客户身份登录验证”的习惯。ThingsBoard 提供了一个很实用的功能在客户列表里点某个客户旁边的“以客户身份登录”图标界面就会直接切换成该客户视角。这个状态下你能看到客户登录后看到的所有菜单、设备、仪表盘左上角会标示当前身份。排查“客户看不到数据”“菜单不对”“仪表盘空白”这些问题时这一个操作能省掉大量来回沟通的时间。注意在这个切换视角状态下生成的数据操作、界面设置是以客户身份进行的。验证结束后要主动退出客户模式回到管理员视角别带着客户身份去改配置改了半天发现改在了客户名下。3. Public 仪表盘开启公开链接与 iframe 嵌入3.1 开启步骤、链接结构和数据归属Public 公开链接适合的场景很明确数据大屏展示、临时演示、嵌入公司官网。开启步骤其实不复杂在客户页面确认是否存在名为 Public 的客户。部分版本在第一次分享时会自动创建稳妥起见可以先手动创建一个名为 Public 的客户。把需要公开展示的设备、资产都分配给 Public 客户。漏掉这一步是最常见的错误分配完链接后打开面板图表一片空白。在仪表盘列表里打开目标 Dashboard 的分享弹窗在客户列表中勾选 Public。复制生成的公开链接直接发给访客。生成的公开链接 URL 结构大概是这样的http://你的服务器地址:8080/dashboard/{仪表盘ID}?publicId{Public客户ID}。这个链接长期有效没有过期时间。这里我必须把风险说清楚所有通过公开链接访问的人共用的都是同一个匿名身份。你无法知道谁访问过也无法单独回收某个人的访问权。只要链接存在一天这些数据就对全世界可见。所以敏感业务数据绝对不要接公开面板。如果确实要用建议在前面加一层 Nginx 反向代理用 referer 白名单或者 IP 限制做一道闸门。公开访问的本质是“链接即凭证”这个认知一定要建立起来。3.2 iframe 嵌入公司门户的正确姿势把公开面板嵌入到已有公司网站或者 OA 系统里是挺常见的需求。代码层面其实就一个 iframe 标签iframe srchttp://你的服务器地址:8080/dashboard/xxxx?publicIdyyyy stylewidth:100%; height:850px; border:0; allowfullscreen /iframe有几点实操经验供你参考HTTPS 页面里嵌入 HTTP 地址的 iframe浏览器会直接拦截混合内容。如果公司门户是 HTTPSThingsBoard 也必须走 HTTPS或者前面挂一层 HTTPS 反代。这一点不提前处理的话门户页面上只会看到一片空白。iframe 加载速度慢的时候大屏会有一段白屏期。可以在外层套一个简单的 loading 遮罩等 iframe onload 事件触发后再隐藏感官体验会好很多。大屏投放屏幕如果是 1920x1080 或者更大的拼接屏记得把 Dashboard 里 Widget 的标题、数值字体调大。默认字号在普通浏览器上看没问题一上大屏就缩成一团。3.3 公开模式下的能力边界Public 模式下匿名用户能做什么、不能做什么心里要有数。能做的查看分配给 Public 客户的设备数据切换 Dashboard 页面查看 Widget 上已经配置好的所有可视化内容。不能做的默认不能下发 RPC 命令、不能修改 Widget 配置、不能访问其他 Dashboard。这是安全模型的底线不要试图通过修改配置去突破它。经常有人问“我 public 面板上放了一个开关按钮为什么点不动”答案很简单匿名身份默认没有 RPC 权限。如果大屏上真的要有一个“一键开启”按钮正确做法是单独建一个低权限的 customer 用户用登录态访问面板或者由后台提供一个带令牌的 API再由前端页面代理调用。4. UI 定制第一层官方外观设置与自定义 CSS4.1 主题色、Logo、登录页背景的配置路径ThingsBoard 从 3.x 开始官方就提供了比较完整的 UI 外观设置入口不需要改代码。以租户管理员或系统管理员身份登录后进入右上角个人资料找到“外观Appearance”配置或者从系统设置里进不同版本入口略有差别但认准“Appearance”这个词就行。官方外观设置能覆盖的需求基本覆盖了绝大多数甲方的高频要求需求配置位置更换顶栏/侧边栏 Logo外观设置中的 Logo 上传修改主色调、顶栏背景色外观设置中的主题色配置更换登录页背景图外观设置中的登录页背景是否启用深色模式外观设置中的主题切换开关只要把主题色改成公司主配色所有按钮、导航高亮、选中态都会跟着变。这一步做完整个平台观感已经从“默认搬家模板”变成“有点定制的味道”了。4.2 自定义 CSS 能补的“官方不做”的细节官方外观设置覆盖不了所有细节。比如调整侧边栏收起后的图标间距、隐藏右下角某个默认控件、微调登录表单宽度这些需求就得靠自定义 CSS。外观设置页面本身通常提供一个“自定义 CSS”输入框直接把样式写进去即可。/* 示例让侧边栏底色更暗、字体更紧凑 */ mat-sidenav .mat-toolbar { background-color: #0f172a !important; } /* 自定义登录页背景 */ .tb-login-content { background: linear-gradient(135deg, #1e293b 0%, #0f172a 100%); }写自定义 CSS 有三条教训一是注意全局生效问题。自定义 CSS 不只对客户端生效管理员后台也同样生效。别为了美化客户界面把管理端的布局也带崩了。如果只想影响客户界面优先用更具体的选择器限定生效范围。二是类名会变。ThingsBoard 升级时前端 DOM 结构可能调整你写的类名在新版本里可能就不存在了。升级后样式失效是常态别慌用浏览器开发者工具检查新类名重新适配。三是 CSS 能少写就少写。能用官方配置做掉的不要自己手写。自定义 CSS 写多了排查问题和升级的成本会成倍增加。4.3 修改后不生效的常见原因外观设置和自定义 CSS 改了没反应我排查过很多次原因基本就三条。第一个是浏览器缓存。前端资源缓存时间普遍较长改完样式后要强制刷新CtrlShiftR才能看到效果。很多“改了没用”其实是缓存问题。第二个是主题模式没对上。如果当前系统是深色模式自定义 CSS 要适配的是深色主题变量而不是默认的浅色变量。你写在浅色主题下的样式到了深色模式下确实不会生效。第三个是选择器优先级不够。ThingsBoard 前端基于 Angular Material默认样式优先级不低。自定义 CSS 写选择器时优先级不够就会被覆盖掉。必要时用!important但别到处滥用。5. UI 定制第二层静态资源覆盖与源码级改动5.1 哪些需求必须进入这一层官方外观设置和自定义 CSS 解决不了的才轮到这一层。常见需求包括修改浏览器标签页的 Favicon 图标。替换登录页的大背景图到固定路径有些版本官方配置只能填图片 URL不方便。修改页面底部版本号或版权文字。使用自定义字体文件。调整登录页整块版式而不只是换个图。这些需求已经进入“动前端产物”的范围每一步操作风险都在上升需要额外谨慎。5.2 容器化部署下覆盖静态资源的操作与风险以 Docker Compose 部署的 ThingsBoard 为例。不同版本前端静态资源的真实路径有差异先确认再动手。进入容器docker exec -it thingsboard ls /usr/share/thingsboard/ui看到assets、favicon.ico这类文件说明路径没问题。直接用docker cp替换文件docker cp favicon.ico thingsboard:/usr/share/thingsboard/ui/favicon.ico docker restart thingsboard注意这个做法适合单文件覆盖。如果改动文件较多或者要长期维护建议用 bind mount 把自定义文件挂载到容器内对应路径避免容器重建后全部丢失。我自己的习惯是先把容器里的原始文件备份到宿主机的backup目录再覆盖出问题能第一时间回滚。操作之前务必想清楚改 JS 和 HTML 比改 CSS 风险高得多。CSS 写坏了最多是样式错乱JS 或 HTML 改坏了可能导致整个控制台都打不开。我自己就在一次改登录页 HTML 时碰到过语法问题页面直接白屏最后只能恢复备份。5.3 升级版本时的兼容策略静态资源覆盖和源码级定制最怕的就是升级。ThingsBoard 每次版本升级前端资源都会被新版本覆盖自定义内容几乎必定丢失。更麻烦的是新版本前端结构可能变化你原来覆盖的文件在新版本里可能根本不存在了。我的应对策略是每次修改文件前从容器里把原始文件备份到宿主机独立目录。升级前先查看 Release Notes如果涉及前端结构变化就要评估静态覆盖方案是否继续适用。当定制需求较多时不要试图靠改编译产物维持直接基于 ThingsBoard 前端开源仓库 fork 一个分支来做定制走构建流水线输出自有镜像。这条路前期成本高但长期维护和升级都更可控。给你的选择优先级是官方外观设置 自定义 CSS 静态资源覆盖 源码定制。能用上一层解决的绝不动下一层。6. 界面卡顿排查链路从全局卡到单面板卡6.1 先判断症状类型再动手“UI 界面卡顿”这个反馈用户说出口的时候往往很模糊。接到问题第一步不是冲进服务器看 CPU而是确定卡在哪里。症状表现优先怀疑方向排查手段所有页面切换都慢服务器资源不足看 CPU、内存、磁盘 IO打开某个特定 Dashboard 卡数据量过大或 Widget 太多看该面板数据范围、加载请求耗时图表一直转圈不刷新WebSocket 连接异常浏览器 Network 面板看 WS 状态地图/大屏场景卡顿浏览器渲染压力大缩小时间范围减少图层6.2 数据量、规则引擎与 WebSocket 的隐性影响我排查过的真实卡顿案例里服务器 CPU 飙升只是表象真正的原因往往藏在下面三个地方。第一个是 Dashboard 时间范围过大。默认情况下Widget 数据查询会加载所选时间范围内的全部历史数据。演示时一上来选“全部时间”上万个原始数据点全部压到浏览器端任谁都会卡。解决方法很简单把时间范围固定到最近 1 小时或 24 小时或者直接在 Widget 数据源里开启聚合。第二个是规则引擎处理压力过大。设备上报频率高的场景规则引擎每条消息都触发处理链然后全部写入数据库。高频的写操作会让数据库忙不过来进而拖慢整个控制台响应。这种问题要从源头治理规则链里用过滤节点把不需要入库的数据直接丢弃只保留关键量。设备几秒上报一次的频率不是每一个点都有入库价值。第三个是 WebSocket 推送过量。ThingsBoard 的实时更新机制是后端数据一变就向前端推送。如果页面上放了很多个“最新值”表格而设备又高频上报浏览器每秒都在高频重绘 DOM界面就会卡顿。合理做法是降低 Widget 的刷新频率或者在前端展示前做聚合降采样——比如前端只要每分钟一个点就不要把每秒钟的数据都推过去。6.3 展示场景下的优化配置如果这套 ThingsBoard 主要就是用来做数据大屏和对外展示我建议提前把优化做到位而不是等卡了再调。时间序列类的 Widget优先使用聚合函数AVG、MAX、MIN不要直接展示原始数据点。大型看板尽量拆分页面。一个 Dashboard 堆上几十个 Widget加载和渲染压力都很大拆成多页再自动轮播体验会好很多。尽量用“最新值”类 Widget 展示设备状态而不是把大量历史曲线堆在首页。历史数据设置 TTL按设备 profile 设定保留时间比如 90 天自动清理避免数据库无限膨胀。前端静态资源走 Nginx 缓存设置合理的expires时间能明显降低每次打开控制台的资源加载等待。这一套组合拳打下来大多数“打开卡、刷新卡、切换卡”的问题都能缓解不少。最后说一个我自己的教训。以前为了省事我直接在容器里改了登录页的 HTML 和 JS改完确实立竿见影。但过了一个多月升级 ThingsBoard 时原来的定制全没了而且新版本前端结构变化很大我定位到的原文件位置根本对不上最后只能回退备份重新做。从那以后我给自己定了条规矩能用官方外观设置做的绝不动静态资源必须动的时候先备份、再覆盖、并且把自定义文件放进独立目录单独管理。这一篇把公共发布的三种形态和 UI 定制的几个层次讲完了按这个思路走下来后续遇到“客户要换 logo”“领导要大屏公开访问”这类需求基本不会慌。下一篇我打算聊一聊多租户场景下的数据权限设计有空继续看。