大家好,欢迎来到IT知识分享网。
目录
实在是太折腾,太难懂了。也太坑了。
下面是这几天来,有关微信公众号的工作总结。算不上全面,只是作为一个初学者的记录,仅以备忘。
一、微信公众号开发,开发什么?
但是,如果要更强的功能,就需要开发了。比如,机器人客服。通过公众号管理后台,可以定义一些自动回复语句,但毕竟不够智能,这时我们可以在互联网上搭建服务器,提供相应服务。当然,这需要准备好网址、域名。
第二个,菜单。公众号的菜单项点击后,可以是回复一些消息,或跳转到小程序,或打开一张网页。打开网页的话,如果是未经认证的公众号,只能打开公众号里的素材,或已经发表在当前公众号里的文章或图片、视频之类;而经过认证的公众号,则可以直接打开任意网址。这些网页,通常都会是部署在互联网上的所谓微信网页,它们使用了微信JS-SDK,上面有各种微信的元素,比如扫一扫啦,分享到朋友圈啦,诸如此类。那这部分,当然需要开发。
还有就是给关注者发送消息。我认为这是微信公众号最大的卖点。比如说,我关注了某个公众号,通过这个公众号的菜单,打开了相关小程序办事,事情有了进展,系统就可以通过这个公众号给我发消息,提示一下当前办事进度。我认为这是公众号开发中最值得做的工作。
当然,还有可以通过程序自动在公众号上发表文章。不过这种事情,人工在公众号管理后台也能做,无非动动手而已。
二、开发铺垫
1、公众号的分类
那怎么开发?微信又很“贴心”地提供了测试账号这个机制。我们可以先不申请公众号,而是先申请一个测试账号,用这个账号来测试微信服务接口。测试账号所有微信服务接口都能访问。然鹅!像微信网页这些,需要跑在手机上才能看到效果,而如果用的是测试账号的话,有些东西是渲染不出来的。比如所谓的微信开放标签(就是微信自己定义的,类似HTML的标签)。
订阅号与服务号侧重点不同。按我的理解,订阅号侧重发表文章,服务号则侧重针对性发通知消息。总的来说,服务号功能比订阅号要强大许多。
表面上看,订阅号每天可以群发1条消息,而服务号只能每个月发4条,订阅号要强。问题是,群发消息有啥用?我们网上办事,要的是针对本人的消息。只有服务号才能发送这针对性的通知消息。
文档里,管这种消息叫模板消息。为啥叫模板消息呢?是因为这种消息要结合模板生成。就像我们的手机短信。做过手机短信开发都知道,手机短信可不是随随便便就能发送的,因为众所周知的原因,怕有违法、出格的内容,要有所谓的模板,即短信格式是固定的,许多字眼也都是固定的,我们每次发送时只需往里面填一些内容。这个模板要事先创建好并通过电信运营商审批。微信这个消息也要使用模板,调用发送接口时,需要将模板ID作为参数传递。
目前,小程序的模板消息功能已经废弃了,改用所谓“统一服务消息”,其实就是改用服务号进行发送。就是说,小程序如果要给用户发送通知的话,必须要对应一个服务号。
不过世界上还有一种东西叫做订阅消息。公众号里叫做订阅通知,小程序里叫订阅消息。分为一次性和长期2种。订阅消息要用户主动订阅。比如用麦当劳小程序订餐,每次付款以后,它都会问你,要不要接受取餐通知。长期性只对某些民生、医院类的公众号开放。这个在申请公众号时就给定了性。不要心存侥幸,低估了微信折腾人的本事。否则,发送时对方总是收不到,错误提示可能没有;就算有,可能也专业得很,根本想不到是账号类型的问题。
模板消息和订阅消息有啥区别呢?订阅消息不就是要用户手动订阅一下嘛,也没啥。问题是,这就一定要用手机来操作啊。假如我是通过PC端来办事,想手机收到提醒呢?订阅消息就完犊子了。(是不是这样啊?微信PC端也能进行订阅操作?)
2、公众号调试工具
3、查看微信网页运行结果
4、开发文档
公众号开发文档在 公众号管理后台 – 设置与开发 – 开发者工具 – 开发文档 打开。
微信有两个平台,小程序的叫“微信开发开放平台”,公众号的叫“微信开发公众平台”。
5、一些术语
1)管理员及运营者
开发过程中,免不了要访问微信公众号管理后台,对某些设置作出修改或设置,但是改变设置必须扫二维码进行身份认证。这就尴尬了。申请公众号的人是管理员,但他不一定参与开发。如果下下都惊动这个大老板来扫二维码不方便,甚至不可能。可以把开发人员加到运营者名单里,自己扫码就行。运营者分长期和短期2种,应该是长期运营者,权限才够大。
可在 微信公众号管理后台 – 设置与开发 – 人员设置 里设置。
IP白名单是给我们在本地调试微信开发者工具用的。手机运行不需要。
也是微信网页开发需要。我们的页面,需要放在这个域名里,微信的js-sdk才可以使用。
填写这个域名的时候,需要下载一个txt文件放到域名下,微信验证域名真伪才能保存。不过填好以后,我们在本地开发时,可以修改host文件,将本机ip映射为该域名。毕竟是前端的一个东西,js-sdk本身需要微信浏览器支持,它也判断不出来请求来自于哪个IP,我们传给它什么它都信。
跳转小程序:<wx-open-launch-weapp> 跳转App:<wx-open-launch-app> 服务号订阅通知:<wx-open-subscribe> 音频播放:<wx-open-audio>
三、微信网页开发
1、概述
以下是一张微信网页(spring boot下,结合了thymeleaf)
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<title>wechat</title>
<script type="text/javascript" src="http://res.wx..com/open/js/jweixin-1.6.0.js"></script>
<script src="./libs/jquery.min.js"></script>
<style>
.sao{
text-align: center;width: 100%;height:5.5em;background-color: #ddd;line-height: 5.5em;
cursor:pointer;
}
.block{
height: 100px;
border:solid 1px red;
}
</style>
</head>
<body>
<!-- 在手机微信上打开本页面,点这个扫一扫,真的会打开摄像头 -->
<div class="sao qr_btn">扫一扫</div>
<div class="block">
<!-- 微信开放标签。测试号不支持,只有认证服务号才可以 -->
<wx-open-subscribe th:template="${template}" id="subscribe-btn">
<script type="text/wxtag-template" slot="style">
<style>
.subscribe-btn {
color: #fff;
background-color: #07c160;
}
</style>
</script>
<script type="text/wxtag-template">
<button class="subscribe-btn">
模版消息订阅
</button>
</script>
</wx-open-subscribe>
</div>
</body>
<script th:inline="javascript">
//微信验证
wx.config({
debug: true,
appId: /*[[${wc.appId}]]*/'',
timestamp: /*[[${wc.timestamp}]]*/'',
nonceStr: /*[[${wc.nonceStr}]]*/'',//随机串
signature: /*[[${wc.signature}]]*/'',
jsApiList: ['chooseImage','scanQRCode','updateAppMessageShareData','updateTimelineShareData'],//需要使用的微信js-sdk函数列表
openTagList: ['wx-open-subscribe']//开放标签列表
});
wx.ready(function () {
// 微信分享 -- 分享给朋友
wx.updateAppMessageShareData({
title: '分享给您的猪朋狗友吧',
desc: '独食难肥',
link: location.href.split('#')[0],
imgUrl: "http://test.duduchuhai.com/images/share.png",
success: function(res) {
console.log(res);
}
});
// 微信分享 -- 分享到朋友圈
wx.updateTimelineShareData({
title: '分享到猪圈',
link: location.href.split('#')[0],
imgUrl: "http://test.duduchuhai.com/images/share.png",
success: function(res) {
console.log(res);
}
});
});
wx.error(function (res) {
console.log(res); // res为微信返回的错误结果
});
// 微信扫一扫
$(".qr_btn").on('click', function() {
wx.scanQRCode({
needResult: 0, // 默认为0,扫描结果由微信处理,1则直接返回扫描结果,
scanType: ["qrCode","barCode"], // 可以指定扫二维码还是一维码,默认二者都有
success: function (res) {
var result = res.resultStr; // 当needResult 为 1 时,扫码返回的结果
}
});
});
</script>
<script>
var btn = document.getElementById('subscribe-btn');
console.log(btn);
btn.addEventListener('success', function (e) {
console.log('success', e.detail);
});
btn.addEventListener('error',function (e) {
console.log('fail', e.detail);
});
</script>
</html>
2、验证
微信网页运行时,首先要经过微信验证,然后才能正常使用微信各种功能。验证流程是,
1)凭appId和appSecret访问微信服务器获得token
2)用token访问微信服务器获得ticket
3)依次用ticket、随机串、时间戳、当前页面地址组合成一个字符串,然后对字符串进行sha散列运算,得到一个摘要
4)用摘要访问微信服务器获得签名
5)将 appId、时间戳、随机串、签名、本页面需要使用的微信函数、微信开放标签注册到微信
上面例子中,
wx.config({ debug: true, appId: /*[[${wc.appId}]]*/'', timestamp: /*[[${wc.timestamp}]]*/'',//注意是秒,不是毫秒 nonceStr: /*[[${wc.nonceStr}]]*/'',//随机串 signature: /*[[${wc.signature}]]*/'',//关键所在 jsApiList: ['chooseImage','scanQRCode','updateAppMessageShareData','updateTimelineShareData'],//需要使用的微信js-sdk函数列表 openTagList: ['wx-open-subscribe']//开放标签列表 });
获取这个json对象的java代码:
import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.JSONObject; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import redis.clients.jedis.Jedis; import javax.annotation.PostConstruct; import java.io.*; import java.net.HttpURLConnection; import java.net.URL; import java.net.URLConnection; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.util.Date; import java.util.Random; import java.util.concurrent.locks.ReentrantLock; @Service public class WxServiceImpl implements WxService { @Override public WxConfig getWxConfig(String url) {//url是微信网页地址 WxConfig wc = new WxConfig();//这个是自定义的对象,不必深究 wc.setAppId(APPID); wc.setNonceStr(getNonceStr());//随机串 wc.setTimestamp((long) (new Date()).getTime() / 1000);//时间戳 wc.setSignature(getSignature(wc.getNonceStr(), wc.getTimestamp(), url));//签名 return wc; } @PostConstruct void init() { /* 由于从微信服务器获取token和ticket的函数有调用次数限制(每天<=2000),因此用redis将它们缓存起来 */ this.jedis = new Jedis(redis的IP, redis端口号); } private String getSignature(String nonceStr, long timestamp, String url) {//获取签名 String signature = null; String ticket = getTicket(); if (ticket != null) { String string1 = String.format("jsapi_ticket=%s&noncestr=%s×tamp=%d&url=%s", ticket, nonceStr, timestamp, url); signature = getSha1(string1); } return signature; } //redis对象 private Jedis jedis; //除了redis缓存,也用静态变量保存一份。不过,应用程序重启它们就消失了,并且不会自动过期 //而从微信获取到的token和ticket有效期是7200秒 private String _ticket = null; private String _token = null; //锁。为避免并发,使用锁机制,不要大家都去获取token和ticket private ReentrantLock lockTok = new ReentrantLock(); private ReentrantLock lockTik = new ReentrantLock(); private String getTicket() { String ticket = null; String key = "ticket"; ticket = getKey(key, this._ticket); if (ticket == null) { //锁定。 lockTik.lock(); ticket = getKey(key, this._ticket);//再努力一把 if (ticket == null) { String token = getToken(); if (token != null) { ticket = callGet(String.format("https://api.weixin..com/cgi-bin/ticket/getticket?access_token=%s&type=jsapi", token), "ticket"); if (ticket != null) { this._ticket = ticket; setKey(key, ticket); } } } //解锁 lockTik.unlock(); } return ticket; } private String getToken() { String token = null; String key = "token"; token = getKey(key, this._token); if (token == null) { lockTok.lock(); token = getKey(key, this._token); if (token == null) { token = callGet(String.format("https://api.weixin..com/cgi-bin/token?grant_type=client_credential&appid=%s&secret=%s",APPID,AppSecret), "access_token"); if (token != null) { this._token = token; setKey(key, token); } } lockTok.unlock(); } return token; } final static int EXPIRTED = 7200; private String getKey(String key, String v) { String value = null; try { value = jedis.get(key); } catch (Exception ex) { value = v;//如果无法从redis中读取则将候补变量值返回。但变量值可能有过期的问题 System.err.println(ex.getMessage()); } return value; } private void setKey(String key, String value) { try { jedis.set(key, value); jedis.expire(key, EXPIRTED); } catch (Exception ex) { System.err.println(ex.getMessage()); } } final static int NONCESTR = 16;//随机串的长度为16。这个数值是自己定的 private String getNonceStr() {// 生成随机字符串noncestr String chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0"; StringBuffer noncestr = new StringBuffer(); int limit = chars.length() - 1; for (int i = 0; i < NONCESTR; i++) { Random r = new Random(); int j = r.nextInt(limit); noncestr.append(chars.substring(j, j + 1)); } return noncestr.toString(); } private static String getSha1(String string1) { MessageDigest sha = null; // 此处的sha代表sha1 try { sha = MessageDigest.getInstance("SHA"); } catch (NoSuchAlgorithmException e) { e.printStackTrace(); } byte[] md5Bytes = new byte[0]; try { md5Bytes = sha.digest(string1.getBytes("UTF-8")); } catch (UnsupportedEncodingException e) { e.printStackTrace(); } StringBuffer hexValue = new StringBuffer(); for (int i = 0; i < md5Bytes.length; i++) { int val = ((int) md5Bytes[i]) & 0xff; if (val < 16) { hexValue.append("0"); } hexValue.append(Integer.toHexString(val)); } return hexValue.toString(); } public static String callGet(String api, String key) {//get的方式访问微信api String re = null; System.out.println(String.format("正在获取 %s : %s", key, api)); try { URL url = new URL(api); HttpURLConnection connection = (HttpURLConnection) url.openConnection(); connection.connect(); BufferedReader br = new BufferedReader( new InputStreamReader(connection.getInputStream(), "UTF-8")); String line; StringBuilder sb = new StringBuilder(); while ((line = br.readLine()) != null) { sb.append(line); } br.close(); connection.disconnect(); JSONObject json = JSON.parseObject(sb.toString()); if (json.containsKey(key)) { re = json.getString(key); } System.out.println(re); } catch (Exception ex) { ex.printStackTrace(); System.out.println(String.format("访问接口%s失败", api)); } return re; } }
3、调试
wx.config({ debug: true, appId: /*[[${wc.appId}]]*/'', timestamp: /*[[${wc.timestamp}]]*/'',//注意是秒,不是毫秒 nonceStr: /*[[${wc.nonceStr}]]*/'',//随机串 signature: /*[[${wc.signature}]]*/'',//关键所在 jsApiList: ['chooseImage','scanQRCode','updateAppMessageShareData','updateTimelineShareData'],//需要使用的微信js-sdk函数列表 openTagList: ['wx-open-subscribe']//开放标签列表 });
结果从返回的信息看,我有权访问的函数为0。除此之外,就是到处提示什么“fail, the permission value is offline verifying”之类。
打开微信开发者工具调试模式:微信开发者工具中,顶部菜单“微信开发者工具” – 调试 – “调试微信开发者工具” – network,看这个preverify?_r=…的返回信息,结果是 {errcode: 63002, errmsg: “invalid signature”}!
难道是签名算法有问题?我在微信公众号管理后台上,用它们提供的微信 JS 接口签名校验工具,结果完全一致,因此不存在算法有误的问题。
后来发现是URL的问题。生成签名过程中,需要传递当前微信网页的URL参与构造一个字符串,然后散列处理后,传给微信服务器获得签名。问题是,这个URL该怎么写?由于我这个微信页面,名为index.html,一般访问时,习惯在浏览器上这样输入地址:http://www.abc.com,因此我也就把这个地址参与构造签名,结果就是一直提示签名无效。后来将地址写全:http://www.abc.com/index,结果一下子OK了。
但官方开发文档上是怎么写的?
不认真审题害死人。
四、向公众号订阅者发送模板消息
import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.JSONObject; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import redis.clients.jedis.Jedis; import javax.annotation.PostConstruct; import java.io.*; import java.net.HttpURLConnection; import java.net.URL; import java.net.URLConnection; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.util.Date; import java.util.Random; import java.util.concurrent.locks.ReentrantLock; @Service public class WxServiceImpl implements WxService { @Override public String sendMess(String jsonStr) { String re = null; String token = getToken(); if(token != null) { String api = String.format("https://api.weixin..com/cgi-bin/message/template/send?access_token=%s",token); re = callPost(api, jsonStr); } return re; } @PostConstruct void init() { this.jedis = new Jedis(redis的IP, redis端口号); } private Jedis jedis; private String _token = null; private ReentrantLock lockTok = new ReentrantLock(); private String getToken() { String token = null; String key = "token"; token = getKey(key, this._token); if (token == null) { lockTok.lock(); token = getKey(key, this._token); if (token == null) { token = callGet(String.format("https://api.weixin..com/cgi-bin/token?grant_type=client_credential&appid=%s&secret=%s",AppId,AppSecret), "access_token"); if (token != null) { this._token = token; setKey(key, token); } } lockTok.unlock(); } return token; } final static int EXPIRTED = 7200; private String getKey(String key, String v) { String value = null; try { value = jedis.get(key); } catch (Exception ex) { value = v;//如果无法从redis中读取则将候补变量值返回。但变量值可能有过期的问题 System.err.println(ex.getMessage()); } return value; } private void setKey(String key, String value) { try { jedis.set(key, value); jedis.expire(key, EXPIRTED); } catch (Exception ex) { System.err.println(ex.getMessage()); } } private static String callPost(String api, String param) { PrintWriter out = null; BufferedReader in = null; String result = ""; try { URL realUrl = new URL(api); // 打开和URL之间的连接 URLConnection conn = realUrl.openConnection(); // 设置通用的请求属性 conn.setRequestProperty("accept", "*/*"); conn.setRequestProperty("Content-Type", "application/json"); conn.setRequestProperty("connection", "Keep-Alive"); conn.setRequestProperty("user-agent", "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1;SV1)"); // 发送POST请求必须设置如下两行 conn.setDoOutput(true); conn.setDoInput(true); // 获取URLConnection对象对应的输出流 out = new PrintWriter(conn.getOutputStream()); // 发送请求参数 out.print(param); // flush输出流的缓冲 out.flush(); // 定义BufferedReader输入流来读取URL的响应 in = new BufferedReader( new InputStreamReader(conn.getInputStream())); String line; while ((line = in.readLine()) != null) { result += line; } } catch (Exception e) { System.out.println("发送 POST 请求出现异常!" + e); e.printStackTrace(); } finally {//使用finally块来关闭输出流、输入流 try { if (out != null) { out.close(); } if (in != null) { in.close(); } } catch (IOException ex) { ex.printStackTrace(); } } return result; } public static String callGet(String api, String key) { String re = null; System.out.println(String.format("正在获取 %s : %s", key, api)); try { URL url = new URL(api); HttpURLConnection connection = (HttpURLConnection) url.openConnection(); connection.connect(); BufferedReader br = new BufferedReader( new InputStreamReader(connection.getInputStream(), "UTF-8")); String line; StringBuilder sb = new StringBuilder(); while ((line = br.readLine()) != null) { sb.append(line); } br.close(); connection.disconnect(); JSONObject json = JSON.parseObject(sb.toString()); if (json.containsKey(key)) { re = json.getString(key); } System.out.println(re); } catch (Exception ex) { ex.printStackTrace(); System.out.println(String.format("访问接口%s失败", api)); } return re; } }
测试
@Autowired WxService service; @Test public void testSendMess(){ String json = "{\n" + " \"touser\":OPENID,\n" + " \"template_id\":模板ID,\n" + " \"data\":{\n" + " \"name01\": {\n" + " \"value\":\"韩擒虎女士\"\n" + " },\n" + " \"thing01\":{\n" + " \"value\":\"您的大头佛到货了\"\n" + " },\n" + " \"date01\": {\n" + " \"value\":\"2022-04-15\"\n" + " }\n" + " }\n" + "}"; String re = service.sendMess(json); System.out.println(re); }
这个功能只有通过认证的服务号才能权限使用。但开发时候,可以通过申请一个测试账号来试验。需要在公众号管理后台创建一个模板。模板参考如下:
用户的OPENID也容易得到:
测试结果
五、小结
而且这种二次开发并没有什么意义。
原文:【精选】微信公众号开发入门-CSDN博客
免责声明:本站所有文章内容,图片,视频等均是来源于用户投稿和互联网及文摘转载整编而成,不代表本站观点,不承担相关法律责任。其著作权各归其原作者或其出版社所有。如发现本站有涉嫌抄袭侵权/违法违规的内容,侵犯到您的权益,请在线联系站长,一经查实,本站将立刻删除。 本文来自网络,若有侵权,请联系删除,如若转载,请注明出处:https://yundeesoft.com/152401.html