微信小程序 - 登錄 - code2Session - 服務(wù)端PHP開(kāi)發(fā)

這段時(shí)間因?yàn)閼?yīng)公司需要去開(kāi)發(fā)小程序業(yè)務(wù),從0到有開(kāi)始一個(gè)全新的電商平臺(tái)開(kāi)發(fā),這類(lèi)微信第三方的開(kāi)發(fā)其實(shí)在百度、知乎、簡(jiǎn)書(shū)等多個(gè)平臺(tái)都能搜到相對(duì)應(yīng)的代碼,今天只是把這類(lèi)代碼以正常人看得懂角度進(jìn)行分享。

強(qiáng)調(diào)一下,這里筆者應(yīng)用的是YII1.0,框架沒(méi)啥用到內(nèi)部的方法,主要還是面向?qū)ο蟮乃季S,如果框架不同唯獨(dú)就是框架的配置與查詢(xún)數(shù)據(jù)庫(kù)方式還有文件層級(jí)目錄的不同,咱們學(xué)習(xí)的時(shí)候脫離框架思維運(yùn)用面向?qū)ο蠼嵌瓤创纯?/p>

第一步操作

小程序這邊需要進(jìn)行 wx.login文檔 中指出的使用wx.login調(diào)用接口獲取登錄憑證(code)。

什么意思呢?微信的登錄無(wú)論是小程序還是公眾號(hào)登錄都是需要code作為請(qǐng)求授權(quán)的參數(shù)

    // 登錄
    wx.login({
      success: res => {
        console.log(res.code)  // 就這么簡(jiǎn)單就能拿到微信的code,同時(shí)發(fā)起業(yè)務(wù)請(qǐng)求
      }
    })

拿到code之后,我們就需要授權(quán)了,調(diào)用后端提供的接口,傳參只需要給定一個(gè)code參數(shù)即可

TokenController.php 接口文件

<?php
/**
 * Created by PhpStorm.
 * User: 俗俗俗俗俗人。
 * Date: 2019-04-16
 * Time: 09:04
 */

class TokenController extends Controller
{
    public function actionGetToken()
    {
        $params = $_POST;
        if (empty($params['code'])) {
            // 校驗(yàn)參數(shù)是否傳遞,根據(jù)個(gè)人業(yè)務(wù)理解設(shè)計(jì)即可,錯(cuò)誤返回業(yè)務(wù)code為非200的值即可
            $this->resultJson(DeBugCode::$codeNotEmpty);
        }

        $autoToken = new UserTokenService($params['code']);
        $token = $autoToken->get();
        if ($token) {
            return $this->resultJson(true, $token);
        }
        return $this->resultJson(DeBugCode::$wechatLoginError);
    }
}

UserTokenService.php 業(yè)務(wù)文件

<?php
/**
 * Created by PhpStorm.
 * User: 俗俗俗俗俗人。
 * Date: 2019-04-16
 * Time: 09:22
 */

class UserTokenService extends TokenService
{
    /**
     * @var code 臨時(shí)登錄憑證 code
     */
    protected $code;

    /**
     * @var expireDate 過(guò)期時(shí)間
     */
    protected $expireDate;

    /**
     * @var wechat 配置
     */
    protected $config;

    /**
     * @var appID 微信小程序AppID
     */
    protected $appID;

    /**
     * @var AppSecret 微信小程序AppSecret
     */
    protected $appSecret;

    /**
     * @var wechatLoginUrl 請(qǐng)求地址
     */
    protected $wechatLoginUrl;

    /**
     * 構(gòu)造函數(shù)主要是將微信配置項(xiàng)讀取出來(lái),同時(shí)做一下聲明
     * UserTokenService constructor.
     * @param $code
     */
    public function __construct($code)
    {
        $this->code           = $code;
        $this->expireDate     = Yii::app()->params['wechat']['expireDate'];
        $this->config         = Yii::app()->params['wechat']['miniprogram'];
        $this->appID          = $this->config['appID'];
        $this->appSecret      = $this->config['appSecret'];
        $this->wechatLoginUrl = sprintf($this->config['getUrl'], $this->appID, $this->appSecret, $this->code);
    }

    /**
     * 發(fā)起微信小程序登錄請(qǐng)求
     * @return array|bool
     * @throws Exception
     */
    public function get()
    {
        $result = NnCommon::curlGet($this->wechatLoginUrl);
        $wechatResult = json_decode($result, true);
        if (empty($wechatResult)) {
            throw new Exception('微信內(nèi)部錯(cuò)誤,獲取openID發(fā)生異常');
        } else {
            $loginFail = array_key_exists('errcode', $wechatResult);
            // 微信調(diào)用失敗
            if ($loginFail) {
                // 返回false,記錄錯(cuò)誤日志內(nèi)容,方便查詢(xún)問(wèn)題
                return false;
            } else {
                return $this->grantToken($wechatResult);
            }
        }
    }

    /**
     * 生成Token用戶(hù)令牌
     * @param $wechatResult
     * @return array
     */
    private function grantToken($wechatResult)
    {
        // 取出openID
        $openID = $wechatResult['openid'];

        // 查詢(xún)數(shù)據(jù)庫(kù),判斷openID是否存在  存在則不處理,否則新增
        $user = UserService::getByOpenId($openID);

        if ($user) {
            $userId = $user->ID;
            // 當(dāng)unionid為空,并且微信返回時(shí),我們單獨(dú)去做一次處理
            if (empty($user->unionid) && !empty($wechatResult['unionid'])) {
                UserService::updateUser($user, $wechatResult);
            }
        } else {
            $userId = UserService::newUser($wechatResult);
        }

        // 創(chuàng)建令牌寫(xiě)入緩存 key:token value:wxResult,userId,scope
        $cachedValue = $this->prepareCachedValue($wechatResult, $userId);

        // 返回令牌至客戶(hù)端
        $token = $this->saveToCache($cachedValue);

        return array('token' => $token);
    }

    /**
     * 拼裝CacheValue值
     * @param $wechatResult
     * @param $userId
     * @return mixed
     */
    private function prepareCachedValue($wechatResult, $userId)
    {
        $cachedValue           = $wechatResult;
        $cachedValue['userID'] = $userId;
        return $cachedValue;
    }

    /**
     * 將數(shù)據(jù)存入緩存
     * @param $cachedValue
     * @return bool|string
     */
    private function saveToCache($cachedValue)
    {
        $key        = self::generateToken();
        $value      = json_encode($cachedValue);
        $expireDate = $this->expireDate;

        // 這里我使用的是memcached,根據(jù)自己情況而定
        $request = UtilCache::setCache($key, $value, $expireDate);
        if (!$request) {
            // 記錄日志
            MonoLog::setCacheError('用戶(hù)數(shù)據(jù)緩存失敗', $cachedValue);
            return false;
        }

        return $key;
    }
}

TokenService.php 工具類(lèi)業(yè)務(wù)

<?php
/**
 * Created by PhpStorm.
 * User: j
 * Date: 2019-04-16
 * Time: 14:28
 */

class TokenService
{
    /**
     * 隨機(jī)字符串,32位
     * @param int $length
     * @return string
     */
    public static function createRandom($length = 32)
    {
        $chars = "abcdefghijklmnopqrstuvwxyz0123456789";
        $str = "";
        for ($i = 0; $i < $length; $i++) {
            $str .= substr($chars, mt_rand(0, strlen($chars) - 1), 1);
        }
        return $str;
    }
    /**
     * 隨機(jī)32位三項(xiàng)定義md5()字符串
     * @return string
     */
    public static function generateToken()
    {
        // 隨機(jī)32位字符串
        $randChars = NnCommon::getRandChar(32);
        // 時(shí)間戳
        $timeStamp = time();
        // salt 鹽
        $salt = Secure::$tokenSalt;

        return md5($randChars . $timeStamp . $salt);
    }

    /**
     * 從緩沖中根據(jù)key獲取對(duì)應(yīng)所需的內(nèi)容
     * @param $key
     * @return bool|mixed
     */
    public static function getCurrentTokenVar($key)
    {
        // 從HTTP請(qǐng)求頭中獲取用戶(hù)Token
        $token = $_SERVER['HTTP_TOKEN'];

        // 緩沖中獲取用戶(hù)數(shù)據(jù)
        $vars  = UtilCache::getCache($token);
        if (!$vars) {
            return false;
        }

        // 防止取出就是數(shù)組
        if (!is_array($vars)) {
            $vars = json_decode($vars, true);
        }

        if (array_key_exists($key, $vars)) {
            return $vars[$key];
        }

        return false;
    }

    /**
     * 獲取用戶(hù)ID
     * @return bool|mixed
     */
    public static function getCurrentUserID()
    {
        $userID = self::getCurrentTokenVar('userID');
        return $userID;
    }

    /**
     * 獲取用戶(hù)sessionKey
     * @return bool|mixed
     */
    public static function getCurrentSessionKey()
    {
        $sessionKey = self::getCurrentTokenVar('session_key');
        return $sessionKey;
    }

    /**
     * 檢查操作UID是否合法
     * @param $checkedUserID
     * @return bool
     */
    public static function isValidOperate($checkedUserID)
    {
        if(!$checkedUserID){
            return false;
        }
        $currentOperateUID = self::getCurrentUserID();
        if($currentOperateUID == $checkedUserID){
            return true;
        }
        return false;
    }

UserService.php 數(shù)據(jù)庫(kù)操作業(yè)務(wù)

<?php
/**
 * Created by PhpStorm.
 * User: j
 * Date: 2019-04-16
 * Time: 11:19
 */

class UserService
{
    /************************************************** 數(shù)據(jù)查詢(xún) **************************************************/

    /**
     * 根據(jù)主鍵ID進(jìn)行查詢(xún)
     * @param $userID
     * @return UserNn
     */
    public static function getUserID($userID)
    {
        return UserNn::model()->findByPk($userID);
    }

    /**
     * 根據(jù)openID查詢(xún)一條數(shù)據(jù)
     * @param $openID
     * @return UserNn
     */
    public static function getByOpenId($openID)
    {
        return UserNn::model()->find('openid = ?', array($openID));
    }

    /************************************************** 數(shù)據(jù)更新 **************************************************/
    /**
     * 根據(jù)openID新增一條用戶(hù)數(shù)據(jù)
     * @param $wechatResult
     * @return mixed
     */
    public static function newUser($wechatResult)
    {
        $userModel = new UserNn;
        $userModel->openid     = $wechatResult['openid'];
        $userModel->unionid    = $wechatResult['unionid'] ? $wechatResult['unionid'] : '';
        $userModel->createDate = date('Y-m-d H:i:s');
        $userModel->updateDate = date('Y-m-d H:i:s');
        $userModel->save();
        // 返回用戶(hù)自增ID  
        return $userModel->attributes['ID'];
    }

    /**
     * 更新微信unionid
     * @param $user
     * @param $wechatResult
     * @return bool
     */
    public static function updateUser($user, $wechatResult)
    {
        $user->unionid    = $wechatResult['unionid'] ? $wechatResult['unionid'] : '';
        $user->updateDate = date('Y-m-d H:i:s');
        $user->save();
        return true;
    }

用戶(hù)表結(jié)構(gòu)設(shè)計(jì)

CREATE TABLE `tbl_userNn` (
  `ID` int(11) NOT NULL AUTO_INCREMENT COMMENT 'ID',
  `mobile` varchar(16) NOT NULL DEFAULT '' COMMENT '用戶(hù)手機(jī)號(hào)',
  `unionid` varchar(255) NOT NULL DEFAULT '' COMMENT '微信開(kāi)放平臺(tái)帳號(hào)關(guān)聯(lián)ID',
  `openid` varchar(255) NOT NULL DEFAULT '' COMMENT '微信openid',
  `flagDeleted` tinyint(1) NOT NULL DEFAULT '1' COMMENT '已刪除 1.正常 -1.刪除',
  `createDate` datetime DEFAULT NULL COMMENT '創(chuàng)建時(shí)間',
  `updateDate` datetime DEFAULT NULL COMMENT '修改時(shí)間',
  PRIMARY KEY (`ID`),
  KEY `mobile` (`mobile`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4 COMMENT='用戶(hù)表';

到這,不知道大家是否理解業(yè)務(wù)是怎么設(shè)計(jì)的。

首先:需要小程序前端給我們服務(wù)端的PHP接口傳遞code

其次:我們使用code拼接微信官方提供的URL地址,進(jìn)行HTTP請(qǐng)求,獲得微信返回的參數(shù)

然后:使用參數(shù)中的openid進(jìn)行數(shù)據(jù)庫(kù)記錄查詢(xún),如果存在著使用該ID作為userID,如果不存在則新增返回自增ID

接著:將用戶(hù)的userID與微信返回的參數(shù)一起拼接成新的數(shù)字,將數(shù)組轉(zhuǎn)化json,進(jìn)行緩存key就是生成的Token,Token的實(shí)效為7200秒,過(guò)期后緩存清楚

最后:Token就是平臺(tái)中所使用的用戶(hù)令牌,該Token在業(yè)務(wù)中起到用戶(hù)身份標(biāo)識(shí)的作用,所有業(yè)務(wù)完成之后,將Token返回給前端,前端當(dāng)其使用的接口需要用戶(hù)信息時(shí),將Token以Headers傳遞即可

接口返回實(shí)例

{
  "code": 200,
  "message": "Success",
  "result": {
    "token": "59f3af623aee5b1607a1752ef3b0e5c4"
  }
}

為什么使用Token而不直接使用userID,幾個(gè)點(diǎn)考慮吧:

  • 安全性:直接暴露出userID首先別人一眼就能看出,而且userID基本上是數(shù)據(jù)庫(kù)主鍵,不合適
  • 時(shí)效性:安全性的延伸,會(huì)過(guò)期,不好仿照
  • 專(zhuān)業(yè)性:去面試面試官問(wèn)你怎么生成令牌,起碼這么說(shuō)下來(lái),比你說(shuō)我直接返回userID聽(tīng)起來(lái)有逼格有格調(diào)吧?
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀(guān)點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容