近期接到了涉及微信开放平台和微信公众平台相关的开发需求,开发过程中踩了许多坑,把相关问题整理记录下来以便巩固记忆,并把总结的经验分享出来,本篇分享微信服务号开发,希望可以给大家提供帮助
1、微信开放平台:面向开发人员,为网站、App提供微信第三方登录功能,为App提供支付功能。
2、微信公众平台:对应的是公众号,包括订阅号、服务号、企业号,面向运营人员和开发人员,运营可以直接登录公众号管理后台查看公众号的整体情况,开发人员则是通过调用微信提供的各种接口来增强公众号的功能;
3、微信商户平台,用户通过微信支付的钱,最终到达商户账号。无论是开放平台还是公众平台,涉及到支付,都需要商户平台账号
通过官方提供的图片可以了解到,订阅号的优势就是进行消息推送,而服务号的优势是能够提供个性化的服务
公众平台只能通过管理员扫码登录,当运营、开发人员较多时,可以进行绑定运营/开发者微信号进行自行扫码登录(http://kf.qq.com/faq/120911VrYVrA141211FbEnq2.html)
登录后可以在管理后台进行一系列操作,例如:
创作管理:图文素材、多媒体素材上传
公众号设置:设置公众号关注回复内容,收到关键词回复规则和内容,设置收到消息自动回复内容,自定义公众号菜单
(注意:如果在开发者中心开启回调URL和Token进行公众号二次开发后,官方提供的公众号设置功能将被关闭,后面我会针对这个场景做介绍)
管理功能:查看关注该公众号的用户、接收的用户消息,可以通过管理后台对关注的用户进行消息回复、消息群发
统计功能:用户分析、内容分析、菜单分析、图文分析、消息分析等
公众号开发切记保存好AppID和AppSecret信息(请求获取AccessToken时用到),其中AppSecret在管理后台不显示,且无法查看,忘记只能重置,因此AppSecret一定要进行备份并注意防止泄露。
(官方对AccessToken进行了详细说明https://developers.weixin.qq.com/doc/offiaccount/Basic_Information/Get_access_token.html)
设置开发环境、测试环境、正式环境的服务器 IP为白名单,白名单以外的ip请求access_token接口会报40164错误,有了 access_token 才能调用微信的各种接口
开启服务器配置,开启以后服务号的推送信息将会传送到所配置的服务器中,包括关注事件、事件回复、关键词回复、用户消息接收、自定义菜单等功能都将被服务器接管,并且在公众平台配置的一切推送规则都将被停用,需要注意。
(PS:在配置服务器URL时,由于微信会发送请求进行签名校验,填写的URL必须是可以外网访问的,开发时建议使用ngrok进行内网穿透方便调试 附上网址: https://ngrok.com/)
其他详情可以参考服务器配置接入指南(https://developers.weixin.qq.com/doc/offiaccount/Basic_Information/Access_Overview.html)
开发者提交信息后,微信服务器将发送GET请求到填写的服务器地址URL上,GET请求携带参数如下表所示:
参数 | 描述 |
---|---|
signature | 微信加密签名,signature结合了开发者填写的token参数和请求中的timestamp参数、nonce参数。 |
timestamp | 时间戳 |
nonce | 随机数 |
echostr | 随机字符串 |
开发者通过检验signature对请求进行校验(下面有校验方式)。若确认此次GET请求来自微信服务器,请原样返回echostr参数内容,则接入生效,成为开发者成功,否则接入失败。加密/校验流程如下:
1)将token、timestamp、nonce三个参数进行字典序排序
2)将三个参数字符串拼接成一个字符串进行sha1加密
3)开发者获得加密后的字符串可与signature对比,标识该请求来源于微信
代码示例:
代码示例:
//解析
@GetMapping("/weChatParseXml")
public String parseXmlGet(@RequestParam("signature") String signature,
@RequestParam("timestamp") String timestamp,
@RequestParam("nonce") String nonce,
@RequestParam("echostr") String echostr) {
logger.info("signature: " + signature);
logger.info("timestamp: " + timestamp);
logger.info("nonce: " + nonce);
logger.info("echostr: " + echostr);
//排序
String[] strArray = {TOKEN, timestamp, nonce}; //TOKEN为公众平台填写的Token
Arrays.sort(strArray);
StringBuilder sb = new StringBuilder();
for (String str : strArray) {
sb.append(str);
}
String sortString = sb.toString();
logger.info("sortString: " + sortString);
//加密 这里主要是进行sha1加密 可以使用 SecurityUtil工具类进行加密
String myString = weChatAppletBiz.weChatParseXmlSha1(sortString);
logger.info("myString: " + myString);
//校验
if (myString != null && myString != "" && myString.equals(signature)) {
logger.info("签名校验通过");
//如果检验成功原样返回echostr,微信服务器接收到此输出,才会确认检验完成。
return echostr;
} else {
logger.info("签名校验失败");
return "";
}
}
当普通微信用户向公众账号发消息时,微信服务器将POST消息的XML数据包到开发者填写的URL上。
(PS:官方提供了对文本、图片、语音、视频、地理位置等消息接收的方法,一般对文本的解析足以满足大部分需求,因此下面只针对文本解析进行说明,其他详情可以查阅上面的服务器接入指南)
请注意:
公众平台官网的开发者中心处设置消息加密。开启加密后,用户发来的消息和开发者回复的消息都会被加密(但开发者通过客服接口等API调用形式向用户发送消息,则不受影响)。关于消息加解密的详细说明,请见“发送消息-被动回复消息加解密说明”。 关于文本消息的推送XML数据包结构如下:
文本消息
<xml>
<ToUserName>ToUserName>
<FromUserName>FromUserName>
<CreateTime>1348831860CreateTime>
<MsgType>MsgType>
<Content>Content>
<MsgId>1234567890123456MsgId>
xml>
参数 | 描述 |
---|---|
ToUserName | 开发者微信号 |
FromUserName | 发送方帐号(一个OpenID) |
CreateTime | 消息创建时间 (整型) |
MsgType | 消息类型,文本为text |
Content | 文本消息内容 |
MsgId | 消息id,64位整型 |
代码示例:
@PostMapping("/weChatParseXml")
public Object parseXmlPost(HttpServletRequest request) throws Exception {
logger.info("==================接收到微信消息==================");
//解析xml
// Map stringStringMap = weChatAppletBiz.parseXml(request);
Map messageMap = new HashMap<>();
// 从request中取得输入流
InputStream inputStream = request.getInputStream();
// 读取输入流
SAXReader reader = new SAXReader();
Document document = reader.read(inputStream);
String asXML = document.asXML();
logger.info(asXML);
// 得到xml根元素
Element root = document.getRootElement();
// 得到根元素的所有子节点
List elementList = root.elements();
// 遍历所有子节点
for (Element e : elementList) {
messageMap.put(e.getName(), e.getText());
}
// 释放资源
inputStream.close();
logger.info("获取到的信息:\n" + messageMap);
weChatAppletBiz.dealWithInfo(messageMap);//该方法内根据用户行为做对应的消息推送
logger.info("==================接收微信消息结束==================");
return "";
}
实际效果:
用户事件:
当服务器后台获取到用户发送的信息后,可以根据Event参数获取事件类型,针对不用的事件进行对应处理
事件类型分为subscribe(订阅)、unsubscribe(取消订阅)、 SCAN(扫描二维码)、 CLICK(菜单点击)、 LOCATION(地理位置)、 VIEW(菜单跳转)
不同事件相关参数也不同,具体还请到上面分享的配置接入指南页面,"消息管理模块-接收事件推送"进行参考
接口调用请求说明
http请求方式:
POST https://api.weixin.qq.com/cgi-bin/message/custom/send?access_token=ACCESS_TOKEN
消息类型分为多种,包含文本、图片、语音、视频、音频、视频、图文、菜单,这里举例几个常用JSON数据包如下:
发送文本消息
{
"touser":"OPENID",
"msgtype":"text",
"text":
{
"content":"Hello World"
}
}
发送图片消息
{
"touser":"OPENID",
"msgtype":"image",
"image":
{
"media_id":"MEDIA_ID"
}
}
发送视频消息
{
"touser":"OPENID",
"msgtype":"video",
"video":
{
"media_id":"MEDIA_ID",
"thumb_media_id":"MEDIA_ID",
"title":"TITLE",
"description":"DESCRIPTION"
}
}
发送图文消息(有两种方式,这里举例一种) 注意!!图文消息条数限制在1条以内,如果图文数超过1,则将会返回错误码45008。 这里非常坑!
{
"touser":"OPENID",
"msgtype":"mpnews",
"mpnews":
{
"media_id":"MEDIA_ID"
}
}
发送文本消息时,还支持插入跳小程序的文字链
文本内容点击跳小程序
说明:
1.data-miniprogram-appid 项,填写小程序appid,则表示该链接跳小程序;
2.data-miniprogram-path项,填写小程序路径,路径与app.json中保持一致,可带参数;
3.对于不支持data-miniprogram-appid 项的客户端版本,如果有herf项,则仍然保持跳href中的网页链接;
4.data-miniprogram-appid对应的小程序必须与公众号有绑定关系。
JSON数据包发送时需要注意以下几点
1、OPENID可以通过接收参数中fromUserName字段获得
2、msgtype一定要遵从规范,否则会出错
3、media_id是管理员自行上传的相关资源,上传成功后会成为微信素材同时生成唯一的media_id,上传方式可以从公众平台页面上传或使用curl命令进行上传
(PS:这里我curl用的很少,大多数资源都是通过公众平台进行上传,而且上传的都是永久素材)
新增永久视频素材的调用示例(慎用):
curl “https://api.weixin.qq.com/cgi-bin/material/add_material?access_token=ACCESS_TOKEN&type=TYPE” -F [email protected] -F description=’{“title”:VIDEO_TITLE, “introduction”:INTRODUCTION}’
参数说明
参数 | 是否必须 | 说明 |
---|---|---|
title | 是 | 视频素材的标题 |
introduction | 是 | 视频素材的描述 |
返回说明
{
"media_id":MEDIA_ID,
"url":URL
}
返回参数说明
参数 | 描述 |
---|---|
media_id | 新增的永久素材的media_id |
url | 新增的图片素材的图片URL(仅新增图片素材时会返回该字段) |
调用微信系统的素材接口可以获取对应的资源列表,注意!!获取资源接口调用时有每日次数限制,不建议实时查询,我这里是通过设计表,找恰当的时机将数据同步到我们自己服务器中。
接口说明:
1、获取永久素材的列表,也包含公众号在公众平台官网素材管理模块中新建的图文消息、语音、视频等素材
2、临时素材无法通过本接口获取
3、调用该接口需https协议
接口调用请求说明
http请求方式:
POST https://api.weixin.qq.com/cgi-bin/material/batchget_material?access_token=ACCESS_TOKEN
调用示例
{
"type":TYPE,
"offset":OFFSET,
"count":COUNT
}
参数说明
参数 | 是否必须 | 说明 |
---|---|---|
type | 是 | 素材的类型,图片(image)、视频(video)、语音 (voice)、图文(news) |
offset | 是 | 从全部素材的该偏移位置开始返回,0表示从第一个素材 返回 |
count | 是 | 返回素材的数量,取值在1到20之间 |
返回说明
永久图文消息素材列表的响应如下:
{
"total_count": TOTAL_COUNT,
"item_count": ITEM_COUNT,
"item": [{
"media_id": MEDIA_ID,
"content": {
"news_item": [{
"title": TITLE,
"thumb_media_id": THUMB_MEDIA_ID,
"show_cover_pic": SHOW_COVER_PIC(0 / 1),
"author": AUTHOR,
"digest": DIGEST,
"content": CONTENT,
"url": URL,
"content_source_url": CONTETN_SOURCE_URL
},
//多图文消息会在此处有多篇文章
]
},
"update_time": UPDATE_TIME
},
//可能有多个图文消息item结构
]
}
其他类型(图片、语音、视频)的返回如下:
{
"total_count": TOTAL_COUNT,
"item_count": ITEM_COUNT,
"item": [{
"media_id": MEDIA_ID,
"name": NAME,
"update_time": UPDATE_TIME,
"url":URL
},
//可能会有多个素材
]
}
返回参数说明
参数 | 描述 |
---|---|
total_count | 该类型的素材的总数 |
item_count | 本次调用获取的素材的数量 |
title | 图文消息的标题 |
thumb_media_id | 图文消息的封面图片素材id(必须是永久mediaID) |
show_cover_pic | 是否显示封面,0为false,即不显示,1为true,即显示 |
author | 作者 |
digest | 图文消息的摘要,仅有单图文消息才有摘要,多图文此处为空 |
content | 图文消息的具体内容,支持HTML标签,必须少于2万字符,小于1M,且此处会去除JS |
url | 图文页的URL,或者,当获取的列表是图片素材列表时,该字段是图片的URL |
content_source_url | 图文消息的原文地址,即点击“阅读原文”后的URL |
update_time | 这篇图文消息素材的最后更新时间 |
name | 文件名称 |
微信开发过程中很容易踩坑,遇到问题建议仔细阅读官方文档或通过微信开放社区寻找帮助