聊天室进阶:KV、保活、优先级与禁言

聊天室是在线态会话:成员关系绑定在 WebSocket 会话上,断连即离开;消息只广播给当时在线的成员,不落离线历史、不产生投递 ACK、不能拉取历史。本页覆盖房间级 KV 公告、保活与心跳、低优先级消息配置、白名单/禁言/封禁、销毁策略与 TTL,以及每一步的真实限制与错误码。

先记住三条能力边界

客户端 IMLib 只提供 5 个聊天室语义操作:createChatroom / joinChatroom / leaveChatroom / getChatroomInfo / getChatroomMembers。KV、保活、优先级、白名单、禁言、销毁都必须由 App Server 调 Open API(客户端不能持有 AppSecret)。

聊天室消息(conversationType=3)不走离线队列、没有 send_ack、getHistoryMessages(3, …) 返回空页;需要“可回溯的直播弹幕”时,请另建普通群或自行在业务侧归档。

保活只对空闲销毁策略生效;fixed(定时销毁)优先,保活不会顺延销毁时间。

生命周期与 TTL​

聊天室从创建开始计时,默认 30 分钟无活动即销毁(服务端可通过 IM_CHATROOM_IDLE_MS 调整,上限由部署决定)。房间内任何有效活动(加入、发言、KV 写入等)都会按策略顺延销毁时间。

阶段触发方式服务端行为客户端可观测
创建Open API chatroom.create / 客户端 createChatroom幂等:同 chatroomId 已存在返回现有房间;创建即写入销毁时间回调 chatroom.created;调用方拿到 Chatroom
加入WS chatroom_join → chatroom_joinedWS 会话级成员;房内广播 member_changed;10 秒未收到 joined 客户端按 3002 处理`Events.CHATROOM_EVENT`(kind=joined/member_changed)
活动发言 / KV / 加入 / 离开idle 策略下顺延 destroyAt;fixed 策略不下延getChatroomInfo 返回新的 destroyAt
销毁空闲超时 / chatroom.destroy / 管理端销毁 / fixed 到期踢出所有在线成员、广播 destroyed、清理 KV/禁言/封禁/白名单、回调 chatroom.destroyed`CHATROOM_EVENT`(kind=destroyed),后续调用返回 1012/1013
策略设置接口destroyAt 计算保活是否生效
idle(默认)chatroom.destroy.policy.set {destroyType:"idle", idleMs?}当前时间 + idleMs(缺省 1800000ms),每次活动顺延是:保活期内固定为保活到期时间
fixedchatroom.destroy.policy.set {destroyType:"fixed", idleMs}创建时间 + idleMs,活动不再顺延否:定时销毁优先
JavaScript
import { AppClient } from '@ni-paas-im/server-sdk';

const app = new AppClient({
  appKey: process.env.IM_APP_KEY,
  appSecret: process.env.IM_APP_SECRET,
  baseUrl: 'http://127.0.0.1:8080',
});

// 1) 创建(chatroomId 可省略,服务端生成)
const room = await app.createChatroom({ chatroomId: 'live_1001', name: '新品发布会', owner: 'anchor_1' });

// 2) 定时销毁:创建后 2 小时销毁,活动不延长
await app.openApi('chatroom.destroy.policy.set', {
  chatroomId: room.chatroomId,
  destroyType: 'fixed',
  idleMs: 2 * 60 * 60 * 1000,
});

// 3) 恢复空闲销毁(默认 30 分钟)
await app.openApi('chatroom.destroy.policy.set', {
  chatroomId: room.chatroomId,
  destroyType: 'idle',
  idleMs: 1800000,
});

// 4) 主动销毁:踢人 + 广播 destroyed + 清理房间级状态
await app.destroyChatroom({ chatroomId: room.chatroomId });

KV 公告:房间级键值​

房间级 KV 用于公告、置顶、榜单快照等随房间销毁的小数据。值是字符串,支持可选 ttlMs(惰性过期:读取时清理,不保证到点即删)。KV 变更会回调 chatroom.kv_changed,便于 App Server 同步外部缓存。

操作Open API关键参数说明
写入chatroom.kv.set{chatroomId, key, value, ttlMs?}同 key 覆盖;ttlMs 省略或 ≤0 表示不过期;服务端未设 key/value 长度上限(受请求体与网关约束,建议 key ≤128 字节、value ≤4KB)
读取chatroom.kv.get{chatroomId, keys?}keys 省略返回全部未过期项
删除chatroom.kv.remove{chatroomId, key}返回删除后的剩余 KV 列表;幂等
客户端读取getChatroomInfo 不含 KV—客户端没有 KV API;公告由 App Server 下发或通过 chatroom.kv_changed 推送到业务通道
JavaScript
// 公告写入:30 分钟后自动过期(房间销毁时一并清理)
await app.openApi('chatroom.kv.set', {
  chatroomId: 'live_1001',
  key: 'announcement',
  value: JSON.stringify({ text: '21:00 抽奖,请留在直播间', pinnedAt: Date.now() }),
  ttlMs: 30 * 60 * 1000,
});

// 读取单个键 + 读取全部
const one = await app.openApi('chatroom.kv.get', { chatroomId: 'live_1001', keys: ['announcement'] });
const all = await app.openApi('chatroom.kv.get', { chatroomId: 'live_1001' });

// 删除公告
await app.openApi('chatroom.kv.remove', { chatroomId: 'live_1001', key: 'announcement' });
回调接线

订阅 chatroom.kv_changed 时,载荷包含房间与键的变更信息;把公告同步到你自己的直播控制台或推流侧,不要依赖客户端轮询 KV。

KV 不做历史版本,也没有 CAS/事务;并发写入以最后一次为准。需要强一致的计数请用业务库,不要用 KV 做库存扣减。

保活与心跳​

聊天室有两套“心跳”,作用完全不同,不要混用:

机制参数 / 行为用途
WS 心跳客户端每 30s 发送 ping;服务端 90s 无活动判定空闲;要求 pong;连续 60s 未收到 pong 触发重连判断一个 WebSocket 会话是否还活着;断连即离开房间
聊天室保活chatroom.keepalive:把 destroyAt 置为当前时间 + 3 个月(90 天)防止空闲销毁;只对 idle 策略生效
保活查询 / 移除chatroom.keepalive.get(返回应用下已保活的房间列表)/ chatroom.keepalive.remove运营排查与释放保活
重连自动补 joinSDK 在重连成功后自动重新加入已加入的聊天室(10 秒超时 3002)避免重连后“人在房间但收不到消息”
JavaScript
// 长期活动房间:保活 3 个月,避免空闲销毁
await app.openApi('chatroom.keepalive', { chatroomId: 'live_1001' });

// 查询应用下当前处于保活状态的聊天室
const { chatroomIds } = await app.openApi('chatroom.keepalive.get', {});

// 释放保活:恢复空闲销毁计时(下次活动起重新计算)
await app.openApi('chatroom.keepalive.remove', { chatroomId: 'live_1001' });
保活不是永久

3 个月到期后房间仍会被空闲销毁;长期房间应在到期前重新调用 chatroom.keepalive,或在业务侧用定时任务续期。

消息优先级​

聊天室支持配置“低优先级消息类型”清单。服务端只存储与查询该配置,当前投递路径不会据此改变发送顺序或丢弃消息;它的用途是让客户端/业务侧按同一份配置做渲染降级(例如高并发时折叠图片、文件、位置等次要消息)。

项值
允许的类型text / image / file / voice / location / custom / recall / system / quote(共 9 种,大小写不敏感;未知类型返回 1001)
写入语义chatroom.message.priority.set 全量覆盖(不是增量),重复类型自动去重
查询chatroom.message.priority.list 返回 {chatroomId, lowPriorityTypes}
前置条件房间必须存在且未销毁;否则 1012 / 1013
JavaScript
// 高并发直播间:图片/文件/位置按低优先级渲染(配置全量覆盖)
await app.openApi('chatroom.message.priority.set', {
  chatroomId: 'live_1001',
  messageTypes: ['image', 'file', 'location'],
});

const { lowPriorityTypes } = await app.openApi('chatroom.message.priority.list', {
  chatroomId: 'live_1001',
});
console.log(lowPriorityTypes); // ['image','file','location']
不要把它当限流或 QoS

低优先级配置不改变服务端投递、不限频、不丢消息。真正的频率约束是每用户每分钟发送上限(120 条,超出 1008)与消息体 5KB(1007)。

白名单、禁言与封禁​

房间级治理有四种状态,优先级从高到低:白名单(豁免)> 封禁(踢出且不可再加入)> 全员禁言 > 单人禁言。白名单用户不受封禁、禁言与全员禁言限制。

能力Open API参数行为
单人禁言 / 解禁chatroom.mute{chatroomId, userId, mutedUntil}mutedUntil 为 epoch ms;0 表示解禁;房主/管理员可操作
封禁 / 解封chatroom.ban{chatroomId, userId, banned}封禁后立即踢出且不可再加入;解封后可重新加入
封禁列表chatroom.ban.list{chatroomId, offset?, limit?}分页;time 为解禁时间(UTC),空串表示未设置时长
禁言列表chatroom.mute.list{chatroomId, offset?, limit?}仅返回未到期记录
全员禁言chatroom.mute-all / chatroom.mute-all.get{chatroomId, muted, operatorId?}房主/管理员可操作;白名单成员不受限
白名单chatroom.whitelist.add/remove/list{chatroomId, userIds}豁免封禁/禁言/全员禁言;随房间销毁清理
全局禁言chatroom.globalMute.set/remove/list{userIds ≤20, minute 1..43200}应用级:约束该用户在所有聊天室的客户端发送
消息类型白名单chatroom.messageWhitelist.add/list/remove{objectnames ≤20 种,≤32 字符}应用级共享;重复添加/移除幂等
JavaScript
// 房主/管理员:禁言 10 分钟 + 全员禁言 + 白名单豁免
const mutedUntil = Date.now() + 10 * 60 * 1000;
await app.openApi('chatroom.mute', { chatroomId: 'live_1001', userId: 'spammer_1', mutedUntil });

await app.openApi('chatroom.mute-all', { chatroomId: 'live_1001', muted: true, operatorId: 'anchor_1' });

// 运营号/管理员进白名单:不受禁言/封禁/全员禁言影响
await app.openApi('chatroom.whitelist.add', { chatroomId: 'live_1001', userIds: ['ops_1', 'anchor_1'] });

// 封禁恶意用户(立即踢出,不可再加入)
await app.openApi('chatroom.ban', { chatroomId: 'live_1001', userId: 'spammer_1', banned: true });
权限与错误码

禁言/封禁/全员禁言要求操作者是房主或管理员,否则 1003;目标用户不在房间返回 1014;房间不存在 1012、已销毁 1013。

被禁言用户发送消息会收到 1009(群/聊天室禁言统一语义);被封禁用户无法加入,返回 1014。

全局禁言只约束客户端发送;服务端 Open API 以用户身份发消息不受此限制(运营需要可控补发时才有意义)。

客户端接入(Web / Android / Flutter)​

客户端只负责“进房、收消息、离房、查在线成员”。以下代码使用各端真实绑定方法名与事件名,可直接对照 API 参考核对签名。

Web(浏览器 ES Module)​

JavaScript
import { createClient, ConversationType, Events } from '@ni-paas/im-web';

const client = createClient({ appkey: 'YOUR_APP_KEY', serverUrl: 'http://127.0.0.1:8080' });
await client.connect(TOKEN);

client.addEventListener(Events.CHATROOM_EVENT, (event) => {
  // kind: joined | left | kicked | destroyed | member_changed
  console.log(event.chatroomId, event.kind, event.onlineCount);
});

const room = await client.createChatroom({ chatroomId: 'live_1001', name: '新品发布会', owner: 'anchor_1' });
await client.joinChatroom(room.chatroomId);

client.addEventListener(Events.MESSAGES, ({ messages }) => {
  for (const message of messages) {
    if (message.conversationType === ConversationType.CHATROOM) renderDanmaku(message);
  }
});

// 聊天室消息:仅广播在线成员,无 ack、无历史
await client.sendTextMessage(ConversationType.CHATROOM, 'live_1001', '主播好!');
const { onlineCount, members } = await client.getChatroomInfo('live_1001').then(async (info) => ({
  onlineCount: info.onlineCount,
  members: await client.getChatroomMembers('live_1001', { offset: 0, limit: 50 }),
}));
await client.leaveChatroom('live_1001');

Android(Kotlin,回调式)​

Kotlin
val client = IMClient.init(applicationContext, IMClientOptions(appkey = "YOUR_APP_KEY", serverUrl = "http://127.0.0.1:8080"))
client.connect(token, object : ConnectCallback {
    override fun onSuccess(userId: String) {
        client.addEventListener(IMClientEvent.CHATROOM_EVENT) { event ->
            // event.kind: joined / left / kicked / destroyed / member_changed
            Log.d("chatroom", "${event.chatroomId} ${event.kind}")
        }
        client.createChatroom(CreateChatroomOptions(chatroomId = "live_1001", name = "新品发布会", owner = "anchor_1")) { created ->
            client.joinChatroom(created.chatroomId, object : ResultCallback<Chatroom> {
                override fun onSuccess(room: Chatroom) = Unit
                override fun onError(error: IMError) = Log.e("chatroom", "${error.code} ${error.message}")
            })
        }
    }
    override fun onError(error: IMError) = Log.e("chatroom", "${error.code} ${error.message}")
})

Flutter(Dart,Future)​

Dart
final engine = await RCIMIWEngine.create(
  'YOUR_APP_KEY',
  RCIMIWEngineOptions.create(naviServer: 'http://127.0.0.1:8080'),
);
await client.connect(token);

client.addEventListener('CHATROOM_EVENT', (event) {
  // event.kind: joined / left / kicked / destroyed / member_changed
  debugPrint('${event.chatroomId} ${event.kind}');
});

final room = await client.createChatroom(chatroomId: 'live_1001', name: '新品发布会', owner: 'anchor_1');
await client.joinChatroom(room.chatroomId);
await client.sendTextMessage(3, 'live_1001', '主播好!'); // 3 = ConversationType.CHATROOM
final info = await client.getChatroomInfo('live_1001');
final page = await client.getChatroomMembers('live_1001', offset: 0, limit: 50);
await client.leaveChatroom('live_1001');

限制与错误码​

限制项值来源
单房间在线人数5,000protocol.limits.chatroomMaxOnline
消息体≤5KB(5120 字节),文本 ≤5,000 字符protocol.limits.messageBytes / textChars
发送频率每用户 120 条/分钟(超出 1008)protocol.limits.sendRatePerMinute
默认空闲销毁1,800,000ms(30 分钟)protocol.chatroomSemantics.idleDestroyMs
保活时长90 天(3 个月)服务端常量 KEEPALIVE_MS
低优先级消息类型9 种允许值;全量覆盖Open API 校验
全局禁言批量≤20 用户;1–43,200 分钟chatroom.globalMute.set
存在性批量查询≤1,000 用户chatroom.members.existsBatch
自定义属性批量≤100 对 / ≤100 keychatroom.entry.batch.*
消息类型白名单应用级 ≤20 种,每种 ≤32 字符chatroom.messageWhitelist.*
错误码名称典型场景
1001INVALID_PARAMETER参数缺失/类型错误;低优先级类型不在允许清单;批量超限
1003FORBIDDEN非房主/管理员执行禁言、封禁、全员禁言
1008RATE_LIMITED发言超过每分钟 120 条(客户端发送经 WS 同样受限)
1009GROUP_MUTED被禁言用户尝试发送
1010SENSITIVE_WORD内容命中敏感词(按控制台处置动作)
1012CHATROOM_NOT_FOUND房间不存在或从未创建
1013CHATROOM_CLOSED房间已销毁(含空闲/定时到期)
1014NOT_CHATROOM_MEMBER未加入房间就发言 / 封禁后尝试加入 / 禁言目标不在房间
1015MEMBERSHIP_LIMIT创建房间时成员数超限(批量初始化场景)
1021SERVICE_NOT_ACTIVATED应用未开通聊天室服务
排障顺序

先看错误码:1012/1013 是房间生命周期问题,1014 是成员关系问题,1003 是角色问题。

房间“突然没了”优先检查销毁策略与保活:idle 默认 30 分钟,fixed 到期即销毁;销毁后 KV/白名单不可恢复。

客户端收不到消息但 REST 显示在线:确认 joinChatroom 的 chatroom_joined 是否成功(10 秒超时 3002),以及 CHATROOM_EVENT 监听是否在 connect 之前注册。

相关页面​