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();
🔧 配置参数详解
必填参数
| 参数 | 类型 | 说明 | 示例 |
|---|---|---|---|
appid | string | 轻推应用ID | 'wx1234567890abcdef' |
appsecret | string | 轻推应用密钥 | 'your_app_secret_here' |
eventType | string | 事件类型,只能是 'event' 或 'action' | 'event' |
可选参数
| 参数 | 类型 | 默认值 | 说明 |
|---|---|---|---|
serverUrl | string | 'http://localhost:3000' | 消息代理服务地址 |
openUrl | string | 'https://open.qingtui.com' | 轻推开放平台地址 |
heartbeatInterval | number | 25000 | 心跳间隔(毫秒),建议 20000-30000 |
heartbeatTimeout | number | 5000 | 心跳超时时间(毫秒),建议 3000-10000 |
maxReconnectAttempts | number | 5 | 最大重连次数,0 表示不重连 |
reconnectInterval | number | 5000 | 重连间隔(毫秒),实际采用指数退避策略 |
autoAck | boolean | true | 是否自动为 event_callback 消息发送 ACK |
debug | boolean | false | 是否开启调试日志 |
📡 事件列表
系统事件
| 事件名 | 回调参数 | 说明 |
|---|---|---|
connected | 无 | WebSocket 连接成功 |
authenticated | { accessToken, baseUrl } | 认证成功,返回 access_token 和基础 URL |
disconnected | reason | 连接断开,reason 为断开原因 |
reconnected | 无 | 重连成功 |
error | Error | 发生错误,可能包含 code 属性 |
tokenUpdate | { access_token } | Token 更新通知 |
heartbeatTimeout | 无 | 心跳超时(连续3次失败) |
业务事件
| 事件名 | 回调参数 | 说明 |
|---|---|---|
message | Object | 收到消息,包含完整的消息对象 |
📨 消息类型说明
消息结构总览
{
"type": "text", // 消息类型
"sendType": "channel", // 发送方式
"message": { // 消息内容
// 根据不同 type 和 sendType 有不同的字段
}
}
type 字段取值
| 值 | 说明 |
|---|---|
text | 文本消息 |
image | 图片消息 |
textCard | 文本卡片消息 |
news | 图文消息 |
keyValue | Key-Value 消息 |
file | 文件消息 |
card | 卡片消息 |
todo | 待办消息 |
sendType 字段取值
| 值 | 说明 | 适用场景 |
|---|---|---|
single | 单发 | 发送给单个用户 |
mass | 群发给部分人 | 发送给指定的多个用户(最多50个) |
service | 群发给所有人 | 发送给应用的所有关注者 |
channel | 发送到群聊 | 发送到指定的群聊 |
message 字段结构
根据不同的 type 和 sendType,message 字段的结构会有所不同。以下是详细说明:
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' |
| 50002 | Socket 连接未配置 | 检查 Redis 中 qingtui:app:{eventType}:callbacktype:{appId} 的值是否为 SOCKET |
3. 消息格式验证
- 确保
type字段是有效的消息类型 - 确保
sendType字段是有效的发送方式 - 确保
message字段的结构符合对应类型的要求 - 单发和部分群发时必须提供
to_user或to_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: 检查以下几点:
- 消息格式是否正确
access_token是否有效- 接收者是否存在(单发和部分群发)
channel_id是否正确(群聊发送)- 查看服务端的错误响应
Q5: 如何实现断线重连?
A: SDK 已内置自动重连功能,无需额外配置。可以通过监听事件了解重连状态:
client.on("disconnected", (reason) => {
console.log("连接断开:", reason);
});
client.on("reconnected", () => {
console.log("重连成功");
});