_AccessToken($refresh); } /** * 获取JsApiTicket * * @param boolean $refresh 是否强制刷新 * @return void * @date 2020-05-29 * @example * @author arw * @since 1.0.0 */ public function GetJsApiTicket($refresh = false) { return $this->_JsApiTicket($refresh); } /** * 网页授权/获取用户基本信息 * https://developers.weixin.qq.com/doc/offiaccount/OA_Web_Apps/Wechat_webpage_authorization.html * @param array $config 配置项 [ * "scope" => "snsapi_base", 应用授权作用域, * snsapi_base (不弹出授权页面,直接跳转,只能获取用户openid), * snsapi_userinfo (弹出授权页面,可通过openid拿到昵称、性别、所在地。并且, 即使在未关注的情况下,只要用户授权,也能获取其信息 ) * "state" => "", 重定向后会带上state参数,开发者可以填写a-zA-Z0-9的参数值,最多128字节 * "lang" => "zh_CN", 当scope等于snsapi_userinfo才生效 * 返回国家地区语言版本,zh_CN 简体,zh_TW 繁体,en 英语 * ] * @param callable $success 回调用户授权成功调用函数 ($userinfo,$state) * @param callable $error 执行错误回调 ($msg) * @return null */ public function authorize(array $config, callable $success, callable $error) { try { $config["scope"] = Arr::get($config, "scope", "snsapi_base"); $config["state"] = Arr::get($config, "state", ""); $config["lang"] = Arr::get($config, "lang", "zh_CN"); $config["redirect_uri"] = Arr::get($config, "redirect_uri", Request::url(true)); $code = Arr::get($_GET, "code"); if (!$code) { $url = "https://open.weixin.qq.com/connect/oauth2/authorize"; $url .= "?" . http_build_query([ "appid" => $this->appid, "redirect_uri" => $config["redirect_uri"], "response_type" => "code", "scope" => $config["scope"], "state" => $config["state"], ]); $url .= "#wechat_redirect"; return Response::create($url, 'redirect', 302); } $curl = Curl::instance(); $param = []; $param["appid"] = $this->appid; $param["secret"] = $this->secret; $param["code"] = $code; $param["grant_type"] = "authorization_code"; $url = "https://api.weixin.qq.com/sns/oauth2/access_token"; $access_token = $curl->setParams($param)->get($url); $judge = (Arr::get($access_token, "access_token") && Arr::get($access_token, "openid")); if (!$judge || !$access_token) { $this->log([ 'action' => __FUNCTION__, 'url' => $curl->getUrl(), 'params' => $curl->getParams(), 'content' => $curl->getContent(), 'msg' => '获取access_token失败', ], 'error'); return $error("获取access_token失败"); } if ($config["scope"] === "snsapi_userinfo") { $curl = Curl::instance(); $url = "https://api.weixin.qq.com/sns/userinfo"; $param = []; $param["access_token"] = Arr::get($access_token, "access_token"); $param["openid"] = Arr::get($access_token, "openid"); $param["lang"] = $config["lang"]; $user_info = $curl->setParams($param)->get($url); $errcode = Arr::get($user_info, "errcode", false); if ($errcode !== false) { $this->log([ 'action' => __FUNCTION__, 'url' => $curl->getUrl(), 'params' => $curl->getParams(), 'result' => $curl->getContent(), 'msg' => '获取用户信息失败', ], 'error'); return $error("获取微信用户信息失败"); } $access_token = array_merge($access_token, $user_info); } return $success($access_token, $_GET["state"]); } catch (\Throwable $th) { $this->log([ 'action' => __FUNCTION__, 'msg' => '客户端错误:' . $th->getMessage(), 'line' => $th->getLine(), 'file' => $th->getFile(), ], 'error'); return $error($th->getMessage()); } } /** * 微信模板消息推送 * 微信返回errcode等于40001会更新缓存AccessToken * 并重新推送一次 * https://developers.weixin.qq.com/doc/offiaccount/Message_Management/Template_Message_Interface.html#5 * @param string $touser 推送给谁 * @param string $template_id 模板名称 * @param array $data 推送数据 * [ * "touser" => "openid", * "template_id" => "template_id", * "url" => "url", * "miniprogram" => [ * "appid" => "appid", * "pagepath" => "pagepath", * ], * "data" => [ * "first" => [ * "value" => "value", * "color" => "color", * ], * "keyword1" => [ * "value" => "value", * "color" => "color", * ], * "keyword2" => [ * "value" => "value", * "color" => "color", * ], * "keyword3" => [ * "value" => "value", * "color" => "color", * ], * "remark" => [ * "value" => "value", * "color" => "color", * ], * ], * ] * @param boolean $refresh 是否刷新【 access_token 】 * @param integer $limit 调用上限(允许自动重试次数) * @return bool * @date 2020-05-26 * @example * @author arw * @since 1.1.1 */ public function TemplateMessageInterface($touser = null, $template_id = null, array $data, $refresh = false, $limit = 3) { $this->log([ 'action' => __FUNCTION__, 'touser' => $touser, 'template_id' => $template_id, 'data' => $data, ], 'log'); try { $template = $this->template; if (!$touser) { if (isset($data["touser"])) { $touser = $data["touser"]; } } if (!$template_id) { if (isset($data["template_id"])) { $template_id = $data["template_id"]; } } if (empty($touser)) { throw new \Exception("data.touser不能为空", 1); } if (empty($template_id)) { throw new \Exception("data.template_id不能为空", 1); } else { if (isset($template[$template_id])) { $template_id = $template[$template_id]; } } $data["touser"] = $touser; $data["template_id"] = $template_id; $limit--; if ($limit <= 0) { throw new \Exception("超过调用上限", 1); } $access_token = $this->_AccessToken($refresh); if (!$access_token) { return false; } $appid = Arr::get($data, "miniprogram.appid"); if ($appid == 'APPID') { Arr::set($data, 'miniprogram.appid', Config::get('xcx.appid')); } $param = json_encode($data); if (!isset(self::$TemplateMessageInterfaceCache[$param])) { self::$TemplateMessageInterfaceCache[$param] = []; } if (in_array($touser, self::$TemplateMessageInterfaceCache[$param])) { throw new \Exception("重复推送", 1); } $url = "https://api.weixin.qq.com/cgi-bin/message/template/send"; $url .= "?access_token=" . $access_token; $curl = Curl::instance(); $send = $curl->setData($param)->post($url); $errcode = Arr::get($send, "errcode", false); if ($errcode === 0) { self::$TemplateMessageInterfaceCache[$param][] = $touser; return true; } if ($errcode === 40001) { return $this->TemplateMessageInterface($touser, $template_id, $data, true, $limit); } $this->log([ 'action' => __FUNCTION__, 'url' => $curl->getUrl(), 'params' => $curl->getData(), 'content' => $curl->getContent(), 'msg' => '模板消息推送失败', ], 'error'); } catch (\Throwable $th) { $this->log([ 'action' => __FUNCTION__, 'msg' => '客户端错误:' . $th->getMessage(), 'line' => $th->getLine(), 'file' => $th->getFile(), ], 'error'); } return false; } /** * Jssdk授权 * @param string $url 授权url * @return null */ public function JsSdk(string $url = "") { $jsapiTicket = $this->_JsApiTicket(); $protocol = (!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off' || $_SERVER['SERVER_PORT'] == 443) ? "https://" : "http://"; if (!$url) { $url = "$protocol$_SERVER[HTTP_HOST]$_SERVER[REQUEST_URI]"; } $timestamp = time(); $chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"; $nonceStr = ""; for ($i = 0; $i < 16; $i++) { $nonceStr .= substr($chars, mt_rand(0, strlen($chars) - 1), 1); } // 这里参数的顺序要按照 key 值 ASCII 码升序排序 $string = "jsapi_ticket=$jsapiTicket&noncestr=$nonceStr×tamp=$timestamp&url=$url"; $signature = sha1($string); $signPackage = [ "appId" => $this->appid, "nonceStr" => $nonceStr, "timestamp" => $timestamp, "url" => $url, "signature" => $signature, "rawString" => $string, ]; return $signPackage; } }