完美利用Yii2微信后台开发的系列总结


Posted in PHP onJuly 18, 2016

网上有很多关于YII2.0微信开发教程,但是太过复杂凌乱,所以今天在这里给大家整理总结利用Yii2微信后台开发的系列了,给需要的小伙伴们参考。

一:接入微信

Yii2后台配置

1.在app/config/params.php中配置token参数

return [
 //微信接入
 'wechat' =>[
 'token' => 'your token',
 ],
];

2.在app/config/main.php中配置路由

因为接口模块使用的RESTful API,所以需要定义路由规则。

'urlManager' => [
 'enablePrettyUrl' => true,
 'enableStrictParsing' => true,
 'showScriptName' => false,
 'rules' => [
 [
  'class' => 'yii\rest\UrlRule',
  'controller' => 'wechat',
  'extraPatterns' => [
  'GET valid' => 'valid',
  ],
 ],
 ],
],

3.在app/controllers中新建WechatController

<?php

namespace api\controllers;

use Yii;
use yii\rest\ActiveController;

class WechatController extends ActiveController
{

 public $modelClass = '';

 public function actionValid()
 {
 $echoStr = $_GET["echostr"];
 $signature = $_GET["signature"];
 $timestamp = $_GET["timestamp"];
 $nonce = $_GET["nonce"];
 //valid signature , option
 if($this->checkSignature($signature,$timestamp,$nonce)){
  echo $echoStr;
 }
 }

 private function checkSignature($signature,$timestamp,$nonce)
 {
 // you must define TOKEN by yourself
 $token = Yii::$app->params['wechat']['token'];
 if (!$token) {
  echo 'TOKEN is not defined!';
 } else {
  $tmpArr = array($token, $timestamp, $nonce);
  // use SORT_STRING rule
  sort($tmpArr, SORT_STRING);
  $tmpStr = implode( $tmpArr );
  $tmpStr = sha1( $tmpStr );

  if( $tmpStr == $signature ){
  return true;
  }else{
  return false;
  }
 }
 }

}

微信公众号后台配置

在微信公众号后台配置URL和Token,然后提交验证即可。

URL:http://app.demo.com/wechats/valid
Token:your token

二:获取用户信息

用户表设计

CREATE TABLE `wechat_user` (

  `id` int(11) NOT NULL,

  `openid` varchar(255) COLLATE utf8_unicode_ci NOT NULL,

  `nickname` varchar(50) COLLATE utf8_unicode_ci NOT NULL COMMENT '微信昵称',

  `sex` tinyint(4) NOT NULL COMMENT '性别',

  `headimgurl` varchar(255) COLLATE utf8_unicode_ci NOT NULL COMMENT '头像',

  `country` varchar(50) COLLATE utf8_unicode_ci NOT NULL COMMENT '国家',

  `province` varchar(50) COLLATE utf8_unicode_ci NOT NULL COMMENT '省份',

  `city` varchar(50) COLLATE utf8_unicode_ci NOT NULL COMMENT '城市',

  `access_token` varchar(255) COLLATE utf8_unicode_ci NOT NULL,

  `refresh_token` varchar(255) COLLATE utf8_unicode_ci NOT NULL,

  `created_at` timestamp NULL DEFAULT CURRENT_TIMESTAMP

) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;

ALTER TABLE `wechat_user`

  ADD PRIMARY KEY (`id`);

获取用户信息的相关接口

1.用户授权接口:获取access_token、openId等;获取并保存用户资料到数据库

public function actionAccesstoken()

{

    $code = $_GET["code"];

    $state = $_GET["state"];

    $appid = Yii::$app->params['wechat']['appid'];

    $appsecret = Yii::$app->params['wechat']['appsecret'];

    $request_url = 'https://api.weixin.qq.com/sns/oauth2/access_token?appid='.$appid.'&secret='.$appsecret.'&code='.$code.'&grant_type=authorization_code';

    //初始化一个curl会话

    $ch = curl_init();

    curl_setopt($ch, CURLOPT_URL, $request_url);

    curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);

    $result = curl_exec($ch);

    curl_close($ch);

    $result = $this->response($result);

    //获取token和openid成功,数据解析

    $access_token = $result['access_token'];

    $refresh_token = $result['refresh_token'];

    $openid = $result['openid'];

    //请求微信接口,获取用户信息

    $userInfo = $this->getUserInfo($access_token,$openid);

    $user_check = WechatUser::find()->where(['openid'=>$openid])->one();

    if ($user_check) {

        //更新用户资料

    } else {

        //保存用户资料

    }

    //前端网页的重定向

    if ($openid) {

        return $this->redirect($state.$openid);

    } else {

        return $this->redirect($state);

    }

}

2.从微信获取用户资料

public function getUserInfo($access_token,$openid)

{

    $request_url = 'https://api.weixin.qq.com/sns/userinfo?access_token='.$access_token.'&openid='.$openid.'&lang=zh_CN';

    //初始化一个curl会话

    $ch = curl_init();

    curl_setopt($ch, CURLOPT_URL, $request_url);

    curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);

    $result = curl_exec($ch);

    curl_close($ch);

    $result = $this->response($result);

    return $result;

}

3.获取用户资料接口

public function actionUserinfo()
{
 if(isset($_REQUEST["openid"])){
  $openid = $_REQUEST["openid"];
  $user = WechatUser::find()->where(['openid'=>$openid])->one();
  if ($user) {
   $result['error'] = 0;
   $result['msg'] = '获取成功';
   $result['user'] = $user;
  } else {
   $result['error'] = 1;
   $result['msg'] = '没有该用户';
  }
 } else {
  $result['error'] = 1;
  $result['msg'] = 'openid为空';
 }
 return $result;
}

 

三:微信支付

1.微信支付接口:打包支付数据

public function actionPay(){

    if(isset($_REQUEST["uid"])&&isset($_REQUEST["oid"])&&isset($_REQUEST["totalFee"])){

        //uid、oid、totalFee

        $uid = $_REQUEST["uid"];

        $oid = $_REQUEST["oid"];

        $totalFee = $_REQUEST["totalFee"];

        $timestamp = time();

        //微信支付参数

        $appid = Yii::$app->params['wechat']['appid'];

        $mchid = Yii::$app->params['wechat']['mchid'];

        $key = Yii::$app->params['wechat']['key'];

        $notifyUrl = Yii::$app->params['wechat']['notifyUrl'];

        //支付打包

        $wx_pay = new WechatPay($mchid, $appid, $key);

        $package = $wx_pay->createJsBizPackage($uid, $totalFee, $oid, $notifyUrl, $timestamp);

        $result['error'] = 0;

        $result['msg'] = '支付打包成功';

        $result['package'] = $package;

        return $result;

    }else{

        $result['error'] = 1;

        $result['msg'] = '请求参数错误';

    }

    return $result;

}

2.接收微信发送的异步支付结果通知

public function actionNotify(){

    $postStr = $GLOBALS["HTTP_RAW_POST_DATA"];

    $postObj = simplexml_load_string($postStr, 'SimpleXMLElement', LIBXML_NOCDATA);

    //

    if ($postObj === false) {

        die('parse xml error');

    }

    if ($postObj->return_code != 'SUCCESS') {

        die($postObj->return_msg);

    }

    if ($postObj->result_code != 'SUCCESS') {

        die($postObj->err_code);

    }

    //微信支付参数

    $appid = Yii::$app->params['wechat']['appid'];

    $mchid = Yii::$app->params['wechat']['mchid'];

    $key = Yii::$app->params['wechat']['key'];

    $wx_pay = new WechatPay($mchid, $appid, $key);

    //验证签名

    $arr = (array)$postObj;

    unset($arr['sign']);

    if ($wx_pay->getSign($arr, $key) != $postObj->sign) {

        die("签名错误");

    }

    //支付处理正确-判断是否已处理过支付状态

    $orders = Order::find()->where(['uid'=>$postObj->openid, 'oid'=>$postObj->out_trade_no, 'status' => 0])->all();

    if(count($orders) > 0){

        //更新订单状态

        foreach ($orders as $order) {

            //更新订单

            $order['status'] = 1;

            $order->update();

        }

        return '<xml><return_code><![CDATA[SUCCESS]]></return_code><return_msg><![CDATA[OK]]></return_msg></xml>';

    } else {

        //订单状态已更新,直接返回

        return '<xml><return_code><![CDATA[SUCCESS]]></return_code><return_msg><![CDATA[OK]]></return_msg></xml>';

    }

}

3.微信支付类 WechatPay.php

<?php

namespace api\sdk;

use Yii;

class WechatPay

{

    protected $mchid;

    protected $appid;

    protected $key;

    public function __construct($mchid, $appid, $key){

        $this->mchid = $mchid;

        $this->appid = $appid;

        $this->key = $key;

    }

    public function createJsBizPackage($openid, $totalFee, $outTradeNo, $orderName, $notifyUrl, $timestamp){

        $config = array(

            'mch_id' => $this->mchid,

            'appid' => $this->appid,

            'key' => $this->key,

        );

        $unified = array(

            'appid' => $config['appid'],

            'attach' => '支付',

            'body' => $orderName,

            'mch_id' => $config['mch_id'],

            'nonce_str' => self::createNonceStr(),

            'notify_url' => $notifyUrl,

            'openid' => $openid,

            'out_trade_no' => $outTradeNo,

            'spbill_create_ip' => '127.0.0.1',

            'total_fee' => intval($totalFee * 100),

            'trade_type' => 'JSAPI',

        );

        $unified['sign'] = self::getSign($unified, $config['key']);

        $responseXml = self::curlPost('https://api.mch.weixin.qq.com/pay/unifiedorder', self::arrayToXml($unified));

        $unifiedOrder = simplexml_load_string($responseXml, 'SimpleXMLElement', LIBXML_NOCDATA);

        if ($unifiedOrder === false) {

            die('parse xml error');

        }

        if ($unifiedOrder->return_code != 'SUCCESS') {

            die($unifiedOrder->return_msg);

        }

        if ($unifiedOrder->result_code != 'SUCCESS') {

            die($unifiedOrder->err_code);

        }

        $arr = array(

            "appId" => $config['appid'],

            "timeStamp" => $timestamp,

            "nonceStr" => self::createNonceStr(),

            "package" => "prepay_id=" . $unifiedOrder->prepay_id,

            "signType" => 'MD5',

        );

        $arr['paySign'] = self::getSign($arr, $config['key']);

        return $arr;

    }

    public static function curlGet($url = '', $options = array()){

        $ch = curl_init($url);

        curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);

        curl_setopt($ch, CURLOPT_TIMEOUT, 30);

        if (!empty($options)) {

            curl_setopt_array($ch, $options);

        }

        //https请求 不验证证书和host

        curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);

        curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);

        $data = curl_exec($ch);

        curl_close($ch);

        return $data;

    }

    public static function curlPost($url = '', $postData = '', $options = array()){

        if (is_array($postData)) {

            $postData = http_build_query($postData);

        }

        $ch = curl_init();

        curl_setopt($ch, CURLOPT_URL, $url);

        curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);

        curl_setopt($ch, CURLOPT_POST, 1);

        curl_setopt($ch, CURLOPT_POSTFIELDS, $postData);

        curl_setopt($ch, CURLOPT_TIMEOUT, 30); //设置cURL允许执行的最长秒数

        if (!empty($options)) {

            curl_setopt_array($ch, $options);

        }

        //https请求 不验证证书和host

        curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);

        curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);

        $data = curl_exec($ch);

        curl_close($ch);

        return $data;

    }

    public static 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;

    }

    public static function arrayToXml($arr){

        $xml = "<xml>";

        foreach ($arr as $key => $val){

            if (is_numeric($val)) {

                $xml .= "<" . $key . ">" . $val . "</" . $key . ">";

            } else {

                $xml .= "<" . $key . "><![CDATA[" . $val . "]]></" . $key . ">";

            }

        }

        $xml .= "</xml>";

        return $xml;

    }

    public static function getSign($params, $key){

        ksort($params, SORT_STRING);

        $unSignParaString = self::formatQueryParaMap($params, false);

        $signStr = strtoupper(md5($unSignParaString . "&key=" . $key));

        return $signStr;

    }

    protected static function formatQueryParaMap($paraMap, $urlEncode = false){

        $buff = "";

        ksort($paraMap);

        foreach ($paraMap as $k => $v){

            if (null != $v && "null" != $v) {

                if ($urlEncode) {

                    $v = urlencode($v);

                }

                $buff .= $k . "=" . $v . "&";

            }

        }

        $reqPar = '';

        if (strlen($buff)>0) {

            $reqPar = substr($buff, 0, strlen($buff) - 1);

        }

        return $reqPar;

    }

}

四:获取JS-SDK的config参数

根据微信公众平台开发者文档:

所有需要使用JS-SDK的页面必须先注入配置信息,否则将无法调用(同一个url仅需调用一次,对于变化url的SPA的web app可在每次url变化时进行调用,目前Android微信客户端不支持pushState的H5新特性,所以使用pushState来实现web app的页面会导致签名失败,此问题会在Android6.2中修复)。

即:

wx.config({

    debug: true, // 开启调试模式,调用的所有api的返回值会在客户端alert出来,若要查看传入的参数,可以在pc端打开,参数信息会通过log打出,仅在pc端时才会打印。

    appId: '', // 必填,公众号的唯一标识

    timestamp: , // 必填,生成签名的时间戳

    nonceStr: '', // 必填,生成签名的随机串

    signature: '',// 必填,签名,见附录1

    jsApiList: [] // 必填,需要使用的JS接口列表,所有JS接口列表见附录2

});

1.微信支付类 WechatPay.php

<?php

namespace api\sdk;

use Yii;

class WechatPay

{

    public function getSignPackage($url) {

        $jsapiTicket = self::getJsApiTicket();

        $timestamp = time();

        $nonceStr = self::createNonceStr();

        // 这里参数的顺序要按照 key 值 ASCII 码升序排序

        $string = "jsapi_ticket=".$jsapiTicket."&noncestr=".$nonceStr."×tamp=".$timestamp."&url=".$url;

        $signature = sha1($string);

        $signPackage = array(

            "appId"     => $this->appid,

            "nonceStr"  => $nonceStr,

            "timestamp" => $timestamp,

            "url"       => $url,

            "signature" => $signature,

            "rawString" => $string

        );

        return $signPackage;

    }

    public static function getJsApiTicket() {

        //使用Redis缓存 jsapi_ticket

        $redis = Yii::$app->redis;

        $redis_ticket = $redis->get('wechat:jsapi_ticket');

        if ($redis_ticket) {

            $ticket = $redis_ticket;

        } else {

            $accessToken = self::getAccessToken();

            $url = "https://api.weixin.qq.com/cgi-bin/ticket/getticket?type=jsapi&access_token=".$accessToken;

            $res = json_decode(self::curlGet($url));

            $ticket = $res->ticket;

            if ($ticket) {

                $redis->set('wechat:jsapi_ticket', $ticket);

                $redis->expire('wechat:jsapi_ticket', 7000);

            }

        }

        return $ticket;

    }

    public static function getAccessToken() {

        //使用Redis缓存 access_token

        $redis = Yii::$app->redis;

        $redis_token = $redis->get('wechat:access_token');

        if ($redis_token) {

            $access_token = $redis_token;

        } else {

            $appid = Yii::$app->params['wechat']['appid'];

            $appsecret = Yii::$app->params['wechat']['appsecret'];

            $url = "https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=".$appid."&secret=".$appsecret;

            $res = json_decode(self::curlGet($url));

            $access_token = $res->access_token;

            if ($access_token) {

                $redis->set('wechat:access_token', $access_token);

                $redis->expire('wechat:access_token', 7000);

            }

        }

        return $access_token;

    }

    public static function curlGet($url = '', $options = array()){

        $ch = curl_init($url);

        curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);

        curl_setopt($ch, CURLOPT_TIMEOUT, 30);

        if (!empty($options)) {

            curl_setopt_array($ch, $options);

        }

        //https请求 不验证证书和host

        curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);

        curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);

        $data = curl_exec($ch);

        curl_close($ch);

        return $data;

    }

    public static function curlPost($url = '', $postData = '', $options = array()){

        if (is_array($postData)) {

            $postData = http_build_query($postData);

        }

        $ch = curl_init();

        curl_setopt($ch, CURLOPT_URL, $url);

        curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);

        curl_setopt($ch, CURLOPT_POST, 1);

        curl_setopt($ch, CURLOPT_POSTFIELDS, $postData);

        curl_setopt($ch, CURLOPT_TIMEOUT, 30); //设置cURL允许执行的最长秒数

        if (!empty($options)) {

            curl_setopt_array($ch, $options);

        }

        //https请求 不验证证书和host

        curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);

        curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);

        $data = curl_exec($ch);

        curl_close($ch);

        return $data;

    }

    public static 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;

    }

}

2.获取config参数接口

public function actionConfig(){
 if (isset($_REQUEST['url'])) {
 $url = $_REQUEST['url'];
 //微信支付参数
 $appid = Yii::$app->params['wechat']['appid'];
 $mchid = Yii::$app->params['wechat']['mchid'];
 $key = Yii::$app->params['wechat']['key'];
 $wx_pay = new WechatPay($mchid, $appid, $key);
 $package = $wx_pay->getSignPackage($url);
 $result['error'] = 0;
 $result['msg'] = '获取成功';
 $result['config'] = $package;
 } else {
 $result['error'] = 1;
 $result['msg'] = '参数错误';
 }
 return $result;
}

以上就是利用Yii2微信后台开发全部过程及示例代码,希望本文对大家基于php的微信公众平台开发有所帮助。

PHP 相关文章推荐
PHP如何编写易读的代码
Jul 10 PHP
洪恩在线成语词典小偷程序php版
Apr 20 PHP
非常精妙的PHP递归调用与静态变量使用
Dec 16 PHP
php在window iis的莫名问题的测试方法
May 14 PHP
探讨php中防止SQL注入最好的方法是什么
Jun 10 PHP
PHP imagegrabscreen和imagegrabwindow(截取网站缩略图)的实例代码
Nov 07 PHP
thinkphp四种url访问方式详解
Nov 28 PHP
ThinkPHP采用原生query实现关联查询left join实例
Dec 02 PHP
php实现用于计算执行时间的类实例
Apr 18 PHP
php数组指针操作详解
Feb 14 PHP
PHP自动载入类文件函数__autoload的使用方法
Mar 25 PHP
PHP防止sql注入小技巧之sql预处理原理与实现方法分析
Dec 13 PHP
PHP中set_include_path()函数相关用法分析
Jul 18 #PHP
PHP中spl_autoload_register()函数用法实例详解
Jul 18 #PHP
详谈PHP程序Laravel 5框架的优化技巧
Jul 18 #PHP
3种方法轻松处理php开发中emoji表情的问题
Jul 18 #PHP
PHP生成图像验证码的方法小结(2种方法)
Jul 18 #PHP
Yii2中DropDownList简单用法示例
Jul 18 #PHP
Yii2使用dropdownlist实现地区三级联动功能的方法
Jul 18 #PHP
You might like
40个迹象表明你还是PHP菜鸟
2008/09/29 PHP
PHP实现更新中间关联表数据的两种方法
2014/09/01 PHP
php中的观察者模式简单实例
2015/01/20 PHP
Laravel 5框架学习之路由、控制器和视图简介
2015/04/07 PHP
thinkphp,onethink和thinkox中验证码不显示的解决方法分析
2016/06/06 PHP
CI框架无限级分类+递归的实现代码
2016/11/01 PHP
PHP Cli 模式设置进程名称的方法
2019/06/12 PHP
Autocomplete Textbox Example javascript实现自动完成成功
2007/08/17 Javascript
JavaScript DOM基础
2015/04/13 Javascript
Jquery全选与反选点击执行一次的解决方案
2015/08/14 Javascript
jquery实现简易的移动端验证表单
2015/11/08 Javascript
Wireshark基本介绍和学习TCP三次握手
2016/08/15 Javascript
jQuery实现拼图小游戏(实例讲解)
2017/07/24 jQuery
利用JQuery操作iframe父页面、子页面的元素和方法汇总
2017/09/10 jQuery
React native ListView 增加顶部下拉刷新和底下点击刷新示例
2018/04/27 Javascript
原生JavaScript实现滑动拖动验证的示例代码
2019/12/06 Javascript
JS表格的动态操作完整示例
2020/01/13 Javascript
微信小程序以ssm做后台开发的实现示例
2020/04/08 Javascript
jQuery实现倒计时功能完整示例
2020/06/01 jQuery
Vue路由的模块自动化与统一加载实现
2020/06/05 Javascript
js实现验证码干扰(静态)
2021/02/22 Javascript
用python写asp详细讲解
2013/12/16 Python
Python获取当前脚本文件夹(Script)的绝对路径方法代码
2019/08/27 Python
Python 装饰器原理、定义与用法详解
2019/12/07 Python
Python imutils 填充图片周边为黑色的实现
2020/01/19 Python
django-利用session机制实现唯一登录的例子
2020/03/16 Python
使用python脚本自动生成K8S-YAML的方法示例
2020/07/12 Python
Marc Jacobs彩妆官网:Marc Jacobs Beauty
2017/07/03 全球购物
椰子猫砂:CatSpot
2018/08/27 全球购物
学院领导推荐信
2013/10/30 职场文书
学生个人的自我评价分享
2013/11/05 职场文书
妇产医师自荐信
2014/01/29 职场文书
城市精细化管理实施方案
2014/03/04 职场文书
2016教师暑期培训学习心得体会
2016/01/09 职场文书
2016年禁毒宣传活动总结
2016/04/05 职场文书
Anaconda安装pytorch及配置PyCharm 2021环境
2021/06/04 Python