微信公众号开发相关问题

淳于熙云
2023-12-01

官方文档

关于access_token

公众号有两个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是不一样的。所以如果你有几个公众号同一个用户的openid不同,你就很难统计数据,这个时候你就要把公众号绑定到微信开放平台帐号下后,就可以获取UnionID,这个UnionID同一个用户在不同公众号中也是相同的。

遇到的问题

官方文档只帖出部分代码,对于我这种老菜鸟来说犹如天书。还有就是微信平台太多,一会儿开放平台一会儿公众平台,他们的文档也让人不知道用哪个好。
其实遇到的问题很多,主要是openid的获取。
现在我主要记录一下调用微信扫一扫的问题:
扫一扫属于微信JSSDK问题,解决了这个问题,对微信JSSDK就比较熟悉了,使用其他JS接口就轻车熟路了
一切都要感谢这位大哥的代码https://www.cnblogs.com/leaf-cq/p/8877270.html

使用微信JSSDK多图片上传

使用微信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&timestamp=$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

上面的例子里,使用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&timestamp=$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中使用JSSDK问题

前面所谓的异步可能还是没有逃脱传统的方式,下面是我使用vue开发公众号所遇到的问题

  1. 微信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>
    我个人推荐第二种方式,简单而且可以及时跟上官方的最新版,第一的文件好久没有更新了

  2. 签名验证问题
    使用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 实体转换回普通字符

 类似资料: