公众号有两个access_token。
一个是基础支持中的access_token
通过下面的接口获取,这个接口有调用次数限制
https请求方式: GET
https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=APPID&secret=APPSECRET
另一个是网页授权access_token
这个就比较麻烦了,这个接口调用没有次数限制
第一步:用户同意授权,获取code
https://open.weixin.qq.com/connect/oauth2/authorize?appid=APPID&redirect_uri=REDIRECT_URI&response_type=code&scope=SCOPE&state=STATE#wechat_redirect
第二步:通过code换取网页授权access_token
获取code后,请求以下链接获取access_token: https://api.weixin.qq.com/sns/oauth2/access_token?appid=APPID&secret=SECRET&code=CODE&grant_type=authorization_code
我主要用想用一下微信的账号体系,通过公众号里面的菜单链接到自己的网站,当用户进入我的网站后我就能得到这个用户的openid,当用户提交了信息后,下一次打开我的网站就可以通过openid查询到他提交的信息了,因为openid对于这个公众号来讲是不会变的。同一公众号的同一用户openid是不会变的,不同公众号的同一用户openid是不一样的。所以如果你有几个公众号同一个用户的openid不同,你就很难统计数据,这个时候你就要把公众号绑定到微信开放平台帐号下后,就可以获取UnionID,这个UnionID同一个用户在不同公众号中也是相同的。
官方文档只帖出部分代码,对于我这种老菜鸟来说犹如天书。还有就是微信平台太多,一会儿开放平台一会儿公众平台,他们的文档也让人不知道用哪个好。
其实遇到的问题很多,主要是openid的获取。
现在我主要记录一下调用微信扫一扫的问题:
扫一扫属于微信JSSDK问题,解决了这个问题,对微信JSSDK就比较熟悉了,使用其他JS接口就轻车熟路了
一切都要感谢这位大哥的代码https://www.cnblogs.com/leaf-cq/p/8877270.html
使用微信JSSDK上传图片的思路是这样的,获取微信JSSDK的图片选择权限chooseImage
,选择图片后我们可以获得该图片的本地IDlocalIds
它是一个数组,使用微信JSSDK的图片上传接口uploadImage
,传入图片的本地ID localId
,图片会被上传到微信的服务器上,并返回图片在服务器上的IDserverId
,我们使用隐藏域<input type="hidden">
来保存serverId
,我们可以通过JS来操作serverId
数组,达到删除图片,重新选择图片操作,使用这种方式的好处就是,不管用户上传了多少照片,只要他没点提交,图片就不会真正的保存到我们自己的服务器上,把serverId
提交到我们自己的服务器,再通过微信提供的多媒体下载接口将图片下载到我们自己的服务器。
说明:图片在微信服务器上只会保存三天,所以最好在后台存一下serverIds,如果没有下载成功,在三天内再次下载
我看有的教程说上传图片uploadImage
不能用循环,事实证明可以用循环上传的方式。
实例:
前端代码
这是一个基础版的实例,我们可以在这个基础上增加删除图片功能,其实就是删除serverIds
数组里面的元素,因为后台代码是根据serverIds
的值下载图片的,所以很简单也很方便。
<?php
$appid = "你的appid";
$secret = "你的secret";
$ac = getJson("https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=$appid&secret=$secret");
$tk=getJson("https://api.weixin.qq.com/cgi-bin/ticket/getticket?access_token=".$ac['access_token']."&type=jsapi");
$jsapiTicket =$tk['ticket'];
$protocol = (!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off' || $_SERVER['SERVER_PORT'] == 443) ? "https://" : "http://";
$url = "$protocol$_SERVER[HTTP_HOST]$_SERVER[REQUEST_URI]";
$timestamp = time();
$noncestr=createNonceStr();
$str="jsapi_ticket=$jsapiTicket&noncestr=$noncestr×tamp=$timestamp&url=$url";
$sign = sha1($str);
/******************************************/
function getJson($url){
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, FALSE);
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, FALSE);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
$output = curl_exec($ch);
curl_close($ch);
return json_decode($output, true);
}
function createNonceStr($length = 16) {
$chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
$str = "";
for ($i = 0; $i < $length; $i++) {
$str .= substr($chars, mt_rand(0, strlen($chars) - 1), 1);
}
return $str;
}
?>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=Edge,chrome=1">
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0"/>
<title>多图片上传</title>
</head>
<body>
<form action="upload.php" method="post" enctype="multipart/form-data">
<h1 id="wxtp" style="text-align: center;">选择图片</h1>
<input type="hidden" name="imgservers" id="imgservers">
<input type="submit" value="提交">
<div id="dlwz"></div>
</form>
</body>
<script src="https://apps.bdimg.com/libs/jquery/2.1.4/jquery.min.js"></script>
<script src='http://res.wx.qq.com/open/js/jweixin-1.4.0.js'></script>
<script>
wx.config({
// 开启调试模式,调用的所有api的返回值会在客户端alert出来,若要查看传入的参数,可以在pc端打开,参数信息会通过log打出,仅在pc端时才会打印。
debug: false,
// 必填,公众号的唯一标识
appId: "<?php echo $appid; ?>",
// 必填,生成签名的时间戳
timestamp:"<?php echo $timestamp; ?>",
// 必填,生成签名的随机串
nonceStr:"<?php echo $noncestr; ?>",
// 必填,签名,见附录1
signature:"<?php echo $sign; ?>",
// 必填,需要使用的JS接口列表,所有JS接口列表见附录2
jsApiList : [ 'chooseImage','uploadImage']
});
wx.error(function(res) {
// alert("----------出错了-----------:" + res.errMsg);//这个地方的好处就是wx.config配置错误,会弹出窗口哪里错误,然后根据微信文档查询即可。
});
wx.ready(function() {
wx.checkJsApi({
jsApiList : ['chooseImage','uploadImage'],
success : function(res) {
}
});
//点击按钮选择图片
$('#wxtp').click(function(){
wx.chooseImage({
count: 4, // 默认9
sizeType: ['compressed'], // 可以指定是原图还是压缩图,默认二者都有
sourceType: ['album', 'camera'], // 可以指定来源是相册还是相机,默认二者都有
success: function (res) {
var localIds = res.localIds; // 返回选定照片的本地ID列表,localId可以作为img标签的src属性显示图片
var serverIds=[];
for(x in localIds){
wx.uploadImage({
localId: localIds[x], // 需要上传的图片的本地ID,由chooseImage接口获得
isShowProgressTips: 1, // 默认为1,显示进度提示
success: function (res) {
var serverId = res.serverId; // 返回图片的服务器端ID
serverIds.push(serverId);
$("#imgservers").val(serverIds);
$("#dlwz").append("[外链图片转存失败(img-5Kcib8Zj-1562048482341)(https://mp.csdn.net/mdeditor/+%20this.localId%20+)]");
}, fail: function (res) {
alert('上传图片失败,请重试')
}
});
}
}
});
});
});
</script>
</html>
实例:
后端代码
因为我使用的是新浪云的Storage存储,所以
<?php
use sinacloud\sae\Storage as Storage;
$s = new Storage();
require_once('../class/myFunction.php');
$appid = "你的appid";
$secret = "你的secret";
$ac = getJson("https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=$appid&secret=$secret");
foreach(explode(",",$_POST['imgservers']) as $val){
$urll="http://file.api.weixin.qq.com/cgi-bin/media/get?access_token=".$ac['access_token']."&media_id=".$val;
$s->putObject(file_get_contents($urll), "test", "wx/".$val.'.jpg', array(), array('Content-Type' => 'image/jpeg'));
}
?>
上面的例子里,使用JSSDK前后端代码混在一起,如果我们采用前后端分离的方式,应该怎样编写代码呢?
首先前端通过AJAX发送请求,获取需要的那几个参数就行,这里只需要注意一个问题,那就是异步请求的执行顺序,我们应该怎么组织代码。
<!DOCTYPE html>
<html lang="zh-cmn-Hans">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width,initial-scale=1,user-scalable=0,viewport-fit=cover">
<title>录音</title>
<link rel="stylesheet" href="http://hellsing-test.stor.sinaapp.com/wx/css/weui.min.css"/>
<link rel="stylesheet" href="http://hellsing-test.stor.sinaapp.com/wx/css/example.css"/>
<script src="https://apps.bdimg.com/libs/jquery/2.1.4/jquery.min.js"></script>
<script src='http://res.wx.qq.com/open/js/jweixin-1.6.0.js'></script>
</head>
<body ontouchstart>
<div class="bottom_menu">
<div class="weui-tab">
<div class="weui-tab__panel">
<div class="button-sp-area" style="margin-top:30px">
<a id="wxcode" href="#" class="weui-btn weui-btn_primary" >开始录音</a>
<a id="jscode" href="#" class="weui-btn weui-btn_primary" >结束录音</a>
<a id="bfcode" href="#" class="weui-btn weui-btn_primary" >播放录音</a>
<a id="sccode" href="#" class="weui-btn weui-btn_primary" >上传录音</a>
<input type="text" id="sidd" />
</div>
</div>
</div>
</div>
<script>
$.ajax({
url:'jsjkyb.php',
success:function(res){
let fh=JSON.parse(res)
wx.config({
// 开启调试模式,调用的所有api的返回值会在客户端alert出来,若要查看传入的参数,可以在pc端打开,参数信息会通过log打出,仅在pc端时才会打印。
debug: false,
// 必填,公众号的唯一标识
appId: fh.appid,
// 必填,生成签名的时间戳
timestamp:fh.timestamp,
// 必填,生成签名的随机串
nonceStr:fh.nonceStr,
// 必填,签名,见附录1
signature:fh.signature,
// 必填,需要使用的JS接口列表,所有JS接口列表见附录2
jsApiList : [ 'startRecord' ,'stopRecord','playVoice','uploadVoice']
});
}
})
wx.error(function(res) {
alert("----------出错了-----------:" + res.errMsg);//这个地方的好处就是wx.config配置错误,会弹出窗口哪里错误,然后根据微信文档查询即可。
});
wx.ready(function() {
wx.checkJsApi({
jsApiList : [ 'startRecord' ,'stopRecord','playVoice','uploadVoice'],
success : function(res) {
}
});
//开始录音
$('#wxcode').click(function(){
wx.startRecord();
})
var localId1;
var serverId1;
//结束录音
$('#jscode').click(function(){
wx.stopRecord({
success: function (res) {
localId1 = res.localId;
}
});
})
//播放录音
$('#bfcode').click(function(){
wx.playVoice({
localId: localId1 // 需要播放的音频的本地ID,由stopRecord接口获得
});
})
//上传录音
$('#sccode').click(function(){
wx.uploadVoice({
localId: localId1, // 需要上传的音频的本地ID,由stopRecord接口获得
isShowProgressTips: 1, // 默认为1,显示进度提示
success: function (res) {
serverId = res.serverId; // 返回音频的服务器端ID
$('#sidd').val(serverId);
}
});
})
});
</script>
</body>
</html>
后端
<?php
function getJson($url){
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, FALSE);
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, FALSE);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
$output = curl_exec($ch);
curl_close($ch);
return json_decode($output, true);
}
function createNonceStr($length = 16) {
$chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
$str = "";
for ($i = 0; $i < $length; $i++) {
$str .= substr($chars, mt_rand(0, strlen($chars) - 1), 1);
}
return $str;
}
$appid='wxx7';
$secret='012x3';
$timestamp = time();
$ac = getJson("https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=$appid&secret=$secret");
$tk=getJson("https://api.weixin.qq.com/cgi-bin/ticket/getticket?access_token=".$ac['access_token']."&type=jsapi");
$jsapiTicket =$tk['ticket'];
$url=$_SERVER['HTTP_REFERER'];
$noncestr=createNonceStr();
$str="jsapi_ticket=$jsapiTicket&noncestr=$noncestr×tamp=$timestamp&url=$url";
$sign = sha1($str);
$arr['appid']=$appid;
$arr['timestamp']=$timestamp;
$arr['nonceStr']=$noncestr;
$arr['signature']=$sign;
echo json_encode($arr);
是这样的以前前后端在一起的时候这些都不是事,现在这个前后端分离的时代我们必须解决这个问题
前端
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<script src="https://cdn.bootcdn.net/ajax/libs/axios/0.19.2/axios.min.js"></script>
</head>
<body>
<h3>哈哈哈哈</h3>
<script>
function getUrlParam (name) {//参数获取函数
var reg = new RegExp('(^|&)' + name + '=([^&]*)(&|$)')
let url = window.location.href.split('#')[0]
let search = url.split('?')[1]
if (search) {
var r = search.substr(0).match(reg)
if (r !== null) return unescape(r[2])
return null
} else {
return null
}
}
function wxAuthorize() {
let link = window.location.href;
let params =getUrlParam('code'); //code获取
console.log(params);
if (params) {
axios.get('f2.php', {
params: {
code: params
}
})
.then(function (response) {
console.log(response);
})
.catch(function (error) {
console.log(error);
})
.finally(function () {
// always executed
});
}else {
let appid = 'wxa0f7';
let uri = encodeURIComponent(link);
let authURL = `https://open.weixin.qq.com/connect/oauth2/authorize?appid=${appid}&redirect_uri=${uri}&response_type=code&scope=snsapi_base&state=123#wechat_redirect`;
window.location.href = authURL;
}
}
wxAuthorize();
</script>
</body>
</html>
后端
<?php
function getJson($url){
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, FALSE);
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, FALSE);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
$output = curl_exec($ch);
curl_close($ch);
return json_decode($output, true);
}
$code = $_GET["code"];
$appid='wxa05d7';
$secret='012c0cgg1aa23';
$oauth2Url = "https://api.weixin.qq.com/sns/oauth2/access_token?appid=$appid&secret=$secret&code=$code&grant_type=authorization_code";
$oauth2 = getJson($oauth2Url);
echo $openid=$oauth2['openid'];
前面所谓的异步可能还是没有逃脱传统的方式,下面是我使用vue开发公众号所遇到的问题
微信js文件的引用
引用方式有两种,
一是通过npm安装npm install weixin-js-sdk
然后引用import wx from 'weixin-js-sdk'
二是在public/index文件中引用<script src="http://res2.wx.qq.com/open/js/jweixin-1.6.0.js"></script>
我个人推荐第二种方式,简单而且可以及时跟上官方的最新版,第一的文件好久没有更新了
签名验证问题
使用vue遇到签名验证问题错误码:63002,invalid signature,这里需要注意三点
一是签名的生成需要使用当前网址,而我们使用的是完全前后端分离的方式,所以当前网址需要传递给后端使用。
二是签名使用的网址是出去hash的部分也就是#号和#号之后的部分不需要,所以传递的网址需要这样处理location.href.split('#')[0]
。
三是注意前后端时候将网址编码,我就遇到了这个问题,vue的使用有很多坑当然不是他的错,我的vue路由模式使用的是hash模式,这种模式会把登陆的参数带上,于是我的网址中就是这样的http://12.6.6.13/?code=0112Z3ev1FVwle0xSndv1rpPdv12Z3e5&state=123#/jwxldk/1
,首先我要去掉hash部分,然后网址传到后台就把特殊字符转换为HTML字符了,这个把我坑惨了,网址里的**&被转化为&**,这样导致我签名的地址就不对了,这是PHP框架为了安全使用了htmlspecialchars
函数将特殊符号转化,所以我们需要使用htmlspecialchars_decode
将特殊的HTML 实体转换回普通字符