# 消息推送回复
当你使用微信云托管配置消息推送后,云托管服务就会接收用户从小程序/公众号发来的消息,并以你设定的格式(json或xml)传入你的服务路径中。
你可以在云托管服务中回复特定的json数据来回复。
# 一、被动回复
原理可以参考此文档,发送被动响应消息其实并不是一种接口,而是对微信服务器发过来消息的一次回复。
需要注意,被动回复只支持公众号,小程序需要使用主动回复。
具体消息格式如下:
# 1. 回复文本消息
{
"ToUserName": "用户OPENID",
"FromUserName": "公众号/小程序原始ID",
"CreateTime": "发送时间", // 整型,例如:1648014186
"MsgType": "text",
"Content": "文本消息"
}
# 2. 回复图片消息
{
"ToUserName": "用户OPENID",
"FromUserName": "公众号/小程序原始ID",
"CreateTime": "发送时间", // 整型,例如:1648014186
"MsgType": "image",
"Image": {
"MediaId": "素材ID"
}
}
素材的上传请移步文档最后
# 3. 回复语音消息
{
"ToUserName": "用户OPENID",
"FromUserName": "公众号/小程序原始ID",
"CreateTime": "发送时间", // 整型,例如:1648014186
"MsgType": "voice",
"Voice": {
"MediaId": "素材ID"
}
}
素材的上传请移步文档最后
# 4. 回复视频消息
{
"ToUserName": "用户OPENID",
"FromUserName": "公众号/小程序原始ID",
"CreateTime": "发送时间", // 整型,例如:1648014186
"MsgType": "video",
"Video": {
"MediaId": "素材ID",
"Title": "视频标题",
"Description": "视频描述"
}
}
素材的上传请移步文档最后
# 5. 回复音乐消息
{
"ToUserName": "用户OPENID",
"FromUserName": "公众号/小程序原始ID",
"CreateTime": "发送时间", // 整型,例如:1648014186
"MsgType": "music",
"Music": {
"Title": "音乐标题",
"Description": "音乐描述",
"MusicUrl": "音乐的链接地址",
"HQMusicUrl": "高质量音乐链接,WIFI环境优先使用该链接播放音乐",
"ThumbMediaId":"缩略图的媒体id"
}
}
素材的上传请移步文档最后
# 6. 回复图文消息
{
"ToUserName": "用户OPENID",
"FromUserName": "公众号/小程序原始ID",
"CreateTime": "发送时间", // 整型,例如:1648014186
"MsgType": "news",
"ArticleCount": 2, // 图文消息个数;当用户发送文本、图片、语音、视频、图文、地理位置这六种消息时,开发者只能回复1条图文消息;其余场景最多可回复8条图文消息
"Articles": [{
"Title": "图文标题",
"Description": "图文描述",
"PicUrl": "图片链接", // 支持JPG、PNG格式,较好的效果为大图360*200,小图200*200
"Url":"点击图文消息跳转链接"
},{
"Title": "图文标题",
"Description": "图文描述",
"PicUrl": "图片链接", // 支持JPG、PNG格式,较好的效果为大图360*200,小图200*200
"Url":"点击图文消息跳转链接"
}]
}
# 二、主动回复
如果你无法即时回复,需要一段时间后再回复,可以使用接口发起回复。
以下使用建议配合开放接口服务一块使用。
# 接口地址
http://api.weixin.qq.com/cgi-bin/message/custom/send
需要将 /cgi-bin/message/custom/send
加入配置白名单
# 入参数据
- 发送文本
{
"touser":"用户OPENID",
"msgtype":"text",
"text": {
"content":"文本消息"
}
}
- 发送图片
{
"touser":"用户OPENID",
"msgtype":"image",
"image": {
"media_id":"素材ID"
}
}
- 发送链接,公众号等同图文,只限1条
{
"touser":"用户OPENID",
"msgtype":"link",
"link": {
"title": "图文标题",
"description": "图文描述",
"thumb_url": "图片链接", // 支持JPG、PNG格式,较好的效果为大图360*200,小图200*200
"url": "跳转链接"
}
}
- 发送音乐
{
"touser":"用户OPENID",
"msgtype":"music",
"music": {
"title": "音乐标题",
"description": "音乐描述",
"music_url": "音乐链接地址",
"HQ_music_url": "高清音乐链接地址,用于wifi",
"thumb_media_id": "缩略图素材ID"
}
}
- 发送视频
{
"touser":"用户OPENID",
"msgtype":"video",
"video":
{
"media_id": "媒体素材ID",
"title": "视频标题",
"description": "视频描述"
}
}
- 发送语音
{
"touser":"用户OPENID",
"msgtype":"voice",
"voice":
{
"media_id": "媒体素材ID"
}
}
- 小程序卡片
{
"touser":"用户OPENID",
"msgtype":"miniprogrampage",
"miniprogrampage":
{
"title": "消息标题",
"pagepath":"小程序的页面路径", // 跟app.json对齐,支持参数,比如pages/index/index?foo=bar
"thumb_media_id": "缩略图素材ID"
}
}
# 回参数据
{
"errcode": 0,
"errmsg": "ok"
}
注意,上述的消息类型不是所有都能发送的,不同类型的账号有一定的限制。
比如,公众号类型不可发送「小程序卡片」,小程序类型不可发「视频」、「语音」等,具体都会动态变化,请先验证一下是否可以发送成功。
如果报invalid appid
、 invalid type
信息,就意味着你的appid类型不能发这种消息,这是正常的,不要将其视为错误。
# 三、素材上传
上面发送消息需要的各类素材,可根据业务实际使用情况按需使用对应的接口上传素材,下面以小程序新增图片素材接口为例。公众号可按公众号素材管理相关接口使用即可。
以下使用建议配合开放接口服务一块使用。
# 接口地址
http://api.weixin.qq.com/cgi-bin/media/upload?type=TYPE
需要将 /cgi-bin/media/upload
加入配置白名单
# 入参数据
URL参数中的 type
需要填写自己上传的素材类型值,范围如下:
type值 | 类型 | 描述 |
---|---|---|
image | 图片 | 10M,支持PNG\JPEG\JPG\GIF格式 |
voice | 语音 | 2M,播放长度不超过60s,支持AMR\MP3格式 |
video | 视频 | 10MB,支持MP4格式 |
thumb | 缩略图 | 64KB,支持JPG格式 |
body中传form-data
形式,参数如下:
参数值 | 类型 | 描述 |
---|---|---|
media | File | 媒体文件,有filename、filelength、content-type等信息 |
# 出参数据
正确情况下的返回JSON数据包结果如下:
{
"type":"TYPE",
"media_id":"MEDIA_ID",
"created_at":123456789
}
参数 | 描述 |
---|---|
type | 媒体文件类型,分别有图片(image)、语音(voice)、视频(video)和缩略图(thumb,主要用于视频与音乐格式的缩略图) |
media_id | 媒体文件上传后,获取标识,3天内有效 |
created_at | 媒体文件上传时间戳 |
错误情况下的返回JSON数据包示例如下:
{
"errcode":40004,
"errmsg":"invalid media type"
}
# 四、演示DEMO
# 1. 主动回复例子
以下例子使用node.js
来构建公众号主动回复DEMO,其中mediaid自行上传替换,文件夹下共三个文件。
小程序演示效果如下:
index.js
文件,需要注意替换里面所有media_id,以及小程序路径等配置项。
const express = require('express')
const request = require('request')
const app = express()
app.use(express.json())
app.all('/', async (req, res) => {
console.log('消息推送', req.body)
// 从header中取appid,如果from-appid不存在,则不是资源复用场景,可以直接传空字符串,使用环境所属账号发起云调用
const appid = req.headers['x-wx-from-appid'] || ''
const { ToUserName, FromUserName, MsgType, Content, CreateTime } = req.body
console.log('推送接收的账号', ToUserName, '创建时间', CreateTime)
if (MsgType === 'text') {
if (Content === '回复文字') { // 小程序、公众号可用
await sendmess(appid, {
touser: FromUserName,
msgtype: 'text',
text: {
content: '这是回复的消息'
}
})
} else if (Content === '回复图片') { // 小程序、公众号可用
await sendmess(appid, {
touser: FromUserName,
msgtype: 'image',
image: {
media_id: 'P-hoCzCgrhBsrvBZIZT3jx1M08WeCCHf-th05M4nac9TQO8XmJc5uc0VloZF7XKI'
}
})
} else if (Content === '回复语音') { // 仅公众号可用
await sendmess(appid, {
touser: FromUserName,
msgtype: 'voice',
voice: {
media_id: '06JVovlqL4v3DJSQTwas1QPIS-nlBlnEFF-rdu03k0dA9a_z6hqel3SCvoYrPZzp'
}
})
} else if (Content === '回复视频') { // 仅公众号可用
await sendmess(appid, {
touser: FromUserName,
msgtype: 'video',
video: {
media_id: 'XrfwjfAMf820PzHu9s5GYsvb3etWmR6sC6tTH2H1b3VPRDedW-4igtt6jqYSBxJ2',
title: '微信云托管官方教程',
description: '微信官方团队打造,贴近业务场景的实战教学'
}
})
} else if (Content === '回复音乐') { // 仅公众号可用
await sendmess(appid, {
touser: FromUserName,
msgtype: 'music',
music: {
title: 'Relax|今日推荐音乐',
description: '每日推荐一个好听的音乐,感谢收听~',
music_url: 'https://c.y.qq.com/base/fcgi-bin/u?__=0zVuus4U',
HQ_music_url: 'https://c.y.qq.com/base/fcgi-bin/u?__=0zVuus4U',
thumb_media_id: 'XrfwjfAMf820PzHu9s5GYgOJbfbnoUucToD7A5HFbBM6_nU6TzR4EGkCFTTHLo0t'
}
})
} else if (Content === '回复图文') { // 小程序、公众号可用
await sendmess(appid, {
touser: FromUserName,
msgtype: 'link',
link: {
title: 'Relax|今日推荐音乐',
description: '每日推荐一个好听的音乐,感谢收听~',
thumb_url: 'https://y.qq.com/music/photo_new/T002R300x300M000004NEn9X0y2W3u_1.jpg?max_age=2592000', // 支持JPG、PNG格式,较好的效果为大图360*200,小图200*200
url: 'https://c.y.qq.com/base/fcgi-bin/u?__=0zVuus4U'
}
})
} else if (Content === '回复小程序') { // 仅小程序可用
await sendmess(appid, {
touser: FromUserName,
msgtype: 'miniprogrampage',
miniprogrampage: {
title: '小程序卡片标题',
pagepath: 'pages/index/index', // 跟app.json对齐,支持参数,比如pages/index/index?foo=bar
thumb_media_id: 'XrfwjfAMf820PzHu9s5GYgOJbfbnoUucToD7A5HFbBM6_nU6TzR4EGkCFTTHLo0t'
}
})
}
res.send('success')
} else {
res.send('success')
}
})
app.listen(80, function () {
console.log('服务启动成功!')
})
function sendmess (appid, mess) {
return new Promise((resolve, reject) => {
request({
method: 'POST',
url: `http://api.weixin.qq.com/cgi-bin/message/custom/send?from_appid=${appid}`,
body: JSON.stringify(mess)
}, function (error, response) {
if (error) {
console.log('接口返回错误', error)
reject(error.toString())
} else {
console.log('接口返回内容', response.body)
resolve(response.body)
}
})
})
}
Dockerfile
文件
FROM node:12-slim
WORKDIR /usr/src/app
COPY package*.json ./
RUN npm config set registry https://mirrors.tencent.com/npm/
RUN npm install
COPY . ./
CMD ["node", "index.js"]
package.json
文件
{
"name": "cloudbase-push",
"version": "1.0.0",
"description": "call push server",
"main": "index.js",
"scripts": {},
"author": "",
"license": "ISC",
"dependencies": {
"express": "^4.16.4",
"request": "^2.88.2"
}
}
首先,在云托管控制台-云调用中打开开放接口服务,并配置 /cgi-bin/message/custom/send
到微信令牌权限配置。
将上述3个文件组成文件夹,版本创建上传,端口填写 80
,部署发布。
在消息推送配置填写对应的服务,路径填写 /
。
# 2. 被动回复例子
以下例子使用node.js
来构建公众号被动回复DEMO,其中mediaid自行上传替换,文件夹下共三个文件,只适用于公众号。
演示效果如下:
index.js
文件
const express = require('express')
const bodyParser = require('body-parser')
const PORT = process.env.PORT || 80
const app = express()
app.use(bodyParser.raw())
app.use(bodyParser.json({}))
app.use(bodyParser.urlencoded({ extended: true }))
app.all('/', async (req, res) => {
console.log('消息推送', req.body)
const { ToUserName, FromUserName, MsgType, Content, CreateTime } = req.body
if (MsgType === 'text') {
if (Content === '回复文字') {
res.send({
ToUserName: FromUserName,
FromUserName: ToUserName,
CreateTime: CreateTime,
MsgType: 'text',
Content: '这是回复的消息'
})
} else if (Content === '回复图片') {
res.send({
ToUserName: FromUserName,
FromUserName: ToUserName,
CreateTime: CreateTime,
MsgType: 'image',
Image: {
MediaId: 'P-hoCzCgrhBsrvBZIZT3jx1M08WeCCHf-th05M4nac9TQO8XmJc5uc0VloZF7XKI'
}
})
} else if (Content === '回复语音') {
res.send({
ToUserName: FromUserName,
FromUserName: ToUserName,
CreateTime: CreateTime,
MsgType: 'voice',
Voice: {
MediaId: '06JVovlqL4v3DJSQTwas1QPIS-nlBlnEFF-rdu03k0dA9a_z6hqel3SCvoYrPZzp'
}
})
} else if (Content === '回复视频') {
res.send({
ToUserName: FromUserName,
FromUserName: ToUserName,
CreateTime: CreateTime,
MsgType: 'video',
Video: {
MediaId: 'XrfwjfAMf820PzHu9s5GYsvb3etWmR6sC6tTH2H1b3VPRDedW-4igtt6jqYSBxJ2',
Title: '微信云托管官方教程',
Description: '微信官方团队打造,贴近业务场景的实战教学'
}
})
} else if (Content === '回复音乐') {
res.send({
ToUserName: FromUserName,
FromUserName: ToUserName,
CreateTime: CreateTime,
MsgType: 'music',
Music: {
Title: 'Relax|今日推荐音乐',
Description: '每日推荐一个好听的音乐,感谢收听~',
MusicUrl: 'https://c.y.qq.com/base/fcgi-bin/u?__=0zVuus4U',
HQMusicUrl: 'https://c.y.qq.com/base/fcgi-bin/u?__=0zVuus4U',
ThumbMediaId: 'XrfwjfAMf820PzHu9s5GYgOJbfbnoUucToD7A5HFbBM6_nU6TzR4EGkCFTTHLo0t'
}
})
} else if (Content === '回复图文') {
res.send({
ToUserName: FromUserName,
FromUserName: ToUserName,
CreateTime: CreateTime,
MsgType: 'news',
ArticleCount: 1,
Articles: [{
Title: 'Relax|今日推荐音乐',
Description: '每日推荐一个好听的音乐,感谢收听~',
PicUrl: 'https://y.qq.com/music/photo_new/T002R300x300M000004NEn9X0y2W3u_1.jpg?max_age=2592000',
Url: 'https://c.y.qq.com/base/fcgi-bin/u?__=0zVuus4U'
}]
})
} else {
res.send('success')
}
} else {
res.send('success')
}
})
app.listen(PORT, function () {
console.log(`运行成功,端口:${PORT}`)
})
Dockerfile
文件
FROM node:12-slim
WORKDIR /usr/src/app
COPY package*.json ./
RUN npm config set registry https://mirrors.tencent.com/npm/
RUN npm install
COPY . ./
CMD ["node", "index.js"]
package.json
文件
{
"name": "cloudbase-push",
"version": "1.0.0",
"description": "call push server",
"main": "index.js",
"scripts": {},
"author": "",
"license": "ISC",
"dependencies": {
"express": "^4.16.4"
}
}
将3个文件组成文件夹,版本创建上传,端口填写 80
,部署发布。
在消息推送配置填写对应的服务,路径填写 /