外观
机器人(Bot)开发文档
概述
Bot(机器人)是 miniApp 内置的自动化消息账号,只能私聊(一对一)给本应用的关注用户(miniAppUser)发送消息,也能通过 Webhook 接收用户消息并做出响应。不支持群发、不支持给非本应用用户发消息。适用于:客服机器人、消息通知推送、自动回复、第三方系统集成等场景。
关注关系
用户通过 JSSDK 在本 miniApp 完成授权(jssdk/init)后,即成为该应用的关注用户(miniAppUser),此时 Bot 才能向其私聊发送消息。Bot 只能触达自己所属 miniApp 的用户,无法跨应用发送。
整体架构
text
用户/群组
│ 发消息给 Bot
▼
平台 WebSocket 服务(Pitaya Frontend)
│ 消息路由
▼
平台消息队列(NATS / Kafka)
│ Webhook 回调(HTTP POST)
▼
你的 Bot 服务器
│ 处理逻辑后回复
▼ POST /api/mini/bot/send
平台 HTTP API(apiGw :9050)
│ 写入 MQ
▼
平台消息队列 → 私聊推送给目标关注用户(miniAppUser)创建机器人
在开发者中心「机器人管理」页面创建,或通过管理 API 创建:
http
# 管理 API(需管理员 token)
POST /api/miniAdmin/bots/create
Content-Type: application/json
{
"miniAppId": 1,
"name": "客服机器人",
"webhookUrl": "https://your-server.com/bot/webhook"
}
# 响应
{
"code": 200,
"data": {
"id": 10,
"botKey": "bf37f92b4da604753f1ae787f7f36465",
"botSecret": "5c91c4f14dec40f5434bd39324a77f4f"
}
}Bot 基本属性
| 字段 | 类型 | 说明 |
|---|---|---|
botKey | string | 机器人公钥,用于换取 access_token(等同 AppKey) |
botSecret | string | 机器人密钥,需保存在服务端(等同 AppSecret) |
userId | int64 | 对应 mem_user.id,IsAi=1 的特殊用户 |
miniAppId | int64 | 所属 miniApp,Bot 归属于某个应用 |
webhookUrl | string | 接收用户消息回调的地址(可选) |
status | int | 0-禁用 1-启用 |
安全提示
botSecret 务必保存在服务端,不要暴露在前端代码或客户端中。
鉴权:获取 access_token
所有 Bot API 都需要在请求头携带 Authorization: Bearer <access_token>。access_token 通过 botKey + botSecret 换取,有效期 7200 秒(2 小时)。
POST /api/mini/bot/token
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
botKey | string | 是 | 在机器人管理页获取 |
botSecret | string | 是 | 在机器人管理页获取,务必保存在服务端 |
http
POST /api/mini/bot/token
Content-Type: application/json
{
"botKey": "bf37f92b4da604753f1ae787f7f36465",
"botSecret": "5c91c4f14dec40f5434bd39324a77f4f"
}响应:
json
{
"code": 200,
"data": {
"access_token": "a1b2c3d4e5f6...",
"expires_in": 7200
}
}TIP
建议在 token 过期前 5 分钟提前刷新,避免发送消息时报 401 错误。
发送消息
POST /api/mini/bot/send
向本应用的一个关注用户(miniAppUser)私聊发送一条消息。仅支持私聊,不支持群发。
请求参数
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
openId | string | 是 | 私聊目标,本应用关注用户的 miniAppUser.openId |
msgType | int | 否 | 消息类型,默认 0(文本),范围 0~11 |
msg | string | 是 | 消息内容 |
消息类型(msgType)
| 值 | 类型 | 说明 |
|---|---|---|
| 0 | 文本 | msg 为纯文本字符串 |
| 1 | 图片 | msg 为图片 URL |
| 2 | 视频 | msg 为视频 URL |
| 3 | 音频 | msg 为音频 URL |
| 10 | 自定义 | msg 为 JSON 字符串,由客户端自行解析 |
| 11 | Markdown | msg 为 Markdown 文本,客户端渲染为富文本卡片 |
私聊示例
http
POST /api/mini/bot/send
Authorization: Bearer <access_token>
Content-Type: application/json
{
"openId": "3f2a9c8b7e6d5a4b3c2d1e0f9a8b7c6d",
"msgType": 0,
"msg": "你好,这是一条来自机器人的消息"
}响应
json
{
"code": 200,
"data": { "ok": true }
}WARNING
目标 openId 必须是本 miniApp 下、status=1 的关注用户。该 openId 由「应用用户」接口返回,或在用户 JSSDK 授权后通过 Webhook 获取。Bot 只能触达自己所属 miniApp 的用户。
获取应用用户列表
GET /api/mini/bot/userList
分页返回本 miniApp 的关注用户(miniAppUser)列表,用于获取可私聊的目标 openId。
请求参数(Query)
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
page | int | 否 | 页码,从 1 开始,默认 1 |
pageSize | int | 否 | 每页条数,默认 20,最大 200 |
http
GET /api/mini/bot/userList?page=1&pageSize=20
Authorization: Bearer <access_token>
# 响应
{
"code": 200,
"data": {
"list": [
{
"openId": "3f2a9c8b7e6d5a4b3c2d1e0f9a8b7c6d",
"userId": 1001,
"guid": "550e8400-...",
"nickname": "张三",
"avatar": "https://cdn.example.com/avatar.jpg",
"faceUrl": "",
"online": 1
}
],
"total": 1
}
}响应字段
| 字段 | 类型 | 说明 |
|---|---|---|
openId | string | 关注用户 openId,发私聊消息时使用 |
userId | int64 | 系统用户 ID(mem_user.id) |
guid | string | 用户全局唯一 ID |
nickname | string | 用户昵称 |
avatar | string | 用户头像 URL |
faceUrl | string | IM 自定义头像 URL |
online | int | 是否在线:1-在线 0-离线 |
Webhook 消息回调
在机器人管理页设置 webhookUrl 后,当用户给 Bot 发消息时,平台会向该 URL 发送一个 HTTP POST 请求,携带消息内容。
回调请求格式
http
POST https://your-server.com/bot/webhook
Content-Type: application/json
{
"event": "message",
"openId": "3f2a9c8b7e6d5a4b3c2d1e0f9a8b7c6d",
"fromUserGuid": "550e8400-e29b-41d4-a716-446655440000",
"fromUserName": "张三",
"chatType": "private",
"msgType": 0,
"msg": "你好,机器人",
"timestamp": 1717000000
}Payload 字段
| 字段 | 类型 | 说明 |
|---|---|---|
event | string | 事件类型,目前为 "message" |
openId | string | 消息发送者在本应用的 openId,回复时直接用于 /bot/send |
fromUserGuid | string | 消息发送者 guid |
fromUserName | string | 消息发送者昵称 |
chatType | string | 固定为 "private"(Bot 仅支持私聊) |
msgType | int | 消息类型(同发送接口) |
msg | string | 消息内容 |
timestamp | int64 | 消息时间戳(Unix 秒) |
你的 Webhook 服务需要做
- 接收 POST 请求,解析 JSON body
- 在 5 秒内响应 HTTP 200(否则平台会重试)
- 根据
openId/msg决定是否回复 - 调用
POST /api/mini/bot/send(携带openId)进行私聊回复
演示 Webhook 接口
平台内置了演示用 Webhook,方便调试:
text
# 演示接收端(无需鉴权,仅用于调试)
POST /api/mini/webhook ← 接收并暂存最近 100 条
GET /api/mini/webhook ← 查看已收到的消息列表Webhook 接收示例(Go)
go
package main
import (
"encoding/json"
"fmt"
"net/http"
)
type WebhookMsg struct {
Event string `json:"event"`
OpenId string `json:"openId"`
FromUserGuid string `json:"fromUserGuid"`
FromUserName string `json:"fromUserName"`
ChatType string `json:"chatType"`
MsgType int `json:"msgType"`
Msg string `json:"msg"`
Timestamp int64 `json:"timestamp"`
}
func webhookHandler(w http.ResponseWriter, r *http.Request) {
var msg WebhookMsg
json.NewDecoder(r.Body).Decode(&msg)
fmt.Printf("收到消息 from=%s: %s\n", msg.FromUserName, msg.Msg)
// 自动回复(私聊,使用 openId)
if msg.Msg == "你好" {
replyToUser(msg.OpenId, "你好!我是客服机器人,有什么可以帮助你?")
}
w.WriteHeader(http.StatusOK) // 必须在 5 秒内响应
}
func main() {
http.HandleFunc("/bot/webhook", webhookHandler)
http.ListenAndServe(":8080", nil)
}完整示例(Go)
包含 token 缓存刷新、发送私聊消息、Webhook 自动回复:
go
package main
import (
"bytes"
"encoding/json"
"fmt"
"net/http"
"sync"
"time"
)
const (
baseURL = "http://your-server.com"
botKey = "bf37f92b4da604753f1ae787f7f36465"
botSecret = "5c91c4f14dec40f5434bd39324a77f4f"
)
var (
tokenMu sync.Mutex
token string
tokenExp time.Time
)
// getToken 获取或刷新 access_token
func getToken() (string, error) {
tokenMu.Lock()
defer tokenMu.Unlock()
if token != "" && time.Now().Before(tokenExp) {
return token, nil
}
body, _ := json.Marshal(map[string]string{
"botKey": botKey, "botSecret": botSecret,
})
resp, err := http.Post(baseURL+"/api/mini/bot/token",
"application/json", bytes.NewReader(body))
if err != nil {
return "", err
}
defer resp.Body.Close()
var result struct {
Code int `json:"code"`
Data struct {
AccessToken string `json:"access_token"`
ExpiresIn int64 `json:"expires_in"`
} `json:"data"`
}
json.NewDecoder(resp.Body).Decode(&result)
token = result.Data.AccessToken
tokenExp = time.Now().Add(time.Duration(result.Data.ExpiresIn-300) * time.Second)
return token, nil
}
// sendMsg 发送私聊消息(openId 为本应用关注用户)
func sendMsg(openId, msg string) error {
tk, err := getToken()
if err != nil {
return err
}
body, _ := json.Marshal(map[string]interface{}{
"openId": openId, "msgType": 0, "msg": msg,
})
req, _ := http.NewRequest("POST", baseURL+"/api/mini/bot/send",
bytes.NewReader(body))
req.Header.Set("Authorization", "Bearer "+tk)
req.Header.Set("Content-Type", "application/json")
resp, err := http.DefaultClient.Do(req)
if err != nil {
return err
}
defer resp.Body.Close()
fmt.Println("消息发送成功 →", openId)
return nil
}
// webhookHandler Webhook 回调处理
func webhookHandler(w http.ResponseWriter, r *http.Request) {
var payload struct {
OpenId string `json:"openId"`
Msg string `json:"msg"`
}
json.NewDecoder(r.Body).Decode(&payload)
go sendMsg(payload.OpenId, "收到你的消息:"+payload.Msg)
w.WriteHeader(http.StatusOK)
}
func main() {
http.HandleFunc("/bot/webhook", webhookHandler)
fmt.Println("Bot 服务已启动 :8080")
http.ListenAndServe(":8080", nil)
}压测工具
平台提供压测工具 apisvc/test/minibot,用于验证 Bot 发送链路的吞吐与延迟:
bash
cd apisvc
# 基础用法
go run ./test/minibot -bot-key <botKey> -bot-secret <botSecret>
# 自定义参数
go run ./test/minibot \
-url http://127.0.0.1:9050 \
-bot-key bf37f92b4da604753f1ae787f7f36465 \
-bot-secret 5c91c4f14dec40f5434bd39324a77f4f \
-concurrency 20 \
-total 500 \
-msg "压测消息"参数说明
| 参数 | 默认值 | 说明 |
|---|---|---|
-url | http://localhost:9050 | API 服务地址 |
-bot-key | (必填) | 机器人 botKey |
-bot-secret | (必填) | 机器人 botSecret |
-concurrency | 10 | 并发协程数 |
-total | 200 | 总发送消息数 |
-msg-type | 0 | 消息类型(0=文本) |
-msg | load test | 消息内容前缀 |
输出示例
text
==> Authenticating bot at http://127.0.0.1:9050
==> Token: bf37f92...
==> Fetching contacts...
contacts: 3
==> Fetching groups...
groups: 2
==> Load test concurrency=20 total=500
[100/500] elapsed=1.234s qps=81.0
[200/500] elapsed=2.456s qps=81.4
[300/500] elapsed=3.678s qps=81.6
[400/500] elapsed=4.891s qps=81.8
[500/500] elapsed=6.100s qps=82.0
===== Load Test Report =====
Total: 500
Success: 498 (99.6%)
Failed: 2 (0.4%)
Elapsed: 6.100s
Throughput: 82.0 req/s
Latency (success only):
Min: 8ms
P50: 12ms
P95: 28ms
P99: 45ms
Max: 98ms
============================错误码
| HTTP | code | message | 说明 |
|---|---|---|---|
| 200 | 200 | ok | 成功 |
| 200 | 400 | botKey 和 botSecret 不能为空 | 参数缺失,检查请求体 |
| 200 | 400 | msg 不能为空 | msg 字段为空 |
| 200 | 400 | openId 不能为空 | 发送目标 openId 缺失 |
| 200 | 400 | msgType 无效 | msgType 超出 0~11 范围 |
| 401 | 401 | 缺少 Authorization Bearer token | 请求头缺少 token |
| 401 | 401 | token 无效或已过期 | 重新调用 /token 接口获取 |
| 200 | 400 | botKey 不存在或已禁用 | botKey 错误或 Bot 被禁用 |
| 200 | 400 | botSecret 错误 | botSecret 不匹配 |
| 200 | 400 | 目标用户不存在或未关注 | openId 不属于本应用,或用户未关注/已禁用 |
| 500 | 500 | 消息发送失败 | MQ 异常,检查服务端日志 |