问题排查手册
本页把“用户说有问题”收敛成 10 类可复现症状,每类给出判断依据、服务端已暴露的排查接口与可直接执行的命令。排查依赖的四个数据源是:消息投递轨迹(K19 消息流转)、连接明细(K25 连接监控)、回调投递日志、操作日志(审计)。所有接口都使用 X-Admin-Secret,默认 dev-admin。
概念
| 数据源 | 能看到什么 | 看不到什么 |
|---|---|---|
| 消息投递轨迹 | 消息是否入库、在线投递到几个会话、是否离线待补发、ACK 时间、关联回调投递结果 | 客户端 UI 是否真的渲染;聊天室消息(不落库) |
| 连接明细 | 当前在线 sessionId、connectedAt、lastHeartbeatAt、用户会话数 | 历史连接断线原因;device 字段当前恒为空 |
| 回调投递日志 | 事件类型、URL、响应码/错误、尝试次数、重试结果 | 对端业务处理结果(只看 HTTP 状态) |
| 操作日志(审计) | 管理端写操作:谁、何时、对什么对象、成功/失败 | 失败请求的完整入参;读操作不记录 |
排查顺序建议
先确认“连接是否在线”,再看“消息是否有投递记录”,最后看“回调/审计”。90% 的“收不到消息”可以先用一条消息的 trace 定位到 delivered / offline_queued / failed。
前置条件
- 服务端已启动且 Admin API 可达:
curl -sS http://127.0.0.1:8080/health返回{"ok":true}。 - 知道要排查的
appkey、userId;消息问题尽量拿到messageId(或按用户/时间检索)。 X-Admin-Secret与服务端IM_ADMIN_SECRET一致。- 若要看服务端日志,确认启动时
IM_DATA_DIR指向的目录(默认data/)。
步骤:10 类症状的排查路径
1. 客户端一直“连接中”或反复重连
| 项 | 内容 |
|---|---|
| 先看 | 用户是否有在线会话、最近心跳时间 |
| 再看 | Token 是否有效(被吊销/封禁后建连直接失败) |
| 常见原因 | 地址写错(http/https 与 wss/ws 不匹配)、Token 过期(7 天)、用户被封禁(1016)、应用停用(1017)、本机网络拦截 WS |
Shell
curl -sS -X GET "http://127.0.0.1:8080/api/admin/apps/demo/connections?userId=alice" \
-H 'X-Admin-Secret: dev-admin'
# 连接统计:在线用户数 / 会话数 / 今日峰值
curl -sS -X GET "http://127.0.0.1:8080/api/admin/apps/demo/connections/stats" \
-H 'X-Admin-Secret: dev-admin'2. 建连返回 401/403(1002 / 1016 / 1017)
| 项 | 内容 |
|---|---|
| 先看 | 用户详情中的 banned / tokenVersion;应用是否停用 |
| 判断 | 1002=Token 无效或已吊销;1016=用户被封禁/注销;1017=AppKey 不存在或应用停用 |
| 处置 | 解封(banned:false)或重新签发 Token;确认 AppKey 拼写 |
Shell
curl -sS -X GET "http://127.0.0.1:8080/api/admin/apps/demo/users/alice" \
-H 'X-Admin-Secret: dev-admin'
# 重新签发 Token(返回 data.token,7 天有效)
curl -sS -X POST "http://127.0.0.1:8080/api/admin/apps/demo/users/alice/token" \
-H 'X-Admin-Secret: dev-admin'
# 让旧 Token 全部失效(会向在线端推送 kick,code=1002)
curl -sS -X POST "http://127.0.0.1:8080/api/admin/apps/demo/users/alice/token/reset" \
-H 'X-Admin-Secret: dev-admin'3. 发送消息返回 1009 / 1016 / 1021
| 项 | 内容 |
|---|---|
| 1009 | 被禁言:用户级禁言(mutedUntil)、群成员禁言、群全员禁言或聊天室全员禁言 |
| 1016 | 发送者被封禁或已注销 |
| 1021 | 发送者所在能力未开通(单聊/群/聊天室/敏感词等) |
| 先看 | 用户详情 + 服务开通项 + 功能开关 |
Shell
curl -sS -X GET "http://127.0.0.1:8080/api/admin/apps/demo/users/alice" \
-H 'X-Admin-Secret: dev-admin'
# 服务开通项(8 个 service key)
curl -sS -X GET "http://127.0.0.1:8080/api/admin/apps/demo/services" \
-H 'X-Admin-Secret: dev-admin'
# 功能开关(映射到服务开通项与推送开关)
curl -sS -X GET "http://127.0.0.1:8080/api/admin/apps/demo/features" \
-H 'X-Admin-Secret: dev-admin'4. 发送方显示成功,接收方没收到
| 项 | 内容 |
|---|---|
| 第一步 | 按发送者/目标检索消息,拿到 messageId 与 seq |
| 第二步 | 查该消息轨迹:status 为 delivered 表示已投递到在线会话;offline_queued 表示对方不在线,等待重连补发;failed 表示回调链路失败 |
| 第三步 | 若 offlineQueued=true,查对方是否在线、是否 ACK 过更早的消息(游标已推进) |
Shell
curl -sS -X GET "http://127.0.0.1:8080/api/admin/apps/demo/messages?userId=alice&limit=1" \
-H 'X-Admin-Secret: dev-admin'
# 用上一步返回的 messageId 查轨迹
curl -sS -X GET "http://127.0.0.1:8080/api/admin/apps/demo/messages/<MESSAGE_ID>/trace" \
-H 'X-Admin-Secret: dev-admin'5. 重连后收不到离线消息,或同一条消息重复出现
| 项 | 内容 |
|---|---|
| 收不到 | 查看消息轨迹的 offlineQueued;确认对方重连后 hello.syncState 的 deliveredSeq 是否已覆盖该消息;确认历史是否被 message.history.clean 清理 |
| 重复 | 至少一次投递语义:客户端必须按 messageId 去重;未 ACK 的消息每次重连都会重放 |
| 游标 | 游标按“用户+会话”记录,任一端 ACK 即推进,与设备无关 |
Shell
curl -sS -X GET "http://127.0.0.1:8080/api/admin/apps/demo/messages/<MESSAGE_ID>/trace" \
-H 'X-Admin-Secret: dev-admin'
# 服务端当前连接(重连后应为新 sessionId)
curl -sS -X GET "http://127.0.0.1:8080/api/admin/apps/demo/connections?userId=alice" \
-H 'X-Admin-Secret: dev-admin'6. 回调没有到达应用服务器
| 项 | 内容 |
|---|---|
| 先看 | 回调配置:enabled、url、订阅的 events |
| 再看 | 投递日志:按 status=failed 过滤,读 error / responseCode / attempts |
| 重试 | 失败记录可单条重试;重试会重新 POST 当前事件 |
Shell
curl -sS -X GET "http://127.0.0.1:8080/api/admin/apps/demo/callbacks" \
-H 'X-Admin-Secret: dev-admin'
curl -sS -X GET "http://127.0.0.1:8080/api/admin/apps/demo/callbacks/deliveries?status=failed&limit=20" \
-H 'X-Admin-Secret: dev-admin'
# 取上一步 items[].deliveryId 重试
curl -sS -X POST "http://127.0.0.1:8080/api/admin/apps/demo/callbacks/deliveries/<DELIVERY_ID>/retry" \
-H 'X-Admin-Secret: dev-admin'7. 回调到达但签名校验失败
| 项 | 内容 |
|---|---|
| 算法 | X-IM-Signature = hex(hmac_sha256(secret, timestamp + '.' + nonce + '.' + rawBody)),另有 X-IM-Timestamp / X-IM-Nonce |
| 注意 | 签名基于原始请求体字节,必须先取 raw body 再解析 JSON;中间件改写 body 会导致验签失败 |
| 排查 | 用「回调测试」触发一条真实投递,核对服务端记录与对端日志 |
Shell
curl -sS -X POST "http://127.0.0.1:8080/api/admin/apps/demo/callbacks/test" \
-H 'X-Admin-Secret: dev-admin' \
-H 'content-type: application/json' \
-d '{"eventType":"test.ping","payload":{"hello":"world"}}'
# 修改回调地址/密钥/事件(secret 传空串表示不修改)
curl -sS -X POST "http://127.0.0.1:8080/api/admin/apps/demo/callbacks" \
-H 'X-Admin-Secret: dev-admin' \
-H 'content-type: application/json' \
-d '{"enabled":true,"url":"https://example.com/im/callback","secret":"new-secret","events":["message.routed","message.offline"]}'8. 消息内容被拦截或被替换
| 项 | 内容 |
|---|---|
| 判断 | 敏感词服务命中后按策略 block(拦截)或 replace(替换);消息轨迹可能仍有记录但正文已掩码 |
| 查看 | 敏感词列表与动作策略;审核任务(replace 模式会建任务并关联 messageId) |
| 复核 | 用消息检索确认实际入库内容;审核结果会通过 message.audit_result 回调推送 |
Shell
curl -sS -X GET "http://127.0.0.1:8080/api/admin/apps/demo/sensitive-words" \
-H 'X-Admin-Secret: dev-admin'
# 默认只返回 pending;status 支持 pending/approved/rejected
curl -sS -X GET "http://127.0.0.1:8080/api/admin/apps/demo/moderation/tasks?status=pending&limit=20" \
-H 'X-Admin-Secret: dev-admin'9. 接口持续返回 429(1008 限流)或 400(1015 成员上限)
| 项 | 内容 |
|---|---|
| 1008 | Open API 接口级 QPS 超限;响应头带 Retry-After: 1 |
| 1015 | 成员数量超出上限:普通群/超级群/聊天室人数触达配额或协议上限 |
| 1025 | QUOTA_EXCEEDED 在错误码表声明为 429,但当前服务端实现未返回;遇到 429 先按 1008 处理 |
| 配置 | 接口 QPS 与配额在管理端配置;未配置时按协议默认(不额外限频) |
Shell
curl -sS -X GET "http://127.0.0.1:8080/api/admin/apps/demo/ratelimit" \
-H 'X-Admin-Secret: dev-admin'
# 关闭某个操作的 QPS 限制(qps=0 表示不限制)
curl -sS -X POST "http://127.0.0.1:8080/api/admin/apps/demo/ratelimit" \
-H 'X-Admin-Secret: dev-admin' \
-H 'content-type: application/json' \
-d '{"overrides":{"message.private.publish":0}}'
# 查看/调整群成员配额
curl -sS -X GET "http://127.0.0.1:8080/api/admin/apps/demo/services" \
-H 'X-Admin-Secret: dev-admin'10. 找不到“是谁改了配置/封了人”
| 项 | 内容 |
|---|---|
| 数据源 | 操作日志(审计):actor / action / object / result / detail / timestamp |
| 过滤 | 支持 actor、action、object、from、to,按时间倒序分页 |
| 注意 | 自托管单管理员模型下 actor 固定为 admin;审计只记录管理端写操作 |
Shell
curl -sS -X GET "http://127.0.0.1:8080/api/admin/apps/demo/audit?limit=20" \
-H 'X-Admin-Secret: dev-admin'
# 只看封禁相关动作
curl -sS -X GET "http://127.0.0.1:8080/api/admin/apps/demo/audit?action=admin.users.ban&limit=20" \
-H 'X-Admin-Secret: dev-admin'参数表
排查接口速查
| 用途 | 方法与路径 | 关键参数 | 鉴权 |
|---|---|---|---|
| 消息检索 | GET /api/admin/apps/{appkey}/messages | userId、conversationType、targetId、keyword、from、to、offset、limit | X-Admin-Secret |
| 消息轨迹 | GET /api/admin/apps/{appkey}/messages/{message_id}/trace | 路径参数 message_id | X-Admin-Secret |
| 连接明细 | GET /api/admin/apps/{appkey}/connections | userId、offset、limit | X-Admin-Secret |
| 连接统计 | GET /api/admin/apps/{appkey}/connections/stats | — | X-Admin-Secret |
| 用户详情 | GET /api/admin/apps/{appkey}/users/{user_id} | 路径参数 user_id | X-Admin-Secret |
| 回调配置 | GET/POST /api/admin/apps/{appkey}/callbacks | POST:enabled、url、secret、events、retry | X-Admin-Secret |
| 回调投递日志 | GET /api/admin/apps/{appkey}/callbacks/deliveries | eventType、status、from、to、offset、limit | X-Admin-Secret |
| 回调重试 | POST /api/admin/apps/{appkey}/callbacks/deliveries/{delivery_id}/retry | 路径参数 delivery_id | X-Admin-Secret |
| 回调测试 | POST /api/admin/apps/{appkey}/callbacks/test | eventType?、payload? | X-Admin-Secret |
| 操作日志 | GET /api/admin/apps/{appkey}/audit | actor、action、object、from、to、offset、limit | X-Admin-Secret |
| 服务开通 / 功能开关 | GET/POST /api/admin/apps/{appkey}/services、/features | services、quotas / features | X-Admin-Secret |
| 接口调频 | GET/POST /api/admin/apps/{appkey}/ratelimit | overrides(operationId→QPS,0 表示不限) | X-Admin-Secret |
| 敏感词 / 审核任务 | GET /api/admin/apps/{appkey}/sensitive-words、/moderation/tasks | limit、offset、status(pending/approved/rejected) | X-Admin-Secret |
| 告警事件 | GET /api/admin/apps/{appkey}/alerts/events | metric、offset、limit | X-Admin-Secret |
| 错误码对照 | GET /api/admin/api-metadata + 状态码页 | — | X-Admin-Secret |
消息轨迹 status 取值
| status | 含义 | 下一步 |
|---|---|---|
stored | 已入库,尚无投递与路由记录 | 检查接收者是否在线(connections) |
delivered | 已投递到至少一个在线会话(deliveredSessions>0) | 问题在客户端渲染或端上过滤 |
offline_queued | 接收者不在线,等待重连补发 | 确认重连与 deliveredSeq;确认未被历史清理 |
routed | 已进入回调路由(routed=true),但未在线投递 | 查回调投递日志 |
failed | 存在失败的回调投递 | 查 callbackDeliveries[].error 并重试 |
示例
一条消息的全链路排查(Node.js)
triage.mjs
// triage.mjs — 按 messageId 输出:轨迹 → 接收者连接 → 回调投递 → 最近审计
const HOST = process.env.IM_HOST || 'http://127.0.0.1:8080';
const SECRET = process.env.IM_ADMIN_SECRET || 'dev-admin';
const APPKEY = process.env.IM_APPKEY || 'demo';
const MESSAGE_ID = process.env.IM_MESSAGE_ID || 'm_10001'; // 变量:先用 /messages 查到
const USER_ID = process.env.IM_USER_ID || 'bob'; // 变量:接收者
async function api(path) {
const res = await fetch(HOST + '/api/admin/apps/' + APPKEY + path, {
headers: { 'X-Admin-Secret': SECRET },
});
if (!res.ok) throw new Error(path + ' -> HTTP ' + res.status + ' ' + (await res.text()));
return (await res.json()).data;
}
const trace = await api('/messages/' + encodeURIComponent(MESSAGE_ID) + '/trace');
console.log('轨迹:', JSON.stringify({
status: trace.status,
ackAt: trace.ackAt,
deliveredSessions: trace.deliveredSessions,
offlineQueued: trace.offlineQueued,
routed: trace.routed,
}, null, 2));
const conns = await api('/connections?userId=' + encodeURIComponent(USER_ID));
console.log('接收者会话数:', conns.total, JSON.stringify(conns.connections.map((c) => c.sessionId)));
const deliveries = await api('/callbacks/deliveries?limit=5');
console.log('最近回调投递:', JSON.stringify(deliveries.items.map((d) => ({
eventType: d.eventType, status: d.status, responseCode: d.responseCode, error: d.error,
})), null, 2));
const audit = await api('/audit?limit=5');
console.log('最近审计:', JSON.stringify(audit.items.map((a) => ({
action: a.action, object: a.object, result: a.result,
})), null, 2));错误码快速复现(1017 / 1019)
Shell
# 1017:AppKey 不存在(签名头仍需齐全,服务端先查应用)
curl -sS -i -X POST "http://127.0.0.1:8080/im/v2/user/get" \
-H 'App-Key: no-such-app' -H 'Nonce: 9f2a7c1b' -H 'Timestamp: 1750000000000' -H 'Signature: deadbeef' \
-H 'content-type: application/json' -d '{"userId":"alice"}'
# 1019:Timestamp 超出 ±300000ms(用一个明显过期的时间戳)
curl -sS -i -X POST "http://127.0.0.1:8080/im/v2/user/get" \
-H 'App-Key: demo' -H 'Nonce: 9f2a7c1c' -H 'Timestamp: 1' -H 'Signature: deadbeef' \
-H 'content-type: application/json' -d '{"userId":"alice"}'常见问题
消息轨迹显示 delivered,用户仍说没看到
轨迹只证明消息到达了某个在线 WS 会话。继续查:客户端是否真的渲染(本地库是否插入)、会话是否被免打扰/折叠、消息类型是否被客户端丢弃(未知 objectName)。
trace 返回 404
消息不存在或不属于该 appkey。先用 /messages 按用户/时间检索确认 messageId;聊天室消息不落库,trace 查不到属于预期。
connections 里没有该用户
用户当前不在线。离线问题应查 trace 的 offlineQueued 与重连后的 deliveredSeq,而不是连接列表。
回调日志里 status=success 但对端说没收到
只看 HTTP 2xx。请对端确认是否在网关层丢弃、是否返回了 200 但业务未处理;必要时用 callbacks/test 复现。
审计查不到某次操作
审计只记录管理端写操作,且环形缓冲上限 5000 条;读操作与被拒绝的鉴权请求不在其中。
重试回调后仍失败
检查 URL 是否公网可达、是否要求 HTTPS、请求体是否超过 256KB、对端是否 5 秒内响应。重试策略为最多 3 次、退避 1s。