first commit
This commit is contained in:
@@ -0,0 +1,228 @@
|
||||
<?php
|
||||
namespace lib\wechat;
|
||||
|
||||
use Exception;
|
||||
|
||||
class WeWorkAPI
|
||||
{
|
||||
private $wid;
|
||||
private $accessToken;
|
||||
|
||||
public function __construct($id)
|
||||
{
|
||||
$this->wid = $id;
|
||||
}
|
||||
|
||||
public function getAccessToken($force = false)
|
||||
{
|
||||
global $DB;
|
||||
if(!empty($this->accessToken)) return $this->accessToken;
|
||||
$DB->beginTransaction();
|
||||
try{
|
||||
$row = $DB->getRow("SELECT * FROM pre_wework WHERE id='{$this->wid}' LIMIT 1 FOR UPDATE");
|
||||
if(!$row) throw new Exception('当前企业微信不存在');
|
||||
if($row['access_token'] && strtotime($row['expiretime']) - 200 >= time() && !$force){
|
||||
$DB->rollback();
|
||||
$this->accessToken = $row['access_token'];
|
||||
return $this->accessToken;
|
||||
}
|
||||
$corpId = $row['appid'];
|
||||
$secret = $row['appsecret'];
|
||||
$url = "https://qyapi.weixin.qq.com/cgi-bin/gettoken?corpid=".$corpId."&corpsecret=".$secret;
|
||||
$output = get_curl($url);
|
||||
$res = json_decode($output, true);
|
||||
if (isset($res['access_token'])) {
|
||||
$this->accessToken = $res['access_token'];
|
||||
$expire_time = time() + $res['expires_in'];
|
||||
$DB->exec("UPDATE pre_wework SET access_token=:access_token,updatetime=NOW(),expiretime=:expiretime WHERE id=:id", [':access_token'=>$this->accessToken, ':expiretime'=>date("Y-m-d H:i:s", $expire_time), ':id'=>$this->wid]);
|
||||
}elseif(isset($res['errmsg'])){
|
||||
throw new Exception('AccessToken获取失败:'.$res['errmsg']);
|
||||
}else{
|
||||
throw new Exception('AccessToken获取失败');
|
||||
}
|
||||
$DB->commit();
|
||||
return $this->accessToken;
|
||||
}catch(Exception $e){
|
||||
$DB->rollback();
|
||||
throw $e;
|
||||
}
|
||||
}
|
||||
|
||||
//获取客服帐号列表
|
||||
public function getKFList()
|
||||
{
|
||||
$accessToken = $this->getAccessToken();
|
||||
$url = 'https://qyapi.weixin.qq.com/cgi-bin/kf/account/list?access_token='.$accessToken;
|
||||
$post = ['offset'=>0, 'limit'=>100];
|
||||
$response = get_curl($url, json_encode($post));
|
||||
$result = json_decode($response, true);
|
||||
if ($result['errcode'] == 0) {
|
||||
return $result['account_list'];
|
||||
}else{
|
||||
throw new Exception('客服帐号列表获取失败:'.$result['errmsg']);
|
||||
}
|
||||
}
|
||||
|
||||
//获取微信客服链接
|
||||
public function getKFURL($kfid, $scene)
|
||||
{
|
||||
$accessToken = $this->getAccessToken();
|
||||
$url = 'https://qyapi.weixin.qq.com/cgi-bin/kf/add_contact_way?access_token='.$accessToken;
|
||||
$post = ['open_kfid'=>$kfid, 'scene'=>$scene];
|
||||
$response = get_curl($url, json_encode($post));
|
||||
$result = json_decode($response, true);
|
||||
if ($result['errcode'] == 0 && $result['url']) {
|
||||
return $result['url'];
|
||||
}else{
|
||||
throw new Exception('微信客服链接获取失败:'.$result['errmsg']);
|
||||
}
|
||||
}
|
||||
|
||||
//读取消息
|
||||
public function syncMsg($kfid, $token, $cursor = '')
|
||||
{
|
||||
$accessToken = $this->getAccessToken();
|
||||
$url = 'https://qyapi.weixin.qq.com/cgi-bin/kf/sync_msg?access_token='.$accessToken;
|
||||
$post = ['cursor'=>$cursor, 'token'=>$token, 'open_kfid'=>$kfid];
|
||||
$response = get_curl($url, json_encode($post));
|
||||
$result = json_decode($response, true);
|
||||
if ($result['errcode'] == 0) {
|
||||
return $result;
|
||||
}else{
|
||||
throw new Exception('客服消息列表获取失败:'.$result['errmsg']);
|
||||
}
|
||||
}
|
||||
|
||||
//加锁获取最新消息
|
||||
public function lockGetMsg($kfid, $token)
|
||||
{
|
||||
global $DB;
|
||||
$this->getAccessToken();
|
||||
$DB->beginTransaction();
|
||||
$wxkfaccount = $DB->getRow("SELECT `id`,`cursor` FROM pre_wxkfaccount WHERE openkfid=:openkfid LIMIT 1 FOR UPDATE", [':openkfid'=>$kfid]);
|
||||
$cursor = $wxkfaccount['cursor'];
|
||||
try{
|
||||
$result = $this->syncMsg($kfid, $token, $cursor?$cursor:'');
|
||||
}catch(Exception $e){
|
||||
$DB->rollBack();
|
||||
throw $e;
|
||||
}
|
||||
$cursor = $result['next_cursor'];
|
||||
$DB->update('wxkfaccount', ['cursor'=>$cursor], ['id'=>$wxkfaccount['id']]);
|
||||
$DB->commit();
|
||||
return $result['msg_list'];
|
||||
}
|
||||
|
||||
//发送消息
|
||||
public function sendMsg($touser, $kfid, $msgtype, $msgparam)
|
||||
{
|
||||
$accessToken = $this->getAccessToken();
|
||||
$url = 'https://qyapi.weixin.qq.com/cgi-bin/kf/send_msg?access_token='.$accessToken;
|
||||
$post = ['touser'=>$touser, 'open_kfid'=>$kfid, 'msgtype'=>$msgtype];
|
||||
$post[$msgtype] = $msgparam;
|
||||
$response = get_curl($url, json_encode($post));
|
||||
$result = json_decode($response, true);
|
||||
if ($result['errcode'] == 0) {
|
||||
return $result['msgid'];
|
||||
}else{
|
||||
throw new Exception('发送消息失败:'.$result['errmsg']);
|
||||
}
|
||||
}
|
||||
|
||||
//发送文本消息
|
||||
public function sendTextMsg($touser, $kfid, $content)
|
||||
{
|
||||
$param = ['content'=>$content];
|
||||
return $this->sendMsg($touser, $kfid, 'text', $param);
|
||||
}
|
||||
|
||||
//发送菜单消息
|
||||
public function sendMenuMsg($touser, $kfid, $head_content, $list, $tail_content = '')
|
||||
{
|
||||
$param = ['head_content'=>$head_content, 'list'=>$list];
|
||||
if($tail_content) $param['tail_content'] = $tail_content;
|
||||
return $this->sendMsg($touser, $kfid, 'msgmenu', $param);
|
||||
}
|
||||
|
||||
//发送欢迎消息
|
||||
public function sendWelcomeMsg($code, $msgtype, $msgparam)
|
||||
{
|
||||
$accessToken = $this->getAccessToken();
|
||||
$url = 'https://qyapi.weixin.qq.com/cgi-bin/kf/send_msg_on_event?access_token='.$accessToken;
|
||||
$post = ['code'=>$code, 'msgtype'=>$msgtype];
|
||||
$post[$msgtype] = $msgparam;
|
||||
$response = get_curl($url, json_encode($post));
|
||||
$result = json_decode($response, true);
|
||||
if ($result['errcode'] == 0) {
|
||||
return $result['msgid'];
|
||||
}else{
|
||||
throw new Exception('发送消息失败:'.$result['errmsg']);
|
||||
}
|
||||
}
|
||||
|
||||
//发送欢迎文本消息
|
||||
public function sendWelcomeTextMsg($code, $content)
|
||||
{
|
||||
$param = ['content'=>$content];
|
||||
return $this->sendWelcomeMsg($code, 'text', $param);
|
||||
}
|
||||
|
||||
//发送欢迎菜单消息
|
||||
public function sendWelcomeMenuMsg($code, $head_content, $list, $tail_content = '')
|
||||
{
|
||||
$param = ['head_content'=>$head_content, 'list'=>$list];
|
||||
if($tail_content) $param['tail_content'] = $tail_content;
|
||||
return $this->sendWelcomeMsg($code, 'msgmenu', $param);
|
||||
}
|
||||
|
||||
public function connect($appid, $callback, $agentid){
|
||||
$url = 'https://open.weixin.qq.com/connect/oauth2/authorize';
|
||||
$param = [
|
||||
"appid" => $appid,
|
||||
"redirect_uri" => $callback,
|
||||
"response_type" => "code",
|
||||
"scope" => "snsapi_base",
|
||||
"agentid" => $agentid
|
||||
];
|
||||
|
||||
$url .= '?'.http_build_query($param)."#wechat_redirect";
|
||||
header("Location: ".$url);
|
||||
exit;
|
||||
}
|
||||
|
||||
public function get_userinfo($code){
|
||||
$accessToken = $this->getAccessToken();
|
||||
$url = 'https://qyapi.weixin.qq.com/cgi-bin/auth/getuserinfo';
|
||||
$param = [
|
||||
"access_token" => $accessToken,
|
||||
"code" => $code
|
||||
];
|
||||
|
||||
$url .= '?'.http_build_query($param);
|
||||
$response = get_curl($url);
|
||||
$arr = json_decode($response, true);
|
||||
if(isset($arr['errcode']) && $arr['errcode'] == 0){
|
||||
return $arr;
|
||||
}else{
|
||||
throw new Exception('获取用户信息失败 ['.$arr['errcode'].']'.$arr['errmsg']);
|
||||
}
|
||||
}
|
||||
|
||||
public function send_message($touser, $agentid, $msgtype, $msgparam){
|
||||
$accessToken = $this->getAccessToken();
|
||||
$url = 'https://qyapi.weixin.qq.com/cgi-bin/message/send?access_token='.$accessToken;
|
||||
$post = [
|
||||
'touser' => $touser,
|
||||
'agentid' => intval($agentid),
|
||||
'msgtype' => $msgtype
|
||||
];
|
||||
$post[$msgtype] = $msgparam;
|
||||
$response = get_curl($url, json_encode($post));
|
||||
$result = json_decode($response, true);
|
||||
if ($result['errcode'] == 0) {
|
||||
return true;
|
||||
}else{
|
||||
throw new Exception('发送消息失败:'.$result['errmsg']);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,103 @@
|
||||
<?php
|
||||
namespace lib\wechat;
|
||||
|
||||
use Exception;
|
||||
|
||||
class WeWorkMsg
|
||||
{
|
||||
private $token;
|
||||
private $crypt;
|
||||
|
||||
public function __construct($token, $aeskey)
|
||||
{
|
||||
global $conf;
|
||||
$this->token = $token;
|
||||
$this->crypt = new WechatCrypt($aeskey);
|
||||
}
|
||||
|
||||
//验证URL有效性
|
||||
public function verifyURL()
|
||||
{
|
||||
if (!$this->verifySignature($_GET['echostr'])) {
|
||||
exit('签名验证失败');
|
||||
}
|
||||
$msg = $this->crypt->decrypt($_GET['echostr']);
|
||||
if(!$msg) exit('消息解密失败');
|
||||
exit($msg);
|
||||
}
|
||||
|
||||
//获取回调的消息内容
|
||||
public function getMessage()
|
||||
{
|
||||
$xml = file_get_contents('php://input');
|
||||
$arr = $this->xml2array($xml);
|
||||
if (!$arr) exit('消息体解析失败');
|
||||
if (!$this->verifySignature($arr['Encrypt'])) {
|
||||
exit('签名验证失败');
|
||||
}
|
||||
$msgtext = $this->crypt->decrypt($arr['Encrypt']);
|
||||
if(!$msgtext) exit('消息解密失败');
|
||||
$msg = $this->xml2array($msgtext);
|
||||
return $msg;
|
||||
}
|
||||
|
||||
//响应消息内容
|
||||
public function responseMessage($array, $corpid)
|
||||
{
|
||||
$xml = $this->array2Xml($array);
|
||||
$encrypted = $this->crypt->encrypt($xml, $corpid);
|
||||
$timestamp = time();
|
||||
$nonce = time();
|
||||
$signature = $this->getSignature($timestamp, $nonce, $encrypted);
|
||||
$array = [
|
||||
'Encrypt' => $encrypted,
|
||||
'MsgSignature' => $signature,
|
||||
'TimeStamp' => $timestamp,
|
||||
'Nonce' => $nonce
|
||||
];
|
||||
echo $this->array2Xml($array);
|
||||
}
|
||||
|
||||
//验证回调签名
|
||||
private function verifySignature($msg_encrypt)
|
||||
{
|
||||
if (!(isset($_GET['msg_signature']) && isset($_GET['timestamp']) && isset($_GET['nonce']))) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$signature = $this->getSignature($_GET['timestamp'], $_GET['nonce'], $msg_encrypt);
|
||||
|
||||
return $signature === $_GET['msg_signature'];
|
||||
}
|
||||
|
||||
//生成SHA1签名
|
||||
private function getSignature($timestamp, $nonce, $encrypt_msg)
|
||||
{
|
||||
$signatureArray = array($encrypt_msg, $this->token, $timestamp, $nonce);
|
||||
sort($signatureArray, SORT_STRING);
|
||||
return sha1(implode($signatureArray));
|
||||
}
|
||||
|
||||
//转为XML数据
|
||||
private function array2Xml($data)
|
||||
{
|
||||
if (!is_array($data)) {
|
||||
return false;
|
||||
}
|
||||
$xml = '<xml>';
|
||||
foreach ($data as $key => $val) {
|
||||
$xml .= (is_numeric($val) ? "<{$key}>{$val}</{$key}>" : "<{$key}><![CDATA[{$val}]]></{$key}>");
|
||||
}
|
||||
return $xml . '</xml>';
|
||||
}
|
||||
|
||||
//解析XML数据
|
||||
private function xml2array($xml)
|
||||
{
|
||||
if (!$xml) {
|
||||
return false;
|
||||
}
|
||||
LIBXML_VERSION < 20900 && libxml_disable_entity_loader(true);
|
||||
return json_decode(json_encode(simplexml_load_string($xml, 'SimpleXMLElement', LIBXML_NOCDATA), JSON_UNESCAPED_UNICODE), true);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,149 @@
|
||||
<?php
|
||||
namespace lib\wechat;
|
||||
|
||||
use Exception;
|
||||
|
||||
class WechatAPI
|
||||
{
|
||||
private $wid;
|
||||
private $accessToken;
|
||||
private $jsapiTicket;
|
||||
|
||||
public function __construct($id)
|
||||
{
|
||||
$this->wid = $id;
|
||||
}
|
||||
|
||||
public function getAccessToken($force = false)
|
||||
{
|
||||
global $DB;
|
||||
if(!empty($this->accessToken)) return $this->accessToken;
|
||||
$DB->beginTransaction();
|
||||
try{
|
||||
$row = $DB->getRow("SELECT * FROM pre_weixin WHERE id='{$this->wid}' LIMIT 1 FOR UPDATE");
|
||||
if(!$row) throw new Exception('记录不存在');
|
||||
if($row['access_token'] && strtotime($row['expiretime']) - 200 >= time() && !$force){
|
||||
$DB->rollback();
|
||||
$this->accessToken = $row['access_token'];
|
||||
return $this->accessToken;
|
||||
}
|
||||
$appid = $row['appid'];
|
||||
$secret = $row['appsecret'];
|
||||
$url = "https://api.weixin.qq.com/cgi-bin/stable_token";
|
||||
$post = json_encode(['grant_type'=>'client_credential', 'appid'=>$appid, 'secret'=>$secret]);
|
||||
$output = get_curl($url, $post);
|
||||
$res = json_decode($output, true);
|
||||
if (isset($res['access_token'])) {
|
||||
$this->accessToken = $res['access_token'];
|
||||
$expire_time = time() + $res['expires_in'];
|
||||
$DB->exec("UPDATE pre_weixin SET access_token=:access_token,updatetime=NOW(),expiretime=:expiretime WHERE id=:id", [':access_token'=>$this->accessToken, ':expiretime'=>date("Y-m-d H:i:s", $expire_time), ':id'=>$this->wid]);
|
||||
}elseif(isset($res['errmsg'])){
|
||||
throw new Exception('AccessToken获取失败:'.$res['errmsg']);
|
||||
}else{
|
||||
throw new Exception('AccessToken获取失败');
|
||||
}
|
||||
$DB->commit();
|
||||
return $this->accessToken;
|
||||
}catch(Exception $e){
|
||||
$DB->rollback();
|
||||
throw $e;
|
||||
}
|
||||
}
|
||||
|
||||
public function generate_scheme($path, $query, $expire = 600)
|
||||
{
|
||||
$access_token = $this->getAccessToken();
|
||||
$url = "https://api.weixin.qq.com/wxa/generatescheme?access_token=".$access_token;
|
||||
$data = ['jump_wxa'=>['path'=>$path, 'query'=>$query]];
|
||||
if($expire>0){
|
||||
$data['is_expire'] = true;
|
||||
$data['expire_time'] = time()+$expire;
|
||||
}
|
||||
$output = get_curl($url, json_encode($data));
|
||||
$res = json_decode($output, true);
|
||||
if ($res && $res['errcode'] == 0) {
|
||||
return $res['openlink'];
|
||||
}else{
|
||||
throw new Exception('urlscheme生成失败:'.$res['errmsg']);
|
||||
}
|
||||
}
|
||||
|
||||
//发送微信公众号模板消息
|
||||
public function sendTemplateMessage($openid, $template_id, $jumpurl, $data){
|
||||
$access_token = $this->getAccessToken();
|
||||
$url = 'https://api.weixin.qq.com/cgi-bin/message/template/send?access_token='.$access_token;
|
||||
$post = [
|
||||
'touser' => $openid,
|
||||
'template_id' => $template_id,
|
||||
'url' => $jumpurl,
|
||||
'data' => $data
|
||||
];
|
||||
$response = get_curl($url, json_encode($post));
|
||||
$res = json_decode($response, true);
|
||||
if ($res && $res['errcode'] == 0) {
|
||||
return true;
|
||||
}else{
|
||||
throw new Exception('模板消息发送失败:'.$res['errmsg']);
|
||||
}
|
||||
}
|
||||
|
||||
public function getJsapiTicket($force = false)
|
||||
{
|
||||
global $CACHE;
|
||||
if(!empty($this->jsapiTicket)) return $this->jsapiTicket;
|
||||
|
||||
$cachekey = 'wx_jsapi_ticket_'.$this->wid;
|
||||
$row = $CACHE->read($cachekey);
|
||||
if($row){
|
||||
$row = unserialize($row);
|
||||
if($row['ticket'] && strtotime($row['expiretime']) - 200 >= time() && !$force){
|
||||
$this->jsapiTicket = $row['ticket'];
|
||||
return $this->jsapiTicket;
|
||||
}
|
||||
}
|
||||
|
||||
$access_token = $this->getAccessToken();
|
||||
$url = 'https://api.weixin.qq.com/cgi-bin/ticket/getticket?access_token='.$access_token.'&type=jsapi';
|
||||
$output = get_curl($url);
|
||||
$res = json_decode($output, true);
|
||||
if (isset($res['ticket'])) {
|
||||
$this->jsapiTicket = $res['ticket'];
|
||||
$expire_time = time() + $res['expires_in'];
|
||||
$CACHE->save($cachekey, ['ticket'=>$this->jsapiTicket, 'expiretime'=>date("Y-m-d H:i:s", $expire_time)], $res['expires_in']);
|
||||
}elseif(isset($res['errmsg'])){
|
||||
throw new Exception('JsapiTicket获取失败:'.$res['errmsg']);
|
||||
}else{
|
||||
throw new Exception('JsapiTicket获取失败');
|
||||
}
|
||||
}
|
||||
|
||||
public function getJsapiConfig($appid, $url, $jsApiList, $debug = false)
|
||||
{
|
||||
$ticket = $this->getJsapiTicket();
|
||||
$data = [
|
||||
'jsapi_ticket' => $ticket,
|
||||
'timestamp' => time(),
|
||||
'noncestr' => random(16),
|
||||
'url' => $url
|
||||
];
|
||||
$config = [
|
||||
'debug' => $debug,
|
||||
'appId' => $appid,
|
||||
'timestamp' => $data['timestamp'],
|
||||
'nonceStr' => $data['noncestr'],
|
||||
'signature' => $this->getSignature($data),
|
||||
'jsApiList' => $jsApiList
|
||||
];
|
||||
return $config;
|
||||
}
|
||||
|
||||
public function getSignature($data)
|
||||
{
|
||||
ksort($data);
|
||||
$params = array();
|
||||
foreach ($data as $key => $value) {
|
||||
$params[] = "{$key}={$value}";
|
||||
}
|
||||
return sha1(join('&', $params));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
<?php
|
||||
namespace lib\wechat;
|
||||
|
||||
class WechatCrypt
|
||||
{
|
||||
private $key;
|
||||
private $iv;
|
||||
private static $block_size = 32;
|
||||
|
||||
public function __construct($enckey)
|
||||
{
|
||||
$this->key = base64_decode($enckey . '=');
|
||||
$this->iv = substr($this->key, 0, 16);
|
||||
}
|
||||
|
||||
public function encrypt($data, $appid)
|
||||
{
|
||||
$str = $this->getRandomStr() . pack('N', strlen($data)) . $data . $appid;
|
||||
$str = $this->enPKSC7($str);
|
||||
$encrypted = openssl_encrypt($str, 'AES-256-CBC', $this->key, OPENSSL_ZERO_PADDING, $this->iv);
|
||||
return $encrypted;
|
||||
}
|
||||
|
||||
public function decrypt($data)
|
||||
{
|
||||
$decrypted = openssl_decrypt($data, 'AES-256-CBC', $this->key, OPENSSL_ZERO_PADDING, $this->iv);
|
||||
if(!$decrypted) return false;
|
||||
$decrypted = $this->dePKSC7($decrypted);
|
||||
|
||||
$content = substr($decrypted, 16, strlen($decrypted));
|
||||
$len_list = unpack('N', substr($content, 0, 4));
|
||||
$xml_len = $len_list[1];
|
||||
$xml_content = substr($content, 4, $xml_len);
|
||||
$from_appid = substr($content, $xml_len + 4);
|
||||
|
||||
return $xml_content;
|
||||
}
|
||||
|
||||
private function enPKSC7($text)
|
||||
{
|
||||
$block_size = self::$block_size; //128:16、256:32
|
||||
$text_length = strlen($text);
|
||||
//计算需要填充的位数
|
||||
$amount_to_pad = $block_size - ($text_length % $block_size);
|
||||
if ($amount_to_pad == 0) {
|
||||
$amount_to_pad = $block_size;
|
||||
}
|
||||
//获得补位所用的字符
|
||||
$pad_chr = chr($amount_to_pad);
|
||||
$tmp = "";
|
||||
for ($index = 0; $index < $amount_to_pad; $index++) {
|
||||
$tmp .= $pad_chr;
|
||||
}
|
||||
return $text . $tmp;
|
||||
}
|
||||
|
||||
private function dePKSC7($text)
|
||||
{
|
||||
$block_size = self::$block_size; //128:16、256:32
|
||||
$pad = ord(substr($text, -1));
|
||||
if ($pad < 1 || $pad > $block_size) {
|
||||
$pad = 0;
|
||||
}
|
||||
return substr($text, 0, (strlen($text) - $pad));
|
||||
}
|
||||
|
||||
private function getRandomStr()
|
||||
{
|
||||
$str = '';
|
||||
$str_pol = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyl';
|
||||
$max = strlen($str_pol) - 1;
|
||||
for ($i = 0; $i < 16; $i++) {
|
||||
$str .= $str_pol[mt_rand(0, $max)];
|
||||
}
|
||||
return $str;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user