跳到主要内容

JS SDK 指南

📖 概述

Socket.IO 客户端 SDK 是一个用于连接轻推消息代理服务的 JavaScript 库,提供简单易用的 API 来实现 WebSocket 长连接、消息收发、心跳保活、自动重连等功能。

依赖: socket.io-client v4.x


🚀 快速开始

1. 安装方式

方式一:CDN 引入(推荐)

通过轻推官方 CDN 引入 SDK:

<!-- 先引入 socket.io-client -->
<script src="https://cdn.socket.io/4.7.4/socket.io.js"></script>
<!-- 再引入 QingtuiSocketSDK -->
<script src="https://static.qingtui.com/open/libs/sdk/js/qingtui-socket-client.min.js"></script>

SDK 下载地址:

如果你需要下载 SDK 文件到本地使用,可以从以下地址下载:

# 使用 curl 下载
curl -O https://static.qingtui.com/open/libs/sdk/js/qingtui-socket-client.min.js

# 或使用 wget 下载
wget https://static.qingtui.com/open/libs/sdk/js/qingtui-socket-client.min.js

方式二:本地文件引入

将 SDK 文件下载到本地项目中使用:

1. 下载 SDK 文件

# 下载到项目目录
curl -o ./libs/qingtui-socket-client.min.js https://static.qingtui.com/open/libs/sdk/js/qingtui-socket-client.min.js

2. 在 HTML 中引入

<!-- 先引入 socket.io-client -->
<script src="https://cdn.socket.io/4.7.4/socket.io.js"></script>
<!-- 再引入 QingtuiSocketSDK -->
<script src="./libs/qingtui-socket-client.min.js"></script>

注意:目前 SDK 尚未发布到 npm,请使用 CDN 或本地文件方式引入。

方式三:在 Node.js 项目中使用

如果你的项目使用 Node.js(非浏览器环境),可以直接引用源文件:

const QingtuiSocketClient = require("./sdk/js/app-qt-sa-socket-sdk/src/QingtuiSocketClient");

const client = new QingtuiSocketClient({
appid: "your_appid",
appsecret: "your_appsecret",
eventType: "event",
});

注意:这种方式需要确保已安装 socket.io-client 依赖:

npm install socket.io-client

2. 创建客户端实例

const client = new QingtuiSocketClient({
appid: "your_appid", // 必填:应用ID
appsecret: "your_appsecret", // 必填:应用密钥
eventType: "event", // 必填:事件类型,'event' 或 'action'
serverUrl: "https://open.qingtui.com", // 必填:服务端地址
openUrl: "https://open.qingtui.com", // 可选:开放平台地址
heartbeatInterval: 25000, // 可选:心跳间隔(ms),默认 25000
heartbeatTimeout: 5000, // 可选:心跳超时(ms),默认 5000
maxReconnectAttempts: 5, // 可选:最大重连次数,默认 5
reconnectInterval: 5000, // 可选:重连间隔(ms),默认 5000
autoAck: true, // 可选:自动ACK确认,默认 true
debug: false, // 可选:调试模式,默认 false
});

3. 连接到服务器

// 方式一:Promise
client
.connect()
.then(() => {
console.log("连接成功");
})
.catch((error) => {
console.error("连接失败:", error.message);
if (error.code) {
console.error("错误码:", error.code);
}
});

// 方式二:async/await
try {
await client.connect();
console.log("连接成功");
} catch (error) {
console.error("连接失败:", error.message);
if (error.code) {
console.error("错误码:", error.code);
}
}

4. 监听事件

// 连接成功
client.on("connected", () => {
console.log("WebSocket 已连接");
});

// 认证成功
client.on("authenticated", (data) => {
console.log("认证成功", data.accessToken);
});

// 收到消息
client.on("message", (message) => {
console.log("收到消息:", message);

// 如果是 event_callback 类型且 autoAck=false,需要手动发送ACK
if (message.type === "event_callback" && message.event_id) {
// 处理业务逻辑...

// 手动发送ACK确认
client.sendAck(message.event_id, true);
}
});

// 断开连接
client.on("disconnected", (reason) => {
console.log("连接断开:", reason);
});

// 重连成功
client.on("reconnected", () => {
console.log("重连成功");
});

// 错误处理
client.on("error", (error) => {
console.error("发生错误:", error.message);
if (error.code) {
console.error("错误码:", error.code);
// 50001: 事件类型不正确
// 50002: Socket 连接未配置
}
});

// Token 更新
client.on("tokenUpdate", (token) => {
console.log("Token 已更新");
});

// 心跳超时
client.on("heartbeatTimeout", () => {
console.warn("心跳超时,将触发断线重连");
});

5. 发送消息

SDK 只提供统一的 sendMessage 方法,你需要按照轻推开放平台的 API 格式构造消息对象。

消息结构说明

所有通过 WebSocket 发送的消息都会转发到轻推开放平台 API,消息格式必须遵循以下结构:

{
"type": "text", // 消息类型:text, image, textCard, news, keyValue, file, card, todo
"sendType": "channel", // 发送方式:single(单发), mass(群发给部分人), service(群发全部), channel(发送到群聊)
"message": {
// 具体的消息内容,根据 type 和 sendType 不同而不同
}
}

5.1 发送文本消息

单发文本消息:

await client.sendMessage({
type: "text",
sendType: "single",
message: {
to_user: "OPENID1",
message: {
content: "这是一条文本消息",
},
},
});

群发文本消息(给部分人):

await client.sendMessage({
type: "text",
sendType: "mass",
message: {
to_users: ["OPENID1", "OPENID2"],
message: {
content: "这是一条群发消息",
},
},
});

群发文本消息(给所有人):

await client.sendMessage({
type: "text",
sendType: "service",
message: {
message: {
content: "这是一条全员消息",
},
},
});

发送到群聊:

await client.sendMessage({
type: "text",
sendType: "channel",
message: {
channel_id: "42364ab42cd444638d96383e72634106",
message: {
content: "这是一条群聊消息",
},
},
});

5.2 发送图片消息

await client.sendMessage({
type: "image",
sendType: "channel",
message: {
channel_id: "fb287edb5259427283118535682c165e",
message: {
media_id: "MEDIA_ID_HERE", // 需要先通过上传接口获取
},
},
});

5.3 发送文本卡片消息

await client.sendMessage({
type: "textCard",
sendType: "single",
message: {
to_user: "OPENID",
message: {
title: "标题\r标题第二行",
url: "https://www.qingtui.cn",
button_text: "详情",
content_list: [
{
text: "这是黑色文本",
attr: { color: "BLACK" },
},
{
text: "高亮消息文本",
attr: { color: "HIGHLIGHT" },
},
{
text: "这是灰色文本\n换一行",
attr: { color: "GRAY" },
},
],
},
},
});

5.4 发送图文消息

await client.sendMessage({
type: "news",
sendType: "single",
message: {
to_user: "OPENID",
message: {
article_list: [
{
title:
"路透社与Ipsos合作的调查显示,75%的美国用户仍然每天使用Facebook",
url: "https://www.qingtui.cn",
content:
"该调查样本覆盖了美国大陆、夏威夷以及阿拉斯加2194位18岁以上用户",
thumbMediaId: "95ee6faef5d69", // 图片media_id,需要先上传
},
],
},
},
});

群发给部分人:

await client.sendMessage({
type: "news",
sendType: "mass",
message: {
to_users: ["OPENID1", "OPENID2"],
message: {
article_list: [
{
title: "文章标题",
url: "https://www.example.com/article",
content: "文章摘要",
thumbMediaId: "IMAGE_MEDIA_ID",
},
],
},
},
});

发送到群聊:

await client.sendMessage({
type: "news",
sendType: "channel",
message: {
channel_id: "CHANNEL_ID",
message: {
article_list: [
{
title: "文章标题",
url: "https://www.example.com/article",
content: "文章摘要",
thumbMediaId: "IMAGE_MEDIA_ID",
},
],
},
},
});

5.5 发送 Key-Value 消息

await client.sendMessage({
type: "keyValue",
sendType: "single",
message: {
to_user: "OPENID",
message: {
title: "标题",
sub_title: {
text: "从这里内容开始\n\n",
color: "GRAY",
},
url: "https://www.qingtui.cn",
content: [
{
key: "栏目一",
value: "栏目一的值高亮显示",
valueColor: "HIGHLIGHT",
},
{
key: "栏目二",
value: "栏目二的值默认颜色显示\n",
},
{
key: "栏目三",
value: "栏目三的值灰色显示",
valueColor: "GRAY",
},
],
footer: {
text: "\n\n结尾",
},
button_text: "按钮文本",
},
},
});

群发给部分人:

await client.sendMessage({
type: "keyValue",
sendType: "mass",
message: {
to_users: ["OPENID1", "OPENID2"],
message: {
title: "标题",
content: [
{ key: "键1", value: "值1" },
{ key: "键2", value: "值2" },
],
},
},
});

发送到群聊:

await client.sendMessage({
type: "keyValue",
sendType: "channel",
message: {
channel_id: "CHANNEL_ID",
message: {
title: "标题",
content: [{ key: "键1", value: "值1" }],
},
},
});

5.6 发送文件消息

需要先通过上传接口获取 media_id

await client.sendMessage({
type: "file",
sendType: "single",
message: {
to_user: "OPENID",
message: {
media_id: "FILE_MEDIA_ID",
},
},
});

5.7 发送卡片消息

卡片消息的 content 字段是一个 JSON 字符串,需要使用轻推卡片编辑器生成。

await client.sendMessage({
type: "card",
sendType: "single",
message: {
to_user: "OPENID",
message: {
content:
'{"ver":1,"content":{"blocks":[{"name":"divider"},{"name":"section","text":{"name":"text","content":"12312312","type":0,"maxLine":-1,"color":"#222222","size":28}}]}}',
},
},
});

5.8 发送待办消息

await client.sendMessage({
type: "todo",
sendType: "single",
message: {
to_user: "OPENID",
message: {
title: "OA待办",
body: "2018/01/08 10:52\n赛迪信息-公司请假审批流程-文君-2018-04-16\n请假时间2018年4月17日至2018年4月20日",
url: "https://www.qingtui.cn",
},
},
});

群发给部分人:

await client.sendMessage({
type: "todo",
sendType: "mass",
message: {
to_users: ["OPENID1", "OPENID2"],
message: {
title: "待办标题",
body: "待办内容",
url: "https://www.example.com/todo/123",
},
},
});

6. 发送 ACK 确认

当收到 event_callback 类型的消息时,如果关闭了自动 ACK(autoAck: false),需要手动发送确认:

client.on("message", (message) => {
if (message.type === "event_callback" && message.event_id) {
try {
// 处理业务逻辑
handleBusinessLogic(message);

// 手动发送ACK确认(成功)
client.sendAck(message.event_id, true);
} catch (error) {
// 处理失败时发送失败的ACK
client.sendAck(message.event_id, false);
}
}
});

7. 断开连接

client.disconnect();

8. 销毁客户端

client.destroy();

🔧 配置参数详解

必填参数

参数类型说明示例
appidstring轻推应用ID'wx1234567890abcdef'
appsecretstring轻推应用密钥'your_app_secret_here'
eventTypestring事件类型,只能是 'event''action''event'

可选参数

参数类型默认值说明
serverUrlstring'http://localhost:3000'消息代理服务地址
openUrlstring'https://open.qingtui.com'轻推开放平台地址
heartbeatIntervalnumber25000心跳间隔(毫秒),建议 20000-30000
heartbeatTimeoutnumber5000心跳超时时间(毫秒),建议 3000-10000
maxReconnectAttemptsnumber5最大重连次数,0 表示不重连
reconnectIntervalnumber5000重连间隔(毫秒),实际采用指数退避策略
autoAckbooleantrue是否自动为 event_callback 消息发送 ACK
debugbooleanfalse是否开启调试日志

📡 事件列表

系统事件

事件名回调参数说明
connectedWebSocket 连接成功
authenticated{ accessToken, baseUrl }认证成功,返回 access_token 和基础 URL
disconnectedreason连接断开,reason 为断开原因
reconnected重连成功
errorError发生错误,可能包含 code 属性
tokenUpdate{ access_token }Token 更新通知
heartbeatTimeout心跳超时(连续3次失败)

业务事件

事件名回调参数说明
messageObject收到消息,包含完整的消息对象

📨 消息类型说明

消息结构总览

{
"type": "text", // 消息类型
"sendType": "channel", // 发送方式
"message": { // 消息内容
// 根据不同 type 和 sendType 有不同的字段
}
}

type 字段取值

说明
text文本消息
image图片消息
textCard文本卡片消息
news图文消息
keyValueKey-Value 消息
file文件消息
card卡片消息
todo待办消息

sendType 字段取值

说明适用场景
single单发发送给单个用户
mass群发给部分人发送给指定的多个用户(最多50个)
service群发给所有人发送给应用的所有关注者
channel发送到群聊发送到指定的群聊

message 字段结构

根据不同的 typesendTypemessage 字段的结构会有所不同。以下是详细说明:

1. text(文本消息)

single(单发):

{
to_user: 'OPENID',
message: {
content: '文本内容'
}
}

mass(群发给部分人):

{
to_users: ['OPENID1', 'OPENID2'],
message: {
content: '文本内容'
}
}

service(群发给所有人):

{
message: {
content: "文本内容";
}
}

channel(发送到群聊):

{
channel_id: 'CHANNEL_ID',
message: {
content: '文本内容'
}
}

2. image(图片消息)

需要先通过上传接口获取 media_id

single:

{
to_user: 'OPENID',
message: {
media_id: 'MEDIA_ID'
}
}

channel:

{
channel_id: 'CHANNEL_ID',
message: {
media_id: 'MEDIA_ID'
}
}

3. textCard(文本卡片消息)

{
to_user: 'OPENID',
message: {
title: '标题',
url: 'https://example.com',
button_text: '详情',
content_list: [
{
text: '内容文本',
attr: { color: 'BLACK' } // BLACK, GRAY, HIGHLIGHT
}
]
}
}

4. news(图文消息)

single(单发):

{
to_user: 'OPENID',
message: {
article_list: [
{
title: '文章标题', // 最多45个字符
url: 'https://example.com/article',
content: '文章摘要', // 最多120个字符
thumbMediaId: 'IMAGE_MEDIA_ID' // 图片media_id,必填
}
]
}
}

mass(群发给部分人):

{
to_users: ['OPENID1', 'OPENID2'],
message: {
article_list: [
{
title: '文章标题',
url: 'https://example.com/article',
content: '文章摘要',
thumbMediaId: 'IMAGE_MEDIA_ID'
}
]
}
}

service(群发给所有人):

{
message: {
article_list: [
{
title: "文章标题",
url: "https://example.com/article",
content: "文章摘要",
thumbMediaId: "IMAGE_MEDIA_ID",
},
];
}
}

channel(发送到群聊):

{
channel_id: 'CHANNEL_ID',
message: {
article_list: [
{
title: '文章标题',
url: 'https://example.com/article',
content: '文章摘要',
thumbMediaId: 'IMAGE_MEDIA_ID'
}
]
}
}

5. keyValue(Key-Value 消息)

single(单发):

{
to_user: 'OPENID',
message: {
title: '标题',
sub_title: {
text: '首行说明',
color: 'GRAY' // GRAY, HIGHLIGHT, BLACK
},
url: 'https://example.com',
content: [
{
key: '栏目名',
value: '栏目值',
valueColor: 'HIGHLIGHT' // 可选:HIGHLIGHT, GRAY, BLACK
}
],
footer: {
text: '末尾说明'
},
button_text: '详情'
}
}

mass(群发给部分人):

{
to_users: ['OPENID1', 'OPENID2'],
message: {
title: '标题',
content: [
{ key: '键1', value: '值1' }
]
}
}

channel(发送到群聊):

{
channel_id: 'CHANNEL_ID',
message: {
title: '标题',
content: [
{ key: '键1', value: '值1' }
]
}
}

6. file(文件消息)

需要先通过上传接口获取 media_id

{
to_user: 'OPENID',
message: {
media_id: 'FILE_MEDIA_ID'
}
}

7. card(卡片消息)

卡片消息的 content 字段是一个 JSON 字符串,需要使用轻推卡片编辑器生成。

{
to_user: 'OPENID',
message: {
content: '{"ver":1,"content":{"blocks":[...]}}'
}
}

8. todo(待办消息)

single(单发):

{
to_user: 'OPENID',
message: {
title: '待办标题',
body: '待办内容',
url: 'https://example.com/todo/123'
}
}

mass(群发给部分人):

{
to_users: ['OPENID1', 'OPENID2'],
message: {
title: '待办标题',
body: '待办内容',
url: 'https://example.com/todo/123'
}
}

💡 完整示例

示例 1:基本的消息接收和发送

const { QingtuiSocketClient } = require("app-qt-sa-socket-sdk");

// 创建客户端
const client = new QingtuiSocketClient({
appid: "your_appid",
appsecret: "your_appsecret",
eventType: "event",
serverUrl: "http://localhost:3000",
debug: true,
});

// 注册事件监听
client.on("connected", () => {
console.log("✅ 连接成功");
});

client.on("authenticated", (data) => {
console.log("✅ 认证成功");
});

client.on("message", async (message) => {
console.log("📨 收到消息:", message);

// 如果是文本消息回调
if (message.type === "event_callback") {
const eventData = message.data;

// 处理业务逻辑
if (eventData.content) {
console.log("文本内容:", eventData.content);

// 回复消息
try {
await client.sendMessage({
type: "text",
sendType: "single",
message: {
to_user: eventData.from_user,
message: {
content: "收到你的消息:" + eventData.content,
},
},
});
console.log("✅ 回复成功");
} catch (error) {
console.error("❌ 回复失败:", error);
}
}
}
});

client.on("error", (error) => {
console.error("❌ 错误:", error.message);
if (error.code) {
console.error("错误码:", error.code);
}
});

// 连接
(async () => {
try {
await client.connect();
console.log("客户端已启动");
} catch (error) {
console.error("连接失败:", error);
}
})();

示例 2:发送多种类型的消息

const { QingtuiSocketClient } = require("app-qt-sa-socket-sdk");

const client = new QingtuiSocketClient({
appid: "your_appid",
appsecret: "your_appsecret",
eventType: "action",
debug: true,
});

client.on("connected", async () => {
console.log("连接成功,开始发送测试消息");

try {
// 1. 发送文本消息到群聊
await client.sendMessage({
type: "text",
sendType: "channel",
message: {
channel_id: "42364ab42cd444638d96383e72634106",
message: {
content: "这是一条文本消息",
},
},
});
console.log("✅ 文本消息发送成功");

// 2. 发送文本卡片消息
await client.sendMessage({
type: "textCard",
sendType: "single",
message: {
to_user: "OPENID123",
message: {
title: "重要通知",
url: "https://www.qingtui.com",
button_text: "查看",
content_list: [
{
text: "这是一个重要的通知",
attr: { color: "HIGHLIGHT" },
},
{
text: "请点击查看详情",
attr: { color: "GRAY" },
},
],
},
},
});
console.log("✅ 文本卡片消息发送成功");

// 3. 发送图文消息
await client.sendMessage({
type: "news",
sendType: "mass",
message: {
to_users: ["OPENID1", "OPENID2"],
message: {
articles: [
{
title: "最新文章",
description: "这是一篇精彩的文章",
url: "https://www.example.com/article/1",
pic_url: "https://www.example.com/image.jpg",
},
],
},
},
});
console.log("✅ 图文消息发送成功");
} catch (error) {
console.error("❌ 发送失败:", error);
}
});

client.connect();

示例 3:手动控制 ACK

const { QingtuiSocketClient } = require("app-qt-sa-socket-sdk");

const client = new QingtuiSocketClient({
appid: "your_appid",
appsecret: "your_appsecret",
eventType: "event",
autoAck: false, // 关闭自动ACK
debug: true,
});

client.on("message", async (message) => {
if (message.type === "event_callback" && message.event_id) {
try {
// 异步处理业务逻辑
const result = await processMessage(message);

if (result.success) {
// 处理成功,发送成功的ACK
client.sendAck(message.event_id, true);
console.log("✅ ACK发送成功");
} else {
// 处理失败,发送失败的ACK
client.sendAck(message.event_id, false);
console.log("⚠️ ACK发送失败");
}
} catch (error) {
// 异常时发送失败的ACK
client.sendAck(message.event_id, false);
console.error("❌ 处理异常:", error);
}
}
});

async function processMessage(message) {
// 模拟异步处理
return new Promise((resolve) => {
setTimeout(() => {
resolve({ success: true });
}, 1000);
});
}

client.connect();

⚠️ 注意事项

1. 连接限制

  • 每个 appId:eventType 组合最多支持 5 个并发连接
  • 超过限制会拒绝新的连接请求

2. 错误码说明

错误码说明解决方案
50001事件类型不正确检查 eventType 是否为 'event''action'
50002Socket 连接未配置检查 Redis 中 qingtui:app:{eventType}:callbacktype:{appId} 的值是否为 SOCKET

3. 消息格式验证

  • 确保 type 字段是有效的消息类型
  • 确保 sendType 字段是有效的发送方式
  • 确保 message 字段的结构符合对应类型的要求
  • 单发和部分群发时必须提供 to_userto_users
  • 群聊发送时必须提供 channel_id

4. 重连机制

  • SDK 内置自动重连功能,采用指数退避策略
  • 重连间隔:5s → 10s → 20s → ...
  • 达到最大重连次数后不再重连

5. 心跳机制

  • 客户端每隔 heartbeatInterval 发送一次 ping
  • 如果在 heartbeatTimeout 内未收到 pong,计为一次失败
  • 连续 3 次失败后主动断开连接并触发重连

6. 自动 ACK

  • 默认情况下,收到 event_callback 类型的消息会自动发送 ACK
  • 如果需要手动控制 ACK,设置 autoAck: false
  • 手动 ACK 适用于需要异步处理或根据处理结果决定 ACK 状态的场景

🛠️ 常见问题

Q1: 连接失败,提示"事件类型不正确"

A: 检查 eventType 参数是否正确,必须是 'event''action'

const client = new QingtuiSocketClient({
appid: "your_appid",
appsecret: "your_appsecret",
eventType: "event", // 或 'action'
});

Q2: 连接失败,提示"Socket 连接未配置"

A: 需要在 Redis 中配置 callbacktype:

redis-cli SET qingtui:app:event:callbacktype:YOUR_APPID SOCKET

Q3: 如何查看调试日志?

A: 在配置中启用 debug 模式:

const client = new QingtuiSocketClient({
// ...其他配置
debug: true,
});

Q4: 消息发送失败怎么办?

A: 检查以下几点:

  1. 消息格式是否正确
  2. access_token 是否有效
  3. 接收者是否存在(单发和部分群发)
  4. channel_id 是否正确(群聊发送)
  5. 查看服务端的错误响应

Q5: 如何实现断线重连?

A: SDK 已内置自动重连功能,无需额外配置。可以通过监听事件了解重连状态:

client.on("disconnected", (reason) => {
console.log("连接断开:", reason);
});

client.on("reconnected", () => {
console.log("重连成功");
});