陈德本 6 years ago
commit
3d34e6d262
99 changed files with 14894 additions and 0 deletions
  1. 1 0
      .gitignore
  2. 23 0
      .idea/$CACHE_FILE$
  3. 8 0
      .idea/.gitignore
  4. 10 0
      .idea/inspectionProfiles/Project_Default.xml
  5. 6 0
      .idea/misc.xml
  6. 8 0
      .idea/modules.xml
  7. 4 0
      .idea/php.xml
  8. 8 0
      .idea/taoqu88.iml
  9. 6 0
      .idea/vcs.xml
  10. 34 0
      App/Api/Config/Route.php
  11. 19 0
      App/Api/Config/Site.php
  12. 133 0
      App/Api/Controller.php
  13. 20 0
      App/Api/Controller/Category.php
  14. 21 0
      App/Api/Controller/Detail.php
  15. 21 0
      App/Api/Controller/Index.php
  16. 26 0
      App/Api/Controller/Pages.php
  17. 36 0
      App/Api/Controller/Search.php
  18. 70 0
      App/Api/Model/Dataoke.php
  19. 51 0
      App/Script/Controller.php
  20. 94 0
      App/Script/Controller/Check.php
  21. 186 0
      App/Script/Controller/Server.php
  22. 85 0
      App/Script/Controller/Task.php
  23. 13 0
      App/Script/Controller/cookie.txt
  24. 18 0
      App/Script/Package/Database/Code.php
  25. 29 0
      Autoload.php
  26. 21 0
      Config/Route.php
  27. 98 0
      Frame/Config.php
  28. 283 0
      Frame/Controller.php
  29. 187 0
      Frame/Dispatcher.php
  30. 20 0
      Frame/Exception.php
  31. 13 0
      Frame/Exception/Database.php
  32. 12 0
      Frame/Exception/Validate.php
  33. 251 0
      Frame/Filter.php
  34. 341 0
      Frame/Helper.php
  35. 67 0
      Frame/Helper/Date.php
  36. 39 0
      Frame/Helper/File.php
  37. 92 0
      Frame/Helper/Format.php
  38. 269 0
      Frame/Helper/Html.php
  39. 31 0
      Frame/Helper/Location.php
  40. 11 0
      Frame/Helper/Network.php
  41. 54 0
      Frame/Helper/Validate.php
  42. 74 0
      Frame/Init.php
  43. 58 0
      Frame/Library.php
  44. 83 0
      Frame/Library/AntiSpam.php
  45. 161 0
      Frame/Library/BaseConverter.php
  46. 8 0
      Frame/Library/Benchmark.php
  47. 19 0
      Frame/Library/Curl.php
  48. 44 0
      Frame/Library/Memcache.php
  49. 41 0
      Frame/Library/Memcached.php
  50. 252 0
      Frame/Library/MySQL.php
  51. 402 0
      Frame/Library/MySQL/Query.php
  52. 63 0
      Frame/Library/Redis.php
  53. 79 0
      Frame/Library/Ticket.php
  54. 313 0
      Frame/Logger.php
  55. 85 0
      Frame/Model.php
  56. 59 0
      Frame/Object.php
  57. 56 0
      Frame/Package.php
  58. 11 0
      Frame/Package/Cache.php
  59. 152 0
      Frame/Package/Cache/Memcache.php
  60. 179 0
      Frame/Package/Cache/Redis.php
  61. 11 0
      Frame/Package/Counter.php
  62. 148 0
      Frame/Package/Counter/Hash.php
  63. 141 0
      Frame/Package/Counter/Single.php
  64. 13 0
      Frame/Package/Database.php
  65. 275 0
      Frame/Package/Database/Common.php
  66. 342 0
      Frame/Package/Database/Sharding.php
  67. 435 0
      Frame/Package/Database/Single.php
  68. 62 0
      Frame/Package/Queue.php
  69. 82 0
      Frame/Package/RedisQueue.php
  70. 291 0
      Frame/Package/Set.php
  71. 203 0
      Frame/Request.php
  72. 104 0
      Frame/Response.php
  73. 39 0
      Frame/Router.php
  74. 144 0
      Frame/Session.php
  75. 226 0
      Frame/Template.php
  76. 219 0
      Library/Curl.php
  77. 82 0
      Library/Email.php
  78. 1280 0
      Library/Medoo/Medoo.php
  79. 108 0
      Library/Memcached.php
  80. 232 0
      Library/Redis.php
  81. 36 0
      Library/Warming.php
  82. 17 0
      Library/WeatherAdapt/Weather.php
  83. 47 0
      Library/WeatherAdapt/WeatherStruct.php
  84. 423 0
      Library/WeatherAdapt/caiyunAdapter.php
  85. 153 0
      Library/WeatherAdapt/heweatherAdapter.php
  86. 509 0
      Library/WeatherAdapt/mixedAdapter.php
  87. 246 0
      Library/WeatherAdapt/spareAdapter.php
  88. 24 0
      Library/WeatherAdapt/tianqiapiAdapter.php
  89. 24 0
      Library/WeatherAdapt/weatherdtAdapter.php
  90. 36 0
      Library/WeatherLib.php
  91. 701 0
      Library/WechatLib.php
  92. 5 0
      composer.json
  93. 1646 0
      composer.lock
  94. 19 0
      constants.php
  95. 47 0
      functions.php
  96. 12 0
      index.php
  97. 506 0
      register_shutdown_function.log
  98. 198 0
      static/new_home.js
  99. 950 0
      tao.php

+ 1 - 0
.gitignore

@@ -0,0 +1 @@
+/vendor/

+ 23 - 0
.idea/$CACHE_FILE$

@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+  <component name="ProjectInspectionProfilesVisibleTreeState">
+    <entry key="Project Default">
+      <profile-state>
+        <expanded-state>
+          <State>
+            <id />
+          </State>
+          <State>
+            <id>Bitwise operation issuesJavaScript</id>
+          </State>
+          <State>
+            <id>DOM issuesJavaScript</id>
+          </State>
+          <State>
+            <id>JavaScript</id>
+          </State>
+        </expanded-state>
+      </profile-state>
+    </entry>
+  </component>
+</project>

+ 8 - 0
.idea/.gitignore

@@ -0,0 +1,8 @@
+# Default ignored files
+/shelf/
+/workspace.xml
+# Datasource local storage ignored files
+/dataSources/
+/dataSources.local.xml
+# Editor-based HTTP Client requests
+/httpRequests/

+ 10 - 0
.idea/inspectionProfiles/Project_Default.xml

@@ -0,0 +1,10 @@
+<component name="InspectionProjectProfileManager">
+  <profile version="1.0">
+    <option name="myName" value="Project Default" />
+    <inspection_tool class="SpellCheckingInspection" enabled="false" level="TYPO" enabled_by_default="false">
+      <option name="processCode" value="true" />
+      <option name="processLiterals" value="true" />
+      <option name="processComments" value="true" />
+    </inspection_tool>
+  </profile>
+</component>

+ 6 - 0
.idea/misc.xml

@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+  <component name="JavaScriptSettings">
+    <option name="languageLevel" value="ES6" />
+  </component>
+</project>

+ 8 - 0
.idea/modules.xml

@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+  <component name="ProjectModuleManager">
+    <modules>
+      <module fileurl="file://$PROJECT_DIR$/.idea/taoqu88.iml" filepath="$PROJECT_DIR$/.idea/taoqu88.iml" />
+    </modules>
+  </component>
+</project>

+ 4 - 0
.idea/php.xml

@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+  <component name="PhpProjectSharedConfiguration" php_language_level="7" />
+</project>

+ 8 - 0
.idea/taoqu88.iml

@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<module type="WEB_MODULE" version="4">
+  <component name="NewModuleRootManager">
+    <content url="file://$MODULE_DIR$" />
+    <orderEntry type="inheritedJdk" />
+    <orderEntry type="sourceFolder" forTests="false" />
+  </component>
+</module>

+ 6 - 0
.idea/vcs.xml

@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+  <component name="VcsDirectoryMappings">
+    <mapping directory="$PROJECT_DIR$" vcs="Git" />
+  </component>
+</project>

+ 34 - 0
App/Api/Config/Route.php

@@ -0,0 +1,34 @@
+<?php
+/**
+ * Created by PhpStorm.
+ * User: chendeben
+ * Date: 2018/6/20
+ * Time: 下午2:26
+ */
+
+namespace Heanup\App\Api\Config;
+
+use Heanup\Frame\Config;
+
+class Route extends Config
+{
+    protected static $data = [
+        [
+            "name" => "分类",
+            "pattern" => "/category/:s.html",//api/material/sort/beauty/pre
+            "action" => "Heanup\App\Api\Controller\Category"
+        ], [
+            "name" => "搜索",
+            "pattern" => "/kw/:s.html",//api/material/sort/beauty/pre
+            "action" => "Heanup\App\Api\Controller\Search"
+        ], [
+            "name" => "详情页",
+            "pattern" => "/product/:s.html",//api/material/sort/beauty/pre
+            "action" => "Heanup\App\Api\Controller\Detail"
+        ], [
+            "name" => "专题页",
+            "pattern" => "/pages/:s.html",//api/material/sort/beauty/pre
+            "action" => "Heanup\App\Api\Controller\Pages"
+        ]
+    ];
+}

+ 19 - 0
App/Api/Config/Site.php

@@ -0,0 +1,19 @@
+<?php
+/**
+ * Created by PhpStorm.
+ * User: chendeben
+ * Date: 2018/6/20
+ * Time: 下午2:26
+ */
+
+namespace Heanup\App\Api\Config;
+
+use Heanup\Frame\Config;
+
+class Site extends Config
+{
+    protected static $data = [
+       "site_url"=>"http://www.taoqu88.com",
+       "user_tag"=>"1350274"
+    ];
+}

+ 133 - 0
App/Api/Controller.php

@@ -0,0 +1,133 @@
+<?php
+
+namespace Heanup\App\Api;
+
+
+use Exception;
+use Heanup\App\Api\Config\GlobalConfig;
+use Heanup\Library\Memcached;
+
+abstract class Controller extends \Heanup\Frame\Controller
+{
+    protected $need_cache = false;
+    /**
+     * @var string
+     */
+    protected $cache_key;
+
+    protected function main()
+    {
+
+    }
+
+    /**
+     * 验证登录信息
+     * 无需验证或者单独验证的类可以覆盖该方法
+     *
+     * @param bool $checkLogin
+     * @return bool
+     * @throws Exception
+     */
+    protected function checkAuth($checkLogin = false)
+    {
+        return true;
+    }
+
+
+    /**
+     * 输出模板,自动查找模板路径
+     *
+     * @param string $layout
+     */
+    protected function render($layout = 'Common/Layout')
+    {
+        header("Content-type: text/html; charset=utf-8");
+        $this->getTemplate()->render(false, $layout);
+    }
+
+    /**
+     * 检测缓存是否存在,缓存存在则直接输出
+     */
+    public function checkCache()
+    {
+        if ($data = Memcached::instance()->get($this->cache_key)) {
+            header('Content-type: application/json');
+            echo $data;
+            die();
+        }
+
+        $this->need_cache = true;
+    }
+
+
+    /**
+     * 输出json信息
+     *
+     * @param int $code
+     * @param string $msg
+     * @param array $data
+     * @param string $callback
+     */
+    protected function output($code = 0, $msg = '', $data = array(), $callback = "")
+    {
+        $output = array(
+            'code' => $code,
+            'msg' => $msg,
+            'update' => date("Y-m-d H:i:s"),
+            'data' => $data ?: array()
+        );
+        header('Content-type: application/json');
+
+        if ($callback) {
+            $response = $callback . "(" . json_encode($output) . ")";
+        } else {
+            $response = json_encode($output, JSON_UNESCAPED_UNICODE);
+        }
+        if ($this->need_cache) {
+            Memcached::instance()->set($this->cache_key, $response, ALL_CACHE_TIME);
+        }
+        echo $response;
+        die();
+        // 记录调试信息
+    }
+
+    protected function directOutput($response)
+    {
+        header('Content-type: application/json');
+        if (is_array($response)) {
+            $response = json_encode($response,JSON_UNESCAPED_UNICODE);
+        }
+        if ($this->need_cache) {
+            Memcached::instance()->set($this->cache_key, $response, ALL_CACHE_TIME);
+        }
+        echo $response;
+    }
+
+    /**
+     * 输出错误信息
+     *
+     * @param int $code
+     * @param string $message
+     */
+    protected function showError($code = 0, $message = '')
+    {
+        $output = array(
+            'code' => intval($code),
+            'message' => $message,
+            'data' => []
+        );
+        header('Content-type: application/json');
+        echo json_encode($output);
+        exit();
+    }
+
+    /**
+     * 显示成功信息
+     * @param $data
+     */
+    protected function showSuccess($data = array())
+    {
+        $this->output(0, '请求成功', $data);
+    }
+
+}

+ 20 - 0
App/Api/Controller/Category.php

@@ -0,0 +1,20 @@
+<?php
+
+
+namespace Heanup\App\Api\Controller;
+
+
+use Heanup\App\Api\Config\Site;
+use Heanup\App\Api\Controller;
+use Heanup\App\Api\Model\Dataoke;
+
+class Category extends Controller
+{
+    protected function main()
+    {
+        $id = $this->getQuery()->intval("id");
+        $url = sprintf(Site::getData('site_url') . "/index.php?r=l&cid=%d&site=classify", $id);
+        echo Dataoke::parse(file_get_contents($url));
+    }
+
+}

+ 21 - 0
App/Api/Controller/Detail.php

@@ -0,0 +1,21 @@
+<?php
+
+
+namespace Heanup\App\Api\Controller;
+
+
+use Heanup\App\Api\Config\Site;
+use Heanup\App\Api\Controller;
+use Heanup\App\Api\Model\Dataoke;
+
+class Detail extends Controller
+{
+    protected function main()
+    {
+        $id = $this->getQuery()->intval("id");
+        $u = Site::getData("user_tag");
+        $url = sprintf(Site::getData('site_url') . "/index.php?r=l/d&id=%s&u=%s", $id, $u);
+        echo Dataoke::parse(file_get_contents($url));
+    }
+
+}

+ 21 - 0
App/Api/Controller/Index.php

@@ -0,0 +1,21 @@
+<?php
+
+
+namespace Heanup\App\Api\Controller;
+
+
+use Heanup\App\Api\Config\Site;
+use Heanup\App\Api\Controller;
+use Heanup\App\Api\Model\Dataoke;
+
+class Index extends Controller
+{
+
+    protected function main()
+    {
+        $url = Site::getData('site_url') . "/index.php?r=index/index";
+        $content = file_get_contents($url);
+        $content = Dataoke::parse($content);
+        echo $content;
+    }
+}

+ 26 - 0
App/Api/Controller/Pages.php

@@ -0,0 +1,26 @@
+<?php
+
+
+namespace Heanup\App\Api\Controller;
+
+
+use Heanup\App\Api\Config\Site;
+use Heanup\App\Api\Controller;
+use Heanup\App\Api\Model\Dataoke;
+
+class Pages extends Controller
+{
+    protected function main()
+    {
+        $route=[
+            'fengqiang'=>'p',
+            'nine'=>'nine',
+            'dongdong'=>'ddq',
+            'haohuo'=>'l',
+        ];
+        $page = $this->getQuery()->string("args_0");
+        $url = sprintf(Site::getData('site_url') . "/index.php?r=%s", $route[$page]);
+        echo Dataoke::parse(file_get_contents($url));
+    }
+
+}

+ 36 - 0
App/Api/Controller/Search.php

@@ -0,0 +1,36 @@
+<?php
+
+
+namespace Heanup\App\Api\Controller;
+
+
+use Heanup\App\Api\Config\Site;
+use Heanup\App\Api\Controller;
+use Heanup\App\Api\Model\Dataoke;
+
+class Search extends Controller
+{
+    protected function main()
+    {
+        $kw = $this->getQuery()->string("args_0");
+        $bids = $this->getQuery()->string("bids");
+        $cid = $this->getQuery()->string("cid");
+        $request=[
+            'r'=>'l',
+            'u'=>Site::getData("user_tag")
+        ];
+        if ($kw){
+            $request['kw']=urldecode($kw);
+        }
+        if ($bids){
+            $request['bids']=$bids;
+        }
+        if ($cid){
+            $request['cid']=$cid;
+        }
+
+        $url = Site::getData('site_url') . "/index.php?".http_build_query($request);
+        echo Dataoke::parse(file_get_contents($url));
+    }
+
+}

+ 70 - 0
App/Api/Model/Dataoke.php

@@ -0,0 +1,70 @@
+<?php
+
+
+namespace Heanup\App\Api\Model;
+
+
+use QL\QueryList;
+use function GuzzleHttp\Psr7\parse_query;
+
+class Dataoke
+{
+
+
+    public static function parse($content)
+    {
+        $ql = QueryList::html($content);
+
+// 元数据采集规则
+        $rules = [
+            'link' => ["a", "href"]
+        ];
+// 切片选择器
+        $rt = $ql->find("a")->attrs('href');
+        $data = $rt->all();
+        $data = array_unique($data);
+        foreach ($data as $url) {
+            if (strstr($url, "index.php") === false) {
+                continue;
+            }
+            $_url=str_replace("index.php", "", $url);
+            $t = parse_query($_url);
+            if ($t['kw']) {
+//                if ($t['bids']){
+//                    $new_url="/kw/" . $t['kw'] . ".html?bids=".$t['bids'];
+//                }elseif ($t['cid']){
+//                    $new_url="/kw/" . $t['kw'] . ".html?cid=".$t['cid'];
+//                } else{
+//                    $d=$t;
+//                    unset($d['kw']);
+//                }
+                $new_url="/kw/" . $t['kw'] . ".html?".http_build_query($t);
+
+                $content = str_replace($url, $new_url, $content);
+            }else{
+                if ($t['cid']) {
+                    $content = str_replace($url, "/category/" . $t['cid'] . ".html", $content);
+                }else{
+                    if ($t['id']) {
+                        $content = str_replace($url, "/product/" . $t['id'] . ".html", $content);
+                    }
+                }
+
+            }
+
+            ///index.php?r=index
+//            if ($url=="/index.php?r=index"){
+//                $content = str_replace($url, "/" , $content);
+//            }
+
+        }
+        $content=str_replace('"/index.php?r=p"', '"/pages/fengqiang.html"', $content);
+        $content=str_replace('"/index.php?r=nine"', '"/pages/nine.html"', $content);
+        $content=str_replace('"/index.php?r=ddq"', '"/pages/dongdong.html"', $content);
+        $content=str_replace('"/index.php?r=l"', '"/pages/haohuo.html"', $content);
+        $content=str_replace('https://cmsstatic.ffquan.cn//js/new_home.js', "/static/new_home.js", $content);
+//        $content=str_replace('index.php', "tao.php", $content);
+        return $content;
+    }
+
+}

+ 51 - 0
App/Script/Controller.php

@@ -0,0 +1,51 @@
+<?php
+
+namespace Heanup\App\Script;
+
+
+use Heanup\Frame\Helper;
+use Heanup\Frame\Logger;
+
+abstract class Controller extends \Heanup\Frame\Controller
+{
+
+    /**
+     * 获取命令行参数
+     * @return array
+     */
+    protected function getArguments()
+    {
+        return Helper::getArguments();
+    }
+
+    /**
+     * 记录下运行日志
+     */
+    public function after()
+    {
+        parent::after();
+        Logger::debug('', 'runtime');
+    }
+
+    protected function addLogs($msg,$file="admin")
+    {
+        Logger::setLog($msg, $file);
+    }
+    /**
+     * 输出错误信息
+     *
+     * @param int $code
+     * @param string $message
+     */
+    protected function showError($code = 0, $message = '')
+    {
+        $output = array(
+            'code' => intval($code),
+            'message' => $message,
+            'data' => []
+        );
+        print_r($output);
+//        exit();
+    }
+
+}

+ 94 - 0
App/Script/Controller/Check.php

@@ -0,0 +1,94 @@
+<?php
+
+
+namespace Heanup\App\Script\Controller;
+
+
+use Heanup\App\Api\Package\Database\Proxy;
+use Heanup\App\Script\Controller;
+
+class Check extends Controller
+{
+    protected function main()
+    {
+        $_status=1;
+        while (true){
+            $ips=Proxy::select([],['status'=>$_status]);
+            $_status=1-$_status;
+            foreach ($ips as $ip) {
+                $status=$this->check_proxy_ip_info($ip['ip'].":".$ip['port']);
+                if (!$status){
+                    echo sprintf("%s无效,已删除",$ip['ip'] ). PHP_EOL;
+                    Proxy::update(['status'=>0], ['id'=>$ip['id']]);
+                }else{
+                    echo sprintf("%s:%s有效",$ip['ip'],$ip['port'] ). PHP_EOL;
+                }
+
+            }
+            echo "休眠30s" . PHP_EOL;
+            sleep(30);
+        }
+
+    }
+    /**
+     * +-----------------------------------------------------------------------------
+     * 检查代理ip信息有效性
+     * +-----------------------------------------------------------------------------
+     * @param string $proxy_ip [117.95.100.126:8998]
+     * @param int $times 执行检查次数
+     * @return bool
+     * @author elinx <654753115@qq.com> 2016-07-29
+     * +-----------------------------------------------------------------------------
+     */
+    function check_proxy_ip_info($proxy_ip = '', $times = 1)
+    {
+        $status = false;
+        $header = array(
+            // "GET / HTTP/1.1",
+            // "HOST: www.baidu.com",
+            "accept: application/json",
+            "accept-encoding: gzip, deflate",
+            "accept-language: en-US,en;q=0.8",
+            "content-type: application/json",
+            "user-agent: Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/51.0.2704.106 Safari/537.36",
+        );
+        $url = 'http://www.baidu.com/';
+        $result['succeed_times'] = 0; //成功次数
+        $result['defeat_times'] = 0; //失败次数
+        $result['total_spen'] = 0; //总用时
+        $ip = explode(":", $proxy_ip);
+        for ($i = 0; $i < $times; $i++) {
+            $curl = curl_init();
+            curl_setopt($curl, CURLOPT_URL, $url); //设置传输的url
+            curl_setopt($curl, CURLOPT_HTTPHEADER, $header); //发送http报头
+            curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1);
+            curl_setopt($curl, CURLOPT_ENCODING, 'gzip,deflate'); // 解码压缩文件
+            curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, false); //不验证证SSL书
+            curl_setopt($curl, CURLOPT_SSL_VERIFYHOST, false); //不验证SSL证书
+
+            curl_setopt($curl, CURLOPT_HTTPHEADER, array(
+                'Client_Ip: ' . $ip,
+            ));
+            curl_setopt($curl, CURLOPT_HTTPHEADER, array(
+                'X-Forwarded-For: ' . $ip,
+            ));
+            curl_setopt($curl, CURLOPT_PROXYTYPE, CURLPROXY_HTTP);
+            curl_setopt($curl, CURLOPT_PROXY, $proxy_ip);
+
+            curl_setopt($curl, CURLOPT_COOKIEFILE, dirname(__FILE__) . '/cookie.txt');
+            curl_setopt($curl, CURLOPT_COOKIEJAR, dirname(__FILE__) . '/cookie.txt');
+            curl_setopt($curl, CURLOPT_TIMEOUT, 10); // 设置超时限制防止死循环
+            // $response_header = curl_getinfo($curl); // 获取返回response报头
+            $content = curl_exec($curl);
+            if (strstr($content, '百度一下,你就知道')) {
+                $status = true;
+                break;
+            } else {
+                $status = false;
+            }
+
+            // $result['list'][$i]['response_header'] = $response_header;
+        }
+        return $status;
+    }
+}

+ 186 - 0
App/Script/Controller/Server.php

@@ -0,0 +1,186 @@
+<?php
+/**
+ * Created by PhpStorm.
+ * User: chendeben
+ * Date: 2018/8/31
+ * Time: 下午9:35
+ */
+
+namespace Heanup\App\Script\Controller;
+
+
+use Exception;
+use Heanup\App\Api\Package\Database\Proxy;
+use Heanup\App\Script\Controller;
+use Heanup\Library\Curl;
+
+class Server extends Controller
+{
+
+    /**
+     * 执行逻辑
+     * @throws Exception
+     */
+    protected function main()
+    {
+        while (true){
+            echo "开始获取IP" . PHP_EOL;
+            $ips = $this->get_ip();
+            $this->check_ip($ips);
+            echo "休眠30s" . PHP_EOL;
+            sleep(30);
+        }
+//        $ips=Proxy::select();
+
+
+    }
+
+    private function check_ip($ip_arr)
+    {
+
+
+        foreach ($ip_arr as $key => $value) {
+
+
+            $response = $this->check_proxy_ip_info($value);
+            $ips = explode(":", $value);
+            //将结果存入数据库
+            if ($response) {
+                $insert = [
+                    'ip' => $ips[0],
+                    'port' => $ips[1],
+                    'status' => 1
+                ];
+                Proxy::replace($insert, ['ip' => $ips[0], 'port' => $ips[1]]);
+                echo $value . "\tsuccess " . PHP_EOL;
+            } else {
+                Proxy::update(['status' => 0], ['ip' => $ips[0], 'port' => $ips[1]]);
+                echo $value . "\terror  " . PHP_EOL;
+            }
+
+        }
+    }
+
+    //获取各大网站代理IP
+    private function get_ip()
+    {
+        $ip_arr = [];
+        echo "正在获取第一个IP列表" . PHP_EOL;
+        $ip_arr = $this->get_xici_ip($ip_arr);
+        echo "正在获取第二个IP列表" . PHP_EOL;
+        $ip_arr = $this->get_kuaidaili_ip($ip_arr);
+        return $ip_arr;
+    }
+
+    private function get_xici_ip($ip_arr)
+    {
+        for ($i = 1; $i <= 10; $i++) {
+            echo sprintf("正在获取xici第%s页IP列表", $i) . PHP_EOL;
+            $url = sprintf("http://www.xicidaili.com/nn/%s", intval($i));
+            $infoContent = Curl::get($url);
+            $this->convert_encoding($infoContent);
+            preg_match_all('/<tr.*>[\s\S]*?<td class="country">[\s\S]*?<\/td>[\s\S]*?<td>(.*?)<\/td>[\s\S]*?<td>(.*?)<\/td>/', $infoContent, $match);
+
+            $host_arr = $match[1];
+            $port_arr = $match[2];
+            foreach ($host_arr as $key => $value) {
+                $ip_arr[] = $host_arr[$key] . ":" . $port_arr[$key];
+            }
+            sleep(2);
+        }
+
+        print_r($ip_arr);
+        return $ip_arr;
+    }
+
+    private function get_kuaidaili_ip($ip_arr)
+    {
+        for ($i = 1; $i <= 10; $i++) {
+            echo sprintf("正在获取kuaidaili第%s页IP列表", $i) . PHP_EOL;
+            $url = 'https://www.kuaidaili.com/free/inha/' . $i;
+            $infoContent = Curl::get($url);
+            $this->convert_encoding($infoContent);
+
+            preg_match_all('/<td data-title="IP">(.*?)<\/td>/', $infoContent, $host);
+            preg_match_all('/<td data-title="PORT">(.*?)<\/td>/', $infoContent, $port);
+
+            $host_arr = $host[1];
+            $port_arr = $port[1];
+            foreach ($host_arr as $key => $value) {
+                $ip_arr[] = $host_arr[$key] . ":" . $port_arr[$key];
+            }
+            sleep(2);
+        }
+        print_r($ip_arr);
+        return $ip_arr;
+    }
+
+    /**
+     * +-----------------------------------------------------------------------------
+     * 检查代理ip信息有效性
+     * +-----------------------------------------------------------------------------
+     * @param string $proxy_ip [117.95.100.126:8998]
+     * @param int $times 执行检查次数
+     * @return bool
+     * @author elinx <654753115@qq.com> 2016-07-29
+     * +-----------------------------------------------------------------------------
+     */
+    function check_proxy_ip_info($proxy_ip = '', $times = 1)
+    {
+        $status = false;
+        $header = array(
+            // "GET / HTTP/1.1",
+            // "HOST: www.baidu.com",
+            "accept: application/json",
+            "accept-encoding: gzip, deflate",
+            "accept-language: en-US,en;q=0.8",
+            "content-type: application/json",
+            "user-agent: Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/51.0.2704.106 Safari/537.36",
+        );
+        $url = 'http://www.baidu.com/';
+        $result['succeed_times'] = 0; //成功次数
+        $result['defeat_times'] = 0; //失败次数
+        $result['total_spen'] = 0; //总用时
+        $ip = explode(":", $proxy_ip);
+        for ($i = 0; $i < $times; $i++) {
+            $curl = curl_init();
+            curl_setopt($curl, CURLOPT_URL, $url); //设置传输的url
+            curl_setopt($curl, CURLOPT_HTTPHEADER, $header); //发送http报头
+            curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1);
+            curl_setopt($curl, CURLOPT_ENCODING, 'gzip,deflate'); // 解码压缩文件
+            curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, false); //不验证证SSL书
+            curl_setopt($curl, CURLOPT_SSL_VERIFYHOST, false); //不验证SSL证书
+
+            curl_setopt($curl, CURLOPT_HTTPHEADER, array(
+                'Client_Ip: ' . $ip,
+            ));
+            curl_setopt($curl, CURLOPT_HTTPHEADER, array(
+                'X-Forwarded-For: ' . $ip,
+            ));
+            curl_setopt($curl, CURLOPT_PROXYTYPE, CURLPROXY_HTTP);
+            curl_setopt($curl, CURLOPT_PROXY, $proxy_ip);
+
+            curl_setopt($curl, CURLOPT_COOKIEFILE, dirname(__FILE__) . '/cookie.txt');
+            curl_setopt($curl, CURLOPT_COOKIEJAR, dirname(__FILE__) . '/cookie.txt');
+            curl_setopt($curl, CURLOPT_TIMEOUT, 10); // 设置超时限制防止死循环
+            // $response_header = curl_getinfo($curl); // 获取返回response报头
+            $content = curl_exec($curl);
+            if (strstr($content, '百度一下,你就知道')) {
+                $status = true;
+                break;
+            } else {
+                $status = false;
+            }
+
+            // $result['list'][$i]['response_header'] = $response_header;
+        }
+        return $status;
+    }
+
+    private function convert_encoding(&$str)
+    {
+        $encode = mb_detect_encoding($str, ['GB2312', 'UTF-8', 'GBK', 'ASCII']);
+        $str = iconv($encode, 'utf-8//TRANSLIT//IGNORE', $str);
+    }
+
+}

+ 85 - 0
App/Script/Controller/Task.php

@@ -0,0 +1,85 @@
+<?php
+
+
+namespace Heanup\App\Script\Controller;
+
+
+use Heanup\App\Api\Model\Dataoke;
+use Heanup\App\Api\Package\Database\Goods;
+use Heanup\App\Api\Package\Database\WechatTask;
+use Heanup\App\Script\Controller;
+use Heanup\Library\WechatLib;
+
+class Task extends Controller
+{
+    protected function main()
+    {
+
+        while (true) {
+            try {
+
+
+                //开始商品采集
+                $dataoke = new Dataoke();
+                $wechat['wxid_saebe60wd0mi22'] = WechatLib::getInstance("http://myhome.97admin.com:8074/send");
+                $wechat['wxid_2tv9hne5x9bm22'] = WechatLib::getInstance("http://myhome.97admin.com:8073/send");
+                $hour = intval(date("H"));
+                $key = "/tmp/get_data_v2_" . date("YmdH");
+                $data = file_get_contents($key);
+                if (!$data) {
+                    Goods::delete([':add_time' => date("Y-m-d H:i:s", time() - 86400 * 2)], "add_time<:add_time");
+                    for ($i = 1; $i <= 4; $i++) {
+                        $dataoke->getProducts($i);
+                    }
+                    file_put_contents($key, 1);
+                }
+                //商品采集结束,开始进行消息推送
+                if ($hour >= 7 && $hour <= 24) {
+                    //收集待推送的群组列表
+                    $configs = $task_config = WechatTask::select([], ['status' => 1], "", null, 0, ['id' => false]);
+                    $result = $dataoke->send(0);
+                    while (!$result) {
+                        $result = $dataoke->send(0);
+                    }
+                    foreach ($configs as $item) {
+                        if ($item['id'] != 1) {
+                            $result = $dataoke->send(0, $item['user'], $result['data']);
+                        }
+                        if (!$result) {
+                            continue;
+                        }
+                        echo date("Y-m-d H:i:s") . "\t正在发送" . $item['user'] . "的宝贝:\t" . $result['data']['dtitle'] . PHP_EOL;
+                        if ($item['id'] == 1) {
+                            $wechat[$item['robot_wxid']]->sendImageMsg($result['pic'], $item['robot_wxid'], $item['group_id']);
+                            sleep(1);
+                            $wechat[$item['robot_wxid']]->sendTextMsg($result['msg'], $item['robot_wxid'], $item['group_id']);
+                            sleep(1);
+                            $_key = "/tmp/cache_" . $item['robot_wxid'];
+                            $date = intval(file_get_contents($_key));
+                            if (time() - $date > 3600 * 4 && $hour < 20) {
+                                $wechat[$item['robot_wxid']]->sendImageMsg("https://www.phplife.net/ele.jpg", $item['robot_wxid'], '6657767651@chatroom');
+                                file_put_contents($_key, time());
+                            }
+                        } else {
+                            $wechat[$item['robot_wxid']]->sendTextMsg($result['msg'], $item['robot_wxid'], $item['group_id']);
+                            sleep(1);
+                            $wechat[$item['robot_wxid']]->sendImageMsg($result['pic'], $item['robot_wxid'], $item['group_id']);
+                            sleep(1);
+                        }
+
+                    }
+                    $sleep = rand(500, 800);
+                    echo "休息" . $sleep . "秒" . PHP_EOL;
+                    sleep($sleep);
+                } else {
+                    echo "当前时段不支持发送消息!";
+                    sleep(1801);
+                }
+
+            } catch (\Exception $exception) {
+                print_r($exception);
+            }
+        }
+
+    }
+}

+ 13 - 0
App/Script/Controller/cookie.txt

@@ -0,0 +1,13 @@
+# Netscape HTTP Cookie File
+# http://curl.haxx.se/docs/http-cookies.html
+# This file was generated by libcurl! Edit at your own risk.
+
+.baidu.com	TRUE	/	FALSE	3708582133	BAIDUID	181AB8E9C8259EED03E2A7A36579D046:FG=1
+.baidu.com	TRUE	/	FALSE	3708582133	BIDUPSID	181AB8E9C8259EED03E2A7A36579D046
+.baidu.com	TRUE	/	FALSE	3708582133	PSTM	1561098486
+.baidu.com	TRUE	/	FALSE	0	delPer	0
+www.baidu.com	FALSE	/	FALSE	0	BDSVRTM	0
+www.baidu.com	FALSE	/	FALSE	0	BD_HOME	0
+.baidu.com	TRUE	/	FALSE	0	H_PS_PSSID	1442_21088_29135_29237_28518_29098_29131_29369_28836_29220_26350_22160
+.baidu.com	TRUE	/	FALSE	1561110336	_cnzz_zz	1101
+www.baidu.com	FALSE	/	FALSE	1561111167	EHBMkWrwVDCRpbJuINRKPjR	1

+ 18 - 0
App/Script/Package/Database/Code.php

@@ -0,0 +1,18 @@
+<?php
+
+namespace Heanup\App\Script\Package\Database;
+
+use Heanup\Frame\Package\Database\Single;
+
+class Code extends Single
+{
+
+    const CONFIG_CLASS = 'MySQL\\Shadowsocks';
+
+    protected static $tableName = 'invite_code';
+
+    protected static $fields = array();
+
+    protected static $primaryKey = 'id';
+
+}

+ 29 - 0
Autoload.php

@@ -0,0 +1,29 @@
+<?php
+
+namespace Heanup;
+
+// 设置自动加载
+spl_autoload_register('\Heanup\Autoload::main');
+
+class Autoload
+{
+    /**
+     * 自动加载
+     * @param $className
+     */
+    public static function main($className)
+    {
+        $file = trim($className, '\\');
+        $file = explode('\\', $file);
+        // 判断顶级命名空间
+        $path = __DIR__;
+        $topNameSpace = array_shift($file);
+        if ($topNameSpace != 'Heanup') {
+            $path = $path . DIRECTORY_SEPARATOR . 'vendor' . DIRECTORY_SEPARATOR . $topNameSpace;
+        }
+        $file = $path . DIRECTORY_SEPARATOR . implode(DIRECTORY_SEPARATOR, $file) . '.php';
+        if (is_file($file)) {
+            require_once($file);
+        }
+    }
+}

+ 21 - 0
Config/Route.php

@@ -0,0 +1,21 @@
+<?php
+/**
+ * Created by PhpStorm.
+ * User: chendeben
+ * Date: 2019-02-22
+ * Time: 16:50
+ */
+
+namespace Heanup\Config;
+
+use Heanup\Frame\Config;
+
+class Route extends Config
+{
+    public static function getData($section = null)
+    {
+        $class="Heanup\App\\".$section."\Config\Route";
+        return $class::getData();
+    }
+
+}

+ 98 - 0
Frame/Config.php

@@ -0,0 +1,98 @@
+<?php
+
+namespace Heanup\Frame;
+
+/**
+ * 配置文件
+ */
+class Config
+{
+    protected static $dataFile = ''; // 配置文件目录,绝对路径,json格式,优先使用覆盖$data
+    protected static $data = array();
+
+    /**
+     * 获取配置信息,
+     * 如果配置了$dataFile则读取后者中的数据
+     * @param null $section
+     * @return mixed
+     */
+    public static function getData($section = null)
+    {
+        $class = get_called_class();
+        // 只读取一次文件
+        if ($class::$dataFile) {
+            $path = self::getPathByMode($class::$dataFile);
+            $class::$data = self::readFile($path);
+            $class::$dataFile = '';
+        }
+        $data = $class::$data;
+        if (!is_null($section)) {
+            if (isset($data[$section])) {
+                return $data[$section];
+            } else {
+                return null;
+            }
+        }
+        return $data;
+    }
+
+    /**
+     * 根据后缀读取文件内容并且返回
+     * ini 直接解析
+     * json 读取后解析,如果解析失败返回空
+     * php 文件, include
+     * 其他文件 直接读取内容并且返回
+     *
+     * @param $path
+     * @return array | string;
+     */
+    public static function readFile($path)
+    {
+        $data = array();
+        if (!file_exists($path)) {
+            return $data;
+        }
+        $pathInfo = pathinfo($path);
+        $extension = strtolower($pathInfo['extension']);
+        if ($extension == 'ini') {
+            $data = parse_ini_file($path);
+        } elseif ($extension == 'json') {
+            $content = file_get_contents($path);
+            if (!$content) {
+                return $data;
+            }
+            $content = json_decode($content, true);
+            if (!$content) {
+                return $data;
+            }
+            $data = $content;
+            unset($content);
+        } elseif ($extension == 'php') {
+            $data = include($path);
+        } else {
+            $data = file_get_contents($path);
+        }
+        return $data;
+    }
+
+    /**
+     * 根据环境变量$mode获取文件路径
+     * @param $filename
+     * @return string
+     */
+    protected static function getPathByMode($filename)
+    {
+        $envPath = dirname(__DIR__) . '/env';
+        $mode = Request::getMode();
+        $path = $envPath.'/product/';
+        switch ($mode) {
+            case Request::MODE_ONLINE:
+                $path = $envPath.'/product/';
+                break;
+        }
+        $filename = $path . ltrim($filename, '/');
+        return $filename;
+    }
+
+
+}

+ 283 - 0
Frame/Controller.php

@@ -0,0 +1,283 @@
+<?php
+
+namespace Heanup\Frame;
+
+abstract class Controller
+{
+    protected $tpl; // 模板路径, 可选
+    protected $queryObj;
+    protected $data_name = false;
+    protected $isrestful = false;
+
+    final public function __construct()
+    {
+        $this->queryObj = $this->getQuery();
+    }
+
+    /**
+     * 主逻辑
+     */
+    final public function run()
+    {
+        try {
+            $this->before();
+            $method = strtolower($_SERVER['REQUEST_METHOD']);
+            if ($method == 'options') {
+                $this->optionsData();
+            }
+            $this->checkAuth();
+            $data_name = $method . 'Data';
+            if ($this->isrestful) {
+                if (!method_exists(get_called_class(), $data_name)) {
+                    Response::sendResponse(405, ['method' => strtoupper($method)], 'Method not allowed');
+                    die();
+                }
+                $this->$data_name();
+            } else {
+                $this->main();
+            }
+        } catch (\Exception $ex) {
+            $this->showError($ex->getCode(), $ex->getMessage());
+        }
+    }
+
+    public function optionsData()
+    {
+        header("HTTP/1.1 200");
+        header("Access-Control-Allow-Origin: *");
+        header("Access-Control-Allow-Methods: OPTIONS, GET, POST, PUT, PATCH, DELETE");
+//        header("Access-Control-Max-Age: 3");
+        header("Access-Control-Allow-Headers: lang,token,content-type");
+        die();
+
+    }
+
+    /**
+     * 尽快输出结果,然后执行后续逻辑
+     * 本方法的的信息不会被输出
+     * 即使exit之后仍旧能被执行
+     */
+    final public function __destruct()
+    {
+        if (function_exists('fastcgi_finish_request')) {
+            fastcgi_finish_request();
+        }
+        $this->after();
+    }
+
+    /**
+     * 前置方法,处理参数
+     */
+    protected function before()
+    {
+
+    }
+
+    /**
+     * 执行验证
+     */
+    protected function checkAuth()
+    {
+
+    }
+
+    /**
+     * 执行逻辑
+     */
+    protected function main()
+    {
+    }
+
+    /**
+     * 后续方法
+     */
+    protected function after()
+    {
+
+    }
+    /**
+     * @return Filter
+     */
+    protected function getRequest()
+    {
+        static $instance = null;
+        parse_str(file_get_contents('php://input'), $data);
+        $data = array_merge($_POST, $_GET, $_COOKIE, $data);
+        if (is_null($instance)) {
+            $instance = new Filter($data);
+        }
+        return $instance;
+    }
+
+    /**
+     * @return Filter
+     */
+    protected function getPut()
+    {
+        static $instance = null;
+        try {
+            $putData = file_get_contents("php://input");
+            $resultData = json_decode($putData, true);
+            if (is_array($resultData)) {
+                //解析IOS提交的PUT数据
+                $data = $resultData;
+            } elseif (!strstr($putData, "\r\n")) {
+                //解析本地测试工具提交的PUT数据
+                parse_str($putData, $putData);
+                $data = $putData;
+            } else {
+                //解析PHP CURL提交的PUT数据
+                $putData = explode("\r\n", $putData);
+                $resultData = [];
+                foreach ($putData as $key => $data) {
+                    if (substr($data, 0, 20) == 'Content-Disposition:') {
+                        $match = explode(";", $data);
+                        if (count($match) == 2) {
+                            preg_match('/.*\"(.*)\"/', $data, $matchName);
+                            $resultData[$matchName[1]] = $putData[$key + 2];
+                        } else {
+                            $info = self::parserInfo($data);
+                            $file = fopen($info['tmp_name'], "r+");
+                            fwrite($file, $putData[$key + 3]);
+                            fclose($file);
+                            $resultData[$info['field']] = $info;
+                        }
+
+                    }
+                }
+                $data = $resultData;
+            }
+
+        } catch (\Exception $e) {
+            $data = [];
+        }
+
+
+        if (is_null($instance)) {
+            $instance = new Filter($data);
+        }
+        return $instance;
+    }
+
+    private static function parserInfo($data, $options = ['saveFile' => true])
+    {
+        //获取参数名称, type
+        $infoPattern = '/name="(.+?)"(; )?(filename="(.+?)")?/'; //todo: 待优化
+        preg_match($infoPattern, $data, $matches);
+
+        $info['field'] = $matches[1];
+        $info['type'] = 'json';
+
+        //如果是文件
+        if (count($matches) > 4) {
+            $info['type'] = 'file';
+            $info['name'] = $matches[4];
+            //如果设置保存文件, 保存到临时文件
+            if (isset($options['saveFile']) && $options['saveFile']) {
+                $tmpFile = tempnam(sys_get_temp_dir(), 'FD');
+
+                $info['tmp_name'] = $tmpFile;
+            }
+        }
+
+        return $info;
+    }
+
+    protected function getJson()
+    {
+        static $instance = null;
+        $data = file_get_contents('php://input');
+        $data = json_decode($data, true) ?: [];
+        if (is_null($instance)) {
+            $instance = new Filter($data);
+        }
+        return $instance;
+    }
+
+    /**
+     * @return Filter
+     */
+    protected function getQuery()
+    {
+        static $instance = null;
+        if (is_null($instance)) {
+            $instance = new Filter($_GET);
+        }
+        return $instance;
+    }
+
+    protected function getFilter()
+    {
+        $str = $this->getQuery()->string("filter");
+        $str = explode(";", $str);
+        if ($str) {
+            foreach ($str as $item) {
+                $item = explode(":", $item);
+                if ($item[1] == "") {
+                    continue;
+                }
+                $_Filter[$item[0]] = $item[1];
+            }
+        }
+        $_Filter['page'] = $_GET['page'] ?: ($_Filter['page'] ?: 1);
+        $_Filter['pageSize'] = $_GET['pageSize'] ?: ($_Filter['pageSize'] ?: 20);
+        static $instance = null;
+        if (is_null($instance)) {
+            $instance = new Filter($_Filter);
+        }
+        return $instance;
+    }
+
+    /**
+     * @return Filter
+     */
+    protected function getPost()
+    {
+        static $instance = null;
+        if (is_null($instance)) {
+            $instance = new Filter($_POST);
+        }
+        return $instance;
+    }
+
+    /**
+     * 获取模板, 固定模板, 与controller名称类似
+     * @return Template
+     */
+    protected function getTemplate()
+    {
+        static $template;
+        if ($template) {
+            return $template;
+        }
+        $class = get_called_class();
+        $class = explode('\\', $class);
+        $path = dirname(__DIR__) . '/' . implode('/', array_slice($class, 1, 2)) . '/Template/';
+        if (!$this->tpl) {
+            $this->tpl = implode('/', array_slice($class, 4));
+        }
+        $template = new Template();
+        $template->setPath($path)->setTpl($this->tpl);
+        $data = get_object_vars($this);
+        if ($data) {
+            $template->assign($data);
+        }
+        return $template;
+    }
+
+    /**
+     * 检查CSRF攻击
+     * @return bool
+     */
+    protected function checkCsrf()
+    {
+        $host = strtolower(trim($_SERVER['HTTP_HOST']));
+        $referer = parse_url($_SERVER['HTTP_REFERER']);
+        $referer = substr(strtolower(trim($referer['host'])), -(strlen($host)));
+        if (!empty($referer) AND $referer != $host AND $referer != ('www.' . $host)) {
+            return false;
+        }
+        return true;
+    }
+
+}

+ 187 - 0
Frame/Dispatcher.php

@@ -0,0 +1,187 @@
+<?php
+
+namespace Heanup\Frame;
+
+
+use Heanup\Config\Route;
+use Heanup\Frame\Logger;
+
+class Dispatcher
+{
+    const CONFIG_PREFIX = '\\Heanup\\Config';
+
+    const CONTROLLER_ERROR = 'Error';
+    const CONTROLLER_DEFAULT = 'Index';
+    const METHOD_DEFAULT = 'main';
+
+    /**
+     * 使用方法
+     * @param $customPrefix
+     * @param string $pathInfo
+     */
+    public static function run($customPrefix, $pathInfo = '')
+    {
+        // 获取前缀
+        $prefix = '\Heanup\\App\\' . self::convertToClassName($customPrefix) . "\\Controller";
+        if (!$pathInfo) {
+            $pathInfo = self::getPathInfo();
+        }
+        $pathInfo = self::convertToClassName($pathInfo);
+        $pathInfo = explode('\\', $pathInfo);
+        if (count($pathInfo) > 1) {
+            $method = array_pop($pathInfo);
+        } else {
+            $method = self::METHOD_DEFAULT;
+        }
+        $controller = $prefix . '\\' . implode('\\', $pathInfo);
+        // 加入一次出错重试的机会
+        for ($i = 0; $i < 2; $i++) {
+            try {
+                // 判断是否是公开方法
+                $reflection = new \ReflectionMethod($controller, $method);
+                if ($reflection->isPublic() == false) {
+                    throw new \Exception($controller . "::$method is not public");
+                }
+                $controller = new $controller();
+                $controller->$method();
+                break;
+            } catch (\Exception $ex) {
+                Logger::emerg($ex, 'dispatcher.run');
+                $controller = $prefix . '\\' . self::CONTROLLER_ERROR;
+                $method = self::METHOD_DEFAULT;
+            }
+        }
+    }
+
+    /**
+     * 分发请求
+     * @param string $customPrefix
+     * @param string $pathInfo
+     */
+    public static function dispatch($customPrefix, $pathInfo = '')
+    {
+        // 获取前缀
+        $prefix = '\Heanup\\App\\' . self::convertToClassName($customPrefix) . "\\Controller";
+        if (!$pathInfo) {
+            $pathInfo = self::getPathInfo();
+        }
+        $pathInfo = self::convertToClassName($pathInfo);
+        try {
+            $path = explode('\\', $pathInfo);
+            $id = array_pop($path);
+            if (is_numeric($id)) {
+                $_GET['id'] = $id;
+                $pathInfo = implode('\\', $path);
+            }
+            $controller = $prefix . '\\' . $pathInfo;
+            // 如果类不存在则使用默认的错误类
+            if (!class_exists($controller)) {
+                //类不存在则尝试通过自定义路由解析
+                $url = $_SERVER["REQUEST_URI"];
+                // 去除 ?后面的部分
+                $request = parse_url($url);
+                $url = $request['path'];
+                $route = Route::getData($customPrefix);
+                foreach ($route as $val) {
+                    $pattern = $val["pattern"];
+                    $params = self::checkUrl(strtolower($url), strtolower($pattern));
+                    if ($params != null) {
+                        $controller = $val["action"];
+                        $count = count($params);
+                        for ($i = 0; $i < $count; $i++) {
+                            $_GET['args_' . $i] = $params[$i];
+                        }
+                        break;// 寻找到第一个匹配的路由即执行,然后返回
+                    }
+                }
+                if (!class_exists($controller)) {
+                    Logger::error($controller, 'dispatcher.controller_not_exist');
+                    throw new \Exception($controller . ' not exist');
+                }
+
+            }
+            // 判断主方法
+            if (!method_exists($controller, 'run')) {
+                throw new \Exception($controller . ' has no method called "run"');
+            }
+            $controller = new $controller();
+            $controller->run();
+        } catch (\Exception $ex) {
+            $method = strtolower($_SERVER['REQUEST_METHOD']);
+            if ($method == 'options') {
+                header("HTTP/1.1 200");
+                header("Access-Control-Allow-Origin: *");
+                header("Access-Control-Allow-Methods: GET, POST, PUT, PATCH, DELETE");
+                header("Access-Control-Allow-Headers: lang,token,content-type");
+                die();
+            }
+            Response::sendResponse(404, [], $ex->getMessage());
+            Logger::emerg($ex, 'dispatcher.dispatch');
+        }
+    }
+
+    /**
+     * 正则匹配检查,并提取出参数
+     * @param $str
+     * @param $pattern
+     * @return array|null
+     */
+    private static function checkUrl($str, $pattern)
+    {
+        $ma = array();
+        $pattern = ltrim(rtrim($pattern, "/"));
+        $pattern = "/" . str_replace("/", "\/", $pattern) . "\/?$/";
+        $pattern = str_replace(":s", "([^\/]+)", $pattern);
+        if (preg_match($pattern, $str, $ma) > 0) {
+            array_shift($ma);
+            return $ma;
+        }
+        return null;
+    }
+
+    /**
+     * 获取pathInfo, 兼容Apache, Nginx
+     * 如果使用Apache的话可以直接使用 $_SERVER['PATH_INFO']的
+     * @return string
+     */
+    protected static function getPathInfo()
+    {
+        //去掉uri中的PHP_SELF 信息
+        $length = strlen($_SERVER['PHP_SELF']);
+        if (substr($_SERVER['REQUEST_URI'], 0, $length) == $_SERVER['PHP_SELF']) {
+            $request = substr($_SERVER['REQUEST_URI'], $length);
+        } else {
+            $request = $_SERVER['REQUEST_URI'];
+        }
+        // 去除 ?后面的部分
+        $request = parse_url($request);
+        $request = $request['path'];
+        $request = urldecode($request);
+        $request = trim($request, '/_ ');
+        // 去掉 '.' 后缀
+        if (strpos($request, '.') !== false) {
+            $request = explode('.', $request);
+            $request = array_shift($request);
+        }
+        if (empty($request)) {
+            $request = self::CONTROLLER_DEFAULT;
+        }
+        return $request;
+    }
+
+    /**
+     * 将字符串转成带命名空间的类名
+     * @param $string
+     * @return string
+     */
+    public static function convertToClassName($string)
+    {
+        $string = trim($string, '\\/_ ');
+        $string = strtolower($string);
+        $string = str_replace('/', '\\', $string);
+        $string = str_replace(' ', '\\', ucwords(str_replace('\\', ' ', $string)));
+        $string = str_replace(' ', '', ucwords(str_replace('_', ' ', $string)));
+        return $string;
+    }
+
+}

+ 20 - 0
Frame/Exception.php

@@ -0,0 +1,20 @@
+<?php
+
+namespace Heanup\Frame;
+
+/**
+ * 主要是在抛出异常的时候记录出错信息
+ * Class Exception
+ * @package Heanup\Frame
+ */
+class Exception extends \Exception
+{
+    protected $filename = 'exception';
+
+    public function __construct($message = '', $code = 0, $previous = null)
+    {
+        $data = array('message' => $message, 'code' => $code, 'previous' => $previous);
+        Logger::info($data, $this->filename);
+        parent::__construct($message, $code, $previous);
+    }
+}

+ 13 - 0
Frame/Exception/Database.php

@@ -0,0 +1,13 @@
+<?php
+
+namespace Heanup\Frame\Exception;
+
+/**
+ * 数据库异常
+ * @package Heanup\Frame\Exception
+ */
+class Database extends \Heanup\Frame\Exception
+{
+    protected $filename = 'exception.database';
+
+}

+ 12 - 0
Frame/Exception/Validate.php

@@ -0,0 +1,12 @@
+<?php
+
+namespace Heanup\Frame\Exception;
+
+/**
+ * 输入信息验证出错
+ */
+class Validate extends \Heanup\Frame\Exception
+{
+    protected $filename = 'exception.validate';
+
+}

+ 251 - 0
Frame/Filter.php

@@ -0,0 +1,251 @@
+<?php
+namespace Heanup\Frame;
+
+use Heanup\Frame\Helper\Validate;
+
+/**
+ * 参数过滤类
+ */
+class Filter
+{
+
+    protected $_data = array();
+
+    public function __construct(Array $data = array())
+    {
+        foreach ($data as $name => $value) {
+            $this->_data[$name] = $value;
+        }
+    }
+
+    /**
+     * 过滤字符串
+     *
+     * @param string $name
+     * @param string $default
+     * @return string
+     */
+    public function string($name, $default = null)
+    {
+        $result = null;
+        if (isset($this->_data[$name])) {
+            $result = $this->_data[$name];
+            $result = trim($result);
+            $result = (string)$result;
+        } elseif (!is_null($default)) {
+            $result = $default;
+        }
+        return $result;
+    }
+
+    /**
+     * 批量获取字符串
+     *
+     * @param string $name
+     * @param string $default
+     * @return string
+     */
+    public function getBatchString($arr, $isString = TRUE)
+    {
+        $result = null;
+        if (is_array($arr)) {
+            foreach ($arr as $v) {
+                if (isset($this->_data[$v])) {
+                    $result[$v] = $isString ? (string)trim($this->_data[$v]) : $this->_data[$v];
+                }
+            }
+        }
+        return $result;
+    }
+
+    /**
+     * 获取手机区号
+     *
+     * @param string $name
+     * @param string $default
+     * @return string
+     */
+    public function mobileCode($name = 'mobile_code', $default = '+86')
+    {
+        $mobileCode = $this->string($name, $default);
+        $mobileCode = '+' . ltrim($mobileCode, '+');
+        return $mobileCode;
+    }
+
+    /**
+     * 过滤整型
+     *
+     * @param string $name
+     * @param int $default
+     * @return int
+     */
+    public function intval($name, $default = null)
+    {
+        $result = null;
+        if (isset($this->_data[$name])) {
+            $result = $this->_data[$name];
+            $result = trim($result);
+            $result = intval($result);
+        } elseif (!is_null($default)) {
+            $result = $default;
+        }
+        return $result;
+    }
+
+    /**
+     * 过滤浮点类型
+     *
+     * @param string $name
+     * @param float $default
+     * @return float
+     */
+    public function float($name, $default = null)
+    {
+        $result = null;
+        if (isset($this->_data[$name])) {
+            $result = $this->_data[$name];
+            $result = trim($result);
+            $result = floatval($result);
+        } elseif (!is_null($default)) {
+            $result = $default;
+        }
+        return $result;
+    }
+
+    /**
+     * 验证手机号
+     *
+     * @param string $message
+     * @param int $code
+     * @param string $name
+     * @return int|string
+     * @throws \Exception
+     */
+    public function mobile($message = '手机号格式错误', $code = 45050, $name = 'mobile')
+    {
+        $result = '';
+        if (isset($this->_data[$name])) {
+            $result = $this->_data[$name];
+            $result = trim($result);
+            $result = intval($result);
+        }
+        if (!$result || Validate::isMobile($result) == false) {
+            throw new \Heanup\Frame\Exception\Validate($message, $code);
+        }
+        return $result;
+    }
+
+    /**
+     * 获取json数据格式
+     *
+     * @param
+     *            $name
+     * @param
+     *            $default
+     * @return array
+     */
+    public function json($name, $default = null)
+    {
+        $result = null;
+        if (isset($this->_data[$name])) {
+            $result = $this->_data[$name];
+            $result = trim($result);
+            $decoded = json_decode($result,true);
+            if ($decoded) {
+                $result = $decoded;
+            } else {
+                $result = array();
+            }
+        } elseif (!is_null($default)) {
+            $result = $default;
+        }
+        return $result;
+    }
+
+    /**
+     * 检查字段是否存在,可选检查是否为空
+     *
+     * @param string $name
+     * @param bool $checkEmpty
+     * @param string $message
+     * @param int $code
+     * @throws \Exception
+     * @return bool
+     */
+    public function exists($name, $checkEmpty = false, $message = '', $code = 0)
+    {
+        if ($checkEmpty) {
+            $result = isset($this->_data[$name]) && (is_string($this->_data[$name]) && strlen($this->_data[$name]) > 0);
+        } else {
+            $result = isset($this->_data[$name]);
+        }
+        if (!$result && $message) {
+            throw new \Heanup\Frame\Exception\Validate($message, $code);
+        }
+        return $result;
+    }
+
+    /**
+     * 获取数组数据
+     *
+     * @param
+     *            $name
+     * @param array $default
+     * @return array|mixed
+     */
+    public function getArray($name, $default = array())
+    {
+        $result = array();
+        if (isset($this->_data[$name])) {
+            $result = $this->_data[$name];
+        } elseif (!is_null($default)) {
+            $result = $default;
+        }
+        return $result;
+    }
+
+    /**
+     * 参数验证
+     * @param $data -参数
+     * @param bool $required -是否必须
+     * @return array
+     * @throws Exception
+     */
+    public function params($data, $required = false)
+    {
+
+        $res = array();
+        foreach ($data as $name => $rule) {
+            if ($required) {
+                $this->required($name);
+            }
+            $res[$name] = $this->$rule($name);
+        }
+        return $res;
+    }
+
+    public function required($name)
+    {
+        if($this->_data[$name] || strlen($this->_data[$name]) > 0){
+            return $this->_data[$name];
+        }else{
+            throw new Exception('缺少必填参数:' . $name,400);
+        }
+    }
+
+    public function compare($name, $value, $echo = 'selected')
+    {
+        if (isset($this->_data[$name]) && $this->_data[$name] !== '' && $this->_data[$name] == $value) {
+            echo $echo;
+            return;
+        } else {
+            echo '';
+            return;
+        }
+    }
+
+    public function toArray()
+    {
+        return $this->_data;
+    }
+}

+ 341 - 0
Frame/Helper.php

@@ -0,0 +1,341 @@
+<?php
+
+namespace Heanup\Frame;
+
+
+class Helper
+{
+
+    /*
+    * 检测链接是否是SSL连接
+    * @return bool
+    */
+    public static function isSSL()
+    {
+        if (!isset($_SERVER['HTTPS']))
+            return false;
+        if ($_SERVER['HTTPS'] === 1) {  //Apache
+            return true;
+        } elseif ($_SERVER['HTTPS'] === 'on') { //IIS
+            return true;
+        } elseif ($_SERVER['SERVER_PORT'] == 443) { //其他
+            return true;
+        }
+        return false;
+    }
+
+    /**
+     * 随机数生成器
+     */
+    public static function random()
+    {
+        return md5(microtime() . rand() . uniqid() . $_SERVER['REMOTE_ADDR']);
+    }
+
+    /**
+     * 简单验证email
+     * @param $email
+     * @return bool
+     */
+    public static function validateEmail($email)
+    {
+        $email = trim($email);
+        if (!preg_match("/([\w\-]+\@[\w\-]+\.[\w\-]+)/", $email)) {
+            return False;
+        }
+        return True;
+    }
+
+    /**
+     * 简单验证是否有效的URL
+     * @param $url
+     * @return bool
+     */
+    public static function validateURL($url)
+    {
+        $url = trim($url);
+        if (!preg_match("/\b(?:(?:https?|ftp):\/\/|www\.)[-a-z0-9+&@#\/%?=~_|!:,.;]*[-a-z0-9+&@#\/%=~_|]/i", $url)) {
+            return False;
+        }
+        return True;
+    }
+
+
+    /**
+     * 转换为友好时间
+     * @param int|string $timestamp
+     * @return string
+     */
+    public static function convertToFriendlyTime($timestamp)
+    {
+        if (!is_numeric($timestamp)) {
+            $timestamp = strtotime($timestamp);
+        }
+        $now = time();
+        $interval = abs($now - $timestamp);
+        $today = strtotime(date("Ymd"));
+        $tomorrow = strtotime(date("Ymd", $now + 86400));
+        $thisYear = strtotime(date("Y-01-01 00:00"));
+        if ($interval < 60) {
+            if ($interval < 10) {
+                $interval = 10;
+            }
+            $string = $interval . "秒前";
+        } else if ($interval < 3600) {
+            $string = floor($interval / 60) . '分钟前';
+        } else if ($today < $timestamp AND $timestamp < $tomorrow) {
+            $string = '今天' . date('H:i', $timestamp);
+        } else if ($thisYear < $timestamp) {
+            $string = date('n月j日', $timestamp);
+        } else {
+            $string = date('Y-m-d', $timestamp);
+        }
+        return $string;
+    }
+
+
+    /**
+     * 计算字符串长度,汉字按两个字符
+     * @param string $str
+     * @return int
+     */
+    public static function strlenUtf8($str)
+    {
+        $length = strlen(preg_replace('/[u4e00-u9fa5 ]/', '', $str));
+
+        if ($length) {
+            if($length < 3){
+                return $length;
+            }
+            return strlen($str) - $length + intval($length / 3) * 2;
+        } else {
+            return strlen($str);
+        }
+    }
+
+    /**
+     * 获取服务器IP
+     * @param bool $toLong 是否返回整形
+     * @return string
+     */
+    public static function getServerIp($toLong = false)
+    {
+        if (isset($_SERVER['SERVER_ADDR'])) {
+            $ip = $_SERVER['SERVER_ADDR'];
+        } else {
+            $domain = gethostname();
+            $ip = gethostbyname($domain);
+        }
+        if ($toLong) {
+            $ip = ip2long($ip);
+        }
+        return $ip;
+    }
+
+    /**
+     * 获取客户端Ip
+     * @param bool $toLong 是否返回整形
+     * @return string|int
+     */
+    public static function getClientIp($toLong = false)
+    {
+        $ip = '';
+        if (isset($_SERVER["HTTP_X_FORWARDED_FOR"]) && $_SERVER["HTTP_X_FORWARDED_FOR"]) {
+            $ip = $_SERVER["HTTP_X_FORWARDED_FOR"];
+        } elseif (isset($_SERVER["HTTP_CLIENT_IP"]) && $_SERVER["HTTP_CLIENT_IP"]) {
+            $ip = $_SERVER["HTTP_CLIENT_IP"];
+        } elseif (isset($_SERVER["REMOTE_ADDR"]) && $_SERVER["REMOTE_ADDR"]) {
+            $ip = $_SERVER["REMOTE_ADDR"];
+        }
+        $pos = strpos($ip, ',');
+        if ($pos) {
+            $ip = substr($ip, 0, $pos);
+        }
+        $ip = trim($ip);
+        if ($toLong) {
+            $ip = ip2long($ip);
+        }
+        return $ip;
+    }
+
+    /**
+     * 从 $argv 中解析合并参数, 兼容一下情况:
+     *      无前缀, 被解析成索引数组
+     *      单前缀(只能是单字母, 后面可以直接跟value, 也可以空格后面跟着value,二者不能混用), 被解析成关联数组
+     *      双前缀形式(可以是单字母也可以是多字母,后面必需跟着 '='), 被解析成关联数组
+     * 如果不确定, 请自行打印结果查看是否正确
+     * @example : php index.php first -s 'second' --third='third'
+     * @return array('first', 's' => 'second', 'third' => 'third')
+     */
+    public static function getArguments()
+    {
+        $arguments = $_SERVER['argv'];
+        for ($i = 0; $i < $_SERVER['argc']; $i++) {
+            if (!isset($arguments[$i][0])) {
+                continue;
+            }
+
+            //判断第一位是否是 '-'
+            if (substr($arguments[$i], 0, 1) != '-') {
+                continue;
+            }
+
+            //判断第二位是否是 '-'
+            if (substr($arguments[$i], 1, 1) != '-') {
+                if (strlen($arguments[$i]) == 2) {
+                    $key = substr($arguments[$i], 1);
+                    $value = $arguments[$i + 1];
+                    $arguments[$key] = trim($value);
+                    unset($arguments[$i]);
+                    unset($arguments[$i + 1]);
+                } elseif (strlen($arguments[$i]) > 2) {
+                    $key = substr($arguments[$i], 1, 1);
+                    $value = substr($arguments[$i], 2);
+                    $arguments[$key] = trim($value);
+                    unset($arguments[$i]);
+                }
+            } else {
+                $value = substr($arguments[$i], 2);
+                $value = explode('=', $value, 2);
+                $arguments[$value[0]] = trim($value[1]);
+                unset($arguments[$i]);
+            }
+        }
+        return $arguments;
+    }
+
+    /**
+     * 尝试解析字符串,如果解析失败则返回原值
+     * @param $string
+     * @return string | array
+     */
+    public static function jsonDecode($string)
+    {
+        if (!$string) {
+            return $string;
+        }
+        $result = json_decode($string, true);
+        if ($result) {
+            $string = $result;
+        }
+        return $string;
+    }
+
+    /**
+     * 获取数组分片, 从指定列表中获取指定元素之后的若干个切片
+     * @param $idList
+     * @param $lastId
+     * @param $count
+     * @return array
+     */
+    public static function getSlice($idList, $lastId = 0, $count = 20)
+    {
+        $result = array();
+        if (!$idList || ($lastId && !in_array($lastId, $idList))) {
+            return $result;
+        }
+        if ($lastId) {
+            $position = array_search($lastId, $idList) + 1;
+        } else {
+            $position = 0;
+        }
+        $result = array_slice($idList, $position, $count);
+        return $result;
+    }
+
+    /**
+     * 创建目录
+     * @param string $dir
+     * @param integer $mode
+     */
+    public static function mkdir($dir, $mode = 0755)
+    {
+        if (!is_dir($dir)) {
+            $temp = explode('/', $dir);
+            $cur_dir = '';
+            for ($i = 0; $i < count($temp); $i++) {
+                $cur_dir .= $temp[$i] . '/';
+                if (!is_dir($cur_dir)) {
+                    @mkdir($cur_dir, 0777);
+                    @chmod($cur_dir, $mode);
+                }
+            }
+        }
+    }
+
+    public static function copydir($strSrcDir, $strDstDir)
+    {
+        $dir = opendir($strSrcDir);
+        if (!$dir) {
+            return false;
+        }
+        if (!is_dir($strDstDir)) {
+            if (!mkdir($strDstDir)) {
+                return false;
+            }
+        }
+        while (false !== ($file = readdir($dir))) {
+            if (($file != '.') && ($file != '..')) {
+                if (is_dir($strSrcDir . '/' . $file)) {
+                    if (!self::copydir($strSrcDir . '/' . $file, $strDstDir . '/' . $file)) {
+                        return false;
+                    }
+                } else {
+                    if (!copy($strSrcDir . '/' . $file, $strDstDir . '/' . $file)) {
+                        return false;
+                    }
+                }
+            }
+        }
+        closedir($dir);
+        return true;
+    }
+
+    /**
+     * 获取强密码
+     * @param int $length
+     * @return string
+     */
+    public static function getStrongCode($length = 8)
+    {
+        $length = $length > 8 ? intval($length) : 8;
+        $arr = array();
+//        $arr[] = self::getRandoms($length - 4);
+        $arr[] = strtolower(self::getRandoms($length, 'char'));
+//        $arr[] = strtolower(self::getRandoms(1, 'char'));
+        $arr[] = mt_rand(0, 9);
+//        $special = '!@#$%^*()';
+//        $arr[] = $special{mt_rand(0, 11)};
+        shuffle($arr);
+        return implode('', $arr);
+    }
+
+    /**
+     * 获取随机数
+     *
+     * @param int $length
+     * @param string $type (可选)
+     * @return string
+     */
+    public static function getRandoms($length = 4, $type = '')
+    {
+        $returnStr = '';
+        $pattern = '1234567890abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLOMNOPQRSTUVWXYZ';
+        if ($type == 'char') {
+            $start = 10;
+            $end = 61;
+        } elseif ($type == 'num') {
+            $start = 0;
+            $end = 9;
+        } else {
+            $start = 0;
+            $end = 61;
+        }
+        for ($i = 0; $i < $length; $i++) {
+            $returnStr .= $pattern{mt_rand($start, $end)}; // 生成php随机数
+        }
+        return $returnStr;
+    }
+
+
+}

+ 67 - 0
Frame/Helper/Date.php

@@ -0,0 +1,67 @@
+<?php
+
+namespace Heanup\Frame\Helper;
+
+/**
+ * 文件操作
+ */
+class Date extends \Heanup\Frame\Helper
+{
+
+    /**
+     * 转换为友好时间
+     * @param int|string $timestamp
+     * @return string
+     */
+    public static function convert($timestamp)
+    {
+        if (!is_numeric($timestamp)) {
+            $timestamp = strtotime($timestamp);
+        }
+        $now = time();
+        $interval = abs($now - $timestamp);
+        $today = strtotime(date("Ymd"));
+        $tomorrow = strtotime(date("Ymd", $now + 86400));
+        $thisYear = strtotime(date("Y-01-01 00:00"));
+        if ($interval < 60) {
+            $string = "1分钟前";
+        } else if ($interval < 3600) {
+            $string = floor($interval / 60) . '分钟前';
+        } else if ($today < $timestamp AND $timestamp < $tomorrow) {
+            $string = '今天' . date('H:i', $timestamp);
+        } else if ($thisYear < $timestamp) {
+            $string = date('m-d H:i', $timestamp);
+        } else {
+            $string = date('y-m-d H:i', $timestamp);
+        }
+        return $string;
+    }
+
+    /**
+     * 获取相对时间
+     * @param $timestamp
+     * @param $customString ;
+     * @return string
+     */
+    public static function related($timestamp, $customString = '')
+    {
+        if (!is_numeric($timestamp)) {
+            $timestamp = strtotime($timestamp);
+        }
+        $interval = abs(time() - $timestamp);
+        if ($interval == 0) {
+            $interval = 1;
+        }
+        if ($interval < 3600) {
+            $string = ceil($interval / 60) . '分钟前';
+        } elseif ($interval < 86400) {
+            $string = ceil($interval / 3600) . '小时前';
+        } else {
+            $string = ceil($interval / 86400) . '天前';
+        }
+        $string = $string . $customString;
+        return $string;
+    }
+
+
+}

+ 39 - 0
Frame/Helper/File.php

@@ -0,0 +1,39 @@
+<?php
+
+namespace Heanup\Frame\Helper;
+
+/**
+ * 文件操作
+ */
+class File extends \Heanup\Frame\Helper
+{
+
+    /**
+     * 写入ini文件
+     * @param string $path
+     * @param string|array $data
+     * @return int|bool
+     */
+    public static function writeIniFile($path, $data)
+    {
+        $content = null;
+        foreach ($data as $key => $item) {
+            if (is_array($item)) {
+                $content .= "\n[{$key}]\n";
+                foreach ($item as $k => $v) {
+                    if (is_numeric($v) || is_bool($v)) {
+                        $v = '"' . $v . '"';
+                    }
+                    $content .= "{$k} = {$v}\n";
+                }
+            } else {
+                if (is_numeric($item) || is_bool($item)) {
+                    $item = '"' . $item . '"';
+                }
+                $content .= "{$key} = {$item}\n";
+            }
+        }
+        return file_put_contents($path, $content);
+    }
+
+}

+ 92 - 0
Frame/Helper/Format.php

@@ -0,0 +1,92 @@
+<?php
+
+namespace Heanup\Frame\Helper;
+
+/**
+ * 格式转换
+ */
+class Format extends \Heanup\Frame\Helper
+{
+    /**
+     * @hack 转换数字类型到字符串
+     * @param $data
+     * @return mixed
+     */
+    public static function convertNumberToString($data)
+    {
+        if (is_array($data)) {
+            foreach ($data as $k => $v) {
+                if (is_array($v)) {
+                    $data[$k] = self::convertNumberToString($v);
+                } else if (is_numeric($v)) {
+                    $data[$k] = (string)$v;
+                }
+            }
+        } else if (is_numeric($data)) {
+            $data = (string)$data;
+        }
+        return $data;
+    }
+
+    /**
+     * Json数据格式化,拷贝自网上
+     * @param  Mixed $data 数据
+     * @param  String $indent 缩进字符,默认4个空格
+     * @return JSON
+     */
+    public static function jsonFormat($data, $indent = null)
+    {
+        // 对数组中每个元素递归进行url_encode操作,保护中文字符
+        array_walk_recursive($data, 'self::jsonFormatProtect');
+        // json encode
+        $data = json_encode($data);
+        // 将url_encode的内容进行url_decode
+        $data = urldecode($data);
+        // 缩进处理
+        $ret = '';
+        $pos = 0;
+        $length = strlen($data);
+        $indent = isset($indent) ? $indent : '    ';
+        $newline = "\n";
+        $prevChar = '';
+        $outOfQuotes = true;
+
+        for ($i = 0; $i <= $length; $i++) {
+            $char = substr($data, $i, 1);
+            if ($char == '"' && $prevChar != '\\') {
+                $outOfQuotes = !$outOfQuotes;
+            } elseif (($char == '}' || $char == ']') && $outOfQuotes) {
+                $ret .= $newline;
+                $pos--;
+                for ($j = 0; $j < $pos; $j++) {
+                    $ret .= $indent;
+                }
+            }
+            $ret .= $char;
+            if (($char == ',' || $char == '{' || $char == '[') && $outOfQuotes) {
+                $ret .= $newline;
+                if ($char == '{' || $char == '[') {
+                    $pos++;
+                }
+                for ($j = 0; $j < $pos; $j++) {
+                    $ret .= $indent;
+                }
+            }
+            $prevChar = $char;
+        }
+        return $ret;
+    }
+
+    /**
+     * 将数组元素进行
+     * @param String $val
+     */
+    protected static function jsonFormatProtect(&$val)
+    {
+        if ($val !== true && $val !== false && $val !== null) {
+            $val = urldecode($val);
+        }
+    }
+
+}
+

+ 269 - 0
Frame/Helper/Html.php

@@ -0,0 +1,269 @@
+<?php
+
+namespace Heanup\Frame\Helper;
+
+/**
+ * 格式转换
+ */
+class Html extends \Heanup\Frame\Helper
+{
+
+    /**
+     * @param null $msg
+     * @param null $url
+     * @param array $get
+     * @param int $wait
+     */
+    public static function location($msg = null, $url = null,$isParent=FALSE, $get = Array(), $wait = 1)
+    {
+        $url = self::getSiteUrl($url, $get);
+        $target=$isParent?".parent":"";
+        $str = "<script type='text/javascript'>function flash(){window".$target.".location.href='$url';}setTimeout('flash()','" . intval($wait) * 500 . "');</script>";
+        if (!is_string($msg)) {
+            $msg = $msg ? "操作成功!" : "操作失败!";
+        }
+        echo $str . $msg . "&nbsp;&nbsp;正在<a href='$url'>跳转中</a>.....";
+        die();
+    }
+
+    /**
+     * 取得当前页地址
+     * @return mixed
+     */
+    public static function self()
+    {
+        return $_SERVER['REQUEST_URI'];
+    }
+
+    /**
+     * 调整url自动生成规则
+     * @param string $action
+     * @param array $get
+     * @param bool $mergeGet
+     * @return string
+     */
+    public static function getSiteUrl($action = '', Array $get = array(), $mergeGet = false)
+    {
+        $url = '/' . trim($action, '/');
+        if ($mergeGet) {
+            $get = array_merge($_GET, $get);
+        }
+        if ($get) {
+            $get = urldecode(http_build_query($get));
+        }
+        if ($get) {
+            $url .= '?' . $get;
+        }
+        return $url;
+    }
+
+    /**
+     * 链接生成器
+     * @param $url
+     * @param array $get
+     * @param null $text
+     * @param null $attributes
+     * @return string
+     */
+    public static function anchor($url, Array $get = array(), $text = null, $attributes = null)
+    {
+        $url = '/' . trim($url, "/") . '/?' . http_build_query($get);
+        $text = strlen($text) <= 0 ? $url : $text;
+        $string = "<a href='$url' $attributes>$text</a>";
+        return $string;
+    }
+
+    /**
+     * 简易分页函数
+     * @param string $url 跳转地址
+     * @param int $totalPage 总页数
+     * @param int $current 当前页
+     * @param int $records 总记录数
+     * @param int $displayCount 总共显示的页数
+     * @return string
+     */
+    public static function pagination($url, $totalPage = 10, $current = 1, $records = 100, $displayCount = 10)
+    {
+        $current = ($current < $totalPage) ? $current : $totalPage;
+        $current = ($current >= 1) ? $current : 1;
+        $first = 1;
+        $half = ceil($displayCount / 2);
+        $end = $totalPage;
+
+        $prev = $current - 1;
+        $next = $current + 1;
+        foreach ($_GET as $key => $value) {
+            if($value === ''){
+                unset($_GET[$key]);
+            }
+        }
+        $linkSum = "<li><span class='sum'>总数:$records</span></li>";
+        $linkFirst = '<li>' . self::anchor($url, array_merge($_GET, array('page' => $first)), '&laquo;') . '</li>';
+        $linkPrev = '<li>' . self::anchor($url, array_merge($_GET, array('page' => $prev)), '&#8249;') . '</li>';
+        $linkNext = '<li>' . self::anchor($url, array_merge($_GET, array('page' => $next)), '&#8250;') . '</li>';
+        $linkEnd = '<li>' . self::anchor($url, array_merge($_GET, array('page' => $end)), '&raquo;') . '</li>';
+
+
+        if ($current <= $half) {
+            $linkFirst = '';
+            $last = $displayCount;
+            if ($last > $end) {
+                $last = $end;
+            }
+            if ($current == 1) {
+                $linkPrev = '';
+            }
+            if ($current == $end) {
+                $linkNext = '';
+            }
+        } elseif ($current > ($end - $half) AND $current <= $end) {
+            $first = $current - $half + 1;
+            $last = $end;
+            $linkEnd = '';
+            if ($current == $end) {
+                $linkNext = '';
+            }
+        } else {
+            $first = $current - $half + 1;
+            $last = $current + $half;
+        }
+
+        $string = null;
+        for ($i = $first; $i <= $last; $i++) {
+            if ($i == $current) {
+                $string .= '<li class="active">' . self::anchor($url, array_merge($_GET, array('page' => $i)), "$i", "class='active'") . '</li>';
+            } else {
+                $string .= '<li>' . self::anchor($url, array_merge($_GET, array('page' => $i)), "$i") . '</li>';
+            }
+        }
+
+        return $linkSum . $linkFirst . $linkPrev . $string . $linkNext . $linkEnd;
+    }
+
+    /**
+     * 下拉菜单生成器
+     * @param $list
+     * @param $key
+     * @param $text
+     * @param null $attributes
+     * @return null|string
+     */
+    public static function loopOutputSelect($list, $key = '', $text = '', $attributes = null)
+    {
+        if (!is_array($list) OR empty($list)) {
+            return null;
+        }
+        $string = null;
+        foreach ($list as $k => $v) {
+            $selected = null;
+            if ($k == $key) {
+                $selected = "selected='selected'";
+            }
+            $string .= "<option value='$k' $selected >$v</option>\n";
+        }
+        if (!empty($text)) {
+            $string = "<select name='$text' id='$text' $attributes>" . $string . "</select>";
+        }
+        return $string;
+    }
+
+    /**
+     * 单选按钮生成器
+     * @param $list
+     * @param $key
+     * @param $text
+     * @param null $attributes
+     * @return null|string
+     */
+    public static function loopOutputRadio($list, $key = '', $text = '', $attributes = null)
+    {
+        if (!is_array($list) OR empty($list)) {
+            return null;
+        }
+        $i = 0;
+        $string = null;
+        foreach ($list as $k => $v) {
+            $checked = null;
+            if ($k == $key) {
+                $checked = "checked='checked'";
+            }
+            $string .= "<input type='radio' name='$text' value='$k' id='$text-$i' $checked $attributes /><label for='$text-$i'>$v</label>\n";
+            $i++;
+        }
+        return $string;
+    }
+
+    /**
+     * 多选框生成器
+     * @param $list
+     * @param string $key
+     * @param string $text
+     * @param string $attributes
+     * @param string $separator
+     * @return bool|string
+     */
+    public static function loopOutputCheckbox($list, $key = "", $text = '', $attributes = "", $separator = "\n")
+    {
+        if (!is_array($list) || empty($list)) {
+            return false;
+        }
+        $string = '';
+
+        if (!is_array($key)) {
+            $key = array($key);
+        }
+        foreach ($list as $k => $v) {
+            $string .= "<input type='checkbox' name='$text" . "[]' value='$k' ";
+            $string .= in_array($k, $key) ? " checked ='checked'" : "";
+            $string .= $attributes;
+            $string .= " />$v" . $separator;
+        }
+        return $string;
+    }
+    
+    /**
+     * 获取强密码
+     * @param int $length
+     * @return string
+     */
+    public static function getStrongCode($length = 8)
+    {
+        $length = $length > 8 ? intval($length) : 8;
+        $arr = array();
+        $arr[] = self::getRandoms($length - 4);
+        $arr[] = strtoupper(self::getRandoms(1, 'char'));
+        $arr[] = strtolower(self::getRandoms(1, 'char'));
+        $arr[] = mt_rand(0, 9);
+        $special = '!@#$%^*()';
+        $arr[] = $special{mt_rand(0, 11)};
+        shuffle($arr);
+        return implode('', $arr);
+    }
+    /**
+     * 获取随机数
+     *
+     * @param int $length
+     * @param string $type (可选)
+     * @return string
+     */
+    public static function getRandoms($length = 4, $type = '')
+    {
+        $returnStr = '';
+        $pattern = '1234567890abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLOMNOPQRSTUVWXYZ';
+        if ($type == 'char') {
+            $start = 10;
+            $end = 61;
+        } elseif ($type == 'num') {
+            $start = 0;
+            $end = 9;
+        } else {
+            $start = 0;
+            $end = 61;
+        }
+        for ($i = 0; $i < $length; $i++) {
+            $returnStr .= $pattern{mt_rand($start, $end)}; // 生成php随机数
+        }
+        return $returnStr;
+    }
+
+}

+ 31 - 0
Frame/Helper/Location.php

@@ -0,0 +1,31 @@
+<?php
+
+namespace Heanup\Frame\Helper;
+
+/**
+ * 地理位置
+ */
+class Location extends \Heanup\Frame\Helper
+{
+
+    /**
+     * 求两个已知经纬度之间的距离,单位为米
+     * @url http://www.phpernote.com/php-function/890.html
+     * @param lng1 ,lng2 经度
+     * @param lat1 ,lat2 纬度
+     * @return float 距离,单位米
+     **/
+    public static function getDistance($lng1, $lat1, $lng2, $lat2)
+    {
+        //将角度转为弧度, deg2rad()函数将角度转换为弧度
+        $radLat1 = deg2rad($lat1);
+        $radLat2 = deg2rad($lat2);
+        $radLng1 = deg2rad($lng1);
+        $radLng2 = deg2rad($lng2);
+        $a = $radLat1 - $radLat2;
+        $b = $radLng1 - $radLng2;
+        $s = 2 * asin(sqrt(pow(sin($a / 2), 2) + cos($radLat1) * cos($radLat2) * pow(sin($b / 2), 2))) * 6378.137 * 1000;
+        return $s;
+    }
+
+}

+ 11 - 0
Frame/Helper/Network.php

@@ -0,0 +1,11 @@
+<?php
+
+namespace Heanup\Frame\Helper;
+
+/**
+ * 网络操作
+ */
+class Network extends \Heanup\Frame\Helper
+{
+
+}

+ 54 - 0
Frame/Helper/Validate.php

@@ -0,0 +1,54 @@
+<?php
+
+namespace Heanup\Frame\Helper;
+
+/**
+ * 校验类, 尚未严格验证
+ * @package Heanup\Frame\Helper
+ */
+class Validate extends \Heanup\Frame\Helper
+{
+
+    public static function isUrl($url)
+    {
+        $regex = '/((https?|ftp|news):\/\/)?([a-z]*([a-z0-9\-]*[\.。])+([a-z]{2}|aero|arpa|biz|com|coop|edu|gov|info|int|jobs|mil|museum|name|nato|net|org|pro|travel)|(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5]))(\/[a-z0-9_\-\.~]+)*(\/([a-z0-9_\-\.]*)(\?[a-z0-9+_\-\.%=&]*)?)?(#[a-z][a-z0-9_]*)?/i';
+        return preg_match($regex, $url);
+    }
+
+    public static function isEmail($email)
+    {
+        return preg_match("/^(\w+((-\w+)|(\w.\w+))*)\@(\w+((\.|-)\w+)*\.\w+$)/", $email);
+    }
+
+    public static function isMobile($phone)
+    {
+        return preg_match("/^1\d{10}$/", $phone);
+    }
+
+    public static function isCountryMobile($phone)
+    {
+        return preg_match("/^[1-9]{1}\d{3,14}$/", $phone);
+    }
+
+
+    public static function isDate($str)
+    {
+        if (!preg_match("/^\d{4}-\d{2}-\d{2}$/", $str)) {
+            return FALSE;
+        }
+        $stamp = strtotime($str);
+        if (!is_numeric($stamp)) {
+            return FALSE;
+        }
+        if (checkdate(date('m', $stamp), date('d', $stamp), date('Y', $stamp))) {
+            return TRUE;
+        }
+        return FALSE;
+    }
+
+    public static function isIntval($mixed)
+    {
+        return (preg_match('/^\d*$/', $mixed) == 1);
+    }
+
+}

+ 74 - 0
Frame/Init.php

@@ -0,0 +1,74 @@
+<?php
+
+namespace Heanup\Frame;
+
+// 引入全局常量
+require_once dirname(__DIR__) . '/constants.php';
+// 引入全局函数
+require_once dirname(__DIR__) . '/functions.php';
+// 引入自动加载
+require_once dirname(__DIR__) . '/Autoload.php';
+require_once dirname(__DIR__) . '/vendor/autoload.php';
+
+class Init
+{
+    /**
+     * 环境变量设置
+     * @param $configFile
+     * @return bool
+     */
+    public static function setEnvironment($configFile = '')
+    {
+        // 设置错误显示
+        error_reporting(E_ERROR | E_WARNING | E_PARSE);
+        ini_set('display_errors', false);
+        // 无需浏览器缓存
+        header("Cache-Control: no-cache, must-revalidate");
+        header("Expires: Sat, 26 Jul 1997 05:00:00 GMT");
+        // 设置时区和编码
+        date_default_timezone_set('Asia/Shanghai');
+        setlocale(LC_ALL, 'en_US.utf-8');
+        header('Content-type:text/html;charset=utf-8');
+        // 设置错误捕获机制
+//        set_error_handler('\Heanup\Frame\Init::errorHandler');
+//        set_exception_handler('\Heanup\Frame\Init::exceptionHandler');
+//        Sentry::instance()->setException();
+
+        // 如果没有附加配置文件的话则可以结束配置了,反之,则要重新设置一些变量
+        if (!$configFile || !file_exists($configFile)) {
+            return true;
+        }
+        // 解析配置文件
+        $config = parse_ini_file($configFile);
+        if (!$config) {
+            return true;
+        }
+        // 调整错误显示
+        if (isset($config['display_errors'])) {
+            ini_set('display_errors', intval($config['display_errors']));
+        }
+        // 设置日志路径
+        if (isset($config['log_path'])) {
+            Logger::setLogPath($config['log_path']);
+        }
+        // 设置日志等级
+        if (isset($config['log_level'])) {
+            Logger::setLogLevel(intval($config['log_level']));
+        }
+        // 设置环境变量
+        if (isset($config['mode'])) {
+            Request::setMode($config['mode']);
+        }
+        return true;
+    }
+
+    /**
+     * 异常捕获
+     * @param $ex
+     */
+    public static function exceptionHandler($ex)
+    {
+//        Sentry::instance()->exceptionHandler($ex);
+//        Logger::error($ex, 'uncaught_exception');
+    }
+}

+ 58 - 0
Frame/Library.php

@@ -0,0 +1,58 @@
+<?php
+
+namespace Heanup\Frame;
+
+/**
+ * Library的父类, 基础类库
+ */
+class Library
+{
+
+    protected $configClass = null;
+    protected $configSection = null;
+
+    /**
+     * 构造函数
+     * @param null $configClass
+     * @param null $configSection
+     */
+    public function __construct($configClass = null, $configSection = null)
+    {
+        if ($configClass) {
+            // 判断是否是全路径
+            if(substr($configClass, 0, strlen(Dispatcher::CONFIG_PREFIX)) != Dispatcher::CONFIG_PREFIX){
+                $this->configClass = Dispatcher::CONFIG_PREFIX . '\\' . trim($configClass, '\\');
+            }else{
+                $this->configClass = $configClass;
+            }
+        }
+        // 设置配置文件内容片段
+        if (!is_null($configSection)) {
+            $this->configSection = $configSection;
+        }
+    }
+
+    /**
+     * 根据索引取出详细配置信息
+     */
+    protected function getConfig()
+    {
+        $class = $this->configClass;
+        if (class_exists($class) && method_exists($class, 'getData')) {
+            return $class::getData($this->configSection);
+        }
+        return array();
+    }
+
+    /**
+     * 获取一组标识
+     * @param $flag
+     * @return string
+     */
+    protected function getFlag($flag = false)
+    {
+        $flag = $this->configClass . ':' . $this->configSection . ':' . intval($flag);
+        return $flag;
+    }
+
+}

+ 83 - 0
Frame/Library/AntiSpam.php

@@ -0,0 +1,83 @@
+<?php
+
+namespace Heanup\Frame\Library;
+
+/**
+ * IP频率限制,一定程度上反垃圾
+ */
+class AntiSpam
+{
+    const ANTI_SPAM_MINUTE = 'anti_spam:minute';
+    const ANTI_SPAM_HOUR = 'anti_spam:hour';
+    const ANTI_SPAM_DAY = 'anti_spam:year';
+    const ANTI_SPAM_LOCKED = 'anti_spam:locked';
+    // 频率设置
+    protected static $limits = array(
+        self::ANTI_SPAM_MINUTE => 500,
+        self::ANTI_SPAM_HOUR => 2000,
+        self::ANTI_SPAM_DAY => 10000,
+    );
+    // 正常过期
+    protected static $expire = array(
+        self::ANTI_SPAM_MINUTE => 60,
+        self::ANTI_SPAM_HOUR => 3600,
+        self::ANTI_SPAM_DAY => 86400,
+    );
+    // 封禁时间
+    protected static $forbiddenTime = array(
+        self::ANTI_SPAM_MINUTE => 60,
+        self::ANTI_SPAM_HOUR => 3600,
+        self::ANTI_SPAM_DAY => 86400,
+    );
+
+    public static function check()
+    {
+        // 检查是否已经被封杀
+        if (self::getConnection()->get(self::getKey(self::ANTI_SPAM_LOCKED))) {
+            return FALSE;
+        }
+        foreach (self::$limits as $key => $limit) {
+            $expire = self::$expire[$key];
+            $key = self::getKey($key);
+            $value = self::getConnection()->get($key);
+            if (!$value) {
+                // 值不存在就设置
+                self::getConnection()->set($key, 1, $expire);
+            } elseif ($value > $limit) {
+                // 封禁时间
+                $expire = self::$forbiddenTime[$key];
+                self::getConnection()->set(self::getKey(self::ANTI_SPAM_LOCKED), 1, $expire);
+                return FALSE;
+            } else {
+                // 正常情况下+1
+                self::getConnection()->increment($key, 1);
+            }
+        }
+        return TRUE;
+    }
+
+    protected static function getKey($key)
+    {
+        $ip = \Heanup\Frame\Helper::getClientIp(TRUE);
+        return $key . ':' . $ip;
+    }
+
+    protected static function getConnection()
+    {
+        static $connection = null;
+        if ($connection) {
+            return $connection;
+        }
+        $cfg = \Heanup\Config\Memcache\AntiSpam::getData();
+        $connection = new \Memcached;
+        $connection->setOption(\Memcached::OPT_COMPRESSION, TRUE);
+        $connection->setOption(\Memcached::OPT_DISTRIBUTION, TRUE);
+        $connection->setOption(\Memcached::OPT_LIBKETAMA_COMPATIBLE, TRUE);
+        $connection->setOption(\Memcached::OPT_NO_BLOCK, TRUE);
+        $connection->setOption(\Memcached::OPT_CONNECT_TIMEOUT, 50);
+        $connection->setOption(\Memcached::OPT_POLL_TIMEOUT, 50);
+        $connection->addServers($cfg);
+        return $connection;
+    }
+
+}

+ 161 - 0
Frame/Library/BaseConverter.php

@@ -0,0 +1,161 @@
+<?php
+
+namespace Heanup\Frame\Library;
+
+/**
+ * BASE62 解析类,专门针对长10进制与62进制互转
+ */
+class BaseConverter
+{
+    private static $string = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ';
+    private static $encodeBlockSize = 7;
+    private static $decodeBlockSize = 4;
+
+    /**
+     * 批量从10进制转换到62进制
+     *
+     * @param array $mids
+     * @return array 以10进制mid为key的62进制mid数组
+     */
+    public static function multi_from10to62(array $mids)
+    {
+        $ret = array();
+        foreach ($mids as $mid) {
+            $ret[$mid] = self::from10to62($mid);
+        }
+        return $ret;
+    }
+
+    /**
+     * 批量从62进制转换到10进制
+     *
+     * @param array $mids
+     * @param bool $compat
+     * @param bool $for_mid
+     * @return array 以62进制mid为key的10进制mid数组
+     */
+    public static function multi_from62to10(array $mids, $compat = false, $for_mid = true)
+    {
+        $ret = array();
+        foreach ($mids as $mid) {
+            $ret[$mid] = self::from62to10($mid, $compat, $for_mid);
+        }
+        return $ret;
+    }
+
+    /**
+     * 将mid从10进制转换成62进制字符串
+     *
+     * @param    string $mid
+     * @return    string
+     */
+    public static function from10to62($mid)
+    {
+        $str = "";
+        $midlen = strlen($mid);
+        $segments = ceil($midlen / self::$encodeBlockSize);
+        $start = $midlen;
+        for ($i = 1; $i < $segments; $i += 1) {
+            $start -= self::$encodeBlockSize;
+            $seg = substr($mid, $start, self::$encodeBlockSize);
+            $seg = self::encodeSegment($seg);
+            $str = str_pad($seg, self::$decodeBlockSize, '0', STR_PAD_LEFT) . $str;
+        }
+        $str = self::encodeSegment(substr($mid, 0, $start)) . $str;
+        return $str;
+    }
+
+    /**
+     * 将62进制字符串转成10进制mid
+     *
+     * $compat $for_mid 参数意图不确定,直接从原作者处考过来。使用时直接使用默认参数即可。
+     *
+     * @param    string $str
+     * @param    bool $compat
+     * @param    bool $for_mid
+     * @return    string
+     */
+    public static function from62to10($str, $compat = false, $for_mid = true)
+    {
+        $mid = "";
+        $strlen = strlen($str);
+        $segments = ceil($strlen / self::$decodeBlockSize);
+        $start = $strlen;
+        for ($i = 1; $i < $segments; $i += 1) {
+            $start -= self::$decodeBlockSize;
+            $seg = substr($str, $start, self::$decodeBlockSize);
+            $seg = self::decodeSegment($seg);
+            /* Note by Rodin:
+             * 由于 decodeBlockSize设置为4,而 encodeBlockSize 设置为 7
+             * 而 base 62里4位最大的数为 ZZZZ,对应到10进制为 14776336,比encodeBlockSize
+             * 多1位……所以,将10进制转成62进制且在不足位时左补0,不会有任何问题。反之,
+             * 在62转成10进制的时候就可能会出现溢出,这是个潜在的bug,不清楚为什么目前没有
+             * bug爆出且当初采用了这种不标准不严谨的62进制转换法……
+             * 目前常用的mid里存在类似: l4ETJ4DfL (9位),在每segment大约出现大于 GZZZ 的串以后
+             * 总位数就会不符合目前的预期……
+             * 例如:
+             * ZZZZZZZZZ转换成10进制会成为 611477633614776336,总位数已经达到了20位,而常见的7位
+             * mid的62进制数字通常只有16位
+             */
+            $mid = str_pad($seg, self::$encodeBlockSize, '0', STR_PAD_LEFT) . $mid;
+        }
+        $mid = self::decodeSegment(substr($str, 0, $start)) . $mid;
+
+        //判断v3、v4版本mid
+        if ($for_mid) {
+            $midlen = strlen($mid);
+            $first = substr($mid, 0, 1);
+            if ($midlen == 16 && ($first == '3' || $first == '4')) {
+                return $mid;
+            }
+            if ($midlen == 19 && $first == '5') {
+                return $mid;
+            }
+        }
+        //end
+        if ($compat && !in_array(substr($mid, 0, 3), array('109', '110', '201', '211', '221', '231', '241'))) {
+            $mid = self::decodeSegment(substr($str, 0, 4)) . self::decodeSegment(substr($str, 4));
+        }
+        if ($for_mid) {
+            if (substr($mid, 0, 1) == '1' && substr($mid, 7, 1) == '0') {
+                $mid = substr($mid, 0, 7) . substr($mid, 8);
+            }
+        }
+        return $mid;
+    }
+
+    /**
+     * 将10进制转换成62进制
+     *
+     * @param    string $str 10进制字符串
+     * @return    string
+     */
+    private static function encodeSegment($str)
+    {
+        $out = '';
+        while ($str > 0) {
+            $idx = $str % 62;
+            $out = substr(self::$string, $idx, 1) . $out;
+            $str = floor($str / 62);
+        }
+        return $out;
+    }
+
+    /**
+     * 将62进制转换成10进制
+     *
+     * @param    string $str 62进制字符串
+     * @return    string
+     */
+    private static function decodeSegment($str)
+    {
+        $out = 0;
+        $base = 1;
+        for ($t = strlen($str) - 1; $t >= 0; $t -= 1) {
+            $out = $out + $base * strpos(self::$string, substr($str, $t, 1));
+            $base *= 62;
+        }
+        return strval($out);
+    }
+
+}

+ 8 - 0
Frame/Library/Benchmark.php

@@ -0,0 +1,8 @@
+<?php
+
+namespace Heanup\Frame\Library;
+
+class Benchmark extends \Heanup\Frame\Library
+{
+
+}

+ 19 - 0
Frame/Library/Curl.php

@@ -0,0 +1,19 @@
+<?php
+
+namespace Heanup\Frame\Library;
+
+class Curl extends \Heanup\Frame\Library
+{
+
+
+    public static function get()
+    {
+
+    }
+
+    public static function post()
+    {
+
+    }
+
+}

+ 44 - 0
Frame/Library/Memcache.php

@@ -0,0 +1,44 @@
+<?php
+
+namespace Heanup\Frame\Library;
+
+/**
+ * 缓存设置,使用memcache缓存数据
+ */
+class Memcache extends \Heanup\Frame\Library
+{
+
+    /**
+     * 获取连接池
+     * @return \Memcached
+     */
+    public function getConnection()
+    {
+        static $pool = array();
+        $connectionName = $this->getFlag(true);
+        if (!isset($pool[$connectionName]) || ($pool[$connectionName] == false)) {
+            $cfg = $this->getConfig();
+            $pool[$connectionName] = $this->connect($cfg);
+        }
+        return $pool[$connectionName];
+    }
+
+    /**
+     * 连接Memcache
+     * @param $cfg
+     * @return \Memcached
+     */
+    protected function connect($cfg)
+    {
+        $connection = new \Memcached;
+        $connection->setOption(\Memcached::OPT_COMPRESSION, true);
+        $connection->setOption(\Memcached::OPT_DISTRIBUTION, true);
+        $connection->setOption(\Memcached::OPT_LIBKETAMA_COMPATIBLE, true);
+        $connection->setOption(\Memcached::OPT_NO_BLOCK, true);
+        $connection->setOption(\Memcached::OPT_CONNECT_TIMEOUT, 50);
+        $connection->setOption(\Memcached::OPT_POLL_TIMEOUT, 50);
+        $connection->addServers($cfg);
+        return $connection;
+    }
+
+}

+ 41 - 0
Frame/Library/Memcached.php

@@ -0,0 +1,41 @@
+<?php
+namespace Heanup\Frame\Library;
+
+/**
+ * 缓存设置,使用memcache缓存数据
+ */
+class Memcached extends \Heanup\Frame\Library
+{
+
+    /**
+     * 获取连接池
+     * 
+     * @return \Memcached
+     */
+    public function getConnection()
+    {
+        static $pool = array();
+        $connectionName = $this->getFlag(true);
+        if (! isset($pool[$connectionName]) || ($pool[$connectionName] == false)) {
+            $cfg = $this->getConfig();
+            $pool[$connectionName] = $this->connect($cfg);
+        }
+        return $pool[$connectionName];
+    }
+
+    /**
+     * 连接Memcache
+     * 
+     * @param
+     *            $cfg
+     * @return \Memcached
+     */
+    protected function connect($cfg)
+    {
+        $connection = new \Memcache();
+        foreach ($cfg as $val) {
+            $connection->addServer($val['host'], $val['port']);
+        }
+        return $connection;
+    }
+}

+ 252 - 0
Frame/Library/MySQL.php

@@ -0,0 +1,252 @@
+<?php
+namespace Heanup\Frame\Library;
+
+use Heanup\Frame\Logger;
+use Heanup\Frame\Exception;
+
+/**
+ * 处理MySQL连接、查询和写入
+ *
+ * @package Heanup\Frame\Library
+ */
+class MySQL extends \Heanup\Frame\Library
+{
+
+    private static $throwException = false;
+    // 出错后是否抛出异常
+    private static $inTransaction = false;
+    // 是否在事务中
+    private static $newConnection = false;
+    // 是否使用新连接
+    const CONNECTION_TIMEOUT = 21600;
+    // 每个连接最多可以使用时间,超过这个时间之后创建新连接
+    protected $exception;
+
+    /**
+     * 执行查询操作
+     *
+     * @param string $sql            
+     * @param array $data            
+     * @param bool $isMaster            
+     * @param bool $fetchAll            
+     * @throws \Exception
+     * @return array|mixed
+     */
+    public function read($sql, $data, $isMaster = false, $fetchAll = true)
+    {
+        try {
+            $stmt = $this->getConnection($isMaster)->prepare($sql);
+            $stmt->execute($data);
+            $stmt->setFetchMode(\PDO::FETCH_ASSOC);
+            if ($fetchAll) {
+                $result = $stmt->fetchAll();
+            } else {
+                $result = $stmt->fetch();
+            }
+        } catch (\Exception $ex) {
+            $result = null;
+            $this->exception = $ex;
+            $message = array(
+                'info' => $ex->getCode() . '=>' . $ex->getMessage() . ';' . json_encode($ex->errorInfo),
+                'sql' => $sql,
+                'data' => $data
+            );
+            Logger::error($message, 'mysql.read.error');
+            // 如果是失去连接的错误,则进行重试
+            if (in_array($ex->errorInfo[1], array(
+                '2006',
+                '2013'
+            ))) {
+                self::$newConnection = true;
+                return $this->read($sql, $data, $isMaster, $fetchAll);
+            }
+            // 处理事务时抛出异常
+            if (self::$throwException) {
+                throw new \Exception($ex->getCode() . '=>' . $ex->getMessage(), '90203');
+            }
+        }
+        Logger::debug(array(
+            'sql' => $sql,
+            'data' => $data,
+            'result' => $result
+        ), 'mysql.read.debug');
+        return $result;
+    }
+
+    /**
+     * 执行写入操作
+     *
+     * @param string $sql            
+     * @param
+     *            $data
+     * @throws \Exception
+     * @return boolean
+     */
+    public function write($sql, $data)
+    {
+        try {
+            $stmt = $this->getConnection(true)->prepare($sql);
+            $stmt->execute($data);
+            $result = $stmt->rowCount();
+        } catch (\Exception $ex) {
+            $result = false;
+            $this->exception = $ex;
+            $message = array(
+                'info' => $ex->getCode() . '=>' . $ex->getMessage() . ';' . json_encode($ex->errorInfo),
+                'sql' => $sql,
+                'data' => $data
+            );
+            Logger::error($message, 'mysql.write.error');
+            // 如果是失去连接的错误,则进行重试
+            if (in_array($ex->errorInfo[1], array(
+                '2006',
+                '2013'
+            ))) {
+                self::$newConnection = true;
+                return $this->write($sql, $data);
+            }
+            // 处理事务时抛出异常
+            if (self::$throwException) {
+                throw new \Exception($ex->getCode() . '=>' . $ex->getMessage(), '90202');
+            }
+        }
+        Logger::debug(array(
+            'sql' => $sql,
+            'data' => $data,
+            'result' => $result
+        ), 'mysql.write.debug');
+        return $result;
+    }
+
+    /**
+     * 事务处理, $callback中如果出错必需抛出异常,否则会真正执行
+     * 开始时会设置为主动抛出异常,结束时会设置为关闭抛出异常
+     *
+     * @param
+     *            $callback
+     * @param
+     *            $throwException
+     * @return bool
+     */
+    public function transaction($callback, $throwException = false)
+    {
+        self::$inTransaction = true; // 处于事务中
+        self::$throwException = $throwException; // 执行SQL出错时候是否抛出异常
+        try {
+            $connection = $this->getConnection(true);
+            $connection->beginTransaction();
+            $callback();
+            $connection->commit();
+            $result = true;
+        } catch (\Exception $ex) {
+            $result = false;
+            $message = array(
+                'info' => $ex->getCode() . '=>' . $ex->getMessage() . ';' . json_encode($ex->errorInfo)
+            );
+            Logger::error($message, 'mysql.transaction.error');
+            // 如果是失去连接的错误,则进行重试
+            if (in_array($ex->errorInfo[1], array(
+                '2006',
+                '2013'
+            ))) {
+                self::$newConnection = true;
+                return $this->transaction($callback, $throwException);
+            }
+         
+            try {
+                $this->getConnection(true)->rollBack();
+               
+            } catch (\Exception $e) {
+                
+                // rollback failed
+            }
+            if($throwException){
+                throw new Exception($ex->getMessage(),$ex->getCode());
+            }
+        }
+        self::$throwException = false; // 关闭抛出异常
+        self::$inTransaction = false; // 事务结束
+        Logger::debug(array(
+            'result' => $result
+        ), 'mysql.transaction.debug');
+        return $result;
+    }
+
+    /**
+     * 获取自增ID
+     *
+     * @return string
+     */
+    public function getLastInsertId()
+    {
+        return $this->getConnection(true)->lastInsertId();
+    }
+
+    /**
+     * 获取异常
+     *
+     * @return mixed|\Exception
+     */
+    public function getLastException()
+    {
+        return $this->exception;
+    }
+
+    /**
+     * 获取连接池
+     * 如果超时时处于事务中,则不主动断开重连
+     *
+     * @param bool $isMaster            
+     * @return \PDO
+     */
+    protected function getConnection($isMaster = false)
+    {
+        static $pool = array();
+        $connectionName = $this->getFlag($isMaster);
+        $connectionTimeout = $connectionName . '_timeout';
+        if (self::$newConnection || ! isset($pool[$connectionName]) || $pool[$connectionName] == false || (self::$inTransaction == false && (time() - $pool[$connectionTimeout] > self::CONNECTION_TIMEOUT))) {
+            $cfg = $this->getConfig();
+            if ($isMaster) {
+                $cfg = $cfg['master'];
+            } else {
+                $cfg = $cfg['slave_list'][array_rand($cfg['slave_list'])];
+            }
+            $pool[$connectionName] = $this->connect($cfg);
+            $pool[$connectionTimeout] = time();
+            self::$newConnection = false;
+        }
+        return $pool[$connectionName];
+    }
+
+    /**
+     * 连接数据库
+     *
+     * @param
+     *            $cfg
+     * @return \PDO
+     * @throws \Exception
+     */
+    protected function connect($cfg)
+    {
+        $opts = array(
+            \PDO::ATTR_AUTOCOMMIT => true,
+            \PDO::ATTR_ERRMODE => \PDO::ERRMODE_EXCEPTION,
+            \PDO::ATTR_TIMEOUT => $cfg['timeout'],
+            \PDO::MYSQL_ATTR_INIT_COMMAND => "SET NAMES '" . $cfg['charset'] . "'",
+            \PDO::ATTR_PERSISTENT => $cfg['persistent']?true : false
+        ); // 启用长连接
+        
+        $dsn = 'mysql:dbname=' . $cfg['dbname'] . ';host=' . $cfg['host'] . ';port=' . $cfg['port'];
+        try {
+            $db = new \PDO($dsn, $cfg['username'], $cfg['password'], $opts);
+        } catch (\PDOException $ex) {
+            $this->exception = $ex;
+            $message = "MySQL connection failed [" . $dsn . ';' . $cfg['username'] . ';password]' . $ex->getCode() . '=>' . $ex->getMessage();
+            Logger::emerg($message, 'mysql.connect.error');
+            throw new \PDOException($message, 90201);
+        }
+        $message = "MySQL connect [" . $dsn . ';' . $cfg['username'] . ';password]';
+        Logger::debug($message, 'mysql.connect.debug');
+        return $db;
+    }
+}

+ 402 - 0
Frame/Library/MySQL/Query.php

@@ -0,0 +1,402 @@
+<?php
+
+namespace Heanup\Frame\Library\MySQL;
+
+/**
+ * 查询生成器
+ */
+class Query
+{
+
+    const PREPARE_PREFIX = ':prepared_';
+
+    private $tableName;
+    private $operation = 'SELECT';
+    private $field = array();
+    private $primaryKey = '';
+    private $data = array();
+    private $where;
+    private $orderBy;
+    private $groupBy;
+    private $limit = 'LIMIT 2000';
+    //替换后的映射表
+    private $prepared = array();
+    private $sql = '';
+
+    /**
+     * 实例
+     * @return Query
+     */
+    public static function newInstance()
+    {
+        return new Query();
+    }
+
+    /**
+     * 获取select
+     * @return Query
+     */
+    public function select()
+    {
+        $this->operation = 'SELECT';
+        return $this;
+    }
+
+    /**
+     * 获取count
+     * @return Query
+     */
+    public function count()
+    {
+        $this->operation = 'COUNT';
+        return $this;
+    }
+
+    /**
+     * 插入条记录
+     * @return Query
+     */
+    public function insert()
+    {
+        $this->operation = 'INSERT';
+        return $this;
+    }
+
+    /**
+     * replace into
+     * @return Query
+     */
+    public function replace()
+    {
+        $this->operation = 'REPLACE';
+        return $this;
+    }
+
+    /**
+     * 更新
+     * @return Query
+     */
+    public function update()
+    {
+        $this->operation = 'UPDATE';
+        return $this;
+    }
+
+    /**
+     * 删除
+     * @return Query
+     */
+    public function delete()
+    {
+        $this->operation = 'DELETE';
+        return $this;
+    }
+
+    /**
+     * 设置表名
+     * @param string $tableName
+     * @return Query
+     */
+    public function setTableName($tableName)
+    {
+        if ($tableName) {
+            $this->tableName = $this->_prepareFieldName($tableName);
+        }
+        return $this;
+    }
+
+    /**
+     * 设置字段
+     * @param $field
+     * @return Query
+     */
+    public function setField($field)
+    {
+        if ($field) {
+            if (!is_array($field)) {
+                $field = explode(',', $field);
+            }
+            $this->field = $field;
+        }
+        return $this;
+    }
+
+    /**
+     * 设置主键名
+     * @param $primaryKey
+     * @return $this
+     */
+    public function setPrimaryKey($primaryKey)
+    {
+        if ($primaryKey) {
+            $this->primaryKey = trim($primaryKey);
+        }
+        return $this;
+    }
+
+    /**
+     * 设置where条件
+     * @param array $where
+     * @param string $whereString
+     * @return Query
+     */
+    public function setWhere(Array $where, $whereString = '')
+    {
+        if (empty($where) && empty($whereString)) {
+            return $this;
+        }
+        if ($whereString) {
+            foreach ($where as $key => $val) {
+                $preparedKeys = array();
+                if (is_array($val)) {
+                    foreach ($val as $row) {
+                        $preparedKeys[] = $this->_prepareData($row);
+                    }
+                } else {
+                    $preparedKeys[] = $this->_prepareData($val);
+                }
+                $key = ':' . ltrim($key, ':');
+                $whereString = str_replace($key, implode(',', $preparedKeys), $whereString);
+            }
+            $this->where = 'WHERE ' . $whereString;
+        } elseif ($where) {
+            $data = array();
+            foreach ($where as $key => $val) {
+                if (is_array($val)) {
+                    $preparedKeys = array();
+                    foreach ($val as $row) {
+                        $preparedKeys[] = $this->_prepareData($row);
+                    }
+                    $data[] = $this->_prepareFieldName($key) . ' IN (' . implode(',', $preparedKeys) . ')';
+                } else {
+                    $data[] = $this->_prepareFieldName($key) . " = " . $this->_prepareData($val);
+                }
+            }
+            $this->where = 'WHERE ' . implode(' AND ', $data);
+        }
+        return $this;
+    }
+
+    /**
+     * 设置排序
+     * @param array  $orderBy  array('id' => 1); value 是 true 表示倒序,反之是正序
+     * @return Query
+     */
+    public function setOrderBy(Array $orderBy)
+    {
+        if ($orderBy && is_array($orderBy)) {
+            $tmp = array();
+            foreach ($orderBy as $key => $val) {
+                if (is_numeric($key)) {
+                    continue;
+                }
+                $order = $val ? 'DESC' : 'ASC';
+                $tmp[] = $this->_prepareFieldName($key) . ' ' . $order;
+            }
+            $tmp = implode(',', $tmp);
+            $this->orderBy = "ORDER BY " . $tmp;
+        }
+        return $this;
+    }
+
+    /**
+     * 设置范围
+     * @param int $limit
+     * @param int $offset
+     * @return Query
+     */
+    public function setLimit($limit = 2000, $offset = 0)
+    {
+        if ($limit) {
+            $this->limit = 'LIMIT ' . intval($offset) . ',' . intval($limit);
+        } else {
+            $this->limit = '';
+        }
+        return $this;
+    }
+
+    /**
+     * 设置分组
+     * @param $groupBy
+     * @return Query
+     */
+    public function setGroupBy($groupBy)
+    {
+        if (empty($groupBy)) {
+            return $this;
+        }
+        if (is_array($groupBy)) {
+            $tmp = array();
+            foreach ($groupBy as $v) {
+                $tmp[] = $this->_prepareFieldName($v);
+            }
+            $this->groupBy = 'GROUP BY ' . implode(",", $tmp);
+        } else {
+            $this->groupBy = 'GROUP BY ' . $this->_prepareFieldName($groupBy);
+        }
+        return $this;
+    }
+
+    /**
+     * 设置数据
+     * @param $data
+     * @return Query
+     */
+    public function setData(Array $data)
+    {
+        if ($data) {
+            $this->data = $data;
+        }
+        return $this;
+    }
+
+    /**
+     * 获取相关SQL
+     * @param boolean $raw
+     * @return String $sql
+     */
+    public function getSQL($raw = false)
+    {
+        $sql = $this->_getSQL();
+        if ($raw) {
+            foreach ($this->prepared as $key => $value) {
+                $key = ':' . trim($key, ':');
+                if (is_array($value)) {
+                    foreach ($value as $k => $v) {
+                        $value[$k] = addslashes($v);
+                    }
+                    $value = implode(',', $value);
+                } else {
+                    $value = "'" . addslashes($value) . "'";
+                }
+                $sql = str_replace($key, $value, $sql);
+            }
+        }
+        return $sql;
+    }
+
+    /**
+     * 返回Prepare过的数据
+     * @return array
+     */
+    public function getData()
+    {
+        $sql = $this->_getSQL();
+        $data = array();
+        foreach ($this->prepared as $key => $value) {
+            if (strpos($sql, $key) !== false) {
+                $key = ltrim($key, ':');
+                $data[$key] = $value;
+            }
+        }
+        return $data;
+    }
+
+    /**
+     * 拼SQL
+     * @return string
+     */
+    private function _getSQL()
+    {
+        if ($this->sql) {
+            return $this->sql;
+        }
+        switch ($this->operation) {
+            case 'INSERT':
+                $sql = 'INSERT INTO  ' . $this->tableName . ' SET ' . $this->_getData();
+                break;
+            case 'UPDATE':
+                $sql = 'UPDATE ' . $this->tableName . ' SET ' . $this->_getData() . ' ' . $this->where;
+                break;
+            case 'REPLACE':
+                $sql = 'REPLACE INTO  ' . $this->tableName . ' SET ' . $this->_getData();
+                break;
+            case 'DELETE':
+                $sql = 'DELETE FROM ' . $this->tableName . ' ' . $this->where;
+                break;
+            case 'COUNT':
+                $sql = 'SELECT COUNT(*) AS `count`  FROM ' . $this->tableName . ' ' . $this->where . ' ' . $this->groupBy;
+                break;
+            case 'SELECT':
+            default:
+                $sql = 'SELECT ' . $this->_getField() . ' FROM ' . $this->tableName . ' ' . $this->where . ' ' . $this->orderBy . ' ' . $this->groupBy . ' ' . $this->limit;
+                break;
+        }
+        return $this->sql = $sql;
+    }
+
+    /**
+     * 获取字段名称
+     * @return String
+     */
+    private function _getField()
+    {
+        if (empty($this->field)) {
+            $field = '*';
+        } else {
+            $tmp = array();
+            foreach ($this->field as $v) {
+                if(strstr($v,'sum')){
+                    $tmp[] = $v;
+                }else{
+                    $tmp[] = $this->_prepareFieldName($v);
+                }
+            }
+            $field = implode(",", $tmp);
+        }
+        return $field;
+    }
+
+    /**
+     * 获取prepare后的数据
+     * @return String
+     */
+    private function _getData()
+    {
+        $data = array();
+        foreach ($this->data as $key => $value) {
+            //简单字段检查
+            if (($this->field AND !in_array($key, $this->field)) || is_null($value)) {
+                continue;
+            }
+            $data[$key] = $this->_prepareFieldName($key) . ' = ' . $this->_prepareData($value);
+        }
+        $data = implode(",", $data);
+        return $data;
+    }
+
+    /**
+     * 过滤字段名,表名
+     * @param String $value
+     * @return string
+     */
+    private function _prepareFieldName($value)
+    {
+        $value = strtr($value, array(' ' => '', '`' => ''));
+        $value = "`" . $value . "`";
+        return $value;
+    }
+
+    /**
+     * prepare替换
+     * @param String $value
+     * @return string
+     */
+    private function _prepareData($value)
+    {
+        $count = count($this->prepared);
+        $key = self::PREPARE_PREFIX . $count . '_';
+        $this->prepared[$key] = $value;
+        return $key;
+    }
+
+    /**
+     * 魔术输出方法
+     * @return String
+     */
+    public function __toString()
+    {
+        return $this->getSQL();
+    }
+
+}

+ 63 - 0
Frame/Library/Redis.php

@@ -0,0 +1,63 @@
+<?php
+
+namespace Heanup\Frame\Library;
+
+/**
+ * Redis连接
+ */
+class Redis extends \Heanup\Frame\Library
+{
+    protected $exception;
+
+    public function getLastException()
+    {
+        return $this->exception;
+    }
+
+    /**
+     * 获取redis连接池
+     * @param bool $isMaster
+     * @return \Redis
+     */
+    public function getConnection($isMaster = false)
+    {
+        static $pool = array();
+        $cacheName = $this->getFlag($isMaster);
+        if (!isset($pool[$cacheName]) || $pool[$cacheName] == false) {
+            $cfg = $this->getConfig();
+            if ($isMaster) {
+                $cfg = $cfg['master'];
+            } else {
+                $cfg = $cfg['slave_list'][array_rand($cfg['slave_list'])];
+            }
+            $pool[$cacheName] = $this->connect($cfg);
+        }
+        return $pool[$cacheName];
+    }
+
+    /**
+     * 连接Redis
+     * @param $cfg
+     * @return \Redis
+     * @throws \Exception
+     */
+    protected function connect($cfg)
+    {
+        try {
+            $redis = new \Redis();
+            if (isset($cfg['pconnect']) && $cfg['pconnect'] == 1) {
+                $redis->pconnect($cfg['host'], $cfg['port'], $cfg['timeout']);
+            } else {
+                $redis->connect($cfg['host'], $cfg['port'], $cfg['timeout']);
+            }
+            if (isset($cfg['password']) && $cfg['password']) {
+                $redis->auth($cfg['password']);
+            }
+        } catch (\Exception $ex) {
+            $this->exception = $ex;
+            $message = "Redis connection failed : [" . $cfg['host'] . ';' . $cfg['port'] . ']';
+            throw new \Exception($message, 90301);
+        }
+        return $redis;
+    }
+}

+ 79 - 0
Frame/Library/Ticket.php

@@ -0,0 +1,79 @@
+<?php
+
+namespace Heanup\Frame\Library;
+
+/**
+ * 发号器类
+ */
+class Ticket extends \Heanup\Frame\Library
+{
+    const TICKET_START_TIME = 1262275200; // 2010-1-1 00:00:00
+    const DEFAULT_SERVER_ID = 9; //使用微秒发出去的id编号
+    const TICKET_KEY_PREFIX = 'id_';
+
+    /**
+     * 获取发号
+     * @param $businessId
+     * @return mixed
+     * @throws \Exception
+     */
+    public static function getTicket($businessId = 0)
+    {
+//         for ($i = 0; $i < 10; $i++) {
+//             $result = self::getFromRemote($businessId);
+//             if ($result) {
+//                 return $result;
+//             }
+//         }
+//         \Heanup\Frame\Logger::crit('ticket error', 'ticket');
+        $result = self::getTicketFromTimestamp();
+        return $result;
+    }
+
+    /**
+     * 从发号器获取号码
+     * @param $businessId
+     * @return mixed
+     */
+    protected static function getFromRemote($businessId = 0)
+    {
+        $configClass = 'Memcache\\Ticket';
+        $memcached = new Memcache($configClass);
+        $connection = $memcached->getConnection();
+        $key = self::TICKET_KEY_PREFIX . intval($businessId);
+        $result = $connection->get($key);
+        return $result;
+    }
+
+    /**
+     * 获取发号
+     * @return int
+     */
+    protected static function getTicketFromTimestamp()
+    {
+        $time = self::getTimestamp();
+        $time[0] = $time[0] - self::TICKET_START_TIME;
+        $time = implode('', $time);
+        $rand = sprintf('%03d', rand(0, 999));
+        $code = $time . $rand . self::DEFAULT_SERVER_ID;
+        return $code;
+    }
+
+    /**
+     * 获取时间戳信息,
+     * 返回值分别是秒,毫秒,微秒
+     * @return array
+     */
+    protected static function getTimestamp()
+    {
+        list($microTime, $seconds) = explode(" ", microtime());
+        $microTime = intval($microTime * 1000000);
+        $timeStamp = array(
+            0 => $seconds,
+            1 => substr($microTime, 0, 3),
+            2 => substr($microTime, 3, 3),
+        );
+        return $timeStamp;
+    }
+
+}

+ 313 - 0
Frame/Logger.php

@@ -0,0 +1,313 @@
+<?php
+
+namespace Heanup\Frame;
+
+/**
+ * 简单日志记录
+ */
+class Logger
+{
+
+    private static $logPath = '/data/wwwlogs/';
+
+    private static $logLevel = LOG_ERR;
+
+    private static $maps = array(
+        LOG_DEBUG => 'debug',
+        LOG_INFO => 'info',
+        LOG_NOTICE => 'notice',
+        LOG_WARNING => 'warning',
+        LOG_ERR => 'err',
+        LOG_CRIT => 'crit',
+        LOG_ALERT => 'alert',
+        LOG_EMERG => 'emerg'
+    );
+
+    /**
+     * 获取单例
+     *
+     * @return Logger
+     */
+    public static function getInstance()
+    {
+        static $instance = null;
+        if (is_null($instance)) {
+            $instance = new Logger();
+        }
+        return $instance;
+    }
+
+    /**
+     * 设置日志记录错误等级
+     *
+     * @param int $level
+     * @return Logger
+     */
+    public static function setLogLevel($level = LOG_DEBUG)
+    {
+        self::$logLevel = intval($level);
+        return self::getInstance();
+    }
+
+    /**
+     * 设置日志记录路径
+     *
+     * @param
+     *            $path
+     * @return Logger
+     */
+    public static function setLogPath($path)
+    {
+        $path = trim($path);
+        if (!empty($path) and is_writable($path)) {
+            self::$logPath = $path;
+        }
+        return self::getInstance();
+    }
+
+    public static function debug($message = '', $filename = null)
+    {
+        return self::log($message, $filename, LOG_DEBUG);
+    }
+
+    public static function info($message = '', $filename = null)
+    {
+        return self::log($message, $filename, LOG_INFO);
+    }
+
+    public static function notice($message = '', $filename = null)
+    {
+        return self::log($message, $filename, LOG_NOTICE);
+    }
+
+    public static function warning($message = '', $filename = null)
+    {
+        return self::log($message, $filename, LOG_WARNING);
+    }
+
+    public static function error($message = '', $filename = null)
+    {
+        return self::log($message, $filename, LOG_ERR);
+    }
+
+    public static function crit($message = '', $filename = null)
+    {
+        return self::log($message, $filename, LOG_CRIT);
+    }
+
+    public static function alert($message = '', $filename = null)
+    {
+        return self::log($message, $filename, LOG_ALERT);
+    }
+
+    public static function emerg($message = '', $filename = null)
+    {
+        return self::log($message, $filename, LOG_EMERG);
+    }
+
+    public static function log($message = '', $filename = null, $logType = LOG_DEBUG)
+    {
+        // 达不到错误记录级别的不与记录,主要是线上环境忽略debug信息
+        if ($logType > self::getLogLevel()) {
+            return false;
+        }
+        self::file($message, $filename, $logType);
+        return true;
+    }
+
+    /**
+     * 自定义记录日志
+     *
+     * @param null $email
+     * @param string $message
+     * @param string $filename
+     * @return bool
+     */
+    public static function setLog($email = NULL, $message = "", $filename = "")
+    {
+        if (empty($message) || empty($filename))
+            return false;
+        $filename = self::getLogPath($filename);
+        $message = "[" . date('Y-m-d H:i:s') . "]" . "-[IP:" . self::getClientIp() . "]" . "-[http://" . self::getServerIp() . $_SERVER['REQUEST_URI'] . "]" . ($email ? "-[EMAIL:$email]" : "") . "-[操作:$message]" . "\n";
+        return error_log($message, 3, $filename);
+    }
+
+    /**
+     * 自定义记录日志
+     *
+     * @param string $message
+     * @param string $filename
+     * @return bool
+     */
+    public static function logApi($message = "", $filename = "")
+    {
+        if (empty($message) || empty($filename))
+            return false;
+        $filename = self::getLogPath($filename);
+        $message = "[" . date('Y-m-d H:i:s') . "]" . "-[IP:" . self::getClientIp() . "]" . "-[http://" . self::getServerIp() . $_SERVER['REQUEST_URI'] . "]" . '[post:' . http_build_query($_POST) . ']' . '[get:' . http_build_query($_GET) . ']' . "-[msg:$message]" . "\n";
+        return error_log($message, 3, $filename);
+    }
+
+
+
+    /**
+     * 记录简单的日志内容
+     *
+     * @param
+     *            $message
+     * @param
+     *            $filename
+     * @return bool
+     */
+    public static function singleLog($data, $filename)
+    {
+        if (is_array($data)) {
+            $msg = http_build_query($data);
+        } else {
+            $msg = $data;
+        }
+        $filename = self::getLogPath($filename);
+        $message = "[" . date('Y-m-d H:i:s') . "][" . $_SERVER['REQUEST_URI'] . "]-[IP:" . self::getClientIp() . "][data:" . $msg . "]\r\n";
+        return error_log($message, 3, $filename);
+    }
+
+    /**
+     * 使用文件记录推送日志
+     *
+     * @param string $message
+     * @param string $filename
+     * @param int $logType
+     * @return bool
+     */
+    private static function file($message = '', $filename = null, $logType = LOG_INFO)
+    {
+        if (!$filename) {
+            $logType = intval($logType);
+            $filename = self::$maps[$logType];
+        }
+        $filename = self::getLogPath($filename);
+        $message = self::formatFields($message);
+        return error_log($message, 3, $filename);
+    }
+
+    /**
+     * 格式日志记录
+     *
+     * @param string $message
+     * @return string
+     */
+    private static function formatFields($message)
+    {
+        if ($message instanceof \Exception) {
+            $trace = $message->getTrace();
+            $trace = $trace[1];
+            $trace = ' ;trace => ' . json_encode($trace);
+            $message = $message->getCode() . ' => ' . $message->getMessage() . $trace;
+        } elseif ($message) {
+            $message = (is_string($message) || is_numeric($message)) ? $message : json_encode($message);
+        } else {
+            $message = ' - ';
+        }
+        $data = array();
+        $data['time'] = date('Y-m-d H:i:s');
+        $data['message'] = trim($message);
+        $data['client_ip'] = self::getClientIp();
+        $string = '[' . implode("]  [", $data) . ']' . "\n";
+        return $string;
+    }
+
+    /**
+     * 获取错误等级
+     *
+     * @return int
+     */
+    private static function getLogLevel()
+    {
+        return self::$logLevel;
+    }
+
+    /**
+     * 获取日志路径,如果不存在或者不可写则使用tmp路径
+     *
+     * @param
+     *            $filename
+     * @return string
+     */
+    private static function getLogPath($filename)
+    {
+        static $path = '';
+        if (!$path) {
+            if ((!file_exists(self::$logPath))) {
+                @mkdir(self::$logPath, 0777, true);
+            }
+            $path = is_writable(self::$logPath) ? self::$logPath : sys_get_temp_dir();
+            $path = rtrim($path, DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR . date('Ym') . DIRECTORY_SEPARATOR . date('d') . DIRECTORY_SEPARATOR;
+            if (!is_writable($path)) {
+                if (!file_exists($path)) {
+                    mkdir($path, 0777, true);
+                }
+            }
+        }
+        $filename = strtolower(strtr($filename, array(
+            ' ' => '',
+            DIRECTORY_SEPARATOR => ''
+        )));
+        $filename = $path . $filename . '.log';
+        return $filename;
+    }
+
+
+    /**
+     * 获取客户端Ip
+     *
+     * @return string|int
+     */
+    private static function getClientIp()
+    {
+        static $ip;
+        if ($ip) {
+            return $ip;
+        }
+        if (isset($_SERVER["HTTP_X_FORWARDED_FOR"])) {
+            $ip = $_SERVER["HTTP_X_FORWARDED_FOR"];
+        } elseif (isset($_SERVER["HTTP_CLIENT_IP"])) {
+            $ip = $_SERVER["HTTP_CLIENT_IP"];
+        } elseif (isset($_SERVER["HTTP_CLIENTIP"])) {
+            $ip = $_SERVER["HTTP_CLIENTIP"];
+        } elseif (isset($_SERVER["HTTP_X_REAL_IP"])) {
+            $ip = $_SERVER["HTTP_X_REAL_IP"];
+        } elseif (isset($_SERVER["REMOTE_ADDR"])) {
+            $ip = $_SERVER["REMOTE_ADDR"];
+        } else {
+            $ip = '127.0.0.1';
+        }
+        $pos = strpos($ip, '|');
+        if ($pos) {
+            $ip = substr($ip, 0, $pos);
+        }
+        $ip = trim($ip);
+        return $ip;
+    }
+
+    /**
+     * 获取服务器IP
+     *
+     * @return string
+     */
+    private static function getServerIp()
+    {
+        static $ip;
+        if ($ip) {
+            return $ip;
+        }
+        if (isset($_SERVER['SERVER_ADDR'])) {
+            $ip = $_SERVER['SERVER_ADDR'];
+        } else {
+            $cmd = "/sbin/ifconfig|grep 'inet addr'|awk '{print $2}'|awk -F':' '{print $2}'|awk '$1 !~ /127.0.0.1/{print}'|tail -n 1";
+            $handle = popen($cmd, 'r');
+            $ip = trim(fread($handle, 1024));
+            pclose($handle);
+        }
+        return $ip;
+    }
+}

+ 85 - 0
Frame/Model.php

@@ -0,0 +1,85 @@
+<?php
+
+namespace Heanup\Frame;
+
+class Model
+{
+
+    /**
+     * 创建单例模式
+     * @return Model
+     */
+    public static function getInstance()
+    {
+        static $pool;
+        $class = get_called_class();
+        if (!isset($pool[$class])) {
+            $pool[$class] = new $class();
+        }
+        return $pool[$class];
+    }
+
+    /**
+     * 强大的批量获取方法,从数据库或者缓存中读取数据
+     * 1. 先读取缓存
+     * 2. 然后从数据库中读取缓存没有命中的
+     * 3. 将没有命中的写入缓存
+     * 4. 返回结果
+     * @param $idArray
+     * @param string $databaseClass 完整数据库类路径, 要求拥有getBatch方法
+     * @param string $cacheClass 完整缓存类路径, 要求拥有 getBatch, setBatch 方法
+     * @return bool
+     */
+    public static function getBatchFromDatabaseOrCache($idArray, $databaseClass = '', $cacheClass = '')
+    {
+        $cachedData = array();
+        // 从缓存中读取数据
+        if ($cacheClass && class_exists($cacheClass) && method_exists($cacheClass, 'getBatch')) {
+            $cachedData = $cacheClass::getBatch($idArray);
+        }
+        // 取出剩余的ID
+        $idLeft = array();
+        foreach ($idArray as $id) {
+            if (isset($cachedData[$id]) && $cachedData[$id]) {
+                continue;
+            }
+            $idLeft[] = $id;
+        }
+        // 从数据库取出内容
+        $databaseData = array();
+        if ($idLeft && $databaseClass && class_exists($databaseClass) && method_exists($databaseClass, 'getBatch')) {
+            $databaseData = $databaseClass::getBatch($idLeft);
+        }
+        // 设置从数据库读取的缓存
+        if ($databaseData && $cacheClass && class_exists($cacheClass) && method_exists($cacheClass, 'setBatch')) {
+            $cacheClass::setBatch($databaseData);
+        }
+        // 合并数据
+        $data = array();
+        foreach ($idArray as $id) {
+            if (isset($cachedData[$id]) && $cachedData[$id]) {
+                $data[$id] = $cachedData[$id];
+            } elseif (isset($databaseData[$id]) && $databaseData[$id]) {
+                $data[$id] = $databaseData[$id];
+            }
+        }
+        return $data;
+    }
+
+    /**
+     * 检查状态位, 如果code是布尔, 则直接返回
+     * @param $stat
+     * @param $code
+     * @return int
+     */
+    protected static function checkStat($stat, $code)
+    {
+        if (is_bool($code)) {
+            return $code;
+        }
+        $code = intval($code);
+        $result = $stat & $code;
+        return $result;
+    }
+
+}

+ 59 - 0
Frame/Object.php

@@ -0,0 +1,59 @@
+<?php
+
+namespace Heanup\Frame;
+
+/**
+ * 数据结构父类
+ */
+class Object
+{
+
+    private $_data = array ();
+
+    public function __construct(Array $data = array ())
+    {
+        foreach ($data as $name => $value) {
+            $this->_data[$name] = $value;
+        }
+    }
+
+    public function __isset($name)
+    {
+        return isset($this->_data[$name]);
+    }
+
+    public function __set($name, $value)
+    {
+        if (isset($this->_data[$name])) {
+            $this->_data[$name] = $value;
+        }
+    }
+
+    public function __get($name)
+    {
+        if (isset($this->_data[$name])) {
+            return $this->_data[$name];
+        }
+    }
+
+    public function toArray()
+    {
+        return $this->_data;
+    }
+
+    public function __toString()
+    {
+        return json_encode($this->_data);
+    }
+
+    public function __sleep()
+    {
+        return $this->_data;
+    }
+
+    public function __wakeUp()
+    {
+        // ;
+    }
+
+}

+ 56 - 0
Frame/Package.php

@@ -0,0 +1,56 @@
+<?php
+
+namespace Heanup\Frame;
+
+/**
+ * 数据层父类
+ * Class Package
+ * @package Heanup\Frame
+ */
+class Package
+{
+    const PREFIX = '';
+
+    /**
+     * 创建单例模式
+     * @return Package
+     */
+    public static function getInstance()
+    {
+        static $pool;
+        $class = get_called_class();
+        if (!isset($pool[$class])) {
+            $pool[$class] = new $class();
+        }
+        return $pool[$class];
+    }
+
+    /**
+     * 获取带前缀的key
+     * @param $id
+     * @return string
+     */
+    protected static function getKey($id = 0)
+    {
+        $class = get_called_class();
+        if (!$class::PREFIX) {
+            $prefix = substr(md5($class), 0, 16);
+        } else {
+            $prefix = $class::PREFIX;
+        }
+        $prefix = strtolower($prefix);
+        $key = trim($prefix, ':') . ':' . $id;
+        return $key;
+    }
+
+    /**
+     * 获取发号器ID
+     * @return mixed
+     * @throws \Exception
+     */
+    protected static function getTicket()
+    {
+        return \Heanup\Frame\Library\Ticket::getTicket();
+    }
+
+}

+ 11 - 0
Frame/Package/Cache.php

@@ -0,0 +1,11 @@
+<?php
+
+namespace Heanup\Frame\Package;
+
+/**
+ * 缓存
+ */
+class Cache extends \Heanup\Frame\Package
+{
+
+}

+ 152 - 0
Frame/Package/Cache/Memcache.php

@@ -0,0 +1,152 @@
+<?php
+
+namespace Heanup\Frame\Package\Cache;
+
+use Heanup\Frame\Request;
+
+/**
+ * 使用Memcache做缓存
+ */
+class Memcache extends \Heanup\Frame\Package\Cache
+{
+    const CONFIG_CLASS = 'Memcache\\Core';
+    const PREFIX = '';
+    const EXPIRE = 259200;
+
+    /**
+     * 单个读取
+     * @param $id
+     * @return array
+     */
+    public static function get($id)
+    {
+        if(Request::isCacheEnabled() == false){
+            return array();
+        }
+        $key = self::getKey($id);
+        $result = self::getConnection()->get($key);
+        return $result;
+    }
+
+    /**
+     * 批量读取,如果没有数据,则不在返回的列表中
+     * @param array $idList
+     * @return array
+     */
+    public static function getBatch(Array $idList)
+    {
+        if(Request::isCacheEnabled() == false){
+            return array();
+        }
+        $keyList = array();
+        foreach ($idList as $id) {
+            $keyList[$id] = self::getKey($id);
+        }
+        $result = self::getConnection()->getMulti($keyList);
+        $data = array();
+        if ($result) {
+            $keyList = array_flip($keyList);
+            foreach ($result as $k => $v) {
+                $k = $keyList[$k];
+                $data[$k] = $v;
+            }
+        }
+        unset($result);
+        unset($idList);
+        unset($keyList);
+        return $data;
+    }
+
+    /**
+     * @param $id
+     * @param $data
+     * @param expire
+     * @return bool
+     */
+    public static function set($id, $data, $expire = null)
+    {
+        $key = self::getKey($id);
+        $class = get_called_class();
+        if(is_null($expire)){
+            $expire = $class::EXPIRE;
+        }
+        $expire = time() + intval($expire);
+        $result = self::getConnection()->set($key, $data, $expire);
+        return $result;
+    }
+
+    /**
+     * 批量设置
+     * @param array $data
+     * @param $expire
+     * @return bool
+     */
+    public static function setBatch($data, $expire = null)
+    {
+        $dataList = array();
+        foreach ($data as $k => $v) {
+            $k = self::getKey($k);
+            $dataList[$k] = $v;
+        }
+        $class = get_called_class();
+        if(is_null($expire)){
+            $expire = $class::EXPIRE;
+        }
+        $expire = time() + intval($expire);
+        $result = self::getConnection()->setMulti($dataList, $expire);
+        unset($data);
+        unset($dataList);
+        return $result;
+    }
+
+    /**
+     * 增加
+     * @param $id
+     * @param int $value
+     * @return int
+     */
+    public static function increase($id, $value = 1)
+    {
+        $key = self::getKey($id);
+        $value = abs($value);
+        return self::getConnection()->increment($key, $value);
+    }
+
+    /**
+     * 减少
+     * @param $id
+     * @param int $value
+     * @return int
+     */
+    public static function decrease($id, $value = 1)
+    {
+        $key = self::getKey($id);
+        $value = abs($value);
+        return self::getConnection()->decrement($key, $value);
+    }
+
+    /**
+     * 删除
+     * @param $id
+     * @return bool
+     */
+    public static function delete($id)
+    {
+        $key = self::getKey($id);
+        return self::getConnection()->delete($key);
+    }
+
+    /**
+     * 获取连接
+     * @return \Memcached
+     */
+    protected static function getConnection()
+    {
+        $class = get_called_class();
+        $configClass = $class::CONFIG_CLASS;
+        $memcached = new \Heanup\Frame\Library\Memcache($configClass);
+        $connection = $memcached->getConnection();
+        return $connection;
+    }
+
+}

+ 179 - 0
Frame/Package/Cache/Redis.php

@@ -0,0 +1,179 @@
+<?php
+
+namespace Heanup\Frame\Package\Cache;
+
+/**
+ * 使用Redis做缓存
+ */
+class Redis extends \Heanup\Frame\Package\Cache
+{
+    const CONFIG_CLASS = 'Redis\\Core';
+    const PREFIX = '';
+    const EXPIRE = 0; // 3 天
+
+    /**
+     * 单个读取
+     * @param $id
+     * @return array
+     */
+    public static function get($id)
+    {
+        $key = self::getKey($id);
+        try {
+            $result = self::getConnection()->get($key);
+        } catch (\Exception $ex) {
+            $result = null;
+        }
+        return $result;
+    }
+
+    /**
+     * 批量读取,如果没有数据,则对应的value为空
+     * @param array $idList
+     * @return array
+     */
+    public static function getBatch(Array $idList)
+    {
+        $keyList = array();
+        foreach ($idList as $id) {
+            $keyList[$id] = self::getKey($id);
+        }
+        try {
+            $result = self::getConnection()->mget($keyList);
+        } catch (\Exception $ex) {
+            $result = null;
+        }
+        $data = array();
+        if ($result) {
+            $i = 0;
+            foreach ($keyList as $k => $v) {
+                $data[$k] = $result[$i];
+                $i++;
+            }
+        }
+        unset($result);
+        unset($idList);
+        unset($keyList);
+        return $data;
+    }
+
+    /**
+     * @param $id
+     * @param int $data
+     * @return bool
+     */
+    public static function set($id, $data)
+    {
+        if (is_array($data)) {
+            $data = serialize($data);
+        }
+        $key = self::getKey($id);
+        $class = get_called_class();
+        try {
+            if ($class::EXPIRE) {
+                $result = self::getConnection(true)->setex($key, $class::EXPIRE, $data);
+            } else {
+                $result = self::getConnection(true)->set($key, $data);
+            }
+        } catch (\Exception $ex) {
+            $result = false;
+        }
+        return $result;
+    }
+
+    /**
+     * 批量设置
+     * @param array $data
+     * @return bool
+     */
+    public static function setBatch($data)
+    {
+        $dataList = array();
+        foreach ($data as $k => $v) {
+            $k = self::getKey($k);
+            $v = is_array($v) ? serialize($v) : $v;
+            $dataList[$k] = $v;
+        }
+        $class = get_called_class();
+        try {
+            $result = self::getConnection(true)->mset($dataList);
+            // 设置过期时间
+            if ($class::EXPIRE) {
+                foreach ($dataList as $k => $v) {
+                    self::getConnection()->expire($k, $class::EXPIRE);
+                }
+            }
+        } catch (\Exception $ex) {
+            $result = false;
+        }
+        unset($data);
+        unset($dataList);
+        return $result;
+    }
+
+    /**
+     * 加1
+     * @param $id
+     * @param int $value
+     * @return int
+     */
+    public static function increase($id, $value = 1)
+    {
+        $key = self::getKey($id);
+        $value = abs($value);
+        try {
+            $result = self::getConnection(true)->incr($key, $value);
+        } catch (\Exception $ex) {
+            $result = false;
+        }
+        return $result;
+    }
+
+    /**
+     * @param $id
+     * @param int $value
+     * @return int
+     */
+    public static function decrease($id, $value = 1)
+    {
+        $key = self::getKey($id);
+        $value = abs($value);
+        try {
+            $result = self::getConnection(true)->decr($key, $value);
+        } catch (\Exception $ex) {
+            $result = false;
+        }
+        return $result;
+    }
+
+    /**
+     * 删除
+     * @param $id
+     * @return bool
+     */
+    public static function delete($id)
+    {
+        $key = self::getKey($id);
+        try {
+            $result = self::getConnection(true)->delete($key);
+        } catch (\Exception $ex) {
+            $result = false;
+        }
+        return $result;
+    }
+
+    /**
+     * 获取连接池
+     * @param bool $isMaster
+     * @return \Redis
+     */
+    protected static function getConnection($isMaster = false)
+    {
+        $class = get_called_class();
+        $configClass = $class::CONFIG_CLASS;
+        $redis = new \Heanup\Frame\Library\Redis($configClass);
+        $connection = $redis->getConnection($isMaster);
+        return $connection;
+    }
+
+}

+ 11 - 0
Frame/Package/Counter.php

@@ -0,0 +1,11 @@
+<?php
+
+namespace Heanup\Frame\Package;
+
+/**
+ * 计数器类
+ */
+class Counter extends \Heanup\Frame\Package
+{
+
+}

+ 148 - 0
Frame/Package/Counter/Hash.php

@@ -0,0 +1,148 @@
+<?php
+
+namespace Heanup\Frame\Package\Counter;
+
+/**
+ * 计数器类,使用hash数据结构
+ * redis连不上server会抛出异常!!
+ */
+class Hash extends \Heanup\Frame\Package\Counter
+{
+    const CONFIG_CLASS = 'Redis\\Counter';
+    const PREFIX = '';
+    const EXPIRE = 2592000; // 30 天
+
+    protected static $hashKeyList = array();
+
+    /**
+     * 批量获取一系列的id
+     * @param $idList
+     * @return array
+     */
+    public static function hBatchGetAll($idList)
+    {
+        $data = array();
+        foreach ($idList as $id) {
+            $data[$id] = self::hGetAll($id);
+        }
+        return $data;
+    }
+
+    /**
+     * 读取单个hash
+     * @param $id
+     * @return array
+     */
+    public static function hGetAll($id)
+    {
+        $key = self::getKey($id);
+        try {
+            $result = self::getConnection()->hMGet($key, self::getHashKeyList());
+            foreach ($result as $k => $v) {
+                $result[$k] = intval($v);
+            }
+        } catch (\Exception $ex) {
+            $result = null;
+        }
+        return $result;
+    }
+
+    /**
+     * @param $id
+     * @param $hashKey
+     * @param int $value
+     * @return bool
+     */
+    public static function hSet($id, $hashKey, $value)
+    {
+        $key = self::getKey($id);
+        try {
+            $result = self::getConnection(true)->hSet($key, $hashKey, $value);
+        } catch (\Exception $ex) {
+            $result = false;
+        }
+        return $result;
+    }
+
+    /**
+     * 加1
+     * @param $id
+     * @param $hashKey
+     * @param int $value 负数时减少
+     * @return int
+     */
+    public static function hIncrBy($id, $hashKey, $value = 1)
+    {
+        $key = self::getKey($id);
+        try {
+            $result = self::getConnection(true)->hIncrBy($key, $hashKey, $value);
+        } catch (\Exception $ex) {
+            $result = false;
+        }
+        return $result;
+    }
+
+    /**
+     * 删除
+     * @param $id
+     * @return bool
+     */
+    public static function delete($id)
+    {
+        $key = self::getKey($id);
+        try {
+            $result = self::getConnection(true)->delete($key);
+        } catch (\Exception $ex) {
+            $result = false;
+        }
+        return $result;
+    }
+
+    /**
+     * 设置过期时间
+     * @param $id
+     * @param $expire
+     * @return mixed
+     */
+    public static function setExpire($id, $expire = null)
+    {
+        if (is_null($expire)) {
+            $class = get_called_class();
+            $expire = $class::EXPIRE;
+        } else {
+            $expire = intval($expire);
+        }
+        $key = self::getKey($id);
+        try {
+            $result = self::getConnection(true)->expire($key, $expire);
+        } catch (\Exception $ex) {
+            $result = false;
+        }
+        return $result;
+    }
+
+    /**
+     * 获取hashKey列表
+     * @return mixed
+     */
+    protected static function getHashKeyList()
+    {
+        $class = get_called_class();
+        $hashKeyList = $class::$hashKeyList;
+        return $hashKeyList;
+    }
+
+    /**
+     * 获取连接池
+     * @param bool $isMaster
+     * @return \Redis
+     */
+    protected static function getConnection($isMaster = false)
+    {
+        $class = get_called_class();
+        $configClass = $class::CONFIG_CLASS;
+        $redis = new \Heanup\Frame\Library\Redis($configClass);
+        $connection = $redis->getConnection($isMaster);
+        return $connection;
+    }
+}

+ 141 - 0
Frame/Package/Counter/Single.php

@@ -0,0 +1,141 @@
+<?php
+
+namespace Heanup\Frame\Package\Counter;
+
+/**
+ * 计数器类
+ * redis连不上server会抛出异常!!
+ */
+class Single extends \Heanup\Frame\Package\Counter
+{
+    const CONFIG_CLASS = 'Redis\\Counter';
+    const PREFIX = '';
+    const EXPIRE = 2592000; // 30 天
+
+    /**
+     * 单个读取
+     * @param $id
+     * @return array
+     */
+    public static function get($id)
+    {
+        $key = self::getKey($id);
+        try {
+            $result = self::getConnection()->get($key);
+            $result = intval($result);
+        } catch (\Exception $ex) {
+            $result = null;
+        }
+        return $result;
+    }
+
+    /**
+     * 批量获取
+     * @param array $idList
+     * @return array|null
+     */
+    public static function mGet(Array $idList)
+    {
+        $keyList = array();
+        foreach ($idList as $id) {
+            $k = self::getKey($id);
+            $keyList[$k] = $id;
+        }
+        try {
+            $result = array();
+            $data = self::getConnection()->mget(array_keys($keyList));
+            $i = 0;
+            foreach ($keyList as $k => $v) {
+                $result[$v] = intval($data[$i]);
+                $i++;
+            }
+        } catch (\Exception $ex) {
+            $result = null;
+        }
+        return $result;
+    }
+
+    /**
+     * @param $id
+     * @param int $value
+     * @return bool
+     */
+    public static function set($id, $value)
+    {
+        $key = self::getKey($id);
+        try {
+            $result = self::getConnection(true)->set($key, $value);
+        } catch (\Exception $ex) {
+            $result = false;
+        }
+        return $result;
+    }
+
+    /**
+     * 加1
+     * @param $id
+     * @param int $value
+     * @return int
+     */
+    public static function increase($id, $value = 1)
+    {
+        $key = self::getKey($id);
+        $value = abs($value);
+        try {
+            $result = self::getConnection(true)->incr($key, $value);
+        } catch (\Exception $ex) {
+            $result = false;
+        }
+        return $result;
+    }
+
+    /**
+     * @param $id , 如果值小于0则设为0
+     * @param int $value
+     * @return int
+     */
+    public static function decrease($id, $value = 1)
+    {
+        $key = self::getKey($id);
+        $value = abs($value);
+        try {
+            $result = self::getConnection(true)->decr($key, $value);
+            if ($result < 0) {
+                $result = self::getConnection(true)->set($key, 0);
+            }
+        } catch (\Exception $ex) {
+            $result = false;
+        }
+        return $result;
+    }
+
+    /**
+     * 删除
+     * @param $id
+     * @return bool
+     */
+    public static function delete($id)
+    {
+        $key = self::getKey($id);
+        try {
+            $result = self::getConnection(true)->delete($key);
+        } catch (\Exception $ex) {
+            $result = false;
+        }
+        return $result;
+    }
+
+    /**
+     * 获取连接池
+     * @param bool $isMaster
+     * @return \Redis
+     */
+    protected static function getConnection($isMaster = false)
+    {
+        $class = get_called_class();
+        $configClass = $class::CONFIG_CLASS;
+        $redis = new \Heanup\Frame\Library\Redis($configClass);
+        $connection = $redis->getConnection($isMaster);
+        return $connection;
+    }
+}

+ 13 - 0
Frame/Package/Database.php

@@ -0,0 +1,13 @@
+<?php
+
+namespace Heanup\Frame\Package;
+
+/**
+ * 数据库操作类
+ * 一种是单表模式,一种是分表模式
+ */
+class Database extends \Heanup\Frame\Package
+{
+
+
+}

+ 275 - 0
Frame/Package/Database/Common.php

@@ -0,0 +1,275 @@
+<?php
+namespace Heanup\Frame\Package\Database;
+
+/**
+ * 数据库操作类
+ * 单表模式
+ */
+class Common extends \Heanup\Frame\Package\Database\Single
+{
+
+    /**
+     * 通过ID获取单条记录
+     * 
+     * @param string $id            
+     * @return array
+     */
+    public function getOne($id)
+    {
+        return self::getLine($id);
+    }
+
+    /**
+     * 通过字段条件获取单条记录
+     * 
+     * @param array $where
+     *            查询条件
+     * @param array $fields
+     *            要获取的字段信息
+     * @return array
+     */
+    public function getOneByField($where = array(), $fields = array())
+    {
+        $list = $this->select($fields, $where, '', 1);
+        return ! empty($list) ? $list[0] : null;
+    }
+
+    /**
+     * 通过字段条件获取排序后的限制记录
+     * 
+     * @param array $where
+     *            查询条件
+     * @param array $fields
+     *            要获取的字段信息
+     * @param int $limit
+     *            限制记录数
+     * @param int $offset
+     *            查询偏移量
+     * @param array $orderBy
+     *            排序 array("field1"=>1,"field2"=>0); 0升序1降序
+     * @return array
+     */
+    public function getList($where = array(), $fields = array(), $limit = null, $offset = null, $orderBy = array())
+    {
+        return $this->select($fields, $where, '', $limit, $offset, $orderBy);
+    }
+
+    /**
+     * 复杂语句的计数查询
+     * 
+     * @param array $where            
+     * @return int
+     */
+    public function countByMuti($where = array())
+    {
+        $arr = $this->buildWhere($where);
+        return $this->count($arr['where'], $arr['whereString']);
+    }
+
+    /**
+     * 复杂的查询语句
+     * 
+     * @param array $where   <br>   
+     *              1.'key'=>$value 等于的查询条件<br>
+     *              2.'key'=>array( '_logic'=>'比较运算符或like' , 'value'=>$value ) '<','<=','>','>=','!=','LIKE'的查询条件<br>
+     *              3.'key'=>array($value1,$value2,$value3,...) IN范围查询<br>
+     *              4.'key'=>array( array( '_logic'=>'比较运算符或like' , 'value'=>$value ) ,'key'=>$value ) 一个字段多个查询条件<br>      
+     * @param array $fields            
+     * @param int $limit            
+     * @param int $offset            
+     * @param array $orderBy            
+     * @return array
+     */
+    public function getListByMuti($where = array(), $fields = array(), $limit = null, $offset = null, $orderBy = array())
+    {
+        $arr = $this->buildWhere($where);
+        return $this->select($fields, $arr['where'], $arr['whereString'], $limit, $offset, $orderBy);
+    }
+    /**
+     * 复杂的查询语句
+     *
+     * @param array $where   <br>
+     *              1.'key'=>$value 等于的查询条件<br>
+     *              2.'key'=>array( '_logic'=>'比较运算符或like' , 'value'=>$value ) '<','<=','>','>=','!=','LIKE'的查询条件<br>
+     *              3.'key'=>array($value1,$value2,$value3,...) IN范围查询<br>
+     *              4.'key'=>array( array( '_logic'=>'比较运算符或like' , 'value'=>$value ) ,'key'=>$value ) 一个字段多个查询条件<br>
+     * @param array $fields
+     * @param int $limit
+     * @param int $offset
+     * @param array $orderBy
+     * @return array
+     */
+    public function getAllByMutiple($where = array(), $fields = array(), $orderBy = array(),$limit=null,$offset=null)
+    {
+        $arr = $this->buildWhere($where);
+        return $this->select($fields, $arr['where'], $arr['whereString'], $limit, $offset, $orderBy);
+    }
+    /**
+     * 更新(基于ID)
+     * @param int $id
+     * @param array $data
+     * @return int
+     */
+    public function update($id, $data)
+    {
+        return parent::update($data, array('id'=>$id));
+    }
+
+    /**
+     * 复杂的更新语句
+     * 
+     * @param array $set     
+     *            1.'key'=>$value 等于参数设置
+     *            2.'key'=>array( '_logic'=>'运算符','value'=>$value ) 含 '+', '-','*','/','%'运算符号的参数设置       
+     * @param array $where            
+     * @return bool|int
+     */
+    public function updateByMuti($set = array(), $where = array())
+    {
+        $wArr = $this->buildWhere($where);
+        $sArr = $this->buildSet($set);
+        $sql = " UPDATE `" . $this->tableName . "` SET " . $sArr['setString'] . ' WHERE ' . $wArr['whereString'];
+        $prepared = array_merge($wArr['where'], $sArr['set']);
+        return $this->write($sql, $prepared);
+    }
+
+    /**
+     * 将复杂的查询条件转换为PDO类可解读的参数绑定方式
+     * 
+     * @param array $map
+     *            条件
+     *            $map允许的键值格式(所有的条件是AND组合的):
+     *            1.'key'=>$value 等于的查询条件
+     *            2.'key'=>array( '_logic'=>'比较运算符或like' , 'value'=>$value ) '<','<=','>','>=','!=','LIKE'的查询条件
+     *            3.'key'=>array($value1,$value2,$value3,...) IN范围查询
+     *            4.'key'=>array( array( '_logic'=>'比较运算符或like' , 'value'=>$value ) ,'key'=>$value ) 一个字段多个查询条件
+     * @return array
+     */
+    public function buildWhere($map)
+    {
+        $fhArr = array(
+            '<',
+            '<=',
+            '>',
+            '>=',
+            '!=',
+            'LIKE'
+        );
+        $where = array();
+        $whereString = array();
+        foreach ($map as $k => $v) {
+            if (is_array($v)) {
+                if (! empty($v['_logic'])) {
+                    $v['_logic'] = strtoupper($v['_logic']);
+                    if (in_array($v['_logic'], $fhArr)) {
+                        $whereString[] = self::escapeKey($k) . ' ' . $v['_logic'] . ' ' . self::replaceKey($k, 'wh_') . '';
+                        $where[self::replaceKey($k, 'wh_')] = $v['value'];
+                    }
+                } else {
+                    $mixArr = array();
+                    foreach ($v as $ke => $va) {
+                        if (is_array($va)) {
+                            if (! empty($va['_logic'])) {
+                                $va['_logic'] = strtoupper($va['_logic']);
+                                if (in_array($va['_logic'], $fhArr)) {
+                                    $whereString[] = self::escapeKey($k) . ' ' . $va['_logic'] . ' ' . self::replaceKey($k . $ke, 'wh_') . '';
+                                    $where[self::replaceKey($k . $ke, 'wh_')] = $va['value'];
+                                }
+                            }
+                        } else {
+                            $mixArr[] = $va;
+                        }
+                    }
+                    if (! empty($mixArr)) {
+                        $string = self::escapeKey($k) . ' IN ( ';
+                        $first = '';
+                        foreach ($mixArr as $ke => $va) {
+                            $string .= $first . self::replaceKey($k . $ke, 'wh_');
+                            $where[self::replaceKey($k . $ke, 'wh_')] = $va;
+                            $first = ',';
+                        }
+                        $string .= ' ) ';
+                        $whereString[] = $string;
+                    }
+                }
+            } else {
+                $whereString[] = self::escapeKey($k) . ' = ' . self::replaceKey($k, 'wh_');
+                $where[self::replaceKey($k, 'wh_')] = $v;
+            }
+        }
+        return array(
+            'where' => $where,
+            'whereString' => implode(' AND ', $whereString)
+        );
+    }
+
+    /**
+     * 将复杂的更新条件转换为PDO类可解读的参数绑定方式
+     * 
+     * @param array $map
+     *            参数设置
+     *            $map设置参数的格式:
+     *            1.'key'=>$value 等于参数设置
+     *            2.'key'=>array( '_logic'=>'运算符','value'=>$value ) 含 '+', '-','*','/','%'运算符号的参数设置
+     * @return array()
+     */
+    public function buildSet($map)
+    {
+        if (! is_array($map) || empty($map)) {
+            return '';
+        }
+        $setString = array();
+        $set = array();
+        foreach ($map as $k => $v) {
+            if (is_array($v)) {
+                if (! empty($v['_logic'])) {
+                    if (in_array($v['_logic'], array(
+                        '+',
+                        '-',
+                        '*',
+                        '/',
+                        '%'
+                    ))) {
+                        $setString[] = self::escapeKey($k) . ' = ' . self::escapeKey($k) . ' ' . $v['_logic'] . ' ' . self::replaceKey($k, 'st_');
+                        $set[self::replaceKey($k, 'st_')] = $v['value'];
+                    }
+                }
+            } else {
+                $setString[] = self::escapeKey($k) . '=' . self::replaceKey($k, 'st_');
+                $set[self::replaceKey($k, 'st_')] = $v;
+            }
+        }
+        return array(
+            'set' => $set,
+            'setString' => implode(' , ', $setString)
+        );
+    }
+
+    /**
+     * 安全过滤key
+     * 
+     * @param string $key            
+     * @return string
+     */
+    protected function escapeKey($key)
+    {
+        $key = str_replace(array(
+            '`',
+            ' '
+        ), '', $key);
+        return ' `' . addslashes(stripslashes($key)) . '` ';
+    }
+
+    /**
+     * 参数绑定value
+     * 
+     * @param string $key            
+     * @return string
+     */
+    protected function replaceKey($key, $prefix = '')
+    {
+        return ':' . $prefix . $key;
+    }
+
+  
+}

+ 342 - 0
Frame/Package/Database/Sharding.php

@@ -0,0 +1,342 @@
+<?php
+
+namespace Heanup\Frame\Package\Database;
+
+/**
+ * 数据库操作类
+ * 分表模式
+ */
+class Sharding extends \Heanup\Frame\Package\Database
+{
+    protected $configClass = 'MySQL\\Core';
+    protected $configSection = null;
+    protected $tableName = '';
+    protected $fields = array();
+    protected $primaryKey = 'id';
+
+    /**
+     * 获取计数
+     * @param Array $whereParam
+     * @param string $whereString
+     * @return int
+     */
+    public function count($whereParam, $whereString = '')
+    {
+        $query = $this->getQuery()->setWhere($whereParam, $whereString)->count();
+        $result = $this->getConnection()->read($query->getSQL(), $query->getData());
+        if (!$result) {
+            return 0;
+        }
+        $result = current($result);
+        return intval($result['count']);
+    }
+
+    /**
+     * 获取列表
+     * @param array $fields
+     * @param array $whereParam
+     * @param string $whereString
+     * @param int $limit
+     * @param int $offset
+     * @param array $orderBy
+     * @return array
+     */
+    public function select(
+        $fields = array(),
+        $whereParam = array(),
+        $whereString = '',
+        $limit = 10,
+        $offset = 0,
+        $orderBy = array())
+    {
+        $query = $this->getQuery()
+            ->setField($fields)
+            ->setWhere($whereParam, $whereString)
+            ->setLimit($limit, $offset)
+            ->setOrderBy($orderBy)
+            ->select();
+        $result = $this->getConnection()->read($query->getSQL(), $query->getData());
+        if (!$result) {
+            return $result;
+        }
+        $data = array();
+        foreach ($result as $row) {
+            $data[$row[$this->primaryKey]] = $row;
+        }
+        unset($result);
+        return $data;
+    }
+
+    /**
+     * 获取列表数据,简单版
+     * @param array $whereParam
+     * @param int $limit
+     * @return array
+     */
+    public function getList($whereParam = array(), $limit = 10)
+    {
+        return $this->select(array(), $whereParam, '', $limit, 0, array());
+    }
+
+    /**
+     * 获取列表ID信息
+     * @param string $field
+     * @param array $whereParam
+     * @param $orderBy
+     * @param $limit
+     * @return array
+     */
+    public function getListId($field = '', $whereParam = array(), $orderBy = array(), $limit = 2000)
+    {
+        $query = self::getQuery()
+            ->setField($field)
+            ->setWhere($whereParam)
+            ->setLimit($limit)
+            ->setOrderBy($orderBy)
+            ->select();
+        $result = $this->getConnection()->read($query->getSQL(), $query->getData());
+        if (!$result) {
+            return $result;
+        }
+        $data = array();
+        foreach ($result as $row) {
+            $data[] = $row[$field];
+        }
+        unset($result);
+        return $data;
+    }
+
+    /**
+     * 获取单条记录
+     * @param array $whereParam
+     * @param string $whereString
+     * @param array $orderBy
+     * @return array;
+     */
+    public function getOne($whereParam = array(), $whereString = '', $orderBy = array())
+    {
+        $query = $this->getQuery()
+            ->setWhere($whereParam, $whereString)
+            ->setOrderBy($orderBy)
+            ->setLimit(1)
+            ->select();
+        $result = $this->getConnection()->read($query->getSQL(), $query->getData(), false, false);
+        return $result;
+    }
+
+    /**
+     * 根据主键获取单条记录
+     * @param int $id
+     * @return array
+     */
+    public function getLine($id)
+    {
+        $where = array($this->primaryKey => $id);
+        $query = $this->getQuery()->setWhere($where)->setLimit(1)->select();
+        $result = $this->getConnection()->read($query->getSQL(), $query->getData(), false, false);
+        return $result;
+    }
+
+    /**
+     * 批量根据主键查询
+     * @param $idList
+     * @return array|mixed
+     */
+    public function getBatch($idList)
+    {
+        $where = array($this->primaryKey => $idList);
+        $query = $this->getQuery()->setWhere($where)->select();
+        $result = $this->getConnection()->read($query->getSQL(), $query->getData());
+        if (!$result) {
+            return $result;
+        }
+        $data = array();
+        foreach ($result as $row) {
+            $data[$row[$this->primaryKey]] = $row;
+        }
+        unset($result);
+        return $data;
+    }
+
+    /**
+     * 创建记录
+     * @param $data
+     * @param bool 是否返回最后最后插入ID
+     * @return bool|int
+     */
+    public function insert($data, $returnLastInsertId = false)
+    {
+        $query = $this->getQuery()->setData($data)->insert();
+        $result = $this->getConnection()->write($query->getSQL(), $query->getData());
+        // 返回最后插入的id
+        if ($result && $returnLastInsertId) {
+            $result = $this->getConnection()->getLastInsertId();
+        }
+        return $result;
+    }
+
+    /**
+     * 批量插入
+     * $data = array(
+     *      array(
+     *          'id' => 10001,
+     *          'target_id' => 100,
+     *      ),
+     *      array(
+     *          'id' => 10002,
+     *          'target_id' => 100,
+     *      ),
+     * );
+     * @param $data
+     * @return bool|int
+     */
+    public function insertBatch($data)
+    {
+        $prefix = 'prepared_';
+        // 确定字段名
+        $fields = implode('`, `', array_keys(current($data)));
+        // 拼SQL和数据
+        $dataList = array();
+        $values = array();
+        $i = 0;
+        foreach ($data as $k => $v) {
+            foreach ($v as $x => $y) {
+                $key = $prefix . $i;
+                $dataList[$key] = $y;
+                $v[$x] = ':' . $key;
+                $i++;
+            }
+            $values[] = "(" . implode(", ", $v) . ")";
+        }
+        $values = implode(', ', $values);
+        $sql = "INSERT INTO `" . $this->tableName . "` (`$fields`) VALUES $values";
+        $result = $this->write($sql, $dataList);
+        return $result;
+    }
+
+    /**
+     * 更新记录
+     * @param $data
+     * @param $whereParam
+     * @param $whereString
+     * @return bool
+     */
+    public function update($data, $whereParam, $whereString = '')
+    {
+        $query = $this->getQuery()->setData($data)->setWhere($whereParam, $whereString)->update();
+        $result = $this->getConnection()->write($query->getSQL(), $query->getData());
+        return $result;
+    }
+
+    /**
+     * 替换输入
+     * @param $data
+     * @param $whereParam
+     * @param $whereString
+     * @return bool
+     */
+    public function replace($data, $whereParam, $whereString = '')
+    {
+        $query = $this->getQuery()->setData($data)->setWhere($whereParam, $whereString)->replace();
+        $result = $this->getConnection()->write($query->getSQL(), $query->getData());
+        return $result;
+    }
+
+    /**
+     * 删除逻辑
+     * @param $whereParam
+     * @param $whereString
+     * @return bool
+     */
+    public function delete($whereParam, $whereString = '')
+    {
+        $query = $this->getQuery()->setWhere($whereParam, $whereString)->delete();
+        $result = $this->getConnection()->write($query->getSQL(), $query->getData());
+        return $result;
+    }
+
+
+    /**
+     * 获取一个新的SQL生成器
+     * @return \Heanup\Frame\Library\MySQL\Query
+     */
+    protected function getQuery()
+    {
+        return \Heanup\Frame\Library\MySQL\Query::newInstance()
+            ->setTableName($this->tableName)
+            ->setField($this->fields)
+            ->setPrimaryKey($this->primaryKey);
+    }
+
+    /**
+     * 读取
+     * @param $sql
+     * @param $data
+     * @param bool $isMaster
+     * @return mixed|Array
+     */
+    protected function read($sql, $data, $isMaster = false)
+    {
+        $result = $this->getConnection()->read($sql, $data, $isMaster);
+        return $result;
+    }
+
+    /**
+     * 写入
+     * @param $sql
+     * @param $data
+     * @return bool|int
+     */
+    protected function write($sql, $data)
+    {
+        $result = $this->getConnection()->write($sql, $data);
+        return $result;
+    }
+
+    /**
+     * 获取连接池
+     * @return \Heanup\Frame\Library\MySQL
+     */
+    protected function getConnection()
+    {
+        return new \Heanup\Frame\Library\MySQL($this->configClass, $this->configSection);
+    }
+    /**
+     * 根据数值来确定分表-分库方法
+     * 1.
+     * 可以自定义分表-分库的模板前缀$tbl变量
+     * 2. 可以自定义截取长度
+     * 3. 一般可以根据用户UID来获取分表或者分库
+     *
+     * @param int $num
+     *            数值
+     * @param string $tbl
+     *            模板前缀
+     * @param int $default
+     *            默认截取长度
+     */
+    protected function num_identify($num, $tbl, $default = 1)
+    {
+        $num = (string) $num;
+        $len = strlen($num);
+        if ($len >= $default)
+            $str = substr($num, $len - $default, $default);
+        else
+            $str = str_pad($num, $default, '0', STR_PAD_LEFT);
+        return $tbl . '_' . $str;
+    }
+    /**
+     * 求余数的方式获取分表-分库方法
+     * 1. 求余方式余数比较少,适合小型的分表法
+     * 2. 可以自定义求余除数
+     * Dao中使用方法:$this->dao->db->fmod_identify($num, $tbl, $default = 7)
+     * @param int $num
+     * @param string $tbl
+     * @param int $default
+     * @return
+     */
+    public function fmod_identify($num, $tbl, $default = 7) {
+        return $tbl . '_' . fmod($num/$default);
+    }
+
+}

+ 435 - 0
Frame/Package/Database/Single.php

@@ -0,0 +1,435 @@
+<?php
+
+namespace Heanup\Frame\Package\Database;
+use Exception;
+use Heanup\Frame\Filter;
+use Heanup\Frame\Library\MySQL;
+use Heanup\Frame\Library\MySQL\Query;
+use Heanup\Frame\Logger;
+use Heanup\Frame\Package\Database;
+
+/**
+ * 数据库操作类
+ * 单表模式
+ */
+class Single extends Database
+{
+    const CONFIG_CLASS = 'MySQL\\Core';
+    protected static $prefix = 'ph_'; // 表前缀
+    protected static $tableName = '';
+    protected static $fields = array();
+    protected static $primaryKey = 'id';
+
+    /**
+     * 获取计数
+     * @param array $whereParam
+     * @param string $whereString
+     * @return int
+     * @throws Exception
+     */
+    public static function count($whereParam, $whereString = '')
+    {
+        $query = self::getQuery()->setWhere($whereParam, $whereString)->count();
+        $result = self::getConnection()->read($query->getSQL(), $query->getData());
+        if (!$result) {
+            return 0;
+        }
+        $result = current($result);
+        return intval($result['count']);
+    }
+
+    /**
+     * 获取列表,limit 不传取全部
+     * @param array $fields
+     * @param array $whereParam
+     * @param string $whereString
+     * @param int $limit 不传取全部
+     * @param int $offset
+     * @param array $orderBy
+     * @return array
+     */
+    public static function select(
+        $fields = array(),
+        $whereParam = array(),
+        $whereString = '',
+        $limit = null,
+        $offset = 0,
+        $orderBy = array())
+    {
+        $query = self::getQuery()
+            ->setField($fields)
+            ->setWhere($whereParam, $whereString)
+            ->setLimit($limit, $offset)
+            ->setOrderBy($orderBy)
+            ->select();
+        try {
+            $result = self::getConnection()->read($query->getSQL(), $query->getData());
+        } catch (Exception $e) {
+            Logger::setLog(null,$e);
+        }
+        if (!$result) {
+            return $result;
+        }
+//        $data = array();
+//        $class = get_called_class();
+//        foreach ($result as $row) {
+//            $data[$row[$class::$primaryKey]] = $row;
+//        }
+//        unset($data);
+        return $result;
+    }
+
+    /**
+     * 获取列表数据,简单版
+     * @param array $whereParam
+     * @param int $limit
+     * @param array $orderBy
+     * @return array
+     * @throws Exception
+     */
+    public static function getList($whereParam = array(), $limit = 10, $orderBy = array())
+    {
+        return self::select(array(), $whereParam, '', $limit, 0, $orderBy);
+    }
+
+    /**
+     * 获取列表ID信息
+     * @param string $field
+     * @param array $whereParam
+     * @param array $orderBy
+     * @param int $limit
+     * @return array
+     * @throws Exception
+     */
+    public static function getListId($field = '', $whereParam = array(), $orderBy = array(), $limit = 2000)
+    {
+        $query = self::getQuery()
+            ->setField($field)
+            ->setWhere($whereParam)
+            ->setLimit($limit)
+            ->setOrderBy($orderBy)
+            ->select();
+        $result = self::getConnection()->read($query->getSQL(), $query->getData());
+        if (!$result) {
+            return $result;
+        }
+        $data = array();
+        foreach ($result as $row) {
+            $data[] = $row[$field];
+        }
+        unset($result);
+        return $data;
+    }
+
+    /**
+     * 分页获取数据
+     * @param array $whereParam
+     * @param string $whereString
+     * @param int $page
+     * @param int $pageSize
+     * @param array $orderBy
+     * @param array $fields
+     * @return array
+     * @throws Exception
+     */
+    public static function getPageList($whereParam = array(), $whereString='', $page = 1, $pageSize = 20, $orderBy = array(), $fields = array()){
+        $page = $page ? $page : 1;
+        $offset = ($page - 1) * $pageSize;
+        //获取总条数
+        $total_count = self::count($whereParam,$whereString);
+        $pager = array(
+            'total_count' => $total_count,
+            'total_page' => 0,
+            'current_page' => $page,
+            'page_size' => $pageSize,
+            'data' => array()
+        );
+        if($total_count > 0){
+            list($url,) = explode('?',$_SERVER['REQUEST_URI']);
+            unset($_GET['page']);
+            //链接
+            $pager['url'] = $url;
+            //请求参数
+            $pager['filter'] =  $_GET ? http_build_query($_GET) : '';
+            //总页数
+            $pager['total_page'] = ceil($total_count / $pageSize);
+            //内容
+            $pager['data'] = self::select($fields,$whereParam,$whereString,$pageSize,$offset,$orderBy);
+        }
+        return $pager;
+    }
+
+    /**
+     * 获取单条记录
+     * @param array $whereParam
+     * @param string $whereString
+     * @param array $orderBy
+     * @return array;
+     * @throws Exception
+     */
+    public static function getOne($whereParam = array(), $whereString = '', $orderBy = array())
+    {
+        $query = self::getQuery()
+            ->setWhere($whereParam, $whereString)
+            ->setOrderBy($orderBy)
+            ->setLimit(1)
+            ->select();
+        $result = self::getConnection()->read($query->getSQL(), $query->getData(), false, false);
+        return $result;
+    }
+
+    /**
+     * 根据主键获取单条记录
+     * @param int $id
+     * @return array
+     */
+    public static function getLine($id)
+    {
+        if (!$id) {
+            return array();
+        }
+        $class = get_called_class();
+        $where = array($class::$primaryKey => $id);
+        $query = self::getQuery()->setWhere($where)->setLimit(1)->select();
+        $result = self::getConnection()->read($query->getSQL(), $query->getData(), false, false);
+        return $result;
+    }
+
+    /**
+     * 批量根据主键查询, 按照传入的ID进行排序
+     * @param $idList
+     * @return array|mixed
+     */
+    public static function getBatch($idList)
+    {
+        if (!$idList) {
+            return array();
+        }
+        $class = get_called_class();
+        $where = array($class::$primaryKey => $idList);
+        $query = self::getQuery()->setWhere($where)->select();
+        $result = self::getConnection()->read($query->getSQL(), $query->getData());
+        if (!$result) {
+            return $result;
+        }
+        $data = array();
+        foreach ($result as $row) {
+            $data[$row[$class::$primaryKey]] = $row;
+        }
+        // 按照传入的id顺序进行重新排序,如果id对应的value不存在则忽略
+        $result = array();
+        foreach ($idList as $id) {
+            if (isset($data[$id])) {
+                $result[$id] = $data[$id];
+            }
+        }
+        unset($data);
+        return $result;
+    }
+
+    /**
+     * 聚合获取列表数据
+     * @param $field
+     * @param $whereParam
+     * @param $whereString
+     * @param $groupBy
+     * @param $limit
+     * @return array|mixed
+     */
+    public static function getListByGroup($field,$whereParam,$whereString,$groupBy,$limit=null){
+        $query = self::getQuery()
+            ->setField($field)
+            ->setWhere($whereParam,$whereString)
+            ->setGroupBy($groupBy)
+            ->setLimit($limit)
+            ->select();
+        return self::getConnection()->read($query->getSQL(), $query->getData());
+    }
+
+    /**
+     * 联合获取列表数据
+     * @param $whereString
+     * @param $join
+     * @param $sort
+     * @param string $field
+     * @return array|mixed
+     */
+    public static function getListByJoin($whereString,$join,$sort='',$field=''){
+        $class = get_called_class();
+        $tableName = $class::$tableName;
+        $field = $field ? : $tableName.'.*';
+        $sql = 'select '.$field.' from '.$tableName.' left join '.$join[0].' on '.$tableName.'.'.$join[1].'='.$join[0].'.'.$join[2];
+        $whereString ? $sql .= ' where '.$whereString : null;
+        $sort ? $sql .= ' order by '.$sort : null;
+        $sql = str_replace('SELF',$tableName,$sql);//SELF代表主表
+        $sql = str_replace('JOIN',$join[0],$sql);//JOIN代表连接表
+        $result =  self::getConnection()->read($sql, array());
+        $data = array();
+        $class = get_called_class();
+        foreach ($result as $row) {
+            $data[$row[$class::$primaryKey]] = $row;
+        }
+        return $data;
+    }
+
+    /**
+     * 创建记录
+     * @param $data
+     * @param bool 是否返回最后最后插入ID
+     * @return bool|int
+     */
+    public static function insert($data, $returnLastInsertId = true)
+    {
+        $query = self::getQuery()->setData($data)->insert();
+        $result = self::getConnection()->write($query->getSQL(), $query->getData());
+        // 返回最后插入的id
+        if ($result && $returnLastInsertId) {
+            $result = self::getConnection()->getLastInsertId();
+        }
+        return $result;
+    }
+
+    /**
+     * 批量插入
+     * $data = array(
+     *      array(
+     *          'id' => 10001,
+     *          'target_id' => 100,
+     *      ),
+     *      array(
+     *          'id' => 10002,
+     *          'target_id' => 100,
+     *      ),
+     * );
+     * @param $data
+     * @return bool|int
+     */
+    public static function insertBatch($data)
+    {
+        $prefix = 'prepared_';
+        // 确定字段名
+        $fields = implode('`, `', array_keys(current($data)));
+        //print_r($data);
+        // 拼SQL和数据
+        $dataList = array();
+        $values = array();
+        $i = 0;
+        foreach ($data as $k => $v) {
+            foreach ($v as $x => $y) {
+                $key = $prefix . $i;
+                $dataList[$key] = $y;
+                $v[$x] = ':' . $key;
+                $i++;
+            }
+            $values[] = "(" . implode(", ", $v) . ")";
+        }
+        $values = implode(', ', $values);
+        $class = get_called_class();
+        $tableName = $class::$tableName;
+        //print_r($values);die();
+        $sql = "INSERT INTO `" . $tableName . "` (`$fields`) VALUES $values";
+        //print_r($sql);
+        $result = self::write($sql, $dataList);
+        return $result;
+    }
+
+    /**
+     * 更新记录
+     * @param $data
+     * @param $whereParam
+     * @param $whereString
+     * @return bool
+     */
+    public static function update($data, $whereParam, $whereString = '')
+    {
+        $query = self::getQuery()->setData($data)->setWhere($whereParam, $whereString)->update();
+        $result = self::getConnection()->write($query->getSQL(), $query->getData());
+        return $result;
+    }
+
+    /**
+     * 替换输入
+     * @param $data
+     * @param $whereParam
+     * @param $whereString
+     * @return bool
+     */
+    public static function replace($data, $whereParam, $whereString = '')
+    {
+        $query = self::getQuery()->setData($data)->setWhere($whereParam, $whereString)->replace();
+        $result = self::getConnection()->write($query->getSQL(), $query->getData());
+        return $result;
+    }
+
+    /**
+     * 删除逻辑
+     * @param $whereParam
+     * @param $whereString
+     * @return bool
+     */
+    public static function delete($whereParam, $whereString = '')
+    {
+        $query = self::getQuery()->setWhere($whereParam, $whereString)->delete();
+        $result = self::getConnection()->write($query->getSQL(), $query->getData());
+        return $result;
+    }
+
+
+    /**
+     * 获取一个新的SQL生成器
+     * @return Query
+     */
+    protected static function getQuery()
+    {
+        $class = get_called_class();
+        return Query::newInstance()
+            ->setTableName($class::$tableName)
+            ->setField($class::$fields)
+            ->setPrimaryKey($class::$primaryKey);
+    }
+
+    /**
+     * 从数据库读取数据,可以强制从主库读取
+     * @param string $sql
+     * @param array $data
+     * @param bool $isMaster
+     * @return mixed|Array
+     */
+    protected static function read($sql, $data, $isMaster = false)
+    {
+        $result = self::getConnection()->read($sql, $data, $isMaster);
+        return $result;
+    }
+
+    /**
+     * 写入
+     * @param $sql
+     * @param $data
+     * @return bool|int
+     */
+    protected static function write($sql, $data)
+    {
+        $result = self::getConnection()->write($sql, $data);
+        return $result;
+    }
+
+    /**
+     * 获取连接池
+     * @return MySQL
+     */
+    static function getConnection()
+    {
+        $class = get_called_class();
+        $configClass = $class::CONFIG_CLASS;
+        return new MySQL($configClass);
+    }
+
+    /**
+     * 获取表前缀
+     * @return string
+     */
+    public static function getPrefix(){
+        return isset(self::$prefix) ? self::$prefix : '';
+    }
+
+}

+ 62 - 0
Frame/Package/Queue.php

@@ -0,0 +1,62 @@
+<?php
+
+namespace Heanup\Frame\Package;
+
+/**
+ * 队列
+ * 使用redis实现
+ */
+class Queue extends \Heanup\Frame\Package
+{
+    const CONFIG_CLASS = 'Redis\\Queue';
+    const PREFIX = '';
+
+    /**
+     * 获取队列长度
+     * @return int
+     */
+    public static function length()
+    {
+        $key = self::getKey();
+        $result = self::getConnection(true)->lLen($key);
+        return $result;
+    }
+
+    /**
+     * 插入数据
+     * @param $data
+     * @return int
+     */
+    public static function push($data)
+    {
+        $key = self::getKey();
+        $result = self::getConnection(true)->rPush($key, $data);
+        return $result;
+    }
+
+    /**
+     * 取出数据
+     * @return string
+     */
+    public static function pop()
+    {
+        $key = self::getKey();
+        $result = self::getConnection(true)->lPop($key);
+        return $result;
+    }
+
+    /**
+     * 获取连接池
+     * @param bool $isMaster
+     * @return \Redis
+     */
+    protected static function getConnection($isMaster = false)
+    {
+        $class = get_called_class();
+        $configClass = $class::CONFIG_CLASS;
+        $redis = new \Heanup\Frame\Library\Redis($configClass);
+        $connection = $redis->getConnection($isMaster);
+        return $connection;
+    }
+
+}

+ 82 - 0
Frame/Package/RedisQueue.php

@@ -0,0 +1,82 @@
+<?php
+
+namespace Heanup\Frame\Package;
+
+/**
+ * @author cxm@meitu.com
+ * 队列
+ */
+class RedisQueue
+{
+
+    protected static $_instance = NULL;
+    protected $redisObj = NULL;
+    protected $key = NULL;
+
+    /**
+     * 
+     * @param unknown $key
+     * @param string $config
+     * @return self
+     */
+    public static function getInstance($key, $config = "Redis\\Queue")
+    {
+        if (empty($key)) {
+            return false;
+        }
+        if (!isset(self::$_instance[$key][$config]) || !(self::$_instance[$key][$config] instanceof self)) {
+            self::$_instance[$key][$config] = new self($key, $config);
+        }
+        return self::$_instance[$key][$config];
+    }
+
+    /**
+     * 获取队列长度
+     * @return int
+     */
+    public function length()
+    {
+        $result = $this->getConnection(true)->lLen($this->key);
+        return $result;
+    }
+
+    /**
+     * 插入数据
+     * @param $data
+     * @return int
+     */
+    public function push($data)
+    {
+        $result = $this->getConnection(true)->lPush($this->key, $data);
+        return $result;
+    }
+
+    /**
+     * 取出数据
+     * @return string
+     */
+    public function pop()
+    {
+        $result = $this->getConnection(true)->rPop($this->key);
+        return $result;
+    }
+
+    public function delete()
+    {
+        $result = $this->getConnection(true)->delete($this->key);
+        return $result;
+    }
+
+    protected function __construct($key, $config)
+    {
+        $this->key = $key;
+        $this->redisObj = new \Heanup\Frame\Library\Redis($config);
+    }
+
+    protected function getConnection($isMaster)
+    {
+        $connection = $this->redisObj->getConnection($isMaster);
+        return $connection;
+    }
+
+}

+ 291 - 0
Frame/Package/Set.php

@@ -0,0 +1,291 @@
+<?php
+
+namespace Heanup\Frame\Package;
+
+/**
+ * 使用 redis sorted sets
+ */
+class Set extends \Heanup\Frame\Package
+{
+    const CONFIG_CLASS = 'Redis\\Core';
+    const PREFIX = '';
+    const EXPIRE = 0; // 3 天
+
+
+    /**
+     * 获取列表,从$start 到 $end,默认获取全部
+     * 从大到小获取!!
+     * @param $id
+     * @param $lastId
+     * @param $limit
+     * @return array
+     */
+    public static function get($id, $lastId, $limit = 20, $withscores = false)
+    {
+        $key = self::getKey($id);
+        try {
+            if ($lastId <= 0) {
+                $options = ($limit > 0) ? array('limit' => array(0, $limit)) : array();
+                if ($withscores) {
+                    $options['withscores'] = true;
+                }
+                $result = self::getConnection()->zRevRangeByScore($key, PHP_INT_MAX, 0, $options);
+            } else {
+                if ($withscores) {
+                    $options['withscores'] = true;
+                }
+                // 获取$lastId对应的score, 如果没有找到,则返回空
+                $score = self::getConnection()->zScore($key, $lastId);
+
+                if ($score === false) {
+                    return array();
+                }
+                if ($limit <= 0) {
+                    $result = self::getConnection()->zRevRangeByScore($key, $score, 0, $options);
+                    if ($result) {
+                        if ($lastId == current($result)) {
+                            array_shift($result);
+                        }
+                    }
+                } else {
+                    // 排除第一个元素, 多取出一个元素, 然后删除
+                    $limit = $limit + 1;
+                    $options = array('limit' => array(0, $limit));
+                    if ($withscores) {
+                        $options['withscores'] = true;
+                    }
+                    $result = self::getConnection()->zRevRangeByScore($key, $score, 0, $options);
+
+                    if ($result) {
+                        if ($lastId == current($result)) {
+                            array_shift($result);
+                        } elseif (count($result) >= $limit) {
+                            array_pop($result);
+                        }
+                    }
+                }
+            }
+        } catch (\Exception $ex) {
+            $result = array();
+        }
+        return $result;
+    }
+
+    public static function getsByIndex($id, $lastId, $limit = 20, $withscores = false)
+    {
+        $key = self::getKey($id);
+       
+        try {
+            if ($lastId <= 0) { 
+                $start = 0;
+                $stop = $limit-1; 
+                $result = self::getConnection()->zRevRange($key, $start, $stop, $withscores);
+            } else {
+                $last_index = self::getConnection()->zRevRank($key, $lastId);
+                if ($last_index) {
+                    $start = $last_index;
+                    $stop = ($start + $limit);
+                    $result = self::getConnection()->zRevRange($key, $start, $stop, $withscores);
+                    if ($result) {
+                        if ($lastId == current($result)) {
+                            array_shift($result);
+                        }
+                    }
+                } 
+            }
+        } catch (\Exception $ex) {
+            $result = array();
+        }
+        return $result;
+    }
+
+    public static function getByPage($id, $page, $limit = 20, $withscores = false)
+    {
+        $key = self::getKey($id);
+        try {
+            $options = array('limit' => array($page * $limit, $limit));
+            if ($withscores) {
+                $options['withscores'] = true;
+            }
+            $result = self::getConnection()->zRevRangeByScore($key, PHP_INT_MAX, 0, $options);
+
+        } catch (\Exception $ex) {
+            $result = array();
+        }
+        return $result;
+    }
+
+    /**
+     * 获取所有的ID列表
+     * @param $id
+     * @return array
+     */
+    public static function getAll($id, $withscores = true)
+    {
+        $key = self::getKey($id);
+        //89de3bf93d9670af:
+        $options = array();
+        if ($withscores) {
+            $options['withscores'] = true;
+        }
+        try {
+            $result = self::getConnection()->zRevRangeByScore($key, PHP_INT_MAX, 0, $options);
+        } catch (\Exception $ex) {
+            $result = array();
+        }
+        return $result;
+    }
+
+    /**
+     * 获取 $value 的索引值
+     */
+    public static function getIndex($id, $value)
+    {
+        $key = self::getKey($id);
+        try {
+            $result = self::getConnection(true)->zRank($key, $value);
+        } catch (\Exception $ex) {
+            $result = false;
+        }
+        return $result;
+    }
+
+    /**
+     * 添加元素, 注意score相同时可能会有问题, 建议使用 addBatch()
+     * @param $id
+     * @param int $score
+     * @param int $value
+     * @return bool
+     */
+    public static function add($id, $score, $value)
+    {
+        $key = self::getKey($id);
+        try {
+            $result = self::getConnection(true)->zAdd($key, $score, $value);
+            // 可选过期
+            $class = get_called_class();
+            if ($class::EXPIRE) {
+                self::getConnection(true)->expire($key, $class::EXPIRE);
+            }
+        } catch (\Exception $ex) {
+            $result = false;
+        }
+        return $result;
+    }
+
+    /**
+     * 批量添加, 重新计算权重
+     * @param $id
+     * @param array $data key为列表id,value为score,会重新排序,然后计算score
+     * @return bool|int
+     */
+    public static function addBatch($id, Array $data)
+    {
+        asort($data); // 升序排列
+        $i = 1;
+        foreach ($data as $k => $score) {
+            $score = $i * 10000;
+            self::add($id, $score, $k);
+            $i++;
+        }
+        return true;
+    }
+
+    /**
+     * 移除元素
+     * @param $id
+     * @param int $value
+     * @return int
+     */
+    public static function remove($id, $value)
+    {
+        $key = self::getKey($id);
+        try {
+            $result = self::getConnection(true)->zDelete($key, $value);
+        } catch (\Exception $ex) {
+            $result = false;
+        }
+        return $result;
+    }
+
+    /**
+     * 更新集合内容, 先删除(不判断结果),然后插入
+     * @param $id
+     * @param $score
+     * @param $value
+     * @return bool|int
+     */
+    public static function update($id, $score, $value)
+    {
+        self::remove($id, $value);
+        $result = self::add($id, $score, $value);
+        return $result;
+    }
+
+    /**
+     * 移除指定范围内元素
+     * @param $id
+     * @param int $start
+     * @param int $stop
+     * @return int
+     */
+    public static function removeByLex($id, $start, $stop)
+    {
+        $key = self::getKey($id);
+        try {
+            $result = self::getConnection(true)->zDeleteRangeByRank($key, $start, $stop);
+        } catch (\Exception $ex) {
+            $result = false;
+        }
+        return $result;
+    }
+
+    /**
+     * 获取列表长度
+     * @param $id
+     * @param int $start
+     * @param int $end
+     * @return int
+     */
+    public static function count($id, $start = 0, $end = PHP_INT_MAX)
+    {
+        $key = self::getKey($id);
+        try {
+            $result = self::getConnection(true)->zCount($key, $start, $end);
+        } catch (\Exception $ex) {
+            $result = 0;
+        }
+        return $result;
+    }
+
+    /**
+     * 删除
+     * @param $id
+     * @return bool
+     */
+    public static function delete($id)
+    {
+        $key = self::getKey($id);
+        try {
+            $result = self::getConnection(true)->delete($key);
+        } catch (\Exception $ex) {
+            $result = false;
+        }
+        return $result;
+    }
+
+    /**
+     * 获取连接池
+     * @param bool $isMaster
+     * @return \Redis
+     */
+    protected static function getConnection($isMaster = false)
+    {
+        $class = get_called_class();
+        $configClass = $class::CONFIG_CLASS;
+        $redis = new \Heanup\Frame\Library\Redis($configClass);
+        $connection = $redis->getConnection($isMaster);
+        return $connection;
+    }
+
+}

+ 203 - 0
Frame/Request.php

@@ -0,0 +1,203 @@
+<?php
+
+namespace Heanup\Frame;
+
+class Request
+{
+    const MODE_ONLINE = 0; // 正式环境
+    const MODE_BETA = 1; // Beta 环境
+    const MODE_PRE = 2; // Pre 预览环境
+    const MODE_DEVELOP = 3; // 开发环境
+    
+    const MODE_ONLINE_PRE=4;
+    const MODE_BETA_PRE=5;
+    const MODE_DEVLOP_PRE=6;
+
+    protected static $privateDataPath = ''; // 私有文件路径
+    protected static $mode = null;
+    protected static $exception;
+    protected static $currentUserId = null; // 当前登录UserID
+    protected static $sessionId = null;
+    protected static $isCacheEnabled = true; // 缓存是否有效
+
+    protected static $data = array(); // 运行期缓存
+
+    /**
+     * 获取一个随机的唯一的ID
+     * @return null|string
+     */
+    public static function getRequestId()
+    {
+        static $id = null;
+        if (is_null($id)) {
+            $id = md5(uniqid('', true) . microtime() . rand(0, 999999999));
+        }
+        return $id;
+    }
+
+    /**
+     * 设置运行期缓存,尽量不要设置大数据
+     * @param $key
+     * @param $value
+     */
+    public static function set($key, $value)
+    {
+        self::$data[$key] = $value;
+    }
+
+    /**
+     * 读取运行期缓存
+     * @param $key
+     * @return mixed
+     */
+    public static function get($key)
+    {
+        return self::$data[$key];
+    }
+
+    /**
+     * 抛出异常,并记录
+     * @param string $message
+     * @param int $code
+     * @throws \Exception
+     */
+    public static function throwException($message = '', $code = 0)
+    {
+        self::$exception = new \Exception($message, $code);
+        Logger::error(self::$exception, 'request.exception');
+        throw self::$exception;
+    }
+
+    /**
+     * 设置异常
+     * @param $exception
+     * @return bool
+     */
+    public static function setException($exception)
+    {
+        if ($exception instanceof \Exception) {
+            self::$exception = $exception;
+            Logger::error($exception, 'request.exception');
+        }
+        return true;
+    }
+
+    /**
+     * 获取异常
+     * @return mixed
+     */
+    public static function getException()
+    {
+        return self::$exception;
+    }
+
+    /**
+     * 设置环境变量
+     * @param int $value
+     */
+    public static function setMode($value = 0)
+    {
+        self::$mode = $value;
+    }
+
+    /**
+     * 获取环境变量,优先使用程序中设置的,其次使用环境变量
+     * @return int
+     */
+    public static function getMode()
+    {
+        return intval(self::$mode);
+    }
+
+    /**
+     * 检查环境变量
+     * @param $value
+     * @return bool
+     */
+    public static function checkMode($value)
+    {
+        if (self::getMode() == $value) {
+            return true;
+        } else {
+            return false;
+        }
+    }
+
+    /**
+     * 设置当前登录用户ID
+     * @param $userId
+     */
+    public static function setCurrentUserId($userId)
+    {
+        if ($userId) {
+            self::$currentUserId = $userId;
+        }
+    }
+
+    /**
+     * 返回当前登录用户ID
+     * @return int|null
+     */
+    public static function getCurrentUserId()
+    {
+        return intval(self::$currentUserId);
+    }
+
+    /**
+     * 设置会话ID
+     * @param $sessionId
+     */
+    public static function setSessionId($sessionId)
+    {
+        if ($sessionId) {
+            self::$sessionId = $sessionId;
+        }
+    }
+
+    /**
+     * 获取会话ID
+     * @return int|null
+     */
+    public static function getSessionId()
+    {
+        return self::$sessionId;
+    }
+
+    /**
+     * 设置私有文件路径
+     * @param $path
+     */
+    public static function setPrivateDataPath($path)
+    {
+        self::$privateDataPath = rtrim($path, '/');
+    }
+
+    /**
+     * 获取私有文件路径
+     * @param string $filename
+     * @return string
+     */
+    public static function getPrivateDataPath($filename = '')
+    {
+        return self::$privateDataPath . '/' . rtrim($filename) . '/';
+    }
+
+    /**
+     * 判断是否开启缓存
+     * @return bool
+     */
+    public static function isCacheEnabled()
+    {
+        return self::$isCacheEnabled;
+    }
+
+    /**
+     * 是否启用缓存
+     * @param bool $value
+     */
+    public static function setCacheEnabled($value = true)
+    {
+        self::$isCacheEnabled = intval($value);
+    }
+
+}

+ 104 - 0
Frame/Response.php

@@ -0,0 +1,104 @@
+<?php
+
+namespace Heanup\Frame;
+
+class Response
+{
+    const HTTP_VERSION = "HTTP/1.1";
+
+    //返回结果
+    public static function sendResponse($code, $data = [], $msg = 'OK')
+    {
+        //获取数据
+        if ($code == 200) {
+            $status = 200;
+            $message = 'OK';
+        } elseif ($code == 404) {
+            $status = 404;
+            $data = array('error' => 'Not Found');
+            $message = 'Not Found';
+        } elseif ($code == 405) {
+            $status = 405;
+            $data = array('error' => 'Method not allowed');
+            $message = 'Method not allowed';
+        } else {
+            $status = 200;
+            $message = $msg;
+        }
+
+        //输出结果
+        header(self::HTTP_VERSION . " " . $status . " " . $message);
+        $output = array(
+            'code' => (int)intval($code),
+            'message' => $message,
+        );
+        $content_type = isset($_SERVER['CONTENT_TYPE']) ? $_SERVER['CONTENT_TYPE'] : $_SERVER['HTTP_ACCEPT'];
+        if (strpos($content_type, 'application/json') !== false) {
+            if (is_array($data) &&  $data['page_config']) {
+                $output = array_merge($output, $data);
+            } else {
+                $output['data'] = $data;
+            }
+            header("Content-Type: application/json");
+            echo self::encodeJson($output);
+            die();
+        } else if (strpos($content_type, 'application/xml') !== false) {
+            header("Content-Type: application/xml");
+            echo self::encodeXml($output);
+            die();
+        } else {
+            if (is_array($data) && $data['page_config']) {
+                $output = array_merge($output, $data);
+            } else {
+                $output['data'] = $data;
+            }
+            header("Content-Type: application/json");
+            echo self::encodeJson($output);
+
+            die();
+        }
+    }
+
+    //json格式
+    private static function encodeJson($responseData)
+    {
+        $str = json_encode($responseData);
+        return $str;
+    }
+
+    //xml格式
+    private static function encodeXml($responseData)
+    {
+        $xml = new \SimpleXMLElement('<?xml version="1.0"?><rest></rest>');
+        foreach ($responseData as $key => $value) {
+            if (is_array($value)) {
+                foreach ($value as $k => $v) {
+                    $xml->addChild($k, $v);
+                }
+            } else {
+                $xml->addChild($key, $value);
+            }
+        }
+        return $xml->asXML();
+    }
+
+    //html格式
+    private static function encodeHtml($responseData)
+    {
+        $html = "<table border='1'>";
+        foreach ($responseData as $key => $value) {
+            $html .= "<tr>";
+            if (is_array($value)) {
+                foreach ($value as $k => $v) {
+                    $html .= "<td>" . $k . "</td><td>" . $v . "</td>";
+                }
+            } else {
+                $html .= "<td>" . $key . "</td><td>" . $value . "</td>";
+            }
+            $html .= "</tr>";
+        }
+        $html .= "</table>";
+        return $html;
+    }
+
+}

+ 39 - 0
Frame/Router.php

@@ -0,0 +1,39 @@
+<?php
+
+namespace Heanup\Frame;
+
+class Router
+{
+    protected static $list = array();
+
+    /**
+     * 添加路由
+     * @param $key
+     * @param $value
+     */
+    public static function add($key, $value)
+    {
+        self::$list[$key] = $value;
+    }
+
+    /**
+     * 移除路由
+     * @param $key
+     */
+    public static function remove($key)
+    {
+        if (isset(self::$list[$key])) {
+            unset(self::$list[$key]);
+        }
+    }
+
+    /**
+     * 获取路由列表
+     * @return array
+     */
+    public static function getList()
+    {
+        return self::$list;
+    }
+
+}

+ 144 - 0
Frame/Session.php

@@ -0,0 +1,144 @@
+<?php
+
+namespace Heanup\Frame;
+
+use Heanup\Frame\Library\Redis;
+
+class Session
+{
+
+    protected static $expire = 1800;
+
+    /**
+     * 设置超时时间, 初始化 cookie等
+     *
+     * @param int $expire
+     * @throws \Exception
+     */
+    public static function start($expire = 1800)
+    {
+        if (is_numeric($expire)) {
+            self::$expire = $expire;
+        }
+        self::getSessionId();
+    }
+
+    /**
+     * 设置会话信息
+     *
+     * @param string $key
+     * @param string $data
+     * @return bool
+     */
+    public static function set($key, $data)
+    {
+        try {
+            $sessionId = SessionID;
+            $redis = self::getConnection(true);
+            $isExisted = $redis->exists($sessionId);
+            $result = $redis->hSet($sessionId, $key, $data);
+            if (!$isExisted) {
+                $redis->expire($sessionId, self::$expire);
+            }
+        } catch (\Exception $ex) {
+            $result = false;
+        }
+        return $result;
+    }
+
+    /**
+     * 读取会话信息
+     *
+     * @param $key
+     * @return mixed
+     */
+    public static function get($key)
+    {
+
+        try {
+            $sessionId = SessionID;
+            $redis = self::getConnection();
+            $result = $redis->hGet($sessionId, $key);
+            $redis->expire($sessionId, self::$expire);
+        } catch (\Exception $ex) {
+            Logger::debug($ex, 'session.get');
+            $result = null;
+        }
+        return $result;
+    }
+
+    /**
+     * 获取所有的会话信息
+     *
+     * @param null $user_id
+     * @return array
+     */
+    public static function getAll($user_id = null)
+    {
+        try {
+            $sessionId = self::getSessionId($user_id);
+            $result = self::getConnection()->hGetAll($sessionId);
+        } catch (\Exception $ex) {
+            Logger::debug($ex, 'session.get_all');
+            $result = array();
+        }
+        return $result;
+    }
+
+    /**
+     * 删除会话
+     *
+     * @param null $user_id
+     * @return bool
+     */
+    public static function destroy($user_id = null)
+    {
+        try {
+            $sessionId = self::getSessionId($user_id);
+            $result = self::getConnection(true)->del($sessionId);
+            $_COOKIE[$sessionId] = null;
+        } catch (\Exception $ex) {
+            $result = false;
+        }
+        return $result;
+    }
+
+    /**
+     * 获取会话ID
+     *
+     * @param null $user_id
+     * @return string
+     */
+    public static function getSessionId($user_id = null)
+    {
+        if (SessionID){
+            return SessionID;
+        }
+        if ($user_id) {
+            $sessionId = md5($user_id);
+        } else {
+            $host = $_SERVER['HTTP_HOST'];
+            $key = 'sid:' . crc32($host);
+            if (!$_COOKIE[$key]) {
+                $sessionId = md5(microtime() . rand() . uniqid());
+                setcookie($key, $sessionId, time() + self::$expire * 10, '/', $host, null, true);
+            } else {
+                $sessionId = $_COOKIE[$key];
+            }
+        }
+        return $sessionId;
+    }
+
+    /**
+     *
+     * @param bool $isMaster
+     * @return \Redis
+     * @internal param $ $isMaster*            $isMaster
+     */
+    protected static function getConnection($isMaster = false)
+    {
+        $configClass = 'Redis\Core';
+        $redis = new Redis($configClass);
+        return $redis->getConnection($isMaster);
+    }
+}

+ 226 - 0
Frame/Template.php

@@ -0,0 +1,226 @@
+<?php
+
+namespace Heanup\Frame;
+
+/**
+ * 简易模板类
+ */
+class Template
+{
+    private $data = array();
+    private $path;
+    private $tpl = null;
+    private $suffix = '.phtml';
+
+
+    public function __isset($name)
+    {
+        return isset($this->data[$name]);
+    }
+
+    public function __set($name, $value = null)
+    {
+        $this->data[$name] = $value;
+    }
+
+    public function __get($name)
+    {
+        return isset($this->data[$name]) ? $this->data[$name] : null;
+    }
+
+    /**
+     * 设置模板路径
+     * @param $path
+     * @return $this
+     */
+    public function setPath($path)
+    {
+        if ($path) {
+            $this->path = rtrim($path, '/') . DIRECTORY_SEPARATOR;
+        }
+        return $this;
+    }
+
+    /**
+     * 设置模板后缀
+     * @param $suffix
+     * @return $this
+     */
+    public function setSuffix($suffix)
+    {
+        if ($suffix) {
+            $this->suffix = $suffix;
+        }
+        return $this;
+    }
+
+    /**
+     * 设置模板名称
+     * @param $tpl
+     * @return $this
+     */
+    public function setTpl($tpl)
+    {
+        if ($tpl) {
+            $this->tpl = $tpl;
+        }
+        return $this;
+    }
+
+    /**
+     * 变量赋值, 可以单独赋值,也可以批量赋值
+     * @param $name
+     * @param null $value
+     * @return $this
+     */
+    public function assign($name, $value = null)
+    {
+        if (is_array($name)) {
+            foreach ($name as $k => $v) {
+                $this->data[$k] = $v;
+            }
+        } else {
+            $this->data[$name] = $value;
+        }
+        return $this;
+    }
+
+    /**
+     * 获取全部数据
+     * @return array
+     */
+    public function getData()
+    {
+        return $this->data;
+    }
+
+    /**
+     * 载入其他模板
+     * @param $tpl
+     * @return null
+     */
+    public function load($tpl)
+    {
+        $tpl = $this->path . $tpl . $this->suffix;
+        if (is_readable($tpl)) {
+            include($tpl);
+        } else {
+            return null;
+        }
+    }
+
+    /**
+     * 渲染页面
+     * @param bool $isReturn
+     * @param string $layout
+     * @return string
+     */
+    public function render($isReturn = false, $layout = '')
+    {
+        $filename = $this->path . $this->tpl . $this->suffix;
+        // 判断模板是否存在
+        if (!is_readable($filename)) {
+            Logger::error($filename, 'template.not_found');
+            return '';
+        }
+        // 判断layout是否存在
+        if ($layout && is_readable($this->path . $layout . $this->suffix)) {
+            $filename = $this->path . $layout . $this->suffix;
+        }
+        if ($isReturn == false) {
+            include($filename);
+        } else {
+            ob_start();
+            include($filename);
+            $content = ob_get_contents();
+            ob_end_clean();
+            return $content;
+        }
+    }
+
+    /**
+     * 输出ajax信息
+     * @param bool $result
+     * @param null $code
+     * @param null $msg
+     * @param null $data
+     */
+    public function output($result = true, $code = null, $msg = null, $data = null)
+    {
+        if ($result instanceof Exception) {
+            $code = $result->getCode();
+            $msg = $result->getMessage();
+            $data = $result->__toString();
+            $result = false;
+        }
+        if ($result AND !is_bool($result) AND is_null($data)) {
+            $data = $result;
+        }
+        $result = (bool)$result;
+        $code = (int)$code;
+        $msg = (!empty($msg)) ? $msg : '';
+        $data = (!empty($data)) ? $data : '';
+
+        $data = array(
+            'result' => $result,
+            'code' => $code,
+            'msg' => $msg,
+            'data' => $data,
+        );
+
+        header('Content-type: application/json');
+        echo json_encode($data);
+    }
+
+    /**
+     * 获取同域名下的其他子域名
+     * @param $domain
+     * @return string
+     */
+    protected function getDomainHost($domain)
+    {
+        $domainHost = $_SERVER['HTTP_HOST'];
+        $domainHost = $domainHost."/".$domain;
+        return $domainHost;
+    }
+
+
+    protected function getPager($pager){
+//        if($pager['total_page'] == 0 || $pager['total_page'] <= $pager['page_size']){
+//            return '';
+//        }
+        $current_start = ($pager['current_page']-1)*$pager['page_size']+1;
+        $current_end = $pager['current_page']*$pager['page_size'] < $pager['total_count'] ? $pager['current_page']*$pager['page_size']:$pager['total_count'];
+        $html = '<div class="row">
+        <div class="col-sm-5">
+        <div class="dataTables_info">当前显示 '.$current_start.' 到 '.$current_end.' 条,共 '.$pager['total_count'].' 条记录</div>
+        </div><div class="col-sm-7">
+        <div class="text-right">
+        <ul class="pagination" style="margin:0">';
+        $start = 1;
+        $skip = 0;
+        $end = $pager['total_page'];
+        if($pager['current_page'] - 3 > $start){
+            $start = $pager['current_page'] - 3;
+            $html .= '<li class="paginate_button"><a href="'.$pager['url'].'?page=1&'.$pager['filter'].'">首页</a></li>';
+        }else{
+            $skip = 4 - $pager['current_page'];
+        }
+        if($pager['current_page'] + 3 < $end){
+            $end = $pager['current_page'] + 3 + $skip;
+        }
+        for($i = $start; $i <= $end; $i++){
+            if($i == $pager['current_page']){
+                $html .= '<li class="paginate_button active"><a href="javascript:;">'.$i.'</a></li>';
+            }else{
+                $html .= '<li class="paginate_button"><a href="'.$pager['url'].'?page='.$i.'&'.$pager['filter'].'">'.$i.'</a></li>';
+            }
+        }
+        if($end < $pager['total_page']){
+            $html .= '<li class="paginate_button"><a href="'.$pager['url'].'?page='.$pager['total_page'].'&'.$pager['filter'].'">末页</a></li>';
+        }
+        $html .= '</ul></div>';
+        return $html;
+    }
+
+}

+ 219 - 0
Library/Curl.php

@@ -0,0 +1,219 @@
+<?php
+/**
+ * Created by PhpStorm.
+ * User: chendeben
+ * Date: 2018/8/17
+ * Time: 下午5:26
+ */
+
+namespace Heanup\Library;
+
+use Heanup\Frame\Logger;
+
+class Curl
+{
+    private static $cookie_file = "curl_cookie.log";
+
+
+    /**
+     * CURL-get方式获取数据
+     * @param string $url URL
+     * @param string $proxy 是否代理
+     * @param int $timeout 请求时间
+     * @param array $header header信息  array('Host: api.kingdee.meitushop.com' )设置请求的host
+     * @param null $REFERER
+     * @return string
+     */
+    public static function get($url, $proxy = null, $timeout = 10, $header = null, $REFERER = null)
+    {
+        if (!$url)
+            return false;
+        $ssl = stripos($url, 'https://') === 0 ? true : false;
+        $curl = curl_init();
+        if (!is_null($proxy))
+            curl_setopt($curl, CURLOPT_PROXY, $proxy);
+        curl_setopt($curl, CURLOPT_URL, $url);
+        if ($ssl) {
+            curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, 0); // 对认证证书来源的检查
+            curl_setopt($curl, CURLOPT_SSL_VERIFYHOST, 1); // 从证书中检查SSL加密算法是否存在
+        }
+        if ($REFERER) {
+            curl_setopt($curl, CURLOPT_REFERER, $REFERER);//模拟来路
+
+        }
+        $cookie_file = self::$cookie_file;
+        curl_setopt($curl, CURLOPT_COOKIEJAR, $cookie_file); //连接结束后保存cookie信息的文件。
+        curl_setopt($curl, CURLOPT_COOKIEFILE, $cookie_file); //包含cookie数据的文件名,cookie文件的格式可以是Netscape格式,或者只是纯HTTP头部信息存入文件。
+        curl_setopt($curl, CURLOPT_USERAGENT, $_SERVER['HTTP_USER_AGENT']); //在HTTP请求中包含一个"User-Agent: "头的字符串。
+        curl_setopt($curl, CURLOPT_HEADER, 0); //启用时会将头文件的信息作为数据流输出。
+        curl_setopt($curl, CURLOPT_FOLLOWLOCATION, 1); //启用时会将服务器服务器返回的"Location: "放在header中递归的返回给服务器,使用CURLOPT_MAXREDIRS可以限定递归返回的数量。
+        curl_setopt($curl, CURLOPT_RETURNTRANSFER, true); //文件流形式
+        curl_setopt($curl, CURLOPT_TIMEOUT, $timeout); //设置cURL允许执行的最长秒数。
+        if (is_array($header))
+            curl_setopt($curl, CURLOPT_HTTPHEADER, $header); //设置请求的Header
+
+        $content = curl_exec($curl);
+        $curl_errno = curl_errno($curl);
+        if ($curl_errno > 0) {
+            Logger::setlog($error = sprintf("curl error=%s, errno=%d.", curl_error($curl), $curl_errno));
+            curl_close($curl);
+            return false;
+        }
+//        $this->http_code = curl_getinfo($curl, CURLINFO_HTTP_CODE);
+        curl_close($curl);
+        return $content;
+    }
+
+    /**
+     * CURL-post方式获取数据
+     * @param string $url URL
+     * @param array $data POST数据
+     * @param string $proxy 是否代理
+     * @param int $timeout 请求时间
+     * @param array $header header信息
+     * @return string
+     */
+    public static function post($url, $data, $proxy = null, $timeout = 10, $header = null)
+    {
+        if (!$url)
+            return false;
+//        if ($data) {
+//            $data = http_build_query($data);
+//        }
+        $ssl = stripos($url, 'https://') === 0 ? true : false;
+        $curl = curl_init();
+        if (!is_null($proxy))
+            curl_setopt($curl, CURLOPT_PROXY, $proxy);
+        curl_setopt($curl, CURLOPT_URL, $url);
+        if ($ssl) {
+            curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, 0); // 对认证证书来源的检查
+            curl_setopt($curl, CURLOPT_SSL_VERIFYHOST, 1); // 从证书中检查SSL加密算法是否存在
+        }
+        $cookie_file = self::$cookie_file;
+        curl_setopt($curl, CURLOPT_COOKIEJAR, $cookie_file); //连接结束后保存cookie信息的文件。
+        curl_setopt($curl, CURLOPT_COOKIEFILE, $cookie_file); //包含cookie数据的文件名,cookie文件的格式可以是Netscape格式,或者只是纯HTTP头部信息存入文件。
+        curl_setopt($curl, CURLOPT_USERAGENT, $_SERVER['HTTP_USER_AGENT']); //在HTTP请求中包含一个"User-Agent: "头的字符串。
+        curl_setopt($curl, CURLOPT_HEADER, 0); //启用时会将头文件的信息作为数据流输出。
+        curl_setopt($curl, CURLOPT_POST, true); //发送一个常规的Post请求
+        curl_setopt($curl, CURLOPT_POSTFIELDS, $data); //Post提交的数据包
+        curl_setopt($curl, CURLOPT_FOLLOWLOCATION, 1); //启用时会将服务器服务器返回的"Location: "放在header中递归的返回给服务器,使用CURLOPT_MAXREDIRS可以限定递归返回的数量。
+        curl_setopt($curl, CURLOPT_RETURNTRANSFER, true); //文件流形式
+        curl_setopt($curl, CURLOPT_TIMEOUT, $timeout); //设置cURL允许执行的最长秒数。
+        if (is_array($header))
+            curl_setopt($curl, CURLOPT_HTTPHEADER, $header); //设置请求的Header
+
+        $content = curl_exec($curl);
+        $curl_errno = curl_errno($curl);
+        if ($curl_errno > 0) {
+            Logger::setlog($error = sprintf("curl error=%s, errno=%d.", curl_error($curl), $curl_errno));
+            curl_close($curl);
+            return false;
+        }
+//        $this->http_code = curl_getinfo($curl, CURLINFO_HTTP_CODE);
+        curl_close($curl);
+        return $content;
+    }
+
+    /**
+     * CURL-put方式获取数据
+     * @param string $url URL
+     * @param array $data POST数据
+     * @param string $proxy 是否代理
+     * @param int $timeout 请求时间
+     * @param array $header header信息
+     * @return string
+     */
+    public static function put($url, $data, $proxy = null, $timeout = 10, $header = null)
+    {
+        if (!$url)
+            return false;
+        if ($data) {
+            $data = http_build_query($data);
+        }
+        $ssl = stripos($url, 'https://') === 0 ? true : false;
+        $curl = curl_init();
+        if (!is_null($proxy))
+            curl_setopt($curl, CURLOPT_PROXY, $proxy);
+        curl_setopt($curl, CURLOPT_URL, $url);
+        if ($ssl) {
+            curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, 0); // 对认证证书来源的检查
+            curl_setopt($curl, CURLOPT_SSL_VERIFYHOST, 1); // 从证书中检查SSL加密算法是否存在
+        }
+        $cookie_file = self::$cookie_file;
+        curl_setopt($curl, CURLOPT_COOKIEJAR, $cookie_file); //连接结束后保存cookie信息的文件。
+        curl_setopt($curl, CURLOPT_COOKIEFILE, $cookie_file); //包含cookie数据的文件名,cookie文件的格式可以是Netscape格式,或者只是纯HTTP头部信息存入文件。
+        curl_setopt($curl, CURLOPT_USERAGENT, $_SERVER['HTTP_USER_AGENT']); //在HTTP请求中包含一个"User-Agent: "头的字符串。
+        curl_setopt($curl, CURLOPT_HEADER, 0); //启用时会将头文件的信息作为数据流输出。
+        curl_setopt($curl, CURLOPT_FOLLOWLOCATION, 1); //启用时会将服务器服务器返回的"Location: "放在header中递归的返回给服务器,使用CURLOPT_MAXREDIRS可以限定递归返回的数量。
+        curl_setopt($curl, CURLOPT_RETURNTRANSFER, true); //文件流形式
+        curl_setopt($curl, CURLOPT_TIMEOUT, $timeout); //设置cURL允许执行的最长秒数
+        $data = (is_array($data)) ? http_build_query($data) : $data;
+        curl_setopt($curl, CURLOPT_CUSTOMREQUEST, 'PUT');
+        curl_setopt($curl, CURLOPT_HTTPHEADER, array('Content-Length: ' . strlen($data)));
+        curl_setopt($curl, CURLOPT_POSTFIELDS, $data);
+        if (is_array($header))
+            curl_setopt($curl, CURLOPT_HTTPHEADER, $header); //设置请求的Header
+
+        $content = curl_exec($curl);
+        $curl_errno = curl_errno($curl);
+        if ($curl_errno > 0) {
+            Logger::setlog($error = sprintf("curl error=%s, errno=%d.", curl_error($curl), $curl_errno));
+            curl_close($curl);
+            return false;
+        }
+        curl_close($curl);
+        return $content;
+    }
+
+    /**
+     * CURL-DEL方式获取数据
+     * @param string $url URL
+     * @param array $data POST数据
+     * @param string $proxy 是否代理
+     * @param int $timeout 请求时间
+     * @param array $header header信息
+     * @return string
+     */
+    public static function del($url, $data, $proxy = null, $timeout = 10, $header = null)
+    {
+        if (!$url)
+            return false;
+        if ($data) {
+            $data = http_build_query($data);
+        }
+        $ssl = stripos($url, 'https://') === 0 ? true : false;
+        $curl = curl_init();
+        if (!is_null($proxy))
+            curl_setopt($curl, CURLOPT_PROXY, $proxy);
+        curl_setopt($curl, CURLOPT_URL, $url);
+        if ($ssl) {
+            curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, 0); // 对认证证书来源的检查
+            curl_setopt($curl, CURLOPT_SSL_VERIFYHOST, 1); // 从证书中检查SSL加密算法是否存在
+        }
+        $cookie_file = self::$cookie_file;
+        curl_setopt($curl, CURLOPT_COOKIEJAR, $cookie_file); //连接结束后保存cookie信息的文件。
+        curl_setopt($curl, CURLOPT_COOKIEFILE, $cookie_file); //包含cookie数据的文件名,cookie文件的格式可以是Netscape格式,或者只是纯HTTP头部信息存入文件。
+        curl_setopt($curl, CURLOPT_USERAGENT, $_SERVER['HTTP_USER_AGENT']); //在HTTP请求中包含一个"User-Agent: "头的字符串。
+        curl_setopt($curl, CURLOPT_HEADER, 0); //启用时会将头文件的信息作为数据流输出。
+        curl_setopt($curl, CURLOPT_FOLLOWLOCATION, 1); //启用时会将服务器服务器返回的"Location: "放在header中递归的返回给服务器,使用CURLOPT_MAXREDIRS可以限定递归返回的数量。
+        curl_setopt($curl, CURLOPT_RETURNTRANSFER, true); //文件流形式
+        curl_setopt($curl, CURLOPT_TIMEOUT, $timeout); //设置cURL允许执行的最长秒数
+        $data = (is_array($data)) ? http_build_query($data) : $data;
+        curl_setopt($curl, CURLOPT_CUSTOMREQUEST, 'DEL');
+        curl_setopt($curl, CURLOPT_HTTPHEADER, array('Content-Length: ' . strlen($data)));
+        curl_setopt($curl, CURLOPT_POSTFIELDS, $data);
+        if (is_array($header))
+            curl_setopt($curl, CURLOPT_HTTPHEADER, $header); //设置请求的Header
+
+        $content = curl_exec($curl);
+        $curl_errno = curl_errno($curl);
+        if ($curl_errno > 0) {
+            Logger::setlog($error = sprintf("curl error=%s, errno=%d.", curl_error($curl), $curl_errno));
+            curl_close($curl);
+            return false;
+        }
+        curl_close($curl);
+        return $content;
+    }
+
+}

+ 82 - 0
Library/Email.php

@@ -0,0 +1,82 @@
+<?php
+/**
+ * Created by PhpStorm.
+ * User: chendeben
+ * Date: 2018/8/10
+ * Time: 上午11:22
+ */
+
+namespace Heanup\Library;
+
+use Heanup\App\Api\Config\GlobalConfig;
+use Heanup\Frame\Logger;
+use PHPMailer\PHPMailer\Exception;
+use PHPMailer\PHPMailer\PHPMailer;
+
+class Email
+{
+    private static $instance;
+    private $mail;
+
+    public function __construct()
+    {
+        $config = GlobalConfig::getData();
+        $this->mail = new PHPMailer();
+        $this->mail->CharSet = 'UTF-8';
+        $this->mail->isSMTP();                                        // Set mailer to use SMTP
+        $this->mail->Host = $config['host'];                          // Specify main and backup SMTP servers
+        $this->mail->SMTPAuth = $config['auth'];                      // Enable SMTP authentication
+        $this->mail->Username = $config['user'];                      // SMTP username
+        $this->mail->Password = $config['pwd'];                       // SMTP password
+        $this->mail->SMTPSecure = $config['secure'];                  // Enable TLS encryption, `ssl` also accepted
+        $this->mail->Port = $config['port'];                          // TCP port to connect to
+        try {
+            $this->mail->setFrom($config['user'], $config['from']);
+        } catch (Exception $e) {
+        }
+        $this->mail->addCustomHeader('X-Priority', '1\r\n');
+    }
+
+    public static function instance()
+    {
+        if (!self::$instance) {
+            self::$instance = new self();
+        }
+        return self::$instance;
+    }
+
+
+    /**
+     * @param array $receipt 收件人地址
+     * @param string $subject 邮件标题
+     * @param string $body 邮件内容
+     * @param array $attachment 附件
+     * @param bool $is_html 是否HTML格式
+     * @return bool
+     * @throws Exception
+     */
+    public function sendMail(array $receipt, string $subject, string $body, array $attachment = array(), bool $is_html = true)
+    {
+        foreach ($receipt as $list) {
+            $this->mail->addAddress($list);
+        }
+        try {
+            if ($attachment) {
+                $this->mail->addAttachment($attachment['path'], $attachment['name']);
+            }
+        } catch (Exception $e) {
+        }
+        $this->mail->isHTML($is_html);
+        $this->mail->Subject = $subject;
+        $this->mail->Body = $body;
+        try {
+            if (!$this->mail->send()) {
+                Logger::error($this->mail->ErrorInfo, 'mail_err');
+                return false;
+            }
+        } catch (Exception $e) {
+            Logger::error($this->mail->ErrorInfo, 'mail_err');
+        }
+        return true;
+    }
+}

+ 1280 - 0
Library/Medoo/Medoo.php

@@ -0,0 +1,1280 @@
+<?php
+/*!
+ * Medoo database framework
+ * https://medoo.in
+ * Version 1.4.5
+ *
+ * Copyright 2017, Angel Lai
+ * Released under the MIT license
+ */
+
+namespace Medoo;
+
+use PDO;
+use Exception;
+use PDOException;
+
+class Medoo
+{
+    protected $database_type;
+
+    protected $prefix;
+
+    protected $statement;
+
+    protected $option = [];
+
+    protected $logs = [];
+
+    protected $logging = false;
+
+    protected $debug_mode = false;
+
+    protected $guid = 0;
+
+    public function __construct($options = null)
+    {
+        try {
+            if (is_array($options)) {
+                if (isset($options['database_type'])) {
+                    $this->database_type = strtolower($options['database_type']);
+                }
+            } else {
+                return false;
+            }
+
+            if (isset($options['prefix'])) {
+                $this->prefix = $options['prefix'];
+            }
+
+            if (isset($options['option'])) {
+                $this->option = $options['option'];
+            }
+
+            if (isset($options['logging']) && is_bool($options['logging'])) {
+                $this->logging = $options['logging'];
+            }
+
+            if (isset($options['command']) && is_array($options['command'])) {
+                $commands = $options['command'];
+            } else {
+                $commands = [];
+            }
+
+            if (isset($options['dsn'])) {
+                if (isset($options['dsn']['driver'])) {
+                    $attr = $options['dsn'];
+                } else {
+                    return false;
+                }
+            } else {
+                if (
+                    isset($options['port']) &&
+                    is_int($options['port'] * 1)
+                ) {
+                    $port = $options['port'];
+                }
+
+                $is_port = isset($port);
+
+                switch ($this->database_type) {
+                    case 'mariadb':
+                    case 'mysql':
+                        $attr = [
+                            'driver' => 'mysql',
+                            'dbname' => $options['database_name']
+                        ];
+
+                        if (isset($options['socket'])) {
+                            $attr['unix_socket'] = $options['socket'];
+                        } else {
+                            $attr['host'] = $options['server'];
+
+                            if ($is_port) {
+                                $attr['port'] = $port;
+                            }
+                        }
+
+                        // Make MySQL using standard quoted identifier
+                        $commands[] = 'SET SQL_MODE=ANSI_QUOTES';
+                        break;
+
+                    case 'pgsql':
+                        $attr = [
+                            'driver' => 'pgsql',
+                            'host' => $options['server'],
+                            'dbname' => $options['database_name']
+                        ];
+
+                        if ($is_port) {
+                            $attr['port'] = $port;
+                        }
+
+                        break;
+
+                    case 'sybase':
+                        $attr = [
+                            'driver' => 'dblib',
+                            'host' => $options['server'],
+                            'dbname' => $options['database_name']
+                        ];
+
+                        if ($is_port) {
+                            $attr['port'] = $port;
+                        }
+
+                        break;
+
+                    case 'oracle':
+                        $attr = [
+                            'driver' => 'oci',
+                            'dbname' => $options['server'] ?
+                                '//' . $options['server'] . ($is_port ? ':' . $port : ':1521') . '/' . $options['database_name'] :
+                                $options['database_name']
+                        ];
+
+                        if (isset($options['charset'])) {
+                            $attr['charset'] = $options['charset'];
+                        }
+
+                        break;
+
+                    case 'mssql':
+                        if (strstr(PHP_OS, 'WIN')) {
+                            $attr = [
+                                'driver' => 'sqlsrv',
+                                'Server' => $options['server'] . ($is_port ? ',' . $port : ''),
+                                'Database' => $options['database_name']
+                            ];
+                        } else {
+                            $attr = [
+                                'driver' => 'dblib',
+                                'host' => $options['server'] . ($is_port ? ':' . $port : ''),
+                                'dbname' => $options['database_name']
+                            ];
+                        }
+
+                        // Keep MSSQL QUOTED_IDENTIFIER is ON for standard quoting
+                        $commands[] = 'SET QUOTED_IDENTIFIER ON';
+
+                        // Make ANSI_NULLS is ON for NULL value
+                        $commands[] = 'SET ANSI_NULLS ON';
+                        break;
+
+                    case 'sqlite':
+                        $this->pdo = new PDO('sqlite:' . $options['database_file'], null, null, $this->option);
+
+                        return;
+                }
+            }
+
+            $driver = $attr['driver'];
+
+            unset($attr['driver']);
+
+            $stack = [];
+
+            foreach ($attr as $key => $value) {
+                if (is_int($key)) {
+                    $stack[] = $value;
+                } else {
+                    $stack[] = $key . '=' . $value;
+                }
+            }
+
+            $dsn = $driver . ':' . implode($stack, ';');
+
+            if (
+                in_array($this->database_type, ['mariadb', 'mysql', 'pgsql', 'sybase', 'mssql']) &&
+                isset($options['charset'])
+            ) {
+                $commands[] = "SET NAMES '" . $options['charset'] . "'";
+            }
+
+            $this->pdo = new PDO(
+                $dsn,
+                $options['username'],
+                $options['password'],
+                $this->option
+            );
+
+            foreach ($commands as $value) {
+                $this->pdo->exec($value);
+            }
+        } catch (PDOException $e) {
+            throw new PDOException($e->getMessage());
+        }
+    }
+
+    public function query($query, $map = [])
+    {
+        if (!empty($map)) {
+            foreach ($map as $key => $value) {
+                switch (gettype($value)) {
+                    case 'NULL':
+                        $map[$key] = [null, PDO::PARAM_NULL];
+                        break;
+
+                    case 'resource':
+                        $map[$key] = [$value, PDO::PARAM_LOB];
+                        break;
+
+                    case 'boolean':
+                        $map[$key] = [($value ? '1' : '0'), PDO::PARAM_BOOL];
+                        break;
+
+                    case 'integer':
+                    case 'double':
+                        $map[$key] = [$value, PDO::PARAM_INT];
+                        break;
+
+                    case 'string':
+                        $map[$key] = [$value, PDO::PARAM_STR];
+                        break;
+                }
+            }
+        }
+
+        return $this->exec($query, $map);
+    }
+
+    public function exec($query, $map = [])
+    {
+        if ($this->debug_mode) {
+            echo $this->generate($query, $map);
+
+            $this->debug_mode = false;
+
+            return false;
+        }
+
+        if ($this->logging) {
+            $this->logs[] = [$query, $map];
+        } else {
+            $this->logs = [[$query, $map]];
+        }
+
+        $statement = $this->pdo->prepare($query);
+
+        if ($statement) {
+            foreach ($map as $key => $value) {
+                $statement->bindValue($key, $value[0], $value[1]);
+            }
+
+            $statement->execute();
+
+            $this->statement = $statement;
+
+            return $statement;
+        } else {
+            return false;
+        }
+    }
+
+    protected function generate($query, $map)
+    {
+        foreach ($map as $key => $value) {
+            if ($value[1] === PDO::PARAM_STR) {
+                $query = str_replace($key, $this->quote($value[0]), $query);
+            } elseif ($value[1] === PDO::PARAM_NULL) {
+                $query = str_replace($key, 'NULL', $query);
+            } else {
+                $query = str_replace($key, $value[0], $query);
+            }
+        }
+
+        return $query;
+    }
+
+    public function quote($string)
+    {
+        return $this->pdo->quote($string);
+    }
+
+    protected function tableQuote($table)
+    {
+        return '"' . $this->prefix . $table . '"';
+    }
+
+    protected function mapKey()
+    {
+        return ':MeDoO_' . $this->guid++ . '_mEdOo';
+    }
+
+    protected function columnQuote($string)
+    {
+        preg_match('/(^#)?([a-zA-Z0-9_]*)\.([a-zA-Z0-9_]*)(\s*\[JSON\]$)?/', $string, $column_match);
+
+        if (isset($column_match[2], $column_match[3])) {
+            return '"' . $this->prefix . $column_match[2] . '"."' . $column_match[3] . '"';
+        }
+
+        return '"' . $string . '"';
+    }
+
+    protected function columnPush(&$columns)
+    {
+        if ($columns === '*') {
+            return $columns;
+        }
+
+        $stack = [];
+
+        if (is_string($columns)) {
+            $columns = [$columns];
+        }
+
+        foreach ($columns as $key => $value) {
+            if (is_array($value)) {
+                $stack[] = $this->columnPush($value);
+            } else {
+                preg_match('/(?<column>[a-zA-Z0-9_\.]+)(?:\s*\((?<alias>[a-zA-Z0-9_]+)\)|\s*\[(?<type>(String|Bool|Int|Number|Object|JSON))\])?/i', $value, $match);
+
+                if (!empty($match['alias'])) {
+                    $stack[] = $this->columnQuote($match['column']) . ' AS ' . $this->columnQuote($match['alias']);
+
+                    $columns[$key] = $match['alias'];
+                } else {
+                    $stack[] = $this->columnQuote($match['column']);
+                }
+            }
+        }
+
+        return implode($stack, ',');
+    }
+
+    protected function arrayQuote($array)
+    {
+        $stack = [];
+
+        foreach ($array as $value) {
+            $stack[] = is_int($value) ? $value : $this->pdo->quote($value);
+        }
+
+        return implode($stack, ',');
+    }
+
+    protected function innerConjunct($data, $map, $conjunctor, $outer_conjunctor)
+    {
+        $stack = [];
+
+        foreach ($data as $value) {
+            $stack[] = '(' . $this->dataImplode($value, $map, $conjunctor) . ')';
+        }
+
+        return implode($outer_conjunctor . ' ', $stack);
+    }
+
+    protected function fnQuote($column, $string)
+    {
+        return (strpos($column, '#') === 0 && preg_match('/^[A-Z0-9\_]*\([^)]*\)$/', $string)) ?
+
+            $string :
+
+            $this->quote($string);
+    }
+
+    protected function dataImplode($data, &$map, $conjunctor)
+    {
+        $wheres = [];
+
+        foreach ($data as $key => $value) {
+            $map_key = $this->mapKey();
+
+            $type = gettype($value);
+
+            if (
+                preg_match("/^(AND|OR)(\s+#.*)?$/i", $key, $relation_match) &&
+                $type === 'array'
+            ) {
+                $wheres[] = 0 !== count(array_diff_key($value, array_keys(array_keys($value)))) ?
+                    '(' . $this->dataImplode($value, $map, ' ' . $relation_match[1]) . ')' :
+                    '(' . $this->innerConjunct($value, $map, ' ' . $relation_match[1], $conjunctor) . ')';
+            } else {
+                if (
+                    is_int($key) &&
+                    preg_match('/([a-zA-Z0-9_\.]+)\[(?<operator>\>|\>\=|\<|\<\=|\!|\=)\]([a-zA-Z0-9_\.]+)/i', $value, $match)
+                ) {
+                    $wheres[] = $this->columnQuote($match[1]) . ' ' . $match['operator'] . ' ' . $this->columnQuote($match[3]);
+                } else {
+                    preg_match('/(#?)([a-zA-Z0-9_\.]+)(\[(?<operator>\>|\>\=|\<|\<\=|\!|\<\>|\>\<|\!?~)\])?/i', $key, $match);
+                    $column = $this->columnQuote($match[2]);
+
+                    if (!empty($match[1])) {
+                        $wheres[] = $column .
+                            (isset($match['operator']) ? ' ' . $match['operator'] . ' ' : ' = ') .
+                            $this->fnQuote($key, $value);
+
+                        continue;
+                    }
+
+                    if (isset($match['operator'])) {
+                        $operator = $match['operator'];
+
+                        if ($operator === '!') {
+                            switch ($type) {
+                                case 'NULL':
+                                    $wheres[] = $column . ' IS NOT NULL';
+                                    break;
+
+                                case 'array':
+                                    $wheres[] = $column . ' NOT IN (' . $this->arrayQuote($value) . ')';
+                                    break;
+
+                                case 'integer':
+                                case 'double':
+                                    $wheres[] = $column . ' != ' . $map_key;
+                                    $map[$map_key] = [$value, PDO::PARAM_INT];
+                                    break;
+
+                                case 'boolean':
+                                    $wheres[] = $column . ' != ' . $map_key;
+                                    $map[$map_key] = [($value ? '1' : '0'), PDO::PARAM_BOOL];
+                                    break;
+
+                                case 'string':
+                                    $wheres[] = $column . ' != ' . $map_key;
+                                    $map[$map_key] = [$value, PDO::PARAM_STR];
+                                    break;
+                            }
+                        }
+
+                        if ($operator === '<>' || $operator === '><') {
+                            if ($type === 'array') {
+                                if ($operator === '><') {
+                                    $column .= ' NOT';
+                                }
+
+                                $wheres[] = '(' . $column . ' BETWEEN ' . $map_key . 'a AND ' . $map_key . 'b)';
+
+                                $data_type = (is_numeric($value[0]) && is_numeric($value[1])) ? PDO::PARAM_INT : PDO::PARAM_STR;
+
+                                $map[$map_key . 'a'] = [$value[0], $data_type];
+                                $map[$map_key . 'b'] = [$value[1], $data_type];
+                            }
+                        }
+
+                        if ($operator === '~' || $operator === '!~') {
+                            if ($type !== 'array') {
+                                $value = [$value];
+                            }
+
+                            $connector = ' OR ';
+                            $stack = array_values($value);
+
+                            if (is_array($stack[0])) {
+                                if (isset($value['AND']) || isset($value['OR'])) {
+                                    $connector = ' ' . array_keys($value)[0] . ' ';
+                                    $value = $stack[0];
+                                }
+                            }
+
+                            $like_clauses = [];
+
+                            foreach ($value as $index => $item) {
+                                $item = strval($item);
+
+                                if (!preg_match('/(\[.+\]|_|%.+|.+%)/', $item)) {
+                                    $item = '%' . $item . '%';
+                                }
+
+                                $like_clauses[] = $column . ($operator === '!~' ? ' NOT' : '') . ' LIKE ' . $map_key . 'L' . $index;
+                                $map[$map_key . 'L' . $index] = [$item, PDO::PARAM_STR];
+                            }
+
+                            $wheres[] = '(' . implode($connector, $like_clauses) . ')';
+                        }
+
+                        if (in_array($operator, ['>', '>=', '<', '<='])) {
+                            $condition = $column . ' ' . $operator . ' ';
+
+                            if (is_numeric($value)) {
+                                $condition .= $map_key;
+                                $map[$map_key] = [$value, PDO::PARAM_INT];
+                            } else {
+                                $condition .= $map_key;
+                                $map[$map_key] = [$value, PDO::PARAM_STR];
+                            }
+
+                            $wheres[] = $condition;
+                        }
+                    } else {
+                        switch ($type) {
+                            case 'NULL':
+                                $wheres[] = $column . ' IS NULL';
+                                break;
+
+                            case 'array':
+                                $wheres[] = $column . ' IN (' . $this->arrayQuote($value) . ')';
+                                break;
+
+                            case 'integer':
+                            case 'double':
+                                $wheres[] = $column . ' = ' . $map_key;
+                                $map[$map_key] = [$value, PDO::PARAM_INT];
+                                break;
+
+                            case 'boolean':
+                                $wheres[] = $column . ' = ' . $map_key;
+                                $map[$map_key] = [($value ? '1' : '0'), PDO::PARAM_BOOL];
+                                break;
+
+                            case 'string':
+                                $wheres[] = $column . ' = ' . $map_key;
+                                $map[$map_key] = [$value, PDO::PARAM_STR];
+                                break;
+                        }
+                    }
+                }
+            }
+        }
+
+        return implode($conjunctor . ' ', $wheres);
+    }
+
+    protected function whereClause($where, &$map)
+    {
+        $where_clause = '';
+
+        if (is_array($where)) {
+            $where_keys = array_keys($where);
+            $where_AND = preg_grep("/^AND\s*#?$/i", $where_keys);
+            $where_OR = preg_grep("/^OR\s*#?$/i", $where_keys);
+
+            $single_condition = array_diff_key($where, array_flip(
+                ['AND', 'OR', 'GROUP', 'ORDER', 'HAVING', 'LIMIT', 'LIKE', 'MATCH']
+            ));
+
+            if (!empty($single_condition)) {
+                $condition = $this->dataImplode($single_condition, $map, ' AND');
+
+                if ($condition !== '') {
+                    $where_clause = ' WHERE ' . $condition;
+                }
+            }
+
+            if (!empty($where_AND)) {
+                $value = array_values($where_AND);
+                $where_clause = ' WHERE ' . $this->dataImplode($where[$value[0]], $map, ' AND');
+            }
+
+            if (!empty($where_OR)) {
+                $value = array_values($where_OR);
+                $where_clause = ' WHERE ' . $this->dataImplode($where[$value[0]], $map, ' OR');
+            }
+
+            if (isset($where['MATCH'])) {
+                $MATCH = $where['MATCH'];
+
+                if (is_array($MATCH) && isset($MATCH['columns'], $MATCH['keyword'])) {
+                    $mode = '';
+
+                    $mode_array = [
+                        'natural' => 'IN NATURAL LANGUAGE MODE',
+                        'natural+query' => 'IN NATURAL LANGUAGE MODE WITH QUERY EXPANSION',
+                        'boolean' => 'IN BOOLEAN MODE',
+                        'query' => 'WITH QUERY EXPANSION'
+                    ];
+
+                    if (isset($MATCH['mode'], $mode_array[$MATCH['mode']])) {
+                        $mode = ' ' . $mode_array[$MATCH['mode']];
+                    }
+
+                    $columns = implode(array_map([$this, 'columnQuote'], $MATCH['columns']), ', ');
+                    $map_key = $this->mapKey();
+                    $map[$map_key] = [$MATCH['keyword'], PDO::PARAM_STR];
+
+                    $where_clause .= ($where_clause !== '' ? ' AND ' : ' WHERE') . ' MATCH (' . $columns . ') AGAINST (' . $map_key . $mode . ')';
+                }
+            }
+
+            if (isset($where['GROUP'])) {
+                $GROUP = $where['GROUP'];
+
+                if (is_array($GROUP)) {
+                    $stack = [];
+
+                    foreach ($GROUP as $column => $value) {
+                        $stack[] = $this->columnQuote($value);
+                    }
+
+                    $where_clause .= ' GROUP BY ' . implode($stack, ',');
+                } else {
+                    $where_clause .= ' GROUP BY ' . $this->columnQuote($where['GROUP']);
+                }
+
+                if (isset($where['HAVING'])) {
+                    $where_clause .= ' HAVING ' . $this->dataImplode($where['HAVING'], $map, ' AND');
+                }
+            }
+
+            if (isset($where['ORDER'])) {
+                $ORDER = $where['ORDER'];
+
+                if (is_array($ORDER)) {
+                    $stack = [];
+
+                    foreach ($ORDER as $column => $value) {
+                        if (is_array($value)) {
+                            $stack[] = 'FIELD(' . $this->columnQuote($column) . ', ' . $this->arrayQuote($value) . ')';
+                        } else if ($value === 'ASC' || $value === 'DESC') {
+                            $stack[] = $this->columnQuote($column) . ' ' . $value;
+                        } else if (is_int($column)) {
+                            $stack[] = $this->columnQuote($value);
+                        }
+                    }
+
+                    $where_clause .= ' ORDER BY ' . implode($stack, ',');
+                } else {
+                    $where_clause .= ' ORDER BY ' . $this->columnQuote($ORDER);
+                }
+
+                if (
+                    isset($where['LIMIT']) &&
+                    in_array($this->database_type, ['oracle', 'mssql'])
+                ) {
+                    $LIMIT = $where['LIMIT'];
+
+                    if (is_numeric($LIMIT)) {
+                        $where_clause .= ' FETCH FIRST ' . $LIMIT . ' ROWS ONLY';
+                    }
+
+                    if (
+                        is_array($LIMIT) &&
+                        is_numeric($LIMIT[0]) &&
+                        is_numeric($LIMIT[1])
+                    ) {
+                        $where_clause .= ' OFFSET ' . $LIMIT[0] . ' ROWS FETCH NEXT ' . $LIMIT[1] . ' ROWS ONLY';
+                    }
+                }
+            }
+
+            if (isset($where['LIMIT']) && !in_array($this->database_type, ['oracle', 'mssql'])) {
+                $LIMIT = $where['LIMIT'];
+
+                if (is_numeric($LIMIT)) {
+                    $where_clause .= ' LIMIT ' . $LIMIT;
+                }
+
+                if (
+                    is_array($LIMIT) &&
+                    is_numeric($LIMIT[0]) &&
+                    is_numeric($LIMIT[1])
+                ) {
+                    $where_clause .= ' LIMIT ' . $LIMIT[1] . ' OFFSET ' . $LIMIT[0];
+                }
+            }
+        } else {
+            if ($where !== null) {
+                $where_clause .= ' ' . $where;
+            }
+        }
+
+        return $where_clause;
+    }
+
+    protected function selectContext($table, &$map, $join, &$columns = null, $where = null, $column_fn = null)
+    {
+        preg_match('/(?<table>[a-zA-Z0-9_]+)\s*\((?<alias>[a-zA-Z0-9_]+)\)/i', $table, $table_match);
+
+        if (isset($table_match['table'], $table_match['alias'])) {
+            $table = $this->tableQuote($table_match['table']);
+
+            $table_query = $table . ' AS ' . $this->tableQuote($table_match['alias']);
+        } else {
+            $table = $this->tableQuote($table);
+
+            $table_query = $table;
+        }
+
+        $join_key = is_array($join) ? array_keys($join) : null;
+
+        if (
+            isset($join_key[0]) &&
+            strpos($join_key[0], '[') === 0
+        ) {
+            $table_join = [];
+
+            $join_array = [
+                '>' => 'LEFT',
+                '<' => 'RIGHT',
+                '<>' => 'FULL',
+                '><' => 'INNER'
+            ];
+
+            foreach ($join as $sub_table => $relation) {
+                preg_match('/(\[(?<join>\<|\>|\>\<|\<\>)\])?(?<table>[a-zA-Z0-9_]+)\s?(\((?<alias>[a-zA-Z0-9_]+)\))?/', $sub_table, $match);
+
+                if ($match['join'] !== '' && $match['table'] !== '') {
+                    if (is_string($relation)) {
+                        $relation = 'USING ("' . $relation . '")';
+                    }
+
+                    if (is_array($relation)) {
+                        // For ['column1', 'column2']
+                        if (isset($relation[0])) {
+                            $relation = 'USING ("' . implode($relation, '", "') . '")';
+                        } else {
+                            $joins = [];
+
+                            foreach ($relation as $key => $value) {
+                                $joins[] = (
+                                    strpos($key, '.') > 0 ?
+                                        // For ['tableB.column' => 'column']
+                                        $this->columnQuote($key) :
+
+                                        // For ['column1' => 'column2']
+                                        $table . '."' . $key . '"'
+                                    ) .
+                                    ' = ' .
+                                    $this->tableQuote(isset($match['alias']) ? $match['alias'] : $match['table']) . '."' . $value . '"';
+                            }
+
+                            $relation = 'ON ' . implode($joins, ' AND ');
+                        }
+                    }
+
+                    $table_name = $this->tableQuote($match['table']) . ' ';
+
+                    if (isset($match['alias'])) {
+                        $table_name .= 'AS ' . $this->tableQuote($match['alias']) . ' ';
+                    }
+
+                    $table_join[] = $join_array[$match['join']] . ' JOIN ' . $table_name . $relation;
+                }
+            }
+
+            $table_query .= ' ' . implode($table_join, ' ');
+        } else {
+            if (is_null($columns)) {
+                if (is_null($where)) {
+                    if (
+                        is_array($join) &&
+                        isset($column_fn)
+                    ) {
+                        $where = $join;
+                        $columns = null;
+                    } else {
+                        $where = null;
+                        $columns = $join;
+                    }
+                } else {
+                    $where = $join;
+                    $columns = null;
+                }
+            } else {
+                $where = $columns;
+                $columns = $join;
+            }
+        }
+
+        if (isset($column_fn)) {
+            if ($column_fn === 1) {
+                $column = '1';
+
+                if (is_null($where)) {
+                    $where = $columns;
+                }
+            } else {
+                if (empty($columns)) {
+                    $columns = '*';
+                    $where = $join;
+                }
+
+                $column = $column_fn . '(' . $this->columnPush($columns) . ')';
+            }
+        } else {
+            $column = $this->columnPush($columns);
+        }
+
+        return 'SELECT ' . $column . ' FROM ' . $table_query . $this->whereClause($where, $map);
+    }
+
+    protected function columnMap($columns, &$stack)
+    {
+        if ($columns === '*') {
+            return $stack;
+        }
+
+        foreach ($columns as $key => $value) {
+            if (is_int($key)) {
+                preg_match('/(?<column>[a-zA-Z0-9_\.]*)(?:\s*\((?<alias>[a-zA-Z0-9_]+)\)|\s*\[(?<type>(String|Bool|Int|Number|Object|JSON))\])?/i', $value, $key_match);
+
+                $column_key = !empty($key_match['alias']) ?
+                    $key_match['alias'] :
+                    preg_replace('/^[\w]*\./i', '', $key_match['column']);
+
+                if (isset($key_match['type'])) {
+                    $stack[$value] = [$column_key, $key_match['type']];
+                } else {
+                    $stack[$value] = [$column_key, 'String'];
+                }
+            } else {
+                $this->columnMap($value, $stack);
+            }
+        }
+
+        return $stack;
+    }
+
+    protected function dataMap($data, $columns, $column_map, &$stack)
+    {
+        foreach ($columns as $key => $value) {
+            if (is_int($key)) {
+                $map = $column_map[$value];
+                $column_key = $map[0];
+
+                if (isset($map[1])) {
+                    switch ($map[1]) {
+                        case 'Number':
+                        case 'Int':
+                            $stack[$column_key] = (int)$data[$column_key];
+                            break;
+
+                        case 'Bool':
+                            $stack[$column_key] = (bool)$data[$column_key];
+                            break;
+
+                        case 'Object':
+                            $stack[$column_key] = unserialize($data[$column_key]);
+                            break;
+
+                        case 'JSON':
+                            $stack[$column_key] = json_decode($data[$column_key], true);
+                            break;
+
+                        case 'String':
+                            $stack[$column_key] = $data[$column_key];
+                            break;
+                    }
+                } else {
+                    $stack[$column_key] = $data[$column_key];
+                }
+            } else {
+                $current_stack = [];
+
+                $this->dataMap($data, $value, $column_map, $current_stack);
+
+                $stack[$key] = $current_stack;
+            }
+        }
+    }
+
+    public function select($table, $join, $columns = null, $where = null)
+    {
+        $map = [];
+        $stack = [];
+        $column_map = [];
+
+        $index = 0;
+
+        $column = $where === null ? $join : $columns;
+
+        $is_single_column = (is_string($column) && $column !== '*');
+
+        $query = $this->exec($this->selectContext($table, $map, $join, $columns, $where), $map);
+
+        $this->columnMap($columns, $column_map);
+
+        if (!$query) {
+            return false;
+        }
+
+        if ($columns === '*') {
+            return $query->fetchAll(PDO::FETCH_ASSOC);
+        }
+
+        if ($is_single_column) {
+            return $query->fetchAll(PDO::FETCH_COLUMN);
+        }
+
+        while ($data = $query->fetch(PDO::FETCH_ASSOC)) {
+            $current_stack = [];
+
+            $this->dataMap($data, $columns, $column_map, $current_stack);
+
+            $stack[$index] = $current_stack;
+
+            $index++;
+        }
+
+        return $stack;
+    }
+
+    public function insert($table, $datas)
+    {
+        $stack = [];
+        $columns = [];
+        $fields = [];
+        $map = [];
+
+        if (!isset($datas[0])) {
+            $datas = [$datas];
+        }
+
+        foreach ($datas as $data) {
+            foreach ($data as $key => $value) {
+                $columns[] = $key;
+            }
+        }
+
+        $columns = array_unique($columns);
+
+        foreach ($datas as $data) {
+            $values = [];
+
+            foreach ($columns as $key) {
+                if (strpos($key, '#') === 0) {
+                    $values[] = $this->fnQuote($key, $data[$key]);
+                    continue;
+                }
+
+                $map_key = $this->mapKey();
+
+                $values[] = $map_key;
+
+                if (!isset($data[$key])) {
+                    $map[$map_key] = [null, PDO::PARAM_NULL];
+                } else {
+                    $value = $data[$key];
+
+                    switch (gettype($value)) {
+                        case 'NULL':
+                            $map[$map_key] = [null, PDO::PARAM_NULL];
+                            break;
+
+                        case 'array':
+                            $map[$map_key] = [
+                                strpos($key, '[JSON]') === strlen($key) - 6 ?
+                                    json_encode($value) :
+                                    serialize($value),
+                                PDO::PARAM_STR
+                            ];
+                            break;
+
+                        case 'object':
+                            $map[$map_key] = [serialize($value), PDO::PARAM_STR];
+                            break;
+
+                        case 'resource':
+                            $map[$map_key] = [$value, PDO::PARAM_LOB];
+                            break;
+
+                        case 'boolean':
+                            $map[$map_key] = [($value ? '1' : '0'), PDO::PARAM_BOOL];
+                            break;
+
+                        case 'integer':
+                        case 'double':
+                            $map[$map_key] = [$value, PDO::PARAM_INT];
+                            break;
+
+                        case 'string':
+                            $map[$map_key] = [$value, PDO::PARAM_STR];
+                            break;
+                    }
+                }
+            }
+
+            $stack[] = '(' . implode($values, ', ') . ')';
+        }
+
+        foreach ($columns as $key) {
+            $fields[] = $this->columnQuote(preg_replace("/(^#|\s*\[JSON\]$)/i", '', $key));
+        }
+
+        return $this->exec('INSERT INTO ' . $this->tableQuote($table) . ' (' . implode(', ', $fields) . ') VALUES ' . implode(', ', $stack), $map);
+    }
+
+    public function update($table, $data, $where = null)
+    {
+        $fields = [];
+        $map = [];
+
+        foreach ($data as $key => $value) {
+            $column = $this->columnQuote(preg_replace("/(^#|\s*\[(JSON|\+|\-|\*|\/)\]$)/i", '', $key));
+
+            if (strpos($key, '#') === 0) {
+                $fields[] = $column . ' = ' . $value;
+                continue;
+            }
+
+            $map_key = $this->mapKey();
+
+            preg_match('/(?<column>[a-zA-Z0-9_]+)(\[(?<operator>\+|\-|\*|\/)\])?/i', $key, $match);
+
+            if (isset($match['operator'])) {
+                if (is_numeric($value)) {
+                    $fields[] = $column . ' = ' . $column . ' ' . $match['operator'] . ' ' . $value;
+                }
+            } else {
+                $fields[] = $column . ' = ' . $map_key;
+
+                switch (gettype($value)) {
+                    case 'NULL':
+                        $map[$map_key] = [null, PDO::PARAM_NULL];
+                        break;
+
+                    case 'array':
+                        $map[$map_key] = [
+                            strpos($key, '[JSON]') === strlen($key) - 6 ?
+                                json_encode($value) :
+                                serialize($value),
+                            PDO::PARAM_STR
+                        ];
+                        break;
+
+                    case 'object':
+                        $map[$map_key] = [serialize($value), PDO::PARAM_STR];
+                        break;
+
+                    case 'resource':
+                        $map[$map_key] = [$value, PDO::PARAM_LOB];
+                        break;
+
+                    case 'boolean':
+                        $map[$map_key] = [($value ? '1' : '0'), PDO::PARAM_BOOL];
+                        break;
+
+                    case 'integer':
+                    case 'double':
+                        $map[$map_key] = [$value, PDO::PARAM_INT];
+                        break;
+
+                    case 'string':
+                        $map[$map_key] = [$value, PDO::PARAM_STR];
+                        break;
+                }
+            }
+        }
+
+        return $this->exec('UPDATE ' . $this->tableQuote($table) . ' SET ' . implode(', ', $fields) . $this->whereClause($where, $map), $map);
+    }
+
+    public function delete($table, $where)
+    {
+        $map = [];
+
+        return $this->exec('DELETE FROM ' . $this->tableQuote($table) . $this->whereClause($where, $map), $map);
+    }
+
+    public function replace($table, $columns, $where = null)
+    {
+        $map = [];
+
+        if (is_array($columns)) {
+            $replace_query = [];
+
+            foreach ($columns as $column => $replacements) {
+                if (is_array($replacements[0])) {
+                    foreach ($replacements as $replacement) {
+                        $map_key = $this->mapKey();
+
+                        $replace_query[] = $this->columnQuote($column) . ' = REPLACE(' . $this->columnQuote($column) . ', ' . $map_key . 'a, ' . $map_key . 'b)';
+
+                        $map[$map_key . 'a'] = [$replacement[0], PDO::PARAM_STR];
+                        $map[$map_key . 'b'] = [$replacement[1], PDO::PARAM_STR];
+                    }
+                } else {
+                    $map_key = $this->mapKey();
+
+                    $replace_query[] = $this->columnQuote($column) . ' = REPLACE(' . $this->columnQuote($column) . ', ' . $map_key . 'a, ' . $map_key . 'b)';
+
+                    $map[$map_key . 'a'] = [$replacements[0], PDO::PARAM_STR];
+                    $map[$map_key . 'b'] = [$replacements[1], PDO::PARAM_STR];
+                }
+            }
+
+            $replace_query = implode(', ', $replace_query);
+        }
+
+        return $this->exec('UPDATE ' . $this->tableQuote($table) . ' SET ' . $replace_query . $this->whereClause($where, $map), $map);
+    }
+
+    public function get($table, $join = null, $columns = null, $where = null)
+    {
+        $map = [];
+        $stack = [];
+        $column_map = [];
+
+        $column = $where === null ? $join : $columns;
+
+        $is_single_column = (is_string($column) && $column !== '*');
+
+        $query = $this->exec($this->selectContext($table, $map, $join, $columns, $where) . ' LIMIT 1', $map);
+
+        if ($query) {
+            $data = $query->fetchAll(PDO::FETCH_ASSOC);
+
+            if (isset($data[0])) {
+                if ($column === '*') {
+                    return $data[0];
+                }
+
+                $this->columnMap($columns, $column_map);
+
+                $this->dataMap($data[0], $columns, $column_map, $stack);
+
+                if ($is_single_column) {
+                    return $stack[$column_map[$column][0]];
+                }
+
+                return $stack;
+            } else {
+                return false;
+            }
+        } else {
+            return false;
+        }
+    }
+
+    public function has($table, $join, $where = null)
+    {
+        $map = [];
+        $column = null;
+
+        $query = $this->exec('SELECT EXISTS(' . $this->selectContext($table, $map, $join, $column, $where, 1) . ')', $map);
+
+        if ($query) {
+            return $query->fetchColumn() === '1';
+        } else {
+            return false;
+        }
+    }
+
+    public function count($table, $join = null, $column = null, $where = null)
+    {
+        $map = [];
+
+        $query = $this->exec($this->selectContext($table, $map, $join, $column, $where, 'COUNT'), $map);
+
+        return $query ? 0 + $query->fetchColumn() : false;
+    }
+
+    public function max($table, $join, $column = null, $where = null)
+    {
+        $map = [];
+
+        $query = $this->exec($this->selectContext($table, $map, $join, $column, $where, 'MAX'), $map);
+
+        if ($query) {
+            $max = $query->fetchColumn();
+
+            return is_numeric($max) ? $max + 0 : $max;
+        } else {
+            return false;
+        }
+    }
+
+    public function min($table, $join, $column = null, $where = null)
+    {
+        $map = [];
+
+        $query = $this->exec($this->selectContext($table, $map, $join, $column, $where, 'MIN'), $map);
+
+        if ($query) {
+            $min = $query->fetchColumn();
+
+            return is_numeric($min) ? $min + 0 : $min;
+        } else {
+            return false;
+        }
+    }
+
+    public function avg($table, $join, $column = null, $where = null)
+    {
+        $map = [];
+
+        $query = $this->exec($this->selectContext($table, $map, $join, $column, $where, 'AVG'), $map);
+
+        return $query ? 0 + $query->fetchColumn() : false;
+    }
+
+    public function sum($table, $join, $column = null, $where = null)
+    {
+        $map = [];
+
+        $query = $this->exec($this->selectContext($table, $map, $join, $column, $where, 'SUM'), $map);
+
+        return $query ? 0 + $query->fetchColumn() : false;
+    }
+
+    public function action($actions)
+    {
+        if (is_callable($actions)) {
+            $this->pdo->beginTransaction();
+
+            $result = $actions($this);
+
+            if ($result === false) {
+                $this->pdo->rollBack();
+            } else {
+                $this->pdo->commit();
+            }
+        } else {
+            return false;
+        }
+    }
+
+    public function id()
+    {
+        $type = $this->database_type;
+
+        if ($type === 'oracle') {
+            return 0;
+        } elseif ($type === 'mssql') {
+            return $this->pdo->query('SELECT SCOPE_IDENTITY()')->fetchColumn();
+        } elseif ($type === 'pgsql') {
+            return $this->pdo->query('SELECT LASTVAL()')->fetchColumn();
+        }
+
+        return $this->pdo->lastInsertId();
+    }
+
+    public function debug()
+    {
+        $this->debug_mode = true;
+
+        return $this;
+    }
+
+    public function error()
+    {
+        return $this->statement ? $this->statement->errorInfo() : null;
+    }
+
+    public function last()
+    {
+        $log = end($this->logs);
+
+        return $this->generate($log[0], $log[1]);
+    }
+
+    public function log()
+    {
+        return array_map(function ($log) {
+            return $this->generate($log[0], $log[1]);
+        },
+            $this->logs
+        );
+    }
+
+    public function info()
+    {
+        $output = [
+            'server' => 'SERVER_INFO',
+            'driver' => 'DRIVER_NAME',
+            'client' => 'CLIENT_VERSION',
+            'version' => 'SERVER_VERSION',
+            'connection' => 'CONNECTION_STATUS'
+        ];
+
+        foreach ($output as $key => $value) {
+            $output[$key] = @$this->pdo->getAttribute(constant('PDO::ATTR_' . $value));
+        }
+
+        return $output;
+    }
+}
+
+?>

+ 108 - 0
Library/Memcached.php

@@ -0,0 +1,108 @@
+<?php
+
+namespace Heanup\Library;
+
+use Exception;
+use Heanup\Config\Memcached\Core;
+
+class Memcached
+{
+
+    private $memcache;
+    private $type;
+    private static $instance;
+
+    /**
+     * Memcache constructor.
+     * @throws Exception
+     */
+    public function __construct()
+    {
+        $config = Core::getData(null);
+        $host = $config[0]['host'];
+        $port = $config[0]['port'];
+        $this->type = $config[0]['type'] ?: 'memcached';
+        try {
+            $this->memcache = new \Memcached;
+            $this->memcache->setOption(\Memcached::OPT_COMPRESSION, true);
+            $this->memcache->setOption(\Memcached::OPT_DISTRIBUTION, true);
+            $this->memcache->setOption(\Memcached::OPT_LIBKETAMA_COMPATIBLE, true);
+            $this->memcache->setOption(\Memcached::OPT_NO_BLOCK, true);
+            $this->memcache->setOption(\Memcached::OPT_CONNECT_TIMEOUT, 50);
+            $this->memcache->setOption(\Memcached::OPT_POLL_TIMEOUT, 50);
+            $this->memcache->addServer($host, $port);
+        } catch (Exception $ex) {
+            $message = "Memcached connection failed : [" . $this->type . ';' . $host . ';' . $port . ']' . $ex->getMessage();
+            throw new Exception($message, 90301);
+        }
+    }
+
+    public static function instance()
+    {
+        if (!isset(self::$instance)) {
+            try {
+                $instance = new self();
+            } catch (Exception $e) {
+            }
+            self::$instance = $instance;
+        } else {
+            $instance = self::$instance;
+        }
+        return $instance;
+    }
+
+    public function __destruct()
+    {
+        if ($this->type != 'memcached') {
+            $this->memcache->close();
+        }
+    }
+
+    public function get($key)
+    {
+        if (empty($key)) {
+            return '';
+        } else {
+            return $this->memcache->get($key);
+        }
+    }
+
+    public function delete($key, $time = 0)
+    {
+        return $this->memcache->delete($key, $time);
+    }
+
+    public function set($key, $data, $life_time_limit = 0, $memcache_compressed = 2)
+    {
+        if ($this->type == 'memcached') {
+            return $this->memcache->set($key, $data, $life_time_limit);
+        } else {
+            return $this->memcache->set($key, $data, $memcache_compressed, $life_time_limit);
+        }
+    }
+
+    public function replace($key, $data, $memcache_compressed = 2, $life_time_limit = 0)
+    {
+        if ($this->type == 'memcached') {
+            return $this->memcache->replace($key, $data, $life_time_limit);
+        } else {
+            return $this->memcache->replace($key, $data, $memcache_compressed, $life_time_limit);
+        }
+
+    }
+
+    public function increment($key, $value = 1)
+    {
+        return $this->memcache->increment($key, $value);
+    }
+
+    public function decrement($key, $value = 1)
+    {
+        return $this->memcache->decrement($key, $value);
+    }
+
+    public function flush()
+    {
+        return $this->memcache->flush();
+    }
+}

+ 232 - 0
Library/Redis.php

@@ -0,0 +1,232 @@
+<?php
+/**
+ * Created by PhpStorm.
+ * User: dongdx
+ * Date: 2017/6/21
+ * Time: 10:25
+ */
+
+namespace Heanup\Library;
+
+use Heanup\Config\Redis\Core;
+use Heanup\Frame\Logger;
+
+class Redis
+{
+
+    private $cfg;
+    private $w_redis;
+    private $r_redis;
+
+    public static $instance;
+
+    public function __construct()
+    {
+        $this->cfg = Core::getData();
+    }
+
+    public static function instance()
+    {
+        if (!isset(self::$instance)) {
+            $instance = new self();
+            self::$instance = $instance;
+        } else {
+            $instance = self::$instance;
+        }
+        return $instance;
+    }
+
+    public function get($key)
+    {
+        $redis = $this->connect(true);
+        if (empty($key)) {
+            return '';
+        } else {
+            $res = $redis->get($key);
+            $row = json_decode($res, true);
+            if ($res == '[]') $res = array();
+            return $row ?: $res;
+        }
+    }
+
+    public function set($key, $value, $life_time_limit = 0)
+    {
+        $redis = $this->connect(false);
+        if (is_array($value)) {
+            $value = json_encode($value);
+        }
+        if ($life_time_limit) {
+            return $redis->setex($key, $life_time_limit, $value);
+        } else {
+            return $redis->set($key, $value);
+        }
+    }
+
+    public function del($key)
+    {
+        $redis = $this->connect(false);
+        return $redis->del($key);
+    }
+
+    public function hExists($key, $hashkey)
+    {
+        $redis = $this->connect(true);
+        return boolval($redis->hExists($key, $hashkey));
+    }
+
+    public function hSet($key, $hashkey, $value)
+    {
+        $redis = $this->connect(false);
+        if (is_array($value)) {
+            $value = json_encode($value);
+        }
+        return $redis->hSet($key, $hashkey, $value);
+    }
+    public function hDel($key, $hashkey)
+    {
+        $redis = $this->connect(false);
+        return $redis->hDel($key, $hashkey);
+    }
+
+    public function hGet($key, $hashkey)
+    {
+        $redis = $this->connect(true);
+        $res = $redis->hGet($key, $hashkey);
+        $row = json_decode($res, true);
+        if ($res == '[]') $res = array();
+        return $row ?: $res;
+    }
+
+    public function hLen($key)
+    {
+        $redis = $this->connect(true);
+        $res = $redis->hLen($key);
+        return intval($res);
+    }
+
+    public function sAdd($key, $values)
+    {
+        $redis = $this->connect(false);
+        if (is_array($values)) {
+            $res = $redis->sAddArray($key,$values);
+        } else if(is_scalar($values)) {
+            $res = $redis->sAdd($key, $values);
+        }
+        return $res;
+    }
+
+    public function sRem($key,$value)
+    {
+        $redis = $this->connect(false);
+        return $redis->sRem($key,$value);
+    }
+
+    public function sMembers($key)
+    {
+        $redis = $this->connect(true);
+        return $redis->sMembers($key);
+    }
+
+    public function hMGet($key, $hashkey)
+    {
+        $redis = $this->connect(true);
+        return $redis->hMGet($key, $hashkey);
+    }
+
+    public function hGetAll($key)
+    {
+        $redis = $this->connect(true);
+        return $redis->hGetAll($key);
+    }
+
+    public function hVals($key)
+    {
+        $redis = $this->connect(true);
+        return $redis->hVals($key);
+    }
+
+    public function delete($key)
+    {
+        $redis = $this->connect(false);
+        return $redis->delete($key);
+    }
+
+    public function incr($key)
+    {
+        $redis = $this->connect(false);
+        return $redis->incr($key);
+    }
+
+    public function multi()
+    {
+        $redis = $this->connect(false);
+        $redis ->multi();
+    }
+
+    public function exec()
+    {
+        $redis = $this->connect(false);
+        try {
+            $result = $redis ->exec();
+            return $result;
+        } catch (\Exception $ex) {
+            Logger::error($ex->getMessage()."-".$ex->getTraceAsString()."(".$ex->getFile().":".$ex->getFile().")");
+//            return false;
+        }
+
+    }
+
+    /**
+     * @param bool $is_read
+     * @return bool|\Redis
+     */
+    public function connect($is_read = false)
+    {
+        try {
+            if ($is_read) {
+                //获取redis读连接
+                if ($this->r_redis) {
+                    return $this->r_redis;
+                }
+                $this->r_redis = new \Redis();
+                $config = $this->cfg['slave_list'][0];
+                if (isset($config['pconnect']) && $config['pconnect'] == 1) {
+                    $this->r_redis->pconnect($config['host'], $config['port'], $config['timeout']);
+                } else {
+                    $this->r_redis->connect($config['host'], $config['port'], $config['timeout']);
+                }
+                if (isset($config['password']) && $config['password']) {
+                    $this->r_redis->auth($config['password']);
+                }
+
+                //选择redis-db  -by edison
+                if (isset($config['db']))
+                    $this->r_redis->select(intval($config['db']));
+                return $this->r_redis;
+            } else {
+                //获取redis写连接
+                if ($this->w_redis) {
+                    return $this->w_redis;
+                }
+                $this->w_redis = new \Redis();
+                $config = $this->cfg['master'];
+                if (isset($config['pconnect']) && $config['pconnect'] == 1) {
+                    $this->w_redis->pconnect($config['host'], $config['port'], $config['timeout']);
+                } else {
+                    $this->w_redis->connect($config['host'], $config['port'], $config['timeout']);
+                }
+                if (isset($config['password']) && $config['password']) {
+                    $this->w_redis->auth($config['password']);
+                }
+                //选择redis-db  -by edison
+                if (isset($config['db']))
+                    $this->w_redis->select(intval($config['db']));
+                return $this->w_redis;
+            }
+        } catch (\Exception $ex) {
+            $message = "Redis connection failed : [" . $config['host'] . ';' . $config['port'] . ']' . $ex->getMessage();
+            Logger::error($message);
+//            return false;
+        }
+    }
+}

+ 36 - 0
Library/Warming.php

@@ -0,0 +1,36 @@
+<?php
+
+
+namespace Heanup\Library;
+
+
+use Heanup\App\Api\Config\GlobalConfig;
+
+class Warming
+{
+    public static function semdMail($type, $domain, $total, $log_file = null)
+    {
+        $day = date("Ymd");
+        $count = Redis::instance()->incr("alert_" . $type . $day . "_count");
+        if ($count >= $total) {
+            $key = "alert_" . $type . "_" . $day;
+            if (Redis::instance()->get($key)) {
+                return true;
+            } else {
+                Redis::instance()->set($key, 1, 86400);
+                $receipt = GlobalConfig::getData('recieve');
+                $subject = GlobalConfig::getData('subject');
+                $subject = sprintf($subject, $domain);
+                $body = GlobalConfig::getData('body');
+                if ($log_file) {
+                    $attachment = ['path' => $log_file, 'name'=>sprintf('log[%s].txt',date("Y-m-d"))];
+                } else {
+                    $attachment = [];
+                }
+                Email::instance()->sendMail($receipt, $subject, $body, $attachment);
+
+            }
+        }
+    }
+
+}

+ 17 - 0
Library/WeatherAdapt/Weather.php

@@ -0,0 +1,17 @@
+<?php
+
+
+namespace Heanup\Library\WeatherAdapt;
+
+
+interface Weather
+{
+    /**
+     * @param $city_id
+     * @return mixed
+     */
+    public function getWeatherData($city_id);
+
+    public function parseData($data1,$data2);
+
+}

+ 47 - 0
Library/WeatherAdapt/WeatherStruct.php

@@ -0,0 +1,47 @@
+<?php
+
+
+namespace Heanup\Library\WeatherAdapt;
+
+
+class WeatherStruct
+{
+    public $returnBody = [
+        'Weather' => [
+            [
+                'basic' => [
+                    'location' => '',
+                    'update' => '',
+                ],
+                'status' => 'ok',
+                'air_now_city' => '',
+                'alarm' => '',
+                'now' => '',
+                'daily_forecast' => [
+                    [
+                        "cond_txt_d" => "晴",
+                        "cond_txt_n" => "多云",
+                        "date" => "2019-06-11",
+                        "tmp_max" => "32",
+                        "tmp_min" => "20",
+                        "wind_dir" => "南风",
+                        "wind_sc" => "3-4"
+                    ]],
+                'hourly' => [
+                    [
+                        "cond_txt" => "晴",
+                        "time" => "2019-06-11 16:00",
+                        "tmp" => "30"
+                    ]
+                ],
+                'lifestyle' => [
+                    [
+                        "type" => "comf",
+                        "brf" => "较不舒适",
+                        "txt" => "白天天气晴好,明媚的阳光在给您带来好心情的同时,也会使您感到有些热,不很舒适。"
+                    ]
+                ],
+            ]
+        ]
+    ];
+}

+ 423 - 0
Library/WeatherAdapt/caiyunAdapter.php

@@ -0,0 +1,423 @@
+<?php
+
+
+namespace Heanup\Library\WeatherAdapt;
+
+
+use Heanup\App\Api\Config\CityToCoordinate;
+use Heanup\App\Api\Config\GlobalConfig;
+use Heanup\Library\Curl;
+use Heanup\Library\Redis;
+use Heanup\Library\Warming;
+
+class caiyunAdapter implements Weather
+{
+    private $city_id;
+
+    private $location;
+
+
+    /**
+     * @param $city_id
+     * @return mixed
+     */
+    public function getWeatherData($city_id)
+    {
+        $this->city_id = $city_id;
+//        $city_id = is_numeric($city_id) ? ("CN" . $city_id) : $city_id;
+        $key = $this->getKey();
+        $caiyun_version=GlobalConfig::getData("caiyun_version");
+        $url = sprintf("https://api.caiyunapp.com/v%s/%s/%s/weather_2.json?alert=true&dailysteps=15",$caiyun_version, $key, $this->getCoodinate($city_id));
+        $data = Curl::get($url);
+        $origin = $data = json_decode($data, true);
+        $data = $this->parseData($data);
+        $i = 0;
+        while ($data['Weather']['now']['cond_txt'] == "" && $i < 2) {//数据有误,重试3次
+            $log_file = APP_DIR . "/logs/" . date("Ymd") . "_null.log";
+            file_put_contents($log_file, sprintf("%s\t\tcity_id:%s", date("Y-m-d H:i:s"), $city_id) . "\t\tdata:" . json_encode($origin) . "\r\n", FILE_APPEND);
+            Warming::semdMail("no_data", "caiyun", ALERT_TOTAL, $log_file);
+            $data = Curl::get($url);
+            $origin = $data = json_decode($data, true);
+            $data = $this->parseData($data);
+            $i++;
+        }
+
+        return $data;
+    }
+
+
+    private function getKey()
+    {
+        $keys = GlobalConfig::getData("caiyun_key");
+        $key = date("Ymd_") . $keys;
+        Redis::instance()->incr($key);
+        return $keys;
+
+    }
+
+    /**
+     *
+     * @param $data
+     * @param null $data2
+     * @return array
+     */
+    public function parseData($data, $data2 = null)
+    {
+        $originData = $data;
+
+        $return = [];
+        $weather = [];
+        $weather['basic'] = [
+            'location' => $this->location,
+            'city_code' => $this->city_id,
+            'update' => date("Y-m-d H:i", $data['server_time'])
+        ];
+        $weather['status'] = $originData['status'] ?: "ok";
+        $result = $originData['result'];
+//        $weather['key'] = $k;
+        $weather['air_now_city'] = [
+            "aqi" => (string)$result['realtime']['air_quality']['aqi']['chn'] ?? "",  //                                                       ->a3
+            "qlty" => (string)$result['realtime']['air_quality']['description']['chn'] ?? "",      //                                            ->a4
+            "pm25" => (string)$result['realtime']['air_quality']['pm25'] ?? "", //                                                        ->a5
+            "pm10" => (string)$result['realtime']['air_quality']['pm10'] ?? "",    //                                                     ->a6
+            "no2" => (string)$result['realtime']['air_quality']['no2'] ?? "",       //                                                   ->a7
+            "so2" => (string)$result['realtime']['air_quality']['so2'] ?? ""
+        ];
+        $weather['alarm'] = [
+            "level" => "",                              //                        ->a9
+            "link" => $this->getAlert($result['alert']['content'][0]['code']),      //                       ->b1
+            "type" => "",                                    //                    ->b2
+            'alertId' => (string)$result['alert']['content'][0]['alertId'] ?? "",
+            "title" => (string)$result['alert']['content'][0]['title'] ?? "",//                  ->z2
+            "description" => (string)($result['alert']['content'][0]['description'] ? $result['alert']['content'][0]['description'] . sprintf("(来源:%s)", $result['alert']['content'][0]['source']) : ""),   //                 ->z3
+            "pubtimestamp" => (string)($result['alert']['content'] ? date("Y-m-d H:i", $result['alert']['content'][0]['pubtimestamp']) : ""),  //                                       ->z4
+            "location" => (string)$result['alert']['content'][0]['location'] ?? "",   //                                        ->z5
+            "status" => (string)$result['alert']['content'][0]['status'] ?? ""      //                                              ->z6
+        ];
+        $weather['now'] = [
+            "cond_txt" => $this->getSkycon($result['realtime']['skycon']), //                                                    ->b3
+            "fl" => (string)(intval($result['realtime']['apparent_temperature']) ?: ""),//                                                           ->b4
+            "hum" => (string)intval($result['realtime']['humidity'] * 100),                 //                                         ->b5
+            "pres" => (string)intval($result['realtime']['pressure'] / 100),               //                                        ->b6
+            "tmp" => (string)(intval($result['realtime']['temperature']) ?: ""),                 //                                         ->b7
+            "vis" => (string)$result['realtime']['visibility'] ?: "",                 //                                        ->b8
+            "wind_dir" => (string)$this->getWindDirect($result['realtime']['wind']['direction']) ?: "",            //                                       ->b9
+            "wind_sc" => (string)$this->getWindSpeed($result['realtime']['wind']['speed'])['level'] ?: "",               //                                        ->c1
+            "sr" => (string)$result['daily']['astro'][0]['sunrise']['time'],                //                                        ->c2
+            "ss" => (string)$result['daily']['astro'][0]['sunset']['time'],                 //                                        ->c3
+            'forecast_keypoint' => (string)$result['forecast_keypoint'] ?? "",
+            'description' => (string)$result['hourly']['description'] ?? "",
+        ];
+        if (!empty($result['daily'])) {
+            foreach ($result['daily']['skycon_08h_20h'] as $key => $value) {
+                $temp = [
+                    "cond_txt_d" => $this->getSkycon($value['value']),
+                    "cond_txt_n" => $this->getSkycon($result['daily']['skycon_20h_32h'][$key]['value']),
+                    "date" => date("Y-m-d", strtotime($value['date'])),
+                    "tmp_max" => (string)intval($result['daily']['temperature'][$key]['max']),
+                    "tmp_min" => (string)intval($result['daily']['temperature'][$key]['min']),
+                    "wind_dir" => (string)$this->getWindDirect($result['daily']['wind'][$key]['avg']['direction']),
+                    "wind_sc" => (string)$this->getWindSpeed($result['daily']['wind'][$key]['avg']['speed'])['level'],
+                ];
+                $weather['daily_forecast'][] = $temp;
+            }
+        }
+        if (!empty($result['hourly'])) {
+            $i = 0;
+            foreach ($result['hourly']['temperature'] as $key => $value) {
+                if ($i >= 24) {
+                    break;
+                }
+                $i++;
+                $temp = [
+                    'cond_txt' => $this->getSkycon($result['hourly']['skycon'][$key]['value']),
+                    'time' => date("Y-m-d H:i", strtotime($value['datetime'])),
+                    'tmp' => (string)intval($value['value']),
+                ];
+                $weather['hourly'][] = $temp;
+            }
+        }
+        $weather['lifestyle'] = [
+            [
+                'type' => 'flu',
+                'brf' => $result['daily']['life_index']['coldRisk'][0]['desc'],
+                'txt' => ''
+            ], [
+                'type' => 'cw',
+                'brf' => $result['daily']['life_index']['carWashing'][0]['desc'],
+                'txt' => ''
+            ], [
+                'type' => 'uv',
+                'brf' => $result['daily']['life_index']['ultraviolet'][0]['desc'],
+                'txt' => ''
+            ], [
+                'type' => 'comf',
+                'brf' => $result['daily']['life_index']['comfort'][0]['desc'],
+                'txt' => ''
+            ], [
+                'type' => 'drsg',
+                'brf' => $result['daily']['life_index']['dressing'][0]['desc'],
+                'txt' => ''
+            ],
+
+        ];
+
+        $return['Weather'] = $weather;
+        $return['source'] = "caiyun";
+        return $return;
+    }
+
+    /**
+     * @param $code
+     * @return array|mixed|string
+     */
+    private function getCoodinate($code)
+    {
+        $key = "coodinateData";
+        $coodinate = Redis::instance()->hGet($key, $code);
+//        unset($coodinate);
+        if (!$coodinate['lo'] || !$coodinate['la']) {
+            $coodinate = CityToCoordinate::getData($code);
+            if (!empty($coodinate)) {
+//                $coodinate = $coodinate['lo'] . "," . $coodinate['la'];
+                Redis::instance()->hSet($key, $code, $coodinate);
+            }
+        }
+        $this->location = $coodinate['location'];
+        return $coodinate['lo'] . "," . $coodinate['la'];
+
+    }
+
+    /**
+     * 获取风向
+     * @param $degree
+     * @return mixed
+     */
+    private function getWindDirect($degree)
+    {
+        $directArr = ["北风", "东北风", "东北风", "东北风", "东风", "东南风", "东南风", "东南风", "南风", "西南风", "西南风", "西南风", "西风", "西北风", "西北风", "西北风"];
+        $index = 0;
+        if (348.75 <= $degree && $degree <= 360) {
+            $index = 0;
+        } else {
+            if (0 <= $degree && $degree <= 11.25) {
+                $index = 0;
+            } else if (11.25 < $degree && $degree <= 33.75) {
+                $index = 1;
+            } else if (33.75 < $degree && $degree <= 56.25) {
+                $index = 2;
+            } else if (56.25 < $degree && $degree <= 78.75) {
+                $index = 3;
+            } else if (78.75 < $degree && $degree <= 101.25) {
+                $index = 4;
+            } else if (101.25 < $degree && $degree <= 123.75) {
+                $index = 5;
+            } else if (123.75 < $degree && $degree <= 146.25) {
+                $index = 6;
+            } else if (146.25 < $degree && $degree <= 168.75) {
+                $index = 7;
+            } else if (168.75 < $degree && $degree <= 191.25) {
+                $index = 8;
+            } else if (191.25 < $degree && $degree <= 213.75) {
+                $index = 9;
+            } else if (213.75 < $degree && $degree <= 236.25) {
+                $index = 10;
+            } else if (236.25 < $degree && $degree <= 258.75) {
+                $index = 11;
+            } else if (258.75 < $degree && $degree <= 281.25) {
+                $index = 12;
+            } else if (281.25 < $degree && $degree <= 303.75) {
+                $index = 13;
+            } else if (303.75 < $degree && $degree <= 326.25) {
+                $index = 14;
+            } else if (326.25 < $degree && $degree < 348.75) {
+                $index = 15;
+            }
+        }
+        return $directArr[$index];
+    }
+
+    private function getWindSpeed($speed)
+    {
+        $wind = [
+            'name' => '无风',
+            'level' => 0
+        ];
+
+        if ($speed > 1 && $speed <= 5) {
+            $wind = [
+                'name' => '软风',
+                'level' => 1
+            ];
+        }
+        if ($speed > 5 && $speed <= 11) {
+            $wind = [
+                'name' => '轻风',
+                'level' => 2
+            ];
+        }
+        if ($speed > 11 && $speed <= 19) {
+            $wind = [
+                'name' => '微风',
+                'level' => 3
+            ];
+        }
+        if ($speed > 19 && $speed <= 28) {
+            $wind = [
+                'name' => '和风',
+                'level' => 4
+            ];
+        }
+        if ($speed > 28 && $speed <= 38) {
+            $wind = [
+                'name' => '清风',
+                'level' => 5
+            ];
+        }
+        if ($speed > 38 && $speed <= 49) {
+            $wind = [
+                'name' => '强风',
+                'level' => 6
+            ];
+        }
+        if ($speed > 49 && $speed <= 61) {
+            $wind = [
+                'name' => '劲风',
+                'level' => 7
+            ];
+        }
+        if ($speed > 61 && $speed <= 74) {
+            $wind = [
+                'name' => '大风',
+                'level' => 8
+            ];
+        }
+        if ($speed > 74 && $speed <= 88) {
+            $wind = [
+                'name' => '烈风',
+                'level' => 9
+            ];
+        }
+        if ($speed > 88 && $speed <= 102) {
+            $wind = [
+                'name' => '狂风',
+                'level' => 10
+            ];
+        }
+        if ($speed > 102 && $speed <= 117) {
+            $wind = [
+                'name' => '暴风',
+                'level' => 11
+            ];
+        }
+        if ($speed > 117 && $speed <= 134) {
+            $wind = [
+                'name' => '台风',
+                'level' => 12
+            ];
+        }
+        if ($speed > 134 && $speed <= 149) {
+            $wind = [
+                'name' => '台风',
+                'level' => 13
+            ];
+        }
+        if ($speed > 149 && $speed <= 166) {
+            $wind = [
+                'name' => '台风',
+                'level' => 14
+            ];
+        }
+        if ($speed > 166 && $speed <= 183) {
+            $wind = [
+                'name' => '台风',
+                'level' => 15
+            ];
+        }
+        if ($speed > 183 && $speed <= 201) {
+            $wind = [
+                'name' => '台风',
+                'level' => 16
+            ];
+        }
+        if ($speed > 201 && $speed <= 220) {
+            $wind = [
+                'name' => '台风',
+                'level' => 17
+            ];
+        }
+        if ($speed > 220) {
+            $wind = [
+                'name' => '台风',
+                'level' => 18
+            ];
+        }
+        return $wind;
+    }
+
+    private function getSkycon($skycon)
+    {
+        $arr = [
+            'CLEAR_DAY' => '晴',
+            'CLEAR_NIGHT' => '晴',
+            'PARTLY_CLOUDY_DAY' => '多云',
+            'PARTLY_CLOUDY_NIGHT' => '多云',
+            'CLOUDY' => '阴',
+            'LIGHT_HAZE' => '轻度雾霾',
+            'MODERATE_HAZE' => '中度雾霾',
+            'HEAVY_HAZE' => '重度雾霾',
+            'LIGHT_RAIN' => '小雨',
+            'MODERATE_RAIN' => '中雨',
+            'HEAVY_RAIN' => '大雨',
+            'STORM_RAIN' => '暴雨',
+            'FOG' => '雾',
+            'LIGHT_SNOW' => '小雪',
+            'MODERATE_SNOW' => '中雪',
+            'HEAVY_SNOW' => '大雪',
+            'STORM_SNOW' => '暴雪',
+            'DUST' => '浮尘',
+            'SAND' => '沙尘',
+            'WIND' => '大风',
+            'SLEET' => '雨夹雪',
+            'THUNDER_SHOWER' => '雷阵雨',
+            'HAIL' => '冰雹',
+        ];
+        return $arr[$skycon] ?? "";
+    }
+
+    private function getAlert($code, $type = 1)
+    {
+        $alert_type = [
+            '01' => '台风',
+            '02' => '暴雨',
+            '03' => '暴雪',
+            '04' => '寒潮',
+            '05' => '⼤风',
+            '06' => '沙尘暴',
+            '07' => '高温',
+            '08' => '干旱',
+            '09' => '雷电',
+            '10' => '冰雹',
+            '11' => '霜冻',
+            '12' => '大雾',
+            '13' => '霾',
+            '14' => '道路结冰',
+            '15' => '森林火灾',
+            '16' => '雷雨大风',
+        ];
+        $alert_level = [
+            '01' => '蓝色',
+            '02' => '黄色',
+            '03' => '橙色',
+            '04' => '红色',
+
+        ];
+        $code_1 = substr($code, 0, 2);
+        $code_2 = substr($code, 2, 2);
+        if ($type == 1) {
+            return $code ? $alert_type[$code_1] . $alert_level[$code_2] . '预警' : "";
+        } else {
+            return $alert_level[$code_2] ?? "";
+        }
+    }
+}

+ 153 - 0
Library/WeatherAdapt/heweatherAdapter.php

@@ -0,0 +1,153 @@
+<?php
+
+
+namespace Heanup\Library\WeatherAdapt;
+
+
+use Heanup\App\Api\Config\GlobalConfig;
+use Heanup\Library\Curl;
+use Heanup\Library\Redis;
+use Heanup\Library\Warming;
+
+class heweatherAdapter implements Weather
+{
+    private $city_id;
+
+
+    /**
+     * @param $city_id
+     * @return mixed
+     */
+    public function getWeatherData($city_id)
+    {
+        $this->city_id = $city_id;
+        $city_id = is_numeric($city_id) ? ("CN" . $city_id) : $city_id;
+        $key = $this->getKey();
+        if ($key) {
+            $url = sprintf("https://api.heweather.net/s6/weather?location=%s&key=%s", $city_id, $key);
+        }
+        $data = Curl::get($url);
+        $origin = $data = json_decode($data, true);
+        $data = $this->parseData($data);
+
+        $i = 0;
+        while ($data['Weather']['now']['cond_txt'] == "" && $i < 2) {//数据有误,重试3次
+            $log_file=APP_DIR . "/logs/" . date("Ymd") . "_null.log";
+            file_put_contents($log_file, sprintf("%s\t\tcity_id:%s", date("Y-m-d H:i:s"), $city_id) . "\t\tdata:" . json_encode($origin) . "\r\n", FILE_APPEND);
+            Warming::semdMail("no_data", "api.heweather.net", ALERT_TOTAL,$log_file);
+            $data = Curl::get($url);
+            $origin = $data = json_decode($data, true);
+            $data = $this->parseData($data);
+            $i++;
+        }
+
+        return $data;
+    }
+
+
+    private function getKey()
+    {
+        //2019/07/11    接口切换为收费接口,不再限制使用次数
+//        $day = "keys_use_" . date("Ymd");
+//        $count = Redis::instance()->incr($day);
+
+        $keys = GlobalConfig::getData("keys");
+//        if ($count / 16000 > count($keys)) {
+//            //报警
+//            Warming::semdMail("key_usage", "free-api.heweather.net", 1);
+//        }
+//        $key_count = intval($count / 16000);
+        $key = date("Ymd_") . $keys[0];
+        Redis::instance()->incr($key);
+//        $keys = [
+//            "_1" => '2b33a509d3134c2a8844e3ce1472d3bd',
+//            "_2" => 'f576a47c99aa4746b7145c4bc3138316',
+//            "_3" => '420475fd0e77489c843ef389b733bb48',
+//            "_4" => 'ce36b0498e444bc98c5bed07180828b3',
+//        ];
+        return $keys[0];
+
+    }
+
+    /**
+     *
+     * @param $data
+     * @param null $data2
+     * @return array
+     */
+    public function parseData($data, $data2 = null)
+    {
+        $originData = $data['HeWeather6'][0];
+        $return = [];
+        $weather = [];
+        $weather['basic'] = [
+            'location' => $originData['basic']['location'] ?: "",
+            'city_code' => $this->city_id,
+            'update' => $originData['update']['loc'] ?: "",
+        ];
+        $weather['status'] = $originData['status'] ?: "ok";
+//        $weather['key'] = $k;
+        $weather['air_now_city'] = [
+            "aqi" => "",  //                                                       ->a3
+            "qlty" => "",      //                                            ->a4
+            "pm25" => "", //                                                        ->a5
+            "pm10" => "",    //                                                     ->a6
+            "no2" => "",       //                                                   ->a7
+            "so2" => ""
+        ];
+        $weather['alarm'] = [
+            "level" => "",                              //                        ->a9
+            "link" => "",      //                       ->b1
+            "type" => ""                                    //                    ->b2
+        ];
+        $weather['now'] = [
+            "cond_txt" => $originData['now']['cond_txt'] ?: "", //                                                    ->b3
+            "fl" => $originData['now']['fl'] ?: "",//                                                           ->b4
+            "hum" => $originData['now']['hum'] ?: "",                 //                                         ->b5
+            "pres" => $originData['now']['pres'] ?: "",               //                                        ->b6
+            "tmp" => $originData['now']['tmp'] ?: "",                 //                                         ->b7
+            "vis" => $originData['now']['vis'] ?: "",                 //                                        ->b8
+            "wind_dir" => $originData['now']['wind_dir'] ?: "",            //                                       ->b9
+            "wind_sc" => $originData['now']['wind_sc'] ?: "",               //                                        ->c1
+            "sr" => $originData['daily_forecast'][0]['sr'] ?: "",                //                                        ->c2
+            "ss" => $originData['daily_forecast'][0]['ss'] ?: "",                 //                                        ->c3
+        ];
+        if (!empty($originData['daily_forecast'])) {
+            foreach ($originData['daily_forecast'] as $key => $value) {
+                $temp = [
+                    "cond_txt_d" => $value['cond_txt_d'] ?: "",
+                    "cond_txt_n" => $value['cond_txt_n'] ?: "",
+                    "date" => $value['date'] ?: "",
+                    "tmp_max" => $value['tmp_max'] ?: "",
+                    "tmp_min" => $value['tmp_min'] ?: "",
+                    "wind_dir" => $value['wind_dir'] ?: "",
+                    "wind_sc" => $value['wind_sc'] ?: "",
+                ];
+                $weather['daily_forecast'][] = $temp;
+            }
+        }
+        if (!empty($originData['hourly'])) {
+            foreach ($originData['hourly'] as $key => $value) {
+                $temp = [
+                    'cond_txt' => $value['cond_txt'] ?: "",
+                    'time' => $value['time'] ?: "",
+                    'tmp' => $value['tmp'] ?: "",
+                ];
+                $weather['hourly'][] = $temp;
+            }
+        }
+        if (!empty($originData['lifestyle'])) {
+            foreach ($originData['lifestyle'] as $key => $value) {
+                $temp = [
+                    'type' => $value['type'] ?: "",
+                    'brf' => $value['brf'] ?: "",
+                    'txt' => $value['txt'] ?: "",
+                ];
+                $weather['lifestyle'][] = $temp;
+            }
+        }
+        $return['Weather'] = $weather;
+        $return['source'] = "heweather.net";
+        return $return;
+    }
+}

+ 509 - 0
Library/WeatherAdapt/mixedAdapter.php

@@ -0,0 +1,509 @@
+<?php
+
+
+namespace Heanup\Library\WeatherAdapt;
+
+
+use Heanup\App\Api\Config\CityToCoordinate;
+use Heanup\App\Api\Config\GlobalConfig;
+use Heanup\Library\Curl;
+use Heanup\Library\Redis;
+use Heanup\Library\Warming;
+
+/**
+ * Class mixedAdapter 混合接口(彩云+天气网)
+ * @package Heanup\Library\WeatherAdapt
+ */
+class mixedAdapter implements Weather
+{
+    private $city_id;
+
+    private $location;
+
+    private $weather_code = [
+        0 => '晴',
+        1 => '多云',
+        2 => '阴',
+        3 => '阵雨',
+        4 => '雷阵雨',
+        5 => '雷阵雨伴有冰雹',
+        6 => '雨夹雪',
+        7 => '小雨',
+        8 => '中雨',
+        9 => '大雨',
+        10 => '暴雨',
+        11 => '大暴雨',
+        12 => '特大暴雨',
+        13 => '阵雪',
+        14 => '小雪',
+        15 => '中雪',
+        16 => '大雪',
+        17 => '暴雪',
+        18 => '雾',
+        19 => '冻雨',
+        20 => '沙尘暴',
+        21 => '小到中雨',
+        22 => '中到大雨',
+        23 => '大到暴雨',
+        24 => '暴雨到大暴雨',
+        25 => '大暴雨到特大暴雨',
+        26 => '小到中雪',
+        27 => '中到大雪',
+        28 => '大到暴雪',
+        29 => '浮尘',
+        30 => '扬沙',
+        31 => '强沙尘暴',
+        53 => '霾',
+        99 => '无',
+        32 => '浓雾',
+        49 => '强浓雾',
+        54 => '中度霾',
+        55 => '重度霾',
+        56 => '严重霾',
+        57 => '大雾',
+        58 => '特强浓雾',
+        301 => '雨',
+        302 => '雪',
+    ];
+
+
+    /**
+     * @param $city_id
+     * @return mixed
+     */
+    public function getWeatherData($city_id)
+    {
+        $this->city_id = $city_id;
+//        $city_id = is_numeric($city_id) ? ("CN" . $city_id) : $city_id;
+        $key = $this->getKey();
+        $caiyun_version = GlobalConfig::getData("caiyun_version");
+        $url = sprintf("https://api.caiyunapp.com/v%s/%s/%s/weather_2.json?alert=true&dailysteps=15", $caiyun_version, $key, $this->getCoodinate($city_id));
+        $data1 = Curl::get($url);
+        $data1 = json_decode($data1, true);
+        $data2=Redis::instance()->get("weather:$city_id");
+        if (!$data2){
+            $url = sprintf("https://apip.weatherdt.com/plugin/data?key=FKh9Y6PdLP&location=%s", $city_id);
+            $data2 = Curl::get($url);
+            $data2 = json_decode($data2, true);
+            Redis::instance()->set("weather:$city_id", $data2,7200);
+        }
+
+
+        $data = $this->parseData($data1, $data2);
+        $i = 0;
+        while ($data['Weather']['now']['cond_txt'] == "" && $i < 2) {//数据有误,重试3次
+            $log_file = APP_DIR . "/logs/" . date("Ymd") . "_null.log";
+            file_put_contents($log_file, sprintf("%s\t\tcity_id:%s", date("Y-m-d H:i:s"), $city_id) . "\t\tdata1:" . json_encode($data1) . "\t\tdata2:".json_encode($data2)."\r\n", FILE_APPEND);
+            Warming::semdMail("no_data", "混合接口", ALERT_TOTAL, $log_file);
+            $url = sprintf("https://api.caiyunapp.com/v%s/%s/%s/weather_2.json?alert=true&dailysteps=15", $caiyun_version, $key, $this->getCoodinate($city_id));
+            $data1 = Curl::get($url);
+            $url = sprintf("https://apip.weatherdt.com/plugin/data?key=FKh9Y6PdLP&location=%s", $city_id);
+            $data2 = Curl::get($url);
+            $data1 = json_decode($data1, true);
+            $data2 = json_decode($data2, true);
+            Redis::instance()->set("weather:$city_id", $data2,7200);
+            $data = $this->parseData($data1, $data2);
+            $i++;
+        }
+
+        return $data;
+    }
+
+
+    private function getKey()
+    {
+        $keys = GlobalConfig::getData("caiyun_key");
+        $key = date("Ymd_") . $keys;
+        Redis::instance()->incr($key);
+        return $keys;
+
+    }
+
+    /**
+     *
+     * @param $data
+     * @param null $data2
+     * @return array
+     */
+    public function parseData($data, $data2 = null)
+    {
+        $originData = $data;
+
+        $return = [];
+        $weather = [];
+        $weather['basic'] = [
+            'location' => $this->location,
+            'city_code' => $this->city_id,
+            'update' => date("Y-m-d H:i", $data['server_time'])
+        ];
+        $weather['status'] = $originData['status'] ?: "ok";
+        $result = $originData['result'];
+//        $weather['key'] = $k;
+        $weather['air_now_city'] = [
+            "aqi" => (string)$result['realtime']['air_quality']['aqi']['chn'] ?? "",  //                                                       ->a3
+            "qlty" => (string)$result['realtime']['air_quality']['description']['chn'] ?? "",      //                                            ->a4
+            "pm25" => (string)$result['realtime']['air_quality']['pm25'] ?? "", //                                                        ->a5
+            "pm10" => (string)$result['realtime']['air_quality']['pm10'] ?? "",    //                                                     ->a6
+            "no2" => (string)$result['realtime']['air_quality']['no2'] ?? "",       //                                                   ->a7
+            "so2" => (string)$result['realtime']['air_quality']['so2'] ?? ""
+        ];
+        $weather['alarm'] = [
+            "level" => "",                              //                        ->a9
+            "link" => $this->getAlert($result['alert']['content'][0]['code']),      //                       ->b1
+            "type" => "",                                    //                    ->b2
+            'alertId' => (string)$result['alert']['content'][0]['alertId'] ?? "",
+            "title" => (string)$result['alert']['content'][0]['title'] ?? "",//                  ->z2
+            "description" => (string)($result['alert']['content'][0]['description'] ? $result['alert']['content'][0]['description'] . sprintf("(来源:%s)", $result['alert']['content'][0]['source']) : ""),   //                 ->z3
+            "pubtimestamp" => (string)($result['alert']['content'] ? date("Y-m-d H:i", $result['alert']['content'][0]['pubtimestamp']) : ""),  //                                       ->z4
+            "location" => (string)$result['alert']['content'][0]['location'] ?? "",   //                                        ->z5
+            "status" => (string)$result['alert']['content'][0]['status'] ?? ""      //                                              ->z6
+        ];
+        $weather['now'] = [
+            "cond_txt" => $this->getSkycon($result['realtime']['skycon']), //                                                    ->b3
+            "fl" => (string)(intval($result['realtime']['apparent_temperature']) ?: ""),//                                                           ->b4
+            "hum" => (string)intval($result['realtime']['humidity'] * 100),                 //                                         ->b5
+            "pres" => (string)intval($result['realtime']['pressure'] / 100),               //                                        ->b6
+            "tmp" => (string)(intval($result['realtime']['temperature']) ?: ""),                 //                                         ->b7
+            "vis" => (string)$result['realtime']['visibility'] ?: "",                 //                                        ->b8
+            "wind_dir" => (string)$this->getWindDirect($result['realtime']['wind']['direction']) ?: "",            //                                       ->b9
+            "wind_sc" => (string)$this->getWindSpeed($result['realtime']['wind']['speed'])['level'] ?: "",               //                                        ->c1
+            "sr" => (string)$result['daily']['astro'][0]['sunrise']['time'],                //                                        ->c2
+            "ss" => (string)$result['daily']['astro'][0]['sunset']['time'],                 //                                        ->c3
+            'forecast_keypoint' => (string)$result['forecast_keypoint'] ?? "",
+            'description' => (string)$result['hourly']['description'] ?? "",
+        ];
+        if (!empty($result['daily'])) {
+            if (!empty($data2['forecast'])) {
+                $i = 0;
+                $date = date("Y-m-d", strtotime($data2['forecast']['date']));
+                foreach ($data2['forecast']['forecast'] as $key => $value) {
+                    $temp = [
+                        "cond_txt_d" => $this->weather_code[intval($value['tqw_code_d'])] ?: ($value['tqw_code_d'] ?: ""),//$value['tqw_code_d'] ?: "",
+                        "cond_txt_n" => $this->weather_code[intval($value['tqw_code_n'])] ?: ($value['tqw_code_n'] ?: ""),//$value['tqw_code_n'] ?: "",
+                        "date" => date("Y-m-d", strtotime("+$i days", strtotime($date))),
+                        "tmp_max" => $value['tmp_max'] ?: "",
+                        "tmp_min" => $value['tmp_min'] ?: "",
+                        "wind_dir" => $value['wind_dir_d_txt'] ?: "",
+                        "wind_sc" => $value['wind_sc_d_txt'] ?: "",
+                    ];
+                    $i++;
+                    $weather['daily_forecast'][] = $temp;
+                }
+            }
+            $j = 0;
+            foreach ($result['daily']['skycon_08h_20h'] as $key => $value) {
+                if ($j < $i) {
+                    $j++;
+                    continue;
+                }
+                $temp = [
+                    "cond_txt_d" => $this->getSkycon($value['value']),
+                    "cond_txt_n" => $this->getSkycon($result['daily']['skycon_20h_32h'][$key]['value']),
+                    "date" => date("Y-m-d", strtotime($value['date'])),
+                    "tmp_max" => (string)intval($result['daily']['temperature'][$key]['max']),
+                    "tmp_min" => (string)intval($result['daily']['temperature'][$key]['min']),
+                    "wind_dir" => (string)$this->getWindDirect($result['daily']['wind'][$key]['avg']['direction']),
+                    "wind_sc" => (string)$this->getWindSpeed($result['daily']['wind'][$key]['avg']['speed'])['level'],
+                ];
+                $weather['daily_forecast'][] = $temp;
+            }
+        }
+        if (!empty($result['hourly'])) {
+            $i = 0;
+            foreach ($result['hourly']['temperature'] as $key => $value) {
+                if ($i >= 24) {
+                    break;
+                }
+                $i++;
+                $temp = [
+                    'cond_txt' => $this->getSkycon($result['hourly']['skycon'][$key]['value']),
+                    'time' => date("Y-m-d H:i", strtotime($value['datetime'])),
+                    'tmp' => (string)intval($value['value']),
+                ];
+                $weather['hourly'][] = $temp;
+            }
+        }
+        $weather['lifestyle'] = [
+            [
+                'type' => 'flu',
+                'brf' => $result['daily']['life_index']['coldRisk'][0]['desc'],
+                'txt' => ''
+            ], [
+                'type' => 'cw',
+                'brf' => $result['daily']['life_index']['carWashing'][0]['desc'],
+                'txt' => ''
+            ], [
+                'type' => 'uv',
+                'brf' => $result['daily']['life_index']['ultraviolet'][0]['desc'],
+                'txt' => ''
+            ], [
+                'type' => 'comf',
+                'brf' => $result['daily']['life_index']['comfort'][0]['desc'],
+                'txt' => ''
+            ], [
+                'type' => 'drsg',
+                'brf' => $result['daily']['life_index']['dressing'][0]['desc'],
+                'txt' => ''
+            ],
+
+        ];
+
+        $return['Weather'] = $weather;
+        $return['source'] = "caiyun";
+        return $return;
+    }
+
+    /**
+     * @param $code
+     * @return array|mixed|string
+     */
+    private function getCoodinate($code)
+    {
+        $key = "coodinateData";
+        $coodinate = Redis::instance()->hGet($key, $code);
+//        unset($coodinate);
+        if (!$coodinate['lo'] || !$coodinate['la']) {
+            $coodinate = CityToCoordinate::getData($code);
+            if (!empty($coodinate)) {
+//                $coodinate = $coodinate['lo'] . "," . $coodinate['la'];
+                Redis::instance()->hSet($key, $code, $coodinate);
+            }
+        }
+        $this->location = $coodinate['location'];
+        return $coodinate['lo'] . "," . $coodinate['la'];
+
+    }
+
+    /**
+     * 获取风向
+     * @param $degree
+     * @return mixed
+     */
+    private function getWindDirect($degree)
+    {
+        $directArr = ["北风", "东北风", "东北风", "东北风", "东风", "东南风", "东南风", "东南风", "南风", "西南风", "西南风", "西南风", "西风", "西北风", "西北风", "西北风"];
+        $index = 0;
+        if (348.75 <= $degree && $degree <= 360) {
+            $index = 0;
+        } else {
+            if (0 <= $degree && $degree <= 11.25) {
+                $index = 0;
+            } else if (11.25 < $degree && $degree <= 33.75) {
+                $index = 1;
+            } else if (33.75 < $degree && $degree <= 56.25) {
+                $index = 2;
+            } else if (56.25 < $degree && $degree <= 78.75) {
+                $index = 3;
+            } else if (78.75 < $degree && $degree <= 101.25) {
+                $index = 4;
+            } else if (101.25 < $degree && $degree <= 123.75) {
+                $index = 5;
+            } else if (123.75 < $degree && $degree <= 146.25) {
+                $index = 6;
+            } else if (146.25 < $degree && $degree <= 168.75) {
+                $index = 7;
+            } else if (168.75 < $degree && $degree <= 191.25) {
+                $index = 8;
+            } else if (191.25 < $degree && $degree <= 213.75) {
+                $index = 9;
+            } else if (213.75 < $degree && $degree <= 236.25) {
+                $index = 10;
+            } else if (236.25 < $degree && $degree <= 258.75) {
+                $index = 11;
+            } else if (258.75 < $degree && $degree <= 281.25) {
+                $index = 12;
+            } else if (281.25 < $degree && $degree <= 303.75) {
+                $index = 13;
+            } else if (303.75 < $degree && $degree <= 326.25) {
+                $index = 14;
+            } else if (326.25 < $degree && $degree < 348.75) {
+                $index = 15;
+            }
+        }
+        return $directArr[$index];
+    }
+
+    private function getWindSpeed($speed)
+    {
+        $wind = [
+            'name' => '无风',
+            'level' => 0
+        ];
+
+        if ($speed > 1 && $speed <= 5) {
+            $wind = [
+                'name' => '软风',
+                'level' => 1
+            ];
+        }
+        if ($speed > 5 && $speed <= 11) {
+            $wind = [
+                'name' => '轻风',
+                'level' => 2
+            ];
+        }
+        if ($speed > 11 && $speed <= 19) {
+            $wind = [
+                'name' => '微风',
+                'level' => 3
+            ];
+        }
+        if ($speed > 19 && $speed <= 28) {
+            $wind = [
+                'name' => '和风',
+                'level' => 4
+            ];
+        }
+        if ($speed > 28 && $speed <= 38) {
+            $wind = [
+                'name' => '清风',
+                'level' => 5
+            ];
+        }
+        if ($speed > 38 && $speed <= 49) {
+            $wind = [
+                'name' => '强风',
+                'level' => 6
+            ];
+        }
+        if ($speed > 49 && $speed <= 61) {
+            $wind = [
+                'name' => '劲风',
+                'level' => 7
+            ];
+        }
+        if ($speed > 61 && $speed <= 74) {
+            $wind = [
+                'name' => '大风',
+                'level' => 8
+            ];
+        }
+        if ($speed > 74 && $speed <= 88) {
+            $wind = [
+                'name' => '烈风',
+                'level' => 9
+            ];
+        }
+        if ($speed > 88 && $speed <= 102) {
+            $wind = [
+                'name' => '狂风',
+                'level' => 10
+            ];
+        }
+        if ($speed > 102 && $speed <= 117) {
+            $wind = [
+                'name' => '暴风',
+                'level' => 11
+            ];
+        }
+        if ($speed > 117 && $speed <= 134) {
+            $wind = [
+                'name' => '台风',
+                'level' => 12
+            ];
+        }
+        if ($speed > 134 && $speed <= 149) {
+            $wind = [
+                'name' => '台风',
+                'level' => 13
+            ];
+        }
+        if ($speed > 149 && $speed <= 166) {
+            $wind = [
+                'name' => '台风',
+                'level' => 14
+            ];
+        }
+        if ($speed > 166 && $speed <= 183) {
+            $wind = [
+                'name' => '台风',
+                'level' => 15
+            ];
+        }
+        if ($speed > 183 && $speed <= 201) {
+            $wind = [
+                'name' => '台风',
+                'level' => 16
+            ];
+        }
+        if ($speed > 201 && $speed <= 220) {
+            $wind = [
+                'name' => '台风',
+                'level' => 17
+            ];
+        }
+        if ($speed > 220) {
+            $wind = [
+                'name' => '台风',
+                'level' => 18
+            ];
+        }
+        return $wind;
+    }
+
+    private function getSkycon($skycon)
+    {
+        $arr = [
+            'CLEAR_DAY' => '晴',
+            'CLEAR_NIGHT' => '晴',
+            'PARTLY_CLOUDY_DAY' => '多云',
+            'PARTLY_CLOUDY_NIGHT' => '多云',
+            'CLOUDY' => '阴',
+            'LIGHT_HAZE' => '轻度雾霾',
+            'MODERATE_HAZE' => '中度雾霾',
+            'HEAVY_HAZE' => '重度雾霾',
+            'LIGHT_RAIN' => '小雨',
+            'MODERATE_RAIN' => '中雨',
+            'HEAVY_RAIN' => '大雨',
+            'STORM_RAIN' => '暴雨',
+            'FOG' => '雾',
+            'LIGHT_SNOW' => '小雪',
+            'MODERATE_SNOW' => '中雪',
+            'HEAVY_SNOW' => '大雪',
+            'STORM_SNOW' => '暴雪',
+            'DUST' => '浮尘',
+            'SAND' => '沙尘',
+            'WIND' => '大风',
+            'SLEET' => '雨夹雪',
+            'THUNDER_SHOWER' => '雷阵雨',
+            'HAIL' => '冰雹',
+        ];
+        return $arr[$skycon] ?? "";
+    }
+
+    private function getAlert($code, $type = 1)
+    {
+        $alert_type = [
+            '01' => '台风',
+            '02' => '暴雨',
+            '03' => '暴雪',
+            '04' => '寒潮',
+            '05' => '⼤风',
+            '06' => '沙尘暴',
+            '07' => '高温',
+            '08' => '干旱',
+            '09' => '雷电',
+            '10' => '冰雹',
+            '11' => '霜冻',
+            '12' => '大雾',
+            '13' => '霾',
+            '14' => '道路结冰',
+            '15' => '森林火灾',
+            '16' => '雷雨大风',
+        ];
+        $alert_level = [
+            '01' => '蓝色',
+            '02' => '黄色',
+            '03' => '橙色',
+            '04' => '红色',
+
+        ];
+        $code_1 = substr($code, 0, 2);
+        $code_2 = substr($code, 2, 2);
+        if ($type == 1) {
+            return $code ? $alert_type[$code_1] . $alert_level[$code_2] . '预警' : "";
+        } else {
+            return $alert_level[$code_2] ?? "";
+        }
+    }
+}

+ 246 - 0
Library/WeatherAdapt/spareAdapter.php

@@ -0,0 +1,246 @@
+<?php
+
+
+namespace Heanup\Library\WeatherAdapt;
+
+
+use Heanup\Library\Curl;
+use Heanup\Library\Warming;
+
+/**
+ * Class spareAdapter 备用接口
+ * @package Heanup\Library\WeatherAdapt
+ */
+class spareAdapter implements Weather
+{
+    private $city_id;
+    private $weather_code = [
+        0 => '晴',
+        1 => '多云',
+        2 => '阴',
+        3 => '阵雨',
+        4 => '雷阵雨',
+        5 => '雷阵雨伴有冰雹',
+        6 => '雨夹雪',
+        7 => '小雨',
+        8 => '中雨',
+        9 => '大雨',
+        10 => '暴雨',
+        11 => '大暴雨',
+        12 => '特大暴雨',
+        13 => '阵雪',
+        14 => '小雪',
+        15 => '中雪',
+        16 => '大雪',
+        17 => '暴雪',
+        18 => '雾',
+        19 => '冻雨',
+        20 => '沙尘暴',
+        21 => '小到中雨',
+        22 => '中到大雨',
+        23 => '大到暴雨',
+        24 => '暴雨到大暴雨',
+        25 => '大暴雨到特大暴雨',
+        26 => '小到中雪',
+        27 => '中到大雪',
+        28 => '大到暴雪',
+        29 => '浮尘',
+        30 => '扬沙',
+        31 => '强沙尘暴',
+        53 => '霾',
+        99 => '无',
+        32 => '浓雾',
+        49 => '强浓雾',
+        54 => '中度霾',
+        55 => '重度霾',
+        56 => '严重霾',
+        57 => '大雾',
+        58 => '特强浓雾',
+        301 => '雨',
+        302 => '雪',
+    ];
+
+
+    /**
+     * @param $city_id
+     * @return mixed
+     */
+    public function getWeatherData($city_id)
+    {
+        $this->city_id = $city_id;
+        $url = sprintf("http://aider.meizu.com/app/weather/listWeather?cityIds=%s", $city_id);
+        $data1 = Curl::get($url);
+        $url = sprintf("https://apip.weatherdt.com/plugin/data?key=j3fsWopid0&location=%s", $city_id);
+        $data2 = Curl::get($url);
+        $data1 = json_decode($data1, true);
+        $data2 = json_decode($data2, true);
+        $data = $this->parseData($data1, $data2);
+        $i = 0;
+        while ($data['Weather']['now']['cond_txt'] == "" && $i < 2) {//数据有误,重试3次
+            $log_file = APP_DIR . "/logs/" . date("Ymd") . "_null.log";
+            file_put_contents($log_file, sprintf("%s\t\tcity_id:%s", date("Y-m-d H:i:s"), $city_id) . "\t\tdata1:" . json_encode($data1) . "\t\tdata2:" . json_encode($data2) . "\r\n", FILE_APPEND);
+            Warming::semdMail("no_data", "spare", ALERT_TOTAL, $log_file);
+            $url = sprintf("http://aider.meizu.com/app/weather/listWeather?cityIds=%s", $city_id);
+            $data1 = Curl::get($url);
+            $url = sprintf("https://apip.weatherdt.com/plugin/data?key=j3fsWopid0&location=%s", $city_id);
+            $data2 = Curl::get($url);
+            $data1 = json_decode($data1, true);
+            $data2 = json_decode($data2, true);
+            $data = $this->parseData($data1, $data2);
+            $i++;
+        }
+        return $data;
+    }
+
+
+    /**
+     *
+     * <code>
+     * $data=[
+     * 'Weather' => [
+     * [
+     * 'basic' => [
+     * 'location' => '',
+     * 'update' => '',
+     * ],
+     * 'status' => 'ok',
+     * 'air_now_city' => '',
+     * 'alarm' => '',
+     * 'now' => '',
+     * 'daily_forecast' => [
+     * [
+     * "cond_txt_d" => "晴",
+     * "cond_txt_n" => "多云",
+     * "date" => "2019-06-11",
+     * "tmp_max" => "32",
+     * "tmp_min" => "20",
+     * "wind_dir" => "南风",
+     * "wind_sc" => "3-4"
+     * ]],
+     * 'hourly' => [
+     * [
+     * "cond_txt" => "晴",
+     * "time" => "2019-06-11 16:00",
+     * "tmp" => "30"
+     * ]
+     * ],
+     * 'lifestyle' => [
+     * [
+     * "type" => "comf",
+     * "brf" => "较不舒适",
+     * "txt" => "白天天气晴好,明媚的阳光在给您带来好心情的同时,也会使您感到有些热,不很舒适。"
+     * ]
+     * ],
+     * ]
+     * ]
+     * ];
+     * </code>
+     * @param $data1
+     * @param $data2
+     * @return array
+     */
+    public function parseData($data1, $data2 = null)
+    {
+        $return = [];
+        $weather = [];
+        $weather['basic'] = [
+            'location' => $data2['basic']['city'] ?: "",
+            'city_code' => $this->city_id,
+            'update' => $data2['basic']['updateTime'] ?: date("H:i"),
+        ];
+        $weather['status'] = $data1['status'] ?: "ok";
+//        $weather['key'] = $k;
+        $weather['air_now_city'] = [
+            "aqi" => $data1['value'][0]['pm25']['aqi'] ?: "",  //                                                       ->a3
+            "qlty" => $data1['value'][0]['pm25']['quality'] ?: "",      //                                            ->a4
+            "pm25" => $data1['value'][0]['pm25']['pm25'] ?: "", //                                                        ->a5
+            "pm10" => $data1['value'][0]['pm25']['pm10'] ?: "",    //                                                     ->a6
+            "no2" => $data1['value'][0]['pm25']['no2'] ?: "",       //                                                   ->a7
+            "so2" => $data1['value'][0]['pm25']['so2'] ?: ""
+        ];
+        if ($data1['value'][0]['alarms'][0]) {
+            $weather['alarm'] = [
+//            "level" => $data1['value'][0]['alarms'][0]['alarmTypeDesc'] ?: "",                              //                        ->a9
+//            "link" => $data1['value'][0]['alarms'][0]['alarmContent'] ?: "",      //                       ->b1
+//            "type" => $data1['value'][0]['alarms'][0]['publishTime'] ?: ""                                    //                    ->b2
+                "level" => "",                              //                        ->a9
+                "link" => $data1['value'][0]['alarms'][0]['alarmTypeDesc'] ? ($data1['value'][0]['alarms'][0]['alarmTypeDesc'] . "预警") : "",      //                       ->b1
+                "type" => "",                                    //                    ->b2
+                'alertId' => $data1['value'][0]['alarms'][0]['alarmId'] ?? "",
+                "title" => $data1['value'][0]['alarms'][0]['alarmDesc'] ?? "",//                  ->z2
+                "description" => (string)($data1['value'][0]['alarms'][0]['alarmContent'] ? $data1['value'][0]['alarms'][0]['alarmContent'] : ""),   //                 ->z3
+                "pubtimestamp" => (string)($data1['value'][0]['alarms'][0]['publishTime'] ? date("Y-m-d H:i", strtotime($data1['value'][0]['alarms'][0]['publishTime'])) : ""),  //                                       ->z4
+                "location" => "",   //                                        ->z5
+                "status" => "预警中"      //                                              ->z6
+            ];
+        } else {
+            $weather['alarm'] = [
+//            "level" => $data1['value'][0]['alarms'][0]['alarmTypeDesc'] ?: "",                              //                        ->a9
+//            "link" => $data1['value'][0]['alarms'][0]['alarmContent'] ?: "",      //                       ->b1
+//            "type" => $data1['value'][0]['alarms'][0]['publishTime'] ?: ""                                    //                    ->b2
+                "level" => "",                              //                        ->a9
+                "link" => "",      //                       ->b1
+                "type" => "",                                    //                    ->b2
+                'alertId' => "",
+                "title" => "",//                  ->z2
+                "description" => "",   //                 ->z3
+                "pubtimestamp" => "",  //                                       ->z4
+                "location" => "",   //                                        ->z5
+                "status" => ""      //                                              ->z6
+            ];
+        }
+
+        $weather['now'] = [
+            "cond_txt" => $data2['now']['txt'] ?: "", //                                                    ->b3
+            "fl" => $data1['value'][0]['realtime']['sendibleTemp'] ?: "",//                                                           ->b4
+            "hum" => $data2['now']['hum'] ?: "",                 //                                         ->b5
+            "pres" => $data2['now']['pres'] ?: "",               //                                        ->b6
+            "tmp" => $data1['value'][0]['realtime']['temp'] ?: "",                 //                                         ->b7
+            "vis" => "",                 //                                        ->b8
+            "wind_dir" => $data2['now']['wind_dir_txt'] ?: "",            //                                       ->b9
+            "wind_sc" => $data2['now']['wind_sc'] ?: "",               //                                        ->c1
+            "sr" => $data1['value'][0]['weathers'][0]['sun_rise_time'] ?: "",                //                                        ->c2
+            "ss" => $data1['value'][0]['weathers'][0]['sun_down_time'] ?: "",                 //                                        ->c3
+        ];
+        if (!empty($data2['forecast'])) {
+            $i = 0;
+            $date = date("Y-m-d", strtotime($data2['forecast']['date']));
+            foreach ($data2['forecast']['forecast'] as $key => $value) {
+                $temp = [
+                    "cond_txt_d" => $this->weather_code[intval($value['tqw_code_d'])] ?: ($value['tqw_code_d'] ?: ""),//$value['tqw_code_d'] ?: "",
+                    "cond_txt_n" => $this->weather_code[intval($value['tqw_code_n'])] ?: ($value['tqw_code_n'] ?: ""),//$value['tqw_code_n'] ?: "",
+                    "date" => date("Y-m-d", strtotime("+$i days", strtotime($date))),
+                    "tmp_max" => $value['tmp_max'] ?: "",
+                    "tmp_min" => $value['tmp_min'] ?: "",
+                    "wind_dir" => $value['wind_dir_d_txt'] ?: "",
+                    "wind_sc" => $value['wind_sc_d_txt'] ?: "",
+                ];
+                $i++;
+                $weather['daily_forecast'][] = $temp;
+            }
+        }
+        if (!empty($data2['hourly'])) {
+            foreach ($data2['hourly'] as $key => $value) {
+                $temp = [
+                    'cond_txt' => $this->weather_code[intval($value['tqw_code'])] ?: ($value['tqw_code'] ?: ""),
+                    'time' => date("Y-m-d H:00", strtotime($value['time'])),
+                    'tmp' => $value['tmp'] ?: "",
+                ];
+                $weather['hourly'][] = $temp;
+            }
+        }
+        if (!empty($data1['value'][0]['indexes'])) {
+            foreach ($data1['value'][0]['indexes'] as $key => $value) {
+                $temp = [
+                    'type' => $value['name'] ?: "",
+                    'brf' => $value['level'] ?: "",
+                    'txt' => $value['content'] ?: "",
+                ];
+                $weather['lifestyle'][] = $temp;
+            }
+        }
+        $return['Weather'] = $weather;
+        $return['source'] = "spare";
+        return $return;
+    }
+}

+ 24 - 0
Library/WeatherAdapt/tianqiapiAdapter.php

@@ -0,0 +1,24 @@
+<?php
+
+
+namespace Heanup\Library\WeatherAdapt;
+
+
+class tianqiapiAdapter implements Weather
+{
+
+    public function __construct()
+    {
+    }
+
+    public function getWeatherData($city_id)
+    {
+        $data=file_get_contents(sprintf("https://www.tianqiapi.com/api/?version=v1&cityid=%s", $city_id));
+        return json_decode($data,true);
+    }
+
+    public function parseData($data1, $data2)
+    {
+        // TODO: 需要实现 parseData() 方法.
+    }
+}

+ 24 - 0
Library/WeatherAdapt/weatherdtAdapter.php

@@ -0,0 +1,24 @@
+<?php
+
+
+namespace Heanup\Library\WeatherAdapt;
+
+
+class weatherdtAdapter implements Weather
+{
+
+    public function __construct()
+    {
+    }
+
+    public function getWeatherData($city_id)
+    {
+        $data = file_get_contents(sprintf("https://apip.weatherdt.com/plugin/data?key=j3fsWopid0&location=%s", $city_id));
+        return json_decode($data, true);
+    }
+
+    public function parseData($data1,$data2)
+    {
+        // TODO: 需要实现 parseData() 方法.
+    }
+}

+ 36 - 0
Library/WeatherLib.php

@@ -0,0 +1,36 @@
+<?php
+
+namespace Heanup\Library;
+
+
+
+use Heanup\Library\WeatherAdapt\Weather;
+
+class WeatherLib
+{
+    private static $instance;
+
+    public function __construct()
+    {
+
+    }
+
+    /**
+     * @param $adapter
+     * @return Weather
+     */
+    public static function getInstance($adapter)
+    {
+        if (!isset(self::$instance[md5($adapter)])) {
+            try{
+                $class = "Heanup\\Library\\WeatherAdapt\\".$adapter . "Adapter";
+                $instance = new $class();
+                self::$instance[md5($adapter)] = $instance;
+            }catch (\Throwable $exception){
+                var_dump($exception);
+            }
+
+        }
+        return self::$instance[md5($adapter)];
+    }
+}

+ 701 - 0
Library/WechatLib.php

@@ -0,0 +1,701 @@
+<?php
+
+
+namespace Heanup\Library;
+
+
+use Heanup\Library\WeatherAdapt\Weather;
+
+/**
+ * Class WechatLib
+ *
+ * @package Heanup\Library
+ */
+class WechatLib
+{
+    private static $instance;
+    /**
+     * @var int 事件类型<br><br> 100=> 私聊消息<br>200=> 群聊消息<br>300=> 暂无<br>400=> 群成员增加<br>410=> 群成员减少<br>500=> 收到好友请求<br>600=> 二维码收款<br>700=> 收到转账<br>800=> 软件开始启动<br>900=> 新的账号登录完成<br>910=> 账号下线
+     */
+    public $type;
+    /**
+     * @var string 1级来源id(群消息事件下,1级来源为群id,2级来源为发消息的成员id,私聊事件下都一样)
+     */
+    public $from_wxid;
+    /**
+     * @var string 1级来源昵称(比如发消息的人昵称)<br>
+     */
+    public $from_name;
+    /**
+     * @var string 2级来源id(群消息事件下,1级来源为群id,2级来源为发消息的成员id,私聊事件下都一样)
+     */
+    public $final_from_wxid;
+    /**
+     * @var string 2级来源昵称
+     */
+    public $final_from_name;
+    /**
+     * @var string 当前登录的账号(机器人)标识id
+     */
+    public $robot_wxid;
+    /**
+     * @var string 消息内容
+     */
+    public $msg;
+    /**
+     * @var int 消息类型(请务必使用新版http插件)<br><br> 1 =>文本消息 <br>3 => 图片消息 <br>34 => 语音消息 <br>42 => 名片消息 <br>43 =>视频 <br>47 => 动态表情 <br> 48 =>地理位置<br>49 => 分享链接 <br>2001 => 红包<br>2002 => 小程序<br>2003 => 群邀请 <br><br>更多请参考sdk模块常量值
+     * )
+     */
+    public $msg_type;
+    /**
+     * @var string 如果是文件消息(图片、语音、视频、动态表情),这里则是可直接访问的网络地址,非文件消息时为空
+     */
+    public $file_url;
+    /**
+     * @var int 请求时间(时间戳10位版本)
+     */
+    public $time;
+    /**
+     * @var string 机器猫接口地址
+     */
+    public $url = "http://127.0.0.1:8073/send";
+
+    public function __construct($config = null)
+    {
+        if (!empty($config)) {//未配置,使用默认配置
+            $this->url = $config['url'];
+        }
+        $this->parseWechat($_POST);
+    }
+
+    /**
+     * 单例模式使用
+     * @param string|null $url 可爱猫API接口地址
+     * @return WechatLib
+     */
+    public static function getInstance($url = null)
+    {
+        if (!isset(self::$instance[md5($url)])) {
+            $instance = new self(['url' => $url]);
+            self::$instance[md5($url)] = $instance;
+        }
+        return self::$instance[md5($url)];
+    }
+
+    /**
+     * 解析可爱猫回调消息
+     * @param $data
+     */
+    public function parseWechat($data)
+    {
+        $this->type = $data['type'];
+        $this->from_wxid = $data['from_wxid'];
+        $this->from_name = urldecode($data['from_name']);
+        $this->final_from_wxid = $data['final_from_wxid'];
+        $this->final_from_name = urldecode($data['final_from_name']);
+        $this->robot_wxid = $data['robot_wxid'];
+        $this->msg = $data['type']<410?urldecode($data['msg']):$data['msg'];
+        $this->msg_type = intval($data['msg_type']);
+        $this->file_url = $data['file_url'];
+        $this->time = $data['time'];
+    }
+
+
+    /**
+     * 发送文字消息(好友或者群)
+     *
+     * @access public
+     * @param string $msg 消息内容
+     * @param string $robwxid 登录账号id,用哪个账号去发送这条消息,不传则使用当前回调的机器人ID
+     * @param string $to_wxid 对方的id,可以是群或者好友id,不传则表示回复当前消息
+     * @return array
+     */
+    public function sendTextMsg($msg, $robwxid = null, $to_wxid = null)
+    {
+        $data = array();
+        $data['type'] = 100;
+        $data['msg'] = urlencode($msg); // 发送内容
+        $data['to_wxid'] = $to_wxid ?: $this->from_wxid;     // 对方id
+        $data['robot_wxid'] = $robwxid ?: $this->robot_wxid;  // 账户id,用哪个账号去发送这条消息
+        $response = array('data' => json_encode($data));
+        return $this->sendRequest($response, 'post');
+    }
+
+
+    /**
+     * 发送群消息并艾特某人
+     *
+     * @access public
+     * @param string $robwxid 账户id,用哪个账号去发送这条消息
+     * @param string $group_wxid 群id
+     * @param string $at_wxid 艾特的id,群成员的id
+     * @param string $at_name 艾特的昵称,群成员的昵称
+     * @param string $msg 消息内容
+     * @return array
+     */
+    public function sendGroupAtMsg($robwxid, $group_wxid, $at_wxid, $at_name, $msg)
+    {
+        $data = array();
+        $data['type'] = 102;
+        $data['msg'] = urlencode($msg);
+        $data['to_wxid'] = $group_wxid;
+        $data['at_wxid'] = $at_wxid;
+        $data['at_name'] = $at_name;
+        $data['robot_wxid'] = $robwxid;
+        $response = array('data' => json_encode($data));
+        return $this->sendRequest($response, 'post');
+    }
+
+
+    /**
+     * 发送图片消息
+     *
+     * @access public
+     * @param string $img_url 图片的绝对路径,可以是本地图片地址/网络图片地址
+     * @param string $robwxid 登录账号id,用哪个账号去发送这条消息,不传则使用当前机器人ID
+     * @param string $to_wxid 对方的id,可以是群或者好友id,不传则表示回复当前消息
+     * @return array
+     */
+    public function sendImageMsg($img_url, $robwxid = null, $to_wxid = null)
+    {
+        $data = array();
+        $data['type'] = 103;
+        $data['msg'] = $img_url;
+        $data['to_wxid'] = $to_wxid ?: $this->from_wxid;
+        $data['robot_wxid'] = $robwxid ?: $this->robot_wxid;
+        $response = array('data' => json_encode($data));
+        return $this->sendRequest($response, 'post');
+    }
+
+
+    /**
+     * 发送视频消息
+     *
+     * @access public
+     * @param string $mp4_path 视频文件的绝对路径
+     * @param string $robwxid 账户id,用哪个账号去发送这条消息
+     * @param string $to_wxid 对方的id,可以是群或者好友id
+     * @return array
+     */
+    public function sendVideoMsg($mp4_path, $robwxid = null, $to_wxid = null)
+    {
+
+        $data = array();
+        $data['type'] = 104;
+        $data['msg'] = $mp4_path;
+        $data['to_wxid'] = $to_wxid ?: $this->from_wxid;
+        $data['robot_wxid'] = $robwxid ?: $this->robot_wxid;
+        $response = array('data' => json_encode($data));
+        return $this->sendRequest($response, 'post');
+    }
+
+
+    /**
+     * 发送文件消息
+     *
+     * @access public
+     * @param string $file_path 文件的绝对路径
+     * @param string $robwxid 账户id,用哪个账号去发送这条消息
+     * @param string $to_wxid 对方的id,可以是群或者好友id
+     * @return array
+     */
+    public function sendFileMsg($file_path, $robwxid = null, $to_wxid = null)
+    {
+
+        $data = array();
+        $data['type'] = 105;
+        $data['msg'] = $file_path;           // 发送的文件的绝对路径
+        $data['to_wxid'] = $to_wxid ?: $this->from_wxid;     // 对方id(默认发送至来源的id,也可以发给其他人)
+        $data['robot_wxid'] = $robwxid ?: $this->robot_wxid;  // 账户id,用哪个账号去发送这条消息
+        $response = array('data' => json_encode($data));
+        return $this->sendRequest($response, 'post');
+    }
+
+
+    /**
+     * 发送动态表情
+     *
+     * @access public
+     * @param string $path 动态表情文件(通常是gif)的绝对路径
+     * @param string $robwxid 账户id,用哪个账号去发送这条消息
+     * @param string $to_wxid 对方的id,可以是群或者好友id
+     * @return array
+     */
+    public function sendEmojiMsg($path, $robwxid = null, $to_wxid = null)
+    {
+
+        $data = array();
+        $data['type'] = 106;
+        $data['msg'] = $path;           // 发送的动态表情的绝对路径
+        $data['to_wxid'] = $to_wxid ?: $this->from_wxid;     // 对方id(默认发送至来源的id,也可以发给其他人)
+        $data['robot_wxid'] = $robwxid ?: $this->robot_wxid;  // 账户id,用哪个账号去发送这条消息
+        $response = array('data' => json_encode($data));
+        return $this->sendRequest($response, 'post');
+    }
+
+
+    /**
+     * 发送分享链接
+     *
+     * @access public
+     * @param string $title 链接标题
+     * @param string $text 链接内容
+     * @param string $target_url 跳转链接
+     * @param string $pic_url 图片链接
+     * @param string $robwxid 账户id,用哪个账号去发送这条消息
+     * @param string $to_wxid 对方的id,可以是群或者好友id
+     * @return array
+     */
+    public function sendLinkMsg($title, $text, $target_url, $pic_url, $robwxid = null, $to_wxid = null)
+    {
+
+        // 封装链接结构体
+        $link = array();
+        $link['title'] = $title;
+        $link['text'] = $text;
+        $link['url'] = $target_url;
+        $link['pic'] = $pic_url;
+
+
+        $data = array();
+        $data['type'] = 107;
+        $data['msg'] = $link;           // 发送的分享链接结构体
+        $data['to_wxid'] = $to_wxid ?: $this->from_wxid;
+        $data['robot_wxid'] = $robwxid ?: $this->robot_wxid;
+        $response = array('data' => json_encode($data));
+        return $this->sendRequest($response, 'post');
+    }
+
+
+    /**
+     * 发送音乐分享
+     *
+     * @access public
+     * @param string $name 歌曲名字
+     * @param string $robwxid 账户id,用哪个账号去发送这条消息
+     * @param string $to_wxid 对方的id,可以是群或者好友id
+     * @return array
+     */
+    public function sendMusicMsg($name, $robwxid = null, $to_wxid = null)
+    {
+        $data = array();
+        $data['type'] = 108;
+        $data['msg'] = $name;
+        $data['to_wxid'] = $to_wxid ?: $this->from_wxid;
+        $data['robot_wxid'] = $robwxid ?: $this->robot_wxid;
+        $response = array('data' => json_encode($data));
+        return $this->sendRequest($response, 'post');
+    }
+
+
+    /**
+     * 取指定登录账号的昵称
+     *
+     * @access public
+     * @param string $robwxid 账户id
+     * @return array 账号昵称
+     */
+    public function getRobotName($robwxid = null)
+    {
+
+        $data = array();
+        $data['type'] = 201;
+        $data['robot_wxid'] = $robwxid ?: $this->robot_wxid;
+        $response = array('data' => json_encode($data));
+        return $this->sendRequest($response, 'post');
+    }
+
+
+    /**
+     * 取指定登录账号的头像
+     *
+     * @access public
+     * @param string $robwxid 账户id
+     * @return array 头像http地址
+     */
+    public function getRobotHeadImageUrl($robwxid = null)
+    {
+
+        $data = array();
+        $data['type'] = 202;
+        $data['robot_wxid'] = $robwxid ?: $this->robot_wxid;  // 账户id
+        $response = array('data' => json_encode($data));
+        return $this->sendRequest($response, 'post');
+    }
+
+
+    /**
+     * 取登录账号列表
+     *
+     * @access public
+     * @return array 当前框架已登录的账号信息列表
+     */
+    public function getLoggedAccount_list()
+    {
+
+        $data = array();
+        $data['type'] = 203;
+        $response = array('data' => json_encode($data));
+        return $this->sendRequest($response, 'post');
+    }
+
+
+    /**
+     * 取好友列表
+     *
+     * @access public
+     * @param string $robwxid 账户id
+     * @param int $is_refresh 是否刷新
+     * @return array 当前框架已登录的账号信息列表
+     */
+    public function getFriendList($robwxid = '', $is_refresh = 0)
+    {
+
+        $data = array();
+        $data['type'] = 204;
+        $data['robot_wxid'] = $robwxid;     // 账户id(可选,如果填空字符串,即取所有登录账号的好友列表,反正取指定账号的列表)
+        $data['is_refresh'] = $is_refresh;  // 是否刷新列表,0 从缓存获取 / 1 刷新并获取
+        $response = array('data' => json_encode($data));
+        return $this->sendRequest($response, 'post');
+    }
+
+
+    /**
+     * 取群聊列表
+     *
+     * @access public
+     * @param string $robwxid 账户id
+     * @param int $is_refresh 是否刷新
+     * @return array 当前框架已登录的账号信息列表
+     */
+    public function getGroupList($robwxid = '', $is_refresh = 0)
+    {
+
+        $data = array();
+        $data['type'] = 205;
+        $data['robot_wxid'] = $robwxid;     // 账户id(可选,如果填空字符串,即取所有登录账号的好友列表,反正取指定账号的列表)
+        $data['is_refresh'] = $is_refresh;  // 是否刷新列表,0 从缓存获取 / 1 刷新并获取
+        $response = array('data' => json_encode($data));
+        return $this->sendRequest($response, 'post');
+    }
+
+
+    /**
+     * 取群成员列表
+     *
+     * @access public
+     * @param string $robwxid 账户id
+     * @param string $group_wxid 群id
+     * @param int $is_refresh 是否刷新
+     * @return array 当前框架已登录的账号信息列表
+     */
+    public function getGroupMemberList($robwxid, $group_wxid, $is_refresh = 0)
+    {
+
+        $data = array();
+        $data['type'] = 206;
+        $data['robot_wxid'] = $robwxid;     // 账户id
+        $data['group_wxid'] = $group_wxid;  // 群id
+        $data['is_refresh'] = $is_refresh;  // 是否刷新列表,0 从缓存获取 / 1 刷新并获取
+        $response = array('data' => json_encode($data));
+        $result = $this->sendRequest($response, 'post');
+        return json_decode($result, true);
+    }
+
+
+    /**
+     * 取群成员资料
+     *
+     * @access public
+     * @param string $robwxid 账户id
+     * @param string $group_wxid 群id
+     * @param string $member_wxid 群成员id
+     * @return array
+     */
+    public function getGroupMember($robwxid, $group_wxid, $member_wxid)
+    {
+
+        $data = array();
+        $data['type'] = 207;
+        $data['robot_wxid'] = $robwxid;       // 账户id,取哪个账号的资料
+        $data['group_wxid'] = $group_wxid;    // 群id
+        $data['member_wxid'] = $member_wxid;  // 群成员id
+        $response = array('data' => json_encode($data));
+        return $this->sendRequest($response, 'post');
+    }
+
+
+    /**
+     * 接收好友转账
+     *
+     * @access public
+     * @param string $robwxid 账户id
+     * @param string $friend_wxid 朋友id
+     * @param $json_string
+     * @return array
+     */
+    public function acceptTransfer($robwxid = null, $friend_wxid = null, $json_string = null)
+    {
+
+        $data = array();
+        $data['type'] = 301;
+        $data['robot_wxid'] = $robwxid ?: $this->robot_wxid;      // 账户id
+        $data['friend_wxid'] = $friend_wxid ?: $this->from_wxid;  // 朋友id
+        $data['msg'] = $json_string ?: $this->msg;         // 转账事件原消息
+        $response = array('data' => json_encode($data));
+        return $this->sendRequest($response, 'post');
+    }
+
+
+    /**
+     * 同意群聊邀请
+     *
+     * @access public
+     * @param string $robwxid 账户id
+     * @param string $json_string 同步消息事件中群聊邀请原消息
+     * @return array
+     */
+    public function agreeGroupInvite($robwxid = null, $json_string = null)
+    {
+
+        $data = array();
+        $data['type'] = 302;
+        $data['robot_wxid'] = $robwxid ?: $this->robot_wxid;      // 账户id
+        $data['msg'] = $json_string ?: $this->msg;         // 同步消息事件中群聊邀请原消息
+        $response = array('data' => json_encode($data));
+        return $this->sendRequest($response, 'post');
+    }
+
+
+    /**
+     * 同意好友请求
+     *
+     * @access public
+     * @param string $robwxid 账户id
+     * @param string $json_string 好友请求事件中原消息
+     * @return array
+     */
+    public function agreeFriendVerify($robwxid = null, $json_string = null)
+    {
+
+        $data = array();
+        $data['type'] = 303;
+        $data['robot_wxid'] = $robwxid ?: $this->robot_wxid;      // 账户id
+        $data['msg'] = $json_string ?: $this->msg;         // 好友请求事件中原消息
+        $response = array('data' => json_encode($data));
+        return $this->sendRequest($response, 'post');
+    }
+
+    /**
+     * 修改好友备注
+     *
+     * @access public
+     * @param string $robwxid 机器人账户id
+     * @param string $friend_wxid 好友id
+     * @param string $note 新备注(空字符串则是删除备注)
+     * @return array
+     */
+    public function setFriendNote($robwxid, $friend_wxid, $note)
+    {
+
+        $data = array();
+        $data['type'] = 304;
+        $data['robot_wxid'] = $robwxid;
+        $data['friend_wxid'] = $friend_wxid;
+        $data['note'] = $note;
+        $response = array('data' => json_encode($data));
+        return $this->sendRequest($response, 'post');
+    }
+
+
+    /**
+     * 删除好友
+     *
+     * @access public
+     * @param string $robwxid 机器人账户id
+     * @param string $friend_wxid 好友id
+     * @return array
+     */
+    public function deleteFriend($robwxid, $friend_wxid)
+    {
+
+        $data = array();
+        $data['type'] = 305;
+        $data['robot_wxid'] = $robwxid;
+        $data['friend_wxid'] = $friend_wxid;
+        $response = array('data' => json_encode($data));
+        return $this->sendRequest($response, 'post');
+    }
+
+
+    /**
+     * 踢出群成员
+     *
+     * @access public
+     * @param string $member_wxid 群成员id
+     * @param string $group_wxid 群id
+     * @param string $robwxid 账户id
+     * @return array|string
+     */
+    public function removeGroupMember($member_wxid, $group_wxid = null, $robwxid = null)
+    {
+
+        $data = array();
+        $data['type'] = 306;
+        $data['robot_wxid'] = $robwxid ?: $this->robot_wxid;
+        $data['group_wxid'] = $group_wxid ?: $this->from_wxid;
+        $data['member_wxid'] = $member_wxid;
+        $response = array('data' => json_encode($data));
+        return $this->sendRequest($response, 'post');
+    }
+
+
+    /**
+     * 修改群名称
+     *
+     * @access public
+     * @param string $robwxid 账户id
+     * @param string $group_wxid 群id
+     * @param string $group_name 新群名
+     * @return array
+     */
+    public function setGroupName($robwxid, $group_wxid, $group_name)
+    {
+        $data = array();
+        $data['type'] = 307;
+        $data['robot_wxid'] = $robwxid;      // 账户id
+        $data['group_wxid'] = $group_wxid;  // 群id
+        $data['group_name'] = $group_name;   // 新群名
+        $response = array('data' => json_encode($data));
+        return $this->sendRequest($response, 'post');
+    }
+
+    /**
+     * 修改群公告
+     *
+     * @access public
+     * @param string $robwxid 账户id
+     * @param string $group_wxid 群id
+     * @param string $notice 新公告
+     * @return array
+     */
+    public function setGroupNotice($robwxid, $group_wxid, $notice)
+    {
+
+        $data = array();
+        $data['type'] = 308;
+        $data['robot_wxid'] = $robwxid;      // 账户id
+        $data['group_wxid'] = $group_wxid;  // 群id
+        $data['notice'] = $notice;       // 新公告
+        $response = array('data' => json_encode($data));
+        return $this->sendRequest($response, 'post');
+    }
+
+
+    /**
+     * 建立新群
+     *
+     * @access public
+     * @param string $robwxid 账户id
+     * @param array $friends 三个人及以上的好友id数组,['wxid_1xxx', 'wxid_2xxx', 'wxid_3xxx', 'wxid_4xxx']
+     * @return array
+     */
+    public function creatGroup($robwxid, array $friends)
+    {
+        $data = array();
+        $data['type'] = 309;
+        $data['robot_wxid'] = $robwxid;  // 账户id
+        $data['friends'] = $friends;  // 好友id数组
+        $response = array('data' => json_encode($data));
+        return $this->sendRequest($response, 'post');
+    }
+
+
+    /**
+     * 退出群聊
+     *
+     * @access public
+     * @param string $robwxid 账户id
+     * @param string $group_wxid 群id
+     * @return array
+     */
+    public function quitGroup($robwxid, $group_wxid)
+    {
+        $data = array();
+        $data['type'] = 310;
+        $data['robot_wxid'] = $robwxid;    // 账户id
+        $data['group_wxid'] = $group_wxid; // 群id
+        $response = array('data' => json_encode($data));
+        return $this->sendRequest($response, 'post');
+    }
+
+    /**
+     * 邀请加入群聊
+     *
+     * @access public
+     * @param string $robwxid 账户id
+     * @param string $group_wxid 群id
+     * @param string $friend_wxid 好友id
+     * @return array
+     */
+    public function inviteInGroup($robwxid, $group_wxid, $friend_wxid)
+    {
+        $data = array();
+        $data['type'] = 311;
+        $data['robot_wxid'] = $robwxid;
+        $data['group_wxid'] = $group_wxid;
+        $data['friend_wxid'] = $friend_wxid;
+        $response = array('data' => json_encode($data));
+        return $this->sendRequest($response, 'post');
+    }
+
+
+    /**
+     * 执行一个 HTTP 请求,仅仅是post组件,其他语言请自行替换即可
+     *
+     * @param mixed $params 表单参数
+     * @param int $timeout 超时时间
+     * @param string $method 请求方法 post / get
+     * @return array|string
+     */
+    public function sendRequest($params, $method = 'get', $timeout = 3)
+    {
+        $curl = curl_init();
+        $is_https = stripos($this->url, 'https://') === 0 ? true : false;
+        if ('get' == $method) {//以GET方式发送请求
+            curl_setopt($curl, CURLOPT_URL, $this->url);
+        } else {//以POST方式发送请求
+            curl_setopt($curl, CURLOPT_URL, $this->url);
+            curl_setopt($curl, CURLOPT_POST, 1);//post提交方式
+            curl_setopt($curl, CURLOPT_POSTFIELDS, $params);//设置传送的参数
+        }
+
+        curl_setopt($curl, CURLOPT_HEADER, false);//设置header
+        curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);//要求结果为字符串且输出到屏幕上
+        curl_setopt($curl, CURLOPT_CONNECTTIMEOUT, $timeout);//设置等待时间
+        if ($is_https) {
+            curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, 0); // 对认证证书来源的检查
+            curl_setopt($curl, CURLOPT_SSL_VERIFYHOST, 1); // 从证书中检查SSL加密算法是否存在
+        }
+        $res = curl_exec($curl);//运行curl
+        $err = curl_error($curl);
+
+        if (false === $res || !empty($err)) {
+//            $Errno = curl_errno($curl);
+//            $Info = curl_getinfo($curl);
+//            curl_close($curl);
+            return false;//$err . ' result: ' . $res . 'error_msg: ' . $Errno;
+        }
+        curl_close($curl);//关闭curl
+        $res = json_decode($res, true);
+        $data = json_decode($res['data'], true);
+        if ($data) {
+            $res = $data;
+        } else {
+            $res = urldecode($res['data']);
+        }
+        return $res;
+    }
+
+
+}

+ 5 - 0
composer.json

@@ -0,0 +1,5 @@
+{
+  "require": {
+    "jaeger/querylist": "^4.1"
+  }
+}

+ 1646 - 0
composer.lock

@@ -0,0 +1,1646 @@
+{
+    "_readme": [
+        "This file locks the dependencies of your project to a known state",
+        "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
+        "This file is @generated automatically"
+    ],
+    "content-hash": "a21dabb608d5c9d22c9c0565d3a614fb",
+    "packages": [
+        {
+            "name": "cache/adapter-common",
+            "version": "1.1.0",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/php-cache/adapter-common.git",
+                "reference": "6320bb5f5574cb88438059b59f8708da6b6f1d32"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/php-cache/adapter-common/zipball/6320bb5f5574cb88438059b59f8708da6b6f1d32",
+                "reference": "6320bb5f5574cb88438059b59f8708da6b6f1d32",
+                "shasum": "",
+                "mirrors": [
+                    {
+                        "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%",
+                        "preferred": true
+                    }
+                ]
+            },
+            "require": {
+                "cache/tag-interop": "^1.0",
+                "php": "^5.6 || ^7.0",
+                "psr/cache": "^1.0",
+                "psr/log": "^1.0",
+                "psr/simple-cache": "^1.0"
+            },
+            "require-dev": {
+                "cache/integration-tests": "^0.16",
+                "phpunit/phpunit": "^5.7.21"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "1.1-dev"
+                }
+            },
+            "autoload": {
+                "psr-4": {
+                    "Cache\\Adapter\\Common\\": ""
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Aaron Scherer",
+                    "email": "aequasi@gmail.com",
+                    "homepage": "https://github.com/aequasi"
+                },
+                {
+                    "name": "Tobias Nyholm",
+                    "email": "tobias.nyholm@gmail.com",
+                    "homepage": "https://github.com/Nyholm"
+                }
+            ],
+            "description": "Common classes for PSR-6 adapters",
+            "homepage": "http://www.php-cache.com/en/latest/",
+            "keywords": [
+                "cache",
+                "psr-6",
+                "tag"
+            ],
+            "time": "2018-07-08T13:04:33+00:00"
+        },
+        {
+            "name": "cache/filesystem-adapter",
+            "version": "1.0.0",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/php-cache/filesystem-adapter.git",
+                "reference": "d50680b6dabbe39f9831f5fc9efa61c09d936017"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/php-cache/filesystem-adapter/zipball/d50680b6dabbe39f9831f5fc9efa61c09d936017",
+                "reference": "d50680b6dabbe39f9831f5fc9efa61c09d936017",
+                "shasum": "",
+                "mirrors": [
+                    {
+                        "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%",
+                        "preferred": true
+                    }
+                ]
+            },
+            "require": {
+                "cache/adapter-common": "^1.0",
+                "league/flysystem": "^1.0",
+                "php": "^5.6 || ^7.0",
+                "psr/cache": "^1.0",
+                "psr/simple-cache": "^1.0"
+            },
+            "provide": {
+                "psr/cache-implementation": "^1.0"
+            },
+            "require-dev": {
+                "cache/integration-tests": "^0.16",
+                "phpunit/phpunit": "^5.7.21"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "1.0-dev"
+                }
+            },
+            "autoload": {
+                "psr-4": {
+                    "Cache\\Adapter\\Filesystem\\": ""
+                },
+                "exclude-from-classmap": [
+                    "/Tests/"
+                ]
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Aaron Scherer",
+                    "email": "aequasi@gmail.com",
+                    "homepage": "https://github.com/aequasi"
+                },
+                {
+                    "name": "Tobias Nyholm",
+                    "email": "tobias.nyholm@gmail.com",
+                    "homepage": "https://github.com/Nyholm"
+                }
+            ],
+            "description": "A PSR-6 cache implementation using filesystem. This implementation supports tags",
+            "homepage": "http://www.php-cache.com/en/latest/",
+            "keywords": [
+                "cache",
+                "filesystem",
+                "psr-6",
+                "tag"
+            ],
+            "time": "2017-07-16T21:09:25+00:00"
+        },
+        {
+            "name": "cache/tag-interop",
+            "version": "1.0.0",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/php-cache/tag-interop.git",
+                "reference": "c7496dd81530f538af27b4f2713cde97bc292832"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/php-cache/tag-interop/zipball/c7496dd81530f538af27b4f2713cde97bc292832",
+                "reference": "c7496dd81530f538af27b4f2713cde97bc292832",
+                "shasum": "",
+                "mirrors": [
+                    {
+                        "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%",
+                        "preferred": true
+                    }
+                ]
+            },
+            "require": {
+                "php": "^5.5 || ^7.0",
+                "psr/cache": "^1.0"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "2.0-dev"
+                }
+            },
+            "autoload": {
+                "psr-4": {
+                    "Cache\\TagInterop\\": ""
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Tobias Nyholm",
+                    "email": "tobias.nyholm@gmail.com",
+                    "homepage": "https://github.com/Nyholm"
+                },
+                {
+                    "name": "Nicolas Grekas",
+                    "email": "p@tchwork.com",
+                    "homepage": "https://github.com/nicolas-grekas"
+                }
+            ],
+            "description": "Framework interoperable interfaces for tags",
+            "homepage": "http://www.php-cache.com/en/latest/",
+            "keywords": [
+                "cache",
+                "psr",
+                "psr6",
+                "tag"
+            ],
+            "time": "2017-03-13T09:14:27+00:00"
+        },
+        {
+            "name": "guzzlehttp/guzzle",
+            "version": "6.5.5",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/guzzle/guzzle.git",
+                "reference": "9d4290de1cfd701f38099ef7e183b64b4b7b0c5e"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/guzzle/guzzle/zipball/9d4290de1cfd701f38099ef7e183b64b4b7b0c5e",
+                "reference": "9d4290de1cfd701f38099ef7e183b64b4b7b0c5e",
+                "shasum": "",
+                "mirrors": [
+                    {
+                        "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%",
+                        "preferred": true
+                    }
+                ]
+            },
+            "require": {
+                "ext-json": "*",
+                "guzzlehttp/promises": "^1.0",
+                "guzzlehttp/psr7": "^1.6.1",
+                "php": ">=5.5",
+                "symfony/polyfill-intl-idn": "^1.17.0"
+            },
+            "require-dev": {
+                "ext-curl": "*",
+                "phpunit/phpunit": "^4.8.35 || ^5.7 || ^6.4 || ^7.0",
+                "psr/log": "^1.1"
+            },
+            "suggest": {
+                "psr/log": "Required for using the Log middleware"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "6.5-dev"
+                }
+            },
+            "autoload": {
+                "psr-4": {
+                    "GuzzleHttp\\": "src/"
+                },
+                "files": [
+                    "src/functions_include.php"
+                ]
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Michael Dowling",
+                    "email": "mtdowling@gmail.com",
+                    "homepage": "https://github.com/mtdowling"
+                }
+            ],
+            "description": "Guzzle is a PHP HTTP client library",
+            "homepage": "http://guzzlephp.org/",
+            "keywords": [
+                "client",
+                "curl",
+                "framework",
+                "http",
+                "http client",
+                "rest",
+                "web service"
+            ],
+            "time": "2020-06-16T21:01:06+00:00"
+        },
+        {
+            "name": "guzzlehttp/promises",
+            "version": "v1.3.1",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/guzzle/promises.git",
+                "reference": "a59da6cf61d80060647ff4d3eb2c03a2bc694646"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/guzzle/promises/zipball/a59da6cf61d80060647ff4d3eb2c03a2bc694646",
+                "reference": "a59da6cf61d80060647ff4d3eb2c03a2bc694646",
+                "shasum": "",
+                "mirrors": [
+                    {
+                        "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%",
+                        "preferred": true
+                    }
+                ]
+            },
+            "require": {
+                "php": ">=5.5.0"
+            },
+            "require-dev": {
+                "phpunit/phpunit": "^4.0"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "1.4-dev"
+                }
+            },
+            "autoload": {
+                "psr-4": {
+                    "GuzzleHttp\\Promise\\": "src/"
+                },
+                "files": [
+                    "src/functions_include.php"
+                ]
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Michael Dowling",
+                    "email": "mtdowling@gmail.com",
+                    "homepage": "https://github.com/mtdowling"
+                }
+            ],
+            "description": "Guzzle promises library",
+            "keywords": [
+                "promise"
+            ],
+            "time": "2016-12-20T10:07:11+00:00"
+        },
+        {
+            "name": "guzzlehttp/psr7",
+            "version": "1.6.1",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/guzzle/psr7.git",
+                "reference": "239400de7a173fe9901b9ac7c06497751f00727a"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/guzzle/psr7/zipball/239400de7a173fe9901b9ac7c06497751f00727a",
+                "reference": "239400de7a173fe9901b9ac7c06497751f00727a",
+                "shasum": "",
+                "mirrors": [
+                    {
+                        "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%",
+                        "preferred": true
+                    }
+                ]
+            },
+            "require": {
+                "php": ">=5.4.0",
+                "psr/http-message": "~1.0",
+                "ralouphie/getallheaders": "^2.0.5 || ^3.0.0"
+            },
+            "provide": {
+                "psr/http-message-implementation": "1.0"
+            },
+            "require-dev": {
+                "ext-zlib": "*",
+                "phpunit/phpunit": "~4.8.36 || ^5.7.27 || ^6.5.8"
+            },
+            "suggest": {
+                "zendframework/zend-httphandlerrunner": "Emit PSR-7 responses"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "1.6-dev"
+                }
+            },
+            "autoload": {
+                "psr-4": {
+                    "GuzzleHttp\\Psr7\\": "src/"
+                },
+                "files": [
+                    "src/functions_include.php"
+                ]
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Michael Dowling",
+                    "email": "mtdowling@gmail.com",
+                    "homepage": "https://github.com/mtdowling"
+                },
+                {
+                    "name": "Tobias Schultze",
+                    "homepage": "https://github.com/Tobion"
+                }
+            ],
+            "description": "PSR-7 message implementation that also provides common utility methods",
+            "keywords": [
+                "http",
+                "message",
+                "psr-7",
+                "request",
+                "response",
+                "stream",
+                "uri",
+                "url"
+            ],
+            "time": "2019-07-01T23:21:34+00:00"
+        },
+        {
+            "name": "jaeger/g-http",
+            "version": "V1.6.0",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/jae-jae/GHttp.git",
+                "reference": "eb34d266a07c687aef45087370ef47d48321bd2e"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/jae-jae/GHttp/zipball/eb34d266a07c687aef45087370ef47d48321bd2e",
+                "reference": "eb34d266a07c687aef45087370ef47d48321bd2e",
+                "shasum": "",
+                "mirrors": [
+                    {
+                        "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%",
+                        "preferred": true
+                    }
+                ]
+            },
+            "require": {
+                "cache/filesystem-adapter": "^1.0",
+                "guzzlehttp/guzzle": "^6.2"
+            },
+            "type": "library",
+            "autoload": {
+                "psr-4": {
+                    "Jaeger\\": "src"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Jaeger",
+                    "email": "JaegerCode@gmail.com"
+                }
+            ],
+            "description": "Simple Http client base on GuzzleHttp",
+            "time": "2018-12-12T04:21:15+00:00"
+        },
+        {
+            "name": "jaeger/phpquery-single",
+            "version": "1.0.1",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/jae-jae/phpQuery-single.git",
+                "reference": "fb80b4a0e5a337438d26e061ec3fb725c9f2a116"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/jae-jae/phpQuery-single/zipball/fb80b4a0e5a337438d26e061ec3fb725c9f2a116",
+                "reference": "fb80b4a0e5a337438d26e061ec3fb725c9f2a116",
+                "shasum": "",
+                "mirrors": [
+                    {
+                        "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%",
+                        "preferred": true
+                    }
+                ]
+            },
+            "require": {
+                "php": ">=5.3.0"
+            },
+            "type": "library",
+            "autoload": {
+                "classmap": [
+                    "phpQuery.php"
+                ]
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Tobiasz Cudnik",
+                    "email": "tobiasz.cudnik@gmail.com",
+                    "homepage": "https://github.com/TobiaszCudnik",
+                    "role": "Developer"
+                },
+                {
+                    "name": "Jaeger",
+                    "role": "Packager"
+                }
+            ],
+            "description": "phpQuery单文件版本,是Querylist的依赖(http://querylist.cc/),phpQuery项目主页:http://code.google.com/p/phpquery/",
+            "homepage": "http://code.google.com/p/phpquery/",
+            "time": "2019-11-05T01:50:34+00:00"
+        },
+        {
+            "name": "jaeger/querylist",
+            "version": "V4.2.5",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/jae-jae/QueryList.git",
+                "reference": "85903fa9b5dcd961a1dd6c292f2a7f5afa7c389d"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/jae-jae/QueryList/zipball/85903fa9b5dcd961a1dd6c292f2a7f5afa7c389d",
+                "reference": "85903fa9b5dcd961a1dd6c292f2a7f5afa7c389d",
+                "shasum": "",
+                "mirrors": [
+                    {
+                        "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%",
+                        "preferred": true
+                    }
+                ]
+            },
+            "require": {
+                "ext-dom": "*",
+                "jaeger/g-http": "^1.1",
+                "jaeger/phpquery-single": "^1",
+                "php": ">=7.1",
+                "tightenco/collect": "^5"
+            },
+            "require-dev": {
+                "phpunit/phpunit": "^8.5",
+                "symfony/var-dumper": "^3.3"
+            },
+            "type": "library",
+            "autoload": {
+                "psr-4": {
+                    "QL\\": "src"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Jaeger",
+                    "email": "JaegerCode@gmail.com"
+                }
+            ],
+            "description": "Simple, elegant, extensible PHP Web Scraper (crawler/spider),Use the css3 dom selector,Based on phpQuery! 简洁、优雅、可扩展的PHP采集工具(爬虫),基于phpQuery。",
+            "homepage": "http://querylist.cc",
+            "keywords": [
+                "QueryList",
+                "phpQuery",
+                "spider"
+            ],
+            "time": "2020-04-03T12:16:00+00:00"
+        },
+        {
+            "name": "league/flysystem",
+            "version": "1.0.69",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/thephpleague/flysystem.git",
+                "reference": "7106f78428a344bc4f643c233a94e48795f10967"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/thephpleague/flysystem/zipball/7106f78428a344bc4f643c233a94e48795f10967",
+                "reference": "7106f78428a344bc4f643c233a94e48795f10967",
+                "shasum": "",
+                "mirrors": [
+                    {
+                        "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%",
+                        "preferred": true
+                    }
+                ]
+            },
+            "require": {
+                "ext-fileinfo": "*",
+                "php": ">=5.5.9"
+            },
+            "conflict": {
+                "league/flysystem-sftp": "<1.0.6"
+            },
+            "require-dev": {
+                "phpspec/phpspec": "^3.4",
+                "phpunit/phpunit": "^5.7.26"
+            },
+            "suggest": {
+                "ext-fileinfo": "Required for MimeType",
+                "ext-ftp": "Allows you to use FTP server storage",
+                "ext-openssl": "Allows you to use FTPS server storage",
+                "league/flysystem-aws-s3-v2": "Allows you to use S3 storage with AWS SDK v2",
+                "league/flysystem-aws-s3-v3": "Allows you to use S3 storage with AWS SDK v3",
+                "league/flysystem-azure": "Allows you to use Windows Azure Blob storage",
+                "league/flysystem-cached-adapter": "Flysystem adapter decorator for metadata caching",
+                "league/flysystem-eventable-filesystem": "Allows you to use EventableFilesystem",
+                "league/flysystem-rackspace": "Allows you to use Rackspace Cloud Files",
+                "league/flysystem-sftp": "Allows you to use SFTP server storage via phpseclib",
+                "league/flysystem-webdav": "Allows you to use WebDAV storage",
+                "league/flysystem-ziparchive": "Allows you to use ZipArchive adapter",
+                "spatie/flysystem-dropbox": "Allows you to use Dropbox storage",
+                "srmklive/flysystem-dropbox-v2": "Allows you to use Dropbox storage for PHP 5 applications"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "1.1-dev"
+                }
+            },
+            "autoload": {
+                "psr-4": {
+                    "League\\Flysystem\\": "src/"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Frank de Jonge",
+                    "email": "info@frenky.net"
+                }
+            ],
+            "description": "Filesystem abstraction: Many filesystems, one API.",
+            "keywords": [
+                "Cloud Files",
+                "WebDAV",
+                "abstraction",
+                "aws",
+                "cloud",
+                "copy.com",
+                "dropbox",
+                "file systems",
+                "files",
+                "filesystem",
+                "filesystems",
+                "ftp",
+                "rackspace",
+                "remote",
+                "s3",
+                "sftp",
+                "storage"
+            ],
+            "funding": [
+                {
+                    "url": "https://offset.earth/frankdejonge",
+                    "type": "other"
+                }
+            ],
+            "time": "2020-05-18T15:13:39+00:00"
+        },
+        {
+            "name": "paragonie/random_compat",
+            "version": "v9.99.99",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/paragonie/random_compat.git",
+                "reference": "84b4dfb120c6f9b4ff7b3685f9b8f1aa365a0c95"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/paragonie/random_compat/zipball/84b4dfb120c6f9b4ff7b3685f9b8f1aa365a0c95",
+                "reference": "84b4dfb120c6f9b4ff7b3685f9b8f1aa365a0c95",
+                "shasum": "",
+                "mirrors": [
+                    {
+                        "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%",
+                        "preferred": true
+                    }
+                ]
+            },
+            "require": {
+                "php": "^7"
+            },
+            "require-dev": {
+                "phpunit/phpunit": "4.*|5.*",
+                "vimeo/psalm": "^1"
+            },
+            "suggest": {
+                "ext-libsodium": "Provides a modern crypto API that can be used to generate random bytes."
+            },
+            "type": "library",
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Paragon Initiative Enterprises",
+                    "email": "security@paragonie.com",
+                    "homepage": "https://paragonie.com"
+                }
+            ],
+            "description": "PHP 5.x polyfill for random_bytes() and random_int() from PHP 7",
+            "keywords": [
+                "csprng",
+                "polyfill",
+                "pseudorandom",
+                "random"
+            ],
+            "time": "2018-07-02T15:55:56+00:00"
+        },
+        {
+            "name": "psr/cache",
+            "version": "1.0.1",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/php-fig/cache.git",
+                "reference": "d11b50ad223250cf17b86e38383413f5a6764bf8"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/php-fig/cache/zipball/d11b50ad223250cf17b86e38383413f5a6764bf8",
+                "reference": "d11b50ad223250cf17b86e38383413f5a6764bf8",
+                "shasum": "",
+                "mirrors": [
+                    {
+                        "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%",
+                        "preferred": true
+                    }
+                ]
+            },
+            "require": {
+                "php": ">=5.3.0"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "1.0.x-dev"
+                }
+            },
+            "autoload": {
+                "psr-4": {
+                    "Psr\\Cache\\": "src/"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "PHP-FIG",
+                    "homepage": "http://www.php-fig.org/"
+                }
+            ],
+            "description": "Common interface for caching libraries",
+            "keywords": [
+                "cache",
+                "psr",
+                "psr-6"
+            ],
+            "time": "2016-08-06T20:24:11+00:00"
+        },
+        {
+            "name": "psr/http-message",
+            "version": "1.0.1",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/php-fig/http-message.git",
+                "reference": "f6561bf28d520154e4b0ec72be95418abe6d9363"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/php-fig/http-message/zipball/f6561bf28d520154e4b0ec72be95418abe6d9363",
+                "reference": "f6561bf28d520154e4b0ec72be95418abe6d9363",
+                "shasum": "",
+                "mirrors": [
+                    {
+                        "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%",
+                        "preferred": true
+                    }
+                ]
+            },
+            "require": {
+                "php": ">=5.3.0"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "1.0.x-dev"
+                }
+            },
+            "autoload": {
+                "psr-4": {
+                    "Psr\\Http\\Message\\": "src/"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "PHP-FIG",
+                    "homepage": "http://www.php-fig.org/"
+                }
+            ],
+            "description": "Common interface for HTTP messages",
+            "homepage": "https://github.com/php-fig/http-message",
+            "keywords": [
+                "http",
+                "http-message",
+                "psr",
+                "psr-7",
+                "request",
+                "response"
+            ],
+            "time": "2016-08-06T14:39:51+00:00"
+        },
+        {
+            "name": "psr/log",
+            "version": "1.1.3",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/php-fig/log.git",
+                "reference": "0f73288fd15629204f9d42b7055f72dacbe811fc"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/php-fig/log/zipball/0f73288fd15629204f9d42b7055f72dacbe811fc",
+                "reference": "0f73288fd15629204f9d42b7055f72dacbe811fc",
+                "shasum": "",
+                "mirrors": [
+                    {
+                        "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%",
+                        "preferred": true
+                    }
+                ]
+            },
+            "require": {
+                "php": ">=5.3.0"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "1.1.x-dev"
+                }
+            },
+            "autoload": {
+                "psr-4": {
+                    "Psr\\Log\\": "Psr/Log/"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "PHP-FIG",
+                    "homepage": "http://www.php-fig.org/"
+                }
+            ],
+            "description": "Common interface for logging libraries",
+            "homepage": "https://github.com/php-fig/log",
+            "keywords": [
+                "log",
+                "psr",
+                "psr-3"
+            ],
+            "time": "2020-03-23T09:12:05+00:00"
+        },
+        {
+            "name": "psr/simple-cache",
+            "version": "1.0.1",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/php-fig/simple-cache.git",
+                "reference": "408d5eafb83c57f6365a3ca330ff23aa4a5fa39b"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/php-fig/simple-cache/zipball/408d5eafb83c57f6365a3ca330ff23aa4a5fa39b",
+                "reference": "408d5eafb83c57f6365a3ca330ff23aa4a5fa39b",
+                "shasum": "",
+                "mirrors": [
+                    {
+                        "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%",
+                        "preferred": true
+                    }
+                ]
+            },
+            "require": {
+                "php": ">=5.3.0"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "1.0.x-dev"
+                }
+            },
+            "autoload": {
+                "psr-4": {
+                    "Psr\\SimpleCache\\": "src/"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "PHP-FIG",
+                    "homepage": "http://www.php-fig.org/"
+                }
+            ],
+            "description": "Common interfaces for simple caching",
+            "keywords": [
+                "cache",
+                "caching",
+                "psr",
+                "psr-16",
+                "simple-cache"
+            ],
+            "time": "2017-10-23T01:57:42+00:00"
+        },
+        {
+            "name": "ralouphie/getallheaders",
+            "version": "3.0.3",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/ralouphie/getallheaders.git",
+                "reference": "120b605dfeb996808c31b6477290a714d356e822"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/ralouphie/getallheaders/zipball/120b605dfeb996808c31b6477290a714d356e822",
+                "reference": "120b605dfeb996808c31b6477290a714d356e822",
+                "shasum": "",
+                "mirrors": [
+                    {
+                        "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%",
+                        "preferred": true
+                    }
+                ]
+            },
+            "require": {
+                "php": ">=5.6"
+            },
+            "require-dev": {
+                "php-coveralls/php-coveralls": "^2.1",
+                "phpunit/phpunit": "^5 || ^6.5"
+            },
+            "type": "library",
+            "autoload": {
+                "files": [
+                    "src/getallheaders.php"
+                ]
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Ralph Khattar",
+                    "email": "ralph.khattar@gmail.com"
+                }
+            ],
+            "description": "A polyfill for getallheaders.",
+            "time": "2019-03-08T08:55:37+00:00"
+        },
+        {
+            "name": "symfony/polyfill-intl-idn",
+            "version": "v1.18.0",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/symfony/polyfill-intl-idn.git",
+                "reference": "bc6549d068d0160e0f10f7a5a23c7d1406b95ebe"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/symfony/polyfill-intl-idn/zipball/bc6549d068d0160e0f10f7a5a23c7d1406b95ebe",
+                "reference": "bc6549d068d0160e0f10f7a5a23c7d1406b95ebe",
+                "shasum": "",
+                "mirrors": [
+                    {
+                        "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%",
+                        "preferred": true
+                    }
+                ]
+            },
+            "require": {
+                "php": ">=5.3.3",
+                "symfony/polyfill-intl-normalizer": "^1.10",
+                "symfony/polyfill-php70": "^1.10",
+                "symfony/polyfill-php72": "^1.10"
+            },
+            "suggest": {
+                "ext-intl": "For best performance"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "1.18-dev"
+                },
+                "thanks": {
+                    "name": "symfony/polyfill",
+                    "url": "https://github.com/symfony/polyfill"
+                }
+            },
+            "autoload": {
+                "psr-4": {
+                    "Symfony\\Polyfill\\Intl\\Idn\\": ""
+                },
+                "files": [
+                    "bootstrap.php"
+                ]
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Laurent Bassin",
+                    "email": "laurent@bassin.info"
+                },
+                {
+                    "name": "Trevor Rowbotham",
+                    "email": "trevor.rowbotham@pm.me"
+                },
+                {
+                    "name": "Symfony Community",
+                    "homepage": "https://symfony.com/contributors"
+                }
+            ],
+            "description": "Symfony polyfill for intl's idn_to_ascii and idn_to_utf8 functions",
+            "homepage": "https://symfony.com",
+            "keywords": [
+                "compatibility",
+                "idn",
+                "intl",
+                "polyfill",
+                "portable",
+                "shim"
+            ],
+            "funding": [
+                {
+                    "url": "https://symfony.com/sponsor",
+                    "type": "custom"
+                },
+                {
+                    "url": "https://github.com/fabpot",
+                    "type": "github"
+                },
+                {
+                    "url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
+                    "type": "tidelift"
+                }
+            ],
+            "time": "2020-07-14T12:35:20+00:00"
+        },
+        {
+            "name": "symfony/polyfill-intl-normalizer",
+            "version": "v1.18.0",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/symfony/polyfill-intl-normalizer.git",
+                "reference": "37078a8dd4a2a1e9ab0231af7c6cb671b2ed5a7e"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/symfony/polyfill-intl-normalizer/zipball/37078a8dd4a2a1e9ab0231af7c6cb671b2ed5a7e",
+                "reference": "37078a8dd4a2a1e9ab0231af7c6cb671b2ed5a7e",
+                "shasum": "",
+                "mirrors": [
+                    {
+                        "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%",
+                        "preferred": true
+                    }
+                ]
+            },
+            "require": {
+                "php": ">=5.3.3"
+            },
+            "suggest": {
+                "ext-intl": "For best performance"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "1.18-dev"
+                },
+                "thanks": {
+                    "name": "symfony/polyfill",
+                    "url": "https://github.com/symfony/polyfill"
+                }
+            },
+            "autoload": {
+                "psr-4": {
+                    "Symfony\\Polyfill\\Intl\\Normalizer\\": ""
+                },
+                "files": [
+                    "bootstrap.php"
+                ],
+                "classmap": [
+                    "Resources/stubs"
+                ]
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Nicolas Grekas",
+                    "email": "p@tchwork.com"
+                },
+                {
+                    "name": "Symfony Community",
+                    "homepage": "https://symfony.com/contributors"
+                }
+            ],
+            "description": "Symfony polyfill for intl's Normalizer class and related functions",
+            "homepage": "https://symfony.com",
+            "keywords": [
+                "compatibility",
+                "intl",
+                "normalizer",
+                "polyfill",
+                "portable",
+                "shim"
+            ],
+            "funding": [
+                {
+                    "url": "https://symfony.com/sponsor",
+                    "type": "custom"
+                },
+                {
+                    "url": "https://github.com/fabpot",
+                    "type": "github"
+                },
+                {
+                    "url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
+                    "type": "tidelift"
+                }
+            ],
+            "time": "2020-07-14T12:35:20+00:00"
+        },
+        {
+            "name": "symfony/polyfill-mbstring",
+            "version": "v1.18.0",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/symfony/polyfill-mbstring.git",
+                "reference": "a6977d63bf9a0ad4c65cd352709e230876f9904a"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/a6977d63bf9a0ad4c65cd352709e230876f9904a",
+                "reference": "a6977d63bf9a0ad4c65cd352709e230876f9904a",
+                "shasum": "",
+                "mirrors": [
+                    {
+                        "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%",
+                        "preferred": true
+                    }
+                ]
+            },
+            "require": {
+                "php": ">=5.3.3"
+            },
+            "suggest": {
+                "ext-mbstring": "For best performance"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "1.18-dev"
+                },
+                "thanks": {
+                    "name": "symfony/polyfill",
+                    "url": "https://github.com/symfony/polyfill"
+                }
+            },
+            "autoload": {
+                "psr-4": {
+                    "Symfony\\Polyfill\\Mbstring\\": ""
+                },
+                "files": [
+                    "bootstrap.php"
+                ]
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Nicolas Grekas",
+                    "email": "p@tchwork.com"
+                },
+                {
+                    "name": "Symfony Community",
+                    "homepage": "https://symfony.com/contributors"
+                }
+            ],
+            "description": "Symfony polyfill for the Mbstring extension",
+            "homepage": "https://symfony.com",
+            "keywords": [
+                "compatibility",
+                "mbstring",
+                "polyfill",
+                "portable",
+                "shim"
+            ],
+            "funding": [
+                {
+                    "url": "https://symfony.com/sponsor",
+                    "type": "custom"
+                },
+                {
+                    "url": "https://github.com/fabpot",
+                    "type": "github"
+                },
+                {
+                    "url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
+                    "type": "tidelift"
+                }
+            ],
+            "time": "2020-07-14T12:35:20+00:00"
+        },
+        {
+            "name": "symfony/polyfill-php70",
+            "version": "v1.18.0",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/symfony/polyfill-php70.git",
+                "reference": "0dd93f2c578bdc9c72697eaa5f1dd25644e618d3"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/symfony/polyfill-php70/zipball/0dd93f2c578bdc9c72697eaa5f1dd25644e618d3",
+                "reference": "0dd93f2c578bdc9c72697eaa5f1dd25644e618d3",
+                "shasum": "",
+                "mirrors": [
+                    {
+                        "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%",
+                        "preferred": true
+                    }
+                ]
+            },
+            "require": {
+                "paragonie/random_compat": "~1.0|~2.0|~9.99",
+                "php": ">=5.3.3"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "1.18-dev"
+                },
+                "thanks": {
+                    "name": "symfony/polyfill",
+                    "url": "https://github.com/symfony/polyfill"
+                }
+            },
+            "autoload": {
+                "psr-4": {
+                    "Symfony\\Polyfill\\Php70\\": ""
+                },
+                "files": [
+                    "bootstrap.php"
+                ],
+                "classmap": [
+                    "Resources/stubs"
+                ]
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Nicolas Grekas",
+                    "email": "p@tchwork.com"
+                },
+                {
+                    "name": "Symfony Community",
+                    "homepage": "https://symfony.com/contributors"
+                }
+            ],
+            "description": "Symfony polyfill backporting some PHP 7.0+ features to lower PHP versions",
+            "homepage": "https://symfony.com",
+            "keywords": [
+                "compatibility",
+                "polyfill",
+                "portable",
+                "shim"
+            ],
+            "funding": [
+                {
+                    "url": "https://symfony.com/sponsor",
+                    "type": "custom"
+                },
+                {
+                    "url": "https://github.com/fabpot",
+                    "type": "github"
+                },
+                {
+                    "url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
+                    "type": "tidelift"
+                }
+            ],
+            "time": "2020-07-14T12:35:20+00:00"
+        },
+        {
+            "name": "symfony/polyfill-php72",
+            "version": "v1.18.0",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/symfony/polyfill-php72.git",
+                "reference": "639447d008615574653fb3bc60d1986d7172eaae"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/symfony/polyfill-php72/zipball/639447d008615574653fb3bc60d1986d7172eaae",
+                "reference": "639447d008615574653fb3bc60d1986d7172eaae",
+                "shasum": "",
+                "mirrors": [
+                    {
+                        "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%",
+                        "preferred": true
+                    }
+                ]
+            },
+            "require": {
+                "php": ">=5.3.3"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "1.18-dev"
+                },
+                "thanks": {
+                    "name": "symfony/polyfill",
+                    "url": "https://github.com/symfony/polyfill"
+                }
+            },
+            "autoload": {
+                "psr-4": {
+                    "Symfony\\Polyfill\\Php72\\": ""
+                },
+                "files": [
+                    "bootstrap.php"
+                ]
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Nicolas Grekas",
+                    "email": "p@tchwork.com"
+                },
+                {
+                    "name": "Symfony Community",
+                    "homepage": "https://symfony.com/contributors"
+                }
+            ],
+            "description": "Symfony polyfill backporting some PHP 7.2+ features to lower PHP versions",
+            "homepage": "https://symfony.com",
+            "keywords": [
+                "compatibility",
+                "polyfill",
+                "portable",
+                "shim"
+            ],
+            "funding": [
+                {
+                    "url": "https://symfony.com/sponsor",
+                    "type": "custom"
+                },
+                {
+                    "url": "https://github.com/fabpot",
+                    "type": "github"
+                },
+                {
+                    "url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
+                    "type": "tidelift"
+                }
+            ],
+            "time": "2020-07-14T12:35:20+00:00"
+        },
+        {
+            "name": "symfony/polyfill-php80",
+            "version": "v1.18.0",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/symfony/polyfill-php80.git",
+                "reference": "d87d5766cbf48d72388a9f6b85f280c8ad51f981"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/d87d5766cbf48d72388a9f6b85f280c8ad51f981",
+                "reference": "d87d5766cbf48d72388a9f6b85f280c8ad51f981",
+                "shasum": "",
+                "mirrors": [
+                    {
+                        "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%",
+                        "preferred": true
+                    }
+                ]
+            },
+            "require": {
+                "php": ">=7.0.8"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "1.18-dev"
+                },
+                "thanks": {
+                    "name": "symfony/polyfill",
+                    "url": "https://github.com/symfony/polyfill"
+                }
+            },
+            "autoload": {
+                "psr-4": {
+                    "Symfony\\Polyfill\\Php80\\": ""
+                },
+                "files": [
+                    "bootstrap.php"
+                ],
+                "classmap": [
+                    "Resources/stubs"
+                ]
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Ion Bazan",
+                    "email": "ion.bazan@gmail.com"
+                },
+                {
+                    "name": "Nicolas Grekas",
+                    "email": "p@tchwork.com"
+                },
+                {
+                    "name": "Symfony Community",
+                    "homepage": "https://symfony.com/contributors"
+                }
+            ],
+            "description": "Symfony polyfill backporting some PHP 8.0+ features to lower PHP versions",
+            "homepage": "https://symfony.com",
+            "keywords": [
+                "compatibility",
+                "polyfill",
+                "portable",
+                "shim"
+            ],
+            "funding": [
+                {
+                    "url": "https://symfony.com/sponsor",
+                    "type": "custom"
+                },
+                {
+                    "url": "https://github.com/fabpot",
+                    "type": "github"
+                },
+                {
+                    "url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
+                    "type": "tidelift"
+                }
+            ],
+            "time": "2020-07-14T12:35:20+00:00"
+        },
+        {
+            "name": "symfony/var-dumper",
+            "version": "v4.4.10",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/symfony/var-dumper.git",
+                "reference": "56b3aa5eab0ac6720dcd559fd1d590ce301594ac"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/symfony/var-dumper/zipball/56b3aa5eab0ac6720dcd559fd1d590ce301594ac",
+                "reference": "56b3aa5eab0ac6720dcd559fd1d590ce301594ac",
+                "shasum": "",
+                "mirrors": [
+                    {
+                        "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%",
+                        "preferred": true
+                    }
+                ]
+            },
+            "require": {
+                "php": ">=7.1.3",
+                "symfony/polyfill-mbstring": "~1.0",
+                "symfony/polyfill-php72": "~1.5",
+                "symfony/polyfill-php80": "^1.15"
+            },
+            "conflict": {
+                "phpunit/phpunit": "<4.8.35|<5.4.3,>=5.0",
+                "symfony/console": "<3.4"
+            },
+            "require-dev": {
+                "ext-iconv": "*",
+                "symfony/console": "^3.4|^4.0|^5.0",
+                "symfony/process": "^4.4|^5.0",
+                "twig/twig": "^1.34|^2.4|^3.0"
+            },
+            "suggest": {
+                "ext-iconv": "To convert non-UTF-8 strings to UTF-8 (or symfony/polyfill-iconv in case ext-iconv cannot be used).",
+                "ext-intl": "To show region name in time zone dump",
+                "symfony/console": "To use the ServerDumpCommand and/or the bin/var-dump-server script"
+            },
+            "bin": [
+                "Resources/bin/var-dump-server"
+            ],
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "4.4-dev"
+                }
+            },
+            "autoload": {
+                "files": [
+                    "Resources/functions/dump.php"
+                ],
+                "psr-4": {
+                    "Symfony\\Component\\VarDumper\\": ""
+                },
+                "exclude-from-classmap": [
+                    "/Tests/"
+                ]
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Nicolas Grekas",
+                    "email": "p@tchwork.com"
+                },
+                {
+                    "name": "Symfony Community",
+                    "homepage": "https://symfony.com/contributors"
+                }
+            ],
+            "description": "Symfony mechanism for exploring and dumping PHP variables",
+            "homepage": "https://symfony.com",
+            "keywords": [
+                "debug",
+                "dump"
+            ],
+            "funding": [
+                {
+                    "url": "https://symfony.com/sponsor",
+                    "type": "custom"
+                },
+                {
+                    "url": "https://github.com/fabpot",
+                    "type": "github"
+                },
+                {
+                    "url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
+                    "type": "tidelift"
+                }
+            ],
+            "time": "2020-05-30T20:06:45+00:00"
+        },
+        {
+            "name": "tightenco/collect",
+            "version": "v5.8.38",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/tightenco/collect.git",
+                "reference": "c93a7039e6207ad533a09109838fe80933fcc72c"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/tightenco/collect/zipball/c93a7039e6207ad533a09109838fe80933fcc72c",
+                "reference": "c93a7039e6207ad533a09109838fe80933fcc72c",
+                "shasum": "",
+                "mirrors": [
+                    {
+                        "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%",
+                        "preferred": true
+                    }
+                ]
+            },
+            "require": {
+                "php": "^7.1.3",
+                "symfony/var-dumper": ">=3.4 <5"
+            },
+            "require-dev": {
+                "mockery/mockery": "^1.0",
+                "nesbot/carbon": "^1.26.3",
+                "phpunit/phpunit": "^7.0"
+            },
+            "type": "library",
+            "autoload": {
+                "files": [
+                    "src/Collect/Support/helpers.php",
+                    "src/Collect/Support/alias.php"
+                ],
+                "psr-4": {
+                    "Tightenco\\Collect\\": "src/Collect"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Taylor Otwell",
+                    "email": "taylorotwell@gmail.com"
+                }
+            ],
+            "description": "Collect - Illuminate Collections as a separate package.",
+            "keywords": [
+                "collection",
+                "laravel"
+            ],
+            "time": "2019-09-17T18:57:01+00:00"
+        }
+    ],
+    "packages-dev": [],
+    "aliases": [],
+    "minimum-stability": "stable",
+    "stability-flags": [],
+    "prefer-stable": false,
+    "prefer-lowest": false,
+    "platform": [],
+    "platform-dev": [],
+    "plugin-api-version": "1.1.0"
+}

+ 19 - 0
constants.php

@@ -0,0 +1,19 @@
+<?php
+// 全局常量
+define("APP_DIR", dirname(__FILE__));
+define("SUPPORT_RESTFUL", false);
+define("SESSION_KEY", "HEANUP_SESSION_UID");
+define("SIGN", "weatherapisign2019");
+define("ALERT_TOTAL", 40);
+if (get_magic_quotes_gpc()) {
+    function stripslashes_deep($value)
+    {
+        $value = is_array($value) ? array_map('stripslashes_deep', $value) : stripslashes($value);
+        return $value;
+    }
+
+    $_POST = array_map('stripslashes_deep', $_POST);
+    $_GET = array_map('stripslashes_deep', $_GET);
+    $_COOKIE = array_map('stripslashes_deep', $_COOKIE);
+    $_REQUEST = array_map('stripslashes_deep', $_REQUEST);
+}

+ 47 - 0
functions.php

@@ -0,0 +1,47 @@
+<?php
+
+// 全局函数
+
+// 捕获所有请求信息,包含fatal error
+register_shutdown_function('shutdownHandler');
+
+function shutdownHandler()
+{
+    if ($error = error_get_last()) {
+        // $filename 必须是一个绝对路径
+        if (!defined('REGISTER_SHUTDOWN_FUNCTION_LOG_FILENAME')) {
+            $filename = dirname(__FILE__) . DIRECTORY_SEPARATOR . 'register_shutdown_function.log';
+        } else {
+            $filename = REGISTER_SHUTDOWN_FUNCTION_LOG_FILENAME;
+        }
+        if ($error['type']<3){
+            $message = '时间 : ' . date('Y-m-d H:i:s') . PHP_EOL;
+            $message .= '文件 : ' . $error['file'] . PHP_EOL;
+            $message .= '行数 : ' . $error['line'] . PHP_EOL;
+            $message .= '错误 : ' . $error['message'] . PHP_EOL;
+            $message .= '类型 : ' . $error['type'] . PHP_EOL . PHP_EOL;
+            file_put_contents($filename, $message, FILE_APPEND);
+        }
+
+    }
+}
+
+/**
+ * 获取当前语言环境
+ *
+ * @return string
+ */
+function lang()
+{
+    static $global_lang;
+    if ($global_lang !== null) {
+        return $global_lang;
+    }
+    $global_lang = !empty($_REQUEST['global_lang']) ? $_REQUEST['global_lang'] : $_COOKIE['global_lang'];
+    if (empty($global_lang) && isset($_SERVER['HTTP_ACCEPT_LANGUAGE'])) {
+        $global_lang = substr($_SERVER['HTTP_ACCEPT_LANGUAGE'], 0, 2);
+        $global_lang = $global_lang == 'zh' ? $global_lang : "en";
+        setcookie("global_lang", $global_lang, time() + 3600 * 24 * 30, '/');
+    }
+    return $global_lang;
+}

+ 12 - 0
index.php

@@ -0,0 +1,12 @@
+<?php
+//ini_set('session.use_strict_mode', 0);
+//$sid=md5($_POST['from_wxid']."123".$_POST['final_from_wxid']);
+//session_id($sid);
+//session_start();
+error_reporting(E_ERROR);
+ini_set('date.timezone', 'Asia/Shanghai');
+
+use Heanup\Frame\Dispatcher;
+
+require_once './Frame/Init.php';
+Dispatcher::dispatch('Api');

+ 506 - 0
register_shutdown_function.log

@@ -0,0 +1,506 @@
+时间 : 2020-07-20 19:07:28
+文件 : /www/taoqu88/Frame/Dispatcher.php
+行数 : 84
+错误 : Uncaught Error: Class 'Heanup\Config\Route' not found in /www/taoqu88/Frame/Dispatcher.php:84
+Stack trace:
+#0 /www/taoqu88/index.php(12): Heanup\Frame\Dispatcher::dispatch('Api')
+#1 {main}
+  thrown
+类型 : 1
+
+时间 : 2020-07-20 19:08:21
+文件 : /www/taoqu88/Frame/Dispatcher.php
+行数 : 84
+错误 : Uncaught Error: Class 'Heanup\Config\Route' not found in /www/taoqu88/Frame/Dispatcher.php:84
+Stack trace:
+#0 /www/taoqu88/index.php(12): Heanup\Frame\Dispatcher::dispatch('Api')
+#1 {main}
+  thrown
+类型 : 1
+
+时间 : 2020-07-20 19:08:22
+文件 : /www/taoqu88/Frame/Dispatcher.php
+行数 : 84
+错误 : Uncaught Error: Class 'Heanup\Config\Route' not found in /www/taoqu88/Frame/Dispatcher.php:84
+Stack trace:
+#0 /www/taoqu88/index.php(12): Heanup\Frame\Dispatcher::dispatch('Api')
+#1 {main}
+  thrown
+类型 : 1
+
+时间 : 2020-07-20 19:08:54
+文件 : /www/taoqu88/Frame/Dispatcher.php
+行数 : 84
+错误 : Uncaught Error: Class 'Heanup\Config\Route' not found in /www/taoqu88/Frame/Dispatcher.php:84
+Stack trace:
+#0 /www/taoqu88/index.php(12): Heanup\Frame\Dispatcher::dispatch('Api')
+#1 {main}
+  thrown
+类型 : 1
+
+时间 : 2020-07-20 19:11:57
+文件 : /www/taoqu88/Frame/Dispatcher.php
+行数 : 84
+错误 : Uncaught Error: Class 'Heanup\Config\Route' not found in /www/taoqu88/Frame/Dispatcher.php:84
+Stack trace:
+#0 /www/taoqu88/index.php(12): Heanup\Frame\Dispatcher::dispatch('Api')
+#1 {main}
+  thrown
+类型 : 1
+
+时间 : 2020-07-20 19:14:54
+文件 : /www/taoqu88/App/Api/Controller/Category.php
+行数 : 16
+错误 : file_get_contents(http://www.taoqu88.comindex.php?r=l&amp;cid=1&amp;site=classify): failed to open stream: php_network_getaddresses: getaddrinfo failed: Name does not resolve
+类型 : 2
+
+时间 : 2020-07-20 19:14:54
+文件 : /www/taoqu88/Frame/Dispatcher.php
+行数 : 84
+错误 : Uncaught Error: Class 'Heanup\Config\Route' not found in /www/taoqu88/Frame/Dispatcher.php:84
+Stack trace:
+#0 /www/taoqu88/index.php(12): Heanup\Frame\Dispatcher::dispatch('Api')
+#1 {main}
+  thrown
+类型 : 1
+
+时间 : 2020-07-20 19:15:01
+文件 : /www/taoqu88/App/Api/Controller/Category.php
+行数 : 16
+错误 : file_get_contents(http://www.taoqu88.comindex.php?r=l&amp;cid=1&amp;site=classify): failed to open stream: php_network_getaddresses: getaddrinfo failed: Name does not resolve
+类型 : 2
+
+时间 : 2020-07-20 19:15:02
+文件 : /www/taoqu88/Frame/Dispatcher.php
+行数 : 84
+错误 : Uncaught Error: Class 'Heanup\Config\Route' not found in /www/taoqu88/Frame/Dispatcher.php:84
+Stack trace:
+#0 /www/taoqu88/index.php(12): Heanup\Frame\Dispatcher::dispatch('Api')
+#1 {main}
+  thrown
+类型 : 1
+
+时间 : 2020-07-20 19:15:29
+文件 : /www/taoqu88/Frame/Dispatcher.php
+行数 : 84
+错误 : Uncaught Error: Class 'Heanup\Config\Route' not found in /www/taoqu88/Frame/Dispatcher.php:84
+Stack trace:
+#0 /www/taoqu88/index.php(12): Heanup\Frame\Dispatcher::dispatch('Api')
+#1 {main}
+  thrown
+类型 : 1
+
+时间 : 2020-07-20 19:16:53
+文件 : /www/taoqu88/Frame/Dispatcher.php
+行数 : 84
+错误 : Uncaught Error: Class 'Heanup\Config\Route' not found in /www/taoqu88/Frame/Dispatcher.php:84
+Stack trace:
+#0 /www/taoqu88/index.php(12): Heanup\Frame\Dispatcher::dispatch('Api')
+#1 {main}
+  thrown
+类型 : 1
+
+时间 : 2020-07-20 19:18:23
+文件 : /www/taoqu88/Frame/Dispatcher.php
+行数 : 84
+错误 : Uncaught Error: Class 'Heanup\Config\Route' not found in /www/taoqu88/Frame/Dispatcher.php:84
+Stack trace:
+#0 /www/taoqu88/index.php(12): Heanup\Frame\Dispatcher::dispatch('Api')
+#1 {main}
+  thrown
+类型 : 1
+
+时间 : 2020-07-20 19:18:56
+文件 : /www/taoqu88/Frame/Dispatcher.php
+行数 : 84
+错误 : Uncaught Error: Class 'Heanup\Config\Route' not found in /www/taoqu88/Frame/Dispatcher.php:84
+Stack trace:
+#0 /www/taoqu88/index.php(12): Heanup\Frame\Dispatcher::dispatch('Api')
+#1 {main}
+  thrown
+类型 : 1
+
+时间 : 2020-07-20 19:21:33
+文件 : /www/taoqu88/Frame/Dispatcher.php
+行数 : 84
+错误 : Uncaught Error: Class 'Heanup\Config\Route' not found in /www/taoqu88/Frame/Dispatcher.php:84
+Stack trace:
+#0 /www/taoqu88/index.php(12): Heanup\Frame\Dispatcher::dispatch('Api')
+#1 {main}
+  thrown
+类型 : 1
+
+时间 : 2020-07-20 19:59:18
+文件 : /www/taoqu88/vendor/jaeger/phpquery-single/phpQuery.php
+行数 : 328
+错误 : DOMDocument::loadHTML(): Unexpected end tag : style in Entity, line: 872
+类型 : 2
+
+时间 : 2020-07-20 19:59:19
+文件 : /www/taoqu88/Frame/Dispatcher.php
+行数 : 84
+错误 : Uncaught Error: Class 'Heanup\Config\Route' not found in /www/taoqu88/Frame/Dispatcher.php:84
+Stack trace:
+#0 /www/taoqu88/index.php(12): Heanup\Frame\Dispatcher::dispatch('Api')
+#1 {main}
+  thrown
+类型 : 1
+
+时间 : 2020-07-20 19:59:38
+文件 : /www/taoqu88/vendor/jaeger/phpquery-single/phpQuery.php
+行数 : 328
+错误 : DOMDocument::loadHTML(): Unexpected end tag : style in Entity, line: 872
+类型 : 2
+
+时间 : 2020-07-20 19:59:38
+文件 : /www/taoqu88/Frame/Dispatcher.php
+行数 : 84
+错误 : Uncaught Error: Class 'Heanup\Config\Route' not found in /www/taoqu88/Frame/Dispatcher.php:84
+Stack trace:
+#0 /www/taoqu88/index.php(12): Heanup\Frame\Dispatcher::dispatch('Api')
+#1 {main}
+  thrown
+类型 : 1
+
+时间 : 2020-07-20 19:59:40
+文件 : /www/taoqu88/vendor/jaeger/phpquery-single/phpQuery.php
+行数 : 328
+错误 : DOMDocument::loadHTML(): Unexpected end tag : style in Entity, line: 872
+类型 : 2
+
+时间 : 2020-07-20 19:59:40
+文件 : /www/taoqu88/Frame/Dispatcher.php
+行数 : 84
+错误 : Uncaught Error: Class 'Heanup\Config\Route' not found in /www/taoqu88/Frame/Dispatcher.php:84
+Stack trace:
+#0 /www/taoqu88/index.php(12): Heanup\Frame\Dispatcher::dispatch('Api')
+#1 {main}
+  thrown
+类型 : 1
+
+时间 : 2020-07-20 20:01:34
+文件 : /www/taoqu88/App/Api/Model/Dataoke.php
+行数 : 25
+错误 : Uncaught Error: Call to a member function all() on string in /www/taoqu88/App/Api/Model/Dataoke.php:25
+Stack trace:
+#0 /www/taoqu88/App/Api/Controller/Category.php(17): Heanup\App\Api\Model\Dataoke::parse('<!DOCTYPE html>...')
+#1 /www/taoqu88/Frame/Controller.php(37): Heanup\App\Api\Controller\Category->main()
+#2 /www/taoqu88/Frame/Dispatcher.php(108): Heanup\Frame\Controller->run()
+#3 /www/taoqu88/index.php(12): Heanup\Frame\Dispatcher::dispatch('Api')
+#4 {main}
+  thrown
+类型 : 1
+
+时间 : 2020-07-20 20:01:34
+文件 : /www/taoqu88/Frame/Dispatcher.php
+行数 : 84
+错误 : Uncaught Error: Class 'Heanup\Config\Route' not found in /www/taoqu88/Frame/Dispatcher.php:84
+Stack trace:
+#0 /www/taoqu88/index.php(12): Heanup\Frame\Dispatcher::dispatch('Api')
+#1 {main}
+  thrown
+类型 : 1
+
+时间 : 2020-07-20 20:01:53
+文件 : /www/taoqu88/App/Api/Model/Dataoke.php
+行数 : 25
+错误 : Uncaught Error: Call to a member function all() on string in /www/taoqu88/App/Api/Model/Dataoke.php:25
+Stack trace:
+#0 /www/taoqu88/App/Api/Controller/Category.php(17): Heanup\App\Api\Model\Dataoke::parse('<!DOCTYPE html>...')
+#1 /www/taoqu88/Frame/Controller.php(37): Heanup\App\Api\Controller\Category->main()
+#2 /www/taoqu88/Frame/Dispatcher.php(108): Heanup\Frame\Controller->run()
+#3 /www/taoqu88/index.php(12): Heanup\Frame\Dispatcher::dispatch('Api')
+#4 {main}
+  thrown
+类型 : 1
+
+时间 : 2020-07-20 20:01:53
+文件 : /www/taoqu88/Frame/Dispatcher.php
+行数 : 84
+错误 : Uncaught Error: Class 'Heanup\Config\Route' not found in /www/taoqu88/Frame/Dispatcher.php:84
+Stack trace:
+#0 /www/taoqu88/index.php(12): Heanup\Frame\Dispatcher::dispatch('Api')
+#1 {main}
+  thrown
+类型 : 1
+
+时间 : 2020-07-20 20:02:10
+文件 : /www/taoqu88/vendor/jaeger/phpquery-single/phpQuery.php
+行数 : 328
+错误 : DOMDocument::loadHTML(): Unexpected end tag : style in Entity, line: 872
+类型 : 2
+
+时间 : 2020-07-20 20:02:10
+文件 : /www/taoqu88/Frame/Dispatcher.php
+行数 : 84
+错误 : Uncaught Error: Class 'Heanup\Config\Route' not found in /www/taoqu88/Frame/Dispatcher.php:84
+Stack trace:
+#0 /www/taoqu88/index.php(12): Heanup\Frame\Dispatcher::dispatch('Api')
+#1 {main}
+  thrown
+类型 : 1
+
+时间 : 2020-07-20 20:02:57
+文件 : /www/taoqu88/vendor/jaeger/phpquery-single/phpQuery.php
+行数 : 328
+错误 : DOMDocument::loadHTML(): Unexpected end tag : style in Entity, line: 872
+类型 : 2
+
+时间 : 2020-07-20 20:02:57
+文件 : /www/taoqu88/Frame/Dispatcher.php
+行数 : 84
+错误 : Uncaught Error: Class 'Heanup\Config\Route' not found in /www/taoqu88/Frame/Dispatcher.php:84
+Stack trace:
+#0 /www/taoqu88/index.php(12): Heanup\Frame\Dispatcher::dispatch('Api')
+#1 {main}
+  thrown
+类型 : 1
+
+时间 : 2020-07-20 20:05:00
+文件 : /www/taoqu88/Frame/Dispatcher.php
+行数 : 84
+错误 : Uncaught Error: Class 'Heanup\Config\Route' not found in /www/taoqu88/Frame/Dispatcher.php:84
+Stack trace:
+#0 /www/taoqu88/index.php(12): Heanup\Frame\Dispatcher::dispatch('Api')
+#1 {main}
+  thrown
+类型 : 1
+
+时间 : 2020-07-20 20:05:21
+文件 : /www/taoqu88/vendor/jaeger/phpquery-single/phpQuery.php
+行数 : 328
+错误 : DOMDocument::loadHTML(): Unexpected end tag : style in Entity, line: 872
+类型 : 2
+
+时间 : 2020-07-20 20:05:21
+文件 : /www/taoqu88/Frame/Dispatcher.php
+行数 : 84
+错误 : Uncaught Error: Class 'Heanup\Config\Route' not found in /www/taoqu88/Frame/Dispatcher.php:84
+Stack trace:
+#0 /www/taoqu88/index.php(12): Heanup\Frame\Dispatcher::dispatch('Api')
+#1 {main}
+  thrown
+类型 : 1
+
+时间 : 2020-07-20 20:05:29
+文件 : /www/taoqu88/vendor/jaeger/phpquery-single/phpQuery.php
+行数 : 328
+错误 : DOMDocument::loadHTML(): Unexpected end tag : style in Entity, line: 872
+类型 : 2
+
+时间 : 2020-07-20 20:05:29
+文件 : /www/taoqu88/Frame/Dispatcher.php
+行数 : 84
+错误 : Uncaught Error: Class 'Heanup\Config\Route' not found in /www/taoqu88/Frame/Dispatcher.php:84
+Stack trace:
+#0 /www/taoqu88/index.php(12): Heanup\Frame\Dispatcher::dispatch('Api')
+#1 {main}
+  thrown
+类型 : 1
+
+时间 : 2020-07-20 20:06:02
+文件 : /www/taoqu88/vendor/jaeger/phpquery-single/phpQuery.php
+行数 : 328
+错误 : DOMDocument::loadHTML(): Unexpected end tag : style in Entity, line: 872
+类型 : 2
+
+时间 : 2020-07-20 20:06:02
+文件 : /www/taoqu88/Frame/Dispatcher.php
+行数 : 84
+错误 : Uncaught Error: Class 'Heanup\Config\Route' not found in /www/taoqu88/Frame/Dispatcher.php:84
+Stack trace:
+#0 /www/taoqu88/index.php(12): Heanup\Frame\Dispatcher::dispatch('Api')
+#1 {main}
+  thrown
+类型 : 1
+
+时间 : 2020-07-20 20:06:26
+文件 : /www/taoqu88/vendor/jaeger/phpquery-single/phpQuery.php
+行数 : 328
+错误 : DOMDocument::loadHTML(): Unexpected end tag : style in Entity, line: 872
+类型 : 2
+
+时间 : 2020-07-20 20:06:26
+文件 : /www/taoqu88/Frame/Dispatcher.php
+行数 : 84
+错误 : Uncaught Error: Class 'Heanup\Config\Route' not found in /www/taoqu88/Frame/Dispatcher.php:84
+Stack trace:
+#0 /www/taoqu88/index.php(12): Heanup\Frame\Dispatcher::dispatch('Api')
+#1 {main}
+  thrown
+类型 : 1
+
+时间 : 2020-07-20 20:06:32
+文件 : /www/taoqu88/vendor/jaeger/phpquery-single/phpQuery.php
+行数 : 328
+错误 : DOMDocument::loadHTML(): Unexpected end tag : style in Entity, line: 872
+类型 : 2
+
+时间 : 2020-07-20 20:06:33
+文件 : /www/taoqu88/Frame/Dispatcher.php
+行数 : 84
+错误 : Uncaught Error: Class 'Heanup\Config\Route' not found in /www/taoqu88/Frame/Dispatcher.php:84
+Stack trace:
+#0 /www/taoqu88/index.php(12): Heanup\Frame\Dispatcher::dispatch('Api')
+#1 {main}
+  thrown
+类型 : 1
+
+时间 : 2020-07-20 20:18:42
+文件 : /www/taoqu88/vendor/jaeger/phpquery-single/phpQuery.php
+行数 : 328
+错误 : DOMDocument::loadHTML(): Unexpected end tag : style in Entity, line: 1146
+类型 : 2
+
+时间 : 2020-07-20 20:18:42
+文件 : /www/taoqu88/Frame/Dispatcher.php
+行数 : 84
+错误 : Uncaught Error: Class 'Heanup\Config\Route' not found in /www/taoqu88/Frame/Dispatcher.php:84
+Stack trace:
+#0 /www/taoqu88/index.php(12): Heanup\Frame\Dispatcher::dispatch('Api')
+#1 {main}
+  thrown
+类型 : 1
+
+时间 : 2020-07-20 20:22:40
+文件 : /www/taoqu88/Frame/Dispatcher.php
+行数 : 84
+错误 : Uncaught Error: Class 'Heanup\Config\Route' not found in /www/taoqu88/Frame/Dispatcher.php:84
+Stack trace:
+#0 /www/taoqu88/index.php(12): Heanup\Frame\Dispatcher::dispatch('Api')
+#1 {main}
+  thrown
+类型 : 1
+
+时间 : 2020-07-20 20:23:00
+文件 : /www/taoqu88/Frame/Dispatcher.php
+行数 : 84
+错误 : Uncaught Error: Class 'Heanup\Config\Route' not found in /www/taoqu88/Frame/Dispatcher.php:84
+Stack trace:
+#0 /www/taoqu88/index.php(12): Heanup\Frame\Dispatcher::dispatch('Api')
+#1 {main}
+  thrown
+类型 : 1
+
+时间 : 2020-07-20 20:23:46
+文件 : /www/taoqu88/Frame/Dispatcher.php
+行数 : 84
+错误 : Uncaught Error: Class 'Heanup\Config\Route' not found in /www/taoqu88/Frame/Dispatcher.php:84
+Stack trace:
+#0 /www/taoqu88/index.php(12): Heanup\Frame\Dispatcher::dispatch('Api')
+#1 {main}
+  thrown
+类型 : 1
+
+时间 : 2020-07-20 20:23:52
+文件 : /www/taoqu88/Frame/Dispatcher.php
+行数 : 84
+错误 : Uncaught Error: Class 'Heanup\Config\Route' not found in /www/taoqu88/Frame/Dispatcher.php:84
+Stack trace:
+#0 /www/taoqu88/index.php(12): Heanup\Frame\Dispatcher::dispatch('Api')
+#1 {main}
+  thrown
+类型 : 1
+
+时间 : 2020-07-20 20:24:59
+文件 : /www/taoqu88/Frame/Dispatcher.php
+行数 : 84
+错误 : Uncaught Error: Class 'Heanup\Config\Route' not found in /www/taoqu88/Frame/Dispatcher.php:84
+Stack trace:
+#0 /www/taoqu88/index.php(12): Heanup\Frame\Dispatcher::dispatch('Api')
+#1 {main}
+  thrown
+类型 : 1
+
+时间 : 2020-07-20 20:25:21
+文件 : /www/taoqu88/Frame/Dispatcher.php
+行数 : 84
+错误 : Uncaught Error: Class 'Heanup\Config\Route' not found in /www/taoqu88/Frame/Dispatcher.php:84
+Stack trace:
+#0 /www/taoqu88/index.php(12): Heanup\Frame\Dispatcher::dispatch('Api')
+#1 {main}
+  thrown
+类型 : 1
+
+时间 : 2020-07-20 20:27:33
+文件 : /www/taoqu88/Frame/Dispatcher.php
+行数 : 84
+错误 : Uncaught Error: Class 'Heanup\Config\Route' not found in /www/taoqu88/Frame/Dispatcher.php:84
+Stack trace:
+#0 /www/taoqu88/index.php(12): Heanup\Frame\Dispatcher::dispatch('Api')
+#1 {main}
+  thrown
+类型 : 1
+
+时间 : 2020-07-20 20:27:44
+文件 : /www/taoqu88/Frame/Dispatcher.php
+行数 : 84
+错误 : Uncaught Error: Class 'Heanup\Config\Route' not found in /www/taoqu88/Frame/Dispatcher.php:84
+Stack trace:
+#0 /www/taoqu88/index.php(12): Heanup\Frame\Dispatcher::dispatch('Api')
+#1 {main}
+  thrown
+类型 : 1
+
+时间 : 2020-07-20 20:28:05
+文件 : /www/taoqu88/Frame/Dispatcher.php
+行数 : 84
+错误 : Uncaught Error: Class 'Heanup\Config\Route' not found in /www/taoqu88/Frame/Dispatcher.php:84
+Stack trace:
+#0 /www/taoqu88/index.php(12): Heanup\Frame\Dispatcher::dispatch('Api')
+#1 {main}
+  thrown
+类型 : 1
+
+时间 : 2020-07-20 20:28:06
+文件 : /www/taoqu88/Frame/Dispatcher.php
+行数 : 84
+错误 : Uncaught Error: Class 'Heanup\Config\Route' not found in /www/taoqu88/Frame/Dispatcher.php:84
+Stack trace:
+#0 /www/taoqu88/index.php(12): Heanup\Frame\Dispatcher::dispatch('Api')
+#1 {main}
+  thrown
+类型 : 1
+
+时间 : 2020-07-20 20:28:45
+文件 : /www/taoqu88/Frame/Dispatcher.php
+行数 : 84
+错误 : Uncaught Error: Class 'Heanup\Config\Route' not found in /www/taoqu88/Frame/Dispatcher.php:84
+Stack trace:
+#0 /www/taoqu88/index.php(12): Heanup\Frame\Dispatcher::dispatch('Api')
+#1 {main}
+  thrown
+类型 : 1
+
+时间 : 2020-07-20 20:38:20
+文件 : /www/taoqu88/vendor/jaeger/phpquery-single/phpQuery.php
+行数 : 328
+错误 : DOMDocument::loadHTML(): Unexpected end tag : style in Entity, line: 992
+类型 : 2
+
+时间 : 2020-07-20 20:42:53
+文件 : /www/taoqu88/App/Api/Controller/Search.php
+行数 : 33
+错误 : file_get_contents(http://www.taoqu88.com/index.phpr=l&amp;u=1350274&amp;kw=%25e9%259d%25a2%25e8%2586%259c): failed to open stream: HTTP request failed! HTTP/1.1 404 Not Found
+
+类型 : 2
+
+时间 : 2020-07-20 20:42:54
+文件 : /www/taoqu88/App/Api/Controller/Search.php
+行数 : 33
+错误 : file_get_contents(http://www.taoqu88.com/index.phpr=l&amp;u=1350274&amp;kw=%25e9%259d%25a2%25e8%2586%259c): failed to open stream: HTTP request failed! HTTP/1.1 404 Not Found
+
+类型 : 2
+
+时间 : 2020-07-20 21:39:49
+文件 : /www/taoqu88/vendor/jaeger/phpquery-single/phpQuery.php
+行数 : 328
+错误 : DOMDocument::loadHTML(): Unexpected end tag : style in Entity, line: 1124
+类型 : 2
+
+时间 : 2020-07-20 22:07:15
+文件 : /www/taoqu88/App/Api/Model/Dataoke.php
+行数 : 44
+错误 : Allowed memory size of 268435456 bytes exhausted (tried to allocate 71409689 bytes)
+类型 : 1
+

File diff suppressed because it is too large
+ 198 - 0
static/new_home.js


+ 950 - 0
tao.php

@@ -0,0 +1,950 @@
+<?php
+/*
+ * 请勿使用windows下的记事本修改本文件。推荐使用 notepad++
+ *  * 版本v2.8
+ *  1.缓存支持https
+ *
+ *  * 版本v2.7
+ *  1.优化自动选择最优服务api和增加api数量
+ *
+ *  版本v2.6
+ *  1.自动选择最优服务api地址返回数据
+ *  2.请求时添加压缩返回
+ * 版本v2.5
+ *  1.500页面优化
+ *  2.数据请求增加重试,减少请求失败
+ * 版本v2.4
+ *  新增加cdn节点检测
+ * 版本 v2.3
+ *  1.500页面展示
+ * 版本 v2.2
+ *  1.增加mbstring检测
+ *  2.错误显示
+ *版本 v2.1
+ *  1.增加cache检测
+ *  2.增加显示debug信息限制
+ *
+ * 版本 v2.0
+ *  1.documentUrl 做兼容解决部分用户二级目录报错
+ *  2.增加debug信息方便为用户定位错误
+ *
+ * 版本 v1.1.0
+ * 升级日志:
+ *  1、添加自动更新
+ *  2、修正缓存的BUG
+ *  3、添加自动清理cache
+ *  4、实现http code的转发
+ *
+ * 版本 v1.0.1
+ * 升级日志:
+ *  1、修正第一次无法打开,需要刷新才能打开的BUG
+ *  2、添加对二级目录的支持
+ *  3、添加对非index.php文件名的支持。
+ *
+ * */
+$appId = '1350274';  // 站点的APPID (请勿修改和泄漏)
+$appKey = '7C8F55348F476C5904F7814E598771E2';// 站点的APP KEY(请勿修改和泄漏)
+
+$proxyVersion = 16;
+$autoCleanCache = 100;
+
+//===============================================================================
+//===============================================================================
+//===============================================================================
+//================               请勿修改以下程序            ====================
+//===============================================================================
+//===============================================================================
+//===============================================================================
+
+$host = "http://cms4.dataoke.com";
+
+@date_default_timezone_set('Asia/Shanghai');
+$is_host_data = false;
+try {
+    if (file_exists(dirname(__FILE__) . DIRECTORY_SEPARATOR . '/dtk.data')) {
+        $host_data = @file_get_contents(dirname(__FILE__) . DIRECTORY_SEPARATOR . '/dtk.data');
+        $host_data = @json_decode($host_data, true);
+        if (!empty($host_data['host']) && !empty($host_data['time']) && $host_data['time'] > strtotime('-1 day')) {
+            $is_host_data = true;
+        }
+    }
+    if ($is_host_data == false) {
+        $host_data = array(
+            'http://cms1.dataoke.com' => 0,
+            'http://cms2.dataoke.com' => 0,
+            'http://cmsserver1.dataoke.com' => 0,
+            'http://cmsserver2.dataoke.com' => 0,
+        );
+        @file_put_contents(dirname(__FILE__) . DIRECTORY_SEPARATOR . '/dtk.data', @json_encode(array('host' => $host_data, 'time' => time())));
+    }
+} catch (Exception $e) {
+
+}
+
+$test_env = strrpos(@$_SERVER['HTTP_USER_AGENT'], 'test') === false ? false : true;
+$requestMethod = strtoupper(@$_SERVER["REQUEST_METHOD"]);
+
+$requestUrl = @$_SERVER["REQUEST_URI"];
+if ($test_env) {
+    ini_set("display_errors", "On");
+    error_reporting(E_ALL | E_STRICT);
+} else {
+    @ini_set("display_errors", "Off");
+}
+
+
+$css_static = @date('Y-m-d');
+$html_500 = <<<html
+<!DOCTYPE html>
+<html>
+    <head>
+        <meta charset="utf-8">
+        <meta http-equiv="x-dns-prefetch-control" content="on"/>
+        <meta name="apple-mobile-web-app-capable" content="yes"/>
+        <meta content="telephone=no" name="format-detection"/>
+        <meta name="viewport" content="width=device-width,initial-scale=1,minimum-scale=1,maximum-scale=1,user-scalable=no"/>
+        <link href="https://cmsstatic.dataoke.com/error/error.css?v={$css_static}" rel="stylesheet">
+ </head>
+
+<body>
+  <div class="error_main">
+    <p class="img"></p>
+    <p class="text">咦?出错啦,请稍后再试~ </p>
+    <p class="botton">
+      <a  onclick="window.location.reload();" href="javascript:;" class="botton_par">点击刷新</a>
+    </p>
+  </div>
+</body>
+<script src="https://cmsstatic.dataoke.com/error/error.js?v={$css_static}"></script>
+</html>
+html;
+
+//debug
+if ($test_env && isset($_GET['debug']) && $_GET['debug'] == 'php') {
+    header("Content-type: text/html; charset=utf-8");
+    echo 'cms 版本:' . $proxyVersion . '<br>';
+    echo 'php 版本:' . PHP_VERSION . '<br>';
+    if (function_exists('curl_init')) {
+        echo 'curl 已经开启 ' . '<br>';
+    } else {
+        echo 'curl <span style="color: red">未开启,请先开启curl扩展,否则无法运行,请联系您的空间或者服务器提供商</span>' . '<br>';
+    }
+    if (function_exists('mb_substr')) {
+        echo 'mbstring 已经开启 ' . '<br>';
+    } else {
+        echo 'mbstring <span style="color: red">未开启,请先开启mbstring扩展,否则无法运行</span>' . '<br>';
+    }
+    $test_dir = dirname(__FILE__) . DIRECTORY_SEPARATOR . 'cache';
+    $test_file = $test_dir . '/test.txt';
+    if (!is_dir($test_dir)) {
+        @mkdir($test_dir);
+    }
+    @file_put_contents($test_file, 'test');
+    if (file_exists($test_file)) {
+        echo 'cache:有效<br>';
+    } else {
+        echo 'cache <span style="color: red">无效,请设置读写修改权限</span>' . '<br>';
+    }
+    if (function_exists('gethostbyname')) {
+        echo $host . '  --CDN 节点 ---' . gethostbyname(str_replace('http://', '', $host)) . '<br/>';
+        echo 'www.dataoke.com  --CDN 节点 ---' . gethostbyname("www.dataoke.com");
+    }
+    exit;
+}
+if ($test_env && isset($_GET['debug']) && $_GET['debug'] == 'fast') {
+    var_dump($host_data);
+    exit;
+}
+$cache = new CacheHelper();
+
+if (isset($_REQUEST['clean'])) {
+    $cache->clean();
+    header("Content-type: text/html; charset=utf-8");
+    echo '已清除缓存';
+    exit;
+}
+if (mt_rand(0, $autoCleanCache) == 1) {
+    $cache->clean();
+}
+$key = md5($requestUrl . CacheHelper::isMobile() . CacheHelper::isHttps() . CacheHelper::isIPad() . CacheHelper::isIPhone() . CacheHelper::isMicroMessenger() . CacheHelper::isWeibo() . CacheHelper::isSpider());
+if ($requestMethod == 'GET') {
+    if (!$test_env) {
+        $cacheData = $cache->Get($key);
+        if ($cacheData !== false && !empty($cacheData)) {
+            @header('Dtk-Cache-Check:2');
+            echo $cacheData;
+            exit;
+        }
+    }
+}
+
+$documentUrl = @$_SERVER["PHP_SELF"];
+if (empty($documentUrl)) {
+    $documentUrl = @$_SERVER["SCRIPT_NAME"];
+}
+if (empty($documentUrl)) {
+    $documentUrl = @$_SERVER["DOCUMENT_URI"];
+}
+if (empty($documentUrl)) {
+    $documentUrl = $requestUrl;
+    $str_pos = strpos($requestUrl, '?');
+    if ($str_pos !== false) {
+        $documentUrl = substr($requestUrl, 0, $str_pos);
+    }
+}
+if (empty($documentUrl)) {
+    $documentUrl = '/index.php';
+}
+
+if (!function_exists('gzdecode')) {
+    if (file_exists('gzinflate')) {
+        function gzdecode($data)
+        {
+            return @gzinflate(substr($data, 10, -8));
+        }
+    }
+}
+
+$httpHelper = new HttpHelper($appId, $appKey, $proxyVersion, $documentUrl);
+$httpHelper->host_data = $host_data;
+if ($is_host_data) {
+    $fast_host = $httpHelper->getFastUrl();
+    if (!empty($fast_host)) {
+        $host = $fast_host;
+    }
+}
+$html = $httpHelper->getHtml($host, $requestUrl, $requestMethod == 'POST' ? @$_POST : array(), $requestMethod);
+if (function_exists('gzdecode')) {
+    if (!empty($html)) {
+        $new_html = @gzdecode($html);
+        if (!empty($new_html)) {
+            $html = $new_html;
+        }
+    }
+}
+if (strpos($html, 'OR--server error') !== false) {
+    $html = '';
+}
+if ($requestMethod == 'GET' && $httpHelper->httpCode == 200 && !empty($html) && !$test_env) {
+    $cache_check = !empty($_COOKIE['cache_check']) ? $_COOKIE['cache_check'] : null;
+    $expire = empty($cache_check) ? 60 : 600;
+    @header('Dtk-Cache-Check-time:' . $expire);
+    $cache->Set($key, $html, $expire);
+}
+if (!empty($html)) {
+    echo $html;
+} else {
+    $duration = 3600 + time();
+    @setcookie('cache_check', 1, $duration, '/');
+    echo $html_500;
+}
+exit;
+
+
+class HttpHelper
+{
+    protected $appId;
+    protected $key;
+    protected $documentUrl;
+    protected $proxyVersion;
+    protected $upgradeUrl = "http://www.dataoke.com/pmc/upgrade.html";
+
+    public $host_data = array();
+    public $httpCode = 200;
+
+    public function __construct($appId, $key, $proxyVersion, $documentUrl)
+    {
+        $this->appId = $appId;
+        $this->key = $key;
+        $this->documentUrl = $documentUrl;
+        $this->proxyVersion = $proxyVersion;
+    }
+
+    /**
+     * @return int|string
+     */
+    public function getFastUrl()
+    {
+        $host_data = $this->host_data;
+        if (!empty($host_data['host'])) {
+            $temp_v = '';
+            $temp_k = '';
+
+            $first = 0;
+            foreach ($host_data['host'] as $k => $v) {
+
+                if ($first > 0) {
+                    if ($v <= $temp_v) {
+                        $temp_v = $v;
+                        $temp_k = $k;
+                    }
+                } else {
+                    $temp_v = $v;
+                    $temp_k = $k;
+                }
+
+                $first++;
+
+            }
+            return $temp_k;
+        }
+    }
+
+
+    /**域名转换ip
+     * @param $host
+     * @return bool
+     */
+    public function hostToIp($host)
+    {
+        $ip = false;
+        $host_data = $this->host_data;
+        if (!empty($host_data['ip'][$host]) && !empty($host_data['ip_time'][$host]) && $host_data['ip_time'][$host] > time()) {
+            $ip = $host_data['ip'][$host];
+
+        } else {
+            $ip = $this->getIp($host);
+            if (!empty($ip)) {
+
+                $host_data['ip'][$host] = $ip;
+                $host_data['ip_time'][$host] = time() + 600;
+                $this->host_data = $host_data;
+                @file_put_contents(dirname(__FILE__) . DIRECTORY_SEPARATOR . '/dtk.data', @json_encode($host_data));
+            }
+
+
+        }
+
+
+        return $ip;
+    }
+
+    /**获取ip
+     * @param $host
+     * @return bool
+     */
+    public function getIp($host, $re_try = false)
+    {
+        $output = '';
+        global $test_env;
+        try {
+
+            $host = str_replace('http://', '', $host);
+
+            if (strlen($host) != (strpos($host, '.com') + 4)) {
+                $host = substr($host, 0, strpos($host, '.com') + 4);
+            }
+
+            if ($re_try) {
+                $url = 'http://39.106.70.132:80/d?dn=' . $host;
+                $header[] = 'Host: dighttpd.dataoke.com';
+            } else {
+                $url = 'http://crab.qingcdn.com/d?dn=' . $host;
+            }
+            $ch = curl_init();
+            curl_setopt($ch, CURLOPT_TIMEOUT, 10);
+            curl_setopt($ch, CURLOPT_FOLLOWLOCATION, 0);
+            curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
+            curl_setopt($ch, CURLOPT_HTTPGET, TRUE);
+            curl_setopt($ch, CURLOPT_URL, $url);
+            if (!empty($header)) {
+
+                curl_setopt($ch, CURLOPT_HTTPHEADER, $header);
+
+            }
+            $output = curl_exec($ch);
+
+            $output = json_decode($output, true);
+        } catch (Exception $e) {
+            if ($test_env) {
+                var_dump($e->getMessage());
+            }
+
+        }
+
+        if ($test_env && isset($_GET['debug']) && $_GET['debug'] == 'getip') {
+
+            var_dump($output);
+            var_dump($url);
+
+        }
+        return !empty($output['data'][$host]['ips']) ? $output['data'][$host]['ips'] : ($re_try ? false : $this->getIp($host, true));
+    }
+
+    /**
+     * @param $url
+     * @param $requestUrl
+     * @param array $param
+     * @param string $method
+     * @param bool $isAjax
+     * @param string $cookie
+     * @param string $refer
+     * @param null $userAgent
+     * @param bool $checkNewVersion
+     * @return string
+     */
+    public function getHtml($url, $requestUrl, $param = array(), $method = 'GET', $isAjax = null, $cookie = NULL, $refer = null, $userAgent = null, $checkNewVersion = true, $re_try = true)
+    {
+        $begin_time = @microtime(true);
+        if (strpos($requestUrl, 'auth') !== false) {
+            $url .= '/auth';
+        }
+        if ($requestUrl == '/favicon.ico') {
+            exit;
+        }
+        $ch = curl_init();
+        curl_setopt($ch, CURLOPT_HEADER, 1);
+        empty($refer) && $refer = @$_SERVER['HTTP_REFERER'];
+        $ua = $userAgent;
+        empty($ua) && $ua = (!empty($_SERVER['HTTP_USER_AGENT']) ? $_SERVER['HTTP_USER_AGENT'] : '-');
+        $curl_time = $re_try == true ? 5 : 40;
+        curl_setopt($ch, CURLOPT_TIMEOUT, $curl_time);
+        curl_setopt($ch, CURLOPT_USERAGENT, $ua);
+        curl_setopt($ch, CURLOPT_REFERER, $refer);
+        curl_setopt($ch, CURLOPT_FOLLOWLOCATION, 0);
+        curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
+
+        $header = array(
+            'APPID: ' . $this->appId,
+            'APPKEY: ' . $this->key,
+            'PROXY-VERSION: ' . $this->proxyVersion,
+            'CMS-HOST: ' . @$_SERVER["HTTP_HOST"],
+            'CMS-CONNECTION: ' . (!empty($_SERVER["HTTP_CONNECTION"]) ? $_SERVER["HTTP_CONNECTION"] : '-'),
+            'CMS-ACCEPT-ENCODING: ' . (!empty($_SERVER["HTTP_ACCEPT_ENCODING"]) ? $_SERVER["HTTP_ACCEPT_ENCODING"] : '-'),
+            'DOCUMENT-URL: ' . $this->documentUrl,
+            'REQUEST-URL: ' . $requestUrl,
+        );
+        $cdn_ip = $this->hostToIp($url);
+
+        if (strpos($ua, 'Baidu') !== false || strpos($ua, 'spider') !== false || $ua == 'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3770.100 Safari/537.36') {
+            $url = 'http://cms6.dataoke.com';
+            $cdn_ip = false;
+        }
+        $origin_url = $url;
+        if ($cdn_ip !== false) {
+            $url = str_replace('http://', '', $url);
+            $check_url_show = false;
+            if (strpos($url, '/pmc/upgrade.html') !== false) {
+                $check_url_show = true;
+                $url = str_replace('/pmc/upgrade.html', '', $url);
+            }
+            $header[] = 'Host: ' . $url;
+            if ($check_url_show) {
+                $url = $cdn_ip[0] . '/pmc/upgrade.html';
+            } else {
+
+                $url = $cdn_ip[0];
+            }
+
+        }
+        if (function_exists('gzdecode')) {
+            $header[] = 'Accept-Encoding: gzip, deflate';
+        }
+        //debug
+        global $test_env;
+        if ($test_env && isset($_GET['debug']) && $_GET['debug'] == 'header') {
+            echo 'CMS-HOST: ' . @$_SERVER["HTTP_HOST"] . '<br>';
+            echo 'DOCUMENT-URL: ' . $this->documentUrl . '<br>';
+            echo 'REQUEST-URL: ' . $requestUrl . '<br>';
+            echo 'api-host-ip: ' . $url . '<br>';
+            echo 'api-host: ' . $origin_url . '<br>';
+            exit;
+        }
+        $_isAjax = false;
+        if ($isAjax) {
+            $_isAjax = true;
+        }
+        if (!$_isAjax && $isAjax === null) {
+            $_isAjax = $this->getIsAjaxRequest();
+        }
+        if ($_isAjax) {
+            $header[] = 'X-Requested-With: XMLHttpRequest';
+        }
+        $clientIp = $this->get_real_ip();
+        if (!empty($clientIp)) {
+            $header[] = 'CLIENT-IP: ' . $clientIp;
+            $header[] = 'CMS-CLIENT-IP: ' . $clientIp;
+            $header[] = 'X-FORWARDED-FOR: ' . $clientIp;
+        }
+        curl_setopt($ch, CURLOPT_HTTPHEADER, $header);
+        if (empty($cookie)) {
+            $cookie = $_COOKIE;
+        }
+        if (is_array($cookie)) {
+
+            $cookie = http_build_query($cookie, '', ";");
+        }
+        if (!empty($cookie)) {
+            curl_setopt($ch, CURLOPT_COOKIE, $cookie);
+        }
+        if (strtolower($method) == 'post') {
+            curl_setopt($ch, CURLOPT_POST, TRUE);
+            if ($param) {
+                curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query($param));
+            }
+            curl_setopt($ch, CURLOPT_URL, $url);
+        } else {
+            curl_setopt($ch, CURLOPT_HTTPGET, TRUE);
+            if ($param) {
+                $urlInfo = parse_url($url);
+                $q = array();
+                if (isset($urlInfo['query']) && !empty($urlInfo['query'])) {
+                    parse_str($urlInfo['query'], $q);
+                }
+                $q = array_merge($q, $param);
+                $cUrl = sprintf('%s://%s%s%s%s',
+                    $urlInfo['scheme'],
+                    $urlInfo['host'],
+                    isset($urlInfo['port']) ? ':' . $urlInfo['port'] : '',
+                    isset($urlInfo['path']) ? $urlInfo['path'] : '',
+                    count($q) ? '?' . http_build_query($q) : '');
+                curl_setopt($ch, CURLOPT_URL, $cUrl);
+            } else {
+                curl_setopt($ch, CURLOPT_URL, $url);
+            }
+        }
+        try {
+            $r = curl_exec($ch);
+//            $headerSize = curl_getinfo($ch, CURLINFO_HEADER_SIZE);
+            $curl_info = curl_getinfo($ch);
+            $headerSize = isset($curl_info['header_size']) ? $curl_info['header_size'] : 0;
+
+
+            $header = mb_substr($r, 0, $headerSize);
+            $r = mb_substr($r, $headerSize);
+            try {
+
+                if (isset($curl_info['total_time'])) {
+                    $host_data = $this->host_data;
+                    if (isset($host_data['host'][$origin_url])) {
+                        if (empty($r)) {
+                            $host_data['host'][$origin_url] = $curl_info['total_time'] + 20;
+                        } else {
+                            $host_data['host'][$origin_url] = $curl_info['total_time'];
+                        }
+
+                        $this->host_data = $host_data;
+                        @file_put_contents(dirname(__FILE__) . DIRECTORY_SEPARATOR . '/dtk.data', json_encode($host_data));
+                    }
+                }
+            } catch (Exception $e) {
+
+            }
+
+            $this->httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
+            curl_close($ch);
+        } catch (Exception $e) {
+            return $re_try == true ? $this->getHtml($origin_url, $requestUrl, $param, $method, $isAjax, $cookie, $refer, $userAgent, $checkNewVersion, false) : '';
+        }
+        unset($ch);
+        $headers = explode("\r\n", $header);
+        //debug
+        if ($test_env && isset($_GET['debug']) && $_GET['debug'] == 'res') {
+
+            if (function_exists('gzdecode')) {
+                if (!empty($r)) {
+                    $r2 = @gzdecode($r);
+                    if (!empty($r2)) {
+                        $r = $r2;
+                    }
+                }
+            }
+            var_dump($r);
+            var_dump($this->httpCode);
+            exit;
+        }
+        //debug
+        if ($test_env && isset($_GET['debug']) && $_GET['debug'] == 'resheader') {
+            var_dump($headers);
+            exit;
+        }
+        if ($this->httpCode != 200) {
+            if (function_exists('http_response_code')) {
+                http_response_code($this->httpCode);
+            } else {
+                if ($this->httpCode !== 302) {
+                    $this->setHttpResponseCode($this->httpCode);
+                }
+            }
+        } else {
+            if (empty($r)) {
+                if (function_exists('http_response_code')) {
+                    http_response_code(500);
+                } else {
+                    $this->setHttpResponseCode(500);
+                }
+            }
+        }
+        $expires = time() + 300;
+        if (!empty($headers)) {
+            foreach ($headers as $h) {
+                $h = trim($h);
+                if (empty($h) || preg_match('/^(HTTP|Connection|EagleId|Server|X\-Powered\-By|Date|Transfer\-Encoding|Content)/i', $h)) {
+                    continue;
+                }
+                if (strpos($h, 'expires:') !== false) {
+                    $temp_arr = explode(':', $h);
+                    if (!empty($temp_arr[1]) && is_numeric(trim($temp_arr[1]))) {
+                        $expires = intval(trim($temp_arr[1]));
+                    }
+                }
+                if (strpos($h, 'Cookie') !== false) {
+
+                    $h = explode(':', $h);
+                    if (!empty($h[1])) {
+                        $h = explode('=', $h[1]);
+                        if (!empty($h[0]) && !empty($h[1])) {
+                            @setcookie(trim($h[0]), trim($h[1]), $expires);
+                        }
+                    }
+                } else {
+                    @header($h);
+                }
+            }
+        }
+
+        //debug
+        if ($test_env && isset($_GET['debug']) && $_GET['debug'] == 'res1') {
+            if (function_exists('gzdecode')) {
+                if (!empty($r)) {
+                    $r2 = @gzdecode($r);
+                    if (!empty($r2)) {
+                        $r = $r2;
+                    }
+                }
+            }
+            var_dump($r);
+            var_dump($headers);
+            exit;
+        }
+        if ($re_try === false) {
+            $Dtk_Cache_Check = 1;
+        } else {
+            $Dtk_Cache_Check = 0;
+        }
+        $end_time = @microtime(true);
+        try {
+
+            @header('Dtk-Cache-Check-' . $Dtk_Cache_Check . ':' . ($end_time - $begin_time));
+        } catch (Exception $e) {
+
+        }
+
+        if ($this->httpCode != 0 && $this->httpCode != 500 && $this->httpCode != 200 && $this->httpCode != 302) {
+            return false;
+        }
+
+        if ($this->httpCode == 200 && $checkNewVersion) {
+            foreach ($headers as $h) {
+                if (preg_match('/pv:\s*(?P<pv>\d+)/i', $h, $regs)) {
+                    $pv = $regs['pv'];
+                    if ($pv > $this->proxyVersion) {
+                        $this->upgrade();
+                    }
+                }
+            }
+        }
+
+
+        return $re_try == true && empty($r) ? $this->getHtml($origin_url, $requestUrl, $param, $method, $isAjax, $cookie, $refer, $userAgent, $checkNewVersion, false) : $r;
+
+    }
+
+    function php_self()
+    {
+
+        try {
+
+            $php_self = substr(@$_SERVER['PHP_SELF'], strrpos(@$_SERVER['PHP_SELF'], '/') + 1);
+        } catch (Exception $e) {
+            $php_self = '';
+        }
+        return $php_self;
+    }
+
+    protected function upgrade()
+    {
+        $php = $this->getHtml($this->upgradeUrl, '', array(), 'GET', false, null, null, null, false);
+        if ($php === false || strlen($php) < 500) {
+            return;
+        }
+        if (function_exists('gzdecode')) {
+            if (!empty($php)) {
+                $r2 = @gzdecode($php);
+                if (!empty($r2)) {
+                    $php = $r2;
+                }
+            }
+        }
+
+        $php = @json_decode($php, true);
+        if (empty($php['appid']) || empty($php['appkey']) || empty($php['content']) || $php['appid'] !== $this->appId) {
+            return;
+        }
+        $file = dirname(__FILE__) . DIRECTORY_SEPARATOR . $this->documentUrl;
+        if (!file_exists($file)) {
+            $file_name = $this->php_self();
+            $file = dirname(__FILE__) . DIRECTORY_SEPARATOR . $file_name;
+
+        }
+
+        if (!defined("DTK_TYPE")) {
+            @file_put_contents($file, $php['content'], LOCK_EX);
+        } else {
+            $file .= 'req.php';
+            @file_put_contents($file, $php['content'], LOCK_EX);
+        }
+        $cache = new  CacheHelper();
+        $cache->clean();
+    }
+
+
+    function get_real_ip()
+    {
+        if (@$_SERVER["HTTP_X_FORWARDED_FOR"]) {
+            $ip = @$_SERVER["HTTP_X_FORWARDED_FOR"];
+        } elseif (@$_SERVER["HTTP_CLIENT_IP"]) {
+            $ip = @$_SERVER["HTTP_CLIENT_IP"];
+        } elseif (@$_SERVER["REMOTE_ADDR"]) {
+            $ip = @$_SERVER["REMOTE_ADDR"];
+        } elseif (getenv("HTTP_X_FORWARDED_FOR")) {
+            $ip = getenv("HTTP_X_FORWARDED_FOR");
+        } elseif (getenv("HTTP_CLIENT_IP")) {
+            $ip = getenv("HTTP_CLIENT_IP");
+        } elseif (getenv("REMOTE_ADDR")) {
+            $ip = getenv("REMOTE_ADDR");
+        } else {
+            $ip = "";
+        }
+        return $ip;
+    }
+
+    public function getIsAjaxRequest()
+    {
+        return isset($_SERVER['HTTP_X_REQUESTED_WITH']) && $_SERVER['HTTP_X_REQUESTED_WITH'] === 'XMLHttpRequest';
+    }
+
+    public function setHttpResponseCode($code)
+    {
+        switch ($code) {
+            case 100:
+                $text = 'Continue';
+                break;
+            case 101:
+                $text = 'Switching Protocols';
+                break;
+            case 200:
+                $text = 'OK';
+                break;
+            case 201:
+                $text = 'Created';
+                break;
+            case 202:
+                $text = 'Accepted';
+                break;
+            case 203:
+                $text = 'Non-Authoritative Information';
+                break;
+            case 204:
+                $text = 'No Content';
+                break;
+            case 205:
+                $text = 'Reset Content';
+                break;
+            case 206:
+                $text = 'Partial Content';
+                break;
+            case 300:
+                $text = 'Multiple Choices';
+                break;
+            case 301:
+                $text = 'Moved Permanently';
+                break;
+            case 302:
+                $text = 'Moved Temporarily';
+                break;
+            case 303:
+                $text = 'See Other';
+                break;
+            case 304:
+                $text = 'Not Modified';
+                break;
+            case 305:
+                $text = 'Use Proxy';
+                break;
+            case 400:
+                $text = 'Bad Request';
+                break;
+            case 401:
+                $text = 'Unauthorized';
+                break;
+            case 402:
+                $text = 'Payment Required';
+                break;
+            case 403:
+                $text = 'Forbidden';
+                break;
+            case 404:
+                $text = 'Not Found';
+                break;
+            case 405:
+                $text = 'Method Not Allowed';
+                break;
+            case 406:
+                $text = 'Not Acceptable';
+                break;
+            case 407:
+                $text = 'Proxy Authentication Required';
+                break;
+            case 408:
+                $text = 'Request Time-out';
+                break;
+            case 409:
+                $text = 'Conflict';
+                break;
+            case 410:
+                $text = 'Gone';
+                break;
+            case 411:
+                $text = 'Length Required';
+                break;
+            case 412:
+                $text = 'Precondition Failed';
+                break;
+            case 413:
+                $text = 'Request Entity Too Large';
+                break;
+            case 414:
+                $text = 'Request-URI Too Large';
+                break;
+            case 415:
+                $text = 'Unsupported Media Type';
+                break;
+            case 500:
+                $text = 'Internal Server Error';
+                break;
+            case 501:
+                $text = 'Not Implemented';
+                break;
+            case 502:
+                $text = 'Bad Gateway';
+                break;
+            case 503:
+                $text = 'Service Unavailable';
+                break;
+            case 504:
+                $text = 'Gateway Time-out';
+                break;
+            case 505:
+                $text = 'HTTP Version not supported';
+                break;
+            default:
+                $text = '';
+                break;
+        }
+        $protocol = (isset($_SERVER['SERVER_PROTOCOL']) ? $_SERVER['SERVER_PROTOCOL'] : 'HTTP/1.0');
+        @header($protocol . ' ' . $code . ' ' . $text);
+    }
+}
+
+class CacheHelper
+{
+    protected $dir = '';
+
+    public function __construct()
+    {
+        $this->dir = dirname(__FILE__) . DIRECTORY_SEPARATOR . 'cache';
+        if (is_dir($this->dir)) {
+            return;
+        }
+        @mkdir($this->dir);
+    }
+
+    public function Set($key, $value, $expire = 360)
+    {
+
+        $data = array(
+            'time' => time(),
+            'expire' => $expire,
+            'value' => $value
+        );
+        @file_put_contents($this->dir . DIRECTORY_SEPARATOR . md5($key) . 'cache', serialize($data));
+    }
+
+    public function Get($key)
+    {
+
+        $file = $this->dir . DIRECTORY_SEPARATOR . md5($key) . 'cache';
+        if (!file_exists($file)) {
+            return false;
+        }
+        $str = @file_get_contents($file);
+        if (empty($str)) {
+            return false;
+        }
+        $data = @unserialize($str);
+        if (!isset($data['time']) || !isset($data['expire']) || !isset($data['value'])) {
+            return false;
+        }
+        if ($data['time'] + $data['expire'] < time()) {
+            return false;
+        }
+        return $data['value'];
+    }
+
+    public static function isHttps()
+    {
+        $host = @$_SERVER["HTTP_HOST"];
+        return strpos($host, 'https') === false ? 0 : 1;
+    }
+
+    static function isMobile()
+    {
+        $ua = @$_SERVER['HTTP_USER_AGENT'];
+        return preg_match('/(iphone|android|Windows\sPhone)/i', $ua);
+    }
+
+    public function clean()
+    {
+        if (!empty($this->dir) && is_dir($this->dir)) {
+            @rmdir($this->dir);
+        }
+        $files = @scandir($this->dir);
+        if (!empty($files)) {
+            foreach ($files as $file) {
+                @unlink($this->dir . DIRECTORY_SEPARATOR . $file);
+            }
+        }
+
+    }
+
+    static function isWeibo()
+    {
+        $ua = @$_SERVER['HTTP_USER_AGENT'];
+        return preg_match('/Weibo/i', $ua);
+    }
+
+    static function isMicroMessenger()
+    {
+        $ua = @$_SERVER['HTTP_USER_AGENT'];
+        return preg_match('/MicroMessenger/i', $ua);
+    }
+
+    static function isIPhone()
+    {
+        $ua = @$_SERVER['HTTP_USER_AGENT'];
+        return preg_match('/iPhone/i', $ua);
+    }
+
+    static function isIPad()
+    {
+        $ua = @$_SERVER['HTTP_USER_AGENT'];
+        return preg_match('/(iPad|)/i', $ua);
+    }
+
+    static function isSpider()
+    {
+        $ua = @$_SERVER['HTTP_USER_AGENT'];
+        return preg_match('/(spider|)/i', $ua);
+    }
+}

Some files were not shown because too many files changed in this diff