前言
在构建基于微信的自动化系统或消息中台时,开发者首先要面对的问题就是:微信的消息体系到底有多复杂?与短信、邮件等单一格式不同,微信支持十余种消息类型,每种类型的数据结构、处理逻辑、存储方式都有显著差异。如果在架构设计阶段没有对各消息类型建立清晰认知,后期往往会出现消息解析错误、类型遗漏、功能缺失等问题。
本文系统梳理微信的主要消息类型,包括文本、图片、语音、视频、链接卡片和小程序消息,分析每种类型的数据结构特点、典型应用场景,并通过 REST API 调用示例说明如何在程序中进行发送与接收处理。无论是做客服系统、消息机器人还是数据采集,本文的内容都可以直接参考落地。
一、微信消息体系概述
微信消息从大类上可以划分为私聊消息和群聊消息,两者在字段层面的主要区别在于 toWxid:私聊时该字段是对方微信 ID,群聊时是群 ID(通常以 @chatroom 结尾)。
从消息格式来看,微信消息可以分为以下几个主要类型:
| 类型标识 | 消息类型 | 说明 |
|---|---|---|
| 1 | 文本消息 | 纯文字,支持 @ 提及 |
| 3 | 图片消息 | 内嵌图片内容 |
| 34 | 语音消息 | AMR/SILK 格式音频 |
| 43 | 视频消息 | 短视频内容 |
| 49 | 富媒体消息 | 链接卡片、小程序、文件等共用此类型,通过子类型区分 |
| 10000 | 系统消息 | 进群通知、撤回提示等 |
在回调接收场景中,平台会将所有消息以统一结构推送到你设置的回调地址,开发者需要根据 type 字段做分支处理。以下逐一展开各类型的细节。
二、文本消息
2.1 结构特点
文本消息是最基础、使用频率最高的类型。发送时只需要指定 appId、收件人 toWxid 和 content 字段。内容本身是纯 UTF-8 字符串,支持 emoji、换行符和中英文混排。
在群聊中发送带 @ 提醒的文本消息时,需要在 content 中插入 @昵称 文本,同时通过 ats 字段传入被 @ 用户的微信 ID 列表,两者要保持一致,否则 @ 提醒可能不生效。
2.2 发送示例
pythonimport requests
BASE = "https://你的接口域名" # 注册后在官方文档获取
TOKEN = "你的Token"
APPID = "你的appId"
HEADERS = {"token": TOKEN} # 鉴权字段名以官方文档为准
# 私聊发送文本
def send_text(to_wxid: str, content: str):
url = f"{BASE}/message/postText"
payload = {
"appId": APPID,
"toWxid": to_wxid,
"content": content
}
resp = requests.post(url, json=payload, headers=HEADERS)
return resp.json()
# 群聊 @ 指定成员
def send_text_at(room_id: str, content: str, at_wxids: list):
url = f"{BASE}/message/postText"
payload = {
"appId": APPID,
"toWxid": room_id,
"content": content,
"ats": at_wxids # 被@用户的wxid列表
}
resp = requests.post(url, json=payload, headers=HEADERS)
return resp.json()
代码为示例,具体接口路径和字段名以官方文档为准。
2.3 接收回调结构
json{
"appId": "你的appId",
"fromWxid": "发送者wxid",
"toWxid": "接收者wxid或群id",
"type": 1,
"content": "消息正文",
"msgId": "消息唯一ID",
"createTime": 1718000000
}
文本消息的 content 字段即消息全文,处理时注意去除 @昵称 前缀再做关键词匹配,否则 @ 触发的机器人指令会匹配失败。
三、图片消息
3.1 结构特点
图片消息的发送方式分两种:上传本地图片和转发已有图片。
- 上传本地图片:使用
postImage接口,传入图片的 Base64 编码或 URL(以官方文档为准)。 - 转发图片:使用
forwardImage接口,直接传入原始消息的msgId,平台会帮你复制转发,避免重复上传。
在批量场景下(如向多人群发同一张图),应先上传一次拿到内部 ID,然后用转发接口群发,可以显著降低带宽消耗和接口调用压力。
3.2 发送示例
pythonimport base64
def send_image_by_url(to_wxid: str, img_url: str):
"""通过图片URL发送图片(具体字段以文档为准)"""
url = f"{BASE}/message/postImage"
payload = {
"appId": APPID,
"toWxid": to_wxid,
"imgUrl": img_url
}
resp = requests.post(url, json=payload, headers=HEADERS)
return resp.json()
def forward_image(to_wxid: str, msg_id: str):
"""转发已有图片消息"""
url = f"{BASE}/message/forwardImage"
payload = {
"appId": APPID,
"toWxid": to_wxid,
"msgId": msg_id
}
resp = requests.post(url, json=payload, headers=HEADERS)
return resp.json()
3.3 接收与下载
收到图片消息时,回调里的 content 通常是一段 XML 或加密内容(取决于平台实现),真正的图片数据需要调用 downloadImage 接口,传入 msgId 来下载原图。
注意:图片下载要做队列化处理,每条消息间隔建议在 3-10 秒,避免并发请求过高触发限流。
四、语音消息
4.1 格式说明
微信语音的底层格式是 SILK(一种专为语音通话优化的有损编码格式)。直接用普通播放器是无法播放的,需要先将 SILK 转换为 MP3 或 WAV 才能使用。
常见处理流程:
- 收到语音消息回调,记录
msgId。 - 调用
downloadFile或平台提供的语音下载接口,获取 SILK 文件。 - 使用
silk-decoder等工具转码为 MP3。 - 将 MP3 送入语音识别(如 Whisper、百度 ASR)转为文字。
4.2 发送语音
pythondef send_voice(to_wxid: str, voice_url: str, voice_duration: int):
"""
voice_url: 语音文件的访问地址(SILK或AMR格式,具体以文档为准)
voice_duration: 语音时长,单位秒
"""
url = f"{BASE}/message/postVoice"
payload = {
"appId": APPID,
"toWxid": to_wxid,
"voiceUrl": voice_url,
"voiceDuration": voice_duration
}
resp = requests.post(url, json=payload, headers=HEADERS)
return resp.json()
4.3 典型应用
- 客服系统录音留档:收到语音后异步下载转码,存入对话记录。
- 语音指令机器人:将语音转文字后,按文本处理流程路由到业务逻辑。
五、视频消息
5.1 结构特点
视频消息在微信中包含两个元素:视频本体和缩略图(封面图)。发送视频时通常需要同时提供这两个文件,缺少封面会导致在某些客户端显示异常。
视频文件体积较大,不适合内嵌传输。常见做法是先将视频上传到 CDN,再将可访问的 URL 和封面 URL 一并传给发送接口。
5.2 发送示例
pythondef send_video(to_wxid: str, video_url: str, thumb_url: str, duration: int):
"""
video_url: 视频文件URL
thumb_url: 封面图URL
duration: 视频时长(秒)
"""
url = f"{BASE}/message/postVideo"
payload = {
"appId": APPID,
"toWxid": to_wxid,
"videoUrl": video_url,
"thumbUrl": thumb_url,
"videoDuration": duration
}
resp = requests.post(url, json=payload, headers=HEADERS)
return resp.json()
5.3 转发视频
与图片类似,批量场景优先用 forwardFile 或平台的视频转发接口,减少重复上传开销。
六、链接卡片消息(type=49 子类型)
6.1 为什么用链接卡片
普通文本里的 URL 在微信中只是一串字符,点击后才打开浏览器。链接卡片则会展示标题、摘要和缩略图,视觉效果更突出,点击率通常高于纯链接文本。
6.2 数据结构
链接卡片属于 type=49 的富媒体消息,其 content 字段中包含一段 XML,结构示意如下(字段以官方文档为准):
xml<msg>
<appmsg>
<title>文章标题</title>
<des>文章摘要</des>
<url>https://目标页面URL</url>
<thumburl>https://封面图URL</thumburl>
<type>5</type> <!-- 5 表示链接卡片 -->
</appmsg>
</msg>
6.3 发送示例
pythondef send_link(to_wxid: str, title: str, desc: str, link_url: str, thumb_url: str):
"""发送链接卡片消息"""
url = f"{BASE}/message/postLink"
payload = {
"appId": APPID,
"toWxid": to_wxid,
"title": title,
"desc": desc,
"linkUrl": link_url,
"thumbUrl": thumb_url
}
resp = requests.post(url, json=payload, headers=HEADERS)
return resp.json()
6.4 注意事项
- 链接必须是 HTTPS,HTTP 链接在微信中会被屏蔽或标记为不安全。
- 缩略图建议尺寸不超过 300x300,格式为 JPEG 或 PNG。
- 链接目标域名若频繁出现在举报中,可能导致卡片显示异常。
七、小程序消息
7.1 特点与应用场景
小程序消息是一种深度集成微信生态的富媒体类型,点击后可以直接跳转到指定小程序的特定页面,无需离开微信。常用于:
- 电商订单通知(跳转到订单详情页)
- 活动邀请(跳转到报名页面)
- 服务触达(跳转到预约/查询功能页)
小程序消息同样属于 type=49,通过 XML 中的子 type 字段来标识(通常为 33 或 36,以官方文档为准)。
7.2 发送所需参数
发送小程序消息通常需要以下关键字段:
| 字段 | 说明 |
|---|---|
| ghId | 小程序 GH 原始 ID(以 gh_ 开头) |
| title | 消息卡片标题 |
| thumbUrl | 封面图 URL |
| pagePath | 跳转页面路径,如 pages/detail/index?id=123 |
| appName | 小程序显示名称 |
pythondef send_mini_program(to_wxid: str, gh_id: str, title: str,
page_path: str, thumb_url: str, app_name: str):
"""发送小程序消息卡片(字段以官方文档为准)"""
url = f"{BASE}/message/postMiniApp" # 接口路径以文档为准
payload = {
"appId": APPID,
"toWxid": to_wxid,
"ghId": gh_id,
"title": title,
"pagePath": page_path,
"thumbUrl": thumb_url,
"appName": app_name
}
resp = requests.post(url, json=payload, headers=HEADERS)
return resp.json()
八、消息接收:回调机制与类型分发
不管发送多简单,消息接收这一侧往往是系统复杂度的来源。微信消息接收依赖回调(Webhook)机制:平台在收到消息后将其 POST 到你预先用 setCallback 注册的地址。
8.1 回调地址要求
- 必须是公网可访问的 HTTPS 地址。
- 接口需要在收到 POST 后立即返回 HTTP 200,否则平台会认为推送失败并重试。
- 不要在回调里做耗时操作(如调用外部 AI 接口),应先入队列,异步处理。
通过 HTTP API 主动发出的消息不会触发回调,只有对方发来的消息才会。
8.2 统一分发逻辑
WechatApi 提供扫码登录、消息收发、好友与群管理等 REST 接口,HTTP 调用即可,适合快速搭建消息中台。
下面是一个按消息类型做分发的基础框架示例:
pythonfrom flask import Flask, request, jsonify
app = Flask(__name__)
MSG_TYPE_TEXT = 1
MSG_TYPE_IMAGE = 3
MSG_TYPE_VOICE = 34
MSG_TYPE_VIDEO = 43
MSG_TYPE_RICH = 49 # 链接卡片、小程序、文件等
@app.route("/callback", methods=["POST"])
def callback():
data = request.json
msg_type = data.get("type")
if msg_type == MSG_TYPE_TEXT:
handle_text(data)
elif msg_type == MSG_TYPE_IMAGE:
handle_image(data)
elif msg_type == MSG_TYPE_VOICE:
handle_voice(data)
elif msg_type == MSG_TYPE_VIDEO:
handle_video(data)
elif msg_type == MSG_TYPE_RICH:
handle_rich(data) # 进一步解析 XML 中的子类型
else:
pass # 系统消息、表情等暂不处理
return jsonify({"code": 200})
def handle_text(data):
content = data.get("content", "").strip()
# 去掉 @ 前缀后进行关键词匹配
# ...
def handle_image(data):
msg_id = data.get("msgId")
# 异步触发下载任务,不要在回调里同步下载
# download_queue.put(msg_id)
pass
def handle_voice(data):
# 同上,异步下载 + SILK 转码
pass
def handle_video(data):
pass
def handle_rich(data):
import xml.etree.ElementTree as ET
content = data.get("content", "")
# 解析 XML 中的 appmsg.type 判断子类型
try:
root = ET.fromstring(content)
sub_type = root.find(".//type")
if sub_type is not None:
print(f"富媒体子类型: {sub_type.text}")
except Exception:
pass
以上代码为示例,具体接口路径和回调字段以官方文档为准。
8.3 各类型接收注意事项汇总
| 消息类型 | 接收处理要点 |
|---|---|
| 文本 | 去除 @ 前缀再做关键词匹配 |
| 图片 | 需额外调用下载接口获取图片文件,建议异步队列 |
| 语音 | SILK 格式需转码,转文字前先解码 |
| 视频 | 文件较大,务必异步下载,不要同步阻塞回调响应 |
| 链接卡片 | 解析 content 中的 XML 获取标题/URL |
| 小程序 | 解析 XML 获取 ghId/pagePath,通常只做记录不做回复 |
九、常见问题与排错
Q:回调地址配置了但收不到消息?
检查以下几点:
- 回调地址是否公网可访问,本地 localhost 无效。
- 接口返回是否为 HTTP 200,非 200 会被判定为失败。
- 设备(微信账号)是否在线,离线状态无法收到消息推送。
Q:type=49 的消息解析出来是乱码?
type=49 的 content 字段是 XML,需要用 XML 解析器处理,不能当普通字符串截取。注意 XML 中可能包含 CDATA 节点。
Q:发送图片失败,ret 不等于 200?
常见原因:
- 图片 URL 无法被平台服务器访问(内网地址无效)。
- 图片文件过大,超过接口限制(通常 5-10 MB)。
- 账号今日发送频率过高触发限流,稍等后重试。
Q:语音发送后对方听不到声音?
确认音频格式是否为平台要求的 SILK 或 AMR,普通 MP3 文件需要先转码为 SILK 再发送。
总结
微信的消息类型体系涵盖文本、图片、语音、视频、链接卡片和小程序等多种格式,每种类型在发送参数和接收解析上都有各自的特点。掌握 type 字段的含义、富媒体消息的 XML 子类型、以及回调异步处理的最佳实践,是构建稳健微信消息系统的基础。
