微信小程序 - 登录 - code2Session - 服务端PHP开发

这段时间因为应公司需要去开发小程序业务,从0到有开始一个全新的电商平台开发,这类微信第三方的开发其实在百度、知乎、简书等多个平台都能搜到相对应的代码,今天只是把这类代码以正常人看得懂角度进行分享。

强调一下,这里笔者应用的是YII1.0,框架没啥用到内部的方法,主要还是面向对象的思维,如果框架不同唯独就是框架的配置与查询数据库方式还有文件层级目录的不同,咱们学习的时候脱离框架思维运用面向对象角度看待即可

第一步操作

小程序这边需要进行 wx.login文档 中指出的使用wx.login调用接口获取登录凭证(code)。

什么意思呢?微信的登录无论是小程序还是公众号登录都是需要code作为请求授权的参数

    // 登录
    wx.login({
      success: res => {
        console.log(res.code)  // 就这么简单就能拿到微信的code,同时发起业务请求
      }
    })

拿到code之后,我们就需要授权了,调用后端提供的接口,传参只需要给定一个code参数即可

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'])) {
            // 校验参数是否传递,根据个人业务理解设计即可,错误返回业务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 业务文件

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

class UserTokenService extends TokenService
{
    /**
     * @var code 临时登录凭证 code
     */
    protected $code;

    /**
     * @var expireDate 过期时间
     */
    protected $expireDate;

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

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

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

    /**
     * @var wechatLoginUrl 请求地址
     */
    protected $wechatLoginUrl;

    /**
     * 构造函数主要是将微信配置项读取出来,同时做一下声明
     * 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);
    }

    /**
     * 发起微信小程序登录请求
     * @return array|bool
     * @throws Exception
     */
    public function get()
    {
        $result = NnCommon::curlGet($this->wechatLoginUrl);
        $wechatResult = json_decode($result, true);
        if (empty($wechatResult)) {
            throw new Exception('微信内部错误,获取openID发生异常');
        } else {
            $loginFail = array_key_exists('errcode', $wechatResult);
            // 微信调用失败
            if ($loginFail) {
                // 返回false,记录错误日志内容,方便查询问题
                return false;
            } else {
                return $this->grantToken($wechatResult);
            }
        }
    }

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

        // 查询数据库,判断openID是否存在  存在则不处理,否则新增
        $user = UserService::getByOpenId($openID);

        if ($user) {
            $userId = $user->ID;
            // 当unionid为空,并且微信返回时,我们单独去做一次处理
            if (empty($user->unionid) && !empty($wechatResult['unionid'])) {
                UserService::updateUser($user, $wechatResult);
            }
        } else {
            $userId = UserService::newUser($wechatResult);
        }

        // 创建令牌写入缓存 key:token value:wxResult,userId,scope
        $cachedValue = $this->prepareCachedValue($wechatResult, $userId);

        // 返回令牌至客户端
        $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;
    }

    /**
     * 将数据存入缓存
     * @param $cachedValue
     * @return bool|string
     */
    private function saveToCache($cachedValue)
    {
        $key        = self::generateToken();
        $value      = json_encode($cachedValue);
        $expireDate = $this->expireDate;

        // 这里我使用的是memcached,根据自己情况而定
        $request = UtilCache::setCache($key, $value, $expireDate);
        if (!$request) {
            // 记录日志
            MonoLog::setCacheError('用户数据缓存失败', $cachedValue);
            return false;
        }

        return $key;
    }
}

TokenService.php 工具类业务

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

class TokenService
{
    /**
     * 随机字符串,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;
    }
    /**
     * 随机32位三项定义md5()字符串
     * @return string
     */
    public static function generateToken()
    {
        // 随机32位字符串
        $randChars = NnCommon::getRandChar(32);
        // 时间戳
        $timeStamp = time();
        // salt 盐
        $salt = Secure::$tokenSalt;

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

    /**
     * 从缓冲中根据key获取对应所需的内容
     * @param $key
     * @return bool|mixed
     */
    public static function getCurrentTokenVar($key)
    {
        // 从HTTP请求头中获取用户Token
        $token = $_SERVER['HTTP_TOKEN'];

        // 缓冲中获取用户数据
        $vars  = UtilCache::getCache($token);
        if (!$vars) {
            return false;
        }

        // 防止取出就是数组
        if (!is_array($vars)) {
            $vars = json_decode($vars, true);
        }

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

        return false;
    }

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

    /**
     * 获取用户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 数据库操作业务

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

class UserService
{
    /************************************************** 数据查询 **************************************************/

    /**
     * 根据主键ID进行查询
     * @param $userID
     * @return UserNn
     */
    public static function getUserID($userID)
    {
        return UserNn::model()->findByPk($userID);
    }

    /**
     * 根据openID查询一条数据
     * @param $openID
     * @return UserNn
     */
    public static function getByOpenId($openID)
    {
        return UserNn::model()->find('openid = ?', array($openID));
    }

    /************************************************** 数据更新 **************************************************/
    /**
     * 根据openID新增一条用户数据
     * @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();
        // 返回用户自增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;
    }

用户表结构设计

CREATE TABLE `tbl_userNn` (
  `ID` int(11) NOT NULL AUTO_INCREMENT COMMENT 'ID',
  `mobile` varchar(16) NOT NULL DEFAULT '' COMMENT '用户手机号',
  `unionid` varchar(255) NOT NULL DEFAULT '' COMMENT '微信开放平台帐号关联ID',
  `openid` varchar(255) NOT NULL DEFAULT '' COMMENT '微信openid',
  `flagDeleted` tinyint(1) NOT NULL DEFAULT '1' COMMENT '已删除 1.正常 -1.删除',
  `createDate` datetime DEFAULT NULL COMMENT '创建时间',
  `updateDate` datetime DEFAULT NULL COMMENT '修改时间',
  PRIMARY KEY (`ID`),
  KEY `mobile` (`mobile`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4 COMMENT='用户表';

到这,不知道大家是否理解业务是怎么设计的。

首先:需要小程序前端给我们服务端的PHP接口传递code

其次:我们使用code拼接微信官方提供的URL地址,进行HTTP请求,获得微信返回的参数

然后:使用参数中的openid进行数据库记录查询,如果存在着使用该ID作为userID,如果不存在则新增返回自增ID

接着:将用户的userID与微信返回的参数一起拼接成新的数字,将数组转化json,进行缓存key就是生成的Token,Token的实效为7200秒,过期后缓存清楚

最后:Token就是平台中所使用的用户令牌,该Token在业务中起到用户身份标识的作用,所有业务完成之后,将Token返回给前端,前端当其使用的接口需要用户信息时,将Token以Headers传递即可

接口返回实例

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

为什么使用Token而不直接使用userID,几个点考虑吧:

  • 安全性:直接暴露出userID首先别人一眼就能看出,而且userID基本上是数据库主键,不合适
  • 时效性:安全性的延伸,会过期,不好仿照
  • 专业性:去面试面试官问你怎么生成令牌,起码这么说下来,比你说我直接返回userID听起来有逼格有格调吧?
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 205,033评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,725评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,473评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,846评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,848评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,691评论 1 282
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,053评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,700评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 42,856评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,676评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,787评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,430评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,034评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,990评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,218评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,174评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,526评论 2 343

推荐阅读更多精彩内容