问题排查手册

本页把“用户说有问题”收敛成 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 成员上限)​

项内容
1008Open API 接口级 QPS 超限;响应头带 Retry-After: 1
1015成员数量超出上限:普通群/超级群/聊天室人数触达配额或协议上限
1025QUOTA_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}/messagesuserId、conversationType、targetId、keyword、from、to、offset、limitX-Admin-Secret
消息轨迹GET /api/admin/apps/{appkey}/messages/{message_id}/trace路径参数 message_idX-Admin-Secret
连接明细GET /api/admin/apps/{appkey}/connectionsuserId、offset、limitX-Admin-Secret
连接统计GET /api/admin/apps/{appkey}/connections/stats—X-Admin-Secret
用户详情GET /api/admin/apps/{appkey}/users/{user_id}路径参数 user_idX-Admin-Secret
回调配置GET/POST /api/admin/apps/{appkey}/callbacksPOST:enabled、url、secret、events、retryX-Admin-Secret
回调投递日志GET /api/admin/apps/{appkey}/callbacks/deliverieseventType、status、from、to、offset、limitX-Admin-Secret
回调重试POST /api/admin/apps/{appkey}/callbacks/deliveries/{delivery_id}/retry路径参数 delivery_idX-Admin-Secret
回调测试POST /api/admin/apps/{appkey}/callbacks/testeventType?、payload?X-Admin-Secret
操作日志GET /api/admin/apps/{appkey}/auditactor、action、object、from、to、offset、limitX-Admin-Secret
服务开通 / 功能开关GET/POST /api/admin/apps/{appkey}/services、/featuresservices、quotas / featuresX-Admin-Secret
接口调频GET/POST /api/admin/apps/{appkey}/ratelimitoverrides(operationId→QPS,0 表示不限)X-Admin-Secret
敏感词 / 审核任务GET /api/admin/apps/{appkey}/sensitive-words、/moderation/taskslimit、offset、status(pending/approved/rejected)X-Admin-Secret
告警事件GET /api/admin/apps/{appkey}/alerts/eventsmetric、offset、limitX-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。

相关链接​