前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >PHP实现微信小程序用户授权的工具类示例

PHP实现微信小程序用户授权的工具类示例

作者头像
砸漏
发布2020-10-21 09:56:11
1.2K0
发布2020-10-21 09:56:11
举报
文章被收录于专栏:恩蓝脚本

事先准备工作

1.申请一个小程序,申请地址:传送门 2.仔细阅读小程序的用户授权登陆官方文档: 《用户授权登陆的流程》 3.仔细阅读微信用户数据解密的相关文档: 《用户数据解密说明文档》 4.在小程序后台配置好相应的后端请求地址,路径是:开发—- 开发设置,如图

5.小程序如果需要做多个小程序的打通,还需要在微信开放平台绑定到开发者账号下面, 如果不需要union_id请忽略

6.服务端准备一个用户授权的接口,假设接口链接为http://test.dev.com/user/authorization,此接口接受如下参数

  • code:微信登陆接口返回的登陆凭证,用户获取session_key
  • iv:微信小程序登陆接口返回的向量,用于数据解密
  • encrypted_data : 微信获取用户信息接口的返回的用户加密数据,用于后端的接口解析
  • signature加密数据

接口返回的数据如下

代码语言:javascript
复制
{
  "errcode": 200,
  "msg": "SUCCESS",
  "data": {
    "uid": 34098,
    "unionid": "xxx",
  }
}

6.建表

1)用户表,其中比较重要的字段是union_id,因为我们是有多个小程序和公众号,因此使用这个来区分唯一的用户编号

代码语言:javascript
复制
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;

实现步骤

用户授权时序图

关键代码

小程序端

小程序端的获取用户信息流程

1)调用login方法获取code 2)调用getUserInfo方法获取用户的加密数据 3)调用后端的用户授权接口将用户信息保存到服务端 4)保存后端接口返回的uid和unionid到localstorage中,作为全局参数

获取用户的授权信息

代码语言:javascript
复制
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)
    }
   }
  })
 },

服务端

入口方法

代码语言:javascript
复制
/**
   * 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;
  }

保存用户信息的方法

代码语言:javascript
复制
/**
* 保存用户信息的方法
* @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获取用户信息

代码语言:javascript
复制
/**
* 根据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工具类

代码语言:javascript
复制
<?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工具类

代码语言:javascript
复制
<?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工具类

代码语言:javascript
复制
<?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状态代码类

代码语言:javascript
复制
<?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;
}

以上就是本文的全部内容,希望对大家的学习有所帮助。

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2020-09-11 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
相关产品与服务
云开发 CloudBase
云开发(Tencent CloudBase,TCB)是腾讯云提供的云原生一体化开发环境和工具平台,为200万+企业和开发者提供高可用、自动弹性扩缩的后端云服务,可用于云端一体化开发多种端应用(小程序、公众号、Web 应用等),避免了应用开发过程中繁琐的服务器搭建及运维,开发者可以专注于业务逻辑的实现,开发门槛更低,效率更高。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档