├── .gitignore ├── .travis.yml ├── README.md ├── app ├── application.php ├── bucket.php ├── control │ ├── agent.php │ └── import.php ├── controller.php ├── daemon.php ├── dispatcher.php ├── model │ ├── consist.php │ ├── router.php │ ├── server.php │ └── table.php ├── queque.php ├── security.php ├── setting.php ├── task.php ├── task │ ├── checktable.php │ ├── delete.php │ ├── example.php │ ├── import.php │ ├── ready.php │ └── rsplit.php ├── worker.php └── worker │ ├── checktable.php │ ├── cleaner.php │ ├── monitor.php │ ├── processor.php │ ├── test.php │ └── uniquebuilder.php ├── before_install.sh ├── bin └── run.php ├── build.xml ├── build ├── http │ └── test_dispatcher_time_out.php ├── parse_array_list.php ├── parse_object_list.php ├── parse_server_list.php ├── phing │ ├── init.xml │ ├── makeconf.xml │ ├── release.xml │ └── unittest.xml └── tpl │ ├── alert.ini │ ├── default.properties │ ├── myfox.ini │ ├── mysql.ini │ ├── mysql_test.ini │ └── security_example.ini ├── database.sql ├── etc └── .gitignore ├── lib ├── TestShell.php ├── alert.php ├── autoload.php ├── blackhole.php ├── cache │ └── apc.php ├── config.php ├── config │ └── ini.php ├── context.php ├── debug │ ├── pool.php │ └── timer.php ├── exception.php ├── factory.php ├── fetcher │ ├── ftp.php │ └── scp.php ├── fileset.php ├── fsplit.php ├── livebox.php ├── log.php ├── mysql.php └── parser │ └── url.php ├── release.properties ├── resource └── bash │ ├── gateway_call_myfox_import.sh │ └── myfoxctl.sh ├── test └── unit │ ├── AlertTest.php │ ├── ApcTest.php │ ├── AutoLoadTest.php │ ├── BlackHoleTest.php │ ├── BucketTest.php │ ├── ChecktableTest.php │ ├── CleanerTest.php │ ├── ConfigTest.php │ ├── ConsistTest.php │ ├── ContextTest.php │ ├── ControllerTest.php │ ├── DaemonTest.php │ ├── DebugTest.php │ ├── DispatcherTest.php │ ├── FactoryTest.php │ ├── FilesetTest.php │ ├── FsplitTest.php │ ├── LiveBoxTest.php │ ├── LogTest.php │ ├── MonitorTest.php │ ├── MysqlTest.php │ ├── ProcessorTest.php │ ├── QuequeTest.php │ ├── RouterTest.php │ ├── SecurityTest.php │ ├── ServerTest.php │ ├── SettingTest.php │ ├── TableTest.php │ ├── TaskTest.php │ ├── UniquebuilderTest.php │ ├── UrlTest.php │ ├── autoload │ └── com │ │ ├── aleafs │ │ ├── autoloadordertestclass.php │ │ ├── autoloadtestcaseclassnamenotmatched.php │ │ └── autoloadtestclass.php │ │ └── autoloadordertestclass.php │ ├── factory │ └── test.php │ ├── ini │ └── config_test.ini │ └── resource │ ├── mirror_import_data_file.txt │ └── numsplit_import_data_file.txt └── www └── index.php /.gitignore: -------------------------------------------------------------------------------- 1 | _private.* 2 | bin/*.sh 3 | build/code_coverage/ 4 | build/unit_test/ 5 | etc/*.ini 6 | test/unit/ini/*.ini 7 | test/unit/tmp/ 8 | resource/agent 9 | resource/bash/route_sync_diff.sh 10 | test/unit/resource/route.diff 11 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: php 2 | php: 3 | - 5.3 4 | 5 | before_script: 6 | - "mysql -uroot < database.sql" 7 | - "/bin/bash ./before_install.sh &> /dev/null" 8 | 9 | script: cp -f ./build/phing.php ./phing/bin/ && ./phing/bin/phing 10 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # 简介 # 2 | 3 | * Myfox是淘宝数据产品部开发的一套应用在OLAP系统上的分布式MySQL集群代理服务. 原始的海量 4 | 数据通常在Hadoop等分布式计算平台上得到了一定程度的计算,然后通过预先定义的分库分表规 5 | 则(route_type)装入不同的mysql服务中;前段应用程序需要从这些分散的mysql中查询数据时, 6 | 通过Myfox代理,实现透明的访问。 7 | 8 | * Myfox的典型应用场景是OLAP系统. 数据在大部分时间内是静态的,数据通过后台离线的方式批量 9 | 写入. 需要实时更新的数据我们不建议通过Myfox来实现;Myfox目前也确实没这个能力,可预见的 10 | 未来应该也不会支持这样的需求。 11 | 12 | * 您看到的这个源码包只是 Myfox 中负责路由计算和数据装载的部分,提供在线透明查询的SQL代理 13 | 部分我们用Node.js做了重新实现,您可以在另外一个源码包里找到. 14 | 15 | # 设计思想 # 16 | 17 | * Myfox的数据装载服务本质上是一个基于HTTP协议和MySQL存储的任务队列系统。在淘宝内部,Myfox 18 | 数据运行在几个虚拟机上,数据是在Hadoop平台上计算并且根据Myfox的路由规则进行切分的。然后 19 | 通过Myfox提供的API远程提交数据装载任务。 20 | 21 | * Myfox用一个常驻进程来处理外部提交的数据装载任务,或者其他任务。这个进程您可以通过下列命 22 | 令来启动: 23 | 24 | ```bash 25 | $ nohup /usr/bin/php bin/run.php processor & 26 | ``` 27 | 28 | # 代码结构 # 29 | 30 | ```bash 31 | . 32 | |-- READEME 自述文件 33 | |-- app Myfox自身类代码 34 | |-- bin 后台运行脚本的启动命令 35 | |-- build build文件,采用phing进行代码build 36 | |-- build.xml 37 | |-- etc 配置文件 38 | |-- lib Myfox无关的PHP Class代码 39 | |-- release.properties release模式的properties文件 40 | |-- resource 部署在Hadoop系统上的shell脚本模版 41 | |-- test 测试代码 42 | `-- www htdocs目录, index.php作为统一入口程序 43 | ``` 44 | 45 | # 环境依赖 # 46 | 47 | * HTTP服务器,且支持php;推荐nginx + php-fpm; 48 | 49 | * php >= 5.3.3, 且需要 mysqli (with mysqlnd)、json、apc、pcntl、ib_binary、ftp和spl支持; 50 | 51 | * 元数据和路由库(MySQL),建议采用M/S结构,支持InnoDB; 52 | 53 | * 数据节点(MySQL),不小于2个. 支持读写账号的分离,支持远程LOAD DATA; 54 | 55 | * 数据文件切分服务器,默认通过ftp进行文件传输,因此需要ftp支持; 56 | 57 | # 部署与配置 # 58 | 59 | * 我们假设: 60 | 61 | > 元数据和路由库MySQL部署在172.0.0.1 (master) 和 172.0.0.2 (slave)上; 62 | > 有两个数据节点(MySQL),分别是192.168.1.1 和 192.168.1.2; 63 | > 所有MySQL通过3306端口对外服务,写账号为db_write,读账号为db_read,密码均为123456; 64 | 65 | * mysql -h172.0.0.1 -P3306 -udb_write -p123456 < database.sql 66 | 67 | * 配置修改: 68 | 69 | 整个项目在开发过程中采用phing来进行build,开发、单元测试以及release所用到的不同配置都用 70 | phing来管理。因此,对于配置文件的管理,我仍然建议你采用phing进行. 71 | 72 | > etc/myfox.ini 是主配置文件,其中 url.prefix 需要根据你的nginx配置而指定; 73 | 74 | > etc/mysql.ini 是元数据和路由库的配置文件,这一数据库实例在代码中以default命名. 75 | 76 | * 测试数据装入: 77 | 78 | ```bash 79 | $ sh bin/gateway_call_myfox_import.sh -tnumsplit_v2 -rthedate=20110610,cid=1 test/unit/resource/numsplit_import_data_file.txt 80 | $ sh bin/gateway_call_myfox_import.sh -tmirror_v2 test/unit/resource/mirror_import_data_file.txt 1 81 | ``` 82 | 83 | # Contributors # 84 | 85 | ``` 86 | project: Myfox-load-module 87 | commits: 57 88 | files : 113 89 | authors: 90 | 53 aleafs 93.0% 91 | 4 Zhiqiang Zhao 7.0% 92 | ``` 93 | 94 | -------------------------------------------------------------------------------- /app/application.php: -------------------------------------------------------------------------------- 1 | | 7 | // +------------------------------------------------------------------------+ 8 | // 9 | // $Id: application.php 18 2010-04-13 15:40:37Z zhangxc83 $ 10 | 11 | namespace Myfox\App; 12 | 13 | class Application 14 | { 15 | 16 | /* {{{ 静态变量 */ 17 | 18 | private static $autoloaded = false; 19 | 20 | /* }}} */ 21 | 22 | /* {{{ public static void setAutoLoad() */ 23 | /** 24 | * 自动加载初始化 25 | * 26 | * @access public static 27 | * @return void 28 | */ 29 | public static function setAutoLoad() 30 | { 31 | if (self::$autoloaded) { 32 | return; 33 | } 34 | 35 | require_once __DIR__ . '/../lib/autoload.php'; 36 | 37 | \Myfox\Lib\AutoLoad::init(); 38 | \Myfox\Lib\AutoLoad::register('myfox\\app', __DIR__); 39 | 40 | self::$autoloaded = true; 41 | } 42 | /* }}} */ 43 | 44 | /* {{{ public static void init() */ 45 | /** 46 | * 各个对象初始化 47 | * 48 | * @access public static 49 | * @return void 50 | */ 51 | public static function init($ini) 52 | { 53 | self::setAutoLoad(); 54 | 55 | /** 56 | * @配置文件 57 | */ 58 | \Myfox\Lib\Config::register('default', $ini); 59 | $config = \Myfox\Lib\Config::instance('default'); 60 | 61 | /** 62 | * @数据库 63 | */ 64 | foreach ((array)$config->get('mysql') AS $name => $file) { 65 | \Myfox\Lib\Mysql::register($name, $file); 66 | } 67 | 68 | /** 69 | * @日志对象 70 | */ 71 | foreach ((array)$config->get('log') AS $name => $url) { 72 | \Myfox\Lib\Factory::registerLog($name, $url); 73 | } 74 | 75 | /** 76 | * @告警提醒 77 | */ 78 | \Myfox\Lib\Alert::init(__DIR__ . '/../etc/alert.ini'); 79 | } 80 | /* }}} */ 81 | 82 | } 83 | -------------------------------------------------------------------------------- /app/bucket.php: -------------------------------------------------------------------------------- 1 | | 7 | // +------------------------------------------------------------------------+ 8 | // 9 | // $Id: bucket.php 22 2010-04-15 16:28:45Z zhangxc83 $ 10 | // 11 | namespace Myfox\App; 12 | 13 | class Bucket 14 | { 15 | 16 | /* {{{ 成员变量 */ 17 | 18 | private $cubage; 19 | 20 | private $maxcnt; 21 | 22 | private $cargos; /**< 原始数据 */ 23 | 24 | private $slices; /**< 切片数据 */ 25 | 26 | private $marks; /**< 使用标记 */ 27 | 28 | /* }}} */ 29 | 30 | /* {{{ public void __construct() */ 31 | /** 32 | * 构造函数 33 | * 34 | * @access public 35 | * @return void 36 | */ 37 | public function __construct($cubage, $float = 0) 38 | { 39 | $this->cubage = (int)abs($cubage); 40 | if (empty($this->cubage)) { 41 | $this->cubage = 2000000; 42 | } 43 | $this->maxcnt = (int)((1 + abs($float)) * $this->cubage); 44 | 45 | $this->cargos = array(); 46 | $this->slices = array(); 47 | } 48 | /* }}} */ 49 | 50 | /* {{{ public void push() */ 51 | /** 52 | * 压入一行数据 53 | * 54 | * @access public 55 | * @param Array $row 56 | * @param Integer $size 57 | * @return void 58 | */ 59 | public function push($row, $size) 60 | { 61 | if ($size < 1) { 62 | return; 63 | } 64 | 65 | $offset = count($this->cargos); 66 | $this->cargos[$offset] = $row; 67 | foreach ($this->split($size) AS $zone) { 68 | $this->slices[] = array( 69 | 'size' => $zone, 70 | 'key' => $offset, 71 | ); 72 | } 73 | } 74 | /* }}} */ 75 | 76 | /* {{{ public Array allot() */ 77 | /** 78 | * 分配 79 | * 80 | * @access public 81 | * @return Array 82 | */ 83 | public function allot() 84 | { 85 | if (!usort($this->slices, function($a, $b) {return $a['size'] > $b['size'] ? -1 : 1;})) { 86 | return false; 87 | } 88 | 89 | $return = array(); 90 | $this->marks = array(); 91 | foreach ($this->slices AS $key => $zone) { 92 | if (!empty($this->marks[$key])) { 93 | continue; 94 | } 95 | $this->marks[$key] = true; 96 | $bucket = array( 97 | array( 98 | 'size' => $zone['size'], 99 | 'data' => $this->cargos[$zone['key']], 100 | ), 101 | ); 102 | $offset = $key; 103 | $remain = $this->maxcnt - $zone['size']; 104 | while ($remain > 0) { 105 | $offset = $this->search($remain, $offset); 106 | if ($offset < 0) { 107 | break; 108 | } 109 | 110 | $bucket[] = array( 111 | 'size' => $this->slices[$offset]['size'], 112 | 'data' => $this->cargos[$this->slices[$offset]['key']], 113 | ); 114 | $this->marks[$offset] = true; 115 | $remain -= $this->slices[$offset]['size']; 116 | } 117 | 118 | $return[] = $bucket; 119 | } 120 | 121 | return $return; 122 | } 123 | /* }}} */ 124 | 125 | /* {{{ private Array split() */ 126 | /** 127 | * 数据切片 128 | * 129 | * @access private 130 | * @param Integer $count 131 | * @return Array 132 | */ 133 | private function split($count) 134 | { 135 | $split = array(); 136 | $count = max(0, (int)$count); 137 | while ($count > $this->maxcnt) { 138 | $count -= $this->cubage; 139 | $split[] = $this->cubage; 140 | } 141 | $split[] = $count; 142 | 143 | return $split; 144 | } 145 | /* }}} */ 146 | 147 | /* {{{ private Integer search() */ 148 | /** 149 | * 二分法查找第一个小于$number的值 150 | * 151 | * @access private 152 | * @param Integer $count 153 | * @return Integer 154 | */ 155 | private function search($number, $left = 0, $right = -1) 156 | { 157 | $count = count($this->slices); 158 | $right = ($right < 0) ? $count : (int)$right; 159 | $middle = (int)(($left + $right) / 2); 160 | $comp = $this->slices[$middle]['size']; 161 | if ($comp <= $number && ($middle - 1 <= $left || $this->slices[$middle - 1]['size'] > $number)) { 162 | for ($i = $middle; $i < $count; $i++) { 163 | if (empty($this->marks[$i])) { 164 | return $i; 165 | } 166 | } 167 | 168 | return -1; 169 | } 170 | 171 | if (abs($right - $left) <= 1) { 172 | return -1; 173 | } 174 | 175 | if ($comp > $number) { 176 | return $this->search($number, $middle, $right); 177 | } 178 | 179 | return $this->search($number, $left, $middle); 180 | } 181 | /* }}} */ 182 | 183 | } 184 | 185 | -------------------------------------------------------------------------------- /app/control/agent.php: -------------------------------------------------------------------------------- 1 | | 7 | // +------------------------------------------------------------------------+ 8 | // 9 | 10 | namespace Myfox\App\Control; 11 | 12 | use \Myfox\Lib\Context; 13 | use \Myfox\App\Security; 14 | 15 | class Agent extends \Myfox\App\Controller 16 | { 17 | 18 | /* {{{ private static void output() */ 19 | /** 20 | * 消息输出 21 | * 22 | * @access private static 23 | * @return void 24 | */ 25 | private static function output($code, $message, $data = '') 26 | { 27 | @header('Content-Type: text/plain;charset=utf-8'); 28 | $header = sprintf("[%u] %s", $code, trim($message)); 29 | echo $header, "\n", is_scalar($data) ? (string)$data : json_encode($data); 30 | } 31 | /* }}} */ 32 | 33 | /* {{{ private static Integer access() */ 34 | private static function access() 35 | { 36 | $secure = new \Myfox\App\Security(__DIR__ . '/../../etc/secure/agent.ini'); 37 | return $secure->priority(Context::userip()); 38 | } 39 | /* }}} */ 40 | 41 | /* {{{ public void actionQueque() */ 42 | /** 43 | * 获取agent任务队列 44 | * 45 | * @access public 46 | * @return void 47 | */ 48 | public function actionQueque($gets, $data = null) 49 | { 50 | } 51 | /* }}} */ 52 | 53 | /* {{{ public void actionCommit() */ 54 | /** 55 | * 更新队列状态 56 | * 57 | * @access public 58 | * @return void 59 | */ 60 | public function actionCommit($gets, $data) 61 | { 62 | } 63 | /* }}} */ 64 | 65 | /* {{{ public void actionCreatetable() */ 66 | /** 67 | * 创建表 68 | * 69 | * @access public 70 | * @return void 71 | */ 72 | public function actionCreatetable($gets, $data = null) 73 | { 74 | } 75 | /* }}} */ 76 | 77 | /* {{{ public void actionDroptable() */ 78 | /** 79 | * 删除表 80 | * 81 | * @access public 82 | * @return void 83 | */ 84 | public function actionDroptable($gets, $data = null) 85 | { 86 | } 87 | /* }}} */ 88 | 89 | } 90 | 91 | -------------------------------------------------------------------------------- /app/controller.php: -------------------------------------------------------------------------------- 1 | | 7 | // +------------------------------------------------------------------------+ 8 | // 9 | // $Id: controller.php 22 2010-04-15 16:28:45Z zhangxc83 $ 10 | 11 | namespace Myfox\App; 12 | 13 | class Controller 14 | { 15 | 16 | /* {{{ public void __construct() */ 17 | /** 18 | * 构造函数 19 | * 20 | * @access public 21 | * @return void 22 | */ 23 | public function __construct() 24 | { 25 | } 26 | /* }}} */ 27 | 28 | /* {{{ public void execute() */ 29 | /** 30 | * 执行请求 31 | * 32 | * @access public 33 | * @return void 34 | */ 35 | public function execute($action, $param, $post = null) 36 | { 37 | $action = strtolower(trim($action)); 38 | if (empty($action)) { 39 | $action = 'index'; 40 | } 41 | 42 | $method = sprintf('action%s', ucfirst($action)); 43 | if (!method_exists($this, $method)) { 44 | throw new \Myfox\Lib\Exception(sprintf('Undefined action named as "%s"', $action)); 45 | } 46 | 47 | return $this->$method($param, $post); 48 | } 49 | /* }}} */ 50 | 51 | /* {{{ protected void actionIndex() */ 52 | /** 53 | * 默认动作 54 | * 55 | * @access protected 56 | * @return void 57 | */ 58 | protected function actionIndex($param, $post = null) 59 | { 60 | echo ''; 61 | } 62 | /* }}} */ 63 | 64 | /* {{{ protected static vars() */ 65 | /** 66 | * 从一组变量中获取某个值 67 | * 68 | * @access protected static 69 | * @return Mixture 70 | */ 71 | protected static function vars($name /*, $set1, $set2, ...*/) 72 | { 73 | $args = func_get_args(); 74 | $name = trim(array_shift($args)); 75 | 76 | foreach ($args AS $map) { 77 | if (isset($map[$name])) { 78 | return $map[$name]; 79 | } 80 | } 81 | 82 | return null; 83 | } 84 | /* }}} */ 85 | 86 | } 87 | 88 | -------------------------------------------------------------------------------- /app/dispatcher.php: -------------------------------------------------------------------------------- 1 | | 7 | // +------------------------------------------------------------------------+ 8 | // 9 | // $Id: dispatcher.php 18 2010-04-13 15:40:37Z zhangxc83 $ 10 | 11 | namespace Myfox\App; 12 | 13 | class Dispatcher 14 | { 15 | 16 | /* {{{ 成员变量 */ 17 | 18 | private $url; 19 | 20 | private $log; 21 | 22 | private $prefix; 23 | 24 | private $config; 25 | 26 | private static $timeout = false; 27 | 28 | /* }}} */ 29 | 30 | /* {{{ public static void run() */ 31 | /** 32 | * 处理器入口 33 | * 34 | * @access public static 35 | * @return void 36 | */ 37 | public static function run($ini, $url, $post = null) 38 | { 39 | $dsp = new self($ini); 40 | $dsp->dispach($url, $post); 41 | 42 | if (empty($GLOBALS['__in_debug_tools']) && function_exists('fastcgi_finish_request')) { 43 | fastcgi_finish_request(); 44 | } 45 | } 46 | /* }}} */ 47 | 48 | /* {{{ public void shutdownCallBack() */ 49 | /** 50 | * 请求结束时的回调函数 51 | * 52 | * @access public 53 | * @return void 54 | */ 55 | public function shutdownCallBack() 56 | { 57 | if (true === self::$timeout) { 58 | $this->log->error('RUN_TIMEOUT', array( 59 | 'url' => $this->url, 60 | )); 61 | } 62 | } 63 | /* }}} */ 64 | 65 | /* {{{ private void __construct() */ 66 | /** 67 | * 构造函数 68 | * 69 | * @access public 70 | * @param String $ini 71 | * @return void 72 | */ 73 | private function __construct($ini) 74 | { 75 | require_once __DIR__ . '/application.php'; 76 | Application::init($ini); 77 | 78 | $this->config = \Myfox\Lib\Config::instance('default'); 79 | $this->prefix = rtrim($this->config->get('url.prefix', ''), '/'); 80 | 81 | $logurl = $this->config->get('log/default'); 82 | if (empty($logurl)) { 83 | $this->log = new \Myfox\Lib\BlackHole(); 84 | } else { 85 | $this->log = new \Myfox\Lib\Log($logurl); 86 | } 87 | } 88 | /* }}} */ 89 | 90 | /* {{{ private void dispach() */ 91 | /** 92 | * 分发处理 93 | * 94 | * @access private 95 | * @return void 96 | */ 97 | private function dispach($url, $post = null) 98 | { 99 | $this->url = preg_replace( 100 | sprintf('/^\/?%s/is', strtr($this->prefix, array('/' => '\\/'))), 101 | '', $url, 1 102 | ); 103 | 104 | $url = new \Myfox\Lib\Parser\Url($this->url); 105 | $module = $url->module(); 106 | if (empty($module)) { 107 | $ctrl = sprintf('Myfox\App\Controller'); 108 | } else { 109 | $ctrl = sprintf('Myfox\App\Control\%s', ucfirst(strtolower($module))); 110 | } 111 | 112 | set_time_limit($this->config->get('run.timeout', 30)); 113 | register_shutdown_function(array(&$this, 'shutdownCallBack')); 114 | try { 115 | self::$timeout = true; 116 | 117 | parse_str($post, $post); 118 | $ctrl = new $ctrl(); 119 | $ctrl->execute($url->action(), $url->param(), array_map('trim', $post)); 120 | $this->log->debug('REQUEST', array( 121 | 'url' => $this->url, 122 | 'post' => $post, 123 | )); 124 | self::$timeout = false; 125 | } catch (\Exception $e) { 126 | self::$timeout = false; 127 | $this->log->error('EXCEPTION', array( 128 | 'url' => $this->url, 129 | 'post' => $post, 130 | 'error' => $e->getMessage(), 131 | )); 132 | } 133 | } 134 | /* }}} */ 135 | 136 | } 137 | 138 | -------------------------------------------------------------------------------- /app/model/consist.php: -------------------------------------------------------------------------------- 1 | | 7 | // +------------------------------------------------------------------------+ 8 | // 9 | // $Id: consist.php 22 2010-04-15 16:28:45Z zhangxc83 $ 10 | // 11 | // 12 | namespace Myfox\App\Model; 13 | 14 | use \Myfox\App\Model\Server; 15 | 16 | class Consist 17 | { 18 | 19 | /* {{{ 静态常量 */ 20 | 21 | const COUNTS = 1; 22 | const BITXOR = 2; 23 | 24 | /* }}} */ 25 | 26 | /* {{{ 静态变量 */ 27 | 28 | private static $callers = array( 29 | self::COUNTS => 'counts', 30 | self::BITXOR => 'bitxor', 31 | ); 32 | 33 | private static $columns = array(); 34 | 35 | /* }}} */ 36 | 37 | /* {{{ public static Mixture check() */ 38 | /** 39 | * 检查多个服务器之间表的数据一致性 40 | * 41 | * @access public static 42 | * @return Mixture 43 | */ 44 | public static function check($table, $server, $type = null) 45 | { 46 | if (empty($server)) { 47 | throw new \Myfox\Lib\Exception('Empty server list for consistency check'); 48 | } 49 | 50 | $dbname = self::idxname($table); 51 | if (empty(self::$columns[$dbname]) && !self::describe($table, $server, $dbname)) { 52 | return true; 53 | } 54 | 55 | $querys = array(); 56 | $type = empty($type) ? (self::COUNTS | self::BITXOR) : (int)$type; 57 | foreach (self::$callers AS $key => $call) { 58 | if (0 < ($key & $type)) { 59 | $querys[] = self::$call($table); 60 | } 61 | } 62 | 63 | if (empty($querys)) { 64 | return false; 65 | } 66 | 67 | $query = implode(' UNION ALL ', $querys); 68 | $pools = array(); 69 | foreach ((array)$server AS $name) { 70 | $pools[$name] = Server::instance($name)->getlink()->async($query); 71 | } 72 | 73 | $result = array(); 74 | foreach ($pools AS $name => $id) { 75 | $db = Server::instance($name)->getlink(); 76 | foreach ((array)$db->getAll($db->wait($id)) AS $row) { 77 | $result[$name][$row['c0']] = $row['c1']; 78 | } 79 | } 80 | 81 | $item = reset($result); 82 | $index = key($result); 83 | foreach ($result AS $key => $val) { 84 | if ($key == $index) { 85 | continue; 86 | } 87 | 88 | if ($item != $val) { 89 | return $result; 90 | } 91 | } 92 | 93 | return true; 94 | } 95 | /* }}} */ 96 | 97 | /* {{{ private static String idxname() */ 98 | /** 99 | * 获取归一化的表键 100 | * 101 | * @access private static 102 | * @return String 103 | */ 104 | private static function idxname($table) 105 | { 106 | $tbname = explode('.', $table); 107 | return strtolower(preg_replace('/_\d+$/', '', reset($tbname))); 108 | } 109 | /* }}} */ 110 | 111 | /* {{{ private static void describe() */ 112 | /** 113 | * 获取表结构 114 | * 115 | * @access private static 116 | * @return void 117 | */ 118 | private static function describe($table, $server, $dbname) 119 | { 120 | foreach ((array)$server AS $name) { 121 | $mysql = Server::instance($name)->getlink(); 122 | $column = $mysql->getAll($mysql->query(sprintf('DESC %s', $table))); 123 | if (!empty($column)) { 124 | break; 125 | } 126 | } 127 | 128 | if (empty($column)) { 129 | return false; 130 | } 131 | 132 | $pk = 'autokid'; 133 | $cl = array(); 134 | foreach ((array)$column AS $row) { 135 | if (0 === strcasecmp('PRI', $row['Key'])) { 136 | $pk = $row['Field']; 137 | } 138 | 139 | if (0 !== strcasecmp('auto_increment', $row['Extra'])) { 140 | $cl[] = $row['Field']; 141 | } 142 | } 143 | 144 | self::$columns[$dbname] = array( 145 | 'prikey' => $pk, 146 | 'column' => implode(',', $cl), 147 | ); 148 | 149 | return true; 150 | } 151 | /* }}} */ 152 | 153 | /* {{{ private static String counts() */ 154 | /** 155 | * 求表的总行数 156 | * 157 | * @access private static 158 | * @return String 159 | */ 160 | private static function counts($table) 161 | { 162 | return sprintf("SELECT 'counts' AS c0, COUNT(1) AS c1 FROM %s", $table); 163 | } 164 | /* }}} */ 165 | 166 | /* {{{ private static String bitxor() */ 167 | /** 168 | * 求表的BITXOR值 169 | * 170 | * @access private static 171 | * @return String 172 | */ 173 | private static function bitxor($table) 174 | { 175 | $dbname = self::idxname($table); 176 | $query = sprintf( 177 | "SELECT crc32(CONCAT_WS('', %s)) AS a FROM %s ORDER BY %s DESC LIMIT 100", 178 | self::$columns[$dbname]['column'], $table, self::$columns[$dbname]['prikey'] 179 | ); 180 | 181 | return sprintf( 182 | "SELECT 'bitxor' AS c0, BIT_XOR(a) AS c1 FROM (%s) b", 183 | $query 184 | ); 185 | } 186 | /* }}} */ 187 | 188 | } 189 | -------------------------------------------------------------------------------- /app/model/server.php: -------------------------------------------------------------------------------- 1 | | 5 | // +------------------------------------------------------------------------+ 6 | // 7 | // $Id: server.php 18 2010-04-13 15:40:37Z zhangxc83 $ 8 | 9 | namespace Myfox\App\Model; 10 | 11 | class Server 12 | { 13 | 14 | /* {{{ 静态常量 */ 15 | 16 | const TYPE_REALITY = 1; 17 | const TYPE_ARCHIVE = 2; 18 | 19 | const STAT_ONLINE = 0; 20 | const STAT_PREPARE = 1; 21 | const STAT_ISDOWN = 9; 22 | 23 | /* }}} */ 24 | 25 | /* {{{ 静态变量 */ 26 | 27 | private static $objects = array(); 28 | 29 | private static $mysql = null; 30 | 31 | /* }}} */ 32 | 33 | /* {{{ 成员变量 */ 34 | 35 | private $dbname = ''; 36 | 37 | private $option = null; 38 | 39 | /* }}} */ 40 | 41 | /* {{{ public static Object instance() */ 42 | /** 43 | * 获取一个实例 44 | * 45 | * @access public static 46 | * @return Object 47 | */ 48 | public static function instance($name) 49 | { 50 | $name = strtolower(trim($name)); 51 | if (empty(self::$objects[$name])) { 52 | self::$objects[$name] = new self($name); 53 | } 54 | 55 | return self::$objects[$name]; 56 | } 57 | /* }}} */ 58 | 59 | /* {{{ public Mixture option() */ 60 | /** 61 | * 返回属性 62 | * 63 | * @access public 64 | * @return Mixture 65 | */ 66 | public function option($key, $default = null) 67 | { 68 | $this->init(); 69 | $key = strtolower(trim($key)); 70 | return isset($this->option[$key]) ? $this->option[$key] : $default; 71 | } 72 | /* }}} */ 73 | 74 | /* {{{ public Object getlink() */ 75 | /** 76 | * 获取数据库连接 77 | * 78 | * @access public 79 | * @return object 80 | */ 81 | public function getlink() 82 | { 83 | $this->init(); 84 | $id = sprintf('__%s', $this->dbname); 85 | 86 | try { 87 | return \Myfox\Lib\Mysql::instance($id); 88 | } catch (\Exception $e) { 89 | \Myfox\Lib\Mysql::register($id, array( 90 | 'persist' => false, 91 | 'master' => array(sprintf( 92 | 'mysql://%s:%s@%s:%d', rawurlencode($this->option['write_user']), 93 | rawurlencode($this->option('write_pass')), 94 | $this->option['conn_host'], $this->option['conn_port'] 95 | )), 96 | 'slave' => array(sprintf( 97 | 'mysql://%s:%s@%s:%d', rawurlencode($this->option['read_user']), 98 | rawurlencode($this->option('read_pass')), 99 | $this->option['conn_host'], $this->option['conn_port'] 100 | )), 101 | )); 102 | 103 | return \Myfox\Lib\Mysql::instance($id); 104 | } 105 | } 106 | /* }}} */ 107 | 108 | /* {{{ private void __construct() */ 109 | /** 110 | * 构造函数 111 | * 112 | * @access private 113 | * @return void 114 | */ 115 | private function __construct($name) 116 | { 117 | if (empty(self::$mysql)) { 118 | self::$mysql = \Myfox\Lib\Mysql::instance('default'); 119 | } 120 | 121 | $this->dbname = $name; 122 | $this->option = null; 123 | } 124 | /* }}} */ 125 | 126 | /* {{{ private void init() */ 127 | /** 128 | * 初始化server对象 129 | * 130 | * @access private 131 | * @return void 132 | */ 133 | private function init() 134 | { 135 | if (null !== $this->option) { 136 | return; 137 | } 138 | 139 | $this->option = self::$mysql->getRow(self::$mysql->query(sprintf( 140 | "SELECT * FROM %shost_list WHERE host_name = '%s'", 141 | self::$mysql->option('prefix'), self::$mysql->escape($this->dbname) 142 | ))); 143 | if (empty($this->option)) { 144 | throw new \Myfox\Lib\Exception(sprintf( 145 | 'Undefined mysql server named as "%s"', $this->dbname 146 | )); 147 | } 148 | $this->option = array_change_key_case((array)$this->option, CASE_LOWER); 149 | } 150 | /* }}} */ 151 | 152 | } 153 | -------------------------------------------------------------------------------- /app/security.php: -------------------------------------------------------------------------------- 1 | | 9 | // +------------------------------------------------------------------------+ 10 | 11 | namespace Myfox\App; 12 | 13 | use \Myfox\Lib\Config; 14 | 15 | class Security 16 | { 17 | 18 | /* {{{ 成员变量 */ 19 | 20 | private $config; 21 | 22 | /* }}} */ 23 | 24 | /* {{{ public void __construct() */ 25 | /** 26 | * 27 | * 构造函数 28 | * 29 | * @access public 30 | * @return void 31 | */ 32 | public function __construct($ini) 33 | { 34 | $this->config = new Config($ini); 35 | } 36 | /* }}} */ 37 | 38 | /* {{{ public Boolean priority() */ 39 | /** 40 | * 获取优先级 41 | * 42 | * @access public 43 | * @return Boolean true or false 44 | */ 45 | public function priority($remote) 46 | { 47 | $allows = (array)$this->config->get('priority'); 48 | if (isset($allows[$remote])) { 49 | return $allows[$remote]; 50 | } 51 | 52 | foreach ($allows AS $rule => $value) { 53 | if (self::ipmatch($remote, $rule)) { 54 | return $value; 55 | } 56 | } 57 | 58 | return false; 59 | } 60 | /* }}} */ 61 | 62 | /* {{{ public Mixture modify() */ 63 | /** 64 | * 修改参数 65 | * 66 | * @access public 67 | * @return Mixture 68 | */ 69 | public function modify($param) 70 | { 71 | return (array)$this->config->get('option') + (array)$param; 72 | } 73 | /* }}} */ 74 | 75 | /* {{{ private static Boolean ipmatch() */ 76 | /** 77 | * 判断IP地址匹配 78 | * 79 | * @access private static 80 | * @return Boolean true or false 81 | */ 82 | private static function ipmatch($ip, $str) 83 | { 84 | $ps = -1; 85 | $at = array_filter(explode('*', trim($str))); 86 | foreach ($at AS $tk) { 87 | $ps = strpos($ip, $tk, $ps + 1); 88 | if (false === $ps) { 89 | return false; 90 | } 91 | } 92 | 93 | return true; 94 | } 95 | /* }}} */ 96 | 97 | } 98 | -------------------------------------------------------------------------------- /app/setting.php: -------------------------------------------------------------------------------- 1 | | 7 | // +------------------------------------------------------------------------+ 8 | // 9 | // $Id: setting.php 22 2010-04-15 16:28:45Z zhangxc83 $ 10 | 11 | namespace Myfox\App; 12 | 13 | class Setting 14 | { 15 | 16 | /* {{{ 静态变量 */ 17 | 18 | public static $queries = 0; 19 | 20 | private static $mysql; 21 | 22 | private static $expire = 60; 23 | 24 | private static $option = array(); 25 | 26 | /* }}} */ 27 | 28 | /* {{{ public static void init() */ 29 | /** 30 | * 初始化 31 | * 32 | * @access public static 33 | * @return void 34 | */ 35 | public static function init($expire = 60) 36 | { 37 | self::$expire = (int)$expire; 38 | if (empty(self::$mysql)) { 39 | self::$mysql = \Myfox\Lib\Mysql::instance('default'); 40 | } 41 | } 42 | /* }}} */ 43 | 44 | /* {{{ public static Mixture get() */ 45 | /** 46 | * 获取配置值 47 | * 48 | * @access public static 49 | * @return Mixture 50 | */ 51 | public static function get($key, $own = '') 52 | { 53 | $now = microtime(true); 54 | $idx = self::idx($key, $own); 55 | if (!isset(self::$option[$idx]) || $now > self::$option[$idx]['t']) { 56 | self::init(self::$expire); 57 | 58 | $option = self::$mysql->getAll(self::$mysql->query(sprintf( 59 | "SELECT cfgname,ownname,cfgvalue FROM %ssettings WHERE ownname='%s'", 60 | self::$mysql->option('prefix', ''), 61 | self::$mysql->escape($own) 62 | ))); 63 | foreach ((array)$option AS $row) { 64 | self::$option[self::idx($row['cfgname'], $row['ownname'])] = array( 65 | 't' => $now + self::$expire, 66 | 'v' => $row['cfgvalue'], 67 | ); 68 | } 69 | self::$queries++; 70 | } 71 | 72 | return isset(self::$option[$idx]['v']) ? self::$option[$idx]['v'] : null; 73 | } 74 | /* }}} */ 75 | 76 | /* {{{ public static Boolean set() */ 77 | /** 78 | * 设置配置值 79 | * 80 | * @access public static 81 | * @return Boolean true or false 82 | */ 83 | public static function set($key, $value, $own = '', $comma = true) 84 | { 85 | unset(self::$option[self::idx($key, $own)]); 86 | self::init(self::$expire); 87 | self::$queries++; 88 | 89 | $time = date('Y-m-d H:i:s'); 90 | $value = self::$mysql->escape($value); 91 | if ($comma) { 92 | $value = sprintf("'%s'", $value); 93 | } 94 | 95 | return self::$mysql->query(sprintf( 96 | "INSERT INTO %ssettings (cfgname,ownname,cfgvalue,addtime,modtime) VALUES ('%s','%s',%s,'%s','%s')" . 97 | " ON DUPLICATE KEY UPDATE modtime = '%s',cfgvalue=%s", 98 | self::$mysql->option('prefix', ''), self::$mysql->escape($key), self::$mysql->escape($own), 99 | $value, $time, $time, $time, $value 100 | )); 101 | } 102 | /* }}} */ 103 | 104 | /* {{{ private static string idx() */ 105 | /** 106 | * 组织KEY名字 107 | * 108 | * @access private static 109 | * @return String 110 | */ 111 | private static function idx($key, $own) 112 | { 113 | return strtolower(sprintf('%s/%s', trim($own), trim($key))); 114 | } 115 | /* }}} */ 116 | 117 | } 118 | -------------------------------------------------------------------------------- /app/task/checktable.php: -------------------------------------------------------------------------------- 1 | | 10 | // +------------------------------------------------------------------------+ 11 | // 12 | // $Id: checktable.php 18 2010-04-13 15:40:37Z zhangxc83 $ 13 | // 14 | 15 | namespace Myfox\App\Task; 16 | 17 | use \Myfox\App\Model\Server; 18 | 19 | class Checktable extends \Myfox\App\Task 20 | { 21 | 22 | /* {{{ public Integer execute() */ 23 | /** 24 | * 执行check table操作 25 | */ 26 | public function execute() 27 | { 28 | if (!$this->isReady('host', 'path')) { 29 | return self::IGNO; 30 | } 31 | 32 | $exist = sprintf('DESC %s', self::$mysql->escape($this->option('path'))); 33 | $query = sprintf('CHECK TABLE %s', self::$mysql->escape($this->option('path'))); 34 | 35 | $return = self::FAIL; 36 | $ignore = array_flip(explode(',', (string)$this->status)); 37 | foreach (explode(',', trim($this->option('host', '{}'))) AS $id) { 38 | if (!isset(self::$hosts[$id]) || isset($ignore[$id])) { 39 | continue; 40 | } 41 | 42 | $mysql = Server::instance(self::$hosts[$id]['name'])->getlink(); 43 | if ((bool)$mysql->query($exist)) { 44 | 45 | // @see: http://dev.mysql.com/doc/refman/5.1/en/check-table.html 46 | 47 | $ok = false; 48 | foreach (array('FAST', 'MEDIUM', 'MEDIUM', 'MEDIUM', 'EXTENDED') AS $mode) { 49 | if (self::success($mysql->getAll($mysql->query(sprintf('%s %s', $query, $mode))))) { 50 | $ok = true; 51 | break; 52 | } 53 | } 54 | 55 | if (!$ok) { 56 | continue; 57 | } 58 | } 59 | 60 | $return = self::SUCC; 61 | $ignore[$id] = true; 62 | } 63 | 64 | return $return; 65 | } 66 | /* }}} */ 67 | 68 | /* {{{ private static Boolean success() */ 69 | /** 70 | * 判断check table是否成功 71 | * 72 | * @access private static 73 | * @return Boolean true or false 74 | */ 75 | private static function success($result) 76 | { 77 | if (empty($result) || !is_array($result)) { 78 | return false; 79 | } 80 | 81 | foreach ((array)$result AS $row) { 82 | if (!isset($row['Msg_type']) || !isset($row['Msg_text'])) { 83 | continue; 84 | } 85 | if ('status' == $row['Msg_type'] && 0 != strcasecmp('OK', $row['Msg_text'])) { 86 | return false; 87 | } 88 | } 89 | 90 | return true; 91 | } 92 | /* }}} */ 93 | 94 | } 95 | 96 | -------------------------------------------------------------------------------- /app/task/delete.php: -------------------------------------------------------------------------------- 1 | | 5 | // +------------------------------------------------------------------------+ 6 | // 7 | // $Id: delete.php 18 2010-04-13 15:40:37Z zhangxc83 $ 8 | // 9 | 10 | namespace Myfox\App\Task; 11 | 12 | use \Myfox\App\Model\Server; 13 | 14 | class Delete extends \Myfox\App\Task 15 | { 16 | 17 | /* {{{ 成员变量 */ 18 | 19 | private $optimize = false; 20 | 21 | private $dbpools = array(); 22 | 23 | /* }}} */ 24 | 25 | /* {{{ public Integer execute() */ 26 | public function execute() 27 | { 28 | if (!$this->isReady('host', 'path')) { 29 | return self::IGNO; 30 | } 31 | 32 | self::metadata($flush); 33 | 34 | $where = trim((string)$this->option('where')); 35 | if ('' == $where) { 36 | $query = sprintf('DROP TABLE IF EXISTS %s', $this->option('path')); 37 | } else { 38 | $query = sprintf('DELETE FROM %s WHERE %s', $this->option('path'), $where); 39 | $this->optimize = true; 40 | } 41 | 42 | $ignore = array_flip(explode(',', (string)$this->status)); 43 | foreach (explode(',', trim($this->option('host', '{}'))) AS $id) { 44 | if (!isset(self::$hosts[$id]) || isset($ignore[$id])) { 45 | continue; 46 | } 47 | 48 | $ignore[$id] = true; 49 | $server = self::$hosts[$id]; 50 | $db = Server::instance($server['name'])->getlink(); 51 | $this->dbpools[] = array($db, $db->async($query), $server['name']); 52 | } 53 | 54 | return self::WAIT; 55 | } 56 | /* }}} */ 57 | 58 | /* {{{ public Integer wait() */ 59 | public function wait() 60 | { 61 | $rt = self::SUCC; 62 | $sc = array(); 63 | foreach ($this->dbpools AS $pool) { 64 | list($db, $key, $host) = $pool; 65 | if (false === $key || false === $db->wait($key)) { 66 | $rt = self::FAIL; 67 | $this->setError($db->lastError($key)); 68 | } else { 69 | $sc[$host] = true; 70 | if ($this->optimize) { 71 | $db->async(sprintf('OPTIMIZE TABLE %s', $this->option('path'))); 72 | } 73 | } 74 | } 75 | 76 | $this->dbpools = array(); 77 | $this->result = implode(',', array_keys($sc)); 78 | 79 | return $rt; 80 | } 81 | /* }}} */ 82 | 83 | } 84 | -------------------------------------------------------------------------------- /app/task/example.php: -------------------------------------------------------------------------------- 1 | | 5 | // +------------------------------------------------------------------------+ 6 | // 7 | // $Id: example.php 18 2010-04-13 15:40:37Z zhangxc83 $ 8 | // 9 | 10 | namespace Myfox\App\Task; 11 | 12 | class Example extends \Myfox\App\Task 13 | { 14 | 15 | public $counter = 0; 16 | 17 | public function execute() 18 | { 19 | if (!$this->isReady('type')) { 20 | return self::FAIL; 21 | } 22 | 23 | $this->counter++; 24 | return self::SUCC; 25 | } 26 | 27 | public function wait() 28 | { 29 | $this->setError('None sense for wait'); 30 | return self::FAIL; 31 | } 32 | 33 | } 34 | 35 | -------------------------------------------------------------------------------- /app/task/ready.php: -------------------------------------------------------------------------------- 1 | | 7 | // +------------------------------------------------------------------------+ 8 | // 9 | 10 | namespace Myfox\App\Task; 11 | 12 | use \Myfox\App\Setting; 13 | use \Myfox\App\Model\Router; 14 | 15 | class Ready extends \Myfox\App\Task 16 | { 17 | 18 | /* {{{ public Integer wait() */ 19 | public function wait() 20 | { 21 | return self::SUCC; 22 | } 23 | /* }}} */ 24 | 25 | /* {{{ public Integer execute() */ 26 | public function execute() 27 | { 28 | if (!$this->isReady('thedate', 'priority')) { 29 | return self::IGNO; 30 | } 31 | 32 | $date1 = date('Ymd', strtotime(Setting::get('last_date'))); 33 | $date2 = date('Ymd', strtotime($this->option('thedate'))); 34 | if ($date1 >= $date2) { 35 | return self::IGNO; 36 | } 37 | 38 | $mysql = \Myfox\Lib\Mysql::instance('default'); 39 | $count = (int)$mysql->getOne($mysql->query(sprintf( 40 | 'SELECT COUNT(*) FROM %stask_queque WHERE autokid < %u AND priority <= %u '. 41 | "AND task_flag NOT IN (%d,%d) AND task_type='import'", 42 | $mysql->option('prefix'), $this->id, $this->option('priority'), 43 | \Myfox\App\Queque::FLAG_IGNO, \Myfox\App\Queque::FLAG_DONE 44 | ))); 45 | 46 | if ($count > 0) { 47 | $this->setError(sprintf('Waiting for %d import task(s)', $count)); 48 | return self::FAIL; 49 | } 50 | 51 | if (!Router::flush()) { 52 | $this->setError('flush route failed'); 53 | return self::FAIL; 54 | } 55 | 56 | Setting::set('last_date', $date2, '', '集群数据最新日期'); 57 | 58 | // TODO: 59 | // CALL nodefox to reload route info info cache 60 | 61 | return self::SUCC; 62 | } 63 | /* }}} */ 64 | 65 | } 66 | 67 | -------------------------------------------------------------------------------- /app/task/rsplit.php: -------------------------------------------------------------------------------- 1 | | 7 | // +------------------------------------------------------------------------+ 8 | // 9 | // $Id: rsplit.php 18 2010-04-13 15:40:37Z zhangxc83 $ 10 | // 11 | 12 | namespace Myfox\App\Task; 13 | 14 | use \Myfox\Lib\Config; 15 | use \Myfox\Lib\Fileset; 16 | use \Myfox\Lib\Fsplit; 17 | 18 | use \Myfox\App\Queque; 19 | use \Myfox\App\Model\Router; 20 | use \Myfox\App\Model\Table; 21 | 22 | class Rsplit extends \Myfox\App\Task 23 | { 24 | 25 | /* {{{ public Integer execute() */ 26 | /** 27 | * OPTIONS: 28 | * ---------------------------------------------------------------------- 29 | * @file : complete file url, such as "ftp://user:pass@hostname/path" 30 | * @table : name of the logic table 31 | * @route : route value of the file, ex: thedate=20111001,cid=210 32 | * @lines : 33 | * --------------------------------------------------------------------- 34 | */ 35 | public function execute() 36 | { 37 | if (!$this->isReady('table', 'route', 'lines', 'file', 'priority')) { 38 | return self::IGNO; 39 | } 40 | 41 | $table = Table::instance($this->option('table')); 42 | if (!$table->get('autokid')) { 43 | $this->setError(sprintf('Undefined table named as "%s".', $this->option('table'))); 44 | return self::IGNO; 45 | } 46 | 47 | try { 48 | $routes = Router::set($this->option('table'), array(array( 49 | 'field' => Router::parse($this->option('route'), $this->option('table')), 50 | 'count' => (int)$this->option('lines') 51 | ))); 52 | if (!is_array($routes)) { 53 | $this->setError('route failed.'); 54 | return self::FAIL; 55 | } 56 | 57 | $config = Config::instance('default'); 58 | $fname = Fileset::getfile($this->option('file'), $config->get('path/download')); 59 | if (empty($fname)) { 60 | $this->setError(sprintf('getfile:%s', Fileset::lastError())); 61 | return self::FAIL; 62 | } 63 | 64 | $splits = array(); 65 | foreach ($routes AS $key => $bucket) { 66 | foreach ($bucket AS $item) { 67 | $splits[] = $item['rows']; 68 | } 69 | } 70 | 71 | $fsplit = new Fsplit($fname, "\n", 16777216); 72 | $chunks = $fsplit->split($splits, $config->get('path/filesplit')); 73 | if (empty($chunks)) { 74 | $this->setError(sprintf('fsplit failed,%s', $fsplit->lastError())); 75 | return self::FAIL; 76 | } 77 | 78 | if (preg_match('/^\w+:/i', $this->option('file'))) { 79 | @unlink($fname); 80 | } 81 | 82 | $result = array(); 83 | $queque = Queque::instance(); 84 | foreach ($routes AS $key => $bucket) { 85 | foreach ($bucket AS $item) { 86 | $fname = array_shift($chunks); 87 | if (empty($fname)) { 88 | break 2; 89 | } 90 | 91 | $info = array( 92 | 'table' => $this->option('table'), 93 | 'route' => $key, 94 | 'file' => $fname, 95 | 'bucket' => $item['table'], 96 | 'hosts' => $item['hosts'], 97 | ); 98 | 99 | $option = array( 100 | 'openrace' => 0, 101 | 'priority' => (int)$this->option('priority') - 1, 102 | 'trytimes' => 3, 103 | 'task_flag' => Queque::FLAG_WAIT, 104 | 'adduser' => 'rsplit', 105 | ); 106 | 107 | if (!$queque->insert('import', $info, -1, $option)) { 108 | $this->setError(sprintf('queque: %s', $queque->lastError())); 109 | return self::FAIL; 110 | } 111 | 112 | $result[] = $queque->lastId(); 113 | } 114 | } 115 | $this->result = implode(',', $result); 116 | } catch (\Exception $e) { 117 | $this->setError($e->getMessage()); 118 | return self::FAIL; 119 | } 120 | 121 | return self::SUCC; 122 | } 123 | /* }}} */ 124 | 125 | } 126 | 127 | -------------------------------------------------------------------------------- /app/worker.php: -------------------------------------------------------------------------------- 1 | | 9 | // +------------------------------------------------------------------------+ 10 | 11 | namespace Myfox\App; 12 | 13 | class Worker 14 | { 15 | 16 | /* {{{ 成员变量 */ 17 | 18 | protected $option = array(); 19 | 20 | protected $log = null; 21 | 22 | /* }}} */ 23 | 24 | /* {{{ public void __construct() */ 25 | /** 26 | * 构造函数 27 | * 28 | * @access public 29 | * @return void 30 | */ 31 | public function __construct($option) 32 | { 33 | $this->option = (array)$option + $this->option; 34 | $this->cleanup(); 35 | $this->log = \Myfox\Lib\Factory::getLog('daemon'); 36 | } 37 | /* }}} */ 38 | 39 | /* {{{ public void cleanup() */ 40 | /** 41 | * 临时数据清理接口 42 | * 43 | * @access public 44 | * @return void 45 | */ 46 | public function cleanup() 47 | { 48 | } 49 | /* }}} */ 50 | 51 | /* {{{ public Boolean execute() */ 52 | /** 53 | * 执行 54 | * 55 | * @access public 56 | * @return Boolean true or false (next loop) 57 | */ 58 | public function execute($loop = true) 59 | { 60 | return (bool)$loop; 61 | } 62 | /* }}} */ 63 | 64 | /* {{{ public Integer interval() */ 65 | /** 66 | * 下次运行sleep时间(ms) 67 | * 68 | * @access public 69 | * @return Integer 70 | */ 71 | public function interval() 72 | { 73 | return 1; 74 | } 75 | /* }}} */ 76 | 77 | /* {{{ public String locker() */ 78 | /** 79 | * 进程锁名字 80 | * 81 | * @access public 82 | * @return String 83 | */ 84 | public function locker() 85 | { 86 | return ''; 87 | } 88 | /* }}} */ 89 | 90 | /* {{{ public static void breakup() */ 91 | /** 92 | * 处理系统信号的断点 93 | * 94 | * @access public static 95 | * @return void 96 | */ 97 | public static function breakup() 98 | { 99 | if (function_exists('pcntl_signal_dispatch')) { 100 | pcntl_signal_dispatch(); 101 | } 102 | } 103 | /* }}} */ 104 | 105 | } 106 | 107 | -------------------------------------------------------------------------------- /app/worker/checktable.php: -------------------------------------------------------------------------------- 1 | | 9 | // +------------------------------------------------------------------------+ 10 | 11 | namespace Myfox\App\Worker; 12 | 13 | use \Myfox\App\Model\Server; 14 | 15 | class Checktable extends \Myfox\App\Worker 16 | { 17 | 18 | /* {{{ 成员变量 */ 19 | 20 | protected $option = array( 21 | 'sleep' => 300000, /**< 300s */ 22 | 'full' => false, /**< full check */ 23 | ); 24 | 25 | /* }}} */ 26 | 27 | /* {{{ public String locker() */ 28 | /** 29 | * 进程锁名字 30 | * 31 | * @access public 32 | * @return String 33 | */ 34 | public function locker() 35 | { 36 | return 'checktable'; 37 | } 38 | /* }}} */ 39 | 40 | /* {{{ public Integer interval() */ 41 | /** 42 | * 下次运行sleep时间(ms) 43 | * 44 | * @access public 45 | * @return Integer 46 | */ 47 | public function interval() 48 | { 49 | return $this->option['sleep']; 50 | } 51 | /* }}} */ 52 | 53 | /* {{{ public Boolean execute() */ 54 | /** 55 | * 执行 56 | * 57 | * @access public 58 | * @return Boolean true or false (next loop) 59 | */ 60 | public function execute($loop = true) 61 | { 62 | $mysql = \Myfox\Lib\Mysql::instance('default'); 63 | 64 | $count = 0; 65 | $query = sprintf('SELECT host_id AS id, host_name AS name FROM %shost_list', $mysql->option('prefix')); 66 | foreach ((array)$mysql->getAll($mysql->query($query)) AS $host) { 67 | $db = Server::instance($host['name'])->getlink(); 68 | try { 69 | $databases = $db->getAll($db->query('SHOW DATABASES')); 70 | } catch (\Exception $e) { 71 | $this->log->error('EXCEPTION', $e->getMessage()); 72 | continue; 73 | } 74 | 75 | foreach ((array)$databases AS $dbname) { 76 | $dbname = reset($dbname); 77 | if (preg_match('/^(mysql|test|information_schema)$/', $dbname) || !preg_match('/^\w+_\d+$/', $dbname)) { 78 | continue; 79 | } 80 | 81 | foreach ((array)$db->getAll($db->query('SHOW TABLE STATUS FROM ' . $dbname)) AS $row) { 82 | $table = sprintf('%s.%s', $dbname, $row['Name']); 83 | $logvar = array( 84 | 'server' => $host['name'], 85 | 'table' => $table, 86 | 'engine' => $row['Engine'], 87 | 'create' => $row['Create_time'], 88 | 'update' => $row['Update_time'], 89 | 'check' => $row['Check_time'], 90 | ); 91 | 92 | if (true !== $this->option['full'] && $row['Check_time'] > $row['Update_time'] && !empty($row['Engine'])) { 93 | $this->log->debug('CHECK_IGN1', $logvar); 94 | continue; 95 | } 96 | 97 | switch (self::realcheck($table, $db)) { 98 | case 0: 99 | $this->log->debug('CHECK_IGN2', $logvar); 100 | break; 101 | 102 | case 1: 103 | $this->log->notice('CHECK_OK', $logvar); 104 | break; 105 | 106 | default: 107 | $this->log->debug('CHECK_FAIL', $logvar); 108 | break; 109 | } 110 | 111 | if (((++$count) % 10) == 0) { 112 | self::breakup(); 113 | } 114 | } 115 | } 116 | } 117 | 118 | return false; 119 | } 120 | /* }}} */ 121 | 122 | /* {{{ private static Integer realcheck() */ 123 | /** 124 | * really check table 125 | * 126 | * @access private static 127 | * @return Integer 128 | */ 129 | private static function realcheck($table, $mysql) 130 | { 131 | $query = sprintf('SELECT * FROM %s LIMIT 1', $table); 132 | if ($mysql->query($query)/*|| false === stripos($mysql->lastError(), ' is marked as crashed')*/) { 133 | return 0; 134 | } 135 | 136 | foreach (array('FAST', 'MEDIUM', 'MEDIUM', 'MEDIUM', 'EXTENDED') AS $mode) { 137 | $check = sprintf('CHECK TABLE %s %s', $table, $mode); 138 | if (self::success($mysql->getAll($mysql->query($check)))) { 139 | break; 140 | } 141 | } 142 | 143 | return $mysql->query($query) ? 1 : -1; 144 | } 145 | /* }}} */ 146 | 147 | /* {{{ private static Boolean success() */ 148 | /** 149 | * 判断check table是否成功 150 | * 151 | * @access private static 152 | * @return Boolean true or false 153 | */ 154 | private static function success($result) 155 | { 156 | if (empty($result) || !is_array($result)) { 157 | return false; 158 | } 159 | 160 | foreach ((array)$result AS $row) { 161 | if (!isset($row['Msg_type']) || !isset($row['Msg_text'])) { 162 | continue; 163 | } 164 | if ('status' == $row['Msg_type'] && 0 == strcasecmp('OK', $row['Msg_text'])) { 165 | return true; 166 | } 167 | } 168 | 169 | return false; 170 | } 171 | /* }}} */ 172 | 173 | } 174 | -------------------------------------------------------------------------------- /app/worker/cleaner.php: -------------------------------------------------------------------------------- 1 | | 9 | // +------------------------------------------------------------------------+ 10 | 11 | namespace Myfox\App\Worker; 12 | 13 | use \Myfox\App\Model\Router; 14 | use \Myfox\App\Queque; 15 | 16 | class Cleaner extends \Myfox\App\Worker 17 | { 18 | 19 | /* {{{ public Boolean execute() */ 20 | /** 21 | * 执行函数 22 | * 23 | * @access public 24 | * @return Boolean true or false : next loop ? 25 | */ 26 | public function execute($loop = true) 27 | { 28 | self::clean_invalidated_routes(); 29 | return false; 30 | } 31 | /* }}} */ 32 | 33 | /* {{{ public static void clean_invalidated_routes() */ 34 | /** 35 | * 清理失效的路由 36 | * 37 | * @access public static 38 | * @return void 39 | */ 40 | public static function clean_invalidated_routes() 41 | { 42 | $mysql = \Myfox\Lib\Mysql::instance('default'); 43 | $query = sprintf("SHOW TABLES LIKE '%sroute_info%%'", $mysql->option('prefix')); 44 | foreach ((array)$mysql->getAll($mysql->query($query)) AS $table) { 45 | $table = end($table); 46 | $query = sprintf( 47 | 'SELECT autokid,hosts_list,real_table,route_text FROM %s WHERE route_flag IN (%d) AND modtime < %d', 48 | $table, Router::FLAG_IS_DELETED, time() - 100000 49 | ); 50 | 51 | $ids = array(); 52 | foreach ((array)$mysql->getAll($mysql->query($query)) AS $row) { 53 | $hosts = trim($row['hosts_list'], '{}$'); 54 | if (!empty($hosts)) { 55 | $where = array(); 56 | foreach (Router::parse($row['route_text']) AS $k => $v) { 57 | $where[] = sprintf('%s = %s', $k, $v); 58 | } 59 | $info = array( 60 | 'host' => $hosts, 61 | 'path' => $row['real_table'], 62 | 'where' => implode(' AND ', $where), 63 | ); 64 | 65 | if (!Queque::instance()->insert('delete', $info, 0, array('adduser' => 'cleaner'))) { 66 | continue; 67 | } 68 | } 69 | $ids[] = (int)$row['autokid']; 70 | } 71 | 72 | $mysql->query(sprintf( 73 | 'DELETE FROM %s WHERE (route_flag = %d AND addtime < %d)%s', 74 | $table, Router::FLAG_PRE_IMPORT, time() - 10 * 86400, 75 | empty($ids) ? '' : sprintf(' OR (autokid IN (%s))', implode(',', $ids)) 76 | )); 77 | } 78 | } 79 | /* }}} */ 80 | 81 | } 82 | 83 | -------------------------------------------------------------------------------- /app/worker/processor.php: -------------------------------------------------------------------------------- 1 | | 9 | // +------------------------------------------------------------------------+ 10 | 11 | namespace Myfox\App\Worker; 12 | 13 | use \Myfox\App\Queque; 14 | 15 | class Processor extends \Myfox\App\Worker 16 | { 17 | 18 | /* {{{ 静态变量 */ 19 | 20 | /** 21 | * @状态映射表 22 | */ 23 | private static $codemap = array( 24 | \Myfox\App\Task::SUCC => \Myfox\App\Queque::FLAG_DONE, 25 | \Myfox\App\Task::FAIL => \Myfox\App\Queque::FLAG_WAIT, 26 | \Myfox\App\Task::WAIT => \Myfox\App\Queque::FLAG_WAIT, 27 | \Myfox\App\Task::IGNO => \Myfox\App\Queque::FLAG_IGNO, 28 | ); 29 | 30 | /* }}} */ 31 | 32 | /* {{{ 成员变量 */ 33 | 34 | protected $option = array( 35 | 'p' => null, /**< worker标记 */ 36 | 'n' => 10, /**< 每次取多少条记录 */ 37 | 't' => '', /**< 任务类型 */ 38 | ); 39 | 40 | private $tasks = array(); 41 | 42 | /* }}} */ 43 | 44 | /* {{{ public void __construct() */ 45 | /** 46 | * 构造函数 47 | * 48 | * @access public 49 | * @return void 50 | */ 51 | public function __construct($option) 52 | { 53 | parent::__construct($option); 54 | if (empty($this->option['p'])) { 55 | $this->option['p'] = ip2long(exec('hostname -i')); 56 | } 57 | } 58 | /* }}} */ 59 | 60 | /* {{{ public void cleanup() */ 61 | /** 62 | * 运行结束后数据清理 63 | * 64 | * @access public 65 | * @return void 66 | */ 67 | public function cleanup() 68 | { 69 | $this->tasks = array(); 70 | } 71 | /* }}} */ 72 | 73 | /* {{{ public Boolean execute() */ 74 | /** 75 | * 执行函数 76 | * 77 | * @access public 78 | * @return Boolean true or false : next loop ? 79 | */ 80 | public function execute($loop = true) 81 | { 82 | $this->tasks = (array)Queque::instance()->fetch( 83 | $this->option['n'], $this->option['p'], 84 | Queque::FLAG_WAIT, $this->option['t'] 85 | ); 86 | 87 | if (2 > $this->option['n']) { 88 | $this->tasks = array($this->tasks); 89 | } 90 | 91 | foreach ($this->tasks AS $queque) { 92 | if (!self::lock($queque['id'])) { 93 | continue; 94 | } 95 | 96 | $runner = \Myfox\App\Task::create($queque); 97 | if (!($runner instanceof \Myfox\App\Task)) { 98 | $flag = Queque::FLAG_IGNO; 99 | $error = sprintf("Undefined task_type named as '%s'", $queque['type']); 100 | $status = ''; 101 | } else { 102 | $flag = $runner->execute(); 103 | while (\Myfox\App\Task::WAIT === $flag) { 104 | $flag = $runner->wait(); 105 | } 106 | $flag = isset(self::$codemap[$flag]) ? self::$codemap[$flag] : Queque::FLAG_WAIT; 107 | $error = $runner->lastError(); 108 | $status = $runner->result(); 109 | } 110 | 111 | self::unlock($queque['id'], $flag, array( 112 | 'last_error' => $error, 113 | 'tmp_status' => $status, 114 | )); 115 | } 116 | 117 | return (bool)$loop; 118 | } 119 | /* }}} */ 120 | 121 | /* {{{ public Integer interval() */ 122 | /** 123 | * sleep时间, ms 124 | * 125 | * @access public 126 | * @return Integer 127 | */ 128 | public function interval() 129 | { 130 | return empty($this->tasks) ? 120000 : 1; 131 | } 132 | /* }}} */ 133 | 134 | /* {{{ private static Integer lock() */ 135 | /** 136 | * 任务加锁 137 | * 138 | * @access private static 139 | * @return Integer 140 | */ 141 | private static function lock($id) 142 | { 143 | return Queque::instance()->update($id, array( 144 | 'begtime' => sprintf( 145 | "IF(task_flag=%d, '%s', begtime)", 146 | Queque::FLAG_WAIT, date('Y-m-d H:i:s') 147 | ), 148 | 'task_flag' => Queque::FLAG_LOCK, 149 | ), array( 150 | 'begtime' => true, 151 | )); 152 | } 153 | /* }}} */ 154 | 155 | /* {{{ public Boolean unlock() */ 156 | /** 157 | * 完成后任务解锁 158 | * 159 | * @access public 160 | * @return Boolean true or false 161 | */ 162 | private static function unlock($id, $flag = Queque::FLAG_DONE, $option = null, $comma = null) 163 | { 164 | return Queque::instance()->update($id, array( 165 | 'trytimes' => sprintf('IF(task_flag=%d && task_flag > 0,trytimes-1,trytimes)', Queque::FLAG_LOCK), 166 | 'endtime' => sprintf("IF(task_flag=%d,'%s',endtime)", Queque::FLAG_LOCK, date('Y-m-d H:i:s')), 167 | 'task_flag' => $flag, 168 | ) + (array)$option, array( 169 | 'trytimes' => true, 170 | 'endtime' => true, 171 | 'task_flag' => true, 172 | ) + (array)$comma); 173 | } 174 | /* }}} */ 175 | 176 | } 177 | 178 | -------------------------------------------------------------------------------- /app/worker/test.php: -------------------------------------------------------------------------------- 1 | | 9 | // +------------------------------------------------------------------------+ 10 | 11 | namespace Myfox\App\Worker; 12 | 13 | class Test extends \Myfox\App\Worker 14 | { 15 | 16 | /* {{{ 静态变量 */ 17 | 18 | public static $number = 0; 19 | 20 | /* }}} */ 21 | 22 | /* {{{ public void __construct() */ 23 | public function __construct($option) 24 | { 25 | parent::__construct((array)$option + array( 26 | 'locker' => '', 27 | )); 28 | } 29 | /* }}} */ 30 | 31 | /* {{{ public Boolean execute() */ 32 | /** 33 | * 执行函数 34 | * 35 | * @access public 36 | * @return Boolean true or false : next loop ? 37 | */ 38 | public function execute($loop = true) 39 | { 40 | return (++self::$number < 5) && $this->option['loop']; 41 | } 42 | /* }}} */ 43 | 44 | /* {{{ public Integer interval() */ 45 | /** 46 | * sleep时间, ms 47 | * 48 | * @access public 49 | * @return Integer 50 | */ 51 | public function interval() 52 | { 53 | return $this->option['sleep']; 54 | } 55 | /* }}} */ 56 | 57 | /* {{{ public void cleanup() */ 58 | /** 59 | * 运行结束后数据清理 60 | * 61 | * @access public 62 | * @return void 63 | */ 64 | public function cleanup() 65 | { 66 | } 67 | /* }}} */ 68 | 69 | /* {{{ public String locker() */ 70 | /** 71 | * 进程锁名字 72 | * 73 | * @access public 74 | * @return String 75 | */ 76 | public function locker() 77 | { 78 | return $this->option['locker']; 79 | } 80 | /* }}} */ 81 | 82 | } 83 | 84 | -------------------------------------------------------------------------------- /before_install.sh: -------------------------------------------------------------------------------- 1 | # !/bin/bash 2 | # 3 | 4 | declare __pwd=`pwd` 5 | 6 | # install apc 7 | cd ${__pwd} && wget http://pecl.php.net/get/APC-3.1.9.tgz && \ 8 | tar zxvf APC-3.1.9.tgz && \ 9 | cd APC-3.1.9/ && \ 10 | phpize && \ 11 | ./configure && make && make install && \ 12 | echo "extension=\"apc.so\"" >> `php --ini | grep "Loaded Configuration" | sed -e "s|.*:\s*||"` 13 | 14 | # install igbinary 15 | cd ${__pwd} && wget http://pecl.php.net/get/igbinary-1.1.1.tgz && \ 16 | tar zxvf igbinary-1.1.1.tgz && cd igbinary-1.1.1 && \ 17 | phpize && ./configure CFLAGS="-O2 -g" --enable-igbinary && \ 18 | make && make install && \ 19 | echo "extension=\"igbinary.so\"" >> `php --ini | grep "Loaded Configuration" | sed -e "s|.*:\s*||"` 20 | 21 | # install phing 22 | cd ${__pwd} && mkdir phing && cd phing && \ 23 | wget http://www.phing.info/get/phing-2.4.9.tgz && \ 24 | tar zxvf phing-2.4.9.tgz 25 | 26 | cd ${__pwd} && ls -l 27 | -------------------------------------------------------------------------------- /bin/run.php: -------------------------------------------------------------------------------- 1 | | 9 | // +--------------------------------------------------------------------+ 10 | // 11 | // $Id: run.php 48 2010-12-20 15:58:11Z pengchun $ 12 | 13 | require_once __DIR__ . '/../app/daemon.php'; 14 | 15 | \Myfox\App\Daemon::run( 16 | __DIR__ . '/../etc/myfox.ini', 17 | $_SERVER['argv'] 18 | ); 19 | 20 | -------------------------------------------------------------------------------- /build.xml: -------------------------------------------------------------------------------- 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 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | -------------------------------------------------------------------------------- /build/http/test_dispatcher_time_out.php: -------------------------------------------------------------------------------- 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 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | -------------------------------------------------------------------------------- /build/phing/release.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | SVN tag name 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 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | All settings is correctly (y/n) 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | -------------------------------------------------------------------------------- /build/phing/unittest.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | -------------------------------------------------------------------------------- /build/tpl/alert.ini: -------------------------------------------------------------------------------- 1 | prefix = "##run.mode##" 2 | 3 | [normal] 4 | command = "##alert.normal.command##" 5 | 6 | [urgence] 7 | command = "##alert.urgence.command##" 8 | 9 | -------------------------------------------------------------------------------- /build/tpl/default.properties: -------------------------------------------------------------------------------- 1 | dir.root=##dir.root## 2 | run.mode=##run.mode## 3 | 4 | [svn] 5 | code.svn.url=##code.svn.url## 6 | 7 | [mysql] 8 | default.mysql.logurl=##default.mysql.logurl## 9 | default.mysql.dbname=##default.mysql.dbname## 10 | default.mysql.prefix=##default.mysql.prefix## 11 | default.mysql.master=##default.mysql.master## 12 | default.mysql.slave=##default.mysql.slave## 13 | 14 | [path] 15 | myfox.download.path=##myfox.download.path## 16 | myfox.filesplit.path=##myfox.filesplit.path## 17 | 18 | [bash] 19 | myfox.rpc.server=##myfox.rpc.server## 20 | myfox.rpc.agent=##myfox.rpc.agent## 21 | net.filename.prefix=##net.filename.prefix## 22 | 23 | [alert] 24 | alert.normal.command=##alert.normal.command## 25 | alert.urgence.command=##alert.urgence.command## 26 | 27 | -------------------------------------------------------------------------------- /build/tpl/myfox.ini: -------------------------------------------------------------------------------- 1 | run.mode = "##run.mode##" 2 | 3 | ; 运行超时时间 4 | run.timeout = ##run.timeout## 5 | 6 | ; URL前缀 7 | url.prefix = "##url.prefix##" 8 | 9 | [log] 10 | default = "##log.url##" 11 | 12 | [mysql] 13 | ; 默认的MySQL数据库 14 | default = "##dir.root##/etc/mysql.ini" 15 | 16 | [path] 17 | ; 数据下载暂存路径 18 | download = "##myfox.download.path##" 19 | 20 | ; 数据文件切分路径 21 | filesplit = "##myfox.filesplit.path##" 22 | 23 | -------------------------------------------------------------------------------- /build/tpl/mysql.ini: -------------------------------------------------------------------------------- 1 | ; 日志配置 2 | logurl = "##default.mysql.logurl##" 3 | 4 | ; 连接超时 5 | timeout = 1 6 | 7 | ; 持久连接 8 | persist = 1 9 | 10 | ; 字符集 11 | charset = "utf8" 12 | 13 | ; DB名字 14 | dbname = "##default.mysql.dbname##" 15 | 16 | ; 表名前缀 17 | prefix = "##default.mysql.prefix##" 18 | 19 | ; 主库配置 20 | [master] 21 | host1 = "##default.mysql.master##" 22 | 23 | ; 从库配置 24 | [slave] 25 | host1 = "##default.mysql.slave##" 26 | 27 | -------------------------------------------------------------------------------- /build/tpl/mysql_test.ini: -------------------------------------------------------------------------------- 1 | ; 日志配置 2 | logurl = "##mysql.log.url##" 3 | 4 | ; 连接超时 5 | timeout = 3 6 | 7 | ; 持久连接 8 | persist = 0 9 | 10 | ; 字符集 11 | charset = "utf8" 12 | 13 | ; DB名字 14 | dbname = "" 15 | 16 | ; 表名前缀 17 | prefix = "" 18 | 19 | ; 主库配置 20 | [master] 21 | ; host = mysql://user_rw:password@host:port 22 | 23 | ; 从库配置 24 | [slave] 25 | host1 = mysql://user_ro:password@127.0.0.1:3306 26 | 27 | -------------------------------------------------------------------------------- /build/tpl/security_example.ini: -------------------------------------------------------------------------------- 1 | ; 安全策略配置文件 2 | 3 | ; 队列属性 4 | [option] 5 | 6 | ; const FLAG_NEW = 0; 7 | ; const FLAG_WAIT = 100; 8 | ; const FLAG_LOCK = 200; 9 | ; const FLAG_IGNO = 300; 10 | ; const FLAG_DONE = 900; 11 | task_flag = 100 12 | trytimes = 3 13 | 14 | [priority] 15 | ; IP list 16 | ##security.allow.iplist## 17 | 18 | -------------------------------------------------------------------------------- /etc/.gitignore: -------------------------------------------------------------------------------- 1 | *.ini 2 | -------------------------------------------------------------------------------- /lib/TestShell.php: -------------------------------------------------------------------------------- 1 | | 7 | // +--------------------------------------------------------------------+ 8 | // 9 | // $Id: TestShell.php 47 2010-04-26 05:27:46Z zhangxc83 $ 10 | 11 | namespace Myfox\Lib; 12 | 13 | require_once(__DIR__ . '/autoload.php'); 14 | require_once('PHPUnit/Framework/TestCase.php'); 15 | 16 | date_default_timezone_set('Asia/Shanghai'); 17 | 18 | class TestShell extends \PHPUnit_Framework_TestCase 19 | { 20 | 21 | protected function setUp() 22 | { 23 | parent::setUp(); 24 | 25 | set_time_limit(0); 26 | 27 | \Myfox\Lib\AutoLoad::init(); 28 | \Myfox\Lib\AutoLoad::register('myfox\\app', __DIR__ . '/../app'); 29 | 30 | \Myfox\Lib\Cache\Apc::cleanAllCache(); 31 | } 32 | 33 | protected static function getLogContents($file, $offs = -1) 34 | { 35 | $data = array_filter(array_map('trim', (array)@file($file))); 36 | $lines = count($data); 37 | $offs = $offs < 0 ? $lines + $offs : $offs; 38 | $offs = max(0, $offs); 39 | 40 | return $offs >= $lines ? end($data) : $data[$offs]; 41 | } 42 | 43 | protected static function cleanTable($mysql, $prefix) 44 | { 45 | $table = null; 46 | $mysql = \Myfox\Lib\Mysql::instance($mysql); 47 | $query = sprintf("SHOW TABLES LIKE '%s%s%%'", $mysql->option('prefix'), trim($prefix)); 48 | foreach ((array)$mysql->getAll($mysql->query($query)) AS $table) { 49 | $table = end($table); 50 | if (preg_match('/(_merge)$/', $table)) { 51 | continue; 52 | } 53 | $mysql->query(sprintf('TRUNCATE TABLE %s', $table)); 54 | } 55 | 56 | return $table; 57 | } 58 | 59 | } 60 | 61 | -------------------------------------------------------------------------------- /lib/alert.php: -------------------------------------------------------------------------------- 1 | | 9 | // +------------------------------------------------------------------------+ 10 | 11 | namespace Myfox\Lib; 12 | 13 | use \Myfox\Lib\Config; 14 | 15 | class Alert 16 | { 17 | 18 | /* {{{ 静态常量 */ 19 | 20 | const NORMAL = 1; 21 | 22 | const URGENCE = 2; 23 | 24 | /* }}} */ 25 | 26 | /* {{{ 静态变量 */ 27 | 28 | private static $inited = false; 29 | 30 | private static $config = null; 31 | 32 | /* }}} */ 33 | 34 | /* {{{ public static void init() */ 35 | /** 36 | * 配置初始化方法 37 | * 38 | * @access public static 39 | * @return void 40 | */ 41 | public static function init($ini) 42 | { 43 | self::$config = new Config($ini); 44 | self::$inited = true; 45 | } 46 | /* }}} */ 47 | 48 | /* {{{ public static void push() */ 49 | /** 50 | * 压入报警 51 | * 52 | * @access public static 53 | * @return void 54 | */ 55 | public static function push($title, $level = self::NORMAL) 56 | { 57 | if (!self::$inited) { 58 | return; 59 | } 60 | 61 | $level = (int)$level; 62 | $title = sprintf('[%s] %s', self::$config->get('prefix'), trim($title)); 63 | 64 | if (self::URGENCE & $level) { 65 | $rt = self::call('urgence', $title, $error); 66 | if (empty($rt)) { 67 | $title = sprintf('%s (%s)', $title, $error); 68 | $level = self::NORMAL; 69 | } 70 | } 71 | 72 | if ((self::NORMAL & $level) && !self::call('normal', $title, $error)) { 73 | printf("[%s] %s\n", date('Y-m-d H:i:s'), $title); 74 | } 75 | } 76 | /* }}} */ 77 | 78 | /* {{{ private static Boolean call() */ 79 | /** 80 | * alert命令调用 81 | * 82 | * @access private static 83 | * @return Boolean true or false 84 | */ 85 | private static function call($name, $title, &$error) 86 | { 87 | $caller = self::$config->get(sprintf('%s/command', trim($name))); 88 | if (empty($caller)) { 89 | $error = 'undefined command for ' . $name; 90 | return false; 91 | } 92 | 93 | $error = system(strtr($caller, array( 94 | '{TITLE}' => escapeshellcmd($title), 95 | )), $code); 96 | 97 | return empty($code) ? true : false; 98 | } 99 | /* }}} */ 100 | 101 | /* {{{ private void __construct() */ 102 | /** 103 | * 构造函数占位 104 | * 105 | * @access private 106 | * @return void 107 | */ 108 | private function __construct() 109 | { 110 | } 111 | /* }}} */ 112 | 113 | } 114 | 115 | -------------------------------------------------------------------------------- /lib/autoload.php: -------------------------------------------------------------------------------- 1 | | 7 | // +------------------------------------------------------------------------+ 8 | // 9 | // $Id: autoload.php 22 2010-04-15 16:28:45Z zhangxc83 $ 10 | 11 | namespace Myfox\Lib; 12 | 13 | class AutoLoad 14 | { 15 | 16 | /* {{{ 静态变量 */ 17 | /** 18 | * @路径解析规则 19 | */ 20 | private static $rules = array(); 21 | 22 | private static $order = array(); 23 | 24 | private static $sorted = false; 25 | 26 | private static $index = 0; 27 | 28 | /* }}} */ 29 | 30 | /* {{{ public static void init() */ 31 | /** 32 | * 初始化 33 | * 34 | * @access public static 35 | * @return void 36 | */ 37 | public static function init() 38 | { 39 | self::register('myfox\\lib', __DIR__); 40 | 41 | spl_autoload_register(array(__CLASS__, 'callback')); 42 | } 43 | /* }}} */ 44 | 45 | /* {{{ public static void register() */ 46 | /* 47 | * 注册路径解析规则 48 | * 49 | * @access public static 50 | * @param String $key 51 | * @param String $dir 52 | * @param String $pre = null 53 | * @return void 54 | */ 55 | public static function register($key, $dir, $pre = null) 56 | { 57 | $dir = realpath($dir); 58 | if (empty($dir) || !is_dir($dir)) { 59 | return; 60 | } 61 | 62 | /** 63 | * 有unset动作,所以得用这个变量来标记曾经有过多少个元素 64 | * 避免register / unregister 导致排序不稳定 65 | */ 66 | self::$index++; 67 | 68 | $key = self::normalize($key); 69 | $pre = self::normalize($pre); 70 | $idx = 1000 * self::$index; 71 | if (!empty($pre) && isset(self::$order[$pre])) { 72 | /** 73 | * AutoLoad::register('root', '...'); 74 | * AutoLoad::register('son1', '...', 'root'); 75 | * AutoLoad::register('son2', '...', 'root'); 76 | */ 77 | $idx = self::$order[$pre] - intval(1000 / self::$index + 0.5); /*< 稳定排序 */ 78 | self::$sorted = false; 79 | } 80 | 81 | self::$rules[$key] = $dir; 82 | self::$order[$key] = $idx; 83 | } 84 | /* }}} */ 85 | 86 | /* {{{ public static void unregister() */ 87 | /** 88 | * 注销路径解析规则 89 | * 90 | * @access public static 91 | * @param String $name 92 | * @return void 93 | */ 94 | public static function unregister($name) 95 | { 96 | $name = self::normalize($name); 97 | if (isset(self::$rules[$name])) { 98 | unset(self::$rules[$name]); 99 | unset(self::$order[$name]); 100 | } 101 | } 102 | /* }}} */ 103 | 104 | /* {{{ public static void removeAllRules() */ 105 | /** 106 | * @清理所有规则 107 | * 108 | * @access public static 109 | * @return void 110 | */ 111 | public static function removeAllRules() 112 | { 113 | self::$rules = array(); 114 | self::$order = array(); 115 | self::$index = 0; 116 | self::$sorted = false; 117 | } 118 | /* }}} */ 119 | 120 | /* {{{ public static void callback() */ 121 | /** 122 | * 自动加载回调函数 123 | * 124 | * @access public static 125 | * @param String $class 126 | * @return void 127 | */ 128 | public static function callback($class) 129 | { 130 | $ordina = $class; 131 | $class = preg_replace('/[\/\\\]{1,}/', '/', $class); 132 | $index = strrpos($class, '/'); 133 | 134 | if (false === $index) { 135 | require_once(__DIR__ . '/exception.php'); 136 | throw new \Myfox\Lib\Exception(sprintf('Unregistered namespace when class "%s" defined.', $class)); 137 | } 138 | 139 | if (!self::$sorted && array_multisort(self::$order, self::$rules, SORT_ASC, SORT_NUMERIC)) { 140 | self::$sorted = true; 141 | } 142 | 143 | $path = strtolower(substr($class, 0, $index)); 144 | $name = strtolower(substr($class, $index + 1)); 145 | 146 | reset(self::$rules); 147 | foreach (self::$rules AS $key => $dir) { 148 | if (0 !== strpos($path, $key)) { 149 | continue; 150 | } 151 | 152 | $file = $dir . substr($path, strlen($key)) . '/' . $name . '.php'; 153 | if (is_file($file)) { 154 | require $file; 155 | } else { 156 | require_once(__DIR__ . '/exception.php'); 157 | throw new \Myfox\Lib\Exception(sprintf('File "%s" Not Found.', $file)); 158 | } 159 | 160 | if (!class_exists($ordina) && !interface_exists($ordina)) { 161 | require_once(__DIR__ . '/exception.php'); 162 | throw new \Myfox\Lib\Exception(sprintf('Class "%s" Not Found in "%s".', $ordina, $file)); 163 | } 164 | 165 | return; 166 | } 167 | 168 | require_once(__DIR__ . '/exception.php'); 169 | throw new \Myfox\Lib\Exception(sprintf('Class "%s" Not Found.', $ordina)); 170 | } 171 | /* }}} */ 172 | 173 | /* {{{ private static String normalize() */ 174 | /** 175 | * 规则归一化处理 176 | * 177 | * @access private static 178 | * @param String $name 179 | * @return String 180 | */ 181 | private static function normalize($name) 182 | { 183 | $name = preg_replace('/[\/\\\]+/', '/', preg_replace('/\s+/', '/', $name)); 184 | return strtolower(trim($name, '/')); 185 | } 186 | /* }}} */ 187 | 188 | } 189 | 190 | -------------------------------------------------------------------------------- /lib/blackhole.php: -------------------------------------------------------------------------------- 1 | | 7 | // +------------------------------------------------------------------------+ 8 | // 9 | // $Id: blackhole.php 22 2010-04-15 16:28:45Z zhangxc83 $ 10 | 11 | namespace Myfox\Lib; 12 | 13 | class Blackhole 14 | { 15 | 16 | public function __call($name, $args) 17 | { 18 | } 19 | 20 | public static function __callStatic($name, $args) 21 | { 22 | } 23 | 24 | public function __get($key) 25 | { 26 | } 27 | 28 | public function __set($key, $value) 29 | { 30 | } 31 | 32 | } 33 | 34 | -------------------------------------------------------------------------------- /lib/config.php: -------------------------------------------------------------------------------- 1 | | 7 | // +--------------------------------------------------------------------+ 8 | // 9 | // $Id: config.php 1 2010-06-01 03:52:51Z zhangxc83 $ 10 | // 11 | 12 | namespace Myfox\Lib; 13 | 14 | class Config 15 | { 16 | 17 | /* {{{ 静态变量 */ 18 | 19 | private static $alias = array(); 20 | 21 | private static $objects = array(); 22 | 23 | /* }}} */ 24 | 25 | /* {{{ 成员变量 */ 26 | 27 | private $scheme; 28 | 29 | private $params = null; 30 | 31 | /* }}} */ 32 | 33 | /* {{{ public static Object instance() */ 34 | /** 35 | * 根据名字获取实例 36 | * 37 | * @access public static 38 | * @return Object 39 | */ 40 | public static function instance($name) 41 | { 42 | $name = self::normalize($name); 43 | if (empty(self::$objects[$name])) { 44 | if (empty(self::$alias[$name])) { 45 | throw new \Myfox\Lib\Exception(sprintf( 46 | 'Undefined config name as "%s"', $name 47 | )); 48 | } 49 | self::$objects[$name] = new self(self::$alias[$name]); 50 | } 51 | 52 | return self::$objects[$name]; 53 | } 54 | /* }}} */ 55 | 56 | /* {{{ public static void register() */ 57 | /** 58 | * 注册别名 59 | * 60 | * @access public static 61 | * @return void 62 | */ 63 | public static function register($name, $url) 64 | { 65 | self::$alias[self::normalize($name)] = trim($url); 66 | } 67 | /* }}} */ 68 | 69 | /* {{{ public static void removeAllNames() */ 70 | /** 71 | * 清理所有的对象 72 | * 73 | * @access public static 74 | * @return void 75 | */ 76 | public static function removeAllNames() 77 | { 78 | self::$objects = array(); 79 | self::$alias = array(); 80 | } 81 | /* }}} */ 82 | 83 | /* {{{ public Mixture get() */ 84 | /** 85 | * 获取配置值 86 | * 87 | * @access public 88 | * @return void 89 | */ 90 | public function get($key, $default = null) 91 | { 92 | if (null === $this->params) { 93 | $this->params = (array)self::load($this->scheme); 94 | } 95 | 96 | $key = trim($key); 97 | if (isset($this->params[$key])) { 98 | return $this->params[$key]; 99 | } 100 | 101 | if ('' == $key) { 102 | return $this->params; 103 | } 104 | 105 | $val = $this->params; 106 | foreach (explode('/', $key) AS $id) { 107 | $id = trim($id); 108 | if (!isset($val[$id])) { 109 | return $default; 110 | } 111 | $val = $val[$id]; 112 | } 113 | 114 | return $val; 115 | } 116 | /* }}} */ 117 | 118 | /* {{{ public void __construct() */ 119 | /** 120 | * 构造函数 121 | * 122 | * @access public 123 | * @return void 124 | */ 125 | public function __construct($url, $name = null) 126 | { 127 | $this->scheme = trim($url); 128 | $this->params = null; 129 | if (!empty($name)) { 130 | self::register($name, $url); 131 | } 132 | } 133 | /* }}} */ 134 | 135 | /* {{{ private static String normalize() */ 136 | /** 137 | * 名字归一化 138 | * 139 | * @access private static 140 | * @return String 141 | */ 142 | private static function normalize($name) 143 | { 144 | return strtolower(preg_replace('/\s+/', '', $name)); 145 | } 146 | /* }}} */ 147 | 148 | /* {{{ private static Mixture load() */ 149 | /** 150 | * 加载数据 151 | * 152 | * @access private static 153 | * @return Mixture 154 | */ 155 | private static function load($url) 156 | { 157 | $url = parse_url($url); 158 | if (empty($url) || empty($url['path'])) { 159 | return false; 160 | } 161 | 162 | $class = explode('.', trim($url['path'], "\x00..\x20.")); 163 | $class = sprintf('%s\%s', __CLASS__, ucfirst(strtolower(end($class)))); 164 | $object = new $class($url); 165 | 166 | return $object->parse(); 167 | } 168 | /* }}} */ 169 | 170 | } 171 | 172 | -------------------------------------------------------------------------------- /lib/config/ini.php: -------------------------------------------------------------------------------- 1 | | 7 | // +--------------------------------------------------------------------+ 8 | // 9 | // $Id: ini.php 1 2010-06-01 03:52:51Z zhangxc83 $ 10 | // 11 | 12 | namespace Myfox\Lib\Config; 13 | 14 | class Ini 15 | { 16 | 17 | /* {{{ 成员变量 */ 18 | 19 | private $inifile; 20 | private $section; 21 | 22 | /* }}} */ 23 | 24 | /* {{{ public void __construct() */ 25 | /** 26 | * 构造函数 27 | * 28 | * @access public 29 | * @param Mixture $option 30 | * @return void 31 | */ 32 | public function __construct($option) 33 | { 34 | if (is_string($option)) { 35 | $option = parse_url($option); 36 | } 37 | if (empty($option['path'])) { 38 | throw new \Myfox\Lib\Exception('Uncomplete ini option'); 39 | } 40 | 41 | $this->inifile = trim($option['path']); 42 | $this->section = true; 43 | } 44 | /* }}} */ 45 | 46 | /* {{{ public Mixture parse() */ 47 | /** 48 | * 解析返回结果 49 | * 50 | * @access public 51 | * @return Mixture 52 | */ 53 | public function parse() 54 | { 55 | return parse_ini_file($this->inifile, $this->section); 56 | } 57 | /* }}} */ 58 | 59 | } 60 | 61 | -------------------------------------------------------------------------------- /lib/context.php: -------------------------------------------------------------------------------- 1 | | 7 | // +------------------------------------------------------------------------+ 8 | // 9 | // $Id: context.php 4 2010-03-09 05:20:36Z zhangxc83 $ 10 | 11 | namespace Myfox\Lib; 12 | 13 | class Context 14 | { 15 | 16 | /* {{{ 静态变量 */ 17 | 18 | /** 19 | * @数据列表 20 | */ 21 | private static $data = array(); 22 | 23 | /* }}} */ 24 | 25 | /* {{{ public static void register() */ 26 | /** 27 | * 注册一个变量 28 | * 29 | * @access public static 30 | * @param String $key 31 | * @param Mixture $val 32 | * @return void 33 | */ 34 | public static function register($key, $val) 35 | { 36 | self::$data[(string)$key] = $val; 37 | } 38 | /* }}} */ 39 | 40 | /* {{{ public static void unregister() */ 41 | /** 42 | * 注销一个变量 43 | * 44 | * @access public static 45 | * @param String $key 46 | * @return void 47 | */ 48 | public static function unregister($key) 49 | { 50 | $key = (string)$key; 51 | if (isset(self::$data[$key])) { 52 | unset(self::$data[$key]); 53 | } 54 | } 55 | /* }}} */ 56 | 57 | /* {{{ public static void cleanAllContext() */ 58 | /** 59 | * 清理所有上下文数据 60 | * 61 | * @access public static 62 | * @return void 63 | */ 64 | public static function cleanAllContext() 65 | { 66 | self::$data = array(); 67 | } 68 | /* }}} */ 69 | 70 | /* {{{ public static Mixture get() */ 71 | /** 72 | * 获取变量 73 | * 74 | * @access public static 75 | * @param String $key 76 | * @param Mixture $default : default null 77 | * @return Mixture 78 | */ 79 | public static function get($key, $default = null) 80 | { 81 | $key = (string)$key; 82 | if (isset(self::$data[$key])) { 83 | return self::$data[$key]; 84 | } 85 | 86 | return $default; 87 | } 88 | /* }}} */ 89 | 90 | /* {{{ public static Mixture addr() */ 91 | /** 92 | * 获取本地IP地址 93 | * 94 | * @access public static 95 | * @return Mixture 96 | */ 97 | public static function addr($int = false) 98 | { 99 | if (null === ($ip = self::get('__addr__'))) { 100 | $ip = trim(exec('hostname -i')); 101 | self::register('__addr__', $ip); 102 | } 103 | 104 | return $int ? sprintf('%u', ip2long($ip)) : $ip; 105 | } 106 | /* }}} */ 107 | 108 | /* {{{ public static Mixture userip() */ 109 | /** 110 | * 获取当前用户IP 111 | * 112 | * @access public static 113 | * @param Boolean $bolInt (default false) 114 | * @return String or Integer 115 | */ 116 | public static function userip($bolInt = false) 117 | { 118 | if (null === ($ret = self::get('__ip__'))) { 119 | $ret = self::_userip(); 120 | self::register('__ip__', $ret); 121 | } 122 | 123 | return $bolInt ? sprintf('%u', ip2long($ret)) : $ret; 124 | } 125 | /* }}} */ 126 | 127 | /* {{{ public static Integer pid() */ 128 | /** 129 | * 获取当前进程号 130 | * 131 | * @access public static 132 | * @return Integer 133 | */ 134 | public static function pid() 135 | { 136 | if (null === ($ret = self::get('__pid__'))) { 137 | $ret = is_callable('posix_getpid') ? posix_getpid() : getmypid(); 138 | self::register('__pid__', $ret); 139 | } 140 | 141 | return $ret; 142 | } 143 | /* }}} */ 144 | 145 | /* {{{ private static String _userip() */ 146 | /** 147 | * 读取用户实际IP 148 | * 149 | * @access private static 150 | * @return String 151 | */ 152 | private static function _userip() 153 | { 154 | $check = array( 155 | 'HTTP_VIA', 156 | 'HTTP_X_FORWARDED_FOR', 157 | 'HTTP_CLIENT_IP', 158 | 'REMOTE_ADDR', 159 | ); 160 | 161 | foreach ($check AS $key) { 162 | if (empty($_SERVER[$key])) { 163 | continue; 164 | } 165 | 166 | if (!preg_match_all('/\d+\.\d+\.\d+.\d+/', $_SERVER[$key], $match)) { 167 | continue; 168 | } 169 | 170 | return end($match[0]); 171 | } 172 | 173 | return 'unknown'; 174 | } 175 | /* }}} */ 176 | 177 | } 178 | 179 | -------------------------------------------------------------------------------- /lib/debug/pool.php: -------------------------------------------------------------------------------- 1 | | 7 | // +--------------------------------------------------------------------+ 8 | // 9 | // $Id: pool.php 2010-04-23 zhangxc83 Exp $ 10 | 11 | namespace Myfox\Lib\Debug; 12 | 13 | class Pool 14 | { 15 | 16 | /* {{{ 静态变量 */ 17 | 18 | private static $open = false; 19 | 20 | private static $debug = array(); 21 | 22 | /* }}} */ 23 | 24 | /* {{{ public static void init() */ 25 | /** 26 | * 切换debug开关 27 | * 28 | * @access public static 29 | * @param Boolean $open 30 | * @return void 31 | */ 32 | public static function init($open) 33 | { 34 | self::$open = (bool)$open; 35 | } 36 | /* }}} */ 37 | 38 | /* {{{ public static void push() */ 39 | /** 40 | * 压入debug数据 41 | * 42 | * @access public static 43 | * @param String $key 44 | * @param Mixture $val 45 | * @return void 46 | */ 47 | public static function push($key, $val) 48 | { 49 | if (!self::$open) { 50 | return false; 51 | } 52 | 53 | if (!isset(self::$debug[$key])) { 54 | self::$debug[$key] = $val; 55 | return $val; 56 | } 57 | 58 | if (!is_array(self::$debug[$key])) { 59 | self::$debug[$key] = array(self::$debug[$key]); 60 | } 61 | self::$debug[$key][] = $val; 62 | 63 | return count(self::$debug[$key]); 64 | } 65 | /* }}} */ 66 | 67 | /* {{{ public static void clean() */ 68 | /** 69 | * 清理所有debug数据 70 | * 71 | * @access public static 72 | * @return void 73 | */ 74 | public static function clean() 75 | { 76 | self::$debug = array(); 77 | } 78 | /* }}} */ 79 | 80 | /* {{{ public static String dump() */ 81 | /** 82 | * 打出debug数据 83 | * 84 | * @access public static 85 | * @param String $key (default null) 86 | * @return String 87 | */ 88 | public static function dump($key = null) 89 | { 90 | if (null === $key) { 91 | return var_export(self::$debug, true); 92 | } 93 | 94 | if (!isset(self::$debug[$key])) { 95 | return 'NULL'; 96 | } 97 | 98 | return var_export(self::$debug[$key], true); 99 | } 100 | /* }}} */ 101 | 102 | } 103 | 104 | -------------------------------------------------------------------------------- /lib/debug/timer.php: -------------------------------------------------------------------------------- 1 | | 7 | // +--------------------------------------------------------------------+ 8 | // 9 | // $Id: timer.php 48 2010-04-26 15:58:11Z zhangxc83 $ 10 | 11 | namespace Myfox\Lib\Debug; 12 | 13 | class Timer 14 | { 15 | 16 | /* {{{ 静态变量 */ 17 | 18 | private static $opened = false; 19 | 20 | private static $timer = array(); 21 | 22 | /* }}} */ 23 | 24 | /* {{{ public static void init() */ 25 | /** 26 | * 初始化计时器 27 | * 28 | * @access public static 29 | * @param Boolean $open 30 | * @return void 31 | */ 32 | public static function init($open) 33 | { 34 | self::$opened = (bool)$open; 35 | } 36 | /* }}} */ 37 | 38 | /* {{{ public static void start() */ 39 | /** 40 | * 开始计时 41 | * 42 | * @access public static 43 | * @param String $key 44 | * @return void 45 | */ 46 | public static function start($key) 47 | { 48 | if (self::$opened) { 49 | self::$timer[self::normarlize($key)] = microtime(true); 50 | } 51 | } 52 | /* }}} */ 53 | 54 | /* {{{ public static Mixture elapsed() */ 55 | /** 56 | * 获取耗时 57 | * 58 | * @access public static 59 | * @param String $key 60 | * @return Mixture 61 | */ 62 | public static function elapsed($key) 63 | { 64 | $key = self::normarlize($key); 65 | if (empty(self::$timer[$key])) { 66 | return null; 67 | } 68 | 69 | $beg = self::$timer[$key]; 70 | unset(self::$timer[$key]); 71 | 72 | return number_format(microtime(true) - $beg, 6); 73 | } 74 | /* }}} */ 75 | 76 | /* {{{ private static string normarlize() */ 77 | /** 78 | * KEY归一化 79 | * 80 | * @access private static 81 | * @param string $key 82 | * @return string 83 | */ 84 | private static function normarlize($key) 85 | { 86 | return strtolower(trim($key)); 87 | } 88 | /* }}} */ 89 | 90 | } 91 | 92 | -------------------------------------------------------------------------------- /lib/exception.php: -------------------------------------------------------------------------------- 1 | | 7 | // +--------------------------------------------------------------------+ 8 | // 9 | // $Id: exception.php 2010-04-19 13:54:32 zhangxc83 Exp $ 10 | 11 | namespace Myfox\Lib; 12 | 13 | class Exception extends \Exception 14 | { 15 | 16 | public function __construct($message, $code = 0) 17 | { 18 | parent::__construct($message, $code); 19 | } 20 | 21 | public function __toString() 22 | { 23 | return sprintf('%s : [%s] : %s', __CLASS__, $this->code, $this->message); 24 | } 25 | 26 | } 27 | 28 | -------------------------------------------------------------------------------- /lib/factory.php: -------------------------------------------------------------------------------- 1 | | 7 | // +------------------------------------------------------------------------+ 8 | // 9 | // $Id: factory.php 22 2010-04-15 16:28:45Z zhangxc83 $ 10 | 11 | namespace Myfox\Lib; 12 | 13 | class Factory 14 | { 15 | 16 | /* {{{ 静态变量 */ 17 | 18 | private static $objects = array(); 19 | 20 | private static $logpool = array(); 21 | 22 | /* }}} */ 23 | 24 | /* {{{ public static Object getObject() */ 25 | /** 26 | * 获取一个类的指定实例 27 | * 28 | * @access public static 29 | * @param String $class 30 | * @param String $name 31 | * @return Object (refferrence) 32 | */ 33 | public static function &getObject($class) 34 | { 35 | $args = func_get_args(); 36 | $class = array_shift($args); 37 | $index = self::names($class, json_encode($args)); 38 | if (empty(self::$objects[$index])) { 39 | if (!class_exists($class)) { 40 | \Myfox\Lib\AutoLoad::callback($class); 41 | } 42 | 43 | $rf = new \ReflectionClass($class); 44 | self::$objects[$index] = $rf->newInstanceArgs($args); 45 | } 46 | 47 | return self::$objects[$index]; 48 | } 49 | /* }}} */ 50 | 51 | /* {{{ public static void removeAllObject() */ 52 | /** 53 | * 清理掉所有对象和注册信息 54 | * 55 | * @access public static 56 | * @param Boolean $reg (default false) 57 | * @return void 58 | */ 59 | public static function removeAllObject($reg = false) 60 | { 61 | self::$objects = array(); 62 | self::$logpool = array(); 63 | } 64 | /* }}} */ 65 | 66 | /* {{{ private static String names() */ 67 | /** 68 | * 构造对象索引 69 | * 70 | * @access private static 71 | * @param String $class 72 | * @param String $name 73 | * @return String 74 | */ 75 | private static function names($class, $name) 76 | { 77 | return sprintf('%s:%s', self::normalize($class), self::normalize($name)); 78 | } 79 | /* }}} */ 80 | 81 | /* {{{ private static String normalize() */ 82 | /** 83 | * 类名归一化处理 84 | * 85 | * @access private static 86 | * @param String $class 87 | * @return String 88 | */ 89 | private static function normalize($class) 90 | { 91 | $class = preg_replace('/\s+/', '', preg_replace('/[\/\\\]+/', '/', $class)); 92 | return strtolower(trim($class, '/')); 93 | } 94 | /* }}} */ 95 | 96 | /* {{{ public static void registerLog() */ 97 | /** 98 | * 注册log对象 99 | */ 100 | public static function registerLog($name, $url) 101 | { 102 | self::$logpool[strtolower(trim($name))] = new \Myfox\Lib\Log($url); 103 | } 104 | /* }}} */ 105 | 106 | /* {{{ public static Object getLog() */ 107 | /** 108 | * 根据名字获取日志对象 109 | * 110 | * @access public static 111 | * @return Object 112 | */ 113 | public static function getLog($name) 114 | { 115 | $name = strtolower(trim($name)); 116 | if (isset(self::$logpool[$name])) { 117 | return self::$logpool[$name]; 118 | } 119 | 120 | return new \Myfox\Lib\BlackHole('Log:' . $name); 121 | } 122 | /* }}} */ 123 | 124 | } 125 | 126 | -------------------------------------------------------------------------------- /lib/fetcher/ftp.php: -------------------------------------------------------------------------------- 1 | | 7 | // +------------------------------------------------------------------------+ 8 | // 9 | // $Id: ftp.php 22 2010-04-15 16:28:45Z zhangxc83 $ 10 | 11 | namespace Myfox\Lib\Fetcher; 12 | 13 | class Ftp 14 | { 15 | 16 | /* {{{ 成员变量 */ 17 | 18 | private $error = ''; 19 | 20 | private $handle = null; 21 | 22 | private $option; 23 | 24 | private $tmout = 2; /**< 连接超时(s) */ 25 | 26 | /* }}} */ 27 | 28 | /* {{{ public void __construct() */ 29 | /** 30 | * 构造函数 31 | * 32 | * @access public 33 | * @return void 34 | */ 35 | public function __construct($option) 36 | { 37 | $this->option = (array)$option + array( 38 | 'host' => '', 39 | 'port' => 21, 40 | 'user' => 'anonymous', 41 | 'pass' => '', 42 | ); 43 | if (!empty($this->option['query'])) { 44 | parse_str($this->option['query'], $mc); 45 | if (!empty($mc['timeout'])) { 46 | $this->tmout = (int)$mc['timeout']; 47 | } 48 | } 49 | } 50 | /* }}} */ 51 | 52 | /* {{{ public void __destruct() */ 53 | public function __destruct() 54 | { 55 | $this->close(); 56 | } 57 | /* }}} */ 58 | 59 | /* {{{ public Boolean fetch() */ 60 | /** 61 | * 获取文件 62 | * 63 | * @access public 64 | * @return Boolean true or false 65 | */ 66 | public function fetch($fname, $cache = true) 67 | { 68 | if (!$this->connect()) { 69 | return false; 70 | } 71 | 72 | // TODO: 73 | // 74 | // @ 基于md5文件的抓取 , 变更判断 75 | // @ 基于 filesize + filemtime 的变更判断 76 | if (!$this->get($this->option['path'], $fname)) { 77 | return false; 78 | } 79 | 80 | return true; 81 | } 82 | /* }}} */ 83 | 84 | /* {{{ public Mixture lastError() */ 85 | /** 86 | * 获取错误描述 87 | * 88 | * @access public 89 | * @return Mixture 90 | */ 91 | public function lastError() 92 | { 93 | return $this->error; 94 | } 95 | /* }}} */ 96 | 97 | /* {{{ private Boolean connect() */ 98 | /** 99 | * 连接FTP服务器 100 | * 101 | * @access private 102 | * @return Boolean true or false 103 | */ 104 | private function connect() 105 | { 106 | if (!empty($this->handle)) { 107 | return true; 108 | } 109 | 110 | $this->handle = ftp_connect($this->option['host'], $this->option['port'], $this->tmout); 111 | if (empty($this->handle)) { 112 | $this->error = sprintf( 113 | 'Connect failed with the host as "%s" on port %d', 114 | $this->option['host'], $this->option['port'] 115 | ); 116 | return false; 117 | } 118 | 119 | if (!ftp_login($this->handle, $this->option['user'], $this->option['pass'])) { 120 | $this->error = sprintf( 121 | 'Access Denied for user "%s", use password %s', 122 | $this->option['user'], empty($this->option['pass']) ? 'NO' : 'YES' 123 | ); 124 | return false; 125 | } 126 | ftp_pasv($this->handle, true); 127 | 128 | return true; 129 | } 130 | /* }}} */ 131 | 132 | /* {{{ private void close() */ 133 | /** 134 | * 关闭连接 135 | * 136 | * @access private 137 | * @return void 138 | */ 139 | private function close() 140 | { 141 | if (!empty($this->handle)) { 142 | ftp_close($this->handle); 143 | } 144 | $this->handle = null; 145 | } 146 | /* }}} */ 147 | 148 | /* {{{ private Boolean get() */ 149 | /** 150 | * 获取文件 151 | * 152 | * @access private 153 | * @return Boolean true or false 154 | */ 155 | private function get($remote, $local) 156 | { 157 | $fname = sprintf('%s.%d', $local, getmypid()); 158 | if (!ftp_get($this->handle, $fname, $remote, FTP_BINARY)) { 159 | @unlink($fname); 160 | $this->error = sprintf(''); 161 | return false; 162 | } 163 | 164 | return rename($fname, $local); 165 | } 166 | /* }}} */ 167 | 168 | } 169 | -------------------------------------------------------------------------------- /lib/fetcher/scp.php: -------------------------------------------------------------------------------- 1 | | 7 | // +------------------------------------------------------------------------+ 8 | 9 | namespace Myfox\Lib\Fetcher; 10 | 11 | class Scp 12 | { 13 | 14 | /* {{{ 成员变量 */ 15 | 16 | private $error = ''; 17 | 18 | private $option; 19 | 20 | /* }}} */ 21 | 22 | /* {{{ public void __construct() */ 23 | /** 24 | * 构造函数 25 | * 26 | * @access public 27 | * @return void 28 | */ 29 | public function __construct($option) 30 | { 31 | if(!empty($option['query'])) { 32 | parse_str($option['query'], $query); 33 | $this->option = (array)$option + (array)$query; 34 | } 35 | 36 | $this->option = (array)$option + array( 37 | 'user' => get_current_user(), 38 | ); 39 | } 40 | /* }}} */ 41 | 42 | /* {{{ public Boolean fetch() */ 43 | /** 44 | * 获取文件 45 | * 46 | * @access public 47 | * @return Boolean true or false 48 | */ 49 | public function fetch($fname, $cache = true) 50 | { 51 | if (!$this->isChange($fname)) { 52 | return true; 53 | } 54 | return self::ssh(sprintf('scp %s@%s:%s %s', 55 | escapeshellcmd($this->option['user']), 56 | escapeshellcmd($this->option['host']), 57 | escapeshellcmd($this->option['path']), 58 | escapeshellcmd($fname) 59 | ), $this->error); 60 | } 61 | /* }}} */ 62 | 63 | /* {{{ public Mixture lastError() */ 64 | /** 65 | * 获取错误描述 66 | * 67 | * @access public 68 | * @return Mixture 69 | */ 70 | public function lastError() 71 | { 72 | return $this->error; 73 | } 74 | /* }}} */ 75 | 76 | /* {{{ private Boolean ssh() */ 77 | /** 78 | * ssh 79 | * 80 | * @access private 81 | * @return Boolean true or false 82 | */ 83 | private static function ssh($script, &$output) 84 | { 85 | $return = exec(sprintf('%s 2>&1', $script), $output, $rt); 86 | $output = !empty($return) ? $return : $output; 87 | return 0 === $rt ? true : false; 88 | } 89 | /* }}}*/ 90 | 91 | /*{{{ private Boolean isChange()*/ 92 | /** 93 | * 文件是否变化 94 | * 95 | * @access private 96 | * @return Boolean true or false 97 | */ 98 | 99 | public function isChange($file) 100 | { 101 | if (!is_file($file)) { 102 | return true; 103 | } 104 | $rt = self::ssh(sprintf( 105 | 'ssh %s@%s "ls -l --full-time \"%s\""', 106 | escapeshellcmd($this->option['user']), 107 | escapeshellcmd($this->option['host']), 108 | escapeshellcmd($this->option['path']) 109 | ), $stat); 110 | if (false === $rt) { 111 | return true; 112 | } 113 | 114 | $stat = self::parsestat(trim($stat)); 115 | if ($stat['size'] != filesize($file) || $stat['mtime'] >= filemtime($file)) { 116 | return true; 117 | } 118 | return false; 119 | } 120 | /*}}}*/ 121 | 122 | /* {{{ public static Mixture parsestat() */ 123 | /** 124 | * 解析ls -l --full-time的结果 125 | * 126 | * @access public static 127 | * @return Mixture 128 | */ 129 | public static function parsestat($stat) 130 | { 131 | $stat = explode(' ', trim($stat)); 132 | return array( 133 | 'size' => $stat[4], 134 | 'mtime' => strtotime(sprintf('%s %s', $stat[5], substr($stat[6], 0, 8), $stat[7])), 135 | ); 136 | } 137 | /* }}} */ 138 | 139 | } 140 | -------------------------------------------------------------------------------- /lib/fileset.php: -------------------------------------------------------------------------------- 1 | | 7 | // +------------------------------------------------------------------------+ 8 | // 9 | // $Id: fileset.php 22 2010-04-15 16:28:45Z zhangxc83 $ 10 | 11 | namespace Myfox\Lib; 12 | 13 | class Fileset 14 | { 15 | 16 | /* {{{ 静态常量 */ 17 | 18 | const TEMP_PATH = '/tmp/myfox/download'; 19 | 20 | /* }}} */ 21 | 22 | /* {{{ 静态变量 */ 23 | 24 | private static $lastError = ''; 25 | 26 | /* }}} */ 27 | 28 | /* {{{ public static Mixture getfile() */ 29 | /** 30 | * 从给定URL获取文件 31 | * 32 | * @access public static 33 | * @return Mixture 34 | */ 35 | public static function getfile($url, $path = '', $cache = true) 36 | { 37 | $option = parse_url((string)$url); 38 | if (empty($option) || empty($option['path'])) { 39 | self::setError(sprintf('Unrecognized url as "%s" for fileset.', $url)); 40 | return false; 41 | } 42 | 43 | if (empty($option['scheme'])) { 44 | $fname = realpath($option['path']); 45 | if (empty($fname)) { 46 | self::setError(sprintf('File not found as the path "%s"', $option['path'])); 47 | return false; 48 | } 49 | 50 | return $fname; 51 | } 52 | 53 | if (empty($path)) { 54 | $fpath = self::TEMP_PATH; 55 | $fname = basename($option['path']); 56 | } elseif ('/' == substr($path, -1)) { 57 | $fpath = rtrim($path, '/'); 58 | $fname = basename($option['path']); 59 | } else { 60 | $fpath = dirname($path); 61 | $fname = basename($path); 62 | } 63 | 64 | if (!is_dir($fpath) && !@mkdir($fpath, 0755, true)) { 65 | self::setError(sprintf('Path "%s" doesn\'t exist, and create failed.', $fpath)); 66 | return false; 67 | } 68 | 69 | $class = sprintf('%s\\Fetcher\\%s', __NAMESPACE__, ucfirst($option['scheme'])); 70 | try { 71 | $worker = new $class($option); 72 | $fname = sprintf('%s/%s', $fpath, $fname); 73 | if (!$worker->fetch($fname, $cache)) { 74 | self::setError($worker->lastError()); 75 | return false; 76 | } 77 | 78 | return $fname; 79 | } catch (\Exception $e) { 80 | self::setError($e->getMessage()); 81 | } 82 | 83 | return false; 84 | } 85 | /* }}} */ 86 | 87 | /* {{{ public String lastError() */ 88 | /** 89 | * 获取错误信息 90 | * 91 | * @access public 92 | * @return String 93 | */ 94 | public static function lastError() 95 | { 96 | return self::$lastError; 97 | } 98 | /* }}} */ 99 | 100 | /* {{{ private static void setError() */ 101 | /** 102 | * 设置错误描述 103 | * 104 | * @access private static 105 | * @return void 106 | */ 107 | private static function setError($error) 108 | { 109 | self::$lastError = trim($error); 110 | } 111 | /* }}} */ 112 | 113 | } 114 | -------------------------------------------------------------------------------- /lib/livebox.php: -------------------------------------------------------------------------------- 1 | | 7 | // +--------------------------------------------------------------------+ 8 | // 9 | // $Id: livebox.php 2010-04-23 zhangxc83 Exp $ 10 | 11 | namespace Myfox\Lib; 12 | 13 | use \Myfox\Lib\Cache\Apc; 14 | 15 | class LiveBox 16 | { 17 | 18 | /* {{{ 静态常量 */ 19 | 20 | const OFFS = 1; 21 | 22 | const PROF = 2; 23 | 24 | /* }}} */ 25 | 26 | /* {{{ 成员变量 */ 27 | 28 | private $host = array(); /**< 服务器列表 */ 29 | 30 | private $offs = array(); /**< 不可用列表 */ 31 | 32 | private $prof = array(); /**< 故障历史 */ 33 | 34 | private $pool = null; 35 | 36 | private $flush = 0; /**< 刷新数据 */ 37 | 38 | private $last = null; /**< 上次返回的服务器 */ 39 | 40 | private $live = 300; /**< 自动存活检查时间 */ 41 | 42 | private $cache = null; /**< 缓存服务 */ 43 | 44 | /* }}} */ 45 | 46 | /* {{{ public Object __construct() */ 47 | /** 48 | * 构造函数 49 | * 50 | * @access public 51 | * @param String $token : 缓存前缀 52 | * @param Integer $live : default 300 53 | * @return Object $this 54 | */ 55 | public function __construct($token, $live = 300) 56 | { 57 | $this->live = max(0, (int)$live); 58 | $this->flush = 0; 59 | if (function_exists('apc_add')) { 60 | $this->cache = new Apc($token, $this->live); 61 | } else { 62 | $this->cache = null; 63 | } 64 | 65 | if (!empty($this->cache)) { 66 | $this->offs = self::filterOffs($this->cache->get('offs')); 67 | $this->prof = $this->cache->get('prof'); 68 | } 69 | } 70 | /* }}} */ 71 | 72 | /* {{{ public Boolean __destruct() */ 73 | /** 74 | * 析构函数 75 | * 76 | * @access public 77 | * @return Boolean true 78 | */ 79 | public function __destruct() 80 | { 81 | if (empty($this->cache)) { 82 | return; 83 | } 84 | 85 | if (self::OFFS & $this->flush) { 86 | $this->cache->set('offs', self::filterOffs(array_merge( 87 | (array)$this->cache->get('offs'), 88 | (array)$this->offs 89 | )), (int)(1.2 * $this->live)); 90 | } 91 | 92 | if (self::PROF & $this->flush) { 93 | } 94 | } 95 | /* }}} */ 96 | 97 | /* {{{ public Object register() */ 98 | /** 99 | * 添加一台服务器 100 | * 101 | * @access public 102 | * @param Mixture $host 103 | * @return Object $this 104 | */ 105 | public function register($host, $id = null) 106 | { 107 | $this->host[(null === $id) ? count($this->host) : $id] = $host; 108 | $this->pool = null; 109 | return $this; 110 | } 111 | /* }}} */ 112 | 113 | /* {{{ public Object setOffline() */ 114 | /** 115 | * 标记一台服务器为不可用 116 | * 117 | * @access public 118 | * @param Mixture $host (default null) 119 | * @return Object $this 120 | */ 121 | public function setOffline($id = null, $num = 1, $ttl = 120) 122 | { 123 | $tm = time(); 124 | $id = (null === $id) ? $this->last : $id; 125 | if ($num >= 2) { 126 | $va = &$this->prof[$id][(int)($tm / 6)]; 127 | $va = empty($va) ? 1 : $va + 1; 128 | $this->flush |= self::PROF; 129 | } 130 | 131 | if ($num < 2 || $va >= $num) { 132 | $this->offs[$id] = $tm + $this->live; 133 | $this->flush |= self::OFFS; 134 | } 135 | $this->pool = null; 136 | 137 | return $this; 138 | } 139 | /* }}} */ 140 | 141 | /* {{{ public Object cleanAllCache() */ 142 | /** 143 | * 清理所有的对象属性 144 | * 145 | * @access public 146 | * @return void 147 | */ 148 | public function cleanAllCache() 149 | { 150 | $this->host = array(); 151 | $this->offs = array(); 152 | $this->prof = array(); 153 | $this->pool = null; 154 | $this->last = null; 155 | 156 | if (!empty($this->cache)) { 157 | $this->cache->cleanAllCache(); 158 | } 159 | 160 | return $this; 161 | } 162 | /* }}} */ 163 | 164 | /* {{{ public Mixture fetch() */ 165 | /** 166 | * 随机获取一台可用服务器 167 | * 168 | * @access public 169 | * @return Mixture 170 | */ 171 | public function fetch() 172 | { 173 | if (null === $this->pool) { 174 | $this->pool = array_keys(array_diff_key($this->host, $this->offs)); 175 | } 176 | if (empty($this->pool)) { 177 | $this->pool = array_keys($this->host); 178 | $this->offs = array(); 179 | } 180 | 181 | if (empty($this->pool)) { 182 | return null; 183 | } 184 | 185 | $this->last = $this->pool[array_rand($this->pool)]; 186 | return $this->host[$this->last]; 187 | } 188 | /* }}} */ 189 | 190 | /* {{{ private static Mixture filterOffs() */ 191 | /** 192 | * 根据时间过滤不可用列表 193 | * 194 | * @access private static 195 | * @param Array $offs 196 | * @return Array 197 | */ 198 | private static function filterOffs($offs) 199 | { 200 | $tsamp = time(); 201 | $return = array(); 202 | foreach ((array)$offs AS $host => $time) { 203 | if ($time <= $tsamp) { 204 | continue; 205 | } 206 | $return[$host] = $time; 207 | } 208 | 209 | return $return; 210 | } 211 | /* }}} */ 212 | 213 | } 214 | 215 | -------------------------------------------------------------------------------- /lib/parser/url.php: -------------------------------------------------------------------------------- 1 | | 7 | // +--------------------------------------------------------------------+ 8 | // 9 | // $Id: apc.php 380 2011-01-07 10:18:20Z zhangxc83 $ 10 | 11 | namespace Myfox\Lib\Parser; 12 | 13 | class Url 14 | { 15 | 16 | /* {{{ 成员变量 */ 17 | 18 | private $url; 19 | 20 | private $module; 21 | 22 | private $action; 23 | 24 | private $param; 25 | 26 | /* }}} */ 27 | 28 | /* {{{ public void __construct() */ 29 | /** 30 | * 构造函数 31 | * 32 | * @access public 33 | * @param String $restUrl 34 | * @return void 35 | */ 36 | public function __construct($url) 37 | { 38 | $this->url = trim(ltrim($url, '/')); 39 | $this->parse(); 40 | } 41 | /* }}} */ 42 | 43 | /* {{{ public Mixture __get() */ 44 | /** 45 | * __get魔术方法 46 | * 47 | * @access public 48 | * @return Mixture 49 | */ 50 | public function __get($key) 51 | { 52 | return isset($this->$key) ? $this->$key : null; 53 | } 54 | /* }}} */ 55 | 56 | /* {{{ public String module() */ 57 | /** 58 | * 返回module 59 | * 60 | * @access public 61 | * @return String 62 | */ 63 | public function module() 64 | { 65 | return $this->module; 66 | } 67 | /* }}} */ 68 | 69 | /* {{{ public String action() */ 70 | /** 71 | * 返回action 72 | * 73 | * @access public 74 | * @return String 75 | */ 76 | public function action() 77 | { 78 | return $this->action; 79 | } 80 | /* }}} */ 81 | 82 | /* {{{ public Mixture param() */ 83 | /** 84 | * 获取URL中的参数值 85 | * 86 | * @access public 87 | * @return Mixture 88 | */ 89 | public function param($key = null, $default = null) 90 | { 91 | if (null === $key) { 92 | return $this->param; 93 | } 94 | 95 | return isset($this->param[$key]) ? $this->param[$key] : $default; 96 | } 97 | /* }}} */ 98 | 99 | /* {{{ public static string build() */ 100 | /** 101 | * 构造URL 102 | * 103 | * @access public static 104 | * @param String $module 105 | * @param String $action 106 | * @param Mixture $param : default null 107 | * @return String 108 | */ 109 | public static function build($module, $action, $param = null) 110 | { 111 | $parts = array( 112 | self::escape($module), 113 | self::escape($action), 114 | ); 115 | foreach ((array)$param AS $key => $val) { 116 | if (!is_scalar($val)) { 117 | continue; 118 | } 119 | $parts[] = sprintf('%s/%s', self::escape($key), urlencode($val)); 120 | } 121 | 122 | return implode('/', $parts); 123 | } 124 | /* }}} */ 125 | 126 | /* {{{ private void parse() */ 127 | /** 128 | * URL解析程序 129 | * 130 | * @access private 131 | * @return void 132 | */ 133 | private function parse() 134 | { 135 | $urls = explode('?', $this->url); 136 | $urls = array_values(array_filter(array_map('trim', 137 | explode('/', reset($urls)) 138 | ), 'strlen')); 139 | $this->module = isset($urls[0]) ? self::escape($urls[0]) : ''; 140 | $this->action = isset($urls[1]) ? self::escape($urls[1]) : ''; 141 | $this->param = array(); 142 | 143 | for ($i = 2, $max = count($urls); $i < $max; $i++) { 144 | $name = self::escape($urls[$i]); 145 | if (!isset($urls[++$i])) { 146 | $this->param[$name] = true; 147 | } else { 148 | $this->param[$name] = urldecode($urls[$i]); 149 | } 150 | } 151 | } 152 | /* }}} */ 153 | 154 | /* {{{ private static String escape() */ 155 | /** 156 | * 过滤URL中的非安全字符 157 | * 158 | * @access private static 159 | * @param String $str 160 | * @return String 161 | */ 162 | private static function escape($str) 163 | { 164 | return trim(preg_replace('/[^\w]/is', '', $str)); 165 | } 166 | /* }}} */ 167 | 168 | } 169 | -------------------------------------------------------------------------------- /release.properties: -------------------------------------------------------------------------------- 1 | dir.root=/home/pengchun/work/datacube/myfox/branches/open 2 | run.mode=online 3 | 4 | [svn] 5 | code.svn.url=SVN.URL.FOR.YOUR.CODES 6 | 7 | [mysql] 8 | default.mysql.logurl=log://notice.warn.error//home/pengchun/mysql.dev.log 9 | default.mysql.dbname=meta_myfox_config 10 | default.mysql.prefix= 11 | default.mysql.master=mysql://db_write:123456@172.0.0.1 12 | default.mysql.slave=mysql://db_read:123456@172.0.0.2 13 | 14 | [path] 15 | myfox.download.path=/disk1/var/myfox/download 16 | myfox.filesplit.path=/disk2/var/myfox/filesplit 17 | 18 | [bash] 19 | myfox.rpc.server=127.0.0.1:9999/pengchun/myfox2,localhost:9999/pengchun/myfox2 20 | myfox.rpc.agent=Myfox RPC Caller (0.8.1) 21 | net.filename.prefix=ftp://user:pass@{HOST}:21 22 | 23 | [alert] 24 | alert.normal.command=echo \"${TITLE}\" | /bin/mail -s alert YOURNAME@COMPANY.COM 25 | alert.urgence.command=短信报警 26 | 27 | -------------------------------------------------------------------------------- /resource/bash/gateway_call_myfox_import.sh: -------------------------------------------------------------------------------- 1 | # ! /bin/bash 2 | # 3 | # vim: set expandtab tabstop=4 shiftwidth=4 foldmethod=marker: # 4 | # ---------------------------------------------------------------- 5 | # 6 | # Myfox部署在 HDFS 网关机上的客户端 7 | # 8 | # @author: Aleafs 9 | # ---------------------------------------------------------------- 10 | 11 | export LANG=en_US.UTF-8 12 | 13 | declare -r CFG_MYFOX_RPC_SERVER=(\ 14 | ##myfox.rpc.server.list## 15 | ) 16 | declare -r CFG_HTTP_USER_AGENT="##myfox.rpc.agent##" 17 | 18 | # @远程服务器获取本机文件的协议前缀 19 | declare -r CFG_FILENAME_PREFIX="##net.filename.prefix##" 20 | 21 | # {{{ function usage() # 22 | function usage() { 23 | cat < 25 | 26 | [OPTION]: 27 | -t logic table name in myfox, such as "numsplit" 28 | -r route value, such as "thedate=2011-06-10,cid=1" 29 | EOF 30 | } 31 | # }}} # 32 | 33 | # {{{ 运行时变量 # 34 | 35 | declare -r __RUN_ROOT__="$(dirname -- $(readlink -f -- ${BASH_SOURCE[0]}))" 36 | declare -r RUN_HTTP_OUT="${__RUN_ROOT__}/http.out" 37 | declare -r RUN_AGENT_IP=`hostname -i | awk -F. '{print lshift($1,24)+lshift($2,16)+lshift($3,8)+$4}'` 38 | 39 | if [ -z "${CFG_FILENAME_PREFIX}" ] ; then 40 | declare -r RUN_FILENAME_PREFIX="" 41 | else 42 | declare -r RUN_FILENAME_PREFIX=${CFG_FILENAME_PREFIX//\{HOST\}/`hostname -i`} 43 | fi 44 | 45 | declare OPT_TABLE_NAME="" 46 | declare OPT_ROUTE_TEXT="" 47 | while getopts 't:r:' opt ; do 48 | case ${opt} in 49 | t) 50 | OPT_TABLE_NAME="${OPTARG}";; 51 | r) 52 | OPT_ROUTE_TEXT="${OPTARG//=/:}";; 53 | *);; 54 | esac 55 | done 56 | shift $(($OPTIND - 1)) 57 | declare OPT_FILE_NAME=$(readlink -f -- "${1}") 58 | declare OPT_IS_READY="${2}" 59 | 60 | if [ -z "${OPT_TABLE_NAME}" -o -z "${OPT_FILE_NAME}" ] ; then 61 | usage 62 | exit 100 63 | fi 64 | 65 | if [ ! -f ${OPT_FILE_NAME} ] ; then 66 | echo "No such file named as \"${OPT_FILE_NAME}\"." 67 | exit 200 68 | fi 69 | 70 | if [ -z "${OPT_IS_READY}" ] ; then 71 | OPT_IS_READY=0 72 | fi 73 | 74 | # }}} # 75 | 76 | # {{{ function log() 77 | function log() { 78 | printf "%s:\t[%s %s]\t{%d}\t%s\t%s\n" \ 79 | `echo "${1}" | tr a-z A-Z` `date +"%Y-%m-%d %H:%M:%S"` \ 80 | ${$} `echo ${2} | tr a-z A-Z` "${3}" 81 | } 82 | # }}} 83 | 84 | # {{{ function urlencode() # 85 | function urlencode() { 86 | echo "${1}" | xxd -plain | tr -d "\n" | sed 's/\(..\)/%\1/g' 87 | } 88 | # }}} # 89 | 90 | # {{{ function http() # 91 | function http() { 92 | local url="${1}" 93 | local ext="${2}" 94 | 95 | for prefix in ${CFG_MYFOX_RPC_SERVER[@]} ; do 96 | curl -s -m30 ${ext} -A"${CFG_HTTP_USER_AGENT}" -o"${RUN_HTTP_OUT}" "http://${prefix}${url}" &> /dev/null 97 | if [ ${?} -eq 0 -a `grep -c "\[0\] OK" ${RUN_HTTP_OUT}` -gt 0 ] ; then 98 | return 0 99 | fi 100 | done 101 | 102 | return 1 103 | } 104 | # }}} # 105 | 106 | # {{{ Call API Hello() # 107 | 108 | declare -i RUN_FILE_LINES=`wc -l ${OPT_FILE_NAME} | cut -d" " -f1` 109 | 110 | log "NOTICE" "START" "table=${OPT_TABLE_NAME}&route=${OPT_ROUTE_TEXT}&file=${OPT_FILE_NAME}&line=${RUN_FILE_LINES}" 111 | 112 | declare -r OPT_TABLE_NAME=`urlencode "${OPT_TABLE_NAME}"` 113 | declare -r OPT_ROUTE_TEXT=`urlencode "${OPT_ROUTE_TEXT}"` 114 | declare -r OPT_FILE_NAME=`urlencode "${RUN_FILENAME_PREFIX}/${OPT_FILE_NAME}"` 115 | declare -r RUN_POST_DATA="table=${OPT_TABLE_NAME}&route=${OPT_ROUTE_TEXT}&file=${OPT_FILE_NAME}&lines=${RUN_FILE_LINES}" 116 | 117 | http "/import/hello" "-d${RUN_POST_DATA}" 118 | if [ ${?} -ne 0 ] ; then 119 | log "FATAL" "RPC_HELLO_FAIL" "`head -n1 ${RUN_HTTP_OUT}`" 120 | exit 210 121 | fi 122 | 123 | log "NOTICE" "RPC_HELLO" 124 | 125 | if [ "${OPT_IS_READY}" -gt 0 ] ; then 126 | http "/import/ready" 127 | log "NOTICE" "RPC_READY" 128 | fi 129 | 130 | if [ -f "${RUN_HTTP_OUT}" ] ; then 131 | /bin/rm -f "${RUN_HTTP_OUT}" 132 | fi 133 | 134 | exit 0 135 | 136 | # }}} # 137 | 138 | -------------------------------------------------------------------------------- /resource/bash/myfoxctl.sh: -------------------------------------------------------------------------------- 1 | # !/bin/bash 2 | # vim: set expandtab tabstop=2 shiftwidth=2 foldmethod=marker: # 3 | 4 | export LANG=en_US.UTF-8 5 | 6 | . /etc/init.d/functions 7 | 8 | declare -r MYFOX_RUN_MODE="##run.mode##" 9 | declare -r CFG_PHP_CLI_PATH="##php.cli.path##" 10 | 11 | declare -r __ROOT__="$(dirname -- $(dirname -- $(readlink -f -- ${0})))" 12 | 13 | # {{{ function usage() # 14 | function usage() { 15 | echo "Usage: ${0} {start|stop|restart}" 16 | exit 1; 17 | } 18 | # }}} # 19 | 20 | # {{{ function checkuser() # 21 | function checkuser() { 22 | if [ "##run.user##" != `id -nu` ] ; then 23 | echo "Only ${MYFOX_RUN_USER} is allowed to run myfox!" 24 | exit 2 25 | fi 26 | } 27 | checkuser 28 | # }}} # 29 | 30 | # {{{ function daemonpid() # 31 | function daemonpid() { 32 | ps uxwwww | grep "run.php" | grep -w "${1}" | awk '{print $2}' 33 | } 34 | # }}} # 35 | 36 | # {{{ function daemonstart() # 37 | function daemonstart() { 38 | local pid=`daemonpid ${1}` 39 | if [ -z "${pid}" ] ; then 40 | echo "Start ${1} process ... " 41 | nohup ${CFG_PHP_CLI_PATH} ${__ROOT__}/bin/run.php ${1} & 42 | if [ ${?} -ne 0 ] ; then 43 | echo_failure 44 | else 45 | echo_success 46 | fi 47 | else 48 | echo "${1} (pid: ${pid}) is running" 49 | fi 50 | } 51 | # }}} # 52 | 53 | # {{{ function daemonstop() # 54 | function daemonstop() { 55 | local pid=`daemonpid ${1}` 56 | if [ -z "${pid}" ] ; then 57 | echo "${1} is not running" 58 | else 59 | echo "Stop ${1} process ..." 60 | kill ${pid} 61 | 62 | for num in 1 1 2 3 ; do 63 | if [ ${num} -gt 0 ] ; then 64 | sleep ${num} 65 | fi 66 | 67 | pid=`daemonpid ${1}` 68 | if [ -z "${pid}" ] ; then 69 | echo_success 70 | break 71 | fi 72 | sleep 1 73 | done 74 | 75 | if [ ! -z "${pid}" ] ; then 76 | kill -9 ${pid} 77 | echo_success 78 | fi 79 | fi 80 | } 81 | # }}} # 82 | 83 | # {{{ function start() # 84 | function start() { 85 | daemonstart metasync 86 | } 87 | # }}} # 88 | 89 | # {{{ function stop() # 90 | function stop() { 91 | daemonstop metasync 92 | } 93 | # }}} # 94 | 95 | case "${1}" in 96 | start) 97 | start;; 98 | stop) 99 | stop;; 100 | restart) 101 | stop 102 | start 103 | ;; 104 | *) 105 | usage;; 106 | esac 107 | -------------------------------------------------------------------------------- /test/unit/AlertTest.php: -------------------------------------------------------------------------------- 1 | assertContains(' [dev] test', ob_get_contents()); 19 | 20 | @ob_end_clean(); 21 | } 22 | /* }}} */ 23 | 24 | } 25 | 26 | -------------------------------------------------------------------------------- /test/unit/ApcTest.php: -------------------------------------------------------------------------------- 1 | 'b', 'c' => array('d' => 'e')); 28 | $apc = new Apc(__METHOD__); 29 | $this->assertEquals(null, $apc->get('key1'), 'Apc should be empty.'); 30 | 31 | $apc->set('key1', $val, 1); 32 | $apc->set('key2', $apc->get('key1')); 33 | 34 | $this->assertEquals($val, $apc->get('key1'), 'Apc set / get Error.'); 35 | 36 | // XXX: 同一进程内apc不过期 37 | //$this->assertEquals(null, $apc->get('key1'), 'Apc should has been expired.'); 38 | $this->assertEquals($val, $apc->get('key2'), 'Apc set / get Error.'); 39 | 40 | $apc->delete('key1'); 41 | $apc->delete('key2'); 42 | $this->assertEquals(null, $apc->get('key2'), 'Apc should has been delete.'); 43 | } 44 | /* }}} */ 45 | 46 | /* {{{ public void test_should_apc_with_compress_works_fine() */ 47 | public function test_should_apc_with_compress_works_fine() 48 | { 49 | $val = array('a' => 'b', 'c' => array('d' => 'e')); 50 | $apc = new Apc(__METHOD__, true); 51 | $this->assertEquals(null, $apc->get('key1'), 'Apc should be empty.'); 52 | 53 | $apc->set('key1', $val, 1); 54 | $apc->set('key2', $apc->get('key1')); 55 | 56 | $this->assertEquals($val, $apc->get('key1'), 'Apc set / get Error with compress.'); 57 | } 58 | /* }}} */ 59 | 60 | /* {{{ public void test_should_cache_shell_works_fine() */ 61 | public function test_should_cache_shell_works_fine() 62 | { 63 | $apc = new Apc(__METHOD__); 64 | $this->shell = 0; 65 | 66 | $this->assertEquals(md5(1), $apc->shell(array(&$this, 'loadShellData'), 1)); 67 | $this->assertEquals(1, $this->shell); 68 | 69 | $this->assertEquals(md5(1), $apc->shell(array(&$this, 'loadShellData'), 1)); 70 | $this->assertEquals(1, $this->shell); 71 | 72 | $this->assertEquals(md5(3), $apc->shell(array(&$this, 'loadShellData'), 3)); 73 | $this->assertEquals(2, $this->shell); 74 | } 75 | /* }}} */ 76 | 77 | /* {{{ public void test_should_apc_add_works_fine() */ 78 | public function test_should_apc_add_works_fine() 79 | { 80 | $apc = new Apc(__METHOD__); 81 | $this->assertTrue($apc->add('key1', 'val1')); 82 | $this->assertFalse($apc->add('key1', 'val2')); 83 | } 84 | /* }}} */ 85 | 86 | public function test_should_data_compress_works_fine() 87 | { 88 | $apc = new Apc(__METHOD__, true); 89 | 90 | $val = array( 91 | 'a' => 1111, 92 | 'b' => str_pad('a', Apc::COMPRESS_SIZE + 1), 93 | ); 94 | $this->assertTrue($apc->set('key1', $val)); 95 | 96 | // xxx: __destruct only for flush to apc 97 | $apc = null; 98 | 99 | $apc = new Apc(__METHOD__, true); 100 | $this->assertEquals($val, $apc->get('key1', false)); 101 | } 102 | 103 | /* {{{ public Mixture loadShellData() */ 104 | public function loadShellData($key) 105 | { 106 | $this->shell++; 107 | return md5($key); 108 | } 109 | /* }}} */ 110 | 111 | } 112 | 113 | -------------------------------------------------------------------------------- /test/unit/AutoLoadTest.php: -------------------------------------------------------------------------------- 1 | assertEquals(1, \Com\Aleafs\AutoLoadTestClass::$requireTime, 'Class Load Failed.'); 29 | 30 | $case2 = new \Com\Aleafs\AutoLoadTestClass(); 31 | $this->assertEquals(1, \Com\Aleafs\AutoLoadTestClass::$requireTime, 'Class Load Duplicate.'); 32 | $this->assertContains( 33 | strtr(__DIR__ . '/autoload/com/aleafs/autoloadtestclass.php', '\\', '/'), 34 | strtr($case2->path(), '\\', '/'), 35 | 'Class Load Error Rules.' 36 | ); 37 | } 38 | 39 | public function test_should_class_loader_by_order_worked_fine() 40 | { 41 | AutoLoad::register('com', __DIR__ . '/autoload/com'); 42 | AutoLoad::register('com\\\\aleafs1', __DIR__ . '/autoload/com', 'com'); 43 | AutoLoad::register('com\\\\aleafs2', __DIR__ . '/autoload/com', 'com'); 44 | AutoLoad::unregister('com/aleafs2'); 45 | AutoLoad::register('com\\\\aleafs', __DIR__ . '/autoload/com', 'com'); 46 | 47 | $case = new \Com\Aleafs\AutoLoadOrderTestClass(); 48 | $this->assertEquals( 49 | strtr(__DIR__ . '/autoload/com/autoloadordertestclass.php', '\\', '/'), 50 | strtr($case->path(), '\\', '/'), 51 | 'Class Load by Order Error.' 52 | ); 53 | } 54 | 55 | public function test_should_throw_file_not_found_when_cant_find_class_file() 56 | { 57 | AutoLoad::register('com', __DIR__ . '/autoload/com'); 58 | try { 59 | $case1 = new \Com\I\Am\Not\Exists(); 60 | } catch (\Exception $e) { 61 | $this->assertTrue($e instanceof \Myfox\Lib\Exception, 'Exception Type doesn\'t match,'); 62 | $this->assertContains( 63 | sprintf('File "%s/autoload/com/i/am/not/exists.php', strtr(__DIR__, '\\', '/')), 64 | strtr($e->getMessage(), '\\', '/'), 65 | 'Exception Message doesn\'t match.' 66 | ); 67 | } 68 | } 69 | 70 | public function test_should_throw_class_not_found_when_rule_not_defined() 71 | { 72 | AutoLoad::register('com', __DIR__ . '/autoload/com'); 73 | try { 74 | $case1 = new \I\Am\Not\Exists(); 75 | } catch (\Exception $e) { 76 | $this->assertTrue($e instanceof \Myfox\Lib\Exception, 'Exception Type doesn\'t match,'); 77 | $this->assertContains( 78 | 'Class "I\Am\Not\Exists" Not Found', 79 | $e->getMessage(), 80 | 'Exception Message doesn\'t match.' 81 | ); 82 | } 83 | } 84 | 85 | public function test_should_throw_class_not_found_when_class_not_in_file() 86 | { 87 | AutoLoad::register('com', __DIR__ . '/autoload/com'); 88 | try { 89 | $case1 = new \Com\Aleafs\AutoLoadTestCaseClassNameNotMatched(); 90 | } catch (\Exception $e) { 91 | $this->assertTrue($e instanceof \Myfox\Lib\Exception, 'Exception Type doesn\'t match,'); 92 | $this->assertTrue( 93 | (bool)preg_match( 94 | '/^Class "(.+?)" NOT FOUND IN "(.+?)"/is', 95 | $e->getMessage() 96 | ), 97 | 'Exception Message doesn\'t match.' 98 | ); 99 | } 100 | } 101 | } 102 | 103 | -------------------------------------------------------------------------------- /test/unit/BlackHoleTest.php: -------------------------------------------------------------------------------- 1 | lalala = '我不起作用的'; 16 | 17 | $this->assertNull($object->lalala); 18 | $this->assertNull($object->helloworld()); 19 | $this->assertNull(Blackhole::staticHello()); 20 | } 21 | 22 | } 23 | 24 | 25 | -------------------------------------------------------------------------------- /test/unit/BucketTest.php: -------------------------------------------------------------------------------- 1 | push('aa', 0); 16 | $cargos = array( 17 | array('id' => 1, 'count' => 12), 18 | array('id' => 2, 'count' => 13), 19 | array('id' => 3, 'count' => 11), 20 | array('id' => 4, 'count' => 1), 21 | array('id' => 5, 'count' => 1), 22 | ); 23 | foreach ($cargos AS $row) { 24 | $bucket->push($row, $row['count']); 25 | } 26 | 27 | $this->assertEquals( 28 | array( 29 | array( 30 | array( 31 | 'size' => 12, 32 | 'data' => $cargos[0], 33 | ), 34 | ), 35 | array( 36 | array( 37 | 'size' => 11, 38 | 'data' => $cargos[2], 39 | ), 40 | array( 41 | 'size' => 1, 42 | 'data' => $cargos[3], 43 | ), 44 | ), 45 | array( 46 | array( 47 | 'size' => 10, 48 | 'data' => $cargos[1], 49 | ), 50 | array( 51 | 'size' => 1, 52 | 'data' => $cargos[4], 53 | ), 54 | ), 55 | array( 56 | array( 57 | 'size' => 3, 58 | 'data' => $cargos[1], 59 | ), 60 | ), 61 | ), 62 | $bucket->allot() 63 | ); 64 | } 65 | /* }}} */ 66 | 67 | /* {{{ public void test_should_zero_cubage_works_fine() */ 68 | /** 69 | * XXX : THIS IS A BUG CASE 70 | */ 71 | public function test_should_zero_cubage_works_fine() 72 | { 73 | $bucket = new Bucket(0, 0.2); 74 | $bucket->push('aa', 0); 75 | $cargos = array( 76 | array('id' => 1, 'count' => 2410000), 77 | ); 78 | foreach ($cargos AS $row) { 79 | $bucket->push($row, $row['count']); 80 | } 81 | 82 | $this->assertEquals( 83 | array( 84 | array( 85 | array( 86 | 'size' => 2000000, 87 | 'data' => $cargos[0], 88 | ), 89 | ), 90 | array( 91 | array( 92 | 'size' => 410000, 93 | 'data' => $cargos[0], 94 | ), 95 | ), 96 | ), 97 | $bucket->allot() 98 | ); 99 | } 100 | /* }}} */ 101 | 102 | } 103 | 104 | -------------------------------------------------------------------------------- /test/unit/ChecktableTest.php: -------------------------------------------------------------------------------- 1 | 11, 28 | )); 29 | $this->assertEquals(11, $worker->interval()); 30 | $this->assertEquals(false, $worker->execute()); 31 | } 32 | /* }}} */ 33 | 34 | } 35 | 36 | -------------------------------------------------------------------------------- /test/unit/CleanerTest.php: -------------------------------------------------------------------------------- 1 | route_table = self::cleanTable('default', 'route_info'); 27 | $this->queque_table = self::cleanTable('default', 'task_queque'); 28 | } 29 | /* }}} */ 30 | 31 | /* {{{ public void test_should_clean_deleted_route_works_fine() */ 32 | public function test_should_clean_deleted_route_works_fine() 33 | { 34 | $query = sprintf( 35 | 'INSERT INTO %s (route_flag,hosts_list,real_table,route_text,addtime,modtime) VALUES ' . 36 | "(%d,'{1,2,3}$$','a.t_1_1','cid:1,thedate:2222',%d,%d)," . 37 | "(%d,'{1,2,3}','a.t_1_2','cid:2,thedate:2222',%d,%d)," . 38 | "(%d,'{1,2,3}','a.t_1_3','',%d,%d)," . 39 | "(%d,'}$','a.t_1_4','cid:4,thedate:2222',%d,%d)," . 40 | "(%d,'}$','a.t_1_5','cid:5,thedate:2222',%d,%d)," . 41 | "(%d,'}$','a.t_1_6','cid:6,thedate:2222',%d,%d)", 42 | $this->route_table, 43 | Router::FLAG_IS_DELETED, time() - 100100, time() - 100100, // OK 44 | Router::FLAG_IS_DELETED, time() - 99900, time() - 100000, // modtime 45 | Router::FLAG_IS_DELETED, time() - 100100, time() - 100100, // where empty 46 | Router::FLAG_IS_DELETED, time() - 100100, time() - 100100, // empty hosts 47 | Router::FLAG_PRE_IMPORT, time() - 865000, time() - 100100, // delete 48 | Router::FLAG_PRE_IMPORT, time() - 863000, time() - 100100 // don't delete 49 | ); 50 | 51 | $this->assertEquals(6, self::$mysql->query($query)); 52 | 53 | Cleaner::clean_invalidated_routes(); 54 | 55 | $this->assertEquals(array( 56 | array( 57 | 'real_table' => 'a.t_1_6', 58 | ), 59 | array( 60 | 'real_table' => 'a.t_1_2', 61 | ), 62 | ), self::$mysql->getAll(self::$mysql->query(sprintf( 63 | 'SELECT real_table FROM %s ORDER BY autokid DESC LIMIT 6', $this->route_table 64 | )))); 65 | 66 | $this->assertEquals(array( 67 | array( 68 | 'task_flag' => Queque::FLAG_WAIT, 69 | 'task_info' => json_encode(array( 70 | 'host' => '1,2,3', 71 | 'path' => 'a.t_1_3', 72 | 'where' => '', 73 | )), 74 | ), 75 | array( 76 | 'task_flag' => Queque::FLAG_WAIT, 77 | 'task_info' => json_encode(array( 78 | 'host' => '1,2,3', 79 | 'path' => 'a.t_1_1', 80 | 'where' => 'cid = 1 AND thedate = 2222', 81 | )), 82 | ), 83 | ), self::$mysql->getAll(self::$mysql->query(sprintf( 84 | "SELECT task_flag,task_info FROM %s WHERE task_type = 'delete' ORDER BY autokid DESC LIMIT 6", 85 | $this->queque_table 86 | )))); 87 | 88 | $clean = new Cleaner(array()); 89 | $this->assertEquals(false, $clean->execute(false)); 90 | } 91 | /* }}} */ 92 | 93 | } 94 | 95 | -------------------------------------------------------------------------------- /test/unit/ConfigTest.php: -------------------------------------------------------------------------------- 1 | assertTrue(false); 28 | } catch (\Exception $e) { 29 | $this->assertTrue($e instanceof \Myfox\Lib\Exception); 30 | $this->assertContains('Undefined config name as "i_am_not_exists"', $e->getMessage()); 31 | } 32 | 33 | Config::register('confIg1', 'http://localhost/a.ini'); 34 | $obj1 = Config::instance('config1'); 35 | $obj2 = Config::instance('CONF Ig1'); 36 | $this->assertEquals($obj1, $obj2); 37 | } 38 | /* }}} */ 39 | 40 | /* {{{ public void test_should_config_with_ini_works_fine() */ 41 | public function test_should_config_with_ini_works_fine() 42 | { 43 | $config = new Config(__DIR__ . '/ini/config_test.ini', 'unittest'); 44 | $this->assertEquals('abc', $config->get('key1')); 45 | $this->assertEquals(array( 46 | 'a' => 1.23456789, 47 | 'b' => 9.87654321, 48 | ), $config->get('key2')); 49 | $this->assertEquals('null', $config->get('key3', 'null')); 50 | 51 | $config = Config::instance('unittest'); 52 | $this->assertEquals('', $config->get('key2/a')); 53 | $this->assertEquals(9.87654321, $config->get('key2/b')); 54 | } 55 | /* }}} */ 56 | 57 | } 58 | 59 | -------------------------------------------------------------------------------- /test/unit/ConsistTest.php: -------------------------------------------------------------------------------- 1 | assertTrue(false); 30 | } catch (\Exception $e) { 31 | $this->assertTrue($e instanceof \Myfox\Lib\Exception); 32 | $this->assertContains("Empty server list for consistency check", $e->getMessage()); 33 | } 34 | 35 | $this->assertTrue(Consist::check('i_am_not_exists.lalalla', array('edp1_9801', 'edp2_9902'))); 36 | $this->assertTrue(Consist::check('mirror_0.mirror_583_2', array('edp1_9801', 'edp2_9902'))); 37 | } 38 | /* }}} */ 39 | 40 | } 41 | 42 | -------------------------------------------------------------------------------- /test/unit/ContextTest.php: -------------------------------------------------------------------------------- 1 | assertEquals('val1', Context::get('key1', 'val2')); 31 | $this->assertEquals('default', Context::get('key2', 'default')); 32 | } 33 | /* }}} */ 34 | 35 | /* {{{ public void test_should_get_correct_pid() */ 36 | public function test_should_get_correct_pid() 37 | { 38 | $this->assertEquals(getmypid(), Context::pid()); 39 | } 40 | /* }}} */ 41 | 42 | /* {{{ public void test_should_get_correct_userip() */ 43 | public function test_should_get_correct_userip() 44 | { 45 | $_SERVER = array(); 46 | $this->assertEquals('unknown', Context::userip()); 47 | $this->assertEquals(0, Context::userip(true)); 48 | 49 | $_SERVER['REMOTE_ADDR'] = '127.0.0.1'; 50 | $this->assertEquals('unknown', Context::userip()); 51 | $this->assertEquals(0, Context::userip(true)); 52 | 53 | Context::cleanAllContext(); 54 | $this->assertEquals('127.0.0.1', Context::userip()); 55 | $this->assertEquals(ip2long('127.0.0.1'), Context::userip(true)); 56 | 57 | $_SERVER['HTTP_X_FORWARDED_FOR'] = '202.111.111.123,59.66.192.112'; 58 | Context::cleanAllContext(); 59 | $this->assertEquals('59.66.192.112', Context::userip()); 60 | $this->assertEquals(ip2long('59.66.192.112'), Context::userip(true)); 61 | } 62 | /* }}} */ 63 | 64 | /* {{{ public void test_should_addr_works_fine() */ 65 | public function test_should_addr_works_fine() 66 | { 67 | $ip = trim(`hostname -i`); 68 | $this->assertEquals($ip, Context::addr()); 69 | } 70 | /* }}} */ 71 | 72 | } 73 | 74 | -------------------------------------------------------------------------------- /test/unit/DaemonTest.php: -------------------------------------------------------------------------------- 1 | sigaction(SIGINT, function() use ($daemon) { 49 | echo "Got signal SIGINT\n"; 50 | $daemon->freelock(); 51 | }); 52 | 53 | $time1 = microtime(true); 54 | 55 | ob_start(); 56 | $daemon->dispatch(); 57 | $this->assertEquals(true, 1000 * (microtime(true) - $time1 - 1) < 50); 58 | $this->assertContains('Got signal SIGINT', ob_get_contents()); 59 | @ob_end_clean(); 60 | } 61 | /* }}} */ 62 | 63 | /* {{{ public void test_should_daemon_works_fine() */ 64 | public function test_should_daemon_works_fine() 65 | { 66 | \Myfox\App\Worker\Test::$number = 0; 67 | 68 | $time1 = microtime(true); 69 | Daemon::run(self::$inifile, array( 70 | 'script', 71 | 'test', 72 | '--sleep', 73 | 10, 74 | '--loop' 75 | )); 76 | $this->assertEquals( 77 | true, 78 | abs(1000 * (microtime(true) - $time1) - 50) < 10 79 | ); 80 | $this->assertEquals( 81 | 5, 82 | \Myfox\App\Worker\Test::$number 83 | ); 84 | } 85 | /* }}} */ 86 | 87 | /* {{{ public void test_should_parse_option_works_fine() */ 88 | public function test_should_parse_option_works_fine() 89 | { 90 | $this->assertEquals(array( 91 | 'a' => 1, 92 | 'b' => 2, 93 | 'c' => true, 94 | 'f' => 0, 95 | 'data' => 2, 96 | 'debug' => true, 97 | 'eof' => 'abcdefg', 98 | ), Daemon::parse(array( 99 | '-a', 100 | '1', 101 | '-b2', 102 | '-c', 103 | '-f0', 104 | '--data', 105 | '2', 106 | '--debug', 107 | '--eof=abcdefg', 108 | ))); 109 | } 110 | /* }}} */ 111 | 112 | /* {{{ public void test_should_daemon_lock_works_fine() */ 113 | public function test_should_daemon_lock_works_fine() 114 | { 115 | Daemon::run(self::$inifile, array( 116 | 'script', 117 | 'test', 118 | '--locker', 119 | 'test loCkeR ', 120 | '--sleep=1' 121 | )); 122 | Daemon::run(self::$inifile, array( 123 | 'script', 124 | 'test', 125 | '--locker', 126 | 'test locker', 127 | '--sleep=1' 128 | )); 129 | } 130 | /* }}} */ 131 | 132 | } 133 | 134 | -------------------------------------------------------------------------------- /test/unit/DebugTest.php: -------------------------------------------------------------------------------- 1 | assertEquals(null, Timer::elapsed('key1')); 33 | 34 | Timer::init(true); 35 | $t1 = microtime(true); 36 | Timer::start('key1'); 37 | usleep(300000); // 3ms 38 | $this->assertEquals(null, Timer::elapsed('key2')); 39 | 40 | $t2 = microtime(true); 41 | $tt = Timer::elapsed('key1'); 42 | $this->assertTrue(10 > 1000 * (int)abs($tt - $t2 + $t1)); 43 | 44 | $this->assertEquals(null, Timer::elapsed('key1')); 45 | } 46 | /* }}} */ 47 | 48 | /* {{{ public void test_should_ignore_push_when_debug_not_open() */ 49 | /** 50 | * 调试关闭时Push不起作用 51 | * @return void 52 | */ 53 | public function test_should_ignore_push_when_debug_not_open() 54 | { 55 | Pool::init(false); 56 | $this->assertFalse(Pool::push('test', '啦啦啦啦')); 57 | $this->assertEquals('NULL', Pool::dump('test')); 58 | } 59 | /* }}} */ 60 | 61 | /* {{{ public void test_should_push_message_works_fine() */ 62 | /** 63 | * 测试开启状态下的push功能 64 | */ 65 | public function test_should_push_message_works_fine() 66 | { 67 | Pool::init(true); 68 | 69 | $val = 'debug1'; 70 | Pool::push('test1', $val); 71 | $this->assertEquals(var_export($val, true), Pool::dump('test1')); 72 | 73 | $exp = array( 74 | $val, 75 | array( 76 | 'text' => '我是中文', 77 | ), 78 | ); 79 | Pool::push('test1', end($exp)); 80 | $this->assertEquals(var_export($exp, true), Pool::dump('test1')); 81 | 82 | $obj = new Stdclass(); 83 | $obj->val1 = 'key1'; 84 | $obj->val2 = array('啦啦啦'); 85 | 86 | Pool::push('test2', $obj); 87 | $this->assertEquals(var_export($obj, true), Pool::dump('test2')); 88 | 89 | $exp = array( 90 | 'test1' => $exp, 91 | 'test2' => $obj, 92 | ); 93 | $this->assertEquals(var_export($exp, true), Pool::dump()); 94 | } 95 | /* }}} */ 96 | 97 | } 98 | -------------------------------------------------------------------------------- /test/unit/DispatcherTest.php: -------------------------------------------------------------------------------- 1 | inifile = __DIR__ . '/ini/myfox.ini'; 21 | $config = new Config($this->inifile); 22 | $logurl = parse_url($config->get('log/default', '')); 23 | 24 | $this->prefix = rtrim($config->get('url.prefix', ''), '/'); 25 | $this->logfile = $logurl['path']; 26 | @unlink($this->logfile); 27 | ob_start(); 28 | } 29 | 30 | protected function tearDown() 31 | { 32 | @ob_end_clean(); 33 | @unlink($this->logfile); 34 | parent::tearDown(); 35 | } 36 | 37 | /* {{{ public void test_should_dispatcher_works_fine() */ 38 | public function test_should_dispatcher_works_fine() 39 | { 40 | \Myfox\App\Dispatcher::run( 41 | __DIR__ . '/ini/myfox.ini', 42 | $this->prefix . '/', 43 | null 44 | ); 45 | 46 | $this->assertContains('', ob_get_clean()); 47 | $this->assertContains( 48 | "\tREQUEST\t-\t{\"url\":\"\/\",\"post\":[]}", 49 | self::getLogContents($this->logfile, -1) 50 | ); 51 | 52 | \Myfox\App\Dispatcher::run( 53 | __DIR__ . '/ini/myfox.ini', 54 | $this->prefix . '/i_am_not_exist', 55 | 'a[]=b&c1=' . urlencode('//') 56 | ); 57 | $this->assertContains( 58 | "\tEXCEPTION\t-\t{\"url\":\"\/i_am_not_exist\",\"post\":{\"a\":[\"b\"],\"c1\":\"\/\/\"},\"error\":", 59 | self::getLogContents($this->logfile, -1) 60 | ); 61 | } 62 | /* }}} */ 63 | 64 | } 65 | -------------------------------------------------------------------------------- /test/unit/FactoryTest.php: -------------------------------------------------------------------------------- 1 | assertEquals('hello', $object->a); 30 | $this->assertEquals('default', $object->b); 31 | $object->b = 'world'; 32 | 33 | $this->assertEquals($object, Factory::getObject( 34 | 'Myfox\\Test\\Factory\\Test', 'hello' 35 | )); 36 | } 37 | /* }}} */ 38 | 39 | /* {{{ public void test_should_factory_get_log_works_fine() */ 40 | public function test_should_factory_get_log_works_fine() 41 | { 42 | $log = Factory::getLog('Iamnotexist'); 43 | $this->assertTrue($log instanceof \Myfox\Lib\Blackhole); 44 | 45 | Factory::registerLog('unittest', sprintf( 46 | 'log://debug.notice.warning.error/%s/tmp/test.log?buffer=0', 47 | __DIR__ 48 | )); 49 | 50 | $log = Factory::getLog('UnittEst '); 51 | $this->assertTrue($log instanceof \Myfox\Lib\Log); 52 | } 53 | /* }}} */ 54 | 55 | } 56 | 57 | -------------------------------------------------------------------------------- /test/unit/FilesetTest.php: -------------------------------------------------------------------------------- 1 | assertEquals(false, Fileset::getfile('/i/am/not/exists?query string does\'t effect')); 16 | $this->assertContains('File not found as the path "/i/am/not/exists"', Fileset::lastError()); 17 | $this->assertEquals(__FILE__, Fileset::getfile(__FILE__)); 18 | } 19 | /* }}} */ 20 | 21 | /* {{{ public void test_should_getfile_exception_works_fine() */ 22 | public function test_should_getfile_exception_works_fine() 23 | { 24 | $this->assertEquals(false, Fileset::getfile('http://www.taobao.com', '/i/am/not/exists/')); 25 | $this->assertContains('Unrecognized url as "http://www.taobao.com" for fileset.', Fileset::lastError()); 26 | 27 | $this->assertEquals(false, Fileset::getfile('http://www.taobao.com/index.html', '/i/am/not/exists/taobao.txt')); 28 | $this->assertContains('Path "/i/am/not/exists" doesn\'t exist, and create failed.', Fileset::lastError()); 29 | 30 | $this->assertEquals(false, Fileset::getfile('undefined://user:pass@ftp.a.com//b.txt')); 31 | $this->assertContains(sprintf( 32 | 'File "%s/lib/fetcher/undefined.php" Not Found.', 33 | realpath(__DIR__ . '/../../') 34 | ), Fileset::lastError()); 35 | } 36 | /* }}} */ 37 | 38 | /* {{{ public void test_should_get_file_from_ftp_works_fine() */ 39 | public function test_should_get_file_from_ftp_works_fine() 40 | { 41 | $this->assertEquals(false, Fileset::getfile('ftp://www.baidu.com/aa.txt')); 42 | $this->assertContains('Connect failed with the host as "www.baidu.com" on port 21', Fileset::lastError()); 43 | 44 | $fname = Fileset::getfile('ftp://ftp.adobe.com/license.txt?timeout=5'); 45 | $this->assertEquals(sprintf('%s/license.txt', Fileset::TEMP_PATH), $fname); 46 | } 47 | /* }}} */ 48 | 49 | /* {{{ public void test_should_get_file_from_scp_works_fine() */ 50 | public function test_should_get_file_from_scp_works_fine() 51 | { 52 | #exec('ssh-keygen -t rsa'); 53 | /* 信任关系打通 设置免登 */ 54 | exec('ssh-copy-id -i ~/.ssh/id_rsa.pub 10.232.128.63'); 55 | #$user = get_current_user(); 56 | $user = array_shift(explode(' ', trim(exec('who am i')))); 57 | $fname = sprintf('Myfox_scp_test%s.txt', getmypid()); 58 | 59 | $lpath = sprintf('/tmp/myfox/download/Myfox_scp_test%s.txt', getmypid()); 60 | $rpath = sprintf('/home/%s/Myfox_scp_test%s.txt', $user, getmypid()); 61 | 62 | $url = sprintf('scp://%s@10.232.128.63%s', $user, $rpath); 63 | 64 | exec(sprintf('ssh %s@10.232.128.63 "rm -rf %s"', $user, $rpath)); 65 | @unlink($lpath); 66 | 67 | 68 | $this->assertEquals(false, Fileset::getfile($url)); 69 | $this->assertContains('No such file or directory', Fileset::lastError()); 70 | 71 | exec(sprintf('ssh %s@10.232.128.63 "touch %s"', $user, $rpath)); 72 | sleep(2); 73 | $this->assertEquals( $lpath, Fileset::getfile($url)); 74 | 75 | $scp = new Scp(array( 76 | 'scheme' => 'scp', 77 | 'host' => '10.232.128.63', 78 | 'user' => $user, 79 | 'path' => $rpath, 80 | )); 81 | 82 | $this->assertEquals( false, $scp->isChange($lpath)); 83 | 84 | exec(sprintf('ssh %s@10.232.128.63 "echo \"test data\" >> %s"', $user, $rpath)); 85 | 86 | $this->assertEquals( $lpath, Fileset::getfile($url)); 87 | $content = file_get_contents($lpath); 88 | $this->assertContains('test data', $content); 89 | } 90 | /* }}} */ 91 | } 92 | -------------------------------------------------------------------------------- /test/unit/FsplitTest.php: -------------------------------------------------------------------------------- 1 | 10000, 86 | __DIR__ . '/tmp/fsplit_test.txt_1' => 10000, 87 | __DIR__ . '/tmp/fsplit_test.txt_2' => 6000, 88 | ); 89 | 90 | $fname = __DIR__ . '/tmp/fsplit_test.txt'; 91 | $this->assertTrue(self::prepare_test_file($fname, 27000)); 92 | $this->assertEquals( 93 | array_keys($expect), 94 | Fsplit::chunk($fname, array_values($expect), __DIR__ . '/tmp') 95 | ); 96 | 97 | $total = 0; 98 | foreach ($expect AS $fname => $lines) { 99 | $realn = self::fileline($fname); 100 | $total += $realn; 101 | if (false !== next($expect)) { 102 | $this->assertTrue($lines * 0.95 < $realn && $lines * 1.05 > $realn); 103 | } 104 | } 105 | $this->assertEquals(27000, $total); 106 | } 107 | /* }}} */ 108 | 109 | /* {{{ public void test_should_ignore_chunks_more_than_total_line() */ 110 | public function test_should_ignore_chunks_more_than_total_line() 111 | { 112 | $fname = __DIR__ . '/tmp/fsplit_test.txt'; 113 | $this->assertTrue(self::prepare_test_file($fname, 2700)); 114 | $this->assertEquals(array( 115 | __DIR__ . '/tmp/fsplit_test.txt_0', 116 | __DIR__ . '/tmp/fsplit_test.txt_1', 117 | ), Fsplit::chunk($fname, array(2000, 1000, 1000), __DIR__ . '/tmp')); 118 | } 119 | /* }}} */ 120 | 121 | /* {{{ public void test_should_get_last_error_works_fine() */ 122 | public function test_should_get_last_error_works_fine() 123 | { 124 | $object = new Fsplit('/i/am/not/exits'); 125 | $this->assertEquals(false, $object->split(array(100))); 126 | $this->assertContains('No such file named as "/i/am/not/exits"', $object->lastError()); 127 | 128 | $object = new Fsplit(__FILE__); 129 | $this->assertEquals(false, $object->split(array(100), '/created/denied')); 130 | $this->assertContains('Directory "/created/denied" created failed', $object->lastError()); 131 | 132 | $fname = __DIR__ . '/tmp/fsplit_test.txt'; 133 | !is_dir(dirname($fname)) && @mkdir(dirname($fname), 0755, true); 134 | file_put_contents($fname, 'bbbbbbb'); 135 | 136 | $object = new Fsplit($fname); 137 | $this->assertEquals(false, $object->split(array(100), __DIR__ . '/tmp')); 138 | $this->assertContains('Unrecognized text formmat, or line size larger than ' . Fsplit::BUFFER_SIZE, $object->lastError()); 139 | } 140 | /* }}} */ 141 | 142 | } 143 | 144 | -------------------------------------------------------------------------------- /test/unit/LiveBoxTest.php: -------------------------------------------------------------------------------- 1 | | 9 | // +--------------------------------------------------------------------+ 10 | // 11 | // $Id: LiveBoxTest.php 47 2010-04-26 05:27:46Z zhangxc83 $ 12 | 13 | use \Myfox\Lib\LiveBox; 14 | require_once(__DIR__ . '/../../lib/TestShell.php'); 15 | 16 | class LiveBoxTest extends \Myfox\Lib\TestShell 17 | { 18 | 19 | protected function setUp() 20 | { 21 | parent::setUp(); 22 | $this->pool = new LiveBox(__CLASS__); 23 | } 24 | 25 | protected function tearDown() 26 | { 27 | if (!empty($this->pool)) { 28 | $this->pool->cleanAllCache(); 29 | unset($this->pool); 30 | } 31 | 32 | parent::tearDown(); 33 | } 34 | 35 | /* {{{ public function test_should_right_select_by_random() */ 36 | public function test_should_random_select_works_fine() 37 | { 38 | $hosts = array( 39 | '127.0.0.1:1234', 40 | '127.0.0.1:1234', 41 | '127.0.0.1:1235', 42 | '127.0.0.1:1236', 43 | '127.0.0.1:1237', 44 | '127.0.0.1:1238', 45 | '127.0.0.1:1239', 46 | ); 47 | 48 | foreach ($hosts AS $host) { 49 | $this->pool->register($host); 50 | } 51 | $this->pool->register('I\m not exists'); 52 | 53 | $result = array(); 54 | for ($i = 0; $i < 10000; $i++) { 55 | $host = $this->pool->fetch(); 56 | if (!preg_match('/^[\d\.]+:\d+$/is', $host)) { /**< 模拟连接 */ 57 | $this->pool->setOffline(); 58 | } 59 | 60 | if (!isset($result[$host])) { 61 | $result[$host] = 1; 62 | } else { 63 | $result[$host]++; 64 | } 65 | } 66 | $this->assertTrue($result['I\m not exists'] < 5, 'setOffline Doesn\t work.'); 67 | 68 | $total = 10000 / count($hosts); 69 | $weight = array(); 70 | foreach ($hosts AS $host) { 71 | $weight[$host] = isset($weight[$host]) ? $weight[$host] + 1 : 1; 72 | } 73 | 74 | foreach ($weight AS $host => $wt) { 75 | $this->assertTrue( 76 | ($result[$host] >= 0.8 * $wt * $total) && ($result[$host] <= 1.2 * $wt * $total), 77 | sprintf('Host "%s" random selector error.', $host) 78 | ); 79 | } 80 | 81 | } 82 | /* }}} */ 83 | 84 | /* {{{ public function test_should_reset_when_all_offline() */ 85 | public function test_should_reset_when_all_offline() 86 | { 87 | $this->pool->register('www.baidu.com', 'baidu') 88 | ->register('www.google.com', 'google') 89 | ->setOffline('baidu') 90 | ->setOffline('google'); 91 | 92 | $this->assertContains( 93 | $this->pool->fetch(), 94 | array( 95 | 'www.baidu.com', 96 | 'www.google.com', 97 | ) 98 | ); 99 | } 100 | /* }}} */ 101 | 102 | /* {{{ public function test_should_live_time_works_fine() */ 103 | public function test_should_live_time_works_fine() 104 | { 105 | if (!function_exists('apc_add')) { 106 | return; 107 | } 108 | 109 | $pool = new LiveBox(__CLASS__, 2); 110 | $pool->register('www.baidu.com', 'www.baidu.com')->setOffline('www.baidu.com'); 111 | unset($pool); 112 | 113 | $pool = new LiveBox(__CLASS__); 114 | $pool->register('www.baidu.com', 'www.baidu.com')->register('www.google.com'); 115 | 116 | for ($i = 0; $i < 10; $i++) { 117 | $this->assertTrue( 118 | $pool->fetch() != 'www.baidu.com', 119 | 'Offline host should NOT appear!' 120 | ); 121 | } 122 | unset($pool); 123 | 124 | sleep(2); 125 | $pool = new LiveBox(__CLASS__); 126 | $pool->register('www.baidu.com', 'www.baidu.com')->register('www.google.com'); 127 | 128 | $return = array(); 129 | for ($i = 0; $i < 1000; $i++) { 130 | $host = $pool->fetch(); 131 | if (!isset($return[$host])) { 132 | $return[$host] = 1; 133 | } else { 134 | $return[$host]++; 135 | } 136 | } 137 | 138 | $this->assertTrue( 139 | 400 <= $return['www.baidu.com'] && 140 | $return['www.baidu.com'] <= 600 141 | ); 142 | $this->assertTrue( 143 | 400 <= $return['www.google.com'] && 144 | $return['www.google.com'] <= 600 145 | ); 146 | } 147 | /* }}} */ 148 | 149 | } 150 | 151 | -------------------------------------------------------------------------------- /test/unit/MonitorTest.php: -------------------------------------------------------------------------------- 1 | assertEquals(array( 32 | 'failed' => 0, 33 | 'pending' => 0, 34 | ), $status['']); 35 | 36 | self::$mysql->query(sprintf( 37 | "INSERT INTO %stask_queque (addtime,task_flag,trytimes) VALUES('%s',%u,1),('%s',%u,4),('%s',%u,0),('%s',%u,0)", 38 | self::$mysql->option('prefix'), date('Y-m-d H:i:s', time() - 301), \Myfox\App\Queque::FLAG_WAIT, 39 | date('Y-m-d H:i:s', time() - 290), \Myfox\App\Queque::FLAG_WAIT, 40 | date('Y-m-d H:i:s', time() - 290), \Myfox\App\Queque::FLAG_WAIT, 41 | date('Y-m-d H:i:s', time() - 390), \Myfox\App\Queque::FLAG_NEW 42 | )); 43 | $status = Monitor::queque_status_monitor(); 44 | $this->assertEquals(array( 45 | 'failed' => 1, 46 | 'pending' => 1, 47 | ), $status['']); 48 | 49 | $monitor = new Monitor(array('sleep' => 22)); 50 | $this->assertEquals(true, $monitor->execute(true)); 51 | $this->assertEquals(22, $monitor->interval()); 52 | } 53 | /* }}} */ 54 | 55 | /* {{{ public void test_should_import_consist_works_fine() */ 56 | public function test_should_import_consist_works_fine() 57 | { 58 | $hosts = self::$mysql->getAll(self::$mysql->query(sprintf( 59 | 'SELECT host_id, host_name FROM %shost_list WHERE host_type = %d', 60 | self::$mysql->option('prefix'), Server::TYPE_REALITY 61 | ))); 62 | 63 | $create = "CREATE TABLE IF NOT EXISTS test.test_a ( 64 | id int(10) unsigned not null auto_increment primary key, 65 | num1 int(10) not null default 0, 66 | char1 varchar(15) not null default '' 67 | ) ENGINE=MYISAM DEFAULT CHARSET=UTF8"; 68 | 69 | $ids = array(); 70 | foreach ($hosts AS $server) { 71 | $mysql = Server::instance($server['host_name'])->getlink(); 72 | $mysql->query('DROP TABLE IF EXISTS test.test_a'); 73 | $mysql->query($create); 74 | $this->assertEquals(3, $mysql->query( 75 | "INSERT INTO test.test_a (id,num1,char1) VALUES (1, 2, 'bbb'),(2,3,'cccc'),(3,4,'dddd')" 76 | )); 77 | $ids[] = (int)$server['host_id']; 78 | } 79 | 80 | $this->assertEquals(1, self::$mysql->query(sprintf( 81 | "INSERT INTO %sroute_info (real_table,hosts_list,table_name,modtime,route_flag)". 82 | " VALUES ('test.test_a', '%s', 'test',%d,%d)", 83 | self::$mysql->option('prefix'), implode(',', $ids), time(), \Myfox\App\Model\Router::FLAG_IMPORT_END 84 | ))); 85 | 86 | \Myfox\App\Setting::set('monitor_consist_check', date('Y-m-d H:i:s', time() - 3600)); 87 | $this->assertEquals(array(), Monitor::import_consist_monitor()); 88 | 89 | $server = reset($hosts); 90 | Server::instance($server['host_name'])->getlink()->query(sprintf( 91 | 'DELETE FROM test.test_a ORDER BY id ASC LIMIT 1' 92 | )); 93 | 94 | \Myfox\App\Setting::set('monitor_consist_check', date('Y-m-d H:i:s', time() - 3600)); 95 | $result = Monitor::import_consist_monitor(); 96 | 97 | $this->assertTrue(!empty($result)); 98 | $result = reset($result); 99 | $this->assertTrue(!empty($result['checks'])); 100 | } 101 | /* }}} */ 102 | 103 | } 104 | 105 | -------------------------------------------------------------------------------- /test/unit/MysqlTest.php: -------------------------------------------------------------------------------- 1 | get('logurl', '')); 21 | 22 | $this->logfile = $logurl['path']; 23 | Mysql::removeAllNames(); 24 | 25 | self::$mysql = new Mysql(__DIR__ . '/ini/mysql.ini'); 26 | self::$mysql->query('DROP TABLE IF EXISTS only_for_test'); 27 | self::$mysql->query('CREATE TABLE `only_for_test` ( 28 | `id` int(10) unsigned NOT NULL AUTO_INCREMENT, 29 | `content` varchar(32) DEFAULT NULL, 30 | PRIMARY KEY (`id`) 31 | ) ENGINE=InnoDB DEFAULT CHARSET=utf8'); 32 | 33 | @unlink($this->logfile); 34 | } 35 | /* }}} */ 36 | 37 | /* {{{ protected void tearDown() */ 38 | protected function tearDown() 39 | { 40 | @unlink($this->logfile); 41 | parent::tearDown(); 42 | } 43 | /* }}} */ 44 | 45 | /* {{{ public void test_should_mysql_factory_works_fine() */ 46 | public function test_should_mysql_factory_works_fine() 47 | { 48 | try { 49 | $mysql = Mysql::instance('i_am_not_exists'); 50 | $this->assertTrue(false); 51 | } catch (\Exception $e) { 52 | $this->assertTrue($e instanceof \Myfox\Lib\Exception); 53 | $this->assertContains('Undefined mysql instance named as "i_am_not_exists"', $e->getMessage()); 54 | } 55 | 56 | $mysql = new Mysql('', 'mysql3'); 57 | $this->assertEquals(Mysql::instance('MYSQl3'), $mysql); 58 | 59 | Mysql::register('test1', array( 60 | 'dbname' => 'test', 61 | 'prefix' => 'myfox_', 62 | )); 63 | $mysql = Mysql::instance('test1'); 64 | $this->assertEquals('test', $mysql->option('dbname')); 65 | $this->assertEquals('myfox_', $mysql->option('prefix')); 66 | $this->assertEquals('utf8', $mysql->option('charset')); 67 | } 68 | /* }}} */ 69 | 70 | /* {{{ public void test_should_simple_query_works_fine() */ 71 | public function test_should_simple_query_works_fine() 72 | { 73 | $mysql = new Mysql(__DIR__ . '/ini/mysql_test.ini'); 74 | try { 75 | $mysql->connectToSlave(); 76 | } catch (\Exception $e) { 77 | $this->assertContains( 78 | "\tCONNECT_ERROR\t-\t{\"host\":\"127.0.0.1\",\"port\":3306,\"user\":\"user_ro\",\"pass\":\"**\",\"error\":", 79 | self::getLogContents($this->logfile, -1) 80 | ); 81 | } 82 | 83 | $rs = self::$mysql->getAll(self::$mysql->query('SHOW DATABASES')); 84 | $this->assertContains("\tQUERY_OK\t-\t{\"sql\":\"SHOW DATABASES\"", self::getLogContents($this->logfile, -1)); 85 | $this->assertContains(array('Database' => 'test'), $rs); 86 | 87 | $this->assertFalse(self::$mysql->query('I AM A WRONG QUERY')); 88 | $this->assertContains( 89 | "\tQUERY_ERROR\t-\t{\"sql\":\"I AM A WRONG QUERY\",\"async\":false,\"error\":", 90 | self::getLogContents($this->logfile, -1) 91 | ); 92 | 93 | $this->assertEquals(1, self::$mysql->query('INSERT INTO only_for_test (content) VALUES ("aabbcc")')); 94 | $lastId = self::$mysql->lastId(); 95 | $this->assertEquals($lastId, self::$mysql->getOne(self::$mysql->query( 96 | 'SELECT MAX(id) FROM only_for_test' 97 | ))); 98 | self::$mysql->query('INSERT INTO only_for_test (content) VALUES ("aabbcc2")'); 99 | $this->assertTrue($lastId < self::$mysql->lastId()); 100 | } 101 | /* }}} */ 102 | 103 | /* {{{ public void test_should_escape_works_fine() */ 104 | public function test_should_escape_works_fine() 105 | { 106 | $this->assertEquals(array( 107 | 'a' => 'i\\\'m chinese', 108 | '\\\'' => '省', 109 | ), self::$mysql->escape(array( 110 | 'a' => 'i\'m chinese', 111 | "'" => '省', 112 | ))); 113 | } 114 | /* }}} */ 115 | 116 | /* {{{ public void test_should_async_query_works_fine() */ 117 | public function test_should_async_query_works_fine() 118 | { 119 | $id = self::$mysql->async( 120 | 'INSERT INTO only_for_test (content) VALUES ("aabbcc")' 121 | ); 122 | $this->assertEquals($id + 1, self::$mysql->async('SELECT MAX(id) FROM only_for_test')); 123 | 124 | $this->assertEquals(1, self::$mysql->wait($id)); 125 | $this->assertEquals(1, self::$mysql->getOne(self::$mysql->wait($id + 1))); 126 | } 127 | /* }}} */ 128 | 129 | /* {{{ public void test_should_mysql_reconnect_when_gone_away_works_fine() */ 130 | public function test_should_mysql_reconnect_when_gone_away_works_fine() 131 | { 132 | $mysql = new Mysql(__DIR__ . '/ini/mysql.ini'); 133 | $result = $mysql->getAll($mysql->query('SHOW DATABASES')); 134 | $this->assertEquals(0, $mysql->query(sprintf('KILL %d', $mysql->thread_id))); 135 | $this->assertEquals($result, $mysql->getAll($mysql->query('SHOW DATABASES'))); 136 | } 137 | /* }}} */ 138 | 139 | } 140 | 141 | -------------------------------------------------------------------------------- /test/unit/ProcessorTest.php: -------------------------------------------------------------------------------- 1 | query(sprintf('TRUNCATE TABLE %stask_queque', self::$mysql->option('prefix', ''))); 21 | } 22 | /* }}} */ 23 | 24 | /* {{{ public void test_should_undefined_task_type_ignore_works_fine() */ 25 | public function test_should_undefined_task_type_ignore_works_fine() 26 | { 27 | $this->assertTrue(Queque::instance()->insert( 28 | 'i am not defined', array( 29 | 'src' => 'http://www.taobao.com', 30 | ), 31 | 1, array( 32 | 'trytimes' => 2, 33 | 'adduser' => 'unittest', 34 | 'priority' => 201, 35 | ) 36 | )); 37 | 38 | $worker = new Processor(array( 39 | 'n' => 1, 40 | )); 41 | 42 | $this->assertTrue($worker->execute()); 43 | $this->assertEquals(1, $worker->interval()); 44 | 45 | $queque = self::$mysql->getRow(self::$mysql->query( 46 | sprintf('SELECT * FROM %stask_queque LIMIT 1', self::$mysql->option('prefix', '')) 47 | )); 48 | $this->assertEquals(1, $queque['trytimes']); 49 | $this->assertEquals(Queque::FLAG_IGNO, $queque['task_flag']); 50 | $this->assertContains("Undefined task_type named as 'i am not defined'", $queque['last_error']); 51 | } 52 | /* }}} */ 53 | 54 | } 55 | 56 | -------------------------------------------------------------------------------- /test/unit/QuequeTest.php: -------------------------------------------------------------------------------- 1 | assertEquals(null, $queque->fetch()); 38 | $this->assertTrue($queque->insert( 39 | 'test ', array( 40 | 'src' => 'http://www.taobao.com', 41 | ), 42 | 1, 43 | array( 44 | 'trytimes' => 2, 45 | 'adduser' => 'unittest', 46 | 'priority' => 201, 47 | ) 48 | )); 49 | 50 | $task = $queque->fetch(1, 2); 51 | $this->assertEquals(array( 52 | 'id' => 1, 53 | 'type' => 'test', 54 | 'status' => '', 55 | 'info' => json_encode(array( 56 | 'src' => 'http://www.taobao.com', 57 | )), 58 | ), $task); 59 | 60 | $this->assertEquals(1, $queque->update($task['id'], array( 61 | 'trytimes' => 'trytimes + 1', 62 | 'priority' => 202, 63 | ), array( 64 | 'trytimes' => true, 65 | ))); 66 | 67 | $this->assertEquals(array( 68 | 'trytimes' => 3, 69 | 'priority' => 202, 70 | ), self::$mysql->getRow(self::$mysql->query(sprintf( 71 | 'SELECT trytimes, priority FROM %s.%stask_queque WHERE autokid = %d', 72 | self::$mysql->option('dbname', 'meta_myfox_config'), 73 | self::$mysql->option('prefix', ''), 74 | $task['id'] 75 | )))); 76 | } 77 | /* }}} */ 78 | 79 | /* {{{ public void test_should_close_race_queque_works_fine() */ 80 | public function test_should_close_race_queque_works_fine() 81 | { 82 | $queque = Queque::instance(); 83 | $this->assertEquals(null, $queque->fetch()); 84 | 85 | $this->assertTrue($queque->insert( 86 | 'test ', array( 87 | 'src' => 'http://www.taobao.com', 88 | ), 89 | 1, 90 | array( 91 | 'trytimes' => 2, 92 | 'adduser' => 'unittest', 93 | 'priority' => 201, 94 | 'openrace' => 0, 95 | ) 96 | )); 97 | 98 | $this->assertEquals(null, $queque->fetch(1, 2)); 99 | $this->assertEquals(array( 100 | 'id' => 1, 101 | 'type' => 'test', 102 | 'status' => '', 103 | 'info' => json_encode(array( 104 | 'src' => 'http://www.taobao.com', 105 | )), 106 | ), $queque->fetch(1, 1)); 107 | } 108 | /* }}} */ 109 | 110 | /* {{{ public void test_should_queque_fetch_by_type_works_fine() */ 111 | public function test_should_queque_fetch_by_type_works_fine() 112 | { 113 | $queque = Queque::instance(); 114 | $this->assertTrue($queque->insert( 115 | 'test ', array( 116 | 'src' => 'http://www.taobao.com', 117 | ), 118 | 1, 119 | array( 120 | 'trytimes' => 2, 121 | 'adduser' => 'unittest', 122 | 'priority' => 201, 123 | ) 124 | )); 125 | $this->assertTrue($queque->insert( 126 | 'test2', array( 127 | 'src' => 'http://www.taobao.com', 128 | ), 129 | 1, 130 | array( 131 | 'trytimes' => 2, 132 | 'adduser' => 'unittest', 133 | 'priority' => 200, 134 | ) 135 | )); 136 | 137 | $task = $queque->fetch(1, 2, Queque::FLAG_WAIT, 'test'); 138 | $this->assertEquals(array( 139 | 'id' => 1, 140 | 'type' => 'test', 141 | 'status' => '', 142 | 'info' => json_encode(array( 143 | 'src' => 'http://www.taobao.com', 144 | )), 145 | ), $task); 146 | } 147 | /* }}} */ 148 | 149 | } 150 | 151 | -------------------------------------------------------------------------------- /test/unit/SecurityTest.php: -------------------------------------------------------------------------------- 1 | assertEquals(10, $sec->priority('127.0.0.2')); 16 | $this->assertEquals(911, $sec->priority('221.3.1.2')); 17 | $this->assertEquals(false, $sec->priority('221.31.1.2')); 18 | 19 | $this->assertEquals(array( 20 | 'task_flag' => 100, 21 | 'trytimes' => 3, 22 | 'tasktype' => 'asdf', 23 | 'i_am_none' => 'lalalal', 24 | ), $sec->modify(array( 25 | 'task_flag' => 11, 26 | 'trytimes' => 2, 27 | 'tasktype' => 'asdf', 28 | 'i_am_none' => 'lalalal', 29 | ))); 30 | } 31 | /* }}} */ 32 | 33 | } 34 | -------------------------------------------------------------------------------- /test/unit/ServerTest.php: -------------------------------------------------------------------------------- 1 | query(sprintf( 22 | "DELETE FROM %shost_list WHERE host_name LIKE 'unittest%%'", 23 | self::$mysql->option('prefix', '') 24 | )); 25 | } 26 | /* }}} */ 27 | 28 | /* {{{ public void test_should_server_get_option_works_fine() */ 29 | public function test_should_server_get_option_works_fine() 30 | { 31 | try { 32 | Server::instance('unittest01')->option(''); 33 | $this->assertTrue(false); 34 | } catch (\Exception $e) { 35 | $this->assertTrue($e instanceof \Myfox\Lib\Exception); 36 | $this->assertContains('Undefined mysql server named as "unittest01"', $e->getMessage()); 37 | } 38 | 39 | $this->assertEquals('edp1_9801', Server::instance('edp1_9801')->option('host_name')); 40 | $this->assertEquals(Server::TYPE_REALITY, Server::instance('edp1_9801')->option('host_type')); 41 | } 42 | /* }}} */ 43 | 44 | /* {{{ public void test_should_server_get_link_works_fine() */ 45 | public function test_should_server_get_link_works_fine() 46 | { 47 | $ob = Server::instance('edp1_9801')->getlink(); 48 | $this->assertTrue($ob instanceof \Myfox\Lib\Mysql); 49 | 50 | $my = Server::instance('edp1_9801')->getlink(); 51 | $this->assertEquals($ob, $my); 52 | } 53 | /* }}} */ 54 | 55 | } 56 | 57 | -------------------------------------------------------------------------------- /test/unit/SettingTest.php: -------------------------------------------------------------------------------- 1 | query(sprintf( 21 | 'DELETE FROM %ssettings WHERE ownname LIKE "unittest%%"', 22 | self::$mysql->option('prefix') 23 | )); 24 | 25 | Setting::$queries = 0; 26 | } 27 | /* }}} */ 28 | 29 | /* {{{ protected void tearDown() */ 30 | protected function tearDown() 31 | { 32 | parent::tearDown(); 33 | } 34 | /* }}} */ 35 | 36 | /* {{{ public void test_should_setting_set_and_get_works_fine() */ 37 | public function test_should_setting_set_and_get_works_fine() 38 | { 39 | Setting::init(1); 40 | 41 | $this->assertEquals(0, Setting::$queries); 42 | $this->assertEquals(null, Setting::get('key1', 'unittest1')); 43 | $this->assertEquals(1, Setting::$queries); 44 | 45 | $this->assertEquals(1, Setting::set('key1', 'a am a hacker', 'unittest1')); 46 | $this->assertEquals(2, Setting::$queries); 47 | 48 | $this->assertEquals('a am a hacker', Setting::get('key1', 'unittest1')); 49 | $this->assertEquals(3, Setting::$queries); 50 | 51 | $this->assertEquals('a am a hacker', Setting::get('key1', 'unittest1')); 52 | $this->assertEquals(3, Setting::$queries); 53 | 54 | usleep(1050000); 55 | 56 | $this->assertEquals('a am a hacker', Setting::get('key1', 'unittest1')); 57 | $this->assertEquals(4, Setting::$queries); 58 | 59 | $this->assertEquals(2, Setting::set('key1', 'i am not a hacker', 'unittest1')); 60 | $this->assertEquals(5, Setting::$queries); 61 | 62 | $this->assertEquals('i am not a hacker', Setting::get('key1', 'unittest1')); 63 | $this->assertEquals(6, Setting::$queries); 64 | } 65 | /* }}} */ 66 | 67 | /* {{{ public void test_should_setting_set_without_comma_works_fine() */ 68 | public function test_should_setting_set_without_comma_works_fine() 69 | { 70 | Setting::init(0); 71 | $this->assertEquals(1, Setting::set('key2', 1, 'unittest1')); 72 | $this->assertEquals(1, Setting::get('key2', 'unittest1')); 73 | 74 | $this->assertEquals(2, Setting::set('key2', 'IF(cfgvalue + 0 > 0, 2, 3)', 'unittest1', false)); 75 | $this->assertEquals(2, Setting::get('key2', 'unittest1')); 76 | } 77 | /* }}} */ 78 | 79 | } 80 | -------------------------------------------------------------------------------- /test/unit/UrlTest.php: -------------------------------------------------------------------------------- 1 | assertEquals('', $url->module()); 30 | $this->assertEquals('', $url->action()); 31 | $this->assertEquals(array(), $url->param); 32 | 33 | $url = new Url('test'); 34 | $this->assertEquals('test', $url->module()); 35 | $this->assertEquals('', $url->action()); 36 | $this->assertEquals(array(), $url->param); 37 | 38 | $url = new Url('test//index/a/b/c/2'); 39 | $this->assertEquals('test', $url->module()); 40 | $this->assertEquals('index', $url->action()); 41 | $this->assertEquals(array('a' => 'b', 'c' => '2'), $url->param()); 42 | 43 | $url = new Url("test\t/in\ndex/__SQL__/b/c/2/d"); 44 | $this->assertEquals('test', $url->module()); 45 | $this->assertEquals('index', $url->action()); 46 | $this->assertEquals(array( 47 | '__SQL__' => 'b', 'c' => '2', 'd' => true 48 | ), $url->param); 49 | } 50 | /* }}} */ 51 | 52 | /* {{{ public void test_should_build_right_url_ok() */ 53 | public function test_should_build_right_url_ok() 54 | { 55 | $this->assertEquals( 56 | 'module/action/name/' . urlencode('朱镕基') . '/age/2', 57 | Url::build('module', 'action', array( 58 | 'na me' => '朱镕基', 59 | 'age ' => 2, 60 | 'array' => array('我应该被忽略'), 61 | )) 62 | ); 63 | } 64 | /* }}} */ 65 | 66 | /* {{{ public void test_should_parse_zero_correctly() */ 67 | /** 68 | * THIS IS A BUG CASE 69 | */ 70 | public function test_should_parse_zero_correctly() 71 | { 72 | $url = new Url('test//index/a/b/c/0'); 73 | $this->assertEquals(array('a' => 'b', 'c' => '0'), $url->param); 74 | } 75 | /* }}} */ 76 | 77 | } 78 | 79 | -------------------------------------------------------------------------------- /test/unit/autoload/com/aleafs/autoloadordertestclass.php: -------------------------------------------------------------------------------- 1 | a = $a; 15 | $this->b = $b; 16 | } 17 | 18 | } 19 | -------------------------------------------------------------------------------- /test/unit/ini/config_test.ini: -------------------------------------------------------------------------------- 1 | ; 仅用于config类单元测试 2 | 3 | key1 = "abc" 4 | key2/a = false 5 | 6 | [key2] 7 | a = 1.23456789 ; 这里会被注释掉 8 | b = 9.87654321 9 | -------------------------------------------------------------------------------- /test/unit/resource/mirror_import_data_file.txt: -------------------------------------------------------------------------------- 1 | 1 类目1 2 | 2 类目2 3 | 3 类目3 4 | 4 类目4 5 | 5 类目5 6 | -------------------------------------------------------------------------------- /test/unit/resource/numsplit_import_data_file.txt: -------------------------------------------------------------------------------- 1 | 2011-06-10 1 299 0.42 line1 2 | 2011-06-10 1 399 0.42 line2 3 | 2011-06-10 1 499 0.42 line3 4 | 2011-06-10 1 599 0.42 line4 5 | 2011-06-10 1 699 0.42 line5 6 | 2011-06-10 1 979 0.42 line6 7 | 2011-06-10 1 998 0.42 line7 8 | 2011-06-10 1 345 0.42 line8 9 | 2011-06-10 1 217 0.42 line9 10 | 2011-06-10 1 342 0.42 line10 11 | -------------------------------------------------------------------------------- /www/index.php: -------------------------------------------------------------------------------- 1 |