Skip to content

XHIM Server

XHIM Server 是晞晗IM客户端 SDK 的参考服务端。它与仓库内的 C++ Product Adapter 使用同一套 Protobuf v1 契约,当前已经跑通:

text
Token → Session Bootstrap → HTTPS/WSS → initial Sync → Ready
      → local Outbox → send commit → realtime hint → Sync → SQLite merge

本页只面向服务端部署、运维和 XHIM 产品交付人员。Docker、PostgreSQL、域名、 证书、密钥、联调账号和管理后台都在本页处理,不应要求 iOS、Android、Windows 或 HarmonyOS 开发者执行这些命令。

XHIM 销售方不需要在销售前替购买方配置真实生产环境。购买方取得服务端源码和 部署文件后,可以自主设置域名或 IP、数据库、对象存储、Push 和管理员认证。 XHIM 交付包只提供安全的配置入口和默认模板,不内置任何客户密钥。 整体交付边界见 商业交付与客户接入指南

客户端开发者应直接阅读对应平台 QuickStart,例如 iOS 从零接入

交付给客户端团队的联调信息

服务端部署并初始化完成后,服务端负责人只向客户端团队交付可公开使用的接入 信息,不交付 Docker 命令、数据库连接、Admin Key 或签名私钥。

Development 联调交付模板:

text
XHIM Server URL: https://im-test.customer.com
Test User IDs: alice, bob
Test Conversation ID: xhim-demo-direct
Login Mode: Development Easy Login

Production 交付模板:

text
XHIM Server URL: https://im.customer.com
User ID Source: 当前业务登录账号的稳定用户 ID
Business Authentication Callback: 已接入
Login Mode: Business

服务端负责人交付前应自行确认:

  • Server URL 可以从目标手机或桌面设备访问;
  • Development 测试账号和测试会话已经创建且成员关系正确;
  • Production 域名、证书、API、WebSocket 和媒体地址均已就绪;
  • 业务鉴权接口会根据当前业务登录态签发对应用户的 XHIM 登录票据;
  • 管理凭证、数据库地址和签名密钥没有进入客户端交付物。

客户端团队拿到上述信息后,只配置平台 SDK,不参与服务端部署。两端问题的边界 是:客户端负责 SDK 初始化、系统权限和 UI;服务端负责网络可达、账号、会话、 鉴权、存储和运行状态。

正式业务鉴权的实现细节只需要购买方服务端人员理解。客户端人员看到的是一个 回调,SDK 负责登录票据的提交、过期续期和并发合并。

当前服务能力

  • Ed25519 JWT 用户 Token 和 Scope 校验;
  • 带过期时间、签名和明确 Key ID 的 Endpoint Bundle;
  • 用户、单聊/群聊会话的管理端初始化 API;
  • 嵌入式自托管管理后台:运行概览、部署公开配置、用户、会话、登录设备、 测试 Token 和安全审计;
  • 好友申请、接受/拒绝、好友快照,以及账号流增量关系事件;
  • 客户端建群、Owner 权限、动态成员增删、群 revision 乐观并发控制和成员分页;
  • 黑名单、入群审批、管理员、成员禁言、群主转让与治理审计;
  • (sender_user_id, client_message_id) 服务端幂等;
  • 会话内单调 server_seq
  • 会话历史按 server_seq 倒序 keyset 分页,读取快照同时返回最新序号和当前 用户视图;
  • 消息“仅为我删除”、会话清空高水位和会话隐藏高水位,支持 mutation 幂等、revision CAS 和同账号多设备同步;隐藏会话收到更高序号的新消息后自动 恢复显示;
  • 按用户和会话持久化、带 revision CAS 的单调已读序号;当前账号设备同步 ConversationReadUpsert,其他会话成员同步 ConversationPeerReadUpsert;支持一次事务把当前用户全部有权会话推进到调用 快照的最新序号,并精确幂等回放;
  • 服务端设备会话注册表、multi / single / single_per_platform 登录策略、确定性容量淘汰、用户主动撤销和旧连接实时失效;
  • 基于设备租约聚合的 Presence 与 Typing 临时事件,支持断线/撤销清理、 TTL 过期、跨实例投递和黑名单/成员权限边界;
  • 每账号单调事件流、HMAC 不透明 Cursor 和分页 Sync;
  • 二进制 Protobuf HTTP API;
  • WSS 实时 Sync Hint、会话撤销、Presence、Typing、Ping/Pong 和断线恢复入口;
  • PostgreSQL LISTEN/NOTIFY 跨副本 Hint 总线、租户隔离和周期兜底 Hint;
  • 会话权限绑定的媒体控制面:Prepare、短期上传授权、服务端完整性提交和 短期下载授权;
  • 开发环境 HMAC 签名本地对象存储和生产 S3/S3-compatible 预签名 Adapter;
  • 每客户端 IP 令牌桶、WebSocket 并发上限、请求 ID、结构化访问日志和基础 Prometheus 指标;
  • PostgreSQL 持久化实现、内存测试实现和嵌入式迁移;
  • 设备 Push 注册、耐久 Outbox、租约/退避 Worker、可替换 Webhook Provider, 以及 APNs/FCM/Huawei Push Kit 直连 Provider;
  • 持久化通话邀请/接受/拒绝/挂断信令、账号增量信令分页和每用户短期签名 RTC Token;
  • Caddy TLS 入口、Docker Compose、本机直连 TLS 和优雅关闭;
  • Go race test、HTTP/WebSocket 纵向测试和 C++/Go 跨语言 E2E。

当前管理后台面向客户私有化部署,不包含厂商侧 SaaS 计费、订单、跨客户租户或 多区域运维控制面。黑名单、入群申请、管理员/禁言/转让、耐久 Push Outbox、 Webhook/APNs/FCM/Huawei Push Provider、持久化通话信令和用户级 RTC Token 已实现;媒体转码、 外部内容审核厂商和具体 RTC 厂商/SFU 集群仍按可替换 Adapter 交付,详见 商用门禁

一条命令验证 SDK 与服务端

在 macOS 开发机执行:

bash
./scripts/run_server_e2e.sh

脚本会:

  1. 创建一次性 Ed25519/HMAC 密钥和带 SAN 的本机 TLS 证书;
  2. 启动内存模式 XHIM Server;
  3. 创建 Alice、Bob 和一个单聊会话并签发两人的 Token;
  4. 构建嵌入 reference Product Adapter 的正式模式 C ABI;
  5. 验证登录、WSS、初始同步、发送、ACK/Sync 回流、本地 SERVER_ACCEPTED,以及服务端权威 markRead 回包和变更事件;
  6. 由 Alice 准备并上传媒体,服务端校验 SHA-256 后提交,再由 Bob 通过会话 成员鉴权取得下载授权并校验原始字节;
  7. 停止服务并删除临时证书、Token、媒体文件和数据库。

脚本不会关闭 TLS 校验,也不会使用 LOCAL_PREVIEW

本机运行 Go 测试

bash
cd server
go test -race ./...
go vet ./...

协议变更后,在仓库根目录执行:

bash
buf lint
buf generate
git diff --exit-code -- server/gen

Docker Compose 开发环境

同一局域网快速联调(不需要 Tailscale)

确认 Mac 与 iPhone 在同一 Wi-Fi,取得 Mac 的局域网 IPv4:

bash
ipconfig getifaddr en0

然后用该地址生成仅限 Development 的明文 HTTP/WS 配置。下面的 IP 只是示例:

bash
scripts/dev/configure_server_env.sh \
  --host 192.168.2.250 \
  --port 18080 \
  --transport lan-http \
  --force

docker compose --env-file server/.env \
  -f server/compose.yaml up -d --build

curl http://192.168.2.250:18080/health/ready
scripts/dev/provision_chat_demo.sh

此模式会把 18080 绑定到 Mac 的所有网卡,并显式生成 XHIM_ALLOW_INSECURE_DEVELOPMENT_TRANSPORT=true。服务端仅在 XHIM_SERVER_ENV=development 时接受该开关;Apple Development 制品也必须在 构建期读取匹配的 server/.env.client 才会允许 http:///ws://。Production 构建会直接拒绝这一开关。

这条路径不依赖 Tailscale,也不需要在手机安装本地 CA,但只允许用于受信任的 隔离测试网络。不要在公共 Wi-Fi、互联网端口映射或正式环境中使用;联调结束后 应停止 Compose 或切回 HTTPS。若真机无法连接,依次检查:

  • Mac 与手机是否处于同一网段且路由器未启用客户端隔离;
  • macOS 防火墙是否允许 Docker/XHIM 接受入站连接;
  • curl http://<Mac局域网IP>:18080/health/ready 在其他同网设备是否可达;
  • iOS 首次弹出的“本地网络”权限是否已允许。

本机可信 HTTPS

先生成开发密钥。仅在 Mac 本机联调时使用 localhost;真机 HTTPS 联调必须 传入手机可访问且证书受系统信任的开发域名:

bash
scripts/dev/configure_server_env.sh --host localhost --port 8443

脚本会生成被 Git 忽略且权限为 0600server/.env,以及只包含客户端公开 构建参数的 server/.env.client。轮换本地环境时显式使用 --force,该操作会 使旧 Token 和旧客户端 Endpoint 签名公钥失效。随后运行:

bash
cd server
docker compose up --build
docker compose cp \
  gateway:/data/caddy/pki/authorities/local/root.crt \
  ./xhim-local-ca.crt
curl --cacert ./xhim-local-ca.crt \
  https://localhost:8443/health/ready

Caddy 的 tls internal 只适合本机开发。最简单的浏览器验证可以临时使用 curl -k,但 SDK 联调和生产环境不得关闭证书校验;应导出并显式信任开发 CA, 或改用组织签发的证书。

可选:Tailscale 私网 HTTPS

Tailscale 不是 XHIM 的运行依赖。只有团队希望跨网络保留私网可信 HTTPS 时才 需要它:

bash
scripts/dev/configure_server_env.sh \
  --host <mac-name>.<tailnet>.ts.net \
  --port 443 \
  --force
docker compose up -d --build

# Compose 只把后端映射到 Mac 的 127.0.0.1:18080。
tailscale serve --bg --https=443 http://127.0.0.1:18080
curl https://<mac-name>.<tailnet>.ts.net/health/ready

首次启用 Tailscale Serve/HTTPS 需要 Tailnet 管理员在控制台确认。它、局域网 HTTP 和 Caddy :8443 是三种可选开发入口;不要启用 Funnel,除非明确需要把 测试服务暴露到公网。Endpoint 或签名密钥变更后,重启 Server 并把新的 Key ID/ 公钥更新到 App 的 XHIMDeployment;无需因为 API/WSS 域名变化重新构建 SDK。 签名私钥轮换后应重新签发 Demo Token:

bash
scripts/dev/provision_chat_demo.sh

该脚本幂等复用 Alice、Bob 与 xhim-demo-direct,Token 只写入被 Git 忽略且 权限为 0600server/.env.demo,不会打印到终端。

生产部署不应照搬开发密码。至少需要:

  • XHIM_SERVER_ENV=production
  • PostgreSQL TLS、备份、PITR 和连接池容量规划;
  • S3 Bucket 私有策略、版本/生命周期、跨区复制、KMS 加密和凭证轮换;
  • 独立生成并托管的 Ed25519 私钥、Cursor HMAC Key 和 32 字节以上 Admin Key;
  • 由网关、Ingress 或服务本身终止 TLS;
  • 对公网 API 与管理 API 分网、认证、限流和审计;
  • 多副本实时 fan-out、Push、指标、告警和灾备。

配置

变量用途
XHIM_SERVER_ENVdevelopmenttestproduction
XHIM_LISTEN_ADDRESSHTTP/TLS 监听地址
XHIM_GATEWAY_BIND_ADDRESSCaddy 开发 TLS 入口绑定地址,默认仅回环
XHIM_SERVER_LOOPBACK_PORTCompose 暴露给本机可信反向代理的回环端口
XHIM_SERVER_BIND_ADDRESSCompose 端口绑定地址;默认 127.0.0.1,局域网开发为 0.0.0.0
XHIM_ALLOW_INSECURE_DEVELOPMENT_TRANSPORTdevelopment 可显式允许 HTTP/WS;生产禁止
XHIM_STORAGE_DRIVERmemorypostgres;生产只允许 PostgreSQL
XHIM_DATABASE_URLPostgreSQL DSN
XHIM_ADMIN_KEY管理 API 共享密钥;生产至少 32 字节
XHIM_SIGNING_PRIVATE_KEY_BASE64Ed25519 seed/private key
XHIM_CURSOR_KEY_BASE64HMAC Cursor Key;生产至少 32 字节
XHIM_PUBLIC_API_URLEndpoint Bundle 中的 HTTPS API 根地址
XHIM_PUBLIC_WEBSOCKET_URLEndpoint Bundle 中的 WSS 地址
XHIM_PUBLIC_UPLOAD_URL媒体上传服务根地址
XHIM_PUBLIC_MEDIA_URL媒体下载服务根地址
XHIM_ALLOWED_ORIGINSWeb SDK 允许的浏览器来源,逗号分隔的精确 host[:port] 或审核过的通配主机;生产禁止 *
XHIM_TLS_CERTIFICATE_FILE可选,服务直接终止 TLS 时的证书
XHIM_TLS_PRIVATE_KEY_FILE与证书成对配置的私钥
XHIM_REQUEST_RATE每实例、每客户端 IP 的持续请求速率
XHIM_REQUEST_BURST每客户端 IP 的突发请求容量
XHIM_MAX_WEBSOCKETS每实例允许的活跃 WebSocket 上限
XHIM_MULTI_LOGIN_POLICYmultisinglesingle_per_platform,默认 multi
XHIM_MAX_SESSIONS_PER_USER每个 App + 用户最多保留的活跃设备会话,默认 10,最大 100
XHIM_TRUST_PROXY_HEADERS仅在服务只接受可信代理流量时启用
XHIM_MEDIA_STORAGE_DRIVERlocals3;生产只允许 s3
XHIM_MEDIA_LOCAL_ROOT本地开发对象目录;Compose 使用持久 Volume
XHIM_MEDIA_URL_KEY_BASE64本地开发短期 URL 的独立 HMAC Key
XHIM_MEDIA_AUTH_LIFETIME上传/下载授权有效期,最大 1 小时
XHIM_S3_REGIONS3 Region
XHIM_S3_BUCKET私有媒体 Bucket
XHIM_S3_BASE_ENDPOINT可选,MinIO 等 S3-compatible Endpoint
XHIM_S3_USE_PATH_STYLE自定义服务需要 Path-style 时启用
XHIM_S3_EXPECTED_OWNER可选,AWS S3 Bucket Owner 防误配约束
XHIM_PUSH_MODEdisabledwebhookdirect;未设置时兼容旧 Webhook 配置
XHIM_PUSH_WEBHOOK_URLWebhook 模式的聚合服务;Direct 模式可选作 Web Push Provider
XHIM_PUSH_WEBHOOK_TOKENPush Webhook Bearer Token;生产使用 Webhook 时至少 32 字节
XHIM_PUSH_APNS_TEAM_IDApple Developer Team ID
XHIM_PUSH_APNS_KEY_IDAPNs Token Auth Key ID
XHIM_PUSH_APNS_TOPIC可选固定 Bundle ID;未设置时使用设备登记的 App ID
XHIM_PUSH_APNS_PRIVATE_KEY_BASE64APNs .p8 PEM 文件的 Base64 内容
XHIM_PUSH_FCM_SERVICE_ACCOUNT_JSON_BASE64FCM 服务账号 JSON 的 Base64 内容
XHIM_PUSH_HUAWEI_APP_IDHuawei Push Kit App ID
XHIM_PUSH_HUAWEI_CLIENT_IDHuawei OAuth Client ID
XHIM_PUSH_HUAWEI_CLIENT_SECRETHuawei OAuth Client Secret
XHIM_PUSH_REQUEST_TIMEOUT单次 Provider 请求超时
XHIM_PUSH_BATCH_SIZE每次租约批量
XHIM_PUSH_MAX_ATTEMPTS单条通知最大 Provider 尝试次数,默认 12
XHIM_PUSH_LEASE_DURATIONPush 任务执行租约
XHIM_PUSH_IDLE_INTERVAL空队列轮询间隔
XHIM_PUSH_MAX_DEVICES_PER_USER每个 App + 账号 + 用户的设备注册硬上限,默认 20,最大 100
XHIM_PUSH_DELIVERY_CONCURRENCYProvider 固定 worker pool 并发上限,默认 8,最大 64
XHIM_WEBHOOK_URL可选的耐久业务事件 Webhook HTTPS 地址;不允许 URL 凭证、Query 或 Fragment
XHIM_WEBHOOK_KEY_IDWebhook HMAC Key ID,用于接收方密钥轮换
XHIM_WEBHOOK_SECRET_BASE64Webhook HMAC-SHA256 Key 的 Base64,启用时至少 32 字节
XHIM_WEBHOOK_REQUEST_TIMEOUT单次业务 Webhook HTTP 超时,必须小于租约
XHIM_WEBHOOK_BATCH_SIZE每轮最多租约的业务事件数,默认 100,最大 500
XHIM_WEBHOOK_MAX_ATTEMPTS自动投递最大尝试次数,默认 12
XHIM_WEBHOOK_LEASE_DURATION投递尝试租约;进程重启后过期任务可回收
XHIM_WEBHOOK_IDLE_INTERVAL空队列轮询间隔
XHIM_WEBHOOK_BASE_BACKOFFFull Jitter 指数退避初始上限
XHIM_WEBHOOK_MAX_BACKOFFFull Jitter 指数退避最大上限
XHIM_WEBHOOK_DELIVERY_CONCURRENCY固定投递 worker 并发上限,默认 8,最大 64
XHIM_RTC_PROVIDER返回给客户端的 RTC Provider 标识
XHIM_RTC_ENDPOINT安全 wss://https:// RTC Endpoint
XHIM_RTC_SIGNING_KEY_IDRTC Token 签名 Key ID,用于轮换
XHIM_RTC_TOKEN_KEY_BASE64独立 RTC HMAC Key;生产至少 32 字节
XHIM_RTC_TOKEN_LIFETIME用户级 RTC Token 有效期,最大 1 小时

完整默认值见 .env.example

PostgreSQL 备份、恢复演练与滚动升级

生产 PostgreSQL 运维工具位于 scripts/postgres,提供:

  • pg_dump custom format 备份、canonical 元数据、SHA-256 和 pg_restore --list 离线校验;
  • Hash、TOC 和实际导入共用匿名固定 inode/FD 的单事务恢复,以及 canonical 恢复演练报告;
  • 迁移前备份身份校验、N/N-1 兼容窗口和应用回滚前置门禁。

工具不接收数据库 URL 命令行参数;源库和恢复库分别只从 XHIM_POSTGRES_SOURCE_URLXHIM_POSTGRES_RESTORE_URL 读取。它们不会 自动创建/删除数据库,不使用 pg_restore --clean/--create,也不会执行向后 Schema Migration。正式滚动顺序、受控非空目标例外以及“只回应用、不盲回 Schema”的处理流程见上述运维文档。

Push Provider 模式

默认没有 Push 配置时,XHIM_PUSH_MODE=disabled,Worker 不会启动。原有部署只要 配置 XHIM_PUSH_WEBHOOK_URL,仍会自动进入 webhook 模式并将所有平台交给 同一个聚合服务。

自建直连使用:

dotenv
XHIM_PUSH_MODE=direct

# APNs:三项必须同时配置;Topic 可留空并使用登记设备的 App ID。
XHIM_PUSH_APNS_TEAM_ID=YOUR_TEAM_ID
XHIM_PUSH_APNS_KEY_ID=YOUR_KEY_ID
XHIM_PUSH_APNS_PRIVATE_KEY_BASE64=<AuthKey_xxx.p8 的 Base64>
XHIM_PUSH_APNS_TOPIC=com.example.chat

# FCM:完整 service-account.json 的 Base64,不是客户端 google-services.json。
XHIM_PUSH_FCM_SERVICE_ACCOUNT_JSON_BASE64=<service-account.json 的 Base64>

# Huawei Push Kit:三项必须同时配置。
XHIM_PUSH_HUAWEI_APP_ID=YOUR_APP_ID
XHIM_PUSH_HUAWEI_CLIENT_ID=YOUR_OAUTH_CLIENT_ID
XHIM_PUSH_HUAWEI_CLIENT_SECRET=YOUR_OAUTH_CLIENT_SECRET

Direct 模式按设备登记的 platform 严格路由:

  • apns 使用 Apple ES256 Token Auth 和 Go 标准 HTTP Client 的 TLS/HTTP/2 连接;environment=development|sandbox 走 Sandbox, production|prod|release 或空值走 Production;
  • fcm 使用服务账号 RS256 JWT 换取短期 OAuth2 Token,再调用 FCM HTTP v1;
  • huawei 使用 client_credentials 换取短期 Token,再调用 Push Kit v1;
  • web 只有同时配置 Webhook URL 才可用,否则该平台明确返回 provider_not_configured:web,不会伪造投递成功。

各 Provider 会缓存并在到期前更新短期凭证。网络错误、4295xx 和 Provider 明确的临时错误进入耐久 Outbox 重试,并支持秒数或 HTTP-date 格式的 Retry-After;APNs Unregistered/ExpiredToken、FCM UNREGISTERED、Huawei 80300007 等令牌失效响应会通过结构化 InvalidToken 结果自动停用对应设备。 停用操作同时校验 App、账号、设备、平台和原 Token,防止请求在途期间客户端 刷新 Token 后被旧响应误停用;状态变更与不含 Token 的安全审计原子提交,重复 响应为幂等 no-op。配置缺失、部分配置和认证错误均 fail-closed。进程日志只记录 模式和已启用的平台,不记录私钥、服务账号 JSON、OAuth Secret、访问 Token 或 设备 Token。

Push 注册容量按 app_id + account_id + user_id 计算,启用和已禁用记录都占用 配额,避免通过反复注册/禁用持续扩张数据库。注册一个新的 device_id 到达上限 时,存储事务会先确定性淘汰已禁用设备,再按 updated_atcreated_atdevice_id 淘汰最久未更新的设备;同一设备刷新 Token 不触发淘汰。Memory 和 PostgreSQL Adapter 使用同一策略,PostgreSQL 通过锁定用户行串行化同账号并发 注册。客户端注册响应同时通过 Protobuf 字段 device_limitevicted_count 和兼容响应头返回容量结果。相同 Provider Token 换账号或 设备时会在同一事务迁移所有权,并通过 token_reassigned=true 明确返回,避免 唯一索引冲突或旧账号继续收到 Push。响应、日志与 push.devices_evicted 审计都不包含原始 Device Token。

投递查询始终携带同一硬上限,SQL 使用 LIMIT,Worker 只创建 XHIM_PUSH_DELIVERY_CONCURRENCY 个有界消费者,不会按设备数量无限创建 goroutine。降低设备上限后,下一次新设备注册会在同一事务清理历史超额记录; 正式调整前应先观察 push.devices_evicted 审计数量。

生产环境应通过容器 Secret、Kubernetes Secret 或 Vault 注入这些值,不要提交到 Git。FCM Provider 不使用服务账号 JSON 中可被篡改的 token_uri,而固定调用 Google 官方 OAuth 与 FCM 地址;APNs 和 Huawei 的生产地址同样固定在代码中。

耐久业务事件 Webhook

业务 Webhook 与 Push Webhook 是两套完全独立的协议。业务方法只在原业务 PostgreSQL 事务内追加一条 xhim_webhook_outbox 记录;事务提交前不会访问外部 网络。提交成功后独立 Worker 才租约并投递,因此接收方变慢、宕机或返回错误不会 占用业务事务,也不会造成“业务回滚但 Webhook 已发出”。Memory Adapter 保持相同 状态机语义,但只用于开发和测试;生产重启恢复依赖 PostgreSQL。

启用配置示例:

dotenv
XHIM_WEBHOOK_URL=https://events.example.com/xhim
XHIM_WEBHOOK_KEY_ID=xhim-business-2026-01
XHIM_WEBHOOK_SECRET_BASE64=<至少 32 字节随机 Key 的 Base64>

URL、Key ID、Secret 必须三项同时配置。Secret 太短、配置一半、URL 内含账号密码、 Query/Fragment,或生产环境使用 HTTP,服务都会拒绝启动。开发环境只有同时设置 XHIM_ALLOW_INSECURE_DEVELOPMENT_TRANSPORT=true 才允许 HTTP。Secret、消息 Body、Token 和完整 Envelope 不写普通日志,也不由管理列表返回。

每次请求体是稳定的 1.0 JSON Envelope:

json
{
  "version": "1.0",
  "event_id": "whevt_...",
  "app_id": "com.example.chat",
  "type": "message.sent",
  "occurred_at": "2026-07-26T12:00:00Z",
  "resource": {
    "type": "message",
    "id": "msg_...",
    "conversation_id": "conv_...",
    "content_type": "text/plain",
    "content_version": 1,
    "server_sequence": 42
  },
  "actor": {
    "user_id": "alice",
    "account_id": "account-alice"
  },
  "trace": {
    "trace_id": "trace-...",
    "operation_id": "operation-...",
    "client_id": "client-message-..."
  }
}

当前 after-event 至少包含:

  • message.sentmessage.mutatedprofile.updated
  • friend.requestedfriend.resolvedfriend.deleted
  • group.createdgroup.members.changedgroup.governance.changedgroup.leftgroup.dismissed
  • 额外包含 group.join.requestedgroup.join.resolvedblock.changed

消息事件只携带定位、类型、版本、序列和修订元数据,不复制消息 Payload 或 Fallback Text。接收方如果需要业务内容,应使用 resource.id 通过自己的受控 服务端权限查询。

签名与防重放 Headers:

text
X-XHIM-Webhook-Version: v1
X-XHIM-Webhook-Key-ID: <key id>
X-XHIM-Webhook-Timestamp: <Unix seconds>
X-XHIM-Webhook-Signature: v1=<hex HMAC-SHA256(secret, timestamp + "." + rawBody)>
X-XHIM-Event-ID: <event_id>
X-XHIM-Delivery-ID: <event_id>
Idempotency-Key: <event_id>

接收方必须在解析 JSON 前用收到的原始 Body 做常量时间签名校验,按 Key ID 选择 轮换中的密钥,并拒绝与本机时间相差超过 5 分钟的 Timestamp。event_id 在业务 事件生命周期内稳定;接收方应按它做唯一约束,即使响应丢失导致重复投递也只执行 一次。轮换密钥时先让接收方同时接受旧/新 Key,再切换发送端 Key ID,至少保留旧 Key 一个重放窗口加最长在途时间。

2xx 表示成功;除 429 外的 4xx 进入 Dead Letter;4295xx 和网络错误 重试。Retry-After 同时支持秒数和 HTTP-date,并有 24 小时安全上限;没有该 Header 时使用有限指数 Full Jitter。每次 Claim 都增加 attemptlease_revision,旧 Worker 无法覆盖重启回收或另一个 Worker 的新尝试。达到最大 尝试次数后进入 Dead Letter。

管理接口:

  • GET /v1/admin/webhooks/deliveries:按 app_idstatus 分页查看安全元数据;
  • POST /v1/admin/webhooks/deliveries:retry:仅允许重试 Dead Letter,并在同一 事务写入 webhook.delivery.retry 审计;
  • /metrics:暴露 xhim_webhook_pendingxhim_webhook_leasedxhim_webhook_deliveredxhim_webhook_dead_letterxhim_webhook_retry_due

五端 SDK 的一键发现还使用:

环境变量用途
XHIM_DEFAULT_APP_ID/v1/sdk/config 返回的默认租户 App ID
XHIM_ENABLE_DEVELOPMENT_LOGIN仅 Development 可开启的免手填凭证登录;Production 启动时会拒绝
XHIM_MINIMUM_CLIENT_PROTOCOL_VERSION最低客户端协议版本,默认 1,不得大于当前服务端协议版本

管理 API

管理 API 只用于你的业务服务端调用,不允许 App 持有 Admin Key。

自托管管理后台

启动 Server 后访问:

text
https://<你的 XHIM 域名>/admin/

Development 局域网环境也可访问 http://<Mac局域网IP>:18080/admin/。用 XHIM_ADMIN_KEY 登录后可以:

  • 查看服务状态、版本、媒体存储和公开 Endpoint;
  • 复制 SDK 所需的 Bootstrap URL、Endpoint Key ID 和 Ed25519 公钥;
  • 分页查看/创建用户和单聊/群聊会话;
  • 按 App + 用户/账号精确查看登录设备,并在二次确认后幂等撤销异常会话;
  • 按账号分页查询 Push 注册、查看失败状态并幂等禁用异常设备;
  • 通过管理 API 查询业务 Webhook 投递状态并审计重试 Dead Letter;
  • 为 Alice/Bob 等联调账号按设备 ID 和客户端平台签发短期测试 Token,并显示 session ID 与本次策略淘汰数量;
  • 查看群治理、成员和拉黑等安全审计记录。

五端 SDK Easy Login 使用两个公开、无管理权限的端点:

text
GET  /v1/sdk/config
POST /v1/sdk/development:login

/v1/sdk/config 只返回 Schema/Server 版本、服务端协议版本、最低客户端协议 版本、固定排序能力列表、环境、默认 App ID、Endpoint Key ID、Ed25519 公钥和 Easy Login 是否可用,不返回 Admin Key、服务端签名私钥或业务用户 Token。 该公开快照与认证成功响应来自同一个服务端能力对象,SDK 可在申请用户凭证前先 校验版本和能力,避免登录后才发现不兼容。/v1/sdk/development:login 只有在 XHIM_SERVER_ENV=developmentXHIM_ENABLE_DEVELOPMENT_LOGIN=true 时存在,只为已创建用户签发一小时开发 凭证;Production 配置会在进程启动前拒绝该开关,HTTP 层也会再次校验环境并 fail-closed,不会把生产免密登录暴露给客户端。

Easy Login 只接受 application/json,请求体硬上限为 4 KiB;成功配置响应和 凭证响应分别限制在 8 KiB、16 KiB。两个端点的成功、失败、404429 响应均为 Cache-Control: no-store。免密登录除全局入口限流外,还按可信客户端 IP 使用独立的单进程令牌桶(每秒 1 次、突发 5 次);多副本或可能被非受信网络 访问的 Development 环境仍应在反向代理设置更严格的集中式限流。访问日志只记录 方法、路由、状态、耗时和 Request ID,不记录请求体、用户 Token 或 Admin Key。

普通网页使用独立的 @xihansoftware/xhim-web 包。HTTP API 接受浏览器 CORS 预检;实时连接因浏览器不能自定义 WebSocket Authorization Header,会在 Sec-WebSocket-Protocol 中同时发送 xhim.v1xhim.bearer.<base64url-token>。服务端只协商并回显 xhim.v1,不会把凭证 子协议反射回页面。反向代理、WAF 和 APM 必须允许该 Header,并像 Authorization 一样脱敏,禁止写入访问日志。生产只允许 HTTPS/WSS,且通过 XHIM_ALLOWED_ORIGINS 限制审核过的网页来源。

浏览器只把 Admin Key 保存在当前标签的 sessionStorage,不会写入 URL、Cookie 或服务端日志。Production 应由 VPN/零信任网关或独立管理域名限制 /admin//v1/admin/* 的访问;页面不能代替业务系统的 RBAC。服务端域名、IP、WSS、 上传、下载、S3 和 RTC Provider 均由环境变量配置,客户可完全自主部署。

text
GET  /v1/admin/overview
GET  /v1/admin/users
GET  /v1/admin/conversations
GET  /v1/admin/sessions
POST /v1/admin/users:upsert
POST /v1/admin/conversations:create
POST /v1/admin/tokens:issue
POST /v1/admin/sessions:revoke
GET  /v1/admin/audit
GET  /v1/admin/push/devices
POST /v1/admin/push/devices:disable

设备会话查询必须传 app_id,并至少传一个 user_idaccount_id;两者 同时传入时必须解析为同一租户内的同一用户,否则返回 404,不会退化为模糊 查询或跨租户扫描。默认返回 50 条,最多 100 条,活跃会话优先,再按创建时间 倒序;truncated=true 表示存在未返回的历史记录:

text
GET /v1/admin/sessions?app_id=<app>&user_id=<user>&limit=100
GET /v1/admin/sessions?app_id=<app>&account_id=<account>&limit=100

响应只包含 App/用户/账号、稳定 session_iddevice_id、平台、 active|revoked|expired 状态和创建、最后活跃、过期、撤销时间及撤销原因。 管理端没有可信的“相对当前设备”,因此不会返回或伪造 current;只有用户本人 通过 /v1/sessions:list 查询时才有 principal-relative current。管理 API 和页面都不返回 Access Token、JWT、内部 token_id、Admin Key 或签名密钥。

查询响应固定限制在 2 MiB 以内。撤销请求只接受最大 8 KiB 的严格 JSON,未知 字段会被拒绝,且必须显式传 confirmed=true

json
{
  "app_id": "com.example.chat",
  "user_id": "alice",
  "account_id": "account-alice",
  "session_id": "session-id-from-admin-list",
  "mutation_id": "stable-admin-operation-id",
  "reason": "compromised device",
  "confirmed": true
}

mutation_id 必须在调用方重试期间保持不变。完全相同的重试返回第一次结果并 设置 idempotent_replay=true;改变目标或原因复用同一个 mutation ID 返回 409 idempotency_conflict。首次实际撤销与 session.revoked_by_admin 审计在同一 Memory 锁域或 PostgreSQL 事务中提交, 随后向目标 WSS 发送 SESSION_REVOKED,并清理该 session 的 Presence/Typing 租约。审计 Details 只包含设备、平台、管理控制面 actor 和原因,不包含任何 Token。普通 Bearer Token 不能访问这两个管理端点;普通 /v1/sessions:list/v1/sessions:revoke 仍严格绑定自己的 App + 账号 + 用户, 不能通过请求参数指定其他用户。

Push 设备查询必须同时传 app_idaccount_id,使用 after_registration_id 做稳定 keyset 分页,默认 50 条、每页最多 100 条:

text
GET /v1/admin/push/devices?app_id=<app>&account_id=<account>&limit=50

响应固定使用 schema_version=1,包含稳定 registration_id、用户、平台、环境、 启用状态、更新时间以及 failure_statustoken_fingerprintSHA-256 的不可逆短指纹,只用于客服排障关联;原始 Device Token 不会出现在 管理 API、管理页面或管理员禁用审计中。

禁用设备请求为严格 JSON:

json
{
  "app_id": "com.example.chat",
  "account_id": "account-alice",
  "registration_id": "alice-iphone"
}

同一注册重复禁用返回成功且 changed=false,不会重复写审计;首次从启用变为 禁用时,设备状态和 push.device_disabled_by_admin 审计在同一存储事务中提交。 registration_id 当前对应 SDK 注册时提供的稳定 device_id,其命名空间是 app_id + account_id。两个端点均要求 X-XHIM-Admin-Key,并返回 Cache-Control: no-store

客户端 API:

text
POST /v1/session:authenticate   application/x-protobuf
POST /v1/sessions:list         application/x-protobuf
POST /v1/sessions:revoke       application/x-protobuf
POST /v1/users/profile:get      application/x-protobuf
POST /v1/users/profiles:get     application/x-protobuf
POST /v1/users/profile:update   application/x-protobuf
POST /v1/conversations/direct:getOrCreate application/x-protobuf
POST /v1/messages:send         application/x-protobuf
POST /v1/messages:mutate       application/x-protobuf
POST /v1/messages/history:get  application/x-protobuf
POST /v1/messages:deleteForSelf application/x-protobuf
POST /v1/conversations:clear   application/x-protobuf
POST /v1/conversations:hide    application/x-protobuf
POST /v1/conversations:markRead application/x-protobuf
POST /v1/conversations:markAllRead application/x-protobuf
POST /v1/conversations:setPreference application/x-protobuf
POST /v1/sync                  application/x-protobuf
POST /v1/presence:publish      application/x-protobuf
POST /v1/typing:publish        application/x-protobuf
POST /v1/social/friend-requests:send application/x-protobuf
POST /v1/social/friend-requests:resolve application/x-protobuf
POST /v1/social/friendships:delete application/x-protobuf
POST /v1/social/friendships:setRemark application/x-protobuf
POST /v1/social/snapshot:get   application/x-protobuf
POST /v1/social/groups:create  application/x-protobuf
POST /v1/social/group-members:change application/x-protobuf
POST /v1/social/group-members:list application/x-protobuf
POST /v1/social/groups:leave application/x-protobuf
POST /v1/social/groups:dismiss application/x-protobuf
POST /v1/social/blocks:set application/x-protobuf
POST /v1/social/group-joins:request application/x-protobuf
POST /v1/social/group-joins:resolve application/x-protobuf
POST /v1/social/groups:govern application/x-protobuf
POST /v1/push/devices:register application/x-protobuf
POST /v1/push/devices:disable application/x-protobuf
POST /v1/calls:invite application/x-protobuf
POST /v1/calls:accept application/x-protobuf
POST /v1/calls:reject application/x-protobuf
POST /v1/calls:end application/x-protobuf
POST /v1/calls/signals:list application/x-protobuf
POST /v1/media/uploads:prepare application/x-protobuf
POST /v1/media/uploads:complete application/x-protobuf
POST /v1/media/downloads:authorize application/x-protobuf
GET  /v1/realtime              WebSocket, subprotocol xhim.v1

用户资料接口只接受当前 Bearer Token 所属租户:单用户查询不会暴露 account_id,批量查询最多 100 个去重 user_id,缺失用户在 missing_user_ids 中按请求顺序返回。资料更新只能修改当前用户,并通过 Protobuf optional 字段区分“不修改”和“清空”;重复提交同一最终状态不会 推进 updated_at

/v1/conversations/direct:getOrCreate 不接受客户端指定发起人或 App ID,只以 Token 用户和 peer_user_id 创建单聊。服务端先复用历史同成员单聊;首次创建时 按排序后的两个用户 ID 生成确定性 opaque ID,并在 Memory/PostgreSQL 中串行化 同一用户对的并发请求。双方反向调用会得到同一 conversation_id。接口返回 对端最小资料,消息发送阶段仍会执行双向黑名单校验。首次创建会在同一事务中向 双方账号流追加可跳过、版本化的 ConversationUpsert,因此同一用户的其他设备 无需轮询即可建立会话投影。当前用户资料实际变更时也会向自己的账号流追加 UserProfileUpsert;资料无变化时不追加重复事件。

/v1/messages/history:get 是服务端权威历史入口,结果按 server_seq 从新到旧 返回。before_server_seq=0 从当前高水位开始;下一页把响应中的 next_before_server_seq 原样传回,服务端使用严格 < 游标避免重复。默认每页 50 条、最多 200 条。查询要求 Token 用户仍是会话成员,并自动过滤该用户已执行 “仅为我删除”的消息以及 server_seq <= cleared_through_server_seq 的消息; 这些私有状态不会影响其他成员。响应中的 latest_server_seq 与历史页来自同一 数据库快照。

/v1/messages:deleteForSelf 只写当前用户的消息可见性,不修改共享消息; /v1/messages:mutate 的 Recall 才是面向所有成员的全局消息撤回。清空接口 /v1/conversations:clear 记录客户端明确提交的 through_server_seq,不得超过 会话最新序号;隐藏接口 /v1/conversations:hide 的高水位完全由服务端在事务内 快照当前最新序号,客户端不能伪造。三类写操作均要求调用方提供稳定 mutation_id 和精确 expected_revision:完全相同的重试返回原结果,不同载荷 复用 mutation ID 或 revision 过期返回冲突。成功变更只进入当前用户的账号事件 流,事件 Schema v2 且 skippable=true,旧客户端可安全跳过;新消息序号高于 hidden_through_server_seq 时,会话自然重新显示,无需服务端重置布尔值。

/v1/conversations:markRead 使用用户 Bearer Token,不接受客户端传入 user ID。 服务端校验用户仍是会话成员、read_server_sequence > 0 且不超过该会话最新 消息序号,再在同一事务中单调更新 conversation_reads(app_id, conversation_id, user_id)。首次 revision 为 1; 调用方可传 optional expected_revision 做精确 CAS,未传时兼容旧客户端的纯 单调更新。同值或低值即使带旧 revision 也按幂等读取返回当前状态,不追加重复 事件;提高已读序号时 revision 不匹配则返回冲突。

一次成功提高会原子追加两类账号事件:当前账号收到不可跳过的 ConversationReadUpsert,用于同账号多设备同步;会话内其他当前成员收到 Schema v2、skippable=trueConversationPeerReadUpsert,其中包含 reader_user_id、已读 server_seq 和 revision。客户端按 conversation_id + reader_user_id 覆盖投影即可得到单聊或群聊的成员已读状态; 已退出成员和会话外账号不会收到。已读事件不会触发离线 Push。

/v1/conversations:markAllRead 是独立的原子存储操作,不会循环调用上述单会话 接口。服务端只从 Bearer Token 取 App、账号和用户身份,在 Memory 的单一锁域或 PostgreSQL SERIALIZABLE 事务中锁定该用户当前有权限的全部会话,以每个会话在 该次调用快照中的最新 server_seq 批量提高 watermark。发送与批量已读并发时, 结果严格对应其中一个可串行化顺序;后到的新消息保留未读,不会被调用开始后 猜测性清零。会话数为 0 也会成功写入幂等回执。

请求必须提供稳定 mutation_id。完全相同的重试返回第一次提交的 changed_conversation_countscheduled_burn_count 和明细,并设置 idempotent_replay=true;同一用户复用 mutation ID 但改变 max_changed_reads 会返回幂等冲突。max_changed_reads=0 不返回逐会话明细, 1–100 最多返回对应数量,超过 100 会拒绝;完整变更计数始终返回, changed_reads_truncated 明确表示明细是否被截断,避免大账号产生巨大响应。 每个实际提高的会话仍在同一事务中增加独立单调 revision:当前账号收到不可 跳过的 ConversationReadUpsert,其他当前成员收到可跳过的 ConversationPeerReadUpsert。所有被 watermark 首次跨过的阅后即焚接收状态也 在同一事务中写入 read_crossed_atburn_due_at,不会出现“已读成功但焚毁 未安排”的部分提交。

权威阅后即焚

发送端可在不可变 SendMessageRequest 中附带生命周期策略;字段缺失、 UNSPECIFIED 或显式 DURABLE 都按普通永久消息处理,因而旧客户端完全兼容:

textproto
lifecycle_policy {
  kind: MESSAGE_LIFECYCLE_KIND_BURN_AFTER_READ
  burn_after_read_ms: 30000
}

burn_after_read_ms 是接收者读到消息之后的延时,而不是由发送设备决定的绝对 到期时间。硬边界为 1 秒至 30 天,部署默认再限制为最多 7 天;超出 XHIM_MAX_BURN_AFTER_READ 的发送会在持久化前被拒绝。生命周期属于消息不可变 幂等载荷:相同 client_message_id 改变策略或延时会返回幂等冲突,不能借重试 改变已发送消息的销毁语义。

服务端为每个接收成员分别保存状态,发送者不创建焚毁状态。某个接收者的已读 watermark 第一次跨过消息 server_seq 时,服务端以自己的时钟原子写入且永久 冻结 read_crossed_atburn_due_at;重复、降序、乱序或并发已读不会延后 期限。群聊成员各自计时,一个成员到期不影响发送者或其他成员。成员退出会立即 失去历史、同步、Push 和媒体访问权限,并由 worker 把其未完成状态收敛为私有 删除。Recall 仍是独立的全员永久撤回,不会被私有焚毁替代。

到达 burn_due_at 的精确边界后,即使后台 worker 尚未处理, /v1/messages/history:get/v1/sync、Push Claim 和媒体下载授权也会立即按 当前用户过滤正文。Sync 仍推进原始账号 Cursor,避免被不可见事件卡住。阅后即焚 Push 从创建时就只包含通用提示,不携带 fallback_text 或 Payload。图片、语音、 视频和文件的服务端授权会跟随内置 xhim.media.image/audio/video/file@1 Payload 中的 MediaRef;自定义消息若引用受保护资源,业务扩展必须实现同等的 授权策略,不能只依赖不可猜测 URL。

后台 worker 使用耐久 PostgreSQL 状态、有限批次和 FOR UPDATE ... SKIP LOCKED,支持进程重启和多副本并行;可见性行、私有 MessageVisibilityUpsertmessage.burn_after_read.expired 安全审计和完成 状态在同一事务提交。提交后再按 App/账号发布跨实例 Sync Hint,提示在线设备 拉取事件,Hint 本身不含消息正文。worker 与手工“仅为我删除”共用消息可见性锁, 无论先后或并发最终都只有 revision 1,不会产生第二次删除。

相关部署参数:

环境变量默认值约束/用途
XHIM_MAX_BURN_AFTER_READ168h租户发送上限,必须在 1s720h
XHIM_BURN_WORKER_BATCH_SIZE100单批最多 500 条,限制锁和事务规模
XHIM_BURN_WORKER_IDLE_INTERVAL1s空闲轮询间隔,必须大于 0 且不超过 1 分钟

/metrics 暴露 xhim_burn_after_read_waitingxhim_burn_after_read_scheduledxhim_burn_after_read_duexhim_burn_after_read_burnedxhim_burn_after_read_metrics_available。生产应对 due 持续增长或 metrics_available=0 告警,并结合审计确认 worker 是否正常收敛。

/v1/session:authenticate 返回当前 DeviceSession、登录策略、容量,以及 server_protocol_versionminimum_client_protocol_versionserver_version 和固定、排序、有界的 capabilities。当前服务端协议为 v2; 请求不带新增字段或显式传 client_protocol_version=0 时严格按 legacy v1 处理。默认最低版本保持 v1,因此现有客户端不会因能力协商上线而中断。部署方 只有在所有活跃 SDK 已发送 v2 后,才应把 XHIM_MINIMUM_CLIENT_PROTOCOL_VERSION 提升到 2;配置为 0、非数字或大于 服务端协议版本会导致服务启动失败。

新 SDK 可同时发送可读的 client_sdk_version 和最多 32 个 required_capabilities。缺少必需能力返回 HTTP 422、稳定错误码 unsupported_capability;客户端协议低于最低版本返回 HTTP 426、稳定错误码 client_upgrade_required;高于服务端当前协议返回 HTTP 422unsupported_protocol_version。这些检查在设备会话创建或 Touch 之前完成。 能力名称是闭集,只允许小写 ASCII 标识;响应列表最多 32 项,当前固定为:

text
call.signaling
conversation.direct
conversation.mark_all_read
conversation.peer_read
conversation.preferences
device.sessions
media.object_authorization
message.burn_after_read
message.history
message.mutations
message.private_views
presence
push.devices
session.capability_negotiation
social.friend_remarks
social.friendships
social.governance
social.groups
typing
user.profiles
web.browser_transport

管理端 签发测试 Token、Development Easy Login 以及正式 Credential Provider 都应 提供稳定 device_id 和平台(iosandroidmacoswindowsharmonyosweblinux)。每个 Token 对应一个服务端 session;同一设备 重新签发始终撤销旧 Token。single 只保留该用户最新 session, single_per_platform 每个平台只保留最新 session,multi 允许多端并存; 超过容量时按创建时间确定性淘汰最旧 session。

客户端用 /v1/sessions:list 展示设备列表,用稳定 mutation_id 调用 /v1/sessions:revoke 撤销自己的任一 session。撤销和策略淘汰会向目标 WSS 发送 SESSION_REVOKED 后关闭连接,同时清除该 session 的 Presence/Typing 租约。每个普通 HTTP 请求和 WSS 周期 Ping 都重新检查服务端 session 状态, 因此仅持有尚未过期的 JWT 不能绕过撤销。列表包含历史已撤销 session,便于用户 识别登录记录和原因;响应最多 100 条并优先返回全部活跃 session,再返回最近 历史记录。不得把服务端 session 表当作客户端密钥存储。

Presence 与 Typing 只走 WSS 临时帧,不写消息历史,也不进入 /v1/sync

  • /v1/presence:publish 支持 ONLINEAWAYOFFLINE。默认租约 90 秒, 允许 30–120 秒;同一用户多个 session 聚合时 ONLINE 优先于 AWAY,所有 租约断开、撤销或过期后才投影为 OFFLINE。WSS 心跳只延长当前 session 已有的 ONLINE/AWAY 状态,不会把业务刚设置的 AWAY 强行改回 ONLINE; 已过期或不存在的租约才重新建立为 ONLINE。
  • /v1/typing:publish 要求用户仍是会话成员,默认租约 5 秒,允许 1–10 秒; is_typing=false 可主动结束,未结束也会由 TTL 自动投影为 false。
  • Presence 只投递给本人账号、好友和共同会话成员,并排除任一方向已拉黑的 对端;Typing 只投递给会话成员,单聊存在任一方向黑名单时拒绝发布。两个接口 另外按 App + 账号 + 设备执行每秒 10 次、突发 20 次的独立限流。
  • 每个临时帧带稳定 event_id、作用域内递增 sequenceexpires_at_ms。客户端先按 event_id 去重,再只接受更高 sequence;本地 计时到期可先显示离线/停止输入,之后以服务端更高 sequence 为准。

PostgreSQL 使用共享的 UNLOGGED lease 表,使同一主库上的多副本能看到相同临时 状态,同时明确接受数据库重启/故障转移后全部离线的语义;逻辑 sequence 单独 持久化。每个活跃 WSS 都参与秒级过期触发,但单实例有全局节流,跨实例通过 LISTEN/NOTIFY 传播。临时通知是 best effort,客户端必须依赖 TTL,而不能把它 当作耐久业务事实。

/v1/social/groups:govern 使用 expected_revision 做乐观并发控制。群主可 设置管理员、转让群主、修改入群审批策略,以及原子更新群名、头像 URL、公告和 简介;群主或管理员可按权限禁言成员。成功变更会在同一事务中推进群 revision、 写审计并向成员账号流追加 GroupChanged,客户端通过 /v1/sync 更新 Schema v9 本地群投影。公告正文不会写进审计 Details。

好友删除、主动退群和群解散是独立的耐久社交生命周期操作:

  • /v1/social/friendships:delete 把双方 Friendship 投影设为 active=false,原子清除双方各自的私有备注,并分别向双方账号流追加以本人 视角编码、空备注 revision 0 的 FriendshipUpsert;它不会创建黑名单关系。 需要屏蔽用户必须单独调用 /v1/social/blocks:set
  • /v1/social/groups:leave 要求调用者是当前非群主成员,并使用 expected_revision 做 CAS。群主必须先转让群主,或直接解散群。
  • /v1/social/groups:dismiss 只允许当前群主调用;同一事务删除全部当前成员、 推进 revision、设置 member_count=0,并向解散前的全部成员账号流发送同一个 GroupChanged。空成员群是终态,不能重新申请加入。

三个请求都要求客户端生成独立、稳定的 mutation_id。同请求重试返回 idempotent_replay=true;复用 mutation ID 改变好友、群、操作类型或预期 revision 返回冲突。状态、账号事件、幂等回执和 friendship.delete/group.leave/group.dismiss 审计均原子提交。退出或解散 提交后,消息发送、媒体下载授权、Typing 和其他成员权限立即通过现有成员校验 失效;历史数据不会被物理删除。

好友备注是服务端权威的用户私有投影,不要求也不允许客户端再维护“本地备注优先” 规则。/v1/social/friendships:setRemark 只修改调用者 user -> peer 这一条边; 好友双方的备注、revision 和更新时间相互独立,任何一方都不能读取或覆盖另一方 的备注。请求仅允许当前活跃好友,remark 必须是合法 UTF-8、最多 512 字节, 空串表示清除;首次修改使用 expected_revision=0,之后必须精确匹配响应中的 remark_revision。每次成功设置或清除都会把 revision 加一。

备注请求要求独立、稳定的 mutation_id。相同参数重试返回 idempotent_replay=true,复用 mutation ID 改变 peer、备注或 expected_revision 返回冲突。备注状态、幂等回执、不包含备注正文的 friendship.remark.set 审计,以及仅面向调用者账号流的 FriendshipUpsert 在同一个事务提交;另一方不会收到备注事件或 Sync Hint。 好友请求首次接受或删除后重新接受时,双方备注都初始化为空、revision 为 0, 并开始新的 friendship created_at 生命周期。删除好友会原子清除双方方向的 备注,因此旧备注不会在重新加好友后恢复;inactive 好友不能设置备注。

通话协议位于 protocol/proto/xhim_call_v1.proto。服务端事件只保存 needs_credentials 标记,不把用户 RTC Token 广播或持久化到公共信令;接受方 在 Accept 响应取得自己的 Token,邀请方拉取 Accepted 信令时按身份即时取得 自己的 Token。

开发模式的媒体授权指向同一服务的 PUT/PATCH/GET /v1/media/objects/{opaque-key};本地对象端口支持固定大小分片 和按已提交 Offset 续传。Root 必须是专用真实目录;Root 被替换,或对象/ .partial 是 symlink、目录、设备等非普通文件时,上传、Verify、授权和下载均 fail closed。同一对象严格串行,不同对象使用固定 64 分片锁;完成文件通过同目录 hard-link 的 no-replace 语义发布,不会用可覆盖的 Rename 替换已有不同对象。

该本地 Adapter 只用于开发或可信私有环境。它使用 Go 标准库的 Lstat/Stat/SameFile 前后校验和排他创建降低路径替换风险,但标准库没有在 所有支持平台上提供完全一致的原子 no-follow 路径 API,不能作为对抗同机恶意 用户的多租户生产对象存储边界。生产必须使用私有 S3/S3-compatible Bucket、 最小权限 IAM 和短期预签名 URL。生产 S3 模式下媒体字节不经过 IM API 进程。 当前单对象上限为 256 MiB,适用于图片、语音、常见短视频和中小文件;超出上限 的大视频不受理。需要更大对象或高并发生产媒体面时,仍须交付真实 S3 multipart Session/Part 持久化、CDN 和对应容量证据。

Prepare 必须携带 conversation_id。服务端准备上传时校验上传者的会话成员 身份,并在每次下载授权时重新校验下载者仍是该会话成员,不能仅凭知道 media_id 越权下载。

健康检查:

text
GET /health/live
GET /health/ready

指标端点需要同一个 X-XHIM-Admin-Key,避免默认公开内部容量信息:

text
GET /metrics

Production 用户凭证由客户业务服务端换取,五端 SDK 的 Business Authentication/Credential Provider 会隐藏首次获取和续期细节。Admin Key、 签名私钥和 Cursor Key 绝不能进入客户端包、移动端配置或日志。

XHIM 客户端 SDK 与服务端文档