功能与套餐开通对照表
本项目是自托管产品,“套餐”体现为三层控制:① 服务开通项(8 个 service.activation 开关)决定相关 Open API 是否返回 1021;② 配额(4 项)在协议上限内进一步收紧;③ 控制台功能开关(6 项)写穿到前两层。没有计费、购买与到期,因此本页只对照“能力 ↔ 开通项 ↔ 接口 ↔ SDK ↔ Demo ↔ 控制台”,并标注哪些是代码强制、哪些只是记录。
概念
| 层 | 数据模型 | 作用点 | 默认值 |
|---|---|---|---|
| 服务开通 | ServiceActivation.services: map<ServiceKey,bool> | Open API 分发前、回调入队前、客户端 REST 建聊天室前 | 8 项全部开通 |
| 配额 | ServiceActivation.quotas(4 项) | 建群/加人/入聊天室时取 min(协议上限, 配额) | 0 = 使用协议上限 |
| 功能开关 | 控制台 features(6 项) | 写穿到 services 或 app.pushEnabled | 跟随服务开通项 |
当前契约表(Rust contract::dump_tables() 导出的机器产物 contract-tables.json)共 230 个 Open API 操作,其中 91 个声明了服务开通项;其余操作不校验开通状态。
前置条件
- 服务端已启动;已创建应用(
POST /api/admin/apps)。 - 持有 Admin Secret(
IM_ADMIN_SECRET,默认dev-admin)。 - 若要验证 1021,需要一个可签名的 Open API 调用(AppKey + AppSecret + 签名),见OpenAPI 与 Postman 调试。
客户端 WS 发送(send 帧)不读取服务开通项:关闭 private_chat 不会阻止已连接客户端继续发消息,只影响 Open API 发布接口。
敏感词过滤不依赖 sensitive_words 开关:只要应用配置了词表,命中就按 block/replace 处置;该开关只门禁 6 个词表管理接口。
步骤
- 查看当前开通项与配额
curl -sS -X GET "http://127.0.0.1:8080/api/admin/apps/demo/services" \ -H 'X-Admin-Secret: dev-admin'
- 关闭一个服务并验证 1021
curl -sS -X POST "http://127.0.0.1:8080/api/admin/apps/demo/services" \ -H 'X-Admin-Secret: dev-admin' \ -H 'content-type: application/json' \ -d '{"services":{"private_chat":false},"quotas":{"maxGroupMembers":100,"maxSuperGroupMembers":1000,"maxChatroomMembers":200,"messageRatePerMinute":60}}'
随后用 Open API 调
message.private.publish,应返回403 / code=1021;调user.get仍成功(该操作无 service 声明)。 - 验证配额生效
把
maxGroupMembers设为 3 后创建群并加第 4 个成员,应返回 1015(成员数量超出上限);普通群上限默认取protocol.limits.groupMaxMembers。配额只收紧、不放宽:即使配置值大于协议上限,实际仍以协议上限为准。
- 用控制台功能开关联动
curl -sS -X POST "http://127.0.0.1:8080/api/admin/apps/demo/features" \ -H 'X-Admin-Secret: dev-admin' \ -H 'content-type: application/json' \ -d '{"features":{"friend":false,"group":false,"chatroom":false,"push":false,"sensitiveWord":false,"messageCallbacks":false}}'
该接口把开关写穿到
services.private_chat/group/chatroom/sensitive_words/callbacks与app.pushEnabled。 - 恢复默认
curl -sS -X POST "http://127.0.0.1:8080/api/admin/apps/demo/services" \ -H 'X-Admin-Secret: dev-admin' \ -H 'content-type: application/json' \ -d '{"services":{"private_chat":true,"group":true,"supergroup":true,"chatroom":true,"system_notification":true,"callbacks":true,"sensitive_words":true,"message_routing":true},"quotas":{"maxGroupMembers":0,"maxSuperGroupMembers":0,"maxChatroomMembers":0,"messageRatePerMinute":0}}'
参数表
功能闭环表
| 功能 | 开通项 (service key) | Open API / IDL op | SDK op | Demo 入口 | 控制台 | 代码强制? |
|---|---|---|---|---|---|---|
| 单聊消息 | private_chat | message.private.publish | sendTextMessage / sendMessage | 会话(单聊) | #services | ✅ 代码强制 |
| 状态消息(单聊) | private_chat | statusmessage.private.publish | sendTypingStatus | 会话输入条 | #services | ✅ 代码强制 |
| 群聊消息 | group | message.group.publish | sendMessage(GROUP) | + 群聊 / 群管理 | #services | ✅ 代码强制 |
| 建群 / 群资料 | group | group.create / group.update | createGroup / updateGroupName | + 群聊 | #services | ✅ 代码强制 |
| 超级群(频道/用户组) | supergroup | supergroup.channel.create / supergroup.usergroup.create / message.group.publish(channelId) | createSupergroupChannel / createUserGroup(rev21);sendMessage options.channelId(rev23);频道 UI:ChannelSwitcher / ChannelListDialog / ChannelList(rev24,运行时组件) | 超级群场景 / 频道 / 频道级消息 / 频道 UI 指南 | #services | ✅ 代码强制 |
| 聊天室 | chatroom | chatroom.create / message.chatroom.publish | createChatroom / joinChatroom | + 聊天室 | #chatrooms | ✅ 代码强制 |
| 系统通知 | system_notification | message.system.publish | getSystemMessages(接收侧) | 系统通知会话 | #messages | ✅ 代码强制 |
| 全量消息路由 | message_routing | 无(未在任何 operation 上声明) | 无 | 无 | #services(仅展示) | 🟡 仅记录(代码未读取) |
| 服务端回调 | callbacks | callback.config.set / callback.delivery.retry | 无(接收方为应用服务器) | 无 | #callbacks | ✅ 代码强制 |
| 敏感词词表管理 | sensitive_words | sensitive-word.set / sensitiveword.batch.add | 无(发送时自动过滤) | 发送含敏感词消息 | #sensitive | ✅ 代码强制 |
| 好友关系 | (无) | friend.add / friend.list(未声明 service) | sendFriendRequest / getFriends | 联系人 | #users(关系视图) | ⚪ 无开通项(仅 features.friend → private_chat) |
| 在线状态 | (无) | user.online.check / presence.batch | getOnlineStatus / getOnlineStatusBatch | 联系人在线点 | #connections | ⚪ 无开通项 |
| 离线推送通道 | (无,改为 app.pushEnabled) | push.send / push.test(管理端) | 无(接收方 webhook) | 无 | #features(push) | ✅ 由应用推送开关强制 |
| 消息翻译 / AI 代理 | (无) | message.translate / agent.chat.generate | 无 | 无 | #features | ⚪ 由环境变量端点控制(未配置返回 1021) |
8 个服务开通项的强制点
| service key | 能力 | 声明该服务的操作数 | 强制机制 | 结论 |
|---|---|---|---|---|
private_chat | 单聊消息 | 2 | Open API message.private.publish / statusmessage.private.publish 声明 service;未开通返回 1021 | ✅ 代码强制 |
group | 普通群 | 23 | 23 个群相关 Open API 操作声明 service;群成员上限受配额约束 | ✅ 代码强制 |
supergroup | 超级群 | 29 | 29 个超级群/用户组/频道操作声明 service | ✅ 代码强制 |
chatroom | 聊天室 | 26 | 26 个聊天室操作 + 客户端 REST POST /api/apps/{appkey}/chatrooms | ✅ 代码强制 |
system_notification | 系统通知 | 1 | message.system.publish 发布前校验 | ✅ 代码强制 |
callbacks | 服务端回调 | 4 | 事件入队前校验(未开通则事件被丢弃)+ 4 个回调管理操作 | ✅ 代码强制 |
sensitive_words | 敏感词管理 | 6 | 6 个敏感词词表操作声明 service;命中过滤本身只要词表非空就生效 | ✅ 代码强制 |
message_routing | 全量消息路由 | 0 | 仅记录在开通项中,代码未读取(recorded-only) | 🟡 仅记录 |
计数由构建时解析服务端契约声明中的 service 字段得到;若解析失败则显示 0,页面仍可生成。
配额(services.quotas)
| 配额字段 | 默认 | 生效点 | 超出错误 |
|---|---|---|---|
maxGroupMembers | 3000(协议上限) | 建群、加群成员时取 min(协议上限, 配额) | 1015 MEMBERSHIP_LIMIT |
maxSuperGroupMembers | 100000(协议上限) | 超级群成员上限 | 1015 |
maxChatroomMembers | 5000(协议上限) | 加入聊天室时按在线人数校验 | 1015 |
messageRatePerMinute | 120(仅回显) | 未在发送路径强制;接口级限流请用 ratelimit | —(recorded-only) |
控制台功能开关 → 数据源
| 开关 | 写穿到 | 说明 |
|---|---|---|
friend | services.private_chat | 好友关系依赖单聊通道 |
group | services.group | 普通群;不影响 supergroup |
chatroom | services.chatroom | Open API 与客户端 REST 同源 |
push | app.pushEnabled | 离线推送 webhook 开关 |
sensitiveWord | services.sensitive_words | 词表管理接口开关(过滤逻辑独立) |
messageCallbacks | services.callbacks | 回调事件入队开关 |
示例
查询 → 关闭 → 断言 1021 → 恢复(Node.js)
// service-gate.mjs — 验证服务开通项对 Open API 的强制效果
import { createHash, randomBytes } from 'node:crypto';
const HOST = process.env.IM_HOST || 'http://127.0.0.1:8080';
const ADMIN_SECRET = process.env.IM_ADMIN_SECRET || 'dev-admin';
const APPKEY = process.env.IM_APPKEY || 'demo';
const APP_SECRET = process.env.APP_SECRET || 'YOUR_APP_SECRET'; // 变量:应用 AppSecret
async function admin(method, path, body) {
const res = await fetch(HOST + path, {
method,
headers: { 'X-Admin-Secret': ADMIN_SECRET, 'content-type': 'application/json' },
body: body ? JSON.stringify(body) : undefined,
});
return (await res.json()).data;
}
async function openapi(path, body) {
const nonce = randomBytes(8).toString('hex');
const timestamp = String(Date.now());
const signature = createHash('sha1').update(APP_SECRET + nonce + timestamp).digest('hex');
const res = await fetch(HOST + '/im/v2' + path, {
method: 'POST',
headers: {
'content-type': 'application/json',
'App-Key': APPKEY,
Nonce: nonce, Timestamp: timestamp, Signature: signature,
},
body: JSON.stringify(body),
});
return { status: res.status, body: await res.json() };
}
const before = await admin('GET', '/api/admin/apps/' + APPKEY + '/services');
console.log('当前 private_chat =', before.services.private_chat);
await admin('POST', '/api/admin/apps/' + APPKEY + '/services', { services: { private_chat: false } });
const blocked = await openapi('/message/private/publish', {
fromUserId: 'alice', toUserId: 'bob', content: { type: 'text', text: 'hi' },
});
console.log('关闭后发布单聊:', blocked.status, blocked.body.code); // 期望 403 / 1021
await admin('POST', '/api/admin/apps/' + APPKEY + '/services', { services: { private_chat: true } });
const ok = await openapi('/message/private/publish', {
fromUserId: 'alice', toUserId: 'bob', content: { type: 'text', text: 'hi' },
});
console.log('恢复后发布单聊:', ok.status, ok.body.code); // 期望 200 / 0恢复全部开通(curl)
curl -sS -X POST "http://127.0.0.1:8080/api/admin/apps/demo/services" \
-H 'X-Admin-Secret: dev-admin' \
-H 'content-type: application/json' \
-d '{"services":{"private_chat":true,"group":true,"supergroup":true,"chatroom":true,"system_notification":true,"callbacks":true,"sensitive_words":true,"message_routing":true},"quotas":{"maxGroupMembers":0,"maxSuperGroupMembers":0,"maxChatroomMembers":0,"messageRatePerMinute":0}}'常见问题
关闭 service 后客户端还能发消息?
能。客户端 WS send 帧不校验服务开通项;关闭只影响 Open API(以及客户端 REST 建聊天室)。若要做客户端级管控,请同时下发 features 快照到客户端,由客户端隐藏入口。
配额设得比协议上限大,会放宽吗?
不会。生效值取 min(协议上限, 配额);配额只能收紧。
message_routing 开关有用吗?
当前没有。它只出现在开通项列表中,代码没有读取点;全量消息路由由 callbacks 与订阅事件决定。
messageRatePerMinute 能限流吗?
不能。该字段只回显;真正的接口限流是 ratelimit 的按 operation QPS(超限 1008 + Retry-After: 1)。
为什么 features 里点「好友」关掉,单聊也不能发了?
friend 开关映射到 services.private_chat,因此 message.private.publish 会返回 1021。
如何判断某个 Open API 是否受开通项约束?
看本页「功能闭环表」;或查 API 文档中该接口说明,1021 出现在错误码列表即表示有 service 声明。
相关链接
- 查询服务开通项 / 设置服务开通项
- 限频、配额与容量限制
- 状态码与错误码:1021 / 1015 / 1008
- 问题排查手册:1021 与限流排查
- OpenAPI 与 Postman 调试:构造可签名调用验证 1021
- 消息计数、存储与推送策略