349 lines
9.8 KiB
PHP
349 lines
9.8 KiB
PHP
<?php
|
||
|
||
namespace app\common\arw\adjfut\src\WeChat;
|
||
|
||
use app\common\arw\adjfut\src\Curl;
|
||
use app\common\arw\adjfut\src\WeChat\GzhCommon;
|
||
use think\facade\Config;
|
||
use think\facade\Request;
|
||
use think\helper\Arr;
|
||
use think\Response;
|
||
|
||
class Gzh extends GzhCommon
|
||
{
|
||
/**
|
||
* 当前实例
|
||
*
|
||
* @var $this
|
||
*/
|
||
private static $instance = null;
|
||
|
||
/**
|
||
* 推送缓存 防止重复推送同样的数据
|
||
*
|
||
* @var array
|
||
* @date 2021-10-11
|
||
* @example
|
||
* @author arw
|
||
* @since 1.0.0
|
||
*/
|
||
private static $TemplateMessageInterfaceCache = [];
|
||
/**
|
||
* 初始化
|
||
*
|
||
* @date 2022-05-18
|
||
* @example
|
||
* @author arw
|
||
* @since 1.0.0
|
||
*/
|
||
public function __construct()
|
||
{
|
||
parent::__construct();
|
||
}
|
||
|
||
/**
|
||
* 实例
|
||
*
|
||
* @return self
|
||
* @date 2022-05-18
|
||
* @example
|
||
* @author arw
|
||
* @since 1.0.0
|
||
*/
|
||
public static function instance(): self
|
||
{
|
||
if (!self::$instance) {
|
||
self::$instance = new self;
|
||
}
|
||
return self::$instance;
|
||
}
|
||
|
||
/**
|
||
* 获取AccessToken
|
||
*
|
||
* @param boolean $refresh 是否强制刷新
|
||
* @return void
|
||
* @date 2020-05-29
|
||
* @example
|
||
* @author arw
|
||
* @since 1.0.0
|
||
*/
|
||
public function GetAccessToken($refresh = false)
|
||
{
|
||
return $this->_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;
|
||
}
|
||
}
|