123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462 |
- <?php
- // +----------------------------------------------------------------------
- // | ThinkPHP [ WE CAN DO IT JUST THINK IT ]
- // +----------------------------------------------------------------------
- // | Copyright (c) 2006-2015 http://thinkphp.cn All rights reserved.
- // +----------------------------------------------------------------------
- // | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
- // +----------------------------------------------------------------------
- // | Author: 麦当苗儿 <zuojiazi.cn@gmail.com> <http://www.zjzit.cn>
- // +----------------------------------------------------------------------
- namespace Com;
- use Com\WechatCrypt;
- //支持在非ThinkPHP环境下使用
- defined('NOW_TIME') || define('NOW_TIME', $_SERVER['REQUEST_TIME']);
- defined('IS_GET') || define('IS_GET', $_SERVER['REQUEST_METHOD'] == 'GET');
- class Wechat {
- /**
- * 消息类型常量
- */
- const MSG_TYPE_TEXT = 'text';
- const MSG_TYPE_IMAGE = 'image';
- const MSG_TYPE_VOICE = 'voice';
- const MSG_TYPE_VIDEO = 'video';
- const MSG_TYPE_SHORTVIDEO = 'shortvideo';
- const MSG_TYPE_LOCATION = 'location';
- const MSG_TYPE_LINK = 'link';
- const MSG_TYPE_MUSIC = 'music';
- const MSG_TYPE_NEWS = 'news';
- const MSG_TYPE_EVENT = 'event';
- /**
- * 事件类型常量
- */
- const MSG_EVENT_SUBSCRIBE = 'subscribe';
- const MSG_EVENT_UNSUBSCRIBE = 'unsubscribe';
- const MSG_EVENT_SCAN = 'SCAN';
- const MSG_EVENT_LOCATION = 'LOCATION';
- const MSG_EVENT_CLICK = 'CLICK';
- const MSG_EVENT_VIEW = 'VIEW';
- /**
- * 微信推送过来的数据
- * @var array
- */
- private $data = array();
- /**
- * 微信TOKEN
- * @var string
- */
- private static $token = '';
- /**
- * 微信APP_ID
- * @var string
- */
- private static $appId = '';
- /**
- * 消息加密KEY
- * @var string
- */
- private static $encodingAESKey = '';
- /**
- * 是否使用安全模式
- * @var boolean
- */
- private static $msgSafeMode = false;
- /**
- * 构造方法,用于实例化微信SDK
- * 自动回复消息时实例化该SDK
- * @param string $token 微信后台填写的TOKEN
- * @param string $appid 微信APPID (安全模式和兼容模式有效)
- * @param string $key 消息加密KEY (EncodingAESKey)
- */
- public function __construct($token, $appid = '', $key = ''){
- //设置安全模式
- if(isset($_GET['encrypt_type']) && $_GET['encrypt_type'] == 'aes'){
- self::$msgSafeMode = true;
- }
- //参数验证
- if(self::$msgSafeMode){
- if(empty($key) || empty($appid)){
- throw new \Think\Exception('缺少参数EncodingAESKey或APP_ID!');
- }
- self::$appId = $appid;
- self::$encodingAESKey = $key;
- }
- //TOKEN验证
- if($token){
- self::auth($token) || exit;
- if(IS_GET){
- exit($_GET['echostr']);
- } else {
- self::$token = $token;
- $this->init();
- }
- } else {
- throw new \Think\Exception('缺少参数TOKEN!');
- }
- }
- /**
- * 初始化微信推送的数据
- */
- private function init(){
- $xml = file_get_contents("php://input");
- $data = self::xml2data($xml);
- //安全模式 或兼容模式
- if(self::$msgSafeMode){
- if(isset($data['MsgType'])){
- //兼容模式追加解密后的消息内容
- $data['Decrypt'] = self::extract($data['Encrypt']);
- } else {
- //安全模式
- $data = self::extract($data['Encrypt']);
- }
- }
- $this->data = $data;
- }
- /**
- * 获取微信推送的数据
- * @return array 转换为数组后的数据
- */
- public function request(){
- return $this->data;
- }
- /**
- * * 响应微信发送的信息(自动回复)
- * @param array $content 回复信息,文本信息为string类型
- * @param string $type 消息类型
- */
- public function response($content, $type = self::MSG_TYPE_TEXT){
- /* 基础数据 */
- $data = array(
- 'ToUserName' => $this->data['FromUserName'],
- 'FromUserName' => $this->data['ToUserName'],
- 'CreateTime' => NOW_TIME,
- 'MsgType' => $type,
- );
- /* 按类型添加额外数据 */
- $content = call_user_func(array(self, $type), $content);
- if($type == self::MSG_TYPE_TEXT || $type == self::MSG_TYPE_NEWS){
- $data = array_merge($data, $content);
- } else {
- $data[ucfirst($type)] = $content;
- }
- //安全模式,加密消息内容
- if(self::$msgSafeMode){
- $data = self::generate($data);
- }
- /* 转换数据为XML */
- $xml = new \SimpleXMLElement('<xml></xml>');
- self::data2xml($xml, $data);
- exit($xml->asXML());
- }
- /**
- * 回复文本消息
- * @param string $text 回复的文字
- */
- public function replyText($text){
- return $this->response($text, self::MSG_TYPE_TEXT);
- }
- /**
- * 回复图片消息
- * @param string $media_id 图片ID
- */
- public function replyImage($media_id){
- return $this->response($media_id, self::MSG_TYPE_IMAGE);
- }
- /**
- * 回复语音消息
- * @param string $media_id 音频ID
- */
- public function replyVoice($media_id){
- return $this->response($media_id, self::MSG_TYPE_VOICE);
- }
- /**
- * 回复视频消息
- * @param string $media_id 视频ID
- * @param string $title 视频标题
- * @param string $discription 视频描述
- */
- public function replyVideo($media_id, $title, $discription){
- return $this->response(func_get_args(), self::MSG_TYPE_VIDEO);
- }
- /**
- * 回复音乐消息
- * @param string $title 音乐标题
- * @param string $discription 音乐描述
- * @param string $musicurl 音乐链接
- * @param string $hqmusicurl 高品质音乐链接
- * @param string $thumb_media_id 缩略图ID
- */
- public function replyMusic($title, $discription, $musicurl, $hqmusicurl, $thumb_media_id){
- return $this->response(func_get_args(), self::MSG_TYPE_MUSIC);
- }
- /**
- * 回复图文消息,一个参数代表一条信息
- * @param array $news 图文内容 [标题,描述,URL,缩略图]
- * @param array $news1 图文内容 [标题,描述,URL,缩略图]
- * @param array $news2 图文内容 [标题,描述,URL,缩略图]
- * ... ...
- * @param array $news9 图文内容 [标题,描述,URL,缩略图]
- */
- public function replyNews($news, $news1, $news2, $news3){
- return $this->response(func_get_args(), self::MSG_TYPE_NEWS);
- }
- /**
- * 回复一条图文消息
- * @param string $title 文章标题
- * @param string $discription 文章简介
- * @param string $url 文章连接
- * @param string $picurl 文章缩略图
- */
- public function replyNewsOnce($title, $discription, $url, $picurl){
- return $this->response(array(func_get_args()), self::MSG_TYPE_NEWS);
- }
- /**
- * 数据XML编码
- * @param object $xml XML对象
- * @param mixed $data 数据
- * @param string $item 数字索引时的节点名称
- * @return string
- */
- protected static function data2xml($xml, $data, $item = 'item') {
- foreach ($data as $key => $value) {
- /* 指定默认的数字key */
- is_numeric($key) && $key = $item;
- /* 添加子元素 */
- if(is_array($value) || is_object($value)){
- $child = $xml->addChild($key);
- self::data2xml($child, $value, $item);
- } else {
- if(is_numeric($value)){
- $child = $xml->addChild($key, $value);
- } else {
- $child = $xml->addChild($key);
- $node = dom_import_simplexml($child);
- $cdata = $node->ownerDocument->createCDATASection($value);
- $node->appendChild($cdata);
- }
- }
- }
- }
- /**
- * XML数据解码
- * @param string $xml 原始XML字符串
- * @return array 解码后的数组
- */
- protected static function xml2data($xml){
- $xml = new \SimpleXMLElement($xml);
-
- if(!$xml){
- throw new \Think\Exception('非法XXML');
- }
- $data = array();
- foreach ($xml as $key => $value) {
- $data[$key] = strval($value);
- }
- return $data;
- }
- /**
- * 对数据进行签名认证,确保是微信发送的数据
- * @param string $token 微信开放平台设置的TOKEN
- * @return boolean true-签名正确,false-签名错误
- */
- protected static function auth($token){
- /* 获取数据 */
- $data = array($_GET['timestamp'], $_GET['nonce'], $token);
- $sign = $_GET['signature'];
-
- /* 对数据进行字典排序 */
- sort($data, SORT_STRING);
- /* 生成签名 */
- $signature = sha1(implode($data));
- return $signature === $sign;
- }
- /**
- * 构造文本信息
- * @param string $content 要回复的文本
- */
- private static function text($content){
- $data['Content'] = $content;
- return $data;
- }
- /**
- * 构造图片信息
- * @param integer $media 图片ID
- */
- private static function image($media){
- $data['MediaId'] = $media;
- return $data;
- }
- /**
- * 构造音频信息
- * @param integer $media 语音ID
- */
- private static function voice($media){
- $data['MediaId'] = $media;
- return $data;
- }
- /**
- * 构造视频信息
- * @param array $video 要回复的视频 [视频ID,标题,说明]
- */
- private static function video($video){
- $data = array();
- list(
- $data['MediaId'],
- $data['Title'],
- $data['Description'],
- ) = $video;
- return $data;
- }
- /**
- * 构造音乐信息
- * @param array $music 要回复的音乐[标题,说明,链接,高品质链接,缩略图ID]
- */
- private static function music($music){
- $data = array();
- list(
- $data['Title'],
- $data['Description'],
- $data['MusicUrl'],
- $data['HQMusicUrl'],
- $data['ThumbMediaId'],
- ) = $music;
- return $data;
- }
- /**
- * 构造图文信息
- * @param array $news 要回复的图文内容
- * [
- * 0 => 第一条图文信息[标题,说明,图片链接,全文连接],
- * 1 => 第二条图文信息[标题,说明,图片链接,全文连接],
- * 2 => 第三条图文信息[标题,说明,图片链接,全文连接],
- * ]
- */
- private static function news($news){
- $articles = array();
- foreach ($news as $key => $value) {
- list(
- $articles[$key]['Title'],
- $articles[$key]['Description'],
- $articles[$key]['Url'],
- $articles[$key]['PicUrl']
- ) = $value;
- if($key >= 9) break; //最多只允许10条图文信息
- }
- $data['ArticleCount'] = count($articles);
- $data['Articles'] = $articles;
- return $data;
- }
- /**
- * 验证并解密密文数据
- * @param string $encrypt 密文
- * @return array 解密后的数据
- */
- private static function extract($encrypt){
- //验证数据签名
- $signature = self::sign($_GET['timestamp'], $_GET['nonce'], $encrypt);
- if($signature != $_GET['msg_signature']){
- throw new \Think\Exception('数据签名错误!');
- }
- //消息解密对象
- $WechatCrypt = new WechatCrypt(self::$encodingAESKey, self::$appId);
- //解密得到回明文消息
- $decrypt = $WechatCrypt->decrypt($encrypt);
-
- //返回解密的数据
- return self::xml2data($decrypt);
- }
- /**
- * 加密并生成密文消息数据
- * @param array $data 获取到的加密的消息数据
- * @return array 生成的加密消息结构
- */
- private static function generate($data){
- /* 转换数据为XML */
- $xml = new \SimpleXMLElement('<xml></xml>');
- self::data2xml($xml, $data);
- $xml = $xml->asXML();
- //消息加密对象
- $WechatCrypt = new WechatCrypt(self::$encodingAESKey, self::$appId);
- //加密得到密文消息
- $encrypt = $WechatCrypt->encrypt($xml);
- //签名
- $nonce = mt_rand(0, 9999999999);
- $signature = self::sign(NOW_TIME, $nonce, $encrypt);
- /* 加密消息基础数据 */
- $data = array(
- 'Encrypt' => $encrypt,
- 'MsgSignature' => $signature,
- 'TimeStamp' => NOW_TIME,
- 'Nonce' => $nonce,
- );
- return $data;
- }
- /**
- * 生成数据签名
- * @param string $timestamp 时间戳
- * @param string $nonce 随机数
- * @param string $encrypt 被签名的数据
- * @return string SHA1签名
- */
- private static function sign($timestamp, $nonce, $encrypt){
- $sign = array(self::$token, $timestamp, $nonce, $encrypt);
- sort($sign, SORT_STRING);
- return sha1(implode($sign));
- }
- }
|