微信开发记录

准备工作

获取accesss_token和jsapi_ticket,这个是所有开发的必备,我们使用redis存储,过期时间为返回的过期时间

##sign.php
$redis = new Redis();
$redis->connect('127.0.0.1',6379);
$redis->auth('redispassword');
$redis->select(1);
$redis->setOption(Redis::OPT_PREFIX, 'WXAPI:');

$appid = "wxappid";
$secret = "wxsecret";

if(!$redis->exists('wx_global_ac_tk')){
  $get_token_url = 'https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid='.$appid.'&secret='.$secret;
  $json_obj = json_decode(httpsget($get_token_url),true);
  $access_token = $json_obj['access_token'];
  $redis->set('wx_global_ac_tk', $access_token);
  $redis->setTimeout('wx_global_ac_tk', $json_obj['expires_in']);
}else{
  $access_token = $redis->get('wx_global_ac_tk');
}

if(!$redis->exists('wx_global_js')){
  $jsapi_ticket_url = "https://api.weixin.qq.com/cgi-bin/ticket/getticket?access_token=".$access_token."&type=jsapi";
  $json_obj = json_decode(httpsget($jsapi_ticket_url),true);
  $jsapi_ticket = $json_obj['ticket'];
  $redis->set('wx_global_js', $jsapi_ticket);
  $redis->setTimeout('wx_global_js', $json_obj['expires_in']);
}else{
  $jsapi_ticket = $redis->get('wx_global_js');
}

$noncestr = getRandChar(16);
$timestamp = time();
$url = $_POST['url'];
$string = 'jsapi_ticket='.$jsapi_ticket.'&noncestr='.$noncestr.'&timestamp='.$timestamp.'&url='.$url;
$signature = sha1($string);

$redis->close();

echo json_encode(array("success"=>"1","appid"=>$appid,"signature"=>$signature,"timestamp"=>$timestamp,"noncestr"=>$noncestr,"url"=>$url));

function httpsget($url){
	$ch = curl_init();
	curl_setopt($ch, CURLOPT_URL, $url);
	curl_setopt($ch, CURLOPT_HEADER, 0);
	curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
	curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
	curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);
	curl_setopt($ch, CURLOPT_TIMEOUT, 15);
	$output = curl_exec($ch);
	curl_close($ch);
	return $output;
}

function getRandChar($length){
   $str = null;
   $strPol = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyz";
   $max = strlen($strPol)-1;
   for($i = 0; $i < $length; $i++){
		$str .= $strPol[rand(0,$max)];
   }
   return $str;
}

调用接口:图片和语音

根据开发者文档,需要调用的接口需要先声明,如下:

<!-- index.html -->

<script type="text/javascript" src="http://res.wx.qq.com/open/js/jweixin-1.0.0.js"></script>
<script type="text/javascript" src="jquery.min.js"></script>
<script type="text/javascript">
$.post('sigin.php', {url: location.href.split('#')[0]}, function(res){
  wx.config({
    appId: res.appid,
    timestamp: res.timestamp,
    nonceStr: res.noncestr,
    signature: res.signature,
    jsApiList: [
      'onMenuShareTimeline',//分享接口
      'onMenuShareAppMessage',//分享接口
      'onMenuShareQQ',//分享接口
      'onMenuShareWeibo',//分享接口
      'onMenuShareQZone',//分享接口
      'chooseImage',//图片接口
      'previewImage',//图片接口
      'uploadImage',//图片接口
      'startRecord',//录音接口
      'stopRecord',//录音接口
      'onVoiceRecordEnd',//录音接口
      'playVoice',//录音接口
      'pauseVoice',//录音接口
      'stopVoice',//录音接口
      'onVoicePlayEnd',//录音接口
      'uploadVoice']
  });
  wx.ready(function(){
        var images = {
            localId: '',
            serverId: ''
        };
        var record = {
            localId: '',
            serverId: ''
        }
        $('#img').click(function(){//选择图片
            wx.chooseImage({
                count: 1,
                sizeType: ['original'],
                sourceType: ['album', 'camera'],
                success: function (res) {
                    $('#imgPreview').html('<img src="'+res.localIds+'" />');
                    images.localId = res.localIds;
                }
            });
        });
        $('#uploadimg').click(function(){//上传图片
            if (images.localId.length == 0) {
                alert('请先选择图片');
                return;
            }
            console.log(images.localId[0]);
            images.serverId = '';
            wx.uploadImage({
                localId: images.localId[0],
                isShowProgressTips: 1,
                success: function (res) {
                    alert('图片上传完成');
                    images.serverId = res.serverId;
                }
            });
        });
        $('#startrecord').click(function(){//开始录音
            wx.startRecord();
            $('#status').html('录音中...');
        });
        $('#stoprecord').click(function(){//停止录音
            $('#status').html('已完成');
            wx.stopRecord({
                success: function (res) {
                    record.localId = res.localId;
                }
            });
        });
        $('#playrecord').click(function(){//播放录音
            $('#status').html('播放中...');
            wx.playVoice({
                localId: record.localId
            });
        });
        $('#uploadrecord').click(function(){//上传录音
            wx.uploadVoice({
                localId: record.localId,
                isShowProgressTips: 1,
                    success: function (res) {
                    record.serverId = res.serverId;
                }
            });
        })
        wx.onVoiceRecordEnd({//录音完成(1分钟限制时间到)
            complete: function (res) {
                $('#status').html('已完成');
                record.localId = res.localId;
            }
        });
        wx.onVoicePlayEnd({//播放录音完成
            success: function (res) {
                $('#status').html('播放完毕...');
            }
        });
        $('#tj').click(function(){//保存图片的serverId和录音的serverId到服务器
            $.post('index.php', {img: images.serverId, voc: record.serverId}, function(res){
                alert(res);
            })
        })
    })
})
</script>

服务器上定时根据serverId下载文件,文件保存时间为3天

##download.php
class downloadWeixinFile{
    private $access_token;
    private $media_id;

    function __construct(){
        $redis = new Redis();
        $redis->connect('127.0.0.1',6379);
        $redis->auth('redispassword');
        $redis->setOption(Redis::OPT_PREFIX, 'WXAPI:');
        $appid = "wxappid";
        $secret = "wxsecret";
        if(!$redis->exists('wx_global_ac_tk')){
            $get_token_url = 'https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid='.$appid.'&secret='.$secret;
            $json_obj = json_decode($this->httpsget($get_token_url), true);
            $this->access_token = $json_obj['access_token'];
            $redis->setEx('wx_global_ac_tk', $json_obj['expires_in'], $this->access_token);
        }else{
            $this->access_token = $redis->get('wx_global_ac_tk');
        }
        $redis->close();
    }

    public function download($media_id){
        $this->media_id = $media_id;
        $data = array();
        if (is_array($this->media_id)) {
            foreach ($this->media_id as $media) {
                $url = "http://file.api.weixin.qq.com/cgi-bin/media/get?access_token={$this->access_token}&media_id={$media}";
                $ch = curl_init($url);
                curl_setopt($ch, CURLOPT_HEADER, 0);
                curl_setopt($ch, CURLOPT_NOBODY, 0);
                curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, FALSE);
                curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, FALSE);
                curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
                $package = curl_exec($ch);
                $httpinfo = curl_getinfo($ch);
                curl_close($ch);
                $type = $this->header_byte($httpinfo['content_type']);
                $filename = "wxupload_".time().rand(1111,9999).".".$type;
                $this->saveWeixinFile("/cache/ynwvote/2016sdds/wxupload/".$filename, $package);
                array_push($data, $filename);
            }
        }else{
            $url = "http://file.api.weixin.qq.com/cgi-bin/media/get?access_token={$this->access_token}&media_id={$this->media_id}";
            $ch = curl_init($url);
            curl_setopt($ch, CURLOPT_HEADER, 0);
            curl_setopt($ch, CURLOPT_NOBODY, 0);
            curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, FALSE);
            curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, FALSE);
            curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
            $package = curl_exec($ch);
            $httpinfo = curl_getinfo($ch);
            curl_close($ch);
            $type = $this->header_byte($httpinfo['content_type']);
            $filename = "wxupload_".time().rand(1111,9999).".".$type;
            $this->saveWeixinFile("/cache/ynwvote/2016sdds/wxupload/".$filename, $package);
            array_push($data, $filename);
        }
        return $data;
    }

    private function httpsget($url){
        $ch = curl_init();
        curl_setopt($ch, CURLOPT_URL, $url);
        curl_setopt($ch, CURLOPT_HEADER, 0);
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
        curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
        curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);
        curl_setopt($ch, CURLOPT_TIMEOUT, 15);
        $output = curl_exec($ch);
        curl_close($ch);
        return $output;
    }

    private function header_byte($type){
        switch ($type){
            case 'image/jpeg':
                $tp = 'jpg';
                break;
            case 'image/png':
                $tp = 'png';
                break;
            case 'audio/amr':
                $tp = 'amr';
                break;
            default:
                $tp = "notype";
                break;
        }
        return $tp;
    }

    private function saveWeixinFile($filename, $filecontent){
        $local_file = fopen($filename, 'w');
        if (false !== $local_file){
            if (false !== fwrite($local_file, $filecontent)) {
                fclose($local_file);
            }
        }
    }
}
$file = array('imgserverId', 'voiceserverId');
$download = new downloadWeixinFile();
$name = $ddownload->download($file);

调用接口:获取用户信息

根据微信文档,可以使用静默授权获取用户的openid等基本信息,但是需要经过一个跳转

在首页加入js代码,判断是否获取到了token

//index.html
var wx_tk = '';
var cbname = '2016test';
var $_GET = (function(){
    var url = window.document.location.href.toString();
    var u = url.split("?");
    if(typeof(u[1]) == "string"){
        u = u[1].split("&");
        var get = {};
        for(var i in u){
            var j = u[i].split("=");
            get[j[0]] = j[1];
        }
        return get;
    }else{
        return {};
    }
})();
var wx_tk= sessionStorage.getItem("wx_tk");
if(wx_tk === null || wx_tk === undefined){
    sessionStorage.setItem("wx_tk", 0);
    window.location.href='http://localhost/wxcallback/login.php?cbname='+cbname;
}
if(wx_tk == 0){
    sessionStorage.setItem("wx_tk", $_GET['wx_tk']);
    wx_tk = $_GET['wx_tk'];
}

判断sessionStorage里面有没有wx_tk,没有的话就跳转到login.php,需要新建一个cbname一样的php文件来辅助跳转,主跳转login.php如下:

##login.php
if(isset($_GET['cbname']) && ctype_alnum($_GET['cbname'])) {
    $cbname=$_GET['cbname'].'.php';
} else {
	$cbname='';
}
$appid = "wxappid";
$url = 'https://open.weixin.qq.com/connect/oauth2/authorize?appid='.$appid.'&redirect_uri=http://webapp.yunnan.cn/wxback/'.$cbname.'&response_type=code&scope=snsapi_base&state=1#wechat_redirect';
header("Location:".$url);
exit();

对应的2016test.php文件如下:

##2016test.php
require_once('global.php');
$url='http://localhost/index.html';
header("Location:".$url.'?c=1&wx_tk='.$wx_tk);

global.php里面使用接口获取用户信息,并存入redis

##global.php
$redis = new Redis();
$redis->connect('127.0.0.1',6379);
$redis->auth('redispassword');
$redis->select(1);
$redis->setOption(Redis::OPT_PREFIX, 'WXAPI:');

$appid = "wxappid";
$secret = "wxsecret";

$code = $_GET["code"];//跳转获取到的code
$get_token_url = 'https://api.weixin.qq.com/sns/oauth2/access_token?appid='.$appid.'&secret='.$secret.'&code='.$code.'&grant_type=authorization_code';

$ch = curl_init();
curl_setopt($ch,CURLOPT_URL,$get_token_url);
curl_setopt($ch,CURLOPT_HEADER,0);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1 );
curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 10);
$res = curl_exec($ch);
curl_close($ch);

$json_obj = json_decode($res,true);
$openid = $json_obj['openid'];

if(!$redis->exists('wx_global_ac_tk')){
	$get_token_url = 'https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid='.$appid.'&secret='.$secret;
  	$ch = curl_init();
	curl_setopt($ch,CURLOPT_URL,$get_token_url);
	curl_setopt($ch,CURLOPT_HEADER,0);
	curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1 );
	curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 10);
	$res = curl_exec($ch);
	curl_close($ch);
	$json_obj = json_decode($res,true);

	//根据openid和access_token查询用户信息
	$access_token = $json_obj['access_token'];
	$redis->set('wx_global_ac_tk', $access_token);
	$redis->setTimeout('wx_global_ac_tk', $json_obj['expires_in']-100);
}else{
	$access_token = $redis->get('wx_global_ac_tk');
}
$get_user_info_url = 'https://api.weixin.qq.com/cgi-bin/user/info?access_token='.$access_token.'&openid='.$openid.'&lang=zh_CN';
$ch = curl_init();
curl_setopt($ch,CURLOPT_URL,$get_user_info_url);
curl_setopt($ch,CURLOPT_HEADER,0);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1 );
curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 10);
$res = curl_exec($ch);
curl_close($ch);

$user_info = json_decode($res,true);
$wx_tk=sha1('VVeIx1n'.$user_info['openid']);
$redis->set($wx_tk, $res);
$redis->setTimeout($wx_tk, 7200);
$redis->close();

经过跳转之后已经将用户的信息存入redis,保存的信息有:用户openid,昵称,是否关注本公众号,头像的url等。

调用接口:微信支付

开通微信支付后需要到公共平台上绑定回调域名,在微信支付后台设置KEY,并下载证书保存到服务器上。

##WxPay_Config.php
class WxPayConfig
{
	//=======【基本信息设置】=====================================
	//
	/**
	 * TODO: 修改这里配置为您自己申请的商户信息
	 * 微信公众号信息配置
	 * 
	 * APPID:绑定支付的APPID(必须配置,开户邮件中可查看)
	 * 
	 * MCHID:商户号(必须配置,开户邮件中可查看)
	 * 
	 * KEY:商户支付密钥,参考开户邮件设置(必须配置,登录商户平台自行设置)
	 * 设置地址:https://pay.weixin.qq.com/index.php/account/api_cert
	 * 
	 * APPSECRET:公众帐号secert(仅JSAPI支付的时候需要配置, 登录公众平台,进入开发者中心可设置),
	 * 获取地址:https://mp.weixin.qq.com/advanced/advanced?action=dev&t=advanced/dev&token=2005451881&lang=zh_CN
	 * @var string
	 */
	const APPID = '';
	const MCHID = '';
	const KEY = '';
	const APPSECRET = '';
	
	//=======【证书路径设置】=====================================
	/**
	 * TODO:设置商户证书路径
	 * 证书路径,注意应该填写绝对路径(仅退款、撤销订单时需要,可登录商户平台下载,
	 * API证书下载地址:https://pay.weixin.qq.com/index.php/account/api_cert,下载之前需要安装商户操作证书)
	 * @var path
	 */
	const SSLCERT_PATH = '../cert/apiclient_cert.pem';
	const SSLKEY_PATH = '../cert/apiclient_key.pem';
	
	//=======【curl代理设置】===================================
	/**
	 * TODO:这里设置代理机器,只有需要代理的时候才设置,不需要代理,请设置为0.0.0.0和0
	 * 本例程通过curl使用HTTP POST方法,此处可修改代理服务器,
	 * 默认CURL_PROXY_HOST=0.0.0.0和CURL_PROXY_PORT=0,此时不开启代理(如有需要才设置)
	 * @var unknown_type
	 */
	const CURL_PROXY_HOST = "0.0.0.0";//"10.152.18.220";
	const CURL_PROXY_PORT = 0;//8080;
	
	//=======【上报信息配置】===================================
	/**
	 * TODO:接口调用上报等级,默认紧错误上报(注意:上报超时间为【1s】,上报无论成败【永不抛出异常】,
	 * 不会影响接口调用流程),开启上报之后,方便微信监控请求调用的质量,建议至少
	 * 开启错误上报。
	 * 上报等级,0.关闭上报; 1.仅错误出错上报; 2.全量上报
	 * @var int
	 */
	const REPORT_LEVENL = 1;
}
##WxRedPackHelper.php
class WxRedPackHelper
{
    private $parameters;

    function __construct($param)
    {
        $this->parameters = $param;
    }

    /**
     * 发送单个红包
     */
    public function send($url = 'https://api.mch.weixin.qq.com/mmpaymkttransfers/sendredpack')
    {
        $this->parameters['sign'] = $this->get_sign();
        $postXml = $this->arrayToXml($this->parameters);//生成红包接口XML信息
		
        $responseXml = $this->curl_post_ssl($url, $postXml);
        $responseObj = simplexml_load_string($responseXml, 'SimpleXMLElement', LIBXML_NOCDATA);
        return $responseXml;
    }

    /**
     * 发送裂变红包
     */
    public function send_group()
    {
        return $this->send('https://api.mch.weixin.qq.com/mmpaymkttransfers/sendgroupredpack');
    }

    /**
     * 检查生成签名参数
     */
    protected function check_sign_parameters()
    {
        if ($this->parameters["nonce_str"] &&
            $this->parameters["mch_billno"] &&
            $this->parameters["mch_id"] &&
            $this->parameters["wxappid"] &&
            $this->parameters["send_name"] &&
            $this->parameters["re_openid"] &&
            $this->parameters["total_amount"] &&
//            $this->parameters["max_value"] &&
//            $this->parameters["min_value"] &&
            $this->parameters["total_num"] &&
            $this->parameters["wishing"] &&
//            $this->parameters["client_ip"] &&
            $this->parameters["act_name"] &&
            $this->parameters["remark"]
        ) {
            return true;
        }
        return false;
    }

    /**
     * 例如:
     * appid:    wxd111665abv58f4f
     * mch_id:    10000100
     * device_info:  1000
     * body:    test
     * nonce_str:  ibuaiVcKdpRxkhJA
     * 第一步:对参数按照 key=value 的格式,并按照参数名 ASCII 字典序排序如下:
     * stringA="appid=wxd930ea5d5a258f4f&body=test&device_info=1000&mch_id=10000100&nonce_str=ibuaiVcKdpRxkhJA";
     * 第二步:拼接支付密钥:
     * stringSignTemp="stringA&key=192006250b4c09247ec02edce69f6a2d"
     * sign=MD5(stringSignTemp).toUpperCase()="9A0A8659F005D6984697E2CA0A9CF3B7"
     */
    protected function get_sign()
    {
        if (!WxPayConfig::KEY) {
            die('密钥不能为空');
        }
        if (!$this->check_sign_parameters()) {
            die('生成签名参数缺失');
        }
        ksort($this->parameters);
        $unSignParaString = $this->formatQueryParaMap($this->parameters, false);

        return $this->sign($unSignParaString, WxPayConfig::KEY);
    }

    function curl_post_ssl($url, $vars, $second = 30, $aHeader = array())
    {
        $ch = curl_init();
        //超时时间
        curl_setopt($ch, CURLOPT_TIMEOUT, $second);
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
        //这里设置代理,如果有的话
        curl_setopt($ch, CURLOPT_URL, $url);
        curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
        curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);

        //cert 与 key 分别属于两个.pem文件
        curl_setopt($ch, CURLOPT_SSLCERT, dirname(__FILE__) . '/cert/apiclient_cert.pem');
        curl_setopt($ch, CURLOPT_SSLKEY, dirname(__FILE__) . '/cert/apiclient_key.pem');
        curl_setopt($ch, CURLOPT_CAINFO, dirname(__FILE__) . '/cert/rootca.pem');

        if (count($aHeader) >= 1) {
            curl_setopt($ch, CURLOPT_HTTPHEADER, $aHeader);
        }

        curl_setopt($ch, CURLOPT_POST, 1);
        curl_setopt($ch, CURLOPT_POSTFIELDS, $vars);
		//echo($vars);
        $data = curl_exec($ch);
        curl_close($ch);
        return $data;
    }

    function formatQueryParaMap($paraMap, $urlencode)
    {
        $buff = "";
        ksort($paraMap);
        foreach ($paraMap as $k => $v) {
            if ($v && "sign" != $k) {
                if ($urlencode) {
                    $v = urlencode($v);
                }
                $buff .= "$k=$v&";
            }
        }
        if (strlen($buff) > 0) {
            $reqPar = substr($buff, 0, strlen($buff) - 1);
        }
        return $reqPar;
    }

    function arrayToXml($arr)
    {
        $xml = '<xml>';
        foreach ($arr as $key => $val) {
            if (is_numeric($val&&false)) {
                $xml .= "<$key>$val</$key>";
            } else {
                $xml .= "<$key><![CDATA[$val]]></$key>";
            }
        }
        $xml .= '</xml>';
        return $xml;
    }

    protected function sign($content, $key)
    {
        if (!$content) {
            die('签名内容不能为空');
        }
        $signStr = "$content&key=$key";
        return strtoupper(md5($signStr));
    }

}

发送红包代码:

##redpack.php
include 'WxPay_Config.php';
include 'WxRedPackHelper.php';
function redpack($money, $openid){
    $mch_billno = WxPayConfig::MCHID.time().mt_rand(1000, 100000);
    $act = "活动名称";
    $wish = "祝福语";
    $shares = 1;
    $data = array(
        'nonce_str' => substr(sha1('rpk'.time().mt_rand(1, 1000)), 32),
        'mch_billno' => $mch_billno,
        'mch_id' => WxPayConfig::MCHID,
        'wxappid' => WxPayConfig::APPID,
        'send_name' => '昆明五华发布',
        're_openid' => $openid,
        'total_amount' => $money,
        'total_num' => $shares,
        'wishing' => $wish,
        'client_ip' => '127.0.0.1',
        'act_name' => $act,
        'remark' => '返回语'
    );
    $sender = new WxRedPackHelper($data);
    $sender->send();
}
$money = 100;//最低金额为1元,以分为单位
$openid = '';
redpack($money, $openid);

最后:微信公众号里面要配置安全域名,微信支付也是,不然会报错。微信支付的证书路径一定要正确,不然发不出去。