微信公众号开发 php微信公众号开发,开发什么?备忘。。php微信公众号开发教程
2022-07-26
这太令人沮丧和难以理解。也太坑了。
以下是这几天微信公众号相关工作的总结。不全面,只是作为初学者的记录,仅供参考。
一、微信公众号开发,开发什么?
公众号不同于小程序。小程序类似于手机APP,是独立开发的。微信只提供入口;而公众号基本在微信框架内。微信公众号本质上是用户的一个联系人,但只是一个特殊的联系人。通过微信提供的公众号管理后台,无需任何编程,即可快速搭建像样的公众号,菜单、机器人客服、文章更新一应俱全。
但是小程序开发,如果你想要更多的权力,你需要开发它。比如机器人客服。通过公众号管理后台网站模板,可以定义一些自动回复语句,但毕竟还不够智能。这时候,我们可以在互联网上搭建一个服务器,提供相应的服务。当然,这需要准备好URL和域名。
第二,菜单。点击公众号的菜单项后,可以回复一些消息,跳转到小程序,或者打开网页。如果打开网页,如果是未经验证的公众号,则只能打开公众号内的素材,或当前公众号已发表的文章、图片、视频等;和经过验证的公众号,可以直接打开任意网址。这些网页通常是部署在互联网上的所谓微信网页。他们使用微信JS-SDK,上面有各种微信元素,比如扫描、分享到朋友圈等等。当然,这部分需要开发。
并向关注者发送消息。我认为这是微信公众号最大的卖点。比如我关注了某个公众号,通过这个公众号的菜单打开相关小程序做事,当事情进展的时候,系统可以通过这个公众号给我发消息提醒我目前的工作进展。我认为这是公众号开发中最有价值的工作。
当然,也有可以在公众号上自动发布文章的程序。不过这种事情也可以在公众号管理后台手动完成,无非就是动手。
二、发展铺垫
在开发之前,有必要了解相关规范。建议同时阅读开发文档的开头:微信公众平台开发概述
1、公众号分类
衣着三色,食分五品。所谓微信公众号分为订阅号和服务号。个人只能申请订阅号,企业可以申请订阅号和服务号。然后公众号分为认证和未认证。公众号的类型,是否经过认证,决定了是否可以调用很多微信服务。没有认证,基本没什么玩的。而且很遗憾,个人申请的订阅号根本无法通过微信认证,直接挡住了门。
如何开发它?微信还非常“贴心”地提供了测试号机制。不用申请公众号,我们可以先申请一个测试号,用这个号来测试微信服务接口。测试号所有微信服务接口均可访问。当然是鹅!和微信网页一样,需要在手机上运行才能看到效果,如果使用测试号,有些东西是无法渲染的。比如所谓的微信开放标签(即微信定义的标签,类似于HTML)。
订阅帐号和服务帐号的侧重点不同。据我了解,订阅号侧重于发布文章,而服务号侧重于发送有针对性的通知。一般来说,服务帐户比订阅帐户更强大。
从表面上看,订阅号每天可以发送 1 条消息微信公众号开发 php,而服务号每月只能发送 4 条消息,订阅号更强。问题是,群发消息有什么用?当我们在网上做事时,我们想要的是对我来说是新闻。只有服务帐号可以发送此有针对性的通知消息。
在文档中,这种类型的消息称为模板消息。为什么叫模板消息?这是因为这种消息是结合模板生成的。就像我们的手机短信一样。做过手机短信开发的都知道,手机短信是不能随便发的。因为众所周知的原因,肯定有所谓的模板,就是怕内容不合法,内容离谱,也就是短信的格式是固定的,而且很多字也是固定的,我们只需要填写每次我们发送它时都会包含一些内容。此模板必须事先创建并获得电信运营商的批准。微信消息也使用模板。调用发送接口时,需要将模板ID作为参数传递。
目前小程序的模板消息功能已经废弃,取而代之的是所谓的“统一服务消息”,实际上是通过服务号发送的。也就是说,小程序要给用户发送通知,就必须对应一个服务号。
但世界上还有一种叫做订阅消息的东西。公众号叫订阅通知,小程序叫订阅消息。有两种类型:一次性和长期。订阅消息需要用户主动订阅。例如,使用麦当劳小程序点餐时,每次付款后,都会询问您是否接受取餐通知。长期只对部分民生和医院公众账号开放。这是在申请公共帐户时给出的。不要冒险微信公众号开发 php,低估微信折腾人的能力。否则发送时对方永远收不到,也可能没有错误信息;
模板消息和订阅消息有什么区别?订阅新闻不仅仅是要求用户手动订阅,也没什么。问题是,这必须用手机来完成。如果我通过 PC 做事并想在手机上接收提醒怎么办?订阅新闻已完成。
2、公众号调试工具
是微信开发者工具。请注意,它是开发人员工具,而不是开发工具。这个工具确实是小程序的开发工具;对于公众号,它只是一个调试工具,无法通过它输入任何代码;它只是微信网页的调试工具。此时它只是一个微信浏览器。方法是在微信开发者工具顶部输入微信网页地址进行浏览调试,类似普通浏览器按F12。
3、查看微信网页运行结果
公众号可以通过微信客户端查看。这里所说的查看结果是指查看微信网页的运行结果。虽然有“网页”二字,但这不是一个普通的网页。可以用普通浏览器访问,虽然没有报错,但是看不到效果。它应该通过微信浏览器或微信开发者工具运行。需要注意的是,手机上没有微信浏览器这个app,是微信暗示的。如何召唤它?你可以把微信网页的地址发给微信上的朋友,比如“文件传输助手”,然后在聊天记录里点击这个网址,就会用微信浏览器打开。绝对给力的是微信浏览器,QQ浏览器不好用。
4、开发文档
基于微信进行二次开发,上网查资料基本没用。最好老老实实阅读微信官方开发文档。
在公众号管理后台-设置与开发-开发者工具-开发文档中打开公众号开发文档。
微信有两个平台,小程序叫“微信开发开放平台”,公众号叫“微信开发公共平台”。
5、一些术语
1)管理员和操作员
在开发过程中,不可避免地会访问微信公众号管理后台修改或设置一些设置,但要更改设置,必须扫描二维码进行身份认证。这很尴尬。申请公众号的人是管理员,但不一定参与开发。提醒大老板扫描二维码会很不方便,甚至是不可能的。您可以将开发人员添加到操作员列表并自行扫描代码。运营商分为长期和短期两种。他们应该是具有足够权限的长期运营商。
可以在微信公众号管理后台-设置与开发-人事设置中设置。
2)IP 白名单
在开发过程中,我们需要访问微信服务器,比如采集。发出请求的 IP 需要在白名单中。这个IP是指互联网IP。如果我们在腾云网内部开发,那么这个IP就是腾云网的IP,用于上网。问题是,这个IP经常变化。不知道有什么好办法,所以基本上一天换一次白名单。
IP白名单是供我们在本地调试微信开发者工具的。无需手机操作。
3)开发者微信
官方账号管理后台-设置与开发-web开发者工具,添加我们开发者的微信账号。这是用来开发微信网页的。因为微信开发者工具需要微信登录。
4)JS接口安全域名
公众号管理后台-设置与开发-公众号设置-功能设置。
微信网页开发也需要它。我们的页面需要放在这个域名下,才能使用微信的js-sdk。
填写这个域名时,需要下载一个txt文件放在域名下,微信可以验证域名的真实性后再保存。但是填完之后,我们在本地开发的时候,可以修改host文件,把本地ip映射到域名。毕竟是前端的东西。 js-sdk本身需要微信浏览器的支持,它无法判断请求来自哪个IP,我们传给它什么都会相信。
5)微信打开标签
类似于HTML,微信独有的标签。比如
跳转小程序:<wx-open-launch-weapp>
跳转App:<wx-open-launch-app>
服务号订阅通知:<wx-open-subscribe>
音频播放:<wx-open-audio>
三、微信网页开发
1、概览
微信网页其实就是网页,只不过它引用了微信提供的JS库,可能会使用微信独有的所谓开放标签,类似于html。而这个微信网页似乎是在一个普通的浏览器上运行的。虽然不会报错,但似乎没有任何作用。只能在微信浏览器或微信开发者工具上运行。
以下是微信网页(boot下,合并)
DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<title>wechattitle>
<script type="text/javascript" src="http://res.wx.qq.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)访问微信服务器获取
2)访问微信服务器
3)使用、随机字符串、时间戳、当前页面地址依次组成一个字符串,然后对该字符串进行sha hash运算得到摘要
4)使用摘要访问微信服务器获取签名
5)注册、时间戳、随机字符串、签名、本页面使用的微信功能、微信打开标签到微信
在上面的例子中,
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对象并不容易。可以在微信公众号管理后台获取,每个公众号都有唯一的;时间戳也很容易获得;随机字符串由自己决定,比较容易;最麻烦的是这个签名,我调试了一天左右,总的意思是非法签名。
获取这个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.qq.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.qq.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 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
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。除此之外,到处都是“失败,就是”之类的提示。
开启微信开发者工具的调试模式:在微信开发者工具中,顶部菜单“微信开发者工具”-调试-“调试微信开发者工具”-,看这个的返回信息?_r=... , 结果是 { : , : " "}!
签名算法有问题吗?在微信公众号管理后台,我使用了他们提供的微信JS接口签名验证工具,结果完全一致,所以不存在算法错误的问题。
4、填坑
后来发现是网址有问题。在生成签名的过程中,需要传递当前微信网页的URL参与构造字符串,然后对字符串进行哈希处理,传递给微信服务器获取签名。问题是,这个URL应该怎么写?由于我的微信页面名为.html,所以我在访问的时候一般在浏览器上输入地址是这样的: 所以我也用这个地址参与了签名的构建,结果总是提示签名是无效的。后来把地址全写了:,一下子就OK了。
但是官方开发文档里写的是什么呢?
不认真复习问题会害死人。