first commit

This commit is contained in:
2025-11-28 10:08:12 +08:00
commit 09a14bf0a3
1088 changed files with 145132 additions and 0 deletions
+228
View File
@@ -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']);
}
}
}
+103
View File
@@ -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);
}
}
+149
View File
@@ -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));
}
}
+77
View File
@@ -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;
}
}