# 认证与安全 (/zh/api/authentication) WuKongIM HTTP API 面向受信后端;浏览器通过 SDK 连接 Gateway,或通过同源 BFF 请求服务端能力。 ## 信任边界 [#信任边界] 浏览器只应获得当前连接所需的 UID、临时 Token 和 WebSocket 地址,不应获得 WuKongIM HTTP API 地址或服务端凭据。 `gateway.token_auth_on` 默认为 `true`。`POST /user/token` 保存设备 Token 后,相同 UID 与 `device_flag` 的后续 CONNECT 必须携带完全匹配的值。这个内置检查不提供过期时间或重放策略,生产环境仍必须保护 Token 管理接口并负责签发、轮换和撤销。 ## 生产要求 [#生产要求] | 能力 | 责任方 | | ----------------------------- | --------------------- | | 账号、租户、UID 与设备策略 | 业务身份服务和业务后端 | | Token 生成、过期、撤销与安全存储 | 业务后端 | | CONNECT Token 校验 | Gateway | | WuKongIM HTTP API 鉴权、授权、限流与审计 | API Gateway、服务网格或业务后端 | | TLS、密钥轮换与告警 | 生产平台 | WuKongIM HTTP API 为兼容性接受任意 Origin,但接口本身没有业务鉴权。不要把 CORS 响应头当成安全边界,也不要让浏览器直连这些路由。反向代理必须限制网络来源,并配置请求体大小、请求/响应超时、速率、并发和审计;尤其要约束无上限 UID 数组与 Payload。 系统 UID 的持久集合与实际权限缓存也不是同一观测:`systemuids_add/remove` 只同步更新请求处理进程的缓存,其他节点需分别刷新或滚动重启。`systemuids_add_to_cache/remove_from_cache` 更只影响当前进程,必须使用节点定向的受保护运维入口。 协议加密不能替代 TLS、账号认证或 HTTP 服务鉴权。Token 不得出现在 URL、日志或截图中。 示例 BFF 必须绑定 loopback 并校验 Host/Origin;不要部署到公网、共享测试环境或真实用户流量。 相关参考:[`/user/token`](/zh/api/product-http/users)、[错误响应](/zh/api/product-http/errors)和[集成架构](/zh/guide/integration/architecture)。 # API 版本与兼容性 (/zh/api/compatibility) API 页面描述当前构建对应的合同。URL 不变不代表不同服务端、SDK 或合同版本自动兼容。 ## 当前快照 [#当前快照] 快照中的验证状态只覆盖以下基础接入流程: | 接口 | 验证内容 | | --------------------------- | --------------- | | `POST /user/token` | 创建开发身份 | | `GET /route` | 发现 WebSocket 入口 | | `POST /channel/messagesync` | 重连后恢复已提交消息 | 完整 [WuKongIM HTTP API OpenAPI](/contracts/product-http.openapi.json) 覆盖当前 44 条注册,Operations 与 Webhook 各有独立合同。三份窄 Profile 继续用于受控采用,但上方端到端状态只覆盖基础接入三条操作。源码对齐、Schema 校验和协议页都不会扩大该运行时验收结论。 ## 版本规则 [#版本规则] * 升级服务端、SDK 或合同后,应重新验证应用接入流程。 * 未验证的组合应视为未知兼容性,而不是默认兼容。 * 以完整合同判断当前源码 Surface,以窄 Profile 判断特定接入的采用范围。 兼容不等于可直接公网暴露。WuKongIM HTTP API 仍须放在受信网络或已鉴权代理之后;私有与实验性接口见[接口清单](/zh/api/interface-inventory)。 ## 兼容性快照(共享契约) - 文档频道: `v3-beta-snapshot` - 服务端源码修订: `b469c6e5e2b4ff65611a8fe3389fb5034de3f152` - 验证状态: 未验证(未提供验证 receipt) - Receipt schema: `wukongim.docs.golden-path-verification/v1` - 拓扑: single-node cluster - 物理哈希槽: 256 - JavaScript SDK: `wukongimjssdk@1.3.5` - 黄金场景: `javascript-web-quickstart/alice-bob-reconnect-sync/v1` - 样例 Node.js 要求: `>=20.11` - 样例锁文件 SHA-256: `1aabcbf3a92b5658f7f7cad0abe21a8dbcbfc7ecaab8387665bf3e2ccb0af3f7` - 测试运行时目标: Node.js 22.12.0, npm - Chromium 测试目标: chromium 151.0.7922.34 (revision 1234) via `@playwright/test@1.62.1` - 其他浏览器: unverified - [compatibility.json](/compatibility.json) - [OpenAPI 子集](/contracts/javascript-web-quickstart.openapi.json) # 通用约定 (/zh/api/conventions) 以下规则适用于当前公开的 WuKongIM HTTP API 接口。 ## 地址与格式 [#地址与格式] | 约定 | 行为 | | ---------------- | ------------------------------------------------------------------------------- | | Base URL | 本地示例为 `http://127.0.0.1:5001`;所有路径从 `/` 开始 | | 请求 | POST 使用 UTF-8 JSON;`/route`、`/channel/whitelist`、`/user/systemuids` 使用 GET 查询参数 | | 响应 | JSON;不同端点没有统一响应信封 | | UID / Channel ID | 由业务系统维护的字符串 | | Channel | 由 `channel_id` 与 `channel_type` 共同标识 | | Message sequence | 仅在单个 Channel 内有序 | 调用方应先检查 HTTP 状态,再按端点 Schema 解析响应。不要强行套用统一的 `{data,error}` 类型。 ## 标识与游标 [#标识与游标] * `uid` 是业务身份,不是昵称、连接 ID 或设备 ID。 * 个人 Channel 的客户端视图使用对端 UID。 * `message_seq` 是 Channel 内的 `uint64` 游标,JavaScript 必须用无损 JSON 解析、十进制字符串或 `BigInt` 保存。`message_idstr` 只镜像 `message_id`,不能替代 `message_seq`,且 `/message/send` 响应不返回它。 * `next_cursor` 是不透明值,必须原样回传,直到 `done=true`。 请求对象为兼容旧客户端会忽略未知 JSON 字段。调用方仍应只发送合同声明的字段;拼写错误不会自动失败,不能依赖“额外字段可用”作为扩展机制。 ## 状态与执行范围 [#状态与执行范围] | 范围 | 含义 | | -------------- | --------------------------------------- | | 集群持久状态 | 响应成功后可由其他节点读取,但后续派生动作可能仍在进行 | | 当前进程缓存 | 只影响接收该请求的服务进程;负载均衡切换节点后不能假定仍生效 | | owner-local 动作 | 请求节点会路由或延迟执行 Session 动作;HTTP 返回不代表动作已结束 | | 分阶段写入 | 清空、分批写入和派生标志刷新不是一个事务,失败后必须核对期望状态 | 每个生成接口页的“接口边界”会注明该操作属于哪一种范围,以及 HTTP 200 之外的成功判定。 ## 重试 [#重试] | 操作 | 规则 | | ---------------------- | ------------------------------------------------- | | `/route` | 网络失败或临时 `5xx` 可退避重试 | | `/user/token` | 只重试同一身份意图,不要循环生成新 Token | | `/channel/messagesync` | 使用同一游标重试,并容忍与实时投递重复 | | Channel 变更 | 仅在业务意图仍有效时按期望状态重放;`reset`、set 和 remove-all 可能部分完成 | | `/conversation/list` | 原样传递 `next_cursor`,只以 `done=true` 结束 | | `/conversation/list` | 任一频道读取失败则整页失败;使用原请求、原游标重试 | 这些接口没有通用 `Idempotency-Key`。安全要求见[认证与安全](/zh/api/authentication),错误分类见[错误响应](/zh/api/product-http/errors)。 ## JavaScript Web 黄金路径契约 这是非完整的 v3 Beta 子集;三个调用只能出现在受信任的 localhost BFF 中,浏览器不能直接调用 WuKongIM HTTP API。 - `POST /user/token` - `GET /route` - `POST /channel/messagesync` [OpenAPI 3.1 子集](/contracts/javascript-web-quickstart.openapi.json) # API 与协议 (/zh/api) 这里按受众和协议类型拆分当前源码中的全部接口。公开合同可直接下载;私有、条件启用和实验性入口只记录边界,不升级为兼容承诺。 ## 快速入口 [#快速入口] ## 使用边界 [#使用边界] * WuKongIM HTTP API 仅供受信后端调用,不应直接暴露给浏览器或移动端。 * Operations HTTP 与 Manager 仅应位于隔离的运维网络;两者不是同一合同。 * JSON-RPC 支持固定 EasySDK 的 CONNECT、Ping 与在线 SEND/SENDACK、RECV/RECVACK 与服务端出站公开 stream.\* EVENT;batch、订阅、离线同步和通用 RPC 不在该合同内。 * HTTP 状态、端点响应与 CONNACK/SENDACK Reason Code 属于不同层,需分别处理。 * 版本和验证范围见 [API 兼容性](/zh/api/compatibility),错误处理见 [错误响应](/zh/api/product-http/errors)。 # 接口清单与信任边界 (/zh/api/interface-inventory) 本页回答“源码里还有哪些入口”。它是边界清单,不把内部接口升级为公共兼容承诺。 ## Manager:110 条私有路由 [#manager110-条私有路由] 权威注册点是 `internal/access/manager/server.go`、`backups.go` 和 `restore.go`。 | 分组 | 数量 | `auth_on=true` 权限 | `auth_on=false` | | ------------------------ | -----: | ------------------------------------------------- | --------------- | | Operations MCP endpoint | 1 | 独立 MCP Bearer | 独立 MCP Bearer | | Login info | 1 | 公开已配置的 guest 凭据 | 不注册 | | Login | 1 | 固定用户登录 | 不注册 | | Permissions | 1 | `cluster.permission:r` | 无权限门槛 | | MCP read / write | 2 / 5 | `cluster.mcp:r` / `cluster.mcp:w` | 失败关闭 | | Node read / write | 9 / 12 | `cluster.node:r` / `cluster.node:w` | 无权限门槛 | | Slot read / write | 3 / 3 | `cluster.slot:r` / `cluster.slot:w` | 无权限门槛 | | Controller read / write | 6 / 3 | `cluster.controller:r` / `cluster.controller:w` | 无权限门槛 | | Diagnostics read / write | 4 / 2 | `cluster.diagnostics:r` / `cluster.diagnostics:w` | 无权限门槛 | | Application log read | 3 | `cluster.log:r` | 无权限门槛 | | Database inspect | 3 | `cluster.db:r` | 无权限门槛 | | Channel read / write | 8 / 12 | `cluster.channel:r` / `cluster.channel:w` | 无权限门槛 | | Migration read | 2 | `cluster.channel:r` | 无权限门槛 | | Connection read | 2 | `cluster.connection:r` | 无权限门槛 | | Webhook config read | 1 | `cluster.webhook:r` | 无权限门槛 | | Plugin read / write | 3 / 5 | `cluster.plugin:r` / `cluster.plugin:w` | 无权限门槛 | | User read / write | 3 / 4 | `cluster.user:r` / `cluster.user:w` | 无权限门槛 | | Backup read / write | 2 / 7 | `cluster.backup:r` / `cluster.backup:w` | 读无门槛;写失败关闭 | | Restore write | 2 | 精确 `cluster.restore:w` | 失败关闭 | 合计 `110`。`POST /manager/slots/leader-transfer-plan` 是只生成计划的读权限路由。`ANY /mcp` 的 MCP Handler 实际只接受 POST,拒绝非空 Origin;其余 Manager 路由使用开放 CORS。完整 method/path 集合由 `lib/api-surface-contracts.ts` 保存并由源码测试校验。 `GET /manager/login` 仅向登录页返回已配置的 `guest` 凭据,并设置 `Cache-Control: no-store`;其他账号保持私有。 `GET /manager/nodes/:node_id/config/toml` 返回版本化、脱敏的启动 TOML 文档,使用相同的 `cluster.node:r` 读取权限。 ## Node transport:77 个共享编号 [#node-transport77-个共享编号] 受众是 **cluster-internal**。共享 TCP transport 没有逐调用 Bearer 或 TLS 身份;只有加入流程检查 Join Token,其余调用信任集群路由和隔离网络。 | ID | 目录名称 | | ------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `1–31` | `slot_forward_propose`, `channel_pull`, `channel_ack`, `channel_pull_hint`, `channel_notify`, `control_state_sync`, `control_report_node`, `control_report_slots`, `channel_append`, `channel_append_batch`, `control_raft`, `control_task_result`, `presence_authority`, `presence_owner`, `delivery_push`, `delivery_fanout`, `channel_pull_batch`, `channel_pull_hint_batch`, `channel_last_visible`, `reserved_conversation`, `channel_authority_send`, `manager_connection`, `manager_logs`, `manager_controller_raft`, `manager_slot_raft`, `manager_channels`, `manager_db_inspect`, `manager_app_logs`, `manager_diagnostics`, `manager_plugins`, `plugin_binding_scan` | | `32–33` | `msg_slot_raft`, `msg_slot_raft_batch` | | `64–86` | `control_write`, `manager_message_retention`, `node_lifecycle`, `slot_status`, `manager_task_audit`, `channel_migration_meta`, `message_event_append`, `manager_node_config`, `manager_latest_messages`, `scheduled_backup_messages`, `scheduled_backup_slot`, `scheduled_backup_probe`, `scheduled_backup_restore`, `operations_mcp`, `manager_goroutines`, `slot_subscriber_metadata`, `slot_channel_metadata`, `channel_conversation_heads`, `channel_committed_reads`, `slot_user_membership`, `slot_runtime_metadata`, `slot_permission_metadata_batch`, `channel_quorum_exchange` | | `87` | `slot_identity_metadata` | | `88` | `manager_node_config_document` | | `89` | `slot_message_updates` | | `90` | `message_update_hint` | | `91` | `node_send_permissions` | | `92` | `message_event_delivery` | | `93` | `mqtt_source` | | `94` | `channel_mqtt_replay` | | `95` | `channel_mqtt_copy` | | `96` | `channel_mqtt_anchor` | | `97` | `channel_mqtt_plan` | | `98` | `channel_mqtt_repair` | | `99` | `channel_mqtt_recovery` | | `100` | `channel_mqtt_retirement` | | `101` | `channel_mqtt_retirement_selection` | | `102` | `channel_mqtt_consumer_read` | | `103` | `channel_will_receipt` | | `104` | `channel_mqtt_originals` | | `105` | `mqtt_will_dispatch` | | `106` | `slot_mqtt_metadata` | | `107` | `mqtt_owner` | ID `16` 是退役保留项,ID `20` 是删除功能的保留项,均不得复用。默认 Slot proxy 使用已晋升的 `79/80/83/84/85/87/89`;默认身份处理器仅接受 `get_device`,不开放用户读取或扫描;身份元数据使用 `87`,避免原来的 `4` 与 `channel_pull_hint` 冲突。通用但非默认的 Slot Store 仍声明私有 `47/53`,这些仍不能视为稳定目录。这些协议只能进入“不稳定内部合同”页,不能生成伪 OpenAPI 路径。 ## MCP [#mcp] | Surface | 精确工具 | 边界 | | ------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------- | | Operations MCP `POST /mcp` | `cluster_health`, `node_inspect`, `slot_inspect`, `channel_runtime_inspect`, `controller_tasks_query`, `metrics_query_range`, `logs_search`, `logs_context`, `diagnostics_query`, `config_read_redacted`, `backup_inspect`, `pprof_analyze` | `wko_*` 专用 Bearer;Manager JWT 无效;拒绝非空 Origin;除有界 `pprof_analyze` 外只读 | | Cloud Analysis MCP `POST /mcp` | `run_inspect`, `cluster_snapshot`, `workload_inspect`, `metrics_query_range`, `logs_search`, `logs_context`, `diagnostics_query`, `task_audits_query`, `trace_start`, `trace_query`, `profile_capture`, `profile_top`, `profile_list`, `config_read_redacted` | 单个 live Run 的不可续期 Analysis Bearer;`trace_start` 和 `profile_capture` 是有界主动诊断 | | Review Check stdio MCP | `check_list`, `check_result`, `check_run` | 本机 stdio、无凭据;只执行命名检查,不接受任意命令、参数或路径 | Cloud Analysis Gateway 另有无认证 `GET /healthz`、无认证 `GET /self-check`,以及可选的 GitHub OIDC `POST /analysis/token`。它们都属于 **agent-internal**,不是业务集成 API。 ## Cloud View [#cloud-view] Cloud View 是 Simulation Viewer 的无认证反向代理:`ANY /cloud-view/status` 返回 no-store 状态,`/prometheus[/...]` 代理 Prometheus,任意 WebSocket Upgrade 代理 Gateway;已知 Product 前缀代理 API,其余路径默认代理 Manager。它只提供按 IP/全局限流和 WS 并发限制,不新增身份认证。`X-WuKongIM-Cloud-View-Gate` 只影响 benchmark-purity 标记,不授权请求。 ## Agent 与 benchmark 控制面 [#agent-与-benchmark-控制面] * `wkissueagent`:`reconcile-github`, `recover-task`, `build-context`, `capture-candidate`, `verify-candidate`, `mint-app-token`, `publish-candidate`。 * `wkreviewagent`:`normalize-review-result`, `reconcile-github`, `recover-review`, `build-context`, `verify-baseline`, `validate-review-result`, `validate-explanation`, `append-state`, `publish-review`。 * `wkreviewcheck`:`go-format`, `go-mod-tidy`, `web`, `demo`, `docs`, `docs-integration`, `three-node`。 * Generic benchmark worker:14 条 `/healthz`、`/v1/*` 控制路由;除 Health 外要求 Control Token,或显式启用 insecure control。 * Chat-lifecycle worker:12 条 `/healthz`、`/v1/info`、`/v1/chat-lifecycle/*` 路由;包括 Health 在内全部要求 Bearer。 这些是严格 JSON/stdin 或私有 HTTP 进程合同,只应在 Agent/benchmark 内部清单中维护。 ## 公共集成边界 [#公共集成边界] | Surface | 受众 | 发布建议 | | -------------------------------------------------------- | ------------------ | -------------------------------------------------- | | Plugin host RPC:8 条 Unix-socket 路由 | Plugin author | 单独的 Beta Plugin Protocol;protobuf/wkrpc,不用 OpenAPI | | Webhook:`msg.notify`, `msg.offline`, `user.onlinestatus` | Public integration | 发布正式回调合同;当前 POST JSON、仅 200 成功、无签名 | | Operations HTTP | Operator | 独立运维文档;不要混入 WuKongIM HTTP API | | Manager、Node、Cloud View、Cloud Analysis、Agent、Bench | Private | 只列边界、稳定性和风险,不承诺公共兼容性 | Plugin host 的精确本机路径为 `/plugin/start`、`/close`、`/message/send`、`/channel/messages`、`/cluster/config`、`/cluster/channels/belongNode`、`/conversation/channels`、`/plugin/httpForward`。调用者身份来自 Unix-socket wkrpc UID。Webhook 是 best-effort 内存重试,进程崩溃后不重放;接收方必须自行建立来源认证与幂等。 # 指南 (/zh/guide) WuKongIM 是开源、自托管的实时消息基础设施。本指南从产品选型开始,带你完成第一次运行、理解核心概念,并接入已有业务系统。 第一次了解 WuKongIM?先读产品概览。准备亲自验证?直接进入快速开始。 ## 推荐路径 [#推荐路径] 还在判断是否适合?先读[产品概览](/zh/guide/product-overview)。准备开始验证?进入[快速开始](/zh/guide/quick-start),完成启动、就绪检查和第一次双向消息。 # 选择 SDK (/zh/sdk) WuKongIM 提供两套客户端 SDK。先看你的应用需要由 SDK 管理多少客户端状态,再进入对应文档。 ## 怎么选 [#怎么选] | 你的需求 | 推荐 | | ----------------- | ------------- | | SDK 需要维护本地消息与聊天列表 | WuKongIMSDK | | 需要未读数、历史消息和离线恢复 | WuKongIMSDK | | 只需要在线连接、发送和接收 | WuKongEasySDK | | 希望用尽量少的 API 完成集成 | WuKongEasySDK | 如果你正在开发完整聊天产品,从 [WuKongIMSDK](/zh/sdk/wukongim) 开始。如果只是给现有产品增加轻量实时通信,从 [WuKongEasySDK](/zh/sdk/easy) 开始。 # 服务端 (/zh/server) 服务端文档面向部署者、运维人员和需要理解系统内部结构的工程师。 每个部署都是集群。只有一个节点的部署称为单节点集群,不存在绕过集群行为的独立模式。 新环境从“部署”进入;配置、运维和架构是按需查阅的服务端参考。页面发布不替代目标环境的部署、故障恢复或升级运行证据。 ## 文档分区 [#文档分区] # 连接生命周期 (/zh/api/client-protocols/connection-lifecycle) ## 建立连接 [#建立连接] * `CONNECT` 必须是唯一首包;同批携带其他包或鉴权等待期间再发包都会关闭连接。 * 服务端协商协议版本(当前最新 v6;客户端传 `0` 或高于 v6 时采用 v6),写入 UID、设备以及启用时的加密 Session 状态,再激活在线 Presence。 * 非成功 `CONNACK` 写回后连接关闭。成功 `CONNACK` 写失败时,已完成的 activation 会回滚。 成功只证明协议 Session 已建立。客户端仍需恢复持久消息、合并本地状态,再开放普通业务发送。 ## 活跃会话 [#活跃会话] | 方向 | 交互 | 完成边界 | | --------- | ------------------ | ---------------------- | | 客户端 → 服务端 | `PING` → `PONG` | 仅心跳响应 | | 客户端 → 服务端 | `SEND` → `SENDACK` | 协议发送结果;不等于对端收到或业务执行 | | 服务端 → 客户端 | `RECV` → `RECVACK` | Session 接收反馈;不等于最终用户已读 | 默认读空闲超时是 3 分钟,只有入站活动刷新期限;服务端出站流量不会刷新。代理或负载均衡器的空闲策略必须容纳有效心跳。 ## 关闭与恢复 [#关闭与恢复] * `ReasonAuthFail` 或 `ReasonBan`:停止自动重连,先修复凭据或策略状态。 * `ReasonClientKeyIsEmpty` 或 `ReasonProtocolUpgradeRequired`:先修复客户端配置或版本。 * `ReasonRateLimit`、`ReasonSystemError` 或传输中断:仅使用有界退避与抖动,必要时重新发现 Gateway。 * 其他失败:保留原始 Reason Code 并 fail closed,不猜测为可重试。 关闭后停止新发送。恢复同一业务发送时复用稳定的 `client_msg_no`,并发 Wire 尝试使用不同 `client_seq`;只有 CONNACK 成功且持久消息恢复完成后才重新进入 ready。 当前默认组合启用 Session Payload 加密与已存设备 Token 校验;CONNECT 缺少 `client_key` 会返回 `ReasonClientKeyIsEmpty`,Token 为空、设备记录不存在或不匹配会返回 `ReasonAuthFail`。精确 Token 比对不替代 TLS、账号登录、凭据过期/重放策略或 WuKongIM HTTP API 防护。 下一步查看[数据包类型](/zh/api/client-protocols/packet-types)和 [Reason Code](/zh/api/dictionaries/reason-codes)。 # 加密与安全 (/zh/api/client-protocols/encryption) 当前默认组合启用 WKProto 会话 Payload 加密。它是客户端兼容算法,只保护 SEND/RECV Payload;CONNECT、Header 和路由元数据不在这层加密内。 ## 握手与密钥 [#握手与密钥] 1. 客户端生成临时 X25519 密钥对,把 32 字节公钥按标准 Base64 放入 CONNECT `client_key`。 2. 服务端生成临时 X25519 密钥对,计算共享密钥,并在 CONNACK 返回 Base64 `server_key` 与 `salt`。 3. 双方计算 `shared = X25519(private, peer_public)`。 4. `AES key = lowercase_hex(MD5(Base64(shared)))[0:16]`;这 16 个 ASCII 字节作为 AES-128 key。 5. `salt` 是 16 个随机字母数字 ASCII 字节,作为 IV 并在整个会话中复用。 启用加密时,缺少 `client_key` 返回 `ReasonClientKeyIsEmpty`。 ## Payload [#payload] ```text wire_payload = Base64(AES-128-CBC(PKCS#7(plain_payload), key, iv)) ``` 服务端在 SEND 上先校验 `msg_key`,再解密 Payload;生成 RECV 时先加密 Payload,再计算 `msg_key`。设置 `SettingNoEncrypt (0x10)` 会让该包跳过这两步。 ## msg\_key [#msg_key] 字段按表中顺序转为十进制文本或原始字节,**不加分隔符**拼接;其中 `encrypted_payload` 是 Wire 上的 Base64 Payload 字节。 | 包 | verification bytes | | ---- | ----------------------------------------------------------------------------------------------------------------- | | SEND | `client_seq + client_msg_no + channel_id + channel_type + encrypted_payload` | | RECV | `message_id + message_seq + client_msg_no + timestamp + from_uid + channel_id + channel_type + encrypted_payload` | 随后计算: ```text msg_key = lowercase_hex(MD5(Base64(AES-128-CBC(PKCS#7(verification_bytes), key, iv)))) ``` 当前服务端会验证 SEND `msg_key`。仓库内 Go 工具客户端会解密 RECV,但目前**不会验证 RECV `msg_key`**;其他客户端必须按各自实现确认行为。 ## 安全边界 [#安全边界] 该兼容方案不会认证 X25519 对端,使用复用 IV 的 CBC 与 MD5 校验,也不是 AEAD;它不能抵抗主动中间人,不能替代 TLS、Token 验证、身份认证或端到端加密。CONNECT 中的 Token 在会话密钥建立前发送,没有 TLS 时可被观察。 生产环境应使用 TLS/WSS 或等价的受信私网边界,并在 Gateway 入口完成证书、身份与 Token 验证。不要为敏感消息设置 `SettingNoEncrypt`。 # 客户端协议 (/zh/api/client-protocols) 客户端通过 Gateway 的 TCP 或 WebSocket Listener 建立长连接。WKProto 公共基线包含连接协商、发送、接收确认和心跳。MQTT 使用独立的 MQTT 5 TCP 入口,共用 IM 用户、频道和消息。 TCP 二进制帧和 WebSocket 消息不属于 OpenAPI。这里记录 Wire 与 Gateway 行为,不虚构 HTTP 路径。 ## 当前边界 [#当前边界] * 公共核心包含 CONNECT/CONNACK、SEND/SENDACK、RECV/RECVACK、PING/PONG 与服务端出站 stream.\* EVENT。 * WKProto 的 `DISCONNECT`、`SUB`、`SUBACK` 目前只是 codec 合同,不代表产品 Gateway 已发布对应入口。 * 服务端可通过 `EVENT` 投递公开流式消息事件;任意客户端 EVENT 入站仍不支持,入站只接受 benchmark terminal-fence 工具请求。 * WebSocket 可承载 WKProto bytes;JSON-RPC bridge 支持固定 EasySDK 的核心在线路径,其余方法仍按矩阵标为实验性或缺失。 数值含义见 [公共数据字典](/zh/api/dictionaries),生产信任边界见[认证与安全](/zh/api/authentication)。 # WebSocket JSON-RPC (/zh/api/client-protocols/json-rpc) Product Gateway 已支持固定 EasySDK 的 CONNECT-first 鉴权生命周期、Ping、在线 SEND/SENDACK、RECV/RECVACK 与服务端向客户端的公开 stream.\* EVENT。通用 JSON-RPC、batch、订阅和 SDK 自动离线同步仍不在该合同内。 ## wsmux 选择规则 [#wsmux-选择规则] WebSocket `wsmux` 在首个非空白字节为 `{` 或 `[` 时选择 JSON-RPC,否则选择 WKProto;选择结果在会话内保持。JSON-RPC 出站使用文本消息,WKProto 使用二进制消息。 `[` 只会触发 JSON 模式,当前解码器**不支持 batch 数组**。`jsonrpc` 可省略,请求 ID 必须是 JSON 字符串。SEND `payload` 可为已编码的 JSON 文本字符串、JSON 对象、Base64 字符串或 `null`;服务端在协议边界统一为字节。字符串解码后的内容若是有效 JSON,会优先按 JSON 文本处理,否则再尝试 Base64。JSON-RPC 不执行 WKProto 的 `clientKey` 密钥协商,生产传输必须使用 `wss://`。 ## 实际入站矩阵 [#实际入站矩阵] | 类型 | 方法 | 解码 / 桥接 | 产品结果 | | ------------ | ------------------------------- | ------------------------------- | ----------------------------------------------------------------------------- | | request | `connect` | → `CONNECT` | 必须是首个请求;鉴权、激活成功后返回同 ID `result`,失败返回同 ID `error` 并关闭 | | request | `send` | → `SEND` | 仅在已认证会话处理;按请求 ID 返回 `SENDACK` result/error | | request | `ping` | → `PING` | 返回同 ID 且显式包含 `result: null` | | request | `disconnect` | → `DISCONNECT` | 产品处理器不支持并关闭 | | request | `subscribe` / `unsubscribe` | → `SUB`(`action` 分别为 `0` / `1`) | Product Gateway handler 尚不支持 `SUB`;默认以 handler error 关闭连接,当前产品路径不会产生 `SUBACK` | | notification | `recvack` | → `RECVACK` | 已认证会话中确认在线投递;iOS `v1.1.1` 同时发送 camelCase `messageId` 与 `messageSeq` | | notification | `recv` / `disconnect` / `event` | 仅解码 | 缺少 frame bridge | | response | 任意合法 response | 仅解码为 generic response | 缺少 frame bridge | CONNACK 与 SENDACK 成功时同时输出 camelCase 与 Android snake\_case 字段,客户端可忽略另一套别名;非成功 Reason Code 输出为 JSON-RPC `error`,避免 EasySDK 把失败误判为成功。RECV 是无 ID 的 `recv` notification,始终包含 `header: {}`;有效 JSON 对象 Payload 直接输出对象,非对象字节回退为 Base64。Android `v1.0.5` README 示例仍把已编码的 JSON 消息作为文本字符串传入,协议边界也兼容直接 JSON 对象和 Base64 字符串;服务端对四个固定 profile 的常规 JSON 消息字节都以对象形式输出 RECV。 ## 在线流式事件 [#在线流式事件] 服务端 `EVENT` 编码为无 ID 的 `event` notification,参数是 `{id,type,timestamp,data}`;`data` 的有效 JSON 直接输出对象。EasySDK-JS 通过 `WKIMEvent.CustomEvent` 接收公开 `stream.*` 事件。此能力要求包含在线事件投递实现的服务端,不能归入旧版本的核心消息验收范围。事件尽力投递,无 RECVACK 重放;最终快照通过消息历史恢复。详见 [Agent 流式回复](/zh/sdk/easy/agent-streaming)。 ## 已验证范围 [#已验证范围] 服务端 fixture 覆盖当前 iOS `v1.1.1`、Android `v1.0.5`、Flutter `v1.1.0` 与 Web `v2.0.4` 保留的固定 wire profile;这些补丁版本不改变被测 JSON-RPC 形态。真实 `cmd/wukongim` 256 Slot 单节点集群场景使用 iOS 与 Android profile,覆盖 Alice/Bob 双向 CONNECT → SEND → SENDACK → RECV → RECVACK → Ping、断连后的在线状态清理和重连。另一次正式包验收从四个 Registry 解析这些版本,并在 Android 与 iOS Simulator 上完成双向消息。默认 `gateway.token_auth_on=true` 会执行已存设备 Token 精确匹配;生产验收仍需单独证明错误、缺失、撤销和按业务规则过期的 Token 都会被拒绝。 需要轻量在线消息时可使用 [WuKongEasySDK](/zh/sdk/easy)。需要本地消息库、会话、未读、离线恢复或更完整能力时,选择 WKProto 系列 SDK,并参考 [TCP 二进制协议](/zh/api/client-protocols/tcp-binary)。 # 数据包类型 (/zh/api/client-protocols/packet-types) `FrameType` 位于固定 Header 的高四位。`UNKNOWN=0` 是保留值,不能发送;枚举存在也不代表产品 Gateway 已支持该包。 ## 核心字段 [#核心字段] | 包 | 正文字段 | | --------------- | ------------------------------------------------------------------------------------------------------------ | | `CONNECT` | `version`、`client_key`、`device_id`、`device_flag`、毫秒时间戳、`uid`、`token` | | `CONNACK` | v4+ 请求的协商版本、`server_key`、`salt`、时间差、Reason Code、v4+ `node_id` | | `SEND` | Setting、32 位 Wire `client_seq`、`client_msg_no`、Channel、v3+ `expire`、`msg_key`、可选 Topic、Payload | | `SENDACK` | `message_id`、`client_seq`、`message_seq`、Reason Code、可选 `client_msg_no` | | `RECV` | Setting、`msg_key`、发送者、Channel、v3+ `expire`、`client_msg_no`、`message_id`、`message_seq`、秒级时间戳、可选 Topic、Payload | | `RECVACK` | `message_id`、`message_seq` | | `PING` / `PONG` | 无正文;整个包只有一个固定 Header 字节 | 协议 v5 及以下使用 32 位 `message_seq`,v6 使用 64 位;`client_seq` 的 Wire 宽度始终为 32 位。当前编码器限制 SEND Payload 最多 32767 字节;解码器拒绝 remaining length 超过 1 MiB 的帧。 固定 Header 与 Setting 位见 [Message Flags](/zh/api/dictionaries/message-flags),设备值见 [Device Flag](/zh/api/dictionaries/device-flags),Channel 数值见 [Channel Type](/zh/api/dictionaries/channel-types),结果见 [Reason Code](/zh/api/dictionaries/reason-codes)。 当前产品 Gateway 不接受普通 `DISCONNECT`、`SUB` 或任意 `EVENT` 入站;客户端 `EVENT` 入站只用于 benchmark terminal-fence;服务端出站也投递公开流式消息事件。`UNKNOWN=0` 不进入产品处理;未知数值、畸形帧或已解码但未发布的包通过协议错误、处理器错误或有界超时 fail closed。 ## WKProto 数据包目录 | 值 | 名称 | 方向 | 范围 | 说明 | | --- | --- | --- | --- | --- | | 0 | `UNKNOWN` | — | 保留 | Wire 保留值;不得发送,也不进入产品处理。 | | 1 | `CONNECT` | 客户端 → 服务端 | 公共核心 | 必须是连接上的唯一首包。 | | 2 | `CONNACK` | 服务端 → 客户端 | 公共核心 | 返回连接结果和会话材料;v4+ 请求另带协商版本。 | | 3 | `SEND` | 客户端 → 服务端 | 公共核心 | 提交一条 Channel 消息。 | | 4 | `SENDACK` | 服务端 → 客户端 | 公共核心 | 关联 SEND 并返回协议结果。 | | 5 | `RECV` | 服务端 → 客户端 | 公共核心 | 向当前 Session 投递一条消息。 | | 6 | `RECVACK` | 客户端 → 服务端 | 公共核心 | 确认客户端处理了指定 RECV。 | | 7 | `PING` | 客户端 → 服务端 | 公共核心 | 刷新入站活动并请求 PONG。 | | 8 | `PONG` | 服务端 → 客户端 | 公共核心 | 无正文的 PING 响应。 | | 9 | `DISCONNECT` | 客户端 ↔ 服务端 | 仅编解码 | 双向 codec 合同;当前产品 Gateway 未发布客户端入站或服务端发送语义。 | | 10 | `SUB` | 客户端 → 服务端 | 仅编解码 | 存在编解码器;当前产品 Gateway 入站未支持。 | | 11 | `SUBACK` | 服务端 → 客户端 | 仅编解码 | SUB 的编解码响应;当前产品入口未发布。 | | 12 | `EVENT` | 客户端 ↔ 服务端 | 公共核心 | 服务端向客户端投递公开 stream.* 消息事件;客户端入站仅接受 benchmark terminal-fence 工具请求。 | 协议 v6 使用 64 位 `message_seq`;v5 及以下使用 32 位。`client_seq` 的 Wire 宽度始终为 32 位。 # TCP 二进制协议 (/zh/api/client-protocols/tcp-binary) WKProto 运行在 TCP 字节流上;WebSocket 的 WKProto 模式承载相同字节序列。下表给出当前编码器的**精确正文顺序**,不是 HTTP OpenAPI。 ## 帧边界 [#帧边界] ```text 普通帧 = fixed_header:u8 + remaining_length:varuint + body PING/PONG = fixed_header:u8 ``` * `fixed_header` 高 4 位(7..4)是 `FrameType`。 * 除 `CONNACK` 外,低 4 位依次是 `DUP`(bit 3)、`SyncOnce`(2)、`RedDot`(1)、`NoPersist`(0)。 * `CONNACK` 编码器只使用 bit 0 表示 `HasServerVersion`。 * `remaining_length` 是正文长度,按 base-128 编码:低 7 位组先写,后续字节设置 `0x80`。 * 多字节整数均为大端。`str16be` 是 `i16be` 字节长度加原始字节,最大 32767 字节;`bytes-rest` 消耗正文余下全部字节。 * 解码器拒绝 `remaining_length > 1 MiB`;SEND 编码器拒绝 Payload 超过 32767 字节。 ## WebSocket 承载 [#websocket-承载] * Upgrade 必须使用 WebSocket v13、`GET` 和 Listener 配置的精确 Path;Path 为空时是 `/`。 * 客户端帧必须 Mask,服务端帧不 Mask;分片的 Text/Binary 消息会先重组,并受默认 1 MiB Session 入站上限约束。 * WKProto 使用 Binary 消息。WebSocket Control `PING/PONG` 只由承载层应答,不会替代或进入 WKProto `PING/PONG` 心跳。 ## 正文布局 [#正文布局] `v` 是协商版本,`S` 是 Setting,`H0` 是固定头 bit 0。 | 值 | 包 / 方向 | 正文字段(从左到右) | | -: | ---------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | 0 | `UNKNOWN` / 保留 | 无;不得发送 | | 1 | `CONNECT` C→S | `version:u8`, `device_flag:u8`, `device_id:str16be`, `uid:str16be`, `token:str16be`, `client_timestamp:i64be(ms)`, `client_key:str16be` | | 2 | `CONNACK` S→C | `[server_version:u8 if H0]`, `time_diff:i64be(ms)`, `reason_code:u8`, `server_key:str16be`, `salt:str16be`, `[node_id:u64be if v>=4]` | | 3 | `SEND` C→S | `setting:u8`, `client_seq:u32be`, `client_msg_no:str16be`, `[stream_no:str16be if 2<=v<5 and S.Stream]`, `channel_id:str16be`, `channel_type:u8`, `[expire:u32be if v>=3]`, `msg_key:str16be`, `[topic:str16be if S.Topic]`, `payload:bytes-rest` | | 4 | `SENDACK` S→C | `message_id:i64be`, `client_seq:u32be`, `message_seq:seq(v)`, `reason_code:u8`, `[client_msg_no:str16be if non-empty]` | | 5 | `RECV` S→C | `setting:u8`, `msg_key:str16be`, `from_uid:str16be`, `channel_id:str16be`, `channel_type:u8`, `[expire:u32be if v>=3]`, `client_msg_no:str16be`, `[stream_flag:u8, stream_no:str16be, stream_id:u64be if 2<=v<5 and S.Stream]`, `message_id:i64be`, `message_seq:seq(v)`, `timestamp:i32be(s)`, `[topic:str16be if S.Topic]`, `payload:bytes-rest` | | 6 | `RECVACK` C→S | `message_id:i64be`, `message_seq:seq(v)` | | 7 | `PING` C→S | 无正文,也没有 `remaining_length` | | 8 | `PONG` S→C | 无正文,也没有 `remaining_length` | | 9 | `DISCONNECT` 双向 / codec-only | `reason_code:u8`, `reason:str16be` | | 10 | `SUB` C→S / codec-only | `setting:u8`, `sub_no:str16be`, `channel_id:str16be`, `channel_type:u8`, `action:u8`, `param:str16be` | | 11 | `SUBACK` S→C / codec-only | `sub_no:str16be`, `channel_id:str16be`, `channel_type:u8`, `action:u8`, `reason_code:u8` | | 12 | `EVENT` 双向 / 出站 stream.\* | `id:str16be`, `type:str16be`, `timestamp:i64be`, `data:bytes-rest` | `seq(v)` 在 v5 及以下是 `u32be`,v6 是 `u64be`。当前 SENDACK 解码器还接受兼容顺序:`message_id, client_seq, client_msg_no, message_seq, reason_code`;新编码器始终使用表中主顺序。 ## 版本与 Setting [#版本与-setting] * 服务端当前版本为 v6。CONNECT 的 `version=0` 或大于 6 会协商为 6;1–6 保持原值。 * 客户端请求版本大于 3 时,CONNACK 设置 `H0` 并携带 `server_version`。 * Setting 位:`Receipt=0x80`、`Signal=0x20`、`NoEncrypt=0x10`、`Topic=0x08`、`Stream=0x02`。 产品 Gateway 的公共入站面只处理 `PING`、`SEND`、`RECVACK`;`CONNECT` 必须是唯一首包并由连接阶段处理。普通 `DISCONNECT`、`SUB`、`SUBACK` 不受支持,客户端 `EVENT` 入站只服务 benchmark terminal-fence 工具流程;服务端 `EVENT` 出站也投递公开流式消息事件。 连接状态与心跳见[连接生命周期](/zh/api/client-protocols/connection-lifecycle),数值含义见[公共数据字典](/zh/api/dictionaries)。 # Channel Type (/zh/api/dictionaries/channel-types) `channel_id + channel_type` 共同标识一个 Channel。数值会进入协议、HTTP、存储与路由合同;集成者必须按数值传输,并把名称作为可读元数据保存。 ## 目标与完成标准 [#目标与完成标准] 选择 Channel Type 后,你应能说明该类型的成员模型、消息恢复、会话投影和业务权限来源。仅仅“枚举存在”不代表当前 SDK 或公开 API 已发布完整接入能力。 ## 权威来源 [#权威来源] 下表与当前 `pkg/protocol/frame/common.go` 的 `ChannelType*` 常量 1–12 对齐,由测试检查名称和值。它属于当前源码快照,不是跨任意版本永远不变的承诺。 ## 集成者默认选择 [#集成者默认选择] * 一对一通信从 `ChannelTypePerson=1` 开始,调用方使用对端 UID; * 普通群聊从 `ChannelTypeGroup=2` 开始,业务服务先维护群 ID、成员和策略; * 只有产品需求和所选客户端明确覆盖时才使用社区、资讯、直播、访客或 Agent 类型; * 新访客客服流程不要继续选择源码已标记过时的 `ChannelTypeCustomerService=3`; * 临时 Channel 不能作为稳定业务群 ID 持久化。 同一个业务 Channel 改变 `channel_type` 会形成不同的路由与存储身份。迁移必须作为显式的数据与客户端兼容方案,而不是更新一个配置值。 ## 单聊身份 [#单聊身份] 调用方为 Alice 发送给 Bob 时使用 Bob 的 UID。服务端在入口边界把双方 UID 规范化为内部 person Channel 身份;响应或 Webhook 中可能看到该 canonical 值。业务好友关系仍以双方 UID 为主键,不能把内部值回写为“对端 UID”。 ## 专用类型检查 [#专用类型检查] 使用专用类型前至少验证: 1. 服务端当前路径是否包含所需的成员、权限、持久化和会话语义; 2. 精确 SDK 版本是否能正确编码、解码并展示该类型; 3. 离线同步、推送、回调和历史迁移是否有覆盖测试; 4. 十万成员或高消息率场景是否使用有界成员变更与分页 fanout; 5. 未识别该类型的旧客户端是否安全降级。 ## 失败诊断 [#失败诊断] * **ReasonNotSupportChannelType**:先确认协议版本与调用值,不对其他类型盲重试。 * **单聊历史分裂**:检查是否同时使用对端 UID 和 canonical person ID 作为输入。 * **群聊发送被拒绝**:检查成员、黑白名单、封禁与解散状态,而不是改成 Person 绕过策略。 * **旧客户端无法展示**:维持服务端数据不变,按 Payload/客户端兼容方案降级。 ## 安全边界 [#安全边界] Channel Type 不是授权。即使类型正确,业务服务仍负责创建关系、成员、角色、内容治理和租户隔离。 ## 下一步 [#下一步] 客户端身份使用 [Device Flag](/zh/api/dictionaries/device-flags);消息行为位见 [Message Flags](/zh/api/dictionaries/message-flags)。 ## Channel Type(共享契约) | 值 | 名称 | 范围 | 集成说明 | | --- | --- | --- | --- | | 1 | `ChannelTypePerson` | 基础接入 | 单聊;调用方使用对端 UID,服务端入口负责规范化双方 UID 的 Channel 身份。 | | 2 | `ChannelTypeGroup` | 基础接入 | 群聊;业务服务必须先维护稳定 Channel ID、成员和发送策略。 | | 3 | `ChannelTypeCustomerService` | 兼容 / 旧类型 | 旧客服频道类型;源码已标注过时,新访客流程使用 ChannelTypeVisitors。 | | 4 | `ChannelTypeCommunity` | 专用类型 | 社区容器类型;只在所选 SDK 与业务流程明确支持时使用。 | | 5 | `ChannelTypeCommunityTopic` | 专用类型 | 社区话题类型;不要把它与社区容器或普通群聊互换。 | | 6 | `ChannelTypeInfo` | 专用类型 | 资讯频道,包含临时订阅者语义;接入前验证对应成员生命周期。 | | 7 | `ChannelTypeData` | 专用类型 | 数据频道;枚举存在不等于当前平台已发布完整接入流程。 | | 8 | `ChannelTypeTemp` | 专用类型 | 临时或请求级目标频道;不能当作持久业务群 ID。 | | 9 | `ChannelTypeLive` | 专用类型 | 直播频道;当前语义不保存最近会话数据。 | | 10 | `ChannelTypeVisitors` | 专用类型 | 访客频道;Channel ID 是访客 UID,可对应一个访客和多个客服订阅者。 | | 11 | `ChannelTypeAgent` | 专用类型 | 单聊 Agent 频道;内部身份形如 UID@AgentID,业务服务仍负责 Agent 授权。 | | 12 | `ChannelTypeAgentGroup` | 专用类型 | 群聊 Agent 频道;用于多 Agent 协同,不是普通群聊的透明别名。 | # Device Flag (/zh/api/dictionaries/device-flags) Device Flag 表示客户端设备类别,Device Level 表示同一 UID、同一类别内的连接冲突策略。它们不是操作系统版本、具体设备 ID 或业务角色。 ## 目标与完成标准 [#目标与完成标准] 业务后端应为每个端点选择稳定的 `device_flag`、具体 `device_id` 和由服务端控制的 `device_level`,并能解释同类设备是共存还是单活。 ## 权威来源 [#权威来源] 下表与 `internal/contracts/protocolmeta/types.go` 及 `pkg/protocol/frame/common.go` 对齐。测试同时冻结客户端类别、服务端保留值和两种冲突级别。 ## 三个字段不能混用 [#三个字段不能混用] | 字段 | 回答的问题 | | -------------- | ------------------------------ | | `device_flag` | 这是 APP、WEB、PC 还是服务端 SYSTEM 类别? | | `device_id` | 这是该类别下哪一个具体安装或端点? | | `device_level` | 同 UID、同类别的多个端点允许共存还是执行单活冲突策略? | 不要把手机品牌、浏览器 UA 或系统版本直接作为 `device_id`。生成稳定随机标识,并明确重装、清缓存、设备迁移和恢复备份时是否创建新 ID。 ## 当前鉴权行为 [#当前鉴权行为] `gateway.token_auth_on` 默认为 `true`。Gateway 会按 UID 与 Device Flag 读取已存设备 Token;只有 CONNECT Token 精确匹配后,Session 才采用该设备记录中的 Device Level。Token 为空、设备记录不存在、已清空或不匹配都会返回 `ReasonAuthFail`。 因此,受信后端写入 `device_level=1` 后,匹配 Token 的连接会采用 Master 策略;上线前仍需验证新 Master 连接、旧 Session 关闭、跨节点冲突、撤销和重连拒绝。不要在生产关闭 Token 鉴权来绕过这些检查。 ## 集成建议 [#集成建议] * APP、WEB、PC 同时在线时使用不同类别; * 同类别多端使用 Slave,并让每个 Session 独立处理 RECVACK; * 单活要求由后端签发 Master,客户端不能选择; * SYSTEM 仅供受信服务端身份,终端传入应拒绝; * 业务设备表另存厂商推送 Token、系统权限、风险和撤销状态,不把 Presence 当设备数据库。 ## 失败诊断 [#失败诊断] * **PC 显示成数字名称**:Wire 值仍是 2;日志/管理展示的 String 方法不是数值权威。 * **同类 Master 未互斥**:检查 CONNECT verifier 返回的实际 Device Level。 * **不同平台互相替换**:检查是否复用了同一 Device Flag。 * **退出后可重新连接**:检查 Token 撤销与 Gateway 校验,而不只是 Session close。 ## 安全边界 [#安全边界] 设备类别不能授予业务权限。SYSTEM 也不是普通客户端的“管理员模式”;只允许受信内部组件使用,并从公网入口拒绝伪造。 ## 下一步 [#下一步] 结合[设备](/zh/guide/core-concepts/devices)模型使用这些值,再查看[认证与安全](/zh/guide/integration/authentication)中的签发与撤销流程。 ## Device Flag(共享契约) | 值 | 名称 | 范围 | 集成说明 | | --- | --- | --- | --- | | 0 | `APP` | 客户端 | 原生移动应用设备类别。 | | 1 | `WEB` | 客户端 | Web 浏览器或 Web 应用设备类别。 | | 2 | `PC` | 客户端 | 桌面客户端设备类别。 | | 99 | `SYSTEM` | 服务端保留 | 服务端保留的系统设备类别;终端应用不能冒充。 | ### Device Level | 值 | 名称 | 范围 | 集成说明 | | --- | --- | --- | --- | | 0 | `DeviceLevelSlave` | 客户端 | 允许同一设备类别的多个端点共存。 | | 1 | `DeviceLevelMaster` | 客户端 | 声明同一设备类别的单活冲突策略;是否生效仍取决于已验证的连接鉴权与组合。 | # 公共数据字典 (/zh/api/dictionaries) ## 目标与完成标准 [#目标与完成标准] 本区域集中维护客户端 SDK、Gateway 和兼容 HTTP 响应复用的 Wire 值。Phase 13 在 Reason Code 之外发布 Channel Type、Device Flag / Level 与 Message Flags,帮助集成者跨语言核对数值和行为边界。 ## 兼容目标 [#兼容目标] 所有表都与当前 Go 权威定义对齐并由测试检查名称、值或位序。它们属于当前源码快照;[兼容性快照](/zh/api/compatibility)仍只证明 JavaScript 黄金场景,不自动证明每个 SDK 平台。 ## 前置条件 [#前置条件] * 数值是 Wire 合同,名称用于可读诊断; * 不把“枚举中存在”解释为“当前 SDK 已发布完整能力”; * 遇到未知值时保留原始数字并 fail closed。 ## 已发布字典 [#已发布字典] ## 预期结果 [#预期结果] 开发者应按数值匹配,并同时记录字典名称、协议阶段与兼容快照。应用代码不手写分散的魔法数字;由 SDK 类型或一个内部共享模块映射。 ## 失败诊断 [#失败诊断] * **名称与数值不一致**:让校验失败并重新校准,不要手工维护第二张表。 * **功能存在于字典但 SDK 找不到入口**:该平台能力可能仍未发布,按精确版本文档确认。 * **收到表外数值**:记录数值和兼容快照,升级字典前保持 fail closed。 ## 安全与责任边界 [#安全与责任边界] Wire 字典不是授权,也不是适合直接展示给最终用户的本地化文案。业务服务应映射安全含义,避免暴露内部拓扑、权限策略或敏感请求内容。 ## 下一步 [#下一步] 从 [Channel Type](/zh/api/dictionaries/channel-types) 与 [Device Flag](/zh/api/dictionaries/device-flags)建立身份,再用 [Message Flags](/zh/api/dictionaries/message-flags)和 [Reason Code](/zh/api/dictionaries/reason-codes)解释消息结果。 # Message Flags (/zh/api/dictionaries/message-flags) 消息行为由两组不同位控制:固定 Header 的低位布尔标志,以及消息 `Setting` 位。集成者应使用 SDK 暴露的类型,不手写魔法数字;本页用于跨语言核对 Wire 含义。 ## 目标与完成标准 [#目标与完成标准] 看到任一标志时,你应能说明它是否影响持久化、路由、展示或编码,并避免把协议意图误解为业务完成证明。 ## 权威来源 [#权威来源] 固定 Header 位与 `pkg/protocol/codec/common.go` 对齐;Setting 值与 `pkg/protocol/frame/setting.go` 对齐。测试冻结位序、名称和值。 ## `NoPersist` 在线投递但不保留历史 [#nopersist-在线投递但不保留历史] 普通 `NoPersist=true, SyncOnce=false` 消息使用源频道,命令式消息使用命令频道。 两者都经过权限检查、解析 authority、分配消息 ID,并以序号零进入瞬时在线投递; 不写入持久历史,也不能离线恢复。 * 可靠聊天、通知、审计及需要回放的业务事件使用持久消息。 * 普通在线消息使用 `NoPersist`;在线命令额外使用命令语义。 * 可恢复命令使用持久 `SyncOnce`,并接入独立 CMD bind/sync/ack。 * 瞬时消息的成功 SENDACK 表示在线投递已接收,不能证明设备已收到。 ## `RedDot` 与回执 [#reddot-与回执] `RedDot` 携带客户端展示意图,会随消息进入存储、投递和兼容回调,但它不等于: * 服务端已经修改某个 Conversation 的 `read_seq`; * 最终用户看过消息; * 接收端发出了业务回执。 `SettingReceiptEnabled` 也只是协议回执意图。SENDACK 是 Channel 提交结果,RECVACK 是 Session 传输反馈,最终用户已读与设备执行结果需要独立业务合同。 ## 加密相关位 [#加密相关位] * `SettingNoEncrypt` 会让兼容 WKProto 适配器跳过已协商的 Session Payload 加密;它不关闭或提供 TLS; * 敏感消息不要启用该位; * `SettingSignal` 是专用 signal 模式,只在精确 SDK 与协议版本明确支持时使用; * 不要根据一个位自行发明端到端加密保证,密钥分发、身份验证和轮换仍需完整设计。 ## Topic 与 Stream [#topic-与-stream] `SettingTopic` 表示数据包包含 Topic 字段。`SettingStream` 表示兼容流字段;它不自动承诺实时 token 推送、持久事件投影或重连恢复全部同时存在。当前 AI 流教程使用 durable base + message-event projection,并明确区分实时增量路径。 ## 组合检查 [#组合检查] | 需求 | 选择 | | --------- | ------------------------------------------------------------- | | 普通可恢复聊天消息 | `NoPersist=false`、`SyncOnce=false` | | 普通在线瞬时消息 | `NoPersist=true`、`SyncOnce=false`;不保留历史 | | 在线瞬时命令 | `NoPersist=true`、`SyncOnce=true`,接受无历史 | | 可恢复命令 | `NoPersist=false`、`SyncOnce=true`,另做 CMD 绑定与同步 | | 业务已读 | 独立持久回执,不依赖 `RedDot` / RECVACK | | 流式 AI | 先阅读 [AI 与 IoT](/zh/guide/tutorials/ai-and-iot),不要只设置 Stream 位 | ## 失败诊断 [#失败诊断] * **NoPersist 成功但接收端没有消息:** 检查订阅成员、在线 presence、投递接收情况和 owner Session 写入;离线接收者无法恢复消息。 * **消息无法离线恢复**:检查是否使用 NoPersist 或未建立 CMD 恢复流程。 * **红点与未读数不一致**:分开检查 Message Flag 与 Conversation Badge floor。 * **设置位后某 SDK 解码失败**:核对精确 SDK/协议版本,不盲目保留未知位。 ## 安全边界 [#安全边界] 未知位应 fail closed 或由兼容 SDK 安全忽略,不能默认启用。原始 `setting` 与 Header 可以进入受控诊断,但不能连同完整敏感 Payload 写入普通日志。 ## 下一步 [#下一步] 在[消息收发](/zh/guide/integration/messaging)中应用这些位,错误结果按 [Reason Code](/zh/api/dictionaries/reason-codes)分类。 ## 消息标志(共享契约) ### 固定 Header 位 | Bit | 名称 | 范围 | 集成说明 | | --- | --- | --- | --- | | 0 | `NoPersist` | Wire 标志 | 普通和命令式消息均进入瞬时在线投递;分配消息 ID,序号为零,不写入持久历史,也不能离线恢复。 | | 1 | `RedDot` | Wire 标志 | 携带红点展示意图;它不是消息已读回执,也不单独证明服务端未读数发生变化。 | | 2 | `SyncOnce` | Wire 标志 | 把命令式消息路由到独立 CMD Channel;可恢复命令还需要绑定与 CMD 同步流程。 | | 3 | `DUP` | Wire 标志 | 协议重发标记;业务幂等仍以稳定 client_msg_no 和结果关联为准。 | ### Setting 位值 | 值 | 名称 | 范围 | 集成说明 | | --- | --- | --- | --- | | 128 | `SettingReceiptEnabled` | Wire 标志 | 开启协议回执意图;不能把它等同于 Channel 提交、设备业务执行或最终用户已读。 | | 32 | `SettingSignal` | Wire 标志 | 标记兼容 signal 模式;只在所选 SDK 和协议版本明确支持时使用。 | | 16 | `SettingNoEncrypt` | Wire 标志 | 跳过已协商的会话 Payload 加密;它不替代 TLS,敏感消息不应启用。 | | 8 | `SettingTopic` | Wire 标志 | 表示数据包携带 Topic 字段;Topic 生命周期仍由兼容客户端与业务约定。 | | 2 | `SettingStream` | Wire 标志 | 表示兼容流消息字段;流式 AI 的持久投影与实时增量仍是不同路径。 | # Reason Code (/zh/api/dictionaries/reason-codes) 业务发送拒绝另外保留 128–255,由 `msg.before_send` 返回,并在 SENDACK `reason_code` 与 WuKongIM HTTP API `reason` 中原样透传。它们不是下表的系统枚举;SDK/业务层应保留原值、显示自定义文案,避免自动重试明确拒绝。 ## 目标与完成标准 [#目标与完成标准] 本页把当前 `pkg/protocol/frame.ReasonCode` 枚举完整映射为开发者决策。完成后,你应能保留原始数值,结合 CONNACK、SENDACK 等阶段解释它,并执行有界、不会放大故障的重试策略。 ## 兼容目标 [#兼容目标] 下表由 Go 权威枚举与共享元数据生成,并由 CI 校验数值 `0–29` 和名称。它绑定[当前 API 兼容快照](/zh/api/compatibility),不会在中英文 MDX 中各维护一份手写副本。 ## 前置条件 [#前置条件] * 已记录出现该值的数据包类型,而不是只有一个裸数字; * 已区分 HTTP 状态、CONNACK、SENDACK、在线投递和同步结果; * 重试实现具有幂等标识、退避、次数上限和取消; * 日志与指标会遮蔽身份和消息内容。 ## 如何读表 [#如何读表] * **阶段**说明该值适用于 CONNECT/CONNACK、SEND/SENDACK、断开或兼容路径中的哪类上下文。 * **重试**是安全默认,不是无限重试许可;必须结合幂等标识、退避、上限与取消。 * **可达性**区分当前生产路径可产生、只在特定配置/状态下可产生,以及保留在兼容枚举但当前黄金路径没有证据的值。 * “枚举中存在”不等于每个端点或 SDK 调用都可能返回它。 ## 客户端处理顺序 [#客户端处理顺序] 1. 先记录包类型、Reason Code 数值、兼容快照和请求关联信息。 2. 只有明确 Success 才完成当前协议阶段。 3. 身份、权限、黑白名单、封禁、解散、无效字段或不支持结果应停止盲目重试。 4. 路由变化、速率限制和系统错误只能采用有界指数退避,并在重试前重新发现必要状态。 5. 未知数值按未知失败 fail closed;保留原值,不映射成 Success。 ## 发送结果与消息阶段 [#发送结果与消息阶段] 对持久 SEND,成功 SENDACK 表示 Channel 权威已经作出持久提交成功决定。它不证明在线接收、RECVACK、Webhook、插件、离线同步或业务动作完成。 CONNACK 成功表示当前 Gateway 连接阶段完成;当默认的 `gateway.token_auth_on=true` 时,还表示 CONNECT Token 与相同 UID、设备类别的已存 Token 匹配。它仍不证明账号登录、业务授权、凭据过期或重放策略已经完成。 ## 预期结果 [#预期结果] 黄金样例会记录 SENDACK 的成功/失败状态和 message sequence;失败文本保留原始 Reason Code 数值,便于回到本表解释。事件日志不会显示 Token 或 Payload,产品 UI 应再映射自己的本地化业务文案。 ## 失败诊断 [#失败诊断] * **值与名称不匹配**:这是生成或版本漂移,必须让校验失败。 * **收到表中标为未在黄金路径验证的值**:保留原始证据并报告源码 revision,不要更改可达性结论来迎合一次现象。 * **持续收到可重试结果**:停止超过预算的重试,检查路由、限流、队列压力和集群状态。 * **权限类结果重试后仍失败**:回到业务账号、membership、黑白名单或频道状态修正权威事实。 * **未知数值**:按失败处理并升级兼容清单与字典校准。 ## 安全与责任边界 [#安全与责任边界] Reason Code 不应泄露内部拓扑、风控判断或敏感身份。日志使用低基数字段,并将 UID、Token、Channel ID 与消息正文排除在指标标签之外。面向最终用户的文本由业务产品拥有。 ## 下一步 [#下一步] 回到[错误响应](/zh/api/product-http/errors)组合 HTTP 与协议层处理,或按[Quickstart](/zh/sdk/javascript/quickstart)观察 SENDACK 与同步恢复的区别。 ## Wire ReasonCode 完整枚举(共享契约) | 值 | 名称 | 阶段 | 重试指引 | 可达性 | 说明 | | --- | --- | --- | --- | --- | --- | | 0 | `ReasonUnknown` | CONNECT / SEND | application-policy | compatibility | 未分类或未知结果;先保留上下文并停止盲目重试。 | | 1 | `ReasonSuccess` | CONNECT / SEND | not-applicable | active | 请求成功;具体完成边界取决于数据包类型。 | | 2 | `ReasonAuthFail` | CONNECT / SEND | do-not-retry | active | 认证或发送身份被拒绝;先修复凭据或服务端策略。 | | 3 | `ReasonSubscriberNotExist` | SEND | do-not-retry | active | 发送者不在要求的频道成员集合中。 | | 4 | `ReasonInBlacklist` | SEND | do-not-retry | active | 发送者命中频道黑名单。 | | 5 | `ReasonChannelNotExist` | SEND | application-policy | active | 频道不存在或当前不能接收该发送。 | | 6 | `ReasonUserNotOnNode` | CONNECT / delivery | refresh-route | compatibility | 目标用户不在当前节点;刷新路由后再决定是否重试。 | | 7 | `ReasonSenderOffline` | SEND | application-policy | reserved | 保留的发送者离线结果;当前产品路径未发出该值。 | | 8 | `ReasonMsgKeyError` | SEND | do-not-retry | reserved | 保留的消息密钥或完整性错误;当前产品路径未发出该值。 | | 9 | `ReasonPayloadDecodeError` | SEND | do-not-retry | active | SEND 请求(字段或载荷)格式错误或不受支持,包括解码失败。 | | 10 | `ReasonForwardSendPacketError` | SEND | retry-with-backoff | compatibility | 兼容转发路径未能转发发送包。 | | 11 | `ReasonNotAllowSend` | SEND | do-not-retry | active | 频道策略不允许该发送者发送。 | | 12 | `ReasonConnectKick` | CONNECT / disconnect | application-policy | reserved | 保留的连接踢下线结果;当前产品路径未发出该值。 | | 13 | `ReasonNotInWhitelist` | SEND | do-not-retry | active | 频道启用白名单,而发送者不在其中。 | | 14 | `ReasonQueryTokenError` | CONNECT | retry-with-backoff | reserved | 保留的 Token 查询错误;当前默认组合未发出该值。 | | 15 | `ReasonSystemError` | CONNECT / SEND | retry-with-backoff | active | 服务端压力或内部错误;使用同一幂等键进行有界退避。 | | 16 | `ReasonChannelIDError` | SEND | do-not-retry | reserved | 保留的频道标识错误;当前产品路径使用其他错误映射。 | | 17 | `ReasonNodeMatchError` | CONNECT / SEND | refresh-route | compatibility | 兼容节点匹配失败;刷新路由。 | | 18 | `ReasonNodeNotMatch` | SEND | refresh-route | active | 当前节点不是新鲜权威目标;刷新路由并复用原幂等键。 | | 19 | `ReasonBan` | CONNECT / SEND | do-not-retry | active | 用户连接或频道发送已被封禁。 | | 20 | `ReasonNotSupportHeader` | CONNECT / SEND | do-not-retry | reserved | 保留的 Header 不支持结果;当前产品路径未发出该值。 | | 21 | `ReasonClientKeyIsEmpty` | CONNECT | do-not-retry | active | 需要客户端密钥的握手未提供密钥。 | | 22 | `ReasonRateLimit` | CONNECT / SEND | retry-with-backoff | compatibility | 客户端与压测器保留的限流结果;当前默认产品路径未发出该值。 | | 23 | `ReasonNotSupportChannelType` | SEND | do-not-retry | reserved | 保留的频道类型不支持结果;当前产品路径未发出该值。 | | 24 | `ReasonDisband` | SEND | do-not-retry | active | 频道已解散,不能继续发送。 | | 25 | `ReasonSendBan` | SEND | do-not-retry | active | 发送者被禁止发送。 | | 26 | `ReasonChannelDeleting` | SEND | retry-with-backoff | reserved | 保留的频道删除中结果;当前产品路径未发出该值。 | | 27 | `ReasonProtocolUpgradeRequired` | CONNECT | upgrade-client | compatibility | 网关保留该连接失败分类;当前默认认证路径未发出该值。 | | 28 | `ReasonIdempotencyConflict` | SEND | do-not-retry | reserved | 保留的幂等冲突结果;当前产品路径未发出该值。 | | 29 | `ReasonMessageSeqExhausted` | SEND | do-not-retry | reserved | 保留的消息序号耗尽结果;当前产品路径未发出该值。 | | 30 | `ReasonSystemBusy` | SEND | retry-with-backoff | active | 服务端有界准入已满(网关发送队列或权限读准入),请求未被接受;连接保持,使用同一 ClientMsgNo 退避重试。 | # 健康与就绪 (/zh/api/operations-http/health-and-readiness) | Endpoint | 成功 | 失败与含义 | | -------------- | ------------------------------------------ | -------------------------- | | `GET /healthz` | `200 {"status":"ok"}` | 只证明 HTTP 进程可响应 | | `GET /readyz` | 默认 `200 {"ready":true}`,或 Provider 给出的就绪正文 | Provider 未就绪时返回 `503` 及其正文 | 两条路由始终注册且没有内建认证。`/healthz` 适合作为进程重启探针;只有 `/readyz` 可用于负载均衡接流量。就绪仍不证明容量、消息端到端送达、备份可恢复或高风险变更安全。 ```bash curl --fail http://127.0.0.1:5001/healthz curl --fail http://127.0.0.1:5001/readyz ``` 恢复维护期间这些入口仍可读取。保留 `/readyz` 的原始失败正文,不要用 `/healthz` 的 200 恢复业务流量。 # 运维 HTTP (/zh/api/operations-http) 运维 HTTP 与 WuKongIM HTTP API 共用 API 监听器,但面向运维系统,不是业务集成 API。 [下载 Operations HTTP OpenAPI 3.1](/contracts/operations-http.openapi.json) | Method | Path | 条件 | 稳定性 | | ------ | ------------------ | ------------------------- | ------------------- | | `GET` | `/healthz` | 始终注册 | 稳定 | | `GET` | `/readyz` | 始终注册 | 稳定 | | `ANY` | `/metrics` | 配置 Prometheus Handler 后注册 | 稳定的 Prometheus 暴露格式 | | `GET` | `/top/v1/snapshot` | 始终注册;无 Provider 时返回 404 | 不稳定 | 这四个入口没有内建认证。仅向受控网络暴露,并在反向代理、Service Mesh 或防火墙处添加 TLS、访问控制和限流。 # Prometheus 指标 (/zh/api/operations-http/metrics) `ANY /metrics` 仅在应用装配 Prometheus Handler 时注册;未装配时路由不存在并返回 404。响应是 Prometheus exposition,不是 JSON,也不属于 WuKongIM HTTP API 合同。采集器应使用 `GET`。 ```yaml scrape_configs: - job_name: wukongim static_configs: - targets: ['127.0.0.1:5001'] ``` 入口没有内建认证,恢复维护期间仍可读取。仅在监控网络暴露;跨不可信网络时由外层提供 TLS 和认证。告警应基于同一版本和代表性负载建立基线,并同时观察延迟分位数、队列、拒绝、丢弃和节点偏斜。 指标用于趋势和告警,不单独证明节点就绪或拓扑变更安全。接流量仍看 [`/readyz`](/zh/api/operations-http/health-and-readiness)。 # 只读快照与受限入口 (/zh/api/operations-http/read-only) ## Top 快照 [#top-快照] `GET /top/v1/snapshot` 返回节点本地 JSON,`version=top/v1`、`scope=local_node`。它不依赖 Prometheus,也不做集群 Fan-out。 | Query | 默认值 | 约束 | | -------- | ---------- | ------------------------------------------------------------------- | | `window` | `10s` | Go duration,最小 `2s` | | `view` | `overview` | `overview`、`runtime`、`traffic`、`channel`、`storage`、`delivery`、`all` | | `limit` | `20` | 正整数;超过 `100` 时收敛为 `100` | 无 Provider 返回 404,采集器预热返回 503,非法 Query 返回 400。入口没有内建认证,合同不稳定。 ## Debug:9 条注册 [#debug9-条注册] 全部要求 `observability.debug_api_enable=true`。配置了 `bench.api_token` 时,所有 `/debug/*` 还要求精确 Bearer;未配置时没有内建认证。 | Method | Path | 附加条件 | | ------ | ------------------------------------ | -------------------------------- | | `GET` | `/debug/config` | 需要 Config Provider | | `GET` | `/debug/cluster` | 需要 Cluster Provider | | `GET` | `/debug/diagnostics/trace/:trace_id` | 需要 Diagnostics Store | | `GET` | `/debug/diagnostics/message` | 需要 Diagnostics Store | | `GET` | `/debug/diagnostics/events` | 需要 Diagnostics Store | | `GET` | `/debug/goroutines` | 无 | | `GET` | `/debug/goroutines/summary` | 无 Registry Provider 时返回 404 | | `ANY` | `/debug/pprof` | 无 | | `ANY` | `/debug/pprof/*name` | CPU、heap、goroutine 等标准 pprof 子路由 | ## Bench:12 条注册 [#bench12-条注册] 全部要求 `bench.api_enable=true`。`bench.api_token` 非空时全部要求精确 Bearer;空值保留无认证兼容行为。恢复维护会拒绝 Bench 请求。 | Method | Path | | ------ | --------------------------------------- | | `GET` | `/bench/v1/capabilities` | | `GET` | `/bench/v1/capacity-target` | | `GET` | `/bench/v1/snapshot` | | `GET` | `/bench/v1/presence/snapshot` | | `POST` | `/bench/v1/terminal-fence/prepare` | | `GET` | `/bench/v1/channel-runtime/snapshot` | | `POST` | `/bench/v1/channel-runtime/probe` | | `POST` | `/bench/v1/channel-runtime/evict` | | `POST` | `/bench/v1/users/tokens` | | `POST` | `/bench/v1/channels` | | `POST` | `/bench/v1/channels/subscribers` | | `POST` | `/bench/v1/channels/subscribers/remove` | `terminal-fence/prepare` 还要求 Terminal Fence Controller 和非空 Token。Bench 包含数据准备、运行时驱逐和终止栅栏,不是只读接口,也不是公共集成合同。 `users/tokens` 通过用户用例持久化设备 Token,只有写入成功才计入 `accepted_users`。未接入用户用例时能力标志为 false,请求返回 501;持久化失败返回 500,已成功写入的前缀不会回滚,可用相同凭据重新 upsert。生命周期 worker 在 CONNECT 前完成此准备,并保持 Gateway Token 鉴权开启。 # 稳定性与私有边界 (/zh/api/operations-http/stability) | Surface | 受众 | 稳定性 | Operations OpenAPI | | -------------------- | ----------------- | --------------- | ----------------------- | | `/healthz`、`/readyz` | Operator | 稳定 | 已发布 | | `/metrics` | Operator | Prometheus 格式稳定 | 已发布;响应不是 JSON | | `/top/v1/snapshot` | Operator | 不稳定 | 已发布,但不承诺稳定兼容 | | `/debug/*` | Operator | 不稳定、默认关闭 | 否;只列内部接口和暴露风险 | | `/bench/v1/*` | Agent / benchmark | 不稳定、默认关闭 | 否;只列 Benchmark 私有合同 | | Manager 110 路由 | Operator | 私有运维合同 | 不与 WuKongIM HTTP API 合并 | | Node transport | Cluster internal | 不稳定兼容编号 | 绝不伪装成 HTTP/OpenAPI | | MCP / Agent CLI | Operator / Agent | 闭集工具合同 | 用工具表或 CLI 表,不用 OpenAPI | 关闭 Manager 认证时,普通读写路由不会安装权限中间件,因此多数管理写入可直接到达 Handler。只有备份写入、恢复和 MCP 管理明确失败关闭;`/mcp` 使用独立 MCP Bearer。不要把关闭认证理解为只读模式。 Manager 除 `/mcp` 外会反射调用者 Origin(无 Origin 时为 `*`),因此必须位于隔离的管理网络并在外层终止 TLS。启用认证后,Manager JWT 与各路由 `cluster.*:r|w` 权限才生效;恢复要求精确 `cluster.restore:w`,通配权限不能替代。 这些页面是源码清单,不把私有或不稳定入口升级为兼容承诺。公共业务集成继续使用 [WuKongIM HTTP API](/zh/api/product-http),客户端实时协议继续使用 [WKProto](/zh/api/client-protocols)。 # 错误响应 (/zh/api/product-http/errors) 先判断网络与 HTTP 状态,再按端点 Schema 解析响应。不要依赖统一错误字段或错误文本。 ## HTTP 处理 [#http-处理] | 结果 | 是否重试 | 处理方式 | | ------------------- | ------ | ---------------------------------------- | | `400` | 视端点而定 | 既可能是输入错误,也可能是依赖或下游失败;先按端点语义判断是否可能已产生部分写入 | | `503 maintenance` | 是,有界退避 | 等待恢复维护结束,并观察 `/readyz` | | 其他 `5xx` 或网络超时 | 条件重试 | 使用指数退避、抖动、上限和取消 | | `2xx` 但响应不符合 Schema | 否 | 停止处理并检查版本或合同漂移 | 常见错误体: ```json {"msg":"...","status":400} {"error":"maintenance","message":"restore maintenance is active"} ``` 错误文本不是稳定机器合同;客户端应根据 HTTP 状态和 Schema 分支。 ## HTTP 200 仍需判定业务结果 [#http-200-仍需判定业务结果] * `/message/send` 必须检查 `reason`;只有成功 Reason Code 才表示消息被接受。 * `/channel/messagesyncbatch` 必须逐项检查 `items[].error`,单项失败仍会使用 HTTP 200。 * `/message/syncack` 找不到当前进程保存的 generation 时返回 200,但不会确认任何消息;`last_message_seq` 不参与实际确认。 * 多个旧式删除或退出入口在目标不存在时返回 200。把它理解为“期望状态已满足”,不要当成“本次确实删除了一条记录”。 ## 部分写入与不确定结果 [#部分写入与不确定结果] `/channel`、订阅者 reset、允许/拒绝列表 set,以及 `device_flag=-1` 的设备退出都会分阶段执行。中途出现 400、5xx、连接断开或超时时,前面的阶段可能已经提交。不要盲目反向操作;保存业务侧期望版本,重新读取可观测状态,并幂等重放期望状态。 WuKongIM HTTP API 目前只提供普通允许列表的旧式完整读取,不提供普通订阅者、拒绝列表和临时订阅者的对称读回。需要严格协调时,以业务数据库为期望源,并使用受保护的 Manager 查询进行有界核对。 ## 协议边界 [#协议边界] HTTP 成功不代表 CONNECT、SENDACK、实时投递或已读成功。CONNACK 与 SENDACK 应按对应包类型和 [Reason Code](/zh/api/dictionaries/reason-codes) 单独处理;未知值应停止并报告。 ## 日志安全 [#日志安全] * 记录方法、路径、HTTP 状态和独立请求 ID。 * 不记录 Token、Authorization、Cookie、UID、消息正文或完整原始错误体。 * 对可重试错误设置次数、时长和并发上限。 # WuKongIM HTTP API (/zh/api/product-http) WuKongIM HTTP API 提供基础接入与受信管理接口。完整 OpenAPI 与当前 `internal/access/api` 注册的 48 条操作一一对应。 进入具体接口页可查看每个查询参数和 JSON 字段的类型、必填性、默认值、范围以及运行时兼容行为。 OpenAPI:[完整合同](/contracts/product-http.openapi.json) · 窄 Profile:[基础接入](/contracts/javascript-web-quickstart.openapi.json) · [消息发送](/contracts/product-http-messaging.openapi.json) · [Channel 与 Conversation](/contracts/product-http-management.openapi.json) WuKongIM HTTP API 没有通用业务鉴权。请限制网络可达性,并在 API Gateway、服务网格或业务后端完成认证、授权、限流和审计。 ## 按任务选择接口 [#按任务选择接口] | 任务 | 推荐入口 | 完成判定 | | ------ | -------------------------------------- | --------------------------------- | | 签发连接身份 | `/user/token` → `/route` → SDK CONNECT | Token 写入成功后,仍以 CONNACK 判断连接 | | 服务端发消息 | `/message/send` | HTTP 200 后继续检查 `reason` | | 断线恢复 | `/channel/messagesync` 或批量版本 | 按 `message_seq` 推进;批量逐项检查 `error` | | 协调群成员 | `/channel`、subscriber add/remove | 保存业务期望版本;失败后核对并重放期望状态 | | 同步会话目录 | `/conversation/list` | 原样回传游标,直到 `done=true` | 完整合同是 42 个运行时入口的权威参考;三个窄 Profile 是经过审阅、带可运行示例的任务投影。生成页会合并这些示例,并在“接口边界”中明确调用方、节点作用域、非原子写入和 HTTP 200 后的业务判定。 ## Channel 可观测性边界 [#channel-可观测性边界] | 状态 | WuKongIM HTTP API 读回 | | ----- | --------------------------------- | | 允许列表 | `GET /channel/whitelist`,旧式无界完整列表 | | 普通订阅者 | 无 | | 拒绝列表 | 无 | | 临时订阅者 | 无 | WuKongIM HTTP API 缺少对称读回时,不要把一次 200 当作协调证明。以业务数据库为期望状态源,需要严格核对时使用受保护的 Manager 有界查询。 ## Deprecated 的含义 [#deprecated-的含义] `deprecated` 表示兼容入口仍存在,但不应成为新集成的默认选择:`/channel/info` 改用 `/channel`;名单 set 改用 remove-all 加有界 add;`/conversation/sync` 改用 `/conversation/list`。路由批量、在线状态、Channel 解散/移除、允许列表完整读取以及命令消息 sync/bind 等兼容入口目前没有一一对应的新 WuKongIM HTTP API 替代,应仅在明确理解其页面边界后使用。节点本地 system UID cache 接口只能用于定向运维,不能替代持久变更。 ## 接口目录 [#接口目录] ## 基础接入合同 [#基础接入合同] 上方快照只证明三条黄金路径操作的示例范围,不代表完整合同已做端到端验收。客户端实时收发仍通过 SDK 和 Gateway 完成。 ## JavaScript Web 黄金路径契约 这是非完整的 v3 Beta 子集;三个调用只能出现在受信任的 localhost BFF 中,浏览器不能直接调用 WuKongIM HTTP API。 - `POST /user/token` - `GET /route` - `POST /channel/messagesync` [OpenAPI 3.1 子集](/contracts/javascript-web-quickstart.openapi.json) # 规格与下载 (/zh/api/specifications) 这里集中列出机器可读合同。它们描述当前仓库快照,不自动证明任意服务端、SDK 和合同版本兼容。 | 格式 | 适用范围 | 不适用范围 | | ------------------- | ----------------------------------- | ------------------------------------------ | | OpenAPI 3.1 | HTTP 请求、响应和 Webhook 回调 | WKProto、MCP、Node transport、Unix-socket RPC | | JSON Schema 2020-12 | 固定 EasySDK 核心路径与其余实验性 JSON-RPC 数据形状 | 通用 JSON-RPC 或平台运行兼容承诺 | | 协议页面 | 二进制布局、协商、稳定性与信任边界 | HTTP SDK 自动生成 | 业务后端从完整 WuKongIM HTTP API 合同开始;只实现一个受限场景时,可选择窄 Profile。完整 SDK 实时连接使用 [WKProto](/zh/api/client-protocols/tcp-binary);四个固定 EasySDK 的轻量在线路径可使用受支持的 [WebSocket JSON-RPC 核心合同](/zh/api/client-protocols/json-rpc)。 # JSON-RPC 实验 Schema (/zh/api/specifications/json-rpc-schema) 此 Schema 记录 WebSocket JSON-RPC 边界。当前 Product Gateway 支持固定 EasySDK 的 CONNECT、Ping 与在线双向消息;batch、订阅、离线同步和通用 JSON-RPC 仍是实验性或未实现范围。 [下载 JSON Schema 2020-12](/contracts/json-rpc.experimental.schema.json) Schema 的 `x-wukongim-stability` 为 `experimental-easysdk-core-supported`。当前行为是: * `connect` 是首个请求,并在鉴权、激活后返回关联 result/error; * `send`、`ping` 使用请求 ID 关联 SENDACK/PONG; * `recv` notification 与 `recvack` 完成在线投递确认; * `subscribe`、`unsubscribe` 会在 codec 层转换为 `SUB`,`SUBACK` 也有对应 response 映射;Product Gateway handler 仍拒绝 `SUB`,因此当前产品路径不可用; * `[` 会选择 JSON 模式,但 batch 数组未实现;请求 ID 必须是字符串,SEND Payload 可为对象、Base64 或 `null`。 Schema 中不属于 EasySDK 核心路径的结构仍不产生运行时承诺。完整矩阵、四端固定版本和证据边界见 [WebSocket JSON-RPC](/zh/api/client-protocols/json-rpc)。 # OpenAPI 合同 (/zh/api/specifications/openapi) 所有文件使用 OpenAPI 3.1。完整合同描述当前源码 Surface;窄 Profile 只是同一快照的受限采用子集,不代表完整 API。 ## 完整合同 [#完整合同] | 合同 | 范围 | 信任边界 | 下载 | | ----------------- | -------------------------------------------------- | ---------------------------- | ----------------------------------------------- | | WuKongIM HTTP API | 48 条当前业务 HTTP 操作 | 无内建业务认证;仅可信后端 | [JSON](/contracts/product-http.openapi.json) | | Operations HTTP | `/healthz`、`/readyz`、`/metrics`、`/top/v1/snapshot` | 无内建认证;仅运维网络 | [JSON](/contracts/operations-http.openapi.json) | | Webhooks | `msg.notify`、`msg.offline`、`user.onlinestatus` | 出站 POST JSON;无签名、best effort | [JSON](/contracts/webhooks.openapi.json) | Webhook 合同使用 OpenAPI `webhooks` 对象,不把回调伪装成服务端入站 `paths`。Prometheus `/metrics` 仍是 exposition format,不是普通 JSON 资源。 ## 窄 Profile [#窄-profile] | Profile | 操作数 | 用途 | 下载 | | ------------------------- | --: | --------------------------------------- | --------------------------------------------------------- | | JavaScript Web quickstart | 3 | Token、Route、断线消息同步的受控示例 | [JSON](/contracts/javascript-web-quickstart.openapi.json) | | Message sending | 1 | 普通持久化 `POST /message/send` | [JSON](/contracts/product-http-messaging.openapi.json) | | Channel & Conversation | 15 | 已复核的 10 条 Channel 与 5 条 Conversation 操作 | [JSON](/contracts/product-http-management.openapi.json) | Profile 的端到端验证状态不能外推到完整 WuKongIM HTTP API。生成 SDK 前先固定合同文件、服务端修订和外层认证策略;升级时重新执行合同与业务验收。 # 协议变更记录 (/zh/api/specifications/protocol-changelog) ## 当前快照 [#当前快照] | Surface | 当前合同 | | -------------------------------------- | ------------------------------------------ | | WuKongIM HTTP API | 48 条完整 OpenAPI;另有 3 / 1 / 15 条操作的窄 Profile | | Operations HTTP | 4 条 OpenAPI | | Webhooks | 3 个 OpenAPI Webhook | | WKProto | 最新协议版本 `6` | | WebSocket JSON-RPC | `experimental-easysdk-core-supported` | | Manager、Debug、Bench、MCP、Node transport | 私有或不稳定清单,不是公共兼容承诺 | 这是当前源码快照,不是任意历史发布版本的兼容证明。升级服务端、SDK 或合同文件后都应重新验证。 ## WKProto v5 → v6 [#wkproto-v5--v6] v6 把 `SENDACK.message_seq` 和 `RECV.message_seq` 的 Wire 宽度从 32 位扩展为 64 位;v5 及以下仍按 32 位编码。`client_seq` 的 Wire 宽度保持 32 位。字段顺序和宽度由协商后的协议版本决定,不能仅按整数大小猜测。 客户端必须通过 `CONNECT` / `CONNACK` 协商版本,使用该版本的 codec 读写整个会话。跨版本回放、代理或持久化二进制帧时,应同时保存协议版本。 ## 破坏性变更规则 [#破坏性变更规则] 以下变化视为破坏性:删除或重命名 method/path、改变状态码或既有语义、增加必填字段、收窄合法输入、改变字段类型/顺序/位宽、复用枚举或保留编号、改变认证/信任边界、Webhook Payload/成功规则/投递语义变化。 每个破坏性变更必须: 1. 提供新的合同或协议版本,不覆盖旧语义; 2. 更新 Schema、双语文档、源码漂移测试和迁移说明; 3. 标明服务端与 SDK 的支持窗口、回滚边界和验证证据; 4. 保留已退役或保留的 Frame、Reason Code、Node RPC 编号,不复用。 新增可选字段或新操作只有在旧消费者可安全忽略且信任边界不变时才可视为兼容。Experimental 和内部接口可以变化,但必须继续显式标注,不能悄然成为公共依赖。 # Webhook 事件类型 (/zh/api/webhooks/events) 异步通知运行时产生以下三个事件。`focus_events` 只用于筛选这些精确名称;空数组表示全部启用。 | 事件 | 触发边界 | 请求体 / 默认批次 | | ------------------- | ------------------------------- | ------------------------ | | `msg.notify` | 普通消息完成多数派持久提交后 | 消息数组;最多 100 条,最多等待 500ms | | `msg.offline` | 合格的持久消息存在无在线路由的接收者时 | 单个扁平对象;UID 每批最多 512 个 | | `user.onlinestatus` | UID Owner 本地活跃会话上线,或最后一个本地会话下线时 | 状态字符串数组;最多 512 条,最多等待 2s | `msg.before_send` 是独立启用的同步发送前回调,响应决定放行、修改或拒绝;以下队列和重试语义仅适用于三个异步通知事件。配置、完整请求和失败策略见[发送前业务回调](/zh/guide/integration/webhooks#发送前业务回调)。 ## `msg.notify` [#msgnotify] 用于审计、索引或异步业务通知。它位于持久提交之后;Webhook 失败不会撤销消息,也不会把已成功的 SENDACK 改为失败。 ## `msg.offline` [#msgoffline] 只有同时满足以下条件的提交才进入离线候选计算: * `message_seq > 0`; * `sync_once` 为 false; * 消息没有 `message_scoped_uids` 精确目标列表; * 接收者属于持久投递集合,且当前没有在线路由。 这是**推送候选**,不是未读消息真相,也不能代替客户端重连同步。一个接收者后来上线或另一节点状态变化时,回调仍可能已经发出。 ## `user.onlinestatus` [#useronlinestatus] 事件记录 Owner 上观察到的会话过渡。它是兼容字符串,不保证每个中间状态都到达,也不是全局有序的 Presence 日志。字段格式见[请求结构](/zh/api/webhooks/payloads)。 Plugin 的 Send/Receive/PersistAfter Hook、集群 RPC 和 benchmark `EVENT` 帧都不是 HTTP Webhook 事件。 # Webhook (/zh/api/webhooks) Webhook 把已提交消息、离线候选接收者和在线状态变化 POST 到一个业务端点。它是服务端出站协议,不是客户端收件箱,也不属于 WuKongIM HTTP API 的入站 `paths`。 [下载 OpenAPI 3.1 Webhook 合同](/contracts/webhooks.openapi.json) `msg.before_send` 是独立启用的同步发送前回调,响应决定放行、修改或拒绝;以下队列和重试语义仅适用于三个异步通知事件。配置、完整请求和失败策略见[发送前业务回调](/zh/guide/integration/webhooks#发送前业务回调)。 ## 请求 [#请求] ```text POST {http_addr}?event={event_name} Content-Type: application/json ``` 已有查询参数会保留,并覆盖或追加 `event`。事件名只出现在 Query 中;请求体**没有通用信封**,结构由事件决定。支持的回调包括: | 事件 | 顶层 JSON | | ------------------- | ------------------ | | `msg.before_send` | 单个待发送消息对象,同步决定是否发送 | | `msg.notify` | 已提交消息数组 | | `msg.offline` | 一条消息与离线 UID 的扁平对象 | | `user.onlinestatus` | 兼容状态字符串数组 | ## 配置 [#配置] `webhook.http_addr` 非空即启用异步通知;`focus_events=[]` 表示投递全部三个事件。 ```toml [webhook] http_addr = "https://events.example.com/wukongim" focus_events = ["msg.notify", "msg.offline", "user.onlinestatus"] queue_size = 1024 workers = 16 msg_notify_batch_max_items = 100 msg_notify_batch_max_wait = "500ms" online_status_batch_max_items = 512 online_status_batch_max_wait = "2s" offline_uid_batch_size = 512 request_timeout = "5s" retry_max_attempts = 3 ``` 当前使用节点内存队列,没有磁盘 Outbox 或崩溃重放。接收端应先持久化并去重,再快速返回 HTTP 200。 # Webhook 请求结构 (/zh/api/webhooks/payloads) 事件名通过 `?event=...` 传递,请求体没有公共信封。 ## `msg.before_send` [#msgbefore_send] 同步发送前回调使用单个未提交消息对象,返回放行、Payload 替换或拒绝决定。配置与失败策略见[发送前业务回调](/zh/guide/integration/webhooks#发送前业务回调);完整字段见本页下方 OpenAPI。 ## 消息对象 [#消息对象] `msg.notify` 的请求体是下列对象的数组;`msg.offline` 把同一对象直接展开到顶层。 | 字段 | JSON 类型 | 含义 | | ------------------- | -------------- | ----------------------------------------------- | | `header.no_persist` | number | 对已提交消息固定为 `0` | | `header.red_dot` | number | `0` 或 `1` | | `header.sync_once` | number | `0` 或 `1` | | `setting` | number | WKProto Setting 位掩码 | | `topic` | string,可省略 | 空值时省略 | | `expire` | number (u32) | 过期值 | | `message_id` | number (u64) | 全局消息 ID;JavaScript 可能丢失精度 | | `message_idstr` | string | `message_id` 的十进制字符串;跨语言优先使用它 | | `client_msg_no` | string | 客户端幂等标识 | | `message_seq` | number (u64) | Channel 内消息序号 | | `from_uid` | string | 发送者 UID | | `channel_id` | string | Channel ID | | `channel_type` | number (u8) | Channel Type | | `timestamp` | number (i32) | Unix 秒 | | `payload` | string \| null | 非空 Go `[]byte` 使用标准 Base64;空 Payload 编码为 `null` | ```json [ { "header": {"no_persist": 0, "red_dot": 1, "sync_once": 0}, "setting": 0, "expire": 0, "message_id": 123456789, "message_idstr": "123456789", "client_msg_no": "order-0001", "message_seq": 42, "from_uid": "system", "channel_id": "u1001", "channel_type": 1, "timestamp": 1785398400, "payload": "eyJ0eXBlIjoib3JkZXJfdXBkYXRlIn0=" } ] ``` ## `msg.offline` [#msgoffline] 除消息字段外,顶层还包含以下两种**互斥**接收者表示: | 情况 | 字段 | | ------------ | ----------------------------------------------- | | UID 数量小于压缩阈值 | `to_uids: string[]` | | UID 数量达到阈值 | `compress: "gzip"` 与 `compress_to_uids: string` | `compress_to_uids` 是“JSON 字符串数组 → gzip → 标准 Base64”。默认 `offline_uid_batch_size=512` 同时是 UID 分块上限和压缩阈值。发送节点 ID 非零时还会出现数值字段 `source_id`。 ## `user.onlinestatus` [#useronlinestatus] 请求体是字符串数组,每项格式为: ```text {uid}-{device_flag}-{online:0|1}-{session_id}-{device_online_count}-{total_online_count} ``` 计数来自 UID Owner 的本地活跃会话投影。UID 本身可能包含连字符;兼容接收端应把整串视为不透明值,或从右侧解析最后五个数字段,不能直接按 `-` 固定切片。 # Webhook 可靠性与安全 (/zh/api/webhooks/reliability-and-security) `msg.before_send` 是独立启用的同步发送前回调,响应决定放行、修改或拒绝;以下队列和重试语义仅适用于三个异步通知事件。配置、完整请求和失败策略见[发送前业务回调](/zh/guide/integration/webhooks#发送前业务回调)。 ## 投递语义 [#投递语义] 对于异步通知,只有 HTTP `200` 表示成功;其他状态码、连接错误与超时均失败。响应体会被读取后丢弃,不参与协议。 | 设置 | 默认值 | 作用域 | | -------------------------------------- | ----------: | ---------------------- | | `queue_size` | 1024 | 每类事件的内存等待容量 | | `workers` | 16 | 每类事件的并发 Sender 数 | | `msg_notify_batch_max_items` / wait | 100 / 500ms | `msg.notify` 批次 | | `online_status_batch_max_items` / wait | 512 / 2s | `user.onlinestatus` 批次 | | `offline_uid_batch_size` | 512 | 离线 UID 分块与压缩阈值 | | `request_timeout` | 5s | 每次 HTTP 尝试 | | `retry_max_attempts` | 3 | 总尝试次数,包含首次 | 重试在当前任务内立即进行,**没有退避或延迟**。连续失败在 3 次尝试后丢弃。 队列有界且只在节点内存中。队列满、进程崩溃、取消、超时或重试耗尽都可能丢事件;关停只在给定 Context 内排空。响应丢失会产生重复,多个 Worker/批次之间没有公共全局顺序。Webhook 失败不会回滚持久提交或成功 SENDACK。 ## 接收端合同 [#接收端合同] 关键业务状态必须能从业务数据库或消息历史重建。 ## 安全边界 [#安全边界] 当前 Sender 只添加 `Content-Type: application/json`,**没有签名、共享密钥或 Authorization Header**。Webhook URL 本身也不应携带会进入日志的长期秘密。 生产环境必须在协议外建立信任: * 仅使用 HTTPS;优先私网、服务网格或固定出口代理; * 在反向代理层使用 mTLS、固定出口身份或受控凭据; * 对来源 IP、速率和请求体大小设限; * 避免记录完整 Payload 与个人数据; * 不把回调暴露为匿名公网写入口。 # 频道 (/zh/guide/core-concepts/channels) 频道(Channel)是消息的“地址”。完整身份是 **`channel_id + channel_type`**;同一个 ID 搭配不同类型,属于不同频道。 ## 为什么频道重要 [#为什么频道重要] 发送消息先选频道。它确定接收范围,普通持久消息在各自频道内编号、排序和恢复。通知、客服、直播和 Agent 也可以复用这个入口。 ## 与其他概念的关系 [#与其他概念的关系]
设备不改变频道身份;不同用户看到的会话未读和可见状态可以不同。 ## 单聊:发给对方的 UID [#单聊发给对方的-uid]
调用方始终使用**对方 UID**。WuKongIM 在内部归一双方视角;应用不需要构造内部频道 ID,也不要把它回写为好友 ID。 ## 群聊:发给稳定的群 ID [#群聊发给稳定的群-id] | 你要做什么 | 谁负责 | | ---------- | -------------------- | | 选择频道 ID | 业务系统提供稳定的群 ID | | 创建群、邀请或退群 | 业务系统决定,再由受信后端同步成员与策略 | | 保存、排序和投递消息 | WuKongIM | **知道群 ID 不代表有发送权限。** Channel Type 也不是权限;成员、角色、黑白名单和内容策略仍由业务系统定义。
群资料、其他频道类型与大群处理 群名称、头像、公告、角色、租户隔离和审核归业务系统管理。WuKongIM 保存同步后的频道成员与策略,提供消息持久化、实时投递和离线恢复。 单聊的好友、陌生人和发送策略也由业务系统维护。社区、资讯、直播、访客、Agent 等专用类型,只有在服务端、所选 SDK 与业务流程都支持时才使用,取值见 [Channel Type](/zh/api/dictionaries/channel-types)。 十万成员群需要分页与有界的成员变更、在线投递,见[大群教程](/zh/guide/tutorials/large-groups)。
继续阅读[用户](/zh/guide/core-concepts/users)和[会话](/zh/guide/core-concepts/conversations)。频道副本、提交与迁移见 [Channel 消息层](/zh/server/architecture/channels)。 # 会话 (/zh/guide/core-concepts/conversations) 会话(Conversation)是**某个用户聊天列表中的一行**:指向频道,展示最近消息、未读数和个人状态。消息保存在频道中。 ## 为什么会话重要 [#为什么会话重要] 用户通过会话找到聊天、看到新消息提示,也能隐藏聊天或清除未读。离线恢复时,客户端先同步会话列表,再分别补齐各频道的消息。 ## 与其他概念的关系 [#与其他概念的关系]
这是两位成员的示例状态:**同一频道,个人已读位置和可见范围不同**。同一 UID 的设备同步会话状态,各自保留本地 UI 状态。 ## 频道与会话怎么区分 [#频道与会话怎么区分] | 频道 | 会话 | | ------------------------------- | ---------------- | | 参与者共享的通信空间与有序历史 | 当前用户对频道的个人视图 | | 身份是 `channel_id + channel_type` | 还要结合当前 UID | | 保存已提交消息 | 引用最近消息,呈现未读和可见状态 | ## 常见操作 [#常见操作] | 操作 | 结果 | | ------- | ----------------------------------------- | | 清除未读 | 推进当前 UID 的未读基线;不等于向对方发送已读回执 | | 隐藏或删除会话 | 隐藏自己的当前视图;不退群、不删除其他成员的消息。更高序号的新消息可让会话再次出现 | | 退出或被移除 | 改变频道成员关系 | | 多设备同步 | 同一 UID 的会话和未读更新,在其他设备下一次同步时可见 | 置顶、草稿、分组和最终 UI 排序由客户端维护。
接入时:列表字段、排序与同步游标 会话通常包含频道 ID 与类型、最近消息、未读数、已读和可见边界。客户端用它们决定打开哪个聊天、显示什么摘要和从哪里计算未读。 服务端按 `activated_at` 排列活动会话目录。这是用户显式激活频道的时间,**不是最新消息时间**。未激活且没有可见消息的空频道不出现在列表中;客户端拿到最近消息后决定最终展示顺序。 **会话列表和每个频道的消息列表有各自的同步进度**,不能共用一个游标。WuKongIM 根据用户的频道关系、已提交消息和个人可见状态构建会话视图。
继续阅读[消息](/zh/guide/core-concepts/messages),再选择[完整版 SDK 平台](/zh/sdk/wukongim)查看会话 API。服务端构建与分页见[消息流转架构](/zh/server/architecture/message-flow)。 # 设备 (/zh/guide/core-concepts/devices) 设备(Device)是用户接入 WuKongIM 的具体客户端端点,例如手机 App、浏览器或桌面应用。同一个用户可以拥有多个设备,也可以在多个设备上同时使用消息服务。 设备不是用户本身,也不等于一次网络连接。网络切换或重连会产生新的连接,但用户和设备身份通常保持不变。 ## 为什么设备重要 [#为什么设备重要] * **支持多端使用**:一个 UID 可以同时在手机、Web 和 PC 上收发消息。 * **区分同步范围**:普通持久消息和会话通常按 UID 共享,草稿、下载进度和推送 Token 则属于具体设备。 * **控制在线策略**:业务可以决定同类设备允许多端共存,还是新连接替换旧连接。 * **处理退出与撤销**:移除设备不仅要断开当前连接,还要撤销凭据和清理业务侧设备记录。 ## 与其他概念的关系 [#与其他概念的关系] * **用户**可以拥有多个设备,所有设备共享同一个 UID。 * 普通持久**消息**从一个具体连接发出,并可由用户的其他活动设备接收或稍后同步。 * **频道**和持久消息历史不属于某台设备,更换设备后仍可按权限恢复。 * **会话**通常按 UID 共享,但最终展示和本地缓存由各设备维护。 ## 四个容易混淆的名词 [#四个容易混淆的名词] | 名词 | 表示什么 | 示例 | | -------------------- | ------------------------ | ----------------- | | UID | 业务用户身份 | `alice_123` | | Device Flag | 客户端类别 | APP、WEB、PC | | Device ID | 某个具体安装或端点 | `alice_iphone_01` | | Connection / Session | 当前的一次 TCP 或 WebSocket 连接 | 断网重连后会变化 | Device Level 用于描述同一 UID、同一设备类别下的连接冲突策略,例如允许多个 Web 端共存或保持单活。它不是管理员、会员或群角色等业务权限。 完整取值和当前生效边界见 [Device Flag](/zh/api/dictionaries/device-flags)。 ## 哪些状态会跨设备共享 [#哪些状态会跨设备共享] | 通常按 UID 共享 | 通常由具体设备保存 | | ------------- | -------------------- | | 普通持久频道消息和消息顺序 | 本地草稿、滚动位置和下载进度 | | 会话、未读基线和隐藏范围 | 厂商推送 Token、通知权限和后台限制 | | 业务后端决定的账号与群关系 | 本地缓存、加密材料和设备风险信息 | 一个设备清除未读后,其他设备下一次同步可能看到新的未读状态。普通持久消息也可以投递到多个活动连接;每个设备仍需独立处理接收、去重和本地存储。 ## 设备的一次典型使用过程 [#设备的一次典型使用过程] 客户端不能自行选择更高的 Device Level。默认 Gateway 只有在 CONNECT Token 与已存设备记录匹配后,才采用该记录的 Device Level;因此只有受信后端可以写 Token 管理接口。完整的过期、重放与风险策略仍应纳入[上线检查](/zh/guide/integration/acceptance)。 继续阅读[会话](/zh/guide/core-concepts/conversations)和[认证与安全](/zh/guide/integration/authentication)。 # 核心概念 (/zh/guide/core-concepts) 刚接触 WuKongIM 时,不需要先理解节点、Raft 或数据分片。先记住下面五个业务概念,就能看懂大多数接入文档: | 概念 | 它回答的问题 | 一句话理解 | | ---------------- | ------------ | --------------------------- | | 消息(Message) | 传递什么? | 用户发出的一份内容,例如文字、图片、通知或业务指令 | | 频道(Channel) | 发到哪里? | 消息的目标和接收范围,可以表示单聊、群聊或其他通信场景 | | 用户(User) | 谁在通信? | 由业务系统定义、用稳定 UID 标识的通信身份 | | 设备(Device) | 从哪个端点通信? | 用户使用的手机、浏览器、桌面端或其他客户端实例 | | 会话(Conversation) | 用户如何找到最近的聊天? | 某个用户视角下的频道入口,通常显示最近消息和未读数 | ## 五个概念如何配合 [#五个概念如何配合] 以 Alice 给 Bob 发消息为例: 如果换成群聊,变化的主要是频道:频道 ID 变成群 ID,接收范围变成群成员。用户、设备、消息和会话的基本关系不变。 ## WuKongIM 与业务系统的分工 [#wukongim-与业务系统的分工] WuKongIM 是通信基础设施,不替代你的账号和业务系统。 | 你的业务系统负责 | WuKongIM 负责 | | ------------------- | ------------------- | | 注册登录、昵称头像、好友与群资料 | 客户端连接、消息路由和实时投递 | | 群成员、角色权限、内容审核 | 频道内消息顺序、持久化和离线同步 | | 业务 Token 签发、风控、移动推送 | 设备在线连接和面向 UID 的会话数据 | 核心概念只解释产品语义。需要了解节点、Slot、Leader、副本和故障转移时,请进入[服务端架构](/zh/server/architecture)。 ## 深入每个概念 [#深入每个概念] 理解这些概念后,可以直接运行[快速开始](/zh/guide/quick-start),完成第一条消息的收发。 # 消息 (/zh/guide/core-concepts/messages) 消息(Message)是 WuKongIM 传递信息的基本单位。文字、图片、文件、通知、音视频信令或 IoT 指令,都可以作为消息内容发送。 普通持久消息会进入目标频道,可实时投递并供离线设备稍后同步。消息里的业务含义和展示方式由你的应用定义;特殊瞬时或命令分支具有不同边界。 ## 为什么消息重要 [#为什么消息重要] * **承载业务内容**:同一条消息通道可以承载聊天内容,也可以承载自定义业务事件。 * **支持实时与离线场景**:普通持久消息可实时投递,并在离线或重连后从已提交的频道历史中恢复。 * **可以追踪与去重**:客户端发送编号、服务端消息 ID 和频道内序号分别帮助重试、查询和排序。 ## 与其他概念的关系 [#与其他概念的关系] * **频道**决定消息发到哪里、谁有机会接收,以及消息在哪个范围内排序。 * **用户**是消息的发送者或频道参与者。 * **设备**是用户发送、实时接收和离线恢复消息的具体端点。 * **会话**把频道中的最近消息和未读状态呈现给某个用户。 ## 一条消息包含什么 [#一条消息包含什么] 从应用开发者的角度,可以把一条消息理解为“谁,把什么,发到哪里”: | 信息 | 作用 | 常见字段 | | -------- | ---------------- | --------------------------- | | 发送者 | 标识消息来自哪个用户 | `from_uid` | | 目标 | 指定消息所属的频道 | `channel_id`、`channel_type` | | 内容 | 保存文字、图片信息或自定义数据 | `payload` | | 客户端发送编号 | 标识一次逻辑发送,重试时用于幂等 | `client_msg_no` | | 服务端身份与位置 | 用于查询、去重和频道内排序 | message ID、`message_seq` | 例如,应用可以把一条文本消息的 Payload 定义为: ```json { "type": 1, "content": "周六上午 9 点集合" } ``` `type` 的取值和其他字段由业务与客户端共同约定。建议为自定义 Payload 设计版本和兼容策略。 ## 一条持久消息经历什么 [#一条持久消息经历什么] 发送确认表示这次发送已经达到对应的服务端处理边界。它不表示每台设备都已收到,更不表示最终用户已经阅读。送达回执、已读回执和业务执行结果应分别设计。 ## 三个容易混淆的编号 [#三个容易混淆的编号] | 编号 | 谁生成 | 你通常用它做什么 | | --------------- | ------ | ---------------------- | | `client_msg_no` | 发送方 | 对同一次发送进行幂等重试;重试时保持不变 | | message ID | 服务端 | 标识服务端接受的一条消息,用于结果关联和诊断 | | `message_seq` | 频道消息日志 | 表示消息在当前频道中的位置和顺序 | 消息顺序只在同一个频道内有意义。不要用两个不同频道的 `message_seq` 推断全局先后关系。 ## 开发时记住 [#开发时记住] * 必须可靠到达或需要离线恢复的内容,应使用普通持久消息。 * 重试同一次发送时复用原 `client_msg_no`,不要生成一条新的业务消息。 * Payload 是业务合同,需要限制大小、校验字段并做好版本兼容。 * `NoPersist`、`SyncOnce` 等特殊行为不要仅凭名称猜测,使用前查看 [Message Flags](/zh/api/dictionaries/message-flags)。 继续阅读[频道](/zh/guide/core-concepts/channels)。开始接入时查看[消息收发](/zh/guide/integration/messaging);需要理解提交、副本和投递链路时查看[消息发送链路](/zh/server/architecture/message-flow)。 # 用户 (/zh/guide/core-concepts/users) 用户(User)是 WuKongIM 中发送和接收消息的身份,用 UID 唯一标识。它可以代表真实用户,也可以代表客服、机器人、Agent 或其他受信服务身份。 UID 来自你的业务系统。WuKongIM 使用它进行连接、路由、设备关联和消息投递,但不会替代注册登录、用户资料或业务权限系统。 ## 为什么用户重要 [#为什么用户重要] * **统一通信身份**:同一个 UID 在手机、Web 和桌面端上仍代表同一个业务用户。 * **连接其他概念**:消息有发送者,设备属于用户,会话也从某个用户的视角生成。 * **承载业务边界**:账号封禁、租户隔离、好友关系和群角色最终都要以可信用户身份为基础。 ## 与其他概念的关系 [#与其他概念的关系] * **消息**记录发送者 UID,并通过频道到达其他用户。 * **频道**组织用户之间的通信范围;单聊目标通常就是对方 UID。 * **设备**属于某个 UID,一个用户可以拥有多个设备。 * **会话**属于用户视角,因此同一频道对不同用户可以有不同未读和可见状态。 ## UID 应该怎么设计 [#uid-应该怎么设计] 一个合适的 UID 应当: * 在业务账号生命周期内保持稳定,并且不会分配给另一个用户; * 由受信后端确定,不能让客户端任意选择他人的 UID; * 不直接使用昵称、手机号或可变邮箱等展示字段; * 在日志和指标中避免泄露不必要的个人信息。 例如,Alice 修改昵称或更换手机后,UID 仍然是 `alice_123`。这样历史消息、频道成员关系和会话都不会因为展示资料变化而断开。 ## WuKongIM 知道什么 [#wukongim-知道什么] | WuKongIM 需要知道 | 仍由业务系统保存和判断 | | ------------------- | -------------------- | | 稳定 UID、连接所需设备信息 | 密码、OAuth 身份、手机号和用户资料 | | 与 UID 关联的消息、频道和会话状态 | 好友关系、群角色、订阅套餐和内容权限 | | 当前在线连接的瞬时状态 | 可靠的登录历史、风控结论和账号状态 | 在线状态只是“此刻是否观察到活动连接”,不适合作为付款、授权或一次性业务动作的唯一依据。 ## 用户如何接入 [#用户如何接入] 1. 客户端先登录你的业务服务。 2. 业务后端验证账号和权限,并映射到稳定 UID。 3. 受信后端签发当前设备所需的连接材料。 4. 客户端使用 UID、设备信息和 Token 连接 WuKongIM。 5. 连接成功后,客户端才能以该 UID 收发消息和同步会话。 当前 v3 Beta 默认组合会把 CONNECT Token 与相同 UID、设备类别的已存 Token 精确匹配,但这不是完整的账号与权限系统。客户端不应直接访问 WuKongIM HTTP API;生产环境必须让受信后端控制签发、过期、轮换和撤销,并保护 Token 管理接口。 继续阅读[设备](/zh/guide/core-concepts/devices)和[身份与 Token](/zh/guide/integration/authentication)。需要理解跨节点在线路由时查看[用户连接路由](/zh/server/architecture/user-routing)。 # 上线检查 (/zh/guide/integration/acceptance) 目标:用真实客户端、服务端配置和业务接口,证明上线后的消息闭环可用,关键失败场景能恢复。 ## 1. 固定本次版本 [#1-固定本次版本] 记录 SDK 精确版本与锁文件、服务端版本和配置、客户端设备与网络范围。登录、Token、历史、会话、媒体接口,以及数据变化与回滚方式一并记录;依赖、配置和锁文件作为同一变更单元发布。 ## 2. 跑通六步消息闭环 [#2-跑通六步消息闭环] 使用 Alice、Bob 两个测试账号和独立客户端。保存各步骤的截图或脱敏结果,记录预期与实际值。
发送结果、在线接收和离线同步是**不同事件**,分别检查,不能用发送成功代替后两者。 ## 3. 按 SDK 验证断网恢复 [#3-按-sdk-验证断网恢复] | 接入方式 | 第 ⑤ 步验收 | | ---------------- | ------------------------------------------ | | 完整版 SDK | Bob 断网后 Alice 再发一条;恢复后补齐持久消息,核对顺序、去重、会话与未读 | | EasySDK 在线接入 | 验证断开、重连与错误回调,以及监听解绑、实例销毁;不把它当作自动离线同步 | | EasySDK + 业务历史能力 | 额外验证业务后端的历史补拉、去重、会话与未读,业务未实现时不能承诺这些能力 | 普通与命令式 NoPersist 没有持久历史,不用于离线恢复验收。 ## 4. 满足发布条件 [#4-满足发布条件] | 必查项 | 合格标准 | | ----- | ---------------------------------------------------- | | 身份与网络 | 无效、撤销和错误 UID Token 被拒绝;业务过期策略有效;使用 TLS/WSS,客户端没有管理凭据 | | 消息与连接 | 发送、失败、超时、重试、重复、切网和重启都有正确结果与 UI 状态 | | 容量与诊断 | 达到目标在线数、消息速率和群规模;队列、重试与降级有上限,指标和脱敏日志可定位问题 | | 数据与回滚 | 账号隔离、升级与恢复已演练;回滚使用兼容数据或完整旧代备份 |
按业务功能补充检查 | 已使用的功能 | 检查内容 | | ------ | ------------------------------------------------ | | 多端与会话 | 最近消息、未读、删除和多设备状态收敛;前后台、被踢、Token 失效有明确状态 | | 媒体 | 上传失败、取消、超时、URL 权限和文件清理 | | 本地持久化 | 备份、加密、退出清理和升级迁移 | | 运维接口 | WuKongIM HTTP API、Manager、诊断接口位于受保护的管理边界,不直接暴露公网 | Token 过期需业务后端轮换或撤销;当前 CONNECT 不会自动检查 Token 字符串里的有效期。验证你实际部署的机制,而不是只检查客户端计时。
身份或网络边界未完成,日志暴露 Token / 完整消息,关键失败不能恢复,容量未达标或缺少限流降级,数据不可回滚且没有评审后的前滚修复方案。 通过后先小流量发布,观察连接成功率、发送失败率、延迟、同步错误、队列与存储错误,再扩大范围。 下一步:[健康与监控](/zh/server/operations/health-and-monitoring)与[升级迁移](/zh/server/operations/upgrade-and-migration)。安全边界见[认证与安全](/zh/api/authentication)。 # 集成架构 (/zh/guide/integration/architecture) 一个可靠的接入把业务控制面与消息数据面分开:业务服务决定“谁可以做什么”,WuKongIM 集群负责“消息如何实时、有序地传递和保存”。 ## 职责划分 [#职责划分] | 组件 | 负责 | 不负责 | | ----------- | ------------------------------------ | -------------- | | 终端应用 | 用户交互、SDK 生命周期、本地状态、消息展示 | 签发可信身份、决定服务端权限 | | 客户端 SDK | 路由、连接、协议帧、重连、收发与确认 | 业务账号、群权限、内容审核 | | 业务服务 | 登录、UID、Token、关系、群成员、内容策略、Webhook 副作用 | 长连接和频道日志复制 | | WuKongIM 集群 | Gateway、频道顺序、持久化、路由、在线投递、集群故障转移 | 业务数据库和最终业务规则 | 所有部署都是集群。单节点部署只是只有一个节点的单节点集群,不存在绕过 Controller、Slot、Channel 或路由语义的独立路径。 ## 会话启动顺序 [#会话启动顺序] 连接状态不能替代业务授权状态。 当前 `/route` 返回配置好的接入地址,不会校验 UID 的业务权限。不要把能够获取路由解释为用户已被授权。 ## 两条消息入口 [#两条消息入口] * **客户端入口**:SDK 通过长连接发送消息,接收 SENDACK、RECV 和连接状态事件。 * **服务端入口**:受信业务服务调用 `/message/send` 发送系统消息或代表业务触发消息。 两条入口最终进入同一个消息用例和频道写入路径。业务服务不应直接写节点本地存储,也不应根据“单节点”假设绕过集群路由。 ## 故障边界 [#故障边界] * 对默认持久化消息,SENDACK 成功表示频道权威写入已作出持久化成功决定。 * 普通和命令式 `NoPersist` 均进入瞬时在线投递,分配消息 ID,序号为零;两者都没有持久历史或离线恢复,详见 [Message Flags](/zh/api/dictionaries/message-flags)。 * 在线投递、会话活跃投影、Webhook 和插件属于提交后的独立副作用。 * Webhook 失败不会把已经成功的 SENDACK 改成失败。 * 客户端断线恢复依赖 SDK 的重连与消息同步,不依赖 Webhook 重放。 需要理解服务端内部的提交与投递边界时,阅读[服务端架构](/zh/server/architecture)和[消息发送链路](/zh/server/architecture/message-flow)。下一步阅读[身份认证](/zh/guide/integration/authentication),先收紧信任边界,再实现消息收发。 # 身份认证 (/zh/guide/integration/authentication) 目标:用户登录你的业务后,SDK 使用业务后端提供的连接材料接入 WuKongIM。 ## 1. 先登录你的业务 [#1-先登录你的业务] 业务后端验证账号、租户、设备与风险策略。UID 使用稳定、不可复用的用户标识;昵称不能作为 UID。 ## 2. 后端准备连接材料 [#2-后端准备连接材料] 后端生成高熵 Token,通过受保护的 WuKongIM HTTP API 保存,再查询路由。只向当前用户返回 `uid`、设备参数、Token 和 WebSocket 地址。
**只有受信后端调用 Token 管理与路由接口。** WuKongIM HTTP API 没有通用业务鉴权;CORS 允许浏览器访问,不代表获得授权。
保存 Token:请求与响应 后端调用 [`POST /user/token`](/zh/api/product-http/users/setQuickstartUserToken): ```json { "uid": "u1001", "token": "replace-with-a-random-secret", "device_flag": 0, "device_level": 1 } ``` 成功响应 `{"status": 200}` 表示 Token 已保存,不代表旧连接已关闭。
## 3. SDK 连接 Gateway [#3-sdk-连接-gateway] SDK 使用同一 UID、`device_flag` 和 Token 发起 CONNECT。默认 `gateway.token_auth_on=true`:空 Token、缺失设备记录或 Token 不匹配都会被拒绝,返回 `ReasonAuthFail`。 生产入口使用 HTTPS/WSS。Token 不进入 URL、日志、分析事件或不受保护的客户端存储。 **Token 有效期由业务后端管理。** 当前接口没有过期时间字段,CONNECT 校验保存的 Token;到期时需轮换或撤销,不能只依赖客户端计时。
设备字段与连接边界 | 字段 | 用法 | | -------------- | -------------------------------------------- | | `device_flag` | 平台类别:APP `0`、WEB `1`、PC `2`;后端保存与 SDK 连接保持一致 | | `device_id` | 具体安装或终端;重装策略由业务决定 | | `device_level` | 主设备 / 从设备策略由后端决定,客户端不能自行提升 | WKProto 连接先发送 CONNECT,Gateway 保存身份和协商的协议版本,成功后激活在线路由。默认会话加密不替代业务鉴权或 TLS;`gateway.token_auth_on=false` 仅用于受控兼容迁移。 WuKongIM HTTP API 放在私网或服务网格,由代理验证服务身份。Token、发送、成员变更分别授权;凭据接口限流、启用 TLS,审计调用方、请求 ID 和结果,不记录明文 Token。
退出、撤销与轮换 退出时客户端销毁连接,业务后端调用 [`POST /user/device_quit`](/zh/api/product-http/users/quitUserDevice): ```json { "uid": "u1001", "device_flag": 0 } ``` 此操作清除对应设备类别的持久 Token,后续 CONNECT 会失败,并安排处理请求的节点关闭匹配连接。生产需实测连接关闭与重连拒绝,并同步业务身份系统的撤销、轮换和审计状态。 Token 是精确匹配的凭据,不会因为字符串里包含 JWT `exp` 就自动过期。上线前验证无效、撤销、错误 UID 与业务过期策略。
下一步:[消息收发](/zh/guide/integration/messaging)。 # 集成指南 (/zh/guide/integration) WuKongIM 负责实时连接、频道消息、持久化、集群路由和在线投递;你的业务服务仍然负责账号、权限、业务关系、内容治理和最终业务动作。 当前 v3 Beta 应用默认会完成 CONNECT 协议协商、会话加密以及已存设备 Token 的精确校验。WuKongIM HTTP API 接口仍不包含业务鉴权中间件,内置 Token 记录也没有过期时间或重放策略。生产接入必须把这些接口放在受信网络或经过鉴权的反向代理之后,并由业务身份系统负责凭据签发、轮换与撤销。 ## 推荐集成路径 [#推荐集成路径] 选择平台 API 前,先通过 [WuKongIMSDK 核心概念](/zh/sdk/wukongim/concepts)理解共同词汇,再进入 [WuKongIMSDK](/zh/sdk/wukongim) 选择平台并按任务顺序接入。 ## 最小业务闭环 [#最小业务闭环] 1. 用户先登录你的业务服务,而不是直接向 WuKongIM 证明业务身份。 2. 业务服务为会话分配稳定 UID、设备标识和短生命周期凭据。 3. 客户端获取路由并通过 SDK 或协议实现连接 Gateway。 4. 客户端或受信业务服务发送消息,并检查协议级结果。 5. 业务服务通过 Webhook 接收异步事件,把副作用写入自己的可靠队列。 6. 断线后由客户端 SDK 重连并恢复消息,不把 Webhook 当作客户端收件箱。 ## 上线前检查 [#上线前检查] | 领域 | 必须由你确认 | | -- | ------------------------------------- | | 身份 | UID 来源唯一,Token 不由不可信客户端自行签发 | | 网络 | HTTP 产品接口不直接暴露公网,Gateway 使用受控入口 | | 权限 | 好友、群成员、黑白名单和内容策略由业务规则驱动 | | 消息 | 业务定义 Payload 版本,发送方保存 `client_msg_no` | | 回调 | 接收端先持久化再返回 200,处理逻辑可重复执行 | | 运维 | 监控队列压力、失败、重试耗尽和端到端延迟 | 优先用稳定的服务端接口与 Webhook 建立边界清晰的集成;只有确实需要节点附近的同步或提交后 Hook 时,再引入[插件扩展](/zh/guide/integration/plugins)。 实现完成后,用真实客户端、服务端配置、网络环境和回滚方案执行[上线检查](/zh/guide/integration/acceptance)。 # 消息收发 (/zh/guide/integration/messaging) 目标:Alice 发出消息,确认服务端结果,再在 Bob 的独立客户端验证接收。 ## 1. 发送前确认目标 [#1-发送前确认目标] 两端都已连接。单聊目标是**对方 UID**;群聊使用稳定群 ID 和正确 Channel Type,成员与发送权限由业务后端维护。 ## 2. 分别核对发送与接收 [#2-分别核对发送与接收]
| 观察到什么 | 能证明什么 | | ------------------ | -------------- | | 默认持久消息的 SENDACK 成功 | 服务端已经持久化提交 | | Bob 收到消息 | 在线投递到 Bob | | Bob 已读或处理 | 需要产品自己的回执或业务结果 | **SENDACK 不代表对方已读,HTTP 200 也不代表协议成功。** 检查 SENDACK 或 HTTP 的 `reason`;EasySDK 按所选版本的发送结果处理,接口见[各平台教程](/zh/sdk/easy)。 ① Bob 收到 Alice 的消息。② Bob 回发。截图验证实际双向收发,不提供对方已读证明。 ## 3. 处理失败与恢复 [#3-处理失败与恢复] | 情况 | 做法 | | -------- | ------------------------------------------------------------ | | 明确拒绝 | 按 [Reason Code](/zh/api/dictionaries/reason-codes)修正身份、权限或参数 | | 超时或结果不确定 | 保留原消息标识,先核对结果,再按 SDK 支持的重试方式处理 | | 断网或重连 | 完整版 SDK 同步缺失的持久消息;EasySDK 的历史与会话补齐需业务另行实现 | `message_id` 是服务端消息标识,`message_seq` 只表示**一个频道内的顺序**,不是全局顺序。`client_msg_no` 由发送方生成并保存,用于关联请求和重试。
业务后端发送:HTTP 示例 仅受信后端调用 [`POST /message/send`](/zh/api/product-http/message-send/sendChannelMessage),不把此接口直接交给浏览器或移动端。WuKongIM HTTP API 没有通用业务鉴权,应通过私网或受保护代理访问;`payload` 使用 Base64。 ```bash curl -sS http://127.0.0.1:5001/message/send \ -H 'Content-Type: application/json' \ -d '{ "from_uid": "system", "channel_id": "u1001", "channel_type": 1, "client_msg_no": "order-20260730-0001", "payload": "eyJ0eXBlIjoib3JkZXJfdXBkYXRlIiwidGV4dCI6IlNoaXBwZWQifQ==" }' ``` 成功提交的响应示例: ```json { "message_id": 123456789, "message_seq": 42, "reason": 1 } ``` HTTP 200 时仍检查 `reason`,区分成功、可重试、拒绝与路由变化。
Payload:版本与安全降级 WuKongIM 传递字节,业务系统定义消息结构。使用带版本的信封: ```json { "version": 1, "type": "order_update", "body": { "order_id": "o-10001", "status": "shipped" } } ``` 忽略未知字段,为未知 `type` 提供安全降级;不要放入服务端密钥、访问 Token 或不必要的个人数据。
持久化、副作用与 NoPersist 客户端长连接和服务端 HTTP 都进入同一个消息用例与频道写入路径。默认持久消息提交后,在线投递、会话相关更新、Webhook 入队与发送、插件 Hook 可以独立执行;失败不会撤销已持久化的消息,也不延长已完成的确认。 强业务一致性由业务服务用自己的数据库和幂等键编排;Webhook 与 SENDACK 不是同一事务。 普通与命令式 `NoPersist` 都是瞬时在线投递:有消息 ID,序号为零,没有持久历史与离线恢复,见 [Message Flags](/zh/api/dictionaries/message-flags)。
完整版 SDK 的恢复检查 保存频道同步进度,断线后重新发现路由并 CONNECT,补齐缺失持久消息,按消息标识与频道序号合并去重,并正确处理 RECVACK。会话目录与频道消息使用不同游标。具体连接状态、退避和同步 API 见[完整版 SDK](/zh/sdk/wukongim)。 EasySDK 不自动提供这套历史、会话与未读恢复;业务需要时通过自己的后端补齐。
继续配置 [Webhook](/zh/guide/integration/webhooks),最后完成[上线检查](/zh/guide/integration/acceptance)。 # 插件扩展 (/zh/guide/integration/plugins) WuKongIM v3 Beta 支持以本地 `.wkp` 进程扩展消息路径和 Host RPC。插件适合需要在 WuKongIM 节点附近执行的受控扩展,但不是业务服务、可靠队列或集群级部署编排的替代品。 插件进程、配置和启停 desired/observed 状态属于每个节点。启用一个节点的插件不会自动证明所有节点已经安装、配置或运行同一版本。 ## 何时使用插件 [#何时使用插件] | 需求 | 推荐边界 | | -------------------------- | ------------------------------ | | 在 SENDACK 前同步检查或修改 Payload | Send Hook;保持链路短,并接受它影响发送延迟与可用性 | | 观察特定离线接收者的已提交消息 | Receive Hook;提交后、按 UID 绑定、尽力而为 | | 观察每次 durable commit | PersistAfter;提交后、尽力而为 | | 向业务系统交付普通异步事件 | 优先使用 Webhook 或业务消息队列边界 | | 复杂业务编排、外部事务或长任务 | 放在独立业务服务,不放入同步 Send Hook | ## 生命周期 [#生命周期] Manager 更新配置时会保留被 `SecretHidden` 占位的已有 secret,返回详情时对 secret 脱敏。运行中且声明 `ConfigUpdate` 的插件会收到同步配置更新调用。Uninstall 先写 disabled desired state,再停止并移除本地进程。 ## 三类 Hook [#三类-hook] | Hook | 时机 | 失败语义 | | ------------ | -------------------------------- | ------------------------------------------------- | | Send | 权限成功后、Channel append admission 前 | 同步;默认 fail-closed,可显式配置 fail-open;可修改 Payload 或拒绝 | | Receive | durable commit 后,针对离线 UID batch | 独立接收者失败不改变 SENDACK、在线投递或 membership 状态 | | PersistAfter | durable commit 后 | 失败只进入 worker 日志/指标,不回滚消息 | 候选插件按 priority 降序、plugin number 升序稳定选择。Send 响应只能替换 Payload,不能篡改发送者、Channel、Session 或路由字段。插件来源的发送仍进入消息用例、权限检查、递归 guard 和 Channel authority。 ## UID 绑定 [#uid-绑定] UID 到 plugin number 的绑定是 Slot-authoritative 元数据,以 UID 作为物理哈希槽路由键。绑定只表达集群关系,不保存某节点上的插件进程、配置或运行状态。Receive 执行时会把绑定与当前节点 running/enabled/method candidates 交集,选择最高优先级插件。 这意味着“绑定存在”不等于“所有节点都能执行”。上线前要验证目标 UID 可能落到的执行节点都具有兼容插件。 ## Host RPC [#host-rpc] 当前兼容能力包括: * 通过普通消息用例发送消息; * 读取显式 Channel 的已提交消息; * 读取集群节点和物理哈希槽快照、解析 Channel owner; * 读取 UID 的会话 Channel; * 在本地或指定节点转发有界 HTTP 请求到插件 route。 Host RPC 仍遵守 body、header、query、timeout 和目标验证。`toNodeId=-1` 的 fanout 当前明确未实现,不会偷偷执行部分节点广播。 ## 业务 HTTP 路由 [#业务-http-路由] 业务后端可在 WuKongIM HTTP API 监听器调用 `ANY /plugins/:plugin_no/*path`,例如 `POST /plugins/wk.plugin.search/search`。服务端将 HTTP 方法、路径、请求体及每项首个 header/query 值传给该节点上已连接的插件 `Route` 方法,并返回插件的状态码、响应体和端到端响应头。具体请求与响应结构由插件定义,不属于核心 WuKongIM HTTP API JSON 操作清单。 入口继承 WuKongIM HTTP API 的网络隔离和维护模式限制,不新增身份认证。请求体和响应体均限 10 MiB,执行超时使用 `plugin.timeout`(默认 5 秒);超大请求返回 413,插件不可用或非法响应返回 502,未接入插件运行时返回 503。跳对跳响应头及插件提供的 `Content-Length` 不会透传。 此兼容入口是 `v3.0.0-beta.13` 之后的修复;该已发布版本对此路径返回 404。旧业务依赖它时,需要包含修复的服务端,并验证真实插件调用。 ## 安全与容量检查 [#安全与容量检查] 1. 只安装经过审查的插件制品,并固定版本与摘要。 2. 对每个节点验证安装、desired config、observed methods 和运行状态。 3. 为外部调用设置短超时;同步 Send Hook 不执行长任务。 4. 监控 `method="send"` 等低基数调用指标、worker 队列、失败与超时。 5. 让 Hook 重入和插件来源发送保留递归 guard,不构造无限消息环。 6. 把 Receive/PersistAfter 当作可失败副作用;需要可靠处理时再写入业务系统自己的 durable queue。 继续阅读[集成架构](/zh/guide/integration/architecture)、[消息收发](/zh/guide/integration/messaging)和[Webhook](/zh/guide/integration/webhooks)。 # Webhook (/zh/guide/integration/webhooks) 目标:发送前由业务决定放行、修改或拒绝;消息提交后由业务接收异步通知。 ## 1. 选择回调类型 [#1-选择回调类型] | 回调 | 用途与结果 | | -------------------------- | ------------------------------ | | 发送前
`msg.before_send` | 提交前放行、改写或拒绝,返回明确 JSON 决定 | | 异步通知 | 消费已提交消息或状态事件;HTTP `200` 确认回调请求 | 以默认持久消息为例,顺序如下:
**发送前放行不代表消息已提交。** 明确拒绝会阻止提交;异步通知失败不会撤销已提交消息。 ## 2. 接入业务服务 [#2-接入业务服务] 同步决策可先运行[完整 Go 示例](https://github.com/WuKongIM/WuKongIM/tree/main/docs-site/examples/go-webhook)。从仓库根目录启动,需要 Go 1.22+,无需 SDK 或数据库: ```bash cd docs-site/examples/go-webhook go run . ``` 异步通知则由业务实现接收端:验证来源、解析 `event`,对已收到的事件持久化去重后返回 HTTP `200`。 **回调没有内置签名或共享密钥 Header。** 先通过 HTTPS、私网、服务网格或受保护代理建立来源信任;客户端 Token 鉴权不等于业务回调鉴权。 Go 示例只监听同机回环地址。另一主机或容器里的 `127.0.0.1` 指向它自身;跨环境接入需使用可达的可信端点。
发送前回调:配置、Payload 与失败策略 `msg.before_send` 是同步回调,在权限检查和 Send 插件之后、消息提交之前执行。默认关闭;独立于异步 Webhook 的 `http_addr`、`focus_events` 和插件开关。各发送入口节点必须部署一致配置,重启后生效。 ```toml [webhook.before_send] enabled = true http_addr = "https://business.example.com/im/webhook" timeout = "500ms" on_timeout = "deny" on_error = "deny" max_in_flight = 256 ``` 对应环境变量为 `WK_WEBHOOK_BEFORE_SEND_ENABLED`、`WK_WEBHOOK_BEFORE_SEND_HTTP_ADDR`、`WK_WEBHOOK_BEFORE_SEND_TIMEOUT`、`WK_WEBHOOK_BEFORE_SEND_ON_TIMEOUT`、`WK_WEBHOOK_BEFORE_SEND_ON_ERROR`、`WK_WEBHOOK_BEFORE_SEND_MAX_IN_FLIGHT`;环境变量覆盖 TOML。URL 必须为 HTTP(S),不能包含 userinfo 或 fragment。 ```http POST /im/webhook?event=msg.before_send Content-Type: application/json ``` ```json { "from_uid": "u1", "channel_id": "group1", "channel_type": 2, "client_msg_no": "business-message-001", "payload": "aGVsbG8=", "no_persist": false, "sync_once": false } ``` `channel_id` 是规范化的源频道标识:单聊使用服务端规范化标识;SyncOnce 使用源频道而非内部命令频道;请求级 `subscribers` 使用同一收件人快照推导出的稳定临时源频道。服务端不会在此时分配已提交的 `message_id` 或 `message_seq`。`payload` 为 Base64;一个群消息每次入口发送尝试只调用一次,不按成员回调,跨节点 authority 转发也不重复调用。 业务端返回 HTTP 200 和一个明确的 JSON 决定: | 结果 | 响应 | | ----- | ----------------------------------- | | 原样放行 | `{"allow":true}` | | 修改后放行 | `{"allow":true,"payload":"bmV3"}` | | 默认拒绝 | `{"allow":false}` | | 业务拒绝码 | `{"allow":false,"reason_code":200}` | `allow` 必填。只允许修改 Payload,不能改变身份或路由字段。省略 `payload` 保留输入;替换内容必须是有效 Base64,解码后为 1–32767 字节。响应体最多 64 KiB,必须是单个 JSON 对象;未知或重复字段、缺少决定、多余 JSON,以及放行响应中的错误字段类型都属于回调错误。业务码范围为 128–255,在 SENDACK 的 `reason_code` 和 WuKongIM HTTP API 的 `reason` 中保留原值,与 HTTP 状态码无关。省略或填写无效拒绝码时,已解码的拒绝返回标准 NotAllowSend(11);带有不适用 Payload 的拒绝同样拒绝。 | 情况 | 处理 | | ------------------------- | ------------------------------------------------------- | | 明确的业务拒绝 | 始终拒绝,无消息提交或投递 | | 回调超时 | `on_timeout`:`allow` 保留进入回调前的 Payload,`deny` 返回系统错误(15) | | 网络失败、非 HTTP 200、无法解析出有效决定 | `on_error`:`allow` 保留原 Payload,`deny` 返回系统错误(15) | | 并发达到 `max_in_flight` | 立即返回系统错误(15),无额外等待队列 | | 原发送请求取消或期限耗尽 | 终止发送;不会被放行策略覆盖 | 两种失败策略均默认为 `deny`。回调不自动重试、不跟随重定向,不进入异步通知队列。回调期限受发送请求剩余期限约束,应为消息提交留出时间。明确拒绝优先于回调失败放行;发送请求自身取消仍具有最高优先级。 客户端重试可能再次回调。业务端应按发送者、源频道、`client_msg_no` 做幂等;需要重试关联的调用方必须提供稳定且非空的 `client_msg_no`。业务放行不代表随后一定提交成功,不应仅凭此回调完成不可逆扣费等业务事务。NoPersist 和 SyncOnce 同样经过回调,但保留各自的投递、存储语义。 监控 `wukongim_webhook_before_send_total{result}` 和 `wukongim_webhook_before_send_duration_seconds{result}`,区分 `allow`、`reject`、`timeout_allow`、`timeout_deny`、`error_allow`、`error_deny`、`overloaded`、`invalid_request`、`canceled`。指标不包含 UID、频道、回调地址或消息内容。当前只发送 Content-Type,无内置签名或鉴权头;应通过受控网络或代理建立业务端对回调来源的信任。
异步通知:配置与成功响应 在 `wukongim.toml` 中配置: ```toml [webhook] http_addr = "https://events.example.com/wukongim" focus_events = ["msg.notify", "msg.offline", "user.onlinestatus"] queue_size = 1024 workers = 16 request_timeout = "5s" retry_max_attempts = 3 ``` 服务端会把事件名放在查询参数中: ```text POST https://events.example.com/wukongim?event=msg.notify Content-Type: application/json ``` 只有 HTTP 200 被视为成功。连接错误、超时或其他状态码会在本次内存任务中有限重试。
## 3. 核对实际结果 [#3-核对实际结果] 修改回调配置后,重启单节点集群或所有发送入口节点;生产环境各节点保持一致。 Go 发送前示例分别发送以下三种文本: | 发送内容 | 预期结果 | | ----------------- | ----------------------------- | | `hello` | 原样放行,接收与历史内容一致 | | `[replace] hello` | 收到 `Reviewed: hello`,历史保存改写内容 | | `[reject] hello` | 发送 `reason=200`,拒绝消息不进入历史 | 异步通知:提交一条测试消息,确认接收端得到事件并返回 HTTP `200`;业务处理结果另行核对。重复事件应幂等消费,接收端失败时核对有限重试与丢失边界。 **发送结果、对端接收和历史记录分别检查。** 示例拒绝码 `200` 是发送原因码,与 HTTP 状态码无关。发送前超时与错误默认 `deny`;重试可能重复回调,保留相同发送者、源频道和 `client_msg_no`。 队列满、进程退出、请求取消或重试耗尽都可能丢失事件。当前运行时没有磁盘级 Webhook Outbox 或崩溃重放。Webhook 失败不会影响已经成功的 SENDACK 和消息持久化。
Go 示例:请求、改写规则与自定义 默认地址是 `http://127.0.0.1:8090/webhook`。可用 `go run . -addr 127.0.0.1:8091` 修改端口;按 Ctrl+C 停止。先在另一个终端验证回调: ```bash curl -fsS 'http://127.0.0.1:8090/webhook?event=msg.before_send' \ -H 'Content-Type: application/json' \ -d '{"from_uid":"alice","channel_id":"example-group","channel_type":2,"client_msg_no":"example-allow","payload":"aGVsbG8="}' ``` 预期返回 `{"allow":true}`。更换请求中的 Base64 `payload` 可以验证全部规则: | 内容 | Base64 Payload | 结果 | | ----------------- | ---------------------- | ---------------------------------------- | | `hello` | `aGVsbG8=` | 原样放行 | | `[replace] hello` | `W3JlcGxhY2VdIGhlbGxv` | 改写为 `Reviewed: hello` | | `[reject] hello` | `W3JlamVjdF0gaGVsbG8=` | HTTP 200,`allow=false`、`reason_code=200` | 再将已有 `wukongim.toml` 的发送前回调配置为: ```toml [webhook.before_send] enabled = true http_addr = "http://127.0.0.1:8090/webhook" timeout = "500ms" on_timeout = "deny" on_error = "deny" max_in_flight = 64 ``` 重启同一主机上的单节点集群或各入口节点,然后用现有客户端发送上表中的文字。对于 SDK 文本 Payload `{"type":1,"content":"hello"}`,规则作用于 `content`,改写时保留其他 JSON 字段;自定义类型和二进制 Payload 原样放行。WuKongIM HTTP API 的建群、发送和历史查询命令见示例 README。成功发送的 `reason` 为 1,示例业务拒绝为 200;接收和历史中应看到改写后的内容,拒绝消息没有历史记录。 修改 `main.go` 的 `evaluate` 函数即可替换业务规则。当前规则没有副作用或缓存,相同输入和规则得到相同决定;新增业务写入时需要自行实现持久化幂等,不能把回调放行当作消息已提交。日志只输出决定类型。请求体最多 64 KiB、Payload 最多 32767 字节,每个示例进程最多同时处理 64 个请求,超额返回 HTTP 503。改写后超出 Payload 上限会明确拒绝。 此示例仅接受回环监听地址,用于同机进程联调。其他主机或容器中的 `127.0.0.1` 指向它们自身;跨主机接入需要可达的可信代理及来源鉴权。客户端 Token 鉴权与业务回调鉴权是两件事。
事件 Payload 与在线状态解析 | 事件 | 请求体 | 用途 | | ------------------- | ------------- | ---------------------- | | `msg.before_send` | 单个待发送消息对象 | 同步放行、修改或拒绝 | | `msg.notify` | 已提交消息数组 | 业务审计、搜索索引、异步通知 | | `msg.offline` | 一条消息及一批离线 UID | 离线推送候选计算 | | `user.onlinestatus` | 兼容在线状态字符串数组 | UID Owner 本地、尽力而为的会话提示 | `msg.notify` 的代表性请求: ```json [ { "header": {"no_persist": 0, "red_dot": 1, "sync_once": 0}, "setting": 0, "expire": 0, "message_id": 123456789, "message_idstr": "123456789", "client_msg_no": "order-20260730-0001", "message_seq": 42, "from_uid": "system", "channel_id": "u1001", "channel_type": 1, "timestamp": 1785398400, "payload": "eyJ2ZXJzaW9uIjoxLCJ0eXBlIjoib3JkZXJfdXBkYXRlIn0=" } ] ``` JSON 中的 `payload` 是 Base64。`msg.offline` 在 UID 数量较小时使用 `to_uids`;达到压缩阈值时可能改为 `compress: "gzip"` 与 Base64 编码的 `compress_to_uids`,接收端必须支持两种形式。 `user.onlinestatus` 的每项是: ```text {uid}-{device_flag}-{online:0|1}-{session_id}-{device_online_count}-{total_online_count} ``` 这些计数只来自 UID Owner 当前节点上的活跃 Session,不是集群全局 Presence,也不保证事件完整或有序。UID 可以含 `-`;需要解析时从右侧取最后五个数字段,其余前缀才是 UID。
接收端持久化与幂等流程 推荐的接收端流程: 建议幂等键: * `msg.notify`:`event + message_id`; * `msg.offline`:`event + message_id + uid`,逐个接收者去重; * `user.onlinestatus`:按完整字符串去重,仅作为本地会话提示;不要据此构建全局在线真值。
生产来源鉴权 当前 HTTP Sender 只设置 `Content-Type: application/json`,**不会添加签名或共享密钥 Header**。生产环境必须在它之外建立可信边界: * 使用 HTTPS; * 优先使用私网、服务网格或出口代理; * 在反向代理层使用 mTLS、固定出口身份或受控凭据; * 对来源 IP 和请求速率做限制; * 不把回调端点暴露为匿名公网写入口; * 不在日志中记录完整敏感 Payload。
队列上限、指标与失败处理 * `queue_size` 是每类事件内存队列的有界容量。 * `workers` 控制每类队列并发发送数。 * `msg_notify_batch_max_items` 和等待时间控制消息批次。 * `offline_uid_batch_size` 同时控制离线 UID 分块/压缩边界。 * `retry_max_attempts` 是总尝试次数,不是首次之后的额外次数。 接收端持续失败时,应先修复或隔离接收端,而不是无限增大 WuKongIM 内存队列。关键业务数据应能从业务数据库或消息历史重新构建。
源码故障测试(可选) 从包含此功能的源码仓库根目录运行,需要 `go.mod` 指定的 Go 工具链。测试会构建当前源码,自动启动真实三节点集群和回环地址上的可控回调服务;无需 Docker、云服务器或外部 Webhook 地址。 ```bash GOWORK=off go test -tags=e2e ./test/e2e/message/webhook \ -run '^TestBeforeSendWebhookAuthenticatedFaults$' -count=1 -timeout 2m -p=1 -v ``` 此场景启用客户端 Token 鉴权,使用 256 个 Hash Slot、2 个 Slot 副本和每节点 2 个回调并发。回调超时为 3 秒;三节点故意使用不同失败策略,逐项验证 `deny`、超时放行和错误放行。实际部署应保持各节点配置一致。 测试核对客户端鉴权、放行、Payload 替换、业务拒绝、100 ms 慢响应、超时、HTTP 503、无效 JSON、重定向和连接失败。占满一个节点的两个回调名额后,16 次额外发送必须被拒绝且不能进入回调;其他节点仍可发送,释放后原节点也应恢复。成功消息的接收内容与历史记录必须一致,拒绝消息不能进入历史,回调次数与公开指标增量必须符合预期。 `-v` 输出发送延迟、回调并发峰值及测试前后的堆内存和 goroutine 快照。若平台没有进程 CPU 指标,会输出 `unavailable`。这些是有限故障场景的观察值,不是吞吐容量、延迟分位数或长期内存稳定性结论。测试结束会停止进程并清理临时数据。 本机回调使用测试规则,不验证实际业务逻辑,也不验证 Webhook 签名鉴权。接入真实业务服务后,应复用放行、修改、拒绝和失败策略用例验收。
下一步:[上线检查](/zh/guide/integration/acceptance)。实现接收端时查阅[事件 Payload](/zh/api/webhooks/payloads)与[可靠性边界](/zh/api/webhooks/reliability-and-security)。 # 核心能力 (/zh/guide/product-overview/capabilities) WuKongIM 把实时连接、消息持久化、离线恢复和多设备同步放在同一个集群中。应用通过频道组织个人、群组或自定义主题的消息,业务系统继续掌握身份、关系和产品规则。 WuKongIM 面向大规模连接与消息负载设计,但不提供脱离硬件、存储、副本和业务模型的统一容量数字。请使用真实负载验证吞吐、延迟和资源余量。 ## 能力地图 [#能力地图] | 能力 | 提供的结果与边界 | | ----- | --------------------------------------------------------------------------------------- | | 实时接入 | 支持 TCP WKProto、WebSocket、连接生命周期和有界异步处理。边界:生产身份、Token 和产品权限仍由受信任的业务后端建立。 | | 可靠消息 | 支持个人、群组和自定义频道内的有序消息、持久化复制、重试去重和离线同步。边界:顺序以单个频道为单位;写入成功不等于接收、已读或业务处理完成。 | | 多设备状态 | 提供设备会话、在线状态、最近会话、已读位置和未读状态。边界:业务账号和关系数据仍以业务系统为准。 | | 集群与伸缩 | 单节点集群与多节点集群使用同一模型,通过 256 个物理哈希槽组织路由,并支持副本和故障转移。边界:扩容能力取决于频道分布、热点、存储和真实负载。 | | 扩展与运维 | 提供 Webhook、插件、就绪检查、Prometheus、诊断、Manager 和命令行工具。边界:Webhook 和提交后扩展不是可靠消息队列,运维变更必须遵守安全边界。 | ## 如何理解消息结果 [#如何理解消息结果] 默认持久消息被服务器确认后,表示消息已经完成对应的持久化提交。在线投递、离线同步、业务消费和已读发生在后续阶段;任何一个后续结果都不能只从“发送成功”推断。 集成时应分别定义发送、送达、已读和业务完成的判断方式。更精确的确认边界、重试方式和非持久消息行为见[消息集成](/zh/guide/integration/messaging)与[消息标志](/zh/api/dictionaries/message-flags)。 ## 面向规模的原则 [#面向规模的原则] * 顺序以频道为边界,不让无关频道共享一个全局顺序瓶颈。 * 连接、消息处理和投递使用有界队列与背压,过载时能够暴露积压或拒绝,而不是无限占用内存。 * 大群、热点频道和重连高峰需要分别建模;平均 QPS 不能代表最坏延迟或恢复能力。 * 容量结论应同时记录吞吐、P99、错误率、队列和 CPU、内存、磁盘、网络。具体方法见 [`wkcli bench`](/zh/server/tools/wkbench)。 ## 需要业务系统提供什么 [#需要业务系统提供什么] WuKongIM 不替代账号登录、好友或群业务规则、产品权限、内容审核、移动系统推送、业务数据库和分析平台。典型集成由受信任的业务后端签发身份凭据、维护业务关系、调用受保护的服务端接口,并按自己的业务结果处理异步事件。 # 产品概览 (/zh/guide/product-overview) WuKongIM 是开源、自托管的实时消息基础设施,让应用能够在自己的基础设施上提供聊天、通知和多设备消息同步,并面向大规模连接与消息负载设计。 WuKongIM v3 仍处于 Beta 阶段,API、配置和持久化格式在稳定版前可能变化。生产使用前请阅读[升级与迁移](/zh/server/operations/upgrade-and-migration)、[安全与权限](/zh/server/configuration/security)和[健康与监控](/zh/server/operations/health-and-monitoring)。 ## 它解决什么问题 [#它解决什么问题] 实时消息产品不只需要“把数据发出去”,还要管理大量长连接、频道内消息顺序、持久化、断线补偿和多设备状态。WuKongIM 把这些能力放进一个可自托管的集群,让应用团队专注于自己的产品和业务规则。 应用或业务服务把消息发到一个频道。这个频道可以代表个人、群组或业务自定义主题;WuKongIM 负责保存并同步消息,在线设备实时接收,离线或重连设备随后补齐。 ## 职责边界 [#职责边界] | 参与方 | 主要负责 | | -------- | ---------------------------------- | | WuKongIM | 客户端连接、频道内消息顺序、持久化与复制、在线投递、离线与多设备同步 | | 业务系统 | 账号、身份凭据、好友或群关系、产品权限、内容治理、移动推送和业务数据 | | 客户端 SDK | 连接生命周期、本地消息状态、界面展示和用户交互 | ## 它不是什么 [#它不是什么] * 它不是带界面和完整业务流程的聊天应用。 * 它不替代业务后端,也不决定谁可以发送什么消息。 * 它不是为离线任务、全局事务队列或长期分析设计的通用消息队列。 ## 是否适合你的产品 [#是否适合你的产品] 如果产品需要大量在线用户或设备、频道内有序消息、断线恢复、持久记录或多设备同步,WuKongIM 值得评估。聊天、通知和客服是最直接的场景;直播互动、IoT 和 AI 消息工作流也可以建立在同一套消息能力上。 如果需求只是低频后台任务、离线批处理或分析查询,任务队列、流处理或分析数据库通常更合适。 ## 统一部署模型 [#统一部署模型] 每个 WuKongIM 部署都是集群。只有一个节点时,它是**单节点集群**,仍使用与多节点相同的集群路径;不存在绕过集群语义的独立模式。实际容量取决于硬件、存储、副本、频道分布和业务负载,必须使用真实场景验证。 # 适用场景 (/zh/guide/product-overview/use-cases) WuKongIM 最适合需要实时连接、频道内有序消息、持久记录、断线恢复或多设备同步的产品。它提供消息基础设施,业务系统继续负责身份、关系、权限和完整业务流程。 ## 核心场景 [#核心场景] | 场景 | 能力与职责边界 | | -------- | ---------------------------------------------------------------------------- | | 单聊、群聊与社区 | WuKongIM:个人和群组频道、持久消息、在线投递、离线恢复和多设备同步。
业务系统:账号、好友或群关系、成员生命周期、内容治理和移动推送。 | | 通知与系统消息 | WuKongIM:系统身份、持久消息、在线投递和离线补偿。
业务系统:通知偏好、模板、频控、业务回执,以及 APNs/FCM 等移动推送。 | | 客服与坐席 | WuKongIM:访客与坐席之间的频道消息、Webhook 或插件扩展。
业务系统:排队、分配、工单、服务质量和敏感数据治理。 | ## 扩展场景 [#扩展场景] | 场景 | 能力与职责边界 | | -------- | ----------------------------------------------------------------- | | 直播互动 | WuKongIM:自定义频道、实时消息和按负载验证的在线投递。
业务系统:房间生命周期、礼物与风控、降级策略和热点治理。 | | IoT 与信令 | WuKongIM:稳定的用户或设备标识、双向消息和断线恢复。
业务系统:设备注册、命令授权、状态影子、超时和补偿。 | | AI 消息工作流 | WuKongIM:持久基础消息、消息事件和服务端扩展入口。
业务系统:模型调用、流控、内容安全、成本和最终结果持久化。 | ## 两种常见集成方式 [#两种常见集成方式] ### 客户端消息产品 [#客户端消息产品] 客户端 SDK 连接 WuKongIM、接收实时消息并恢复离线消息;业务 API 管理账号、关系、权限和移动推送。受保护的服务端接口不应直接暴露给不可信客户端。 ### 服务端事件与设备通信 [#服务端事件与设备通信] 受信任的业务服务把事件发送到频道,客户端或设备通过长连接和同步能力消费消息。命令授权、超时、业务结果和补偿仍由业务系统定义;消息写入成功不等于业务动作已经完成。 ## 什么时候选择其他系统 [#什么时候选择其他系统] | 需求 | 更合适的选择 | | -------------- | ---------- | | 低频后台任务或定时作业 | 任务队列 | | 跨业务的通用事件流与离线处理 | 消息队列或流处理平台 | | 长期聚合、检索和分析 | 分析数据库或数据平台 | 这些系统可以与 WuKongIM 配合使用;区别在于 WuKongIM 的核心对象是保持连接并收发实时消息的用户、设备和频道。 ## 如何验证 [#如何验证] 先通过[快速开始](/zh/guide/quick-start)运行单节点集群并发送第一条消息。进入集成阶段后,阅读[消息集成](/zh/guide/integration/messaging);上线前完成[集成验收](/zh/guide/integration/acceptance),再使用 [`wkcli bench`](/zh/server/tools/wkbench)按真实负载验证容量。 # 体验四个 Demo (/zh/guide/quick-start/chat-demo) ## 1. 一键启动 [#1-一键启动] 准备 **Go 1.25+、Node.js 22.12+(或 20.19+)和 npm**。在当前仓库根目录运行: ```bash node demo/start.mjs ``` 启动器会准备一个 **256 hash slots 的单节点集群**和所需业务服务,自动打开首页。首次需要下载依赖;默认模拟模型无需 Key。 首页默认是 `http://127.0.0.1:5174/demos/`,占用时使用空闲端口。**以终端输出和自动打开的地址为准**;各 Demo 已自动连接本次集群。按 `Ctrl+C` 关闭本次全部进程。 从首页选一个场景;每个 Demo 顶部都有“返回首页”。截图来自本地 `main`,发布镜像中的入口取决于其版本。 ## 2. 选一个场景 [#2-选一个场景] | Demo | 先做什么 | 可以体验 | | ----- | ----------------------------------------------------- | ---------------- | | 即时聊天 | [登录 Alice 与 Bob](/zh/guide/quick-start/first-message) | 单聊、群聊、消息编辑、历史恢复 | | 流式回复 | 创建演示账号并连接,发送一个问题 | 逐段回复、取消、失败与恢复 | | 在线客服 | 开始演示,访客提问后转人工,客服接入 | AI 接待、人工接管、多访客会话 | | Agent | 开始演示,发起任务并查看工具卡片 | 工具调用、待办确认、暂停与取消 | 这四个 Demo 来自当前源码;**只启动 Docker 容器不会启动客服或 Agent 的 Node 业务后端**。完整体验使用上面的一键启动。源码及独立运行方式见 [demo/README.md](https://github.com/WuKongIM/WuKongIM/blob/main/demo/README.md)。 启动器只监听本机。聊天 Demo 的“创建或更新演示凭据”会写入测试 UID 的 Token;已有账号与迁移验收保持不勾选。生产身份与权限应由受信任的业务后端管理。
已有 Linux / Docker 集群:访问 Chat Demo ## 访问方式 [#访问方式] | 环境 | Chat Demo 地址 | API 基地址 | | -------------------- | ------------------------------ | ------- | | Linux / Docker 单节点集群 | `http://127.0.0.1:5001/demo/` | 页面同源 | | 仓库三节点 Compose | `http://127.0.0.1:15001/demo/` | 页面同源 | 需要连接另一个 API 地址时,可以使用: ```text http://127.0.0.1:5001/demo/?apiurl=http://host:port ``` Demo 先调用 `/route` 获取客户端 WebSocket 地址,再使用 JavaScript SDK 建立长连接。 ## 排查连接地址 [#排查连接地址] 在浏览器开发者工具的 Network 中检查 `/route` 响应。默认 Docker 端口映射下,未填写 `api.external_ws_addr` 时,`ws_addr` 应使用本次 API 请求的主机名及 Gateway 端口 `5200`。Linux 初始化默认监听 `127.0.0.1:5200`,通过 SSH 同时转发 `5001` 和 `5200` 即可体验,无需填写对外地址。 如果响应仍包含 `ws://0.0.0.0:5200`,确认使用包含自动补全修复的版本,并检查是否显式配置了通配对外地址。旧版本可在已有 `[api]` 中设置 `external_ws_addr = "ws://127.0.0.1:5200"` 后重启;直接远程访问时使用客户端可达的服务器地址和外部端口。环境变量 `WK_EXTERNAL_WSADDR` 优先于 TOML。 页面可打开但 WebSocket 连接失败时,检查 Docker 映射或 SSH 转发是否包含 `5200`。非默认映射端口、独立域名或 HTTPS/WSS 代理需要显式对外地址及实际可用的 TLS/WebSocket 入口,详见 [Docker 部署](/zh/server/deployment/docker)。 ## 登录字段 [#登录字段] * **API 基地址**:WuKongIM HTTP API 地址。 * **登录账号**:业务系统中的唯一用户 UID。 * **登录 Token**:已有用户的 Web 设备 Token。默认直接连接,不调用 `/user/token`,不修改服务端凭据。 * **创建或更新演示凭据**:仅为专用测试账号显式勾选;此模式会调用 `/user/token` 写入 Web 从设备凭据。 凭据仅保存在当前标签页的 `sessionStorage`,不放进 URL。刷新页面或点击“重新同步” 会重建 SDK 的消息、会话和游标状态,再向服务器同步;退出时删除本 Demo 的凭据。 重新同步前应先处理未发送草稿,Demo 会在输入框还有内容时阻止该操作。 迁移验收请使用默认的已有 Token 模式。此行为仅覆盖 Demo 自己的内存缓存,不会 清理其他业务 App 的消息数据库,也不代表其他客户端已完成迁移适配。 相同 UID 和相同设备标记的主设备连接可能互相替换。并行测试时请使用不同 UID,或使用独立浏览器会话。
生产接入的边界 ## 生产边界 [#生产边界] Chat Demo 在显式勾选创建演示凭据时直接调用 `/user/token`,仅用于测试。生产客户端不应自行注册或重置 Token;应由受信任的业务后端完成身份校验和凭据签发。 生产集成还需要: * 为 HTTP、WebSocket 和 TCP 流量配置 TLS 与网络访问策略。 * 替换示例 Manager 账号、JWT Secret、Join Token 和其他固定凭据。 * 在业务后端执行账号、群组、好友、权限和内容规则。 * 选择合适的客户端 SDK,并处理重连、离线同步和本地消息状态。 如果双向消息已经成功,继续阅读[下一步](/zh/guide/quick-start/next-steps)。
# 发送第一条消息 (/zh/guide/quick-start/first-message) 先完成 [Docker 部署](/zh/server/deployment/docker)中的 `/readyz` 检查,或[一键启动源码中的四个 Demo](/zh/guide/quick-start/chat-demo)。下面是新版 Chat Demo 的 Bob 页面:左侧收到 Alice 的消息,右侧是 Bob 的回复。点击图片可放大。 **① 收到消息**:`hello from alice`。**② 回复消息**:`hello from bob`,Alice 也应收到它。 ## 1. 登录 Alice [#1-登录-alice] Docker 用户打开 [Chat Demo](http://127.0.0.1:5001/demo/);源码用户从启动后自动打开的首页选择“即时聊天”。填写下面的测试身份: | 字段 | 值 | | -------- | ----------------------------------------- | | API 基地址 | 保持自动填写;Docker 默认为 `http://127.0.0.1:5001` | | 登录账号 | `quickstart-alice` | | 登录 Token | `alice-local-token` | **① 保持本机 API 地址。② 填写 UID 和 Token。③ 为新测试账号勾选创建演示凭据,再点击登录。** “创建或更新演示凭据”会写入这个 UID 的 Web Token。已有账号或迁移验收请保持不勾选,并填写原有 Web Token;默认登录不会修改凭据。 ## 2. 登录 Bob,选择单聊 [#2-登录-bob选择单聊] 在另一个浏览器配置文件、隐私窗口或不同浏览器中打开 Demo。使用 `quickstart-bob` / `bob-local-token`,同样只为这个新测试账号勾选创建演示凭据。 等待双方页头都显示“已连接”。回到 Alice,点击聊天列表右上角 `+`(或“开始聊天”),选择“单聊”,填写 `quickstart-bob` 并确定。 **① 选择单聊。② 填写对方的 UID。③ 点击确定。**
Docker 发布版界面与截图不同? 截图来自本地 `main` 的新版聊天 Demo。较早的发布镜像可能显示“连接成功”和“与谁会话?”:点击右上角“与谁会话?”,选择“单聊”,填写对方 UID。登录身份与收发检查相同。要体验与截图一致的四个 Demo,使用[源码一键启动](/zh/guide/quick-start/chat-demo)。
## 3. 互发一条消息 [#3-互发一条消息] 1. Alice 发送 `hello from alice`;等待“发送中”消失,并在 Bob 页面打开与 `quickstart-alice` 的单聊,确认相同消息出现。 2. Bob 回复 `hello from bob`;确认 Alice 收到回复。 ## 成功检查 [#成功检查] * 两个 UID 都已连接; * Alice 的发送状态成功,Bob 看到了原文; * Bob 的回复也出现在 Alice 页面。
按现象排查 | 现象 | 先检查 | | -------- | --------------------------------------------------------------- | | 一直未连接 | `/readyz`、WebSocket `5200` 端口、`docker logs --tail 100 wukongim` | | 对方没有收到 | 双方是否在线、目标 UID 是否完全相同、是否选择“单聊” | | 两个页面互相顶掉 | 使用不同 UID 和独立浏览器会话 | Linux 软件包部署时,用 `sudo journalctl -u wukongim -n 100 --no-pager` 查看日志。
这次验证覆盖了什么? 它经过演示 Token 写入、CONNECT Token 精确匹配、路由发现、WebSocket 连接、频道消息提交和在线投递。发送成功与对方收到是两个结果,需要分别核对。 它验证开发链路可用。生产仍需账号鉴权、Token 过期与重放策略、TLS、容量、故障恢复和持久性验证,详见 [Chat Demo 的边界](/zh/guide/quick-start/chat-demo)。
下一步:[选择 SDK,接入自己的应用](/zh/sdk)。 # 快速开始 (/zh/guide/quick-start) 先在自己的电脑上跑通 Alice 与 Bob 的双向消息。需要 Docker 和浏览器,预计 5~10 分钟,不含下载时间。 想体验最新版的流式回复、在线客服和 Agent?查看[四个 Demo 的源码一键启动](/zh/guide/quick-start/chat-demo)。 ## 完成标准 [#完成标准] * `/readyz` 返回 `{"ready":true}`; * Alice 与 Bob 都显示已连接状态; * 双方分别收到 `hello from alice` 和 `hello from bob`。 示例使用测试凭据。接入生产前,需要替换凭据,配置 TLS、业务鉴权、备份和故障恢复,详见[安全配置](/zh/server/configuration/security)。 还没有准备好 Docker?查看[环境准备](/zh/guide/quick-start/prerequisites)。需要直接安装在 Linux 主机上?使用[Linux 安装路径](/zh/guide/quick-start/single-node-cluster)。 # 下一步 (/zh/guide/quick-start/next-steps) 如果就绪检查成功、两个测试 UID 都已连接,并且能够双向收发消息,你已经完成快速验证。 ## 选择阅读路径 [#选择阅读路径] ## 常见问题 [#常见问题] ### 单节点集群是不是独立模式? [#单节点集群是不是独立模式] 不是。它是只有一个节点的集群,仍使用 Controller、Slot、Channel、路由和持久化路径。 ### 可以把示例配置直接用于生产吗? [#可以把示例配置直接用于生产吗] 不可以。至少需要替换固定凭据,规划 TLS 和访问控制,把数据放在受管理的持久化存储上,并按真实负载验证容量、故障恢复和尾延迟。 ### 为什么重启后还能看到数据? [#为什么重启后还能看到数据] Linux 软件包把节点数据保存在 `/var/lib/wukongim/data`。停止 systemd 服务不会删除这个目录。 ### 如何修改端口? [#如何修改端口] 修改 `/etc/wukongim/wukongim.toml`,执行 `sudo wukongim config validate --config /etc/wukongim/wukongim.toml`,再运行 `sudo systemctl restart wukongim`。 ### 为什么生产客户端不能调用 `/user/token`? [#为什么生产客户端不能调用-usertoken] 这是演示身份接口。生产 Token 必须由受信任的业务后端在完成真实身份和权限校验后签发或更新。 ### 三节点环境在哪里? [#三节点环境在哪里] 仓库根目录的 `docker-compose.yml` 提供三节点开发环境。它会暴露开发凭据和 Benchmark 能力,同样不能作为生产模板。 # 环境准备 (/zh/guide/quick-start/prerequisites) ## 在自己的电脑体验 [#在自己的电脑体验] * 安装并启动 Docker;macOS、Windows 可使用 Docker Desktop。 * 准备浏览器,能够打开两个独立会话。 * 本机 `5001`、`5100`、`5200`、`5301` 端口未被其他程序占用。 ```bash docker version ``` 输出中应同时包含 Client 和 Server。下一步:[用 Docker 启动单节点集群](/zh/server/deployment/docker)。 ## 端口用途 [#端口用途] | 端口 | 用途 | | ------ | ------------------------ | | `5001` | HTTP API、就绪检查和 Chat Demo | | `5100` | 客户端 TCP | | `5200` | 客户端 WebSocket | | `5301` | Manager | | `7001` | 节点内部通信,不需要映射到宿主机 | API 和 Manager 的示例端口仅绑定本机。体验无需向公网开放管理端口;远程服务器使用 Docker 部署页中的 SSH 转发。
不使用 Docker:准备 Linux 主机 Linux 软件包支持 `amd64` / `x86_64`,系统为 Ubuntu 24.04、Debian 13、Rocky Linux 9、AlmaLinux 9 或 RHEL 9。需要 `sudo`、`curl`,能够访问 `packages.githubim.com`;远程主机需要 SSH。 ```bash uname -m . /etc/os-release && echo "$PRETTY_NAME" curl --version sudo -v ``` 软件包创建 `/etc/wukongim`、`/var/lib/wukongim` 和 `/var/log/wukongim`。准备完成后,进入[Linux 安装路径](/zh/guide/quick-start/single-node-cluster)。
# 启动单节点集群 (/zh/guide/quick-start/single-node-cluster) 下面的命令都在 Linux 服务器执行。 ## 1. 安装 WuKongIM [#1-安装-wukongim] Ubuntu 或 Debian: ```bash curl -fsSL https://packages.githubim.com/repo | sudo sh sudo apt update sudo apt install -y wukongim ``` Rocky、AlmaLinux 或 RHEL 9: ```bash curl -fsSL https://packages.githubim.com/repo | sudo sh sudo dnf -y --disablerepo='*' --enablerepo=wukongim-preview makecache --refresh sudo dnf install -y wukongim ``` ## 2. 初始化配置 [#2-初始化配置] ```bash sudo wukongim config init --config /etc/wukongim/wukongim.toml sudo wukongim config validate --config /etc/wukongim/wukongim.toml ``` 保存第一条命令显示的 Manager 管理员密码,它只显示一次。 ## 3. 启动并检查 [#3-启动并检查] ```bash sudo systemctl enable --now wukongim curl --fail http://127.0.0.1:5001/readyz ``` `/readyz` 成功表示单节点集群已经可以接收流量。失败时查看日志: ```bash sudo journalctl -u wukongim -n 100 --no-pager ``` ## 4. 从电脑打开页面 [#4-从电脑打开页面] 如果 Linux 是远程服务器,在电脑终端建立 SSH 转发并保持终端运行: ```bash ssh -L 5001:127.0.0.1:5001 -L 5200:127.0.0.1:5200 -L 5301:127.0.0.1:5301 <用户名>@<服务器IP> ``` 然后在电脑浏览器打开: | 页面 | 地址 | | --------- | ----------------------------- | | Chat Demo | `http://127.0.0.1:5001/demo/` | | Manager | `http://127.0.0.1:5301` | Manager 用户名是 `admin`,密码是初始化时保存的密码。 停止或再次启动服务: ```bash sudo systemctl stop wukongim sudo systemctl start wukongim ``` 数据保存在 `/var/lib/wukongim/data`。下一步:[发送第一条消息](/zh/guide/quick-start/first-message)。 # AI 与 IoT 通信 (/zh/guide/tutorials/ai-and-iot) 先选择要完成的场景: 默认 Gateway 校验设备 CONNECT 凭据,当前 WuKongIM HTTP API 路由没有通用业务鉴权。浏览器、移动端和固件通过业务后端授权;模型 API Key、Token 写入和管理请求留在后端。 ## AI:流式回复 [#ai流式回复] 从 [Agent 流式回复](/zh/sdk/easy/agent-streaming)运行真实模型 Demo,再接入前端 SDK 和 Agent 后端。服务端须包含在线 EVENT 投递实现;有事件接口的旧镜像不一定支持在线推送。
**核对:** 基础消息带 stream setting,SENDACK 成功后再串行写入事件。在线使用 SDK 推送;重连由 BFF 查询最终快照,不轮询历史。 增量可能只在有界 Leader 缓存中。Leader 切换后缺少证据时,finish 会拒绝完成;业务需重放完整增量或最终快照,再重试。完整教程同时说明取消与失败处理。
AI:基础消息的完整后端请求 业务服务先完成模型权限、配额、内容安全和取消策略。收到用户问题后,以稳定 `client_msg_no` 发送一条带 legacy stream bit 的基础回答消息;`setting=2` 表示流式消息: ```bash curl -sS http://127.0.0.1:5001/message/send \ -H 'Content-Type: application/json' \ -d '{ "from_uid":"ai-assistant", "channel_id":"alice", "channel_type":1, "client_msg_no":"ai-answer-req-42", "setting":2, "payload":"eyJ2ZXJzaW9uIjoxLCJ0eXBlIjoiYWlfYW5zd2VyIiwic3RhdHVzIjoic3RyZWFtaW5nIiwicmVxdWVzdF9pZCI6InJlcS00MiJ9" }' ``` 等待 `reason=1` 后再写事件。这证明基础消息已达到 Channel quorum commit,但不表示模型生成、客户端渲染或业务任务已完成。 当前产品路径会校验基础消息已提交、带 stream setting 且身份匹配。业务服务仍须保持完全相同的 Channel、发送者与 `client_msg_no`;基础消息不能是 NoPersist 或 SyncOnce。面向 SDK 和真实模型的完整教程见 [Agent 流式回复](/zh/sdk/easy/agent-streaming)。
AI:增量、幂等与完成请求 先打开默认事件 lane: ```bash curl -sS http://127.0.0.1:5001/message/event \ -H 'Content-Type: application/json' \ -d '{ "channel_id":"alice", "channel_type":1, "from_uid":"ai-assistant", "client_msg_no":"ai-answer-req-42", "event_id":"ai-answer-req-42-open", "event_type":"stream.open", "payload":{"kind":"text"} }' ``` 每个生成块使用新的稳定 `event_id`;结果不明确时,用同一个 ID 和相同 Payload 重试: ```bash curl -sS http://127.0.0.1:5001/message/event \ -H 'Content-Type: application/json' \ -d '{ "channel_id":"alice", "channel_type":1, "from_uid":"ai-assistant", "client_msg_no":"ai-answer-req-42", "event_id":"ai-answer-req-42-delta-0001", "event_type":"stream.delta", "payload":{"kind":"text","delta":"Hello"} }' ``` 已应用的 `event_id` 再次出现时返回原结果,不会用新 Payload 重写它。`stream.open`、`stream.delta` 和 `stream.snapshot` 可能只保存在 Slot Leader 的有界缓存中,此时 `msg_event_seq` 可以是 `0`。 生成结束后提交终态: ```bash curl -sS http://127.0.0.1:5001/message/event \ -H 'Content-Type: application/json' \ -d '{ "channel_id":"alice", "channel_type":1, "from_uid":"ai-assistant", "client_msg_no":"ai-answer-req-42", "event_id":"ai-answer-req-42-finish", "event_type":"stream.finish", "payload":{"snapshot":{"kind":"text","text":"Hello"}} }' ``` `stream.finish` 把仍打开的 lane 和保留的 finish marker 作为一个 Slot proposal 形成终态投影。如果领导权变化后新 Leader 没有缓存证据,finish 会 fail-closed;业务服务必须重放完整增量或终态快照,再重试 finish,不能把缺失内容标记为完成。
AI:最终投影与恢复请求 重连客户端可以请求紧凑事件摘要: ```bash curl -sS http://127.0.0.1:5001/channel/messagesync \ -H 'Content-Type: application/json' \ -d '{ "login_uid":"alice", "channel_id":"ai-assistant", "channel_type":1, "start_message_seq":0, "limit":20, "pull_mode":1, "event_summary_mode":"full" }' ``` 响应可包含 `event_meta`、完成状态和最终 snapshot。包含在线事件投递实现的服务端会把公开增量作为 EVENT 发给在线 Session;EasySDK-JS 用 `WKIMEvent.CustomEvent` 接收。在线不轮询历史、不需要 `/message/eventsync`;离线恢复由 BFF 调用 `/channel/messagesync`。旧发布版本不能仅因有事件接口而视为支持在线推送,部署要求见 [Agent 流式回复](/zh/sdk/easy/agent-streaming)。
## IoT:发送有界遥测 [#iot发送有界遥测] ① 为 `device-42` 分配稳定 UID 与可撤销凭据,后端创建 `fleet-west`,按成员策略授权设备与控制服务。 **核对:** 设备上报状态,控制服务独立接收。高频采样先聚合、采样或只发变化;限制单设备速率、Payload 和恢复窗口。`reset=1` 会替换成员,已有群先核对业务名单。
遥测:建群、发送与容量边界 为每台设备分配稳定 UID 和可撤销凭据。群 Channel 必须先存在,而且发送设备必须满足成员策略;在受信服务网络中创建产品拥有的遥测 Channel: ```bash curl -sS http://127.0.0.1:5001/channel \ -H 'Content-Type: application/json' \ -d '{ "channel_id":"fleet-west", "channel_type":2, "reset":1, "subscribers":["device-42","control-service"] }' ``` `reset=1` 用给定列表替换成员,适合首次配置或业务侧期望状态对账;不要在未知现有成员时盲目使用。这里同时授权设备和控制服务,成功后设备可以发送普通持久消息: ```bash curl -sS http://127.0.0.1:5001/message/send \ -H 'Content-Type: application/json' \ -d '{ "from_uid":"device-42", "channel_id":"fleet-west", "channel_type":2, "client_msg_no":"device-42-telemetry-1785897600", "payload":"eyJ2ZXJzaW9uIjoxLCJ0eXBlIjoidGVsZW1ldHJ5IiwidGVtcGVyYXR1cmVfYyI6MjEuNCwicmVwb3J0ZWRfYXQiOjE3ODU4OTc2MDB9" }' ``` 高频采样不要逐条无限写入聊天日志。按设备/时间窗聚合、采样或只发送状态变化,并分别限制单设备速率、Payload 大小、Channel 热点、Webhook/插件消费和离线恢复窗口。
## IoT:发送可恢复命令 [#iot发送可恢复命令] ② 在第一条命令之前用 `/message/cmd/bind` 绑定设备,再在同一源 Channel 发送 `sync_once=1`。绑定从当前 CMD 日志尾部之后开始,不补回更早命令。 **核对:** 设备重连后用 `/message/sync` 恢复绑定后的命令,再调用 `/message/syncack` 推进游标。推进游标不代表执行成功;NoPersist 没有离线恢复。
命令:绑定、发送、同步与解除绑定 可恢复命令应复用一个稳定的源 Channel。必须在第一条目标命令发送前,为设备建立持久 CMD 发现绑定: ```bash curl -sS http://127.0.0.1:5001/message/cmd/bind \ -H 'Content-Type: application/json' \ -d '{ "uid":"device-42", "channel_id":"fleet-west", "channel_type":2 }' ``` 绑定从当前 CMD 日志尾部的下一条 sequence 开始,不会补回更早的命令;因此必须先绑定再发送。随后,受信控制服务在同一源 Channel 上设置 `sync_once=1`: ```bash curl -sS http://127.0.0.1:5001/message/send \ -H 'Content-Type: application/json' \ -d '{ "from_uid":"control-service", "channel_id":"fleet-west", "channel_type":2, "sync_once":1, "client_msg_no":"device-command-op-42", "payload":"eyJ2ZXJzaW9uIjoxLCJ0eXBlIjoiZGV2aWNlX2NvbW1hbmQiLCJvcGVyYXRpb25faWQiOiJvcC00MiIsImFjdGlvbiI6InNldF9mYW4iLCJ2YWx1ZSI6MiwiZGVhZGxpbmUiOjE3ODU4OTc2NjB9" }' ``` 它进入独立 CMD 日志,绑定创建 UID-owned CMD 发现目录,不污染普通会话。设备重连后同步最新命令 generation: ```bash curl -sS http://127.0.0.1:5001/message/sync \ -H 'Content-Type: application/json' \ -d '{"uid":"device-42","limit":20}' ``` 处理完本次返回后调用 `/message/syncack`。该调用推进服务端记录的最新 CMD sync generation;请求 `last_message_seq` 是兼容必填字段,不是设备执行成功证明。设备永久失去该源 Channel 的命令权限时,用相同请求结构调用 `/message/cmd/unbind`。 请求级 `subscribers` 可以做有界的即时目标投递,但不会自动创建 CMD 发现绑定,不能直接承诺重连恢复。在线瞬时命令必须同时设置 `no_persist=1` 和 `sync_once=1`;它没有持久 sequence、离线同步、普通会话或 `msg.offline`。普通在线消息可以使用 `no_persist=1, sync_once=0` 并保留源频道,同样不支持离线恢复。
## 用业务结果闭环 [#用业务结果闭环] ③ 命令携带 `operation_id`、deadline、期望状态与版本。设备按 `operation_id` 幂等执行,再发送独立的持久结果消息。 | 观察结果 | 能确认什么 | | ------------------------ | ---------------- | | SENDACK / `reason=1` | 消息已提交 | | RECV / RECVACK / syncack | 在线投递、接收反馈或游标推进 | | 业务执行回执 | 按业务规则核对目标状态与执行结果 | **核对:** 同一命令重试不重复执行,过期命令按业务规则处理;实际设备状态与结果消息一致。
业务回执与上线核对 每个设备命令携带产品级 `operation_id`、deadline、期望状态和版本。设备先按 `operation_id` 幂等执行,再发送一条独立持久结果消息。SENDACK、RECV、RECVACK 和 `/message/syncack` 分别代表提交、在线写入、接收反馈和游标推进,都不代表风扇已经设置为目标档位。 上线前测试模型取消、重复 delta、Leader 切换、缓存压力、终态重放、设备断电、过期命令、重复执行、乱序业务结果、热点设备群和控制服务限流。继续阅读 [消息](/zh/guide/core-concepts/messages)、[Webhook](/zh/guide/integration/webhooks) 和 [消息收发](/zh/guide/integration/messaging)。
继续阅读[消息收发](/zh/guide/integration/messaging)与[群成员维护](/zh/guide/tutorials/large-groups)。 # 单聊 (/zh/guide/tutorials/direct-chat) 目标:Alice 发给 Bob,Bob 回发,并按所选 SDK 的能力恢复缺失的持久消息。
## 1. 连接两个用户 [#1-连接两个用户] 准备[单节点集群](/zh/server/deployment/docker)和两个独立客户端。可先运行 [Web 双用户示例](/zh/sdk/javascript/quickstart),或用 [Chat Demo](/zh/guide/quick-start/first-message)验证收发。 业务后端负责登录、好友、封禁与 Token 发放。UID 保持稳定,设备类别与凭据一致。保护 WuKongIM HTTP API,按[身份认证](/zh/guide/integration/authentication)实现过期、轮换与撤销。 **核对:** 两端都已连接并可发送。HTTP 示例只在受信业务后端或受保护的测试网络执行。
后端准备测试凭据 业务服务应拥有账号登录、好友关系、封禁和内容策略。为两个测试用户选择稳定 UID;不要使用昵称、连接 ID 或设备 ID 代替 UID。 已运行 [Web 双用户示例](/zh/sdk/javascript/quickstart)时,Alice 和 Bob 的凭据已由 BFF 准备,可以直接进入第 2 步。自行接入时,由受信后端保存测试设备 Token;下例使用 Web 设备类别 `device_flag=1`,客户端须保持一致: ```bash curl -sS http://127.0.0.1:5001/user/token \ -H 'Content-Type: application/json' \ -d '{"uid":"alice","token":"alice-local-only","device_flag":1,"device_level":1}' ``` 为 Bob 保存独立凭据,然后让两个客户端分别使用自己的 UID、设备标识和 Token 建立连接。没有 SDK 集成时,可以先用[内嵌 Chat Demo](/zh/guide/quick-start/first-message)验证两个浏览器会话。
## 2. 向对端 UID 发送 [#2-向对端-uid-发送] Alice 使用 `channel_id=bob`、`channel_type=1`;Bob 回发使用 `channel_id=alice`。内部单聊频道 ID 由服务端归一化。 保留稳定的 `client_msg_no`。结果不确定时,保留同一次逻辑发送的标识,按支持的方式重试。**核对:** 默认持久消息检查 SENDACK 或 HTTP `reason=1`;成功不代表 Bob 已收到或已读。
后端发送:文本格式与完整请求 客户端发送时使用**对端 UID** 作为 `channel_id`,并设置 `channel_type=1`。不要自行拼接或存储服务端内部的单聊频道 ID;服务端会用发送方和接收方 UID 归一化。 受信业务服务也可以用同一语义发送: 本例使用 SDK 内置文本格式,与 Web 示例一致。编码前的 JSON 是: ```json {"type":1,"content":"hello Bob"} ``` 下方 `payload` 是该 JSON 的 UTF-8 Base64。自定义字段和类型需要配套客户端消息解码器,见[自定义消息](/zh/sdk/javascript/advanced/custom-messages)。 ```bash curl -sS http://127.0.0.1:5001/message/send \ -H 'Content-Type: application/json' \ -d '{ "from_uid":"alice", "channel_id":"bob", "channel_type":1, "client_msg_no":"dm-alice-bob-0001", "payload":"eyJ0eXBlIjoxLCJjb250ZW50IjoiaGVsbG8gQm9iIn0=" }' ``` 保留稳定且唯一的 `client_msg_no`。网络结果不明确时,用同一个编号重试同一次逻辑发送,不要换编号制造重复消息。HTTP 返回 `reason=1` 表示持久发送到达 频道持久提交;它不表示 Bob 的每台设备都已收到。
## 3. 接收并回发 [#3-接收并回发] Bob 收到 Alice 的原文且只显示一次,再回发给 Alice;Alice 独立验证接收。发送方成功提示不能代替这一检查。 ① Bob 收到。② Bob 回发。实测 Chat Demo 使用 `quickstart-alice` / `quickstart-bob` 测试 UID,截图不证明对方已读。点击查看原图。 ## 4. 断网后核对恢复 [#4-断网后核对恢复] 先断开 Bob,再由 Alice 发送一条新的持久消息。Bob 重连后,核对缺失消息只显示一次,频道内顺序正确。 | SDK 路径 | 验收方式 | | ------- | -------------------------- | | 完整版 SDK | 同步缺失持久消息,合并在线与恢复结果并去重 | | EasySDK | 验证在线重连;历史、会话和未读恢复由业务后端另行实现 | NoPersist 没有持久历史与离线恢复。`message_seq` 只表示一个频道内的顺序。 ① SYNCED 是恢复的消息。② Sync complete · 1 recovered 是本次实测结果。这是完整版 Web SDK 示例,不表示 EasySDK 自动同步历史。点击查看原图。
后端历史查询与合并规则 在线 Bob 应收到 `RECV` 并在处理后发送 `RECVACK`。再用兼容同步接口验证持久日志;个人频道查询仍使用对端 UID: ```bash curl -sS http://127.0.0.1:5001/channel/messagesync \ -H 'Content-Type: application/json' \ -d '{ "login_uid":"bob", "channel_id":"alice", "channel_type":1, "start_message_seq":0, "limit":20, "pull_mode":1 }' ``` 响应中的 `channel_id` 会映射回 `alice`,`message_seq` 只在这一个 person Channel 内有序。客户端应按消息 ID、`client_msg_no` 和 Channel sequence 合并在线投递与重连同步,允许重复到达。
账号会话与未读检查 ```bash curl -sS http://127.0.0.1:5001/conversation/list \ -H 'Content-Type: application/json' \ -d '{"uid":"bob","limit":20}' ``` Bob 的会话项使用 `channel_id=alice`。`unread` 根据 Bob 在该频道的已读位置和可见消息计算,不是所有设备收到消息的总次数。用户确认当前会话已读后,由受信边界调用: ```bash curl -sS http://127.0.0.1:5001/conversations/clearUnread \ -H 'Content-Type: application/json' \ -d '{"uid":"bob","channel_id":"alice","channel_type":1}' ``` `clearUnread` 会把 Bob 的已读位置(`read_seq`)推进到最新已提交的普通消息。因此操作期间新到达的消息也可能一起被清除红点;这不会向 Alice 证明 Bob 实际阅读了哪些消息。同一 UID 的其他设备在同步后也会看到更新。
多设备与上线场景 1. 用不同 `device_id` 建立 Bob 的第二个 Session。 2. 明确产品的主/从设备冲突策略,不把“同 UID”理解为“只有一条连接”。 3. 断开其中一个设备,再发送一条新的持久消息。 4. 重连后通过 SDK 同步或 `/channel/messagesync` 恢复缺失 sequence。 5. 验证在线投递、离线恢复和 账号未读状态不会被当成同一个完成信号。 上线前还要测试好友解除、黑名单、Token 撤销、断线重试、重复发送、热点单聊分布和 Webhook 重复消费。继续阅读[群聊与超大群](/zh/guide/tutorials/large-groups)或[消息收发](/zh/guide/integration/messaging)。
下一步:[群聊与超大群](/zh/guide/tutorials/large-groups)与[上线检查](/zh/guide/integration/acceptance)。截图版本见 [Web 教程](/zh/sdk/javascript/quickstart)。 # 场景教程 (/zh/guide/tutorials) 选择目标,按教程的步骤核对结果。 当前 WuKongIM HTTP API 没有通用业务鉴权。教程里的 HTTP 管理请求只由受信业务后端或受保护的测试环境执行。 **分别核对:** 消息提交、对端接收、恢复与业务完成。未读更新、推送厂商受理或命令同步游标,都不能代替业务结果。
所有教程共用的成功模型
需要强一致业务结果时,由业务服务使用自己的数据库、幂等键和补偿流程完成。
继续阅读[集成架构](/zh/guide/integration/architecture)与[上线检查](/zh/guide/integration/acceptance)。 # 群聊与超大群 (/zh/guide/tutorials/large-groups) 目标:Alice 向 `team-42` 发消息,Bob、Carol 分别接收,再核对离群后的权限与投递。
## 1. 准备三人测试群 [#1-准备三人测试群] Alice、Bob、Carol 使用业务后端提供的凭据,在独立客户端连接。后端创建稳定群 ID `team-42`,将三人加入普通订阅者列表。 业务系统负责群资料、角色、审批、禁言和成员数据库。**成员变更与发送 HTTP 接口只给受信后端:** 客户端调用业务 API,由后端验证角色后再修改 WuKongIM。 **核对:** 通过已鉴权、分页的 [Manager](/zh/server/operations/manager) 成员查询,确认正好三人。 `reset=1` 替换旧成员列表,仅用于新建测试群或已确认的期望快照。元数据写入、移除、加入和 large 刷新不是一个事务;失败后保留已成功进度,再根据业务快照协调。
创建群:完整后端请求 选择稳定且不可复用的业务群 ID,例如 `team-42`。在受信服务网络中创建 Channel,并用一个小的初始成员集完成验证: ```bash curl -sS http://127.0.0.1:5001/channel \ -H 'Content-Type: application/json' \ -d '{ "channel_id":"team-42", "channel_type":2, "reset":1, "subscribers":["alice","bob","carol"] }' ``` 成功兼容响应是 `{"status":200}`。`channel_type=2` 表示群组 Channel。不要用这个成员列表替代业务群数据库;服务端使用它检查发送权限、同步消息并确定接收人。
## 2. 向群 ID 发送 [#2-向群-id-发送] Alice 使用 `channel_id=team-42`、`channel_type=2`、稳定的 `client_msg_no` 与 SDK 文本格式。发送者需满足当前成员与发送权限。 **核对:** 默认持久消息检查 SENDACK 或 HTTP `reason=1`,确认已持久提交。成功不代表所有成员收到,也不代表会话同步完成。
后端发送与历史查询 发送者必须满足当前群权限与成员策略。客户端 SDK 使用 `channel_id=team-42`、`channel_type=2`;受信服务端示例: 本例使用 SDK 内置文本格式,与 Web 示例一致。编码前的 JSON 是: ```json {"type":1,"content":"hello team"} ``` 下方 `payload` 是该 JSON 的 UTF-8 Base64。自定义字段和类型需要配套客户端消息解码器,见[自定义消息](/zh/sdk/javascript/advanced/custom-messages)。 ```bash curl -sS http://127.0.0.1:5001/message/send \ -H 'Content-Type: application/json' \ -d '{ "from_uid":"alice", "channel_id":"team-42", "channel_type":2, "client_msg_no":"team-42-0001", "payload":"eyJ0eXBlIjoxLCJjb250ZW50IjoiaGVsbG8gdGVhbSJ9" }' ``` `reason=1` 表示这个 Channel 的消息已完成持久提交。它不表示所有成员都在线、所有在线 Session 都写入成功,或者每位用户都已完成 会话目录同步。 Bob 可以同步群日志: ```bash curl -sS http://127.0.0.1:5001/channel/messagesync \ -H 'Content-Type: application/json' \ -d '{ "login_uid":"bob", "channel_id":"team-42", "channel_type":2, "start_message_seq":0, "limit":20, "pull_mode":1 }' ``` 群内 `message_seq` 定义唯一顺序。成员的在线投递、RECVACK、未读数和重连恢复仍是不同观测。
## 3. 分别检查两个接收端 [#3-分别检查两个接收端] | 客户端 | 预期结果 | | ------ | ---------------------------- | | Bob | 收到 `hello team`,只显示一次 | | Carol | 独立收到同一原文,只显示一次 | | 重连的接收端 | 通过完整版 SDK 同步或业务历史能力,恢复缺失持久消息 | `message_seq` 只定义本群顺序。在线投递、RECVACK、未读与恢复分别检查;EasySDK 不自动提供离线历史与会话恢复。 ## 4. 验证移除成员 [#4-验证移除成员] 先提交业务成员决定,再通过受信订阅者接口移除 Carol。保留操作结果并协调部分失败;变更收敛后,按产品策略验证 Carol 后续发送权限与投递行为。 **核对:** 移除不会删除已提交历史或客户端缓存。明确离群用户可见的历史范围,以及再次入群从哪里同步。
移除 Carol:完整后端请求 ```bash curl -sS http://127.0.0.1:5001/channel/subscriber_remove \ -H 'Content-Type: application/json' \ -d '{"channel_id":"team-42","channel_type":2,"subscribers":["carol"]}' ```
添加成员与核对期望名单 新增成员: ```bash curl -sS http://127.0.0.1:5001/channel/subscriber_add \ -H 'Content-Type: application/json' \ -d '{"channel_id":"team-42","channel_type":2,"subscribers":["dave","erin"]}' ``` 移除成员使用 `/channel/subscriber_remove` 和相同请求结构。成员变更会去重,并更新每个用户的频道关系。业务服务仍需保存期望成员列表、版本和操作记录,失败后根据已成功的进度继续核对与补偿。 WuKongIM HTTP API 没有普通订阅者、拒绝列表和临时订阅者的对称读接口。需要严格核对时,使用已鉴权的 Manager `GET /manager/channels/:channel_type/:channel_id/subscribers` 有界分页查询;允许列表虽可由 WuKongIM HTTP API 读取,但旧接口会一次返回完整列表,不适合大群巡检。
离群与再次入群策略 业务系统先提交自己的成员状态,再通过受信调用移除订阅者,并记录可重试任务。成员移除决定之后的发送权限和投递计划,但不会删除既有 Channel 日志,也不会自动清理业务数据库、客户端缓存或历史会话展示策略。 明确产品策略:离群用户能看到哪个历史 sequence、是否删除本地记录、再次入群从哪里同步。这些产品规则不能从“成员行已经删除”自动推导。
进阶:十万成员的有界批次协调 不要把十万 UID 放入一次 `/channel` 或 `subscriber_add` JSON 请求。推荐使用业务侧协调器: * 使用几百到约一千 UID 的有界应用批次,并按实际请求大小、延迟和错误率调节;不要把内部 chunk 默认值当作永久 API 上限。 * 每个请求内部仍会分块和去重,但请求内部各阶段以及跨多个 HTTP 请求都不是一个全局事务。失败时保留已成功进度,再通过期望/实际差异修复。 * `channel.large_group_subscriber_threshold` 默认是 `500`;普通成员变更后,成员数**大于**阈值会刷新 large-group 标记。改变阈值后,要通过受控成员协调和验证确认现有 Channel 状态。 * 大群消息提交后,服务端分批读取成员并查询在线设备,再按目标节点投递;频道消息历史仍只保留一条消息,不会为十万成员各写一份独立日志。 * SENDACK 确认消息已持久提交,不等待所有接收设备完成投递。需要业务“全部处理”语义时,另建业务回执与聚合流程。
进阶:容量验证 上线前至少分别测量: 1. 热点群的发送速率、持久提交耗时与 P99; 2. 分批查询成员、查找在线设备的耗时,以及提交后投递队列的积压; 3. 各节点向客户端写入的耗时、断线比例与重连恢复; 4. 成员变更吞吐、部分失败后的进度保存与恢复; 5. CPU、内存、磁盘、网络、队列深度、拒绝和端到端尾延迟。 不要只用平均 QPS 推导十万成员容量。继续阅读[Channel 核心概念](/zh/guide/core-concepts/channels)、[集群配置](/zh/server/configuration/cluster)和[性能测试工具](/zh/server/tools/wkbench)。
下一步:[Channel 核心概念](/zh/guide/core-concepts/channels)与[上线检查](/zh/guide/integration/acceptance)。实际目标负载使用[性能测试工具](/zh/server/tools/wkbench)测量。 # MQTT 快速开始 (/zh/guide/tutorials/mqtt) 目标:Alice、Bob 各自连接 MQTT,订阅自己的收件箱,再双向发送 SDK 文本格式的消息。检查真实接收内容和稳定服务端消息身份。 默认关闭,需使用包含 MQTT 实现的匹配开发候选。现有正式发布的镜像和安装包不能据此视为支持 MQTT;完整 Linux、故障和负载验收仍未完成。本教程运行在 Node.js;浏览器通过独立的 MQTT WebSocket 监听接入,配置见[部署与排障](/zh/api/client-protocols/mqtt/operations-and-troubleshooting)。 ## 1. 准备单节点集群 [#1-准备单节点集群] 需要 Go 1.25.11、Node.js 20.11 或更高版本、npm,以及包含 MQTT 实现的源码。业务后端、服务端和客户端的职责见[集成架构](/zh/guide/integration/architecture)。以下为回环地址上的开发验证,保留单节点集群和 256 hash slots。 在候选源码根目录执行: ```bash go build -o ./bin/wukongim-mqtt ./cmd/wukongim mkdir -p ./tmp/mqtt-quickstart cp wukongim.toml.example ./tmp/mqtt-quickstart/wukongim.toml WK_NODE_DATA_DIR=./tmp/mqtt-quickstart/data \ WK_MQTT_ENABLE=true \ WK_MQTT_LISTEN_ADDR=127.0.0.1:1883 \ WK_GATEWAY_TOKEN_AUTH_ON=true \ WK_GATEWAY_LISTENERS='[{"name":"tcp-wkproto","network":"tcp","address":"127.0.0.1:5100","transport":"gnet","protocol":"wkproto"}]' \ ./bin/wukongim-mqtt -config ./tmp/mqtt-quickstart/wukongim.toml ``` 保持这个终端运行,等待节点完成启动。检查启动日志无配置错误、1883 正在监听。示例配置的 WuKongIM HTTP API 为 `127.0.0.1:5001`;端口被占用时,修改配置及客户端连接地址。MQTT 全部配置见[配置参考](/zh/server/configuration/reference)。 ## 2. 由后端准备用户凭证 [#2-由后端准备用户凭证] 在另一个终端、源码根目录执行。以下 Token 仅用于受保护的开发验证。WuKongIM HTTP API 没有通用业务鉴权,不能暴露给不可信客户端;生产环境由业务后端验证登录后发放凭证。 ```bash curl -sS http://127.0.0.1:5001/user/token \ -H 'Content-Type: application/json' \ -d '{"uid":"alice","token":"alice-local-only","device_flag":1,"device_level":1}' curl -sS http://127.0.0.1:5001/user/token \ -H 'Content-Type: application/json' \ -d '{"uid":"bob","token":"bob-local-only","device_flag":1,"device_level":1}' ``` 确认两次登记请求成功。客户端使用对应 UID、Token 和 `wk.device_flag="1"`,ClientID 不是密码。详见[认证与 Topic](/zh/api/client-protocols/mqtt/authentication-and-topics)。 ## 3. 安装并运行完整示例 [#3-安装并运行完整示例] 源码附带 `docs-site/examples/mqtt-quickstart/quickstart.mjs`,依赖锁定为 MQTT.js 5.16.0。使用锁文件安装: ```bash cd docs-site/examples/mqtt-quickstart npm ci --no-audit --no-fund MQTT_URL=mqtt://127.0.0.1:1883 \ MQTT_ALICE_TOKEN=alice-local-only \ MQTT_BOB_TOKEN=bob-local-only \ npm start ``` 客户端先安装监听器,然后 CONNECT;用 `protocolVersion=5`,每次运行生成不同 ClientID,`clean=true`、Session Expiry 为 0。双方成功订阅后才发布: | 客户端 | 订阅收件箱 | 发送目标 | | ----- | ------------------------------ | -------------------------------------- | | Alice | `wk/v1/users/YWxpY2U/messages` | Bob 的 `wk/v1/users/Ym9i/messages` | | Bob | `wk/v1/users/Ym9i/messages` | Alice 的 `wk/v1/users/YWxpY2U/messages` | Topic 编码是无填充 base64url。上行 payload 是 `{"type":1,"content":"hello Bob"}` 等 JSON 的 UTF-8 字节,没有额外 MQTT 信封。每条发布带独立、稳定的 `wk.client_msg_no`。 ## 4. 核对双向收发 [#4-核对双向收发] 成功时示例输出 JSON,`passed=true`,`client_version="5.16.0"`,`exchanges` 含 Alice 和 Bob 两个发送方及其 `message_id`、`message_seq` 字符串,然后正常断开退出。 示例分别等待成功 PUBACK 和对方实际 PUBLISH,核对字节、发送方、业务编号与频道类型,不打印 Token 或消息体。数字身份保持字符串,避免 JavaScript 大整数精度损失。成功 PUBACK 表示服务端确认持久提交,不表示用户已读或业务完成。 例子订阅和发布确认最多等待 10 秒,每次接收最多等待 20 秒,整体最多 60 秒;失败以非零状态退出。它是第一条消息验证,不是性能测试。实际产品需要自己的消息处理、去重和重连策略,见[消息契约](/zh/api/client-protocols/mqtt/messages)。 ## 5. 清理和恢复检查 [#5-清理和恢复检查] 示例自动发送正常 DISCONNECT、关闭两条连接;不保留离线会话。验证结束后在服务端终端按 Ctrl+C 停止开发集群。不要删除运行中数据。 再次运行会建立新会话;它不证明离线恢复。后续按[持久会话与 QoS](/zh/api/client-protocols/mqtt/sessions-and-qos)使用稳定 ClientID、非零有效期和 `clean=false`,核对 Session Present。 ## 6. 出错时检查 [#6-出错时检查] * 连接失败:检查监听、版本、UID/Token 与设备类别;此命令使用原始 TCP;浏览器需配置独立的 MQTT WebSocket 监听及 `ws://` 或 `wss://` URL。 * 订阅失败:检查自己的收件箱和 base64url 编码;群订阅需要已有成员资格。 * 收发超时:检查服务端日志、权限、配额、接收端连接和 ACK,保留原业务编号排查未知结果。 详细定位见[部署与排障](/zh/api/client-protocols/mqtt/operations-and-troubleshooting)。跑通后阅读[HTTP / SDK 互通](/zh/api/client-protocols/mqtt/interop)与[遗嘱消息](/zh/api/client-protocols/mqtt/will)。客户端 API 见 [MQTT.js 官方文档](https://github.com/mqttjs/MQTT.js/blob/v5.16.0/README.md)。 # 消息推送 (/zh/guide/tutorials/push) 目标:Alice 离线时收到订单通知,打开应用后恢复已提交的原消息。
WuKongIM 产生离线候选,业务后端调用 APNs、FCM 或厂商通道。SENDACK、Webhook HTTP `200`、厂商受理和用户看到通知分别验证。 ## 1. 登记设备与通知偏好 [#1-登记设备与通知偏好] 业务后端保存 UID、设备、厂商 Token 和授权状态,在登录、刷新、退出或失效回执时更新。 **核对:** 厂商 Token 与 `/user/token` 的 CONNECT 凭据不同。通知权限、免打扰和展示隐私由业务管理。
设备数据:完整字段与更新时机 业务服务至少保存:稳定 UID、设备 ID、平台、厂商 Token、Token 版本、授权状态、语言、静默时段和最近失效原因。`/user/token` 保存的是 CONNECT 凭据,默认 Gateway 会按 UID 与设备类别校验它;它不是厂商推送 Token,当前也没有面向推送工作器的公开 Token 查询接口。 因此,生产推送应以业务数据库为准,并在登录、Token 刷新、退出和厂商失效回执时更新状态。不要把昵称或临时 Session ID 当作推送身份。
## 2. 接收离线回调 [#2-接收离线回调] 启用 `msg.offline`,用受信后端向 Alice 发送普通持久消息;`reason=1` 只证明消息已提交。 **核对:** 离线判断按 UID。Alice 只要还有一条在线路由,就不会进入离线列表;“某台设备离线也推送”需由业务另行计算。 **回调没有内置签名或共享密钥 Header。** 先建立可信网络或受保护代理,再按通知策略过滤发送者、服务身份和未授权用户。
启用回调:完整 TOML 配置 在 `wukongim.toml` 中配置受保护的业务端点: ```toml [webhook] http_addr = "https://events.example.com/wukongim" focus_events = ["msg.notify", "msg.offline"] queue_size = 1024 workers = 16 offline_uid_batch_size = 512 request_timeout = "5s" retry_max_attempts = 3 ``` 当前 Sender 只设置 `Content-Type: application/json`,没有签名或共享密钥 Header。使用 HTTPS、私网或服务网格、mTLS/固定出口身份、请求大小限制和速率限制建立可信边界。完整配置与接收建议见 [Webhook](/zh/guide/integration/webhooks)。
发送订单通知:完整后端请求 先在业务系统中创建受权限约束的通知服务 UID。本例向 Alice 的个人 Channel 发送一条普通持久消息;Payload 是应用消息模型,不是服务端特权类型: ```bash curl -sS http://127.0.0.1:5001/message/send \ -H 'Content-Type: application/json' \ -d '{ "from_uid":"notification-service", "channel_id":"alice", "channel_type":1, "client_msg_no":"order-42-shipped-v1", "payload":"eyJ2ZXJzaW9uIjoxLCJ0eXBlIjoib3JkZXJfdXBkYXRlIiwidGl0bGUiOiJPcmRlciBzaGlwcGVkIiwiYm9keSI6IlRyYWNrIHBhY2thZ2UgaW4gdGhlIGFwcCIsIm9yZGVyX2lkIjoib3JkZXItNDIifQ==" }' ``` `reason=1` 表示消息已达到 Channel quorum commit。保持 `client_msg_no` 稳定,以便不确定结果时安全重试。系统 UID 的权限绕过是可选受信能力,不应替代业务授权、频控和用户通知偏好。
离线候选:Payload、压缩 UID 与适用消息 如果 Alice 和 `notification-service` 在 Presence 解析时都没有在线路由,业务端点会收到类似结果: ```text POST /wukongim?event=msg.offline Content-Type: application/json ``` ```json { "header": {"no_persist": 0, "red_dot": 0, "sync_once": 0}, "message_id": 123456789, "message_idstr": "123456789", "client_msg_no": "order-42-shipped-v1", "message_seq": 8, "from_uid": "notification-service", "channel_id": "", "channel_type": 1, "payload": "eyJ2ZXJzaW9uIjoxLCJ0eXBlIjoib3JkZXJfdXBkYXRlIiwidGl0bGUiOiJPcmRlciBzaGlwcGVkIiwiYm9keSI6IlRyYWNrIHBhY2thZ2UgaW4gdGhlIGFwcCIsIm9yZGVyX2lkIjoib3JkZXItNDIifQ==", "to_uids": ["notification-service", "alice"] } ``` 离线候选在发送者回显抑制之前收集,因此个人 Channel 的 `to_uids` 可能包含 `from_uid`;如果通知服务有在线路由,它就不会出现在列表中。Webhook 中的个人 `channel_id` 是服务端 canonical Channel 身份;业务关系仍使用双方 UID,不能把这个内部值保存为关系主键。接收端还必须支持 `compress: "gzip"` 与 Base64 `compress_to_uids`。`msg.offline` 只针对普通持久消息:`SyncOnce`、请求级 `subscribers` 和瞬态 `NoPersist` 不会产生这个效果。 离线判断粒度是 UID,不是设备。只要 Alice 有一条在线路由,她就不会进入离线 UID 列表;需要“某台设备离线也推送”时,业务服务必须结合自己的设备表、`msg.notify`、客户端回执和产品策略计算目标。
## 3. 先落库,再调用厂商 [#3-先落库再调用厂商] 按 `msg.offline + message_id + uid` 幂等写入业务 Outbox,提交成功后返回 HTTP `200`。后台读取有效厂商 Token,应用通知偏好,再调用厂商并记录结果。 **核对:** 重复回调不重复创建推送任务;厂商失败区分失效 Token 与可重试错误。 WuKongIM Webhook 使用有界内存队列,没有崩溃重放。业务 Outbox 只保护已落库任务;关键通知还需从业务事件或持久消息历史核对与补偿。
接收端与工作器:完整处理顺序 Webhook 接收器应快速完成以下步骤: 1. 验证网络来源和事件白名单,限制请求大小。 2. 展开 `to_uids` 或解压 `compress_to_uids`,再按产品通知策略过滤 `from_uid`、服务身份和未授权用户。 3. 对每个合格 UID,以 `msg.offline + message_id + uid` 为幂等键写入持久 Outbox。 4. 提交成功后返回 HTTP 200。 5. 后台按 UID 查询有效设备 Token,应用免打扰、折叠、语言与隐私规则。 6. 调用推送厂商,分类永久 Token 失效和可重试错误,并保存结果。 Webhook 队列是节点本地内存状态,满队列、进程退出、取消或重试耗尽都可能丢事件,也不会在崩溃后重放。关键推送应能从业务 Outbox、业务事件或持久消息历史补偿,而不是依赖扩大 WuKongIM 内存队列。
## 4. 点击后恢复原消息 [#4-点击后恢复原消息] 推送只携带安全摘要与业务定位键。Alice 打开应用后重新认证,通过 SDK 同步能力或受信后端的 `/channel/messagesync` 读取原消息;EasySDK 的历史恢复由业务另行实现。 **核对:** 通知定位到正确消息,原文只显示一次;用户退出、权限变化或消息已撤销时仍按当前业务规则处理。
恢复与上线核对清单 推送 Payload 只携带安全的展示摘要和业务定位键,不携带完整敏感消息。用户点击通知后,客户端重新认证并从 SDK 同步或 `/channel/messagesync` 读取已提交消息;它不能把厂商通知当作消息真相。 上线前验证:重复 Webhook、Webhook 丢失、压缩 UID、Token 轮换、部分设备在线、厂商限流、静默时段、深链失效、用户退出和消息已撤销等路径。继续阅读 [Webhook](/zh/guide/integration/webhooks) 和 [AI 与 IoT 通信](/zh/guide/tutorials/ai-and-iot)。
继续阅读 [Webhook](/zh/guide/integration/webhooks)与[单聊恢复](/zh/guide/tutorials/direct-chat#4-断网后核对恢复)。 # Android API 参考 (/zh/sdk/android/api-reference) 本页是常用入口速查,不替代各任务页面。入口单例是 `WKIM.getInstance()`。 ## 初始化 [#初始化] | API | 用途 | | ---------------------------- | ------------------ | | `init(Context, uid, token)` | 初始化用户并打开该 UID 的本地库 | | `setDebug(boolean)` | 控制调试日志 | | `setFileCacheDir(String)` | 设置文件缓存目录 | | `getMsgManager()` | 消息管理器 | | `getConnectionManager()` | 连接管理器 | | `getConversationManager()` | 会话管理器 | | `getChannelManager()` | 频道资料管理器 | | `getChannelMembersManager()` | 群成员管理器 | ## `ConnectionManager` [#connectionmanager] | API | 用途 | | ---------------------------------------------- | -------------- | | `addOnGetIpAndPortListener` | 提供连接 IP 或域名和端口 | | `connection()` | 发起连接 | | `disconnect(false)` | 停止连接,保留账号身份 | | `disconnect(true)` | 退出账号并清除 Token | | `addOnConnectionStatusListener(key, listener)` | 监听连接状态 | | `removeOnConnectionStatusListener(key)` | 移除状态监听 | 状态:`fail`、`success`、`kicked`、`syncMsg`、`connecting`、`noNetwork`、`syncCompleted`。 ## `MsgManager` [#msgmanager] | API | 用途 | | -------------------------------------------- | ------------- | | `send(content, channel)` | 发送正文到频道 | | `sendWithOptions(content, channel, options)` | 带高级设置发送 | | `sendMessage(WKMsg)` | 发送或重发已有消息 | | `addOnSendMsgCallback` | 监听本地消息插入 | | `addOnSendMsgAckListener` | 监听服务器发送结果 | | `addOnNewMsgListener` | 监听新消息 | | `addOnRefreshMsgListener` | 监听消息对象变化 | | `getOrSyncHistoryMessages` | 读取本地并按需同步历史消息 | | `addOnSyncChannelMsgListener` | 接入远端频道历史 | | `addOnUploadAttachListener` | 接入媒体上传 | | `registerContentMsg` | 注册自定义正文 | 对应移除方法使用注册时的同一个 key,例如 `removeNewMsgListener`、`removeSendMsgAckListener` 和 `removeRefreshMsgListener`。 ## `ConversationManager` [#conversationmanager] | API | 用途 | | ------------------------------- | --------- | | `getAll(callback)` | 异步读取会话列表 | | `getWithChannel` | 读取一个频道的会话 | | `updateRedDot` | 设置频道未读数 | | `deleteWitchChannel` | 删除会话列表项 | | `addOnRefreshMsgListListener` | 监听会话批量变化 | | `addOnSyncConversationListener` | 接入多设备会话同步 | ## `ChannelManager` [#channelmanager] | API | 用途 | | ----------------------------- | ------------ | | `getChannel` | 读取本地频道资料 | | `fetchChannelInfo` | 请求刷新资料 | | `addOnGetChannelInfoListener` | 接入用户和群资料 API | | `saveOrUpdateChannel` | 保存资料到本地 | | `addOnRefreshChannelInfo` | 监听资料变化 | ## 常用模型 [#常用模型] | 类型 | 说明 | | ---------------------------------- | ---------------------------------- | | `WKChannel` | `channelID`、`channelType`、名称、头像和设置 | | `WKMsg` | 消息身份、正文、序号和发送状态 | | `WKMessageContent` | 消息正文基类 | | `WKTextContent` / `WKImageContent` | 内置文本和图片正文 | | `WKUIConversationMsg` | 聊天列表项与未读数 | | `WKChannelMember` | 群成员资料 | 常用频道类型:`WKChannelType.PERSONAL`、`WKChannelType.GROUP`。发送状态:`WKSendMsgResult.send_loading`、`send_success`、`send_fail`。 # Android 频道管理 (/zh/sdk/android/channels) 频道 `WKChannel` 表示“消息要发给谁”。单聊频道 ID 是对方 UID,群聊频道 ID 是你的业务群 ID。 ## 创建和读取频道 [#创建和读取频道] ```java WKChannel person = new WKChannel("bob", WKChannelType.PERSONAL); WKChannel group = new WKChannel("team-1", WKChannelType.GROUP); WKChannel cached = WKIM.getInstance() .getChannelManager() .getChannel("bob", WKChannelType.PERSONAL); ``` 本地没有资料时,`getChannel` 返回 `null`。用 `channelID + channelType` 作为完整身份,不要只按 ID 缓存。 ## 接入用户和群资料 API [#接入用户和群资料-api] ```java ChannelManager manager = WKIM.getInstance().getChannelManager(); manager.addOnGetChannelInfoListener((channelID, channelType, callback) -> { profileApi.fetch(channelID, channelType, channel -> { callback.onResult(channel); }); return null; }); manager.fetchChannelInfo("bob", WKChannelType.PERSONAL); ``` Provider 可以立即返回已有的 `WKChannel`,也可以像上面一样返回 `null`,稍后通过 callback 交付网络结果。返回对象中的 `channelID`、`channelType` 和 `version` 必须正确。 ## 监听资料变化 [#监听资料变化] ```java manager.addOnRefreshChannelInfo("chat-header", (channel, isEnd) -> { titleView.setText( TextUtils.isEmpty(channel.channelRemark) ? channel.channelName : channel.channelRemark ); loadAvatar(channel.avatar); }); ``` 离开页面时: ```java manager.removeRefreshChannelInfo("chat-header"); ``` ## 群成员 [#群成员] 群成员由 `ChannelMembersManager` 管理。应用通过 `addOnSyncChannelMembers` 接入自己的成员分页接口,再从本地管理器读取成员。大群必须分页,不要一次加载全部成员。 ## 常见问题 [#常见问题] * 只显示频道 ID:配置 `addOnGetChannelInfoListener` 后调用 `fetchChannelInfo`。 * 头像和名称不刷新:确认 Provider 交付了完整 `WKChannel`,并且页面监听 key 没有冲突。 * 单聊和群资料串了:缓存键必须同时包含 `channelID` 和 `channelType`。 # Android 连接管理 (/zh/sdk/android/connection) `ConnectionManager` 管理连接、心跳和重连。应用负责初始化当前用户,并在 SDK 请求时返回可用的网关地址。 ## 初始化当前用户 [#初始化当前用户] ```java WKIM im = WKIM.getInstance(); im.setDebug(false); im.init(applicationContext, uid, token); ``` `init` 会打开这个 UID 对应的本地数据库,因此要传 Application Context,并且只在业务登录完成后调用。切换账号时先退出旧账号,再初始化新 UID。 ## 提供网关地址 [#提供网关地址] 固定地址: ```java im.getConnectionManager().addOnGetIpAndPortListener(callback -> callback.onGetSocketIpAndPort("im.example.com", 5100) ); ``` 动态路由: ```java im.getConnectionManager().addOnGetIpAndPortListener(callback -> gatewayApi.fetch(uid, token, (host, port) -> callback.onGetSocketIpAndPort(host, port) ) ); ``` 不要给 host 添加 URL scheme。业务接口失败时应结束自己的加载状态并按退避策略重试,不能在主线程阻塞等待。 ## 监听完整状态 [#监听完整状态] ```java String key = "app-connection"; private boolean syncInProgress = false; im.getConnectionManager().addOnConnectionStatusListener(key, (status, reason) -> { if (status == WKConnectStatus.connecting) { showConnecting(); } else if (status == WKConnectStatus.syncMsg) { syncInProgress = true; showRestoringMessages(); } else if (status == WKConnectStatus.syncCompleted) { showRestoreCompleted(); } else if (status == WKConnectStatus.success && syncInProgress) { syncInProgress = false; enableSending(); } else if (status == WKConnectStatus.kicked) { syncInProgress = false; requireLoginAgain(reason); } else if (status == WKConnectStatus.noNetwork) { syncInProgress = false; showOffline(); } else if (status == WKConnectStatus.fail) { syncInProgress = false; showConnectionError(reason); } }); im.getConnectionManager().connection(); ``` 监听器回调在主线程。`1.5.5` 会在 `syncMsg` 前先报告一次 `success`,此时还不能发送;只有同步结束后的第二次 `success` 才能打开输入和发送。 ## 断开与退出 [#断开与退出] ```java // 暂停连接,保留当前账号身份 im.getConnectionManager().disconnect(false); // 用户退出账号:停止连接、清除 Token,并关闭当前数据库 im.getConnectionManager().disconnect(true); ``` `disconnect(true)` 是异步收尾。快速切换账号时,应等旧账号的发送操作进入最终状态,再初始化新账号。 移除监听器: ```java im.getConnectionManager().removeOnConnectionStatusListener(key); ``` ## 常见问题 [#常见问题] * 没有任何状态:确认已注册地址监听器,并在 `init` 之后调用 `connection()`。 * 一直连接失败:检查客户端长连接端口,而不是 HTTP API 端口。 * 同一状态回调多次:检查是否使用多个 key 重复注册了相同页面。 # Android 会话管理 (/zh/sdk/android/conversations) `WKUIConversationMsg` 是聊天列表中的一项,包含频道、最后一条消息、时间和未读数。 ## 异步读取聊天列表 [#异步读取聊天列表] ```java WKIM.getInstance().getConversationManager().getAll(conversations -> { adapter.submitList(conversations); }); ``` `getWkMsg()` 返回最后一条消息,`getWkChannel()` 返回本地频道资料,`lastMsgTimestamp` 和 `unreadCount` 可直接用于列表。 ## 监听变化 [#监听变化] ```java ConversationManager manager = WKIM.getInstance().getConversationManager(); manager.addOnRefreshMsgListListener("conversation-list", changedItems -> { mergeByChannel(changedItems); }); manager.addOnDeleteMsgListener("conversation-list", (channelID, channelType) -> { removeRow(channelID, channelType); }); ``` 变化回调只需更新受影响的频道。如果你更看重简单,可以在回调后重新异步读取 `getAll`。 ## 未读数与删除 [#未读数与删除] 用户打开聊天并确认看到消息后,把该频道未读数设为零: ```java manager.updateRedDot("bob", WKChannelType.PERSONAL, 0); ``` 删除一个会话列表项: ```java manager.deleteWitchChannel("bob", WKChannelType.PERSONAL); ``` 这个方法名在当前版本中就是 `deleteWitchChannel`。它删除会话项,不等同于清空该频道的全部消息。 ## 多设备会话同步 [#多设备会话同步] ```java manager.addOnSyncConversationListener((lastMsgSeqs, msgCount, version, callback) -> { conversationApi.sync(lastMsgSeqs, msgCount, version, callback::onBack); }); ``` 业务接口返回 `WKSyncChat`,SDK 保存结果后会通知会话列表。Provider 只做数据转换,不直接操作 Activity 或 Fragment。 页面销毁时移除列表和删除监听器: ```java manager.removeOnRefreshMsgListListener("conversation-list"); manager.removeOnDeleteMsgListener("conversation-list"); ``` # Android (/zh/sdk/android) `WuKongIMAndroidSDK` 是悟空 IM 的完整 Android SDK。它负责长连接、消息收发、本地消息库、会话列表和频道资料,适合需要自行构建聊天界面的应用。 本文档对应 `WuKongIMAndroidSDK 1.5.5`,示例统一使用 Java。 ## 开始前 [#开始前] 你需要准备: * 一个可访问的 WuKongIM 服务; * 两个测试用户,例如 `alice` 和 `bob`; * 由业务服务端返回的 `uid`、`token`、连接 IP 或域名和端口; * 一个 Android 应用工程。 Token 应由你的业务服务端生成并返回给已登录用户。不要在 App 中保存悟空 IM 的管理密钥,也不要让 App 调用管理接口创建 Token。 ## 推荐阅读顺序 [#推荐阅读顺序] 不熟悉“频道”“会话”这些词时,先读[核心概念](/zh/sdk/wukongim/concepts)。只想查方法名时,打开 [API 参考](/zh/sdk/android/api-reference)。 # Android 消息管理 (/zh/sdk/android/messages) 消息由正文 `WKMessageContent` 和目标 `WKChannel` 组成。`MsgManager` 负责本地保存、发送、接收和查询。 ## 发送单聊或群聊消息 [#发送单聊或群聊消息] ```java WKTextContent text = new WKTextContent("你好"); WKChannel person = new WKChannel("bob", WKChannelType.PERSONAL); WKIM.getInstance().getMsgManager().send(text, person); WKChannel group = new WKChannel("team-1", WKChannelType.GROUP); WKIM.getInstance().getMsgManager().send(text, group); ``` `send` 没有返回值。需要立即拿到本地 `WKMsg` 时,监听本地插入: ```java msgManager.addOnSendMsgCallback("chat", message -> { addPendingMessage(message.clientMsgNO, message); }); ``` ## 分别监听新消息和发送结果 [#分别监听新消息和发送结果] ```java msgManager.addOnNewMsgListener("chat", messages -> { for (WKMsg message : messages) { appendIncomingMessage(message); } }); msgManager.addOnSendMsgAckListener("chat", message -> { updateSendState(message.clientMsgNO, message.status); }); ``` 常用发送状态: | 状态 | 含义 | UI 建议 | | -------------------------------------------------- | --------- | -------- | | `WKSendMsgResult.send_loading` | 已创建,正在发送 | 显示发送中 | | `WKSendMsgResult.send_success` | 服务器已接受消息 | 显示已发送 | | `WKSendMsgResult.send_fail` | 本次发送失败 | 显示重试按钮 | | `no_relation` / `black_list` / `not_on_white_list` | 业务关系不允许发送 | 展示可理解的原因 | 重发一个已有消息: ```java msgManager.sendMessage(failedMessage); ``` ## 读取本地或远端历史消息 [#读取本地或远端历史消息] 首次打开聊天页时: ```java msgManager.getOrSyncHistoryMessages( "bob", WKChannelType.PERSONAL, 0, false, 0, // 0:向更早的消息读取 30, 0, new IGetOrSyncHistoryMsgBack() { @Override public void onSyncing() { showLoading(); } @Override public void onResult(List messages) { showMessages(messages); } } ); ``` 如果本地数据不够,这个方法会调用你配置的频道消息同步接口;接入方式见[媒体与历史消息](/zh/sdk/android/advanced/media-and-history)。 页面销毁时移除全部使用过的 key: ```java msgManager.removeSendMsgCallBack("chat"); msgManager.removeNewMsgListener("chat"); msgManager.removeSendMsgAckListener("chat"); ``` # Android 快速开始 (/zh/sdk/android/quickstart) 这一页只做一件事:让两个在线的 Android 客户端互相发送一条文本消息。 ## 前置条件 [#前置条件] * 业务服务端已为 `alice` 和 `bob` 返回各自的 `uid`、`token`、网关地址和端口; * 两个独立的 App 进程或设备; * Android 工程可以访问 JitPack。 ## 1. 安装 [#1-安装] 在依赖仓库中加入 JitPack: ```groovy maven { url 'https://jitpack.io' } ``` 在 App 模块中固定 SDK 版本: ```groovy dependencies { implementation 'com.github.WuKongIM:WuKongIMAndroidSDK:1.5.5' } ``` ## 2. 初始化并提供连接地址 [#2-初始化并提供连接地址] 业务登录成功后,用 Application Context 初始化 SDK: ```java WKIM im = WKIM.getInstance(); im.setDebug(false); im.init(getApplicationContext(), uid, token); im.getConnectionManager().addOnGetIpAndPortListener(callback -> callback.onGetSocketIpAndPort("im.example.com", 5100) ); ``` 如果网关地址来自接口,就在这个监听器中异步请求,成功后再调用 `onGetSocketIpAndPort`。 ## 3. 监听连接和消息 [#3-监听连接和消息] ```java private static final String LISTENER_KEY = "chat-home"; private boolean syncInProgress = false; im.getConnectionManager().addOnConnectionStatusListener( LISTENER_KEY, (status, reason) -> { if (status == WKConnectStatus.syncMsg) { syncInProgress = true; Log.d("WKIM", "restoring messages"); } else if (status == WKConnectStatus.syncCompleted) { Log.d("WKIM", "message restore complete"); } else if (status == WKConnectStatus.success && syncInProgress) { syncInProgress = false; Log.d("WKIM", "connected"); } else if (status == WKConnectStatus.kicked || status == WKConnectStatus.noNetwork) { syncInProgress = false; Log.e("WKIM", "connection interrupted: " + reason); } else if (status == WKConnectStatus.fail) { syncInProgress = false; Log.e("WKIM", "connect failed: " + reason); } } ); im.getMsgManager().addOnNewMsgListener(LISTENER_KEY, messages -> { for (WKMsg message : messages) { if (message.baseContentMsgModel instanceof WKTextContent) { WKTextContent text = (WKTextContent) message.baseContentMsgModel; Log.d("WKIM", message.fromUID + ": " + text.content); } } }); im.getMsgManager().addOnSendMsgAckListener(LISTENER_KEY, message -> { if (message.status == WKSendMsgResult.send_success) { Log.d("WKIM", "message sent: " + message.clientMsgNO); } else { Log.e("WKIM", "message failed: " + message.clientMsgNO); } }); ``` 每个监听器都使用稳定且唯一的 key,页面销毁时再用同一个 key 移除。 ## 4. 连接并发送 [#4-连接并发送] ```java im.getConnectionManager().connection(); ``` `1.5.5` 会在开始同步前和同步结束后各报告一次 `success`。必须等待 `syncMsg`、`syncCompleted` 之后的第二次 `success`,再让 Alice 向 Bob 发送: ```java WKTextContent content = new WKTextContent("你好,Bob"); WKChannel channel = new WKChannel("bob", WKChannelType.PERSONAL); im.getMsgManager().send(content, channel); ``` ## 预期结果 [#预期结果] 1. Alice 和 Bob 都在同步结束后收到最终连接成功状态; 2. Alice 的发送状态监听器收到 `send_success`; 3. Bob 的新消息监听器打印“你好,Bob”。 如果连接失败,检查地址、客户端端口以及 Token 是否属于当前 UID。如果发送没有结果,确认发送发生在连接成功之后,并确认监听器 key 没有被其他页面覆盖。 页面结束时清理: ```java im.getConnectionManager().removeOnConnectionStatusListener(LISTENER_KEY); im.getMsgManager().removeNewMsgListener(LISTENER_KEY); im.getMsgManager().removeSendMsgAckListener(LISTENER_KEY); ``` 下一步阅读[连接管理](/zh/sdk/android/connection)和[消息管理](/zh/sdk/android/messages)。 # Agent 流式回复 (/zh/sdk/easy/agent-streaming) **一条基础消息 + 多次增量事件 = 一个持续更新的聊天气泡。** 使用 `easyjssdk@2.0.5`,先完成 [Web 快速接入](/zh/sdk/easy/javascript/getting-started)。服务端须包含在线 EVENT 投递实现,旧发布镜像需核实。 ## 1. 看懂链路 [#1-看懂链路] | 放在哪里 | 做什么 | | ---------- | ----------------------- | | 前端 SDK | 发问题、收事件、更新气泡 | | 业务后端 / BFF | 发连接凭据、校验权限、提供历史与取消接口 | | Agent 后端 | 持有模型 API Key、调用模型、串行写事件 | ## 2. 跑起来 [#2-跑起来] 先启动支持本功能的 WuKongIM 集群,再运行 Demo: ```bash cd demo/streamdemo npm ci npm run build npm start ``` 打开 `http://127.0.0.1:5175/streamdemo/`,在设置中选择 **真实模型**: | 配置 | 填写 | | ----------- | ------------------------------------------------------ | | 模型 URL | 兼容 Chat Completions 的 `/v1` 或完整 `/chat/completions` 地址 | | API Key | 模型服务密钥 | | 模型名称 | 可留空,尝试 `/models`;不支持发现时手动填写 | | WuKongIM 地址 | 默认 `http://127.0.0.1:5001`,可修改 | Demo 用本地代理处理模型 CORS。**生产环境密钥和 WuKongIM HTTP API 调用留在后端。** [运行说明](https://github.com/WuKongIM/WuKongIM/tree/main/demo/streamdemo) ## 3. 前端:接收 SDK 事件 [#3-前端接收-sdk-事件] ```bash npm install --save-exact easyjssdk@2.0.5 ``` `bootstrap` 由 BFF 发放;两个回调由应用实现,参考 [接收代码](https://github.com/WuKongIM/WuKongIM/blob/main/demo/streamdemo/src/main.ts)。本例用群 Channel `2`,成员包含用户和 Agent。 ```ts import { WKIM, WKIMEvent } from 'easyjssdk'; const im = WKIM.init(bootstrap.websocketUrl, { uid: bootstrap.uid, token: bootstrap.token, deviceFlag: 1, }, { singleton: false, debugLogging: false }); // On exit: use the same callback references. function stop() { im.off(WKIMEvent.Message, upsertMessage); im.off(WKIMEvent.CustomEvent, applyStreamEvent); im.destroy(); } window.addEventListener('pagehide', stop, { once: true }); im.on(WKIMEvent.Message, upsertMessage); im.on(WKIMEvent.CustomEvent, applyStreamEvent); await im.connect(); await im.send(channelId, 2, { type: 1, content: 'Hello' }); ``` | 回调 | 合并规则 | | ------------------ | ------------------------------------------------------ | | `upsertMessage` | 创建基础气泡;事件先到时复用已有气泡 | | `applyStreamEvent` | 先筛选 Channel、Agent UID 和 lane;按 `client_msg_no` 更新同一条回复 | | 去重 / 缺口 | `event_id` 去重;`text_offset` 按 UTF-8 字节合并,缺失部分等待完整快照 | ## 4. 后端:模型增量 → 消息事件 [#4-后端模型增量--消息事件] 以下是**正常流程核心**。`agent` 是已连接的后端 SDK;任务先校验问题来源与成员权限,幂等创建。`chunks` 来自 [模型 SSE 适配器](https://github.com/WuKongIM/WuKongIM/blob/main/demo/streamdemo/src/model.ts),模型 URL、Key 和名称由后端配置。 ```ts const clientMsgNo = job.replyClientMsgNo; // Persist once per reply. const ack = await agent.send(channelId, 2, { type: 1, content: '' }, { clientMsgNo, setting: { stream: true }, }); if (ack.reasonCode !== 1) throw new Error('Base message rejected'); await append('stream.open', { kind: 'text' }); let text = ''; for await (const delta of chunks) { await append('stream.delta', { kind: 'text', delta }); // Serialize writes. text += delta; } await append('stream.finish', { snapshot: { kind: 'text', text } }); ``` `append` 由后端实现:每次调用 [POST /message/event(OpenAPI 详细说明)](/zh/api/product-http/messages/appendMessageEvent),检查 HTTP 与应用层结果;ID 和 Payload 在请求前保存,重试时复用。请求示例: ```json { "channel_id": "room-42", "channel_type": 2, "from_uid": "agent-42", "client_msg_no": "reply-42", "event_id": "reply-42:2", "event_key": "main", "event_type": "stream.delta", "visibility": "public", "payload": { "kind": "text", "delta": "Hello" } } ``` ## 5. 结束与恢复 [#5-结束与恢复] | 场景 | Agent 写入 | UI / 历史 | | --------- | ---------------------------------------- | ------------ | | 正常完成 | `stream.finish` + 完整 `snapshot` | 显示完整回答 | | 用户取消 | 中止模型 → `stream.cancel` → `stream.finish` | 保留部分文本与取消状态 | | 模型失败 | `stream.error` → `stream.finish` | 保留部分文本与失败状态 | | IM 写入结果未知 | 暂停;对账后重试原事件 | 不盲写新的错误或完成事件 | **在线只用 SDK 推送;历史同步用于离线恢复,不轮询,不需要 `/message/eventsync`。** | 必须保留 | 为什么 | | ---------------------- | -------------------------------------------- | | 基础消息持久化、`stream: true` | SENDACK 成功后才能写事件;不使用 NoPersist / SyncOnce | | 每条回复串行写入、稳定事件 ID | 避免乱序与重复追加 | | 完整终态快照、独立写入超时 | 在线事件可能丢失;取消模型后仍需落终态 | | 有界缓冲与完整文本 | 重连期间先缓冲事件;Leader 缓存丢失时重放完整 snapshot 再 finish | | `main` 和 `__finish__` | finish 使用保留 lane,过滤时不要遗漏 | | 后端授权与历史筛选 | private 不在线广播,但 visibility 不代替业务鉴权 | ## 6. 完整实现与验收 [#6-完整实现与验收] 验收顺序:**正常回复 → 重复事件 → 取消 → 模型断流 → 离线重连**。每次核对同一气泡与历史文本一致;fixture 通过后仍需验证实际模型。 接口详情:[消息事件](/zh/api/product-http/messages/appendMessageEvent) · [历史同步](/zh/api/product-http/messages/syncQuickstartChannelMessages) · [JSON-RPC](/zh/api/client-protocols/json-rpc) # 运行官方示例 (/zh/sdk/easy/examples) 先让 Alice 与 Bob 在两个客户端里互发消息,再把对应教程的代码接入应用。以下示例只需要你选择一个平台;两个客户端也可以使用不同平台。 ## 1. 准备开发用单节点集群 [#1-准备开发用单节点集群] 按 [Docker 部署](/zh/server/deployment/docker)启动 WuKongIM 开发用单节点集群,确认 WebSocket 端口 `5200` 和 WuKongIM HTTP API 端口 `5001` 可达。默认使用 256 Hash Slots。保持服务端运行,在受信开发终端检查: ```bash curl -fsS http://127.0.0.1:5001/readyz ``` 就绪请求应成功。开发地址默认是 `ws://127.0.0.1:5200`,仅在 listener 或反向代理配置了 `/ws` 时追加该路径。客户端侧生产连接使用 WSS;管理接口留在可信后端。 ## 2. 准备 Alice 与 Bob [#2-准备-alice-与-bob] **先确定设备类别。** 下方请求以移动端 `0` 为例。Web 改为 `1`,Rust/C#/C++/Python 改为 `2`;两个客户端平台不同,就分别填写各自的值。`alice-token`、`bob-token` 仅用于本机演示。 由受信业务后端为两个用户准备各自的 `uid`、`token` 和 WebSocket 地址。只在本机回环开发环境中,可以由受信终端调用 [`POST /user/token`](/zh/api/product-http/users/setQuickstartUserToken) 建立测试身份。以下以两个原生客户端为例;如果某个用户由 Web example 登录,把该用户的 `device_flag` 改为 `1`: ```bash curl -fsS -X POST http://127.0.0.1:5001/user/token \ -H 'Content-Type: application/json' \ -d '{"uid":"alice","token":"alice-token","device_flag":0,"device_level":1}' curl -fsS -X POST http://127.0.0.1:5001/user/token \ -H 'Content-Type: application/json' \ -d '{"uid":"bob","token":"bob-token","device_flag":0,"device_level":1}' ``` iOS、Android、Flutter 默认 APP `0`,Web 默认 WEB `1`,Rust、C#、C++、Python 默认 PC `2`。默认产品装配会精确校验相同 UID 与设备类别的已存 Token;生产部署仍需保护该接口、实现过期与轮换策略,并单独证明无效、撤销和按业务规则过期的 Token 会被拒绝。 根据客户端运行位置填写 WebSocket 地址: | 客户端 | 本机开发地址 | | ----------------------------------- | ----------------------------------- | | 浏览器、Node.js、macOS App、iOS Simulator | `ws://127.0.0.1:5200` | | Android Emulator | `ws://10.0.2.2:5200` | | 物理手机 | `ws://<开发机局域网 IP>:5200`,并确认防火墙与路由可达 | ## 3. 选择并运行示例 [#3-选择并运行示例] 先准备目标平台的开发工具。下面的源码示例固定在指定版本;应用通过包管理器接入时,使用对应快速接入页的安装命令。 ### iOS [#ios] ```bash git clone https://github.com/WuKongIM/WuKongEasySDK-iOS.git cd WuKongEasySDK-iOS git checkout v1.1.1 swift build -c release cd Examples/WuKongIMExample-Unified ./build.sh macos ./build.sh ios ``` 先启动一个 iOS Simulator,再运行: ```bash ./build.sh ios --run ``` 也可以用 `./build.sh macos --run` 启动 macOS 版本。示例中的 iOS ATS 明文例外只用于本地 `ws://` 开发,不能复制到生产 App。 [iOS 快速接入](/zh/sdk/easy/ios/getting-started) ### Android [#android] ```bash git clone https://github.com/WuKongIM/WuKongEasySDK-Android.git cd WuKongEasySDK-Android git checkout v1.0.5 ./gradlew :example:assembleDebug ./gradlew :example:installDebug ``` 在 API 21+ 设备或模拟器中启动 `example`。Android Emulator 连接宿主机时使用 `ws://10.0.2.2:5200`,不是 `localhost`。需要两个原生身份时使用两台设备或两个独立模拟器;也可以让浏览器 example 作为另一端。 [Android 快速接入](/zh/sdk/easy/android/getting-started) ### Flutter [#flutter] ```bash git clone https://github.com/WuKongIM/WuKongEasySDK-Flutter.git cd WuKongEasySDK-Flutter git checkout v1.1.0 flutter pub get cd example flutter pub get flutter devices flutter run -d ``` 为每个实际发布目标分别运行,不要用 iOS Simulator 结果替代 Android、Web 或桌面端验收。 [Flutter 快速接入](/zh/sdk/easy/flutter/getting-started) ### Web [#javascript] ```bash git clone https://github.com/WuKongIM/WuKongEasySDK-JS.git cd WuKongEasySDK-JS git checkout v2.0.5 npm ci npm run build python3 -m http.server 8080 ``` 打开 `http://127.0.0.1:8080/example/`。用两个隔离的浏览器上下文分别填写 Alice 与 Bob;不要在 Console 中打印 Token 或完整 Payload。 [Web 快速接入](/zh/sdk/easy/javascript/getting-started) ### Rust [#rust] 需要 Rust 1.86+。检出与 crates.io 0.1.0 对应的源码: ```bash git clone --branch v0.1.0 --depth 1 https://github.com/WuKongIM/WuKongEasySDK-Rust.git cd WuKongEasySDK-Rust cargo build --locked --example chat ``` 两个终端分别设置 `WK_WS_URL`、`WK_UID`、`WK_TOKEN`、`WK_PEER_UID`,执行 `cargo run --locked --example chat`。双方连接成功后输入文本,观察接收事件;输入 `/quit` 退出。 [Rust 快速接入](/zh/sdk/easy/rust/getting-started) ### C\# [#csharp] 用 [C# 快速接入](/zh/sdk/easy/csharp/getting-started) 的完整 `Program.cs` 创建 `MyChat`,安装公共 NuGet 1.0.0。两个终端分别设置 `WUKONGIM_WS_URL`、`WUKONGIM_UID`、`WUKONGIM_TOKEN`: ```bash # Alice dotnet run --project MyChat -- bob # Bob dotnet run --project MyChat -- alice ``` 两端显示 `Connected.` 后按 Enter 发送,观察对端的 `Message received.`,完成双向检查后再次按 Enter 退出。 [C# 快速接入](/zh/sdk/easy/csharp/getting-started) ### C++ [#cpp] 按 [C++ 快速接入](/zh/sdk/easy/cpp/getting-started) 用 vcpkg 构建 `my_app`,或用预编译包的 `wukong_chat`。前者设置 `WKIM_URL`、`WKIM_UID`、`WKIM_TOKEN` 后执行 `./build/my_app bob`(Bob 改为 `alice`);后者设置 `WKIM_TOKEN`,执行 `./build/wukong_chat ws://127.0.0.1:5200 alice bob`。 `my_app` 等两端连接后按 Enter 发送,再按 Enter 退出;`wukong_chat` 允许连续输入消息,`/quit` 退出。Windows 可执行文件位于 `build/Release/` 并带 `.exe` 后缀。 [C++ 快速接入](/zh/sdk/easy/cpp/getting-started) ### Python [#python] 在 Python 3.11+ 虚拟环境中运行: ```bash python -m pip install --index-url https://pypi.org/simple "wukong-easy-sdk==0.1.0" git clone --branch v0.1.0 --depth 1 https://github.com/WuKongIM/WuKongEasySDK-Python.git python WuKongEasySDK-Python/examples/chat.py ``` 运行前分别设置 `WKIM_URL`、`WKIM_UID`、`WKIM_TOKEN`、`WKIM_PEER`。双方出现 `Connected` 后输入文本;`/quit` 退出。 [Python 快速接入](/zh/sdk/easy/python/getting-started) ## 4. 检查结果并退出 [#4-检查结果并退出] 1. 两端各自显示连接成功。 2. Alice 发送后看到服务端发送结果;Bob 的消息事件显示 Alice 的消息。 3. Bob 回发,Alice 也能收到。 4. 按平台说明退出或关闭页面,再次进入时没有重复事件或旧连接。 示例界面可以展示测试消息正文,生产日志不要记录 Token 或完整消息。 ## 常见问题 [#常见问题] **无法连接**:先检查 `/readyz`、设备可达地址与端口。Android Emulator 使用 `10.0.2.2`,真机使用开发机局域网地址。 **认证失败**:核对 UID、Token 和 `device_flag`,两端使用不同 UID。 **发送成功但对端没收到**:确认对端已在线、发送目标是对方 UID,且事件监听已注册。SENDACK 不是对端接收证明。 **离线后缺消息**:EasySDK 不补拉历史;需要业务后端恢复或选择完整版 SDK。 ## 下一步 [#下一步] [选择平台接入应用](/zh/sdk/easy)。历史版本、源码与正式包的独立运行结果见[工程验证记录](https://github.com/WuKongIM/WuKongIM/blob/main/docs/superpowers/reports/2026-09-08-easysdk-validation-history.md);实际部署继续检查 WSS、Token 轮换与故障恢复。 # WuKongEasySDK (/zh/sdk/easy) WuKongEasySDK 提供轻量的连接、在线消息和事件 API。你负责业务后端与界面,SDK 负责通过 WebSocket JSON-RPC CONNECT 建立认证连接并收发消息。先选择平台;需要准备服务端和测试账号时,跟随[运行官方示例](/zh/sdk/easy/examples)。 ## 选择平台,发送第一条消息 [#选择平台发送第一条消息] ## 是否适合你的应用 [#是否适合你的应用] | 选择 EasySDK,如果你只需要 | 选择完整版 WuKongIMSDK,如果你还需要 | | ------------------- | ------------------------ | | WebSocket 连接与自动重连 | 本地消息数据库与离线恢复 | | 单聊或群聊 Channel 的在线消息 | 会话列表、未读数与消息同步 | | 发送结果和实时消息事件 | 推送、多设备与完整平台能力 | | 自己维护 UI、持久化和业务回执 | SDK 承担更多客户端消息状态 | EasySDK 不是聊天 UI,也不是完整产品后端。`send` 成功表示服务端返回了发送结果,不表示对方已经收到、展示或处理消息。需要完整能力时,回到 [SDK 选择](/zh/sdk)。 ## 先认识三个概念 [#先认识三个概念] **Channel(频道)** 是消息的目标。单聊时,发送目标是对方 UID;群聊时,目标是后端创建的群 ID。 **Payload(消息内容)** 是应用约定的 JSON,例如 `{"type":1,"content":"你好"}`。SDK 收到消息后,把内容交给你的 UI 或业务处理代码。 **发送结果** 表示服务端返回的处理结果。它与“对方收到”和“对方已读”不同;首次接入要分别观察发送端结果和接收端事件。 ## 准备连接材料 [#准备连接材料] 客户端不应该创建自己的身份或调用 WuKongIM HTTP API 管理接口。用户登录产品后,受信业务后端通过 HTTPS 返回最小连接材料: ```json { "uid": "alice", "token": "short-lived-token", "websocketUrl": "wss://im.example.com/ws" } ``` | 字段 | 所有者与约束 | | -------------- | -------------------------------------------- | | `uid` | 业务后端确认的稳定用户标识;Alice 与 Bob 必须不同 | | `token` | 短期、可撤销,只用于当前身份连接;不能授予 WuKongIM HTTP API 管理权限 | | `websocketUrl` | 后端从部署配置或安全路由结果中选择;生产环境使用 `wss://` | 先完成[认证与 Token](/zh/guide/integration/authentication)。当前默认组合会把 CONNECT Token 与 `/user/token` 保存的相同 UID、设备类别记录精确匹配;部署仍必须保护该接口、实现过期与轮换策略,并证明无效、撤销和按业务规则过期的 Token 会被拒绝。 ## 跑通 Alice 与 Bob [#跑通-alice-与-bob] 各平台遵循同一条最小路径: 1. 为 Alice 和 Bob 分别取得自己的 `uid`、短期 `token` 与 `websocketUrl`; 2. 在两个独立设备、进程或浏览器上下文中按平台教程创建客户端; 3. 两端都观察到连接成功后,Alice 向个人 Channel `bob` 发送文本 JSON Payload; 4. Alice 保存发送结果,Bob 在实时消息事件中核对 `fromUid`、Channel 与 Payload; 5. Bob 向 Alice 回发,验证反向链路; 6. 退出页面或账号,移除监听器并断开连接,确认没有重复事件或后台连接。 这个闭环只验证在线消息。发送确认、实时接收与业务完成是三个不同状态,具体建模见[消息收发](/zh/guide/integration/messaging)。 ## 退出时释放资源 [#退出时释放资源] | 平台 | 事件订阅与取消 | 连接所有者退出 | | ------- | ------------------------------------------ | -------------------------------- | | iOS | `onMessage` / `removeListener` | `disconnect()` | | Android | `addEventListener` / `removeEventListener` | `disconnect()` | | Flutter | `addEventListener` / `removeEventListener` | `disconnect()` + `dispose()` | | Web | `on` / `off` | `destroy()` | | Rust | `subscribe()` / drop receiver | `destroy().await` | | C# | `+=` / `-=` | `await using` / `DisposeAsync()` | | C++ | `on` / `off(listenerId)` | `destroy().get()` | | Python | `on` / `off` | `async with` / `await destroy()` | 连接通常由应用级对象持有。页面只移除自己的订阅,退出账号时再释放连接。各平台的单例、线程和重连约束不同,按对应教程处理。 ## 版本与兼容性 [#版本与兼容性] | 平台 | 本教程版本 | 安装来源 | | ------------------------------------------------------------ | ------- | --------------------------------- | | [iOS](https://github.com/WuKongIM/WuKongEasySDK-iOS) | `1.1.1` | Swift Package Manager / CocoaPods | | [Android](https://github.com/WuKongIM/WuKongEasySDK-Android) | `1.0.5` | Maven Central | | [Flutter](https://github.com/WuKongIM/WuKongEasySDK-Flutter) | `1.1.0` | pub.dev | | [Web](https://github.com/WuKongIM/WuKongEasySDK-JS) | `2.0.5` | npm | | [Rust](https://github.com/WuKongIM/WuKongEasySDK-Rust) | `0.1.0` | crates.io | | [C#](https://github.com/WuKongIM/WuKongEasySDK-CSharp) | `1.0.0` | NuGet | | [C++](https://github.com/WuKongIM/WuKongEasySDK-CPP) | `0.1.0` | vcpkg / ZIP | | [Python](https://github.com/WuKongIM/WuKongEasySDK-Python) | `0.1.0` | PyPI | 教程固定上表版本,方便复现与排障。Web 2.0.5 包含握手失败时的重连修复,见[发布说明](https://github.com/WuKongIM/WuKongEasySDK-JS/releases/tag/v2.0.5)。C++ 使用 WuKongIM 自定义 registry 或匹配系统的预编译包。 本次入门目标是在线双向消息;自动重连不会补拉离线历史,也不等于自动切换到其他节点。集群故障恢复与成员更新还取决于服务端版本。完整历史运行环境、成功与失败范围见[工程验证记录](https://github.com/WuKongIM/WuKongIM/blob/main/docs/superpowers/reports/2026-09-08-easysdk-validation-history.md)。 ## 下一步 [#下一步] [运行官方示例](/zh/sdk/easy/examples) → 选择平台教程 → 完成双向收发 → 按[上线检查](/zh/guide/integration/acceptance)验证实际设备、WSS、Token 轮换和故障恢复。 # Flutter API 参考 (/zh/sdk/flutter/api-reference) 本页是常用入口速查。完整用法和调用顺序请回到对应任务页面。SDK 单例是 `WKIM.shared`。 ## 初始化 [#初始化] | API | 用途 | | ---------------------------------------- | --------------------- | | `setup(Options)` | 配置当前用户并打开该 UID 的本地数据库 | | `Options.newDefault(uid, token, {addr})` | 创建默认配置 | | `Options.getAddr` | 动态提供长连接地址 | | `connectionManager` | 连接与状态 | | `messageManager` | 消息收发与查询 | | `conversationManager` | 会话列表与未读数 | | `channelManager` | 用户和群资料 | ## `WKConnectionManager` [#wkconnectionmanager] | API | 用途 | | -------------------------------------- | --------- | | `connect()` | 发起连接 | | `disconnect(false)` | 断开但保留当前账号 | | `disconnect(true)` | 退出账号 | | `addOnConnectionStatus(key, listener)` | 监听连接状态 | | `removeOnConnectionStatus(key)` | 移除状态监听 | 状态:`fail`、`success`、`kicked`、`syncMsg`、`connecting`、`noNetwork`、`syncCompleted`。 ## `WKMessageManager` [#wkmessagemanager] | API | 用途 | | ------------------------------------------- | --------------- | | `sendWithOption(content, channel, options)` | 发送并等待本地处理完成 | | `sendMessage(content, channel)` | 使用默认选项发送 | | `addOnMsgInsertedListener` | 监听本地消息插入;单一全局回调 | | `addOnRefreshMsgListener(key, listener)` | 监听消息对象和发送状态变化 | | `addOnNewMsgListener(key, listener)` | 监听收到的新消息 | | `getOrSyncHistoryMessages` | 读取本地并按需请求远端历史 | | `addOnSyncChannelMsgListener` | 接入远端历史 API | | `addOnUploadAttachmentListener` | 接入媒体上传 | | `registerMsgContent` | 注册自定义正文 | 使用同一个 key 调用 `removeOnRefreshMsgListener` 和 `removeNewMsgListener` 移除页面监听。 ## `WKConversationManager` [#wkconversationmanager] | API | 用途 | | --------------------------------------------- | ------------- | | `getAll()` | 读取全部本地会话 | | `getAllUnreadCount()` | 读取总未读数 | | `getWithChannel(channelID, channelType)` | 读取一个频道的会话 | | `updateRedDot(channelID, channelType, count)` | 更新未读数 | | `deleteMsg(channelID, channelType)` | 删除会话列表项 | | `addOnRefreshMsgListListener(key, listener)` | 监听会话批量变化 | | `addOnSyncConversationListener` | 接入多设备会话同步 API | ## `WKChannelManager` [#wkchannelmanager] | API | 用途 | | ------------------------------------------ | ------------ | | `getChannel(channelID, channelType)` | 读取本地频道资料 | | `fetchChannelInfo(channelID, channelType)` | 请求刷新资料 | | `addOnGetChannelListener` | 接入用户和群资料 API | | `addOrUpdateChannel` | 保存资料到本地 | | `addOnRefreshListener(key, listener)` | 监听资料变化 | ## 常用模型 [#常用模型] | 类型 | 说明 | | ----------------------------------------- | ------------------------------------ | | `WKChannel` | `channelID` 和 `channelType` 共同确定聊天目标 | | `WKMsg` | 消息身份、正文、序号和发送状态 | | `WKMessageContent` | 消息正文基类 | | `WKTextContent` / `WKImageContent` | 内置文本和图片正文 | | `WKUIConversationMsg` | 会话列表项和未读数 | | `WKSyncConversation` / `WKSyncChannelMsg` | 业务 API 返回给 SDK 的同步结果 | 常用频道类型:`WKChannelType.personal`、`WKChannelType.group`。发送状态:`WKSendMsgResult.sendLoading`、`sendSuccess`、`sendFail`。 # Flutter 频道管理 (/zh/sdk/flutter/channels) 频道 `WKChannel` 表示“消息要发给谁”。单聊频道 ID 是对方 UID,群聊频道 ID 是你的业务群 ID。 ## 创建和读取频道 [#创建和读取频道] ```dart final person = WKChannel('bob', WKChannelType.personal); final group = WKChannel('team-1', WKChannelType.group); final cached = await im.channelManager.getChannel( person.channelID, person.channelType, ); ``` 本地没有资料时返回 `null`。缓存身份必须同时包含 `channelID` 和 `channelType`。 ## 接入用户和群资料 API [#接入用户和群资料-api] ```dart im.channelManager.addOnGetChannelListener( (channelID, channelType, complete) async { final profile = await profileApi.get(channelID, channelType); final channel = WKChannel(channelID, channelType) ..channelName = profile.name ..channelRemark = profile.remark ..avatar = profile.avatar ..version = profile.version; complete(channel); }, ); im.channelManager.fetchChannelInfo('bob', WKChannelType.personal); ``` Provider 必须回传完整的频道身份。它是单一全局槽位,应在应用层安装一次。 ## 监听变化 [#监听变化] ```dart im.channelManager.addOnRefreshListener('chat-header', (channel) { final title = channel.channelRemark.isNotEmpty ? channel.channelRemark : channel.channelName; updateHeader(title, channel.avatar); }); ``` 离开页面时调用 `removeOnRefreshListener('chat-header')`。 ## 群成员 [#群成员] `channelMemberManager` 提供 `getMembers`、`getMember` 和 `saveOrUpdateList`。应用从业务服务端分页取得成员后写入管理器,再通过 keyed listener 更新 UI。大群必须分页,不要一次加载全部成员。 ## 常见问题 [#常见问题] * 会话只有 ID:配置资料 Provider 后调用 `fetchChannelInfo`。 * 名称不刷新:确认 Provider 调用了 `complete`,且页面监听 key 没有冲突。 * 单聊与群资料串了:任何缓存键都必须包含频道类型。 # Flutter 连接管理 (/zh/sdk/flutter/connection) Flutter SDK 的连接由 `WKConnectionManager` 管理。应用先用 `WKIM.shared.setup` 打开当前用户的本地库,再提供网关地址并连接。 ## 固定地址与动态地址 [#固定地址与动态地址] 固定 `host:port`: ```dart final options = Options.newDefault(uid, token, addr: 'im.example.com:5100') ..debug = false ..deviceFlag = 0; await WKIM.shared.setup(options); ``` 每次连接前动态获取地址: ```dart final options = Options.newDefault(uid, token) ..debug = false ..getAddr = (complete) async { final route = await gatewayApi.getAddress(); complete('${route.host}:${route.port}'); }; await WKIM.shared.setup(options); ``` `getAddr` 必须最终调用 `complete`。当前地址解析按冒号分割,因此传普通 DNS/IPv4 `host:port`,不要带 scheme。 ## 理解连接状态顺序 [#理解连接状态顺序] ```dart im.connectionManager.addOnConnectionStatus( 'app-connection', (status, reasonCode, info) { if (status == WKConnectStatus.connecting) { showConnecting(); } else if (status == WKConnectStatus.success) { showRestoringConversations(); } else if (status == WKConnectStatus.syncMsg) { showRestoringConversations(); } else if (status == WKConnectStatus.syncCompleted) { enableSending(); } else if (status == WKConnectStatus.kicked) { requireLoginAgain(); } else if (status == WKConnectStatus.noNetwork) { showOffline(); } else if (status == WKConnectStatus.fail && reasonCode != null) { showConnectionError(reasonCode); } }, ); ``` `connect()` 内部先调用 `disconnect(false)`,因此开始时可能收到一个没有 `reasonCode` 的 `fail`。它是本地清理状态,不是服务端拒绝。正常连接随后是 `connecting → success → syncMsg → syncCompleted`。 会话 Provider 必须结束回调,否则状态会停在 `syncMsg`。正式应用应从业务服务端返回完整的 `WKSyncConversation`。 ## 断开与退出 [#断开与退出] ```dart // 停止连接,保留当前用户配置 im.connectionManager.disconnect(false); // 用户退出:清空 UID/Token,标记发送中消息失败并关闭本地库 im.connectionManager.disconnect(true); ``` 移除状态监听器: ```dart im.connectionManager.removeOnConnectionStatus('app-connection'); ``` ## 常见问题 [#常见问题] * 停在 `syncMsg`:会话同步 Provider 没有调用完成函数。 * 地址越界异常:地址不是简单的 `host:port`。 * 页面回调多次:同一功能用了多个 key 注册,或销毁时没有移除。 # Flutter 会话管理 (/zh/sdk/flutter/conversations) `WKUIConversationMsg` 是聊天列表中的一项,包含频道、最后一条消息、时间和未读数。数据保存在当前 UID 的 sqflite 数据库中。 ## 读取列表 [#读取列表] ```dart final conversations = await im.conversationManager.getAll(); setState(() => items = conversations); ``` 最后消息通过 `await item.getWkMsg()` 读取,频道资料通过 `await item.getWkChannel()` 读取。 ## 监听变化 [#监听变化] ```dart im.conversationManager.addOnRefreshMsgListListener( 'conversation-list', (changedItems) => mergeByChannel(changedItems), ); im.conversationManager.addOnDeleteMsgListener( 'conversation-list', (channelID, channelType) => removeRow(channelID, channelType), ); ``` 如果列表不大,也可以在变化后重新调用 `getAll()`,逻辑更简单。 ## 未读数和删除 [#未读数和删除] ```dart await im.conversationManager.updateRedDot( 'bob', WKChannelType.personal, 0, ); final total = await im.conversationManager.getAllUnreadCount(); await im.conversationManager.deleteMsg('bob', WKChannelType.personal); ``` 删除会话列表项不等同于清空该频道的全部消息。 ## 接入服务端同步 [#接入服务端同步] ```dart im.conversationManager.addOnSyncConversationListener( (lastMsgSeqs, msgCount, version, complete) async { final result = await conversationApi.sync( lastMsgSeqs: lastMsgSeqs, msgCount: msgCount, version: version, ); complete(toWKSyncConversation(result)); }, ); ``` 这个 Provider 是单一全局槽位,没有按页面移除的方法,应通过应用级服务安装一次。所有成功、空数据和错误分支都要结束自己的请求;否则连接会停在 `syncMsg`。 页面销毁时移除 keyed listener。 # Flutter (/zh/sdk/flutter) `wukongimfluttersdk` 是悟空 IM 的完整 Flutter SDK。它负责长连接、消息收发、sqflite 本地数据、会话列表和频道资料,适合需要自行构建聊天界面的 Flutter 应用。 本文档对应 `wukongimfluttersdk 1.7.9`,示例统一使用 Dart。该包依赖 `dart:io` 和 sqflite,不适用于 Flutter Web。 ## 开始前 [#开始前] 你需要准备: * 一个可访问的 WuKongIM 服务; * 两个测试用户,例如 `alice` 和 `bob`; * 由业务服务端返回的 `uid`、`token` 和 `host:port`; * 一个已完成 Android 或 iOS 原生配置的 Flutter 工程。 Token 应由你的业务服务端生成并返回给已登录用户。不要在 App 中保存悟空 IM 的管理密钥,也不要让 App 调用管理接口创建 Token。 ## 推荐阅读顺序 [#推荐阅读顺序] 不熟悉“频道”“会话”这些词时,先读[核心概念](/zh/sdk/wukongim/concepts)。只想查方法名时,打开 [API 参考](/zh/sdk/flutter/api-reference)。 # Flutter 消息管理 (/zh/sdk/flutter/messages) 消息由正文 `WKMessageContent` 和目标 `WKChannel` 组成。`WKMessageManager` 负责本地保存、发送、接收和查询。 ## 发送单聊或群聊消息 [#发送单聊或群聊消息] ```dart await im.messageManager.sendWithOption( WKTextContent('你好'), WKChannel('bob', WKChannelType.personal), WKSendOptions(), ); await im.messageManager.sendWithOption( WKTextContent('大家好'), WKChannel('team-1', WKChannelType.group), WKSendOptions(), ); ``` 推荐直接等待 `sendWithOption`。当前 `sendMessage` 虽然声明为 async,但不会等待内部发送流程。 ## 监听本地插入、发送状态和新消息 [#监听本地插入发送状态和新消息] ```dart // 单一全局回调,没有 key 和移除方法;通过应用级 dispatcher 安装一次。 im.messageManager.addOnMsgInsertedListener((message) { addPendingMessage(message.clientMsgNO, message); }); im.messageManager.addOnRefreshMsgListener('chat', (message) { updateSendState(message.clientMsgNO, message.status); }); im.messageManager.addOnNewMsgListener('chat', (messages) { appendIncomingMessages(messages); }); ``` 发送状态: | 状态 | 含义 | UI 建议 | | --------------------------------------------- | --------- | ------ | | `WKSendMsgResult.sendLoading` | 已保存,正在发送 | 显示发送中 | | `WKSendMsgResult.sendSuccess` | 服务器已接受消息 | 显示已发送 | | `WKSendMsgResult.sendFail` | 本次发送失败 | 显示重试按钮 | | `noRelation` / `blackList` / `notOnWhiteList` | 业务关系不允许发送 | 展示清晰原因 | 已有失败消息可以再次交给连接管理器发送,但产品代码应先确认它仍属于当前账号和频道。 ## 读取消息 [#读取消息] 首次打开聊天页时: ```dart im.messageManager.getOrSyncHistoryMessages( 'bob', WKChannelType.personal, 0, false, 0, // 向更早的消息读取 30, 0, (messages) => showMessages(messages), () => showSyncing(), ); ``` 本地数据不足时会调用远端历史 Provider,接入方式见[媒体与历史消息](/zh/sdk/flutter/advanced/media-and-history)。 页面销毁时调用 `removeOnRefreshMsgListener('chat')` 和 `removeNewMsgListener('chat')`。 # Flutter 快速开始 (/zh/sdk/flutter/quickstart) 这一页只做一件事:让两个在线 Flutter 客户端互相发送一条文本消息。 ## 前置条件 [#前置条件] * 业务服务端已为 `alice` 和 `bob` 返回各自的 `uid`、`token` 和 `host:port`; * 两个独立的 App 进程或设备。`WKIM.shared` 是进程单例; * Flutter 工程已能在目标设备运行。 ## 1. 安装 [#1-安装] 在 `pubspec.yaml` 中固定版本: ```yaml dependencies: wukongimfluttersdk: 1.7.9 ``` 然后执行: ```bash flutter pub get ``` ## 2. 初始化 [#2-初始化] ```dart import 'package:flutter/widgets.dart'; import 'package:wukongimfluttersdk/common/options.dart'; import 'package:wukongimfluttersdk/entity/channel.dart'; import 'package:wukongimfluttersdk/entity/conversation.dart'; import 'package:wukongimfluttersdk/entity/msg.dart'; import 'package:wukongimfluttersdk/model/wk_text_content.dart'; import 'package:wukongimfluttersdk/type/const.dart'; import 'package:wukongimfluttersdk/wkim.dart'; WidgetsFlutterBinding.ensureInitialized(); final im = WKIM.shared; final options = Options.newDefault( bootstrap.uid, bootstrap.token, addr: bootstrap.address, // 例如 im.example.com:5100 ) ..debug = false ..deviceFlag = 0; final initialized = await im.setup(options); if (!initialized) { throw StateError('WuKongIM local database initialization failed'); } ``` 地址不能带 `tcp://`、`http://` 或 `https://`。 ## 3. 提供会话同步并监听状态 [#3-提供会话同步并监听状态] 连接成功后 SDK 会先同步最近会话。即使只测试全新账号,也要结束这个同步步骤: ```dart im.conversationManager.addOnSyncConversationListener( (lastMsgSeqs, msgCount, version, complete) async { // 正式应用应在这里调用业务服务端并返回完整 WKSyncConversation。 final empty = WKSyncConversation() ..uid = bootstrap.uid ..conversations = []; complete(empty); }, ); const listenerKey = 'chat-home'; im.connectionManager.addOnConnectionStatus( listenerKey, (status, reasonCode, info) { if (status == WKConnectStatus.syncCompleted) { print('WuKongIM ready'); } else if (status == WKConnectStatus.fail && reasonCode != null) { print('connect failed: $reasonCode'); } else if (status == WKConnectStatus.kicked) { print('this account was signed in elsewhere'); } }, ); ``` `WKConnectStatus.success` 表示连接协议成功,随后还会出现 `syncMsg` 和 `syncCompleted`。最简单的发送门槛是等待 `syncCompleted`。 ## 4. 监听消息并连接 [#4-监听消息并连接] ```dart im.messageManager.addOnNewMsgListener(listenerKey, (messages) { for (final message in messages) { final content = message.messageContent; if (content is WKTextContent) { print('${message.fromUID}: ${content.content}'); } } }); im.messageManager.addOnRefreshMsgListener(listenerKey, (message) { if (message.status == WKSendMsgResult.sendSuccess) { print('message sent: ${message.clientMsgNO}'); } else if (message.status == WKSendMsgResult.sendFail) { print('message failed: ${message.clientMsgNO}'); } }); im.connectionManager.connect(); ``` ## 5. 发送文本 [#5-发送文本] 收到 `syncCompleted` 后,让 Alice 向 Bob 发送: ```dart await im.messageManager.sendWithOption( WKTextContent('你好,Bob'), WKChannel('bob', WKChannelType.personal), WKSendOptions(), ); ``` 使用 `sendWithOption` 并等待它完成,可以确保本地保存步骤完成;服务器结果仍来自消息刷新监听器。 ## 预期结果 [#预期结果] 1. Alice 和 Bob 都到达 `syncCompleted`; 2. Alice 的消息状态变成 `sendSuccess`; 3. Bob 的新消息监听器打印“你好,Bob”。 页面结束时清理: ```dart im.connectionManager.removeOnConnectionStatus(listenerKey); im.messageManager.removeNewMsgListener(listenerKey); im.messageManager.removeOnRefreshMsgListener(listenerKey); ``` 下一步阅读[连接管理](/zh/sdk/flutter/connection)和[消息管理](/zh/sdk/flutter/messages)。 # HarmonyOS API 参考 (/zh/sdk/harmonyos/api-reference) 本页是常用入口速查。完整用法和调用顺序请回到对应任务页面。SDK 单例是 `WKIM.shared`。 ## 导入与初始化 [#导入与初始化] 包根只导出 `WKIM`: ```typescript import { WKIM } from '@wukong/wkim' ``` 模型和常量从 `@wukong/wkim/src/main/ets/entity/Bean` 导入,正文从 `@wukong/wkim/src/main/ets/model/...` 导入。 | API | 用途 | | -------------------------------------- | ------------------- | | `init(uid, token, address?, context?)` | 配置用户并打开该 UID 的本地数据库 | | `config.provider` | 应用提供的地址、同步、资料和上传接口 | | `connectionManager()` | 连接与状态 | | `messageManager()` | 消息收发与查询 | | `conversationManager()` | 会话列表与未读数 | | `channelManager()` | 用户和群资料 | | `channelMemberManager()` | 群成员资料 | ## 常用 Provider [#常用-provider] | 字段 | 用途 | | ------------------------------- | ---------------- | | `connectAddrCallback` | 动态返回 `host:port` | | `syncConversationCallback` | 同步会话、最近消息和未读数 | | `syncMessageCallback` | 同步一个频道的历史消息 | | `channelInfoCallback` | 获取用户或群资料 | | `channelMemberCallback` | 增量同步群成员 | | `channelMemberWithPageCallback` | 分页或搜索群成员 | | `uploadAttachmentCallback` | 上传媒体并回填 URL | Provider 是进程全局字段,再次赋值会覆盖旧实现。 ## `ConnectionManager` [#connectionmanager] | API | 用途 | | --------------------------------------- | ------------ | | `connection()` | 发起连接 | | `disConnection(false)` | 断开但保留当前账号 | | `disConnection(true)` | 退出账号并关闭本地数据库 | | `addConnectStatusListener(listener)` | 监听连接状态 | | `removeConnectStatusListener(listener)` | 用同一函数对象移除监听 | 状态:`fail`、`success`、`kicked`、`syncing`、`connecting`、`noNetwork`、`syncCompleted`。 ## `MessageManager` [#messagemanager] | API | 用途 | | ------------------------------------------- | ------------- | | `send(content, channel)` | 使用默认选项发送 | | `sendWithOption(content, channel, options)` | 使用自定义选项发送 | | `addInsertedListener` | 监听本地插入;单一全局回调 | | `addSendStatusListener` | 监听服务端发送结果 | | `addNewMsgListener` | 监听新消息 | | `addRefreshListener` | 监听消息对象变化 | | `getOrSyncHistoryMessages` | 读取本地并按需同步历史 | | `registerMsgContent` | 注册自定义正文 | 发送状态:`WKSendMsgResult.loading`、`success`、`fail`,以及关系限制 `noRelation`、`blackList`、`notOnWhiteList`。 ## `ConversationManager` [#conversationmanager] | API | 用途 | | -------------------- | --------- | | `all()` | 读取全部本地会话 | | `getWithChannel` | 读取一个频道的会话 | | `updateRedDot` | 更新未读数 | | `clearAllRedDot` | 清除全部未读数 | | `delete` | 删除本地会话列表项 | | `addRefreshListener` | 监听会话变化 | | `addDeletedListener` | 监听会话删除 | ## 频道与成员 Manager [#频道与成员-manager] | API | 用途 | | -------------------------------------------- | -------- | | `channelManager().getChannel` | 读取本地频道资料 | | `channelManager().fetchChannelInfo` | 请求刷新频道资料 | | `channelManager().addRefreshListener` | 监听资料变化 | | `channelMemberManager().sync` | 增量同步成员 | | `channelMemberManager().getMember` | 读取一个成员 | | `channelMemberManager().getWithPageOrSearch` | 分页或搜索成员 | ## 常用模型 [#常用模型] | 类型 | 说明 | | ----------------------------------------------------- | ------------------------------------ | | `WKChannel` | `channelId` 和 `channelType` 共同确定聊天目标 | | `WKMsg` | 消息身份、正文、序号和发送状态 | | `WKMessageContent` | 消息正文基类 | | `WKTextContent` / `WKImageContent` | 内置文本和图片正文 | | `WKConversation` | 会话列表项和未读数 | | `WKSyncConversation` / `WKSyncChannelMsg` | 业务 API 返回给 SDK 的同步结果 | | `WKSendOptions` / `ChannelMsgOptions` / `SyncOptions` | 发送、读取历史和远端同步选项 | 常用频道类型:`WKChannelType.personal`、`WKChannelType.group`。 # HarmonyOS 频道与资料 (/zh/sdk/harmonyos/channels) 频道表示“消息要发给谁”。`channelId` 和 `channelType` 必须一起使用:个人频道的 ID 通常是对方 UID,群频道的 ID 是群 ID。 ```typescript const personal = new WKChannel('bob', WKChannelType.personal) const group = new WKChannel('team-1', WKChannelType.group) ``` 创建 `WKChannel` 不会创建用户或群组,也不会绕过服务端权限。 ## 提供用户和群资料 [#提供用户和群资料] 在应用初始化时安装资料 Provider: ```typescript WKIM.shared.config.provider.channelInfoCallback = async ( channelId: string, channelType: number ): Promise => { const profile = await channelApi.get(channelId, channelType) if (profile === undefined) return undefined const channel = new WKChannel(channelId, channelType) channel.channelName = profile.name channel.channelRemark = profile.remark ?? '' channel.avatar = profile.avatar ?? '' return channel } ``` 需要资料时先读取本地缓存,不存在或需要刷新时再发起请求: ```typescript const manager = WKIM.shared.channelManager() const local = manager.getChannel('team-1', WKChannelType.group) if (local === undefined) { manager.fetchChannelInfo('team-1', WKChannelType.group) } ``` 监听刷新: ```typescript const refreshListener = (channel: WKChannel) => { updateTitleAndAvatar(channel) } manager.addRefreshListener(refreshListener) // 页面销毁时 manager.removeRefreshListener(refreshListener) ``` ## 群成员 [#群成员] 成员资料由 `channelMemberManager()` 管理。增量同步 Provider 接收当前最大版本: ```typescript WKIM.shared.config.provider.channelMemberCallback = async ( channelId: string, channelType: number, maxVersion: number ): Promise => { return memberApi.sync(channelId, channelType, maxVersion) } WKIM.shared.channelMemberManager().sync( 'team-1', WKChannelType.group ) ``` 读取成员: ```typescript const member = WKIM.shared.channelMemberManager().getMember( 'team-1', WKChannelType.group, 'alice' ) ``` 大群不要一次返回全部成员。使用 `channelMemberWithPageCallback` 和 `getWithPageOrSearch` 做服务端分页或搜索,并限制单页数量。 # HarmonyOS 连接管理 (/zh/sdk/harmonyos/connection) HarmonyOS SDK 通过 `WKIM.shared.connectionManager()` 管理长连接。初始化只配置身份和本地数据库;调用 `connection()` 才会真正连接。 ## 固定地址 [#固定地址] 登录接口直接返回网关地址时,把它传给 `init`: ```typescript await WKIM.shared.init( bootstrap.uid, bootstrap.token, `${bootstrap.host}:${bootstrap.port}`, appContext ) ``` 地址解析使用冒号分割,请传普通 DNS 或 IPv4 `host:port`,不要传 URI 或 IPv6 literal。 ## 动态获取地址 [#动态获取地址] 需要在每次连接前路由时,不传 `address`,改为设置 Provider: ```typescript await WKIM.shared.init(uid, token, undefined, appContext) WKIM.shared.config.provider.connectAddrCallback = async (): Promise => { const route = await gatewayApi.getRoute() return `${route.host}:${route.port}` } ``` 获取失败时抛出错误,SDK 会进入 `fail`,不要返回空字符串。 ## 连接状态 [#连接状态] ```typescript const connectionListener = (status: number, reasonCode?: number) => { if (status === WKConnectStatus.connecting) { showConnecting() } else if (status === WKConnectStatus.success) { showRestoringConversations() } else if (status === WKConnectStatus.syncing) { showRestoringConversations() } else if (status === WKConnectStatus.syncCompleted) { enableChat() } else if (status === WKConnectStatus.noNetwork) { showOffline() } else if (status === WKConnectStatus.kicked) { requireLoginAgain() } else if (status === WKConnectStatus.fail) { showConnectionError(reasonCode) } } WKIM.shared.connectionManager().addConnectStatusListener(connectionListener) WKIM.shared.connectionManager().connection() ``` 正常顺序是 `connecting → success → syncing → syncCompleted`。`success` 只表示连接和身份校验通过;`syncCompleted` 才表示最近会话同步步骤已经结束。 务必在连接前设置 `syncConversationCallback`。否则状态虽然仍可能到达 `syncCompleted`,但离线会话数据不会被补齐。 ## 断开和退出 [#断开和退出] ```typescript // 暂时断开,保留当前身份和本地数据库 WKIM.shared.connectionManager().disConnection(false) // 用户退出,清空身份并关闭本地数据库 WKIM.shared.connectionManager().disConnection(true) ``` 移除监听时传入注册时的同一函数: ```typescript WKIM.shared.connectionManager().removeConnectStatusListener(connectionListener) ``` `WKIM.shared` 和 Manager 都是进程单例。把初始化、Provider 和全局监听集中在一个应用级 IM 服务中,页面只订阅该服务暴露的状态。 # HarmonyOS 会话管理 (/zh/sdk/harmonyos/conversations) 会话是聊天列表中的一项:它保存目标频道、最后一条消息、时间和未读数。会话不是连接,也不是消息正文。 ## 先接入会话同步 [#先接入会话同步] 连接成功后 SDK 会调用 `syncConversationCallback`,把其他设备和离线期间的会话补到本地: ```typescript WKIM.shared.config.provider.syncConversationCallback = async ( lastMsgSeqs: string, msgCount: number, version: number ): Promise => { const response = await conversationApi.sync({ lastMsgSeqs, msgCount, version }) return toWKSyncConversation(response) } ``` 返回对象需要包含当前 UID、会话数组,以及每项的频道、最后消息序号、最后客户端编号、未读数、时间、版本和最近消息。不要给已有数据的账号返回空数组。 ## 读取和监听聊天列表 [#读取和监听聊天列表] ```typescript const manager = WKIM.shared.conversationManager() const initial = manager.all() ?? [] renderConversations(initial) const refreshListener = (changed: WKConversation[]) => { mergeConversations(changed) } const deletedListener = (channelId: string, channelType: number) => { removeConversation(channelId, channelType) } manager.addRefreshListener(refreshListener) manager.addDeletedListener(deletedListener) ``` `addRefreshListener` 返回变化项,不保证每次都是完整列表。用 `channelId + channelType` 合并到现有列表,再按 `lastMsgTimestamp` 排序。 读取单项: ```typescript const conversation = manager.getWithChannel( 'team-1', WKChannelType.group ) ``` ## 未读数 [#未读数] 打开聊天页后清零该会话: ```typescript manager.updateRedDot('team-1', WKChannelType.group, 0) ``` 计算总未读: ```typescript const totalUnread = (manager.all() ?? []) .reduce((total, item) => total + item.unreadCount, 0) ``` `clearAllRedDot()` 会清零所有会话的未读数。 ## 删除会话项 [#删除会话项] ```typescript manager.delete('team-1', WKChannelType.group) ``` 这会从本地聊天列表隐藏该会话,不等于删除服务端消息。若产品要求跨设备删除,需要先调用业务 API,再更新本地状态。 页面销毁时移除监听: ```typescript manager.removeRefreshListener(refreshListener) manager.removeDeletedListener(deletedListener) ``` # HarmonyOS SDK (/zh/sdk/harmonyos) `@wukong/wkim` 是 HarmonyOS NEXT 的完整 SDK。它负责长连接、消息收发、本地消息、会话和频道资料;你的应用负责登录、Token、连接地址以及业务 HTTP API。 如果 `Channel`、`Conversation` 或 Provider 还是陌生概念,先读[核心概念](/zh/sdk/wukongim/concepts)。 ## SDK 和应用各自负责什么 [#sdk-和应用各自负责什么] | 能力 | SDK | 你的应用 | | ---- | ------------------- | ---------------------- | | 登录身份 | 使用 UID 和 Token 建立连接 | 通过业务服务端登录并取得 UID、Token | | 长连接 | 连接、心跳、重连和协议处理 | 提供可访问的 `host:port` | | 消息 | 本地保存、发送、接收和状态更新 | 聊天 UI、业务正文和失败提示 | | 会话 | 保存聊天列表和未读数 | 提供多设备会话同步接口 | | 频道 | 缓存用户与群资料 | 提供用户、群组和成员接口 | | 媒体 | 管理媒体消息发送流程 | 上传文件并返回下载 URL | ## 关于导入路径 [#关于导入路径] 包根目前只导出 `WKIM`: ```typescript import { WKIM } from '@wukong/wkim' ``` `WKChannel`、状态常量和消息正文需要从 `@wukong/wkim/src/main/ets/...` 导入。下面的示例会给出完整路径。升级 SDK 时应重新编译这些导入,确认包结构没有变化。 建议按“快速开始 → 连接 → 消息 → 会话 → 频道 → 高级功能”的顺序阅读。需要查方法名时再打开 [API 参考](/zh/sdk/harmonyos/api-reference)。 # HarmonyOS 消息管理 (/zh/sdk/harmonyos/messages) 一条消息由正文 `WKMessageContent` 和目标 `WKChannel` 组成。`MessageManager` 负责本地保存、发送、接收和历史查询。 ## 发送单聊或群聊文本 [#发送单聊或群聊文本] ```typescript const manager = WKIM.shared.messageManager() manager.send( new WKTextContent('你好'), new WKChannel('bob', WKChannelType.personal) ) manager.send( new WKTextContent('大家好'), new WKChannel('team-1', WKChannelType.group) ) ``` 需要过期时间等选项时使用 `sendWithOption`: ```typescript const options = new WKSendOptions() options.expire = 300 manager.sendWithOption( new WKTextContent('这条消息 5 分钟后过期'), new WKChannel('bob', WKChannelType.personal), options ) ``` 两个发送方法都返回 `void`,不能用 `await` 判断发送成功。 ## 区分三个事件 [#区分三个事件] ```typescript // 单一全局槽位,没有移除方法;在应用级服务中安装一次。 manager.addInsertedListener((message: WKMsg) => { addPendingMessage(message.clientSeq, message) }) const sendStatusListener = ( clientSeq: number, messageId: string, messageSeq: number, reasonCode: number ) => { if (reasonCode === WKSendMsgResult.success) { markSent(clientSeq, messageId, messageSeq) } else { markFailed(clientSeq, reasonCode) } } const newMessagesListener = (messages: WKMsg[]) => { appendIncoming(messages) } manager.addSendStatusListener(sendStatusListener) manager.addNewMsgListener(newMessagesListener) ``` | 事件 | 含义 | | ----------------------- | -------------------- | | `addInsertedListener` | 消息已进入本地数据库,可以先显示在聊天页 | | `addSendStatusListener` | 服务端返回了本次发送结果 | | `addNewMsgListener` | 当前客户端收到了新消息 | `WKSendMsgResult.loading` 表示发送中,`success` 表示服务端已接受,`fail` 表示失败。`noRelation`、`blackList` 和 `notOnWhiteList` 是业务关系限制,应向用户显示清晰原因。 服务端接受不等于对方已经阅读。已读状态需要另外接入你的产品逻辑。 ## 读取本地和远端历史 [#读取本地和远端历史] ```typescript const channel = new WKChannel('bob', WKChannelType.personal) const options = new ChannelMsgOptions( () => showHistoryLoading(), (messages: WKMsg[]) => showMessages(messages) ) options.limit = 30 options.pullMode = 0 options.oldestOrderSeq = 0 manager.getOrSyncHistoryMessages(channel, options) ``` SDK 会先查本地数据库;本地数据不足时调用 `syncMessageCallback`。Provider 的接入见[媒体与历史消息](/zh/sdk/harmonyos/advanced/media-and-history)。 ## 移除监听 [#移除监听] ```typescript manager.removeSendStatusListener(sendStatusListener) manager.removeNewMsgListener(newMessagesListener) ``` 移除时必须传入注册时的同一函数对象。`addInsertedListener` 没有对应移除方法,而且后一次注册会覆盖前一次,所以不要在页面中注册它。 # HarmonyOS 快速开始 (/zh/sdk/harmonyos/quickstart) 这一页只做一件事:让两个在线 HarmonyOS 客户端互相发送一条文本消息。 ## 前置条件 [#前置条件] * 业务服务端已为 `alice` 和 `bob` 返回各自的 `uid`、`token` 和 `host:port`; * 两个独立的应用进程或设备。`WKIM.shared` 是进程单例; * 工程使用 HarmonyOS NEXT,并已声明 `ohos.permission.INTERNET` 和 `ohos.permission.GET_NETWORK_INFO`。 ## 1. 安装 [#1-安装] 在工程目录固定版本: ```bash ohpm install @wukong/wkim@1.1.7 ``` ## 2. 导入并初始化 [#2-导入并初始化] ```typescript import { Context } from '@kit.AbilityKit' import { WKIM } from '@wukong/wkim' import { ConnectionInfo, WKChannel, WKChannelType, WKConnectStatus, WKMsg, WKSendMsgResult, WKSyncConversation } from '@wukong/wkim/src/main/ets/entity/Bean' import { WKTextContent } from '@wukong/wkim/src/main/ets/model/WKTextContent' const im = WKIM.shared await im.init(bootstrap.uid, bootstrap.token, bootstrap.address, appContext as Context) ``` `bootstrap.address` 形如 `im.example.com:5100`,不要带 `tcp://`、`http://` 或 `https://`。 ## 3. 提供会话同步 [#3-提供会话同步] 连接完成前,SDK 会向业务服务端同步最近会话: ```typescript im.config.provider.syncConversationCallback = async ( lastMsgSeqs: string, msgCount: number, version: number ): Promise => { return conversationApi.sync(lastMsgSeqs, msgCount, version) } ``` 如果只是测试从未使用过的开发账号,可以暂时返回空结果: ```typescript const result = new WKSyncConversation() result.uid = bootstrap.uid result.conversations = [] return result ``` 已有聊天数据的账号必须返回真实会话和最近消息。 ## 4. 监听连接和消息 [#4-监听连接和消息] 把监听器保存为属性或应用级常量,移除时必须传入同一个函数对象: ```typescript const connectionListener = ( status: number, reasonCode?: number, info?: ConnectionInfo ) => { if (status === WKConnectStatus.syncCompleted) { console.info(`WuKongIM ready on node ${info?.nodeId ?? '-'}`) } else if (status === WKConnectStatus.fail) { console.error(`connect failed: ${reasonCode ?? '-'}`) } else if (status === WKConnectStatus.kicked) { requireLoginAgain() } } const sendStatusListener = ( clientSeq: number, messageId: string, messageSeq: number, reasonCode: number ) => { if (reasonCode === WKSendMsgResult.success) { markMessageSent(clientSeq, messageId, messageSeq) } else { markMessageFailed(clientSeq, reasonCode) } } const newMessagesListener = (messages: WKMsg[]) => { messages.forEach((message) => { if (message.messageContent instanceof WKTextContent) { console.info(`${message.fromUID}: ${message.messageContent.content}`) } }) } im.connectionManager().addConnectStatusListener(connectionListener) im.messageManager().addSendStatusListener(sendStatusListener) im.messageManager().addNewMsgListener(newMessagesListener) ``` `success` 表示连接协议成功,之后还有 `syncing` 和 `syncCompleted`。等到 `syncCompleted` 再开放发送按钮。 ## 5. 连接并发送文本 [#5-连接并发送文本] ```typescript im.connectionManager().connection() ``` Alice 到达 `syncCompleted` 后向 Bob 发送: ```typescript im.messageManager().send( new WKTextContent('你好,Bob'), new WKChannel('bob', WKChannelType.personal) ) ``` `send()` 没有 Promise 返回值。服务端是否接受消息,以 `sendStatusListener` 中的 `reasonCode` 为准。 ## 预期结果 [#预期结果] 1. Alice 和 Bob 都到达 `syncCompleted`; 2. Alice 的发送回调得到 `WKSendMsgResult.success`; 3. Bob 的新消息监听器打印“你好,Bob”。 页面或应用服务结束时移除监听: ```typescript im.connectionManager().removeConnectStatusListener(connectionListener) im.messageManager().removeSendStatusListener(sendStatusListener) im.messageManager().removeNewMsgListener(newMessagesListener) ``` 下一步阅读[连接管理](/zh/sdk/harmonyos/connection)和[消息管理](/zh/sdk/harmonyos/messages)。 # iOS API 参考 (/zh/sdk/ios/api-reference) 本页是常用入口速查,不替代各任务页面。所有管理器都从 `[WKSDK shared]` 获取。 ## SDK 与配置 [#sdk-与配置] | API | 用途 | | ------------------------- | ------------------------ | | `[WKSDK shared]` | SDK 单例 | | `options` | `host`、`port`、身份、调试和同步配置 | | `registerMessageContent:` | 注册自定义消息正文 | | `sdkVersion` | 当前运行的 SDK 版本 | ## `WKConnectionManager` [#wkconnectionmanager] | API | 用途 | | ---------------------------------- | -------------------- | | `connect` | 发起连接 | | `disconnect:` | 断开;传 `YES` 停止自动重连 | | `logout` | 退出并清除登录信息 | | `addDelegate:` / `removeDelegate:` | 管理连接监听器 | | `getConnectAddr` | 动态提供 `host:port` | | `connectStatus` | 当前 `WKConnectStatus` | 连接回调:`onConnectStatus:reasonCode:`、`onKick:reason:`。 ## `WKChatManager` [#wkchatmanager] | API | 用途 | | ---------------------------------------- | ----------- | | `sendMessage:channel:` | 发送并保存消息 | | `resendMessage:` | 重发失败消息 | | `pullLastMessages:limit:complete:` | 读取最近消息 | | `pullDown:startOrderSeq:limit:complete:` | 读取更早消息 | | `pullUp:startOrderSeq:limit:complete:` | 读取更新消息 | | `clearMessages:` | 清除一个频道的本地消息 | | `syncChannelMessageProvider` | 接入远端频道历史消息 | 消息回调:`onRecvMessages:left:`、`onMessageUpdate:left:`。 ## `WKConversationManager` [#wkconversationmanager] | API | 用途 | | ---------------------------------------- | --------- | | `getConversationList` | 读取最近会话 | | `getConversation:` | 读取一个频道的会话 | | `clearConversationUnreadCount:` | 清除未读数 | | `getAllConversationUnreadCount` | 统计总未读数 | | `deleteConversation:` | 删除会话列表项 | | `setSyncConversationProviderAndAck:ack:` | 接入多设备会话同步 | 会话回调:`onConversationUpdate:`、`onConversationUnreadCountUpdate:unreadCount:`。 ## `WKChannelManager` [#wkchannelmanager] | API | 用途 | | ------------------------------- | ----------- | | `getChannelInfo:` | 读取本地频道资料 | | `fetchChannelInfo:completion:` | 请求刷新频道资料 | | `addOrUpdateChannelInfo:` | 写入频道资料 | | `getMembersWithChannel:limit:` | 读取本地群成员 | | `addOrUpdateMembers:` | 写入群成员 | | `updateChannelSetting:setting:` | 更新免打扰、置顶等设置 | 频道回调:`channelInfoUpdate:oldChannelInfo:`。远端资料入口是 `[WKSDK shared].channelInfoUpdate`。 ## 常用模型与状态 [#常用模型与状态] | 类型 | 说明 | | ------------------ | --------------------------- | | `WKConnectInfo` | `uid`、`token`、名称和头像 | | `WKChannel` | `channelId` 与 `channelType` | | `WKMessage` | 消息身份、发送者、正文、序号和状态 | | `WKMessageContent` | 消息正文基类 | | `WKConversation` | 聊天列表项与未读数 | | `WKChannelInfo` | 名称、头像和频道设置 | | `WKChannelMember` | 群成员资料 | 连接状态:`WKNoConnect`、`WKConnecting`、`WKPullingOffline`、`WKConnected`、`WKDisconnected`。 消息状态:`WK_MESSAGE_WAITSEND`、`WK_MESSAGE_UPLOADING`、`WK_MESSAGE_SUCCESS`、`WK_MESSAGE_FAIL`。 # iOS 频道管理 (/zh/sdk/ios/channels) 频道 `WKChannel` 表示“消息要发给谁”。单聊频道的 ID 是对方 UID,群聊频道的 ID 是你的业务群 ID。`WKChannelInfo` 则保存名称、头像和设置等展示资料。 ## 创建频道 [#创建频道] ```objc WKChannel *person = [WKChannel personWithChannelID:@"bob"]; WKChannel *group = [WKChannel groupWithChannelID:@"team-1"]; ``` 同一个 `channelId` 在不同 `channelType` 下不是同一个聊天,因此缓存和列表都要使用完整的 `WKChannel` 作为身份。 ## 读取本地资料并按需刷新 [#读取本地资料并按需刷新] ```objc WKChannelInfo *cached = [[WKSDK shared].channelManager getChannelInfo:person]; [[WKSDK shared].channelManager fetchChannelInfo:person completion:^(WKChannelInfo *info) { self.titleLabel.text = info.displayName; }]; ``` `getChannelInfo:` 只读本地数据,可能返回 `nil`。`fetchChannelInfo:completion:` 会触发资料获取;真正的数据来源由应用提供。 ## 接入你的用户和群资料 API [#接入你的用户和群资料-api] ```objc [WKSDK shared].channelInfoUpdate = ^WKTaskOperator *(WKChannel *channel, WKChannelInfoCallback callback) { return [profileAPI fetchChannel:channel completion:^(WKChannelInfo *info, NSError *error) { if (info != nil) { [[WKSDK shared].channelManager addOrUpdateChannelInfo:info]; } callback(error, NO); }]; }; ``` Provider 应根据 `channel.channelType` 区分用户接口和群接口,并且无论成功或失败都调用 `callback`。它返回的任务对象用于取消;你的网络层若不支持取消,可以返回 `nil`。 ## 监听资料变化 [#监听资料变化] ```objc [[WKSDK shared].channelManager addDelegate:self]; - (void)channelInfoUpdate:(WKChannelInfo *)channelInfo oldChannelInfo:(WKChannelInfo *)oldChannelInfo { [self updateTitle:channelInfo.displayName avatar:channelInfo.logo]; } ``` 群成员来自同一个管理器: ```objc NSArray *members = [[WKSDK shared].channelManager getMembersWithChannel:group limit:50]; ``` SDK 只管理本地成员数据;成员列表如何从业务服务端获取、分页和更新,需要你的应用实现并通过 `addOrUpdateMembers:` 写入。 ## 常见问题 [#常见问题] * 会话只有 ID,没有名称:为 `channelInfoUpdate` 配置数据源,然后调用 `fetchChannelInfo:`。 * 头像一直不更新:确认新 `WKChannelInfo.version` 大于本地版本,或明确使用不比较版本的 `addOrUpdateChannelInfo:`。 * 大群页面卡顿:成员要分页加载,不要一次把全部成员读取到 UI。 # iOS 连接管理 (/zh/sdk/ios/connection) 连接由 `WKConnectionManager` 管理。你的代码负责提供用户身份和服务器地址,SDK 负责建立连接、心跳和自动重连。 ## 配置身份与固定地址 [#配置身份与固定地址] ```objc WKOptions *options = [WKOptions new]; options.host = @"im.example.com"; options.port = 5100; options.connectInfo = [WKConnectInfo initWithUID:@"alice" token:token name:@"" avatar:@""]; [WKSDK shared].options = options; ``` 每次连接前都可以通过 `connectInfoCallback` 返回最新 Token: ```objc [WKSDK shared].options.connectInfoCallback = ^WKConnectInfo *{ return [WKConnectInfo initWithUID:currentUID token:currentToken name:@"" avatar:@""]; }; ``` 不要在日志中打印 Token。 ## 使用动态连接地址 [#使用动态连接地址] 如果业务服务端会为用户分配网关,用 `getConnectAddr` 返回 `host:port`: ```objc [WKSDK shared].connectionManager.getConnectAddr = ^(void (^complete)(NSString *addr)) { [gatewayAPI fetchAddress:^(NSString *host, uint16_t port) { complete([NSString stringWithFormat:@"%@:%hu", host, port]); }]; }; ``` 回调必须最终执行,否则本次连接会一直等待地址。 ## 监听状态后再开放发送 [#监听状态后再开放发送] ```objc [[WKSDK shared].connectionManager addDelegate:self]; [[WKSDK shared].connectionManager connect]; - (void)onConnectStatus:(WKConnectStatus)status reasonCode:(WKReason)reasonCode { switch (status) { case WKConnecting: self.sendButton.enabled = NO; break; case WKConnected: self.sendButton.enabled = YES; break; case WKDisconnected: self.sendButton.enabled = NO; break; default: break; } } - (void)onKick:(uint8_t)reasonCode reason:(NSString *)reason { // 清理登录态,并提示用户该账号已在其他位置登录。 } ``` `WKPullingOffline` 表示连接已经建立,SDK 正在补离线消息。通常等到 `WKConnected` 再让用户发送最容易理解。 ## 断开还是退出 [#断开还是退出] ```objc // 页面暂时不再使用 IM,且不希望自动重连 [[WKSDK shared].connectionManager disconnect:YES]; // 用户退出账号:断开连接并清除 SDK 登录信息 [[WKSDK shared].connectionManager logout]; ``` 销毁持有监听器的对象前移除它: ```objc [[WKSDK shared].connectionManager removeDelegate:self]; ``` ## 常见问题 [#常见问题] * 一直停在连接中:确认地址回调执行、端口可达,且 Token 属于当前 UID。 * 断开后自动连接:`disconnect:NO` 允许重连;需要停止重连时传 `YES`。 * 切换账号后读到旧数据:退出账号时使用 `logout`,并在新账号连接前替换 `WKOptions`。 接下来可阅读[消息管理](/zh/sdk/ios/messages)。 # iOS 会话管理 (/zh/sdk/ios/conversations) 会话 `WKConversation` 就是聊天列表中的一项。它包含目标频道、最后一条消息、最后消息时间和未读数。 ## 读取聊天列表 [#读取聊天列表] ```objc NSArray *items = [[WKSDK shared].conversationManager getConversationList]; for (WKConversation *item in items) { NSLog(@"channel=%@ unread=%ld", item.channel.channelId, (long)item.unreadCount); } ``` `lastMessage` 可用于生成预览,`lastMsgTimestamp` 用于排序或显示时间。`channelInfo` 可能还没加载,名称和头像的补齐方式见[频道管理](/zh/sdk/ios/channels)。 ## 监听列表变化 [#监听列表变化] ```objc [[WKSDK shared].conversationManager addDelegate:self]; - (void)onConversationUpdate:(NSArray *)conversations { // conversations 只包含本次变化项,可按 channel 更新现有列表。 [self mergeConversations:conversations]; } - (void)onConversationUnreadCountUpdate:(WKChannel *)channel unreadCount:(NSInteger)unreadCount { [self updateBadgeForChannel:channel count:unreadCount]; } ``` 如果列表较小,收到变化后重新调用 `getConversationList` 也可以,逻辑更简单。 ## 清除和统计未读数 [#清除和统计未读数] 用户进入一个聊天并确认已看到消息后,清除该聊天的本地未读数: ```objc WKChannel *channel = [WKChannel personWithChannelID:@"bob"]; [[WKSDK shared].conversationManager clearConversationUnreadCount:channel]; NSInteger total = [[WKSDK shared].conversationManager getAllConversationUnreadCount]; ``` 删除聊天列表项不会删除该频道的全部本地消息: ```objc [[WKSDK shared].conversationManager deleteConversation:channel]; ``` ## 接入服务端会话同步 [#接入服务端会话同步] 多设备之间需要恢复最近会话时,使用 `setSyncConversationProviderAndAck:ack:` 接入你的业务 API。Provider 根据 SDK 给出的版本和各频道最后消息序号返回 `WKSyncConversationWrapModel`;ack 在 SDK 成功处理后把版本提交给服务端。 不要在 Provider 中直接修改 UI,它是数据同步边界。完成同步后,列表变化会通过会话监听器交给界面。 离开页面时调用 `removeDelegate:`,避免重复更新。 # iOS (/zh/sdk/ios) `WuKongIMSDK` 是悟空 IM 的完整 iOS SDK。它负责长连接、消息收发、本地消息库、会话列表和频道资料,适合需要自行构建聊天界面的应用。 本文档对应 `WuKongIMSDK 1.1.1`,示例统一使用 Objective-C。 ## 开始前 [#开始前] 你需要准备: * 一个可访问的 WuKongIM 服务; * 两个测试用户,例如 `alice` 和 `bob`; * 由你的业务服务端返回的 `uid`、`token`、连接 `host` 和 `port`; * 一个支持 CocoaPods 的 iOS 工程。 Token 应由你的业务服务端生成并返回给已登录用户。不要在 App 中保存悟空 IM 的管理密钥,也不要让 App 调用管理接口创建 Token。 ## 推荐阅读顺序 [#推荐阅读顺序] 不熟悉“频道”“会话”这些词时,先读[核心概念](/zh/sdk/wukongim/concepts)。需要查方法名时,直接打开 [API 参考](/zh/sdk/ios/api-reference)。 # iOS 消息管理 (/zh/sdk/ios/messages) 消息由正文 `WKMessageContent` 和目标 `WKChannel` 组成。`WKChatManager` 负责发送、接收、本地保存和查询。 ## 发送单聊或群聊消息 [#发送单聊或群聊消息] ```objc WKTextContent *content = [[WKTextContent alloc] initWithContent:@"你好"]; WKChannel *person = [WKChannel personWithChannelID:@"bob"]; WKMessage *directMessage = [[WKSDK shared].chatManager sendMessage:content channel:person]; WKChannel *group = [WKChannel groupWithChannelID:@"team-1"]; WKMessage *groupMessage = [[WKSDK shared].chatManager sendMessage:content channel:group]; ``` 返回的 `WKMessage` 是本地消息,不代表服务器已经处理完成。用 `clientMsgNo` 将它和后续状态更新关联起来。 ## 监听接收与发送状态 [#监听接收与发送状态] ```objc [[WKSDK shared].chatManager addDelegate:self]; - (void)onRecvMessages:(WKMessage *)message left:(NSInteger)left { // 新消息可能是文本、图片或自定义正文,先判断 message.content 的类型。 [self appendMessage:message]; } - (void)onMessageUpdate:(WKMessage *)message left:(NSInteger)left { [self updateMessage:message.clientMsgNo status:message.status]; } ``` 常用状态: | 状态 | 含义 | UI 建议 | | ---------------------- | -------- | ------ | | `WK_MESSAGE_WAITSEND` | 已保存,等待发送 | 显示发送中 | | `WK_MESSAGE_UPLOADING` | 正在上传媒体 | 显示进度 | | `WK_MESSAGE_SUCCESS` | 服务器已接受消息 | 显示已发送 | | `WK_MESSAGE_FAIL` | 本次发送失败 | 显示重试按钮 | 重发失败消息: ```objc [[WKSDK shared].chatManager resendMessage:failedMessage]; ``` ## 读取某个聊天的消息 [#读取某个聊天的消息] 首次打开聊天页时,读取最近 30 条: ```objc WKChannel *channel = [WKChannel personWithChannelID:@"bob"]; [[WKSDK shared].chatManager pullLastMessages:channel limit:30 complete:^(NSArray *messages, NSError *error) { if (error == nil) { [self showMessages:messages]; } }]; ``` 继续向更早的消息翻页时,使用 `pullDown:startOrderSeq:limit:complete:`;向更新的方向读取时使用 `pullUp:startOrderSeq:limit:complete:`。 ## 清理监听器 [#清理监听器] ```objc [[WKSDK shared].chatManager removeDelegate:self]; ``` 不要在多个页面重复添加同一个监听器而不移除,否则一条消息可能触发多次 UI 更新。离线或远端历史消息的接入见[媒体与历史消息](/zh/sdk/ios/advanced/media-and-history)。 # iOS 快速开始 (/zh/sdk/ios/quickstart) 这一页只做一件事:让两个在线的 iOS 客户端互相发送一条文本消息。完成后,再按需要学习会话、频道资料和离线同步。 ## 前置条件 [#前置条件] * 业务服务端已经为 `alice` 和 `bob` 返回各自的 `uid`、`token`、`host`、`port`; * 两个独立的 App 进程或设备。`WKSDK` 是单例,不能在同一进程同时登录两个用户; * Objective-C 工程已经可以正常使用 CocoaPods。 ## 1. 安装 [#1-安装] 在 `Podfile` 中固定版本: ```ruby pod 'WuKongIMSDK', '1.1.1' ``` 然后执行: ```bash pod install ``` 以后从生成的 `.xcworkspace` 打开工程。 ## 2. 配置并连接 [#2-配置并连接] 创建一个对象统一持有连接和消息监听器: ```objc #import @interface IMClient : NSObject - (void)startWithUID:(NSString *)uid token:(NSString *)token host:(NSString *)host port:(uint16_t)port; - (void)sendText:(NSString *)text toUID:(NSString *)uid; @end @implementation IMClient - (void)startWithUID:(NSString *)uid token:(NSString *)token host:(NSString *)host port:(uint16_t)port { WKOptions *options = [WKOptions new]; options.host = host; options.port = port; options.isDebug = NO; options.connectInfo = [WKConnectInfo initWithUID:uid token:token name:@"" avatar:@""]; [WKSDK shared].options = options; [[WKSDK shared].connectionManager addDelegate:self]; [[WKSDK shared].chatManager addDelegate:self]; [[WKSDK shared].connectionManager connect]; } - (void)onConnectStatus:(WKConnectStatus)status reasonCode:(WKReason)reasonCode { if (status == WKConnected) { NSLog(@"WuKongIM connected"); } else if (status == WKDisconnected) { NSLog(@"WuKongIM disconnected, reason: %ld", (long)reasonCode); } } ``` `connect` 只是发起连接。只有收到 `WKConnected` 后,连接才可以用于发送消息。 ## 3. 发送文本消息 [#3-发送文本消息] Alice 连接成功后,向 Bob 的 UID 发送消息: ```objc - (void)sendText:(NSString *)text toUID:(NSString *)uid { WKTextContent *content = [[WKTextContent alloc] initWithContent:text]; WKChannel *channel = [WKChannel personWithChannelID:uid]; [[WKSDK shared].chatManager sendMessage:content channel:channel]; } ``` 调用: ```objc [client sendText:@"你好,Bob" toUID:@"bob"]; ``` ## 4. 接收结果 [#4-接收结果] 同一个监听器分别处理本端消息状态和对方发来的消息: ```objc - (void)onMessageUpdate:(WKMessage *)message left:(NSInteger)left { if (message.status == WK_MESSAGE_SUCCESS) { NSLog(@"message sent: %@", message.clientMsgNo); } else if (message.status == WK_MESSAGE_FAIL) { NSLog(@"message failed: %@", message.clientMsgNo); } } - (void)onRecvMessages:(WKMessage *)message left:(NSInteger)left { if ([message.content isKindOfClass:[WKTextContent class]]) { WKTextContent *text = (WKTextContent *)message.content; NSLog(@"%@ says: %@", message.fromUid, text.content); } } @end ``` ## 预期结果 [#预期结果] 1. Alice 和 Bob 都收到 `WKConnected`; 2. Alice 的 `onMessageUpdate` 收到 `WK_MESSAGE_SUCCESS`; 3. Bob 的 `onRecvMessages` 打印“你好,Bob”。 如果连接失败,先检查 `host` 不能带 `http://` 或 `https://`,并确认 `port` 是客户端长连接端口。若发送没有结果,确认接收方确实在线、UID 正确,而且发送发生在 `WKConnected` 之后。 下一步阅读[连接管理](/zh/sdk/ios/connection)和[消息管理](/zh/sdk/ios/messages)。 # JavaScript / Web API 参考 (/zh/sdk/javascript/api-reference) 本页是常用入口速查,不替代各任务页面。根单例是 `WKSDK.shared()`。 ## `WKSDK` 与配置 [#wksdk-与配置] | API | 用途 | | ------------------------------- | ---------------------- | | `WKSDK.shared()` | 当前页面上下文的 SDK 单例 | | `config.uid` / `config.token` | 当前用户身份 | | `config.addr` | WebSocket 地址 | | `config.deviceFlag` | `0` App、`1` Web、`2` PC | | `config.provider` | 业务数据源集合 | | `connect()` / `disconnect()` | 连接和停止连接 | | `register(type, factory)` | 注册自定义消息正文 | | `newChannel` / `newMessageText` | 创建常用模型的便捷方法 | ## `ConnectManager` [#connectmanager] | API | 用途 | | ----------------------------- | ------------------ | | `addConnectStatusListener` | 监听连接状态 | | `removeConnectStatusListener` | 移除状态监听 | | `addConnectDelayListener` | 监听心跳延迟 | | `removeConnectDelayListener` | 移除延迟监听 | | `status` | 当前 `ConnectStatus` | 状态:`Disconnect`、`Connected`、`Connecting`、`ConnectFail`、`ConnectKick`。 ## `ChatManager` [#chatmanager] | API | 用途 | | ---------------------------------------------------------- | ------------------- | | `send(content, channel, setting?)` | 发送消息并返回本地 `Message` | | `sendWithOptions(content, channel, options)` | 带高级选项发送 | | `addMessageListener` / `removeMessageListener` | 监听所有收发消息 | | `addMessageStatusListener` / `removeMessageStatusListener` | 监听服务器发送结果 | | `syncMessages(channel, options)` | 通过 Provider 同步消息 | | `syncMessageExtras` | 同步消息扩展数据 | ## `ConversationManager` [#conversationmanager] | API | 用途 | | -------------------------------------------------------- | ----------------- | | `sync(filter?)` | 从业务 Provider 同步会话 | | `conversations` | 当前内存会话列表 | | `findConversation(channel)` | 查找一个会话 | | `createEmptyConversation(channel)` | 创建空会话 | | `removeConversation(channel)` | 移除内存列表项 | | `getAllUnreadCount()` | 统计总未读数 | | `addConversationListener` / `removeConversationListener` | 监听列表变化 | ## `ChannelManager` [#channelmanager] | API | 用途 | | -------------------------------- | ---------------- | | `fetchChannelInfo(channel)` | 通过 Provider 获取资料 | | `getChannelInfo(channel)` | 读取缓存资料 | | `addListener` / `removeListener` | 监听资料变化 | | `syncSubscribes(channel)` | 同步群订阅者 | | `getSubscribes(channel)` | 读取缓存订阅者 | ## 常用 Provider [#常用-provider] | 配置 | 返回值 | | --------------------------- | --------------------------- | | `connectAddrCallback` | 通过 callback 返回 WebSocket 地址 | | `channelInfoCallback` | `Promise` | | `syncSubscribersCallback` | `Promise` | | `syncConversationsCallback` | `Promise` | | `syncMessagesCallback` | `Promise` | | `messageUploadTaskCallback` | `MessageTask` | ## 常用模型 [#常用模型] `Channel`、`ChannelInfo`、`Subscriber`、`Message`、`MessageContent`、`MessageText`、`MessageImage`、`Conversation`、`SyncOptions`、`SendOptions`。 常用频道类型:`ChannelTypePerson`、`ChannelTypeGroup`。本地消息状态:`MessageStatus.Wait`、`MessageStatus.Normal`、`MessageStatus.Fail`。 # JavaScript / Web 频道管理 (/zh/sdk/javascript/channels) 频道 `Channel` 表示“消息要发给谁”。单聊频道 ID 是对方 UID,群聊频道 ID 是你的业务群 ID。`ChannelInfo` 保存 UI 需要的标题和头像。 ## 创建频道 [#创建频道] ```typescript const person = new Channel('bob', ChannelTypePerson) const group = new Channel('team-1', ChannelTypeGroup) ``` `channelID + channelType` 才是完整身份。`channel.getChannelKey()` 可用于前端缓存键。 ## 接入用户和群资料 API [#接入用户和群资料-api] ```typescript sdk.config.provider.channelInfoCallback = async (channel) => { const profile = await profileApi.get( channel.channelID, channel.channelType, ) const info = new ChannelInfo() info.channel = channel info.title = profile.displayName info.logo = profile.avatarUrl info.mute = profile.mute info.top = profile.top info.orgData = profile return info } ``` 请求并读取: ```typescript await sdk.channelManager.fetchChannelInfo(person) const info = sdk.channelManager.getChannelInfo(person) ``` ## 监听资料变化 [#监听资料变化] ```typescript const onChannelInfo = (info: ChannelInfo) => { updateChatHeader(info.title, info.logo) } sdk.channelManager.addListener(onChannelInfo) ``` 页面销毁时调用 `removeListener(onChannelInfo)`。 ## 群订阅者 [#群订阅者] 如果需要群成员或订阅者列表,实现 `syncSubscribersCallback(channel, version)` 并调用 `channelManager.syncSubscribes(channel)`。Provider 返回 `Subscriber[]`,每个元素要包含 UID、频道、版本和删除状态。大群必须分页或增量同步,不能把所有成员一次放进浏览器内存。 ## 常见问题 [#常见问题] * 标题为空:先配置 `channelInfoCallback`,再调用 `fetchChannelInfo`。 * 单聊和群资料串了:缓存键必须包含频道类型。 * Provider 错误导致页面无反馈:在调用 `fetchChannelInfo` 的 UI 层捕获 Promise 错误并提供重试。 # JavaScript / Web 连接管理 (/zh/sdk/javascript/connection) `ConnectManager` 管理 WebSocket、心跳和自动重连。应用负责提供当前用户的 UID、Token 和连接地址。 ## 配置固定地址 [#配置固定地址] ```typescript const sdk = WKSDK.shared() sdk.config.uid = bootstrap.uid sdk.config.token = bootstrap.token sdk.config.addr = bootstrap.websocketUrl sdk.config.deviceFlag = 1 ``` 地址必须是完整的 `ws://` 或 `wss://` URL。浏览器 HTTPS 页面通常只能连接 `wss://`。 ## 动态获取地址 [#动态获取地址] 需要每次连接前查询路由时: ```typescript sdk.config.provider.connectAddrCallback = (complete) => { void gatewayApi.getAddress().then(({ websocketUrl }) => { complete(websocketUrl) }) } ``` Provider 使用 callback,不返回 Promise 给 SDK。业务请求失败时要在应用层显示错误和安排退避重试;不要把 Token 写入 URL 或日志。 ## 监听连接状态 [#监听连接状态] ```typescript const onStatus = ( status: ConnectStatus, reasonCode?: number, info?: ConnectionInfo, ) => { switch (status) { case ConnectStatus.Connecting: showConnecting() break case ConnectStatus.Connected: enableSending(info?.nodeId) break case ConnectStatus.Disconnect: showOffline() break case ConnectStatus.ConnectFail: showConnectionError(reasonCode) break case ConnectStatus.ConnectKick: requireLoginAgain(reasonCode) break } } sdk.connectManager.addConnectStatusListener(onStatus) sdk.connect() ``` 网络异常时 SDK 会尝试重连;被服务端拒绝或踢下线时不会继续无限重连。 ## 页面与账号生命周期 [#页面与账号生命周期] ```typescript sdk.connectManager.removeConnectStatusListener(onStatus) sdk.disconnect() ``` `disconnect()` 会停止自动重连。重新连接前替换 `uid`、`token` 和地址。由于 SDK 是全局单例,账号切换最稳妥的方式是先移除所有旧监听器并断开,再初始化新会话;不要让两个账号共享同一个页面上下文。 ## 常见问题 [#常见问题] * 浏览器报 Mixed Content:HTTPS 页面必须使用 `wss://`。 * 状态回调重复:框架组件重复挂载了监听器,卸载时没有用同一个函数引用移除。 * 本地能连、线上不能连:检查反向代理是否转发 WebSocket Upgrade、证书链和 CSP `connect-src`。 # JavaScript / Web 会话管理 (/zh/sdk/javascript/conversations) 会话 `Conversation` 是聊天列表中的一项。JavaScript SDK 的会话列表保存在内存中;刷新页面后的恢复数据来自你的业务服务端。 ## 接入会话同步 [#接入会话同步] ```typescript sdk.config.provider.syncConversationsCallback = async (filter) => { const rows = await conversationApi.list(filter) return rows.map(toSDKConversation) } const conversations = await sdk.conversationManager.sync() renderConversationList(conversations) ``` 应用映射出的每个 `Conversation` 至少需要 `channel`、`unread`、`timestamp` 和可选的 `lastMessage`。 ## 监听列表变化 [#监听列表变化] ```typescript const onConversation = ( conversation: Conversation, action: ConversationAction, ) => { updateConversationRow(conversation, action) } sdk.conversationManager.addConversationListener(onConversation) ``` 在线收发消息时,管理器会自动新增或更新会话。`ConversationAction` 有 `add`、`update`、`remove`。 ## 打开聊天并处理未读数 [#打开聊天并处理未读数] ```typescript const channel = new Channel('bob', ChannelTypePerson) const conversation = sdk.conversationManager.findConversation(channel) if (conversation) { sdk.conversationManager.openConversation = conversation conversation.unread = 0 sdk.conversationManager.notifyConversationListeners( conversation, ConversationAction.update, ) } ``` 设置 `openConversation` 后,这个频道的新在线消息不会继续增加内存未读数。离开聊天时设为 `undefined`。如果未读数需要多设备一致,还要调用你的业务 API,并通过会话同步恢复服务端值。 总未读数: ```typescript const total = sdk.conversationManager.getAllUnreadCount() ``` 移除列表项只影响当前内存列表: ```typescript sdk.conversationManager.removeConversation(channel) ``` 页面销毁时调用 `removeConversationListener(onConversation)`。 # JavaScript / Web (/zh/sdk/javascript) `wukongimjssdk` 是悟空 IM 的完整 JavaScript SDK。它管理 WebSocket、消息收发、内存中的会话列表和频道资料,适合浏览器、H5 以及经过单独适配验证的 JavaScript 运行时。 本文档对应 `wukongimjssdk 1.3.5`,示例统一使用 TypeScript。 ## 开始前 [#开始前] 你需要准备: * 一个可从目标运行时访问的 WuKongIM WebSocket 地址; * 两个测试用户,例如 `alice` 和 `bob`; * 由业务服务端返回的 `uid`、`token` 和 `ws://` 或 `wss://` 地址; * 一个使用 npm、pnpm 或 Yarn 的前端项目。 浏览器只应从你的业务服务端获取当前用户的短期 Token 和连接地址。创建用户、签发 Token 与访问控制都留在服务端。 ## 推荐阅读顺序 [#推荐阅读顺序] 仓库中的 `docs-site/examples/javascript-web-quickstart` 是可运行的双会话示例。先想理解 API 时直接读本页路径;需要联调真实服务时再运行示例。 不熟悉“频道”“会话”这些词时,先读[核心概念](/zh/sdk/wukongim/concepts)。只想查方法名时,打开 [API 参考](/zh/sdk/javascript/api-reference)。 # JavaScript / Web 消息管理 (/zh/sdk/javascript/messages) 消息由正文 `MessageContent` 和目标 `Channel` 组成。`ChatManager` 负责发送、在线接收和调用远端同步 Provider。 ## 发送单聊或群聊消息 [#发送单聊或群聊消息] ```typescript const direct = await sdk.chatManager.send( new MessageText('你好'), new Channel('bob', ChannelTypePerson), ) const group = await sdk.chatManager.send( new MessageText('大家好'), new Channel('team-1', ChannelTypeGroup), ) ``` 返回值是本地 `Message`,初始状态是 `MessageStatus.Wait`。用 `clientSeq` 或 `clientMsgNo` 关联后续服务器结果。 ## 监听消息与发送结果 [#监听消息与发送结果] ```typescript const onMessage = (message: Message) => { if (message.send) { addOutgoingMessage(message) } else { addIncomingMessage(message) } } const onStatus = (ack: SendackPacket) => { updateSendState(ack.clientSeq, ack.reasonCode, ack.messageSeq) } sdk.chatManager.addMessageListener(onMessage) sdk.chatManager.addMessageStatusListener(onStatus) ``` 发送方法会立即触发消息监听器,所以不要把每个 `onMessage` 都当成对端新消息。`reasonCode === 1` 表示服务器接受发送;对方是否在线收到仍由对方的消息监听器确认。 ## 同步历史消息 [#同步历史消息] JavaScript SDK 不自带持久化消息数据库。离线恢复和翻页数据由你的业务 API 提供: ```typescript sdk.config.provider.syncMessagesCallback = async (channel, options) => { const rows = await messageApi.sync({ channelID: channel.channelID, channelType: channel.channelType, startMessageSeq: options.startMessageSeq, endMessageSeq: options.endMessageSeq, limit: options.limit, pullMode: options.pullMode, }) return rows.map(toSDKMessage) } ``` 调用同步: ```typescript const options = new SyncOptions() options.limit = 30 options.pullMode = PullMode.Down const messages = await sdk.chatManager.syncMessages( new Channel('bob', ChannelTypePerson), options, ) ``` `toSDKMessage` 是应用自己的映射函数,必须填好 `messageID`、`messageSeq`、`clientMsgNo`、`fromUID`、`channel`、`timestamp` 和正确解码的 `content`。 页面销毁时用原函数引用调用 `removeMessageListener` 与 `removeMessageStatusListener`。 # JavaScript / Web 快速开始 (/zh/sdk/javascript/quickstart) 这一页只做一件事:让两个在线浏览器会话互相发送一条文本消息。 **① SENDACK** 是服务器发送结果。**② RECEIVED** 是对端实际收到的事件。下面先运行这个示例,再把代码接入自己的应用。点击图片可放大。 ## 先运行双用户示例 [#先运行双用户示例] 准备 Git、Node.js 20.11 或更新版本,以及 npm。首次使用还没有业务后端时,示例自带一个只监听本机的 Node.js 服务(BFF),负责准备测试身份和查询连接地址。 ### 启动 WuKongIM [#启动-wukongim] 按 [Docker 部署](/zh/server/deployment/docker)或[启动单节点集群](/zh/guide/quick-start/single-node-cluster)启动服务。
服务运行在远程服务器?先建立 SSH 转发 如果 WuKongIM 在远程 Linux 服务器,先在电脑另开终端并保持 SSH 转发运行: ```bash ssh -N -L 127.0.0.1:5001:127.0.0.1:5001 -L 127.0.0.1:5200:127.0.0.1:5200 @ ``` 此路径要求测试服务器的 `/route` 返回浏览器可访问的 `ws://127.0.0.1:5200`;使用其他地址时,按[网络与客户端接入](/zh/server/configuration/networking)配置 `api.external_ws_addr` 并重新启动。隧道只转发端口,不会改写路由响应。
从运行 Node.js 的电脑检查就绪状态,成功后再启动示例: ```bash curl --fail http://127.0.0.1:5001/readyz ``` ### 在电脑启动示例 [#在电脑启动示例] ```bash git clone --depth 1 https://github.com/WuKongIM/WuKongIM.git WuKongIM-web-example cd WuKongIM-web-example/docs-site/examples/javascript-web-quickstart npm ci npm run dev ``` 已有仓库时直接进入同一示例目录。打开 [http://127.0.0.1:5173](http://127.0.0.1:5173),依次操作: 1. 连接 Alice 和 Bob,确认两个会话都已连接。 2. Alice 发送一条消息,检查 Alice 的服务器发送结果和 Bob 的收到事件。 3. 断开 Bob,Alice 再发送一条消息。 4. 重连 Bob,确认缺失消息被恢复并且只显示一次。 首次历史同步可能短暂为空;示例会做有界重试。
重试细节与其他 API 地址 单聊成员目录在 SENDACK 成功后异步建立,首次历史同步可能短暂返回 HTTP 200 空页。示例后端对“最新一页”(起止序号都为 0)的空结果最多请求 20 次,间隔 250 ms;非空结果及普通分页直接返回。真正的空会话会在最多 19 次等待后返回空列表,额外等待最多 4.75 秒,另加 HTTP 请求耗时。其他错误仍失败;生产接入应自行设计一致性和请求期限策略。 默认由 Node.js 访问 `http://127.0.0.1:5001`。其他测试 API 地址通过 `WK_DOCS_QUICKSTART_PRODUCT_HTTP_URL` 设置,例如: ```bash WK_DOCS_QUICKSTART_PRODUCT_HTTP_URL=http://127.0.0.1:15001 npm run dev ```
浏览器只调用示例的 `/api/development/identity` 和 `/api/messages/sync`;BFF 再调用 WuKongIM HTTP API。生产接入需要把开发身份接口换成已有登录与授权系统,客户端不直接访问 `/user/token`。
看看断线恢复后的结果 **① SYNCED** 是恢复的消息。**② Sync complete · 1 recovered** 表示本次恢复完成;同一条消息应只显示一次。
## 接入自己的前端工程 [#接入自己的前端工程] 下面拆解示例中的安装、身份、监听和发送流程。准备两个独立标签页、iframe 或浏览器上下文;`WKSDK.shared()` 是单例,同一页面上下文不要同时登录两个用户。前端构建工具需支持 npm 和 TypeScript。 ### 1. 安装 [#1-安装] ```bash npm install --save-exact wukongimjssdk@1.3.5 ``` 使用 pnpm 或 Yarn 时也固定精确版本,并只保留项目原有的一种锁文件。 ### 2. 配置身份和地址 [#2-配置身份和地址] ```typescript import WKSDK, { Channel, ChannelTypePerson, ConnectStatus, MessageText, } from 'wukongimjssdk' // 示例 BFF 的同源接口;Bob 页面把 uid 改为 bob。 const response = await fetch('/api/development/identity', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ uid: 'alice' }), }) if (!response.ok) throw new Error('identity request failed') const bootstrap: { uid: string; token: string; websocketUrl: string } = await response.json() const sdk = WKSDK.shared() sdk.config.uid = bootstrap.uid sdk.config.token = bootstrap.token sdk.config.addr = bootstrap.websocketUrl sdk.config.deviceFlag = 1 // Web sdk.config.debug = false ``` `bootstrap` 是后端响应对象,不是 SDK 类型。上面的 URL 由可运行示例提供;在自己的项目中替换为已鉴权的业务登录接口,并返回相同三个字段。生产站点使用证书正确的 `wss://` 地址。 ### 3. 先注册监听器 [#3-先注册监听器]
展开完整监听器:连接、接收与发送结果 ```typescript const onConnect = (status: ConnectStatus, reasonCode?: number) => { if (status === ConnectStatus.Connected) { console.log('WuKongIM connected') } else if (status === ConnectStatus.ConnectFail) { console.error('connect failed', reasonCode) } else if (status === ConnectStatus.ConnectKick) { console.error('this account was signed in elsewhere', reasonCode) } } const onMessage = (message: any) => { if (message.send) return // 本端发送时也会产生消息事件 if (message.content instanceof MessageText) { console.log(`${message.fromUID}: ${message.content.text}`) } } const onMessageStatus = (ack: any) => { if (ack.reasonCode === 1) { console.log('message sent', ack.clientSeq, ack.messageSeq) } else { console.error('message failed', ack.clientSeq, ack.reasonCode) } } sdk.connectManager.addConnectStatusListener(onConnect) sdk.chatManager.addMessageListener(onMessage) sdk.chatManager.addMessageStatusListener(onMessageStatus) ``` 保留函数引用,页面销毁时需要用同一个引用移除监听器。
### 4. 连接并发送 [#4-连接并发送] ```typescript sdk.connect() ``` 收到 `ConnectStatus.Connected` 后,让 Alice 向 Bob 发送: ```typescript const message = await sdk.chatManager.send( new MessageText('你好,Bob'), new Channel('bob', ChannelTypePerson), ) console.log('local message', message.clientMsgNo) ``` 返回的 Promise 给出本地消息。服务器结果来自 `onMessageStatus`,Bob 的在线消息来自 `onMessage`,这是两个不同事件。 ## 预期结果 [#预期结果] 1. Alice 和 Bob 都收到 `ConnectStatus.Connected`; 2. Alice 收到 `reasonCode === 1` 的发送状态; 3. Bob 打印“你好,Bob”。 ## 清理页面 [#清理页面] ```typescript sdk.connectManager.removeConnectStatusListener(onConnect) sdk.chatManager.removeMessageListener(onMessage) sdk.chatManager.removeMessageStatusListener(onMessageStatus) sdk.disconnect() ``` 如果连接失败,检查地址是否为 WebSocket 地址而不是 HTTP API 地址,并检查页面的 CSP、反向代理和证书。下一步阅读[连接管理](/zh/sdk/javascript/connection)和[消息管理](/zh/sdk/javascript/messages)。 ## 按现象排查 [#按现象排查] | 现象 | 检查与处理 | | -------------------- | ---------------------------------------------- | | `npm ci` 或构建失败 | 确认 Node.js 版本和当前示例目录,保留仓库的锁文件 | | 测试身份请求失败 | 从 Node.js 所在电脑检查 `/readyz`、API 地址和 `5001` 转发 | | 身份已获取但 WebSocket 连不上 | 检查 `/route` 返回的地址能否从浏览器访问,以及 `5200` 转发、证书与代理路径 | | 连接成功但 Bob 看不到消息 | 确认对端 UID、发送结果以及 Payload 使用 SDK 文本格式 | | 重连后未恢复消息 | 检查 `/api/messages/sync` 响应,确认消息已持久化且未切换测试 UID | # 核心概念 (/zh/sdk/wukongim/concepts) 开始写代码前,先理解这些词。各平台的方法名不同,但含义相同。 ## UID:用户是谁 [#uid用户是谁] `UID` 是用户在你的产品中的稳定唯一标识,例如 `alice` 或 `user_1001`。WuKongIM 不负责注册、密码和用户资料;这些仍由你的业务系统管理。 ## Token:这个用户能否连接 [#token这个用户能否连接] `Token` 是连接 WuKongIM 时使用的凭据。客户端登录你的业务系统后,由业务后端返回 UID、Token 和连接地址。不要在客户端生成 Token,也不要把服务端管理密钥放进 App。 ## Channel:消息要发给谁 [#channel消息要发给谁] 频道(`Channel`)就是**消息要发给谁**: * 单聊频道:频道 ID 是对方的 UID,频道类型是 `1`; * 群聊频道:频道 ID 是你的业务群 ID,频道类型是 `2`。 SDK 用“频道”统一表示单聊、群聊和其他消息目标。 ## Message:一条消息及其状态 [#message一条消息及其状态] 发送消息通常会经历三个不同结果: 1. **发送中**:消息已经创建并写入本地; 2. **发送成功**:服务端已接收并确认; 3. **对方收到**:对方客户端触发了新消息回调。 发送成功不代表对方已读。已读回执属于单独的产品功能。 ## Conversation:聊天列表中的一项 [#conversation聊天列表中的一项] 会话(`Conversation`)就是**聊天列表中的一项**。它通常包含对应频道、最后一条消息、时间和未读数。消息属于频道,会话是当前用户看到的聊天列表视图。 ## Provider:SDK 向业务系统要数据 [#providersdk-向业务系统要数据] 数据提供器(`Provider`)是 SDK 留给应用的回调。当 SDK 缺少频道资料、历史消息或会话数据时,会调用 Provider;你的 App 再向业务后端请求数据,并把结果交还给 SDK。 Provider 必须在回调完成后返回结果,即使结果为空或请求失败,也不要让 SDK 一直等待。 ## 下一步 [#下一步] 选择你的平台并完成快速开始:[Android](/zh/sdk/android)、[iOS](/zh/sdk/ios)、[JavaScript / Web](/zh/sdk/javascript)、[Flutter](/zh/sdk/flutter) 或 [HarmonyOS](/zh/sdk/harmonyos)。 # WuKongIMSDK (/zh/sdk/wukongim) WuKongIMSDK 是完整版客户端 SDK。它不只负责网络连接,还会在客户端维护消息、会话、未读数和离线恢复所需的本地数据。 ## 它在应用中的位置 [#它在应用中的位置]