PHP實現微信小程序用戶受權的工具類

事先準備工做

1.申請一個小程序,申請地址:傳送門
2.仔細閱讀小程序的用戶受權登錄官方文檔:《用戶受權登錄的流程》
3.仔細閱讀微信用戶數據解密的相關文檔:《用戶數據解密說明文檔》
4.在小程序後臺配置好相應的後端請求地址,路徑是:開發---->開發設置,如圖php

clipboard.png
5.小程序若是須要作多個小程序的打通,還須要在微信開放平臺綁定到開發者帳號下面, 若是不須要union_id請忽略html

6.服務端準備一個用戶受權的接口,假設接口連接爲http://test.dev.com/user/auth...,此接口接受以下參數redis

  • code:微信登錄接口返回的登錄憑證,用戶獲取session_key
  • iv:微信小程序登錄接口返回的向量,用於數據解密
  • encrypted_data : 微信獲取用戶信息接口的返回的用戶加密數據,用於後端的接口解析
  • signature加密數據

接口返回的數據以下json

{
    "errcode": 200,
    "msg": "SUCCESS",
    "data": {
        "uid": 34098,
        "unionid": "xxx",
    }
}

6.建表
1)用戶表,其中比較重要的字段是union_id,由於咱們是有多個小程序和公衆號,所以使用這個來區分惟一的用戶編號小程序

DROP TABLE IF EXISTS `jz_wxa_user`;
CREATE TABLE `jz_wxa_user` (
  `id` int(10) unsigned NOT NULL AUTO_INCREMENT,
  `uid` bigint(18) DEFAULT NULL,
  `openid` varchar(255) CHARACTER SET utf8 DEFAULT NULL COMMENT 'openid',
  `user_name` varchar(100) CHARACTER SET utf8mb4 DEFAULT '',
  `nick_name` varchar(100) COLLATE utf8mb4_unicode_ci DEFAULT '' COMMENT '用戶暱稱',
  `sex` enum('0','1') CHARACTER SET utf8 DEFAULT '1' COMMENT '性別',
  `avatar` varchar(255) CHARACTER SET utf8 DEFAULT NULL COMMENT '用戶頭像',
  `province` varchar(100) CHARACTER SET utf8 DEFAULT NULL COMMENT '省份',
  `city` varchar(100) CHARACTER SET utf8 DEFAULT NULL COMMENT '城市',
  `country` varchar(100) CHARACTER SET utf8 DEFAULT NULL COMMENT '國家',
  `wx_union_id` varchar(255) CHARACTER SET utf8 DEFAULT NULL COMMENT '公衆平臺的惟一id',
  `from_url` varchar(255) CHARACTER SET utf8 DEFAULT NULL COMMENT '來源url',
  `created_at` timestamp NULL DEFAULT NULL,
  `updated_at` timestamp NULL DEFAULT NULL,
  `from_appid` varchar(30) COLLATE utf8mb4_unicode_ci DEFAULT 'wx95fc895bebd3743b' COMMENT '來源appid',
  `wx_header` varchar(150) COLLATE utf8mb4_unicode_ci DEFAULT '' COMMENT '微信頭像',
  `gh_openid` varchar(60) COLLATE utf8mb4_unicode_ci DEFAULT '' COMMENT '微信公衆號openid',
  `phone` varchar(30) CHARACTER SET utf8 DEFAULT '' COMMENT '手機號碼',
  PRIMARY KEY (`id`),
  KEY `idx_uid_union_id` (`uid`,`wx_union_id`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

實現步驟

用戶受權時序圖

clipboard.png

關鍵代碼

小程序端

小程序端的獲取用戶信息流程

1)調用login方法獲取code
2)調用getUserInfo方法獲取用戶的加密數據
3)調用後端的用戶受權接口將用戶信息保存到服務端
4)保存後端接口返回的uid和unionid到localstorage中,做爲全局參數後端

獲取用戶的受權信息

getUid:function(cf){
    var that = this
    wx.login({
      success: function (ress) {
        var code = ress.code 
        wx.getUserInfo({ 
          withCredentials: true,          
          success: function (res) {
            that.globalData.userInfo = res.userInfo;
            that.authorize(code, res.signature, res.iv, res.rawData, res.encryptedData, cf)
          }
        })
      }
    })
  },
  authorize: function (code, signature, iv, rawData, encryptedData, cf) {
    var that =this
    var dataobj = {
      code: code,
      signature: signature,
      iv: iv,
      raw_data: rawData,
      encrypted_data: encryptedData
    }
    console.log("code:",code)
    var param = JSON.stringify(dataobj)
    param = that.Encrypt(param)
    var url = that.data.API_DOMAIN2 + "/user/authorization?param=" + param
    wx.request({
      url: url,
      method: "GET",
      header: {
        'content-type': 'application/json'
      },
      success: function (res) {
        if (res.data.errcode == 200) {
          wx.hideToast()       
          wx.setStorage({
            key: "uid",
            data: res.data.data.uid,
            success: function () {
              if (cf) {
                typeof cf == "function" && cf(res.data.data.uid)
              }
            }
          })
        } else {
          that.exceptionHandle('uid', url, res.data.errcode, res.data.msg)
        }
      }
    })
  },

服務端

入口方法

/**
     * api接口開發
     * 獲取詳情的接口
     * @param $uid 用戶編號
     * @param $iv 向量
     * @param $encryptedData 微信加密的數據
     * @param $rawData 判斷是否爲今天
     * @param $signature 簽名
     * @return array
     */
    public static function authorization($appid,$appsecret,$code,$iv,$encryptedData,$rawData,$signature){
        $result = self::decodeWxData($appid,$appsecret,$code,$iv,$encryptedData);
        if($result['errcode'] != 200){
            return $result;
        }
        //處理微信受權的邏輯
        $wxUserData = $result['data'];
        error_log("authorization data=============>");
        error_log(json_encode($wxUserData));
        $uid = WxaUserService::regWxaUser($wxUserData);
        $data['uid'] = $uid['uid'];
        $data['unionid'] =  $uid['unionid'];
        $result['data'] = $data;
        return $result;
    }
    
    /**
     * 解密微信的數據
     * @param $code wx.login接口返回的code
     * @param $iv wx.getUserInfo接口或者wx.getWeRunData返回的iv
     * @param $encryptedData wx.getUserInfo接口或者wx.getWeRunData返回的加密數據
     * @return array
     */
    public static function decodeWxData($appid,$appsecret,$code,$iv,$encryptedData){
        $sessionKeyUrl = sprintf('%s?appid=%s&secret=%s&js_code=%s&grant_type=authorization_code',config('param.wxa_user_info_session_key_url'),$appid,$appsecret,$code);
        $rtnJson = curlRequest($sessionKeyUrl);
        $data = json_decode($rtnJson,true);
        error_log('authorization wx return data========>');
        error_log($rtnJson);
        if(isset($data['errcode'])){
            return $data;
        }
        $sessionKey = $data['session_key'];
        $wxHelper = new WxBizDataHelper($appid,$sessionKey,$encryptedData,$iv);
        $data['errcode'] = 200;
        $data['data'] = [];
        if(!$wxData = $wxHelper->getData()){
            $data['errcode'] = -1;
        }else{
            error_log('current wx return data is =========>'.json_encode($wxData));
            $data['data'] = $wxData;
        }
        return $data;
    }

保存用戶信息的方法

/**
     * 保存用戶信息的方法
     * @param $wxaUserData
     * @param $regFromGh 表示是否從公衆號進行註冊
     */
    public function regWxaUser($wxaUserData,$regFromGh = false)
    {
        $value = $wxaUserData['unionId'];
        $key = getCacheKey('redis_key.cache_key.zset_list.lock') . $value;
        $newExpire = RedisHelper::getLock($key);
        $data =  $this->storeWxaUser($wxaUserData,$regFromGh);
        RedisHelper::releaseLock($key, $newExpire);
        return $data;
    }
    
    /**
     * 保存信息
     * @param $wxaUserData
     * @return mixed
     */
    public function storeWxaUser($wxaUserData,$regFromGh = false)
    {
        $wxUnionId = $wxaUserData['unionId'];
        if (!$user = $this->getByWxUnionId($wxUnionId)) {
            $getAccountDataStartTime = time();
            //這裏是由於須要統一帳戶獲取uid,因此這個是用戶中心的接口,若是沒有這個流程,則直接使用數據
            if($accountData = AccountCenterHelper::regWxaUser($wxaUserData)){
                $getAccountDataEndTime = time();
                $accountRegTime = $getAccountDataEndTime - $getAccountDataStartTime;
                error_log("reg user spend time is ===================>" . $accountRegTime);
                $user = [
                    'uid' => $accountData['uid'],
                    'user_name' => $accountData['user_name'],
                    'nick_name' => $wxaUserData['nickName'],
                    'sex' => $accountData['sex'],
                    'wx_union_id' => $accountData['wx_union_id'],
                    'avatar' => isset($accountData['avatar'])?$accountData['avatar']:"",
                    'from_appid' => $accountData['from_appid'],
                    'province' => $wxaUserData['province'],
                    'city' => $wxaUserData['city'],
                    'country' => $wxaUserData['country'],
                    'openid' => $wxaUserData['openId'],
                    'wx_header' => isset($wxaUserData['avatarUrl'])?$wxaUserData['avatarUrl']:"",
                    'gh_openid' => $regFromGh?$wxaUserData['openId']:"",
                ];
                error_log("insert data=============>" . json_encode($user));
                $user = $this->store($user);
                $regApiUserEndTime = time();
                error_log(" reg api user spend time================>" . ($regApiUserEndTime - $getAccountDataEndTime));
                error_log(" after insert data=============>" . json_encode($user));
            }
        }else{
            if(!$user['wx_header']){
                $updateData = [
                    'id' => $user['id'],
                    'uid' => $user['uid'],
                    'wx_header' => $wxaUserData['avatarUrl'],
                ];
                $this->update($updateData);
            }
            //同步用戶的openid
            if($wxaUserData['openId'] != $user['openid']){
                $updateData = [
                    'id' => $user['id'],
                    'uid' => $user['uid'],
                    'openid' => $wxaUserData['openId'],
                ];
                $this->update($updateData);
            }
        }
        $data['uid'] = $user['uid'];
        $data['unionid'] = $wxUnionId;
        return $data;
    }

根據unionid獲取用戶信息

/**
     * 根據unionid獲取用戶信息
     */
    public function getByWxUnionId($unionId)
    {
        $cacheKey = getCacheKey('redis_key.cache_key.wxa_user.info') . $unionId;
        $value = $this->remember($cacheKey, function () use ($unionId) {
            $userInfo = WxaUser::where('wx_union_id', $unionId)->first();
            $userInfo = $this->compactUserInfo($userInfo);
            return $userInfo;
        });
        return $value;
    }

WxBizDataHelper工具類

<?php
/**
 * Created by PhpStorm.
 * User: Auser
 * Time: 11:17
 */

namespace App\Http\Base\Wx;


class WxBizDataHelper
{

    private $appid;
    private $seesionKey ;
    private $encryptedData;
    private $iv;
    public function __construct($appid, $sessionKey,$encryptedData, $iv)
    {
        $this->appid = $appid;
        $this->seesionKey = $sessionKey;
        $this->encryptedData = $encryptedData;
        $this->iv = $iv;
    }

    public function getData(){
        $pc = new WXBizDataCrypt($this->appid, $this->seesionKey);
        $json = '';
        $errCode = $pc->decryptData($this->encryptedData, $this->iv, $json);
        $data = [];
        if ($errCode == 0) {
            $data = json_decode($json,true);
        }
        return $data;
    }


}

WXBizDataCrypt工具類

<?php
/**
 * Created by PhpStorm.
 * User: Auser
 * Time: 10:38
 */

namespace App\Http\Base\Wx;

use App\Http\Base\Wx\Prpcrypt;
use App\Http\Base\Wx\ErrorCode;
use App\Http\Base\Wx\PKCS7Encoder;
class WXBizDataCrypt
{

    private $appid;
    private $sessionKey;

    /**
     * 構造函數
     * @param $sessionKey string 用戶在小程序登陸後獲取的會話密鑰
     * @param $appid string 小程序的appid
     */
    public function __construct( $appid, $sessionKey)
    {
        $this->sessionKey = $sessionKey;
        $this->appid = $appid;
    }


    /**
     * 檢驗數據的真實性,而且獲取解密後的明文.
     * @param $encryptedData string 加密的用戶數據
     * @param $iv string 與用戶數據一同返回的初始向量
     * @param $data string 解密後的原文
     *
     * @return int 成功0,失敗返回對應的錯誤碼
     */
    public function decryptData( $encryptedData, $iv, &$data )
    {
        if (strlen($this->sessionKey) != 24) {
            return ErrorCode::$IllegalAesKey;
        }
        $aesKey=base64_decode($this->sessionKey);


        if (strlen($iv) != 24) {
            return ErrorCode::$IllegalIv;
        }
        $aesIV=base64_decode($iv);

        $aesCipher=base64_decode($encryptedData);

        $pc = new Prpcrypt($aesKey);
        $result = $pc->decrypt($aesCipher,$aesIV);

        if ($result[0] != 0) {
            return $result[0];
        }

        $dataObj=json_decode( $result[1] );
        if( $dataObj  == NULL )
        {
            return ErrorCode::$IllegalBuffer;
        }
        if( $dataObj->watermark->appid != $this->appid )
        {
            return ErrorCode::$IllegalBuffer;
        }
        $data = $result[1];
        return ErrorCode::$OK;
    }

}

Prpcrypt工具類

<?php
/**
 * Created by PhpStorm.
 * User: Auser
 * Time: 10:55
 */

namespace App\Http\Base\Wx;

class Prpcrypt
{
    public $key;

    public function __construct($key)
    {
        $this->key = $key;
    }

    /**
     * 對密文進行解密
     * @param string $aesCipher 須要解密的密文
     * @param string $aesIV 解密的初始向量
     * @return string 解密獲得的明文
     */
    public function decrypt($aesCipher, $aesIV)
    {

        try {
            $module = mcrypt_module_open(MCRYPT_RIJNDAEL_128, '', MCRYPT_MODE_CBC, '');
            mcrypt_generic_init($module, $this->key, $aesIV);
            //解密
            $decrypted = mdecrypt_generic($module, $aesCipher);
            mcrypt_generic_deinit($module);
            mcrypt_module_close($module);
        } catch (Exception $e) {
            return array(ErrorCode::$IllegalBuffer, null);
        }


        try {
            $result = PKCS7Encoder2::decode($decrypted);
        } catch (Exception $e) {
            //print $e;
            return array(ErrorCode::$IllegalBuffer, null);
        }
        return array(0, $result);
    }
}

ErrorCode狀態代碼類

<?php
/**
 * Created by PhpStorm.
 * User: Auser
 * Time: 10:33
 */

namespace App\Http\Base\Wx;


class ErrorCode
{
    public static $OK = 0;
    public static $IllegalAesKey = -41001;
    public static $IllegalIv = -41002;
    public static $IllegalBuffer = -41003;
    public static $DecodeBase64Error = -41004;

}
相關文章
相關標籤/搜索