'https://api.weixin.qq.com/sns/oauth2/access_token?appid=%s&secret=%s&code=%s&grant_type=authorization_code', // 授权跳转地址 'authorize'=>'https://open.weixin.qq.com/connect/oauth2/authorize?appid=%s&redirect_uri=%s&response_type=code&scope=snsapi_userinfo&state=%s#wechat_redirect', // 获取token 'getToken'=>'https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=%s&secret=%s', // 获取二维码 'getQrcode'=>'https://api.weixin.qq.com/wxa/getwxacodeunlimit?access_token=%s&scene=%s&page=%s&env_version=%s', // 获取用户信息 'getUserInfo'=>'https://api.weixin.qq.com/sns/jscode2session', // 获取手机号 'getPhoneNumber'=>'https://api.weixin.qq.com/wxa/business/getuserphonenumber?access_token=%s', // 获取客服token 'getServiceToken'=>'https://qyapi.weixin.qq.com/cgi-bin/gettoken?corpid=%s&corpsecret=%s', // 获取客服地址 'getServiceUrl'=>'https://qyapi.weixin.qq.com/cgi-bin/kf/add_contact_way?access_token=%s', ]; public function __construct() { $this->mpAppid = ConfigService::make()->getConfigByCode('wechat_mp_appid'); $this->mpAppSecret = ConfigService::make()->getConfigByCode('wechat_mp_appsecret'); $this->corpid = ConfigService::make()->getConfigByCode('wechat_corpid'); $this->corpsecret = ConfigService::make()->getConfigByCode('wechat_corpsecret'); } /** * 静态入口 * @return static|null */ public static function make() { if (!self::$instance) { self::$instance = new static(); } return self::$instance; } /** * web 授权 * @param $code * @return array|false|mixed|string|string[] */ public function auth($code) { try { if(empty($this->mpAppid) || empty($this->mpAppSecret)){ $this->error = '小程序参数未配置'; return false; } // 没有code参数 if(empty($code)){ $uri=urlencode(env("WEB_URL")."/v1/mpAuth"); $state=get_random_code(16); $url= sprintf($this->apiUrls['authorize'], $this->mpAppid, $this->mpAppSecret, $uri, $state); return redirect($url); } $cacheKey = "caches:mpApp:mp_{$this->mpAppid}:"; $url = sprintf($this->apiUrls['auth'],$this->mpAppid, $this->mpAppSecret, $code); $result = httpRequest($url, '', 'get','',5); $this->saveLog($cacheKey.'auth:request', ['url'=>$url,'code'=>$code,'result'=>$result,'date'=>date('Y-m-d H:i:s')]); if(empty($result)){ $this->error = '授权登录失败'; return ""; } return $result; }catch (\Exception $e){ $this->error = $e->getMessage(); $this->saveLog($cacheKey.'auth:error', ['code'=>$code,'error'=>$this->error,'trace'=>$e->getTrace(),'date'=>date('Y-m-d H:i:s')]); return false; } } /** * 获取 access_token * @param false $refresh * @return false|mixed|string */ public function getAccessToken($refresh = false) { try { if(empty($this->mpAppid) || empty($this->mpAppSecret)){ $this->error = '小程序参数未配置'; return false; } $cacheKey = "caches:mpApp:mp_{$this->mpAppid}:"; $tokenData = RedisService::get($cacheKey.'access_token'); $token = isset($tokenData['access_token'])? $tokenData['access_token'] : ''; if($token && !$refresh){ return $token; } $url = sprintf($this->apiUrls['getToken'], $this->mpAppid, $this->mpAppSecret); $result = httpRequest($url,'', 'get','',5); $this->saveLog($cacheKey.'tokens:request', ['url'=>$url,'result'=>$result,'date'=>date('Y-m-d H:i:s')]); $token = isset($result['access_token'])? $result['access_token'] : ''; if(empty($result) || empty($token)){ $this->error = '获取小程序TOKEN失败'; return false; } $result['date'] = date('Y-m-d H:i:s'); RedisService::set($cacheKey.'access_token', $result, 7000); return $token; }catch (\Exception $e){ $this->error = $e->getMessage(); $this->saveLog($cacheKey.'tokens:error', ['error'=>$this->error,'trace'=>$e->getTrace(),'date'=>date('Y-m-d H:i:s')]); return false; } } /** * 小程序二维码 * @param $page 页面 * @param $scene 场景参数 * @param string $version 类型:release-永久 * @param false $refresh * @return false|string */ public function getMiniQrcode($page, $scene, $version='release', $refresh=false) { if (!in_array($version,['release','trial','develop'])) { $version='release'; } try { if(empty($page) || empty($scene)){ $this->error = '缺少二维码参数'; return false; } if(empty($this->mpAppid) || empty($this->mpAppSecret)){ $this->error = '小程序参数未配置'; return false; } if(!$token = $this->getAccessToken()) { $this->error = '获取token失败'; return false; } $cacheKey = "caches:mpQrcode:mp_{$this->mpAppid}:"; $filePath = base_path('public/uploads'); $qrFile = '/qrcodes/mp_'.date("YmdHis")."_".md5($page.$scene).".png"; $qrKey = md5($qrFile); if(RedisService::get($cacheKey.$qrKey) && file_exists($filePath.'/'.$qrFile) && !$refresh){ return $qrFile; } if(!is_dir($filePath.'/qrcodes/')){ @mkdirs($filePath.'/qrcodes/'); } $url = sprintf($this->apiUrls['getQrcode'], $token, $scene, $page, $version); $result = curl_post($url, ''); $datas = $result? json_decode($result, true) : []; $this->saveLog($cacheKey.'qrcode:request', ['page'=>$page,'scene'=>$scene,'url'=>$url,'result'=>$result,'date'=>date('Y-m-d H:i:s')]); $errcode = isset($datas['errcode'])? $datas['errcode'] : ''; $errmsg = isset($datas['errmsg'])? $datas['errmsg'] : ''; if($errcode){ $this->error = $errmsg? $errmsg : '获取二维码失败'; return false; } file_put_contents($filePath.'/'.$qrFile, $result); if(!file_exists($filePath.'/'.$qrFile)){ $this->error = '保存二维码失败'; return false; } $result['date'] = date('Y-m-d H:i:s'); RedisService::set($cacheKey.$qrKey, ['page'=>$page,'scene'=>$scene,'qrcode'=>$qrFile,'date'=>date('Y-m-d H:i:s')], 30 * 86400); return $qrFile; }catch (\Exception $e){ $this->error = $e->getMessage(); $this->saveLog($cacheKey.'qrcode:error', ['page'=>$page,'scene'=>$scene,'error'=>$this->error,'trace'=>$e->getTrace(),'date'=>date('Y-m-d H:i:s')]); return false; } } /** * 获取用户信息 * @param $code * @return array|false|mixed|string[] */ public function getUserinfo($code) { try { if(empty($code)){ $this->error = '缺少授权参数'; return false; } if(empty($this->mpAppid) || empty($this->mpAppSecret)){ $this->error = '小程序参数未配置'; return false; } $data=[ 'appid' => $this->mpAppid, 'secret'=> $this->mpAppSecret, 'js_code'=>$code, 'grant_type'=>'authorization_code' ]; $cacheKey = "caches:mpApp:mp_{$this->mpAppid}:"; $url = $this->apiUrls['getUserInfo']; $result = httpRequest($url, $data,'get','',5); $this->saveLog($cacheKey.'userInfo:request', ['code'=>$code,'url'=>$url,'query'=>$data,'result'=>$result,'date'=>date('Y-m-d H:i:s')]); if(empty($result)){ $this->error = '获取用户信息失败'; return false; } return $result; }catch (\Exception $e){ $this->error = $e->getMessage(); $this->saveLog($cacheKey.'userInfo:error', ['code'=>$code,'error'=>$this->error,'trace'=>$e->getTrace(),'date'=>date('Y-m-d H:i:s')]); return false; } } /** * 获取用户手机号码 * @param $code * @return array|false|mixed|string[] */ public function getPhoneNumber($code) { try { if(empty($code)){ $this->error = '缺少授权参数'; return false; } if(empty($this->mpAppid) || empty($this->mpAppSecret)){ $this->error = '小程序参数未配置'; return false; } if(!$token = $this->getAccessToken()) { $this->error = '获取token失败'; return false; } $cacheKey = "caches:mpApp:mp_{$this->mpAppid}:"; $url = sprintf($this->apiUrls['getPhoneNumber'], $token); $result = httpRequest($url, json_encode(['code'=>$code],256),'post','',5); $this->saveLog($cacheKey.'phone:request', ['code'=>$code,'url'=>$url,'result'=>$result,'date'=>date('Y-m-d H:i:s')]); if(empty($result)){ $this->error = '获取用户手机号失败'; return false; } return $result; }catch (\Exception $e){ $this->error = $e->getMessage(); $this->saveLog($cacheKey.'phone:error', ['code'=>$code,'error'=>$this->error,'trace'=>$e->getTrace(),'date'=>date('Y-m-d H:i:s')]); return false; } } /** * 获取 客服接口 access_token * @param false $refresh * @return false|mixed|string */ public function getServiceToken($refresh = false) { try { if(empty($this->mpAppid) || empty($this->mpAppSecret)){ $this->error = '小程序参数未配置'; return false; } $cacheKey = "caches:mpApp:mp_{$this->mpAppid}:"; $tokenData = RedisService::get($cacheKey.'kf_access_token'); $token = isset($tokenData['access_token'])? $tokenData['access_token'] : ''; if($token && !$refresh){ return $token; } $url = sprintf($this->apiUrls['getServiceToken'], $this->mpAppid, $this->mpAppSecret); $result = httpRequest($url,'', 'get','',5); $this->saveLog($cacheKey.'kfTokens:request', ['url'=>$url,'result'=>$result,'date'=>date('Y-m-d H:i:s')]); $token = isset($result['access_token'])? $result['access_token'] : ''; if(empty($result) || empty($token)){ $this->error = '获取小程序客服TOKEN失败'; return false; } $result['date'] = date('Y-m-d H:i:s'); RedisService::set($cacheKey.'kf_access_token', $result, 7000); return $token; }catch (\Exception $e){ $this->error = $e->getMessage(); $this->saveLog($cacheKey.'kfTokens:error', ['error'=>$this->error,'trace'=>$e->getTrace(),'date'=>date('Y-m-d H:i:s')]); return false; } } /** * 获取客服地址 * @return array|false|mixed|string[] */ public function getServiceUrl() { try { if(empty($this->mpAppid) || empty($this->mpAppSecret)){ $this->error = '小程序参数未配置'; return false; } if(!$token = $this->getServiceToken()) { $this->error = '获取token失败'; return false; } $cacheKey = "caches:mpApp:mp_{$this->mpAppid}:"; $url = sprintf($this->apiUrls['getServiceUrl'], $token); $result = httpRequest($url, '','post','',5); $this->saveLog($cacheKey.'service:request', ['url'=>$url,'result'=>$result,'date'=>date('Y-m-d H:i:s')]); if(empty($result)){ $this->error = '获取小程序客服地址失败'; return false; } return $result; }catch (\Exception $e){ $this->error = $e->getMessage(); $this->saveLog($cacheKey.'service:error', ['error'=>$this->error,'trace'=>$e->getTrace(),'date'=>date('Y-m-d H:i:s')]); return false; } } /** * 保存日志 * @param $cackekey * @param $data * @param $time */ public function saveLog($cackekey, $data, $time=0) { if($this->debug){ RedisService::set($cackekey, $data, $time?$time : $this->expireTime); } } }