完美利用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 相关文章推荐
PHP4实际应用经验篇(7)
Oct 09 PHP
整合了前面的PHP数据库连接类~~做成一个分页类!
Nov 25 PHP
php完全过滤HTML,JS,CSS等标签
Jan 16 PHP
解析用PHP读写音频文件信息的详解(支持WMA和MP3)
May 10 PHP
如何使用php输出时间格式
Aug 31 PHP
PHP生成短网址的3种方法代码实例
Jul 08 PHP
php字符串截取函数用法分析
Nov 25 PHP
php面象对象数据库操作类实例
Dec 02 PHP
PHP使用JSON和将json还原成数组
Feb 12 PHP
php使用mysqli向数据库添加数据的方法
Mar 20 PHP
在PHP站点的页面上添加Facebook评论插件的实例教程
Jan 08 PHP
Thinkphp批量更新数据的方法汇总
Jun 29 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
一个PHP数组应该有多大的分析
2009/07/30 PHP
PHP.ini中配置屏蔽错误信息显示和保存错误日志的例子
2014/05/12 PHP
PHP 使用redis简单示例分享
2015/03/05 PHP
PHP魔术方法使用方法汇总
2016/02/14 PHP
Windows 下安装 swoole 图文教程(php)
2017/06/05 PHP
锋利的jQuery 要点归纳(二) jQuery中的DOM操作(下)
2010/03/23 Javascript
JS对select控件option选项的增删改查示例代码
2013/10/21 Javascript
javascript学习笔记(二)数组和对象部分
2014/09/30 Javascript
JavaScript对Cookie进行读写操作实例
2015/07/25 Javascript
jQuery编程中的一些核心方法简介
2015/08/14 Javascript
JQuery fileupload插件实现文件上传功能
2016/03/18 Javascript
jQuery ajax应用总结
2016/06/02 Javascript
javascript弹出带文字信息的提示框效果
2016/07/19 Javascript
jQuery实现邮箱下拉列表自动补全功能
2016/09/08 Javascript
老生常谈JS中的继承及实现代码
2018/07/06 Javascript
JavaScript中判断为整数的多种方式及保留两位小数的方法
2019/09/09 Javascript
JavaScript 作用域实例分析
2019/10/02 Javascript
[01:05:40]VG vs Newbee 2018国际邀请赛小组赛BO2 第二场 8.17
2018/08/20 DOTA
python计算N天之后日期的方法
2015/03/31 Python
python抓取多种类型的页面方法实例
2019/11/20 Python
python和pywin32实现窗口查找、遍历和点击的示例代码
2020/04/01 Python
Python基于numpy模块实现回归预测
2020/05/14 Python
如何导出python安装的所有模块名称和版本号到文件中
2020/06/05 Python
HTML5+WebSocket实现多文件同时上传的实例
2016/12/29 HTML / CSS
HolidayLettings英国:预订最好的度假公寓、别墅和自助式住宿
2019/08/27 全球购物
大学自主招生自荐信
2013/12/16 职场文书
工业设计专业个人求职信范文
2013/12/28 职场文书
开办加工厂创业计划书
2014/01/03 职场文书
十八届三中全会报告学习材料
2014/02/17 职场文书
竞争上岗实施方案
2014/03/21 职场文书
邀请书格式范文
2015/02/02 职场文书
学校端午节活动总结
2015/02/11 职场文书
通过shell脚本对mysql的增删改查及my.cnf的配置
2021/07/07 MySQL
浅谈MySQL之select优化方案
2021/08/07 MySQL
Python循环之while无限迭代
2022/04/30 Python
在python中读取和写入CSV文件详情
2022/06/28 Python