这段时间因为应公司需要去开发小程序业务,从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听起来有逼格有格调吧?