1. 微信公众号开发指南之从入门到弃坑
致读者: 点击上方 “雪之梦技术驿站” → 点击右上角“ ... ”→ 点选“设为星标★ ” 加上星标,就不会找不到我啦!
偷偷溜进文章的小广告,别害羞,点进去瞅瞅,说不定能发现什么宝藏呢!
文末那个也别错过,说不定是通往秘密花园的钥匙哦!
作为小小的公众号作者,一直坚持原创,虽然中间有过长时间的断更,但是坚持原创的初心一直未变.
前些天偶然间想起来了我还有一个公众号,于是乎又重新下载了订阅号助手
APP,观察了几天的时间.
本来就少的又少的粉丝还上上下下关注脱粉,分析下来大多数还是来源于引流网站的导流.
明明记得早上还能多涨一个粉丝,结果晚上好像又恢复了原样子.我想大概是白嫖结束取关了.
所以,我决定做出了一个艰难的决定: 开发微信公众号,实时统计取关的粉丝!
原创不易,且行且珍惜吧!
1.1. 先决条件
1.1.1. 前提
想清楚目标再行动
正常来说,个人订阅号的权限足够使用,而且微信公众号后台都是可视化操作页面.
然而,翻遍了公众号后台和订阅号助手
app 也没有实现找出取关粉丝
这一朴素需求.
甚至想过利用爬虫技术
定期抓取全部用户,筛选比较也能得到取关用户.
然而,这种技术显然不够优雅也不能实时统计取关明细.
于是乎,翻开了微信公众号开发文档,有了这么的接口,接下来就可以进行公众号开发咯!
<xml>
<ToUserName><![CDATA[toUser]]></ToUserName>
<FromUserName><![CDATA[FromUser]]></FromUserName>
<CreateTime>123456789</CreateTime>
<MsgType><![CDATA[event]]></MsgType>
<Event><![CDATA[subscribe]]></Event>
</xml>
参数说明:
- oUserName 开发者微信号
- FromUserName 发送方账号(一个 OpenID)
- CreateTime 消息创建时间 (整型)
- MsgType 消息类型,event
- Event 事件类型,
subscribe(订阅)
、unsubscribe(取消订阅)
众里寻他千百度,蓦然回首,那人却在灯火阑珊处!
1.1.2. 困难
自动回复和自定义菜单全部失效了,需要自行开发实现!
这里不得不吐槽一下公众号的产品设计思路,只要开启了公众号开发,原来后台设定的自动回复和自定义菜单就统统失效了!
如果公众号开发的功能与微信公众号后台设置的功能有冲突的话,可以设置优先级而不是简单粗暴地以开发为准,二者选其一,这一点就让人很纠结.
所以,务必要考虑清楚能否承受自行开发之痛,相当于要全部实现与公众号后台完全相同的功能.
未开启自定义菜单
由于开发者通过接口修改了菜单配置,当前菜单配置已失效并停用。你可以前往 开发者中心进行停用,或者通过接口配置发布菜单。
1.2. 后台设置
由于已经开发完毕,为了复盘写公众号开发系列的文章,因此反复开关后台开发功能.
为了新手的完整的体验,尽可能地还原从入门到弃坑的完整过程.
1.2.1. 自动回复
微信公众号后台:
内容与互动
-->自动回复
尽可能地完整记录下关键词回复
,收到消息回复
和被关注回复
的具体规则.
因为接下来的开发过程要完整实现这部分的逻辑,如果之前没有设置过相关规则可以忽略,继续接下来的开发.
关键词回复
解锁博客 vip, 验证码, 解锁博客 1 文本
<a href="www.test.com">点击该链接,获取博客解锁验证码</a>
被关注回复
感谢你的关注,希望雪之梦技术驿站能够对您有所帮助!
收到消息回复
如果后台长时间没能回复的话,欢迎添加微信「snowdreams1109」加急提醒一下
1.2.2. 自定义菜单
微信公众号后台:
内容与互动
-->自定义菜单
子菜单信息
完整菜单
系列教程
[Go笔记](http://mp.weixin.qq.com/mp/homepage?__biz=MzU3NTc1MDMwOQ==&hid=1&sn=6766242fb7c9b28220e6d1aafb0b6151&scene=18#wechat_redirect)
[Gitbook教程](http://mp.weixin.qq.com/mp/homepage?__biz=MzU3NTc1MDMwOQ==&hid=5&sn=23f95d8376484dbd9c8ae087a8abf24d&scene=18#wechat_redirect)
[Github教程](http://mp.weixin.qq.com/mp/homepage?__biz=MzU3NTc1MDMwOQ==&hid=3&sn=d92d36448112fe6737fe0bdfc43ec2c5&scene=18#wechat_redirect)
[Git教程](http://mp.weixin.qq.com/mp/homepage?__biz=MzU3NTc1MDMwOQ==&hid=2&sn=2c4e11cc497907211cf22007affe1243&scene=18#wechat_redirect)
[Markdown教程](http://mp.weixin.qq.com/mp/homepage?__biz=MzU3NTc1MDMwOQ==&hid=4&sn=2df16c18749da184ac671c2c110baa15&scene=18#wechat_redirect)
[精选文章](http://mp.weixin.qq.com/mp/homepage?__biz=MzU3NTc1MDMwOQ==&hid=6&sn=1490a24795eca5c71d9554ff50054148&scene=18#wechat_redirect)
了解更多
[历史消息](https://mp.weixin.qq.com/mp/profile_ext?action=home&__biz=MzU3NTc1MDMwOQ==#wechat_redirect)
[我的博客](图片 "我的博客")
[交流合作](图片 "交流合作")
1.3. 公众号开发
微信公众号后台:
设置与开发
-->基本配置
这里服务器地址(URL)
和消息加解密方式
需要重点关注,令牌(Token)
是自定义字符串,消息加解密密钥(EncodingAESKey)
可以随机生成.
开发阶段消息加解密方式
推荐明文模式
,不要选其他方式!
看到这里,真正的调整才刚刚开始,除了服务器地址(URL)
之外的其余参数是不是很简单,毫无压力?
服务器地址(URL)
首先你应该有个能正常访问的公网,支持域名或者 ip,如果没有的话,临时测试也可以搜索内网穿透技术
相关文章,这里不再赘述.
这里的服务器地址(URL)
是以后微信公众号推送消息给开发后台的唯一入口
.
不管是用户私信消息还是关注或者取消关注事件,都是统一这个url
链接.
唯一的区别在于不同的请求方式不一样,例如现在的验证服务器
功能发送的是GET
请求,其余的大多数是POST
请求.
服务器配置(已启用)
: 请先确保上述校验服务器的 url 地址外网可以无限制访问才能成功启用!!!
一旦开启后,自定义菜单和自动回复均失效,都需要通过程序接口调用方式重新设置!
选择已有工具包
现在已经弄清楚了基本流程,工欲善其事,必先利其器.选择好适合的开发工具,能做到事半功倍.
不再关心具体的繁琐的内部逻辑,只需要把重点放在业务逻辑的实现即可.
如果是Java
开发,推荐WxJava项目,实现了全部的微信公众号功能接口,非常好用!
下面以Java
语言演示服务器地址(URL)
接口应该如何编写:
@AnonymousGetMapping(produces = "text/plain;charset=utf-8")
public String checkSignature(@PathVariable String appId,
@RequestParam(name = "signature", required = false) String signature,
@RequestParam(name = "timestamp", required = false) String timestamp,
@RequestParam(name = "nonce", required = false) String nonce,
@RequestParam(name = "echostr", required = false) String echostr) {
// 校验签名
log.info("签名校验 ===》 微信加密签名: {} 时间戳: {} 随机数: {} 随机字符串: {}",signature,timestamp,nonce,echostr);
return echostr;
}
这里忽略具体的验证逻辑,不同的语言实现方式不同,重点关注接口验证成功后的返回值应该是原
echostr随机字符串
.
大多数的Java
项目接口可能返回的都是统一的json
格式,请确保text/plain;charset=utf-8
能够返回echostr 随机字符串文本,而不是带有双引号的奇怪字符串!
自动回复
自定义菜单
1.3.1. 上手开发
始终牢记微信公众号后台无论是开启服务器 url 的验证还是以后正常推送消息,永远都是相同的URL
,不同的是具体的请求方法或者是请求格式不一样而已!
@AnonymousPostMapping(produces = "text/xml; charset=UTF-8")
public String replyMessage(
@PathVariable String appId,
@RequestBody String requestBody,
@RequestParam("signature") String signature,
@RequestParam("timestamp") String timestamp,
@RequestParam("nonce") String nonce,
@RequestParam("openid") String openid,
@RequestParam(name = "encrypt_type", required = false) String encryptType,
@RequestParam(name = "msg_signature", required = false) String msgSignature) {
if (!wxMpService.switchover(appId)) {
throw new IllegalArgumentException(String.format("未找到对应appId=[%s]的配置,请核实!", appId));
}
if (!wxMpService.checkSignature(timestamp, nonce, signature)) {
throw new IllegalArgumentException("非法请求,可能属于伪造的请求!");
}
// 消息加解密方式
String out = null;
if (encryptType == null) {
// 明文传输的消息
WxMpXmlMessage inMessage = WxMpXmlMessage.fromXml(requestBody);
WxMpXmlOutMessage outMessage = myWxMpService.replyWxMpMsg(inMessage);
if (outMessage == null) {
return "";
}
out = outMessage.toXml();
} else if ("aes".equalsIgnoreCase(encryptType)) {
// aes加密的消息
WxMpXmlMessage inMessage = WxMpXmlMessage.fromEncryptedXml(requestBody, wxMpService.getWxMpConfigStorage(),timestamp, nonce, msgSignature);
WxMpXmlOutMessage outMessage = myWxMpService.replyWxMpMsg(inMessage);
if (outMessage == null) {
return "";
}
out = outMessage.toEncryptedXml(wxMpService.getWxMpConfigStorage());
}
return out;
}
依然是相同的
url
路径,不同是请求参数不同,响应格式不同text/xml; charset=UTF-8
.
所以请确保项目支持返回text/plain;charset=utf-8
和text/xml; charset=UTF-8
等格式.
这里是踩了坑的亲身体验才会反复强调,参考网络上的文章结果就是: 明明是相同的代码,为什么我的项目却运行不成功?
实现自动回复
粉丝给公众号一条文本消息,公众号立马回复一条文本消息给粉丝,不需要通过公众平台网页操作。
接受文本消息
粉丝给公众号发送文本消息:“欢迎开启公众号开发者模式”,在开发者后台,收到公众平台发送的 xml 如下:
<xml>
<ToUserName><![CDATA[公众号]]></ToUserName>
<FromUserName><![CDATA[粉丝号]]></FromUserName>
<CreateTime>1460537339</CreateTime>
<MsgType><![CDATA[text]]></MsgType>
<Content><![CDATA[欢迎开启公众号开发者模式]]></Content>
<MsgId>6272960105994287618</MsgId>
</xml>
被动回复文本消息
公众号想回复给粉丝一条文本消息,内容为“test”, 那么开发者发送给公众平台后台的 xml 内容如下:
<xml>
<ToUserName><![CDATA[粉丝号]]></ToUserName>
<FromUserName><![CDATA[公众号]]></FromUserName>
<CreateTime>1460541339</CreateTime>
<MsgType><![CDATA[text]]></MsgType>
<Content><![CDATA[test]]></Content>
</xml>
关键代码:
// 关键词回复
if(StringUtils.equals("text",msgType) && StringUtils.equalsIgnoreCase("vip",content)){
outContent = "<a href=\"https://www.test.com\">点击该链接,获取博客解锁验证码</a>";
}
{
"allFieldsMap": {
"Content": "vip",
"CreateTime": "1722873498",
"ToUserName": "gh_test",
"FromUserName": "opZ4h1Xs40_test",
"MsgType": "text",
"MsgId": "24665616993629308"
},
"articleUrlResult": {},
"content": "vip",
"createTime": 1722873498,
"fromUser": "opZ4h1Xs40_test",
"hardWare": {},
"msgId": 24665616993629308,
"msgType": "text",
"scanCodeInfo": {},
"sendLocationInfo": {},
"sendPicsInfo": {
"picList": []
},
"toUser": "gh_test"
}
关键词回复以及收到消息回复
关键词回复: 可以根据关键词自定义匹配规则,例如接收到
vip
关键词,回复验证码收到消息回复: 没有被关键词匹配成功的默认回复,也可以引入 ai 实现智能问答
接收图片消息
<xml>
<ToUserName><![CDATA[公众号]]></ToUserName>
<FromUserName><![CDATA[粉丝号]]></FromUserName>
<CreateTime>1460536575</CreateTime>
<MsgType><![CDATA[image]]></MsgType>
<PicUrl><![CDATA[http://mmbiz.qpic.cn/xxxxxx /0]]></PicUrl>
<MsgId>6272956824639273066</MsgId>
<MediaId><![CDATA[gyci5a-xxxxx-OL]]></MediaId>
</xml>
被动回复图片消息
<xml>
<ToUserName><![CDATA[粉丝号]]></ToUserName>
<FromUserName><![CDATA[公众号]]></FromUserName>
<CreateTime>1460536576</CreateTime>
<MsgType><![CDATA[image]]></MsgType>
<Image>
<MediaId><![CDATA[gyci5oxxxxxxv3cOL]]></MediaId>
</Image>
</xml>
关键代码:
// 图片消息
if(StringUtils.equals("image",msgType)) {
wxMpXmlOutMessage = WxMpXmlOutMessage.IMAGE()
.mediaId(mediaId)
.fromUser(message.getToUser())
.toUser(message.getFromUser())
.build();
}
效果如下:
{
"allFieldsMap": {
"MediaId": "YLMZPCB2M6Ba4IH8Hzikn_test",
"CreateTime": "1722879118",
"ToUserName": "gh_test",
"FromUserName": "opZ4h1Xs40_test",
"MsgType": "image",
"PicUrl": "http://mmbiz.qpic.cn/sz_mmbiz_jpg/test",
"MsgId": "24665697128505882"
},
"articleUrlResult": {},
"createTime": 1722879118,
"fromUser": "opZ4h1Xs40_test",
"hardWare": {},
"mediaId": "YLMZPCB2M6Ba4IH8Hzikn_test",
"msgId": 24665697128505882,
"msgType": "image",
"picUrl": "http://mmbiz.qpic.cn/sz_mmbiz_jpg/test",
"scanCodeInfo": {},
"sendLocationInfo": {},
"sendPicsInfo": {
"picList": []
},
"toUser": "gh_test"
}
收到消息回复: 根据消息类型不同,定义不同的处理逻辑,返回图片必须来源于素材库
mediaId
!
关注/取消关注事件
实际上,当新用户关注了公众号也会收到消息,取消关注也是同样的道理!
遗憾的是,取消关注无法发送消息给用户,最后的留言不带走一丝遗憾.
更多参考
文本消息
图片消息
语音消息
视频消息
小视频消息
地理位置消息
链接消息
关注/取消关注事件
扫描带参数二维码事件
上报地理位置事件
自定义菜单事件
回复文本消息
回复图片消息
回复语音消息
回复视频消息
回复音乐消息
回复图文消息
自定义菜单
推荐参考 自定义菜单
可选参考 个性化菜单接口
根据官方文档指引,创建自定义菜单是配置模式的,菜单有多种类型可供选择.
其中click
和view
的请求示例:
{
"button": [
{
"type": "click",
"name": "今日歌曲",
"key": "V1001_TODAY_MUSIC"
},
{
"name": "菜单",
"sub_button": [
{
"type": "view",
"name": "搜索",
"url": "http://www.soso.com/"
},
{
"type": "miniprogram",
"name": "wxa",
"url": "http://mp.weixin.qq.com",
"appid": "wx286b93c14bbf93aa",
"pagepath": "pages/lunar/index"
},
{
"type": "click",
"name": "赞一下我们",
"key": "V1001_GOOD"
}
]
}
]
}
关键代码:
@Test
public void testCreateMenu() throws WxErrorException {
String menuJson = "{\"button\":[{\"type\":\"click\",\"name\":\"今日歌曲\",\"key\":\"V1001_TODAY_MUSIC\"},{\"name\":\"菜单\",\"sub_button\":[{\"type\":\"view\",\"name\":\"搜索\",\"url\":\"http://www.soso.com/\"},{\"type\":\"miniprogram\",\"name\":\"wxa\",\"url\":\"http://mp.weixin.qq.com\",\"appid\":\"wx286b93c14bbf93aa\",\"pagepath\":\"pages/lunar/index\"},{\"type\":\"click\",\"name\":\"赞一下我们\",\"key\":\"V1001_GOOD\"}]}]}";
wxMpService.getMenuService().menuCreate(menuJson);
}
然而,看到了这条错误消息,懵逼了!
【错误信息】:错误代码:48001, 错误信息:api 功能未授权,请确认公众号已获得该接口,可以在公众平台官网 - 开发者中心页中查看接口权限,微信原始报文:{"errcode":48001,"errmsg":"api unauthorized rid: 66b225ed-4bdd1a42-115feb8d"}
按照提示,再次确认了自定义菜单的权限竟然没有获得?!
个人订阅号还要微信认证才能获取,然而个人订阅号根本就无法微信认证!
很完美的闭环了,搞来搞去还搞丢了自定义菜单,只能实现自动回复.
由于没有自定义菜单的权限,Game Over
到此结束,等待后续更新...
1.4. 回到原点
你要允许遗憾发生,允许一切意想不到的事情突然打破所有的努力!
暂时决定先恢复原样,在获取到自定义菜单前不轻易放弃,毕竟更加看重 的是取关用户!
先停用服务器配置,然后再重新启用自动回复和自定义菜单.
1.4.1. 停用服务器配置
1.4.2. 启用自动回复
庆幸的是,原先后台设置的自动回复规则并没有被删除,重新启用即可!
1.4.3. 启用自定义菜单
开启自定义菜单,也是会恢复原来设置过的自定义菜单.
顺利一切恢复原样,仿佛什么都没有发生过,只有服务器配置是否启用还证明曾经来过!
1.5. 总结
公众号开发需要慎重考虑,普通个人订阅号后台功能已经足够使用了,自动回复和自定义菜单看起来不起眼的功能,没想到真正开发起来还涉及到账号权限问题.
从入门微信公众号开发到放弃
我想你或多或少也会经历这些步骤:
考虑是否需要开发公众号以及准备好开发必备的公网环境;
熟悉公众号开发入门指引文档,有哪些接口能力以及对应的限制等;
接收普通消息和消息推送事件;
回复消息,不同类型账号有不同的权限;
具体的坑只有自己踩过才知道,毕竟不同的项目环境不同的配置.
关于开发微信公众号要知道的几件事
- 第一步的配置服务器 URL 和后续接收消息的接口 URL 是相同路径
不同的是,验证消息是否来自微信服务器的返回值是文本,被动回复公众号消息是xml.
- 自动回复消息和自定义菜单会全部失效,需要利用接口能力重新配置.
但是
自定义菜单
需要微信认证才能有权调用相关接口,否则你将收获如下错误:
【错误信息】:错误代码:48001, 错误信息:api 功能未授权,请确认公众号已获得该接口,可以在公众平台官网 - 开发者中心页中查看接口权限,微信原始报文:{"errcode":48001,"errmsg":"api unauthorized rid: 66b225ed-4bdd1a42-115feb8d"}
- 接收到消息类型以及消息事件大概分为以下几类:
文本消息
图片消息
语音消息
视频消息
小视频消息
地理位置消息
链接消息
关注/取消关注事件
扫描带参数二维码事件
上报地理位置事件
自定义菜单事件
- 接收到消息大多数类型是可以回复的,但要在
5
秒内迅速响应,否则微信会重发三次,直到出现公众号服务故障,不可用
回复文本消息
回复图片消息
回复语音消息
回复视频消息
回复音乐消息
回复图文消息
- 个人不能微信认证,但是可以个人兴趣认证
申请个人兴趣认证有哪些特殊权限?是免费还是付费?能否解锁自定义菜单权限?
尚未可知,已申请,待后续更新...
嘿,小伙伴们,你们知道吗?这字儿可不是白码的,每一颗键帽下的汗水都能浇出一朵花来!所以,要是看得开心,就请大方地撒点阳光——评论点赞转发走一波,让我这花园更加灿烂!
1.6. 往期精彩文章
- 解锁Nginx日志的宝藏:GoAccess——你的实时、交互式Web日志分析神器!
- GoAccess中配置geoip支持ip地理位置
- GoAccess中配置websocket支持实时日志
- 谁家爬虫这么明目张胆?连UserAgent都不要了!
- GoAccess自定义每日独立访客面板时间格式:利用html-custom-js实现日期显示调整
- GoAccess实战秘籍:从新手到高手,跨越那些“坑”与“惑”!
1.7. 欢迎扫码关注
欢迎扫码关注,私信回复『加群』一起交流技术
作者: 雪之梦技术驿站
来源: 雪之梦技术驿站
本文原创发布于「雪之梦技术驿站」,转载请注明出处,谢谢合作!