news 2026/9/10 19:35:12

Huly 平台客户端接入指南:基于 @hcengineering/client-resources 构建与运行你的 Client

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Huly 平台客户端接入指南:基于 @hcengineering/client-resources 构建与运行你的 Client

Huly 平台客户端接入指南:基于 @hcengineering/client-resources 构建与运行你的 Client

【免费下载链接】platformHuly — All-in-One Project Management Platform (alternative to Linear, Jira, Slack, Notion, Motion)项目地址: https://gitcode.com/GitHub_Trending/platform80/platform

导读

本文围绕 Huly 平台核心包之一@hcengineering/client-resources(位于 foundations/core/packages/client-resources)展开,系统讲解如何创建一个与正在运行的 Huly 平台交互的客户端(Client)。你将掌握GetClient的完整调用链、Token 与连接地址的拼接规则、模型过滤(FilterMode)与 IndexedDB 持久化原理、Node.js 环境下 WebSocket 工厂的配置方法,以及连接心跳、重连、二进制协议等底层容错机制,可直接照搬文中代码集成到自己的脚本或服务中。

一、包概览:client-resources 在整个 Huly 平台中的定位

@hcengineering/client-resources是一个"平台资源包":它不直接提供 API 类,而是以 Huly 平台标准的plugin资源形式,暴露一个名为GetClient的函数资源,供应用层按需加载。从 package.json 可以看到它的核心依赖:

  • @hcengineering/client:定义ClientFactoryOptionsClientSocketFactoryFilterMode等类型与元数据(client/src/index.ts);
  • @hcengineering/core:提供ClientTxTxHandlercreateClientClientConnectEvent等核心抽象(core/src/client.ts);
  • @hcengineering/rpc:提供 RPC 编解码(RPCHandler)与HelloRequest/HelloResponse握手协议;
  • @hcengineering/platform:提供getMetadatasetPlatformStatusStatus等平台基础设施;
  • snappyjs:用于服务端推送数据的 Snappy 解压。

该包自带jest测试(npm test),覆盖了 WebSocket 连接层与客户端集成层,后续章节会结合这些测试说明实际行为。

二、快速开始:三步拿到可用的 Client

官方 readme 给出了最简用法,完整代码如下:

import clientResources from '@hcengineering/client-resources' import core, { Client } from '@hcengineering/core' // token 通过登录/账号服务获得,内容为 JWT 格式(见下文 Token 解析一节) const token = ... // transactorUrl 为 Huly 平台的事务服务(transactor)端点,如 https://host/api/transactor const connection: Client = await (await clientResources()).function.GetClient(token, transactorUrl) // 至此 client 已可用,可以执行 findAll / tx 等操作 // 使用 close 优雅关闭连接 await connection.close()

要点拆解:

  1. clientResources()是一个异步工厂函数,调用后返回{ function: { GetClient } }结构(见 src/index.ts 的默认导出);
  2. GetClient(token, endpoint, opt?)返回Promise<Client>,其中Client接口由@hcengineering/core定义;
  3. 调用close()会关闭底层 WebSocket 并拒绝所有未完成的请求,务必在退出前调用。

三、GetClient 内部实现:从 Token 到连接

GetClient的签名是(token: string, endpoint: string, opt?: ClientFactoryOptions): Promise<Client>,其内部做了三件关键事情,源码位于 src/index.ts。

3.1 Token 解析与合法性校验

GetClient先把 JWT Token 的 payload 段(token.split('.')[1])做atob解码并JSON.parse,得到workspaceaccount两个字段(decodeTokenPayloadgetWSFromToken两个函数,src/index.ts):

interface TokenPayload { workspace?: WorkspaceUuid account?: PersonUuid extra?: any }

如果 payload 中缺少workspaceaccount,会直接抛出'Workspace or account not found in token'。也就是说,Token 中必须携带工作区与账号信息,客户端才能正确建立工作区上下文。

3.2 连接 URL 拼接

真正的 WebSocket 地址由concatLink(endpoint,/${token})生成(src/index.ts),即transactorUrl/token形式;随后connect()(见 src/connection.ts)创建Connection实例,并在 socket 打开后于 URL 末尾追加?sessionId=...用于断线重连时的会话恢复(见 src/connection.ts)。

3.3 服务端推送的事务拦截

连接建立后,所有服务端下推的事务(Tx)会先经过upgradeHandler过滤(src/index.ts):

  • 收到TxModelUpgrade:触发opt?.onUpgrade?.(),提示应用层模型已升级,需要重建客户端;
  • 收到TxWorkspaceEvent且事件为MaintenanceNotification:通过setPlatformStatus抛出Severity.WARNING级别的维护提醒,附带的timeMinutesmessage参数会透传给 UI。

四、模型过滤:FilterMode 的三种模式

GetClient会读取平台元数据client.metadata.FilterModel(默认'none')与client.metadata.ExtraFilter(默认[]),构造一个ModelFilter回调传给createClient(src/index.ts)。三种取值(定义于 client/src/index.ts)行为如下:

FilterModel行为
'none'不过滤,原样返回全部模型事务
'client'过滤掉所有server-前缀插件与未启用插件,并剔除workbench:class:Applicationview:class:Actionnotification:class:NotificationGroup等 20 余类 UI 专属模型元素(见returnClientTxestoExclude集合,src/index.ts)
'ui'过滤掉所有服务端元素与未被启用的 UI 元素,额外叠加ExtraFilter中列出的插件 ID(returnUITxes,src/index.ts)
// 示例:在创建 Client 前设置过滤模式与额外排除项 import client from '@hcengineering/client' import { setMetadata } from '@hcengineering/platform' setMetadata(client.metadata.FilterModel, 'ui') setMetadata(client.metadata.ExtraFilter, ['my-private-plugin'])

ExtraPlugins元数据则用于在'ui'模式下把额外插件纳入"允许加载"集合(src/index.ts)。

五、模型持久化:IndexedDB 缓存

createModelPersistence(workspace)(src/index.ts)在浏览器环境下会打开indexedDB.open('model.db.persistence', 2),并在其中建立model对象仓库(keyPath 为id),以workspace 为键缓存LoadModelResponse

  • load:按 workspace 读取缓存模型;若无缓存返回{ full: false, transactions: [], hash: '' }
  • store:把拉取到的模型写入缓存,下次启动可跳过全量加载;
  • 可通过client.metadata.OverridePersistenceStore传入自定义的TxPersistenceStore覆盖默认实现。

六、Node.js 环境:必须配置 WebSocket 工厂

readme 中特别强调:在 Node.js 环境必须用ws包替换默认 WebSocket 实现。原因是浏览器环境下Connection直接使用全局WebSocket,而 Node.js 没有该全局对象,需要通过client.metadata.ClientSocketFactory元数据注入ws

import client from '@hcengineering/client' import { setMetadata } from '@hcengineering/platform' import WebSocket from 'ws' // 用 'ws' 覆盖默认的 WebSocket 工厂 setMetadata(client.metadata.ClientSocketFactory, (url) => new WebSocket(url)) const connection: Client = await (await clientResources()).function.GetClient(token, transactorUrl) // ... await connection.close()

ClientSocketFactory的类型为(url: string) => ClientSocket,其中ClientSocket是平台自定义的最小 WebSocket 抽象(onmessage/onclose/onopen/onerror/send/close/readyState,见 client/src/index.ts),因此任何符合该形状的实现(浏览器 WebSocket、ws、mock 对象)都可以注入。ws已声明为 devDependencies(package.json),用于测试与 Node 侧运行。

七、ClientFactoryOptions 完整参数

GetClient的第三个可选参数opt类型为ClientFactoryOptions(定义于 client/src/index.ts),逐项说明:

参数类型作用
socketFactoryClientSocketFactory按连接 URL 创建 socket,优先级最高(高于全局ClientSocketFactory元数据)
useBinaryProtocolboolean是否使用二进制 RPC 协议,默认取元数据UseBinaryProtocol,再回退到true(src/connection.ts)
useProtocolCompressionboolean是否启用 Snappy 压缩,默认取元数据UseProtocolCompression,回退到false(src/connection.ts)
connectionTimeoutnumber连接超时(毫秒),大于 0 时启用;超时未连接会触发onDialTimeout并拒绝连接 Promise(src/index.ts)
onHello(serverVersion?: string) => boolean收到服务端hello响应(含版本号)时回调,返回false将主动断开(src/connection.ts)
onUpgrade() => void检测到模型升级时回调
onError(err: StatusCode) => void服务端返回terminate错误(如工作区归档/不存在)时回调
onConnect(event, lastTx, data) => Promise<void>连接/重连/维护等事件回调,eventClientConnectEvent
onDialTimeout() => void \| Promise<void>拨号超时回调
ctxMeasureContext指标上下文,用于性能埋点
useGlobalRPCHandlerbooleantrue时共享全局RPCHandler(默认每个连接新建一个,src/connection.ts)

ClientConnectEvent枚举(core/src/client.ts)包含Connected(首次连接并收到完整模型)、Reconnected(重连后应用增量)、Upgraded(收到全量新模型需重建)、Refresh(需要刷新查询)、Maintenance(工作区维护中)。

八、连接生命周期与容错机制

Connection类(src/connection.ts)是连接层的核心,值得关注的机制包括:

  • 握手协议:socket 打开后立即发送hello请求,携带binarycompression标志;服务端以hello响应确认协议能力并返回lastHashserverVersionaccount,随后才把连接视为可用(helloReceived = true,src/connection.ts);
  • 心跳保活:以 10 秒为周期发送pingpingConst = 'ping'),若 5 分钟(hangTimeout)未收到 pong 则判定挂死并关闭 socket 触发重连(src/connection.ts、src/connection.ts);
  • 拨号超时:30 秒(dialTimeout)内未完成握手会回调onDialTimeout并强制重建连接(src/connection.ts);
  • 自动重连与退避:socket 关闭时自动scheduleOpen(force=true),错误发生后delay从 1 递增至上限 3 秒进行指数退避(src/connection.ts、src/connection.ts);
  • 会话恢复sessionId会写入sessionStorage,页面刷新后重连时携带原 sessionId,服务端据此恢复订阅(src/connection.ts);
  • 限流保护:服务端返回的rateLimit信息被实时跟踪,remaining过低时引入slowDownTimer主动限速;剩余为 0 时按retryAfter延迟重试(src/connection.ts);
  • 分块响应findAll大结果集支持chunk分片,客户端按index排序拼接后再 resolve(src/connection.ts);
  • 去重与重试once请求会按 method+params 去重;非幂等tx在重连时会先查询事务是否已提交再决定是否重发(src/connection.ts、src/connection.ts)。

九、Client 常用操作

连接建立后,Client提供完整的存储与查询 API(ClientImpl见 core/src/client.ts),常用方法包括:

// 查询:findAll / findOne const tasks = await connection.findAll(core.class.Tx, { objectClass: 'tracker:class:Issue' }, { limit: 20 }) const one = await connection.findOne(core.class.Space, { name: 'My Space' }) // 写入:提交事务 await connection.tx(tx) // 模型相关 await connection.getHierarchy() await connection.getModel()

底层 RPC 方法在 src/connection.ts 中均有对应实现(loadModelfindAlltxloadChunkgetDomainHashsearchFulltextdomainRequestsendForceClose等),可作为排查网络问题的参考。

十、测试验证:连接层与集成层

该包用 Jest 覆盖了两层行为,是理解契约的最佳范例:

  • 连接层单测(src/tests/connection.test.ts):自定义MockWebSocket模拟helloloadModelfindAlltx响应与ping/pong回包,验证connect()能建立连接并正确分发服务端下推的事务;
  • 集成测试(src/tests/integration.test.ts):提供MockClientConnectioncreateTestClient()辅助函数,覆盖事务端到端流转、findAll/findOne/searchFulltext/domainRequest、连接断开恢复、多 handler 分发、Upgraded/Maintenance事件以及并发事务提交等场景。

运行测试:

npm test --prefix foundations/core/packages/client-resources

十一、常见问题排查

  • Node.js 下WebSocket is not defined:未设置client.metadata.ClientSocketFactory,按第六节注入ws即可;
  • 报错Workspace or account not found in token:Token 的 payload 段缺少workspace/account字段,请检查登录流程签发的 Token 内容;
  • 连接长时间不建立:确认transactorUrl正确且可达,并观察connectionTimeout是否过小(可在optConnectionTimeout元数据中调整);
  • 模型升级后行为异常:监听onUpgrade回调并重建 Client(ClientConnectEvent.Upgraded语义见 core/src/client.ts);
  • 需要最小化模型体积:为FilterModel元数据设置'client''ui',结合ExtraFilter排除不需要的插件。

总结

@hcengineering/client-resources是 Huly 平台所有客户端接入的统一入口:GetClient负责 Token 校验、URL 拼接、模型过滤与本地持久化,底层Connection则封装了 WebSocket 握手、心跳、重连、限流与二进制/压缩协议等全部传输细节。无论你是编写浏览器端插件、Node.js 脚本,还是测试工具,只需提供合法 Token 与 transactor 端点,并(在 Node 侧)正确注入ws工厂,即可获得功能完整、具备断线自愈能力的平台客户端。

【免费下载链接】platformHuly — All-in-One Project Management Platform (alternative to Linear, Jira, Slack, Notion, Motion)项目地址: https://gitcode.com/GitHub_Trending/platform80/platform

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/10 19:34:32

PLC控制扫描拾取机械手的工业自动化解决方案

1. 项目概述&#xff1a;PLC控制扫描拾取机械手的工业自动化解决方案这个项目实现了一套基于三菱FX系列PLC的扫描拾取机械手系统&#xff0c;包含完整的电气设计、机械结构、控制程序和上位机交互方案。作为工业自动化领域的经典应用&#xff0c;这类系统在物流分拣、生产线上下…

作者头像 李华
网站建设 2026/9/10 19:32:37

北京GEO优化服务商推荐:企业选型避坑全攻略

北京企业对GEO的需求正在从“要不要做”转向“怎样选对服务商”。AI平台不断参与咨询、比较和采购判断&#xff0c;服务商的技术底座、内容方法、监测能力与长期运营方式&#xff0c;都会影响品牌能否被准确理解并持续引用。 本文保留对标原文的概念解析、15强榜单、筛选维度、…

作者头像 李华
网站建设 2026/9/10 19:30:06

威纶通与西门子PLC在污水处理自动化控制中的应用

1. 无人值守污水处理控制系统概述 这套基于威纶通触摸屏与西门子S7-200 SMART PLC的智能控制系统&#xff0c;专为中小型污水处理厂设计&#xff0c;实现了从进水到出水的全流程自动化管理。我在某工业园区污水处理站实施时&#xff0c;系统连续稳定运行超过180天无需人工干预&…

作者头像 李华
网站建设 2026/9/10 19:29:26

高维空间中超平面体积为零的数学原理与应用

1. 高维空间中的超平面体积特性解析在数学和物理学研究中&#xff0c;高维空间的性质常常挑战我们的几何直觉。最近我在研究n维空间几何性质时&#xff0c;发现一个有趣的现象&#xff1a;(n-1)维超平面在n维空间中的"超体积"确实为零。这个结论看似违反直觉&#xf…

作者头像 李华
网站建设 2026/9/10 19:28:53

终端艺术:用代码创造视觉诗学的技术解析

1. 终端艺术的新边界&#xff1a;当代码遇见视觉诗学在Unix哲学中&#xff0c;命令行终端一直被视为纯粹的功能性工具——直到有人开始用ANSI转义码绘制彩色界面。但今天我们要探讨的&#xff0c;是更极致的终端艺术形态&#xff1a;通过精心构造的微型函数&#xff0c;在字符矩…

作者头像 李华